apply_series_recipe framework and boxplot; fix Gadfly scales for tick labels
This commit is contained in:
parent
e8ed79a6bb
commit
f3f29fb54f
@ -66,6 +66,8 @@ export
|
||||
scatter3d,
|
||||
scatter3d!,
|
||||
abline!,
|
||||
boxplot,
|
||||
boxplot!,
|
||||
|
||||
title!,
|
||||
xlabel!,
|
||||
@ -188,6 +190,8 @@ plot3d(args...; kw...) = plot(args...; kw..., linetype = :path3d)
|
||||
plot3d!(args...; kw...) = plot!(args...; kw..., linetype = :path3d)
|
||||
scatter3d(args...; kw...) = plot(args...; kw..., linetype = :scatter3d)
|
||||
scatter3d!(args...; kw...) = plot!(args...; kw..., linetype = :scatter3d)
|
||||
boxplot(args...; kw...) = plot(args...; kw..., linetype = :box)
|
||||
boxplot!(args...; kw...) = plot!(args...; kw..., linetype = :box)
|
||||
|
||||
|
||||
title!(s::AbstractString; kw...) = plot!(; title = s, kw...)
|
||||
|
||||
@ -11,7 +11,7 @@ const _3dTypes = [:path3d, :scatter3d, :surface, :wireframe]
|
||||
const _allTypes = vcat([
|
||||
:none, :line, :path, :steppre, :steppost, :sticks, :scatter,
|
||||
:heatmap, :hexbin, :hist, :hist2d, :hist3d, :density, :bar, :hline, :vline, :ohlc,
|
||||
:contour, :pie, :shape
|
||||
:contour, :pie, :shape, :box
|
||||
], _3dTypes)
|
||||
@compat const _typeAliases = KW(
|
||||
:n => :none,
|
||||
@ -38,6 +38,7 @@ const _allTypes = vcat([
|
||||
:shapes => :shape,
|
||||
:poly => :shape,
|
||||
:polygon => :shape,
|
||||
:boxplot => :box,
|
||||
)
|
||||
|
||||
like_histogram(linetype::Symbol) = linetype in (:hist, :density)
|
||||
|
||||
@ -315,17 +315,25 @@ function addGadflyTicksGuide(gplt, ticks, isx::Bool)
|
||||
replaceType(gplt.guides, gtype(ticks = collect(ticks)))
|
||||
|
||||
# set the ticks and the labels
|
||||
# Note: this is pretty convoluted, but I think it works. We set the ticks using Gadfly.Guide,
|
||||
# and then set the label function (wraps a dict lookup) through a continuous Gadfly.Scale.
|
||||
elseif ttype == :ticks_and_labels
|
||||
gtype = isx ? Gadfly.Guide.xticks : Gadfly.Guide.yticks
|
||||
replaceType(gplt.guides, gtype(ticks = collect(ticks[1])))
|
||||
|
||||
# TODO add xtick_label function (given tick, return label??)
|
||||
# Scale.x_discrete(; labels=nothing, levels=nothing, order=nothing)
|
||||
# # TODO add xtick_label function (given tick, return label??)
|
||||
# # Scale.x_discrete(; labels=nothing, levels=nothing, order=nothing)
|
||||
# filterGadflyScale(gplt, isx)
|
||||
# gfunc = isx ? Gadfly.Scale.x_discrete : Gadfly.Scale.y_discrete
|
||||
# labelmap = Dict(zip(ticks...))
|
||||
# labelfunc = val -> labelmap[val]
|
||||
# push!(gplt.scales, gfunc(levels = collect(ticks[1]), labels = labelfunc))
|
||||
|
||||
filterGadflyScale(gplt, isx)
|
||||
gfunc = isx ? Gadfly.Scale.x_discrete : Gadfly.Scale.y_discrete
|
||||
gfunc = isx ? Gadfly.Scale.x_continuous : Gadfly.Scale.y_continuous
|
||||
labelmap = Dict(zip(ticks...))
|
||||
labelfunc = val -> labelmap[val]
|
||||
push!(gplt.scales, gfunc(levels = collect(ticks[1]), labels = labelfunc))
|
||||
push!(gplt.scales, gfunc(labels = labelfunc))
|
||||
|
||||
else
|
||||
error("Invalid input for $(isx ? "xticks" : "yticks"): ", ticks)
|
||||
|
||||
@ -80,7 +80,7 @@ supportedArgs(::GadflyBackend) = [
|
||||
]
|
||||
supportedAxes(::GadflyBackend) = [:auto, :left]
|
||||
supportedTypes(::GadflyBackend) = [:none, :line, :path, :steppre, :steppost, :sticks,
|
||||
:scatter, :hist2d, :hexbin, :hist, :bar,
|
||||
:scatter, :hist2d, :hexbin, :hist, :bar, :box,
|
||||
:hline, :vline, :contour, :shape]
|
||||
supportedStyles(::GadflyBackend) = [:auto, :solid, :dash, :dot, :dashdot, :dashdotdot]
|
||||
supportedMarkers(::GadflyBackend) = vcat(_allMarkers, Shape)
|
||||
@ -167,7 +167,7 @@ supportedArgs(::PyPlotBackend) = [
|
||||
]
|
||||
supportedAxes(::PyPlotBackend) = _allAxes
|
||||
supportedTypes(::PyPlotBackend) = [:none, :line, :path, :steppre, :steppost, #:sticks,
|
||||
:scatter, :hist2d, :hexbin, :hist, :density, :bar,
|
||||
:scatter, :hist2d, :hexbin, :hist, :density, :bar, :box,
|
||||
:hline, :vline, :contour, :path3d, :scatter3d, :surface, :wireframe, :heatmap]
|
||||
supportedStyles(::PyPlotBackend) = [:auto, :solid, :dash, :dot, :dashdot]
|
||||
# supportedMarkers(::PyPlotBackend) = [:none, :auto, :rect, :ellipse, :diamond, :utriangle, :dtriangle, :cross, :xcross, :star5, :hexagon]
|
||||
|
||||
@ -219,6 +219,13 @@ function _add_series(plt::Plot, d::KW, ::Void, args...;
|
||||
delete!(di, k)
|
||||
end
|
||||
|
||||
# merge in plotarg_overrides
|
||||
plotarg_overrides = pop!(di, :plotarg_overrides, nothing)
|
||||
if plotarg_overrides != nothing
|
||||
merge!(plt.plotargs, plotarg_overrides)
|
||||
end
|
||||
# dumpdict(plt.plotargs, "pargs", true)
|
||||
|
||||
dumpdict(di, "Series $i")
|
||||
|
||||
_add_series(plt.backend, plt; di...)
|
||||
|
||||
@ -28,6 +28,59 @@ function _apply_recipe(d::KW, args...; issubplot=false, kw...)
|
||||
args
|
||||
end
|
||||
|
||||
|
||||
# -------------------------------------------------
|
||||
|
||||
"""
|
||||
`apply_series_recipe` should take a processed series KW dict and break it up
|
||||
into component parts. For example, a box plot is made up of `shape` for the
|
||||
boxes, `path` for the lines, and `scatter` for the outliers.
|
||||
|
||||
Returns a Vector{KW}.
|
||||
"""
|
||||
apply_series_recipe(d::KW, lt) = KW[d]
|
||||
|
||||
# -------------------------------------------------
|
||||
# Box Plot
|
||||
|
||||
function apply_series_recipe(d::KW, ::Type{Val{:box}})
|
||||
# dumpdict(d, "box before", true)
|
||||
# TODO: add scatter series with outliers
|
||||
|
||||
# create a list of shapes, where each shape is a single boxplot
|
||||
shapes = Shape[]
|
||||
d[:linetype] = :shape
|
||||
groupby = extractGroupArgs(d[:x])
|
||||
|
||||
for (i, glabel) in enumerate(groupby.groupLabels)
|
||||
|
||||
# filter y values, then compute quantiles
|
||||
q1,q2,q3,q4,q5 = quantile(d[:y][groupby.groupIds[i]], linspace(0,1,5))
|
||||
|
||||
# make the shape
|
||||
l, m, r = i - 0.3, i, i + 0.3
|
||||
xcoords = [
|
||||
m, l, r, m, m, NaN, # lower T
|
||||
l, l, r, r, l, NaN, # lower box
|
||||
l, l, r, r, l, NaN, # upper box
|
||||
m, l, r, m, m # upper T
|
||||
]
|
||||
ycoords = [
|
||||
q1, q1, q1, q1, q2, NaN, # lower T
|
||||
q2, q3, q3, q2, q2, NaN, # lower box
|
||||
q4, q3, q3, q4, q4, NaN, # upper box
|
||||
q5, q5, q5, q5, q4, NaN, # upper T
|
||||
]
|
||||
push!(shapes, Shape(xcoords, ycoords))
|
||||
end
|
||||
|
||||
d[:x], d[:y] = shape_coords(shapes)
|
||||
d[:plotarg_overrides] = KW(:xticks => (1:length(shapes), groupby.groupLabels))
|
||||
|
||||
KW[d]
|
||||
end
|
||||
|
||||
|
||||
# -------------------------------------------------
|
||||
|
||||
function rotate(x::Real, y::Real, θ::Real; center = (0,0))
|
||||
|
||||
@ -92,7 +92,7 @@ compute_xyz(x::Void, y::Void, z::Void) = error("x/y/z are all nothing!")
|
||||
# create n=max(mx,my) series arguments. the shorter list is cycled through
|
||||
# note: everything should flow through this
|
||||
function build_series_args(plt::AbstractPlot, kw::KW) #, idxfilter)
|
||||
x, y, z = map(a -> pop!(kw, a, nothing), (:x, :y, :z))
|
||||
x, y, z = map(sym -> pop!(kw, sym, nothing), (:x, :y, :z))
|
||||
if nothing == x == y == z
|
||||
return [], nothing, nothing
|
||||
end
|
||||
@ -101,18 +101,6 @@ function build_series_args(plt::AbstractPlot, kw::KW) #, idxfilter)
|
||||
ys, ymeta = convertToAnyVector(y, kw)
|
||||
zs, zmeta = convertToAnyVector(z, kw)
|
||||
|
||||
# if idxfilter != nothing
|
||||
# xs = filter_data(xs, idxfilter)
|
||||
# ys = filter_data(ys, idxfilter)
|
||||
# zs = filter_data(zs, idxfilter)
|
||||
# # # filter the data
|
||||
# # for sym in (:x, :y, :z)
|
||||
# # @show "before" sym, d[sym], idxfilter
|
||||
# # d[sym] = filter_data(get(d, sym, nothing), idxfilter)
|
||||
# # @show "after" sym, d[sym], idxfilter
|
||||
# # end
|
||||
# end
|
||||
|
||||
mx = length(xs)
|
||||
my = length(ys)
|
||||
mz = length(zs)
|
||||
@ -135,30 +123,11 @@ function build_series_args(plt::AbstractPlot, kw::KW) #, idxfilter)
|
||||
n = plt.n + i
|
||||
|
||||
dumpdict(d, "before getSeriesArgs")
|
||||
# @show numUncounted i n commandIndex convertSeriesIndex(plt, n)
|
||||
d = getSeriesArgs(plt.backend, getplotargs(plt, n), d, commandIndex, convertSeriesIndex(plt, n), n)
|
||||
dumpdict(d, "after getSeriesArgs")
|
||||
|
||||
# @show map(typeof, (xs[mod1(i,mx)], ys[mod1(i,my)], zs[mod1(i,mz)]))
|
||||
d[:x], d[:y], d[:z] = compute_xyz(xs[mod1(i,mx)], ys[mod1(i,my)], zs[mod1(i,mz)])
|
||||
# @show map(typeof, (d[:x], d[:y], d[:z]))
|
||||
|
||||
|
||||
# # NOTE: this should be handled by the time it gets here
|
||||
lt = d[:linetype]
|
||||
# if isa(d[:y], Surface)
|
||||
# if lt in (:contour, :heatmap, :surface, :wireframe)
|
||||
# z = d[:y]
|
||||
# d[:y] = 1:size(z,2)
|
||||
# d[:z] = z
|
||||
# end
|
||||
# end
|
||||
|
||||
# if haskey(d, :idxfilter)
|
||||
# idxfilter = pop!(d, :idxfilter)
|
||||
# d[:x] = d[:x][idxfilter]
|
||||
# d[:y] = d[:y][idxfilter]
|
||||
# end
|
||||
|
||||
# for linetype `line`, need to sort by x values
|
||||
if lt == :line
|
||||
@ -169,6 +138,11 @@ function build_series_args(plt::AbstractPlot, kw::KW) #, idxfilter)
|
||||
d[:linetype] = :path
|
||||
end
|
||||
|
||||
# special handling for missing x in box plot... all the same category
|
||||
if lt == :box && xs[mod1(i,mx)] == nothing
|
||||
d[:x] = ones(Int, length(d[:y]))
|
||||
end
|
||||
|
||||
# map functions to vectors
|
||||
if isa(d[:zcolor], Function)
|
||||
d[:zcolor] = map(d[:zcolor], d[:x])
|
||||
@ -177,14 +151,15 @@ function build_series_args(plt::AbstractPlot, kw::KW) #, idxfilter)
|
||||
d[:fillrange] = map(d[:fillrange], d[:x])
|
||||
end
|
||||
|
||||
# cleanup those fields that were used only for generating kw args
|
||||
# delete!(d, :dataframe)
|
||||
# for k in (:idxfilter, :numUncounted, :dataframe)
|
||||
# delete!(d, k)
|
||||
# end
|
||||
# now that we've processed a given series... optionally split into
|
||||
# multiple dicts through a recipe (for example, a box plot is split into component
|
||||
# parts... polygons, lines, and scatters)
|
||||
# note: we pass in a Val type (i.e. Val{:box}) so that we can dispatch on the linetype
|
||||
kwlist = apply_series_recipe(d, Val{lt})
|
||||
append!(ret, kwlist)
|
||||
|
||||
# add it to our series list
|
||||
push!(ret, d)
|
||||
# # add it to our series list
|
||||
# push!(ret, d)
|
||||
end
|
||||
|
||||
ret, xmeta, ymeta
|
||||
|
||||
@ -118,6 +118,7 @@ end
|
||||
|
||||
|
||||
nop() = nothing
|
||||
notimpl() = error("This has not been implemented yet")
|
||||
|
||||
get_mod(v::AVec, idx::Int) = v[mod1(idx, length(v))]
|
||||
get_mod(v::AMat, idx::Int) = size(v,1) == 1 ? v[1, mod1(idx, size(v,2))] : v[:, mod1(idx, size(v,2))]
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user