-
-
Notifications
You must be signed in to change notification settings - Fork 320
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 that can handle curvilinear grids #796
Comments
Closed as duplicate of #742 |
It seems like the standard approach is to compute the contour lines of the It seems easy enough to do. But it would be pretty slow. see also:
If you plot a contour or contourf into a GeoAxis or PolarAxis, this is exactly what happens already. |
Yes I get this impression but how bad is it? Is there an example in Julia that uses Interpolations.jl or another package in the wild? Is there a better solution? I'm not sure how the contour polygons are built but it seems to me that they could be built directly from the curvilinear I also see some Delaunay / Voronoi triangulations being used... but this also goes over my head a bit. Maybe also relevant: |
Yeah tricontourf is probably your best immediate bet for this. It may even give you continuous contour lines. All of these approaches (with the exception of scattered interpolation based things like TopoPlots) seem to use this same bilinear interpolation idea. So it's very doable...just has to get done :D |
I think both tricontourf does the same thing as TopoPlots, no? I still do feel like a package like Contour.jl that handles irregular |
What about using Contour.jl instead of Isoband.jl (cc @jkrumbiegel)? Despite the reply to this issue, Contour.jl works with matrices of using Contour, Test
y = -10:10
x = -20:20
# The curvilinear grid:
X = [x + 0.01y^3 for x in x, y in y]
Y = [y + 10cos(x/40) for x in x, y in y]
# Just a plot to illustrate:
xrows = vcat(([x; NaN] for x in eachrow(X))...)
xcols = vcat(([x; NaN] for x in eachcol(X))...)
yrows = vcat(([x; NaN] for x in eachrow(Y))...)
ycols = vcat(([x; NaN] for x in eachcol(Y))...)
# The data on that grid:
Z = cosd.(X) .* sind.(Y)
levs = -1:0.05:1
contourlevels = Contour.contours(X, Y, Z, levs)
using GLMakie
fig = Figure(size = (400, 400))
# curvilinear surface
ax = Axis(fig[1, 1])
srf = surface!(ax, X, Y, Z; shading = NoShading)
grd = lines!(ax, [xrows; xcols], [yrows; ycols], color=(:black, 0.2))
translate!(grd, 0, 0, 100) # draw the grid above the surface
# contour lines
for cl in levels(contourlevels)
lvl = level(cl) # the z-value of this contour level
for line in Contour.lines(cl)
xs, ys = coordinates(line) # coordinates of this line segment
ls = lines!(ax, xs, ys, color=:red) # pseuod-code; use whatever plotting package you prefer
translate!(ls, 0, 0, 110 + lvl) # draw the contours above all
end
end
fig |
BTW AFAIU Contour.jl does exactly what I mentioned just above and computes the polygons directly without any need for bilinear interpolation, so it should be fast, right?! But maybe there other issues with it? 🤷 |
Contour.jl is also using the same approach :D see https://github.com/JuliaGeometry/Contour.jl/blob/6d155559af262cd7e218d9a7a87b34bfe16bf9c1/src/interpolate.jl#L42-L63 - it traces using marching squares across cells, and then simply pops the transformed coordinates in. It's trivial to enable matrix-like input for @eval Makie begin
# just overload the contourlines function for matrix like input
# nothing else needs to be done
function contourlines(x::AbstractMatrix{<: Real}, y::AbstractMatrix{<: Real}, z::AbstractMatrix{ET}, levels, level_colors, labels, T) where {ET}
contours = Contours.contours(x, y, z, convert(Vector{ET}, levels))
return contourlines(T, contours, level_colors, labels)
end
end Using the torus example, r1, r2 = 1.5, 1/2
r(u,v) = ((r1 + r2*cos(v))*cos(u), (r1 + r2*cos(v))*sin(u), r1/2*sin(u)+r2*sin(v)) # torus
us = vs = range(0, 2pi, length=25)
xs, ys, zs = [[pt[i] for pt in r.(us, vs')] for i in 1:3]
contour(xs, ys, zs) This looks a bit wonky but it technically works. On your warped curvilinear grid example it's a bit better: # issue from @briochemc - curvilinear grid
y = -10:10
x = -20:20
# The curvilinear grid:
X = [x + 0.01y^3 for x in x, y in y]
Y = [y + 10cos(x/40) for x in x, y in y]
# Just a plot to illustrate:
xrows = vcat(([x; NaN] for x in eachrow(X))...)
xcols = vcat(([x; NaN] for x in eachcol(X))...)
yrows = vcat(([x; NaN] for x in eachrow(Y))...)
ycols = vcat(([x; NaN] for x in eachcol(Y))...)
# The data on that grid:
# Z = cosd.(X) .* sind.(Y)
Z = hypot.(X, Y .- 10) # just to make it completely clear what this should look like
levs = -1:0.05:1
contourlevels = Makie.ContoursHygiene.Contour.contours(X, Y, Z, levs)
# plot
fig = Figure(size = (400, 400))
# curvilinear surface
ax = Axis(fig[1, 1])
srf = surface!(ax, X, Y, Z; shading = NoShading)
grd = lines!(ax, [xrows; xcols], [yrows; ycols], color=(:black, 0.2))
translate!(grd, 0, 0, 100) # draw the grid above the surface
contour!(ax, X, Y, Z; color = :red)
fig |
If I don't end up getting to it feel free to PR these changes! |
I'd love to help get this into Makie fast but I don't think I understand the inner workings of Makie enough to do that well, in particular with regards to "code cleanliness" and "abstracting the contour polygon calculation to an external function". The only thing I can note is that AFAIU Isoband.jl is just a wrapper so I don't think it is easily "patched", hence why I was suggesting using Contour.jl in the first place. EDIT: Oh I missed that you had started a PR already! Thank you so much! 🙏 BTW I don't think that
AFAIU the Contour.jl code does not do any bilinear interpolation to the transformed grid. Instead it is figuring out where the contours lie on the quad edges directly through a simple center of mass (regardless of the grid being regular or not). |
Thanks so much @asinghvi17 for the PR for contour! Is there something I can do to help with filled contours to completely close this? |
The only thing that has to be done for Makie.jl/src/basic_recipes/contourf.jl Lines 98 to 127 in 2e0aabe
lives outside of the main plot! function. It probably needs an Observable version that accepts all the observables, and then calls another contourpolys (like contourlines ) function that we would dispatch on. The implementation of the contour recipe is a good target for this.
I basically want the structure to enable what I did in #4670. |
A new function that lives outside the recipe might be: function calculate_polys!(polys::AbstractVector #={<: GeometryBasics.Polygon}=#, colors, xs, ys, zs, levels::Vector{Float32}, is_extended_low, is_extended_high)
empty!(polys)
empty!(colors)
levels = copy(levels)
@assert issorted(levels)
is_extended_low && pushfirst!(levels, -Inf)
is_extended_high && push!(levels, Inf)
lows = levels[1:end-1]
highs = levels[2:end]
# zs needs to be transposed to match rest of makie
isos = Isoband.isobands(xs, ys, zs', lows, highs)
levelcenters = (highs .+ lows) ./ 2
for (i, (center, group)) in enumerate(zip(levelcenters, isos))
points = Point2f.(group.x, group.y)
polygroups = _group_polys(points, group.id)
for polygroup in polygroups
outline = polygroup[1]
holes = polygroup[2:end]
push!(polys, GeometryBasics.Polygon(outline, holes))
# use contour level center value as color
push!(colors, center)
end
end
return (polys, colors)
end and then we just apply a dispatch-based approach to finding xs and ys, if that makes sense! |
I made the changes required in https://github.com/MakieOrg/Makie.jl/tree/as/curvilinear-contourf |
surface
currently accepts curvilinear grids. It would be nice ifcontour
andcontourf
does too for plotting of parameterised values.contour
s would also useVertexSurfaceLike
a la #748Similar to #675.
For example, the pyplot backend via Plots.jl does this:
The text was updated successfully, but these errors were encountered: