reorg of update_subplot_args; add back recipe, DataFrames; random type stability fixes

This commit is contained in:
Thomas Breloff 2016-07-10 18:12:49 -04:00
parent 32c1c31139
commit e3e2a354e7
6 changed files with 156 additions and 201 deletions

View File

@ -141,7 +141,7 @@ include("plot.jl")
include("series.jl") include("series.jl")
include("layouts.jl") include("layouts.jl")
include("subplots.jl") include("subplots.jl")
# include("recipes.jl") include("recipes.jl")
include("animation.jl") include("animation.jl")
include("output.jl") include("output.jl")
include("examples.jl") include("examples.jl")

View File

@ -814,9 +814,9 @@ slice_arg(v, idx) = v
# given an argument key (k), we want to extract the argument value for this index. # given an argument key (k), we want to extract the argument value for this index.
# matrices are sliced by column, otherwise we # matrices are sliced by column, otherwise we
# if nothing is set (or container is empty), return the default or the existing value. # if nothing is set (or container is empty), return the default or the existing value.
function slice_arg!(d_in::KW, d_out::KW, k::Symbol, default_value, idx::Int = 1; new_key::Symbol = k, remove_pair::Bool = true) function slice_arg!(d_in::KW, d_out::KW, k::Symbol, default_value, idx::Int, remove_pair::Bool)
v = get(d_in, k, get(d_out, new_key, default_value)) v = get(d_in, k, get(d_out, k, default_value))
d_out[new_key] = if haskey(d_in, k) && typeof(v) <: AMat && !isempty(v) d_out[k] = if haskey(d_in, k) && typeof(v) <: AMat && !isempty(v)
slice_arg(v, idx) slice_arg(v, idx)
else else
v v
@ -824,6 +824,7 @@ function slice_arg!(d_in::KW, d_out::KW, k::Symbol, default_value, idx::Int = 1;
if remove_pair if remove_pair
delete!(d_in, k) delete!(d_in, k)
end end
return
end end
# ----------------------------------------------------------------------------- # -----------------------------------------------------------------------------
@ -844,12 +845,13 @@ end
function color_or_nothing!(d::KW, k::Symbol) function color_or_nothing!(d::KW, k::Symbol)
v = d[k] v = d[k]
d[k] = if v == nothing || v == false d[k] = if v == nothing || v == false
plot_color(RGBA(0,0,0,0)) RGBA{Float64}(0,0,0,0)
elseif v != :match elseif v != :match
plot_color(v) plot_color(v)
else else
v v
end end
return
end end
# ----------------------------------------------------------------------------- # -----------------------------------------------------------------------------
@ -938,7 +940,7 @@ Base.get(series::Series, k::Symbol, v) = get(series.d, k, v)
# update attr from an input dictionary # update attr from an input dictionary
function _update_plot_args(plt::Plot, d_in::KW) function _update_plot_args(plt::Plot, d_in::KW)
for (k,v) in _plot_defaults for (k,v) in _plot_defaults
slice_arg!(d_in, plt.attr, k, v) slice_arg!(d_in, plt.attr, k, v, 1, true)
end end
# handle colors # handle colors
@ -949,20 +951,12 @@ function _update_plot_args(plt::Plot, d_in::KW)
end end
plt.attr[:background_color] = bg plt.attr[:background_color] = bg
plt.attr[:foreground_color] = plot_color(fg) plt.attr[:foreground_color] = plot_color(fg)
# color_or_match!(plt.attr, :background_color_outside, bg)
color_or_nothing!(plt.attr, :background_color_outside) color_or_nothing!(plt.attr, :background_color_outside)
end end
# -----------------------------------------------------------------------------
# update a subplots args and axes function _update_subplot_periphery(sp::Subplot, anns::AVec)
function _update_subplot_args(plt::Plot, sp::Subplot, d_in::KW, subplot_index::Integer; remove_pair = true)
anns = pop!(sp.attr, :annotations, [])
# grab those args which apply to this subplot
for (k,v) in _subplot_defaults
slice_arg!(d_in, sp.attr, k, v, subplot_index, remove_pair = remove_pair)
end
# extend annotations # extend annotations
sp.attr[:annotations] = vcat(anns, sp[:annotations]) sp.attr[:annotations] = vcat(anns, sp[:annotations])
@ -972,9 +966,11 @@ function _update_subplot_args(plt::Plot, sp::Subplot, d_in::KW, subplot_index::I
if sp.attr[:colorbar] == :legend if sp.attr[:colorbar] == :legend
sp.attr[:colorbar] = sp.attr[:legend] sp.attr[:colorbar] = sp.attr[:legend]
end end
return
end
function _update_subplot_colors(sp::Subplot)
# background colors # background colors
# bg = color_or_match!(sp.attr, :background_color_subplot, plt.attr[:background_color])
color_or_nothing!(sp.attr, :background_color_subplot) color_or_nothing!(sp.attr, :background_color_subplot)
bg = plot_color(sp[:background_color_subplot]) bg = plot_color(sp[:background_color_subplot])
sp.attr[:color_palette] = get_color_palette(sp.attr[:color_palette], bg, 30) sp.attr[:color_palette] = get_color_palette(sp.attr[:color_palette], bg, 30)
@ -986,60 +982,87 @@ function _update_subplot_args(plt::Plot, sp::Subplot, d_in::KW, subplot_index::I
color_or_nothing!(sp.attr, :foreground_color_legend) color_or_nothing!(sp.attr, :foreground_color_legend)
color_or_nothing!(sp.attr, :foreground_color_grid) color_or_nothing!(sp.attr, :foreground_color_grid)
color_or_nothing!(sp.attr, :foreground_color_title) color_or_nothing!(sp.attr, :foreground_color_title)
return
end
# for k in (:left_margin, :top_margin, :right_margin, :bottom_margin) function _update_axis(plt::Plot, sp::Subplot, d_in::KW, letter::Symbol, subplot_index::Int)
# if sp.attr[k] == :match # get (maybe initialize) the axis
# sp.attr[k] = sp.attr[:margin] axis = get_axis(sp, letter)
# end
# end _update_axis(axis, d_in, letter, subplot_index)
# convert a bool into auto or nothing
if isa(axis[:ticks], Bool)
axis[:ticks] = axis[:ticks] ? :auto : nothing
end
_update_axis_colors(axis)
_update_axis_links(plt, axis, letter)
return
end
function _update_axis(axis::Axis, d_in::KW, letter::Symbol, subplot_index::Int)
# grab magic args (for example `xaxis = (:flip, :log)`)
args = wraptuple(get(d_in, Symbol(letter, :axis), ()))
# build the KW of arguments from the letter version (i.e. xticks --> ticks)
kw = KW()
for (k,v) in _axis_defaults
# first get the args without the letter: `tickfont = font(10)`
# note: we don't pop because we want this to apply to all axes! (delete after all have finished)
if haskey(d_in, k)
kw[k] = slice_arg(d_in[k], subplot_index)
end
# then get those args that were passed with a leading letter: `xlabel = "X"`
lk = Symbol(letter, k)
if haskey(d_in, lk)
kw[k] = slice_arg(d_in[lk], subplot_index)
end
end
# update the axis
update!(axis, args...; kw...)
return
end
function _update_axis_colors(axis::Axis)
# # update the axis colors
color_or_nothing!(axis.d, :foreground_color_axis)
color_or_nothing!(axis.d, :foreground_color_border)
color_or_nothing!(axis.d, :foreground_color_guide)
color_or_nothing!(axis.d, :foreground_color_text)
return
end
function _update_axis_links(plt::Plot, axis::Axis, letter::Symbol)
# handle linking here. if we're passed a list of
# other subplots to link to, link them together
link = axis[:link]
if !isempty(link)
for other_sp in link
other_sp = get_subplot(plt, other_sp)
link_axes!(axis, get_axis(other_sp, letter))
end
axis.d[:link] = []
end
return
end
# update a subplots args and axes
function _update_subplot_args(plt::Plot, sp::Subplot, d_in::KW, subplot_index::Int, remove_pair::Bool)
anns = pop!(sp.attr, :annotations, [])
# grab those args which apply to this subplot
for (k,v) in _subplot_defaults
slice_arg!(d_in, sp.attr, k, v, subplot_index, remove_pair)
end
_update_subplot_periphery(sp, anns)
_update_subplot_colors(sp)
for letter in (:x, :y, :z) for letter in (:x, :y, :z)
# get (maybe initialize) the axis _update_axis(plt, sp, d_in, letter, subplot_index)
axis = get_axis(sp, letter)
# grab magic args (for example `xaxis = (:flip, :log)`)
args = wraptuple(get(d_in, Symbol(letter, :axis), ()))
# build the KW of arguments from the letter version (i.e. xticks --> ticks)
kw = KW()
for (k,v) in _axis_defaults
# first get the args without the letter: `tickfont = font(10)`
# note: we don't pop because we want this to apply to all axes! (delete after all have finished)
if haskey(d_in, k)
kw[k] = slice_arg(d_in[k], subplot_index)
end
# then get those args that were passed with a leading letter: `xlabel = "X"`
lk = Symbol(letter, k)
if haskey(d_in, lk)
kw[k] = slice_arg(d_in[lk], subplot_index)
end
end
# update the axis
update!(axis, args...; kw...)
# convert a bool into auto or nothing
if isa(axis[:ticks], Bool)
axis[:ticks] = axis[:ticks] ? :auto : nothing
end
# # update the axis colors
color_or_nothing!(axis.d, :foreground_color_axis)
color_or_nothing!(axis.d, :foreground_color_border)
color_or_nothing!(axis.d, :foreground_color_guide)
color_or_nothing!(axis.d, :foreground_color_text)
# handle linking here. if we're passed a list of
# other subplots to link to, link them together
link = axis[:link]
if !isempty(link)
for other_sp in link
other_sp = get_subplot(plt, other_sp)
link_axes!(axis, get_axis(other_sp, letter))
end
axis.d[:link] = []
end
end end
end end
@ -1071,7 +1094,7 @@ function _add_defaults!(d::KW, plt::Plot, sp::Subplot, commandIndex::Int)
# add default values to our dictionary, being careful not to delete what we just added! # add default values to our dictionary, being careful not to delete what we just added!
for (k,v) in _series_defaults for (k,v) in _series_defaults
slice_arg!(d, d, k, v, commandIndex, remove_pair = false) slice_arg!(d, d, k, v, commandIndex, false)
end end
# this is how many series belong to this subplot # this is how many series belong to this subplot

View File

@ -38,7 +38,7 @@ function get_axis(sp::Subplot, letter::Symbol)
sp.attr[axissym] sp.attr[axissym]
else else
sp.attr[axissym] = Axis(sp, letter) sp.attr[axissym] = Axis(sp, letter)
end end::Axis
end end
function process_axis_arg!(d::KW, arg, letter = "") function process_axis_arg!(d::KW, arg, letter = "")

View File

@ -43,7 +43,6 @@ 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...)
info("started to plot")
d = KW(kw) d = KW(kw)
preprocessArgs!(d) preprocessArgs!(d)
@ -108,7 +107,7 @@ function plot(plt1::Plot, plts_tail::Plot...; kw...)
# first apply any args for the subplots # first apply any args for the subplots
for (idx,sp) in enumerate(plt.subplots) for (idx,sp) in enumerate(plt.subplots)
_update_subplot_args(plt, sp, d, idx, remove_pair = false) _update_subplot_args(plt, sp, d, idx, false)
end end
# do we need to link any axes together? # do we need to link any axes together?
@ -152,16 +151,13 @@ end
# getting ready to add the series... last update to subplot from anything # getting ready to add the series... last update to subplot from anything
# that might have been added during series recipes # that might have been added during series recipes
function _prepare_subplot(plt::Plot, d::KW) function _prepare_subplot{T}(plt::Plot{T}, d::KW)
st = d[:seriestype] st::Symbol = d[:seriestype]
sp = d[:subplot] sp::Subplot{T} = d[:subplot]
sp_idx = get_subplot_index(plt, sp) sp_idx = get_subplot_index(plt, sp)
_update_subplot_args(plt, sp, d, sp_idx) _update_subplot_args(plt, sp, d, sp_idx, true)
# do we want to override the series type? st = _override_seriestype_check(d, st)
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? # change to a 3d projection for this subplot?
if is3d(st) if is3d(st)
@ -173,7 +169,19 @@ function _prepare_subplot(plt::Plot, d::KW)
_initialize_subplot(plt, sp) _initialize_subplot(plt, sp)
sp.attr[:init] = true sp.attr[:init] = true
end end
sp::Subplot sp
end
function _override_seriestype_check(d::KW, st::Symbol)
# do we want to override the series type?
if !is3d(st)
z = d[:z]
if !isa(z, Void) && (size(d[:x]) == size(d[:y]) == size(z))
st = (st == :scatter ? :scatter3d : :path3d)
d[:seriestype] = st
end
end
st
end end
function _prepare_annotations(sp::Subplot, d::KW) function _prepare_annotations(sp::Subplot, d::KW)
@ -244,7 +252,7 @@ function _process_seriesrecipe(plt::Plot, d::KW)
end end
function command_idx(kw_list::AVec{KW}, kw::KW) function command_idx(kw_list::AVec{KW}, kw::KW)
kw[:series_plotindex] - kw_list[1][:series_plotindex] + 1 Int(kw[:series_plotindex]) - Int(kw_list[1][:series_plotindex]) + 1
end end
function _expand_seriestype_array(d::KW, args) function _expand_seriestype_array(d::KW, args)
@ -406,7 +414,7 @@ end
# to generate a list of RecipeData objects (data + attributes). # to generate a list of RecipeData objects (data + attributes).
# If we applied a "plot recipe" without error, then add the returned datalist's KWs, # If we applied a "plot recipe" without error, then add the returned datalist's KWs,
# otherwise we just add the original KW. # otherwise we just add the original KW.
function _process_plotrecipe(kw::KW, kw_list::Vector{KW}, still_to_process::Vector{KW}) function _process_plotrecipe(plt::Plot, kw::KW, kw_list::Vector{KW}, still_to_process::Vector{KW})
if !isa(get(kw, :seriestype, nothing), Symbol) if !isa(get(kw, :seriestype, nothing), Symbol)
# seriestype was never set, or it's not a Symbol, so it can't be a plot recipe # seriestype was never set, or it's not a Symbol, so it can't be a plot recipe
push!(kw_list, kw) push!(kw_list, kw)
@ -511,7 +519,7 @@ function _subplot_setup(plt::Plot, d::KW, kw_list::Vector{KW})
# override subplot/axis args. `sp_attrs` take precendence # override subplot/axis args. `sp_attrs` take precendence
for (idx,sp) in enumerate(plt.subplots) for (idx,sp) in enumerate(plt.subplots)
attr = merge(d, get(sp_attrs, sp, KW())) attr = merge(d, get(sp_attrs, sp, KW()))
_update_subplot_args(plt, sp, attr, idx, remove_pair = false) _update_subplot_args(plt, sp, attr, idx, false)
end end
# do we need to link any axes together? # do we need to link any axes together?
@ -543,7 +551,7 @@ function _plot!(plt::Plot, d::KW, args::Tuple)
kw_list = KW[] kw_list = KW[]
while !isempty(still_to_process) while !isempty(still_to_process)
next_kw = shift!(still_to_process) next_kw = shift!(still_to_process)
_process_plotrecipe(next_kw, kw_list, still_to_process) _process_plotrecipe(plt, next_kw, kw_list, still_to_process)
end end
# -------------------------------- # --------------------------------
@ -560,11 +568,11 @@ function _plot!(plt::Plot, d::KW, args::Tuple)
# -------------------------------- # --------------------------------
for kw in kw_list for kw in kw_list
sp = kw[:subplot] sp::Subplot = kw[:subplot]
idx = get_subplot_index(plt, sp) # idx = get_subplot_index(plt, sp)
# # we update subplot args in case something like the color palatte is part of the recipe # # we update subplot args in case something like the color palatte is part of the recipe
# _update_subplot_args(plt, sp, kw, idx) # _update_subplot_args(plt, sp, kw, idx, true)
# set default values, select from attribute cycles, and generally set the final attributes # set default values, select from attribute cycles, and generally set the final attributes
_add_defaults!(kw, plt, sp, command_idx(kw_list,kw)) _add_defaults!(kw, plt, sp, command_idx(kw_list,kw))
@ -584,7 +592,7 @@ function _plot!(plt::Plot, d::KW, args::Tuple)
# do we want to force display? # do we want to force display?
if plt[:show] if plt[:show]
gui() gui(plt)
end end
plt plt

View File

@ -1,10 +1,5 @@
# TODO: there should be a distinction between an object that will manage a full plot, vs a component of a plot.
# the PlotRecipe as currently implemented is more of a "custom component"
# a recipe should fully describe the plotting command(s) and call them, likewise for updating.
# actually... maybe those should explicitly derive from AbstractPlot???
""" """
You can easily define your own plotting recipes with convenience methods: You can easily define your own plotting recipes with convenience methods:
@ -106,60 +101,51 @@ num_series(x) = 1
RecipesBase.apply_recipe{T}(d::KW, ::Type{T}, plt::Plot) = throw(MethodError("Unmatched plot recipe: $T")) RecipesBase.apply_recipe{T}(d::KW, ::Type{T}, plt::Plot) = throw(MethodError("Unmatched plot recipe: $T"))
# # TODO: remove when StatPlots is ready # TODO: remove when StatPlots is ready
# if is_installed("DataFrames") if is_installed("DataFrames")
# @eval begin @eval begin
# import DataFrames import DataFrames
# # if it's one symbol, set the guide and return the column # if it's one symbol, set the guide and return the column
# function handle_dfs(df::DataFrames.AbstractDataFrame, d::KW, letter, sym::Symbol) function handle_dfs(df::DataFrames.AbstractDataFrame, d::KW, letter, sym::Symbol)
# get!(d, Symbol(letter * "guide"), string(sym)) get!(d, Symbol(letter * "guide"), string(sym))
# collect(df[sym]) collect(df[sym])
# end end
# # if it's an array of symbols, set the labels and return a Vector{Any} of columns # if it's an array of symbols, set the labels and return a Vector{Any} of columns
# function handle_dfs(df::DataFrames.AbstractDataFrame, d::KW, letter, syms::AbstractArray{Symbol}) function handle_dfs(df::DataFrames.AbstractDataFrame, d::KW, letter, syms::AbstractArray{Symbol})
# get!(d, :label, reshape(syms, 1, length(syms))) get!(d, :label, reshape(syms, 1, length(syms)))
# Any[collect(df[s]) for s in syms] Any[collect(df[s]) for s in syms]
# end end
# # for anything else, no-op # for anything else, no-op
# function handle_dfs(df::DataFrames.AbstractDataFrame, d::KW, letter, anything) function handle_dfs(df::DataFrames.AbstractDataFrame, d::KW, letter, anything)
# anything anything
# end end
# # handle grouping by DataFrame column # handle grouping by DataFrame column
# function extractGroupArgs(group::Symbol, df::DataFrames.AbstractDataFrame, args...) function extractGroupArgs(group::Symbol, df::DataFrames.AbstractDataFrame, args...)
# extractGroupArgs(collect(df[group])) extractGroupArgs(collect(df[group]))
# end end
# # if a DataFrame is the first arg, lets swap symbols out for columns # if a DataFrame is the first arg, lets swap symbols out for columns
# @recipe function f(df::DataFrames.AbstractDataFrame, args...) @recipe function f(df::DataFrames.AbstractDataFrame, args...)
# # if any of these attributes are symbols, swap out for the df column # if any of these attributes are symbols, swap out for the df column
# for k in (:fillrange, :line_z, :marker_z, :markersize, :ribbon, :weights, :xerror, :yerror) for k in (:fillrange, :line_z, :marker_z, :markersize, :ribbon, :weights, :xerror, :yerror)
# if haskey(d, k) && isa(d[k], Symbol) if haskey(d, k) && isa(d[k], Symbol)
# d[k] = collect(df[d[k]]) d[k] = collect(df[d[k]])
# end end
# end end
# # return a list of new arguments # return a list of new arguments
# tuple(Any[handle_dfs(df, d, (i==1 ? "x" : i==2 ? "y" : "z"), arg) for (i,arg) in enumerate(args)]...) tuple(Any[handle_dfs(df, d, (i==1 ? "x" : i==2 ? "y" : "z"), arg) for (i,arg) in enumerate(args)]...)
# end end
# end end
# end 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, st) = KW[d]
# for seriestype `line`, need to sort by x values # for seriestype `line`, need to sort by x values
@recipe function f(::Type{Val{:line}}, x, y, z) @recipe function f(::Type{Val{:line}}, x, y, z)
@ -174,21 +160,6 @@ RecipesBase.apply_recipe{T}(d::KW, ::Type{T}, plt::Plot) = throw(MethodError("Un
end end
@deps line path @deps line path
# @recipe function f(::Type{Val{:sticks}}, x, y, z)
# nx = length(x)
# n = 3nx
# newx, newy = zeros(n), zeros(n)
# for i=1:nx
# rng = 3i-2:3i
# newx[rng] = x[i]
# newy[rng] = [0., y[i], 0.]
# end
# x := newx
# y := newy
# seriestype := :path
# ()
# end
# @deps sticks path
function hvline_limits(axis::Axis) function hvline_limits(axis::Axis)
vmin, vmax = axis_limits(axis) vmin, vmax = axis_limits(axis)
@ -898,53 +869,6 @@ end
@deps quiver shape path @deps quiver shape path
# ---------------------------------------------------------------------------
# ---------------------------------------------------------------------------
# ---------------------------------------------------------------------------
# function rotate(x::Real, y::Real, θ::Real; center = (0,0))
# cx = x - center[1]
# cy = y - center[2]
# xrot = cx * cos(θ) - cy * sin(θ)
# yrot = cy * cos(θ) + cx * sin(θ)
# xrot + center[1], yrot + center[2]
# end
#
# # ---------------------------------------------------------------------------
#
# type EllipseRecipe <: PlotRecipe
# w::Float64
# h::Float64
# x::Float64
# y::Float64
# θ::Float64
# end
# EllipseRecipe(w,h,x,y) = EllipseRecipe(w,h,x,y,0)
#
# # return x,y coords of a rotated ellipse, centered at the origin
# function rotatedEllipse(w, h, x, y, θ, rotθ)
# # # coord before rotation
# xpre = w * cos(θ)
# ypre = h * sin(θ)
#
# # rotate and translate
# r = rotate(xpre, ypre, rotθ)
# x + r[1], y + r[2]
# end
#
# function getRecipeXY(ep::EllipseRecipe)
# x, y = unzip([rotatedEllipse(ep.w, ep.h, ep.x, ep.y, u, ep.θ) for u in linspace(0,2π,100)])
# top = rotate(0, ep.h, ep.θ)
# right = rotate(ep.w, 0, ep.θ)
# linex = Float64[top[1], 0, right[1]] + ep.x
# liney = Float64[top[2], 0, right[2]] + ep.y
# Any[x, linex], Any[y, liney]
# end
#
# function getRecipeArgs(ep::EllipseRecipe)
# [(:line, (3, [:dot :solid], [:red :blue], :path))]
# end
# ------------------------------------------------- # -------------------------------------------------
# TODO: move OHLC to PlotRecipes finance.jl # TODO: move OHLC to PlotRecipes finance.jl

View File

@ -30,7 +30,7 @@ get_subplot(plt::Plot, i::Integer) = plt.subplots[i]
get_subplot(plt::Plot, k) = plt.spmap[k] get_subplot(plt::Plot, k) = plt.spmap[k]
get_subplot(series::Series) = series.d[:subplot] get_subplot(series::Series) = series.d[:subplot]
get_subplot_index(plt::Plot, idx::Integer) = idx get_subplot_index(plt::Plot, idx::Integer) = Int(idx)
get_subplot_index(plt::Plot, sp::Subplot) = findfirst(_ -> _ === sp, plt.subplots) get_subplot_index(plt::Plot, sp::Subplot) = findfirst(_ -> _ === sp, plt.subplots)
series_list(sp::Subplot) = filter(series -> series.d[:subplot] === sp, sp.plt.series_list) series_list(sp::Subplot) = filter(series -> series.d[:subplot] === sp, sp.plt.series_list)