ggvfields

Project Status: Active - The project has reached a stable, usable state and is being actively developed.

ggvfields is a powerful package for visualizing vector fields, stream plots, and related visualizations. It provides tools to explore directional data, including options for vector smoothing, gradient fields, potential visualizations, and dynamic flow representations.

Installation

Install ggvfields directly from GitHub:

remotes::install_github("dusty-turner/ggvfields")

Load the package in R:

library("ggvfields")
#> Loading required package: ggplot2
options(ggplot2.continuous.colour="viridis")

Generate sample wind data:

set.seed(1234)
n <- 10

wind_data <- data.frame(
  lon = rnorm(n), 
  lat = rnorm(n), 
  dir = runif(n, -pi/2, pi/2),
  spd = rchisq(n, df = 2)
) |> 
  within({
    fx    <- spd * cos(dir)          # Compute the x-component of the vector
    fy    <- spd * sin(dir)          # Compute the y-component of the vector
    xend  <- lon + fx                # Compute the end x-coordinate
    yend  <- lat + fy                # Compute the end y-coordinate
  })

round(wind_data, digits = 2) 
#>      lon   lat   dir   spd  yend  xend    fy    fx
#> 1  -1.21 -0.48  0.17  3.55  0.11  2.29  0.59  3.50
#> 2   0.28 -1.00  0.46  2.19 -0.03  2.24  0.97  1.96
#> 3   1.08 -0.78 -0.59  2.99 -2.44  3.56 -1.66  2.48
#> 4  -2.35  0.06  0.38 10.81  4.10  7.68  4.04 10.03
#> 5   0.43  0.96 -0.53  3.45 -0.80  3.40 -1.76  2.97
#> 6   0.51 -0.11  0.01  3.91 -0.09  4.41  0.02  3.91
#> 7  -0.57 -0.51  0.56  0.16 -0.43 -0.44  0.08  0.13
#> 8  -0.55 -0.91 -0.05  0.42 -0.93 -0.12 -0.02  0.42
#> 9  -0.56 -0.84 -0.80  0.42 -1.14 -0.28 -0.30  0.29
#> 10 -0.89  2.42  0.83  4.17  5.51  1.91  3.09  2.80

Core Features

geom_vector() and geom_vector2()

These functions allow for flexible visualizations of vector data.

ggplot(wind_data) +
  geom_vector(aes(x = lon, y = lat, xend = xend, yend = yend)) 

geom_vector() also supports both xend/yend format as well as fx/fy format.

ggplot(wind_data) +
  geom_vector(aes(x = lon, y = lat, fx = fx, fy = fy)) 

ggplot(wind_data) +
  geom_vector2(aes(x = lon, y = lat, fx = fx, fy = fy)) 

Why Length Mapping Matters

Mapping vector lengths to their norms allows viewers to immediately understand magnitude differences without relying solely on color.

The norm \(\mathbf{w} = (u, v)\) is calculated \[|\mathbf{w}| = \sqrt{u^2 + v^2}\] .

This feature of geom_vector2() enhances interpretability by using actual vector lengths to represent magnitude. The legend reflects the scaling and ensures consistent interpretation.

Polar Coordinates Support

Both geom_vector() and geom_vector2() also support polar coordinates, where vectors are specified using magnitude (distance) and direction (angle). Instead of providing Cartesian components (fx, fy or xend, yend), users can directly supply polar data. This feature simplifies workflows for directional data and works for all subsequent relevant functions that handle polar coordinates.

Polar coordinates can be visualized like this:

ggplot(wind_data) +
  geom_vector(aes(x = lon, y = lat, distance = spd, angle = dir)) 

Normalize and Center

Two important options in geom_vector() and subsequent functions are normalize and center - both of which default to TRUE. These options help control the visual representation of vectors:

The example below turns off this default behavior:

ggplot(wind_data) +
  geom_vector(aes(x = lon, y = lat, fx = fx, fy = fy), center = FALSE, normalize = FALSE) 

geom_stream_field() and geom_stream_field2()

f <- function(v) c(-v[2], v[1]) # Define a function for the field

ggplot() +
  geom_stream_field(fun = f) 

ggplot() +
  geom_stream_field2(fun = f) 

Grid Density: The user can control the density of the grid by using the n parameter.

ggplot() +
  geom_stream_field(fun = f, n = 4) 

Length

By adjusting the L parameter, we can control the length of each stream.

ggplot() +
  geom_stream_field(fun = f, n = 4, L = .8) 

Normalization

By default, the lengths of each stream is normalized to be the same length. By turning normalization off, each stream becomes time normalized. In other words, each stream grows for the same amount of time.

ggplot() +
  geom_stream_field(fun = f, n = 4, normalize = FALSE) 

Time

When normalization is turned off, we can grow each stream for the same amount of time by using the T parameter.

ggplot() +
  geom_stream_field(fun = f, n = 4, normalize = FALSE, T = .5) 

geom_vector_field() and geom_vector_field2()

Vector fields can be seen as special cases of streams.

ggplot() +
  geom_vector_field(fun = f) 

ggplot() +
  geom_vector_field2(fun = f) 

Length

As with streams, we can set the L parameter to grow vectors to a specified length.

ggplot() +
  geom_vector_field(fun = f, n = 4, L = 2) 

Center

If we turn off normalization and centering, we get a raw look at the vector field data.

ggplot() +
  geom_vector_field(fun = f, n = 4, normalize = FALSE, center = FALSE) 

geom_gradient_field() and geom_gradient_field2()

The geom_gradient_field function computes and visualizes gradient fields derived from scalar functions and displays the gradient vector field of a scalar function, \(f(x, y)\). The gradient is given by:

\[ \nabla f(x, y) = \left( \frac{\partial f}{\partial x}, \frac{\partial f}{\partial y} \right) \]

This vector field points in the direction of the greatest rate of increase of the scalar function. The function numerically evaluates these partial derivatives and visualizes the resulting vectors.

paraboloid_field <- function(v) {
  x <- v[1]
  y <- v[2]
  x^2 + y^2
}

ggplot() +
  geom_gradient_field(fun = paraboloid_field, xlim = c(-10, 10), ylim = c(-10, 10))

ggplot() +
  geom_gradient_field2(fun = paraboloid_field, xlim = c(-10, 10), ylim = c(-10, 10))

The n parameter adjusts the density of the grid used to evaluate the gradient field. Decreasing n reduces the number of vectors which producing a coarser grid while increasing n results in a finer grid with more vectors.

ggplot() +
  geom_gradient_field(fun = paraboloid_field, xlim = c(-10, 10), ylim = c(-10, 10), n = 5)

geom_potential()

A potential function represents a scalar field whose gradient produces a vector field. It is used to describe conservative vector fields which exist when the curl of the vector field is 0.

The geom_potential() function computes and visualizes the scalar potential function for a given conservative vector field. The input function must represent a 2D vector field and the output is the corresponding potential function. If the input field is not conservative, the function checks this condition numerically based on a tolerance parameter. The tolerance determines how strictly the field must satisfy the conservation condition.

conservative_fun <- function(v) {
 x <- v[1]
 y <- v[2]
 c(sin(x) + y, x - sin(y))
}

ggplot() +
  geom_potential(fun = conservative_fun, xlim = c(-2*pi, 2*pi), ylim = c(-2*pi, 2*pi))

The tolerance parameter can be adjusted to control the sensitivity of the conservativeness check. Decreasing the tolerance makes the check stricter, while increasing it allows for more numerical error.

ggplot() +
  geom_potential(fun = conservative_fun, xlim = c(-2*pi, 2*pi), ylim = c(-2*pi, 2*pi), tol = 1e-4)

As with other functions, we can increase the granularity of the visualization with the n parameter.

ggplot() +
  geom_potential(fun = conservative_fun, xlim = c(-2*pi, 2*pi), ylim = c(-2*pi, 2*pi), n = 50)


Advanced Features

This section introduces tools for deeper exploration of vector fields, allowing users to analyze properties such as smoothness, uncertainty, gradients, and potential functions. These tools provide mathematical insights into the structure of vector fields and extend visualization capabilities beyond basic representations.

Key capabilities include:

ggvfields offers two techniques for smoothing noisy vector field data geom_stream_smooth() and geom_vector_smooth()

geom_stream_smooth() uses a dynamical systems approach and geom_vector_smooth() offers a multivariate regression approach that accounts for uncertainty.

geom_stream_smooth()

ggplot(wind_data, aes(x = lon, y = lat, fx = fx, fy = fy)) +
  geom_vector() +
  geom_stream_smooth(aes(x = lon, y = lat, fx = fx, fy = fy)) 

geom_vector_smooth()

Provides smoothed estimates of vector fields by applying statistical techniques to observed vectors.

Smoothing is performed using a multivariate linear model defined by:

\[ \begin{pmatrix} \hat{dx} \\ \hat{dy} \end{pmatrix} = \beta_0 + \beta_1 x + \beta_2 y + \beta_3 xy \]

where \(\beta\) are coefficients estimated by ordinary least squares (OLS). This approach captures linear and interaction effects to approximate the underlying vector field. This function also creates a prediction interval around the vector specified by the conf_level argument and defaults to .95.

When evaluation points are provided, smoothing is performed at those locations and prediction intervals can be visualized using either wedges or ellipses to indicate uncertainty.

eval_point <- data.frame(x = .5, y = .5) 

ggplot(wind_data, aes(x = lon, y = lat, fx = fx, fy = fy)) +
  geom_vector(normalize = FALSE) +
  geom_vector_smooth(eval_points = eval_point) +
  lims(x = c(-7,10), y = c(-3,3))
#> Warning: Removed 2 rows containing missing values or values outside the scale range
#> (`geom_stream()`).

ggplot(wind_data, aes(x = lon, y = lat, fx = fx, fy = fy)) +
  geom_vector(normalize = FALSE) +
  geom_vector_smooth(eval_points = eval_point, pi_type = "wedge") 

ggplot(wind_data, aes(x = lon, y = lat, fx = fx, fy = fy)) +
  geom_vector_smooth(pi_type = "wedge") + 
  geom_vector() 

ggplot(wind_data, aes(x = lon, y = lat, fx = fx, fy = fy)) +
  geom_vector_smooth(n = 6, pi_type = "wedge") 

For all options, you can change the confidence level from the default to another value by using the conf_level argument.

ggplot(wind_data, aes(x = lon, y = lat, fx = fx, fy = fy)) +
  geom_vector(normalize = FALSE) +
  geom_vector_smooth(eval_points = eval_point, pi_type = "wedge") +
  geom_vector_smooth(eval_points = eval_point, pi_type = "wedge", conf_level = .7) 

geom_gradient_smooth()

geom_gradient_smooth() creates a smoothed gradient field from raw scalar data using a fitted linear model. This function estimates gradients when only scalar values (z) are observed at spatial locations (x, y). It is designed for cases where you have scalar data and wish to estimate the gradient.

The gradients are computed numerically from a fitted scalar field model and the resulting gradient vectors are visualized using either streamlines or vector arrows.

f1 <- function(u) {
  x <- u[1]
  y <- u[2]
  x^2 - y^2
}

grid_data <- expand.grid(
  x = seq(-5, 5, length.out = 30),
  y = seq(-5, 5, length.out = 30)
)

set.seed(123)
grid_data$z <- apply(grid_data, 1, f1) + rnorm(nrow(grid_data), mean = 0, sd = 5)

ggplot(grid_data, aes(x = x, y = y, z = z)) +
  geom_gradient_smooth()

To illustrate how geom_gradient_smooth() can adapt to nonlinear surfaces, we can change the formula used to fit the scalar field and switch to a streamline visualization using type = "stream". The example below uses a smooth but noisy scalar function that generates curved gradients and fits a flexible smoothing model to capture these variations.

h1 <- function(u) {
  x <- u[1]
  y <- u[2]
  sin(x / 2) * cos(y / 2)
}

grid_data$z <- apply(grid_data, 1, h1) + rnorm(nrow(grid_data), mean = 0, sd = 1)

ggplot(grid_data, aes(x = x, y = y, z = z)) +
  geom_gradient_smooth(formula = z ~ I(x^2) * I(y^2), n = 5, type = "stream") 

Other Features

Automatic Limit Detection

These functions can automatically determine plot limits based on the function provided. This happens when data exists in previous layers or in the base ggplot object. This allows the limits to be inferred from context. Customize limits with the xlim and ylim parameters if needed for more control.

ggplot(data = wind_data, aes(x = lon, y = lat, fx = fx, fy = fy)) +
  geom_vector() +
  geom_stream_field(fun = f) # Automatically determines limits based on existing data

Custom Grids

The geom_*_field functions allow the user to plot with custom evaluation locations. The user can specify specific points to be evaluated over the field or can also use a “hex” pattern.

ggplot() +
  geom_stream_field(fun = f, grid = "hex")

This shows a custom grid.

custom <- data.frame(x = c(1,3,5), y = c(3,4,5))

ggplot() +
  geom_stream_field(fun = f, grid = custom, normalize = FALSE, center = FALSE, L = 4)

License

This package is licensed under the MIT License.

Contact

For questions or feedback, please open an issue.