Compare commits

..

52 Commits

Author SHA1 Message Date
Thomas Breloff 01b9cc92fd remove mkdir call in tests; closes #397 2016-07-22 11:18:47 -04:00
Thomas Breloff 7a650d918b unicodeplots size fix 2016-07-22 09:45:44 -04:00
Thomas Breloff b6fa4bcda4 plotly ticks fix 2016-07-21 11:01:04 -04:00
Thomas Breloff 73af635be6 bump version 2016-07-19 11:17:27 -04:00
Thomas Breloff c1c97a5fc1 labelfunc for pyplot 2016-07-19 10:32:23 -04:00
Thomas Breloff 6a97dc8825 extrema fix for bool/nothing 2016-07-18 16:19:58 -04:00
Thomas Breloff d832f34733 travis 2016-07-18 15:53:02 -04:00
Thomas Breloff 859a600b92 travis 2016-07-18 15:38:07 -04:00
Thomas Breloff 6b3cf50f38 travis 2016-07-18 15:21:51 -04:00
Thomas Breloff 3ccd1bc368 added Showoff dependency; fix PLOTS_DEFAULTS; fix auto fill to 0; fix scale warning; tick labels with Showoff; fontsize in GR; travis deps 2016-07-18 15:00:02 -04:00
Thomas Breloff 624d33b96f Merge branch 'master' into dev 2016-07-18 09:13:46 -04:00
Tom Breloff 156f61f526 Merge pull request #391 from odow/axisstyle
pass axis style to PGFPlots
2016-07-16 13:13:59 -04:00
odow fd3a04fce3 pass axis style to PGFPlots 2016-07-16 16:48:46 +12:00
Thomas Breloff 62be4e1274 scalefunc fix and cleanup 2016-07-15 09:09:46 -04:00
Thomas Breloff db096196c9 Merge branch 'master' into dev 2016-07-14 15:47:40 -04:00
Thomas Breloff a0ac70be3c axes log scales, colors, ticks, and more; proper 2D axis drawing in GR 2016-07-14 15:46:44 -04:00
Tom Breloff 1f0f89f83c Merge pull request #389 from odow/patch-1
Legend location in subplot
2016-07-14 13:52:37 -04:00
Thomas Breloff ee706ad8c7 axis_drawing_info and get_ticks; parameterized Segments; GR manual drawing of 2D axes 2016-07-13 16:31:21 -04:00
Oscar Dowson ce31ea8bf3 Legend location in subplot 2016-07-13 10:01:48 +12:00
Thomas Breloff dcab8e3ac2 travis fix 2016-07-12 13:14:53 -04:00
Thomas Breloff 1ad9a7e176 fix fillrange for shape; travis fix 2016-07-12 12:53:03 -04:00
Thomas Breloff f1bd7ab1dc travis fix 2016-07-12 11:53:37 -04:00
Thomas Breloff 79399913d7 NEWS, bumped version; travis fixes; added warnings for moved recipes 2016-07-12 11:23:04 -04:00
Thomas Breloff 4a2e88a81c switched Colors dep for PlotUtils dep; removed DataFrames, boxplot, violin, density and added StatPlots to tests 2016-07-12 10:45:58 -04:00
Thomas Breloff 8d5b748b09 gr heatmap fix; pipeline reorg 2016-07-10 23:26:52 -04:00
Thomas Breloff b2dba8af73 split plot into pipeline 2016-07-10 22:59:05 -04:00
Thomas Breloff c258bc49c3 gr trisurface; closes #387 2016-07-10 22:07:00 -04:00
Thomas Breloff e3e2a354e7 reorg of update_subplot_args; add back recipe, DataFrames; random type stability fixes 2016-07-10 18:12:49 -04:00
Thomas Breloff 32c1c31139 working on _plot organization; switch alias dicts to Dict{Symbol,Symbol}; other type stability changes 2016-07-10 15:50:29 -04:00
Thomas Breloff b8b5a33833 margin default to 1mm 2016-07-09 14:54:44 -04:00
Thomas Breloff 5407fa73f8 plotly redesign for splitting shapes 2016-07-09 13:30:39 -04:00
Thomas Breloff 1cb0c0071b improvements to layout padding; handle axis attr better; fix histogram2d 2016-07-09 12:40:27 -04:00
Thomas Breloff a456ac4c90 pyplot line segments fixes; curves remove line_z logic 2016-07-08 14:36:02 -04:00
Thomas Breloff 71b48427c5 pyplot line segments fix; bar fixes 2016-07-08 13:09:36 -04:00
Thomas Breloff ec06a01b6c pgf fixes 2016-07-08 11:21:26 -04:00
Thomas Breloff e3ccc34a2c ensure_gradient; plotly fixes 2016-07-08 10:12:48 -04:00
Thomas Breloff 396721568f color fixes; add PlotUtils to travis_commands 2016-07-08 09:17:39 -04:00
Thomas Breloff e53ab85745 color fixes; pyplot shape iteration; KernelDensity in travis and warning; attribute accessors 2016-07-07 21:46:34 -04:00
Thomas Breloff 4d99b37def working on colors overhaul; fix for rationals in plotly; closes #382 2016-07-07 14:40:09 -04:00
Thomas Breloff 2e24da3dcd update matrix of Shapes recipe 2016-07-07 09:36:03 -04:00
Thomas Breloff 0cc1bd2dc6 sample ticks if too many discrete values; use get_ticks in plotly to fix discrete ticks; allow ticks==false in pyplot; closes #356 2016-07-06 16:51:54 -04:00
Thomas Breloff aec162c50e fix for discrete bars; closes #365 2016-07-06 16:22:11 -04:00
Thomas Breloff d5e9340f8f bar fix for horizontal orientation 2016-07-06 16:10:01 -04:00
Thomas Breloff 6fe87c4c69 bar_width support in bar and boxplot; close the shape for bar plots; closes #366; closes #377 2016-07-06 15:55:38 -04:00
Tom Breloff 85f0a87213 Merge pull request #372 from anowacki/rainbow_palette
Switch blue and violet in rainbow colour palette.
2016-07-05 10:22:03 -04:00
Thomas Breloff 853f85be2e bug fix for passing vectors to bins; closes #375 2016-07-05 10:18:48 -04:00
Andy Nowacki a7448e3a1d Switch blue and violet in rainbow color palette. 2016-07-04 17:43:25 +01:00
Thomas Breloff 521e753183 string fix; iter_segments change to use unused and skip initial NaNs; working on glvisualize shape type 2016-07-01 17:23:25 -04:00
Thomas Breloff 9193941fd0 version bump; NEWS 2016-07-01 09:01:38 -04:00
Tom Breloff f56c6effe4 Merge pull request #368 from jheinen/dev
Added iTerm2 inline display support for gr
2016-07-01 08:09:34 -04:00
Josef Heinen 4cfcb239c1 Added iTerm2 inline display support for gr
Example:
--------
    using Plots
    gr(display_type=:inline)
    plot(Plots.fakedata(50,5), w=3)
2016-07-01 14:03:16 +02:00
Thomas Breloff a4be274718 pyplot display hack 2016-06-30 21:05:32 -04:00
30 changed files with 1879 additions and 1503 deletions
+48 -1
View File
@@ -7,7 +7,54 @@
---
## 0.7 (current master/dev)
## 0.8 (current master/dev)
#### 0.8.0
- added dependency on PlotUtils
- BREAKING: removed DataFrames support (now in StatPlots.jl)
- BREAKING: removed boxplot/violin/density recipes (now in StatPlots.jl)
- GR:
- inline iterm2 support
- trisurface support
- heatmap fix
- PyPlot:
- ijulia display fix
- GLVisualize:
- first try with shapes
- iter_segments improvements
- bar_width support
- horizontal bars
- improve tick display
- better shape handling in pyplot, plotly
- improved padding calcs
- internal reorg of _plots method, add pipeline.jl
---
## 0.7
#### 0.7.5
- GR: LaTeX support
- Changed docs url to juliaplots.github.io
- added `contourf` seriestype
- allow `plt[1]` to return first Subplot
- allow `sp[1]` to return the first Series of the Subplot
- `series[k]` now passes through to `series.d[k]`
- allow calling `plot!(sp, ...)` to update a target Subplot
- PyPlot: zorder fix
- new DataFrames logic/recipe: more flexible/robust and allow Symbols for:
- `(:fillrange, :line_z, :marker_z, :markersize, :ribbon, :weights, :xerror, :yerror)`
- new `display_type` and `extra_kwargs` plot attributes
- surface fix
#### 0.7.4
- added snooped precompiles, but left commented out
- GR fixes: markersize, shapes, legends
- fixes to recipes
- turned on Appveyor
#### 0.7.3
+2 -1
View File
@@ -1,8 +1,9 @@
julia 0.4
RecipesBase
Colors
PlotUtils
Reexport
Compat
FixedSizeArrays
Measures
Showoff
+8 -17
View File
@@ -5,12 +5,13 @@ module Plots
using Compat
using Reexport
@reexport using Colors
# @reexport using Colors
# using Requires
using FixedSizeArrays
@reexport using RecipesBase
using Base.Meta
# using PlotUtils
@reexport using PlotUtils
import Showoff
export
AbstractPlot,
@@ -81,16 +82,6 @@ export
arrow,
Segments,
colorscheme,
ColorScheme,
ColorGradient,
ColorVector,
ColorWrapper,
ColorFunction,
ColorZFunction,
getColor,
getColorZ,
debugplots,
supported_args,
@@ -109,6 +100,7 @@ export
test_examples,
iter_segments,
coords,
translate,
translate!,
@@ -132,15 +124,14 @@ export BBox, BoundingBox, mm, cm, inch, pt, px, pct, w, h
include("types.jl")
include("utils.jl")
include("colors.jl")
include("components.jl")
include("axes.jl")
include("backends.jl")
include("args.jl")
include("themes.jl")
include("plot.jl")
include("series_args.jl")
include("series_new.jl")
include("pipeline.jl")
include("series.jl")
include("layouts.jl")
include("subplots.jl")
include("recipes.jl")
@@ -246,8 +237,8 @@ function __init__()
setup_ijulia()
setup_atom()
if haskey(ENV, "PLOTS_DEFAULTS")
for (k,v) in eval(parse(ENV["PLOTS_DEFAULTS"]))
if isdefined(Main, :PLOTS_DEFAULTS)
for (k,v) in Main.PLOTS_DEFAULTS
default(k, v)
end
end
+239 -105
View File
@@ -1,6 +1,6 @@
const _keyAliases = KW()
const _keyAliases = Dict{Symbol,Symbol}()
function add_aliases(sym::Symbol, aliases::Symbol...)
for alias in aliases
@@ -11,7 +11,7 @@ function add_aliases(sym::Symbol, aliases::Symbol...)
end
end
function add_non_underscore_aliases!(aliases::KW)
function add_non_underscore_aliases!(aliases::Dict{Symbol,Symbol})
for (k,v) in aliases
s = string(k)
if '_' in s
@@ -24,7 +24,7 @@ end
# ------------------------------------------------------------
const _allAxes = [:auto, :left, :right]
const _axesAliases = KW(
const _axesAliases = Dict{Symbol,Symbol}(
:a => :auto,
:l => :left,
:r => :right
@@ -39,7 +39,7 @@ const _allTypes = vcat([
:contour, :pie, :shape, :image
], _3dTypes)
@compat const _typeAliases = KW(
@compat const _typeAliases = Dict{Symbol,Symbol}(
:n => :none,
:no => :none,
:l => :line,
@@ -83,7 +83,7 @@ like_surface(seriestype::Symbol) = seriestype in (:contour, :contourf, :contou
is3d(seriestype::Symbol) = seriestype in _3dTypes
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"
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]
@compat const _styleAliases = KW(
@compat const _styleAliases = Dict{Symbol,Symbol}(
:a => :auto,
:s => :solid,
: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))))
@compat const _markerAliases = KW(
@compat const _markerAliases = Dict{Symbol,Symbol}(
:n => :none,
:no => :none,
: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]
@compat const _scaleAliases = KW(
@compat const _scaleAliases = Dict{Symbol,Symbol}(
:none => :identity,
:log => :log10,
)
@@ -242,7 +242,7 @@ const _subplot_defaults = KW(
:annotations => [], # annotation tuples... list of (x,y,annotation)
:projection => :none, # can also be :polar or :3d
:aspect_ratio => :none, # choose from :none or :equal
:margin => 2mm,
:margin => 1mm,
:left_margin => :match,
:top_margin => :match,
:right_margin => :match,
@@ -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(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
d[sym] = autopick_ignore_none_auto(options, plotIndex)
elseif haskey(aliases, d[sym])
@@ -338,7 +338,7 @@ function aliasesAndAutopick(d::KW, sym::Symbol, aliases::KW, options::AVec, plot
end
end
function aliases(aliasMap::KW, val)
function aliases(aliasMap::Dict{Symbol,Symbol}, val)
sortedkeys(filter((k,v)-> v==val, aliasMap))
end
@@ -498,7 +498,8 @@ function handleColors!(d::KW, arg, csym::Symbol)
if arg == :auto
d[csym] = :auto
else
c = colorscheme(arg)
# c = colorscheme(arg)
c = plot_color(arg)
d[csym] = c
end
return true
@@ -519,13 +520,13 @@ function processLineArg(d::KW, arg)
elseif typeof(arg) <: Stroke
arg.width == nothing || (d[:linewidth] = arg.width)
arg.color == nothing || (d[:linecolor] = arg.color == :auto ? :auto : colorscheme(arg.color))
arg.color == nothing || (d[:linecolor] = arg.color == :auto ? :auto : plot_color(arg.color))
arg.alpha == nothing || (d[:linealpha] = arg.alpha)
arg.style == nothing || (d[:linestyle] = arg.style)
elseif typeof(arg) <: Brush
arg.size == nothing || (d[:fillrange] = arg.size)
arg.color == nothing || (d[:fillcolor] = arg.color == :auto ? :auto : colorscheme(arg.color))
arg.color == nothing || (d[:fillcolor] = arg.color == :auto ? :auto : plot_color(arg.color))
arg.alpha == nothing || (d[:fillalpha] = arg.alpha)
elseif typeof(arg) <: Arrow || arg in (:arrow, :arrows)
@@ -558,13 +559,13 @@ function processMarkerArg(d::KW, arg)
elseif typeof(arg) <: Stroke
arg.width == nothing || (d[:markerstrokewidth] = arg.width)
arg.color == nothing || (d[:markerstrokecolor] = arg.color == :auto ? :auto : colorscheme(arg.color))
arg.color == nothing || (d[:markerstrokecolor] = arg.color == :auto ? :auto : plot_color(arg.color))
arg.alpha == nothing || (d[:markerstrokealpha] = arg.alpha)
arg.style == nothing || (d[:markerstrokestyle] = arg.style)
elseif typeof(arg) <: Brush
arg.size == nothing || (d[:markersize] = arg.size)
arg.color == nothing || (d[:markercolor] = arg.color == :auto ? :auto : colorscheme(arg.color))
arg.color == nothing || (d[:markercolor] = arg.color == :auto ? :auto : plot_color(arg.color))
arg.alpha == nothing || (d[:markeralpha] = arg.alpha)
# linealpha
@@ -584,9 +585,10 @@ end
function processFillArg(d::KW, arg)
# fr = get(d, :fillrange, 0)
if typeof(arg) <: Brush
arg.size == nothing || (d[:fillrange] = arg.size)
arg.color == nothing || (d[:fillcolor] = arg.color == :auto ? :auto : colorscheme(arg.color))
arg.color == nothing || (d[:fillcolor] = arg.color == :auto ? :auto : plot_color(arg.color))
arg.alpha == nothing || (d[:fillalpha] = arg.alpha)
# fillrange function
@@ -601,6 +603,8 @@ function processFillArg(d::KW, arg)
d[:fillrange] = arg
end
# d[:fillrange] = fr
return
end
_replace_markershape(shape::Symbol) = get(_markerAliases, shape, shape)
@@ -687,21 +691,13 @@ function preprocessArgs!(d::KW)
d[:colorbar] = convertLegendValue(d[:colorbar])
end
# # 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
# warnings for moved recipes
st = get(d, :seriestype, :path)
if st in (:boxplot, :violin, :density) && !isdefined(Main, :StatPlots)
warn("seriestype $st has been moved to StatPlots. To use: \`Pkg.add(\"StatPlots\"); using StatPlots\`")
end
return
end
# -----------------------------------------------------------------------------
@@ -782,7 +778,7 @@ function warnOnUnsupported(pkg::AbstractBackend, d::KW)
end
function warnOnUnsupported_scales(pkg::AbstractBackend, d::KW)
for k in (:xscale, :yscale, :zscale)
for k in (:xscale, :yscale, :zscale, :scale)
if haskey(d, k)
v = d[k]
v = get(_scaleAliases, v, v)
@@ -809,6 +805,7 @@ function convertLegendValue(val::Symbol)
end
convertLegendValue(val::Bool) = val ? :best : :none
convertLegendValue(val::Void) = :none
convertLegendValue(v::AbstractArray) = map(convertLegendValue, v)
# -----------------------------------------------------------------------------
@@ -828,9 +825,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
@@ -838,6 +835,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
# -----------------------------------------------------------------------------
@@ -849,7 +847,7 @@ end
# d[k] = if v == :match
# match_color
# elseif v == nothing
# colorscheme(RGBA(0,0,0,0))
# plot_color(RGBA(0,0,0,0))
# else
# v
# end
@@ -858,10 +856,13 @@ end
function color_or_nothing!(d::KW, k::Symbol)
v = d[k]
d[k] = if v == nothing || v == false
colorscheme(RGBA(0,0,0,0))
RGBA{Float64}(0,0,0,0)
elseif v != :match
plot_color(v)
else
v
end
return
end
# -----------------------------------------------------------------------------
@@ -934,36 +935,39 @@ function Base.getindex(series::Series, k::Symbol)
series.d[k]
end
Base.setindex!(plt::Plot, v, k::Symbol) = (plt.attr[k] = v)
Base.setindex!(sp::Subplot, v, k::Symbol) = (sp.attr[k] = v)
Base.setindex!(axis::Axis, v, k::Symbol) = (axis.d[k] = v)
Base.setindex!(series::Series, v, k::Symbol) = (series.d[k] = v)
Base.get(plt::Plot, k::Symbol, v) = get(plt.attr, k, v)
Base.get(sp::Subplot, k::Symbol, v) = get(sp.attr, k, v)
Base.get(axis::Axis, k::Symbol, v) = get(axis.d, k, v)
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
bg = convertColor(plt.attr[:background_color])
bg = plot_color(plt.attr[:background_color])
fg = plt.attr[:foreground_color]
if fg == :auto
fg = isdark(bg) ? colorant"white" : colorant"black"
end
plt.attr[:background_color] = bg
plt.attr[:foreground_color] = convertColor(fg)
# color_or_match!(plt.attr, :background_color_outside, bg)
plt.attr[:foreground_color] = plot_color(fg)
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])
@@ -973,11 +977,13 @@ 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 = convertColor(sp[:background_color_subplot])
bg = plot_color(sp[:background_color_subplot])
sp.attr[:color_palette] = get_color_palette(sp.attr[:color_palette], bg, 30)
color_or_nothing!(sp.attr, :background_color_legend)
color_or_nothing!(sp.attr, :background_color_inside)
@@ -987,64 +993,192 @@ 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
# -----------------------------------------------------------------------------
function has_black_border_for_default(st::Symbol)
like_histogram(st) || st in (:hexbin, :bar)
like_histogram(st) || st in (:hexbin, :bar, :shape)
end
# converts a symbol or string into a colorant (Colors.RGB), and assigns a color automatically
function getSeriesRGBColor(c, α, sp::Subplot, n::Int)
if c == :auto
c = autopick(sp[:color_palette], n)
end
plot_color(c, α)
end
function ensure_gradient!(d::KW, csym::Symbol, asym::Symbol)
if !isa(d[csym], ColorGradient)
d[csym] = cgrad(alpha = d[asym])
end
end
function _replace_linewidth(d::KW)
# get a good default linewidth... 0 for surface and heatmaps
if get(d, :linewidth, :auto) == :auto
d[:linewidth] = (get(d, :seriestype, :path) in (:surface,:heatmap,:image) ? 0 : 1)
end
end
function _add_defaults!(d::KW, plt::Plot, sp::Subplot, commandIndex::Int)
pkg = plt.backend
globalIndex = d[:series_plotindex]
# 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, false)
end
# this is how many series belong to this subplot
plotIndex = count(series -> series.d[:subplot] === sp && series.d[:primary], plt.series_list)
if get(d, :primary, true)
plotIndex += 1
end
aliasesAndAutopick(d, :linestyle, _styleAliases, supported_styles(pkg), plotIndex)
aliasesAndAutopick(d, :markershape, _markerAliases, supported_markers(pkg), plotIndex)
# update alphas
for asym in (:linealpha, :markeralpha, :fillalpha)
if d[asym] == nothing
d[asym] = d[:seriesalpha]
end
end
if d[:markerstrokealpha] == nothing
d[:markerstrokealpha] = d[:markeralpha]
end
# update series color
d[:seriescolor] = getSeriesRGBColor(d[:seriescolor], d[:seriesalpha], sp, plotIndex)
# update other colors
for s in (:line, :marker, :fill)
csym, asym = Symbol(s,:color), Symbol(s,:alpha)
d[csym] = if d[csym] == :match
plot_color(if has_black_border_for_default(d[:seriestype]) && s == :line
sp[:foreground_color_subplot]
else
d[:seriescolor]
end, d[asym])
else
getSeriesRGBColor(d[csym], d[asym], sp, plotIndex)
end
end
# update markerstrokecolor
d[:markerstrokecolor] = if d[:markerstrokecolor] == :match
plot_color(sp[:foreground_color_subplot], d[:markerstrokealpha])
else
getSeriesRGBColor(d[:markerstrokecolor], d[:markerstrokealpha], sp, plotIndex)
end
# if marker_z or line_z are set, ensure we have a gradient
if d[:marker_z] != nothing
ensure_gradient!(d, :markercolor, :markeralpha)
end
if d[:line_z] != nothing
ensure_gradient!(d, :linecolor, :linealpha)
end
# scatter plots don't have a line, but must have a shape
if d[:seriestype] in (:scatter, :scatter3d)
d[:linewidth] = 0
if d[:markershape] == :none
d[:markershape] = :circle
end
end
# set label
label = d[:label]
label = (label == "AUTO" ? "y$globalIndex" : label)
d[:label] = label
_replace_linewidth(d)
d
end
+141 -7
View File
@@ -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 = "")
@@ -117,15 +117,97 @@ Base.setindex!(axis::Axis, v, ks::Symbol...) = setindex!(axis.d, v, ks...)
Base.haskey(axis::Axis, k::Symbol) = haskey(axis.d, k)
Base.extrema(axis::Axis) = (ex = axis[:extrema]; (ex.emin, ex.emax))
# get discrete ticks, or not
function get_ticks(axis::Axis)
ticks = axis[:ticks]
dvals = axis[:discrete_values]
if !isempty(dvals) && ticks == :auto
axis[:continuous_values], dvals
const _scale_funcs = Dict{Symbol,Function}(
:log10 => log10,
:log2 => log2,
:ln => log,
)
const _inv_scale_funcs = Dict{Symbol,Function}(
:log10 => exp10,
:log2 => exp2,
:ln => exp,
)
const _label_func = Dict{Symbol,Function}(
:log10 => x -> "10^$x",
:log2 => x -> "2^$x",
:ln => x -> "e^$x",
)
scalefunc(scale::Symbol) = x -> get(_scale_funcs, scale, identity)(Float64(x))
invscalefunc(scale::Symbol) = x -> get(_inv_scale_funcs, scale, identity)(Float64(x))
labelfunc(scale::Symbol, backend::AbstractBackend) = get(_label_func, scale, string)
function optimal_ticks_and_labels(axis::Axis, ticks = nothing)
lims = axis_limits(axis)
# scale the limits
scale = axis[:scale]
scaled_lims = map(scalefunc(scale), lims)
# @show lims scaled_lims
# get a list of well-laid-out ticks
cv = if ticks == nothing
optimize_ticks(scaled_lims...,
k_min = 5, # minimum number of ticks
k_max = 8, # maximum number of ticks
# span_buffer = 0.0 # padding buffer in case nice ticks are closeby
)[1]
else
ticks
end
# # expand to ensure we see all the ticks
# expand_extrema!(axis, cv)
# rescale and return values and labels
# @show cv
ticklabels = if any(isfinite, cv)
map(labelfunc(scale, backend()), Showoff.showoff(cv, :plain))
else
UTF8String[]
end
tickvals = map(invscalefunc(scale), cv)
# @show tickvals ticklabels
# ticklabels = Showoff.showoff(tickvals, scale == :log10 ? :scientific : :auto)
tickvals, ticklabels
# basestr = scale == :log10 ? "10^" : scale == :log2 ? "2^" : scale == :ln ? "e^" : ""
# tickvals, ["$basestr$cvi" for cvi in cv]
end
# return (continuous_values, discrete_values) for the ticks on this axis
function get_ticks(axis::Axis)
ticks = axis[:ticks]
ticks in (nothing, false) && return nothing
dvals = axis[:discrete_values]
cv, dv = if !isempty(dvals) && ticks == :auto
# discrete ticks...
axis[:continuous_values], dvals
elseif ticks == :auto
# compute optimal ticks and labels
optimal_ticks_and_labels(axis)
elseif typeof(ticks) <: AVec
# override ticks, but get the labels
optimal_ticks_and_labels(axis, ticks)
elseif typeof(ticks) <: NTuple{2}
# assuming we're passed (ticks, labels)
ticks
else
error("Unknown ticks type in get_ticks: $(typeof(ticks))")
end
# @show ticks dvals cv dv
# TODO: better/smarter cutoff values for sampling ticks
if length(cv) > 30
rng = Int[round(Int,i) for i in linspace(1, length(cv), 15)]
cv[rng], dv[rng]
else
cv, dv
end
end
# -------------------------------------------------------------------------
@@ -139,6 +221,12 @@ end
function expand_extrema!(axis::Axis, v::Number)
expand_extrema!(axis[:extrema], v)
end
# these shouldn't impact the extrema
expand_extrema!(axis::Axis, ::Void) = axis[:extrema]
expand_extrema!(axis::Axis, ::Bool) = axis[:extrema]
function expand_extrema!{MIN<:Number,MAX<:Number}(axis::Axis, v::Tuple{MIN,MAX})
ex = axis[:extrema]
ex.emin = min(v[1], ex.emin)
@@ -332,3 +420,49 @@ function pie_labels(sp::Subplot, series::Series)
d[:x]
end
end
# -------------------------------------------------------------------------
# compute the line segments which should be drawn for this axis
function axis_drawing_info(sp::Subplot)
xaxis, yaxis = sp[:xaxis], sp[:yaxis]
xmin, xmax = axis_limits(xaxis)
ymin, ymax = axis_limits(yaxis)
xticks = get_ticks(xaxis)
yticks = get_ticks(yaxis)
spine_segs = Segments(2)
grid_segs = Segments(2)
if !(xaxis[:ticks] in (nothing, false))
f = scalefunc(yaxis[:scale])
invf = invscalefunc(yaxis[:scale])
t1 = invf(f(ymin) + 0.015*(f(ymax)-f(ymin)))
t2 = invf(f(ymax) - 0.015*(f(ymax)-f(ymin)))
push!(spine_segs, (xmin,ymin), (xmax,ymin)) # bottom spine
push!(spine_segs, (xmin,ymax), (xmax,ymax)) # top spine
for xtick in xticks[1]
push!(spine_segs, (xtick, ymin), (xtick, t1)) # bottom tick
push!(grid_segs, (xtick, t1), (xtick, t2)) # vertical grid
push!(spine_segs, (xtick, ymax), (xtick, t2)) # top tick
end
end
if !(yaxis[:ticks] in (nothing, false))
f = scalefunc(xaxis[:scale])
invf = invscalefunc(xaxis[:scale])
t1 = invf(f(xmin) + 0.015*(f(xmax)-f(xmin)))
t2 = invf(f(xmax) - 0.015*(f(xmax)-f(xmin)))
push!(spine_segs, (xmin,ymin), (xmin,ymax)) # left spine
push!(spine_segs, (xmax,ymin), (xmax,ymax)) # right spine
for ytick in yticks[1]
push!(spine_segs, (xmin, ytick), (t1, ytick)) # left tick
push!(grid_segs, (t1, ytick), (t2, ytick)) # horizontal grid
push!(spine_segs, (xmax, ytick), (t2, ytick)) # right tick
end
end
xticks, yticks, spine_segs, grid_segs
end
+17 -4
View File
@@ -57,13 +57,26 @@ _before_layout_calcs(plt::Plot) = nothing
title_padding(sp::Subplot) = sp[:title] == "" ? 0mm : sp[:titlefont].pointsize * pt
guide_padding(axis::Axis) = axis[:guide] == "" ? 0mm : axis[:guidefont].pointsize * pt
# TODO: this should account for both tick font and the size/length/rotation of tick labels
function tick_padding(axis::Axis)
ptsz = axis[:tickfont].pointsize * pt
if axis[:ticks] in (nothing,false)
0mm
elseif axis[:letter] == :x
2mm + ptsz
else
8mm
end
end
# Set the (left, top, right, bottom) minimum padding around the plot area
# to fit ticks, tick labels, guides, colorbars, etc.
function _update_min_padding!(sp::Subplot)
leftpad = 10mm + sp[:left_margin] + guide_padding(sp[:yaxis])
toppad = 2mm + sp[:top_margin] + title_padding(sp)
rightpad = 3mm + sp[:right_margin]
bottompad = 5mm + sp[:bottom_margin] + guide_padding(sp[:xaxis])
# TODO: something different when `is3d(sp) == true`
leftpad = tick_padding(sp[:yaxis]) + sp[:left_margin] + guide_padding(sp[:yaxis])
toppad = sp[:top_margin] + title_padding(sp)
rightpad = sp[:right_margin]
bottompad = tick_padding(sp[:xaxis]) + sp[:bottom_margin] + guide_padding(sp[:xaxis])
# @show (leftpad, toppad, rightpad, bottompad)
sp.minpad = (leftpad, toppad, rightpad, bottompad)
end
+34 -13
View File
@@ -29,7 +29,7 @@ supported_args(::GLVisualizeBackend) = merge_with_base_supported([
# :clims,
# :inset_subplots,
])
supported_types(::GLVisualizeBackend) = [:surface, :scatter, :scatter3d, :path, :path3d]
supported_types(::GLVisualizeBackend) = [:surface, :scatter, :scatter3d, :path, :path3d, :shape]
supported_styles(::GLVisualizeBackend) = [:auto, :solid]
supported_markers(::GLVisualizeBackend) = vcat([:none, :auto, :circle], collect(keys(_gl_marker_map)))
supported_scales(::GLVisualizeBackend) = [:identity]
@@ -44,8 +44,8 @@ function _initialize_backend(::GLVisualizeBackend; kw...)
import GeometryTypes: Point2f0, Point3f0, Vec2f0, Vec3f0
export GLVisualize
# TODO: remove this when PlotUtils is registered
import PlotUtils
# # TODO: remove this when PlotUtils is registered
# import PlotUtils
end
end
@@ -101,8 +101,10 @@ gl_color(c::RGBA{Float32}) = c
# convert to RGBA
function gl_color(c, a=nothing)
@show c, a
c = convertColor(c, a)
RGBA{Float32}(getColor(c))
@show c
RGBA{Float32}(c)
end
function gl_viewport(bb, rect)
@@ -125,11 +127,13 @@ function gl_draw_lines_2d(x, y, color, linewidth, sp_screen)
for rng in iter_segments(x, y)
n = length(rng)
n < 2 && continue
pts = gl_make_points(x[rng], y[rng])
@show pts, n
viz = GLVisualize.visualize(
gl_make_points(x[rng], y[rng]),
pts,
n==2 ? :linesegment : :lines,
color=color,
thickness = Float32(linewidth)
color = color,
thickness = thickness
)
GLVisualize.view(viz, sp_screen, camera=:orthographic_pixel)
end
@@ -141,11 +145,12 @@ function gl_draw_lines_3d(x, y, z, color, linewidth, sp_screen)
for rng in iter_segments(x, y, z)
n = length(rng)
n < 2 && continue
pts = gl_make_points(x[rng], y[rng], z[rng])
viz = GLVisualize.visualize(
gl_make_points(x[rng], y[rng], z[rng]),
pts,
n==2 ? :linesegment : :lines,
color=color,
thickness = Float32(linewidth)
thickness = thickness
)
GLVisualize.view(viz, sp_screen, camera=:perspective)
end
@@ -206,7 +211,7 @@ function gl_display(plt::Plot{GLVisualizeBackend})
sp.o = sp_screen
if !is3d(sp)
gl_draw_axes_2d(sp)
# gl_draw_axes_2d(sp)
end
# loop over the series and add them to the subplot
@@ -216,16 +221,16 @@ function gl_display(plt::Plot{GLVisualizeBackend})
x, y = map(Float32, d[:x]), map(Float32, d[:y])
msize = gl_relative_size(plt, d[:markersize])
viz = if st == :surface
if st == :surface
# TODO: can pass just the ranges and surface
ismatrix(x) || (x = repmat(x', length(y), 1))
ismatrix(y) || (y = repmat(y, 1, length(x)))
z = transpose_z(d, map(Float32, d[:z].surf), false)
viz = GLVisualize.visualize((x, y, z), :surface)
GLVisualize.view(viz, sp_screen, camera = :perspective)
GLVisualize.view(viz, sp_screen, camera = camera)
else
# paths and scatters
# paths, scatters, and shape
_3d && (z = map(Float32, d[:z]))
@@ -274,6 +279,22 @@ function gl_display(plt::Plot{GLVisualizeBackend})
# billboard=true
#))
end
if st == :shape
for rng in iter_segments(x, y)
pts = Point2f0[Point2f0(x[i], y[i]) for i in rng]
@show pts
mesh = GeometryTypes.GLNormalMesh(pts)
@show mesh
if !isempty(GeometryTypes.faces(mesh))
viz = GLVisualize.visualize(
mesh,
color = gl_color(d[:fillcolor], d[:fillalpha])
)
GLVisualize.view(viz, sp_screen, camera = camera)
end
end
end
end
end
GLAbstraction.center!(sp_screen, camera)
+162 -104
View File
@@ -28,6 +28,7 @@ supported_args(::GRBackend) = merge_with_base_supported([
:aspect_ratio,
:normalize, :weights,
:inset_subplots,
:bar_width,
])
supported_types(::GRBackend) = [
:path, :scatter,
@@ -106,17 +107,15 @@ const gr_font_family = Dict(
# --------------------------------------------------------------------------------------
function gr_getcolorind(v, a =nothing)
c = getColor(v)
idx = convert(Int, GR.inqcolorfromrgb(c.r, c.g, c.b))
GR.settransparency(float(a==nothing ? alpha(c) : a))
idx
function gr_getcolorind(c)
GR.settransparency(float(alpha(c)))
convert(Int, GR.inqcolorfromrgb(red(c), green(c), blue(c)))
end
gr_set_linecolor(c, a=nothing) = GR.setlinecolorind(gr_getcolorind(c, a))
gr_set_fillcolor(c, a=nothing) = GR.setfillcolorind(gr_getcolorind(c, a))
gr_set_markercolor(c, a=nothing) = GR.setmarkercolorind(gr_getcolorind(c, a))
gr_set_textcolor(c, a=nothing) = GR.settextcolorind(gr_getcolorind(c, a))
gr_set_linecolor(c) = GR.setlinecolorind(gr_getcolorind(cycle(c,1)))
gr_set_fillcolor(c) = GR.setfillcolorind(gr_getcolorind(cycle(c,1)))
gr_set_markercolor(c) = GR.setmarkercolorind(gr_getcolorind(cycle(c,1)))
gr_set_textcolor(c) = GR.settextcolorind(gr_getcolorind(cycle(c,1)))
# --------------------------------------------------------------------------------------
@@ -272,8 +271,8 @@ end
# draw the markers, one at a time
function gr_draw_markers(d::KW, x, y, msize, mz)
shape = d[:markershape]
function gr_draw_markers(series::Series, x, y, msize, mz)
shape = series[:markershape]
if shape != :none
for i=1:length(x)
msi = cycle(msize, i)
@@ -281,16 +280,19 @@ function gr_draw_markers(d::KW, x, y, msize, mz)
cfuncind = isa(shape, Shape) ? GR.setfillcolorind : GR.setmarkercolorind
# draw a filled in shape, slightly bigger, to estimate a stroke
cfunc(d[:markerstrokecolor], d[:markerstrokealpha])
gr_draw_marker(x[i], y[i], msi*1.2, shape, )
if series[:markerstrokewidth] > 0
cfunc(cycle(series[:markerstrokecolor], i)) #, series[:markerstrokealpha])
gr_draw_marker(x[i], y[i], msi + series[:markerstrokewidth], shape)
end
# draw the shape
if mz == nothing
cfunc(d[:markercolor], d[:markeralpha])
cfunc(cycle(series[:markercolor], i)) #, series[:markeralpha])
else
# pick a color from the pre-loaded gradient
ci = round(Int, 1000 + cycle(mz, i) * 255)
cfuncind(ci)
GR.settransparency(_gr_gradient_alpha[ci-999])
end
gr_draw_marker(x[i], y[i], msi, shape)
end
@@ -299,27 +301,26 @@ end
function gr_draw_markers(series::Series, x, y)
isempty(x) && return
d = series.d
mz = normalize_zvals(d[:marker_z])
mz = normalize_zvals(series[:marker_z])
GR.setfillintstyle(GR.INTSTYLE_SOLID)
gr_draw_markers(d, x, y, d[:markersize], mz)
gr_draw_markers(series, x, y, series[:markersize], mz)
if mz != nothing
gr_colorbar(d[:subplot])
gr_colorbar(series[:subplot])
end
end
# ---------------------------------------------------------
function gr_set_line(w, style, c, a)
function gr_set_line(w, style, c) #, a)
GR.setlinetype(gr_linetype[style])
GR.setlinewidth(w)
gr_set_linecolor(c, a)
gr_set_linecolor(c) #, a)
end
function gr_set_fill(c, a)
gr_set_fillcolor(c, a)
function gr_set_fill(c) #, a)
gr_set_fillcolor(c) #, a)
GR.setfillintstyle(GR.INTSTYLE_SOLID)
end
@@ -327,15 +328,15 @@ end
const _gr_point_mult = zeros(1)
# set the font attributes... assumes _gr_point_mult has been populated already
function gr_set_font(f::Font)
function gr_set_font(f::Font; halign = f.halign, valign = f.valign, color = f.color)
family = lowercase(f.family)
GR.setcharheight(_gr_point_mult[1] * f.pointsize)
GR.setcharup(sin(f.rotation), cos(f.rotation))
if haskey(gr_font_family, family)
GR.settextfontprec(100 + gr_font_family[family], GR.TEXT_PRECISION_STRING)
end
gr_set_textcolor(f.color)
GR.settextalign(gr_halign[f.halign], gr_valign[f.valign])
gr_set_textcolor(color)
GR.settextalign(gr_halign[halign], gr_valign[valign])
end
# --------------------------------------------------------------------------------------
@@ -402,13 +403,14 @@ gr_view_ydiff() = viewport_plotarea[4] - viewport_plotarea[3]
# --------------------------------------------------------------------------------------
const _gr_gradient_alpha = ones(256)
function gr_set_gradient(c, a)
grad = isa(c, ColorGradient) ? c : default_gradient()
grad = ColorGradient(grad, alpha=a)
function gr_set_gradient(c)
grad = isa(c, ColorGradient) ? c : cgrad()
for (i,z) in enumerate(linspace(0, 1, 256))
c = getColorZ(grad, z)
c = grad[z]
GR.setcolorrep(999+i, red(c), green(c), blue(c))
_gr_gradient_alpha[i] = alpha(c)
end
grad
end
@@ -446,7 +448,7 @@ function gr_display(plt::Plot)
# update point mult
px_per_pt = px / pt
_gr_point_mult[1] = px_per_pt / h
_gr_point_mult[1] = 1.5 * px_per_pt / max(h,w)
# subplots:
for sp in plt.subplots
@@ -463,7 +465,7 @@ function gr_display(sp::Subplot{GRBackend}, w, h, viewport_canvas)
viewport_plotarea[:] = gr_viewport_from_bbox(plotarea(sp), w, h, viewport_canvas)
# fill in the plot area background
bg = getColor(sp[:background_color_inside])
bg = plot_color(sp[:background_color_inside])
gr_fill_viewport(viewport_plotarea, bg)
# reduced from before... set some flags based on the series in this subplot
@@ -473,8 +475,8 @@ function gr_display(sp::Subplot{GRBackend}, w, h, viewport_canvas)
draw_axes = true
# axes_2d = true
for series in series_list(sp)
st = series.d[:seriestype]
if st in (:contour, :surface, :heatmap) || series.d[:marker_z] != nothing
st = series[:seriestype]
if st in (:contour, :surface, :heatmap) || series[:marker_z] != nothing
cmap = true
end
if st == :pie
@@ -559,26 +561,61 @@ function gr_display(sp::Subplot{GRBackend}, w, h, viewport_canvas)
gr_polaraxes(rmin, rmax)
elseif draw_axes
if xmax > xmin && ymax > ymin
GR.setwindow(xmin, xmax, ymin, ymax)
end
xticks, yticks, spine_segs, grid_segs = axis_drawing_info(sp)
# @show xticks yticks #spine_segs grid_segs
# draw the grid lines
# TODO: control line style/width
# GR.setlinetype(GR.LINETYPE_DOTTED)
if sp[:grid]
gr_set_linecolor(sp[:foreground_color_grid])
GR.grid(xtick, ytick, 0, 0, majorx, majory)
# gr_set_linecolor(sp[:foreground_color_grid])
# GR.grid(xtick, ytick, 0, 0, majorx, majory)
gr_set_line(1, :dot, sp[:foreground_color_grid])
GR.settransparency(0.5)
gr_polyline(coords(grid_segs)...)
end
GR.settransparency(1.0)
# spine (border) and tick marks
gr_set_line(1, :solid, sp[:xaxis][:foreground_color_axis])
gr_polyline(coords(spine_segs)...)
if !(xticks in (nothing, false))
# x labels
flip = sp[:yaxis][:flip]
gr_set_font(sp[:xaxis][:tickfont], valign = :top, color = sp[:xaxis][:foreground_color_axis])
for (cv, dv) in zip(xticks...)
xi, yi = GR.wctondc(cv, flip ? ymax : ymin)
# @show cv dv ymin xi yi
gr_text(xi, yi-0.01, string(dv))
end
end
window_diag = sqrt(gr_view_xdiff()^2 + gr_view_ydiff()^2)
ticksize = 0.0075 * window_diag
if outside_ticks
ticksize = -ticksize
if !(yticks in (nothing, false))
# y labels
flip = sp[:xaxis][:flip]
gr_set_font(sp[:yaxis][:tickfont], halign = :right, color = sp[:yaxis][:foreground_color_axis])
for (cv, dv) in zip(yticks...)
xi, yi = GR.wctondc(flip ? xmax : xmin, cv)
# @show cv dv xmin xi yi
gr_text(xi-0.01, yi, string(dv))
end
end
# TODO: this should be done for each axis separately
gr_set_linecolor(xaxis[:foreground_color_axis])
x1, x2 = xaxis[:flip] ? (xmax,xmin) : (xmin,xmax)
y1, y2 = yaxis[:flip] ? (ymax,ymin) : (ymin,ymax)
GR.axes(xtick, ytick, x1, y1, 1, 1, ticksize)
GR.axes(xtick, ytick, x2, y2, -1, -1, -ticksize)
# window_diag = sqrt(gr_view_xdiff()^2 + gr_view_ydiff()^2)
# ticksize = 0.0075 * window_diag
# if outside_ticks
# ticksize = -ticksize
# end
# # TODO: this should be done for each axis separately
# gr_set_linecolor(xaxis[:foreground_color_axis])
# x1, x2 = xaxis[:flip] ? (xmax,xmin) : (xmin,xmax)
# y1, y2 = yaxis[:flip] ? (ymax,ymin) : (ymin,ymax)
# GR.axes(xtick, ytick, x1, y1, 1, 1, ticksize)
# GR.axes(xtick, ytick, x2, y2, -1, -1, -ticksize)
end
# end
@@ -612,14 +649,13 @@ function gr_display(sp::Subplot{GRBackend}, w, h, viewport_canvas)
GR.setcolormap(1000 + GR.COLORMAP_COOLWARM)
for (idx, series) in enumerate(series_list(sp))
d = series.d
st = d[:seriestype]
st = series[:seriestype]
# update the current stored gradient
if st in (:contour, :surface, :wireframe, :heatmap)
gr_set_gradient(d[:fillcolor], d[:fillalpha])
elseif d[:marker_z] != nothing
d[:markercolor] = gr_set_gradient(d[:markercolor], d[:markeralpha])
gr_set_gradient(series[:fillcolor]) #, series[:fillalpha])
elseif series[:marker_z] != nothing
series[:markercolor] = gr_set_gradient(series[:markercolor])
end
GR.savestate()
@@ -634,12 +670,21 @@ function gr_display(sp::Subplot{GRBackend}, w, h, viewport_canvas)
end
end
x, y, z = d[:x], d[:y], d[:z]
frng = d[:fillrange]
x, y, z = series[:x], series[:y], series[:z]
frng = series[:fillrange]
# recompute data
if st in (:contour, :surface, :wireframe)
z = vec(transpose_z(d, z.surf, false))
if typeof(z) <: Surface
if st == :heatmap #&& size(z.surf) == (length(y), length(x))
expand_extrema!(sp[:xaxis], (x[1]-0.5*(x[2]-x[1]), x[end]+0.5*(x[end]-x[end-1])))
expand_extrema!(sp[:yaxis], (y[1]-0.5*(y[2]-y[1]), y[end]+0.5*(y[end]-y[end-1])))
# # coords are centers... turn into edges
# xd = diff(x)
# x = vcat(x[1]-0.5xd[1], x[1]+xd, x[end]+0.5xd[end])
# yd = diff(y)
# y = vcat(y[1]-0.5yd[1], y[1]+yd, y[end]+0.5yd[end])
end
z = vec(transpose_z(series, z.surf, false))
elseif ispolar(sp)
if frng != nothing
_, frng = convert_to_polar(x, frng, (rmin, rmax))
@@ -652,7 +697,7 @@ function gr_display(sp::Subplot{GRBackend}, w, h, viewport_canvas)
# do area fill
if frng != nothing
gr_set_fillcolor(d[:fillcolor], d[:fillalpha])
gr_set_fillcolor(series[:fillcolor]) #, series[:fillalpha])
GR.setfillintstyle(GR.INTSTYLE_SOLID)
frng = isa(frng, Number) ? Float64[frng] : frng
nx, ny, nf = length(x), length(y), length(frng)
@@ -668,24 +713,24 @@ function gr_display(sp::Subplot{GRBackend}, w, h, viewport_canvas)
# draw the line(s)
if st == :path
gr_set_line(d[:linewidth], d[:linestyle], d[:linecolor], d[:linealpha])
gr_set_line(series[:linewidth], series[:linestyle], series[:linecolor]) #, series[:linealpha])
gr_polyline(x, y)
end
end
if d[:markershape] != :none
if series[:markershape] != :none
gr_draw_markers(series, x, y)
end
elseif st == :contour
zmin, zmax = gr_lims(zaxis, false)
if typeof(d[:levels]) <: Array
h = d[:levels]
if typeof(series[:levels]) <: Array
h = series[:levels]
else
h = linspace(zmin, zmax, d[:levels])
h = linspace(zmin, zmax, series[:levels])
end
GR.setspace(zmin, zmax, 0, 90)
if d[:fillrange] != nothing
if series[:fillrange] != nothing
GR.surface(x, y, z, GR.OPTION_CELL_ARRAY)
else
GR.contour(x, y, h, z, 1000)
@@ -704,7 +749,11 @@ function gr_display(sp::Subplot{GRBackend}, w, h, viewport_canvas)
elseif st in [:surface, :wireframe]
if st == :surface
GR.gr3.surface(x, y, z, GR.OPTION_COLORED_MESH)
if length(x) == length(y) == length(z)
GR.trisurface(x, y, z)
else
GR.gr3.surface(x, y, z, GR.OPTION_COLORED_MESH)
end
else
GR.setfillcolorind(0)
GR.surface(x, y, z, GR.OPTION_FILLED_MESH)
@@ -712,23 +761,24 @@ function gr_display(sp::Subplot{GRBackend}, w, h, viewport_canvas)
cmap && gr_colorbar(sp)
elseif st == :heatmap
z = vec(transpose_z(d, z.surf, false))
# z = vec(transpose_z(series, z.surf, false))
zmin, zmax = gr_lims(zaxis, true)
GR.setspace(zmin, zmax, 0, 90)
GR.surface(x, y, z, GR.OPTION_COLORED_MESH)
# GR.surface(x, y, z, GR.OPTION_COLORED_MESH)
GR.surface(x, y, z, GR.OPTION_HEATMAP)
cmap && gr_colorbar(sp)
elseif st in (:path3d, :scatter3d)
# draw path
if st == :path3d
if length(x) > 1
gr_set_line(d[:linewidth], d[:linestyle], d[:linecolor], d[:linealpha])
gr_set_line(series[:linewidth], series[:linestyle], series[:linecolor]) #, series[:linealpha])
GR.polyline3d(x, y, z)
end
end
# draw markers
if st == :scatter3d || d[:markershape] != :none
if st == :scatter3d || series[:markershape] != :none
x2, y2 = unzip(map(GR.wc3towc, x, y, z))
gr_draw_markers(series, x2, y2)
end
@@ -749,7 +799,7 @@ function gr_display(sp::Subplot{GRBackend}, w, h, viewport_canvas)
ymin, ymax = ycenter - r, ycenter + r
end
labels = pie_labels(sp, series)
slices = d[:y]
slices = series[:y]
numslices = length(slices)
total = sum(slices)
a1 = 0
@@ -782,17 +832,27 @@ function gr_display(sp::Subplot{GRBackend}, w, h, viewport_canvas)
GR.selntran(1)
elseif st == :shape
# draw the interior
gr_set_fill(d[:fillcolor], d[:fillalpha])
gr_polyline(d[:x], d[:y], GR.fillarea)
for (i,rng) in enumerate(iter_segments(series[:x], series[:y]))
if length(rng) > 1
# connect to the beginning
rng = vcat(rng, rng[1])
# draw the shapes
gr_set_line(d[:linewidth], :solid, d[:linecolor], d[:linealpha])
gr_polyline(d[:x], d[:y])
# get the segments
x, y = series[:x][rng], series[:y][rng]
# draw the interior
gr_set_fill(cycle(series[:fillcolor], i))
GR.fillarea(x, y)
# draw the shapes
gr_set_line(series[:linewidth], :solid, cycle(series[:linecolor], i))
GR.polyline(x, y)
end
end
elseif st == :image
img = d[:z].surf
img = series[:z].surf
w, h = size(img)
if eltype(img) <: Colors.AbstractGray
grey = round(UInt8, float(img) * 255)
@@ -822,11 +882,11 @@ function gr_display(sp::Subplot{GRBackend}, w, h, viewport_canvas)
for series in series_list(sp)
should_add_to_legend(series) || continue
n += 1
if typeof(series.d[:label]) <: Array
if typeof(series[:label]) <: Array
i += 1
lab = series.d[:label][i]
lab = series[:label][i]
else
lab = series.d[:label]
lab = series[:label]
end
tbx, tby = gr_inqtext(0, 0, lab)
w = max(w, tbx[3] - tbx[1])
@@ -844,13 +904,12 @@ function gr_display(sp::Subplot{GRBackend}, w, h, viewport_canvas)
i = 0
for series in series_list(sp)
should_add_to_legend(series) || continue
d = series.d
st = d[:seriestype]
gr_set_line(d[:linewidth], d[:linestyle], d[:linecolor], d[:linealpha])
st = series[:seriestype]
gr_set_line(series[:linewidth], series[:linestyle], series[:linecolor]) #, series[:linealpha])
if st == :path
GR.polyline([xpos - 0.07, xpos - 0.01], [ypos, ypos])
elseif st == :shape
gr_set_fill(d[:fillcolor], d[:fillalpha])
gr_set_fill(series[:fillcolor]) #, series[:fillalpha])
l, r = xpos-0.07, xpos-0.01
b, t = ypos-0.4dy, ypos+0.4dy
x = [l, r, r, l, l]
@@ -859,28 +918,15 @@ function gr_display(sp::Subplot{GRBackend}, w, h, viewport_canvas)
gr_polyline(x, y)
end
gr_draw_markers(d, xpos-[0.06,0.02], [ypos,ypos], 10, nothing)
# shape = d[:markershape]
# if shape != :none #st == :scatter || d[:markershape] != :none
# msize = 10
# for xoff in [0.06,0.02]
# gr_set_markercolor(d[:markerstrokecolor], d[:markerstrokealpha])
# gr_draw_marker(xpos-xoff, ypos, msize*1.1, shape)
# gr_set_markercolor(d[:markercolor], d[:markeralpha])
# gr_draw_marker(xpos-xoff, ypos, msize, shape)
# end
# # gr_setmarkershape(d)
# # if st == :path
# # gr_polymarker(d, [xpos - 0.06, xpos - 0.02], [ypos, ypos])
# # else
# # gr_polymarker(d, [xpos - 0.06, xpos - 0.04, xpos - 0.02], [ypos, ypos, ypos])
# # end
# end
if typeof(d[:label]) <: Array
if series[:markershape] != :none
gr_draw_markers(series, xpos-[0.06,0.02], [ypos,ypos], 10, nothing)
end
if typeof(series[:label]) <: Array
i += 1
lab = d[:label][i]
lab = series[:label][i]
else
lab = d[:label]
lab = series[:label]
end
GR.settextalign(GR.TEXT_HALIGN_LEFT, GR.TEXT_VALIGN_HALF)
gr_set_textcolor(sp[:foreground_color_legend])
@@ -930,5 +976,17 @@ for (mime, fmt) in _gr_mimeformats
end
function _display(plt::Plot{GRBackend})
gr_display(plt)
if plt[:display_type] == :inline
GR.emergencyclosegks()
filepath = tempname() * ".pdf"
ENV["GKS_WSTYPE"] = "pdf"
ENV["GKS_FILEPATH"] = filepath
gr_display(plt)
GR.emergencyclosegks()
content = string("\033]1337;File=inline=1;preserveAspectRatio=0:", base64encode(open(readbytes, filepath)), "\a")
println(content)
rm(filepath)
else
gr_display(plt)
end
end
+14 -8
View File
@@ -94,20 +94,18 @@ const _pgf_series_extrastyle = KW(
# --------------------------------------------------------------------------------------
# takes in color,alpha, and returns color and alpha appropriate for pgf style
function pgf_color(c, a = nothing)
c = getColor(c)
function pgf_color(c)
cstr = @sprintf("{rgb,1:red,%.8f;green,%.8f;blue,%.8f}", red(c), green(c), blue(c))
a = float(a == nothing ? alpha(c) : a)
cstr, a
cstr, alpha(c)
end
function pgf_fillstyle(d::KW)
cstr,a = pgf_color(d[:fillcolor], d[:fillalpha])
cstr,a = pgf_color(d[:fillcolor])
"fill = $cstr, fill opacity=$a"
end
function pgf_linestyle(d::KW)
cstr,a = pgf_color(d[:linecolor], d[:linealpha])
cstr,a = pgf_color(d[:linecolor])
"""
color = $cstr,
draw opacity=$a,
@@ -117,8 +115,8 @@ end
function pgf_marker(d::KW)
shape = d[:markershape]
cstr, a = pgf_color(d[:markercolor], d[:markeralpha])
cstr_stroke, a_stroke = pgf_color(d[:markerstrokecolor], d[:markerstrokealpha])
cstr, a = pgf_color(d[:markercolor])
cstr_stroke, a_stroke = pgf_color(d[:markerstrokecolor])
"""
mark = $(get(_pgfplots_markers, shape, "*")),
mark size = $(0.5 * d[:markersize]),
@@ -214,6 +212,11 @@ function pgf_axis(sp::Subplot, letter)
scale == :ln || push!(style, "log basis $letter=$(scale == :log2 ? 2 : 10)")
end
# ticks on or off
if axis[:ticks] in (nothing, false)
push!(style, "$(letter)majorticks=false")
end
# limits
# TODO: support zlims
if letter != :z
@@ -241,6 +244,9 @@ function _make_pgf_plot!(plt::Plot)
if letter != :z || is3d(sp)
axisstyle, axiskw = pgf_axis(sp, letter)
merge!(kw, axiskw)
for sty in axisstyle
push!(style, sty)
end
end
end
+115 -95
View File
@@ -88,7 +88,7 @@ function plotly_font(font::Font, color = font.color)
KW(
:family => font.family,
:size => round(Int, font.pointsize*1.4),
:color => webcolor(color),
:color => rgba_string(color),
)
end
@@ -126,7 +126,7 @@ end
# # :ay => -40,
# :ax => 10xdiff / dist,
# :ay => -10ydiff / dist,
# :arrowcolor => webcolor(d[:linecolor], d[:linealpha]),
# :arrowcolor => rgba_string(d[:linecolor]),
# :xref => "x",
# :yref => "y",
# :arrowsize => 10a.headwidth,
@@ -175,8 +175,8 @@ function plotly_axis(axis::Axis, sp::Subplot)
ax[:titlefont] = plotly_font(axis[:guidefont], axis[:foreground_color_guide])
ax[:type] = plotly_scale(axis[:scale])
ax[:tickfont] = plotly_font(axis[:tickfont], axis[:foreground_color_text])
ax[:tickcolor] = webcolor(axis[:foreground_color_border])
ax[:linecolor] = webcolor(axis[:foreground_color_border])
ax[:tickcolor] = rgba_string(axis[:foreground_color_border])
ax[:linecolor] = rgba_string(axis[:foreground_color_border])
# lims
lims = axis[:lims]
@@ -190,7 +190,7 @@ function plotly_axis(axis::Axis, sp::Subplot)
end
# ticks
ticks = axis[:ticks]
ticks = get_ticks(axis)
if ticks != :auto
ttype = ticksType(ticks)
if ttype == :ticks
@@ -214,7 +214,7 @@ function plotly_layout(plt::Plot)
w, h = plt[:size]
d_out[:width], d_out[:height] = w, h
d_out[:paper_bgcolor] = webcolor(plt[:background_color_outside])
d_out[:paper_bgcolor] = rgba_string(plt[:background_color_outside])
d_out[:margin] = KW(:l=>0, :b=>0, :r=>0, :t=>20)
d_out[:annotations] = KW[]
@@ -239,7 +239,7 @@ function plotly_layout(plt::Plot)
push!(d_out[:annotations], plotly_annotation_dict(titlex, titley, text(sp[:title], titlefont)))
end
d_out[:plot_bgcolor] = webcolor(sp[:background_color_inside])
d_out[:plot_bgcolor] = rgba_string(sp[:background_color_inside])
# TODO: x/y axis tick values/labels
@@ -259,8 +259,8 @@ function plotly_layout(plt::Plot)
d_out[:showlegend] = sp[:legend] != :none
if sp[:legend] != :none
d_out[:legend] = KW(
:bgcolor => webcolor(sp[:background_color_legend]),
:bordercolor => webcolor(sp[:foreground_color_legend]),
:bgcolor => rgba_string(sp[:background_color_legend]),
:bordercolor => rgba_string(sp[:foreground_color_legend]),
:font => plotly_font(sp[:legendfont], sp[:foreground_color_legend]),
)
end
@@ -298,10 +298,11 @@ function plotly_layout_json(plt::Plot)
end
function plotly_colorscale(grad::ColorGradient, alpha = nothing)
[[grad.values[i], webcolor(grad.colors[i], alpha)] for i in 1:length(grad.colors)]
function plotly_colorscale(grad::ColorGradient, α)
[[grad.values[i], rgb_string(grad.colors[i])] for i in 1:length(grad.colors)]
end
plotly_colorscale(c, alpha = nothing) = plotly_colorscale(default_gradient(), alpha)
plotly_colorscale(c, α) = plotly_colorscale(cgrad(alpha=α), α)
# plotly_colorscale(c, alpha = nothing) = plotly_colorscale(cgrad(), alpha)
const _plotly_markers = KW(
:rect => "square",
@@ -330,10 +331,17 @@ function plotly_close_shapes(x, y)
nanvcat(xs), nanvcat(ys)
end
plotly_data(v) = collect(v)
plotly_data{R<:Rational}(v::AbstractArray{R}) = float(v)
# get a dictionary representing the series params (d is the Plots-dict, d_out is the Plotly-dict)
function plotly_series(plt::Plot, series::Series)
d = series.d
sp = d[:subplot]
st = series[:seriestype]
if st == :shape
return plotly_series_shapes(plt, series)
end
sp = series[:subplot]
d_out = KW()
# these are the axes that the series should be mapped to
@@ -342,12 +350,11 @@ function plotly_series(plt::Plot, series::Series)
d_out[:yaxis] = "y$spidx"
d_out[:showlegend] = should_add_to_legend(series)
x, y = collect(d[:x]), collect(d[:y])
d_out[:name] = d[:label]
st = d[:seriestype]
x, y = plotly_data(series[:x]), plotly_data(series[:y])
d_out[:name] = series[:label]
isscatter = st in (:scatter, :scatter3d, :scattergl)
hasmarker = isscatter || d[:markershape] != :none
# hasline = !isscatter
hasmarker = isscatter || series[:markershape] != :none
hasline = st in (:path, :path3d)
# set the "type"
@@ -358,76 +365,36 @@ function plotly_series(plt::Plot, series::Series)
else
hasline ? "lines" : "none"
end
if d[:fillrange] == true || d[:fillrange] == 0
if series[:fillrange] == true || series[:fillrange] == 0
d_out[:fill] = "tozeroy"
d_out[:fillcolor] = webcolor(d[:fillcolor], d[:fillalpha])
elseif !(d[:fillrange] in (false, nothing))
warn("fillrange ignored... plotly only supports filling to zero. fillrange: $(d[:fillrange])")
d_out[:fillcolor] = rgba_string(series[:fillcolor])
elseif !(series[:fillrange] in (false, nothing))
warn("fillrange ignored... plotly only supports filling to zero. fillrange: $(series[:fillrange])")
end
d_out[:x], d_out[:y] = x, y
elseif st == :shape
# to draw polygons, we actually draw lines with fill
d_out[:type] = "scatter"
d_out[:mode] = "lines"
d_out[:x], d_out[:y] = plotly_close_shapes(x, y)
# @show map(length, (x,y,d_out[:x],d_out[:y]))
# @show d_out[:x] d_out[:y]
d_out[:fill] = "tozeroy"
d_out[:fillcolor] = webcolor(d[:fillcolor], d[:fillalpha])
if d[:markerstrokewidth] > 0
d_out[:line] = KW(
:color => webcolor(d[:linecolor], d[:linealpha]),
:width => d[:linewidth],
:dash => string(d[:linestyle]),
)
end
elseif st == :bar
d_out[:type] = "bar"
d_out[:x], d_out[:y] = x, y
d_out[:orientation] = isvertical(d) ? "v" : "h"
# elseif st == :histogram2d
# d_out[:type] = "histogram2d"
# d_out[:x], d_out[:y] = x, y
# if isa(d[:bins], Tuple)
# xbins, ybins = d[:bins]
# else
# xbins = ybins = d[:bins]
# end
# d_out[:nbinsx] = xbins
# d_out[:nbinsy] = ybins
# d_out[:colorscale] = plotly_colorscale(d[:fillcolor], d[:fillalpha])
# elseif st in (:histogram, :density)
# d_out[:type] = "histogram"
# isvert = isvertical(d)
# d_out[isvert ? :x : :y] = y
# d_out[isvert ? :nbinsx : :nbinsy] = d[:bins]
# if st == :density
# d_out[:histogramnorm] = "probability density"
# end
d_out[:orientation] = isvertical(series) ? "v" : "h"
elseif st == :heatmap
d_out[:type] = "heatmap"
d_out[:x], d_out[:y], d_out[:z] = d[:x], d[:y], transpose_z(d, d[:z].surf, false)
d_out[:colorscale] = plotly_colorscale(d[:fillcolor], d[:fillalpha])
d_out[:x], d_out[:y], d_out[:z] = series[:x], series[:y], transpose_z(series, series[:z].surf, false)
d_out[:colorscale] = plotly_colorscale(series[:fillcolor], series[:fillalpha])
elseif st == :contour
d_out[:type] = "contour"
d_out[:x], d_out[:y], d_out[:z] = d[:x], d[:y], transpose_z(d, d[:z].surf, false)
# d_out[:showscale] = d[:colorbar] != :none
d_out[:ncontours] = d[:levels]
d_out[:contours] = KW(:coloring => d[:fillrange] != nothing ? "fill" : "lines")
d_out[:colorscale] = plotly_colorscale(d[:linecolor], d[:linealpha])
d_out[:x], d_out[:y], d_out[:z] = series[:x], series[:y], transpose_z(series, series[:z].surf, false)
# d_out[:showscale] = series[:colorbar] != :none
d_out[:ncontours] = series[:levels]
d_out[:contours] = KW(:coloring => series[:fillrange] != nothing ? "fill" : "lines")
d_out[:colorscale] = plotly_colorscale(series[:linecolor], series[:linealpha])
elseif st in (:surface, :wireframe)
d_out[:type] = "surface"
d_out[:x], d_out[:y], d_out[:z] = d[:x], d[:y], transpose_z(d, d[:z].surf, false)
d_out[:colorscale] = plotly_colorscale(d[:fillcolor], d[:fillalpha])
d_out[:x], d_out[:y], d_out[:z] = series[:x], series[:y], transpose_z(series, series[:z].surf, false)
d_out[:colorscale] = plotly_colorscale(series[:fillcolor], series[:fillalpha])
elseif st == :pie
d_out[:type] = "pie"
@@ -443,7 +410,7 @@ function plotly_series(plt::Plot, series::Series)
hasline ? "lines" : "none"
end
d_out[:x], d_out[:y] = x, y
d_out[:z] = collect(d[:z])
d_out[:z] = plotly_data(series[:z])
else
warn("Plotly: seriestype $st isn't supported.")
@@ -453,32 +420,32 @@ function plotly_series(plt::Plot, series::Series)
# add "marker"
if hasmarker
d_out[:marker] = KW(
:symbol => get(_plotly_markers, d[:markershape], string(d[:markershape])),
:opacity => d[:markeralpha],
:size => 2 * d[:markersize],
:color => webcolor(d[:markercolor], d[:markeralpha]),
:symbol => get(_plotly_markers, series[:markershape], string(series[:markershape])),
# :opacity => series[:markeralpha],
:size => 2 * series[:markersize],
# :color => rgba_string(series[:markercolor]),
:line => KW(
:color => webcolor(d[:markerstrokecolor], d[:markerstrokealpha]),
:width => d[:markerstrokewidth],
:color => rgba_string(series[:markerstrokecolor]),
:width => series[:markerstrokewidth],
),
)
# gotta hack this (for now?) since plotly can't handle rgba values inside the gradient
if d[:marker_z] != nothing
# d_out[:marker][:color] = d[:marker_z]
# d_out[:marker][:colorscale] = plotly_colorscale(d[:markercolor], d[:markeralpha])
# d_out[:showscale] = true
grad = ColorGradient(d[:markercolor], alpha=d[:markeralpha])
zmin, zmax = extrema(d[:marker_z])
d_out[:marker][:color] = [webcolor(getColorZ(grad, (zi - zmin) / (zmax - zmin))) for zi in d[:marker_z]]
d_out[:marker][:color] = if series[:marker_z] == nothing
rgba_string(series[:markercolor])
else
# grad = ColorGradient(series[:markercolor], alpha=series[:markeralpha])
grad = series[:markercolor]
zmin, zmax = extrema(series[:marker_z])
[rgba_string(grad[(zi - zmin) / (zmax - zmin)]) for zi in series[:marker_z]]
end
end
# add "line"
if hasline
d_out[:line] = KW(
:color => webcolor(d[:linecolor], d[:linealpha]),
:width => d[:linewidth],
:color => rgba_string(series[:linecolor]),
:width => series[:linewidth],
:shape => if st == :steppre
"vh"
elseif st == :steppost
@@ -486,32 +453,85 @@ function plotly_series(plt::Plot, series::Series)
else
"linear"
end,
:dash => string(d[:linestyle]),
:dash => string(series[:linestyle]),
# :dash => "solid",
)
end
plotly_polar!(d_out, series)
plotly_hover!(d_out, series[:hover])
[d_out]
end
function plotly_series_shapes(plt::Plot, series::Series)
d_outs = []
# TODO: create a d_out for each polygon
# x, y = series[:x], series[:y]
# these are the axes that the series should be mapped to
spidx = plotly_subplot_index(series[:subplot])
base_d = KW()
base_d[:xaxis] = "x$spidx"
base_d[:yaxis] = "y$spidx"
base_d[:name] = series[:label]
# base_d[:legendgroup] = series[:label]
x, y = plotly_data(series[:x]), plotly_data(series[:y])
for (i,rng) in enumerate(iter_segments(x,y))
length(rng) < 2 && continue
# to draw polygons, we actually draw lines with fill
d_out = merge(base_d, KW(
:type => "scatter",
:mode => "lines",
:x => vcat(x[rng], x[rng[1]]),
:y => vcat(y[rng], y[rng[1]]),
:fill => "tozeroy",
:fillcolor => rgba_string(cycle(series[:fillcolor], i)),
))
if series[:markerstrokewidth] > 0
d_out[:line] = KW(
:color => rgba_string(cycle(series[:linecolor], i)),
:width => series[:linewidth],
:dash => string(series[:linestyle]),
)
end
d_out[:showlegend] = i==1 ? should_add_to_legend(series) : false
plotly_polar!(d_out, series)
plotly_hover!(d_out, cycle(series[:hover], i))
push!(d_outs, d_out)
end
d_outs
end
function plotly_polar!(d_out::KW, series::Series)
# convert polar plots x/y to theta/radius
if ispolar(d[:subplot])
if ispolar(series[:subplot])
d_out[:t] = rad2deg(pop!(d_out, :x))
d_out[:r] = pop!(d_out, :y)
end
end
function plotly_hover!(d_out::KW, hover)
# hover text
hover = d[:hover]
if hover in (:none, false)
d_out[:hoverinfo] = "none"
elseif hover != nothing
d_out[:hoverinfo] = "text"
d_out[:text] = hover
end
d_out
end
# get a list of dictionaries, each representing the series params
function plotly_series_json(plt::Plot)
JSON.json(map(series -> plotly_series(plt, series), plt.series_list))
slist = []
for series in plt.series_list
append!(slist, plotly_series(plt, series))
end
JSON.json(slist)
# JSON.json(map(series -> plotly_series(plt, series), plt.series_list))
end
# ----------------------------------------------------------------
+6 -4
View File
@@ -33,10 +33,12 @@ end
function _series_added(plt::Plot{PlotlyJSBackend}, series::Series)
syncplot = plt.o
pdict = plotly_series(plt, series)
typ = pop!(pdict, :type)
gt = PlotlyJS.GenericTrace(typ; pdict...)
PlotlyJS.addtraces!(syncplot, gt)
pdicts = plotly_series(plt, series)
for pdict in pdicts
typ = pop!(pdict, :type)
gt = PlotlyJS.GenericTrace(typ; pdict...)
PlotlyJS.addtraces!(syncplot, gt)
end
end
function _series_updated(plt::Plot{PlotlyJSBackend}, series::Series)
+226 -318
View File
@@ -50,10 +50,13 @@ is_subplot_supported(::PyPlotBackend) = true
function _initialize_backend(::PyPlotBackend)
@eval begin
# see: https://github.com/tbreloff/Plots.jl/issues/308
ENV["OVERRIDE_PYPLOT_DISPLAY"] = true
# problem: https://github.com/tbreloff/Plots.jl/issues/308
# solution: hack from @stevengj: https://github.com/stevengj/PyPlot.jl/pull/223#issuecomment-229747768
otherdisplays = splice!(Base.Multimedia.displays, 2:length(Base.Multimedia.displays))
import PyPlot
import LaTeXStrings: latexstring
append!(Base.Multimedia.displays, otherdisplays)
export PyPlot
const pycolors = PyPlot.pywrap(PyPlot.pyimport("matplotlib.colors"))
const pypath = PyPlot.pywrap(PyPlot.pyimport("matplotlib.path"))
@@ -76,26 +79,37 @@ end
# --------------------------------------------------------------------------------------
# --------------------------------------------------------------------------------------
# convert colorant to 4-tuple RGBA
py_color(c::Colorant, α=nothing) = map(f->float(f(convertColor(c,α))), (red, green, blue, alpha))
py_color(cvec::ColorVector, α=nothing) = map(py_color, convertColor(cvec, α).v)
py_color(grad::ColorGradient, α=nothing) = map(c -> py_color(c, α), grad.colors)
py_color(scheme::ColorScheme, α=nothing) = py_color(convertColor(getColor(scheme), α))
py_color(vec::AVec, α=nothing) = map(c->py_color(c,α), vec)
py_color(c, α=nothing) = py_color(convertColor(c, α))
# # convert colorant to 4-tuple RGBA
# py_color(c::Colorant, α=nothing) = map(f->float(f(convertColor(c,α))), (red, green, blue, alpha))
# py_color(cvec::ColorVector, α=nothing) = map(py_color, convertColor(cvec, α).v)
# py_color(grad::ColorGradient, α=nothing) = map(c -> py_color(c, α), grad.colors)
# py_color(scheme::ColorScheme, α=nothing) = py_color(convertColor(getColor(scheme), α))
# py_color(vec::AVec, α=nothing) = map(c->py_color(c,α), vec)
# py_color(c, α=nothing) = py_color(convertColor(c, α))
function py_colormap(c::ColorGradient, α=nothing)
pyvals = [(v, py_color(getColorZ(c, v), α)) for v in c.values]
pycolors.pymember("LinearSegmentedColormap")[:from_list]("tmp", pyvals)
# function py_colormap(c::ColorGradient, α=nothing)
# pyvals = [(v, py_color(getColorZ(c, v), α)) for v in c.values]
# pycolors.pymember("LinearSegmentedColormap")[:from_list]("tmp", pyvals)
# end
# # convert vectors and ColorVectors to standard ColorGradients
# # TODO: move this logic to colors.jl and keep a barebones wrapper for pyplot
# py_colormap(cv::ColorVector, α=nothing) = py_colormap(ColorGradient(cv.v), α)
# py_colormap(v::AVec, α=nothing) = py_colormap(ColorGradient(v), α)
# # anything else just gets a bluesred gradient
# py_colormap(c, α=nothing) = py_colormap(default_gradient(), α)
py_color(c::Colorant) = (red(c), green(c), blue(c), alpha(c))
py_color(cs::AVec) = map(py_color, cs)
py_color(grad::ColorGradient) = py_color(grad.colors)
function py_colormap(grad::ColorGradient)
pyvals = [(z, py_color(grad[z])) for z in grad.values]
pycolors.LinearSegmentedColormap[:from_list]("tmp", pyvals)
end
py_colormap(c) = py_colormap(cgrad())
# convert vectors and ColorVectors to standard ColorGradients
# TODO: move this logic to colors.jl and keep a barebones wrapper for pyplot
py_colormap(cv::ColorVector, α=nothing) = py_colormap(ColorGradient(cv.v), α)
py_colormap(v::AVec, α=nothing) = py_colormap(ColorGradient(v), α)
# anything else just gets a bluesred gradient
py_colormap(c, α=nothing) = py_colormap(default_gradient(), α)
function py_shading(c, z, α=nothing)
cmap = py_colormap(c, α)
@@ -130,27 +144,27 @@ const _path_MOVETO = UInt8(1)
const _path_LINETO = UInt8(2)
const _path_CLOSEPOLY = UInt8(79)
# see http://matplotlib.org/users/path_tutorial.html
# and http://matplotlib.org/api/path_api.html#matplotlib.path.Path
function py_path(x, y)
n = length(x)
mat = zeros(n+1, 2)
codes = zeros(UInt8, n+1)
lastnan = true
for i=1:n
mat[i,1] = x[i]
mat[i,2] = y[i]
nan = !ok(x[i], y[i])
codes[i] = if nan && i>1
_path_CLOSEPOLY
else
lastnan ? _path_MOVETO : _path_LINETO
end
lastnan = nan
end
codes[n+1] = _path_CLOSEPOLY
pypath.pymember("Path")(mat, codes)
end
# # see http://matplotlib.org/users/path_tutorial.html
# # and http://matplotlib.org/api/path_api.html#matplotlib.path.Path
# function py_path(x, y)
# n = length(x)
# mat = zeros(n+1, 2)
# codes = zeros(UInt8, n+1)
# lastnan = true
# for i=1:n
# mat[i,1] = x[i]
# mat[i,2] = y[i]
# nan = !ok(x[i], y[i])
# codes[i] = if nan && i>1
# _path_CLOSEPOLY
# else
# lastnan ? _path_MOVETO : _path_LINETO
# end
# lastnan = nan
# end
# codes[n+1] = _path_CLOSEPOLY
# pypath.pymember("Path")(mat, codes)
# end
# get the marker shape
function py_marker(marker::Symbol)
@@ -160,8 +174,8 @@ function py_marker(marker::Symbol)
marker == :diamond && return "D"
marker == :utriangle && return "^"
marker == :dtriangle && return "v"
marker == :cross && return "+"
marker == :xcross && return "x"
# marker == :cross && return "+"
# marker == :xcross && return "x"
marker == :star5 && return "*"
marker == :pentagon && return "p"
marker == :hexagon && return "h"
@@ -207,29 +221,29 @@ function add_pyfixedformatter(cbar, vals::AVec)
cbar[:update_ticks]()
end
# # TODO: smoothing should be moved into the SliceIt method, should not touch backends
# function handleSmooth(plt::Plot{PyPlotBackend}, ax, d::KW, smooth::Bool)
# if smooth
# xs, ys = regressionXY(d[:x], d[:y])
# ax[:plot](xs, ys,
# # linestyle = py_linestyle(:path, :dashdot),
# color = py_color(d[:linecolor]),
# linewidth = 2
# )
# end
# end
# handleSmooth(plt::Plot{PyPlotBackend}, ax, d::KW, smooth::Real) = handleSmooth(plt, ax, d, true)
function labelfunc(scale::Symbol, backend::PyPlotBackend)
if scale == :log10
x -> latexstring("10^{$x}")
elseif scale == :log2
x -> latexstring("2^{$x}")
elseif scale == :ln
x -> latexstring("e^{$x}")
else
string
end
end
# ---------------------------------------------------------------------------
function fix_xy_lengths!(plt::Plot{PyPlotBackend}, d::KW)
x, y = d[:x], d[:y]
function fix_xy_lengths!(plt::Plot{PyPlotBackend}, series::Series)
x, y = series[:x], series[:y]
nx, ny = length(x), length(y)
if !isa(get(d, :z, nothing), Surface) && nx != ny
if !isa(get(series.d, :z, nothing), Surface) && nx != ny
if nx < ny
d[:x] = Float64[x[mod1(i,nx)] for i=1:ny]
series[:x] = Float64[x[mod1(i,nx)] for i=1:ny]
else
d[:y] = Float64[y[mod1(i,ny)] for i=1:nx]
series[:y] = Float64[y[mod1(i,ny)] for i=1:nx]
end
end
end
@@ -245,14 +259,14 @@ function py_color_fix(c, x)
end
end
py_linecolor(d::KW) = py_color(d[:linecolor], d[:linealpha])
py_markercolor(d::KW) = py_color(d[:markercolor], d[:markeralpha])
py_markerstrokecolor(d::KW) = py_color(d[:markerstrokecolor], d[:markerstrokealpha])
py_fillcolor(d::KW) = py_color(d[:fillcolor], d[:fillalpha])
py_linecolor(series::Series) = py_color(series[:linecolor])
py_markercolor(series::Series) = py_color(series[:markercolor])
py_markerstrokecolor(series::Series) = py_color(series[:markerstrokecolor])
py_fillcolor(series::Series) = py_color(series[:fillcolor])
py_linecolormap(d::KW) = py_colormap(d[:linecolor], d[:linealpha])
py_markercolormap(d::KW) = py_colormap(d[:markercolor], d[:markeralpha])
py_fillcolormap(d::KW) = py_colormap(d[:fillcolor], d[:fillalpha])
py_linecolormap(series::Series) = py_colormap(series[:linecolor])
py_markercolormap(series::Series) = py_colormap(series[:markercolor])
py_fillcolormap(series::Series) = py_colormap(series[:fillcolor])
# ---------------------------------------------------------------------------
@@ -379,9 +393,9 @@ end
# function _series_added(plt::Plot{PyPlotBackend}, series::Series)
function py_add_series(plt::Plot{PyPlotBackend}, series::Series)
d = series.d
st = d[:seriestype]
sp = d[:subplot]
# d = series.d
st = series[:seriestype]
sp = series[:subplot]
ax = sp.o
if !(st in supported_types(plt.backend))
@@ -389,10 +403,10 @@ function py_add_series(plt::Plot{PyPlotBackend}, series::Series)
end
# PyPlot doesn't handle mismatched x/y
fix_xy_lengths!(plt, d)
fix_xy_lengths!(plt, series)
# ax = getAxis(plt, series)
x, y, z = d[:x], d[:y], d[:z]
x, y, z = series[:x], series[:y], series[:z]
xyargs = (st in _3dTypes ? (x,y,z) : (x,y))
# handle zcolor and get c/cmap
@@ -405,7 +419,7 @@ function py_add_series(plt::Plot{PyPlotBackend}, series::Series)
# pass in an integer value as an arg, but a levels list as a keyword arg
levels = d[:levels]
levels = series[:levels]
levelargs = if isscalar(levels)
(levels)
elseif isvector(levels)
@@ -419,14 +433,14 @@ function py_add_series(plt::Plot{PyPlotBackend}, series::Series)
# line plot
if st in (:path, :path3d, :steppre, :steppost)
if d[:linewidth] > 0
if d[:line_z] == nothing
if series[:linewidth] > 0
if series[:line_z] == nothing
handle = ax[:plot](xyargs...;
label = d[:label],
zorder = d[:series_plotindex],
color = py_linecolor(d),
linewidth = py_dpi_scale(plt, d[:linewidth]),
linestyle = py_linestyle(st, d[:linestyle]),
label = series[:label],
zorder = series[:series_plotindex],
color = py_linecolor(series),
linewidth = py_dpi_scale(plt, series[:linewidth]),
linestyle = py_linestyle(st, series[:linestyle]),
solid_capstyle = "round",
drawstyle = py_stepstyle(st)
)[1]
@@ -435,28 +449,38 @@ function py_add_series(plt::Plot{PyPlotBackend}, series::Series)
else
# multicolored line segments
n = length(x) - 1
segments = Array(Any,n)
# segments = Array(Any,n)
segments = []
kw = KW(
:label => d[:label],
:label => series[:label],
:zorder => plt.n,
:cmap => py_linecolormap(d),
:linewidth => py_dpi_scale(plt, d[:linewidth]),
:linestyle => py_linestyle(st, d[:linestyle])
:cmap => py_linecolormap(series),
:linewidth => py_dpi_scale(plt, series[:linewidth]),
:linestyle => py_linestyle(st, series[:linestyle])
)
lz = collect(series[:line_z])
handle = if is3d(st)
for i=1:n
segments[i] = [(cycle(x,i), cycle(y,i), cycle(z,i)), (cycle(x,i+1), cycle(y,i+1), cycle(z,i+1))]
for rng in iter_segments(x, y, z)
length(rng) < 2 && continue
push!(segments, [(cycle(x,i),cycle(y,i),cycle(z,i)) for i in rng])
end
# for i=1:n
# segments[i] = [(cycle(x,i), cycle(y,i), cycle(z,i)), (cycle(x,i+1), cycle(y,i+1), cycle(z,i+1))]
# end
lc = pyart3d.Line3DCollection(segments; kw...)
lc[:set_array](d[:line_z])
lc[:set_array](lz)
ax[:add_collection3d](lc, zs=z) #, zdir='y')
lc
else
for i=1:n
segments[i] = [(cycle(x,i), cycle(y,i)), (cycle(x,i+1), cycle(y,i+1))]
for rng in iter_segments(x, y)
length(rng) < 2 && continue
push!(segments, [(cycle(x,i),cycle(y,i)) for i in rng])
end
# for i=1:n
# segments[i] = [(cycle(x,i), cycle(y,i)), (cycle(x,i+1), cycle(y,i+1))]
# end
lc = pycollections.LineCollection(segments; kw...)
lc[:set_array](d[:line_z])
lc[:set_array](lz)
ax[:add_collection](lc)
lc
end
@@ -464,7 +488,7 @@ function py_add_series(plt::Plot{PyPlotBackend}, series::Series)
needs_colorbar = true
end
a = d[:arrow]
a = series[:arrow]
if a != nothing && !is3d(st) # TODO: handle 3d later
if typeof(a) != Arrow
warn("Unexpected type for arrow: $(typeof(a))")
@@ -473,10 +497,10 @@ function py_add_series(plt::Plot{PyPlotBackend}, series::Series)
:arrowstyle => "simple,head_length=$(a.headlength),head_width=$(a.headwidth)",
:shrinkA => 0,
:shrinkB => 0,
:edgecolor => py_linecolor(d),
:facecolor => py_linecolor(d),
:linewidth => py_dpi_scale(plt, d[:linewidth]),
:linestyle => py_linestyle(st, d[:linestyle]),
:edgecolor => py_linecolor(series),
:facecolor => py_linecolor(series),
:linewidth => py_dpi_scale(plt, series[:linewidth]),
:linestyle => py_linestyle(st, series[:linestyle]),
)
add_arrows(x, y) do xyprev, xy
ax[:annotate]("",
@@ -491,53 +515,16 @@ function py_add_series(plt::Plot{PyPlotBackend}, series::Series)
end
end
# if st == :bar
# bw = d[:bar_width]
# if bw == nothing
# bw = mean(diff(isvertical(d) ? x : y))
# end
# extrakw[isvertical(d) ? :width : :height] = bw
# fr = get(d, :fillrange, nothing)
# if fr != nothing
# extrakw[:bottom] = fr
# d[:fillrange] = nothing
# end
# handle = ax[isvertical(d) ? :bar : :barh](x, y;
# label = d[:label],
# zorder = d[:series_plotindex],
# color = py_fillcolor(d),
# edgecolor = py_linecolor(d),
# linewidth = d[:linewidth],
# align = d[:bar_edges] ? "edge" : "center",
# extrakw...
# )[1]
# push!(handles, handle)
# end
# if st == :sticks
# extrakw[isvertical(d) ? :width : :height] = 0.0
# handle = ax[isvertical(d) ? :bar : :barh](x, y;
# label = d[:label],
# zorder = d[:series_plotindex],
# color = py_linecolor(d),
# edgecolor = py_linecolor(d),
# linewidth = d[:linewidth],
# align = "center",
# extrakw...
# )[1]
# push!(handles, handle)
# end
# add markers?
if d[:markershape] != :none && st in (:path, :scatter, :path3d,
if series[:markershape] != :none && st in (:path, :scatter, :path3d,
:scatter3d, :steppre, :steppost,
:bar)
extrakw = KW()
if d[:marker_z] == nothing
extrakw[:c] = py_color_fix(py_markercolor(d), x)
if series[:marker_z] == nothing
extrakw[:c] = py_color_fix(py_markercolor(series), x)
else
extrakw[:c] = convert(Vector{Float64}, d[:marker_z])
extrakw[:cmap] = py_markercolormap(d)
extrakw[:c] = convert(Vector{Float64}, series[:marker_z])
extrakw[:cmap] = py_markercolormap(series)
clims = sp[:clims]
if is_2tuple(clims)
isfinite(clims[1]) && (extrakw[:vmin] = clims[1])
@@ -545,72 +532,23 @@ function py_add_series(plt::Plot{PyPlotBackend}, series::Series)
end
needs_colorbar = true
end
xyargs = if st == :bar && !isvertical(d)
xyargs = if st == :bar && !isvertical(series)
(y, x)
else
xyargs
end
handle = ax[:scatter](xyargs...;
label = d[:label],
zorder = d[:series_plotindex] + 0.5,
marker = py_marker(d[:markershape]),
s = py_dpi_scale(plt, d[:markersize] .^ 2),
edgecolors = py_markerstrokecolor(d),
linewidths = py_dpi_scale(plt, d[:markerstrokewidth]),
label = series[:label],
zorder = series[:series_plotindex] + 0.5,
marker = py_marker(series[:markershape]),
s = py_dpi_scale(plt, series[:markersize] .^ 2),
edgecolors = py_markerstrokecolor(series),
linewidths = py_dpi_scale(plt, series[:markerstrokewidth]),
extrakw...
)
push!(handles, handle)
end
# if st == :histogram
# handle = ax[:hist](y;
# label = d[:label],
# zorder = d[:series_plotindex],
# color = py_fillcolor(d),
# edgecolor = py_linecolor(d),
# linewidth = d[:linewidth],
# bins = d[:bins],
# normed = d[:normalize],
# weights = d[:weights],
# orientation = (isvertical(d) ? "vertical" : "horizontal"),
# histtype = (d[:bar_position] == :stack ? "barstacked" : "bar")
# )[3]
# push!(handles, handle)
# # expand the extrema... handle is a list of Rectangle objects
# for rect in handle
# xmin, ymin, xmax, ymax = rect[:get_bbox]()[:extents]
# expand_extrema!(sp, xmin, xmax, ymin, ymax)
# # expand_extrema!(sp[:xaxis], (xmin, xmax))
# # expand_extrema!(sp[:yaxis], (ymin, ymax))
# end
# end
# if st == :histogram2d
# clims = sp[:clims]
# if is_2tuple(clims)
# isfinite(clims[1]) && (extrakw[:vmin] = clims[1])
# isfinite(clims[2]) && (extrakw[:vmax] = clims[2])
# end
# handle = ax[:hist2d](x, y;
# label = d[:label],
# zorder = d[:series_plotindex],
# bins = d[:bins],
# normed = d[:normalize],
# weights = d[:weights],
# cmap = py_fillcolormap(d), # applies to the pcolorfast object
# extrakw...
# )[4]
# push!(handles, handle)
# needs_colorbar = true
# # expand the extrema... handle is a AxesImage object
# expand_extrema!(sp, handle[:get_extent]()...)
# # xmin, xmax, ymin, ymax = handle[:get_extent]()
# # expand_extrema!(sp[:xaxis], (xmin, xmax))
# # expand_extrema!(sp[:yaxis], (ymin, ymax))
# end
if st == :hexbin
clims = sp[:clims]
if is_2tuple(clims)
@@ -618,33 +556,20 @@ function py_add_series(plt::Plot{PyPlotBackend}, series::Series)
isfinite(clims[2]) && (extrakw[:vmax] = clims[2])
end
handle = ax[:hexbin](x, y;
label = d[:label],
zorder = d[:series_plotindex],
gridsize = d[:bins],
linewidths = py_dpi_scale(plt, d[:linewidth]),
edgecolors = py_linecolor(d),
cmap = py_fillcolormap(d), # applies to the pcolorfast object
label = series[:label],
zorder = series[:series_plotindex],
gridsize = series[:bins],
linewidths = py_dpi_scale(plt, series[:linewidth]),
edgecolors = py_linecolor(series),
cmap = py_fillcolormap(series), # applies to the pcolorfast object
extrakw...
)
push!(handles, handle)
needs_colorbar = true
end
# if st in (:hline,:vline)
# for yi in d[:y]
# func = ax[st == :hline ? :axhline : :axvline]
# handle = func(yi;
# linewidth=d[:linewidth],
# color=py_linecolor(d),
# linestyle=py_linestyle(st, d[:linestyle])
# )
# push!(handles, handle)
# end
# end
if st in (:contour, :contour3d)
# z = z.surf'
z = transpose_z(d, z.surf)
z = transpose_z(series, z.surf)
needs_colorbar = true
clims = sp[:clims]
@@ -659,21 +584,21 @@ function py_add_series(plt::Plot{PyPlotBackend}, series::Series)
# contour lines
handle = ax[:contour](x, y, z, levelargs...;
label = d[:label],
zorder = d[:series_plotindex],
linewidths = py_dpi_scale(plt, d[:linewidth]),
linestyles = py_linestyle(st, d[:linestyle]),
cmap = py_linecolormap(d),
label = series[:label],
zorder = series[:series_plotindex],
linewidths = py_dpi_scale(plt, series[:linewidth]),
linestyles = py_linestyle(st, series[:linestyle]),
cmap = py_linecolormap(series),
extrakw...
)
push!(handles, handle)
# contour fills
if d[:fillrange] != nothing
if series[:fillrange] != nothing
handle = ax[:contourf](x, y, z, levelargs...;
label = d[:label],
zorder = d[:series_plotindex] + 0.5,
cmap = py_fillcolormap(d),
label = series[:label],
zorder = series[:series_plotindex] + 0.5,
cmap = py_fillcolormap(series),
extrakw...
)
push!(handles, handle)
@@ -685,13 +610,13 @@ function py_add_series(plt::Plot{PyPlotBackend}, series::Series)
x, y, z = map(Array, (x,y,z))
if !ismatrix(x) || !ismatrix(y)
x = repmat(x', length(y), 1)
y = repmat(y, 1, length(d[:x]))
y = repmat(y, 1, length(series[:x]))
end
# z = z'
z = transpose_z(d, z)
z = transpose_z(series, z)
if st == :surface
if d[:marker_z] != nothing
extrakw[:facecolors] = py_shading(d[:fillcolor], d[:marker_z], d[:fillalpha])
if series[:marker_z] != nothing
extrakw[:facecolors] = py_shading(series[:fillcolor], series[:marker_z], series[:fillalpha])
extrakw[:shade] = false
clims = sp[:clims]
if is_2tuple(clims)
@@ -699,28 +624,28 @@ function py_add_series(plt::Plot{PyPlotBackend}, series::Series)
isfinite(clims[2]) && (extrakw[:vmax] = clims[2])
end
else
extrakw[:cmap] = py_fillcolormap(d)
extrakw[:cmap] = py_fillcolormap(series)
needs_colorbar = true
end
end
handle = ax[st == :surface ? :plot_surface : :plot_wireframe](x, y, z;
label = d[:label],
zorder = d[:series_plotindex],
label = series[:label],
zorder = series[:series_plotindex],
rstride = 1,
cstride = 1,
linewidth = py_dpi_scale(plt, d[:linewidth]),
edgecolor = py_linecolor(d),
linewidth = py_dpi_scale(plt, series[:linewidth]),
edgecolor = py_linecolor(series),
extrakw...
)
push!(handles, handle)
# contours on the axis planes
if d[:contours]
if series[:contours]
for (zdir,mat) in (("x",x), ("y",y), ("z",z))
offset = (zdir == "y" ? maximum : minimum)(mat)
handle = ax[:contourf](x, y, z, levelargs...;
zdir = zdir,
cmap = py_fillcolormap(d),
cmap = py_fillcolormap(series),
offset = (zdir == "y" ? maximum : minimum)(mat) # where to draw the contour plane
)
push!(handles, handle)
@@ -741,11 +666,11 @@ function py_add_series(plt::Plot{PyPlotBackend}, series::Series)
isfinite(clims[2]) && (extrakw[:vmax] = clims[2])
end
handle = ax[:plot_trisurf](x, y, z;
label = d[:label],
zorder = d[:series_plotindex],
cmap = py_fillcolormap(d),
linewidth = py_dpi_scale(plt, d[:linewidth]),
edgecolor = py_linecolor(d),
label = series[:label],
zorder = series[:series_plotindex],
cmap = py_fillcolormap(series),
linewidth = py_dpi_scale(plt, series[:linewidth]),
edgecolor = py_linecolor(series),
extrakw...
)
push!(handles, handle)
@@ -757,7 +682,7 @@ function py_add_series(plt::Plot{PyPlotBackend}, series::Series)
if st == :image
# @show typeof(z)
img = Array(transpose_z(d, z.surf))
img = Array(transpose_z(series, z.surf))
z = if eltype(img) <: Colors.AbstractGray
float(img)
elseif eltype(img) <: Colorant
@@ -766,7 +691,7 @@ function py_add_series(plt::Plot{PyPlotBackend}, series::Series)
z # hopefully it's in a data format that will "just work" with imshow
end
handle = ax[:imshow](z;
zorder = d[:series_plotindex],
zorder = series[:series_plotindex],
cmap = py_colormap([:black, :white]),
vmin = 0.0,
vmax = 1.0
@@ -776,11 +701,11 @@ function py_add_series(plt::Plot{PyPlotBackend}, series::Series)
# expand extrema... handle is AxesImage object
xmin, xmax, ymax, ymin = handle[:get_extent]()
expand_extrema!(sp, xmin, xmax, ymin, ymax)
# sp[:yaxis].d[:flip] = true
# sp[:yaxis].series[:flip] = true
end
if st == :heatmap
x, y, z = heatmap_edges(x), heatmap_edges(y), transpose_z(d, z.surf)
x, y, z = heatmap_edges(x), heatmap_edges(y), transpose_z(series, z.surf)
# if !(eltype(z) <: Number)
# z, discrete_colorbar_values = indices_and_unique_values(z)
# end
@@ -796,10 +721,10 @@ function py_add_series(plt::Plot{PyPlotBackend}, series::Series)
end
handle = ax[:pcolormesh](x, y, z;
label = d[:label],
zorder = d[:series_plotindex],
cmap = py_fillcolormap(d),
edgecolors = (d[:linewidth] > 0 ? py_linecolor(d) : "face"),
label = series[:label],
zorder = series[:series_plotindex],
cmap = py_fillcolormap(series),
edgecolors = (series[:linewidth] > 0 ? py_linecolor(series) : "face"),
extrakw...
)
push!(handles, handle)
@@ -817,16 +742,32 @@ function py_add_series(plt::Plot{PyPlotBackend}, series::Series)
end
if st == :shape
path = py_path(x, y)
patches = pypatches.pymember("PathPatch")(path;
label = d[:label],
zorder = d[:series_plotindex],
edgecolor = py_linecolor(d),
facecolor = py_fillcolor(d),
linewidth = py_dpi_scale(plt, d[:linewidth]),
fill = true
)
handle = ax[:add_patch](patches)
handle = []
for (i,rng) in enumerate(iter_segments(x, y))
if length(rng) > 1
path = pypath.pymember("Path")(hcat(x[rng], y[rng]))
patches = pypatches.pymember("PathPatch")(
path;
label = series[:label],
zorder = series[:series_plotindex],
edgecolor = py_color(cycle(series[:linecolor], i)),
facecolor = py_color(cycle(series[:fillcolor], i)),
linewidth = py_dpi_scale(plt, series[:linewidth]),
fill = true
)
push!(handle, ax[:add_patch](patches))
end
end
# path = py_path(x, y)
# patches = pypatches.pymember("PathPatch")(path;
# label = series[:label],
# zorder = series[:series_plotindex],
# edgecolor = py_linecolor(series),
# facecolor = py_fillcolor(series),
# linewidth = py_dpi_scale(plt, series[:linewidth]),
# fill = true
# )
# handle = ax[:add_patch](patches)
push!(handles, handle)
end
@@ -845,10 +786,10 @@ function py_add_series(plt::Plot{PyPlotBackend}, series::Series)
expand_extrema!(sp, -lim, lim, -lim, lim)
end
d[:serieshandle] = handles
series[:serieshandle] = handles
# # smoothing
# handleSmooth(plt, ax, d, d[:smooth])
# handleSmooth(plt, ax, series, series[:smooth])
# add the colorbar legend
if needs_colorbar && sp[:colorbar] != :none
@@ -873,55 +814,29 @@ function py_add_series(plt::Plot{PyPlotBackend}, series::Series)
end
# handle area filling
fillrange = d[:fillrange]
fillrange = series[:fillrange]
if fillrange != nothing && st != :contour
f, dim1, dim2 = if isvertical(d)
f, dim1, dim2 = if isvertical(series)
:fill_between, x, y
else
:fill_betweenx, y, x
end
n = length(dim1)
args = if typeof(fillrange) <: Union{Real, AVec}
dim1, fillrange, dim2
else
dim1, fillrange...
dim1, expand_data(fillrange, n), dim2
elseif is_2tuple(fillrange)
dim1, expand_data(fillrange[1], n), expand_data(fillrange[2], n)
end
handle = ax[f](args...;
zorder = d[:series_plotindex],
facecolor = py_fillcolor(d),
zorder = series[:series_plotindex],
facecolor = py_fillcolor(series),
linewidths = 0
)
push!(handles, handle)
end
end
# --------------------------------------------------------------------------
# function update_limits!(sp::Subplot{PyPlotBackend}, series::Series, letters)
# for letter in letters
# py_set_lims(sp.o, sp[Symbol(letter, :axis)])
# end
# end
# function _series_updated(plt::Plot{PyPlotBackend}, series::Series)
# d = series.d
# for handle in get(d, :serieshandle, [])
# if is3d(series)
# handle[:set_data](d[:x], d[:y])
# handle[:set_3d_properties](d[:z])
# else
# try
# handle[:set_data](d[:x], d[:y])
# catch
# handle[:set_offsets](hcat(d[:x], d[:y]))
# end
# end
# end
# update_limits!(d[:subplot], series, is3d(series) ? (:x,:y,:z) : (:x,:y))
# end
# --------------------------------------------------------------------------
function py_set_lims(ax, axis::Axis)
@@ -933,7 +848,7 @@ end
function py_set_ticks(ax, ticks, letter)
ticks == :auto && return
axis = ax[Symbol(letter,"axis")]
if ticks == :none || ticks == nothing
if ticks == :none || ticks == nothing || ticks == false
kw = KW()
for dir in (:top,:bottom,:left,:right)
kw[dir] = kw[Symbol(:label,dir)] = "off"
@@ -1016,7 +931,6 @@ function _before_layout_calcs(plt::Plot{PyPlotBackend})
w, h = plt[:size]
fig = plt.o
fig[:clear]()
# fig[:set_size_inches](px2inch(w), px2inch(h), forward = true)
dpi = plt[:dpi]
fig[:set_size_inches](w/dpi, h/dpi, forward = true)
fig[:set_facecolor](py_color(plt[:background_color_outside]))
@@ -1114,10 +1028,10 @@ function _update_min_padding!(sp::Subplot{PyPlotBackend})
# TODO: this should initialize to the margin from sp.attr
# figure out how much the axis components and title "stick out" from the plot area
# leftpad = toppad = rightpad = bottompad = 1mm
leftpad = sp[:left_margin]
toppad = sp[:top_margin]
rightpad = sp[:right_margin]
bottompad = sp[:bottom_margin]
leftpad = 0mm
toppad = 0mm
rightpad = 0mm
bottompad = 0mm
for bb in (py_bbox_axis(ax, "x"), py_bbox_axis(ax, "y"), py_bbox_title(ax))
if ispositive(width(bb)) && ispositive(height(bb))
leftpad = max(leftpad, left(plotbb) - left(bb))
@@ -1134,6 +1048,12 @@ function _update_min_padding!(sp::Subplot{PyPlotBackend})
rightpad = rightpad + sp.attr[:cbar_width]
end
# add in the user-specified margin
leftpad += sp[:left_margin]
toppad += sp[:top_margin]
rightpad += sp[:right_margin]
bottompad += sp[:bottom_margin]
sp.minpad = (leftpad, toppad, rightpad, bottompad)
end
@@ -1162,21 +1082,6 @@ end
# -----------------------------------------------------------------
# function _remove_axis(plt::Plot{PyPlotBackend}, isx::Bool)
# if isx
# plot!(plt, xticks=zeros(0), xlabel="")
# else
# plot!(plt, yticks=zeros(0), ylabel="")
# end
# end
#
# function _expand_limits(lims, plt::Plot{PyPlotBackend}, isx::Bool)
# pltlims = plt.o.ax[isx ? :get_xbound : :get_ybound]()
# _expand_limits(lims, pltlims)
# end
# -----------------------------------------------------------------
const _pyplot_legend_pos = KW(
:right => "right",
:left => "center left",
@@ -1197,12 +1102,15 @@ function py_add_legend(plt::Plot, sp::Subplot, ax)
for series in series_list(sp)
if should_add_to_legend(series)
# add a line/marker and a label
push!(handles, if series.d[:seriestype] == :histogram
PyPlot.plt[:Line2D]((0,1),(0,0), color=py_fillcolor(series.d), linewidth=py_dpi_scale(plt, 4))
push!(handles, if series[:seriestype] == :shape
PyPlot.plt[:Line2D]((0,1),(0,0),
color = py_color(cycle(series[:fillcolor],1)),
linewidth = py_dpi_scale(plt, 4)
)
else
series.d[:serieshandle][1]
series[:serieshandle][1]
end)
push!(labels, series.d[:label])
push!(labels, series[:label])
end
end
+11 -11
View File
@@ -38,7 +38,7 @@ end
# do all the magic here... build it all at once, since we need to know about all the series at the very beginning
function rebuildUnicodePlot!(plt::Plot)
function rebuildUnicodePlot!(plt::Plot, width, height)
plt.o = []
for sp in plt.subplots
@@ -57,7 +57,6 @@ function rebuildUnicodePlot!(plt::Plot)
y = Float64[ylim[1]]
# create a plot window with xlim/ylim set, but the X/Y vectors are outside the bounds
width, height = plt[:size]
canvas_type = isijulia() ? UnicodePlots.AsciiCanvas : UnicodePlots.BrailleCanvas
o = UnicodePlots.Plot(x, y, canvas_type;
width = width,
@@ -143,21 +142,22 @@ end
# we don't do very much for subplots... just stack them vertically
function _update_plot_object(plt::Plot{UnicodePlotsBackend})
w, h = plt[:size]
plt.attr[:size] = div(w, 10), div(h, 20)
plt.attr[:color_palette] = [RGB(0,0,0)]
rebuildUnicodePlot!(plt)
function unicodeplots_rebuild(plt::Plot{UnicodePlotsBackend})
w, h = plt[:size]
plt.attr[:color_palette] = [RGB(0,0,0)]
rebuildUnicodePlot!(plt, div(w, 10), div(h, 20))
end
function _writemime(io::IO, ::MIME"text/plain", plt::Plot{UnicodePlotsBackend})
map(show, plt.o)
nothing
unicodeplots_rebuild(plt)
map(show, plt.o)
nothing
end
function _display(plt::Plot{UnicodePlotsBackend})
map(show, plt.o)
nothing
unicodeplots_rebuild(plt)
map(show, plt.o)
nothing
end
+7 -7
View File
@@ -14,13 +14,13 @@ compute_angle(v::P2) = (angle = atan2(v[2], v[1]); angle < 0 ? 2π - angle : ang
immutable Shape
x::Vector{Float64}
y::Vector{Float64}
function Shape(x::AVec, y::AVec)
if x[1] != x[end] || y[1] != y[end]
new(vcat(x, x[1]), vcat(y, y[1]))
else
new(x, y)
end
end
# function Shape(x::AVec, y::AVec)
# # if x[1] != x[end] || y[1] != y[end]
# # new(vcat(x, x[1]), vcat(y, y[1]))
# # else
# new(x, y)
# end
# end
end
Shape(verts::AVec) = Shape(unzip(verts)...)
+6 -4
View File
@@ -32,6 +32,7 @@ getColor(scheme::ColorScheme) = getColor(scheme, 1)
getColorVector(scheme::ColorScheme) = [getColor(scheme)]
colorscheme(scheme::ColorScheme) = scheme
colorscheme(s::AbstractString; kw...) = colorscheme(Symbol(s); kw...)
colorscheme(s::Symbol; kw...) = haskey(_gradients, s) ? ColorGradient(s; kw...) : ColorWrapper(convertColor(s); kw...)
colorscheme{T<:Real}(s::Symbol, vals::AVec{T}; kw...) = ColorGradient(s, vals; kw...)
colorscheme(cs::AVec, vs::AVec; kw...) = ColorGradient(cs, vs; kw...)
@@ -55,10 +56,10 @@ convertColor(b::Bool) = b ? RGBA(0,0,0,1) : RGBA(0,0,0,0)
function convertColor(c, α::Real)
c = convertColor(c)
RGBA(RGB(c), α)
RGBA(RGB(getColor(c)), α)
end
convertColor(cs::AVec, α::Real) = map(c -> convertColor(c, α), cs)
convertColor(c, α::@compat(Void)) = convertColor(c)
convertColor(c, α::Void) = convertColor(c)
# backup... try to convert
getColor(c) = convertColor(c)
@@ -78,7 +79,7 @@ end
# --------------------------------------------------------------
const _rainbowColors = [colorant"blue", colorant"purple", colorant"green", colorant"orange", colorant"red"]
const _rainbowColors = [colorant"purple", colorant"blue", colorant"green", colorant"orange", colorant"red"]
const _testColors = [colorant"darkblue", colorant"blueviolet", colorant"darkcyan",colorant"green",
darken(colorant"yellow",0.3), colorant"orange", darken(colorant"red",0.2)]
@@ -220,7 +221,7 @@ immutable ColorZFunction <: ColorScheme
f::Function
end
getColorZ(scheme::ColorFunction, z::Real) = scheme.f(z)
getColorZ(scheme::ColorZFunction, z::Real) = scheme.f(z)
# --------------------------------------------------------------
@@ -246,6 +247,7 @@ ColorWrapper(s::Symbol; alpha = nothing) = ColorWrapper(convertColor(parse(Color
getColor(scheme::ColorWrapper, idx::Int) = scheme.c
getColorZ(scheme::ColorWrapper, z::Real) = scheme.c
convertColor(c::ColorWrapper, α::Void) = c.c
# --------------------------------------------------------------
-6
View File
@@ -221,12 +221,6 @@ PlotExample("Contours",
p2 = contour(x, y, Z)
plot(p1, p2)
end)]
# [:(begin
# x = 1:0.3:20
# y = x
# f(x,y) = sin(x)+cos(y)
# contour(x, y, f, fill=true)
# end)]
),
PlotExample("Pie",
+30 -24
View File
@@ -147,14 +147,15 @@ function Base.writemime(io::IO, ::MIME"text/html", plt::Plot)
end
end
function _writemime(io::IO, m, plt::Plot)
warn("_writemime is not defined for this backend. m=", string(m))
end
function _display(plt::Plot)
warn("_display is not defined for this backend.")
end
# for writing to io streams... first prepare, then callback
for mime in keys(_mimeformats)
@eval function _writemime(io::IO, m, plt::Plot)
warn("_writemime is not defined for this backend. m=", string(m))
end
@eval function _display(plt::Plot)
warn("_display is not defined for this backend.")
end
@eval function Base.writemime(io::IO, m::MIME{Symbol($mime)}, plt::Plot)
prepare_output(plt)
_writemime(io, m, plt)
@@ -166,24 +167,22 @@ end
# A backup, if no PNG generation is defined, is to try to make a PDF and use FileIO to convert
if is_installed("FileIO")
@eval begin
import FileIO
function _writemime(io::IO, ::MIME"image/png", plt::Plot)
fn = tempname()
@eval import FileIO
function _writemime(io::IO, ::MIME"image/png", plt::Plot)
fn = tempname()
# first save a pdf file
pdf(plt, fn)
# first save a pdf file
pdf(plt, fn)
# load that pdf into a FileIO Stream
s = FileIO.load(fn * ".pdf")
# load that pdf into a FileIO Stream
s = FileIO.load(fn * ".pdf")
# save a png
pngfn = fn * ".png"
FileIO.save(pngfn, s)
# save a png
pngfn = fn * ".png"
FileIO.save(pngfn, s)
# now write from the file
write(io, readall(open(pngfn)))
end
# now write from the file
write(io, readall(open(pngfn)))
end
end
@@ -248,10 +247,11 @@ function setup_atom()
# @eval import Atom, Media
@eval import Atom
# connects the render function
for T in (GadflyBackend,ImmerseBackend,PyPlotBackend,GRBackend)
Atom.Media.media(Plot{T}, Atom.Media.Plot)
end
# # connects the render function
# for T in (GadflyBackend,ImmerseBackend,PyPlotBackend,GRBackend)
# Atom.Media.media(Plot{T}, Atom.Media.Plot)
# end
Atom.Media.media(Plot, Atom.Media.Graphical)
# Atom.Media.media{T <: Union{GadflyBackend,ImmerseBackend,PyPlotBackend,GRBackend}}(Plot{T}, Atom.Media.Plot)
# Atom.displaysize(::Plot) = (535, 379)
@@ -259,9 +259,15 @@ function setup_atom()
# this is like "display"... sends an html div with the plot to the PlotPane
function Atom.Media.render(pane::Atom.PlotPane, plt::Plot)
@show "here"
Atom.Media.render(pane, Atom.div(Atom.d(), Atom.HTML(stringmime(MIME("text/html"), plt))))
end
# # force text/plain to output to the PlotPane
# function Base.writemime(io::IO, ::MIME"text/plain", plt::Plot)
# # writemime(io::IO, MIME("text/html"), plt)
# Atom.Media.render(pane)
# end
# function Atom.Media.render(pane::Atom.PlotPane, plt::Plot{PlotlyBackend})
# html = Media.render(pane, Atom.div(Atom.d(), Atom.HTML(stringmime(MIME("text/html"), plt))))
+399
View File
@@ -0,0 +1,399 @@
# ------------------------------------------------------------------
# preprocessing
function command_idx(kw_list::AVec{KW}, kw::KW)
Int(kw[:series_plotindex]) - Int(kw_list[1][:series_plotindex]) + 1
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
function _preprocess_args(d::KW, args, still_to_process::Vector{RecipeData})
# 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
# the recipe to be applied
if haskey(d, :group)
args = (extractGroupArgs(d[:group], args...), args...)
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.
if !isempty(args)
append!(still_to_process, _expand_seriestype_array(d, args))
end
# remove subplot and axis args from d... they will be passed through in the kw_list
if !isempty(args)
for (k,v) in d
for defdict in (_subplot_defaults,
_axis_defaults,
_axis_defaults_byletter)
if haskey(defdict, k)
delete!(d, k)
end
end
end
end
args
end
# ------------------------------------------------------------------
# user recipes
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
# 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.
# 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
# for processing.
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)
next_series = shift!(still_to_process)
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
if !(typeof(recipedata) <: RecipeData)
error("Inputs couldn't be processed... expected RecipeData but got: $recipedata")
end
if isempty(recipedata.args)
_process_userrecipe(plt, kw_list, recipedata)
else
# args are non-empty, so there's still processing to do... add it back to the queue
push!(still_to_process, recipedata)
end
end
end
# don't allow something else to handle it
d[:smooth] = false
kw_list
end
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
# process... finish up and add to the kw_list
kw = recipedata.d
_preprocess_userrecipe(kw)
warnOnUnsupported_scales(plt.backend, kw)
# add the plot index
plt.n += 1
kw[:series_plotindex] = plt.n
push!(kw_list, kw)
_add_errorbar_kw(kw_list, kw)
_add_smooth_kw(kw_list, kw)
return
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
# ------------------------------------------------------------------
# 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::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)
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
catch err
if isa(err, MethodError)
push!(kw_list, kw)
else
rethrow()
end
end
return
end
# ------------------------------------------------------------------
# setup plot and subplot
function _plot_setup(plt::Plot, d::KW, kw_list::Vector{KW})
# 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
_update_plot_args(plt, d)
if !plt.init
plt.o = _create_backend_figure(plt)
# create the layout and subplots from the inputs
plt.layout, plt.subplots, plt.spmap = build_layout(plt.attr)
for (idx,sp) in enumerate(plt.subplots)
sp.plt = plt
sp.attr[:subplot_index] = idx
end
plt.init = true
end
# handle inset subplots
insets = plt[:inset_subplots]
if insets != nothing
if !(typeof(insets) <: AVec)
insets = [insets]
end
for inset in insets
parent, bb = is_2tuple(inset) ? inset : (nothing, inset)
P = typeof(parent)
if P <: Integer
parent = plt.subplots[parent]
elseif P == Symbol
parent = plt.spmap[parent]
else
parent = plt.layout
end
sp = Subplot(backend(), parent=parent)
sp.plt = plt
sp.attr[:relative_bbox] = bb
sp.attr[:subplot_index] = length(plt.subplots)
push!(plt.subplots, sp)
push!(plt.inset_subplots, sp)
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.
# 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}()
for kw in kw_list
# get the Subplot object to which the series belongs.
sps = get(kw, :subplot, :auto)
sp = get_subplot(plt, cycle(sps == :auto ? plt.subplots : plt.subplots[sps], command_idx(kw_list,kw)))
kw[:subplot] = sp
# extract subplot/axis attributes from kw and add to sp_attr
attr = KW()
for (k,v) in kw
if haskey(_subplot_defaults, k) || haskey(_axis_defaults_byletter, k)
attr[k] = pop!(kw, k)
end
if haskey(_axis_defaults, k)
v = pop!(kw, k)
for letter in (:x,:y,:z)
attr[Symbol(letter,k)] = v
end
end
end
sp_attrs[sp] = attr
end
# 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, false)
end
# do we need to link any axes together?
link_axes!(plt.layout, plt[:link])
end
# getting ready to add the series... last update to subplot from anything
# that might have been added during series recipes
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, true)
st = _override_seriestype_check(d, st)
# 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
end
# ------------------------------------------------------------------
# series types
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)
# 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
# natively by the backend
function _process_seriesrecipe(plt::Plot, d::KW)
# replace seriestype aliases
st = Symbol(d[:seriestype])
st = d[:seriestype] = get(_typeAliases, st, st)
# shapes shouldn't have fillrange set
if d[:seriestype] == :shape
d[:fillrange] = nothing
end
# if it's natively supported, finalize processing and pass along to the backend, otherwise recurse
if st in supported_types()
sp = _prepare_subplot(plt, d)
_prepare_annotations(sp, d)
_expand_subplot_extrema(sp, d, st)
_add_the_series(plt, d)
else
# get a sub list of series for this seriestype
datalist = RecipesBase.apply_recipe(d, Val{st}, d[:x], d[:y], d[:z])
# assuming there was no error, recursively apply the series recipes
for data in datalist
if isa(data, RecipeData)
_process_seriesrecipe(plt, data.d)
else
warn("Unhandled recipe: $(data)")
break
end
end
end
nothing
end
+15 -356
View File
@@ -49,7 +49,7 @@ function plot(args...; kw...)
# create an empty Plot then process
plt = Plot()
# plt.user_attr = d
_plot!(plt, d, args...)
_plot!(plt, d, args)
end
# build a new plot from existing plots
@@ -105,22 +105,11 @@ function plot(plt1::Plot, plts_tail::Plot...; kw...)
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
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
# # 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?
link_axes!(plt.layout, plt[:link])
@@ -150,244 +139,23 @@ function plot!(plt::Plot, args...; kw...)
d = KW(kw)
preprocessArgs!(d)
# merge!(plt.user_attr, d)
_plot!(plt, d, args...)
end
function strip_first_letter(s::Symbol)
str = string(s)
str[1:1], Symbol(str[2:end])
end
# this method recursively applies series recipes when the seriestype is not supported
# natively by the backend
function _apply_series_recipe(plt::Plot, d::KW)
# replace seriestype aliases
st = d[:seriestype]
st = d[:seriestype] = get(_typeAliases, st, st)
# if it's natively supported, finalize processing and pass along to the backend, otherwise recurse
if st in supported_types()
# getting ready to add the series... last update to subplot from anything
# that might have been added during series recipes
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
# 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
# get a sub list of series for this seriestype
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
for data in datalist
if isa(data, RecipeData)
_apply_series_recipe(plt, data.d)
else
warn("Unhandled recipe: $(data)")
break
end
end
end
end
function command_idx(kw_list::AVec{KW}, kw::KW)
kw[:series_plotindex] - kw_list[1][:series_plotindex] + 1
_plot!(plt, d, args)
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...)
d[:plot_object] = plt
# 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
# the recipe to be applied
if haskey(d, :group)
args = (extractGroupArgs(d[:group], args...), args...)
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[]
still_to_process = if isempty(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
# remove subplot and axis args from d... they will be passed through in the kw_list
if !isempty(args)
for (k,v) in d
for defdict in (_subplot_defaults,
_axis_defaults,
_axis_defaults_byletter)
if haskey(defdict, k)
delete!(d, k)
end
end
end
end
function _plot!(plt::Plot, d::KW, args::Tuple)
# d[:plot_object] = plt
# --------------------------------
# "USER RECIPES"
# --------------------------------
# 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.
# 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, and the rest go into the queue
# for processing.
while !isempty(still_to_process)
kw_list = _process_userrecipes(plt, d, args)
# grab the first in line to be processed and pass it through apply_recipe
# to generate a list of RecipeData objects (data + attributes)
next_series = shift!(still_to_process)
for recipedata in RecipesBase.apply_recipe(next_series.d, next_series.args...)
# 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)
error("Inputs couldn't be processed... expected RecipeData but got: $recipedata")
end
if isempty(recipedata.args)
# 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
_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
# args are non-empty, so there's still processing to do... add it back to the queue
push!(still_to_process, recipedata)
end
end
end
# don't allow something else to handle it
d[:smooth] = false
# --------------------------------
# "PLOT RECIPES"
@@ -400,117 +168,15 @@ function _plot!(plt::Plot, d::KW, args...)
still_to_process = kw_list
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)
if !isa(get(next_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, next_kw)
continue
end
try
st = next_kw[:seriestype]
st = next_kw[:seriestype] = get(_typeAliases, st, st)
datalist = RecipesBase.apply_recipe(next_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
catch err
if isa(err, MethodError)
push!(kw_list, next_kw)
else
rethrow()
end
end
_process_plotrecipe(plt, next_kw, kw_list, still_to_process)
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
_update_plot_args(plt, d)
if !plt.init
plt.o = _create_backend_figure(plt)
# create the layout and subplots from the inputs
plt.layout, plt.subplots, plt.spmap = build_layout(plt.attr)
for (idx,sp) in enumerate(plt.subplots)
sp.plt = plt
sp.attr[:subplot_index] = idx
end
plt.init = true
end
# handle inset subplots
insets = plt[:inset_subplots]
if insets != nothing
if !(typeof(insets) <: AVec)
insets = [insets]
end
for inset in insets
parent, bb = is_2tuple(inset) ? inset : (nothing, inset)
P = typeof(parent)
if P <: Integer
parent = plt.subplots[parent]
elseif P == Symbol
parent = plt.spmap[parent]
else
parent = plt.layout
end
sp = Subplot(backend(), parent=parent)
sp.plt = plt
sp.attr[:relative_bbox] = bb
sp.attr[:subplot_index] = length(plt.subplots)
push!(plt.subplots, sp)
push!(plt.inset_subplots, sp)
end
end
# 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 object which they belong to.
# TODO: allow matrices to still apply to all subplots
sp_attrs = Dict{Subplot,Any}()
for kw in kw_list
# get the Subplot object to which the series belongs.
sps = get(kw, :subplot, :auto)
sp = get_subplot(plt, cycle(sps == :auto ? plt.subplots : plt.subplots[sps], command_idx(kw_list,kw)))
kw[:subplot] = sp
attr = KW()
for (k,v) in kw
for defdict in (_subplot_defaults,
_axis_defaults,
_axis_defaults_byletter)
if haskey(defdict, k)
attr[k] = pop!(kw, k)
end
end
end
sp_attrs[sp] = attr
end
# 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)
end
# do we need to link any axes together?
link_axes!(plt.layout, plt[:link])
_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. !!!
# !!! The next step is to recursively apply series recipes until the backend supports that series type !!!
@@ -520,11 +186,11 @@ function _plot!(plt::Plot, d::KW, args...)
# --------------------------------
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))
@@ -535,7 +201,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,
# 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).
_apply_series_recipe(plt, kw)
_process_seriesrecipe(plt, kw)
end
# --------------------------------
@@ -544,20 +210,13 @@ function _plot!(plt::Plot, d::KW, args...)
# do we want to force display?
if plt[:show]
gui()
gui(plt)
end
plt
end
function _replace_linewidth(d::KW)
# get a good default linewidth... 0 for surface and heatmaps
if get(d, :linewidth, :auto) == :auto
d[:linewidth] = (get(d, :seriestype, :path) in (:surface,:heatmap,:image) ? 0 : 1)
end
end
# we're getting ready to display/output. prep for layout calcs, then update
# the plot object after
function prepare_output(plt::Plot)
+220 -287
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:
@@ -82,7 +77,7 @@ function seriestype_supported(pkg::AbstractBackend, st::Symbol)
end
macro deps(st, args...)
:(series_recipe_dependencies($(quot(st)), $(map(quot, args)...)))
:(Plots.series_recipe_dependencies($(quot(st)), $(map(quot, args)...)))
end
# get a list of all seriestypes
@@ -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
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 @@ end
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)
@@ -346,8 +317,8 @@ end
fr = d[:fillrange]
newfr = fr != nothing ? zeros(0) : nothing
newz = z != nothing ? zeros(0) : nothing
lz = d[:line_z]
newlz = lz != nothing ? zeros(0) : nothing
# lz = d[:line_z]
# newlz = lz != nothing ? zeros(0) : nothing
# for each line segment (point series with no NaNs), convert it into a bezier curve
# where the points are the control points of the curve
@@ -362,11 +333,11 @@ end
if fr != nothing
nanappend!(newfr, map(t -> bezier_value(cycle(fr,rng), t), ts))
end
if lz != nothing
lzrng = cycle(lz, rng) # the line_z's for this segment
push!(newlz, 0.0)
append!(newlz, map(t -> lzrng[1+floor(Int, t * (length(rng)-1))], ts))
end
# if lz != nothing
# lzrng = cycle(lz, rng) # the line_z's for this segment
# push!(newlz, 0.0)
# append!(newlz, map(t -> lzrng[1+floor(Int, t * (length(rng)-1))], ts))
# end
end
x := newx
@@ -380,10 +351,10 @@ end
if fr != nothing
fillrange := newfr
end
if lz != nothing
line_z := newlz
linecolor := (isa(d[:linecolor], ColorGradient) ? d[:linecolor] : default_gradient())
end
# if lz != nothing
# # line_z := newlz
# linecolor := (isa(d[:linecolor], ColorGradient) ? d[:linecolor] : cgrad())
# end
# Plots.DD(d)
()
end
@@ -393,31 +364,25 @@ end
# create a bar plot as a filled step function
@recipe function f(::Type{Val{:bar}}, x, y, z)
# if horizontal, switch x/y
if !isvertical(d)
x, y = y, x
end
nx, ny = length(x), length(y)
edges = if nx == ny
# x is centers, calc the edges
# TODO: use bar_width, etc
midpoints = x
halfwidths = diff(midpoints) * 0.5
Float64[if i == 1
midpoints[1] - halfwidths[1]
elseif i == ny+1
midpoints[i-1] + halfwidths[i-2]
else
midpoints[i-1] + halfwidths[i-1]
end for i=1:ny+1]
axis = d[:subplot][isvertical(d) ? :xaxis : :yaxis]
cv = [discrete_value!(axis, xi)[1] for xi=x]
x = if nx == ny
cv
elseif nx == ny + 1
# x is edges
x
0.5diff(cv) + cv[1:end-1]
else
error("bar recipe: x must be same length as y (centers), or one more than y (edges).\n\t\tlength(x)=$(length(x)), length(y)=$(length(y))")
end
# compute half-width of bars
bw = d[:bar_width]
hw = if bw == nothing
0.5mean(diff(x))
else
Float64[0.5cycle(bw,i) for i=1:length(x)]
end
# make fillto a vector... default fills to 0
fillto = d[:fillrange]
if fillto == nothing
@@ -427,9 +392,12 @@ end
# create the bar shapes by adding x/y segments
xseg, yseg = Segments(), Segments()
for i=1:ny
center = x[i]
hwi = cycle(hw,i)
yi = y[i]
fi = cycle(fillto,i)
push!(xseg, edges[i], edges[i], edges[i+1], edges[i+1])
push!(yseg, y[i], fi, fi, y[i])
push!(xseg, center-hwi, center-hwi, center+hwi, center+hwi, center-hwi)
push!(yseg, yi, fi, fi, yi, yi)
end
# switch back
@@ -442,7 +410,7 @@ end
seriestype := :shape
()
end
@deps bar path
@deps bar shape
# ---------------------------------------------------------------------------
# Histograms
@@ -454,7 +422,7 @@ function calc_edges(v, bins::Integer)
end
# just pass through arrays
calc_edges(v, bins::AVec) = v
calc_edges(v, bins::AVec) = bins
# find the bucket index of this value
function bucket_index(vi, edges)
@@ -525,7 +493,7 @@ function my_hist_2d(x, y, bins; normed = false, weights = nothing)
xedges, yedges, counts ./ norm_denom
end
centers(v::AVec) = v[1] + cumsum(diff(v))
centers(v::AVec) = 0.5 * (v[1:end-1] + v[2:end])
@recipe function f(::Type{Val{:histogram2d}}, x, y, z)
xedges, yedges, counts = my_hist_2d(x, y, d[:bins],
@@ -556,170 +524,179 @@ end
# note: don't add dependencies because this really isn't a drop-in replacement
# ---------------------------------------------------------------------------
# Box Plot
# # TODO: move boxplots and violin plots to StatPlots when it's ready
const _box_halfwidth = 0.4
# # ---------------------------------------------------------------------------
# # Box Plot
notch_width(q2, q4, N) = 1.58 * (q4-q2)/sqrt(N)
# const _box_halfwidth = 0.4
# notch_width(q2, q4, N) = 1.58 * (q4-q2)/sqrt(N)
@recipe function f(::Type{Val{:boxplot}}, x, y, z; notch=false, range=1.5)
xsegs, ysegs = Segments(), Segments()
glabels = sort(collect(unique(x)))
warning = false
outliers_x, outliers_y = zeros(0), zeros(0)
for glabel in glabels
# filter y
values = y[filter(i -> cycle(x,i) == glabel, 1:length(y))]
# @recipe function f(::Type{Val{:boxplot}}, x, y, z; notch=false, range=1.5)
# xsegs, ysegs = Segments(), Segments()
# glabels = sort(collect(unique(x)))
# warning = false
# outliers_x, outliers_y = zeros(0), zeros(0)
# for (i,glabel) in enumerate(glabels)
# # filter y
# values = y[filter(i -> cycle(x,i) == glabel, 1:length(y))]
# compute quantiles
q1,q2,q3,q4,q5 = quantile(values, linspace(0,1,5))
# # compute quantiles
# q1,q2,q3,q4,q5 = quantile(values, linspace(0,1,5))
# notch
n = notch_width(q2, q4, length(values))
# # notch
# n = notch_width(q2, q4, length(values))
# warn on inverted notches?
if notch && !warning && ( (q2>(q3-n)) || (q4<(q3+n)) )
warn("Boxplot's notch went outside hinges. Set notch to false.")
warning = true # Show the warning only one time
end
# # warn on inverted notches?
# if notch && !warning && ( (q2>(q3-n)) || (q4<(q3+n)) )
# warn("Boxplot's notch went outside hinges. Set notch to false.")
# warning = true # Show the warning only one time
# end
# make the shape
center = discrete_value!(d[:subplot][:xaxis], glabel)[1]
l, m, r = center - _box_halfwidth, center, center + _box_halfwidth
# # make the shape
# center = discrete_value!(d[:subplot][:xaxis], glabel)[1]
# hw = d[:bar_width] == nothing ? _box_halfwidth : 0.5cycle(d[:bar_width], i)
# l, m, r = center - hw, center, center + hw
# internal nodes for notches
L, R = center - 0.5 * _box_halfwidth, center + 0.5 * _box_halfwidth
# # internal nodes for notches
# L, R = center - 0.5 * hw, center + 0.5 * hw
# outliers
if Float64(range) != 0.0 # if the range is 0.0, the whiskers will extend to the data
limit = range*(q4-q2)
inside = Float64[]
for value in values
if (value < (q2 - limit)) || (value > (q4 + limit))
push!(outliers_y, value)
push!(outliers_x, center)
else
push!(inside, value)
end
end
# change q1 and q5 to show outliers
# using maximum and minimum values inside the limits
q1, q5 = extrema(inside)
end
# # outliers
# if Float64(range) != 0.0 # if the range is 0.0, the whiskers will extend to the data
# limit = range*(q4-q2)
# inside = Float64[]
# for value in values
# if (value < (q2 - limit)) || (value > (q4 + limit))
# push!(outliers_y, value)
# push!(outliers_x, center)
# else
# push!(inside, value)
# end
# end
# # change q1 and q5 to show outliers
# # using maximum and minimum values inside the limits
# q1, q5 = extrema(inside)
# end
# Box
if notch
push!(xsegs, m, l, r, m, m) # lower T
push!(xsegs, l, l, L, R, r, r, l) # lower box
push!(xsegs, l, l, L, R, r, r, l) # upper box
push!(xsegs, m, l, r, m, m) # upper T
# # Box
# if notch
# push!(xsegs, m, l, r, m, m) # lower T
# push!(xsegs, l, l, L, R, r, r, l) # lower box
# push!(xsegs, l, l, L, R, r, r, l) # upper box
# push!(xsegs, m, l, r, m, m) # upper T
push!(ysegs, q1, q1, q1, q1, q2) # lower T
push!(ysegs, q2, q3-n, q3, q3, q3-n, q2, q2) # lower box
push!(ysegs, q4, q3+n, q3, q3, q3+n, q4, q4) # upper box
push!(ysegs, q5, q5, q5, q5, q4) # upper T
else
push!(xsegs, m, l, r, m, m) # lower T
push!(xsegs, l, l, r, r, l) # lower box
push!(xsegs, l, l, r, r, l) # upper box
push!(xsegs, m, l, r, m, m) # upper T
# push!(ysegs, q1, q1, q1, q1, q2) # lower T
# push!(ysegs, q2, q3-n, q3, q3, q3-n, q2, q2) # lower box
# push!(ysegs, q4, q3+n, q3, q3, q3+n, q4, q4) # upper box
# push!(ysegs, q5, q5, q5, q5, q4) # upper T
# else
# push!(xsegs, m, l, r, m, m) # lower T
# push!(xsegs, l, l, r, r, l) # lower box
# push!(xsegs, l, l, r, r, l) # upper box
# push!(xsegs, m, l, r, m, m) # upper T
push!(ysegs, q1, q1, q1, q1, q2) # lower T
push!(ysegs, q2, q3, q3, q2, q2) # lower box
push!(ysegs, q4, q3, q3, q4, q4) # upper box
push!(ysegs, q5, q5, q5, q5, q4) # upper T
end
end
# push!(ysegs, q1, q1, q1, q1, q2) # lower T
# push!(ysegs, q2, q3, q3, q2, q2) # lower box
# push!(ysegs, q4, q3, q3, q4, q4) # upper box
# push!(ysegs, q5, q5, q5, q5, q4) # upper T
# end
# end
# Outliers
@series begin
seriestype := :scatter
markershape := :circle
markercolor := d[:fillcolor]
markeralpha := d[:fillalpha]
markerstrokecolor := d[:linecolor]
markerstrokealpha := d[:linealpha]
x := outliers_x
y := outliers_y
primary := false
()
end
# # Outliers
# @series begin
# seriestype := :scatter
# markershape := :circle
# markercolor := d[:fillcolor]
# markeralpha := d[:fillalpha]
# markerstrokecolor := d[:linecolor]
# markerstrokealpha := d[:linealpha]
# x := outliers_x
# y := outliers_y
# primary := false
# ()
# end
seriestype := :shape
x := xsegs.pts
y := ysegs.pts
()
end
@deps boxplot shape scatter
# seriestype := :shape
# x := xsegs.pts
# y := ysegs.pts
# ()
# end
# @deps boxplot shape scatter
# ---------------------------------------------------------------------------
# Violin Plot
# # ---------------------------------------------------------------------------
# # Violin Plot
# if the user has KernelDensity installed, use this for violin plots.
# otherwise, just use a histogram
if is_installed("KernelDensity")
@eval import KernelDensity
@eval function violin_coords(y; trim::Bool=false)
kd = KernelDensity.kde(y, npoints = 200)
if trim
xmin, xmax = extrema(y)
inside = Bool[ xmin <= x <= xmax for x in kd.x]
return(kd.density[inside], kd.x[inside])
end
kd.density, kd.x
end
else
@eval function violin_coords(y; trim::Bool=false)
edges, widths = hist(y, 30)
centers = 0.5 * (edges[1:end-1] + edges[2:end])
ymin, ymax = extrema(y)
vcat(0.0, widths, 0.0), vcat(ymin, centers, ymax)
end
end
# const _violin_warned = [false]
# # if the user has KernelDensity installed, use this for violin plots.
# # otherwise, just use a histogram
# if is_installed("KernelDensity")
# @eval import KernelDensity
# @eval function violin_coords(y; trim::Bool=false)
# kd = KernelDensity.kde(y, npoints = 200)
# if trim
# xmin, xmax = extrema(y)
# inside = Bool[ xmin <= x <= xmax for x in kd.x]
# return(kd.density[inside], kd.x[inside])
# end
# kd.density, kd.x
# end
# else
# @eval function violin_coords(y; trim::Bool=false)
# if !_violin_warned[1]
# warn("Install the KernelDensity package for best results.")
# _violin_warned[1] = true
# end
# edges, widths = my_hist(y, 10)
# centers = 0.5 * (edges[1:end-1] + edges[2:end])
# ymin, ymax = extrema(y)
# vcat(0.0, widths, 0.0), vcat(ymin, centers, ymax)
# end
# end
@recipe function f(::Type{Val{:violin}}, x, y, z; trim=true)
xsegs, ysegs = Segments(), Segments()
glabels = sort(collect(unique(x)))
for glabel in glabels
widths, centers = violin_coords(y[filter(i -> cycle(x,i) == glabel, 1:length(y))], trim=trim)
isempty(widths) && continue
# @recipe function f(::Type{Val{:violin}}, x, y, z; trim=true)
# xsegs, ysegs = Segments(), Segments()
# glabels = sort(collect(unique(x)))
# for glabel in glabels
# widths, centers = violin_coords(y[filter(i -> cycle(x,i) == glabel, 1:length(y))], trim=trim)
# isempty(widths) && continue
# normalize
widths = _box_halfwidth * widths / maximum(widths)
# # normalize
# widths = _box_halfwidth * widths / maximum(widths)
# make the violin
xcenter = discrete_value!(d[:subplot][:xaxis], glabel)[1]
xcoords = vcat(widths, -reverse(widths)) + xcenter
ycoords = vcat(centers, reverse(centers))
# # make the violin
# xcenter = discrete_value!(d[:subplot][:xaxis], glabel)[1]
# xcoords = vcat(widths, -reverse(widths)) + xcenter
# ycoords = vcat(centers, reverse(centers))
push!(xsegs, xcoords)
push!(ysegs, ycoords)
end
# push!(xsegs, xcoords)
# push!(ysegs, ycoords)
# end
seriestype := :shape
x := xsegs.pts
y := ysegs.pts
()
end
@deps violin shape
# seriestype := :shape
# x := xsegs.pts
# y := ysegs.pts
# ()
# end
# @deps violin shape
# ---------------------------------------------------------------------------
# density
# # ---------------------------------------------------------------------------
# # density
@recipe function f(::Type{Val{:density}}, x, y, z; trim=false)
newx, newy = violin_coords(y, trim=trim)
if isvertical(d)
newx, newy = newy, newx
end
x := newx
y := newy
seriestype := :path
()
end
@deps density path
# @recipe function f(::Type{Val{:density}}, x, y, z; trim=false)
# newx, newy = violin_coords(y, trim=trim)
# if isvertical(d)
# newx, newy = newy, newx
# end
# x := newx
# y := newy
# seriestype := :path
# ()
# end
# @deps density path
# ---------------------------------------------------------------------------
# contourf - filled contours
@@ -790,6 +767,8 @@ end
@deps xerror path
# TODO: move quiver to PlotRecipes
# ---------------------------------------------------------------------------
# quiver
@@ -890,56 +869,10 @@ 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: this should really be in another package...
# TODO: move OHLC to PlotRecipes finance.jl
type OHLC{T<:Real}
open::T
high::T
+99 -77
View File
@@ -1,81 +1,106 @@
# we are going to build recipes to do the processing and splitting of the args
function _add_defaults!(d::KW, plt::Plot, sp::Subplot, commandIndex::Int)
pkg = plt.backend
globalIndex = d[:series_plotindex]
# create a new "build_series_args" which converts all inputs into xs = Any[xitems], ys = Any[yitems].
# Special handling for: no args, xmin/xmax, parametric, dataframes
# Then once inputs have been converted, build the series args, map functions, etc.
# This should cut down on boilerplate code and allow more focused dispatch on type
# note: returns meta information... mainly for use with automatic labeling from DataFrames for now
# 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)
end
typealias FuncOrFuncs @compat(Union{Function, AVec{Function}})
# this is how many series belong to this subplot
plotIndex = count(series -> series.d[:subplot] === sp && series.d[:primary], plt.series_list)
if get(d, :primary, true)
plotIndex += 1
end
all3D(d::KW) = trueOrAllTrue(st -> st in (:contour, :contourf, :heatmap, :surface, :wireframe, :contour3d, :image), get(d, :seriestype, :none))
aliasesAndAutopick(d, :linestyle, _styleAliases, supported_styles(pkg), plotIndex)
aliasesAndAutopick(d, :markershape, _markerAliases, supported_markers(pkg), plotIndex)
# missing
convertToAnyVector(v::@compat(Void), d::KW) = Any[nothing], nothing
# update color
d[:seriescolor] = getSeriesRGBColor(d[:seriescolor], sp, plotIndex)
# fixed number of blank series
convertToAnyVector(n::Integer, d::KW) = Any[zeros(0) for i in 1:n], nothing
# update colors
for csym in (:linecolor, :markercolor, :fillcolor)
d[csym] = if d[csym] == :match
if has_black_border_for_default(d[:seriestype]) && csym == :linecolor
:black
else
d[:seriescolor]
end
else
getSeriesRGBColor(d[csym], sp, plotIndex)
end
end
# numeric vector
convertToAnyVector{T<:Number}(v::AVec{T}, d::KW) = Any[v], nothing
# update markerstrokecolor
c = d[:markerstrokecolor]
c = if c == :match
sp[:foreground_color_subplot]
# string vector
convertToAnyVector{T<:@compat(AbstractString)}(v::AVec{T}, d::KW) = Any[v], nothing
function convertToAnyVector(v::AMat, d::KW)
if all3D(d)
Any[Surface(v)]
else
getSeriesRGBColor(c, sp, plotIndex)
end
d[:markerstrokecolor] = c
# update alphas
for asym in (:linealpha, :markeralpha, :fillalpha)
if d[asym] == nothing
d[asym] = d[:seriesalpha]
end
end
if d[:markerstrokealpha] == nothing
d[:markerstrokealpha] = d[:markeralpha]
end
# scatter plots don't have a line, but must have a shape
if d[:seriestype] in (:scatter, :scatter3d)
d[:linewidth] = 0
if d[:markershape] == :none
d[:markershape] = :circle
end
end
# set label
label = d[:label]
label = (label == "AUTO" ? "y$globalIndex" : label)
d[:label] = label
_replace_linewidth(d)
d
Any[v[:,i] for i in 1:size(v,2)]
end, nothing
end
# -------------------------------------------------------------------
# -------------------------------------------------------------------
# function
convertToAnyVector(f::Function, d::KW) = Any[f], nothing
# instead of process_inputs:
# surface
convertToAnyVector(s::Surface, d::KW) = Any[s], nothing
# # vector of OHLC
# convertToAnyVector(v::AVec{OHLC}, d::KW) = Any[v], nothing
# dates
convertToAnyVector{D<:Union{Date,DateTime}}(dts::AVec{D}, d::KW) = Any[dts], nothing
# list of things (maybe other vectors, functions, or something else)
function convertToAnyVector(v::AVec, d::KW)
if all(x -> typeof(x) <: Number, v)
# all real numbers wrap the whole vector as one item
Any[convert(Vector{Float64}, v)], nothing
else
# something else... treat each element as an item
vcat(Any[convertToAnyVector(vi, d)[1] for vi in v]...), nothing
# Any[vi for vi in v], nothing
end
end
convertToAnyVector(t::Tuple, d::KW) = Any[t], nothing
function convertToAnyVector(args...)
error("In convertToAnyVector, could not handle the argument types: $(map(typeof, args[1:end-1]))")
end
# --------------------------------------------------------------------
# TODO: can we avoid the copy here? one error that crops up is that mapping functions over the same array
# result in that array being shared. push!, etc will add too many items to that array
compute_x(x::Void, y::Void, z) = 1:size(z,1)
compute_x(x::Void, y, z) = 1:size(y,1)
compute_x(x::Function, y, z) = map(x, y)
compute_x(x, y, z) = copy(x)
# compute_y(x::Void, y::Function, z) = error()
compute_y(x::Void, y::Void, z) = 1:size(z,2)
compute_y(x, y::Function, z) = map(y, x)
compute_y(x, y, z) = copy(y)
compute_z(x, y, z::Function) = map(z, x, y)
compute_z(x, y, z::AbstractMatrix) = Surface(z)
compute_z(x, y, z::Void) = nothing
compute_z(x, y, z) = copy(z)
nobigs(v::AVec{BigFloat}) = map(Float64, v)
nobigs(v::AVec{BigInt}) = map(Int64, v)
nobigs(v) = v
@noinline function compute_xyz(x, y, z)
x = compute_x(x,y,z)
y = compute_y(x,y,z)
z = compute_z(x,y,z)
nobigs(x), nobigs(y), nobigs(z)
end
# not allowed
compute_xyz(x::Void, y::FuncOrFuncs, z) = error("If you want to plot the function `$y`, you need to define the x values!")
compute_xyz(x::Void, y::Void, z::FuncOrFuncs) = error("If you want to plot the function `$z`, you need to define x and y values!")
compute_xyz(x::Void, y::Void, z::Void) = error("x/y/z are all nothing!")
# --------------------------------------------------------------------
# we are going to build recipes to do the processing and splitting of the args
# ensure we dispatch to the slicer
immutable SliceIt end
@@ -232,33 +257,30 @@ end
# # plotting arbitrary shapes/polygons
@recipe function f(shape::Shape)
seriestype := :shape
seriestype --> :shape
shape_coords(shape)
end
@recipe function f(shapes::AVec{Shape})
seriestype := :shape
seriestype --> :shape
shape_coords(shapes)
end
@recipe function f(shapes::AMat{Shape})
seriestype --> :shape
for j in 1:size(shapes,2)
# create one series for each column
# @series shape_coords(vec(shapes[:,j]))
di = copy(d)
push!(series_list, RecipeData(di, shape_coords(vec(shapes[:,j]))))
@series shape_coords(vec(shapes[:,j]))
end
nothing # don't create a series for the main block
end
#
#
# # function without range... use the current range of the x-axis
@recipe function f(f::FuncOrFuncs)
plt = d[:plot_object]
f, xmin(plt), xmax(plt)
end
# @recipe function f(f::FuncOrFuncs)
# plt = d[:plot_object]
# f, xmin(plt), xmax(plt)
# end
#
# # --------------------------------------------------------------------
+1 -1
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(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)
+51 -30
View File
@@ -138,24 +138,41 @@ end
# ---------------------------------------------------------------
type Segments
pts::Vector{Float64}
type Segments{T}
pts::Vector{T}
end
Segments() = Segments(zeros(0))
# Segments() = Segments{Float64}(zeros(0))
function Base.push!(segments::Segments, vs...)
push!(segments.pts, NaN)
Segments() = Segments(Float64)
Segments{T}(::Type{T}) = Segments(T[])
Segments(p::Int) = Segments(NTuple{2,Float64}[])
# Segments() = Segments(zeros(0))
to_nan(::Type{Float64}) = NaN
to_nan(::Type{NTuple{2,Float64}}) = (NaN, NaN)
coords(segs::Segments{Float64}) = segs.pts
coords(segs::Segments{NTuple{2,Float64}}) = Float64[p[1] for p in segs.pts], Float64[p[2] for p in segs.pts]
function Base.push!{T}(segments::Segments{T}, vs...)
if !isempty(segments.pts)
push!(segments.pts, to_nan(T))
end
for v in vs
push!(segments.pts, v)
push!(segments.pts, convert(T,v))
end
segments
end
function Base.push!(segments::Segments, vs::AVec)
push!(segments.pts, NaN)
function Base.push!{T}(segments::Segments{T}, vs::AVec)
if !isempty(segments.pts)
push!(segments.pts, to_nan(T))
end
for v in vs
push!(segments.pts, v)
push!(segments.pts, convert(T,v))
end
segments
end
@@ -166,28 +183,33 @@ end
type SegmentsIterator
args::Tuple
nextidx::Int
n::Int
end
function iter_segments(args...)
tup = Plots.wraptuple(args)
n = maximum(map(length, tup))
SegmentsIterator(tup, 0, n)
SegmentsIterator(tup, n)
end
# helpers to figure out if there are NaN values in a list of array types
anynan(i::Int, args...) = any(a -> !isfinite(cycle(a,i)), args)
anynan(istart::Int, iend::Int, args...) = any(i -> anynan(i, args...), istart:iend)
allnan(istart::Int, iend::Int, args...) = all(i -> anynan(i, args...), istart:iend)
anynan(i::Int, args::Tuple) = any(a -> !isfinite(cycle(a,i)), args)
anynan(istart::Int, iend::Int, args::Tuple) = any(i -> anynan(i, args), istart:iend)
allnan(istart::Int, iend::Int, args::Tuple) = all(i -> anynan(i, args), istart:iend)
Base.start(itr::SegmentsIterator) = (itr.nextidx = 1) #resets
Base.done(itr::SegmentsIterator, unused::Int) = itr.nextidx > itr.n
function Base.next(itr::SegmentsIterator, unused::Int)
i = istart = iend = itr.nextidx
function Base.start(itr::SegmentsIterator)
nextidx = 1
if anynan(1, itr.args)
_, nextidx = next(itr, 1)
end
nextidx
end
Base.done(itr::SegmentsIterator, nextidx::Int) = nextidx > itr.n
function Base.next(itr::SegmentsIterator, nextidx::Int)
i = istart = iend = nextidx
# find the next NaN, and iend is the one before
while i <= itr.n + 1
if i > itr.n || anynan(i, itr.args...)
if i > itr.n || anynan(i, itr.args)
# done... array end or found NaN
iend = i-1
break
@@ -195,16 +217,15 @@ function Base.next(itr::SegmentsIterator, unused::Int)
i += 1
end
# find the next non-NaN, and set itr.nextidx
# find the next non-NaN, and set nextidx
while i <= itr.n
if !anynan(i, itr.args...)
if !anynan(i, itr.args)
break
end
i += 1
end
itr.nextidx = i
istart:iend, 0
istart:iend, i
end
# ------------------------------------------------------------------------------------
@@ -224,6 +245,8 @@ Base.cycle(v::AVec, indices::AVec{Int}) = map(i -> cycle(v,i), indices)
Base.cycle(v::AMat, indices::AVec{Int}) = map(i -> cycle(v,i), indices)
Base.cycle(v, idx::AVec{Int}) = v
Base.cycle(grad::ColorGradient, idx::Int) = cycle(grad.colors, idx)
makevec(v::AVec) = v
makevec{T}(v::T) = T[v]
@@ -259,6 +282,7 @@ function _expand_limits(lims, x)
nothing
end
expand_data(v, n::Integer) = [cycle(v, i) for i=1:n]
# if the type exists in a list, replace the first occurence. otherwise add it to the end
function addOrReplace(v::AbstractVector, t::DataType, args...; kw...)
@@ -277,20 +301,16 @@ function replaceType(vec, val)
push!(vec, val)
end
function replaceAlias!(d::KW, k::Symbol, aliases::KW)
function replaceAlias!(d::KW, k::Symbol, aliases::Dict{Symbol,Symbol})
if haskey(aliases, k)
d[aliases[k]] = pop!(d, k)
end
end
function replaceAliases!(d::KW, aliases::KW)
function replaceAliases!(d::KW, aliases::Dict{Symbol,Symbol})
ks = collect(keys(d))
for k in ks
replaceAlias!(d, k, aliases)
# if haskey(aliases, k)
# d[aliases[k]] = d[k]
# delete!(d, k)
# end
end
end
@@ -363,6 +383,7 @@ is_2tuple(v) = typeof(v) <: Tuple && length(v) == 2
isvertical(d::KW) = get(d, :orientation, :vertical) in (:vertical, :v, :vert)
isvertical(series::Series) = isvertical(series.d)
# ticksType{T<:Real,S<:Real}(ticks::@compat(Tuple{T,S})) = :limits
@@ -422,7 +443,7 @@ end
# this is a helper function to determine whether we need to transpose a surface matrix.
# it depends on whether the backend matches rows to x (transpose_on_match == true) or vice versa
# for example: PyPlot sends rows to y, so transpose_on_match should be true
function transpose_z(d::KW, z, transpose_on_match::Bool = true)
function transpose_z(d, z, transpose_on_match::Bool = true)
if d[:match_dimensions] == transpose_on_match
z'
else
+4 -2
View File
@@ -1,12 +1,14 @@
julia 0.4
RecipesBase
Colors
PlotUtils
StatPlots
Reexport
Measures
Showoff
FactCheck
Images
PyPlot
ImageMagick
@osx QuartzImageIO
GR
DataFrames
+10 -8
View File
@@ -13,7 +13,9 @@ try
end
using Plots, FactCheck
using Plots
using StatPlots
using FactCheck
using Glob
default(size=(500,300))
@@ -22,7 +24,7 @@ default(size=(500,300))
# TODO: use julia's Condition type and the wait() and notify() functions to initialize a Window, then wait() on a condition that
# is referenced in a button press callback (the button clicked callback will call notify() on that condition)
const _current_plots_version = v"0.7.5"
const _current_plots_version = v"0.8.1"
function image_comparison_tests(pkg::Symbol, idx::Int; debug = false, popup = isinteractive(), sigma = [1,1], eps = 1e-2)
@@ -76,12 +78,12 @@ function image_comparison_tests(pkg::Symbol, idx::Int; debug = false, popup = is
png(fn)
end
try
run(`mkdir -p $newdir`)
catch err
display(err)
end
# reffn = joinpath(refdir, "ref$idx.png")
# try
# run(`mkdir -p $newdir`)
# catch err
# display(err)
# end
# # reffn = joinpath(refdir, "ref$idx.png")
# the test
vtest = VisualTest(func, reffn, idx)
+2 -2
View File
@@ -23,14 +23,14 @@ facts("PyPlot") do
@fact pyplot() --> Plots.PyPlotBackend()
@fact backend() --> Plots.PyPlotBackend()
image_comparison_facts(:pyplot, skip=[30], eps=img_eps)
image_comparison_facts(:pyplot, skip=[], eps=img_eps)
end
facts("GR") do
@fact gr() --> Plots.GRBackend()
@fact backend() --> Plots.GRBackend()
@linux_only image_comparison_facts(:gr, skip=[30], eps=img_eps)
# @linux_only image_comparison_facts(:gr, skip=[], eps=img_eps)
end
facts("Plotly") do
+12 -11
View File
@@ -1,25 +1,26 @@
# Pkg.clone(pwd())
# Pkg.build("Plots")
# Pkg.clone("ImageMagick")
# Pkg.build("ImageMagick")
Pkg.clone("ImageMagick")
Pkg.build("ImageMagick")
# Pkg.clone("GR")
# Pkg.build("GR")
Pkg.clone("GR")
Pkg.build("GR")
# Pkg.clone("https://github.com/JuliaPlots/RecipesBase.jl.git")
# Pkg.clone("https://github.com/tbreloff/ExamplePlots.jl.git");
Pkg.clone("https://github.com/JuliaPlots/PlotReferenceImages.jl.git")
# Pkg.clone("https://github.com/JuliaStats/KernelDensity.jl.git")
Pkg.clone("StatPlots")
Pkg.checkout("PlotUtils")
# Pkg.clone("https://github.com/JunoLab/Blink.jl.git")
# Pkg.build("Blink")
# import Blink
# Blink.AtomShell.install()
# Pkg.clone("https://github.com/spencerlyon2/PlotlyJS.jl.git")
Pkg.checkout("RecipesBase")
Pkg.clone("VisualRegressionTests")
# Pkg.checkout("RecipesBase")
# Pkg.clone("VisualRegressionTests")
# need this to use Conda
ENV["PYTHON"] = ""
Pkg.add("PyPlot")
Pkg.build("PyPlot")