From c60cc91bd3df6b622f933cf7efc2e6e48bb082e9 Mon Sep 17 00:00:00 2001 From: Mattriks Date: Thu, 7 Jan 2021 09:58:24 +1100 Subject: [PATCH] color grouping for density2d (#1508) --- NEWS.md | 5 ++++ docs/src/gallery/geometries.md | 30 ++++++++++--------- src/geom/line.jl | 3 +- src/statistics.jl | 53 ++++++++++++++++++++++++++++------ 4 files changed, 67 insertions(+), 24 deletions(-) diff --git a/NEWS.md b/NEWS.md index 572ad88c5..9cbdbc734 100644 --- a/NEWS.md +++ b/NEWS.md @@ -1,6 +1,11 @@ This is a log of major changes in Gadfly between releases. It is not exhaustive. Each release typically has a number of minor bug fixes beyond what is listed here. +# Version 1.x + + + * Enable `color` grouping for `Geom.density2d` (#1508) + # Version 1.3.1 * Better define what scales are included in the (internal) `scales` dict (#1468) diff --git a/docs/src/gallery/geometries.md b/docs/src/gallery/geometries.md index f58a13387..26383c2aa 100644 --- a/docs/src/gallery/geometries.md +++ b/docs/src/gallery/geometries.md @@ -177,21 +177,24 @@ plot(layer(x=xs, Geom.density, color=["auto"]), ```@example using Gadfly, Distributions, RDatasets -set_default_plot_size(21cm, 8cm) -iris = dataset("datasets", "iris") +set_default_plot_size(21cm, 18cm) +geyser, iris = dataset("datasets", "faithful"), dataset("datasets", "iris") +geyser.g = geyser.Eruptions.>3 X = rand(Rayleigh(2), 1000,2) levelf(x) = maximum(x)*0.5.^collect(1:2:8) -p1 = plot(x=X[:,1], y=X[:,2], Geom.density2d(levels=levelf), - Geom.point, Scale.color_continuous(colormap=c->colorant"red"), - Theme(key_position=:none)) +p1 = plot(x=X[:,1], y=X[:,2], layer(Geom.density2d(levels=levelf), color=[colorant"red"], + linestyle=[:solid]), Geom.point) +p2 = plot(geyser, x=:Eruptions, y=:Waiting, color=:g, Geom.density2d(levels=4), + Geom.point, alpha=[0.4]) +p3 = plot(iris, x=:SepalLength, y=:SepalWidth, color=:Species, Geom.density2d(levels=4), + Geom.point, alpha=[0.8]) cs = repeat(Scale.default_discrete_colors(3), inner=50) -p2 = plot(iris, x=:SepalLength, y=:SepalWidth, - layer(x=:SepalLength, y=:SepalWidth, color=cs), +p4 = plot(iris, x=:SepalLength, y=:SepalWidth, layer(color=cs, Geom.point), layer(Geom.density2d(levels=[0.1:0.1:0.4;]), order=1), - Scale.color_continuous, Guide.colorkey(title=""), - Guide.manual_color_key("Iris", unique(iris.Species)), - Theme(point_size=3pt, line_width=1.5pt)) -hstack(p1, p2) + Scale.color_continuous, Guide.colorkey(title=""), + Theme(point_size=3pt, line_width=1.5pt), + Guide.manual_color_key("Iris", unique(iris.Species))) +gridstack([p1 p2; p3 p4]) ``` @@ -245,9 +248,8 @@ set_default_plot_size(21cm, 8cm) salaries = dataset("car","Salaries") salaries.Salary /= 1000.0 salaries.Discipline = ["Discipline $(x)" for x in salaries.Discipline] -fn1(x, u=mean(x), s=std(x)) = (Salary=u, ymin=u-s, ymax=u+s, - label="$(round.(Int,u))") -df = combine(:Salary=>fn1, groupby(salaries, [:Rank, :Discipline])) +fn1(x, u=mean(x), s=std(x)) = (Salary=u, ymin=u-s, ymax=u+s, label="$(round.(Int,u))") +df = combine(groupby(salaries, [:Rank, :Discipline]), :Salary=>fn1=>AsTable) p1 = plot(df, x=:Discipline, y=:Salary, color=:Rank, Scale.x_discrete(levels=["Discipline A", "Discipline B"]), diff --git a/src/geom/line.jl b/src/geom/line.jl index d877840e9..bf53737c7 100644 --- a/src/geom/line.jl +++ b/src/geom/line.jl @@ -65,7 +65,8 @@ density(; bandwidth::Real=-Inf) = Geom.density2d[(; bandwidth=(-Inf,-Inf), levels=15)] Draw a set of contours showing the density estimate of the `x` and `y` -aesthetics. This geometry is equivalent to [`Geom.line`](@ref) with +aesthetics. If grouped by `color`, then contour lines are mapped to `linestyle`. +This geometry is equivalent to [`Geom.line`](@ref) with [`Stat.density2d`](@ref); see the latter for more information. """ density2d(; bandwidth::Tuple{Real,Real}=(-Inf,-Inf), levels=15) = diff --git a/src/statistics.jl b/src/statistics.jl index 86f68807f..594546c0b 100644 --- a/src/statistics.jl +++ b/src/statistics.jl @@ -513,18 +513,53 @@ is controlled by `bandwidth`. Calls [`Stat.contour`](@ref) to compute the const density2d = Density2DStatistic function apply_statistic(stat::Density2DStatistic, - scales::Dict{Symbol, Gadfly.ScaleElement}, - coord::Gadfly.CoordinateElement, - aes::Gadfly.Aesthetics) - Gadfly.assert_aesthetics_defined("Density2DStatistic", aes, :x, :y) + scales::Dict{Symbol, Gadfly.ScaleElement}, + coord::Gadfly.CoordinateElement, + aes::Gadfly.Aesthetics) window = (stat.bw[1] <= 0.0 ? KernelDensity.default_bandwidth(aes.x) : stat.bw[1], stat.bw[2] <= 0.0 ? KernelDensity.default_bandwidth(aes.y) : stat.bw[2]) - k = KernelDensity.kde((aes.x,aes.y), bandwidth=window, npoints=stat.n) - aes.z = k.density - aes.x = collect(k.x) - aes.y = collect(k.y) - apply_statistic(ContourStatistic(levels=stat.levels), scales, coord, aes) + + Dat = [aes.x aes.y] + linestyleflag = aes.linestyle ≠ nothing + colorflag = aes.color ≠ nothing + aes_color = colorflag ? aes.color : [nothing] + aes_group = (aes.group ≠ nothing) ? aes.group : [nothing] + CT, GT = eltype(aes_color), eltype(aes_group) + + groups = collect(Tuple{CT, GT}, Compose.cyclezip(aes_color, aes_group)) + ugroups = unique(groups) + nugroups = length(ugroups) + + K, V = Tuple{CT, GT}, Matrix{eltype(Dat)} + + grouped_xy = if nugroups==1 + Dict{K, V}(ugroups[1]=>Dat) + elseif nugroups>1 + Dict{K, V}(g=>Dat[groups.==[g],:] for g in ugroups) + end + + aess = Gadfly.Aesthetics[] + for (g, data) in grouped_xy + aes1 = Gadfly.Aesthetics() + k = KernelDensity.kde(data, bandwidth=window, npoints=stat.n) + aes1.x, aes1.y, aes1.z = k.x, k.y, k.density + apply_statistic(ContourStatistic(levels=stat.levels), scales, coord, aes1) + colorflag && (aes1.color = fill(g[1], length(aes1.x))) + push!(aess, aes1) + end + + aes2 = Gadfly.concat(aess...) + aes.x, aes.y, aes.color = aes2.x, aes2.y, aes2.color + colorflag && (linestyleflag ? (aes.group = aes2.group) : (aes.linestyle = aes2.group.+1)) + if !colorflag + aes.group = aes2.group + aes.color_function = aes2.color_function + aes.color_label = aes2.color_label + aes.color_key_colors = aes2.color_key_colors + aes.color_key_continuous = aes2.color_key_continuous + end + end