From 72915e05d51866828ab87e05b676bc4b5ee73b65 Mon Sep 17 00:00:00 2001 From: t-bltg Date: Mon, 2 Aug 2021 20:55:08 +0200 Subject: [PATCH] allow passing tuple to series_annotations --- src/args.jl | 4 +- src/backends.jl | 1 + src/components.jl | 225 ++++++++++++++++++---------------------- test/test_components.jl | 18 ++-- 4 files changed, 112 insertions(+), 136 deletions(-) diff --git a/src/args.jl b/src/args.jl index 5e624c0ced..d4c0eb0e05 100644 --- a/src/args.jl +++ b/src/args.jl @@ -996,9 +996,9 @@ function processFontArg!(plotattributes::AKW, fontname::Symbol, arg) elseif arg == :center plotattributes[Symbol(fontname, :halign)] = :hcenter plotattributes[Symbol(fontname, :valign)] = :vcenter - elseif arg in (:hcenter, :left, :right) + elseif arg ∈ _haligns plotattributes[Symbol(fontname, :halign)] = arg - elseif arg in (:vcenter, :top, :bottom) + elseif arg ∈ _valigns plotattributes[Symbol(fontname, :valign)] = arg elseif T <: Colorant plotattributes[Symbol(fontname, :color)] = arg diff --git a/src/backends.jl b/src/backends.jl index a82efca5af..d75967a49f 100644 --- a/src/backends.jl +++ b/src/backends.jl @@ -70,6 +70,7 @@ function text_size(lablen::Int, sz::Number, rot::Number = 0) width, height end text_size(lab::AbstractString, sz::Number, rot::Number = 0) = text_size(length(lab), sz, rot) +text_size(lab::PlotText, sz::Number, rot::Number = 0) = text_size(length(lab.str), sz, rot) # account for the size/length/rotation of tick labels function tick_padding(sp::Subplot, axis::Axis) diff --git a/src/components.jl b/src/components.jl index 2f76a2fa38..9e175039ad 100644 --- a/src/components.jl +++ b/src/components.jl @@ -1,10 +1,13 @@ const P2 = GeometryBasics.Point2{Float64} const P3 = GeometryBasics.Point3{Float64} -nanpush!(a::AbstractVector{P2}, b) = (push!(a, P2(NaN,NaN)); push!(a, b)) -nanappend!(a::AbstractVector{P2}, b) = (push!(a, P2(NaN,NaN)); append!(a, b)) -nanpush!(a::AbstractVector{P3}, b) = (push!(a, P3(NaN,NaN,NaN)); push!(a, b)) -nanappend!(a::AbstractVector{P3}, b) = (push!(a, P3(NaN,NaN,NaN)); append!(a, b)) +const _haligns = :hcenter, :left, :right +const _valigns = :vcenter, :top, :bottom + +nanpush!(a::AbstractVector{P2}, b) = (push!(a, P2(NaN, NaN)); push!(a, b)) +nanappend!(a::AbstractVector{P2}, b) = (push!(a, P2(NaN, NaN)); append!(a, b)) +nanpush!(a::AbstractVector{P3}, b) = (push!(a, P3(NaN, NaN, NaN)); push!(a, b)) +nanappend!(a::AbstractVector{P3}, b) = (push!(a, P3(NaN, NaN, NaN)); append!(a, b)) compute_angle(v::P2) = (angle = atan(v[2], v[1]); angle < 0 ? 2π - angle : angle) # ------------------------------------------------------------- @@ -38,9 +41,7 @@ vertices(shape::Shape) = collect(zip(shape.x, shape.y)) @deprecate shape_coords coords "return the vertex points from a Shape or Segments object" -function coords(shape::Shape) - shape.x, shape.y -end +coords(shape::Shape) = shape.x, shape.y #coords(shapes::AVec{Shape}) = unzip(map(coords, shapes)) function coords(shapes::AVec{<:Shape}) @@ -51,12 +52,12 @@ function coords(shapes::AVec{<:Shape}) end "get an array of tuples of points on a circle with radius `r`" -function partialcircle(start_θ, end_θ, n = 20, r=1) - [(r*cos(u), r*sin(u)) for u in range(start_θ, stop=end_θ, length=n)] -end +partialcircle(start_θ, end_θ, n=20, r=1) = [ + (r*cos(u), r*sin(u)) for u in range(start_θ, stop=end_θ, length=n) +] "interleave 2 vectors into each other (like a zipper's teeth)" -function weave(x,y; ordering = Vector[x,y]) +function weave(x,y; ordering=Vector[x,y]) ret = eltype(x)[] done = false while !done @@ -72,7 +73,7 @@ function weave(x,y; ordering = Vector[x,y]) end "create a star by weaving together points from an outer and inner circle. `n` is the number of arms" -function makestar(n; offset = -0.5, radius = 1.0) +function makestar(n; offset=-0.5, radius=1.0) z1 = offset * π z2 = z1 + π / (n) outercircle = partialcircle(z1, z1 + 2π, n+1, radius) @@ -81,12 +82,11 @@ function makestar(n; offset = -0.5, radius = 1.0) end "create a shape by picking points around the unit circle. `n` is the number of point/sides, `offset` is the starting angle" -function makeshape(n; offset = -0.5, radius = 1.0) - z = offset * π - Shape(partialcircle(z, z + 2π, n+1, radius)) -end +makeshape(n; offset=-0.5, radius=1.0) = Shape( + partialcircle(offset * π, offset * π + 2π, n+1, radius) +) -function makecross(; offset = -0.5, radius = 1.0) +function makecross(; offset=-0.5, radius=1.0) z2 = offset * π z1 = z2 - π/8 outercircle = partialcircle(z1, z1 + 2π, 9, radius) @@ -97,10 +97,9 @@ end from_polar(angle, dist) = P2(dist*cos(angle), dist*sin(angle)) -function makearrowhead(angle; h = 2.0, w = 0.4) +function makearrowhead(angle; h=2.0, w=0.4) tip = from_polar(angle, h) - Shape(P2[(0,0), from_polar(angle - 0.5π, w) - tip, - from_polar(angle + 0.5π, w) - tip, (0,0)]) + Shape(P2[(0,0), from_polar(angle - 0.5π, w) - tip, from_polar(angle + 0.5π, w) - tip, (0,0)]) end const _shapes = KW( @@ -121,7 +120,7 @@ const _shapes = KW( :hline => Shape([(1,0),(-1,0)]), ) -for n in [4,5,6,7,8] +for n in (4, 5, 6, 7, 8) _shapes[Symbol("star$n")] = makestar(n) end @@ -136,13 +135,13 @@ function center(shape::Shape) x, y = coords(shape) n = length(x) A, Cx, Cy = 0.0, 0.0, 0.0 - for i=1:n - ip1 = i==n ? 1 : i+1 + for i ∈ 1:n + ip1 = i == n ? 1 : i+1 A += x[i] * y[ip1] - x[ip1] * y[i] end A *= 0.5 - for i=1:n - ip1 = i==n ? 1 : i+1 + for i ∈ 1:n + ip1 = i == n ? 1 : i+1 m = (x[i] * y[ip1] - x[ip1] * y[i]) Cx += (x[i] + x[ip1]) * m Cy += (y[i] + y[ip1]) * m @@ -150,72 +149,64 @@ function center(shape::Shape) Cx / 6A, Cy / 6A end -function scale!(shape::Shape, x::Real, y::Real = x, c = center(shape)) +function scale!(shape::Shape, x::Real, y::Real=x, c=center(shape)) sx, sy = coords(shape) cx, cy = c - for i=eachindex(sx) + for i ∈ eachindex(sx) sx[i] = (sx[i] - cx) * x + cx sy[i] = (sy[i] - cy) * y + cy end shape end -function scale(shape::Shape, x::Real, y::Real = x, c = center(shape)) - shapecopy = deepcopy(shape) - scale!(shapecopy, x, y, c) -end +scale(shape::Shape, x::Real, y::Real=x, c=center(shape)) = scale!(deepcopy(shape), x, y, c) + "translate a Shape in space" -function translate!(shape::Shape, x::Real, y::Real = x) +function translate!(shape::Shape, x::Real, y::Real=x) sx, sy = coords(shape) - for i=eachindex(sx) + for i ∈ eachindex(sx) sx[i] += x sy[i] += y end shape end -function translate(shape::Shape, x::Real, y::Real = x) - shapecopy = deepcopy(shape) - translate!(shapecopy, x, y) -end +translate(shape::Shape, x::Real, y::Real=x) = translate!(deepcopy(shape), x, y) -function rotate_x(x::Real, y::Real, Θ::Real, centerx::Real, centery::Real) +rotate_x(x::Real, y::Real, Θ::Real, centerx::Real, centery::Real) = ( (x - centerx) * cos(Θ) - (y - centery) * sin(Θ) + centerx -end +) -function rotate_y(x::Real, y::Real, Θ::Real, centerx::Real, centery::Real) +rotate_y(x::Real, y::Real, Θ::Real, centerx::Real, centery::Real) = ( (y - centery) * cos(Θ) + (x - centerx) * sin(Θ) + centery -end +) -function rotate(x::Real, y::Real, θ::Real, c = center(shape)) - cx, cy = c - rotate_x(x, y, Θ, cx, cy), rotate_y(x, y, Θ, cx, cy) -end +rotate(x::Real, y::Real, θ::Real, c=center(shape)) = ( + rotate_x(x, y, Θ, c...), + rotate_y(x, y, Θ, c...), +) -function rotate!(shape::Shape, Θ::Real, c = center(shape)) +function rotate!(shape::Shape, Θ::Real, c=center(shape)) x, y = coords(shape) - cx, cy = c - for i=eachindex(x) - xi = rotate_x(x[i], y[i], Θ, cx, cy) - yi = rotate_y(x[i], y[i], Θ, cx, cy) + for i ∈ eachindex(x) + xi = rotate_x(x[i], y[i], Θ, c...) + yi = rotate_y(x[i], y[i], Θ, c...) x[i], y[i] = xi, yi end shape end "rotate an object in space" -function rotate(shape::Shape, θ::Real, c = center(shape)) +function rotate(shape::Shape, θ::Real, c=center(shape)) x, y = coords(shape) - cx, cy = c - x_new = rotate_x.(x, y, θ, cx, cy) - y_new = rotate_y.(x, y, θ, cx, cy) + x_new = rotate_x.(x, y, θ, c...) + y_new = rotate_y.(x, y, θ, c...) Shape(x_new, y_new) end # ----------------------------------------------------------------------- - mutable struct Font family::AbstractString pointsize::Int @@ -242,8 +233,7 @@ julia> font(8) julia> font(family="serif",halign=:center,rotation=45.0) ``` """ -function font(args...;kw...) - +function font(args...; kw...) # defaults family = "sans-serif" pointsize = 14 @@ -265,9 +255,9 @@ function font(args...;kw...) elseif arg == :center halign = :hcenter valign = :vcenter - elseif arg in (:hcenter, :left, :right) + elseif arg ∈ _haligns halign = arg - elseif arg in (:vcenter, :top, :bottom) + elseif arg ∈ _valigns valign = arg elseif T <: Colorant color = arg @@ -282,33 +272,29 @@ function font(args...;kw...) elseif typeof(arg) <: Real rotation = convert(Float64, arg) else - @warn("Unused font arg: $arg ($(typeof(arg)))") + @warn "Unused font arg: $arg ($(typeof(arg)))" end end - for symbol in keys(kw) - if symbol == :family - family = string(kw[:family]) - elseif symbol == :pointsize - pointsize = kw[:pointsize] - elseif symbol == :halign - halign = kw[:halign] - if halign == :center - halign = :hcenter - end - @assert halign in (:hcenter, :left, :right) - elseif symbol == :valign - valign = kw[:valign] - if valign == :center - valign = :vcenter - end + for sym in keys(kw) + if sym == :family + family = string(kw[sym]) + elseif sym == :pointsize + pointsize = kw[sym] + elseif sym == :halign + halign = kw[sym] + halign == :center && (halign = :hcenter) + @assert halign ∈ _halign + elseif sym == :valign + valign = kw[sym] + valign == :center && (valign = :vcenter) @assert valign in (:vcenter, :top, :bottom) - elseif symbol == :rotation - rotation = kw[:rotation] - elseif symbol == :color - color = parse(Colorant, kw[:color]) + elseif sym == :rotation + rotation = kw[sym] + elseif sym == :color + color = parse(Colorant, kw[sym]) else - @warn("Unused font kwarg: $symbol") + @warn "Unused font kwarg: $sym" end end @@ -381,16 +367,12 @@ Create a PlotText object wrapping a string with font info, for plot annotations. text(t::PlotText) = t text(t::PlotText, font::Font) = PlotText(t.str, font) text(str::AbstractString, f::Font) = PlotText(str, f) -function text(str, args...;kw...) - PlotText(string(str), font(args...;kw...)) -end +text(str, args...; kw...) = PlotText(string(str), font(args...; kw...)) Base.length(t::PlotText) = length(t.str) # ----------------------------------------------------------------------- -# ----------------------------------------------------------------------- - struct Stroke width color @@ -403,7 +385,7 @@ end Define the properties of the stroke used in plotting lines """ -function stroke(args...; alpha = nothing) +function stroke(args...; alpha=nothing) width = 1 color = :black style = :solid @@ -426,7 +408,7 @@ function stroke(args...; alpha = nothing) elseif allReals(arg) width = arg else - @warn("Unused stroke arg: $arg ($(typeof(arg)))") + @warn "Unused stroke arg: $arg ($(typeof(arg)))" end end @@ -440,7 +422,7 @@ struct Brush alpha end -function brush(args...; alpha = nothing) +function brush(args...; alpha=nothing) size = 1 color = :black @@ -459,7 +441,7 @@ function brush(args...; alpha = nothing) elseif allReals(arg) size = arg else - @warn("Unused brush arg: $arg ($(typeof(arg)))") + @warn "Unused brush arg: $arg ($(typeof(arg)))" end end @@ -475,14 +457,19 @@ mutable struct SeriesAnnotations scalefactor::Tuple end +_text_label(lab::Tuple, font) = text(lab[1], font, lab[2:end]...) +_text_label(lab::PlotText, font) = lab +_text_label(lab, font) = text(lab, font) + +series_annotations(anns::AMat) = map(series_annotations, anns) series_annotations(scalar) = series_annotations([scalar]) -function series_annotations(anns::AMat) - map(series_annotations, anns) -end +series_annotations(anns::SeriesAnnotations) = anns +series_annotations(::Nothing) = nothing + function series_annotations(strs::AbstractVector, args...) fnt = font() shp = nothing - scalefactor = (1,1) + scalefactor = 1, 1 for arg in args if isa(arg, Shape) || (isa(arg, AbstractVector) && eltype(arg) == Shape) shp = arg @@ -491,11 +478,11 @@ function series_annotations(strs::AbstractVector, args...) elseif isa(arg, Symbol) && haskey(_shapes, arg) shp = _shapes[arg] elseif isa(arg, Number) - scalefactor = (arg,arg) + scalefactor = arg, arg elseif is_2tuple(arg) scalefactor = arg else - @warn("Unused SeriesAnnotations arg: $arg ($(typeof(arg)))") + @warn "Unused SeriesAnnotations arg: $arg ($(typeof(arg)))" end end # if scalefactor != 1 @@ -503,12 +490,10 @@ function series_annotations(strs::AbstractVector, args...) # scale!(s, scalefactor, scalefactor, (0,0)) # end # end - SeriesAnnotations(strs, fnt, shp, scalefactor) + SeriesAnnotations([_text_label(s, fnt) for s ∈ strs], fnt, shp, scalefactor) end -series_annotations(anns::SeriesAnnotations) = anns -series_annotations(::Nothing) = nothing -function series_annotations_shapes!(series::Series, scaletype::Symbol = :pixels) +function series_annotations_shapes!(series::Series, scaletype::Symbol=:pixels) anns = series[:series_annotations] # msw,msh = anns.scalefactor # ms = series[:markersize] @@ -559,13 +544,13 @@ mutable struct EachAnn y end -function Base.iterate(ea::EachAnn, i = 1) +function Base.iterate(ea::EachAnn, i=1) if ea.anns === nothing || isempty(ea.anns.strs) || i > length(ea.y) - return nothing + return end tmp = _cycle(ea.anns.strs,i) - str,fnt = if isa(tmp, PlotText) + str, fnt = if isa(tmp, PlotText) tmp.str, tmp.font else tmp, ea.anns.font @@ -589,12 +574,9 @@ _annotationfont(sp::Subplot) = Plots.font(; color=sp[:annotationcolor], ) -_annotation(sp, font, lab, pos...; alphabet="abcdefghijklmnopqrstuvwxyz") = ( - if lab == :auto - (pos..., text("($(alphabet[sp[:subplot_index]]))", font)) - else - (pos..., isa(lab, PlotText) ? lab : isa(lab, Tuple) ? text(lab[1], font, lab[2:end]...) : text(lab, font)) - end +_annotation(sp::Subplot, font::Font, lab, pos...; alphabet="abcdefghijklmnopqrstuvwxyz") = ( + pos..., + lab == :auto ? text("($(alphabet[sp[:subplot_index]]))", font) : _text_label(lab, font) ) # Expand arrays of coordinates, positions and labels into induvidual annotations @@ -623,8 +605,6 @@ function process_annotation(sp::Subplot, positions::Union{AVec{Symbol},Symbol,Tu anns end -process_any_label(lab, font=Font()) = lab isa Tuple ? text(lab...) : text(lab, font) - _relative_position(xmin, xmax, pos::Length{:pct}) = xmin + pos.value * (xmax - xmin) # Give each annotation coordinates based on specified position @@ -668,7 +648,7 @@ struct ZValues zrange::Tuple{Float64,Float64} end -function zvalues(values::AVec{T}, zrange::Tuple{T,T} = (ignorenan_minimum(values), ignorenan_maximum(values))) where T<:Real +function zvalues(values::AVec{T}, zrange::Tuple{T,T}=(ignorenan_minimum(values), ignorenan_maximum(values))) where T<:Real ZValues(collect(float(values)), map(Float64, zrange)) end @@ -693,8 +673,6 @@ end # # I don't want to clash with ValidatedNumerics, but this would be nice: # ..(a::T, b::T) = (a,b) - - # ----------------------------------------------------------------------- # style is :open or :closed (for now) @@ -714,8 +692,7 @@ Define arrowheads to apply to lines - args are `style` (`:open` or `:closed`), function arrow(args...) style = :simple side = :head - headlength = 0.3 - headwidth = 0.3 + headlength = headwidth = 0.3 setlength = false for arg in args T = typeof(arg) @@ -735,7 +712,7 @@ function arrow(args...) elseif T <: Tuple && length(arg) == 2 headlength, headwidth = Float64(arg[1]), Float64(arg[2]) else - @warn("Skipped arrow arg $arg") + @warn "Skipped arrow arg $arg" end end Arrow(style, side, headlength, headwidth) @@ -745,11 +722,11 @@ end # allow for do-block notation which gets called on every valid start/end pair which # we need to draw an arrow function add_arrows(func::Function, x::AVec, y::AVec) - for i=2:length(x) + for i ∈ 2:length(x) xyprev = (x[i-1], y[i-1]) xy = (x[i], y[i]) if ok(xyprev) && ok(xy) - if i==length(x) || !ok(x[i+1], y[i+1]) + if i == length(x) || !ok(x[i+1], y[i+1]) # add the arrow from xyprev to xy func(xyprev, xy) end @@ -774,15 +751,15 @@ end @deprecate curve_points coords -coords(curve::BezierCurve, n::Integer = 30; range = [0,1]) = map(curve, Base.range(first(range), stop=last(range), length=n)) +coords(curve::BezierCurve, n::Integer=30; range=[0, 1]) = map( + curve, Base.range(first(range), stop=last(range), length=n) +) # build a BezierCurve which leaves point p vertically upwards and arrives point q vertically upwards. # may create a loop if necessary. Assumes the view is [0,1] -function directed_curve(args...; kw...) - error("directed_curve has been moved to PlotRecipes") -end +directed_curve(args...; kw...) = error("directed_curve has been moved to PlotRecipes") -function extrema_plus_buffer(v, buffmult = 0.2) +function extrema_plus_buffer(v, buffmult=0.2) vmin,vmax = ignorenan_extrema(v) vdiff = vmax-vmin buffer = vdiff * buffmult diff --git a/test/test_components.jl b/test/test_components.jl index 2cb2a931a0..aae50da3a2 100644 --- a/test/test_components.jl +++ b/test/test_components.jl @@ -116,7 +116,7 @@ end end @testset "Series Annotations" begin - square = Shape([(0, 0), (1, 0), (1, 1), (0, 1)]) + square = Shape([(0., 0.), (1., 0.), (1., 1.), (0., 1.)]) @test_logs (:warn, "Unused SeriesAnnotations arg: triangle (Symbol)") begin p = plot( [1, 2, 3], @@ -130,7 +130,7 @@ end ), ) sa = p.series_list[1].plotattributes[:series_annotations] - @test sa.strs == ["a"] + @test only(sa.strs).str == "a" @test sa.font.family == "courier" @test sa.baseshape == square @test sa.scalefactor == (1, 4) @@ -141,21 +141,19 @@ end layout = (5, 1), ylims = (-1.1, 1.1), xlims = (0, 5), - series_annotations = permutedims([["1/1"],["1/2"],["1/3"],["1/4"],["1/5"]]), + series_annotations = permutedims([["1/1"], ["1/2"], ["1/3"], ["1/4"], ["1/5"]]), ) - @test spl.series_list[1].plotattributes[:series_annotations].strs == ["1/1"] - @test spl.series_list[2].plotattributes[:series_annotations].strs == ["1/2"] - @test spl.series_list[3].plotattributes[:series_annotations].strs == ["1/3"] - @test spl.series_list[4].plotattributes[:series_annotations].strs == ["1/4"] - @test spl.series_list[5].plotattributes[:series_annotations].strs == ["1/5"] + for i ∈ 1:5 + @test only(spl.series_list[i].plotattributes[:series_annotations].strs).str == "1/$i" + end p = plot([1, 2], annotations=(1.5, 2, text("foo", :left))) - x, y, txt = p.subplots[end][:annotations][end] + x, y, txt = only(p.subplots[end][:annotations]) @test (x, y) == (1.5, 2) @test txt.str == "foo" p = plot([1, 2], annotations=((.1, .5), :auto)) - pos, txt = p.subplots[end][:annotations][end] + pos, txt = only(p.subplots[end][:annotations]) @test pos == (.1, .5) @test txt.str == "(a)" end