664 lines
22 KiB
Julia
664 lines
22 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
|
||
getattr(plt::Plot, idx::Int = 1) = plt.attr
|
||
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...)
|
||
d = KW(kw)
|
||
preprocessArgs!(d)
|
||
|
||
# create an empty Plot then process
|
||
plt = Plot()
|
||
# plt.user_attr = d
|
||
_plot!(plt, d, args...)
|
||
end
|
||
|
||
# build a new plot from existing plots
|
||
# note: we split into plt1 and plts_tail so we can dispatch correctly
|
||
function plot(plt1::Plot, plts_tail::Plot...; kw...)
|
||
d = KW(kw)
|
||
preprocessArgs!(d)
|
||
|
||
# build our plot vector from the args
|
||
n = length(plts_tail) + 1
|
||
plts = Array(Plot, n)
|
||
plts[1] = plt1
|
||
for (i,plt) in enumerate(plts_tail)
|
||
plts[i+1] = plt
|
||
end
|
||
|
||
# compute the layout
|
||
layout = layout_args(d, n)[1]
|
||
num_sp = sum([length(p.subplots) for p in plts])
|
||
|
||
# create a new plot object, with subplot list/map made of existing subplots.
|
||
# note: we create a new backend figure for this new plot object
|
||
# note: all subplots and series "belong" to this new plot...
|
||
plt = Plot()
|
||
|
||
# TODO: build the user_attr dict by creating "Any matrices" for the args of each subplot
|
||
|
||
# TODO: replace this with proper processing from a merged user_attr KW
|
||
# update plot args, first with existing plots, then override with d
|
||
for p in plts
|
||
_update_plot_args(plt, p.attr)
|
||
plt.n += p.n
|
||
end
|
||
_update_plot_args(plt, d)
|
||
|
||
# pass new plot to the backend
|
||
plt.o = _create_backend_figure(plt)
|
||
plt.init = true
|
||
|
||
# create the layout and initialize the subplots
|
||
plt.layout, plt.subplots, plt.spmap = build_layout(layout, num_sp, copy(plts))
|
||
for (idx, sp) in enumerate(plt.subplots)
|
||
_initialize_subplot(plt, sp)
|
||
serieslist = series_list(sp)
|
||
if sp in sp.plt.inset_subplots
|
||
push!(plt.inset_subplots, sp)
|
||
end
|
||
sp.plt = plt
|
||
sp.attr[:subplot_index] = idx
|
||
for series in serieslist
|
||
push!(plt.series_list, series)
|
||
_series_added(plt, series)
|
||
end
|
||
end
|
||
|
||
# # just in case the backend needs to set up the plot (make it current or something)
|
||
# _prepare_plot_object(plt)
|
||
|
||
# first apply any args for the subplots
|
||
for (idx,sp) in enumerate(plt.subplots)
|
||
_update_subplot_args(plt, sp, d, idx, remove_pair = false)
|
||
end
|
||
|
||
# # now we can get rid of the axis keys without a letter
|
||
# for k in keys(_axis_defaults)
|
||
# delete!(d, k)
|
||
# for letter in (:x,:y,:z)
|
||
# delete!(d, Symbol(letter,k))
|
||
# end
|
||
# end
|
||
|
||
# do we need to link any axes together?
|
||
link_axes!(plt.layout, plt[:link])
|
||
|
||
# finish up
|
||
current(plt)
|
||
if get(d, :show, default(:show))
|
||
gui()
|
||
end
|
||
plt
|
||
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)
|
||
# merge!(plt.user_attr, d)
|
||
_plot!(plt, d, args...)
|
||
end
|
||
|
||
function strip_first_letter(s::Symbol)
|
||
str = string(s)
|
||
str[1:1], Symbol(str[2:end])
|
||
end
|
||
|
||
|
||
# this method recursively applies series recipes when the seriestype is not supported
|
||
# natively by the backend
|
||
function _apply_series_recipe(plt::Plot, d::KW)
|
||
# replace seriestype aliases
|
||
st = d[:seriestype]
|
||
st = d[:seriestype] = get(_typeAliases, st, st)
|
||
|
||
# if it's natively supported, finalize processing and pass along to the backend, otherwise recurse
|
||
if st in supported_types()
|
||
|
||
# getting ready to add the series... last update to subplot from anything
|
||
# that might have been added during series recipes
|
||
sp = d[:subplot]
|
||
sp_idx = get_subplot_index(plt, sp)
|
||
_update_subplot_args(plt, sp, d, sp_idx)
|
||
|
||
# do we want to override the series type?
|
||
if !is3d(st) && d[:z] != nothing && (size(d[:x]) == size(d[:y]) == size(d[:z]))
|
||
st = d[:seriestype] = (st == :scatter ? :scatter3d : :path3d)
|
||
end
|
||
|
||
# change to a 3d projection for this subplot?
|
||
if is3d(st)
|
||
sp.attr[:projection] = "3d"
|
||
end
|
||
|
||
# initialize now that we know the first series type
|
||
if !haskey(sp.attr, :init)
|
||
_initialize_subplot(plt, sp)
|
||
sp.attr[:init] = true
|
||
end
|
||
|
||
# strip out series annotations (those which are based on series x/y coords)
|
||
# and add them to the subplot attr
|
||
sp_anns = annotations(sp[:annotations])
|
||
anns = annotations(pop!(d, :series_annotations, []))
|
||
if length(anns) > 0
|
||
x, y = d[:x], d[:y]
|
||
nx, ny, na = map(length, (x,y,anns))
|
||
n = max(nx, ny, na)
|
||
anns = [(x[mod1(i,nx)], y[mod1(i,ny)], text(anns[mod1(i,na)])) for i=1:n]
|
||
end
|
||
sp.attr[:annotations] = vcat(sp_anns, anns)
|
||
|
||
# adjust extrema and discrete info
|
||
if st == :image
|
||
w, h = size(d[:z])
|
||
expand_extrema!(sp[:xaxis], (0,w))
|
||
expand_extrema!(sp[:yaxis], (0,h))
|
||
sp[:yaxis].d[:flip] = true
|
||
elseif !(st in (:pie, :histogram, :histogram2d))
|
||
expand_extrema!(sp, d)
|
||
end
|
||
|
||
|
||
# add the series!
|
||
warnOnUnsupported_args(plt.backend, d)
|
||
warnOnUnsupported(plt.backend, d)
|
||
series = Series(d)
|
||
push!(plt.series_list, series)
|
||
# @show series
|
||
|
||
_series_added(plt, series)
|
||
|
||
else
|
||
# get a sub list of series for this seriestype
|
||
datalist = try
|
||
RecipesBase.apply_recipe(d, Val{st}, d[:x], d[:y], d[:z])
|
||
catch
|
||
warn("Exception during apply_recipe(Val{$st}, ...) with types ($(typeof(d[:x])), $(typeof(d[:y])), $(typeof(d[:z])))")
|
||
rethrow()
|
||
end
|
||
|
||
# assuming there was no error, recursively apply the series recipes
|
||
for data in datalist
|
||
if isa(data, RecipeData)
|
||
_apply_series_recipe(plt, data.d)
|
||
else
|
||
warn("Unhandled recipe: $(data)")
|
||
break
|
||
end
|
||
end
|
||
end
|
||
end
|
||
|
||
function command_idx(kw_list::AVec{KW}, kw::KW)
|
||
kw[:series_plotindex] - kw_list[1][:series_plotindex] + 1
|
||
end
|
||
|
||
|
||
# this is the core plotting function. recursively apply recipes to build
|
||
# a list of series KW dicts.
|
||
# note: at entry, we only have those preprocessed args which were passed in... no default values yet
|
||
function _plot!(plt::Plot, d::KW, args...)
|
||
d[:plot_object] = plt
|
||
|
||
# the grouping mechanism is a recipe on a GroupBy object
|
||
# we simply add the GroupBy object to the front of the args list to allow
|
||
# the recipe to be applied
|
||
if haskey(d, :group)
|
||
args = (extractGroupArgs(d[:group], args...), args...)
|
||
end
|
||
|
||
# if we were passed a vector/matrix of seriestypes and there's more than one row,
|
||
# we want to duplicate the inputs, once for each seriestype row.
|
||
kw_list = KW[]
|
||
still_to_process = if isempty(args)
|
||
[]
|
||
else
|
||
sts = get(d, :seriestype, :path)
|
||
if typeof(sts) <: AbstractArray
|
||
[begin
|
||
dc = copy(d)
|
||
dc[:seriestype] = sts[r,:]
|
||
RecipeData(dc, args)
|
||
end for r=1:size(sts,1)]
|
||
else
|
||
[RecipeData(copy(d), args)]
|
||
end
|
||
end
|
||
|
||
# remove subplot and axis args from d... they will be passed through in the kw_list
|
||
if !isempty(args)
|
||
for (k,v) in d
|
||
for defdict in (_subplot_defaults,
|
||
_axis_defaults,
|
||
_axis_defaults_byletter)
|
||
if haskey(defdict, k)
|
||
delete!(d, k)
|
||
end
|
||
end
|
||
end
|
||
end
|
||
|
||
# --------------------------------
|
||
# "USER RECIPES"
|
||
# --------------------------------
|
||
|
||
# for plotting recipes, swap out the args and update the parameter dictionary
|
||
# we are keeping a queue of series that still need to be processed.
|
||
# each pass through the loop, we pop one off and apply the recipe.
|
||
# the recipe will return a list a Series objects... the ones that are
|
||
# finished (no more args) get added to the kw_list, and the rest go into the queue
|
||
# for processing.
|
||
while !isempty(still_to_process)
|
||
|
||
# grab the first in line to be processed and pass it through apply_recipe
|
||
# to generate a list of RecipeData objects (data + attributes)
|
||
next_series = shift!(still_to_process)
|
||
for recipedata in RecipesBase.apply_recipe(next_series.d, next_series.args...)
|
||
|
||
# recipedata should be of type RecipeData. if it's not then the inputs must not have been fully processed by recipes
|
||
if !(typeof(recipedata) <: RecipeData)
|
||
error("Inputs couldn't be processed... expected RecipeData but got: $recipedata")
|
||
end
|
||
|
||
if isempty(recipedata.args)
|
||
# when the arg tuple is empty, that means there's nothing left to recursively
|
||
# process... finish up and add to the kw_list
|
||
kw = recipedata.d
|
||
_add_markershape(kw)
|
||
|
||
# if there was a grouping, filter the data here
|
||
_filter_input_data!(kw)
|
||
|
||
# map marker_z if it's a Function
|
||
if isa(get(kw, :marker_z, nothing), Function)
|
||
# TODO: should this take y and/or z as arguments?
|
||
kw[:marker_z] = map(kw[:marker_z], kw[:x], kw[:y], kw[:z])
|
||
end
|
||
|
||
# map line_z if it's a Function
|
||
if isa(get(kw, :line_z, nothing), Function)
|
||
kw[:line_z] = map(kw[:line_z], kw[:x], kw[:y], kw[:z])
|
||
end
|
||
|
||
# convert a ribbon into a fillrange
|
||
if get(kw, :ribbon, nothing) != nothing
|
||
make_fillrange_from_ribbon(kw)
|
||
end
|
||
|
||
# add the plot index
|
||
plt.n += 1
|
||
kw[:series_plotindex] = plt.n
|
||
|
||
# check that the backend will support the command and add it to the list
|
||
warnOnUnsupported_scales(plt.backend, kw)
|
||
push!(kw_list, kw)
|
||
|
||
# handle error bars by creating new recipedata data... these will have
|
||
# the same recipedata index as the recipedata they are copied from
|
||
for esym in (:xerror, :yerror)
|
||
if get(d, esym, nothing) != nothing
|
||
# we make a copy of the KW and apply an errorbar recipe
|
||
errkw = copy(kw)
|
||
errkw[:seriestype] = esym
|
||
errkw[:label] = ""
|
||
errkw[:primary] = false
|
||
push!(kw_list, errkw)
|
||
end
|
||
end
|
||
|
||
# handle smoothing by adding a new series
|
||
if get(d, :smooth, false)
|
||
x, y = kw[:x], kw[:y]
|
||
β, α = convert(Matrix{Float64}, [x ones(length(x))]) \ convert(Vector{Float64}, y)
|
||
sx = [minimum(x), maximum(x)]
|
||
sy = β * sx + α
|
||
push!(kw_list, merge(copy(kw), KW(
|
||
:seriestype => :path,
|
||
:x => sx,
|
||
:y => sy,
|
||
:fillrange => nothing,
|
||
:label => "",
|
||
:primary => false,
|
||
)))
|
||
end
|
||
|
||
else
|
||
# args are non-empty, so there's still processing to do... add it back to the queue
|
||
push!(still_to_process, recipedata)
|
||
end
|
||
end
|
||
end
|
||
|
||
# don't allow something else to handle it
|
||
d[:smooth] = false
|
||
|
||
# --------------------------------
|
||
# "PLOT RECIPES"
|
||
# --------------------------------
|
||
|
||
# "plot recipe", which acts like a series type, and is processed before
|
||
# the plot layout is created, which allows for setting layouts and other plot-wide attributes.
|
||
# we get inputs which have been fully processed by "user recipes" and "type recipes",
|
||
# so we can expect standard vectors, surfaces, etc. No defaults have been set yet.
|
||
still_to_process = kw_list
|
||
kw_list = KW[]
|
||
while !isempty(still_to_process)
|
||
# Grab the first in line to be processed and pass it through apply_recipe
|
||
# to generate a list of RecipeData objects (data + attributes).
|
||
# If we applied a "plot recipe" without error, then add the returned datalist's KWs,
|
||
# otherwise we just add the original KW.
|
||
next_kw = shift!(still_to_process)
|
||
if !isa(get(next_kw, :seriestype, nothing), Symbol)
|
||
# seriestype was never set, or it's not a Symbol, so it can't be a plot recipe
|
||
push!(kw_list, next_kw)
|
||
continue
|
||
end
|
||
try
|
||
st = next_kw[:seriestype]
|
||
st = next_kw[:seriestype] = get(_typeAliases, st, st)
|
||
datalist = RecipesBase.apply_recipe(next_kw, Val{st}, plt)
|
||
for data in datalist
|
||
if data.d[:seriestype] == st
|
||
error("Plot recipe $st returned the same seriestype: $(data.d)")
|
||
end
|
||
push!(still_to_process, data.d)
|
||
end
|
||
catch err
|
||
if isa(err, MethodError)
|
||
push!(kw_list, next_kw)
|
||
else
|
||
rethrow()
|
||
end
|
||
end
|
||
end
|
||
|
||
# --------------------------------
|
||
# Plot/Subplot/Layout setup
|
||
# --------------------------------
|
||
|
||
# merge in anything meant for the Plot
|
||
for kw in kw_list, (k,v) in kw
|
||
haskey(_plot_defaults, k) && (d[k] = pop!(kw, k))
|
||
end
|
||
|
||
# TODO: init subplots here
|
||
_update_plot_args(plt, d)
|
||
if !plt.init
|
||
plt.o = _create_backend_figure(plt)
|
||
|
||
# create the layout and subplots from the inputs
|
||
plt.layout, plt.subplots, plt.spmap = build_layout(plt.attr)
|
||
for (idx,sp) in enumerate(plt.subplots)
|
||
sp.plt = plt
|
||
sp.attr[:subplot_index] = idx
|
||
end
|
||
|
||
plt.init = true
|
||
end
|
||
|
||
|
||
# handle inset subplots
|
||
insets = plt[:inset_subplots]
|
||
if insets != nothing
|
||
if !(typeof(insets) <: AVec)
|
||
insets = [insets]
|
||
end
|
||
for inset in insets
|
||
parent, bb = is_2tuple(inset) ? inset : (nothing, inset)
|
||
P = typeof(parent)
|
||
if P <: Integer
|
||
parent = plt.subplots[parent]
|
||
elseif P == Symbol
|
||
parent = plt.spmap[parent]
|
||
else
|
||
parent = plt.layout
|
||
end
|
||
sp = Subplot(backend(), parent=parent)
|
||
sp.plt = plt
|
||
sp.attr[:relative_bbox] = bb
|
||
sp.attr[:subplot_index] = length(plt.subplots)
|
||
push!(plt.subplots, sp)
|
||
push!(plt.inset_subplots, sp)
|
||
end
|
||
end
|
||
|
||
# we'll keep a map of subplot to an attribute override dict.
|
||
# Subplot/Axis attributes set by a user/series recipe apply only to the
|
||
# Subplot object which they belong to.
|
||
# TODO: allow matrices to still apply to all subplots
|
||
sp_attrs = Dict{Subplot,Any}()
|
||
for kw in kw_list
|
||
# get the Subplot object to which the series belongs.
|
||
sps = get(kw, :subplot, :auto)
|
||
sp = get_subplot(plt, cycle(sps == :auto ? plt.subplots : plt.subplots[sps], command_idx(kw_list,kw)))
|
||
kw[:subplot] = sp
|
||
|
||
attr = KW()
|
||
for (k,v) in kw
|
||
for defdict in (_subplot_defaults,
|
||
_axis_defaults,
|
||
_axis_defaults_byletter)
|
||
if haskey(defdict, k)
|
||
attr[k] = pop!(kw, k)
|
||
end
|
||
end
|
||
end
|
||
sp_attrs[sp] = attr
|
||
end
|
||
|
||
# override subplot/axis args. `sp_attrs` take precendence
|
||
for (idx,sp) in enumerate(plt.subplots)
|
||
attr = merge(d, get(sp_attrs, sp, KW()))
|
||
_update_subplot_args(plt, sp, attr, idx, remove_pair = false)
|
||
end
|
||
|
||
# do we need to link any axes together?
|
||
link_axes!(plt.layout, plt[:link])
|
||
|
||
# !!! note: At this point, kw_list is fully decomposed into individual series... one KW per series. !!!
|
||
# !!! The next step is to recursively apply series recipes until the backend supports that series type !!!
|
||
|
||
# --------------------------------
|
||
# "SERIES RECIPES"
|
||
# --------------------------------
|
||
|
||
for kw in kw_list
|
||
sp = kw[:subplot]
|
||
idx = get_subplot_index(plt, sp)
|
||
|
||
# # we update subplot args in case something like the color palatte is part of the recipe
|
||
# _update_subplot_args(plt, sp, kw, idx)
|
||
|
||
# set default values, select from attribute cycles, and generally set the final attributes
|
||
_add_defaults!(kw, plt, sp, command_idx(kw_list,kw))
|
||
|
||
# now we have a fully specified series, with colors chosen. we must recursively handle
|
||
# series recipes, which dispatch on seriestype. If a backend does not natively support a seriestype,
|
||
# we check for a recipe that will convert that series type into one made up of lower-level components.
|
||
# For example, a histogram is just a bar plot with binned data, a bar plot is really a filled step plot,
|
||
# and a step plot is really just a path. So any backend that supports drawing a path will implicitly
|
||
# be able to support step, bar, and histogram plots (and any recipes that use those components).
|
||
_apply_series_recipe(plt, kw)
|
||
end
|
||
|
||
# --------------------------------
|
||
|
||
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]
|
||
if get(d, :show, default(:show))
|
||
gui()
|
||
end
|
||
|
||
plt
|
||
end
|
||
|
||
|
||
function _replace_linewidth(d::KW)
|
||
# get a good default linewidth... 0 for surface and heatmaps
|
||
if get(d, :linewidth, :auto) == :auto
|
||
d[:linewidth] = (get(d, :seriestype, :path) in (:surface,:heatmap,:image) ? 0 : 1)
|
||
end
|
||
end
|
||
|
||
# we're getting ready to display/output. prep for layout calcs, then update
|
||
# the plot object after
|
||
function prepare_output(plt::Plot)
|
||
_before_layout_calcs(plt)
|
||
|
||
w, h = plt.attr[:size]
|
||
plt.layout.bbox = BoundingBox(0mm, 0mm, w*px, h*px)
|
||
|
||
# One pass down and back up the tree to compute the minimum padding
|
||
# of the children on the perimeter. This is an backend callback.
|
||
_update_min_padding!(plt.layout)
|
||
for sp in plt.inset_subplots
|
||
_update_min_padding!(sp)
|
||
end
|
||
|
||
# now another pass down, to update the bounding boxes
|
||
update_child_bboxes!(plt.layout)
|
||
|
||
# update those bounding boxes of inset subplots
|
||
update_inset_bboxes!(plt)
|
||
|
||
# the backend callback, to reposition subplots, etc
|
||
_update_plot_object(plt)
|
||
end
|
||
|
||
function prepared_object(plt::Plot)
|
||
prepare_output(plt)
|
||
plt.o
|
||
end
|
||
|
||
# --------------------------------------------------------------------
|
||
|
||
# function get_indices(orig, labels)
|
||
# Int[findnext(labels, l, 1) for l in orig]
|
||
# end
|
||
|
||
# # TODO: remove?? this is the old way of handling discrete data... should be
|
||
# # replaced by the Axis type and logic
|
||
# function setTicksFromStringVector(plt::Plot, d::KW, di::KW, letter)
|
||
# sym = Symbol(letter)
|
||
# ticksym = Symbol(letter * "ticks")
|
||
# pargs = plt.attr
|
||
# v = di[sym]
|
||
#
|
||
# # do we really want to do this?
|
||
# typeof(v) <: AbstractArray || return
|
||
# isempty(v) && return
|
||
# trueOrAllTrue(_ -> typeof(_) <: AbstractString, v) || return
|
||
#
|
||
# # compute the ticks and labels
|
||
# ticks, labels = if ticksType(pargs[ticksym]) == :ticks_and_labels
|
||
# # extend the existing ticks and labels. only add to labels if they're new!
|
||
# ticks, labels = pargs[ticksym]
|
||
# newlabels = filter(_ -> !(_ in labels), unique(v))
|
||
# newticks = if isempty(ticks)
|
||
# collect(1:length(newlabels))
|
||
# else
|
||
# maximum(ticks) + collect(1:length(newlabels))
|
||
# end
|
||
# ticks = vcat(ticks, newticks)
|
||
# labels = vcat(labels, newlabels)
|
||
# ticks, labels
|
||
# else
|
||
# # create new ticks and labels
|
||
# newlabels = unique(v)
|
||
# collect(1:length(newlabels)), newlabels
|
||
# end
|
||
#
|
||
# d[ticksym] = ticks, labels
|
||
# plt.attr[ticksym] = ticks, labels
|
||
#
|
||
# # add an origsym field so that later on we can re-compute the x vector if ticks change
|
||
# origsym = Symbol(letter * "orig")
|
||
# di[origsym] = v
|
||
# di[sym] = get_indices(v, labels)
|
||
#
|
||
# # loop through existing plt.seriesargs and adjust indices if there is an origsym key
|
||
# for sargs in plt.seriesargs
|
||
# if haskey(sargs, origsym)
|
||
# # TODO: might need to call the setindex function instead to trigger a plot update for some backends??
|
||
# sargs[sym] = get_indices(sargs[origsym], labels)
|
||
# end
|
||
# end
|
||
# end
|
||
|
||
|
||
# --------------------------------------------------------------------
|
||
|
||
|
||
|
||
# --------------------------------------------------------------------
|
||
|
||
# function Base.copy(plt::Plot)
|
||
# backend(plt.backend)
|
||
# plt2 = plot(; plt.attr...)
|
||
# for sargs in plt.seriesargs
|
||
# sargs = filter((k,v) -> haskey(_series_defaults,k), sargs)
|
||
# plot!(plt2; sargs...)
|
||
# end
|
||
# plt2
|
||
# end
|
||
|
||
# --------------------------------------------------------------------
|