| Title: | Focus-Glue-Context Fisheye Transformations for Spatial Visualization |
|---|---|
| Description: | Focus-glue-context (FGC) fisheye transformations to two-dimensional coordinates and spatial vector geometries. Implements a smooth radial distortion that enlarges a focal region, transitions through a glue ring, and preserves outside context. Methods build on generalized fisheye views and focus+context mapping. For more details see Furnas (1986) <doi:10.1145/22339.22342>, Furnas (2006) <doi:10.1145/1124772.1124921> and Yamamoto et al. (2009) <doi:10.1145/1653771.1653788>. |
| Authors: | Alex Nguyen [aut, cre, cph], Dianne Cook [aut] (ORCID: <https://orcid.org/0000-0002-3813-7155>), Michael Lydeamore [aut] (ORCID: <https://orcid.org/0000-0001-6515-827X>) |
| Maintainer: | Alex Nguyen <[email protected]> |
| License: | MIT + file LICENSE |
| Version: | 1.1.1 |
| Built: | 2026-05-11 06:54:01 UTC |
| Source: | https://github.com/alex-nguyen-vn/mapycusmaximus |
Assigns each point to one of three zones based on its radial distance from a specified center:
focus: inside the inner radius r_in
glue: between r_in and r_out
context: outside r_out
This is a helper for visualizing and analyzing fisheye transformations using the Focus–Glue–Context (FGC) model.
classify_zones(coords, cx = 0, cy = 0, r_in = 0.34, r_out = 0.5)classify_zones(coords, cx = 0, cy = 0, r_in = 0.34, r_out = 0.5)
coords |
A numeric matrix or data frame with at least two
columns representing |
cx, cy
|
Numeric. The x and y coordinates of the fisheye center (default = 0, 0). |
r_in |
Numeric. Inner radius of the focus zone (default = 0.34). |
r_out |
Numeric. Outer radius of the glue zone (default = 0.5). |
A character vector of the same length as nrow(coords),
with values "focus", "glue", or "context".
fisheye_fgc(), plot_fisheye_fgc()
# Simple example pts <- matrix(c(0, 0, 0.2, 0.2, 0.6, 0.6), ncol = 2, byrow = TRUE) classify_zones(pts, r_in = 0.3, r_out = 0.5) #> "focus" "glue" "context"# Simple example pts <- matrix(c(0, 0, 0.2, 0.2, 0.6, 0.6), ncol = 2, byrow = TRUE) classify_zones(pts, r_in = 0.3, r_out = 0.5) #> "focus" "glue" "context"
sf)An example LINESTRING layer showing hospital–RACF transfer routes
after applying a Focus–Glue–Context (FGC) fisheye warp.
It demonstrates how line geometries can be spatially distorted in sync
with polygon layers to visualize flow patterns within the magnified focus zone.
conn_fishconn_fish
An sf object with:
Numeric, representing transfer magnitude or connection strength.
LINESTRING geometries in projected CRS (EPSG:3111).
Built from hospital–RACF coordinate pairs in data-raw/transfers_coded.csv
using:
connection creation via make_connections() to form LINESTRINGs,
projection to VicGrid94 (EPSG:3111),
distance-based filtering to keep only sources within r_in = 0.34
of the focus point (cx = 145.0, cy = -37.8),
fisheye transformation using fisheye_fgc()
with r_in = 0.428, r_out = 0.429, and zoom_factor = 1.
The resulting object aligns spatially with vic_fish, allowing
co-visualization of regional flow intensity within the distorted focus region.
Prepared in data-raw/gen-data.R from
transfers_coded.csv and the make_connections() function.
library(sf) plot(st_geometry(vic_fish), col = "grey95", border = "grey70") plot(st_geometry(conn_fish), add = TRUE, col = "black", lwd = 1)library(sf) plot(st_geometry(vic_fish), col = "grey95", border = "grey70") plot(st_geometry(conn_fish), add = TRUE, col = "black", lwd = 1)
Generates a 2D grid of equally spaced points, useful for testing fisheye transformations and other spatial warping functions.
create_test_grid(range = c(-1, 1), spacing = 0.1)create_test_grid(range = c(-1, 1), spacing = 0.1)
range |
Numeric vector of length 2 giving the x and y limits
of the grid (default = |
spacing |
Numeric. Distance between adjacent grid points
along each axis (default = |
A numeric matrix with two columns (x, y) containing
the coordinates of the grid points.
plot_fisheye_fgc(), fisheye_fgc()
# Create a grid from -1 to 1 with spacing 0.25 grid <- create_test_grid(range = c(-1, 1), spacing = 0.25) head(grid)# Create a grid from -1 to 1 with spacing 0.25 grid <- create_test_grid(range = c(-1, 1), spacing = 0.25) head(grid)
Transforms 2D coordinates using a Focus–Glue–Context (FGC) fisheye transformation. The function expands points inside a focus region, compresses points in a glue region, and leaves the surrounding context unchanged. Optionally, a rotational "revolution" can be added to the glue region to produce a swirling effect.
fisheye_fgc(data, ...)fisheye_fgc(data, ...)
data |
Data on which to perform a focus-glue-context transformation. |
... |
Additional arguments passed to specific methods. See |
This is a generic S3 function to perform the focus-glue-context transformation on different types of data.
This function operates in three radial zones around a chosen center:
Focus zone (r <= r_in): expands distances from the center using zoom_factor,
but does not exceed the r_in boundary.
Glue zone (r_in < r <= r_out): compresses distances using a power-law defined
by squeeze_factor, then remaps them to smoothly connect focus and context zones.
Context zone (r > r_out): coordinates remain unchanged.
Optionally, points in the glue zone can be rotated (revolution) to emphasize continuity.
For the specifics of methods for spatial objects, see fisheye_fgc.sf().
For the underlying mathematical transformation, see fisheye_fgc.matrix().
Transforms 2D coordinates using a Focus–Glue–Context (FGC) fisheye transformation. The function expands points inside a focus region, compresses points in a glue region, and leaves the surrounding context unchanged. Optionally, a rotational "revolution" can be added to the glue region to produce a swirling effect.
## S3 method for class 'matrix' fisheye_fgc( data, cx = 0, cy = 0, r_in = 0.34, r_out = 0.5, zoom_factor = 1.5, squeeze_factor = 0.3, method = "expand", revolution = 0, ... ) ## S3 method for class 'data.frame' fisheye_fgc(data, ...)## S3 method for class 'matrix' fisheye_fgc( data, cx = 0, cy = 0, r_in = 0.34, r_out = 0.5, zoom_factor = 1.5, squeeze_factor = 0.3, method = "expand", revolution = 0, ... ) ## S3 method for class 'data.frame' fisheye_fgc(data, ...)
data |
A matrix or data frame with at least two columns representing x and y coordinates. |
cx, cy
|
Numeric. The x and y coordinates of the fisheye center (default = 0, 0). |
r_in |
Numeric. Radius of the focus zone (default = 0.34). |
r_out |
Numeric. Radius of the glue zone boundary (default = 0.5). |
zoom_factor |
Numeric. Expansion factor applied within the focus zone (default = 1.5). |
squeeze_factor |
Numeric in (0,1]. Compression factor applied within the glue zone (smaller values = stronger compression, default = 0.3). |
method |
Character. "expand" or "outward" (default = "expand"). |
revolution |
Numeric. Optional rotation factor applied in the glue zone. Positive values rotate counter-clockwise, negative values clockwise (default = 0.0). |
... |
Additional arguments from the S3 generic, currently ignored. |
This function operates in three radial zones around a chosen center:
Focus zone (r <= r_in): expands distances from the center using zoom_factor,
but does not exceed the r_in boundary.
Glue zone (r_in < r <= r_out): compresses distances using a power-law defined
by squeeze_factor, then remaps them to smoothly connect focus and context zones.
Context zone (r > r_out): coordinates remain unchanged.
Optionally, points in the glue zone can be rotated (revolution) to emphasize continuity.
A numeric matrix with two columns (x_new, y_new) of transformed coordinates.
Additional attributes:
"zones": character vector classifying each point as "focus", "glue", or "context".
"original_radius": numeric vector of original radial distances.
"new_radius": numeric vector of transformed radial distances.
# Create a set of example coordinates grid <- create_test_grid(range = c(-1, 1), spacing = 0.1) # Apply FGC fisheye with expansion and compression transformed <- fisheye_fgc(grid, r_in = 0.34, r_out = 0.5, zoom_factor = 1.3, squeeze_factor = 0.5) # Plot original vs transformed plot_fisheye_fgc(grid, transformed, r_in = 0.34, r_out = 0.5)# Create a set of example coordinates grid <- create_test_grid(range = c(-1, 1), spacing = 0.1) # Apply FGC fisheye with expansion and compression transformed <- fisheye_fgc(grid, r_in = 0.34, r_out = 0.5, zoom_factor = 1.3, squeeze_factor = 0.5) # Plot original vs transformed plot_fisheye_fgc(grid, transformed, r_in = 0.34, r_out = 0.5)
sf/sfc objects (auto-CRS + flexible centers)fisheye_fgc() applies a focus–glue–context fisheye to vector data:
it (1) ensures a sensible projected working CRS, (2) normalizes
coordinates around a chosen center, (3) calls fisheye_fgc() to warp radii,
(4) denormalizes back to map units, and (5) restores the original CRS.
Inside the focus ring (r_in) features enlarge; across the glue ring
(r_out) they transition smoothly; outside, they stay nearly unchanged.
## S3 method for class 'sf' fisheye_fgc( data, center = NULL, center_crs = NULL, normalized_center = FALSE, cx = NULL, cy = NULL, r_in = 0.34, r_out = 0.5, zoom_factor = 1.5, squeeze_factor = 0.35, method = "expand", revolution = 0, target_crs = NULL, preserve_aspect = TRUE, ... ) ## S3 method for class 'sfc' fisheye_fgc(data, ...)## S3 method for class 'sf' fisheye_fgc( data, center = NULL, center_crs = NULL, normalized_center = FALSE, cx = NULL, cy = NULL, r_in = 0.34, r_out = 0.5, zoom_factor = 1.5, squeeze_factor = 0.35, method = "expand", revolution = 0, target_crs = NULL, preserve_aspect = TRUE, ... ) ## S3 method for class 'sfc' fisheye_fgc(data, ...)
data |
An |
center |
Flexible center specification (see Center selection):
|
center_crs |
Optional CRS for a numeric |
normalized_center |
Logical. If |
cx, cy
|
Optional center in working CRS map units (legacy path,
ignored when |
r_in, r_out
|
Numeric radii (in normalized units) defining focus and
glue boundaries; must satisfy |
zoom_factor |
Numeric (> 1 to enlarge). Focus magnification passed to
|
squeeze_factor |
Numeric in [0, 1]. Glue-zone compression strength
passed to |
method |
Character; name understood by |
revolution |
Numeric (radians); optional angular twist for glue zone,
passed to |
target_crs |
Optional working CRS (anything accepted by
|
preserve_aspect |
Logical. If |
... |
Additional arguments from the S3 generic, currently ignored. |
CRS handling. If target_crs is NULL and the input is geographic
(lon/lat), a projected working CRS is chosen from the layer’s centroid:
Victoria, AU region (approximate 140–150°E, 40–30°S): EPSG:7855 (GDA2020 / MGA55).
Otherwise UTM: EPSG:326## (north) or EPSG:327## (south).
You may override with target_crs. The original CRS is restored on return.
Center selection. The fisheye center can be supplied in multiple ways:
center = c(lon, lat), with center_crs = "EPSG:4326" (recommended
for WGS84) or another CRS string/object.
center = c(x, y) already in working CRS map units (meters).
center as any sf/sfc geometry (POINT/LINE/POLYGON/etc.): its
centroid of the combined geometry is used, then transformed to the
working CRS.
center = c(cx, cy) as normalized coordinates in
when normalized_center = TRUE (relative to the bbox midpoint and
scale used for normalization).
Legacy cx, cy (map units) are still accepted and used only when
center is not supplied.
Normalization. Let bbox half-width/height be sx, sy. With
preserve_aspect = TRUE (default), a uniform scale s = max(sx, sy) maps
, so r_in/r_out (e.g.,
0.34/0.5) are interpreted in a unit-like space. If preserve_aspect = FALSE,
X and Y are independently scaled by sx and sy.
Implementation notes. Geometry coordinates are transformed by
st_transform_custom() which safely re-closes polygon rings and drops Z/M.
The radial warp itself is delegated to fisheye_fgc() (which is not modified).
The transformation may introduce self-intersections or other topology issues due to geometric warping.
An object of the same top-level class as sf_obj (sf or sfc),
with geometry coordinates warped by the fisheye and the original CRS
restored.
sf::st_transform(), sf::st_is_longlat(), sf::st_crs(),
sf::st_coordinates(), st_transform_custom(), fisheye_fgc()
library(sf) # Toy polygon in a projected CRS poly <- st_sfc(st_polygon(list(rbind( c(0,0), c(1,0), c(1,1), c(0,1), c(0,0) ))), crs = 3857) # Default center (bbox midpoint), gentle magnification out1 <- fisheye_fgc(poly, r_in = 0.3, r_out = 0.6, zoom_factor = 1.5, squeeze_factor = 0.35) # Explicit map-unit center, stronger focus out2 <- fisheye_fgc(poly, cx = 0.5, cy = 0.5, r_in = 0.25, r_out = 0.55, zoom_factor = 2.0, squeeze_factor = 0.25) # Lon/lat point (auto-project to UTM/MGA), then fisheye around CBD (WGS84) pt_ll <- st_sfc(st_point(c(144.9631, -37.8136)), crs = 4326) # Melbourne CBD out3 <- fisheye_fgc(pt_ll, r_in = 0.2, r_out = 0.5) # Center supplied as an sf polygon: centroid is used as the warp center out4 <- fisheye_fgc(poly, center = poly)library(sf) # Toy polygon in a projected CRS poly <- st_sfc(st_polygon(list(rbind( c(0,0), c(1,0), c(1,1), c(0,1), c(0,0) ))), crs = 3857) # Default center (bbox midpoint), gentle magnification out1 <- fisheye_fgc(poly, r_in = 0.3, r_out = 0.6, zoom_factor = 1.5, squeeze_factor = 0.35) # Explicit map-unit center, stronger focus out2 <- fisheye_fgc(poly, cx = 0.5, cy = 0.5, r_in = 0.25, r_out = 0.55, zoom_factor = 2.0, squeeze_factor = 0.25) # Lon/lat point (auto-project to UTM/MGA), then fisheye around CBD (WGS84) pt_ll <- st_sfc(st_point(c(144.9631, -37.8136)), crs = 4326) # Melbourne CBD out3 <- fisheye_fgc(pt_ll, r_in = 0.2, r_out = 0.5) # Center supplied as an sf polygon: centroid is used as the warp center out4 <- fisheye_fgc(poly, center = poly)
Names, and latitude/longitude of hospitals in Victoria, Australia, taken from the Victorian Department of Health website
hospital_locationshospital_locations
Name of the hospital
Latitude of the hospital
Longitude of the hospital
Creates a side-by-side scatterplot comparing the original and transformed coordinates of a dataset under the Focus–Glue–Context fisheye mapping. Points are colored according to whether they fall in the focus, glue, or context zones, and boundary circles are drawn for clarity.
plot_fisheye_fgc( original_coords, transformed_coords, cx = 0, cy = 0, r_in = 0.34, r_out = 0.5 )plot_fisheye_fgc( original_coords, transformed_coords, cx = 0, cy = 0, r_in = 0.34, r_out = 0.5 )
original_coords |
A matrix or data frame with at least two
columns representing the original |
transformed_coords |
A matrix or data frame with the
transformed |
cx, cy
|
Numeric. The x and y coordinates of the fisheye center (default = 0, 0). |
r_in |
Numeric. Radius of the inner focus boundary (default = 0.34). |
r_out |
Numeric. Radius of the outer glue boundary (default = 0.5). |
A ggplot2 object showing original vs transformed
coordinates, colored by zone, with boundary circles
overlaid.
create_test_grid(), fisheye_fgc()
library(ggplot2) # Generate test grid and apply fisheye grid <- create_test_grid(range = c(-1, 1), spacing = 0.1) warped <- fisheye_fgc(grid, r_in = 0.4, r_out = 0.7) # Visualize transformation plot_fisheye_fgc(grid, warped, r_in = 0.4, r_out = 0.7)library(ggplot2) # Generate test grid and apply fisheye grid <- create_test_grid(range = c(-1, 1), spacing = 0.1) warped <- fisheye_fgc(grid, r_in = 0.4, r_out = 0.7) # Visualize transformation plot_fisheye_fgc(grid, warped, r_in = 0.4, r_out = 0.7)
Launch Interactive Fisheye Lens Explorer
shiny_fisheye(debug = "off", ...)shiny_fisheye(debug = "off", ...)
debug |
Controls whether the Debug tab is shown.
Accepted values are |
... |
Additional arguments passed to |
The value returned by shiny::runApp(), called primarily
for its side effect of launching the application.
st_transform_custom() walks through each geometry in an sf/sfc object,
extracts its XY coordinates, applies a user-supplied transformation function
to those coordinates, and rebuilds the geometry. It preserves the input CRS
on the resulting sfc column. Polygon rings are re-closed after
transformation so the first and last vertex match.
st_transform_custom(sf_obj, transform_fun, args)st_transform_custom(sf_obj, transform_fun, args)
sf_obj |
An object of class |
transform_fun |
A function that accepts a numeric matrix of coordinates
with two columns |
args |
A named list of additional arguments to pass to |
For POLYGON/MULTIPOLYGON, the function uses the ring indices returned by
sf::st_coordinates() (L1 for rings and L2 for parts) to transform each
ring independently, and then ensures each ring is explicitly closed
(last vertex equals first vertex).
Error handling is per-geometry: if a geometry fails to transform, a warning is emitted and an empty geometry of the same "polygonal family" is returned to keep list lengths consistent.
The function does not modify or interpret the CRS numerically; it simply
preserves the CRS attribute on the output sfc. If your transformation
assumes metres (e.g., radial warps), ensure the input is in an appropriate
projected CRS before calling this function.
An object of the same top-level class as sf_obj (sf or sfc), with the
same column structure (if sf) and the same CRS as the input. Geometry
coordinates are replaced by the coordinates returned by transform_fun.
transform_fun
transform_fun <- function(coords, ...) { ## coords: n x 2 matrix (X, Y)
## return an n x 2 matrix with transformed (X, Y)}
sf::st_coordinates(), sf::st_geometry_type(),
sf::st_sfc(), sf::st_crs()
library(sf) # A simple coordinate transformer: scale and shift scale_shift <- function(coords, sx = 1, sy = 1, dx = 0, dy = 0) { X <- coords[, 1] * sx + dx Y <- coords[, 2] * sy + dy cbind(X, Y) } # POINT example pt <- st_sfc(st_point(c(0, 0)), crs = 3857) st_transform_custom(pt, transform_fun = scale_shift, args = list(sx = 2, sy = 2, dx = 1000, dy = -500)) # LINESTRING example ln <- st_sfc(st_linestring(rbind(c(0, 0), c(1, 0), c(1, 1))), crs = 3857) st_transform_custom(ln, transform_fun = scale_shift, args = list(sx = 10, sy = 10)) # POLYGON example (unit square) poly <- st_sfc(st_polygon(list(rbind(c(0,0), c(1,0), c(1,1), c(0,1), c(0,0)))), crs = 3857) st_transform_custom(poly, transform_fun = scale_shift, args = list(sx = 2, sy = 0.5, dx = 5)) # MULTIPOLYGON example (two disjoint squares) mp <- st_sfc(st_multipolygon(list( list(rbind(c(0,0), c(1,0), c(1,1), c(0,1), c(0,0))), list(rbind(c(2,2), c(3,2), c(3,3), c(2,3), c(2,2))) )), crs = 3857) st_transform_custom(mp, transform_fun = scale_shift, args = list(dx = 100, dy = 100)) # In an sf data frame sf_df <- st_sf(id = 1:2, geometry = st_sfc( st_point(c(10, 10)), st_linestring(rbind(c(0,0), c(2,0), c(2,2))) ), crs = 3857) st_transform_custom(sf_df, transform_fun = scale_shift, args = list(sx = 3, sy = 3))library(sf) # A simple coordinate transformer: scale and shift scale_shift <- function(coords, sx = 1, sy = 1, dx = 0, dy = 0) { X <- coords[, 1] * sx + dx Y <- coords[, 2] * sy + dy cbind(X, Y) } # POINT example pt <- st_sfc(st_point(c(0, 0)), crs = 3857) st_transform_custom(pt, transform_fun = scale_shift, args = list(sx = 2, sy = 2, dx = 1000, dy = -500)) # LINESTRING example ln <- st_sfc(st_linestring(rbind(c(0, 0), c(1, 0), c(1, 1))), crs = 3857) st_transform_custom(ln, transform_fun = scale_shift, args = list(sx = 10, sy = 10)) # POLYGON example (unit square) poly <- st_sfc(st_polygon(list(rbind(c(0,0), c(1,0), c(1,1), c(0,1), c(0,0)))), crs = 3857) st_transform_custom(poly, transform_fun = scale_shift, args = list(sx = 2, sy = 0.5, dx = 5)) # MULTIPOLYGON example (two disjoint squares) mp <- st_sfc(st_multipolygon(list( list(rbind(c(0,0), c(1,0), c(1,1), c(0,1), c(0,0))), list(rbind(c(2,2), c(3,2), c(3,3), c(2,3), c(2,2))) )), crs = 3857) st_transform_custom(mp, transform_fun = scale_shift, args = list(dx = 100, dy = 100)) # In an sf data frame sf_df <- st_sf(id = 1:2, geometry = st_sfc( st_point(c(10, 10)), st_linestring(rbind(c(0,0), c(2,0), c(2,2))) ), crs = 3857) st_transform_custom(sf_df, transform_fun = scale_shift, args = list(sx = 3, sy = 3))
sf)An example polygon layer of Victoria's LGAs for demos and tests.
Built from data-raw/map/LGA_POLYGON.shp, Z/M dropped, transformed to a
projected CRS, simplified, validated, and reduced to LGA_NAME + geometry.
vicvic
An sf object with:
Character, LGA name (upper case).
MULTIPOLYGON / POLYGON in a projected CRS.
The CRS stored in the object is whatever st_crs(vic) reports at build time.
In data-raw/gen-data.R we:
drop Z/M (st_zm()),
transform to a projected CRS (st_transform()),
simplify (st_simplify(dTolerance = 100)),
repair geometries (st_make_valid()),
upper-case names and select columns.
Prepared in data-raw/gen-data.R. Update this if you include an
external data source.
library(sf) plot(sf::st_geometry(vic), col = "grey90", border = "grey50")library(sf) plot(sf::st_geometry(vic), col = "grey90", border = "grey50")
sf)An example polygon layer of Victoria’s Local Government Areas (LGAs) after applying a Focus–Glue–Context (FGC) fisheye transformation. This dataset illustrates how local detail can be magnified around a chosen focus point while maintaining geographic context across the state.
vic_fishvic_fish
An sf object with:
Character, name of the LGA (upper case).
MULTIPOLYGON / POLYGON geometries in projected CRS (EPSG:3111).
Built from the base layer vic using:
projection to VicGrid94 (st_transform(vic, 3111)),
defining a focus center near Melbourne (cx = 145.0, cy = -37.8),
applying fisheye_fgc() with
r_in = 0.34, r_out = 0.5, and zoom_factor = 1,
preserving topology with st_make_valid() where needed.
The result is a smoothly warped map emphasizing the metropolitan focus zone.
Prepared in data-raw/gen-data.R using the original vic polygon layer.
library(sf) plot(st_geometry(vic_fish), col = "grey90", border = "grey50")library(sf) plot(st_geometry(vic_fish), col = "grey90", border = "grey50")