Fish-Eye Lens Effect with ggplot2

[This article was first published on CHI(χ)-Files, and kindly contributed to R-bloggers]. (You can report issue about the content on this page here)
Want to share your content on R-bloggers? click here if you have a blog, or here if you don't.

Fascination with “Warping” …

The “fish-eye” lens has always fascinated me. It was my gateway into photography—its quirky, distorted charm never failed to add whimsy to my shots. Some of my favourite memories are capturing our furry friends🐶🐽🐄.

Recently, I started wondering why not bring that same fish-eye🐟👁️ magic into my data visualizations. While it may not suit business dashboards, it’s perfect for artistic visualizations. With ggplot2’s coord_trans() and its "pseudo_log" transformation as inspiration, I decied to push the boundaries and create my first custom transformation.

Digression : The Seigaha Motif as a Canvas 🎨

To test this idea, I used a Seigaha motif I created the other day. It’s a geometric design with overlapping circles that naturally lends itself to experimentation. I introduced randomness to the colors using irrational numbers as denominators, leveraging R’s modulo operation for real numbers. Just another attempt at creating “pseudo-randomness” in the design.

Famous “Irrational” Numbers in Math

Golden Ratio

The Golden Ratio is often represented by the Greek letter and is defined as:

Silver Ratio

The lesser-known Silver Ratio, represented by , is defined as:

Euler’s Number

Euler’s number, represented by , is a fundamental constant in mathematics: It is approximately .

Code
library(tidyverse) # Easily Install and Load the 'Tidyverse' 
library(ggforce) # Accelerating 'ggplot2'   
library(cowplot) # Streamlined Plot Theme and Plot Annotations for 'ggplot2'  
library(gt) # Easily Create Presentation-Ready Display Tables 

### just declaring the colour palette here
col10 <- str_split("001219-005f73-0a9396-94d2bd-e9d8a6-ee9b00-ca6702-bb3e03-ae2012-9b2226","-")
col10_pal <-str_c("#",col10 |> unlist())

### Creating data frame that is basis of layout
df <- expand_grid(x = seq(-12, 12, by = 2), 
                  y = seq(-8, 8, by = 1)) |>
  arrange(y,x) |>
  mutate(y_odd=(y%%2==1)) |>
  mutate(idx=row_number()-1) |>
  mutate(x=if_else(y_odd,x+1,x))

### For each grid location, I want 6 concentric circles
r_values <- seq(0.1,1,length.out=6)

### Just defining some "Irregular Numbers" that I can use with Modulo 
gr <- (1 + sqrt(5)) / 2 ## the golden ratio 
sr <- (1 + sqrt(2)) ##. lesser known silver ratio
euler_num <- exp(1)

df_long <- df |> expand_grid(r=r_values) |>
  mutate(r_var=r*(idx%%sr)+1) ## I'm experimenting here with modulo with pi 

Cartesian Coordinates: The Baseline

Here’s the unaltered design using coord_fixed, maintaining regular Cartesian coordinates.

Code
df_long |>
  arrange(-y,idx, desc(r)) |>
  ggplot() +
  geom_circle(aes(x0=x,y0=y,r=r,fill=r_var),
              linewidth=0.1,color="#fffff3de", linetype=1) +
  coord_fixed(clip="on", xlim=c(-11,11),ylim=c(-7,7)) +
  theme_nothing() +
  scale_fill_gradientn(colors=col10_pal) +
  scale_color_gradientn(colors=col10_pal) +
  #coord_trans(x="pseudo_log",y="pseudo_log",clip="on", xlim=c(-11,11),ylim=c(-7,7)) +
  theme(plot.margin=unit(c(0,0,0,0),"mm"))

Entering the World of Distortion

Pseudo-Log Transformation

Warping begins with using pseudo_log transformation with coord_trans creates an intriguing distortion:

Code
df_long |>
  arrange(-y,idx, desc(r)) |>
  ggplot() +
  geom_circle(aes(x0=x,y0=y,r=r,fill=r_var),
              linewidth=0.1,color="#fffff3de", linetype=1) +
  #coord_fixed(clip="on", xlim=c(-11,11),ylim=c(-7,7)) +
  theme_nothing() +
  scale_fill_gradientn(colors=col10_pal) +
  scale_color_gradientn(colors=col10_pal) +
  coord_trans(x="pseudo_log",y="pseudo_log",clip="on", xlim=c(-11,11),ylim=c(-7,7)) +
  theme(plot.margin=unit(c(0,0,0,0),"mm"))

Custom Fisheye Transformation

Inspired by radial distortion formula, I’ve attempt to write a custom fisheye transformation.

k controls the intensity of the distortion.

  • Positive k = Barrel Distortion (like Fisheye)
  • Negative k = Pincusion Distortion (Inverse Fisheye).
  • r’ is pronouced as “r prime” Here’s the implementation:
Code
library(scales)

# Define a fisheye transformation using trans_new
fisheye_trans <- function(k = 0.01) {
  trans_new(
    name = "fisheye",
    transform = function(r) r * (1 + k * r^2),        # Forward transformation
    inverse = function(r_prime) r_prime / (1 + k * r_prime^2)  # Inverse transformation
  )
}

# Create the fisheye transformation object
fisheye <- fisheye_trans(k = 0.03)

df_long |>
  arrange(-y,idx, desc(r)) |>
  ggplot() +
  geom_circle(aes(x0=x,y0=y,r=r,fill=r_var),
              linewidth=0.1,color="#fffff3de", linetype=1) +
  #coord_fixed(clip="on", xlim=c(-11,11),ylim=c(-7,7)) +
  theme_nothing() +
  scale_fill_gradientn(colors=col10_pal) +
  scale_color_gradientn(colors=col10_pal) +
  #coord_trans(x="pseudo_log",y="pseudo_log",clip="on", xlim=c(-11,11),ylim=c(-7,7)) +
  coord_trans(x=fisheye, y=fisheye) +
  theme(plot.margin=unit(c(0,0,0,0),"mm"))

The result didn’t quite meet my expectations.. 😅🥹, but it’s a start…!

Experimenting with Modulus Transformation in scales package 📦

I also explored transformations like modulus, which yielded effects closer to the fish-eye look I envisioned:

Code
# Define the modulus transformation with a specific parameter (e.g., p = 0.5)
mod_trans_y <- modulus_trans(p = 0.01)


df_long |>
  arrange(-y,idx, desc(r)) |>
  ggplot() +
  geom_circle(aes(x0=x,y0=y,r=r,fill=r_var),
              linewidth=0.1,color="#fffff3de", linetype=1) +
  #coord_fixed(clip="on", xlim=c(-11,11),ylim=c(-7,7)) +
  theme_nothing() +
  scale_fill_gradientn(colors=col10_pal) +
  scale_color_gradientn(colors=col10_pal) +
  coord_trans(x=mod_trans_y ,y=mod_trans_y ,clip="on", xlim=c(-11,11),ylim=c(-7,7)) +
  theme(plot.margin=unit(c(0,0,0,0),"mm"))

Code
mod_trans_x <- modulus_trans(p = 1.5)
df_long |>
  arrange(-y,idx, desc(r)) |>
  ggplot() +
  geom_circle(aes(x0=x,y0=y,r=r,fill=r_var),
              linewidth=0.1,color="#fffff3de", linetype=1) +
  #coord_fixed(clip="on", xlim=c(-11,11),ylim=c(-7,7)) +
  theme_nothing() +
  scale_fill_gradientn(colors=col10_pal) +
  scale_color_gradientn(colors=col10_pal) +
  coord_trans(x=mod_trans_x ,y=mod_trans_y ,clip="on", xlim=c(-11,11),ylim=c(-7,7)) +
  theme(plot.margin=unit(c(0,0,0,0),"mm"))

Final Thoughts

Experimenting with warping in ggplot2 opens up a playful avenue for some quirky visualizations. I’m now thinking what to distort next!

To leave a comment for the author, please follow the link and comment on their blog: CHI(χ)-Files.

R-bloggers.com offers daily e-mail updates about R news and tutorials about learning R and many other topics. Click here if you're looking to post or find an R/data-science job.
Want to share your content on R-bloggers? click here if you have a blog, or here if you don't.

Never miss an update!
Subscribe to R-bloggers to receive
e-mails with the latest R posts.
(You will not see this message again.)

Click here to close (This popup will not appear again)