From aca2aa49b851e0ee582d081fb3d256f59254d980 Mon Sep 17 00:00:00 2001 From: Nicholas Bauer Date: Tue, 5 Oct 2021 07:44:12 -0400 Subject: [PATCH] Generalize GR tick label rotations (#3782) * gr tick rotations * consistency * start of angle offset work * Working! * simplify logic * gr tick rotations * consistency * start of angle offset work * Working! * simplify logic * all offsets * Rebase * tick factor reversion * Fix for 3d plots * simplification * offset update * bump offset * Remove conflict error * Resolve merge conflict error. * Resolve merge conflict error * 3d done * remove excess * adjust offset * restore offset * fix sign check Co-authored-by: t-bltg Co-authored-by: Simon Christ --- src/backends/gr.jl | 116 ++++++++++++++++++++++++++++++++------------- 1 file changed, 83 insertions(+), 33 deletions(-) diff --git a/src/backends/gr.jl b/src/backends/gr.jl index fe5afbf8..9421f26e 100644 --- a/src/backends/gr.jl +++ b/src/backends/gr.jl @@ -671,31 +671,19 @@ end function gr_set_tickfont(sp, letter) axis = sp[get_attr_symbol(letter, :axis)] - - # invalidate alignment changes for small rotations (|θ| < 45°) - trigger(rot) = abs(sind(rot)) < abs(cosd(rot)) ? 0 : sign(rot) - - rot = axis[:rotation] - if letter === :x || (RecipesPipeline.is3d(sp) && letter === :y) - halign = (:left, :hcenter, :right)[trigger(rot) + 2] - valign = (axis[:mirror] ? :bottom : :top, :vcenter)[trigger(abs(rot)) + 1] - else - halign = (axis[:mirror] ? :left : :right, :hcenter)[trigger(abs(rot)) + 1] - valign = (:top, :vcenter, :bottom)[trigger(rot) + 2] - end gr_set_font( tickfont(axis), sp, - halign = halign, - valign = valign, rotation = axis[:rotation], color = axis[:tickfontcolor], ) end +# size of the text with no rotation function gr_text_size(str) GR.savestate() GR.selntran(0) + GR.setcharup(0, 1) xs, ys = gr_inqtext(0, 0, string(str)) l, r = extrema(xs) b, t = extrema(ys) @@ -705,9 +693,11 @@ function gr_text_size(str) return w, h end +# size of the text with rotation applied function gr_text_size(str, rot) GR.savestate() GR.selntran(0) + GR.setcharup(0, 1) xs, ys = gr_inqtext(0, 0, string(str)) l, r = extrema(xs) b, t = extrema(ys) @@ -720,6 +710,15 @@ end text_box_width(w, h, rot) = abs(cosd(rot)) * w + abs(cosd(rot + 90)) * h text_box_height(w, h, rot) = abs(sind(rot)) * w + abs(sind(rot + 90)) * h +function gr_get_3d_axis_angle(cvs, nt, ft, letter) + length(cvs) < 2 && return 0 + tickpoints = [gr_w3tondc(sort_3d_axes(cv, nt, ft, letter)...) for cv ∈ cvs] + + dx = tickpoints[2][1] - tickpoints[1][1] + dy = tickpoints[2][2] - tickpoints[1][2] + return atand(dy, dx) +end + function gr_get_ticks_size(ticks, rot) w, h = 0.0, 0.0 for (cv, dv) in zip(ticks...) @@ -1560,13 +1559,35 @@ 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 ? -1.5e-2 * out_factor : 0 - y_offset = isy ? 0 : -8e-3 * out_factor - + x_base_offset = isy ? -1.5e-2 * out_factor : 0 + y_base_offset = isy ? 0 : -8e-3 * out_factor + + rot = axis[:rotation] % 360 ov = sp[:framestyle] == :origin ? 0 : xor(oaxis[:flip], axis[:mirror]) ? oamax : oamin sgn = axis[:mirror] ? -1 : 1 + sgn2 = iseven(Int(floor(rot / 90))) ? -1 : 1 + sgn3 = if isy + -360 < rot < -180 || 0 < rot < 180 ? 1 : -1 + else + rot < -270 || -90 < rot < 90 || rot > 270 ? 1 : -1 + end for (cv, dv) in zip(ticks...) x, y = GR.wctondc(reverse_if((cv, ov), isy)...) + sz_rot = gr_text_size(dv, rot) + sz = gr_text_size(dv) + x_offset = x_base_offset + y_offset = y_base_offset + if isy + x_offset += -first(sz_rot) / 2 + if rot % 90 != 0 + y_offset += sgn2 * last(sz_rot) / 2 + sgn3 * last(sz) * cosd(rot) / 2 + end + else + if rot % 90 != 0 + x_offset += sgn2 * first(sz_rot) / 2 + sgn3 * last(sz) * sind(rot) / 2 + end + y_offset += -last(sz_rot) / 2 + end gr_text(x + sgn * x_offset, y + sgn * y_offset, dv) end end @@ -1577,6 +1598,9 @@ function gr_label_ticks_3d(sp, letter, ticks) near_letter = letter in (:x, :z) ? :y : :x far_letter = letter in (:x, :y) ? :z : :x + isy = letter === :y + isz = letter === :z + ax = sp[get_attr_symbol(letter, :axis)] nax = sp[get_attr_symbol(near_letter, :axis)] fax = sp[get_attr_symbol(far_letter, :axis)] @@ -1584,7 +1608,7 @@ function gr_label_ticks_3d(sp, letter, ticks) amin, amax = axis_limits(sp, letter) namin, namax = axis_limits(sp, near_letter) famin, famax = axis_limits(sp, far_letter) - n0, n1 = letter === :y ? (namax, namin) : (namin, namax) + n0, n1 = isy ? (namax, namin) : (namin, namax) # find out which axes we are dealing with i = findfirst(==(letter), (:x, :y, :z)) @@ -1601,27 +1625,52 @@ function gr_label_ticks_3d(sp, letter, ticks) 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) - elseif letter === :y - (sp[:yaxis][:mirror] ? -1 : 1) * 1e-2 * (sp[:yaxis][:tick_direction] == :out ? 1.5 : 1) - else - (sp[:zaxis][:mirror] ? 1 : -1) * 1e-2 * (sp[:zaxis][:tick_direction] == :out ? 1.5 : 1) - end - yoffset = if letter === :x - (sp[:xaxis][:mirror] ? 1 : -1) * 1e-2 * (sp[:xaxis][:tick_direction] == :out ? 1.5 : 1) - elseif letter === :y - (sp[:yaxis][:mirror] ? 1 : -1) * 1e-2 * (sp[:yaxis][:tick_direction] == :out ? 1.5 : 1) - else - 0 - end + rot = mod(ax[:rotation], 360) + sgn = ax[:mirror] ? -1 : 1 cvs, dvs = ticks ax[:flip] && reverse!(cvs) + axisθ = isz ? 270 : mod(gr_get_3d_axis_angle(cvs, nt, ft, letter), 360) # issue: doesn't work with 1 tick + axisϕ = mod(axisθ - 90, 360) + + out_factor = ifelse(ax[:tick_direction] === :out, 1.5, 1) + axisoffset = out_factor * 1.2e-2 + x_base_offset = axisoffset * cosd(axisϕ) + y_base_offset = axisoffset * sind(axisϕ) + + sgn2a = sgn2b = sgn3 = 0 + if axisθ != 0 || rot % 90 != 0 + sgn2a = (axisθ != 90) && + (axisθ == 0 && (rot < 90 || 180 ≤ rot < 270)) || + (axisθ == 270) || + (axisθ < 90 && (axisθ < rot < 90 || axisθ + 180 < rot < 270)) || + (axisθ > 270 && (rot < 90 || axisθ - 180 < rot < 270 || rot > axisθ)) ? -1 : 1 + end + + if (axisθ - 90) % 180 != 0 || (rot - 90) % 180 != 0 + sgn2b = axisθ == 0 || + (axisθ == 90 && (90 ≤ rot < 180 || 270 ≤ rot < 360)) || + (axisθ == 270 && (rot < 90 || 180 ≤ rot < 270)) || + (axisθ < 90 && (axisθ < rot < 180 || axisθ + 180 < rot)) || + (axisθ > 270 && (rot < axisθ - 180 || 180 ≤ rot < axisθ)) ? -1 : 1 + end + + if !(axisθ == 0 && rot % 180 == 0) && ((rot - 90) % 180 != 0) + sgn3 = (axisθ == 0 && 90 < rot < 270) || + (axisθ == 90 && rot < 180) || + (axisθ == 270 && rot > 180) || + (axisθ < 90 && (rot < axisθ || 90 ≤ rot < 180 || axisθ + 180 < rot < 270)) || + (axisθ > 270 && (90 ≤ rot < axisθ - 180 || 180 ≤ rot < 270 || rot > axisθ)) ? -1 : 1 + end + 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) + sz_rot = gr_text_size(dv, rot) + sz = gr_text_size(dv) + x_offset = x_base_offset + sgn2a * first(sz_rot) / 2 + sgn3 * last(sz) * sind(rot) / 2 + y_offset = y_base_offset + sgn2b * last(sz_rot) / 2 + sgn3 * last(sz) * cosd(rot) / 2 + gr_text(xi + sgn * x_offset, yi + sgn * y_offset, dv) end end @@ -2084,3 +2133,4 @@ function _display(plt::Plot{GRBackend}) end closeall(::GRBackend) = GR.emergencyclosegks() +