diff --git a/NEWS.md b/NEWS.md index 9cbdbc734..82b827d69 100644 --- a/NEWS.md +++ b/NEWS.md @@ -3,7 +3,7 @@ Each release typically has a number of minor bug fixes beyond what is listed her # Version 1.x - + * Support one-length aesthetics for `Geom.polygon` and `Geom.ribbon` (#1511) * Enable `color` grouping for `Geom.density2d` (#1508) # Version 1.3.1 diff --git a/docs/src/gallery/geometries.md b/docs/src/gallery/geometries.md index 26383c2aa..d01167ecf 100644 --- a/docs/src/gallery/geometries.md +++ b/docs/src/gallery/geometries.md @@ -206,19 +206,15 @@ set_default_plot_size(21cm, 8cm) D = dataset("datasets","faithful") D.g = D.Eruptions.>3.0 coord = Coord.cartesian(ymin=40, ymax=100) -pa = plot(D, coord, - x=:Eruptions, y=:Waiting, group=:g, - Geom.point, Geom.ellipse, - Theme(lowlight_color=c->"gray") ) -pb = plot(D, coord, Guide.ylabel(nothing), - x=:Eruptions, y=:Waiting, color=:g, +pa = plot(D, coord, x=:Eruptions, y=:Waiting, group=:g, + Geom.point, Geom.ellipse, Theme(lowlight_color=c->"gray")) +pb = plot(D, coord, Guide.ylabel(nothing), x=:Eruptions, y=:Waiting, color=:g, Geom.point, Geom.ellipse(levels=[0.95, 0.99]), - Theme(key_position=:none, lowlight_color=identity, line_style=[:solid,:dot])) -pc = plot(D, coord, Guide.ylabel(nothing), - x=:Eruptions, y=:Waiting, color=:g, + Theme(key_position=:none, lowlight_color=identity, line_style=[:solid,:dot])) +pc = plot(D, coord, Guide.ylabel(nothing), x=:Eruptions, y=:Waiting, color=:g, Geom.point, Geom.ellipse(fill=true), layer(Geom.ellipse(levels=[0.99]), style(line_style=[:dot])), - Theme(key_position=:none) ) + Theme(key_position=:none)) hstack(pa,pb,pc) ``` @@ -434,13 +430,18 @@ plot(layer(x=rdata[1,:], y=rdata[2,:], color=[colorant"red"], Geom.point), ## [`Geom.polygon`](@ref) ```@example -using Gadfly -set_default_plot_size(14cm, 8cm) -plot(x=[0, 1, 1, 2, 2, 3, 3, 2, 2, 1, 1, 0, 4, 5, 5, 4], +using Gadfly, DataFrames +set_default_plot_size(21cm, 8cm) + +p1 = plot(x=[0, 1, 1, 2, 2, 3, 3, 2, 2, 1, 1, 0, 4, 5, 5, 4], y=[0, 0, 1, 1, 0, 0, 3, 3, 2, 2, 3, 3, 0, 0, 3, 3], - group=["H", "H", "H", "H", "H", "H", "H", "H", - "H", "H", "H", "H", "I", "I", "I", "I"], + group=reduce(vcat, fill.(["H", "I"], [12,4])), Geom.polygon(preserve_order=true, fill=true)) +Dps = reduce(vcat, [DataFrame(x=[1, 5, 5, 1].+d, y=[14, 14, 10, 10].-d, id=d+1) for d in 0:9]) +p2 = plot(Dps, x=:x, y=:y, color=:id, alpha=[0.3], linestyle=[:dash], + Geom.polygon(fill=true), Scale.color_discrete, + Theme(line_width=2pt, lowlight_color=identity, discrete_highlight_color=identity)) +hstack(p1, p2) ``` @@ -478,7 +479,7 @@ Db = [DataFrame(x=x, ymax=pdf.(Normal(μ),x), ymin=0.0, u="μ=$μ") for μ in [- # In the line below, 0.6 is the color opacity p1 = plot(vcat(Da...), x=:x, y=:y, ymin=:ymin, ymax=:ymax, color=:f, - Geom.line, Geom.ribbon, Theme(alphas=[0.6]) + Geom.line, Geom.ribbon, alpha=[0.6] ) p2 = plot(vcat(Db...), x = :x, y=:ymax, ymin = :ymin, ymax = :ymax, color = :u, alpha=:u, Theme(alphas=[0.8,0.2]), diff --git a/src/geom/polygon.jl b/src/geom/polygon.jl index 268d6598e..b21839606 100644 --- a/src/geom/polygon.jl +++ b/src/geom/polygon.jl @@ -16,7 +16,7 @@ Draw polygons with vertices specified by the `x` and `y` aesthetics. Optionally plot multiple polygons according to the `group`, `color`, `linestyle`, and/or `alpha` aesthetics. `order` controls whether the polygon(s) are underneath or on top of other forms. If `fill=true`, fill the polygons using `Theme.lowlight_color` and stroke the polygons using -`Theme.discrete_highlight_color`. If `fill=false` stroke the polygons using `Theme.lowlight_color` and `Theme.line_style`. +`Theme.discrete_highlight_color`. If `fill=false` stroke the polygons using `Theme.lowlight_color`. If `preserve_order=true` connect points in the order they are given, otherwise order the points around their centroid. """ @@ -51,41 +51,47 @@ function render(geom::PolygonGeometry, theme::Gadfly.Theme, aes::Gadfly.Aestheti Gadfly.assert_aesthetics_defined("Geom.polygon", aes, :x, :y) default_aes = Gadfly.Aesthetics() - default_aes.group = IndirectArray(fill(1,length(aes.x))) - default_aes.color = fill(theme.default_color, length(aes.x)) - default_aes.linestyle = fill(1, length(aes.x)) - default_aes.alpha = fill(1, length(aes.x)) + default_aes.group = IndirectArray([1]) + default_aes.color = Colorant[theme.default_color] + default_aes.linestyle = [1] + default_aes.alpha = [1] aes = inherit(aes, default_aes) aes_x, aes_y, aes_color, aes_linestyle, aes_group, aes_alpha = concretize(aes.x, aes.y, aes.color, aes.linestyle, aes.group, aes.alpha) - + XT, YT, CT, GT, LST, AT = eltype(aes_x), eltype(aes_y), eltype(aes_color), eltype(aes_group), eltype(aes_linestyle), eltype(aes_alpha) - groups = collect((Tuple{CT, GT, LST, AT}), zip(aes_color, aes_group, aes_linestyle, aes_alpha)) - ug = unique(groups) + groups = collect((Tuple{CT, GT, LST, AT}), Compose.cyclezip(aes_color, aes_group, aes_linestyle, aes_alpha)) + ugroups = unique(groups) + nugroups = length(ugroups) - n = length(ug) - polys = Vector{Vector{Tuple{XT,YT}}}(undef, n) - θs = Vector{Float64} - colors = Vector{CT}(undef, n) - line_styles = Vector{LST}(undef, n) + polys = Vector{Vector{Tuple{XT,YT}}}(undef, nugroups) + colors = Vector{Colorant}(undef, nugroups) + stroke_colors = Vector{Colorant}(undef, nugroups) + linestyles = Vector{Vector{Measure}}(undef, nugroups) linestyle_palette_length = length(theme.line_style) - alphas = Vector{Float64}(undef, n) + alphas = Vector{Float64}(undef, nugroups) alpha_discrete = AT <: Int + linestyle_discrete = LST <: Int + + if nugroups==1 + polys[1] = polygon_points(aes_x, aes_y, geom.preserve_order) + elseif nugroups>1 + for (k,g) in enumerate(ugroups) + i = groups.==[g] + polys[k] = polygon_points(aes_x[i], aes_y[i], geom.preserve_order) + end + end - for (k,g) in enumerate(ug) - i = groups.==[g] - polys[k] = polygon_points(aes_x[i], aes_y[i], geom.preserve_order) - colors[k] = first(aes_color[i]) - line_styles[k] = mod1(first(aes_linestyle[i]), linestyle_palette_length) - alphas[k] = first(alpha_discrete ? theme.alphas[aes_alpha[i]] : aes_alpha[i]) + for (k, (c, g, ls, a)) in enumerate(ugroups) + colors[k] = parse_colorant(theme.lowlight_color(c)) + stroke_colors[k] = parse_colorant(theme.discrete_highlight_color(c)) + linestyles[k] = linestyle_discrete ? get_stroke_vector(theme.line_style[mod1(ls, linestyle_palette_length)]) : get_stroke_vector(ls) + alphas[k] = alpha_discrete ? theme.alphas[a] : a end - plinestyles = Gadfly.get_stroke_vector.(theme.line_style[line_styles]) - pcolors = theme.lowlight_color.(colors) - - properties = geom.fill ? (fill(pcolors), stroke(theme.discrete_highlight_color.(colors)), fillopacity(alphas)) : - (fill(nothing), stroke(pcolors), strokedash(plinestyles)) + properties = geom.fill ? (fill(colors), stroke(stroke_colors), fillopacity(alphas), strokedash(linestyles)) : + (fill(nothing), stroke(colors), strokedash(linestyles)) ctx = context(order=geom.order) compose!(ctx, Compose.polygon(polys, geom.tag), properties...) diff --git a/src/geom/ribbon.jl b/src/geom/ribbon.jl index d9c6e6caf..af9d74560 100644 --- a/src/geom/ribbon.jl +++ b/src/geom/ribbon.jl @@ -21,32 +21,30 @@ element_aesthetics(::RibbonGeometry) = [:x, :ymin, :ymax, :color, :linestyle, :a function render(geom::RibbonGeometry, theme::Gadfly.Theme, aes::Gadfly.Aesthetics) Gadfly.assert_aesthetics_defined("Geom.ribbon", aes, :x, :ymin, :ymax) - Gadfly.assert_aesthetics_equal_length("Geom.ribbon", aes, element_aesthetics(geom)...) + Gadfly.assert_aesthetics_equal_length("Geom.ribbon", aes, :x, :ymin, :ymax) default_aes = Gadfly.Aesthetics() - default_aes.linestyle = fill(1, length(aes.x)) - default_aes.color = fill(theme.default_color, length(aes.x)) - default_aes.alpha = fill(1, length(aes.x)) + default_aes.linestyle = [1] + default_aes.color = Colorant[theme.default_color] + default_aes.alpha = [1] aes = inherit(aes, default_aes) aes_x, aes_ymin, aes_ymax, aes_color, aes_linestyle, aes_alpha = concretize(aes.x, aes.ymin, aes.ymax, aes.color, aes.linestyle, aes.alpha) XT, CT, LST, AT = eltype(aes_x), eltype(aes_color), eltype(aes_linestyle), eltype(aes_alpha) YT = eltype(aes_ymin) - groups = collect((Tuple{CT, LST, AT}), zip(aes_color, aes_linestyle, aes_alpha)) - ug = unique(groups) + groups = collect((Tuple{CT, LST, AT}), Compose.cyclezip(aes_color, aes_linestyle, aes_alpha)) + ugroups = unique(groups) + nugroups = length(ugroups) V = Vector{Tuple{XT, YT}} K = Tuple{CT, LST, AT} - max_points = Dict{K, V}(g=>V[] for g in ug) - for (x, y, c, ls, a) in zip(aes_x, aes_ymax, aes_color, aes_linestyle, aes_alpha) - push!(max_points[(c,ls,a)], (x, y)) - end - - min_points = Dict{K, V}(g=>V[] for g in ug) - for (x, y, c, ls, a) in zip(aes_x, aes_ymin, aes_color, aes_linestyle, aes_alpha) - push!(min_points[(c,ls,a)], (x, y)) + max_points = Dict{K, V}(g=>V[] for g in ugroups) + min_points = Dict{K, V}(g=>V[] for g in ugroups) + for (x, ymin, ymax, c, ls, a) in Compose.cyclezip(aes_x, aes_ymin, aes_ymax, aes_color, aes_linestyle, aes_alpha) + push!(max_points[(c,ls,a)], (x, ymax)) + push!(min_points[(c,ls,a)], (x, ymin)) end for k in keys(max_points) @@ -58,22 +56,23 @@ function render(geom::RibbonGeometry, theme::Gadfly.Theme, aes::Gadfly.Aesthetic polys = [collect(Tuple{XT, YT}, Iterators.flatten((min_points[k], max_points[k]))) for k in kys] lines = [collect(Tuple{XT, Union{YT, Missing}}, Iterators.flatten((min_points[k], [(last(min_points[k])[1], missing)], max_points[k]))) for k in kys] - n = length(kys) - colors = Vector{Union{Colorant, String}}(undef, n) - linestyles = Vector{Vector{Measure}}(undef, n) - alphas = Vector{Float64}(undef, n) + colors = Vector{Colorant}(undef, nugroups) + linestyles = Vector{Vector{Measure}}(undef, nugroups) + linestyle_palette_length = length(theme.line_style) + alphas = Vector{Float64}(undef, nugroups) alpha_discrete = AT <: Int + linestyle_discrete = LST <: Int for (i, (c,ls,a)) in enumerate(kys) - colors[i] = theme.lowlight_color(c) - linestyles[i] = Gadfly.get_stroke_vector(theme.line_style[ls]) + colors[i] = parse_colorant(theme.lowlight_color(c)) + linestyles[i] = linestyle_discrete ? get_stroke_vector(theme.line_style[mod1(ls, linestyle_palette_length)]) : get_stroke_vector(ls) alphas[i] = alpha_discrete ? theme.alphas[a] : a end ctx = context() - geom.fill ? compose!(ctx, Compose.polygon(polys, geom.tag), fill(colors), fillopacity(alphas)) : - compose!(ctx, Compose.line(lines, geom.tag), fill(nothing), stroke(colors), strokedash(linestyles)) + geom.fill ? compose!(ctx, Compose.polygon(polys, geom.tag), fill(colors), fillopacity(alphas)) : + compose!(ctx, Compose.line(lines, geom.tag), stroke(colors), strokedash(linestyles)) return compose!(ctx, svgclass("geometry"), linewidth(theme.line_width)) end diff --git a/src/geometry.jl b/src/geometry.jl index f695b81fb..a7a18b9c8 100644 --- a/src/geometry.jl +++ b/src/geometry.jl @@ -13,7 +13,7 @@ import Compose.combine # Prevent DataFrame.combine from taking over. import Gadfly: render, layers, element_aesthetics, inherit, escape_id, default_statistic, default_scales, element_coordinate_type, ScaleElement, svg_color_class_from_label, isconcrete, - concretize, discretize_make_ia + concretize, discretize_make_ia, get_stroke_vector, parse_colorant import IterTools: takestrict const empty_tag = Symbol("") diff --git a/src/misc.jl b/src/misc.jl index 6ec7d87e4..f384d7896 100644 --- a/src/misc.jl +++ b/src/misc.jl @@ -16,47 +16,16 @@ end function concretize(xss::AbstractVector...) - if all(map(isallconcrete, xss)) - return xss - end - - count = 0 - for j in 1:length(xss[1]) - for xs in xss - if !isconcrete(xs[j]) - @goto next_j1 - end - end - - count += 1 - @label next_j1 - end - - yss = Vector{AbstractVector}(undef, length(xss)) - for (i, xs) in enumerate(xss) - yss[i] = Vector{eltype(xs)}(undef, count) - end + all(isallconcrete, xss) && return xss - k = 1 - for j in 1:length(xss[1]) - for xs in xss - if !isconcrete(xs[j]) - @goto next_j2 - end - end - - for (i, xs) in enumerate(xss) - yss[i][k] = xs[j] - end - k += 1 - - @label next_j2 - end - - return tuple(yss...) + cf = mapreduce(x->isconcrete.(x), (x,y)->x.&y, xss) + n = length(cf) + yss = [(length(xs)==n ? xs[cf] : xs) for xs in xss] + return yss end + # How many concrete elements in an iterable function concrete_length(xs) n = 0 diff --git a/src/theme.jl b/src/theme.jl index a3b712c7e..5ae108618 100755 --- a/src/theme.jl +++ b/src/theme.jl @@ -33,7 +33,7 @@ function default_lowlight_color(fill_color::Color) end function default_lowlight_color(fill_color::TransparentColor) - @warn "For opacity, use `Theme(alphas=[a])` and/or `Scale.alpha_discrete()`, or use `Scale.alpha_continuous()`" + @warn "For opacity, use `alpha=[a]`, or use `Theme(alphas=[a])` and/or `Scale.alpha_discrete()`, or use `Scale.alpha_continuous()`" RGBA{Float32}(Gadfly.default_lowlight_color(color(fill_color)), fill_color.alpha) end diff --git a/test/testscripts/polygon.jl b/test/testscripts/polygon.jl index d29742c48..bfc409528 100644 --- a/test/testscripts/polygon.jl +++ b/test/testscripts/polygon.jl @@ -1,15 +1,14 @@ using Gadfly -set_default_plot_size(6inch, 3inch) - -plot(x=[0, 1, 1, 2, 2, 3, 3, 2, 2, 1, 1, 0, 4, 5, 5, 4], - y=[0, 0, 1, 1, 0, 0, 3, 3, 2, 2, 3, 3, 0, 0, 3, 3], - group=["H", "H", "H", "H", "H", "H", "H", "H", - "H", "H", "H", "H", "I", "I", "I", "I"], - Geom.polygon(preserve_order=false, fill=true)) - -plot(x=[0, 1, 1, 2, 2, 3, 3, 2, 2, 1, 1, 0, 4, 5, 5, 4], - y=[0, 0, 1, 1, 0, 0, 3, 3, 2, 2, 3, 3, 0, 0, 3, 3], - group=["H", "H", "H", "H", "H", "H", "H", "H", - "H", "H", "H", "H", "I", "I", "I", "I"], - Geom.polygon(preserve_order=true, fill=true)) +set_default_plot_size(8inch, 3inch) + +x = [0, 1, 1, 2, 2, 3, 3, 2, 2, 1, 1, 0, 4, 5, 5, 4] +y = [0, 0, 1, 1, 0, 0, 3, 3, 2, 2, 3, 3, 0, 0, 3, 3] +group = reduce(vcat, fill.(["H", "I"], [12,4])) + + +p1 = plot(x=x, y=y, group=group, Geom.polygon(preserve_order=false, fill=true)) +p2 = plot(x=x, y=y, group=group, Geom.polygon(preserve_order=true, fill=false), + Theme(line_width=2mm), linestyle=[:dash], color=[colorant"orange"] ) + +hstack(p1, p2) diff --git a/test/testscripts/ribbon.jl b/test/testscripts/ribbon.jl index 7ebb783fc..22cfccd68 100644 --- a/test/testscripts/ribbon.jl +++ b/test/testscripts/ribbon.jl @@ -1,14 +1,13 @@ using Gadfly, DataFrames -set_default_plot_size(6inch, 3inch) +set_default_plot_size(12inch, 3inch) xs = 0:0.1:20 -df = DataFrame( - x=xs, - y=cos.(xs), - ymin=cos.(xs) .- 0.5, - ymax=cos.(xs) .+ 0.5, -) +df = [DataFrame(x=xs, y=y, ymin=y.-0.5, ymax=y.+0.5, f=f) for (y,f) in zip((cos.(xs), sin.(xs)), ("cos", "sin"))] -plot(df, x=:x, y=:y, ymin=:ymin, ymax=:ymax, Geom.line, Geom.ribbon) +p1 = plot(df[1], x=:x, y=:y, ymin=:ymin, ymax=:ymax, Geom.line, Geom.ribbon) +p2 = plot(df[1], Geom.ribbon, x=:x, ymin=:ymin, ymax=:ymax, color=[colorant"red"], alpha=[0.3], Theme(lowlight_color=identity)) +p3 = plot(vcat(df...), x=:x, y=:y, ymin=:ymin, ymax=:ymax, color=:f, Geom.line, Geom.ribbon) + +hstack(p1, p2, p3)