Automatic direct labels for Makie with Voronoi tessellation

Best practice for making plots is to directly label lines and avoid indirect legends.
(See discussions by Edward Tufte on this: https://twitter.com/EdwardTufte/status/1274213603151880192/photo/1)

I’ve been trying to adapt and improve the ideas from this post on directly labeling lines:

[I got something cooked up pretty quickly]
(https://github.com/dgleich/MakieDirectLabels/blob/main/label-explainer.md)
This is a protopackage, a playground until I can figure out more stuff I’d need for a package.

This creates a single command: voronoi_labels!(…) to directly annotate the graph.
(This currently requires an edit to VoronoiCells, see pull request here. )

Example

using Random
Random.seed!(1)
using CairoMakie
include("voronoi-labels.jl")

f = lines(cumsum(randn(100)), label="Are Great",
  color=Cycled(1), marker=:rect)
lines!(f.axis, cumsum(randn(100)), label="Direct",
  color=Cycled(2))
lines!(f.axis, cumsum(randn(100)), label="Labels",
  color=Cycled(3))

# make it look nicer
f.axis.topspinevisible = false
f.axis.leftspinevisible = false
f.axis.rightspinevisible = false
f.axis.xgridvisible = false

labels = voronoi_labels!(f; offset=1.0)
labels[1].offset[] = 1.5 # manual tweak of one label 

f

What I need help with

I’m trying to gauge interest in this idea and ideally, solicit some help.

Interaction with Makie:

  • working with scales – everything really should work in screen / pixel space;
    what’s the best way to get and plot things directly in screen space on top of
    an existing axis with lines / scatter?

  • getting bounding boxes of labels/ checking intersections/etc. (boundingbox(text!(...)) does something I can’t seem to figure out in terms of information about plots in the axis.

  • thoughts on how else this might be done / made easier / etc.!

Vision

The idea is to make adding even easier than a legend!

There are a few ways to add these automatic labels in other software.

R has a similar package: https://tdhock.github.io/directlabels/docs/index.html

Thanks! David

10 Likes

What a great idea!

These are the types of things that would make me move from Plots.jl to Makie (smarter defaults that help me make impactful charts in a breeze)

Already almost possible with GMT (a random not repeatable plot)

using GMT

plot(1:40, GMT.fakedata(40), region=(0,40,-10,10), decorated=(quoted=true, const_label="Direct", curved=true, font=(8,:red)), lc=:red)
plot!(1:40, GMT.fakedata(40), decorated=(quoted=true, const_label="Labels", curved=true, font=(8,:green)), lc=:green)
plot!(1:40, GMT.fakedata(40), decorated=(quoted=true, const_label="Are Great", curved=true, font=(8,:blue)), lc=:blue, show=1)

The improvements here would be to find a sufficiently smooth line chunk to put the labels, and be able to do the plot with a single call.

Neat! I was going to look at the problem of finding straight enough regions soon.

Does anyone know how to show the bounding for a section of text in CairoMakie inside of a plot?

using CairoMakie, StableRNGs
x = cumsum(randn(StableRNG(1), 100).^2)
f = lines(x,  axis=(yscale=log10,))
lbl = text!(f.axis.scene, text="Direct Labels are Great!", 50, 50, align=(:right,:bottom))
bb = boundingbox(lbl)
wireframe!(f.axis.scene, bb, color=:red, space=:pixel)
f

Expected output: The text is framed with a red box!

Actual output:

(The bounding box info is clearly not “data” space …, but none of the other spaces work either.)

This might be a bug in Makie.jl.

This here draws a red box around the text, but the text is then placed wrongly.

using CairoMakie, StableRNGs                                                                                                                                                                                                 
x = cumsum(randn(StableRNG(1), 100).^2)                                                             
f = lines(x)                                                                                        
scene = campixel(f.axis.scene)                                                                      
lbl = text!(scene, 50, 50, text="Direct Labels are Great!", align=(:right,:bottom))                 
bb = boundingbox(lbl)                                                                               
wireframe!(scene, bb, color=:red, space=:pixel)                                                     
save("mwe_makie.png", f)         

What I changed is

  1. remove axis=(yscale=log10,)
  2. Create a new Scene with campixel and use that to place the text.

I think that both points interfer somehow with each other…

Here is the output:

It was mentioned here How to draw a textbox around a text in Makie.jl? - #2 by ffreyer that boundingbox does not do the right thing. However, I could not get the workaround to work either (would need more time).

We should open an issue over at Makie.jl.

I really like the idea of direct labelling. One reason why I’ve so far stayed away from implementing something like it is that I couldn’t quickly come up with clear semantics how labels should be placed given that a multitude of plot objects might be in a given Axis, and the Axis can change size due to layout recalculations. Effectively, the position of one label would depend on all other plot objects in the Axis, correct? (In order to minimize visual overlap). Right now, one plot object doesn’t even have access to the others in the same Axis, so that part is not quite clear to me. And if it’s supposed to work with interactive figures, the updating process would also have to be clarified.

Overall, great effort, and I’m very interested to see where this goes.

1 Like

The interaction with interactive plots will be interesting. Right now, I’m just trying to get things to make sense and be easy to use and give good results for a static plot. However, I would note that just the simple axis and ticks have all of the same challenges in terms of number of plots and datapoints, etc. I will try and take a quick look at how they solve similar issues.

I did open an issue over at Makie.jl

1 Like