Recipe for General TopoPlots

At the core of TopoPlots.jl is the topoplot recipe, which takes an array of measurements and an array of positions, which then creates a heatmap like plot which interpolates between the measurements from the positions.

TopoPlots.topoplotFunction
topoplot(data::Vector{<:Real}, positions::Vector{<: Point2})

Creates an irregular interpolation for each data[i] point at positions[i].

Attributes

  • colormap = Reverse(:RdBu)

  • colorrange = automatic

  • labels::Vector{<:String} = nothing: names for each data point

  • interpolation::Interpolator = CloughTocher(): Applicable interpolators are TopoPlots.CloughTocher, TopoPlots.DelaunayMesh, TopoPlots.NullInterpolator, TopoPlots.ScatteredInterpolationMethod, TopoPlots.SplineInterpolator

  • extrapolation = GeomExtrapolation(): Extrapolation method for adding additional points to get less border artifacts

  • bounding_geometry = Circle: A geometry that defines what to mask and the x/y extend of the interpolation. E.g. Rect(0, 0, 100, 200), will create a heatmap(0..100, 0..200, ...). By default, a circle enclosing the positions points will be used.

  • enlarge = 1.2, enlarges the area that is being drawn. E.g., ifbounding_geometryisCircle`, a circle will be fitted to the points and the interpolation area that gets drawn will be 1.2x that bounding circle.

  • interp_resolution = (512, 512): resolution of the interpolation

  • label_text = false:

    • true: add text plot for each position from labels
    • NamedTuple: Attributes get passed to the Makie.text! call.
  • label_scatter = false:

    • true: add point for each position with default attributes
    • NamedTuple: Attributes get passed to the Makie.scatter! call.
  • markersize = 5: size of the points defined by positions, shortcut for label_scatter=(markersize=5,)

  • contours = false:

    • true: add scatter point for each position
    • NamedTuple: Attributes get passed to the Makie.contour! call.

Example

using TopoPlots, CairoMakie
topoplot(rand(10), rand(Point2f, 10); contours=(color=:red, linewidth=2))
source

Interpolation

The recipe supports different interpolation methods, namely:

TopoPlots.DelaunayMeshType
DelaunayMesh()

Creates a delaunay triangulation of the points and linearly interpolates between the vertices of the triangle. Really fast interpolation that happens on the GPU (for GLMakie), so optimal for exploring larger timeseries.

Warning

DelaunayMesh won't allow you to add a contour plot to the topoplot.

source
TopoPlots.CloughTocherType
CloughTocher(fill_value=NaN, tol=1e-6, maxiter=400, rescale=false)

Piecewise cubic, C1 smooth, curvature-minimizing interpolant in 2D. Find more detailed docs in CloughTocher2DInterpolator.jl.

This is the default interpolator in MNE-Python

source
TopoPlots.ScatteredInterpolationMethodType
ScatteredInterpolationMethod(InterpolationMethod)

Container to specify a `InterpolationMethod` from ScatteredInterpolation.
E.g. ScatteredInterpolationMethod(Shepard(P=4))
source

One can define your own interpolation by subtyping:

TopoPlots.InterpolatorType

Interface for all types <: Interpolator:

interpolator = Interpolator(; kw_specific_to_interpolator)
interpolator(xrange::LinRange, yrange::LinRange, positions::Vector{Point2}, data::Vector{<: Real})::Matrix{<: Real}
source

The different interpolation schemes look quite different:

using TopoPlots, CairoMakie, ScatteredInterpolation

data, positions = TopoPlots.example_data()

f = Figure(resolution=(1000, 1000))

interpolators = [
    DelaunayMesh() CloughTocher();
    SplineInterpolator() NullInterpolator();
    ScatteredInterpolationMethod(ThinPlate()) ScatteredInterpolationMethod(Shepard(3))]

data_slice = data[:, 360, 1]

for idx in CartesianIndices(interpolators)
    interpolation = interpolators[idx]

    # precompile to get accurate measurements
    TopoPlots.topoplot(
        data_slice, positions;
        contours=true, interpolation=interpolation,
        labels = string.(1:length(positions)), colorrange=(-1, 1),
        label_scatter=(markersize=10,),
        axis=(type=Axis, title="...", aspect=DataAspect(),))

    # measure time, to give an idea of what speed to expect from the different interpolators
    t = @elapsed ax, pl = TopoPlots.topoplot(
        f[Tuple(idx)...], data_slice, positions;
        contours=true,
        interpolation=interpolation,
        labels = string.(1:length(positions)), colorrange=(-1, 1),
        label_scatter=(markersize=10,),
        axis=(type=Axis, title="$(typeof(interpolation))()",aspect=DataAspect(),))

   ax.title = ("$(typeof(interpolation))() - $(round(t, digits=2))s")
end
f
Example block output

Extrapolation

There are currently just two extrapolations: None (NullExtrapolation()) and a geometry based one:

TopoPlots.GeomExtrapolationType
GeomExtrapolation(
    method = Shepard(), # extrapolation method
    geometry = Rect, # the geometry to fit around the points
    enlarge = 3.0 # the amount to grow the bounding geometry for adding the extra points
)

Takes positions and data, and returns points and additional datapoints on an enlarged bounding geometry:

extra = GeomExtrapolation()
extra_positions, extra_data, bounding_geometry, bounding_geometry_enlarged = extra(positions, data)
source

The extrapolations in action:

data, positions = TopoPlots.example_data()
titles = ["No Extrapolation", "Rect", "Circle"]
data_slice = data[:, 340, 1]
f = Figure(resolution=(900, 300))
for (i, extra) in enumerate([NullExtrapolation(), GeomExtrapolation(enlarge=3.0), GeomExtrapolation(enlarge=3.0, geometry=Circle)])
    pos_extra, data_extra, rect_extended, rect = extra(positions, data_slice)
    geom = extra isa NullExtrapolation ? Rect : extra.geometry
    # Note, that enlarge doesn't match (the default), the additional points won't be seen and masked by `bounding_geometry` and `enlarge`.
    enlarge = extra isa NullExtrapolation ? 1.0 : extra.enlarge
    ax, p = topoplot(f[1, i], data_slice, positions; extrapolation=extra, bounding_geometry=geom, enlarge=enlarge, axis=(aspect=DataAspect(), title=titles[i]))
    scatter!(ax, pos_extra, color=data_extra, markersize=10, strokewidth=0.5, strokecolor=:white, colormap = p.colormap, colorrange = p.colorrange)
    lines!(ax, rect_extended, color=:black, linewidth=4)
    lines!(ax, rect, color=:red, linewidth=1)
end
resize_to_layout!(f)
f
Example block output

Interactive exploration

DelaunayMesh is best suited for interactive data exploration, which can be done quite easily with Makie's native UI and observable framework:

f = Figure(resolution=(1000, 1000))
s = Slider(f[:, 1], range=1:size(data, 2), startvalue=351)
data_obs = map(s.value) do idx
    data[:, idx, 1]
end
TopoPlots.topoplot(
    f[2, 1],
    data_obs, positions,
    interpolation=DelaunayMesh(),
    labels = string.(1:length(positions)),
    colorrange=(-1, 1),
    colormap=:viridis,
    axis=(title="delaunay mesh",aspect=DataAspect(),))
f
Example block output

Different geometry

The bounding geometry pads the input data with more points in the form of the geometry. So e.g. for maps, one can use Rect as the bounding geometry:

TopoPlots.topoplot(
    rand(10), rand(Point2f, 10),
    axis=(; aspect=DataAspect()),
    colorrange=(-1, 1),
    bounding_geometry = Rect,
    label_scatter=(; strokewidth=2),
    contours=(linewidth=2, color=:white))
Example block output