268 lines
8.0 KiB
Julia
268 lines
8.0 KiB
Julia
|
|
type CurrentPlot
|
|
nullableplot::Nullable{AbstractPlot}
|
|
end
|
|
const CURRENT_PLOT = CurrentPlot(Nullable{AbstractPlot}())
|
|
|
|
isplotnull() = isnull(CURRENT_PLOT.nullableplot)
|
|
|
|
function current()
|
|
if isplotnull()
|
|
error("No current plot/subplot")
|
|
end
|
|
get(CURRENT_PLOT.nullableplot)
|
|
end
|
|
current(plot::AbstractPlot) = (CURRENT_PLOT.nullableplot = Nullable(plot))
|
|
|
|
# ---------------------------------------------------------
|
|
|
|
|
|
Base.string(plt::Plot) = "Plot{$(plt.backend) n=$(plt.n)}"
|
|
Base.print(io::IO, plt::Plot) = print(io, string(plt))
|
|
Base.show(io::IO, plt::Plot) = print(io, string(plt))
|
|
|
|
getplot(plt::Plot) = plt
|
|
getplotargs(plt::Plot, idx::Int = 1) = plt.plotargs
|
|
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:
|
|
|
|
```
|
|
plot(args...; kw...) # creates a new plot window, and sets it to be the current
|
|
plot!(args...; kw...) # adds to the `current`
|
|
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.
|
|
When you pass in matrices, it splits by columns. See the documentation for more info.
|
|
"""
|
|
|
|
# this creates a new plot with args/kw and sets it to be the current plot
|
|
function plot(args...; kw...)
|
|
pkg = backend()
|
|
d = KW(kw)
|
|
preprocessArgs!(d)
|
|
dumpdict(d, "After plot preprocessing")
|
|
|
|
plotargs = merge(d, getPlotArgs(pkg, d, 1))
|
|
dumpdict(plotargs, "Plot args")
|
|
plt = _create_plot(pkg, plotargs) # create a new, blank plot
|
|
|
|
delete!(d, :background_color)
|
|
plot!(plt, args...; d...) # add to it
|
|
end
|
|
|
|
|
|
|
|
# this adds to the current plot, or creates a new plot if none are current
|
|
function plot!(args...; kw...)
|
|
local plt
|
|
try
|
|
plt = current()
|
|
catch
|
|
return plot(args...; kw...)
|
|
end
|
|
plot!(current(), args...; kw...)
|
|
end
|
|
|
|
# this adds to a specific plot... most plot commands will flow through here
|
|
function plot!(plt::Plot, args...; kw...)
|
|
d = KW(kw)
|
|
preprocessArgs!(d)
|
|
|
|
# for plotting recipes, swap out the args and update the parameter dictionary
|
|
args = _apply_recipe(d, args...; kw...)
|
|
|
|
dumpdict(d, "After plot! preprocessing")
|
|
warnOnUnsupportedArgs(plt.backend, d)
|
|
|
|
# just in case the backend needs to set up the plot (make it current or something)
|
|
_before_add_series(plt)
|
|
|
|
# # grouping
|
|
groupby = if haskey(d, :group)
|
|
extractGroupArgs(d[:group], args...)
|
|
else
|
|
nothing
|
|
end
|
|
|
|
_add_series(plt, d, groupby, args...)
|
|
_add_annotations(plt, d)
|
|
|
|
warnOnUnsupportedScales(plt.backend, d)
|
|
|
|
|
|
# add title, axis labels, ticks, etc
|
|
if !haskey(d, :subplot)
|
|
merge!(plt.plotargs, d)
|
|
handlePlotColors(plt.backend, plt.plotargs)
|
|
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
|
|
|
|
# handle the grouping
|
|
function _add_series(plt::Plot, d::KW, groupby::GroupBy, args...)
|
|
starting_n = plt.n
|
|
for (i, glab) in enumerate(groupby.groupLabels)
|
|
tmpd = copy(d)
|
|
tmpd[:numUncounted] = plt.n - starting_n
|
|
_add_series(plt, tmpd, nothing, args...;
|
|
idxfilter = groupby.groupIds[i],
|
|
grouplabel = string(glab))
|
|
end
|
|
end
|
|
|
|
filter_data(v::AVec, idxfilter::AVec{Int}) = v[idxfilter]
|
|
filter_data(v, idxfilter) = v
|
|
|
|
function filter_data!(d::KW, idxfilter)
|
|
for s in (:x, :y, :z)
|
|
d[s] = filter_data(get(d, s, nothing), idxfilter)
|
|
end
|
|
end
|
|
|
|
# no grouping
|
|
function _add_series(plt::Plot, d::KW, ::Void, args...;
|
|
idxfilter = nothing,
|
|
grouplabel = "")
|
|
|
|
# get the list of dictionaries, one per series
|
|
dumpdict(d, "before process_inputs")
|
|
process_inputs(plt, d, args...)
|
|
dumpdict(d, "after process_inputs")
|
|
|
|
if idxfilter != nothing
|
|
# add the group name as the label if there isn't one passed in
|
|
get!(d, :label, grouplabel)
|
|
# filter the data
|
|
filter_data!(d, idxfilter)
|
|
end
|
|
|
|
seriesArgList, xmeta, ymeta = build_series_args(plt, d) #, idxfilter)
|
|
# 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
|
|
for (i,di) in enumerate(seriesArgList)
|
|
plt.n += 1
|
|
|
|
if !stringsSupported()
|
|
setTicksFromStringVector(d, di, :x, :xticks)
|
|
setTicksFromStringVector(d, di, :y, :yticks)
|
|
setTicksFromStringVector(d, di, :z, :zticks)
|
|
end
|
|
|
|
# remove plot args
|
|
for k in keys(_plotDefaults)
|
|
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)
|
|
end
|
|
end
|
|
|
|
# --------------------------------------------------------------------
|
|
|
|
# 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
|
|
function setTicksFromStringVector(d::KW, di::KW, 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
|
|
|
|
v = di[sym]
|
|
isa(v, AbstractArray) || return
|
|
|
|
T = eltype(v)
|
|
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]]
|
|
|
|
if !haskey(d, ticksym) || d[ticksym] == :auto
|
|
d[ticksym] = (collect(1:length(ticks)), UTF8String[t for t in ticks])
|
|
end
|
|
end
|
|
end
|
|
|
|
# --------------------------------------------------------------------
|
|
|
|
_before_add_series(plt::Plot) = nothing
|
|
|
|
# --------------------------------------------------------------------
|
|
|
|
# should we update the x/y label given the meta info during input slicing?
|
|
function updateDictWithMeta(d::KW, plotargs::KW, meta::Symbol, isx::Bool)
|
|
lsym = isx ? :xlabel : :ylabel
|
|
if plotargs[lsym] == default(lsym)
|
|
d[lsym] = string(meta)
|
|
end
|
|
end
|
|
updateDictWithMeta(d::KW, plotargs::KW, meta, isx::Bool) = nothing
|
|
|
|
# --------------------------------------------------------------------
|
|
|
|
annotations(::@compat(Void)) = []
|
|
annotations{X,Y,V}(v::AVec{@compat(Tuple{X,Y,V})}) = v
|
|
annotations{X,Y,V}(t::@compat(Tuple{X,Y,V})) = [t]
|
|
annotations(v::AVec{PlotText}) = v
|
|
annotations(v::AVec) = map(PlotText, v)
|
|
annotations(anns) = error("Expecting a tuple (or vector of tuples) for annotations: ",
|
|
"(x, y, annotation)\n got: $(typeof(anns))")
|
|
|
|
function _add_annotations(plt::Plot, d::KW)
|
|
anns = annotations(get(d, :annotation, nothing))
|
|
if !isempty(anns)
|
|
|
|
# if we just have a list of PlotText objects, then create (x,y,text) tuples
|
|
if typeof(anns) <: AVec{PlotText}
|
|
x, y = plt[plt.n]
|
|
anns = Tuple{Float64,Float64,PlotText}[(x[i], y[i], t) for (i,t) in enumerate(anns)]
|
|
end
|
|
|
|
_add_annotations(plt, anns)
|
|
end
|
|
end
|
|
|
|
|
|
# --------------------------------------------------------------------
|
|
|
|
function Base.copy(plt::Plot)
|
|
backend(plt.backend)
|
|
plt2 = plot(; plt.plotargs...)
|
|
for sargs in plt.seriesargs
|
|
sargs = filter((k,v) -> haskey(_seriesDefaults,k), sargs)
|
|
plot!(plt2; sargs...)
|
|
end
|
|
plt2
|
|
end
|
|
|
|
# --------------------------------------------------------------------
|