From d3c7723615d455025b26691c934641f241e8d459 Mon Sep 17 00:00:00 2001 From: Mattriks Date: Sun, 19 Jan 2020 19:20:08 +1100 Subject: [PATCH] add Scale.size_radius and Scale.size_area --- NEWS.md | 1 + docs/src/gallery/scales.md | 22 ++++++ docs/src/tutorial.md | 24 +++--- src/scale.jl | 4 +- src/scale/scales.jl | 95 ++++++++++++++++++++++++ src/theme.jl | 3 + test/testscripts/point_size_numerical.jl | 13 +++- 7 files changed, 147 insertions(+), 15 deletions(-) diff --git a/NEWS.md b/NEWS.md index 2241049b4..8e8df1a67 100644 --- a/NEWS.md +++ b/NEWS.md @@ -4,6 +4,7 @@ Each release typically has a number of minor bug fixes beyond what is listed her # Version 1.x + * Add `Scale.size_radius` and `Scale.size_area` (#1386) * Add `Guide.sizekey` (#1379) * Add `Scale.size_discrete2` (#1376) * Add `Geom.blank` (#1345) diff --git a/docs/src/gallery/scales.md b/docs/src/gallery/scales.md index 5e0e7bfd8..d3d784233 100644 --- a/docs/src/gallery/scales.md +++ b/docs/src/gallery/scales.md @@ -182,6 +182,28 @@ tipsm = by(tips, [:Day, :Sex], :TotalBill=>mean, :Tip=>mean) ) ``` +## [`Scale.size_radius`](@ref), [`Scale.size_area`](@ref) + +```@example +using Gadfly, RDatasets +set_default_plot_size(21cm, 8cm) +aq = dropmissing(dataset("datasets","airquality")) +aq = filter(x-> 7≤x.Month≤9, aq) + +sizemap(p::Float64; min=0.75mm, max=3mm) = min + p*(max-min) +labels = ["July", "August", "September"] +coord = Coord.cartesian(xmin=0, xmax=32, ymin=0, ymax=160) +plot(aq, x=:Day, y=:Ozone, color=:Month, size=:Wind, + coord, Scale.color_discrete, + Guide.colorkey(title="", labels=labels, pos=[2,240]), + Scale.size_area(sizemap, minvalue=4, maxvalue=16), + Guide.sizekey(title="Wind (mph)"), Guide.xlabel("Day of Month"), + Theme(key_swatch_shape=Shape.circle, key_swatch_color="gray", + discrete_highlight_color=identity, alphas=[0.5]) +) +``` + + ## [`Scale.size_discrete2`](@ref) ```@example diff --git a/docs/src/tutorial.md b/docs/src/tutorial.md index 40f52d45c..fbb88f9ad 100644 --- a/docs/src/tutorial.md +++ b/docs/src/tutorial.md @@ -162,17 +162,18 @@ colorspaces (LUV/LCHuv or LAB/LCHab), though it supports RGB, HSV, HLS, XYZ, and converts arbitrarily between these. Of course, CSS/X11 named colors work too: "old lace", anyone? -All aesthetics (e.g. `x`, `y`, `color`) have a Scale e.g. `Scale.x_continuous()` and some have a Guide e.g. `Guide.xticks()`. [Scales](@ref) can be continuous or discrete. +All aesthetics (e.g. `x`, `y`, `color`) have a Scale e.g. `Scale.x_continuous()` and some have a Guide e.g. `Guide.xticks()`. [Scales](@ref) can be continuous or discrete. Some Scales also have a corresponding palette in `Theme()`. ## Continuous Scales -| Aesthetic | Scale. | Guide. | -|-----------|------------------|-------| -| `x` | `x_continuous` | `xticks` | -| `y` | `y_continuous` | `yticks` | -| `color` | `color_continuous` | `colorkey` | -| `size` | `size_continuous` | sizekey (tbd) | -| `alpha` | `alpha_continuous` | alphakey (tbd) | +| Aesthetic | Scale. | Guide. | Theme palette | +|-----------|------------------|-------|----------| +| `x` | `x_continuous` | `xticks` | | +| `y` | `y_continuous` | `yticks` | | +| `color` | `color_continuous` | `colorkey` | (tbd) | +| `size` | `size_continuous` | --- | `point_size_min`, `point_size_max` | +| | `size_radius` | `sizekey` | `continuous_sizemap` | +| `alpha` | `alpha_continuous` | alphakey (tbd) | | e.g. `Scale.x_continuous(format= , minvalue= , maxvalue= )`\ `format` can be: `:plain`, `:scientific`, `:engineering`, or `:auto`. @@ -188,7 +189,8 @@ p2 = plot(mammals, x=:Body, y=:Brain, label=:Mammal, Geom.point, Geom.label, hstack(p1, p2) ``` -Scale transformations include: `_sqrt`, `_log`, `_log2`, `_log10`, `_asinh`. +Scale transformations include: `_sqrt`, `_log`, `_log2`, `_log10`, `_asinh` for the `x`, `y`, `color` aesthetics, +and `_area` for the `size` aesthetic. ```@example 1 using Printf @@ -206,13 +208,11 @@ hstack(p3, p4) ## Discrete Scales -For some discrete scales, there is a corresponding palette in `Theme()`. - | Aesthetic | Scale. | Guide. | Theme palette | |-----------|------------------|-------|-----------------| | `x` | `x_discrete` | `xticks` | | | `y` | `y_discrete` | `yticks` | | -| `color` | `color_discrete` | `colorkey` | | +| `color` | `color_discrete` | `colorkey` | (tbd) | | `shape` | `shape_discrete` | `shapekey` | `point_shapes` | | `size` | `size_discrete` | --- | `point_size_min`, `point_size_max` | | | `size_discrete2`| `sizekey` | `discrete_sizemap` | diff --git a/src/scale.jl b/src/scale.jl index 702153d7c..a8104d431 100644 --- a/src/scale.jl +++ b/src/scale.jl @@ -11,7 +11,7 @@ using Printf using Base.Iterators import Gadfly: element_aesthetics, isconcrete, concrete_length, discretize_make_ia, - aes2str, valid_aesthetics + aes2str, valid_aesthetics, Maybe import Distributions: Distribution include("color_misc.jl") @@ -159,6 +159,8 @@ end """ size_continuous[(; minvalue=nothing, maxvalue=nothing, labels=nothing, format=nothing, minticks=2, maxticks=10, scalable=true)] + +To be updated in Gadfly 2.0. Now try out the new [`Scale.size_radius`](@ref) and [`Scale.size_area`](@ref). """ const size_continuous = continuous_scale_partial([:size], identity_transform) diff --git a/src/scale/scales.jl b/src/scale/scales.jl index a88e0b033..ab7bc5f69 100644 --- a/src/scale/scales.jl +++ b/src/scale/scales.jl @@ -67,3 +67,98 @@ function Scale.apply_scale(scale::DiscreteSizeScale, aess::Vector{Gadfly.Aesthet end + +area_transform = ContinuousScaleTransform(a->sqrt(a/π), r->π*r*r, identity_formatter) + +struct ContinuousSizeScale <: Gadfly.ScaleElement + # A function of the form f(p) where 0 ≤ p ≤ 1, that returns a Measure. + f::Function + trans::ContinuousScaleTransform + minvalue::Maybe(Compose.MeasureOrNumber) + maxvalue::Maybe(Compose.MeasureOrNumber) + format::Union{Nothing, Symbol} +end + +Scale.element_aesthetics(scale::ContinuousSizeScale) = [:size] + +default_continuous_sizes(p::Float64; min=0mm, max=2mm) = min + p*(max-min) + + +""" + Scale.size_radius(f=default_continuous_sizes; minvalue=0.0, maxvalue=nothing) + +A scale for continuous sizes. Values in the `size` aesthetic are mapped either to: +- x-axis units (if `maxvalue=nothing`), or +- `Measure` units (from Measures.jl). +If `maxvalue` is specified, the continuous sizes are converted to a proportion +(between `minvalue` and `maxvalue`), and then mapped to absolute sizes using the function `f(p)` where `0≤p≤1`. +""" +size_radius(f::Function=Gadfly.current_theme().continuous_sizemap; minvalue=0.0, maxvalue=nothing) = + ContinuousSizeScale(f, identity_transform, minvalue, maxvalue, nothing) + + + +""" + Scale.size_area(f=default_continuous_sizes; minvalue=0.0, maxvalue=nothing) + +Similar to [`Scale.size_radius`](@ref), except that the values in the `size` aesthetic are +scaled to area rather than radius, before mapping to x-axis units or `Measure` units. +""" +function size_area(f::Function=Gadfly.current_theme().continuous_sizemap; minvalue=0.0, maxvalue=nothing) + (isa(minvalue, Measure) || isa(maxvalue, Measure)) && + throw(ArgumentError("Scale.size_area maps the size variable to absolute size via the function `f`. See `?Scale.size_radius` for more info.")) + return ContinuousSizeScale(f, area_transform, minvalue, maxvalue, nothing) +end + + + +function apply_scale(scale::ContinuousSizeScale, aess::Vector{Gadfly.Aesthetics}, datas::Gadfly.Data...) + + sdata = reduce(vcat, [d.size for d in datas if d.size≠nothing]) + + showvals = length(sdata)>1 + dmax = maximum(skipmissing(sdata)) + + strict_span = false + (smin, smax) = + if scale.maxvalue===nothing + promote(scale.minvalue, dmax) + else + strict_span = true + promote(scale.minvalue, scale.maxvalue) + end + ticks = Gadfly.optimize_ticks(smin, smax, strict_span=strict_span)[1] + Δ = scale.trans.f(ticks[end])-scale.trans.f(ticks[1]) + labels = scale.trans.label(ticks) + +# Transform ticks e.g. to areas/porportions/sizes + keyvals = if scale.maxvalue===nothing + showvals = false + scale.trans.f.(ticks.-smin) + else + p = (scale.trans.f.(ticks) .- scale.trans.f(ticks[1]))./Δ + scale.f.(p) + end + + labeldict = Dict(k=>v for (k,v) in zip(keyvals, labels)) + key_vals= OrderedDict(s=>i for (i,s) in enumerate(keyvals)) + labeler(xs) = [labeldict[x] for x in xs] + + for (aes, data) in zip(aess, datas) + data.size===nothing && continue + + ds = if scale.maxvalue === nothing + scale.trans.f.(data.size.-smin) + else + p = (scale.trans.f.(data.size).-scale.trans.f(ticks[1]))./Δ + scale.f.(p) + end + aes.size = ds + showvals && (aes.size_key_vals = key_vals) + aes.size_label = labeler + end +end + + + + diff --git a/src/theme.jl b/src/theme.jl index 127de127e..35bf517a7 100755 --- a/src/theme.jl +++ b/src/theme.jl @@ -88,6 +88,9 @@ $(FIELDS) "The function `f` in [`Scale.size_discrete2`](@ref).", discrete_sizemap, Function, Scale.default_discrete_sizes + "The function `f` in [`Scale.size_radius`](@ref) and [`Scale.size_area`](@ref).", + continuous_sizemap, Function, Scale.default_continuous_sizes + "Shapes of points in the point geometry. (Function in circle, square, diamond, cross, xcross, utriangle, dtriangle, star1, star2, hexagon, octagon, hline, vline, ltriangle, rtriangle)", point_shapes, Vector{Function}, [Shape.circle, Shape.square, Shape.diamond, Shape.cross, Shape.xcross, Shape.utriangle, Shape.dtriangle, Shape.star1, Shape.star2, diff --git a/test/testscripts/point_size_numerical.jl b/test/testscripts/point_size_numerical.jl index 819d59214..97ae974fb 100644 --- a/test/testscripts/point_size_numerical.jl +++ b/test/testscripts/point_size_numerical.jl @@ -1,5 +1,14 @@ using Gadfly -set_default_plot_size(6inch, 6inch) +set_default_plot_size(9inch, 3inch) -plot(x=rand(100), y=rand(100), size=rand(1:8, 100)./100, Geom.point) +y, size = [0.55, 0.7, 0.9, 0.99, 0.9], [0.4, 0.5, 0.6, 0.68, 0.63] +juliaclrs = Gadfly.parse_colorant(["forestgreen", "brown3", "mediumorchid"]) +theme = Theme(key_swatch_shape=Shape.circle, alphas=[0.1], discrete_highlight_color=identity) + +p1 = plot(x=[2,1.13,2.87], y=[3,1.5,1.5], size=[0.75], alpha=[0.8], + color=juliaclrs, Coord.cartesian(fixed=true)) +p2 = plot(x=1:5, y=y, size=10^5*size, Scale.size_radius(maxvalue=10^5), theme) +p3 = plot(x=1:5, y=y, size=10^5*size, Scale.size_area(maxvalue=10^5), theme) + +hstack(p1, p2, p3)