From fe17d4d0b3b9432685fb389d50d8a8d9eaa943af Mon Sep 17 00:00:00 2001 From: Simon Date: Wed, 30 Aug 2023 18:07:03 +0200 Subject: [PATCH] Linestyle type (#3193) * define custom Linestyle, add conversion * small cleanups * fix bug + export Linestyle * Update conversions.jl --------- Co-authored-by: Datseris --- src/Makie.jl | 1 + src/conversions.jl | 97 +++++++++++++++++++++++++++++----------------- 2 files changed, 63 insertions(+), 35 deletions(-) diff --git a/src/Makie.jl b/src/Makie.jl index 11c719e6f70..f72041008aa 100644 --- a/src/Makie.jl +++ b/src/Makie.jl @@ -86,6 +86,7 @@ import MakieCore: convert_arguments, convert_attribute, default_theme, conversio export @L_str, @colorant_str export ConversionTrait, NoConversion, PointBased, SurfaceLike, ContinuousSurface, DiscreteSurface, VolumeLike export Pixel, px, Unit, plotkey, attributes, used_attributes +export Linestyle const RealVector{T} = AbstractVector{T} where T <: Number const RGBAf = RGBA{Float32} diff --git a/src/conversions.jl b/src/conversions.jl index 27b2c18854a..74969aa462e 100644 --- a/src/conversions.jl +++ b/src/conversions.jl @@ -868,38 +868,65 @@ convert_attribute(c::Number, ::key"strokewidth") = Float32(c) convert_attribute(c, ::key"glowcolor") = to_color(c) convert_attribute(c, ::key"strokecolor") = to_color(c) -convert_attribute(x::Nothing, ::key"linestyle") = x +#### +## Line style conversions +#### -# `AbstractVector{<:AbstractFloat}` for denoting sequences of fill/nofill. e.g. -# -# [0.5, 0.8, 1.2] will result in 0.5 filled, 0.3 unfilled, 0.4 filled. 1.0 unit is one linewidth! -convert_attribute(A::AbstractVector, ::key"linestyle") = [float(x - A[1]) for x in A] +convert_attribute(style, ::key"linestyle") = to_linestyle(style) +to_linestyle(::Nothing) = nothing +# add deprecation for old conversion +function convert_attribute(style::AbstractVector, ::key"linestyle") + @warn "Using a `Vector{<:Real}` as a linestyle attribute is deprecated. Wrap it in a `Linestyle`." + return to_linestyle(Linestyle(style)) +end + +""" + Linestyle(value::Vector{<:Real}) + +A type that can be used as value for the `linestyle` keyword argument +of plotting functions to arbitrarily customize the linestyle. + +The `value` is a vector of positions where the line flips from being drawn or not +and vice versa. The values of `value` are in units of linewidth. + +For example, with `value = [0.0, 4.0, 6.0, 9.5]` +you start drawing at 0, stop at 4 linewidths, start again at 6, stop at 9.5, +then repeat with 0 and 9.5 being treated as the same position. +""" +struct Linestyle + value::Vector{Float32} +end + +to_linestyle(style::Linestyle) = Float32[x - style.value[1] for x in style.value] + +# TODO only use NTuple{2, <: Real} and not any other container +const GapType = Union{Real, Symbol, Tuple, AbstractVector} # A `Symbol` equal to `:dash`, `:dot`, `:dashdot`, `:dashdotdot` -convert_attribute(ls::Union{Symbol,AbstractString}, ::key"linestyle") = line_pattern(ls, :normal) +to_linestyle(ls::Union{Symbol, AbstractString}) = line_pattern(ls, :normal) -function convert_attribute(ls::Tuple{<:Union{Symbol,AbstractString},<:Any}, ::key"linestyle") - line_pattern(ls[1], ls[2]) +function to_linestyle(ls::Tuple{<:Union{Symbol, AbstractString}, <: GapType}) + return line_pattern(ls[1], ls[2]) end -function line_pattern(linestyle, gaps) +function line_pattern(linestyle::Symbol, gaps::GapType) pattern = line_diff_pattern(linestyle, gaps) - isnothing(pattern) ? pattern : float.([0.0; cumsum(pattern)]) + return isnothing(pattern) ? pattern : Float32[0.0; cumsum(pattern)] end "The linestyle patterns are inspired by the LaTeX package tikZ as seen here https://tex.stackexchange.com/questions/45275/tikz-get-values-for-predefined-dash-patterns." -function line_diff_pattern(ls::Symbol, gaps = :normal) +function line_diff_pattern(ls::Symbol, gaps::GapType = :normal) if ls === :solid - nothing + return nothing elseif ls === :dash - line_diff_pattern("-", gaps) + return line_diff_pattern("-", gaps) elseif ls === :dot - line_diff_pattern(".", gaps) + return line_diff_pattern(".", gaps) elseif ls === :dashdot - line_diff_pattern("-.", gaps) + return line_diff_pattern("-.", gaps) elseif ls === :dashdotdot - line_diff_pattern("-..", gaps) + return line_diff_pattern("-..", gaps) else error( """ @@ -912,7 +939,7 @@ function line_diff_pattern(ls::Symbol, gaps = :normal) end end -function line_diff_pattern(ls_str::AbstractString, gaps = :normal) +function line_diff_pattern(ls_str::AbstractString, gaps::GapType = :normal) dot = 1 dash = 3 check_line_pattern(ls_str) @@ -947,24 +974,24 @@ function check_line_pattern(ls_str) nothing end -function convert_gaps(gaps) - error_msg = "You provided the gaps modifier $gaps when specifying the linestyle. The modifier must be `∈ ([:normal, :dense, :loose])`, a real number or a collection of two real numbers." - if gaps isa Symbol - gaps in [:normal, :dense, :loose] || throw(ArgumentError(error_msg)) - dot_gaps = (normal = 2, dense = 1, loose = 4) - dash_gaps = (normal = 3, dense = 2, loose = 6) - - dot_gap = getproperty(dot_gaps, gaps) - dash_gap = getproperty(dash_gaps, gaps) - elseif gaps isa Real - dot_gap = gaps - dash_gap = gaps - elseif length(gaps) == 2 && eltype(gaps) <: Real - dot_gap, dash_gap = gaps - else - throw(ArgumentError(error_msg)) - end - (dot_gap = dot_gap, dash_gap = dash_gap) +function convert_gaps(gaps::GapType) + error_msg = "You provided the gaps modifier $gaps when specifying the linestyle. The modifier must be one of the symbols `:normal`, `:dense` or `:loose`, a real number or a tuple of two real numbers." + if gaps isa Symbol + gaps in [:normal, :dense, :loose] || throw(ArgumentError(error_msg)) + dot_gaps = (normal = 2, dense = 1, loose = 4) + dash_gaps = (normal = 3, dense = 2, loose = 6) + + dot_gap = getproperty(dot_gaps, gaps) + dash_gap = getproperty(dash_gaps, gaps) + elseif gaps isa Real + dot_gap = gaps + dash_gap = gaps + elseif length(gaps) == 2 && eltype(gaps) <: Real + dot_gap, dash_gap = gaps + else + throw(ArgumentError(error_msg)) + end + return (dot_gap = dot_gap, dash_gap = dash_gap) end convert_attribute(c::Tuple{<: Number, <: Number}, ::key"position") = Point2f(c[1], c[2])