Skip to content

Commit

Permalink
Geom.line update
Browse files Browse the repository at this point in the history
  • Loading branch information
Mattriks committed Jun 21, 2020
1 parent fe95b77 commit 30ab135
Show file tree
Hide file tree
Showing 5 changed files with 126 additions and 63 deletions.
16 changes: 14 additions & 2 deletions docs/src/tutorial.md
Original file line number Diff line number Diff line change
Expand Up @@ -139,7 +139,7 @@ nothing # hide
Note that with the Array interface, extra elements must be included to specify the
axis labels, whereas with a DataFrame they default to the column names.

## Color
## Aesthetics

Let's do add something meaningful by mapping the color aesthetic.

Expand All @@ -161,8 +161,20 @@ Color scales in Gadfly by default are produced from perceptually uniform
colorspaces (LUV/LCHuv or LAB/LCHab), though it supports RGB, HSV, HLS, XYZ, and
converts arbitrarily between these. Color values can also be specified by most names common to CSS or X11, e.g. `"oldlace"` or `"aliceblue"`. The [full list of valid color names](http://juliagraphics.github.io/Colors.jl/stable/namedcolors/) is defined in the [Colors.jl library](http://juliagraphics.github.io/Colors.jl/stable/).

Color, and other aesthetics, can also be mapped by using arrays with group labels or functional types e.g. `["group label"]` or `[colorant"red"]`.
`["Group labels"]` are added to the key.

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()`.
```@example 1
y1 = [0.1, 0.26, missing, 0.5, 0.4, NaN, 0.48, 0.58, 0.83]
plot(x=1:9, y=y1, Geom.line, Geom.point,
color=["Item 1"], linestyle=[:dash], size=[3pt],
layer(x=1:10, y=rand(10), Geom.line, Geom.point,
color=["Item 2"], size=[5pt], shape=[Shape.square]),
layer(x=1:10, y=rand(10), color=[colorant"hotpink"],
linestyle=[[8pt, 3pt, 2pt, 3pt]], Geom.line))
```

All aesthetics 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

Expand Down
3 changes: 3 additions & 0 deletions src/Gadfly.jl
Original file line number Diff line number Diff line change
Expand Up @@ -1131,11 +1131,13 @@ const default_aes_scales = Dict{Symbol, Dict}(
),

:functional => Dict{Symbol, Any}(
:x => Scale.x_discrete(),
:z => Scale.z_func(),
:y => Scale.y_func(),
:shape => Scale.shape_identity(),
:size => Scale.size_identity(),
:color => Scale.color_identity(),
:linestyle => Scale.linestyle_identity()
),

:numerical => Dict{Symbol, Any}(
Expand Down Expand Up @@ -1221,6 +1223,7 @@ classify_data(data::T) where {T <: Base.Callable} = :functional
classify_data(data::AbstractArray) = :numerical
classify_data(data::Distribution) = :distribution
classify_data(data::Vector{<:Distribution}) = :distribution
classify_data(data::Vector{<:Vector{<:Measure}}) = :functional

function classify_data(data::AbstractArray{Any})
for val in data
Expand Down
95 changes: 43 additions & 52 deletions src/geom/line.jl
Original file line number Diff line number Diff line change
Expand Up @@ -20,10 +20,10 @@ end
Geom.line[(; preserve_order=false, order=2)]
Draw a line connecting the `x` and `y` coordinates. Optionally plot multiple
lines according to the `group` or `color` aesthetics. `order` controls whether
lines according to the `group`, `color` or `linestyle` aesthetics. `order` controls whether
the lines(s) are underneath or on top of other forms.
Set `preserve_order` to `:true` to *not* sort the points according to their
Set `preserve_order=true` to *not* sort the points according to their
position along the x axis, or use the equivalent [`Geom.path`](@ref) alias.
"""
const line = LineGeometry
Expand Down Expand Up @@ -103,65 +103,56 @@ element_aesthetics(::LineGeometry) = [:x, :y, :color, :group, :linestyle]

function Gadfly.Geom.render(geom::LineGeometry, theme::Gadfly.Theme, aes::Gadfly.Aesthetics)
Gadfly.assert_aesthetics_defined("Geom.line", aes, :x, :y)
Gadfly.assert_aesthetics_equal_length("Geom.line", aes, Geom.element_aesthetics(geom)...)
Gadfly.assert_aesthetics_equal_length("Geom.line", 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))
aes = Gadfly.inherit(aes, default_aes)

# Point order:
p = 1:length(aes.x)
!geom.preserve_order && (p = sortperm(aes.x))
aes_x, aes_y, aes_color, aes_g = aes.x[p], aes.y[p], aes.color[p], aes.group[p]
aes_linestyle = aes.linestyle[p]
default_aes.group = IndirectArray([1])
default_aes.color = [theme.default_color]
default_aes.linestyle = theme.line_style[1:1]
aes = inherit(aes, default_aes)

# Render lines, using multivariate groupings:
XT, YT = eltype(aes.x), eltype(aes.y)
GT, CT, LST = Int, eltype(aes.color), eltype(aes.linestyle)
groups = collect(Tuple{GT, CT, LST}, Compose.cyclezip(aes.group, aes.color, aes.linestyle))
ugroups = unique(groups)
nugroups = length(ugroups)
# Recycle groups
(1 .< length(groups) .< length(aes.x)) && (groups = [b for (a, b) in zip(aes.x, cycle(groups))])

# Find the aesthetic with the most levels:
aesv = [aes_g, aes_color, aes_linestyle]
i1 = argmax([length(unique(a)) for a in aesv])
aes_maxlvls = aesv[i1]

# Concrete values?:
cf = Gadfly.isconcrete.(aes_x) .& Gadfly.isconcrete.(aes_y)
fcf = .!cf
ulvls = unique(aes_maxlvls[fcf])
aes_concrete = zeros(Int, length(cf))
for g in ulvls
i = aes_maxlvls.==g
aes_concrete[i] = cumsum(fcf[i])
# Point order
aes_x, aes_y, zgroups = if !geom.preserve_order
p = sortperm(aes.x)
aes.x[p], aes.y[p], (nugroups==1 ? ugroups : groups[p])
else
aes.x, aes.y, (nugroups==1 ? ugroups : groups)
end

aes_x, aes_y, aes_color, aes_g = aes_x[cf], aes_y[cf], aes_color[cf], aes_g[cf]
aes_concrete, aes_linestyle = aes_concrete[cf], aes_linestyle[cf]

# Render lines, using multivariate groupings:
XT, YT, CT, GT, CNT = eltype(aes_x), eltype(aes_y), eltype(aes_color), eltype(aes_g), eltype(aes_concrete)
LST = eltype(aes_linestyle)
groups = collect((Tuple{GT, CT, LST, CNT}), zip(aes_g, aes_color, aes_linestyle, aes_concrete))
ug = unique(groups)

n = length(ug)
lines = Vector{Vector{Tuple{XT,YT}}}(undef, n)
line_colors = Vector{CT}(undef, n)
line_styles = Vector{LST}(undef, n)

gs = Vector{GT}(undef, nugroups)
cs = Vector{CT}(undef, nugroups)
lss = Vector{LST}(undef, nugroups)
lines = Vector{Vector{Tuple{XT,YT}}}(undef, nugroups)
linestyle_palette_length = length(theme.line_style)
for (k,g) in enumerate(ug)
i = groups.==[g]
lines[k] = collect(Tuple{XT,YT}, zip(aes_x[i], aes_y[i]))
line_colors[k] = first(aes_color[i])
line_styles[k] = mod1(first(aes_linestyle[i]), linestyle_palette_length)
if nugroups==1
gs[1], cs[1], lss[1] = zgroups[1]
lines[1] = collect(Tuple{XT, YT}, zip(aes_x, aes_y))
elseif nugroups>1
for (k,g) in enumerate(ugroups)
i = zgroups.==[g]
gs[k], cs[k], lss[k] = g
lines[k] = collect(Tuple{XT,YT}, zip(aes_x[i], aes_y[i]))
end
end

linestyles = Gadfly.get_stroke_vector.(theme.line_style[line_styles])
classes = svg_color_class_from_label.(aes.color_label(line_colors))
linestyles = Gadfly.get_stroke_vector.(LST<:Int ?
theme.line_style[mod1.(lss, linestyle_palette_length)] : lss)

classes = svg_color_class_from_label.(aes.color_label(cs))
ctx = context(order=geom.order)
ctx = compose!(ctx, (context(), Compose.line(lines, geom.tag),
stroke(line_colors),
strokedash(linestyles),
svgclass(classes)), svgclass("geometry"))
stroke(cs), strokedash(linestyles),
svgclass(classes)), svgclass("geometry"))

return compose!(ctx, fill(nothing), linewidth(theme.line_width))


end
4 changes: 2 additions & 2 deletions src/scale.jl
Original file line number Diff line number Diff line change
Expand Up @@ -361,8 +361,6 @@ is set by `Theme(alphas=[])`.
alpha_discrete(; labels=nothing, levels=nothing, order=nothing) =
DiscreteScale([:alpha], labels=labels, levels=levels, order=order)

@doc type_discrete_docstr("linestyle") linestyle_discrete(; labels=nothing, levels=nothing, order=nothing) =
DiscreteScale([:linestyle], labels=labels, levels=levels, order=order)


function apply_scale(scale::DiscreteScale, aess::Vector{Gadfly.Aesthetics}, datas::Gadfly.Data...)
Expand Down Expand Up @@ -808,6 +806,8 @@ shape_identity() = IdentityScale(:shape)
size_identity() = IdentityScale(:size)


linestyle_identity() = IdentityScale(:linestyle)

include("scale/scales.jl")


Expand Down
71 changes: 64 additions & 7 deletions src/scale/scales.jl
Original file line number Diff line number Diff line change
Expand Up @@ -33,20 +33,25 @@ size_discrete2(f::Function=Gadfly.current_theme().discrete_sizemap; levels=nothi
DiscreteSizeScale(f, levels, order, preserve_order)


function Scale.apply_scale(scale::DiscreteSizeScale, aess::Vector{Gadfly.Aesthetics}, datas::Gadfly.Data...)
function apply_scale(scale::DiscreteSizeScale, aess::Vector{Gadfly.Aesthetics}, datas::Gadfly.Data...)

d = []
for (aes, data) in zip(aess, datas)
data.size === nothing && continue
append!(d, skipmissing(data.size))
SZT = eltype(data.size)
if SZT<:Measure
aes.size = data.size
else
append!(d, skipmissing(data.size))
end
end
levelset = unique(d)
isempty(levelset) && return

if scale.levels == nothing
scale_levels = [levelset...]
scale.preserve_order || sort!(scale_levels)
scale_levels = if scale.levels===nothing
scale.preserve_order ? [levelset...] : sort!([levelset...])
else
scale_levels = scale.levels
scale.levels
end
scale.order == nothing || permute!(scale_levels, scale.order)

Expand All @@ -57,7 +62,8 @@ function Scale.apply_scale(scale::DiscreteSizeScale, aess::Vector{Gadfly.Aesthet
key_vals = OrderedDict(s=>i for (i,s) in enumerate(sizes))

for (aes, data) in zip(aess, datas)
data.size === nothing && continue
SZT = eltype(data.size)
(data.size===nothing || SZT<:Measure) && continue
ds = discretize([d for d in skipmissing(data.size)], scale_levels)
vals = sizes[ds.index]
aes.size = discretize_make_ia(vals, sizes)
Expand Down Expand Up @@ -161,4 +167,55 @@ end



struct DiscreteLinestyleScale <: Gadfly.ScaleElement
labels::Union{Nothing, Function}
levels::Union{Nothing, AbstractVector}
order::Union{Nothing, AbstractVector}
preserve_order::Bool
end
#DiscreteLinestyleScale(f; levels=nothing, order=nothing, preserve_order=true) = DiscreteLinestyleScale(f, levels, order, preserve_order)

element_aesthetics(scale::DiscreteLinestyleScale) = [:linestyle]

"""
linestyle_discrete(; levels=nothing, order=nothing, preserve_order=true)
A discrete scale that maps the categorical values in the `linestyle`
aesthetic to the values in `Theme().line_style`.
`levels` are the categorical levels, and level order will be respected. `order` is
a vector of integers giving a permutation of the levels default order. If
`preserve_order` is `true` levels are ordered as they appear in the data.
"""
linestyle_discrete(;labels=nothing, levels=nothing, order=nothing, preserve_order=true) =
DiscreteLinestyleScale(labels, levels, order, preserve_order)

function apply_scale(scale::DiscreteLinestyleScale, aess::Vector{Gadfly.Aesthetics}, datas::Gadfly.Data...)

d = []
for (aes, data) in zip(aess, datas)
data.linestyle===nothing && continue
LST = eltype(data.linestyle)
if LST<:Vector{<:Measure} || LST<:Symbol
aes.linestyle = data.linestyle
else
append!(d, skipmissing(data.linestyle))
end
end
levelset = unique(d)
isempty(levelset) && return

scale_levels = if scale.levels===nothing
scale.preserve_order ? [levelset...] : sort!([levelset...])
else
scale.levels
end
scale.order===nothing || permute!(scale_levels, scale.order)

for (aes, data) in zip(aess, datas)
LST = eltype(data.linestyle)
(data.linestyle===nothing || LST<:Vector{<:Measure} || LST<:Symbol) && continue
ds = discretize(collect(skipmissing(data.linestyle)), scale_levels)
aes.linestyle = discretize_make_ia(ds.index)
end
end

0 comments on commit 30ab135

Please sign in to comment.