diff --git a/REQUIRE b/REQUIRE index 7baf1a2fa..cd128d945 100644 --- a/REQUIRE +++ b/REQUIRE @@ -16,7 +16,7 @@ Loess Showoff 0.0.3 StatsBase Juno -IndirectArrays 0.4.1 +IndirectArrays 0.4.2 Missings # Gadfly doesn't use WeakRefString directly but because of a bug in Julia 0.6.2, Gadfly # will trigger a ambiguity error between WeakRefString methods. A temporary method was diff --git a/src/aesthetics.jl b/src/aesthetics.jl index 284ce67e2..88bb0d8db 100644 --- a/src/aesthetics.jl +++ b/src/aesthetics.jl @@ -235,7 +235,6 @@ function concat(aess::Aesthetics...) mv == nothing ? mu : max(mu, mv)) else - cat_aes_var!(getfield(cataes, var), getfield(aes, var)) setfield!(cataes, var, cat_aes_var!(getfield(cataes, var), getfield(aes, var))) end @@ -288,6 +287,12 @@ function cat_aes_var!(a::AbstractArray{T}, b::AbstractArray{U}) where {T, U} return ab end +function cat_aes_var!(xs::IndirectArray{T,1}, ys::IndirectArray{S,1}) where {T, S} + TS = promote_type(T, S) + return append!(IndirectArray(xs.index, Array{TS}(xs.values)), + IndirectArray(ys.index, Array{TS}(ys.values))) +end + # Summarizing aesthetics # Produce a matrix of Aesthetic or Data objects partitioning the original @@ -320,8 +325,10 @@ function by_xy_group(aes::T, xgroup, ygroup, xgroup === nothing && ygroup === nothing && return aes_grid - make_pooled_array(::Type{IndirectArray{T,R,N,RA}}, arr::AbstractArray) where {T,R,N,RA} = - IndirectArray(convert(Array{T}, arr)) + function make_pooled_array(::Type{IndirectArray{T,N,A,V}}, arr::AbstractArray) where {T,N,A,V} + uarr = unique(arr) + return IndirectArray(A(indexin(arr, uarr)), V(uarr)) + end make_pooled_array(::Type{IndirectArray{T,R,N,RA}}, arr::IndirectArray{T,R,N,RA}) where {T,R,N,RA} = arr diff --git a/src/geom/beeswarm.jl b/src/geom/beeswarm.jl index 1136839ae..3073cb10b 100644 --- a/src/geom/beeswarm.jl +++ b/src/geom/beeswarm.jl @@ -16,7 +16,7 @@ function render(geom::BeeswarmGeometry, theme::Gadfly.Theme, aes::Gadfly.Aesthet Gadfly.assert_aesthetics_equal_length("Geom.point", aes, element_aesthetics(geom)...) default_aes = Gadfly.Aesthetics() - default_aes.color = IndirectArray(RGBA{Float32}[theme.default_color]) + default_aes.color = discretize_make_ia(RGBA{Float32}[theme.default_color]) default_aes.size = Measure[theme.point_size] aes = inherit(aes, default_aes) padding = 1.0mm diff --git a/src/geom/boxplot.jl b/src/geom/boxplot.jl index 5d9c71431..ba37e968f 100644 --- a/src/geom/boxplot.jl +++ b/src/geom/boxplot.jl @@ -27,7 +27,7 @@ function render(geom::BoxplotGeometry, theme::Gadfly.Theme, aes::Gadfly.Aestheti :upper_hinge, :upper_fence, :outliers) default_aes = Gadfly.Aesthetics() - default_aes.color = IndirectArray(RGB{Float32}[theme.default_color]) + default_aes.color = discretize_make_ia(RGB{Float32}[theme.default_color]) default_aes.x = Float64[0.5] aes = inherit(aes, default_aes) diff --git a/src/geom/errorbar.jl b/src/geom/errorbar.jl index 9035d578e..206a6a7f4 100644 --- a/src/geom/errorbar.jl +++ b/src/geom/errorbar.jl @@ -54,7 +54,7 @@ function render(geom::YErrorBarGeometry, theme::Gadfly.Theme, aes::Gadfly.Aesthe element_aesthetics(geom)...) default_aes = Gadfly.Aesthetics() - default_aes.color = IndirectArray(RGB{Float32}[theme.default_color]) + default_aes.color = discretize_make_ia(RGB{Float32}[theme.default_color]) aes = inherit(aes, default_aes) caplen = theme.errorbar_cap_length/2 ttc, teb, tbc = subtags(geom.tag, :top_cap, :error_bar, :bottom_cap) @@ -95,7 +95,7 @@ function render(geom::XErrorBarGeometry, theme::Gadfly.Theme, aes::Gadfly.Aesthe colored = aes.color != nothing default_aes = Gadfly.Aesthetics() - default_aes.color = IndirectArray(RGB{Float32}[theme.default_color]) + default_aes.color = discretize_make_ia(RGB{Float32}[theme.default_color]) aes = inherit(aes, default_aes) caplen = theme.errorbar_cap_length/2 tlc, teb, trc = subtags(geom.tag, :left_cap, :error_bar, :right_cap) diff --git a/src/geom/hexbin.jl b/src/geom/hexbin.jl index def833507..ea921b692 100644 --- a/src/geom/hexbin.jl +++ b/src/geom/hexbin.jl @@ -16,7 +16,7 @@ element_aesthetics(geom::HexagonalBinGeometry) = [:x, :y, :xsize, :ysize, :color function render(geom::HexagonalBinGeometry, theme::Gadfly.Theme, aes::Gadfly.Aesthetics) default_aes = Gadfly.Aesthetics() - default_aes.color = IndirectArray(RGB{Float32}[theme.default_color]) + default_aes.color = discretize_make_ia(RGB{Float32}[theme.default_color]) default_aes.xsize = [1.0] default_aes.ysize = [1.0] aes = inherit(aes, default_aes) diff --git a/src/geom/line.jl b/src/geom/line.jl index 2b5fa2a36..68c4ff4ea 100644 --- a/src/geom/line.jl +++ b/src/geom/line.jl @@ -58,7 +58,7 @@ function render(geom::LineGeometry, theme::Gadfly.Theme, aes::Gadfly.Aesthetics) element_aesthetics(geom)...) default_aes = Gadfly.Aesthetics() - default_aes.color = IndirectArray(RGBA{Float32}[theme.default_color]) + default_aes.color = discretize_make_ia(RGBA{Float32}[theme.default_color]) aes = inherit(aes, default_aes) ctx = context(order=geom.order) diff --git a/src/geom/point.jl b/src/geom/point.jl index 664e87652..e51ae9f5e 100644 --- a/src/geom/point.jl +++ b/src/geom/point.jl @@ -25,7 +25,7 @@ function render(geom::PointGeometry, theme::Gadfly.Theme, aes::Gadfly.Aesthetics default_aes = Gadfly.Aesthetics() default_aes.shape = Function[Shape.circle] - default_aes.color = IndirectArray(RGBA{Float32}[theme.default_color]) + default_aes.color = discretize_make_ia(RGBA{Float32}[theme.default_color]) default_aes.size = Measure[theme.point_size] aes = inherit(aes, default_aes) diff --git a/src/geom/polygon.jl b/src/geom/polygon.jl index d034c113a..f121b5da0 100644 --- a/src/geom/polygon.jl +++ b/src/geom/polygon.jl @@ -38,7 +38,7 @@ function render(geom::PolygonGeometry, theme::Gadfly.Theme, Gadfly.assert_aesthetics_defined("Geom.polygon", aes, :x, :y) default_aes = Gadfly.Aesthetics() - default_aes.color = IndirectArray(RGBA{Float32}[theme.default_color]) + default_aes.color = discretize_make_ia(RGBA{Float32}[theme.default_color]) aes = inherit(aes, default_aes) ctx = context(order=geom.order) diff --git a/src/geom/rectbin.jl b/src/geom/rectbin.jl index 1e11b58c5..98d2b2934 100644 --- a/src/geom/rectbin.jl +++ b/src/geom/rectbin.jl @@ -42,7 +42,7 @@ element_aesthetics(::RectangularBinGeometry) = function render(geom::RectangularBinGeometry, theme::Gadfly.Theme, aes::Gadfly.Aesthetics) default_aes = Gadfly.Aesthetics() - default_aes.color = IndirectArray(RGBA{Float32}[theme.default_color]) + default_aes.color = discretize_make_ia(RGBA{Float32}[theme.default_color]) aes = inherit(aes, default_aes) Gadfly.assert_aesthetics_defined("RectangularBinGeometry", aes, :xmin, :xmax, :ymin, :ymax) diff --git a/src/geom/ribbon.jl b/src/geom/ribbon.jl index 7971d4e51..f9d854441 100644 --- a/src/geom/ribbon.jl +++ b/src/geom/ribbon.jl @@ -17,7 +17,7 @@ function render(geom::RibbonGeometry, theme::Gadfly.Theme, aes::Gadfly.Aesthetic element_aesthetics(geom)...) default_aes = Gadfly.Aesthetics() - default_aes.color = IndirectArray(RGB{Float32}[theme.default_color]) + default_aes.color = discretize_make_ia(RGB{Float32}[theme.default_color]) aes = inherit(aes, default_aes) aes_x, aes_ymin, aes_ymax = concretize(aes.x, aes.ymin, aes.ymax) diff --git a/src/geometry.jl b/src/geometry.jl index 9fdc8532e..f196513f1 100644 --- a/src/geometry.jl +++ b/src/geometry.jl @@ -14,7 +14,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 + concretize, discretize_make_ia import IterTools: chain, distinct, takestrict import Compat.Iterators: cycle, product, repeated diff --git a/src/misc.jl b/src/misc.jl index d6a2ec24e..ca8a58962 100644 --- a/src/misc.jl +++ b/src/misc.jl @@ -392,3 +392,27 @@ function trim_zip(xs...) zip([length(x) == mn ? x : x[1:mn] for x in xs]...) end end + +# Convenience constructors of IndirectArrays +discretize_make_ia(values::AbstractVector, levels) = + IndirectArray(Array{UInt8}(indexin(values, levels)), levels) +discretize_make_ia(values::AbstractVector) = discretize_make_ia(values, unique(values)) +discretize_make_ia(values::AbstractVector, ::Void) = discretize_make_ia(values) + +discretize_make_ia(values::IndirectArray) = values +discretize_make_ia(values::IndirectArray, ::Void) = values + +discretize_make_ia(values::CategoricalArray) = + discretize_make_ia(values, intersect(push!(levels(values), missing), unique(values))) +discretize_make_ia(values::CategoricalArray, ::Void) = discretize_make_ia(values) +function discretize_make_ia(values::CategoricalArray{T}, levels::Vector) where {T} + mapping = coalesce.(indexin(CategoricalArrays.index(values.pool), levels), 0) + unshift!(mapping, coalesce(findfirst(ismissing, levels), 0)) + index = [mapping[x+1] for x in values.refs] + any(iszero, index) && throw(ArgumentError("values not in levels encountered")) + return IndirectArray(index, convert(Vector{T},levels)) +end +function discretize_make_ia(values::CategoricalArray{T}, levels::CategoricalVector{T}) where T + _levels = map!(t -> ismissing(t) ? t : get(t), Vector{T}(length(levels)), levels) + discretize_make_ia(values, _levels) +end diff --git a/src/scale.jl b/src/scale.jl index a1113750e..f8cc51ace 100644 --- a/src/scale.jl +++ b/src/scale.jl @@ -10,7 +10,7 @@ using Showoff using IndirectArrays using CategoricalArrays -import Gadfly: element_aesthetics, isconcrete, concrete_length +import Gadfly: element_aesthetics, isconcrete, concrete_length, discretize_make_ia import Distributions: Distribution include("color_misc.jl") @@ -254,53 +254,6 @@ function apply_scale_typed!(ds, field, scale::ContinuousScale) end end -# Reorder the levels of a pooled data array -function reorder_levels(da::IndirectArray, order::AbstractVector) - level_values = da.values - length(order) != length(level_values) && - error("Discrete scale order is not of the same length as the data's levels.") - permute!(level_values, order) - return IndirectArray(da, level_values) -end - -discretize_make_ia(values::Vector) = IndirectArray(values) -discretize_make_ia(values::Vector, ::Void) = IndirectArray(values) -discretize_make_ia(values::Vector, levels) = - IndirectArray(map(t -> findfirst(levels, t), values), levels) - -discretize_make_ia(values::DataArray) = IndirectArray(values) -discretize_make_ia(values::DataArray, ::Void) = IndirectArray(values) -discretize_make_ia(values::DataArray, levels) = - IndirectArray(convert(DataArray{eltype(levels)}, values), levels) - -discretize_make_ia(values::Range) = IndirectArray(collect(values)) -discretize_make_ia(values::Range, ::Void) = IndirectArray(collect(values)) -discretize_make_ia(values::Range, levels) = IndirectArray(collect(values), levels) - -discretize_make_ia(values::IndirectArray) = values -discretize_make_ia(values::IndirectArray, ::Void) = values -discretize_make_ia(values::IndirectArray, levels) = IndirectArray(values, levels) - -discretize_make_ia(values::CategoricalArray) = - discretize_make_ia(values, intersect(push!(levels(values), missing), unique(values))) -discretize_make_ia(values::CategoricalArray, ::Void) = discretize_make_ia(values) -function discretize_make_ia(values::CategoricalArray{T}, levels::Vector) where {T} - mapping = coalesce.(indexin(CategoricalArrays.index(values.pool), levels), 0) - unshift!(mapping, coalesce(findfirst(ismissing, levels), 0)) - index = [mapping[x+1] for x in values.refs] - any(iszero, index) && throw(ArgumentError("values not in levels encountered")) - return IndirectArray(index, convert(Vector{T},levels)) -end -function discretize_make_ia(values::CategoricalArray{T}, levels::CategoricalVector{T}) where T - _levels = map!(t -> ismissing(t) ? t : get(t), Vector{T}(length(levels)), levels) - discretize_make_ia(values, _levels) -end - -# These methods convert WeakRefStringArrays to Vector{String} and shouldn't really be necessary -# since it has been decided that WeakRefStrings shouldn't be used externally anymore -discretize_make_ia(s::AbstractArray{<:AbstractString}) = discretize_make_ia(Vector{String}(s)) -discretize_make_ia(s::AbstractArray{<:AbstractString}, levels) = discretize_make_ia(Vector{String}(s), levels) - function discretize(values, levels=nothing, order=nothing, preserve_order=true) if levels == nothing if preserve_order @@ -317,7 +270,7 @@ function discretize(values, levels=nothing, order=nothing, preserve_order=true) end if order != nothing - return reorder_levels(da, order) + return discretize_make_ia(da, da.values[order]) else return da end @@ -374,7 +327,7 @@ function apply_scale(scale::DiscreteScale, aess::Vector{Gadfly.Aesthetics}, data getfield(data, var) === nothing && continue disc_data = discretize(getfield(data, var), scale.levels, scale.order) - setfield!(aes, var, IndirectArray([round(Int64,x) for x in disc_data.index])) + setfield!(aes, var, discretize_make_ia(Int64.(disc_data.index))) # The leveler for discrete scales is a closure over the discretized data. if scale.labels === nothing @@ -428,7 +381,7 @@ function apply_scale(scale::IdentityColorScale, aess::Vector{Gadfly.Aesthetics}, datas::Gadfly.Data...) for (aes, data) in zip(aess, datas) data.color === nothing && continue - aes.color = IndirectArray(data.color) + aes.color = discretize_make_ia(data.color) aes.color_key_colors = Dict() end end @@ -525,7 +478,7 @@ function apply_scale(scale::DiscreteColorScale, colorvals = colors[ds.index] - colored_ds = IndirectArray(colorvals, colors) + colored_ds = discretize_make_ia(colorvals, colors) aes.color = colored_ds aes.color_label = labeler diff --git a/src/statistics.jl b/src/statistics.jl index 6454ea6d1..f176246a1 100644 --- a/src/statistics.jl +++ b/src/statistics.jl @@ -15,7 +15,7 @@ using CoupledFields # It is registered in METADATA.jl using IndirectArrays import Gadfly: Scale, Coord, input_aesthetics, output_aesthetics, - default_scales, isconcrete, setfield! + default_scales, isconcrete, setfield!, discretize_make_ia import KernelDensity # import Distributions: Uniform, Distribution, qqbuild import IterTools: chain, distinct @@ -401,7 +401,7 @@ function apply_statistic(stat::HistogramStatistic, end end - aes.color = IndirectArray(colors) + aes.color = discretize_make_ia(colors) end getfield(aes, viewminvar) === nothing && setfield!(aes, viewminvar, 0.0) @@ -528,7 +528,7 @@ function apply_statistic(stat::DensityStatistic, push!(colors, c) end end - aes.color = IndirectArray(colors) + aes.color = discretize_make_ia(colors) end aes.y_label = Gadfly.Scale.identity_formatter end @@ -667,13 +667,13 @@ function apply_statistic(stat::Histogram2DStatistic, if x_categorial aes.xmin, aes.xmax = barminmax(aes.x, false) - aes.x = IndirectArray(aes.x) + aes.x = discretize_make_ia(aes.x) aes.pad_categorical_x = Nullable(false) end if y_categorial aes.ymin, aes.ymax = barminmax(aes.y, false) - aes.y = IndirectArray(aes.y) + aes.y = discretize_make_ia(aes.y) aes.pad_categorical_y = Nullable(false) end @@ -986,8 +986,8 @@ function apply_statistic(stat::BoxplotStatistic, end if aes.color !== nothing - aes.color = IndirectArray([c for (x, c) in groups], - filter(!ismissing, aes.color.values)) + aes.color = discretize_make_ia([c for (x, c) in groups], + filter(!ismissing, aes.color.values)) end return @@ -1067,12 +1067,12 @@ function apply_statistic(stat::BoxplotStatistic, end if isa(aes_x, IndirectArray) - aes.x = IndirectArray(aes.x, aes_x.values) + aes.x = discretize_make_ia(aes.x, aes_x.values) end if aes.color !== nothing - aes.color = IndirectArray(RGB{Float32}[c for (x, c) in keys(groups)], - aes.color.levels) + aes.color = discretize_make_ia(RGB{Float32}[c for (x, c) in keys(groups)], + aes.color.values) end nothing @@ -1156,7 +1156,7 @@ function apply_statistic(stat::SmoothStatistic, end if !(aes.color===nothing) - aes.color = IndirectArray(colors) + aes.color = discretize_make_ia(colors) end end @@ -1343,7 +1343,7 @@ function apply_statistic(stat::FunctionStatistic, aes.color[1+(i-1)*stat.num_samples:i*stat.num_samples] = func_color[i] groups[1+(i-1)*stat.num_samples:i*stat.num_samples] = i end - aes.group = IndirectArray(groups) + aes.group = discretize_make_ia(groups) elseif length(aes.y) > 1 && haskey(scales, :color) data = Gadfly.Data() data.color = Array{AbstractString}(length(aes.y) * stat.num_samples) @@ -1354,7 +1354,7 @@ function apply_statistic(stat::FunctionStatistic, groups[1+(i-1)*stat.num_samples:i*stat.num_samples] = i end Scale.apply_scale(scales[:color], [aes], data) - aes.group = IndirectArray(groups) + aes.group = discretize_make_ia(groups) end data = Gadfly.Data() @@ -1417,7 +1417,7 @@ function apply_statistic(stat::ContourStatistic, stat_levels = typeof(stat.levels) <: Function ? stat.levels(zs) : stat.levels - groups = IndirectArray(Int[]) + groups = discretize_make_ia(Int[]) group = 0 for level in Contour.levels(Contour.contours(xs, ys, zs, stat_levels)) for line in Contour.lines(level) @@ -1661,7 +1661,7 @@ function apply_statistic(stat::BinMeanStatistic, push!(colors, c) end end - aes.color = IndirectArray(colors) + aes.color = discretize_make_ia(colors) end end @@ -1873,7 +1873,7 @@ function Gadfly.Stat.apply_statistic(stat::EllipseStatistic, end end - aes.group = IndirectArray(levels) + aes.group = discretize_make_ia(levels) colorflag && (aes.color = colors) aes.x = ellipse_x aes.y = ellipse_y diff --git a/test/testscripts/colored_boxplot.jl b/test/testscripts/colored_boxplot.jl new file mode 100644 index 000000000..e938a45f2 --- /dev/null +++ b/test/testscripts/colored_boxplot.jl @@ -0,0 +1,6 @@ +using Gadfly, RDatasets + +set_default_plot_size(6inch, 3inch) + +mpg = dataset("ggplot2","mpg") +plot(mpg, x=:Class, y=:Hwy, Geom.boxplot, color=:Class) \ No newline at end of file diff --git a/test/testscripts/grouped_ellipse.jl b/test/testscripts/grouped_ellipse.jl new file mode 100644 index 000000000..b4af94536 --- /dev/null +++ b/test/testscripts/grouped_ellipse.jl @@ -0,0 +1,19 @@ +using RDatasets, Gadfly +Gadfly.set_default_plot_size(14cm, 8cm) + +D = dataset("datasets","faithful") +D[:g] = D[:Eruptions].>3.0 + +coord = Coord.cartesian(ymin=35, ymax=100) + +pa = plot(D, coord, + x=:Eruptions, y=:Waiting, group=:g, + Geom.point, Geom.ellipse +) +pb = plot(D, coord, + x=:Eruptions, y=:Waiting, color=:g, + Geom.point, Geom.ellipse, + layer(Geom.ellipse(levels=[0.99]), style(line_style=:dot)), + style(key_position=:none), Guide.ylabel(nothing) +) +hstack(pa,pb) \ No newline at end of file diff --git a/test/testscripts/issue1125.jl b/test/testscripts/issue1125.jl new file mode 100644 index 000000000..e077043ec --- /dev/null +++ b/test/testscripts/issue1125.jl @@ -0,0 +1,23 @@ +using Gadfly, RDatasets + +set_default_plot_size(6inch, 3inch) + +iris = dataset("datasets", "iris") +sp = unique(iris[:Species]) +Dhl = DataFrame(yint=[3.0, 4.0, 2.5, 3.5, 2.5, 4.0], Species=repeat(sp, inner=[2]) ) + +function plotxg(scalexg::Scale.DiscreteScale) + plot(iris, xgroup=:Species, + Geom.subplot_grid( + layer( x=:SepalLength, y=:SepalWidth, Geom.point), + layer(Dhl, xgroup=:Species, yintercept=:yint, Geom.hline(color="red", style=:dot) ), + ), + scalexg, + Theme(plot_padding=[0mm]) +) +end + +scales = [Scale.xgroup(), Scale.xgroup(levels=["virginica","setosa","versicolor"]), Scale.xgroup(order=[3,1,2])] +plots = plotxg.(scales) + +vstack(plots...)