diff --git a/src/Plots.jl b/src/Plots.jl index d2fefc3a..838d328e 100644 --- a/src/Plots.jl +++ b/src/Plots.jl @@ -141,7 +141,7 @@ include("plot.jl") include("series.jl") include("layouts.jl") include("subplots.jl") -# include("recipes.jl") +include("recipes.jl") include("animation.jl") include("output.jl") include("examples.jl") diff --git a/src/args.jl b/src/args.jl index 943ef8b3..4b7e125d 100644 --- a/src/args.jl +++ b/src/args.jl @@ -814,9 +814,9 @@ slice_arg(v, idx) = v # given an argument key (k), we want to extract the argument value for this index. # matrices are sliced by column, otherwise we # 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) - v = get(d_in, k, get(d_out, new_key, default_value)) - d_out[new_key] = if haskey(d_in, k) && typeof(v) <: AMat && !isempty(v) +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, k, default_value)) + d_out[k] = if haskey(d_in, k) && typeof(v) <: AMat && !isempty(v) slice_arg(v, idx) else v @@ -824,6 +824,7 @@ function slice_arg!(d_in::KW, d_out::KW, k::Symbol, default_value, idx::Int = 1; if remove_pair delete!(d_in, k) end + return end # ----------------------------------------------------------------------------- @@ -844,12 +845,13 @@ end function color_or_nothing!(d::KW, k::Symbol) v = d[k] d[k] = if v == nothing || v == false - plot_color(RGBA(0,0,0,0)) + RGBA{Float64}(0,0,0,0) elseif v != :match plot_color(v) else v end + return end # ----------------------------------------------------------------------------- @@ -938,7 +940,7 @@ Base.get(series::Series, k::Symbol, v) = get(series.d, k, v) # update attr from an input dictionary function _update_plot_args(plt::Plot, d_in::KW) 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 # handle colors @@ -949,20 +951,12 @@ function _update_plot_args(plt::Plot, d_in::KW) end plt.attr[:background_color] = bg plt.attr[:foreground_color] = plot_color(fg) - # color_or_match!(plt.attr, :background_color_outside, bg) color_or_nothing!(plt.attr, :background_color_outside) end +# ----------------------------------------------------------------------------- -# update a subplots args and axes -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 - +function _update_subplot_periphery(sp::Subplot, anns::AVec) # extend 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 sp.attr[:colorbar] = sp.attr[:legend] end + return +end +function _update_subplot_colors(sp::Subplot) # background colors - # bg = color_or_match!(sp.attr, :background_color_subplot, plt.attr[:background_color]) color_or_nothing!(sp.attr, :background_color_subplot) bg = plot_color(sp[:background_color_subplot]) 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_grid) color_or_nothing!(sp.attr, :foreground_color_title) + return +end - # for k in (:left_margin, :top_margin, :right_margin, :bottom_margin) - # if sp.attr[k] == :match - # sp.attr[k] = sp.attr[:margin] - # end - # end +function _update_axis(plt::Plot, sp::Subplot, d_in::KW, letter::Symbol, subplot_index::Int) + # get (maybe initialize) the axis + axis = get_axis(sp, letter) + + _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) - # get (maybe initialize) the axis - 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 + _update_axis(plt, sp, d_in, letter, subplot_index) 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! 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 # this is how many series belong to this subplot diff --git a/src/axes.jl b/src/axes.jl index f00704fa..39d01420 100644 --- a/src/axes.jl +++ b/src/axes.jl @@ -38,7 +38,7 @@ function get_axis(sp::Subplot, letter::Symbol) sp.attr[axissym] else sp.attr[axissym] = Axis(sp, letter) - end + end::Axis end function process_axis_arg!(d::KW, arg, letter = "") diff --git a/src/plot.jl b/src/plot.jl index 03e94be7..84f32b14 100644 --- a/src/plot.jl +++ b/src/plot.jl @@ -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 function plot(args...; kw...) - info("started to plot") d = KW(kw) preprocessArgs!(d) @@ -108,7 +107,7 @@ function plot(plt1::Plot, plts_tail::Plot...; kw...) # first apply any args for the 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 # 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 # that might have been added during series recipes -function _prepare_subplot(plt::Plot, d::KW) - st = d[:seriestype] - sp = d[:subplot] +function _prepare_subplot{T}(plt::Plot{T}, d::KW) + st::Symbol = d[:seriestype] + sp::Subplot{T} = d[:subplot] 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? - if !is3d(st) && d[:z] != nothing && (size(d[:x]) == size(d[:y]) == size(d[:z])) - st = d[:seriestype] = (st == :scatter ? :scatter3d : :path3d) - end + st = _override_seriestype_check(d, st) # change to a 3d projection for this subplot? if is3d(st) @@ -173,7 +169,19 @@ function _prepare_subplot(plt::Plot, d::KW) _initialize_subplot(plt, sp) sp.attr[:init] = true 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 function _prepare_annotations(sp::Subplot, d::KW) @@ -244,7 +252,7 @@ function _process_seriesrecipe(plt::Plot, d::KW) end 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 function _expand_seriestype_array(d::KW, args) @@ -406,7 +414,7 @@ end # 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(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) # seriestype was never set, or it's not a Symbol, so it can't be a plot recipe 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 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) + _update_subplot_args(plt, sp, attr, idx, false) end # do we need to link any axes together? @@ -543,7 +551,7 @@ function _plot!(plt::Plot, d::KW, args::Tuple) kw_list = KW[] while !isempty(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 # -------------------------------- @@ -560,11 +568,11 @@ function _plot!(plt::Plot, d::KW, args::Tuple) # -------------------------------- for kw in kw_list - sp = kw[:subplot] - idx = get_subplot_index(plt, sp) + sp::Subplot = 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) + # _update_subplot_args(plt, sp, kw, idx, true) # set default values, select from attribute cycles, and generally set the final attributes _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? if plt[:show] - gui() + gui(plt) end plt diff --git a/src/recipes.jl b/src/recipes.jl index 01220490..07623d8d 100644 --- a/src/recipes.jl +++ b/src/recipes.jl @@ -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: @@ -106,60 +101,51 @@ num_series(x) = 1 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 +# TODO: remove when StatPlots is ready +if is_installed("DataFrames") + @eval begin + import DataFrames -# # if it's one symbol, set the guide and return the column -# function handle_dfs(df::DataFrames.AbstractDataFrame, d::KW, letter, sym::Symbol) -# get!(d, Symbol(letter * "guide"), string(sym)) -# collect(df[sym]) -# end + # if it's one symbol, set the guide and return the column + function handle_dfs(df::DataFrames.AbstractDataFrame, d::KW, letter, sym::Symbol) + get!(d, Symbol(letter * "guide"), string(sym)) + collect(df[sym]) + end -# # 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}) -# get!(d, :label, reshape(syms, 1, length(syms))) -# Any[collect(df[s]) for s in syms] -# end + # 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}) + get!(d, :label, reshape(syms, 1, length(syms))) + Any[collect(df[s]) for s in syms] + end -# # for anything else, no-op -# function handle_dfs(df::DataFrames.AbstractDataFrame, d::KW, letter, anything) -# anything -# end + # for anything else, no-op + function handle_dfs(df::DataFrames.AbstractDataFrame, d::KW, letter, anything) + anything + end -# # handle grouping by DataFrame column -# function extractGroupArgs(group::Symbol, df::DataFrames.AbstractDataFrame, args...) -# extractGroupArgs(collect(df[group])) -# end + # handle grouping by DataFrame column + function extractGroupArgs(group::Symbol, df::DataFrames.AbstractDataFrame, args...) + extractGroupArgs(collect(df[group])) + end -# # if a DataFrame is the first arg, lets swap symbols out for columns -# @recipe function f(df::DataFrames.AbstractDataFrame, args...) -# # 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) -# 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 + @recipe function f(df::DataFrames.AbstractDataFrame, args...) + # 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) + if haskey(d, k) && isa(d[k], Symbol) + 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 + # 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 # --------------------------------------------------------------------------- -# """ -# `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 @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 @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) vmin, vmax = axis_limits(axis) @@ -898,53 +869,6 @@ end @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 diff --git a/src/subplots.jl b/src/subplots.jl index 6e41bbf5..cd253a69 100644 --- a/src/subplots.jl +++ b/src/subplots.jl @@ -30,7 +30,7 @@ get_subplot(plt::Plot, i::Integer) = plt.subplots[i] get_subplot(plt::Plot, k) = plt.spmap[k] 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) series_list(sp::Subplot) = filter(series -> series.d[:subplot] === sp, sp.plt.series_list)