working on series reorg

This commit is contained in:
Thomas Breloff 2016-03-17 16:52:09 -04:00
parent 175ce3613a
commit 2ab0dc20d7
2 changed files with 155 additions and 178 deletions

View File

@ -1,16 +1,16 @@
type CurrentPlot type CurrentPlot
nullableplot::Nullable{AbstractPlot} nullableplot::Nullable{AbstractPlot}
end end
const CURRENT_PLOT = CurrentPlot(Nullable{AbstractPlot}()) const CURRENT_PLOT = CurrentPlot(Nullable{AbstractPlot}())
isplotnull() = isnull(CURRENT_PLOT.nullableplot) isplotnull() = isnull(CURRENT_PLOT.nullableplot)
function current() function current()
if isplotnull() if isplotnull()
error("No current plot/subplot") error("No current plot/subplot")
end end
get(CURRENT_PLOT.nullableplot) get(CURRENT_PLOT.nullableplot)
end end
current(plot::AbstractPlot) = (CURRENT_PLOT.nullableplot = Nullable(plot)) current(plot::AbstractPlot) = (CURRENT_PLOT.nullableplot = Nullable(plot))
@ -32,9 +32,9 @@ convertSeriesIndex(plt::Plot, n::Int) = n
The main plot command. Use `plot` to create a new plot object, and `plot!` to add to an existing one: The main plot command. Use `plot` to create a new plot object, and `plot!` to add to an existing one:
``` ```
plot(args...; kw...) # creates a new plot window, and sets it to be the current plot(args...; kw...) # creates a new plot window, and sets it to be the current
plot!(args...; kw...) # adds to the `current` plot!(args...; kw...) # adds to the `current`
plot!(plotobj, args...; kw...) # adds to the plot `plotobj` plot!(plotobj, args...; kw...) # adds to the plot `plotobj`
``` ```
There are lots of ways to pass in data, and lots of keyword arguments... just try it and it will likely work as expected. There are lots of ways to pass in data, and lots of keyword arguments... just try it and it will likely work as expected.
@ -43,105 +43,111 @@ When you pass in matrices, it splits by columns. See the documentation for more
# this creates a new plot with args/kw and sets it to be the current plot # this creates a new plot with args/kw and sets it to be the current plot
function plot(args...; kw...) function plot(args...; kw...)
pkg = backend() pkg = backend()
d = KW(kw) d = KW(kw)
preprocessArgs!(d) preprocessArgs!(d)
dumpdict(d, "After plot preprocessing") dumpdict(d, "After plot preprocessing")
plotargs = merge(d, getPlotArgs(pkg, d, 1)) plotargs = merge(d, getPlotArgs(pkg, d, 1))
dumpdict(plotargs, "Plot args") dumpdict(plotargs, "Plot args")
plt = _create_plot(pkg; plotargs...) # create a new, blank plot plt = _create_plot(pkg; plotargs...) # create a new, blank plot
delete!(d, :background_color) delete!(d, :background_color)
plot!(plt, args...; d...) # add to it plot!(plt, args...; d...) # add to it
end end
# this adds to the current plot, or creates a new plot if none are current # this adds to the current plot, or creates a new plot if none are current
function plot!(args...; kw...) function plot!(args...; kw...)
local plt local plt
try try
plt = current() plt = current()
catch catch
return plot(args...; kw...) return plot(args...; kw...)
end end
plot!(current(), args...; kw...) plot!(current(), args...; kw...)
end end
# this adds to a specific plot... most plot commands will flow through here # this adds to a specific plot... most plot commands will flow through here
function plot!(plt::Plot, args...; kw...) function plot!(plt::Plot, args...; kw...)
d = KW(kw) d = KW(kw)
preprocessArgs!(d) preprocessArgs!(d)
# for plotting recipes, swap out the args and update the parameter dictionary # for plotting recipes, swap out the args and update the parameter dictionary
args = _apply_recipe(d, args...; kw...) args = _apply_recipe(d, args...; kw...)
dumpdict(d, "After plot! preprocessing") dumpdict(d, "After plot! preprocessing")
warnOnUnsupportedArgs(plt.backend, d) warnOnUnsupportedArgs(plt.backend, d)
# grouping # just in case the backend needs to set up the plot (make it current or something)
groupargs = get(d, :group, nothing) == nothing ? [] : [extractGroupArgs(d[:group], args...)] _before_add_series(plt)
# just in case the backend needs to set up the plot (make it current or something) # grouping
_before_add_series(plt) groupargs = get(d, :group, nothing) == nothing ? [nothing] : [extractGroupArgs(d[:group], args...)]
# @show groupargs
# get the list of dictionaries, one per series # TODO: get the GroupBy object (or nothing), and loop through the groups, doing the following section many times
@show groupargs args
dumpdict(d, "before process_inputs")
process_inputs(plt, d, groupargs..., args...)
dumpdict(d, "after process_inputs")
seriesArgList, xmeta, ymeta = build_series_args(plt, d)
# seriesArgList, xmeta, ymeta = build_series_args(plt, groupargs..., args...; d...)
# if we were able to extract guide information from the series inputs, then update the plot
# @show xmeta, ymeta
updateDictWithMeta(d, plt.plotargs, xmeta, true)
updateDictWithMeta(d, plt.plotargs, ymeta, false)
# now we can plot the series # get the list of dictionaries, one per series
for (i,di) in enumerate(seriesArgList) @show groupargs map(typeof, args)
plt.n += 1 dumpdict(d, "before process_inputs")
process_inputs(plt, d, groupargs..., args...)
dumpdict(d, "after process_inputs", true)
seriesArgList, xmeta, ymeta = build_series_args(plt, d)
# seriesArgList, xmeta, ymeta = build_series_args(plt, groupargs..., args...; d...)
if !stringsSupported() # if we were able to extract guide information from the series inputs, then update the plot
setTicksFromStringVector(d, di, :x, :xticks) # @show xmeta, ymeta
setTicksFromStringVector(d, di, :y, :yticks) updateDictWithMeta(d, plt.plotargs, xmeta, true)
updateDictWithMeta(d, plt.plotargs, ymeta, false)
# now we can plot the series
for (i,di) in enumerate(seriesArgList)
plt.n += 1
if !stringsSupported()
setTicksFromStringVector(d, di, :x, :xticks)
setTicksFromStringVector(d, di, :y, :yticks)
end
# remove plot args
for k in keys(_plotDefaults)
delete!(di, k)
end
dumpdict(di, "Series $i")
_add_series(plt.backend, plt; di...)
end end
# remove plot args # TODO: this is the end of the groupby loop
for k in keys(_plotDefaults)
delete!(di, k) _add_annotations(plt, d)
warnOnUnsupportedScales(plt.backend, d)
# add title, axis labels, ticks, etc
if !haskey(d, :subplot)
merge!(plt.plotargs, d)
dumpdict(plt.plotargs, "Updating plot items")
_update_plot(plt, plt.plotargs)
end end
dumpdict(di, "Series $i") _update_plot_pos_size(plt, d)
_add_series(plt.backend, plt; di...) current(plt)
end
_add_annotations(plt, d) # NOTE: lets ignore the show param and effectively use the semicolon at the end of the REPL statement
# # do we want to show it?
if haskey(d, :show) && d[:show]
gui()
end
warnOnUnsupportedScales(plt.backend, d) plt
# add title, axis labels, ticks, etc
if !haskey(d, :subplot)
merge!(plt.plotargs, d)
dumpdict(plt.plotargs, "Updating plot items")
_update_plot(plt, plt.plotargs)
end
_update_plot_pos_size(plt, d)
current(plt)
# NOTE: lets ignore the show param and effectively use the semicolon at the end of the REPL statement
# # do we want to show it?
if haskey(d, :show) && d[:show]
gui()
end
plt
end end
# -------------------------------------------------------------------- # --------------------------------------------------------------------
@ -149,21 +155,20 @@ end
# if x or y are a vector of strings, we should create a list of unique strings, # if x or y are a vector of strings, we should create a list of unique strings,
# and map x/y to be the index of the string... then set the x/y tick labels # and map x/y to be the index of the string... then set the x/y tick labels
function setTicksFromStringVector(d::Dict, di::Dict, sym::Symbol, ticksym::Symbol) function setTicksFromStringVector(d::Dict, di::Dict, sym::Symbol, ticksym::Symbol)
# if the x or y values are strings, set ticks to the unique values, and x/y to the indices of the ticks # if the x or y values are strings, set ticks to the unique values, and x/y to the indices of the ticks
v = di[sym] v = di[sym]
isa(v, AbstractArray) || return isa(v, AbstractArray) || return
T = eltype(v) T = eltype(v)
if T <: @compat(AbstractString) || (!isempty(T.types) && all(x -> x <: @compat(AbstractString), T.types)) if T <: @compat(AbstractString) || (!isempty(T.types) && all(x -> x <: @compat(AbstractString), T.types))
ticks = unique(di[sym])
di[sym] = Int[findnext(ticks, v, 1) for v in di[sym]]
ticks = unique(di[sym]) if !haskey(d, ticksym) || d[ticksym] == :auto
di[sym] = Int[findnext(ticks, v, 1) for v in di[sym]] d[ticksym] = (collect(1:length(ticks)), UTF8String[t for t in ticks])
end
if !haskey(d, ticksym) || d[ticksym] == :auto
d[ticksym] = (collect(1:length(ticks)), UTF8String[t for t in ticks])
end end
end
end end
# -------------------------------------------------------------------- # --------------------------------------------------------------------
@ -174,10 +179,10 @@ _before_add_series(plt::Plot) = nothing
# should we update the x/y label given the meta info during input slicing? # should we update the x/y label given the meta info during input slicing?
function updateDictWithMeta(d::Dict, plotargs::Dict, meta::Symbol, isx::Bool) function updateDictWithMeta(d::Dict, plotargs::Dict, meta::Symbol, isx::Bool)
lsym = isx ? :xlabel : :ylabel lsym = isx ? :xlabel : :ylabel
if plotargs[lsym] == default(lsym) if plotargs[lsym] == default(lsym)
d[lsym] = string(meta) d[lsym] = string(meta)
end end
end end
updateDictWithMeta(d::Dict, plotargs::Dict, meta, isx::Bool) = nothing updateDictWithMeta(d::Dict, plotargs::Dict, meta, isx::Bool) = nothing
@ -192,30 +197,30 @@ annotations(anns) = error("Expecting a tuple (or vector of tuples) for annotatio
"(x, y, annotation)\n got: $(typeof(anns))") "(x, y, annotation)\n got: $(typeof(anns))")
function _add_annotations(plt::Plot, d::Dict) function _add_annotations(plt::Plot, d::Dict)
anns = annotations(get(d, :annotation, nothing)) anns = annotations(get(d, :annotation, nothing))
if !isempty(anns) if !isempty(anns)
# if we just have a list of PlotText objects, then create (x,y,text) tuples # if we just have a list of PlotText objects, then create (x,y,text) tuples
if typeof(anns) <: AVec{PlotText} if typeof(anns) <: AVec{PlotText}
x, y = plt[plt.n] x, y = plt[plt.n]
anns = Tuple{Float64,Float64,PlotText}[(x[i], y[i], t) for (i,t) in enumerate(anns)] anns = Tuple{Float64,Float64,PlotText}[(x[i], y[i], t) for (i,t) in enumerate(anns)]
end
_add_annotations(plt, anns)
end end
_add_annotations(plt, anns)
end
end end
# -------------------------------------------------------------------- # --------------------------------------------------------------------
function Base.copy(plt::Plot) function Base.copy(plt::Plot)
backend(plt.backend) backend(plt.backend)
plt2 = plot(; plt.plotargs...) plt2 = plot(; plt.plotargs...)
for sargs in plt.seriesargs for sargs in plt.seriesargs
sargs = filter((k,v) -> haskey(_seriesDefaults,k), sargs) sargs = filter((k,v) -> haskey(_seriesDefaults,k), sargs)
plot!(plt2; sargs...) plot!(plt2; sargs...)
end end
plt2 plt2
end end
# -------------------------------------------------------------------- # --------------------------------------------------------------------

View File

@ -57,50 +57,49 @@ end
# -------------------------------------------------------------------- # --------------------------------------------------------------------
# # in computeXandY, we take in any of the possible items, convert into proper x/y vectors, then return. # TODO: can we avoid the copy here? one error that crops up is that mapping functions over the same array
# # this is also where all the "set x to 1:length(y)" happens, and also where we assert on lengths. # result in that array being shared. push!, etc will add too many items to that array
# computeX(x::@compat(Void), y) = 1:size(y,1)
# computeX(x, y) = copy(x)
# computeY(x, y::Function) = map(y, x)
# computeY(x, y) = copy(y)
# function computeXandY(x, y)
# if x == nothing && isa(y, Function)
# error("If you want to plot the function `$y`, you need to define the x values somehow!")
# end
# x, y = computeX(x,y), computeY(x,y)
# # @assert length(x) == length(y)
# x, y
# end
compute_x(x::Void, y::Void, z) = 1:size(z,1) compute_x(x::Void, y::Void, z) = 1:size(z,1)
compute_x(x::Void, y, z) = 1:size(y,1) compute_x(x::Void, y, z) = 1:size(y,1)
compute_x(x::Function, y, z) = map(x, y) compute_x(x::Function, y, z) = map(x, y)
compute_x(x, y, z) = x compute_x(x, y, z) = copy(x)
compute_y(x::Void, y::Function, z) = error() # compute_y(x::Void, y::Function, z) = error()
compute_y(x::Void, y::Void, z) = 1:size(z,2) compute_y(x::Void, y::Void, z) = 1:size(z,2)
# compute_y(x::Void, y, z) = 1:size(z,2) compute_y(x, y::Function, z) = map(y, x)
compute_y(x, y::Function, z) = map(y, x) compute_y(x, y, z) = copy(y)
compute_y(x, y, z) = y
compute_z(x, y, z::Function) = map(z, x, y) compute_z(x, y, z::Function) = map(z, x, y)
compute_z(x, y, z) = Surface(z) compute_z(x, y, z::AbstractMatrix) = Surface(z)
compute_z(x, y, z::Void) = nothing
compute_z(x, y, z) = copy(z)
compute_xyz(x, y, z) = compute_x(x,y,z), compute_y(x,y,z), compute_z(x,y,z) @noinline function compute_xyz(x, y, z)
x = compute_x(x,y,z)
y = compute_y(x,y,z)
z = compute_z(x,y,z)
x, y, z
end
# not allowed # not allowed
compute_xyz(x::Void, y::FuncOrFuncs, z) = error("If you want to plot the function `$y`, you need to define the x values!") compute_xyz(x::Void, y::FuncOrFuncs, z) = error("If you want to plot the function `$y`, you need to define the x values!")
compute_xyz(x::Void, y::Void, z::FuncOrFuncs) = error("If you want to plot the function `$z`, you need to define x and y values!") compute_xyz(x::Void, y::Void, z::FuncOrFuncs) = error("If you want to plot the function `$z`, you need to define x and y values!")
compute_xyz(x::Void, y::Void, z::Void) = error("x/y/z are all nothing!") 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 # create n=max(mx,my) series arguments. the shorter list is cycled through
# note: everything should flow through this # note: everything should flow through this
function build_series_args(plt::AbstractPlot, kw::KW) function build_series_args(plt::AbstractPlot, kw::KW)
xs, xmeta = convertToAnyVector(pop!(kw, :x, nothing), kw) x, y, z = map(a -> pop!(kw, a, nothing), (:x, :y, :z))
ys, ymeta = convertToAnyVector(pop!(kw, :y, nothing), kw) if nothing == x == y == z
zs, zmeta = convertToAnyVector(pop!(kw, :z, nothing), kw) return [], nothing, nothing
end
xs, xmeta = convertToAnyVector(x, kw)
ys, ymeta = convertToAnyVector(y, kw)
zs, zmeta = convertToAnyVector(z, kw)
mx = length(xs) mx = length(xs)
my = length(ys) my = length(ys)
@ -126,12 +125,13 @@ function build_series_args(plt::AbstractPlot, kw::KW)
d = getSeriesArgs(plt.backend, getplotargs(plt, n), d, i + numUncounted, convertSeriesIndex(plt, n), n) d = getSeriesArgs(plt.backend, getplotargs(plt, n), d, i + numUncounted, convertSeriesIndex(plt, n), n)
dumpdict(d, "after getSeriesArgs") dumpdict(d, "after getSeriesArgs")
@show xs[mod1(i,mx)] ys[mod1(i,my)] zs[mod1(i,mz)] @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)]) d[:x], d[:y], d[:z] = compute_xyz(xs[mod1(i,mx)], ys[mod1(i,my)], zs[mod1(i,mz)])
@show d[:x] d[:y] d[:z] @show map(typeof, (d[:x], d[:y], d[:z]))
# # NOTE: this should be handled by the time it gets here # # NOTE: this should be handled by the time it gets here
# lt = d[:linetype] lt = d[:linetype]
# if isa(d[:y], Surface) # if isa(d[:y], Surface)
# if lt in (:contour, :heatmap, :surface, :wireframe) # if lt in (:contour, :heatmap, :surface, :wireframe)
# z = d[:y] # z = d[:y]
@ -192,30 +192,6 @@ end
function process_inputs(plt::AbstractPlot, d::KW) function process_inputs(plt::AbstractPlot, d::KW)
end end
# # TODO: all methods should probably do this... check for (and pop!) x/y/z values if they exist
#
# function build_series_args(plt::AbstractPlot, d::KW)
#
# build_series_args(plt, d, pop!(d, :x, nothing),
# pop!(d, :y, nothing),
# pop!(d, :z, nothing); d...)
# end
# d = KW(kw)
# if !haskey(d, :y)
# # assume we just want to create an empty plot object which can be added to later
# return [], nothing, nothing
# # error("Called plot/subplot without args... must set y in the keyword args. Example: plot(; y=rand(10))")
# end
#
# if haskey(d, :x)
# return build_series_args(plt, d, d[:x], d[:y])
# else
# return build_series_args(plt, d, d[:y])
# end
# end
# -------------------------------------------------------------------- # --------------------------------------------------------------------
# 1 argument # 1 argument
# -------------------------------------------------------------------- # --------------------------------------------------------------------
@ -292,7 +268,7 @@ function process_inputs(plt::AbstractPlot, d::KW, x::AVec, y::AVec, zvec::AVec)
if !(get(d, :linetype, :none) in _3dTypes) if !(get(d, :linetype, :none) in _3dTypes)
d[:linetype] = :path3d d[:linetype] = :path3d
end end
d[:x], d[:y], d[:z] = x, y, z d[:x], d[:y], d[:z] = x, y, zvec
end end
# surface-like... function # surface-like... function
@ -309,7 +285,7 @@ function process_inputs{T<:Number}(plt::AbstractPlot, d::KW, x::AVec, y::AVec, z
x_idx = sortperm(x) x_idx = sortperm(x)
y_idx = sortperm(y) y_idx = sortperm(y)
x, y = x[x_idx], y[y_idx] x, y = x[x_idx], y[y_idx]
zmat = z[x_idx, y_idx] zmat = zmat[x_idx, y_idx]
end end
d[:x], d[:y], d[:z] = x, y, Surface{Matrix{Float64}}(zmat) d[:x], d[:y], d[:z] = x, y, Surface{Matrix{Float64}}(zmat)
if !like_surface(get(d, :linetype, :none)) if !like_surface(get(d, :linetype, :none))
@ -339,13 +315,13 @@ function process_inputs(plt::AbstractPlot, d::KW, f::FuncOrFuncs, xmin::Number,
end end
# special handling... xmin/xmax with parametric function(s) # special handling... xmin/xmax with parametric function(s)
process_inputs{T<:Number}(plt::AbstractPlot, fx::FuncOrFuncs, fy::FuncOrFuncs, u::AVec{T}) = process_inputs(plt, d, mapFuncOrFuncs(fx, u), mapFuncOrFuncs(fy, u)) process_inputs{T<:Number}(plt::AbstractPlot, d::KW, fx::FuncOrFuncs, fy::FuncOrFuncs, u::AVec{T}) = process_inputs(plt, d, mapFuncOrFuncs(fx, u), mapFuncOrFuncs(fy, u))
process_inputs{T<:Number}(plt::AbstractPlot, u::AVec{T}, fx::FuncOrFuncs, fy::FuncOrFuncs) = process_inputs(plt, d, mapFuncOrFuncs(fx, u), mapFuncOrFuncs(fy, u)) process_inputs{T<:Number}(plt::AbstractPlot, d::KW, u::AVec{T}, fx::FuncOrFuncs, fy::FuncOrFuncs) = process_inputs(plt, d, mapFuncOrFuncs(fx, u), mapFuncOrFuncs(fy, u))
process_inputs(plt::AbstractPlot, d::KW, fx::FuncOrFuncs, fy::FuncOrFuncs, umin::Number, umax::Number, numPoints::Int = 1000) = process_inputs(plt, d, fx, fy, linspace(umin, umax, numPoints)) process_inputs(plt::AbstractPlot, d::KW, fx::FuncOrFuncs, fy::FuncOrFuncs, umin::Number, umax::Number, numPoints::Int = 1000) = process_inputs(plt, d, fx, fy, linspace(umin, umax, numPoints))
# special handling... 3D parametric function(s) # special handling... 3D parametric function(s)
process_inputs{T<:Number}(plt::AbstractPlot, fx::FuncOrFuncs, fy::FuncOrFuncs, fz::FuncOrFuncs, u::AVec{T}) = process_inputs(plt, d, mapFuncOrFuncs(fx, u), mapFuncOrFuncs(fy, u), mapFuncOrFuncs(fz, u)) process_inputs{T<:Number}(plt::AbstractPlot, d::KW, fx::FuncOrFuncs, fy::FuncOrFuncs, fz::FuncOrFuncs, u::AVec{T}) = process_inputs(plt, d, mapFuncOrFuncs(fx, u), mapFuncOrFuncs(fy, u), mapFuncOrFuncs(fz, u))
process_inputs{T<:Number}(plt::AbstractPlot, u::AVec{T}, fx::FuncOrFuncs, fy::FuncOrFuncs, fz::FuncOrFuncs) = process_inputs(plt, d, mapFuncOrFuncs(fx, u), mapFuncOrFuncs(fy, u), mapFuncOrFuncs(fz, u)) process_inputs{T<:Number}(plt::AbstractPlot, d::KW, u::AVec{T}, fx::FuncOrFuncs, fy::FuncOrFuncs, fz::FuncOrFuncs) = process_inputs(plt, d, mapFuncOrFuncs(fx, u), mapFuncOrFuncs(fy, u), mapFuncOrFuncs(fz, u))
process_inputs(plt::AbstractPlot, d::KW, fx::FuncOrFuncs, fy::FuncOrFuncs, fz::FuncOrFuncs, umin::Number, umax::Number, numPoints::Int = 1000) = process_inputs(plt, d, fx, fy, fz, linspace(umin, umax, numPoints)) process_inputs(plt::AbstractPlot, d::KW, fx::FuncOrFuncs, fy::FuncOrFuncs, fz::FuncOrFuncs, umin::Number, umax::Number, numPoints::Int = 1000) = process_inputs(plt, d, fx, fy, fz, linspace(umin, umax, numPoints))
@ -415,10 +391,6 @@ end
function setup_dataframes() function setup_dataframes()
@require DataFrames begin @require DataFrames begin
# function process_inputs(plt::AbstractPlot, d::KW, df::DataFrames.AbstractDataFrame, args...)
# process_inputs(plt, d, args..., dataframe = df)
# end
function process_inputs(plt::AbstractPlot, d::KW, df::DataFrames.AbstractDataFrame, args...) function process_inputs(plt::AbstractPlot, d::KW, df::DataFrames.AbstractDataFrame, args...)
d[:dataframe] = df d[:dataframe] = df
process_inputs(plt, d, args...) process_inputs(plt, d, args...)