DBMath
DBMath

Reputation: 1

small arrow across a contour line

I am working in R version 4.3.2 (2023-10-31).

I am plotting a contour line for a function of 2 variables at a particular level of the function. I am doing so for two different functions on the same plot. I would like to include a small arrow at some point close to each contour line that shows a direction of increase of the function. Any direction will do though the direction of the gradient (at that point) will be best.

Here is some example code that creates contour lines like I want. How do I now include one arrow for each line at some point close to the contour line which shows a direction of increase of the function?

library(tidyverse)
x <- seq(1,2,length.out=100)
y <- seq(1,2,length.out=100)
myf <- function(x,y) {x*y}
myg <- function(x,y) {x^2 + y^2}
d1 <- expand.grid(X1 = x, X2 = y) %>%
  mutate(Z = myf(X1,X2)) %>%
  as.data.frame()
d2 <- expand.grid(X1 = x, X2 = y) %>%
  mutate(Z = myg(X1,X2)) %>%
  as.data.frame()
ggplot(data = d1, aes(x=X1,y=X2,z=Z))+
  stat_contour(breaks = c(2)) +
  stat_contour(data=d2, aes(x=X1,y=X2,z=Z), breaks=c(6))

Upvotes: 0

Views: 57

Answers (2)

Allan Cameron
Allan Cameron

Reputation: 173803

Effectively Z is the value of a scalar field over X1 and X2. To show the gradient of Z over X1, X2 at particular points, you can calculate the rate of change of Z in the y direction at each point, and the rate of change in the x direction at each point. The arctangent of their ratio (theta) will be the direction of the gradient. You can therefore draw a line of length L from any given point a in the direction of the gradient by drawing a segment between [X1a, X2a] and [X1a + L * cos(thetaa), X2a + L * sin(thetaa)]:

d1 <- d1 %>% 
  group_by(X1) %>% 
  arrange(X2) %>% 
  mutate(dzdy = c(NA, diff(Z)/diff(X2))) %>%
  group_by(X2) %>%
  arrange(X1) %>%
  mutate(dzdx = c(NA, diff(Z)/diff(X1))) %>%
  mutate(theta = atan2(dzdy, dzdx)) %>%
  mutate(xend = X1 + 0.1 * cos(theta), yend = X2 + 0.1 * sin(theta))

d2 <- d2 %>% 
  group_by(X1) %>% 
  arrange(X2) %>% 
  mutate(dzdy = c(NA, diff(Z)/diff(X2))) %>%
  group_by(X2) %>%
  arrange(X1) %>%
  mutate(dzdx = c(NA, diff(Z)/diff(X1))) %>%
  mutate(theta = atan2(dzdy, dzdx)) %>%
  mutate(xend = X1 + 0.1 * cos(theta), yend = X2 + 0.1 * sin(theta))

ggplot(data = d1, aes(X1, X2, z = Z)) +
  geom_segment(aes(xend = xend, yend = yend),
               data = d1 %>% filter(abs(Z - 2) < 0.0001), 
               arrow = arrow(length = unit(2, 'mm'), type = 'closed')) +
  stat_contour(breaks = 2) +
  stat_contour(data = d2, breaks = 6) +
  geom_segment(aes(xend = xend, yend = yend),
               data = d2 %>% filter(abs(Z - 6.01) < 0.001), 
               arrow = arrow(length = unit(2, 'mm'), type = 'closed')) +
  coord_fixed() 

enter image description here

We can see that these lines point in the direction of increasing Z by showing them over contour maps of d1

ggplot(data = d1, aes(X1, X2, z = Z)) +
  geom_contour_filled() +
  geom_segment(aes(xend = xend, yend = yend),
               data = d1 %>% filter(abs(Z - 2) < 0.0001), 
               arrow = arrow(length = unit(2, 'mm'), type = 'closed')) +
  coord_fixed() 

enter image description here

and d2:

ggplot(data = d2, aes(X1, X2, z = Z)) +
  geom_contour_filled() +
  geom_segment(aes(xend = xend, yend = yend),
               data = d2 %>% filter(abs(Z - 6.01) < 0.001), 
               arrow = arrow(length = unit(2, 'mm'), type = 'closed')) +
  coord_fixed() 

enter image description here)

Upvotes: 1

Quinten
Quinten

Reputation: 41285

You could use geom_segment to create custom arrow lines in your plot. You could specify the coordinates like this:

library(tidyverse)
ggplot(data = d1, aes(x=X1,y=X2,z=Z))+
  stat_contour(breaks = c(2)) +
  stat_contour(data=d2, aes(x=X1,y=X2,z=Z), breaks=c(6)) +
  geom_segment(data = data.frame(x = c(1.25, 1.75),
                                 xend = c(1.5, 1.5),
                                 y = c(1.5, 1.75),
                                 yend = c(1.25, 2)), 
               aes(x = x, y = y, xend = xend, yend = yend),
               inherit.aes = FALSE,
               arrow = arrow(length = unit(0.5, "cm")))

Created on 2024-01-11 with reprex v2.0.2

Upvotes: 0

Related Questions