[ANN] MakieExtra.jl – more recipes and tools for Makie plots

The promised new feature is:

FPlot: composable plotting specification

What started as a simple proof of concept in the #makie channel several months ago – now became the primary way I do plotting.
I believe it is generally a nice way to do basically all kinds of plots, aside from images/volumes/similar stuff.

The design of most plotting libraries, including Makie, tends to take different properties of the plot (xy coordinates, color, size, …) as separate arrays. This is convenient for tiny self-contained snippets, hard to argue with the simplicity of scatter(rand(100), rand(100)). However, this approach feels suboptimal for anything even slightly more complicated.

It’s very common that one has a dataset – a collection of elements – and thinks of plotting in terms of these elements. Like, "for r ∈ data, plot abs(r.value) along the x axis, angle(r.value) along the y axis, and color them by r.age.
FPlot is an object that encapsulates the whole plot definition – the dataset, xy mapping, and mappings for other attributes like color.

FPlot(
	dataset,
	r->abs(r.value), r->angle(r.value);  # what to plot on x and y axes 
	color=r->r.age,  # mappings for any keyword arguments
)

As FPlot “knows” what is shown along axes, it can automatically set axis labels – lines(FPlot(data, (@o _.a), (@o _.b))) results in this:


It also naturally leads to reusing/modifying the same FPlot for multiple views into a dataset, and makes basic composable interactivity possible with a few lines of code:

See a more detailed overview and more examples in the docs.

Try playing with FPlot to get a feeling how it works, I should say it becomes very natural quickly :slight_smile:
Suggestions for a better name are welcome!

7 Likes

Just on this, I’ve been campaigning for use of StyledStrings for this, and I think I’ll continue to do so :grin:.

Here’s an API I’d put to you all:

# Current Makie
rich("a", subscript("x"), ": ", rich(a, font=:bold), ", b: ", rich(b, font=:italic))
# MakieExtra
"a" * subscript("x") * ": " * rich(a, font=:bold) * ", b: " * rich(b, font=:italic)
@rich "a$(subscript("x")): $(rich(a, font=:bold)), b: $(rich(b, font=:italic))"
# Proposed
styled"a{_:x}: {bold:$a}, b: {italic:$b}"
5 Likes

In general yes, would be nice to have a consistent interface for rich text to be used across different Julia packages.
It’s just that in this thread, I only highlight already existing features, something that one could use right now. The implementation in MakieExtra is extremely simple, just a few lines.
AFAIK, StyledStrings cannot be currently used in Makie. Also, all those annotated strings are an experimental API that has a different behavior between Julia versions. That’s another difference from Makie rich text, something one should also be careful about…

1 Like

I think it would be worth waiting till I can put together another compat release to reflect a few of the changes that slipped into 1.11 last-minute, but I don’t see any hard reason why not: the compat/1.11 differences don’t affect what I think Makie would need to use

Anyway, as you say — not something you can do anything about, but I’m excited about where this can lead and I like to take opportunities to share that with other people :slight_smile:

4 Likes

More than a year has passed since my last post here – and MakieExtra.jl got quite a bit of updates over that time :slight_smile:
Most are quite self-contained – some examples (not a complete list):

  • zoomlines is more flexible and take arbitrary attributes to pass to lines and rects it draws;
  • mouse_position_obs() gets the mouse position as observable
  • @lift ...::T to manually specify the type of the resulting Observable (useful if the value type changes)
  • rel2data() / data2rel() to plot at “x=5, y=bottom of axis” or “x = 5 + 1/10 of the axis width”
  • automatic split of lines/polygons for GeoAxis – to avoid artifacts when crossing the map seam

Some MakieExtra features have even been upstreamed to Makie proper: stuff like textwithbox (Makie got textlabel) or rich strings concatenation.

But I would also like to highlight more major features here once in a while <see the next post…>

5 Likes

Today, it’s time to highlight…

InteractivePoints(): the easiest way to make your plot data directly editable!

Very few changes needed to turn a static plot into an interactive one – this is the full diff:

With that, you can move each point with your mouse, add/remove points to the plot. All interactions are built on Observables, and arbitrary calculations can run on update of data.
For example, here we use Interpolations.jl to create a continuous functions from user-drawn points, which can be used in downstream computations:

Full code
# set up calculations first – nothing specific to InteractivePoints here:
data = Observable([(0.2, 1.), (0.5, 0.5)])
func = @lift @p let
	$data
	sort(by=first)
	linear_interpolation(first.(__), last.(__), extrapolation_bc=Linear())
end

# now create and fill the figure:
fig = Figure()

axplot(scatter; widgets=[InteractivePoints(data)])(
	fig[1,1], (@lift FPlot($data, first, last)), label="Points")
lines!((@lift x->$func(x)); label="Linear", yautolimits=false)
axislegend()

A neat demo with Complex math, showcasing two interactive Axes connected to the same dataset with different functions assigned to their x/y axes:

Full code
# definitions and calculations - generic, not specific to InteractivePoints
# - define data:
data = Observable([
	(z=0. +0im, pow=1.),
	(z=1. -3im, pow=2.),
	(z=-2. -3im, pow=1.5),
])
# - define function: sum of 1/(z - r.z)^r.pow over data
f = @lift z -> sum(r -> 1 / (z - r.z)^r.pow, $data)
# - compute the function on a grid for plotting
reimvals = range(0±5, length=500)
f_plane = @lift map($f, grid(Complex, reimvals, reimvals))

# now create and fill the figure
fig = Figure()

# first axis: show real(z) and imag(z) with interactivity
axplot(scatter; widgets=[InteractivePoints(data)])(fig[1,1],
		(@lift FPlot($data,
						AxFunc(limit=0±5, @o real(_.z)),
						AxFunc(limit=0±5, @o imag(_.z)))),
		axis=(;height=200))

# second axis: show abs(z) and pow with interactivity
axplot(scatter; widgets=[InteractivePoints(data)])(fig[1,2],
		(@lift FPlot($data,
						AxFunc(limit=0..5, @o abs(_.z)),
						AxFunc(limit=-1..3, @o _.pow))),
		axis=(;height=200))

# final axis: display the surface-plot of the function
surface(fig[2,:],
		(@lift abs.($f_plane)),
		color=(@lift angle.($f_plane)),
		colormap=:cyclic_mrybm_35_75_c68_n256, interpolate=false,
		axis=(;zlabel="|f(z)|", type=Axis3, viewmode=:free, height=500, limits=(nothing, nothing, 0..20)))

resize_to_layout!()

Check out the code for these examples – it’s really just a few lines of code to get interactivity, no boilerplate!
More details are available in the docs.

Also, please let me know if you’ve seen even more straightforward interactive plots in other ecosystems – happy to draw inspiration for the best interface from elsewhere :slight_smile:

21 Likes