-
-
Notifications
You must be signed in to change notification settings - Fork 313
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Feature request: contour labels #832
Comments
For this we would need this PR JuliaPlots/AbstractPlotting.jl#430 to be merged, which is still stuck at the GLMakie implementation |
Came up on discourse again: https://discourse.julialang.org/t/how-to-add-value-labels-on-top-of-contours-in-makie/59242 Now that #430 is merged this should be possible somehow? |
Yeah it does work nicely now:
But this approach only works nicely with glmakie right now... |
There is a missing piece in this solution: Adding a colorbar shows that the labels do not correspond to the values in the contour: fig = Figure(backgroundcolor =RGBf0(0.98, 0.98, 0.98))
ax = fig[1,1] = Axis(fig, title = "Heatmap and contours")
np = 500
nc = 200
f(x) = 2*x+1
x = LinRange(-1,1,f(np))
y = rand(f(np))*2 .- 1
x_c = LinRange(-nc/np,nc/np,f(nc))
y_c = LinRange(-nc/np,nc/np,f(nc))
c_mat = @. x_c^2 + y_c'^2
hp = heatmap!(x_c, y_c, c_mat)
cplot = contour!(ax,x_c,y_c,c_mat, linewidth=2, color=:white)
beginnings = Point2f0[]; colors = RGBAf0[]
# First plot in contour is the line plot, first arguments are the points of the contour
segments = cplot.plots[1][1][]
for (i, p) in enumerate(segments)
# the segments are separated by NaN, which signals that a new contour starts
if isnan(p)
push!(beginnings, segments[i-1])
end
end
sc = scatter!(ax, beginnings, markersize=50, color=(:white, 0.1), strokecolor=:white)
translate!(sc, 0, 0, 1)
# Reshuffle the plot order, so that the scatter plot gets drawn before the line plot
delete!(ax, sc)
delete!(ax, cplot)
push!(ax.scene, sc)
push!(ax.scene, cplot)
anno = text!(ax, [(string(i), p) for (i, p) in enumerate(beginnings)],
align=(:center, :center))
# move, so that text is in front
translate!(anno, 0, 0, 2)
cbar = fig[1,2] = Colorbar(fig,hp,label = "Values")
cbar.width = 15
cbar.height = Relative(4/5)
display(fig) |
I think they are just numbers from 1 to n for testing |
Ah yes, thanks. Just wanted to point out that this is not the solution to the feature request yet but is a stepping stone. For some other person that arrives to this issue while looking for labeled contours. |
Is there an easy way to extract the values each segment corresponds to in the snippet above so that we can create the labels accordingly? |
Here is to a gentle bump! It would be great to see a MWE that looks good with CairoMakie 😄 (for printing to PDF quality!) |
FWIW, reshuffling the order more (including the annotations) fixed that part for me. Here is a slightly smaller MWE: using GLMakie
x = range(-3, 3, length=200)
y = range(-2, 2, length=100)
z = @. x^2 + y'^2
fig, ax, hp = heatmap(x, y, z)
levels = 0:1:100
cplot = contour!(ax, x, y, z, color=:black, levels=levels)
beginnings = Point2f0[]; colors = RGBAf0[]
# First plot in contour is the line plot, first arguments are the points of the contour
segments = cplot.plots[1][1][]
for (i, p) in enumerate(segments)
# the segments are separated by NaN, which signals that a new contour starts
if isnan(p)
push!(beginnings, segments[i-1])
end
end
sc = scatter!(ax, beginnings, markersize=30, color=(:white, 0.001), strokecolor=:white)
anno = text!(ax, [(string(float(i)), Point3(p..., 2f0)) for (i, p) in enumerate(beginnings)], align=(:center, :center), color=:black)
translate!(sc, 0, 0, 1)
translate!(anno, 0, 0, 2)
# Reshuffle the plot order, so that the scatter plot gets drawn before the line plot
delete!(ax, sc)
delete!(ax, cplot)
delete!(ax, anno)
push!(ax.scene, anno)
push!(ax.scene, sc)
push!(ax.scene, cplot)
# move, so that text is in front
fig gives me (with GLMakie) Maybe a list of remaining issues is useful?
|
I worked a little bit in a workaround which I think is not elegant at all but It kind of works: using CairoMakie
function name_contours!(ax,cplot,value)
beginnings = Point2f0[]; colors = RGBAf0[]
# First plot in contour is the line plot, first arguments are the points of the contour
segments = cplot.plots[1][1][]
#@info segments[1]
for (i, p) in enumerate(segments)
# the segments are separated by NaN, which signals that a new contour starts
if isnan(p)
push!(beginnings, segments[i-1])
end
end
sc = scatter!(ax, beginnings, markersize=30, color=(:white, 0.1), strokecolor=:white)
translate!(sc, 0, 0, 1)
# Reshuffle the plot order, so that the scatter plot gets drawn before the line plot
delete!(ax, sc)
delete!(ax, cplot)
push!(ax.scene, sc)
push!(ax.scene, cplot)
anno = text!(ax, [(string(value), p) for (i, p) in enumerate(beginnings)],
align=(:center, :center), textsize=10)
# move, so that text is in front
translate!(anno, 0, 0, 2)
end
x = range(-3, 3, length=200)
y = range(-2, 2, length=100)
z = 10(@. x^2 + y'^2)
fig, ax, hp = heatmap(x, y, z)
levels = 0:10:100
for contour_value in levels
contour_plot = contour!(ax,x,y, z, color = :black, levels = [contour_value])
name_contours!(ax,contour_plot,contour_value)
end
fig This code produces: This the second point in @briochemc's list by calling contour on each specified level It is not much, but it is honest work. |
gentle bump to say this would be nice to have in Makie! |
This would be a great addition. Here is the workaround adapted to latest using CairoMakie
name_contours!(ax, cplot, value) = begin
beginnings = Point2f[]; colors = RGBAf[]
# first plot in contour is the line plot, first arguments are the points of the contour
segments = cplot.plots[1][1][]
# @info segments[1]
for (i, p) in enumerate(segments)
# the segments are separated by NaN, which signals that a new contour starts
isnan(p) && push!(beginnings, segments[i-1])
end
sc = scatter!(ax, beginnings, marker = :rect, markersize=20, color=(:white, .1), strokecolor=:white)
translate!(sc, 0, 0, 1)
# reshuffle the plot order, so that the scatter plot gets drawn before the line plot
delete!(ax, sc)
delete!(ax, cplot)
push!(ax.scene, sc)
push!(ax.scene, cplot)
ann = text!(ax, [(string(value), p) for (i, p) in enumerate(beginnings)], align=(:center, :center), fontsize=10)
# move, so that text is in front
translate!(ann, 0, 0, 2)
end
main() = begin
cases = (
paraboloid = (x, y) -> 10(x^2 + y^2),
# en.wikipedia.org/wiki/Himmelblau%27s_function
himmelblau = (x, y) -> (x^2 + y - 11)^2 + (x + y^2 - 7)^2,
# en.wikipedia.org/wiki/Rosenbrock_function
rosenbrock = (x, y) -> (1 - x^2) + 100(y - x^2)^2,
)
x = range(-3, 3; length=100)
y = range(-3, 3; length=200)
z = cases[1].(x, y')
fig, ax, hm = heatmap(x, y, z; interpolate = true)
Colorbar(fig[1, 2], hm)
for level ∈ 0:10:100
contour_plot = contour!(ax, x, y, z, color=:black, levels=[level])
name_contours!(ax, contour_plot, level)
end
save("contour_labels.png", fig)
fig
end
main() |
I think the tricky part is that after text locations have been chosen, the contour lines have to be hidden where they intersect the label bounding boxes. But this calculation needs to happen in screen space, so it needs to update whenever the projection changes. |
Is there a way to get the plot background color (here the heatmap color) so that we can add a label with a custom opaque background allowing to hide the contour line ? That would allow an exact bounding box based on the label fontsize... |
Often, contour lines are plotted on top of filled contours or heatmaps, so this would look bad in all those cases. I think for a good solution, you can't get around removing the actual lines.. (It would be enough to NaN all unwanted points I guess) |
If anyone wants to start playing around with this, my approach would be:
|
Thanks for the comments, this is exactly what I'm doing at the moment (but lower level, not using |
you can get the boundingboxes for each separate text by going into the |
don't know if there's currently an easier, official way |
Great, yeah I can make the labels work now, but masking is a bit trickier because of those bounding boxes... |
I don't think one can annotate contours with values in Makie (yet), for example (matplotlib):
I could not find an existing issue for this, so I thought I'd create one! 😃
FWIW, I only found this discourse post with a potential workaround, with this plot:
The text was updated successfully, but these errors were encountered: