plot logic cleanup; added plot recipes

This commit is contained in:
Thomas Breloff 2016-06-18 09:33:34 -04:00
parent b7a95244db
commit cf12ff8070

View File

@ -244,12 +244,15 @@ function _apply_series_recipe(plt::Plot, d::KW)
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 # this is the core plotting function. recursively apply recipes to build
# a list of series KW dicts. # a list of series KW dicts.
# note: at entry, we only have those preprocessed args which were passed in... no default values yet # note: at entry, we only have those preprocessed args which were passed in... no default values yet
function _plot!(plt::Plot, d::KW, args...) function _plot!(plt::Plot, d::KW, args...)
# d = plt.user_attr
d[:plot_object] = plt d[:plot_object] = plt
# the grouping mechanism is a recipe on a GroupBy object # the grouping mechanism is a recipe on a GroupBy object
@ -259,13 +262,12 @@ function _plot!(plt::Plot, d::KW, args...)
args = (extractGroupArgs(d[:group], args...), args...) args = (extractGroupArgs(d[:group], args...), args...)
end 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[] kw_list = KW[]
# still_to_process = isempty(args) ? [] : [RecipeData(copy(d), args)]
still_to_process = if isempty(args) still_to_process = if isempty(args)
[] []
else else
# if we were passed a vector/matrix of series types and there's more than one row,
# we want to duplicate the inputs, once for each seriestype row.
sts = get(d, :seriestype, :path) sts = get(d, :seriestype, :path)
if typeof(sts) <: AbstractArray if typeof(sts) <: AbstractArray
[begin [begin
@ -278,6 +280,10 @@ function _plot!(plt::Plot, d::KW, args...)
end end
end end
# --------------------------------
# "USER RECIPES"
# --------------------------------
# for plotting recipes, swap out the args and update the parameter dictionary # 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. # 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. # each pass through the loop, we pop one off and apply the recipe.
@ -368,25 +374,49 @@ function _plot!(plt::Plot, d::KW, args...)
# don't allow something else to handle it # don't allow something else to handle it
d[:smooth] = false d[:smooth] = false
# merge in anything meant for plot/subplot/axis # --------------------------------
for kw in kw_list # "PLOT RECIPES"
for (k,v) in kw # --------------------------------
for defdict in (_plot_defaults,)
# _subplot_defaults, # TODO: a new recipe type: "plot recipe", which acts like a series type, and is processed before
# _axis_defaults, # the plot layout is created, which allows for setting layouts and other plot-wide attributes.
# _axis_defaults_byletter) # we get inputs which have been fully processed by "user recipes" and "type recipes",
if haskey(defdict, k) # so we can expect standard vectors, surfaces, etc. No defaults have been set yet.
d[k] = pop!(kw, k) still_to_process = kw_list
end 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)
try
st = next_kw[:seriestype]
st = next_kw[:seriestype] = get(_typeAliases, st, st)
datalist = RecipesBase.apply_recipe(next_kw, Val{st})
info("processed $st $(length(datalist))")
for data in datalist
push!(kw_list, data.d)
end end
catch err
@show err
push!(kw_list, next_kw)
end 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 # TODO: init subplots here
_update_plot_args(plt, d) _update_plot_args(plt, d)
if !plt.init if !plt.init
plt.o = _create_backend_figure(plt) plt.o = _create_backend_figure(plt)
# DD(d)
# create the layout and subplots from the inputs # create the layout and subplots from the inputs
plt.layout, plt.subplots, plt.spmap = build_layout(plt.attr) plt.layout, plt.subplots, plt.spmap = build_layout(plt.attr)
@ -415,30 +445,24 @@ function _plot!(plt::Plot, d::KW, args...)
sp = Subplot(backend(), parent=parent) sp = Subplot(backend(), parent=parent)
sp.plt = plt sp.plt = plt
sp.attr[:relative_bbox] = bb sp.attr[:relative_bbox] = bb
push!(plt.subplots, sp)
sp.attr[:subplot_index] = length(plt.subplots) sp.attr[:subplot_index] = length(plt.subplots)
push!(plt.subplots, sp)
push!(plt.inset_subplots, sp) push!(plt.inset_subplots, sp)
end end
end end
# we'll keep a map of subplot to an attribute override dict. # we'll keep a map of subplot to an attribute override dict.
# any series which belong to that subplot # 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}() sp_attrs = Dict{Subplot,Any}()
for kw in kw_list for kw in kw_list
# get the Subplot object to which the series belongs # get the Subplot object to which the series belongs.
sp = get(kw, :subplot, :auto) sps = get(kw, :subplot, :auto)
command_idx = kw[:series_plotindex] - kw_list[1][:series_plotindex] + 1 sp = get_subplot(plt, cycle(sps == :auto ? plt.subplots : sps, command_idx(kw_list,kw)))
sp = cycle(sp==:auto ? plt.subplots : sp, command_idx) kw[:subplot] = sp
# sp = if sp == :auto
# cycle(plt.subplots, command_idx)
# # mod1(command_idx, length(plt.subplots))
# else
# cycle(sp, command_idx)
# end
sp = kw[:subplot] = get_subplot(plt, sp)
# idx = get_subplot_index(plt, sp)
attr = KW()
attr = KW()
for (k,v) in kw for (k,v) in kw
for defdict in (_subplot_defaults, for defdict in (_subplot_defaults,
_axis_defaults, _axis_defaults,
@ -451,60 +475,33 @@ function _plot!(plt::Plot, d::KW, args...)
sp_attrs[sp] = attr sp_attrs[sp] = attr
end end
# override subplot/axis args. `sp_attrs` take precendence
# # 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) for (idx,sp) in enumerate(plt.subplots)
# if we picked up any subplot-specific overrides, merge them here
attr = merge(d, get(sp_attrs, sp, KW())) attr = merge(d, get(sp_attrs, sp, KW()))
# DD(attr, "sp$idx")
_update_subplot_args(plt, sp, attr, idx, remove_pair = false) _update_subplot_args(plt, sp, attr, idx, remove_pair = false)
end end
# do we need to link any axes together? # do we need to link any axes together?
link_axes!(plt.layout, plt[:link]) link_axes!(plt.layout, plt[:link])
# !!! note: At this point, kw_list is fully decomposed into individual series... one KW per series. !!! # !!! 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 !!! # !!! The next step is to recursively apply series recipes until the backend supports that series type !!!
# --------------------------------
# "SERIES RECIPES"
# --------------------------------
# this is it folks! # this is it folks!
# TODO: we probably shouldn't use i for tracking series index, but rather explicitly track it in recipes # TODO: we probably shouldn't use i for tracking series index, but rather explicitly track it in recipes
for kw in kw_list for kw in kw_list
command_idx = kw[:series_plotindex] - kw_list[1][:series_plotindex] + 1
# # get the Subplot object to which the series belongs
# sp = get(kw, :subplot, :auto)
# sp = if sp == :auto
# mod1(i,length(plt.subplots))
# else
# slice_arg(sp, i)
# end
# sp = kw[:subplot] = get_subplot(plt, sp)
sp = kw[:subplot] sp = kw[:subplot]
idx = get_subplot_index(plt, sp) idx = get_subplot_index(plt, sp)
# # strip out series annotations (those which are based on series x/y coords) # # we update subplot args in case something like the color palatte is part of the recipe
# # and add them to the subplot attr # _update_subplot_args(plt, sp, kw, idx)
# sp_anns = annotations(sp[:annotations])
# anns = annotations(pop!(kw, :series_annotations, []))
# if length(anns) > 0
# x, y = kw[:x], kw[: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)
# 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 # set default values, select from attribute cycles, and generally set the final attributes
_add_defaults!(kw, plt, sp, command_idx) _add_defaults!(kw, plt, sp, command_idx(kw_list,kw))
# now we have a fully specified series, with colors chosen. we must recursively handle # 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, # series recipes, which dispatch on seriestype. If a backend does not natively support a seriestype,
@ -515,6 +512,8 @@ function _plot!(plt::Plot, d::KW, args...)
_apply_series_recipe(plt, kw) _apply_series_recipe(plt, kw)
end end
# --------------------------------
current(plt) current(plt)
# note: lets ignore the show param and effectively use the semicolon at the end of the REPL statement # note: lets ignore the show param and effectively use the semicolon at the end of the REPL statement