diff --git a/.github/workflows/refimages_status.yaml b/.github/workflows/refimages_status.yaml index e11010cacee..e3a1ee59005 100644 --- a/.github/workflows/refimages_status.yaml +++ b/.github/workflows/refimages_status.yaml @@ -56,10 +56,10 @@ jobs: sha: commit_sha, state: n_missing === 0 ? 'success' : 'failure', target_url: null, - description: n_missing + " missing refimages must be uploaded", + description: `${n_missing == 0 ? 'No' : n_missing} missing reference image${n_missing == 1 ? '' : 's'} must be uploaded`, context: 'Reference Tests', headers: { 'X-GitHub-Api-Version': '2022-11-28' } - }) + }) \ No newline at end of file diff --git a/CairoMakie/src/primitives.jl b/CairoMakie/src/primitives.jl index 212b6ba1f62..f636b129b8c 100644 --- a/CairoMakie/src/primitives.jl +++ b/CairoMakie/src/primitives.jl @@ -3,8 +3,7 @@ ################################################################################ function draw_atomic(scene::Scene, screen::Screen, @nospecialize(primitive::Union{Lines, LineSegments})) - fields = @get_attribute(primitive, (color, linewidth, linestyle)) - linestyle = Makie.convert_attribute(linestyle, Makie.key"linestyle"()) + @get_attribute(primitive, (color, linewidth, linestyle)) ctx = screen.context model = primitive[:model][] positions = primitive[1][] @@ -245,7 +244,7 @@ function draw_multi(primitive::Lines, ctx, positions, colors::AbstractArray, lin this_linewidth != prev_linewidth && error("Encountered two different linewidth values $prev_linewidth and $this_linewidth in `lines` at index $(i-1). Different linewidths in one line are only permitted in CairoMakie when separated by a NaN point.") Cairo.line_to(ctx, this_position...) prev_continued = true - + if i == lastindex(positions) # this is the last element so stroke this Cairo.set_line_width(ctx, this_linewidth) @@ -293,8 +292,7 @@ end ################################################################################ function draw_atomic(scene::Scene, screen::Screen, @nospecialize(primitive::Scatter)) - fields = @get_attribute(primitive, (markersize, strokecolor, strokewidth, marker, marker_offset, rotations)) - @get_attribute(primitive, (transform_marker,)) + @get_attribute(primitive, (markersize, strokecolor, strokewidth, marker, marker_offset, rotations, transform_marker)) ctx = screen.context model = primitive.model[] @@ -306,22 +304,15 @@ function draw_atomic(scene::Scene, screen::Screen, @nospecialize(primitive::Scat colors = to_color(primitive.calculated_colors[]) - markerspace = to_value(get(primitive, :markerspace, :pixel)) - space = to_value(get(primitive, :space, :data)) - + markerspace = primitive.markerspace[] + space = primitive.space[] transfunc = Makie.transform_func(primitive) - marker_conv = _marker_convert(marker) - - draw_atomic_scatter(scene, ctx, transfunc, colors, markersize, strokecolor, strokewidth, marker_conv, marker_offset, rotations, model, positions, size_model, font, markerspace, space) + return draw_atomic_scatter(scene, ctx, transfunc, colors, markersize, strokecolor, strokewidth, marker, + marker_offset, rotations, model, positions, size_model, font, markerspace, + space) end -# an array of markers is converted to string by itself, which is inconvenient for the iteration logic -_marker_convert(markers::AbstractArray) = map(m -> convert_attribute(m, key"marker"(), key"scatter"()), markers) -_marker_convert(marker) = convert_attribute(marker, key"marker"(), key"scatter"()) -# image arrays need to be converted as a whole -_marker_convert(marker::AbstractMatrix{<:Colorant}) = [ convert_attribute(marker, key"marker"(), key"scatter"()) ] - function draw_atomic_scatter(scene, ctx, transfunc, colors, markersize, strokecolor, strokewidth, marker, marker_offset, rotations, model, positions, size_model, font, markerspace, space) broadcast_foreach(positions, colors, markersize, strokecolor, strokewidth, marker, marker_offset, remove_billboard(rotations)) do point, col, @@ -336,15 +327,14 @@ function draw_atomic_scatter(scene, ctx, transfunc, colors, markersize, strokeco Cairo.set_source_rgba(ctx, rgbatuple(col)...) Cairo.save(ctx) - marker_converted = Makie.to_spritemarker(m) # Setting a markersize of 0.0 somehow seems to break Cairos global state? # At least it stops drawing any marker afterwards # TODO, maybe there's something wrong somewhere else? if !(norm(scale) ≈ 0.0) - if marker_converted isa Char - draw_marker(ctx, marker_converted, best_font(m, font), pos, scale, strokecolor, strokewidth, offset, rotation) + if m isa Char + draw_marker(ctx, m, best_font(m, font), pos, scale, strokecolor, strokewidth, offset, rotation) else - draw_marker(ctx, marker_converted, pos, scale, strokecolor, strokewidth, offset, rotation) + draw_marker(ctx, m, pos, scale, strokecolor, strokewidth, offset, rotation) end end Cairo.restore(ctx) @@ -691,7 +681,7 @@ function draw_atomic(scene::Scene, screen::Screen, @nospecialize(primitive::Unio t = Makie.transform_func(primitive) identity_transform = (t === identity || t isa Tuple && all(x-> x === identity, t)) && (abs(model[1, 2]) < 1e-15) regular_grid = xs isa AbstractRange && ys isa AbstractRange - xy_aligned = let + xy_aligned = let # Only allow scaling and translation pv = scene.camera.projectionview[] M = Mat4f( @@ -720,7 +710,7 @@ function draw_atomic(scene::Scene, screen::Screen, @nospecialize(primitive::Unio xymax = project_position(scene, space, Point2f(last.(imsize)), model) w, h = xymax .- xy - can_use_fast_path = !(is_vector && !interpolate) && regular_grid && identity_transform && + can_use_fast_path = !(is_vector && !interpolate) && regular_grid && identity_transform && (interpolate || xy_aligned) use_fast_path = can_use_fast_path && !disable_fast_path @@ -862,23 +852,19 @@ nan2zero(x) = !isnan(x) * x function draw_mesh3D(scene, screen, attributes, mesh; pos = Vec4f(0), scale = 1f0) - # Priorize colors of the mesh if present - @get_attribute(attributes, (color,)) + @get_attribute(attributes, (shading, diffuse, specular, shininess, faceculling)) matcap = to_value(get(attributes, :matcap, nothing)) - meshpoints = decompose(Point3f, mesh)::Vector{Point3f} meshfaces = decompose(GLTriangleFace, mesh)::Vector{GLTriangleFace} meshnormals = decompose_normals(mesh)::Vector{Vec3f} meshuvs = texturecoordinates(mesh)::Union{Nothing, Vector{Vec2f}} + # Priorize colors of the mesh if present color = hasproperty(mesh, :color) ? mesh.color : to_value(attributes.calculated_colors) per_face_col = per_face_colors(color, matcap, meshfaces, meshnormals, meshuvs) - @get_attribute(attributes, (shading, diffuse, - specular, shininess, faceculling)) - model = attributes.model[]::Mat4f space = to_value(get(attributes, :space, :data))::Symbol func = Makie.transform_func(attributes) @@ -1052,8 +1038,6 @@ end function draw_atomic(scene::Scene, screen::Screen, @nospecialize(primitive::Makie.MeshScatter)) @get_attribute(primitive, (model, marker, markersize, rotations)) - - m = convert_attribute(marker, key"marker"(), key"meshscatter"()) pos = primitive[1][] # For correct z-ordering we need to be in view/camera or screen space model = copy(model) @@ -1093,7 +1077,7 @@ function draw_atomic(scene::Scene, screen::Screen, @nospecialize(primitive::Maki scale = markersize isa Vector ? markersize[i] : markersize draw_mesh3D( - scene, screen, submesh, m, pos = p, + scene, screen, submesh, marker, pos = p, scale = scale isa Real ? Vec3f(scale) : to_ndim(Vec3f, scale, 1f0) ) end diff --git a/MakieCore/src/attributes.jl b/MakieCore/src/attributes.jl index 30f8d2cf5ad..b552e0d7ecd 100644 --- a/MakieCore/src/attributes.jl +++ b/MakieCore/src/attributes.jl @@ -235,7 +235,12 @@ function get_attribute(dict, key, default=nothing) if haskey(dict, key) value = to_value(dict[key]) value isa Automatic && return default - return convert_attribute(to_value(dict[key]), Key{key}()) + plot_k = plotkey(dict) + if isnothing(plot_k) + return convert_attribute(value, Key{key}()) + else + return convert_attribute(value, Key{key}(), Key{plot_k}()) + end else return default end diff --git a/MakieCore/src/recipes.jl b/MakieCore/src/recipes.jl index 741ab37a169..73fc2e0eb0b 100644 --- a/MakieCore/src/recipes.jl +++ b/MakieCore/src/recipes.jl @@ -21,6 +21,7 @@ func2type(f::Function) = Combined{f} plotkey(::Type{<: AbstractPlot{Typ}}) where Typ = Symbol(lowercase(func2string(Typ))) plotkey(::T) where T <: AbstractPlot = plotkey(T) plotkey(::Nothing) = :scatter +plotkey(any) = nothing """ default_plot_signatures(funcname, funcname!, PlotType) diff --git a/ReferenceTests/src/tests/primitives.jl b/ReferenceTests/src/tests/primitives.jl index 150aab8a35d..dbc411ac2d0 100644 --- a/ReferenceTests/src/tests/primitives.jl +++ b/ReferenceTests/src/tests/primitives.jl @@ -5,7 +5,7 @@ points = Point2f[(1, 1), (1, 2), (2, 3), (2, 1)] linestyles = [ :solid, :dash, :dot, :dashdot, :dashdotdot, - [1, 2, 3], [1, 2, 4, 5] + Linestyle([1, 2, 3]), Linestyle([1, 2, 4, 5]) ] for linewidth in 1:10 for (i, linestyle) in enumerate(linestyles) @@ -270,13 +270,13 @@ end # Same as above markers = [ - :rect, :circle, :cross, :x, :utriangle, :rtriangle, :dtriangle, :ltriangle, :pentagon, + :rect, :circle, :cross, :x, :utriangle, :rtriangle, :dtriangle, :ltriangle, :pentagon, :hexagon, :octagon, :star4, :star5, :star6, :star8, :vline, :hline, 'x', 'X' ] for (i, marker) in enumerate(markers) scatter!( - Point2f.(1:5, i), marker = marker, + Point2f.(1:5, i), marker = marker, markersize = range(10, 30, length = 5), color = :orange, strokewidth = 2, strokecolor = :black ) @@ -451,9 +451,9 @@ end lab1 = L"\int f(x) dx" lab2 = lab1 # lab2 = L"\frac{a}{b} - \sqrt{b}" # this will not work until #2667 is fixed - + barplot(fig[1,1], [1, 2], [0.5, 0.2], bar_labels = [lab1, lab2], flip_labels_at = 0.3, direction=:x) barplot(fig[1,2], [1, 2], [0.5, 0.2], bar_labels = [lab1, lab2], flip_labels_at = 0.3) fig -end \ No newline at end of file +end diff --git a/docs/explanations/nodes.md b/docs/explanations/nodes.md index b59567409bb..2d801eccd39 100644 --- a/docs/explanations/nodes.md +++ b/docs/explanations/nodes.md @@ -72,6 +72,9 @@ nothing # hide ``` \show{code3} +!!! note + If you updated the `Observable` using in-place syntax (e.g. `img[] .= colorant"red"`), you need to manually + `notify(img)` to trigger the function. !!! note All registered functions in a `Observable` are executed synchronously in the order of registration. diff --git a/src/basic_recipes/barplot.jl b/src/basic_recipes/barplot.jl index 2b03956b06c..99430e1a4ce 100644 --- a/src/basic_recipes/barplot.jl +++ b/src/basic_recipes/barplot.jl @@ -162,22 +162,10 @@ function stack_grouped_from_to(i_stack, y, grp) end function calculate_bar_label_align(label_align, label_rotation::Real, in_y_direction::Bool, flip::Bool) - make_align(a::VecTypes{2, <:Real}) = Vec2f(a) - make_align(a::NTuple{2, Symbol}) = to_align(a) - make_align(a) = error("`label_align` needs to be of type NTuple{2, <:Real}, not of type $(typeof(a))") if label_align == automatic - if flip - label_rotation += π - end - if !in_y_direction - label_rotation += π/2 - end - s, c = sincos(label_rotation) - scale = 1 / max(abs(s), abs(c)) - align = Vec2f(0.5 - 0.5scale * s, 0.5 - 0.5scale * c) - return align + return angle2align(-label_rotation - !flip * pi + in_y_direction * pi/2) else - return make_align(label_align) + return to_align(label_align, "Failed to convert `label_align` $label_align.") end end diff --git a/src/conversions.jl b/src/conversions.jl index 74969aa462e..6bfc0484fbc 100644 --- a/src/conversions.jl +++ b/src/conversions.jl @@ -999,10 +999,81 @@ convert_attribute(c::Tuple{<: Number, <: Number, <: Number}, ::key"position") = convert_attribute(c::VecTypes{N}, ::key"position") where N = Point{N, Float32}(c) """ - Text align, e.g.: + to_align(align[, error_prefix]) + +Converts the given align to a `Vec2f`. Can convert `VecTypes{2}` and two +component `Tuple`s with `Real` and `Symbol` elements. + +To specify a custom error message you can add an `error_prefix` or use +`halign2num(value, error_msg)` and `valign2num(value, error_msg)` respectively. +""" +to_align(x::Tuple) = Vec2f(halign2num(x[1]), valign2num(x[2])) +to_align(x::VecTypes{2, <:Real}) = Vec2f(x) + +function to_align(v, error_prefix::String) + try + return to_align(v) + catch + error(error_prefix) + end +end + +""" + halign2num(align[, error_msg]) + +Attempts to convert a horizontal align to a Float32 and errors with `error_msg` +if it fails to do so. +""" +halign2num(v::Real, error_msg = "") = Float32(v) +function halign2num(v::Symbol, error_msg = "Invalid halign $v. Valid values are <:Real, :left, :center and :right.") + if v === :left + return 0.0f0 + elseif v === :center + return 0.5f0 + elseif v === :right + return 1.0f0 + else + error(error_msg) + end +end +function halign2num(v, error_msg = "Invalid halign $v. Valid values are <:Real, :left, :center and :right.") + error(error_msg) +end + +""" + valign2num(align[, error_msg]) + +Attempts to convert a vertical align to a Float32 and errors with `error_msg` +if it fails to do so. +""" +valign2num(v::Real, error_msg = "") = Float32(v) +function valign2num(v::Symbol, error_msg = "Invalid valign $v. Valid values are <:Real, :bottom, :top, and :center.") + if v === :top + return 1f0 + elseif v === :bottom + return 0f0 + elseif v === :center + return 0.5f0 + else + error(error_msg) + end +end +function valign2num(v, error_msg = "Invalid valign $v. Valid values are <:Real, :bottom, :top, and :center.") + error(error_msg) +end + """ -to_align(x::Tuple{Symbol, Symbol}) = Vec2f(alignment2num.(x)) -to_align(x::Vec2f) = x + angle2align(angle::Real) + +Converts a given angle to an alignment by projecting the resulting direction on +a unit square and scaling the result to a 0..1 range appropriate for alignments. +""" +function angle2align(angle::Real) + s, c = sincos(angle) + scale = 1 / max(abs(s), abs(c)) + return Vec2f(0.5scale * c + 0.5, 0.5scale * s + 0.5) +end + const FONT_CACHE = Dict{String, NativeFont}() const FONT_CACHE_LOCK = Base.ReentrantLock() diff --git a/src/layouting/layouting.jl b/src/layouting/layouting.jl index 014a68cb88a..4765bbc7cad 100644 --- a/src/layouting/layouting.jl +++ b/src/layouting/layouting.jl @@ -179,14 +179,8 @@ function glyph_collection( else 0.5f0 end - elseif justification === :left - 0.0f0 - elseif justification === :right - 1.0f0 - elseif justification === :center - 0.5f0 else - Float32(justification) + halign2num(justification, "Invalid justification $justification. Valid values are <:Real, :left, :center and :right.") end xs_justified = map(xs, width_differences) do xsgroup, wd @@ -203,17 +197,7 @@ function glyph_collection( ys = cumsum([0.0; -lineheights[2:end]]) # compute x values after left/center/right alignment - halign = if halign isa Number - Float32(halign) - elseif halign === :left - 0.0f0 - elseif halign === :center - 0.5f0 - elseif halign === :right - 1.0f0 - else - error("Invalid halign $halign. Valid values are <:Number, :left, :center and :right.") - end + halign = halign2num(halign) xs_aligned = [xsgroup .- halign * maxwidth for xsgroup in xs_justified] # for y alignment, we need the largest ascender of the first line @@ -233,17 +217,7 @@ function glyph_collection( ys_aligned = if valign === :baseline ys .- first_line_ascender .+ overall_height .+ last_line_descender else - va = if valign isa Number - Float32(valign) - elseif valign === :top - 1f0 - elseif valign === :bottom - 0f0 - elseif valign === :center - 0.5f0 - else - error("Invalid valign $valign. Valid values are <:Number, :bottom, :baseline, :top, and :center.") - end + va = valign2num(valign, "Invalid valign $valign. Valid values are <:Number, :bottom, :baseline, :top, and :center.") ys .- first_line_ascender .+ (1 - va) .* overall_height end @@ -287,14 +261,6 @@ function padded_vcat(arrs::AbstractVector{T}, fillvalue) where T <: AbstractVect arr end -function alignment2num(x::Symbol) - (x === :center) && return 0.5f0 - (x in (:left, :bottom)) && return 0.0f0 - (x in (:right, :top)) && return 1.0f0 - return 0.0f0 # 0 default, or better to error? -end - - # Backend data _offset_to_vec(o::VecTypes) = to_ndim(Vec3f, o, 0) diff --git a/src/makielayout/blocks/axis.jl b/src/makielayout/blocks/axis.jl index e70b7e61ba5..44ecafcf582 100644 --- a/src/makielayout/blocks/axis.jl +++ b/src/makielayout/blocks/axis.jl @@ -439,15 +439,8 @@ function initialize_block!(ax::Axis; palette = nothing) ignore_equal_values=true) do a, titlegap, align, xaxisposition, xaxisprotrusion - x = if align === :center - a.origin[1] + a.widths[1] / 2 - elseif align === :left - a.origin[1] - elseif align === :right - a.origin[1] + a.widths[1] - else - error("Title align $align not supported.") - end + align_factor = halign2num(align, "Horizontal title align $align not supported.") + x = a.origin[1] + align_factor * a.widths[1] yoffset = top(a) + titlegap + (xaxisposition === :top ? xaxisprotrusion : 0f0) diff --git a/src/makielayout/blocks/axis3d.jl b/src/makielayout/blocks/axis3d.jl index 860a59aa6ea..b96f840efc8 100644 --- a/src/makielayout/blocks/axis3d.jl +++ b/src/makielayout/blocks/axis3d.jl @@ -82,15 +82,8 @@ function initialize_block!(ax::Axis3) titlepos = lift(scene, scene.px_area, ax.titlegap, ax.titlealign) do a, titlegap, align - x = if align === :center - a.origin[1] + a.widths[1] / 2 - elseif align === :left - a.origin[1] - elseif align === :right - a.origin[1] + a.widths[1] - else - error("Title align $align not supported.") - end + align_factor = halign2num(align, "Horizontal title align $align not supported.") + x = a.origin[1] + align_factor * a.widths[1] yoffset = top(a) + titlegap @@ -1122,7 +1115,7 @@ function attribute_examples(::Type{Axis3}) name = "`zreversed` on and off", code = """ using FileIO - + fig = Figure() brain = load(assetpath("brain.stl")) diff --git a/src/makielayout/blocks/polaraxis.jl b/src/makielayout/blocks/polaraxis.jl index a5b5f5ee487..f8f84623d69 100644 --- a/src/makielayout/blocks/polaraxis.jl +++ b/src/makielayout/blocks/polaraxis.jl @@ -153,9 +153,7 @@ function draw_axis!(po::PolarAxis, axis_radius) end thetatick_align[] = map(_thetatickvalues) do angle - s, c = sincos(dir * (angle + theta_0)) - scale = 1 / max(abs(s), abs(c)) # point on ellipse -> point on bbox - Point2f(0.5 - 0.5scale * c, 0.5 - 0.5scale * s) + return angle2align(dir * (angle + theta_0) + pi) end # transform px_pad to radial pad @@ -257,9 +255,7 @@ function draw_axis!(po::PolarAxis, axis_radius) strokecolor = rstrokecolor, visible = po.rticklabelsvisible, align = map(po.direction, po.theta_0, po.rtickangle) do dir, theta_0, angle - s, c = sincos(dir * (angle + theta_0)) - scale = 1 / max(abs(s), abs(c)) # point on ellipse -> point on bbox - Point2f(0.5 - 0.5scale * c, 0.5 - 0.5scale * s) + return angle2align(dir * (angle + theta_0) + pi) end ) @@ -341,15 +337,8 @@ end function calculate_polar_title_position(area, titlegap, align) w, h = area.widths - x::Float32 = if align === :center - area.origin[1] + w / 2 - elseif align === :left - area.origin[1] - elseif align === :right - area.origin[1] + w - else - error("Title align $align not supported.") - end + align_factor = halign2num(align, "Horizontal title align $align not supported.") + x::Float32 = area.origin[1] + align_factor * w # local subtitlespace::Float32 = if ax.subtitlevisible[] && !iswhitespace(ax.subtitle[]) # boundingbox(subtitlet).widths[2] + subtitlegap diff --git a/src/utilities/utilities.jl b/src/utilities/utilities.jl index 564b1a53418..5f8e01a5628 100644 --- a/src/utilities/utilities.jl +++ b/src/utilities/utilities.jl @@ -169,20 +169,20 @@ end attr_broadcast_length(x::NativeFont) = 1 attr_broadcast_length(x::VecTypes) = 1 # these are our rules, and for what we do, Vecs are usually scalars -attr_broadcast_length(x::AbstractArray) = length(x) +attr_broadcast_length(x::AbstractVector) = length(x) attr_broadcast_length(x::AbstractPattern) = 1 attr_broadcast_length(x) = 1 attr_broadcast_length(x::ScalarOrVector) = x.sv isa Vector ? length(x.sv) : 1 attr_broadcast_getindex(x::NativeFont, i) = x attr_broadcast_getindex(x::VecTypes, i) = x # these are our rules, and for what we do, Vecs are usually scalars -attr_broadcast_getindex(x::AbstractArray, i) = x[i] +attr_broadcast_getindex(x::AbstractVector, i) = x[i] attr_broadcast_getindex(x::AbstractPattern, i) = x attr_broadcast_getindex(x, i) = x attr_broadcast_getindex(x::Ref, i) = x[] # unwrap Refs just like in normal broadcasting, for protecting iterables attr_broadcast_getindex(x::ScalarOrVector, i) = x.sv isa Vector ? x.sv[i] : x.sv -is_vector_attribute(x::AbstractArray) = true +is_vector_attribute(x::AbstractVector) = true is_vector_attribute(x::NativeFont) = false is_vector_attribute(x::Quaternion) = false is_vector_attribute(x::VecTypes) = false diff --git a/test/barplot_labels.jl b/test/barplot_labels.jl index 8b3c662f13b..f99bcf1347e 100644 --- a/test/barplot_labels.jl +++ b/test/barplot_labels.jl @@ -44,9 +44,8 @@ @testset "error" begin input = 0.0, false, false - for align in ("center", 0.5, ("center", "center"), (0.5, :center)) - msg = "`label_align` needs to be of type NTuple{2, <:Real}, not of type $(typeof(align))" - @test_throws ErrorException(msg) Makie.calculate_bar_label_align(align, input...) + for align in ("center", 0.5, ("center", "center")) + @test_throws ErrorException Makie.calculate_bar_label_align(align, input...) end end diff --git a/test/conversions.jl b/test/conversions.jl index 2bc5d6bac2c..c735ed4735b 100644 --- a/test/conversions.jl +++ b/test/conversions.jl @@ -355,4 +355,31 @@ end @test isapprox(cs, zs, rtol = 1e-6) end +end + +@testset "align conversions" begin + for (val, halign) in zip((0f0, 0.5f0, 1f0), (:left, :center, :right)) + @test Makie.halign2num(halign) == val + end + @test_throws ErrorException Makie.halign2num(:bottom) + @test_throws ErrorException Makie.halign2num("center") + @test Makie.halign2num(0.73) == 0.73f0 + + for (val, valign) in zip((0f0, 0.5f0, 1f0), (:bottom, :center, :top)) + @test Makie.valign2num(valign) == val + end + @test_throws ErrorException Makie.valign2num(:right) + @test_throws ErrorException Makie.valign2num("center") + @test Makie.valign2num(0.23) == 0.23f0 + + @test Makie.to_align((:center, :bottom)) == Vec2f(0.5, 0.0) + @test Makie.to_align((:right, 0.3)) == Vec2f(1.0, 0.3) + + for angle in 4pi .* rand(10) + s, c = sincos(angle) + @test Makie.angle2align(angle) ≈ Vec2f(0.5c, 0.5s) ./ max(abs(s), abs(c)) .+ Vec2f(0.5) + end + # sanity checks + @test isapprox(Makie.angle2align(pi/4), Vec2f(1, 1), atol = 1e-12) + @test isapprox(Makie.angle2align(5pi/4), Vec2f(0, 0), atol = 1e-12) end \ No newline at end of file