Skip to content

Core Functions

This page documents the main functions and types for creating geofaceted visualizations with GeoFacetMakie.jl.

Main Plotting Function

GeoFacetMakie.geofacet Function
julia
geofacet(data, region_col, plot_func;
         grid = us_state_grid1,
         link_axes = :none,
         hide_inner_decorations = true,
         title = "",
         titlekwargs = NamedTuple[],
         figure_kwargs = NamedTuple(),
         common_axis_kwargs = NamedTuple(),
         axis_kwargs_list = NamedTuple[],
         legend_kwargs = NamedTuple(),
         func_kwargs = (missing_regions = :skip,))

Create a geographically faceted plot using the specified grid layout.

Arguments

  • data: DataFrame or similar tabular data structure

  • region_col: Symbol or string specifying the column containing region identifiers

  • plot_func: Function that takes (gridlayout, data_subset; kwargs...) for single-axis plots or (gridlayout, data_subset; processed_axis_kwargs_list) for multi-axis plots

Keyword Arguments

  • grid: GeoGrid object defining the spatial layout (default: us_state_grid1)

  • link_axes: Symbol controlling axis linking (:none, :x, :y, :both)

  • hide_inner_decorations: Bool controlling whether to hide axis decorations on inner facets when axes are linked (default: true). Only affects linked axes - e.g., if link_axes = :x, only x-axis decorations are hidden for facets with neighbors below.

  • title: String for the overall plot title (default: "" for no title)

  • titlekwargs: NamedTuple of keyword arguments passed to the title Label constructor

  • figure_kwargs: NamedTuple passed to Figure() constructor

  • common_axis_kwargs: NamedTuple applied to all axes in each facet. Important: To ensure proper hiding of axis decorations when hide_inner_decorations = true, axis labels (xlabel, ylabel) should be specified here rather than within the plot function.

  • axis_kwargs_list: Vector of NamedTuples for per-axis specific kwargs. Each element corresponds to an axis in the order they are created in the plot function. These are merged with common_axis_kwargs (per-axis takes precedence). If multiple axes are plotted on the same facet, you should set the position within each NamedTuple using the kwarg yaxisposition (defaults to :left)

  • legend_kwargs: NamedTuple of keyword arguments for legend creation. Special keys:

    • :title: Legend title (extracted and passed separately to Legend)

    • :legend_position: Tuple (row, col) specifying legend position in grid layout. Use nothing for a dimension to use default (e.g., (nothing, ncols+1) for rightmost column)

    • All other keys are passed directly to the Legend constructor

  • func_kwargs: NamedTuple of additional keyword arguments passed to the plot function. Required key:

    • :missing_regions: How to handle regions in grid but not in data (:skip, :empty, :error). Defaults to :skip if not specified.

Returns

A Makie Figure object containing the geofaceted plot.

Examples

julia
using DataFrames, GeoFacetMakie

# Sample data
data = DataFrame(
    state = ["CA", "TX", "NY"],
    population = [39_500_000, 29_000_000, 19_500_000],
    gdp = [3_200_000, 2_400_000, 1_900_000]
)

# Single-axis plot with title and legend
result = geofacet(data, :state, (gl, data; kwargs...) -> begin
    ax = Axis(gl[1, 1]; kwargs...)
    barplot!(ax, [1], data.population, color = :blue, label = "Population")
    ax.title = data.state[1]  # Set title to state name
    ax.xticksvisible = false  # Clean up x-axis
    ax.xticklabelsvisible = false
end;
    title = "US State Population",
    titlekwargs = (fontsize = 16, color = :darkblue),
    common_axis_kwargs = (ylabel = "Population (M)",),
    legend_kwargs = (title = "Metrics",)
)

# Multi-axis plot with common and per-axis kwargs
result = geofacet(data, :state, (gl, data; processed_axis_kwargs_list) -> begin
    ax1 = Axis(gl[1, 1]; processed_axis_kwargs_list[1]...)
    ax2 = Axis(gl[2, 1]; processed_axis_kwargs_list[2]...)
    barplot!(ax1, [1], data.population)
    barplot!(ax2, [1], data.gdp)
end;
    common_axis_kwargs = (titlesize = 12,),
    axis_kwargs_list = [
        (xlabel = "Index", ylabel = "Population (M)"),
        (xlabel = "Index", ylabel = "GDP (B)", yscale = log10)
    ],
    figure_kwargs = (size = (1200, 800),)
)

# Time series example with linked y-axes
time_data = DataFrame(
    state = repeat(["CA", "TX", "NY"], inner = 5),
    year = repeat(2019:2023, 3),
    value = rand(15) .* 100
)

result = geofacet(time_data, :state, (gl, data; kwargs...) -> begin
    ax = Axis(gl[1, 1]; kwargs...)
    lines!(ax, data.year, data.value, color = :darkgreen, linewidth = 2)
    ax.title = data.state[1]
end;
    link_axes = :y,  # Link y-axes for comparison
    common_axis_kwargs = (xlabel = "Year", ylabel = "Value"),
    func_kwargs = (missing_regions = :skip,)  # Skip regions not in data
)

# Error handling example
error_data = DataFrame(
    state = ["CA", "TX", "INVALID"],  # INVALID state will be skipped
    value = [1, 2, 3]
)

result = geofacet(error_data, :state, (gl, data; kwargs...) -> begin
    ax = Axis(gl[1, 1]; kwargs...)
    barplot!(ax, [1], data.value, color = :orange)
    ax.title = data.state[1]
end;
    func_kwargs = (missing_regions = :skip,)  # Gracefully skip invalid regions
)
source

Core Data Types

GeoGrid

GeoFacetMakie.GeoGrid Type
julia
GeoGrid

A geographical grid layout using StructArray for efficient storage. Stores region names and their (row, col) positions in a structure-of-arrays format.

This provides better memory efficiency and performance through:

  • Structure-of-Arrays (SOA) layout for better cache locality

  • Vectorized operations on grid data

  • Natural integration with DataFrames.jl ecosystem

Examples

julia
# Create from vectors (backward compatible)
grid = StructArray{GridEntry}((
    region = ["CA", "NY", "TX", "FL"],
    row = [1, 1, 2, 2],
    col = [1, 2, 1, 2],
    name = ["California", "New York", "Texas", "Florida"],
    metadata = [Dict{String,Any}(), Dict{String,Any}(), Dict{String,Any}(), Dict{String,Any}()]
))

# Access individual fields
regions = grid.region
rows = grid.row
cols = grid.col
names = grid.name
metadata = grid.metadata

# Iterate over entries
for grid_entry in grid
    println("$(grid_entry.region) ($(grid_entry.name)) at ($(grid_entry.row), $(grid_entry.col))")
end
source

GridEntry

GeoFacetMakie.GridEntry Type
julia
GridEntry

A single entry in a geographical grid, containing the region identifier, its position coordinates, display name, and optional metadata.

Fields

  • region::String: Region identifier/code

  • row::Int: Grid row position (≥ 1)

  • col::Int: Grid column position (≥ 1)

  • name::String: Display name for the region (defaults to region if not provided)

  • metadata::Dict{String,Any}: Additional metadata from CSV columns

Constructors

julia
# Basic constructor (backward compatible)
entry = GridEntry("CA", 1, 2)

# With custom name
entry = GridEntry("CA", 1, 2, "California")

# With name and metadata
entry = GridEntry("CA", 1, 2, "California", Dict("population" => 39538223))

Iteration and Indexing

GridEntry supports iteration, unpacking, and indexing for backward compatibility:

julia
entry = GridEntry("CA", 1, 2)

# Unpack core fields (backward compatible)
region, row, col = entry

# Index access (backward compatible)
region = entry[1]  # "CA"
row = entry[2]     # 1
col = entry[3]     # 2

# Access new fields directly
name = entry.name        # "CA" (defaults to region)
metadata = entry.metadata # Dict{String,Any}()

# Iterate over core fields (backward compatible)
for value in entry
    println(value)  # Prints "CA", 1, 2
end

# Convert to array (backward compatible)
values = collect(entry)  # ["CA", 1, 2]

# Functional style
regions = map(e -> e[1], grid)  # Extract all regions

Working with Names and Metadata

julia
# Create entry with custom display name
entry = GridEntry("CA", 1, 2, "California")
println(entry.name)  # "California"

# Create entry with metadata (e.g., from CSV loading)
metadata = Dict("population" => 39538223, "area_km2" => 423970)
entry = GridEntry("CA", 1, 2, "California", metadata)

# Access metadata
pop = entry.metadata["population"]  # 39538223
area = entry.metadata["area_km2"]   # 423970

# Use in plotting functions (assuming you have access to the grid entry)
# Note: This is a conceptual example - in practice you'd need to look up
# the grid entry for the current region within your plot function
geofacet(data, :state, (gl, data; kwargs...) -> begin
    ax = Axis(gl[1, 1]; kwargs...)
    # Use region code for title (standard approach)
    ax.title = data.state[1]  # "CA"
    
    # Or if you have access to the grid entry with display names:
    # ax.title = entry.name  # "California" instead of "CA"
    
    lines!(ax, data.year, data.value)
end)

# Filter grids by metadata
large_states = filter(e -> get(e.metadata, "population", 0) > 10_000_000, grid)
source

Usage Examples

Basic Geofaceted Plot

julia
using GeoFacetMakie, DataFrames, CairoMakie

# Sample data
data = DataFrame(
    state = ["CA", "TX", "NY", "FL"],
    population = [39.5, 29.1, 19.8, 21.5],
    year = [2023, 2023, 2023, 2023]
)

# Define plotting function
function plot_bars!(gl, data; kwargs...)
    ax = Axis(gl[1, 1]; kwargs...)
    barplot!(ax, [1], data.population, color = :steelblue)
    ax.title = data.state[1]
end

# Create geofaceted plot
fig = geofacet(data, :state, plot_bars!;
               figure_kwargs = (size = (800, 600),),
               common_axis_kwargs = (ylabel = "Population (M)",))

Multi-Axis Plot

julia
# Multi-axis plot with different styling per axis
function plot_dual!(gl, data; processed_axis_kwargs_list)
    ax1 = Axis(gl[1, 1]; processed_axis_kwargs_list[1]...)
    ax2 = Axis(gl[2, 1]; processed_axis_kwargs_list[2]...)

    barplot!(ax1, [1], data.population, color = :blue)
    barplot!(ax2, [1], data.gdp, color = :red)
end

fig = geofacet(data, :state, plot_dual!;
               axis_kwargs_list = [
                   (ylabel = "Population (M)",),
                   (ylabel = "GDP (B)", yscale = log10)
               ],
               common_axis_kwargs = (titlesize = 12,))

Custom Grid Usage

julia
# Create a custom grid
custom_grid = GeoGrid(
    ["A", "B", "C", "D"],
    [1, 1, 2, 2],
    [1, 2, 1, 2]
)

# Use with geofacet
fig = geofacet(data, :region, plot_function!; grid = custom_grid)

Plotting Function Interface

Function Signature

Plotting functions must follow this signature:

julia
function my_plot!(gl::GridLayout, data::DataFrame; kwargs...)
    # Create axis in the grid layout
    ax = Axis(gl[1, 1]; kwargs...)

    # Your plotting code here
    lines!(ax, data.x, data.y)

    # Set title to identify the region
    ax.title = data.region[1]

    return nothing
end

Multi-Axis Functions

For plots with multiple axes (e.g., dual y-axes):

julia
function dual_plot!(gl::GridLayout, data::DataFrame; processed_axis_kwargs_list)
    # First axis
    ax1 = Axis(gl[1, 1]; processed_axis_kwargs_list[1]...)
    lines!(ax1, data.x, data.y1, color = :blue)

    # Second axis
    ax2 = Axis(gl[1, 1]; processed_axis_kwargs_list[2]...)
    lines!(ax2, data.x, data.y2, color = :red)

    ax1.title = data.region[1]
    return nothing
end

Best Practices

  1. Always mutate: Function names should end with !

  2. Use kwargs: Accept kwargs... for axis styling

  3. Set titles: Use ax.title = data.region[1] to identify regions

  4. Handle edge cases: Check for empty data, missing values

  5. Return nothing: Explicitly return nothing

Error Handling

Missing Regions

julia
# Skip regions not in grid (default)
fig = geofacet(data, :state, plot_function!; missing_regions = :skip)

# Show empty facets for missing regions
fig = geofacet(data, :state, plot_function!; missing_regions = :empty)

# Throw error if regions are missing
fig = geofacet(data, :state, plot_function!; missing_regions = :error)

Plot Function Errors

The geofacet function includes error handling for plot functions. If a plot function fails for a specific region, a warning is issued and plotting continues for other regions:

julia
function potentially_failing_plot!(gl, data; kwargs...)
    ax = Axis(gl[1, 1]; kwargs...)
    if data.value[1] < 0  # This might fail for some regions
        error("Negative values not supported")
    end
    barplot!(ax, [1], data.value)
end

# This will warn about failed regions but continue plotting others
fig = geofacet(data, :region, potentially_failing_plot!)

Advanced Features

Axis Linking

julia
# Link both X and Y axes across facets
fig = geofacet(data, :state, plot_function!; link_axes = :both)

# Link only Y axes (good for comparing magnitudes)
fig = geofacet(data, :state, plot_function!; link_axes = :y)

# Link only X axes (good for time series)
fig = geofacet(data, :state, plot_function!; link_axes = :x)

# No linking (default)
fig = geofacet(data, :state, plot_function!; link_axes = :none)

Legend Creation

julia
function plot_with_legend!(gl, data; kwargs...)
    ax = Axis(gl[1, 1]; kwargs...)
    barplot!(ax, [1], data.population, color = :blue, label = "Population")
    lines!(ax, [0.5, 1.5], [data.gdp, data.gdp], color = :red, label = "GDP")
    ax.title = data.state[1]
end

fig = geofacet(data, :state, plot_with_legend!;
               legend_kwargs = (
                   title = "Metrics",
                   legend_position = (1, 4)  # Row 1, Column 4
               ))

Decoration Hiding

When axes are linked, inner decorations (tick labels, axis labels) are automatically hidden to reduce clutter:

julia
# Inner decorations hidden automatically when axes are linked
fig = geofacet(data, :state, plot_function!;
               link_axes = :y,
               hide_inner_decorations = true,  # Default
               common_axis_kwargs = (
                   xlabel = "Index",  # Only shown on bottom row
                   ylabel = "Value"   # Only shown on left column
               ))

See Also