working on _plot organization; switch alias dicts to Dict{Symbol,Symbol}; other type stability changes
This commit is contained in:
parent
b8b5a33833
commit
32c1c31139
@ -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")
|
||||||
|
|||||||
37
src/args.jl
37
src/args.jl
@ -1,6 +1,6 @@
|
|||||||
|
|
||||||
|
|
||||||
const _keyAliases = KW()
|
const _keyAliases = Dict{Symbol,Symbol}()
|
||||||
|
|
||||||
function add_aliases(sym::Symbol, aliases::Symbol...)
|
function add_aliases(sym::Symbol, aliases::Symbol...)
|
||||||
for alias in aliases
|
for alias in aliases
|
||||||
@ -11,7 +11,7 @@ function add_aliases(sym::Symbol, aliases::Symbol...)
|
|||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
function add_non_underscore_aliases!(aliases::KW)
|
function add_non_underscore_aliases!(aliases::Dict{Symbol,Symbol})
|
||||||
for (k,v) in aliases
|
for (k,v) in aliases
|
||||||
s = string(k)
|
s = string(k)
|
||||||
if '_' in s
|
if '_' in s
|
||||||
@ -24,7 +24,7 @@ end
|
|||||||
# ------------------------------------------------------------
|
# ------------------------------------------------------------
|
||||||
|
|
||||||
const _allAxes = [:auto, :left, :right]
|
const _allAxes = [:auto, :left, :right]
|
||||||
const _axesAliases = KW(
|
const _axesAliases = Dict{Symbol,Symbol}(
|
||||||
:a => :auto,
|
:a => :auto,
|
||||||
:l => :left,
|
:l => :left,
|
||||||
:r => :right
|
:r => :right
|
||||||
@ -39,7 +39,7 @@ const _allTypes = vcat([
|
|||||||
:contour, :pie, :shape, :image
|
:contour, :pie, :shape, :image
|
||||||
], _3dTypes)
|
], _3dTypes)
|
||||||
|
|
||||||
@compat const _typeAliases = KW(
|
@compat const _typeAliases = Dict{Symbol,Symbol}(
|
||||||
:n => :none,
|
:n => :none,
|
||||||
:no => :none,
|
:no => :none,
|
||||||
:l => :line,
|
:l => :line,
|
||||||
@ -83,7 +83,7 @@ like_surface(seriestype::Symbol) = seriestype in (:contour, :contourf, :contou
|
|||||||
|
|
||||||
is3d(seriestype::Symbol) = seriestype in _3dTypes
|
is3d(seriestype::Symbol) = seriestype in _3dTypes
|
||||||
is3d(series::Series) = is3d(series.d)
|
is3d(series::Series) = is3d(series.d)
|
||||||
is3d(d::KW) = trueOrAllTrue(is3d, d[:seriestype])
|
is3d(d::KW) = trueOrAllTrue(is3d, Symbol(d[:seriestype]))
|
||||||
|
|
||||||
is3d(sp::Subplot) = string(sp.attr[:projection]) == "3d"
|
is3d(sp::Subplot) = string(sp.attr[:projection]) == "3d"
|
||||||
ispolar(sp::Subplot) = string(sp.attr[:projection]) == "polar"
|
ispolar(sp::Subplot) = string(sp.attr[:projection]) == "polar"
|
||||||
@ -92,7 +92,7 @@ ispolar(series::Series) = ispolar(series.d[:subplot])
|
|||||||
# ------------------------------------------------------------
|
# ------------------------------------------------------------
|
||||||
|
|
||||||
const _allStyles = [:auto, :solid, :dash, :dot, :dashdot, :dashdotdot]
|
const _allStyles = [:auto, :solid, :dash, :dot, :dashdot, :dashdotdot]
|
||||||
@compat const _styleAliases = KW(
|
@compat const _styleAliases = Dict{Symbol,Symbol}(
|
||||||
:a => :auto,
|
:a => :auto,
|
||||||
:s => :solid,
|
:s => :solid,
|
||||||
:d => :dash,
|
:d => :dash,
|
||||||
@ -101,7 +101,7 @@ const _allStyles = [:auto, :solid, :dash, :dot, :dashdot, :dashdotdot]
|
|||||||
)
|
)
|
||||||
|
|
||||||
const _allMarkers = vcat(:none, :auto, _shape_keys) #sort(collect(keys(_shapes))))
|
const _allMarkers = vcat(:none, :auto, _shape_keys) #sort(collect(keys(_shapes))))
|
||||||
@compat const _markerAliases = KW(
|
@compat const _markerAliases = Dict{Symbol,Symbol}(
|
||||||
:n => :none,
|
:n => :none,
|
||||||
:no => :none,
|
:no => :none,
|
||||||
:a => :auto,
|
:a => :auto,
|
||||||
@ -143,7 +143,7 @@ const _allMarkers = vcat(:none, :auto, _shape_keys) #sort(collect(keys(_shapes))
|
|||||||
)
|
)
|
||||||
|
|
||||||
const _allScales = [:identity, :ln, :log2, :log10, :asinh, :sqrt]
|
const _allScales = [:identity, :ln, :log2, :log10, :asinh, :sqrt]
|
||||||
@compat const _scaleAliases = KW(
|
@compat const _scaleAliases = Dict{Symbol,Symbol}(
|
||||||
:none => :identity,
|
:none => :identity,
|
||||||
:log => :log10,
|
:log => :log10,
|
||||||
)
|
)
|
||||||
@ -330,7 +330,7 @@ autopick(notarr, idx::Integer) = notarr
|
|||||||
autopick_ignore_none_auto(arr::AVec, idx::Integer) = autopick(setdiff(arr, [:none, :auto]), idx)
|
autopick_ignore_none_auto(arr::AVec, idx::Integer) = autopick(setdiff(arr, [:none, :auto]), idx)
|
||||||
autopick_ignore_none_auto(notarr, idx::Integer) = notarr
|
autopick_ignore_none_auto(notarr, idx::Integer) = notarr
|
||||||
|
|
||||||
function aliasesAndAutopick(d::KW, sym::Symbol, aliases::KW, options::AVec, plotIndex::Int)
|
function aliasesAndAutopick(d::KW, sym::Symbol, aliases::Dict{Symbol,Symbol}, options::AVec, plotIndex::Int)
|
||||||
if d[sym] == :auto
|
if d[sym] == :auto
|
||||||
d[sym] = autopick_ignore_none_auto(options, plotIndex)
|
d[sym] = autopick_ignore_none_auto(options, plotIndex)
|
||||||
elseif haskey(aliases, d[sym])
|
elseif haskey(aliases, d[sym])
|
||||||
@ -338,7 +338,7 @@ function aliasesAndAutopick(d::KW, sym::Symbol, aliases::KW, options::AVec, plot
|
|||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
function aliases(aliasMap::KW, val)
|
function aliases(aliasMap::Dict{Symbol,Symbol}, val)
|
||||||
sortedkeys(filter((k,v)-> v==val, aliasMap))
|
sortedkeys(filter((k,v)-> v==val, aliasMap))
|
||||||
end
|
end
|
||||||
|
|
||||||
@ -687,22 +687,7 @@ function preprocessArgs!(d::KW)
|
|||||||
if haskey(d, :colorbar)
|
if haskey(d, :colorbar)
|
||||||
d[:colorbar] = convertLegendValue(d[:colorbar])
|
d[:colorbar] = convertLegendValue(d[:colorbar])
|
||||||
end
|
end
|
||||||
|
return
|
||||||
# # handle subplot links
|
|
||||||
# if haskey(d, :link)
|
|
||||||
# l = d[:link]
|
|
||||||
# if isa(l, Bool)
|
|
||||||
# d[:linkx] = l
|
|
||||||
# d[:linky] = l
|
|
||||||
# elseif isa(l, Function)
|
|
||||||
# d[:linkx] = true
|
|
||||||
# d[:linky] = true
|
|
||||||
# d[:linkfunc] = l
|
|
||||||
# else
|
|
||||||
# warn("Unhandled/invalid link $l. Should be a Bool or a function mapping (row,column) -> (linkx, linky), where linkx/y can be Bool or Void (nothing)")
|
|
||||||
# end
|
|
||||||
# delete!(d, :link)
|
|
||||||
# end
|
|
||||||
end
|
end
|
||||||
|
|
||||||
# -----------------------------------------------------------------------------
|
# -----------------------------------------------------------------------------
|
||||||
|
|||||||
445
src/plot.jl
445
src/plot.jl
@ -43,13 +43,14 @@ 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)
|
||||||
|
|
||||||
# create an empty Plot then process
|
# create an empty Plot then process
|
||||||
plt = Plot()
|
plt = Plot()
|
||||||
# plt.user_attr = d
|
# plt.user_attr = d
|
||||||
_plot!(plt, d, args...)
|
_plot!(plt, d, args)
|
||||||
end
|
end
|
||||||
|
|
||||||
# build a new plot from existing plots
|
# build a new plot from existing plots
|
||||||
@ -105,22 +106,11 @@ function plot(plt1::Plot, plts_tail::Plot...; kw...)
|
|||||||
end
|
end
|
||||||
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
|
# 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, remove_pair = false)
|
||||||
end
|
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?
|
# do we need to link any axes together?
|
||||||
link_axes!(plt.layout, plt[:link])
|
link_axes!(plt.layout, plt[:link])
|
||||||
|
|
||||||
@ -150,7 +140,7 @@ function plot!(plt::Plot, args...; kw...)
|
|||||||
d = KW(kw)
|
d = KW(kw)
|
||||||
preprocessArgs!(d)
|
preprocessArgs!(d)
|
||||||
# merge!(plt.user_attr, d)
|
# merge!(plt.user_attr, d)
|
||||||
_plot!(plt, d, args...)
|
_plot!(plt, d, args)
|
||||||
end
|
end
|
||||||
|
|
||||||
function strip_first_letter(s::Symbol)
|
function strip_first_letter(s::Symbol)
|
||||||
@ -158,104 +148,120 @@ function strip_first_letter(s::Symbol)
|
|||||||
str[1:1], Symbol(str[2:end])
|
str[1:1], Symbol(str[2:end])
|
||||||
end
|
end
|
||||||
|
|
||||||
|
# -------------------------------------------------------------------------------
|
||||||
|
|
||||||
|
# getting ready to add the series... last update to subplot from anything
|
||||||
|
# that might have been added during series recipes
|
||||||
|
function _prepare_subplot(plt::Plot, d::KW)
|
||||||
|
st = d[:seriestype]
|
||||||
|
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
|
||||||
|
sp::Subplot
|
||||||
|
end
|
||||||
|
|
||||||
|
function _prepare_annotations(sp::Subplot, d::KW)
|
||||||
|
# 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)
|
||||||
|
end
|
||||||
|
|
||||||
|
function _expand_subplot_extrema(sp::Subplot, d::KW, st::Symbol)
|
||||||
|
# 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
|
||||||
|
end
|
||||||
|
|
||||||
|
function _add_the_series(plt, d)
|
||||||
|
warnOnUnsupported_args(plt.backend, d)
|
||||||
|
warnOnUnsupported(plt.backend, d)
|
||||||
|
series = Series(d)
|
||||||
|
push!(plt.series_list, series)
|
||||||
|
_series_added(plt, series)
|
||||||
|
end
|
||||||
|
|
||||||
|
# -------------------------------------------------------------------------------
|
||||||
|
|
||||||
# this method recursively applies series recipes when the seriestype is not supported
|
# this method recursively applies series recipes when the seriestype is not supported
|
||||||
# natively by the backend
|
# natively by the backend
|
||||||
function _apply_series_recipe(plt::Plot, d::KW)
|
function _process_seriesrecipe(plt::Plot, d::KW)
|
||||||
# replace seriestype aliases
|
# replace seriestype aliases
|
||||||
st = d[:seriestype]
|
st = Symbol(d[:seriestype])
|
||||||
st = d[:seriestype] = get(_typeAliases, st, st)
|
st = d[:seriestype] = get(_typeAliases, st, st)
|
||||||
|
|
||||||
# if it's natively supported, finalize processing and pass along to the backend, otherwise recurse
|
# if it's natively supported, finalize processing and pass along to the backend, otherwise recurse
|
||||||
if st in supported_types()
|
if st in supported_types()
|
||||||
|
sp = _prepare_subplot(plt, d)
|
||||||
# getting ready to add the series... last update to subplot from anything
|
_prepare_annotations(sp, d)
|
||||||
# that might have been added during series recipes
|
_expand_subplot_extrema(sp, d, st)
|
||||||
sp = d[:subplot]
|
_add_the_series(plt, d)
|
||||||
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
|
else
|
||||||
# get a sub list of series for this seriestype
|
# get a sub list of series for this seriestype
|
||||||
datalist = RecipesBase.apply_recipe(d, Val{st}, d[:x], d[:y], d[:z])
|
datalist = RecipesBase.apply_recipe(d, Val{st}, d[:x], d[:y], d[:z])
|
||||||
# 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
|
# assuming there was no error, recursively apply the series recipes
|
||||||
for data in datalist
|
for data in datalist
|
||||||
if isa(data, RecipeData)
|
if isa(data, RecipeData)
|
||||||
_apply_series_recipe(plt, data.d)
|
_process_seriesrecipe(plt, data.d)
|
||||||
else
|
else
|
||||||
warn("Unhandled recipe: $(data)")
|
warn("Unhandled recipe: $(data)")
|
||||||
break
|
break
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
nothing
|
||||||
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
|
kw[:series_plotindex] - kw_list[1][:series_plotindex] + 1
|
||||||
end
|
end
|
||||||
|
|
||||||
|
function _expand_seriestype_array(d::KW, args)
|
||||||
|
sts = get(d, :seriestype, :path)
|
||||||
|
if typeof(sts) <: AbstractArray
|
||||||
|
delete!(d, :seriestype)
|
||||||
|
RecipeData[begin
|
||||||
|
dc = copy(d)
|
||||||
|
dc[:seriestype] = sts[r,:]
|
||||||
|
RecipeData(dc, args)
|
||||||
|
end for r=1:size(sts,1)]
|
||||||
|
else
|
||||||
|
RecipeData[RecipeData(copy(d), args)]
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
# this is the core plotting function. recursively apply recipes to build
|
function _preprocess_args(d::KW, args, still_to_process::Vector{RecipeData})
|
||||||
# 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
|
# 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
|
# we simply add the GroupBy object to the front of the args list to allow
|
||||||
# the recipe to be applied
|
# the recipe to be applied
|
||||||
@ -265,21 +271,8 @@ function _plot!(plt::Plot, d::KW, args...)
|
|||||||
|
|
||||||
# if we were passed a vector/matrix of seriestypes and there's more than one row,
|
# 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.
|
# we want to duplicate the inputs, once for each seriestype row.
|
||||||
kw_list = KW[]
|
if !isempty(args)
|
||||||
still_to_process = if isempty(args)
|
append!(still_to_process, _expand_seriestype_array(d, args))
|
||||||
[]
|
|
||||||
else
|
|
||||||
sts = get(d, :seriestype, :path)
|
|
||||||
if typeof(sts) <: AbstractArray
|
|
||||||
delete!(d, :seriestype)
|
|
||||||
[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
|
end
|
||||||
|
|
||||||
# remove subplot and axis args from d... they will be passed through in the kw_list
|
# remove subplot and axis args from d... they will be passed through in the kw_list
|
||||||
@ -295,9 +288,70 @@ function _plot!(plt::Plot, d::KW, args...)
|
|||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
# --------------------------------
|
args
|
||||||
# "USER RECIPES"
|
end
|
||||||
# --------------------------------
|
|
||||||
|
|
||||||
|
function _preprocess_userrecipe(kw::KW)
|
||||||
|
_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
|
||||||
|
return
|
||||||
|
end
|
||||||
|
|
||||||
|
function _add_errorbar_kw(kw_list::Vector{KW}, kw::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(kw, 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
|
||||||
|
end
|
||||||
|
|
||||||
|
function _add_smooth_kw(kw_list::Vector{KW}, kw::KW)
|
||||||
|
# handle smoothing by adding a new series
|
||||||
|
if get(kw, :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
|
||||||
|
end
|
||||||
|
|
||||||
|
function _process_userrecipes(plt::Plot, d::KW, args)
|
||||||
|
still_to_process = RecipeData[]
|
||||||
|
args = _preprocess_args(d, args, still_to_process)
|
||||||
|
|
||||||
# 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.
|
||||||
@ -305,80 +359,20 @@ function _plot!(plt::Plot, d::KW, args...)
|
|||||||
# the recipe will return a list a Series objects... the ones that are
|
# 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
|
# finished (no more args) get added to the kw_list, and the rest go into the queue
|
||||||
# for processing.
|
# for processing.
|
||||||
|
kw_list = KW[]
|
||||||
while !isempty(still_to_process)
|
while !isempty(still_to_process)
|
||||||
|
|
||||||
# grab the first in line to be processed and pass it through apply_recipe
|
# grab the first in line to be processed and pass it through apply_recipe
|
||||||
# to generate a list of RecipeData objects (data + attributes)
|
# to generate a list of RecipeData objects (data + attributes)
|
||||||
next_series = shift!(still_to_process)
|
next_series = shift!(still_to_process)
|
||||||
for recipedata in RecipesBase.apply_recipe(next_series.d, next_series.args...)
|
rd_list = RecipesBase.apply_recipe(next_series.d, next_series.args...)
|
||||||
|
for recipedata in rd_list
|
||||||
# recipedata should be of type RecipeData. if it's not then the inputs must not have been fully processed by recipes
|
# 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)
|
if !(typeof(recipedata) <: RecipeData)
|
||||||
error("Inputs couldn't be processed... expected RecipeData but got: $recipedata")
|
error("Inputs couldn't be processed... expected RecipeData but got: $recipedata")
|
||||||
end
|
end
|
||||||
|
|
||||||
if isempty(recipedata.args)
|
if isempty(recipedata.args)
|
||||||
# when the arg tuple is empty, that means there's nothing left to recursively
|
_process_userrecipe(plt, kw_list, recipedata)
|
||||||
# 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
|
else
|
||||||
# args are non-empty, so there's still processing to do... add it back to the queue
|
# args are non-empty, so there's still processing to do... add it back to the queue
|
||||||
push!(still_to_process, recipedata)
|
push!(still_to_process, recipedata)
|
||||||
@ -388,51 +382,57 @@ 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
|
||||||
|
kw_list
|
||||||
|
end
|
||||||
|
|
||||||
# --------------------------------
|
function _process_userrecipe(plt::Plot, kw_list::Vector{KW}, recipedata::RecipeData)
|
||||||
# "PLOT RECIPES"
|
# 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
|
||||||
|
_preprocess_userrecipe(kw)
|
||||||
|
warnOnUnsupported_scales(plt.backend, kw)
|
||||||
|
|
||||||
# "plot recipe", which acts like a series type, and is processed before
|
# add the plot index
|
||||||
# the plot layout is created, which allows for setting layouts and other plot-wide attributes.
|
plt.n += 1
|
||||||
# we get inputs which have been fully processed by "user recipes" and "type recipes",
|
kw[:series_plotindex] = plt.n
|
||||||
# so we can expect standard vectors, surfaces, etc. No defaults have been set yet.
|
|
||||||
still_to_process = kw_list
|
push!(kw_list, kw)
|
||||||
kw_list = KW[]
|
_add_errorbar_kw(kw_list, kw)
|
||||||
while !isempty(still_to_process)
|
_add_smooth_kw(kw_list, kw)
|
||||||
# Grab the first in line to be processed and pass it through apply_recipe
|
return
|
||||||
# to generate a list of RecipeData objects (data + attributes).
|
end
|
||||||
# If we applied a "plot recipe" without error, then add the returned datalist's KWs,
|
|
||||||
# otherwise we just add the original KW.
|
# Grab the first in line to be processed and pass it through apply_recipe
|
||||||
next_kw = shift!(still_to_process)
|
# to generate a list of RecipeData objects (data + attributes).
|
||||||
if !isa(get(next_kw, :seriestype, nothing), Symbol)
|
# If we applied a "plot recipe" without error, then add the returned datalist's KWs,
|
||||||
# seriestype was never set, or it's not a Symbol, so it can't be a plot recipe
|
# otherwise we just add the original KW.
|
||||||
push!(kw_list, next_kw)
|
function _process_plotrecipe(kw::KW, kw_list::Vector{KW}, still_to_process::Vector{KW})
|
||||||
continue
|
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
|
||||||
|
if data.d[:seriestype] == st
|
||||||
|
error("Plot recipe $st returned the same seriestype: $(data.d)")
|
||||||
|
end
|
||||||
|
push!(still_to_process, data.d)
|
||||||
end
|
end
|
||||||
try
|
catch err
|
||||||
st = next_kw[:seriestype]
|
if isa(err, MethodError)
|
||||||
st = next_kw[:seriestype] = get(_typeAliases, st, st)
|
push!(kw_list, kw)
|
||||||
datalist = RecipesBase.apply_recipe(next_kw, Val{st}, plt)
|
else
|
||||||
for data in datalist
|
rethrow()
|
||||||
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
|
||||||
end
|
end
|
||||||
|
return
|
||||||
|
end
|
||||||
|
|
||||||
# --------------------------------
|
function _plot_setup(plt::Plot, d::KW, kw_list::Vector{KW})
|
||||||
# Plot/Subplot/Layout setup
|
|
||||||
# --------------------------------
|
|
||||||
|
|
||||||
# merge in anything meant for the Plot
|
# merge in anything meant for the Plot
|
||||||
for kw in kw_list, (k,v) in kw
|
for kw in kw_list, (k,v) in kw
|
||||||
haskey(_plot_defaults, k) && (d[k] = pop!(kw, k))
|
haskey(_plot_defaults, k) && (d[k] = pop!(kw, k))
|
||||||
@ -478,7 +478,9 @@ function _plot!(plt::Plot, d::KW, args...)
|
|||||||
push!(plt.inset_subplots, sp)
|
push!(plt.inset_subplots, sp)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
function _subplot_setup(plt::Plot, d::KW, kw_list::Vector{KW})
|
||||||
# we'll keep a map of subplot to an attribute override dict.
|
# 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/Axis attributes set by a user/series recipe apply only to the
|
||||||
# Subplot object which they belong to.
|
# Subplot object which they belong to.
|
||||||
@ -514,6 +516,41 @@ function _plot!(plt::Plot, d::KW, args...)
|
|||||||
|
|
||||||
# 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])
|
||||||
|
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::Tuple)
|
||||||
|
# d[:plot_object] = plt
|
||||||
|
|
||||||
|
# --------------------------------
|
||||||
|
# "USER RECIPES"
|
||||||
|
# --------------------------------
|
||||||
|
|
||||||
|
kw_list = _process_userrecipes(plt, d, args)
|
||||||
|
|
||||||
|
|
||||||
|
# --------------------------------
|
||||||
|
# "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 = shift!(still_to_process)
|
||||||
|
_process_plotrecipe(next_kw, kw_list, still_to_process)
|
||||||
|
end
|
||||||
|
|
||||||
|
# --------------------------------
|
||||||
|
# Plot/Subplot/Layout setup
|
||||||
|
# --------------------------------
|
||||||
|
_plot_setup(plt, d, kw_list)
|
||||||
|
_subplot_setup(plt, d, kw_list)
|
||||||
|
|
||||||
# !!! 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 !!!
|
||||||
@ -538,7 +575,7 @@ function _plot!(plt::Plot, d::KW, args...)
|
|||||||
# For example, a histogram is just a bar plot with binned data, a bar plot is really a filled step plot,
|
# 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
|
# 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).
|
# be able to support step, bar, and histogram plots (and any recipes that use those components).
|
||||||
_apply_series_recipe(plt, kw)
|
_process_seriesrecipe(plt, kw)
|
||||||
end
|
end
|
||||||
|
|
||||||
# --------------------------------
|
# --------------------------------
|
||||||
|
|||||||
@ -106,47 +106,47 @@ 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
|
||||||
|
# if is_installed("DataFrames")
|
||||||
|
# @eval begin
|
||||||
|
# import DataFrames
|
||||||
|
|
||||||
if is_installed("DataFrames")
|
# # if it's one symbol, set the guide and return the column
|
||||||
@eval begin
|
# function handle_dfs(df::DataFrames.AbstractDataFrame, d::KW, letter, sym::Symbol)
|
||||||
import DataFrames
|
# get!(d, Symbol(letter * "guide"), string(sym))
|
||||||
|
# collect(df[sym])
|
||||||
|
# end
|
||||||
|
|
||||||
# if it's one symbol, set the guide and return the column
|
# # 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, sym::Symbol)
|
# function handle_dfs(df::DataFrames.AbstractDataFrame, d::KW, letter, syms::AbstractArray{Symbol})
|
||||||
get!(d, Symbol(letter * "guide"), string(sym))
|
# get!(d, :label, reshape(syms, 1, length(syms)))
|
||||||
collect(df[sym])
|
# Any[collect(df[s]) for s in syms]
|
||||||
end
|
# end
|
||||||
|
|
||||||
# if it's an array of symbols, set the labels and return a Vector{Any} of columns
|
# # for anything else, no-op
|
||||||
function handle_dfs(df::DataFrames.AbstractDataFrame, d::KW, letter, syms::AbstractArray{Symbol})
|
# function handle_dfs(df::DataFrames.AbstractDataFrame, d::KW, letter, anything)
|
||||||
get!(d, :label, reshape(syms, 1, length(syms)))
|
# anything
|
||||||
Any[collect(df[s]) for s in syms]
|
# end
|
||||||
end
|
|
||||||
|
|
||||||
# for anything else, no-op
|
# # handle grouping by DataFrame column
|
||||||
function handle_dfs(df::DataFrames.AbstractDataFrame, d::KW, letter, anything)
|
# function extractGroupArgs(group::Symbol, df::DataFrames.AbstractDataFrame, args...)
|
||||||
anything
|
# extractGroupArgs(collect(df[group]))
|
||||||
end
|
# end
|
||||||
|
|
||||||
# handle grouping by DataFrame column
|
# # if a DataFrame is the first arg, lets swap symbols out for columns
|
||||||
function extractGroupArgs(group::Symbol, df::DataFrames.AbstractDataFrame, args...)
|
# @recipe function f(df::DataFrames.AbstractDataFrame, args...)
|
||||||
extractGroupArgs(collect(df[group]))
|
# # if any of these attributes are symbols, swap out for the df column
|
||||||
end
|
# for k in (:fillrange, :line_z, :marker_z, :markersize, :ribbon, :weights, :xerror, :yerror)
|
||||||
|
# if haskey(d, k) && isa(d[k], Symbol)
|
||||||
|
# d[k] = collect(df[d[k]])
|
||||||
|
# end
|
||||||
|
# end
|
||||||
|
|
||||||
# if a DataFrame is the first arg, lets swap symbols out for columns
|
# # return a list of new arguments
|
||||||
@recipe function f(df::DataFrames.AbstractDataFrame, args...)
|
# tuple(Any[handle_dfs(df, d, (i==1 ? "x" : i==2 ? "y" : "z"), arg) for (i,arg) in enumerate(args)]...)
|
||||||
# if any of these attributes are symbols, swap out for the df column
|
# end
|
||||||
for k in (:fillrange, :line_z, :marker_z, :markersize, :ribbon, :weights, :xerror, :yerror)
|
# end
|
||||||
if haskey(d, k) && isa(d[k], Symbol)
|
# end
|
||||||
d[k] = collect(df[d[k]])
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
# 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)]...)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
|
|
||||||
# ---------------------------------------------------------------------------
|
# ---------------------------------------------------------------------------
|
||||||
@ -553,6 +553,8 @@ end
|
|||||||
|
|
||||||
# note: don't add dependencies because this really isn't a drop-in replacement
|
# note: don't add dependencies because this really isn't a drop-in replacement
|
||||||
|
|
||||||
|
# TODO: move boxplots and violin plots to StatPlots when it's ready
|
||||||
|
|
||||||
# ---------------------------------------------------------------------------
|
# ---------------------------------------------------------------------------
|
||||||
# Box Plot
|
# Box Plot
|
||||||
|
|
||||||
@ -794,6 +796,8 @@ end
|
|||||||
@deps xerror path
|
@deps xerror path
|
||||||
|
|
||||||
|
|
||||||
|
# TODO: move quiver to PlotRecipes
|
||||||
|
|
||||||
# ---------------------------------------------------------------------------
|
# ---------------------------------------------------------------------------
|
||||||
# quiver
|
# quiver
|
||||||
|
|
||||||
@ -943,7 +947,8 @@ end
|
|||||||
|
|
||||||
# -------------------------------------------------
|
# -------------------------------------------------
|
||||||
|
|
||||||
# TODO: this should really be in another package...
|
# TODO: move OHLC to PlotRecipes finance.jl
|
||||||
|
|
||||||
type OHLC{T<:Real}
|
type OHLC{T<:Real}
|
||||||
open::T
|
open::T
|
||||||
high::T
|
high::T
|
||||||
|
|||||||
@ -277,10 +277,10 @@ end
|
|||||||
#
|
#
|
||||||
# # function without range... use the current range of the x-axis
|
# # function without range... use the current range of the x-axis
|
||||||
|
|
||||||
@recipe function f(f::FuncOrFuncs)
|
# @recipe function f(f::FuncOrFuncs)
|
||||||
plt = d[:plot_object]
|
# plt = d[:plot_object]
|
||||||
f, xmin(plt), xmax(plt)
|
# f, xmin(plt), xmax(plt)
|
||||||
end
|
# end
|
||||||
|
|
||||||
#
|
#
|
||||||
# # --------------------------------------------------------------------
|
# # --------------------------------------------------------------------
|
||||||
|
|||||||
@ -284,20 +284,16 @@ function replaceType(vec, val)
|
|||||||
push!(vec, val)
|
push!(vec, val)
|
||||||
end
|
end
|
||||||
|
|
||||||
function replaceAlias!(d::KW, k::Symbol, aliases::KW)
|
function replaceAlias!(d::KW, k::Symbol, aliases::Dict{Symbol,Symbol})
|
||||||
if haskey(aliases, k)
|
if haskey(aliases, k)
|
||||||
d[aliases[k]] = pop!(d, k)
|
d[aliases[k]] = pop!(d, k)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
function replaceAliases!(d::KW, aliases::KW)
|
function replaceAliases!(d::KW, aliases::Dict{Symbol,Symbol})
|
||||||
ks = collect(keys(d))
|
ks = collect(keys(d))
|
||||||
for k in ks
|
for k in ks
|
||||||
replaceAlias!(d, k, aliases)
|
replaceAlias!(d, k, aliases)
|
||||||
# if haskey(aliases, k)
|
|
||||||
# d[aliases[k]] = d[k]
|
|
||||||
# delete!(d, k)
|
|
||||||
# end
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user