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.topoplot
— Functiontopoplot(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 pointinterpolation::Interpolator = CloughTocher()
: Applicable interpolators are TopoPlots.CloughTocher, TopoPlots.DelaunayMesh, TopoPlots.NaturalNeighboursMethod, TopoPlots.NullInterpolator, TopoPlots.ScatteredInterpolationMethod, TopoPlots.SplineInterpolatorextrapolation = GeomExtrapolation()
: Extrapolation method for adding additional points to get less border artifactsbounding_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 aheatmap(0..100, 0..200, ...)
. By default, a circle enclosing thepositions
points will be used.enlarge
= 1.2, enlarges the area that is being drawn. E.g., if
bounding_geometryis
Circle`, 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 interpolationlabel_text = false
:- true: add text plot for each position from
labels
- NamedTuple: Attributes get passed to the Makie.text! call.
- true: add text plot for each position from
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,)plotfnc! = heatmap!
: function to use for plotting the interpolationplotfnc_kwargs_names = [:colorrange, :colormap, :interpolate]
: differentplotfnc
support different kwargs, this array contains the keys to filter the full list which is [:colorrange, :colormap, :interpolate]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))
Interpolation
TopoPlots provides access to interpolators from several different Julia packages through its TopoPlots.Interpolator
interface.
They can be accessed via plotting, or directly by calling the instantiated interpolator object as is shown below, namely with the arguments (::Interpolator)(xrange::LinRange, yrange::LinRange, positions::AbstractVector{<: Point{2}}, data::AbstractVector{<:Number})
. This is similar to using things like Matlab's regrid
function. You can find more details in the Interpolation section.
The recipe supports different interpolation methods, namely:
TopoPlots.DelaunayMesh
— TypeDelaunayMesh()
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.
DelaunayMesh
won't allow you to add a contour plot to the topoplot.
DelaunayMesh
will not behave accurately if rendered via CairoMakie, because Cairo (and SVG in general) does not support color maps on meshes. The color within each triangle will be based only on the values at the vertices, which causes inaccurate visuals.
TopoPlots.CloughTocher
— TypeCloughTocher(fill_value=NaN, tol=1e-6, maxiter=400, rescale=false)
Piecewise cubic, C1 smooth, curvature-minimizing interpolant in 2D. Find more detailed docs in CloughTocher2DInterpolation.jl.
This is the default interpolator in MNE-Python.
TopoPlots.SplineInterpolator
— TypeSplineInterpolator(;kx=2, ky=2, smoothing=0.5)
Uses Dierckx.Spline2D for interpolation.
TopoPlots.ScatteredInterpolationMethod
— TypeScatteredInterpolationMethod(InterpolationMethod)
Container to specify a InterpolationMethod
from ScatteredInterpolation.jl
. E.g. ScatteredInterpolationMethod(Shepard(P=4))
TopoPlots.NaturalNeighboursMethod
— TypeNaturalNeighboursMethod(; method=Sibson(1), kwargs...)
Interpolator that uses the NaturalNeighbours.jl package to interpolate the data. This uses Delaunay triangulations and the corresponding Voronoi diagram to interpolate the data, and offers a variety of methods like Sibson(::Int)
, Nearest()
, and Triangle()
.
The advantage of Voronoi-diagram based methods is that they are more robust to irregularly distributed datasets and some discontinuities, which may throw off some polynomial based methods as well as independent distance weighting (kriging). See this Discourse post for more information on why NaturalNeighbours are cool!
To access the methods easily, you should run using NearestNeighbours
.
See the NaturalNeighbours documentation for more details.
This method is fully configurable and will forward all arguments to the relevant NaturalNeighbours.jl functions. To understand what the methods do, see the documentation for NaturalNeighbours.jl
.
Keyword arguments
The main keyword argument to look at here is method
, which is the method to use for the interpolation. The other keyword arguments are simply forwarded to NaturalNeighbours.jl's interpolator.
method
: The method to use for the interpolation. Defaults to `Sibson(1). Default: NaturalNeighbours.Sibson{1}()parallel
: Whether to use multithreading when interpolating. Defaults totrue
. Default: trueproject
: Whether to project the data onto the Delaunay triangulation. Defaults totrue
. Default: truederivative_method
: The method to use for the differentiation. Defaults toDirect()
. May beDirect()
orIterative()
. Default: NaturalNeighbours.Direct()use_cubic_terms
: Whether to use cubic terms for estimating the second order derivatives. Only relevant for derivative_method == Direct(). Default: truealpha
: The weighting parameter used for estimating the second order derivatives. Only relevant for derivative_method == Iterative(). Default: 0.1
TopoPlots.NullInterpolator
— TypeNullInterpolator()
Interpolator that returns "0", which is useful to display only the electrode locations + labels
You can define your own interpolation by subtyping:
TopoPlots.Interpolator
— TypeInterface for all types <: Interpolator:
interpolator = Interpolator(; kw_specific_to_interpolator)
interpolator(xrange::LinRange, yrange::LinRange, positions::Vector{Point2}, data::Vector{<: Real})::Matrix{<: Real}
and making your interpolator SomeInterpolator
callable with the signature
(::SomeInterpolator)(xrange::LinRange, yrange::LinRange, positions::AbstractVector{<: Point{2}}, data::AbstractVector{<:Number}; mask=nothing)
See also Interpolator Comparison.
Extrapolation
There are currently just two extrapolations: None (NullExtrapolation()
) and a geometry based one:
TopoPlots.GeomExtrapolation
— TypeGeomExtrapolation(
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)
The extrapolations in action:
using CairoMakie, TopoPlots
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
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, 1250))
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
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))
Different plotfunctions
It is possible to exchange the plotting function, from heatmap!
to contourf!
or surface!
. Due to different keyword arguments, one needs to filter which keywords are passed to the plotting function manually.
f = Figure()
TopoPlots.topoplot(f[1,1],
rand(10), rand(Point2f, 10),
axis=(; aspect=DataAspect()),
plotfnc! = contourf!, plotfnc_kwargs_names=[:colormap])
TopoPlots.topoplot(f[1,2],
rand(10), rand(Point2f, 10),
axis=(; aspect=DataAspect()),
plotfnc! = surface!) # surface can take all default kwargs similar to heatmap!
f