I created my own legend entries following the docs, now I’d like to plot with them.
MWE:
using GLMakie
legendentries = Dict("A" => LineElement(linestyle = nothing, linewidth = 0.3, color = :red),
"B" => MarkerElement(color = :white, marker = '⋆', strokecolor = :black, markerstrokewidth = 0.5, strokewidth = 0.5, markersize = 10px))
fig = Figure()
ax = fig[1, 1] = Axis(fig, aspect = DataAspect(), xlabel = "X (cm)", ylabel = "Y (cm)")
lines!(ax, rand(Point2f0, 10)) # Iwant the line here to be red and have a thickness of 0.3
scatter!(ax, rand(Point2f0, 10)) # I want the markers here to be stars etc...
fig[0,1] = Legend(fig, collect(values(legendentries)), collect(keys(legendentries)))
fig
Basically, I suspect I need a method to convert LineElements, MarkerElements and PolyElements to a named tuple for this to work smoothly, or perhaps for the lines! function to accept LineElement as a specifier for the properties of the plotted line. But perhaps there is a better way to do this…?
Maybe I’m missing something, but why don’t you start with the named tuple instead? I.e., plot with the named tuple of the series’ style, and use the same named tuple again for the legend (edited and added the plot output):
using GLMakie
seriestype = Dict("A" => (linestyle = nothing, linewidth = 0.3, color = :red),
"B" => (color = :white, marker = '⋆', strokecolor = :black, markerstrokewidth = 0.5, strokewidth = 0.5, markersize = 10px))
legendentries = Dict("A" => LineElement(; seriestype["A"]...),
"B" => MarkerElement(; seriestype["B"]...))
fig = Figure(resolution = (1200, 700))
ax = fig[1, 1] = Axis(fig, xlabel = "X (cm)", ylabel = "Y (cm)", aspect = DataAspect(), width=500)
lines!(ax, rand(Point2f0, 10); seriestype["A"]...) # Iwant the line here to be red and have a thickness of 0.3
scatter!(ax, rand(Point2f0, 10); seriestype["B"]...) # I want the markers here to be stars etc...
leg = Legend(fig, collect(values(legendentries)), collect(keys(legendentries)), tellheight=true)
fig[2, 1] = leg
leg.tellheight = true
leg.orientation = :horizontal
fig
Hi @jules! The reason is that this plot is part of a whole series of other plots (elsewhere) and I want the styles to be consistent between plots. So by defining the styles first, I can use the styles everywhere and know that they are the same.
So yea, @briochemc, I could define the named tuples first, and use those as-is in the plots, and then wrap them appropriately (i.e. with LineElement) in the Legend call, but I was wondering if I was missing something simpler here. Like, why should there be a discrepancy between the argument types that Legend accepts and the types lines! and scatter! accept when it comes to styling the objects?
@briochemc I tried to implement what you said, but I can’t even get a MWE to work. Can you show me what exactly you mean? I might have misunderstood you, but I thought you meant:
define the styles in named tuples
feed those to the plots
wrap these nt with the appropriate LineElement, MarkerElement etc
Thank you so much @briochemc ! Yes, it works perfectly and solves my issue.
But I think the real solution I’m looking for is using plot recipes. That would be the correct way to avoid defining the styles both as a named tuple and as a LineElement , MarkerElement, etc.
I’m not sure you need a recipe for that. I assumed you wanted to separate the series named tuples and the legend for fine control, but if they are supposed to be the same, you should be able to just do things like @jules suggested, e.g. following the tutorial on layouting, defining the series first and then reusing them for the legend:
mylines = lines!(ax, rand(Point2f0, 10); seriestype["A"]...) # Iwant the line here to be red and have a thickness of 0.3
myscatter = scatter!(ax, rand(Point2f0, 10); seriestype["B"]...) # I want the markers here to be stars etc...
and then use mylines and myscatter to define the legend via
leg = Legend(fig, [mylines, myscatter], ["A", "B"])
fig[2,1] = leg
Yea, that’s one way, my problem with that though has been the fact that I plot multiple similar lines inside a for-loop, so it’s a pain to store the line-object outside the loop just for the sake of a legend. Importantly, the legend does not detail every single line plotted in the loop, just one of them (cause they all are the same “thing”). Let me know if you need to see a MWE to understand my pain-point.
f = Figure()
ax = Axis(f[1, 1])
for i in 1:100
l = lines!(cumsum(randn(1000)), color = (:blue, 0.3))
if i == 1
l.label = "one line out of many"
end
end
Legend(f[1, 2], ax)
f
I have actually thought about adding a unique = true option to this legend constructor, so you can label all your plots but only one line per label will be added. That saves you some if else logic in loops.
@jules, I encountered a detail I thought would be worth mentioning:
As we discussed here, when plotting multiple lines, we might want to have a single legend entry as a “representative” for those lines.
One way to help the reader distinguish between the individual lines is to color them separately (e.g. from red to dark red, say with range(colorant"red", stop=colorant"black", length=5)[1:end-1]). This works very well when the number of lines is limited (impossible to differentiate more than say 10 lines with slightly darker shades), and especially well when the plot includes groups of multiple lines, where each group is colored differently (e.g. group 1 has 4 lines colored red to dark red, group 2 has 3 lines colored green to dark green, group 3 has 7 lines colored blue to dark blue, etc).
Your current method with assigning a label to the first line is problematic in that context: the legend marker will receive the color of the first (or whatever) line. It might have been better to have the color black for that legend marker. Especially if more groups (i.e. color ranges) are included.
I’m not necessarily suggesting what is best here, but only highlighting some aspects/possible issues. Maybe the best way here is to be able to deal with series/groups of lines or things in a more rigorous way. This might fall into the realms of the promising https://github.com/JuliaPlots/AlgebraOfGraphics.jl…
Yeah I never got into the weeds of what to do with continuous attributes of any kind when creating legends. Basically, all the convenience constructors only work for scalar attributes, as I can’t guess what is the best choice for a legend for all possible combinations of array attributes. So if you have a color range, you are a bit on your own And that’s exactly what a meta-package like AlgebraOfGraphics is for
Totally agree. I’m just gonna go ahead and ping @piever so he reads this if he likes. Pietro, I don’t mean for you to “solve” this, just to bring this to your attention. Thanks!
I also think that this type of subtle distinctions (categorical versus ordinal versus continuous, different kinds of groupings) should be handled outside of Makie. Legend and colorbar support is planned in the short term in “AlgebraOfGraphicsNext”: there’s already a WIP PR for legends Support Legends by greimel · Pull Request #32 · piever/SplitApplyPlot.jl (github.com)
(That being said, even with an opinionated approach like AlgebraOfGraphics it may well be that some usecases are very hard to cover.)