From bba971f7eae407e36c6fb67f859f349cb49d1406 Mon Sep 17 00:00:00 2001 From: t-bltg <13423344+t-bltg@users.noreply.github.com> Date: Wed, 30 Jun 2021 22:50:09 +0200 Subject: [PATCH] GR: fix axis flip / mirror in 3D plots (#3584) * fix axis flip in 3D plots * add mwe as example - fix needs_3d_axes * fix major / minor grids when mirroring Co-authored-by: t-bltg --- src/axes.jl | 10 +++++---- src/backends/gr.jl | 51 ++++++++++++++++++++++++++++++---------------- src/examples.jl | 50 +++++++++++++++++++++++++++++++++++++++++++++ src/pipeline.jl | 8 ++++++++ 4 files changed, 98 insertions(+), 21 deletions(-) diff --git a/src/axes.jl b/src/axes.jl index c7837d8c..91be5ba0 100644 --- a/src/axes.jl +++ b/src/axes.jl @@ -853,15 +853,17 @@ function axis_drawing_info_3d(sp, letter) ) end if grid + fa0_, fa1_ = reverse_if((fa0, fa1), ax[:mirror]) + ga0_, ga1_ = reverse_if((ga0, ga1), ax[:mirror]) push!( segments, - sort_3d_axes(tick, ga0, fa0, letter), - sort_3d_axes(tick, ga1, fa0, letter), + sort_3d_axes(tick, ga0_, fa0_, letter), + sort_3d_axes(tick, ga1_, fa0_, letter), ) push!( segments, - sort_3d_axes(tick, ga1, fa0, letter), - sort_3d_axes(tick, ga1, fa1, letter), + sort_3d_axes(tick, ga1_, fa0_, letter), + sort_3d_axes(tick, ga1_, fa1_, letter), ) end end diff --git a/src/backends/gr.jl b/src/backends/gr.jl index 1386da4f..ca31f3d3 100644 --- a/src/backends/gr.jl +++ b/src/backends/gr.jl @@ -1287,12 +1287,22 @@ function gr_set_window(sp, viewport_plotarea) gr_set_viewport_polar(viewport_plotarea) else xmin, xmax, ymin, ymax = gr_xy_axislims(sp) + needs_3d = needs_any_3d_axes(sp) + if needs_3d + zmin, zmax = gr_z_axislims(sp) + zok = zmax > zmin + else + zok = true + end + scaleop = 0 - if xmax > xmin && ymax > ymin - sp[:xaxis][:scale] == :log10 && (scaleop |= GR.OPTION_X_LOG) - sp[:yaxis][:scale] == :log10 && (scaleop |= GR.OPTION_Y_LOG) - sp[:xaxis][:flip] && (scaleop |= GR.OPTION_FLIP_X) - sp[:yaxis][:flip] && (scaleop |= GR.OPTION_FLIP_Y) + if xmax > xmin && ymax > ymin && zok + sp[:xaxis][:scale] == :log10 && (scaleop |= GR.OPTION_X_LOG) + sp[:yaxis][:scale] == :log10 && (scaleop |= GR.OPTION_Y_LOG) + needs_3d && sp[:zaxis][:scale] == :log10 && (scaleop |= GR.OPTION_Z_LOG) + sp[:xaxis][:flip] && (scaleop |= GR.OPTION_FLIP_X) + sp[:yaxis][:flip] && (scaleop |= GR.OPTION_FLIP_Y) + needs_3d && sp[:zaxis][:flip] && (scaleop |= GR.OPTION_FLIP_Z) # NOTE: setwindow sets the "data coordinate" limits of the current "viewport" GR.setwindow(xmin, xmax, ymin, ymax) GR.setscale(scaleop) @@ -1315,7 +1325,7 @@ function gr_draw_axes(sp, viewport_plotarea) if RecipesPipeline.is3d(sp) # set space xmin, xmax, ymin, ymax = gr_xy_axislims(sp) - zmin, zmax = axis_limits(sp, :z) + zmin, zmax = gr_z_axislims(sp) GR.setspace(zmin, zmax, round.(Int, sp[:camera])...) # fill the plot area @@ -1327,7 +1337,7 @@ function gr_draw_axes(sp, viewport_plotarea) GR.fillarea(x_bg, y_bg) for letter in (:x, :y, :z) - gr_draw_axis_3d(sp, letter) + gr_draw_axis_3d(sp, letter, viewport_plotarea) end elseif ispolar(sp) r = gr_set_viewport_polar(viewport_plotarea) @@ -1357,7 +1367,7 @@ function gr_draw_axis(sp, letter, viewport_plotarea) gr_label_axis(sp, letter, viewport_plotarea) end -function gr_draw_axis_3d(sp, letter) +function gr_draw_axis_3d(sp, letter, viewport_plotarea) ax = axis_drawing_info_3d(sp, letter) axis = sp[Symbol(letter, :axis)] @@ -1369,8 +1379,10 @@ function gr_draw_axis_3d(sp, letter) gr_draw_ticks(sp, axis, ax.tick_segments, gr_polyline3d) # labels + GR.setscale(0) gr_label_ticks_3d(sp, letter, ax.ticks) gr_label_axis_3d(sp, letter) + gr_set_window(sp, viewport_plotarea) end function gr_draw_grid(sp, axis, segments, func = gr_polyline) @@ -1445,13 +1457,14 @@ function gr_label_ticks(sp, letter, ticks) oamin, oamax = axis_limits(sp, oletter) gr_set_tickfont(sp, letter) out_factor = ifelse(axis[:tick_direction] === :out, 1.5, 1) - x_offset = isy ? (axis[:mirror] ? 1 : -1) * 1.5e-2 * out_factor : 0 - y_offset = isy ? 0 : (axis[:mirror] ? 1 : -1) * 8e-3 * out_factor + x_offset = isy ? -1.5e-2 * out_factor : 0 + y_offset = isy ? 0 : -8e-3 * out_factor ov = sp[:framestyle] == :origin ? 0 : xor(oaxis[:flip], axis[:mirror]) ? oamax : oamin + sgn = axis[:mirror] ? -1 : 1 for (cv, dv) in zip(ticks...) x, y = GR.wctondc(reverse_if((cv, ov), isy)...) - gr_text(x + x_offset, y + y_offset, dv) + gr_text(x + sgn * x_offset, y + sgn * y_offset, dv) end end @@ -1483,8 +1496,8 @@ function gr_label_ticks_3d(sp, letter, ticks) xax, yax, zax = getindex.(Ref(sp), asyms) gr_set_tickfont(sp, letter) - nt = sp[:framestyle] == :origin ? 0 : xor(ax[:mirror], nax[:flip]) ? n1 : n0 - ft = sp[:framestyle] == :origin ? 0 : xor(ax[:mirror], fax[:flip]) ? famax : famin + nt = sp[:framestyle] == :origin ? 0 : ax[:mirror] ? n1 : n0 + ft = sp[:framestyle] == :origin ? 0 : ax[:mirror] ? famax : famin xoffset = if letter === :x (sp[:yaxis][:mirror] ? 1 : -1) * 1e-2 * (sp[:xaxis][:tick_direction] == :out ? 1.5 : 1) @@ -1501,7 +1514,10 @@ function gr_label_ticks_3d(sp, letter, ticks) 0 end - for (cv, dv) in zip(ticks...) + cvs, dvs = ticks + ax[:flip] && reverse!(cvs) + + for (cv, dv) in zip((cvs, dvs)...) xi, yi = gr_w3tondc(sort_3d_axes(cv, nt, ft, letter)...) gr_text(xi + xoffset, yi + yoffset, dv) end @@ -1570,8 +1586,8 @@ function gr_label_axis_3d(sp, letter) # color = ax[:guidefontcolor], ) ag = (amin + amax) / 2 - ng = xor(ax[:mirror], nax[:flip]) ? n1 : n0 - fg = xor(ax[:mirror], fax[:flip]) ? famax : famin + ng = ax[:mirror] ? n1 : n0 + fg = ax[:mirror] ? famax : famin x, y = gr_w3tondc(sort_3d_axes(ag, ng, fg, letter)...) if letter in (:x, :y) h = gr_axis_height(sp, ax) @@ -1582,7 +1598,8 @@ function gr_label_axis_3d(sp, letter) y_offset = 0 end letter === :z && GR.setcharup(-1, 0) - gr_text(x + x_offset, y + y_offset, ax[:guide]) + sgn = ax[:mirror] ? -1 : 1 + gr_text(x + sgn * x_offset, y + sgn * y_offset, ax[:guide]) GR.restorestate() end end diff --git a/src/examples.jl b/src/examples.jl index 6d4e631d..ea1d233f 100644 --- a/src/examples.jl +++ b/src/examples.jl @@ -1163,6 +1163,56 @@ const _examples = PlotExample[ ), ], ), + PlotExample( # 55 + "3D axis flip / mirror", + "", + [ + :( + begin + meshgrid(x, y) = (ones(eltype(y), length(y)) * x', y * ones(eltype(x), length(x))') + scalefontsizes(.5) + + x, y = meshgrid(-6:0.5:10, -8:0.5:8) + r = sqrt.(x .^ 2 + y .^ 2) .+ eps() + z = sin.(r) ./ r + + args = x[1, :], y[:, 1], z[:] + kwargs = Dict( + :xlabel => "x", :ylabel => "y", :zlabel => "z", + :grid => true, :minorgrid => true, :dpi => 200 + ) + + plots = [wireframe(args..., title = "wire"; kwargs...)] + + for ax ∈ (:x, :y, :z) + push!(plots, wireframe( + args..., + title = "wire-flip-$ax", + xflip = ax == :x, + yflip = ax == :y, + zflip = ax == :z; + kwargs..., + )) + end + + for ax ∈ (:x, :y, :z) + push!(plots, wireframe( + args..., + title = "wire-mirror-$ax", + xmirror = ax == :x, + ymirror = ax == :y, + zmirror = ax == :z; + kwargs..., + )) + end + + plot(plots..., layout=(@layout [_ ° _; ° ° °; ° ° °]), margin=2Plots.mm) + + scalefontsizes() + end + ), + ], + ), ] # Some constants for PlotDocs and PlotReferenceImages diff --git a/src/pipeline.jl b/src/pipeline.jl index d87c5836..94fce068 100644 --- a/src/pipeline.jl +++ b/src/pipeline.jl @@ -338,6 +338,14 @@ function _override_seriestype_check(plotattributes::AKW, st::Symbol) st end +function needs_any_3d_axes(sp::Subplot) + any( + RecipesPipeline.needs_3d_axes( + _override_seriestype_check(s.plotattributes, s.plotattributes[:seriestype]) + ) for s in series_list(sp) + ) +end + function _expand_subplot_extrema(sp::Subplot, plotattributes::AKW, st::Symbol) # adjust extrema and discrete info if st == :image