Compare commits
22 Commits
master
...
as/recipes
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
0d4795cc1b | ||
|
|
94cd1c65cf | ||
|
|
794ffe6975 | ||
|
|
70ec20293a | ||
|
|
97bbabf53b | ||
|
|
36176c0646 | ||
|
|
eb72e50a3a | ||
|
|
f0848e196b | ||
|
|
c31654a520 | ||
|
|
4fab583c02 | ||
|
|
fb82e6c2a5 | ||
|
|
6aa4c4ee7f | ||
|
|
37391f2843 | ||
|
|
9dcc710f66 | ||
|
|
9b3c676cd0 | ||
|
|
1ffda51619 | ||
|
|
a9d32ccaf5 | ||
|
|
dee4a7421d | ||
|
|
8d90546056 | ||
|
|
d507ea9ab6 | ||
|
|
2731b6a945 | ||
|
|
c720a79ca3 |
@ -35,4 +35,4 @@ notifications:
|
|||||||
script:
|
script:
|
||||||
- if [[ -a .git/shallow ]]; then git fetch --unshallow; fi
|
- if [[ -a .git/shallow ]]; then git fetch --unshallow; fi
|
||||||
- if [[ `uname` = "Linux" ]]; then TESTCMD="xvfb-run julia"; else TESTCMD="julia"; fi
|
- if [[ `uname` = "Linux" ]]; then TESTCMD="xvfb-run julia"; else TESTCMD="julia"; fi
|
||||||
- $TESTCMD -e 'using Pkg; Pkg.build(); Pkg.test(coverage=true)'
|
- $TESTCMD -e 'using Pkg; pkg"add https://github.com/mkborregaard/RecipeUtils.jl"; Pkg.build(); Pkg.test(coverage=true)'
|
||||||
|
|||||||
@ -21,6 +21,7 @@ PlotUtils = "995b91a9-d308-5afd-9ec6-746e21dbc043"
|
|||||||
Printf = "de0858da-6303-5e67-8744-51eddeeeb8d7"
|
Printf = "de0858da-6303-5e67-8744-51eddeeeb8d7"
|
||||||
REPL = "3fa0cd96-eef1-5676-8a61-b3b8758bbffb"
|
REPL = "3fa0cd96-eef1-5676-8a61-b3b8758bbffb"
|
||||||
Random = "9a3f8284-a2c9-5f02-9a11-845980a1fd5c"
|
Random = "9a3f8284-a2c9-5f02-9a11-845980a1fd5c"
|
||||||
|
RecipePipeline = "01d81517-befc-4cb6-b9ec-a95719d0359c"
|
||||||
RecipesBase = "3cdcf5f2-1ef4-517c-9805-6587b60abb01"
|
RecipesBase = "3cdcf5f2-1ef4-517c-9805-6587b60abb01"
|
||||||
Reexport = "189a3867-3050-52da-a836-e630ba90ab69"
|
Reexport = "189a3867-3050-52da-a836-e630ba90ab69"
|
||||||
Requires = "ae029012-a4dd-5104-9daa-d747884805df"
|
Requires = "ae029012-a4dd-5104-9daa-d747884805df"
|
||||||
|
|||||||
11
src/Plots.jl
11
src/Plots.jl
@ -18,6 +18,13 @@ using Base.Meta
|
|||||||
import Showoff
|
import Showoff
|
||||||
import StatsBase
|
import StatsBase
|
||||||
import JSON
|
import JSON
|
||||||
|
import RecipePipeline: _process_userrecipe, _process_plotrecipe,
|
||||||
|
_process_seriesrecipe, _preprocess_args,
|
||||||
|
preprocessArgs!, is_st_supported,
|
||||||
|
finalize_subplot!, recipe_pipeline!,
|
||||||
|
_recipe_init!, _recipe_after_user!,
|
||||||
|
_recipe_after_plot!, _recipe_before_series!,
|
||||||
|
_recipe_finish!, is_st_supported
|
||||||
|
|
||||||
using Requires
|
using Requires
|
||||||
|
|
||||||
@ -222,7 +229,7 @@ end
|
|||||||
|
|
||||||
const CURRENT_BACKEND = CurrentBackend(:none)
|
const CURRENT_BACKEND = CurrentBackend(:none)
|
||||||
|
|
||||||
include("precompile.jl")
|
# include("precompile.jl")
|
||||||
_precompile_()
|
# _precompile_()
|
||||||
|
|
||||||
end # module
|
end # module
|
||||||
|
|||||||
120
src/pipeline.jl
120
src/pipeline.jl
@ -1,5 +1,13 @@
|
|||||||
|
function finalize_subplot!(plt::Plot, st, plotattributes::AKW)
|
||||||
|
sp = _prepare_subplot(plt, plotattributes)
|
||||||
|
_prepare_annotations(sp, plotattributes)
|
||||||
|
_expand_subplot_extrema(sp, plotattributes, st)
|
||||||
|
_update_series_attributes!(plotattributes, plt, sp)
|
||||||
|
_add_the_series(plt, sp, plotattributes)
|
||||||
|
end
|
||||||
|
|
||||||
|
# Override the RecipesPipeline `is_st_supported` for Plots.
|
||||||
|
is_st_supported(::Plot, st::Symbol) = is_seriestype_supported(st)
|
||||||
# ------------------------------------------------------------------
|
# ------------------------------------------------------------------
|
||||||
# preprocessing
|
# preprocessing
|
||||||
|
|
||||||
@ -59,39 +67,6 @@ end
|
|||||||
# user recipes
|
# user recipes
|
||||||
|
|
||||||
|
|
||||||
function _process_userrecipes(plt::Plot, plotattributes::AKW, args)
|
|
||||||
still_to_process = RecipeData[]
|
|
||||||
args = _preprocess_args(plotattributes, args, still_to_process)
|
|
||||||
|
|
||||||
# for plotting recipes, swap out the args and update the parameter dictionary
|
|
||||||
# we are keeping a stack 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, the ones that are not
|
|
||||||
# are placed on top of the stack and are then processed further.
|
|
||||||
kw_list = KW[]
|
|
||||||
while !isempty(still_to_process)
|
|
||||||
# grab the first in line to be processed and either add it to the kw_list or
|
|
||||||
# pass it through apply_recipe to generate a list of RecipeData objects (data + attributes)
|
|
||||||
# for further processing.
|
|
||||||
next_series = popfirst!(still_to_process)
|
|
||||||
# recipedata should be of type RecipeData. if it's not then the inputs must not have been fully processed by recipes
|
|
||||||
if !(typeof(next_series) <: RecipeData)
|
|
||||||
error("Inputs couldn't be processed... expected RecipeData but got: $next_series")
|
|
||||||
end
|
|
||||||
if isempty(next_series.args)
|
|
||||||
_process_userrecipe(plt, kw_list, next_series)
|
|
||||||
else
|
|
||||||
rd_list = RecipesBase.apply_recipe(next_series.plotattributes, next_series.args...)
|
|
||||||
prepend!(still_to_process,rd_list)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
# don't allow something else to handle it
|
|
||||||
plotattributes[:smooth] = false
|
|
||||||
kw_list
|
|
||||||
end
|
|
||||||
|
|
||||||
function _process_userrecipe(plt::Plot, kw_list::Vector{KW}, recipedata::RecipeData)
|
function _process_userrecipe(plt::Plot, kw_list::Vector{KW}, recipedata::RecipeData)
|
||||||
# when the arg tuple is empty, that means there's nothing left to recursively
|
# when the arg tuple is empty, that means there's nothing left to recursively
|
||||||
# process... finish up and add to the kw_list
|
# process... finish up and add to the kw_list
|
||||||
@ -168,39 +143,6 @@ function _add_smooth_kw(kw_list::Vector{KW}, kw::AKW)
|
|||||||
end
|
end
|
||||||
|
|
||||||
# ------------------------------------------------------------------
|
# ------------------------------------------------------------------
|
||||||
# plot recipes
|
|
||||||
|
|
||||||
# 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.
|
|
||||||
function _process_plotrecipe(plt::Plot, kw::AKW, kw_list::Vector{KW}, still_to_process::Vector{KW})
|
|
||||||
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
|
|
||||||
push!(kw_list, kw)
|
|
||||||
return
|
|
||||||
end
|
|
||||||
try
|
|
||||||
st = kw[:seriestype]
|
|
||||||
st = kw[:seriestype] = get(_typeAliases, st, st)
|
|
||||||
datalist = RecipesBase.apply_recipe(kw, Val{st}, plt)
|
|
||||||
for data in datalist
|
|
||||||
preprocessArgs!(data.plotattributes)
|
|
||||||
if data.plotattributes[:seriestype] == st
|
|
||||||
error("Plot recipe $st returned the same seriestype: $(data.plotattributes)")
|
|
||||||
end
|
|
||||||
push!(still_to_process, data.plotattributes)
|
|
||||||
end
|
|
||||||
catch err
|
|
||||||
if isa(err, MethodError)
|
|
||||||
push!(kw_list, kw)
|
|
||||||
else
|
|
||||||
rethrow()
|
|
||||||
end
|
|
||||||
end
|
|
||||||
return
|
|
||||||
end
|
|
||||||
|
|
||||||
|
|
||||||
# ------------------------------------------------------------------
|
# ------------------------------------------------------------------
|
||||||
# setup plot and subplot
|
# setup plot and subplot
|
||||||
@ -382,47 +324,3 @@ function _add_the_series(plt, sp, plotattributes)
|
|||||||
push!(sp.series_list, series)
|
push!(sp.series_list, series)
|
||||||
_series_added(plt, series)
|
_series_added(plt, series)
|
||||||
end
|
end
|
||||||
|
|
||||||
# -------------------------------------------------------------------------------
|
|
||||||
|
|
||||||
# this method recursively applies series recipes when the seriestype is not supported
|
|
||||||
# natively by the backend
|
|
||||||
function _process_seriesrecipe(plt::Plot, plotattributes::AKW)
|
|
||||||
#println("process $(typeof(plotattributes))")
|
|
||||||
# replace seriestype aliases
|
|
||||||
st = Symbol(plotattributes[:seriestype])
|
|
||||||
st = plotattributes[:seriestype] = get(_typeAliases, st, st)
|
|
||||||
|
|
||||||
# shapes shouldn't have fillrange set
|
|
||||||
if plotattributes[:seriestype] == :shape
|
|
||||||
plotattributes[:fillrange] = nothing
|
|
||||||
end
|
|
||||||
|
|
||||||
# if it's natively supported, finalize processing and pass along to the backend, otherwise recurse
|
|
||||||
if is_seriestype_supported(st)
|
|
||||||
sp = _prepare_subplot(plt, plotattributes)
|
|
||||||
_prepare_annotations(sp, plotattributes)
|
|
||||||
_expand_subplot_extrema(sp, plotattributes, st)
|
|
||||||
_update_series_attributes!(plotattributes, plt, sp)
|
|
||||||
_add_the_series(plt, sp, plotattributes)
|
|
||||||
|
|
||||||
else
|
|
||||||
# get a sub list of series for this seriestype
|
|
||||||
datalist = RecipesBase.apply_recipe(plotattributes, Val{st}, plotattributes[:x], plotattributes[:y], plotattributes[:z])
|
|
||||||
|
|
||||||
# assuming there was no error, recursively apply the series recipes
|
|
||||||
for data in datalist
|
|
||||||
if isa(data, RecipeData)
|
|
||||||
preprocessArgs!(data.plotattributes)
|
|
||||||
if data.plotattributes[:seriestype] == st
|
|
||||||
error("The seriestype didn't change in series recipe $st. This will cause a StackOverflow.")
|
|
||||||
end
|
|
||||||
_process_seriesrecipe(plt, data.plotattributes)
|
|
||||||
else
|
|
||||||
@warn("Unhandled recipe: $(data)")
|
|
||||||
break
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
nothing
|
|
||||||
end
|
|
||||||
|
|||||||
75
src/plot.jl
75
src/plot.jl
@ -54,7 +54,7 @@ function plot(args...; kw...)
|
|||||||
# create an empty Plot then process
|
# create an empty Plot then process
|
||||||
plt = Plot()
|
plt = Plot()
|
||||||
# plt.user_attr = plotattributes
|
# plt.user_attr = plotattributes
|
||||||
_plot!(plt, plotattributes, args)
|
recipe_pipeline!(plt, plotattributes, args, type_aliases=_typeAliases)
|
||||||
end
|
end
|
||||||
|
|
||||||
# build a new plot from existing plots
|
# build a new plot from existing plots
|
||||||
@ -155,7 +155,7 @@ function plot!(plt::Plot, args...; kw...)
|
|||||||
plotattributes = KW(kw)
|
plotattributes = KW(kw)
|
||||||
preprocessArgs!(plotattributes)
|
preprocessArgs!(plotattributes)
|
||||||
# merge!(plt.user_attr, plotattributes)
|
# merge!(plt.user_attr, plotattributes)
|
||||||
_plot!(plt, plotattributes, args)
|
recipe_pipeline!(plt, plotattributes, args, type_aliases=_typeAliases)
|
||||||
end
|
end
|
||||||
|
|
||||||
# -------------------------------------------------------------------------------
|
# -------------------------------------------------------------------------------
|
||||||
@ -163,78 +163,36 @@ 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, plotattributes::AKW, args::Tuple)
|
|
||||||
|
## here we implement the recipe interface
|
||||||
|
function _recipe_init!(plt::Plot, plotattributes::AKW, args::Tuple)
|
||||||
plotattributes[:plot_object] = plt
|
plotattributes[:plot_object] = plt
|
||||||
|
|
||||||
if !isempty(args) && !isdefined(Main, :StatsPlots) &&
|
if !isempty(args) && !isdefined(Main, :StatsPlots) &&
|
||||||
first(split(string(typeof(args[1])), ".")) == "DataFrames"
|
first(split(string(typeof(args[1])), ".")) == "DataFrames"
|
||||||
@warn("You're trying to plot a DataFrame, but this functionality is provided by StatsPlots")
|
@warn("You're trying to plot a DataFrame, but this functionality is provided by StatsPlots")
|
||||||
end
|
end
|
||||||
|
end
|
||||||
|
|
||||||
# --------------------------------
|
function _recipe_after_plot!(plt::Plot, plotattributes::AKW, kw_list::Vector{KW})
|
||||||
# "USER RECIPES"
|
|
||||||
# --------------------------------
|
|
||||||
|
|
||||||
kw_list = _process_userrecipes(plt, plotattributes, args)
|
|
||||||
|
|
||||||
# @info(1)
|
|
||||||
# map(DD, kw_list)
|
|
||||||
|
|
||||||
|
|
||||||
# --------------------------------
|
|
||||||
# "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)
|
|
||||||
next_kw = popfirst!(still_to_process)
|
|
||||||
_process_plotrecipe(plt, next_kw, kw_list, still_to_process)
|
|
||||||
end
|
|
||||||
|
|
||||||
# @info(2)
|
|
||||||
# map(DD, kw_list)
|
|
||||||
|
|
||||||
# --------------------------------
|
# --------------------------------
|
||||||
# Plot/Subplot/Layout setup
|
# Plot/Subplot/Layout setup
|
||||||
# --------------------------------
|
# --------------------------------
|
||||||
_plot_setup(plt, plotattributes, kw_list)
|
_plot_setup(plt, plotattributes, kw_list)
|
||||||
_subplot_setup(plt, plotattributes, kw_list)
|
_subplot_setup(plt, plotattributes, kw_list)
|
||||||
|
end
|
||||||
|
|
||||||
# !!! note: At this point, kw_list is fully decomposed into individual series... one KW per series. !!!
|
function _recipe_before_series!(plt::Plot, kw, kw_list)
|
||||||
# !!! The next step is to recursively apply series recipes until the backend supports that series type !!!
|
sp::Subplot = kw[:subplot]
|
||||||
|
|
||||||
# --------------------------------
|
# in series attributes given as vector with one element per series,
|
||||||
# "SERIES RECIPES"
|
# select the value for current series
|
||||||
# --------------------------------
|
_slice_series_args!(kw, plt, sp, series_idx(kw_list,kw))
|
||||||
|
|
||||||
# @info(3)
|
series_attr = Attr(kw, _series_defaults)
|
||||||
# map(DD, kw_list)
|
end
|
||||||
|
|
||||||
for kw in kw_list
|
|
||||||
sp::Subplot = kw[:subplot]
|
|
||||||
|
|
||||||
# in series attributes given as vector with one element per series,
|
|
||||||
# select the value for current series
|
|
||||||
_slice_series_args!(kw, plt, sp, series_idx(kw_list,kw))
|
|
||||||
|
|
||||||
|
|
||||||
series_attr = Attr(kw, _series_defaults)
|
|
||||||
# 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).
|
|
||||||
_process_seriesrecipe(plt, series_attr)
|
|
||||||
end
|
|
||||||
|
|
||||||
# --------------------------------
|
|
||||||
|
|
||||||
|
function _recipe_finish!(plt::Plot, plotattributes::AKW, args::Tuple)
|
||||||
current(plt)
|
current(plt)
|
||||||
|
|
||||||
# do we want to force display?
|
# do we want to force display?
|
||||||
@ -246,7 +204,6 @@ function _plot!(plt::Plot, plotattributes::AKW, args::Tuple)
|
|||||||
plt
|
plt
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
||||||
# we're getting ready to display/output. prep for layout calcs, then update
|
# we're getting ready to display/output. prep for layout calcs, then update
|
||||||
# the plot object after
|
# the plot object after
|
||||||
function prepare_output(plt::Plot)
|
function prepare_output(plt::Plot)
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user