diff --git a/CairoMakie/test/runtests.jl b/CairoMakie/test/runtests.jl index f602b28980f..cd46e5ba170 100644 --- a/CairoMakie/test/runtests.jl +++ b/CairoMakie/test/runtests.jl @@ -76,6 +76,7 @@ include(joinpath(@__DIR__, "rasterization_tests.jl")) load_save(s; kw...) = (save("test.png", s; kw...); load("test.png")) @test size(load_save(scene, px_per_unit=2)) == (1600, 1600) @test size(load_save(scene, px_per_unit=1)) == (800, 800) + rm("test.png") end end diff --git a/NEWS.md b/NEWS.md index 4261807a442..7471568c57d 100644 --- a/NEWS.md +++ b/NEWS.md @@ -1,5 +1,9 @@ # News +## master + +- Added `xlabelrotation`, `ylabelrotation` (`Axis`) and `labelrotation` (`Colorbar`) [#2478](https://github.com/MakieOrg/Makie.jl/pull/2478). + ## v0.19.0 - **Breaking** The attribute `textsize` has been removed everywhere in favor of the attribute `fontsize` which had also been in use. @@ -160,7 +164,7 @@ role as `datalimits` in `violin` [#2137](https://github.com/MakieOrg/Makie.jl/pu - Fixed manual cycling of plot attributes [#1873](https://github.com/MakieOrg/Makie.jl/pull/1873). - Fixed type constraints in ticklabelalign attributes [#1882](https://github.com/MakieOrg/Makie.jl/pull/1882). -## v0.16.4 +## v0.16.4 - Fixed WGLMakie performance bug and added option to set fps via `WGLMakie.activate!(fps=30)`. - Implemented `nan_color`, `lowclip`, `highclip` for `image(::Matrix{Float})` in shader. diff --git a/ReferenceTests/src/tests/examples2d.jl b/ReferenceTests/src/tests/examples2d.jl index d179dbd4bd1..04c134fbe1f 100644 --- a/ReferenceTests/src/tests/examples2d.jl +++ b/ReferenceTests/src/tests/examples2d.jl @@ -271,6 +271,63 @@ end st end +@reference_test "Axes label rotations" begin + axis = ( + xlabel = "a long x label for this axis", + ylabel = "a long y\nlabel for this axis", + xlabelrotation = π / 4, + ylabelrotation = 0, + ) + fig, ax, _ = scatter(0:1; axis) + + st = Stepper(fig) + Makie.step!(st) + + ax.yaxisposition[] = :right + ax.ylabelrotation[] = Makie.automatic + ax.xlabelrotation[] = -π / 5 + Makie.step!(st) + + ax.xaxisposition[] = :top + ax.xlabelrotation[] = 3π / 4 + ax.ylabelrotation[] = π / 4 + Makie.step!(st) + + # reset to defaults + ax.xaxisposition[] = :bottom + ax.yaxisposition[] = :left + ax.xlabelrotation[] = ax.ylabelrotation[] = Makie.automatic + Makie.step!(st) + + st +end + +@reference_test "Colorbar label rotations" begin + axis = ( + xlabel = "x axis label", + ylabel = "y axis label", + xlabelrotation = -π / 10, + ylabelrotation = -π / 3, + yaxisposition = :right, + ) + fig, _, _ = scatter(0:1; axis) + + cb_vert = Colorbar(fig[1, 2]; label = "vertical cbar", labelrotation = 0) + cb_horz = Colorbar(fig[2, 1]; label = "horizontal cbar", labelrotation = π / 5, vertical = false) + + st = Stepper(fig) + Makie.step!(st) + + # reset to defaults + cb_vert.labelrotation[] = Makie.automatic + Makie.step!(st) + + cb_horz.labelrotation[] = Makie.automatic + Makie.step!(st) + + st +end + @reference_test "Errorbars x y low high" begin x = 1:10 y = sin.(x) diff --git a/src/makielayout/blocks/axis.jl b/src/makielayout/blocks/axis.jl index 926e6434263..7c8f881f39d 100644 --- a/src/makielayout/blocks/axis.jl +++ b/src/makielayout/blocks/axis.jl @@ -316,7 +316,7 @@ function initialize_block!(ax::Axis; palette = nothing) flipped = xaxis_flipped, ticklabelrotation = ax.xticklabelrotation, ticklabelalign = ax.xticklabelalign, labelsize = ax.xlabelsize, labelpadding = ax.xlabelpadding, ticklabelpad = ax.xticklabelpad, labelvisible = ax.xlabelvisible, - label = ax.xlabel, labelfont = ax.xlabelfont, ticklabelfont = ax.xticklabelfont, ticklabelcolor = ax.xticklabelcolor, labelcolor = ax.xlabelcolor, tickalign = ax.xtickalign, + label = ax.xlabel, labelfont = ax.xlabelfont, labelrotation = ax.xlabelrotation, ticklabelfont = ax.xticklabelfont, ticklabelcolor = ax.xticklabelcolor, labelcolor = ax.xlabelcolor, tickalign = ax.xtickalign, ticklabelspace = ax.xticklabelspace, ticks = ax.xticks, tickformat = ax.xtickformat, ticklabelsvisible = ax.xticklabelsvisible, ticksvisible = ax.xticksvisible, spinevisible = xspinevisible, spinecolor = xspinecolor, spinewidth = ax.spinewidth, ticklabelsize = ax.xticklabelsize, trimspine = ax.xtrimspine, ticksize = ax.xticksize, @@ -329,7 +329,7 @@ function initialize_block!(ax::Axis; palette = nothing) flipped = yaxis_flipped, ticklabelrotation = ax.yticklabelrotation, ticklabelalign = ax.yticklabelalign, labelsize = ax.ylabelsize, labelpadding = ax.ylabelpadding, ticklabelpad = ax.yticklabelpad, labelvisible = ax.ylabelvisible, - label = ax.ylabel, labelfont = ax.ylabelfont, ticklabelfont = ax.yticklabelfont, ticklabelcolor = ax.yticklabelcolor, labelcolor = ax.ylabelcolor, tickalign = ax.ytickalign, + label = ax.ylabel, labelfont = ax.ylabelfont, labelrotation = ax.ylabelrotation, ticklabelfont = ax.yticklabelfont, ticklabelcolor = ax.yticklabelcolor, labelcolor = ax.ylabelcolor, tickalign = ax.ytickalign, ticklabelspace = ax.yticklabelspace, ticks = ax.yticks, tickformat = ax.ytickformat, ticklabelsvisible = ax.yticklabelsvisible, ticksvisible = ax.yticksvisible, spinevisible = yspinevisible, spinecolor = yspinecolor, spinewidth = ax.spinewidth, trimspine = ax.ytrimspine, ticklabelsize = ax.yticklabelsize, ticksize = ax.yticksize, flip_vertical_label = ax.flip_ylabel, reversed = ax.yreversed, tickwidth = ax.ytickwidth, diff --git a/src/makielayout/blocks/colorbar.jl b/src/makielayout/blocks/colorbar.jl index 88ab2ba34aa..e29b95e9928 100644 --- a/src/makielayout/blocks/colorbar.jl +++ b/src/makielayout/blocks/colorbar.jl @@ -315,7 +315,7 @@ function initialize_block!(cb::Colorbar) axis = LineAxis(blockscene, endpoints = axispoints, flipped = cb.flipaxis, limits = limits, ticklabelalign = cb.ticklabelalign, label = cb.label, labelpadding = cb.labelpadding, labelvisible = cb.labelvisible, labelsize = cb.labelsize, - labelcolor = cb.labelcolor, + labelcolor = cb.labelcolor, labelrotation = cb.labelrotation, labelfont = cb.labelfont, ticklabelfont = cb.ticklabelfont, ticks = cb.ticks, tickformat = cb.tickformat, ticklabelsize = cb.ticklabelsize, ticklabelsvisible = cb.ticklabelsvisible, ticksize = cb.ticksize, ticksvisible = cb.ticksvisible, ticklabelpad = cb.ticklabelpad, tickalign = cb.tickalign, diff --git a/src/makielayout/lineaxis.jl b/src/makielayout/lineaxis.jl index 54cf9601f67..aed8d714e90 100644 --- a/src/makielayout/lineaxis.jl +++ b/src/makielayout/lineaxis.jl @@ -23,24 +23,23 @@ end function calculate_protrusion( closure_args, ticksvisible::Bool, label, labelvisible::Bool, labelpadding::Number, tickspace::Number, ticklabelsvisible::Bool, - actual_ticklabelspace::Number, ticklabelpad::Number, _, _, _, _) + actual_ticklabelspace::Number, ticklabelpad::Number, _...) horizontal, labeltext, ticklabel_annotation_obs = closure_args + label_is_empty::Bool = iswhitespace(label) || isempty(label) - local label_is_empty::Bool = iswhitespace(label) || isempty(label) - - local real_labelsize::Float32 = if label_is_empty + real_labelsize::Float32 = if label_is_empty 0f0 else - horizontal[] ? boundingbox(labeltext).widths[2] : boundingbox(labeltext).widths[1] + boundingbox(labeltext).widths[horizontal[] ? 2 : 1] end - local labelspace::Float32 = (labelvisible && !label_is_empty) ? real_labelsize + labelpadding : 0f0 + labelspace::Float32 = (labelvisible && !label_is_empty) ? real_labelsize + labelpadding : 0f0 - local _tickspace::Float32 = (ticksvisible && !isempty(ticklabel_annotation_obs[])) ? tickspace : 0f0 + _tickspace::Float32 = (ticksvisible && !isempty(ticklabel_annotation_obs[])) ? tickspace : 0f0 - local ticklabelgap::Float32 = (ticklabelsvisible && actual_ticklabelspace > 0) ? actual_ticklabelspace + ticklabelpad : 0f0 + ticklabelgap::Float32 = (ticklabelsvisible && actual_ticklabelspace > 0) ? actual_ticklabelspace + ticklabelpad : 0f0 return _tickspace + ticklabelgap + labelspace end @@ -50,7 +49,7 @@ function create_linepoints( pos_ext_hor, flipped::Bool, spine_width::Number, trimspine::Union{Bool, Tuple{Bool, Bool}}, tickpositions::Vector{Point2f}, tickwidth::Number) - (position::Float32, extents::Tuple{Float32, Float32}, horizontal::Bool) = pos_ext_hor + (position::Float32, extents::NTuple{2, Float32}, horizontal::Bool) = pos_ext_hor if trimspine isa Bool trimspine = (trimspine, trimspine) @@ -123,10 +122,10 @@ function calculate_real_ticklabel_align(al, horizontal, fl::Bool, rot::Number) (fl ? :left : :right, :center) end end - elseif al isa Tuple{Symbol, Symbol} + elseif al isa NTuple{2, Symbol} return al else - error("Align needs to be a Tuple{Symbol, Symbol}.") + error("Align needs to be a NTuple{2, Symbol}.") end end @@ -142,7 +141,7 @@ function update_ticklabel_node( nticks = length(tickvalues[]) - local ticklabelgap::Float32 = spinewidth[] + tickspace[] + ticklabelpad[] + ticklabelgap::Float32 = spinewidth[] + tickspace[] + ticklabelpad[] shift = if horizontal[] Point2f(0f0, flipped ? ticklabelgap : -ticklabelgap) @@ -165,16 +164,17 @@ end function update_tick_obs(tick_obs, horizontal::Observable{Bool}, flipped::Observable{Bool}, tickpositions, tickalign, ticksize, spinewidth) result = tick_obs[] empty!(result) # re-use allocated array + sign::Int = flipped[] ? -1 : 1 if horizontal[] for tp in tickpositions - tstart = tp + (flipped[] ? -1f0 : 1f0) * Point2f(0f0, tickalign * ticksize - 0.5f0 * spinewidth) - tend = tstart + (flipped[] ? -1f0 : 1f0) * Point2f(0f0, -ticksize) + tstart = tp + sign * Point2f(0f0, tickalign * ticksize - 0.5f0 * spinewidth) + tend = tstart + sign * Point2f(0f0, -ticksize) push!(result, tstart, tend) end else for tp in tickpositions - tstart = tp + (flipped[] ? -1f0 : 1f0) * Point2f(tickalign * ticksize - 0.5f0 * spinewidth, 0f0) - tend = tstart + (flipped[] ? -1f0 : 1f0) * Point2f(-ticksize, 0f0) + tstart = tp + sign * Point2f(tickalign * ticksize - 0.5f0 * spinewidth, 0f0) + tend = tstart + sign * Point2f(-ticksize, 0f0) push!(result, tstart, tend) end end @@ -185,7 +185,7 @@ end function update_tickpos_string(closure_args, tickvalues_labels_unfiltered, reversed::Bool, scale) tickstrings, tickpositions, tickvalues, pos_extents_horizontal, limits_obs = closure_args - limits = limits_obs[]::Tuple{Float32, Float32} + limits = limits_obs[]::NTuple{2, Float32} tickvalues_unfiltered, tickstrings_unfiltered = tickvalues_labels_unfiltered @@ -226,8 +226,7 @@ function update_tickpos_string(closure_args, tickvalues_labels_unfiltered, rever return end -function update_minor_ticks(minortickpositions, limits::Tuple{Float32, Float32}, pos_extents_horizontal, minortickvalues, scale, reversed::Bool) - +function update_minor_ticks(minortickpositions, limits::NTuple{2, Float32}, pos_extents_horizontal, minortickvalues, scale, reversed::Bool) position::Float32, extents_uncorrected::NTuple{2, Float32}, horizontal::Bool = pos_extents_horizontal extents = reversed ? reverse(extents_uncorrected) : extents_uncorrected @@ -249,8 +248,8 @@ function update_minor_ticks(minortickpositions, limits::Tuple{Float32, Float32}, else [Point2f(position, y) for y in tick_scenecoords] end - return + return end function LineAxis(parent::Scene, attrs::Attributes) @@ -261,13 +260,13 @@ function LineAxis(parent::Scene, attrs::Attributes) ticklabelspace, ticklabelpad, labelpadding, ticklabelsize, ticklabelsvisible, spinewidth, spinecolor, label, labelsize, labelcolor, labelfont, ticklabelfont, ticklabelcolor, - labelvisible, spinevisible, trimspine, flip_vertical_label, reversed, + labelrotation, labelvisible, spinevisible, trimspine, flip_vertical_label, reversed, minorticksvisible, minortickalign, minorticksize, minortickwidth, minortickcolor, minorticks) pos_extents_horizontal = lift(calculate_horizontal_extends, endpoints; ignore_equal_values=true) horizontal = lift(x-> x[3], pos_extents_horizontal) - # Tuple constructor converts more than `convert(Tuple{Float32, Float32}, x)` but we still need the conversion to Float32 tuple: - limits = lift(x-> convert(Tuple{Float32, Float32}, Tuple(x)), attrs.limits; ignore_equal_values=true) + # Tuple constructor converts more than `convert(NTuple{2, Float32}, x)` but we still need the conversion to Float32 tuple: + limits = lift(x-> convert(NTuple{2, Float32}, Tuple(x)), attrs.limits; ignore_equal_values=true) flipped = lift(x-> convert(Bool, x), attrs.flipped; ignore_equal_values=true) ticksnode = Observable(Point2f[]; ignore_equal_values=true) @@ -324,9 +323,7 @@ function LineAxis(parent::Scene, attrs::Attributes) end tickspace = Observable(0f0; ignore_equal_values=true) - map!(tickspace, ticksvisible, ticksize, tickalign) do ticksvisible, - ticksize, tickalign - + map!(tickspace, ticksvisible, ticksize, tickalign) do ticksvisible, ticksize, tickalign ticksvisible ? max(0f0, ticksize * (1f0 - tickalign)) : 0f0 end @@ -348,48 +345,66 @@ function LineAxis(parent::Scene, attrs::Attributes) x_or_y = flipped ? position + labelgap : position - labelgap - if horizontal - return Point2f(middle, x_or_y) - else - return Point2f(x_or_y, middle) - end + return horizontal ? Point2f(middle, x_or_y) : Point2f(x_or_y, middle) end + # Initial values should be overwritten by map!. `ignore_equal_values` doesn't work right now without initial values labelalign = Observable((:none, :none); ignore_equal_values=true) - - map!(labelalign, horizontal, flipped, flip_vertical_label) do horizontal::Bool, flipped::Bool, flip_vertical_label::Bool - if horizontal - return (:center, flipped ? :bottom : :top) - else - return (:center, if flipped + map!(labelalign, labelrotation, horizontal, flipped, flip_vertical_label) do labelrotation, + horizontal::Bool, flipped::Bool, flip_vertical_label::Bool + return if labelrotation isa Automatic + if horizontal + (:center, flipped ? :bottom : :top) + else + (:center, if flipped flip_vertical_label ? :bottom : :top else flip_vertical_label ? :top : :bottom - end - ) - end + end) + end + else + (:center, :center) + end::NTuple{2, Symbol} end - labelrotation = Observable(0f0; ignore_equal_values=true) - map!(labelrotation, horizontal, flip_vertical_label) do horizontal::Bool, flip_vertical_label::Bool - if horizontal - return 0f0 - else - if flip_vertical_label - return Float32(-0.5pi) + labelrot = Observable(0f0; ignore_equal_values=true) + map!(labelrot, labelrotation, horizontal, flip_vertical_label) do labelrotation, + horizontal::Bool, flip_vertical_label::Bool + return if labelrotation isa Automatic + if horizontal + 0f0 else - return Float32(0.5pi) + (flip_vertical_label ? -0.5f0 : 0.5f0) * π end - end + else + Float32(labelrotation) + end::Float32 end labeltext = text!( parent, labelpos, text = label, fontsize = labelsize, color = labelcolor, visible = labelvisible, - align = labelalign, rotation = labelrotation, font = labelfont, + align = labelalign, rotation = labelrot, font = labelfont, markerspace = :data, inspectable = false ) + # translate axis labels on explicit rotations + # in order to prevent plot and axis overlap + onany(labelrotation, flipped, horizontal) do labelrotation, flipped, horizontal + xs::Float32, ys::Float32 = if labelrotation isa Automatic + 0f0, 0f0 + else + wx, wy = widths(boundingbox(labeltext)) + sign::Int = flipped ? 1 : -1 + if horizontal + 0f0, Float32(sign * 0.5f0 * wy) + else + Float32(sign * 0.5f0 * wx), 0f0 + end + end + translate!(labeltext, xs, ys, 0f0) + end + decorations[:labeltext] = labeltext tickvalues = Observable(Float32[]; ignore_equal_values=true) @@ -443,11 +458,11 @@ function LineAxis(parent::Scene, attrs::Attributes) protrusion = Observable(0f0; ignore_equal_values=true) map!(calculate_protrusion, protrusion, - # We pass these as observables, to not trigger on them + # we pass these as observables, to not trigger on them Observable((horizontal, labeltext, ticklabel_annotation_obs)), ticksvisible, label, labelvisible, labelpadding, tickspace, ticklabelsvisible, actual_ticklabelspace, ticklabelpad, - # We don't need these as arguments to calculate it, but we need to pass it because it indirectly influences the protrosion - labelfont, ticklabelfont, labelsize, tickalign) + # we don't need these as arguments to calculate it, but we need to pass it because it indirectly influences the protrusion + labelfont, labelalign, labelrot, labelsize, ticklabelfont, tickalign) # trigger whole pipeline once to fill tickpositions and tickstrings # etc to avoid empty ticks bug #69 @@ -504,10 +519,7 @@ function tight_ticklabel_spacing!(la::LineAxis) end -function iswhitespace(str) - match(r"^\s*$", str) !== nothing -end - +iswhitespace(str) = match(r"^\s*$", str) !== nothing function Base.delete!(la::LineAxis) for (_, d) in la.elements @@ -543,8 +555,6 @@ get_tickvalues(::Automatic, F, vmin, vmax) = get_tickvalues(automatic, identity, # fall back to non-scale aware behavior if no special version is overloaded get_tickvalues(ticks, scale, vmin, vmax) = get_tickvalues(ticks, vmin, vmax) - - function get_ticks(ticks_and_labels::Tuple{Any, Any}, any_scale, ::Automatic, vmin, vmax) n1 = length(ticks_and_labels[1]) n2 = length(ticks_and_labels[2]) diff --git a/src/makielayout/types.jl b/src/makielayout/types.jl index 84a933bb234..bf52b111bb7 100644 --- a/src/makielayout/types.jl +++ b/src/makielayout/types.jl @@ -254,6 +254,10 @@ end xlabelpadding::Float64 = 3f0 "The padding between the ylabel and the ticks or axis." ylabelpadding::Float64 = 5f0 # xlabels usually have some more visual padding because of ascenders, which are larger than the hadvance gaps of ylabels + "The xlabel rotation in radians." + xlabelrotation = Makie.automatic + "The ylabel rotation in radians." + ylabelrotation = Makie.automatic "The font family of the xticklabels." xticklabelfont = :regular "The font family of the yticklabels." @@ -499,6 +503,8 @@ end labelvisible = true "The gap between the label and the ticks." labelpadding = 5f0 + "The label rotation in radians." + labelrotation = Makie.automatic "The font family of the tick labels." ticklabelfont = :regular "The font size of the tick labels." @@ -527,7 +533,7 @@ end tickcolor = RGBf(0, 0, 0) "The horizontal and vertical alignment of the tick labels." ticklabelalign = Makie.automatic - "The rotation of the ticklabels" + "The rotation of the ticklabels." ticklabelrotation = 0f0 "The line width of the spines." spinewidth = 1f0 @@ -1216,11 +1222,11 @@ end ylabelfont = :regular "The z label font" zlabelfont = :regular - "The x label rotation" + "The x label rotation in radians" xlabelrotation = Makie.automatic - "The y label rotation" + "The y label rotation in radians" ylabelrotation = Makie.automatic - "The z label rotation" + "The z label rotation in radians" zlabelrotation = Makie.automatic "The x label align" xlabelalign = Makie.automatic