Compare commits

...

49 Commits

Author SHA1 Message Date
Thomas Breloff 558627e7a9 NEWS 2016-06-27 14:02:01 -04:00
Thomas Breloff 4b276f977c revert pyplot heatmap change 2016-06-27 12:25:45 -04:00
Thomas Breloff 99fe4f615d show arg fix; add image to all3D 2016-06-27 12:09:08 -04:00
Thomas Breloff 928c306803 dpi fixes 2016-06-27 10:06:40 -04:00
Thomas Breloff a3eddf8ba7 dpi support for pyplot 2016-06-27 09:20:42 -04:00
Thomas Breloff 78e0ae31af removed chorddiagram 2016-06-26 12:37:06 -04:00
Thomas Breloff 1a90ce0950 recoded boxplot recipe to match violin approach; switched marker to line/fill for shape seriestype: ref #347 2016-06-26 11:36:09 -04:00
Thomas Breloff 2465b3eb5a fallback warnings for missing _writemime and _display 2016-06-26 11:08:06 -04:00
Thomas Breloff 16a2e34e95 writemime fix for Interact; working on glvisualize 2016-06-26 10:34:34 -04:00
Thomas Breloff 5c52d68091 working on glvisualize 2016-06-25 18:06:27 -04:00
Thomas Breloff 1a2e180f4f working on glvisualize 2016-06-25 17:00:45 -04:00
Thomas Breloff 9048053669 working on glvisualize 2016-06-25 10:56:51 -04:00
Thomas Breloff 69b69714cb working on glvisualize 2016-06-24 12:15:21 -04:00
Thomas Breloff 1ecd9f9e19 working on glvisualize 2016-06-24 11:52:33 -04:00
Thomas Breloff 2bca9ca9ec square link 2016-06-24 11:00:28 -04:00
Thomas Breloff d66028ed0d glvisualize: support for path/path3d with or without markers, 3d markers are spheres 2016-06-23 17:00:08 -04:00
Thomas Breloff 1cf33044bc scatter and scatter3d support in glvisualize 2016-06-23 14:59:04 -04:00
Thomas Breloff 2c138c8355 readme 2016-06-22 13:22:25 -04:00
Thomas Breloff 73ffcb9bc1 changed convertToAnyVector AMat so that all matrices use the version previously for numbers; violin cleanup 2016-06-22 13:20:09 -04:00
Thomas Breloff 730025e144 recoded violin; removed try/catch around apply_recipe; default values for xtick/ytick in GR 2016-06-22 11:45:30 -04:00
Thomas Breloff 91aa1d718a animations: use tmp.gif for ijulia, remove prefix from convert call; closes #346 2016-06-22 11:01:00 -04:00
Thomas Breloff f881bfc4fc add Segments; pyplot: switch bar/hist/hist2d to recipes and path fix; change bar to shape; removed GR from tests 2016-06-20 23:11:23 -04:00
Thomas Breloff e76c3fff69 refactored gr markers fixing bugs and enabling features; clear subplot/axis args from d in _plot 2016-06-20 19:51:03 -04:00
Thomas Breloff 9f168071ff subplot attributes fixes 2016-06-20 15:11:25 -04:00
Thomas Breloff b6652b7619 improvements to bbox construction and inset subplots 2016-06-20 10:13:03 -04:00
Thomas Breloff f6d501f69e plot recipe fixes 2016-06-20 00:11:06 -04:00
Thomas Breloff 4c052cb3b3 colors cgrad and getindex 2016-06-19 11:25:25 -04:00
Thomas Breloff ea8ccf38db plot recipe fixes 2016-06-18 22:30:47 -04:00
Thomas Breloff cf12ff8070 plot logic cleanup; added plot recipes 2016-06-18 09:33:34 -04:00
Thomas Breloff b7a95244db handle seriestype aliases; handle vector of seriestypes; hvline_limits; added cycle for InputWrapper; turn on GR tests for OSX 2016-06-18 00:14:20 -04:00
Tom Breloff 4c884651a8 Merge pull request #343 from jheinen/dev
Added suport for marker borders
2016-06-18 00:02:42 -04:00
Thomas Breloff e9eca577aa plotlyjs supported_scales; violin check 2016-06-17 15:31:01 -04:00
Josef Heinen cff78b477c Added suport for marker borders 2016-06-17 20:21:07 +02:00
Tom Breloff b232410d25 Merge pull request #338 from jheinen/dev
use temporary files when generating MIME files
2016-06-17 08:09:53 -04:00
Josef Heinen 5ff338d4ac use temporary files when generating MIME files 2016-06-17 09:34:10 +02:00
Thomas Breloff a5ceea153d bump plots version 0.7.3 2016-06-16 21:00:25 -04:00
Thomas Breloff f3544dc826 plotlyjs png fix; NEWS update for 0.7.2 2016-06-16 20:58:44 -04:00
Thomas Breloff b0bc68ae8a orientation aliases dir/direction; removed native support for sticks/hline/vline in pyplot; commented out arcdiagram 2016-06-16 17:31:10 -04:00
Thomas Breloff c38e947dc6 animation default file tempname; raised group warning to 100; check for empty vec in axis; use command_idx for slicing; move deletion of smooth outside loop 2016-06-16 10:03:01 -04:00
Thomas Breloff ec826fc8ce moved series_annotations into series recipe processing; change pyplot clf to fig clear 2016-06-16 00:48:35 -04:00
Thomas Breloff e5c4f782a5 anynan isfinite; error on reusing layout 2016-06-15 15:36:36 -04:00
Thomas Breloff a5ddebf44d curves fix 2016-06-15 13:02:31 -04:00
Thomas Breloff 38804898c5 iter_segments and curve series type 2016-06-15 12:52:36 -04:00
Thomas Breloff 2bd67f3519 pyplot 3d line segments 2016-06-15 02:15:51 -04:00
Thomas Breloff c8ed611c9c unicodeplots cleanup: ijulia output fixes, ascii canvas 2016-06-15 01:45:13 -04:00
Thomas Breloff 3d7d8caa82 moved pyplot methods _series_added and _initialize_subplot into the display pipeline 2016-06-14 18:02:44 -04:00
Thomas Breloff f64108523c line_z arg and pyplot implementation 2016-06-14 15:39:21 -04:00
Tom Breloff a7493504ed Merge pull request #334 from jheinen/dev
conserve user defined GR workstation type
2016-06-14 13:31:40 -04:00
Josef Heinen bc5293b5a4 conserve user defined GR workstation type 2016-06-14 18:03:03 +02:00
23 changed files with 1256 additions and 1322 deletions
+37
View File
@@ -9,6 +9,43 @@
## 0.7 (current master/dev)
#### 0.7.3
- rebuild violin and boxplot recipes
- "plot recipes"
- `cgrad` method for easy color gradient creation
- improvements to inset subplots
- Segments and iter_segments for NaN-separated vectors
- `bar` recipe now creates a `shape` series
- writemime fix for Interact.jl
- `link = :square` option
- !!! set `shape` attributes with line/fill, NOT marker/markerstroke !!!
- basic DPI support
- moved chorddiagram to PlotRecipes
- GR:
- use temp files for img output
- basic support for marker strokes and other marker fixes
- PyPlot:
- Switch to recipes for bar, histogram, histogram2d
- GLVisualize
- subplots
- path/scatter and path3d/scatter3d
- initial drawing of axes
- many smaller fixes and improvements
#### 0.7.2
- line_z arg for multicolored line segments
- pyplot
- line_z (2d and 3d)
- pushed all fig updates into display pipeline
- remove native sticks/hline/vline in favor of recipes
- unicodeplots cleanup, ijulia fixes, ascii canvas
- `curves` series type
- `iter_segments` iterator
- moved arcdiagram out and into PlotRecipes (thanks @diegozea)
- several other fixes/checks
#### 0.7.1
- inset (floating) subplots
-2
View File
@@ -22,8 +22,6 @@ Use the [preprocessing pipeline](http://plots.readthedocs.io/en/latest/pipeline/
```julia
using Plots
pyplot(reuse=true)
@gif for i in linspace(0,2π,100)
X = Y = linspace(-5,5,40)
surface(X, Y, (x,y) -> sin(x+10sin(i))+cos(y))
+4 -3
View File
@@ -10,6 +10,7 @@ using Reexport
using FixedSizeArrays
@reexport using RecipesBase
using Base.Meta
# using PlotUtils
export
AbstractPlot,
@@ -78,6 +79,7 @@ export
Surface,
OHLC,
arrow,
Segments,
colorscheme,
ColorScheme,
@@ -103,12 +105,10 @@ export
@animate,
@gif,
PlotRecipe,
spy,
arcdiagram,
chorddiagram,
test_examples,
iter_segments,
translate,
translate!,
@@ -183,6 +183,7 @@ end
@shorthands boxplot
@shorthands violin
@shorthands quiver
@shorthands curves
pie(args...; kw...) = plot(args...; kw..., seriestype = :pie, aspect_ratio = :equal, grid=false, xticks=nothing, yticks=nothing)
pie!(args...; kw...) = plot!(args...; kw..., seriestype = :pie, aspect_ratio = :equal, grid=false, xticks=nothing, yticks=nothing)
+4 -4
View File
@@ -24,7 +24,7 @@ immutable AnimatedGif
filename::Compat.ASCIIString
end
function gif(anim::Animation, fn = "tmp.gif"; fps::Integer = 20)
function gif(anim::Animation, fn = (isijulia() ? "tmp.gif" : tempname()*".gif"); fps::Integer = 20)
fn = abspath(fn)
try
@@ -35,8 +35,8 @@ function gif(anim::Animation, fn = "tmp.gif"; fps::Integer = 20)
if isfile(file) && !haskey(ENV, "MAGICK_CONFIGURE_PATH")
include(file)
end
prefix = get(ENV, "MAGICK_CONFIGURE_PATH", "")
run(`$(joinpath(prefix, "convert")) -delay $speed -loop 0 $(joinpath(anim.dir, "*.png")) -alpha off $fn`)
# prefix = get(ENV, "MAGICK_CONFIGURE_PATH", "")
run(`convert -delay $speed -loop 0 $(joinpath(anim.dir, "*.png")) -alpha off $fn`)
catch err
warn("""Tried to create gif using convert (ImageMagick), but got error: $err
@@ -56,7 +56,7 @@ end
# write out html to view the gif... note the rand call which is a hack so the image doesn't get cached
function Base.writemime(io::IO, ::MIME"text/html", agif::AnimatedGif)
write(io, "<img src=\"$(relpath(agif.filename))?$(rand())>\" />")
write(io, "<img src=\"$(relpath(agif.filename))?$(rand())>\" />")
end
+3 -1
View File
@@ -27,7 +27,8 @@ const _arg_desc = KW(
:x => "Various. Input data. First Dimension",
:y => "Various. Input data. Second Dimension",
:z => "Various. Input data. Third Dimension. May be wrapped by a `Surface` for surface and heatmap types.",
:marker_z => "AbstractVector. z-values for each series data point, which correspond to the color to be used from a markercolor gradient.",
:marker_z => "AbstractVector, Function `f(x,y,z) -> z_value`, or nothing. z-values for each series data point, which correspond to the color to be used from a markercolor gradient.",
:line_z => "AbstractVector, Function `f(x,y,z) -> z_value`, or nothing. z-values for each series line segment, which correspond to the color to be used from a linecolor gradient. Note that for N points, only the first N-1 values are used (one per line-segment).",
:levels => "Integer, NTuple{2,Integer}. Number of levels (or x-levels/y-levels) for a contour type.",
:orientation => "Symbol. Horizontal or vertical orientation for bar types. Values `:h`, `:hor`, `:horizontal` correspond to horizontal (sideways, anchored to y-axis), and `:v`, `:vert`, and `:vertical` correspond to vertical (the default).",
:bar_position => "Symbol. Choose from `:overlay` (default), `:stack`. (warning: May not be implemented fully)",
@@ -61,6 +62,7 @@ const _arg_desc = KW(
:overwrite_figure => "Bool. Should we reuse the same GUI window/figure when plotting (true) or open a new one (false).",
:html_output_format => "Symbol. When writing html output, what is the format? `:png` and `:svg` are currently supported.",
:inset_subplots => "nothing or vector of 2-tuple (parent,bbox). optionally pass a vector of (parent,bbox) tuples which are the parent layout and the relative bounding box of inset subplots",
:dpi => "Number. Dots Per Inch of output figures",
# subplot args
:title => "String. Subplot title.",
+21 -2
View File
@@ -11,6 +11,16 @@ function add_aliases(sym::Symbol, aliases::Symbol...)
end
end
function add_non_underscore_aliases!(aliases::KW)
for (k,v) in aliases
s = string(k)
if '_' in s
aliases[Symbol(replace(s, "_", ""))] = v
end
end
end
# ------------------------------------------------------------
const _allAxes = [:auto, :left, :right]
@@ -61,8 +71,12 @@ const _allTypes = vcat([
:imagesc => :image,
:hist => :histogram,
:hist2d => :histogram2d,
:bezier => :curves,
:bezier_curves => :curves,
)
add_non_underscore_aliases!(_typeAliases)
like_histogram(seriestype::Symbol) = seriestype in (:histogram, :density)
like_line(seriestype::Symbol) = seriestype in (:line, :path, :steppre, :steppost)
like_surface(seriestype::Symbol) = seriestype in (:contour, :contour3d, :heatmap, :surface, :wireframe, :image)
@@ -163,6 +177,7 @@ const _series_defaults = KW(
:y => nothing,
:z => nothing, # depth for contour, surface, etc
:marker_z => nothing, # value for color scale
:line_z => nothing,
:levels => 15,
:orientation => :vertical,
:bar_position => :overlay, # for bar plots and histograms: could also be stack (stack up) or dodge (side by side)
@@ -201,6 +216,7 @@ const _plot_defaults = KW(
:html_output_format => :auto,
:inset_subplots => nothing, # optionally pass a vector of (parent,bbox) tuples which are
# the parent layout and the relative bounding box of inset subplots
:dpi => DPI, # dots per inch for images, etc
)
@@ -375,7 +391,8 @@ add_aliases(:linestyle, :style, :s, :ls)
add_aliases(:marker, :m, :mark)
add_aliases(:markershape, :shape)
add_aliases(:markersize, :ms, :msize)
add_aliases(:marker_z, :markerz, :zcolor)
add_aliases(:marker_z, :markerz, :zcolor, :mz)
add_aliases(:line_z, :linez, :zline, :lz)
add_aliases(:fill, :f, :area)
add_aliases(:fillrange, :fillrng, :frange, :fillto, :fill_between)
add_aliases(:group, :g, :grouping)
@@ -415,6 +432,8 @@ add_aliases(:projection, :proj)
add_aliases(:title_location, :title_loc, :titleloc, :title_position, :title_pos, :titlepos, :titleposition, :title_align, :title_alignment)
add_aliases(:series_annotations, :series_ann, :seriesann, :series_anns, :seriesanns, :series_annotation)
add_aliases(:html_output_format, :format, :fmt, :html_format)
add_aliases(:orientation, :direction, :dir)
add_aliases(:inset_subplots, :inset, :floating)
# add all pluralized forms to the _keyAliases dict
@@ -696,7 +715,7 @@ end
function extractGroupArgs(v::AVec, args...)
groupLabels = sort(collect(unique(v)))
n = length(groupLabels)
if n > 20
if n > 100
warn("You created n=$n groups... Is that intended?")
end
groupIds = Vector{Int}[filter(i -> v[i] == glab, 1:length(v)) for glab in groupLabels]
+226 -24
View File
@@ -29,68 +29,270 @@ supported_args(::GLVisualizeBackend) = merge_with_base_supported([
# :clims,
# :inset_subplots,
])
supported_types(::GLVisualizeBackend) = [:surface]
supported_types(::GLVisualizeBackend) = [:surface, :scatter, :scatter3d, :path, :path3d]
supported_styles(::GLVisualizeBackend) = [:auto, :solid]
supported_markers(::GLVisualizeBackend) = [:none, :auto, :circle]
supported_markers(::GLVisualizeBackend) = vcat([:none, :auto, :circle], collect(keys(_gl_marker_map)))
supported_scales(::GLVisualizeBackend) = [:identity]
is_subplot_supported(::GLVisualizeBackend) = false
is_subplot_supported(::GLVisualizeBackend) = true
# --------------------------------------------------------------------------------------
function _initialize_backend(::GLVisualizeBackend; kw...)
@eval begin
import GLVisualize
import GLVisualize, GeometryTypes, GLAbstraction, GLWindow
import GeometryTypes: Point2f0, Point3f0, Vec2f0, Vec3f0
export GLVisualize
# TODO: remove this when PlotUtils is registered
import PlotUtils
end
end
# ---------------------------------------------------------------------------
immutable GLScreenWrapper
window
end
# initialize the figure/window
function _create_backend_figure(plt::Plot{GLVisualizeBackend})
# init a window
window = GLVisualize.glscreen()
@async GLVisualize.renderloop(window)
window
# init a screen
screen = if isdefined(GLVisualize, :ROOT_SCREEN)
GLVisualize.ROOT_SCREEN
else
s = GLVisualize.glscreen()
@async GLVisualize.renderloop(s)
s
end
empty!(screen)
screen
end
function gl_display(plt::Plot{GLVisualizeBackend})
for sp in plt.subplots
# TODO: setup subplot
# ---------------------------------------------------------------------------
# size as a percentage of the window size
function gl_relative_size(plt::Plot{GLVisualizeBackend}, msize::Number)
winsz = min(plt[:size]...)
Float32(msize / winsz)
end
const _gl_marker_map = KW(
:rect => '■',
:star5 => '★',
:diamond => '◆',
:hexagon => '⬢',
:cross => '✚',
:xcross => '❌',
:utriangle => '▲',
:dtriangle => '▼',
:pentagon => '⬟',
:octagon => '⯄',
:star4 => '✦',
:star6 => '✶',
:star8 => '✷',
:vline => '┃',
:hline => '━',
)
# create a marker/shape type
function gl_marker(shape::Symbol, msize::Number, _3d::Bool)
GeometryTypes.HyperSphere((_3d ? Point3f0 : Point2f0)(0), msize)
end
gl_color(c::RGBA{Float32}) = c
# convert to RGBA
function gl_color(c, a=nothing)
c = convertColor(c, a)
RGBA{Float32}(getColor(c))
end
function gl_viewport(bb, rect)
l, b, bw, bh = bb
rw, rh = rect.w, rect.h
GLVisualize.SimpleRectangle(
round(Int, rect.x + rw * l),
round(Int, rect.y + rh * b),
round(Int, rw * bw),
round(Int, rh * bh)
)
end
gl_make_points(x, y) = Point2f0[Point2f0(x[i], y[i]) for i=1:length(x)]
gl_make_points(x, y, z) = Point3f0[Point3f0(x[i], y[i], z[i]) for i=1:length(x)]
function gl_draw_lines_2d(x, y, color, linewidth, sp_screen)
color = gl_color(color)
thickness = Float32(linewidth)
for rng in iter_segments(x, y)
n = length(rng)
n < 2 && continue
viz = GLVisualize.visualize(
gl_make_points(x[rng], y[rng]),
n==2 ? :linesegment : :lines,
color=color,
thickness = Float32(linewidth)
)
GLVisualize.view(viz, sp_screen, camera=:orthographic_pixel)
end
end
function gl_draw_lines_3d(x, y, z, color, linewidth, sp_screen)
color = gl_color(color)
thickness = Float32(linewidth)
for rng in iter_segments(x, y, z)
n = length(rng)
n < 2 && continue
viz = GLVisualize.visualize(
gl_make_points(x[rng], y[rng], z[rng]),
n==2 ? :linesegment : :lines,
color=color,
thickness = Float32(linewidth)
)
GLVisualize.view(viz, sp_screen, camera=:perspective)
end
end
function gl_annotate(sp::Subplot{GLVisualizeBackend}, x, y, txt::PlotText)
end
function gl_draw_axes_2d(sp::Subplot{GLVisualizeBackend})
sp_screen = sp.o
xaxis = sp[:xaxis]
xmin, xmax = axis_limits(xaxis)
yaxis = sp[:yaxis]
ymin, ymax = axis_limits(yaxis)
# x axis
xsegs, ysegs = Segments(), Segments()
ticksz = 0.03*(ymax-ymin)
push!(xsegs, [xmin,xmax]); push!(ysegs, [ymin,ymin])
for tick in PlotUtils.optimize_ticks(xmin, xmax)[1]
push!(xsegs, [tick,tick]); push!(ysegs, [ymin,ymin+ticksz])
# TODO: add the ticklabel
end
gl_draw_lines_2d(xsegs.pts, ysegs.pts, xaxis[:foreground_color_border], 1, sp_screen)
# y axis
xsegs, ysegs = Segments(), Segments()
push!(xsegs, [xmin,xmin]); push!(ysegs, [ymin,ymax])
for tick in PlotUtils.optimize_ticks(xmin, xmax)[1]
push!(xsegs, [xmin,xmin+ticksz]); push!(ysegs, [tick,tick])
# TODO: add the ticklabel
end
gl_draw_lines_2d(xsegs.pts, ysegs.pts, yaxis[:foreground_color_border], 1, sp_screen)
end
# ---------------------------------------------------------------------------
# draw everything
function gl_display(plt::Plot{GLVisualizeBackend})
screen = plt.o
sw, sh = plt[:size]
sw, sh = sw*px, sh*px
for (name, sp) in plt.spmap
_3d = is3d(sp)
camera = _3d ? :perspective : :orthographic_pixel
# camera = :perspective
# initialize the sub-screen for this subplot
# note: we create a lift function to update the size on resize
rel_bbox = bbox_to_pcts(bbox(sp), sw, sh)
f = rect -> gl_viewport(rel_bbox, rect)
sp_screen = GLVisualize.Screen(
screen,
name = name,
area = GLVisualize.const_lift(f, screen.area)
)
sp.o = sp_screen
if !is3d(sp)
gl_draw_axes_2d(sp)
end
# loop over the series and add them to the subplot
for series in series_list(sp)
# TODO: setup series
d = series.d
st = d[:seriestype]
x, y, z = map(Float32, d[:x]), map(Float32, d[:y]), d[:z]
x, y = map(Float32, d[:x]), map(Float32, d[:y])
msize = gl_relative_size(plt, d[:markersize])
if st == :surface
viz = 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, z.surf), false)
z = transpose_z(d, map(Float32, d[:z].surf), false)
viz = GLVisualize.visualize((x, y, z), :surface)
GLVisualize.view(viz, plt.o)
return
GLVisualize.view(viz, sp_screen, camera = :perspective)
else
error("Series type $st not supported by GLVisualize")
end
# paths and scatters
_3d && (z = map(Float32, d[:z]))
# paths?
lw = d[:linewidth]
if lw > 0
c = gl_color(d[:linecolor], d[:linealpha])
if _3d
gl_draw_lines_3d(x, y, z, c, lw, sp_screen)
else
gl_draw_lines_2d(x, y, c, lw, sp_screen)
end
end
# markers?
if st in (:scatter, :scatter3d) || d[:markershape] != :none
extrakw = KW()
c = gl_color(d[:markercolor], d[:markeralpha])
# get the marker
shape = d[:markershape]
shape = get(_gl_marker_map, shape, shape)
marker = if isa(shape, Char)
# extrakw[:scale] = Vec2f0(_3d ? 0.6*d[:markersize] : msize)
extrakw[:scale] = Vec2f0(msize)
shape
else
gl_marker(d[:markershape], msize, _3d)
end
if !_3d
extrakw[:billboard] = true
end
points = _3d ? gl_make_points(x,y,z) : gl_make_points(x,y)
viz = GLVisualize.visualize(
(marker, points);
color = c,
extrakw...
)
GLVisualize.view(viz, sp_screen, camera = camera)
# TODO: might need to switch to these forms later?
# GLVisualize.visualize((marker ,(x, y, z)))
#GLVisualize.visualize((marker , map(Point3f0, zip(x, y, z),
# billboard=true
#))
end
end
end
GLAbstraction.center!(sp_screen, camera)
end
# TODO: render one frame at a time? (no renderloop)
# GLWindow.render_frame(screen)
end
# ----------------------------------------------------------------
function _update_plot_object(plt::Plot{GLVisualizeBackend})
gl_display(plt)
end
# function _writemime(io::IO, ::MIME"image/png", plt::AbstractPlot{GLVisualizeBackend})
# # TODO: write a png to io
# end
function _display(plt::Plot{GLVisualizeBackend})
gl_display(plt)
end
+70 -68
View File
@@ -41,7 +41,6 @@ supported_scales(::GRBackend) = [:identity, :log10]
is_subplot_supported(::GRBackend) = true
function _initialize_backend(::GRBackend; kw...)
@eval begin
import GR
@@ -121,33 +120,6 @@ gr_set_textcolor(c, a=nothing) = GR.settextcolorind(gr_getcolorind(c, a))
# --------------------------------------------------------------------------------------
function gr_setmarkershape(d)
if d[:markershape] != :none
shape = d[:markershape]
if isa(shape, Shape)
d[:vertices] = vertices(shape)
else
GR.setmarkertype(gr_markertype[shape])
d[:vertices] = :none
end
end
end
function gr_polymarker(d, x, y)
if d[:vertices] != :none
vertices= d[:vertices]
dx = Float64[el[1] for el in vertices] * 0.03
dy = Float64[el[2] for el in vertices] * 0.03
GR.selntran(0)
for i = 1:length(x)
xn, yn = GR.wctondc(x[i], y[i])
GR.fillarea(xn + dx, yn + dy)
end
GR.selntran(1)
else
GR.polymarker(x, y)
end
end
# draw line segments, splitting x/y into contiguous/finite segments
# note: this can be used for shapes by passing func `GR.fillarea`
@@ -260,48 +232,64 @@ function normalize_zvals(zv::AVec)
end
end
# ---------------------------------------------------------
function gr_draw_markers(d::KW, x, y, msize, mz, c, a)
if length(x) > 0
mz == nothing && gr_set_markercolor(c, a)
if typeof(msize) <: Number && mz == nothing
# draw the markers all the same
GR.setmarkersize(msize)
gr_polymarker(d, x, y)
else
# draw each marker differently
for i = 1:length(x)
if mz != nothing
ci = round(Int, 1000 + mz[i] * 255)
GR.setmarkercolorind(ci)
end
GR.setmarkersize(isa(msize, Number) ? msize : msize[mod1(i, length(msize))])
gr_polymarker(d, [x[i]], [y[i]])
# draw ONE Shape
function gr_draw_marker(xi, yi, msize, shape::Shape)
sx, sy = shape_coords(shape)
GR.selntran(0)
xi, yi = GR.wctondc(xi, yi)
GR.fillarea(xi + sx * 0.0015msize,
yi + sy * 0.0015msize)
GR.selntran(1)
end
# draw ONE symbol marker
function gr_draw_marker(xi, yi, msize::Number, shape::Symbol)
GR.setmarkertype(gr_markertype[shape])
GR.setmarkersize(0.3msize)
GR.polymarker([xi], [yi])
end
# draw the markers, one at a time
function gr_draw_markers(d::KW, x, y, msize, mz)
shape = d[:markershape]
if shape != :none
for i=1:length(x)
msize = cycle(msize, i)
cfunc = isa(shape, Shape) ? gr_set_fillcolor : gr_set_markercolor
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], msize*1.2, shape, )
# draw the shape
if mz == nothing
cfunc(d[:markercolor], d[:markeralpha])
else
# pick a color from the pre-loaded gradient
ci = round(Int, 1000 + cycle(mz, i) * 255)
cfuncind(ci)
end
gr_draw_marker(x[i], y[i], msize, shape)
end
end
end
function gr_draw_markers(series::Series, x, y)
isempty(x) && return
d = series.d
msize = 0.5 * d[:markersize]
mz = normalize_zvals(d[:marker_z])
# draw the marker
gr_setmarkershape(d)
GR.setfillintstyle(GR.INTSTYLE_SOLID)
gr_draw_markers(d, x, y, msize, mz, d[:markercolor], d[:markeralpha])
# # draw the stroke
# GR.setfillintstyle(GR.INTSTYLE_HOLLOW)
# gr_draw_markers(d, x, y, msize, mz, d[:markerstrokecolor], d[:markerstrokealpha])
gr_draw_markers(d, x, y, d[:markersize], mz)
if mz != nothing
gr_colorbar(d[:subplot])
end
end
# ---------------------------------------------------------
function gr_set_line(w, style, c, a)
GR.setlinetype(gr_linetype[style])
@@ -495,6 +483,7 @@ function gr_display(sp::Subplot{GRBackend}, w, h, viewport_canvas)
data_lims = gr_xy_axislims(sp)
xmin, xmax, ymin, ymax = data_lims
scale = 0
xtick, ytick = 1, 1
if xmax > xmin && ymax > ymin
# NOTE: for log axes, the major_x and major_y - if non-zero (omit labels) - control the minor grid lines (1 = draw 9 minor grid lines, 2 = no minor grid lines)
# NOTE: for log axes, the x_tick and y_tick - if non-zero (omit axes) - only affect the output appearance (1 = nomal, 2 = scientiic notation)
@@ -775,11 +764,11 @@ function gr_display(sp::Subplot{GRBackend}, w, h, viewport_canvas)
elseif st == :shape
# draw the shapes
gr_set_line(d[:markerstrokewidth], :solid, d[:markerstrokecolor], d[:markerstrokealpha])
gr_set_line(d[:linewidth], :solid, d[:linecolor], d[:linealpha])
gr_polyline(d[:x], d[:y])
# draw the interior
gr_set_fill(d[:markercolor], d[:markeralpha])
gr_set_fill(d[:fillcolor], d[:fillalpha])
gr_polyline(d[:x], d[:y], GR.fillarea)
@@ -845,15 +834,24 @@ function gr_display(sp::Subplot{GRBackend}, w, h, viewport_canvas)
GR.setlinetype(gr_linetype[d[:linestyle]])
GR.polyline([xpos - 0.07, xpos - 0.01], [ypos, ypos])
end
if st == :scatter || d[:markershape] != :none
gr_set_markercolor(d[:markercolor], d[:markeralpha])
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
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
i += 1
lab = d[:label][i]
@@ -895,11 +893,15 @@ const _gr_mimeformats = Dict(
for (mime, fmt) in _gr_mimeformats
@eval function _writemime(io::IO, ::MIME{Symbol($mime)}, plt::Plot{GRBackend})
GR.emergencyclosegks()
wstype = haskey(ENV, "GKS_WSTYPE") ? ENV["GKS_WSTYPE"] : "0"
filepath = tempname() * "." * $fmt
ENV["GKS_WSTYPE"] = $fmt
ENV["GKS_FILEPATH"] = filepath
gr_display(plt)
GR.emergencyclosegks()
write(io, readall("gks." * $fmt))
ENV["GKS_WSTYPE"] = ""
write(io, readall(filepath))
ENV["GKS_WSTYPE"] = wstype
rm(filepath)
end
end
+4 -4
View File
@@ -374,12 +374,12 @@ function plotly_series(plt::Plot, series::Series)
# @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[:markercolor], d[:markeralpha])
d_out[:fillcolor] = webcolor(d[:fillcolor], d[:fillalpha])
if d[:markerstrokewidth] > 0
d_out[:line] = KW(
:color => webcolor(d[:markerstrokecolor], d[:markerstrokealpha]),
:width => d[:markerstrokewidth],
:dash => string(d[:markerstrokestyle]),
:color => webcolor(d[:linecolor], d[:linealpha]),
:width => d[:linewidth],
:dash => string(d[:linestyle]),
)
end
+2 -1
View File
@@ -5,6 +5,7 @@ supported_args(::PlotlyJSBackend) = supported_args(PlotlyBackend())
supported_types(::PlotlyJSBackend) = supported_types(PlotlyBackend())
supported_styles(::PlotlyJSBackend) = supported_styles(PlotlyBackend())
supported_markers(::PlotlyJSBackend) = supported_markers(PlotlyBackend())
supported_scales(::PlotlyJSBackend) = supported_scales(PlotlyBackend())
is_subplot_supported(::PlotlyJSBackend) = true
is_string_supported(::PlotlyJSBackend) = true
@@ -65,7 +66,7 @@ function _writemime(io::IO, ::MIME"image/svg+xml", plt::Plot{PlotlyJSBackend})
end
function _writemime(io::IO, ::MIME"image/png", plt::Plot{PlotlyJSBackend})
tmpfn = tempname() * "png"
tmpfn = tempname() * ".png"
PlotlyJS.savefig(plt.o, tmpfn)
write(io, read(open(tmpfn)))
end
+232 -171
View File
@@ -18,7 +18,9 @@ supported_args(::PyPlotBackend) = merge_with_base_supported([
:guide, :lims, :ticks, :scale, :flip, :rotation,
:tickfont, :guidefont, :legendfont,
:grid, :legend, :colorbar,
:marker_z, :levels,
:marker_z,
:line_z,
:levels,
:ribbon, :quiver, :arrow,
:orientation,
:overwrite_figure,
@@ -28,12 +30,13 @@ supported_args(::PyPlotBackend) = merge_with_base_supported([
:match_dimensions,
:clims,
:inset_subplots,
:dpi,
])
supported_types(::PyPlotBackend) = [
:path, :steppre, :steppost, :shape,
:scatter, :histogram2d, :hexbin, :histogram,
:bar, :sticks,
:hline, :vline, :heatmap, :pie, :image,
:scatter, :hexbin, #:histogram2d, :histogram,
# :bar,
:heatmap, :pie, :image,
:contour, :contour3d, :path3d, :scatter3d, :surface, :wireframe
]
supported_styles(::PyPlotBackend) = [:auto, :solid, :dash, :dot, :dashdot]
@@ -60,7 +63,10 @@ function _initialize_backend(::PyPlotBackend)
const pyticker = PyPlot.pywrap(PyPlot.pyimport("matplotlib.ticker"))
const pycmap = PyPlot.pywrap(PyPlot.pyimport("matplotlib.cm"))
const pynp = PyPlot.pywrap(PyPlot.pyimport("numpy"))
pynp.seterr(invalid="ignore")
const pytransforms = PyPlot.pywrap(PyPlot.pyimport("matplotlib.transforms"))
const pycollections = PyPlot.pywrap(PyPlot.pyimport("matplotlib.collections"))
const pyart3d = PyPlot.pywrap(PyPlot.pyimport("mpl_toolkits.mplot3d.art3d"))
end
# we don't want every command to update the figure
@@ -135,7 +141,7 @@ function py_path(x, y)
mat[i,1] = x[i]
mat[i,2] = y[i]
nan = !ok(x[i], y[i])
codes[i] = if nan
codes[i] = if nan && i>1
_path_CLOSEPOLY
else
lastnan ? _path_MOVETO : _path_LINETO
@@ -184,13 +190,13 @@ function py_stepstyle(seriestype::Symbol)
return "default"
end
# untested... return a FontProperties object from a Plots.Font
function py_font(font::Font)
pyfont.pymember("FontProperties")(
family = font.family,
size = font.size
)
end
# # untested... return a FontProperties object from a Plots.Font
# function py_font(font::Font)
# pyfont.pymember("FontProperties")(
# family = font.family,
# size = font.size
# )
# end
function get_locator_and_formatter(vals::AVec)
pyticker.pymember("FixedLocator")(1:length(vals)), pyticker.pymember("FixedFormatter")(vals)
@@ -323,6 +329,10 @@ function py_bbox_title(ax)
bb
end
function py_dpi_scale(plt::Plot{PyPlotBackend}, ptsz)
ptsz * DPI / plt[:dpi]
end
# ---------------------------------------------------------------------------
# Create the window/figure for this backend.
@@ -339,12 +349,14 @@ function _create_backend_figure(plt::Plot{PyPlotBackend})
end
# clear the figure
PyPlot.clf()
# PyPlot.clf()
fig
end
# Set up the subplot within the backend object.
function _initialize_subplot(plt::Plot{PyPlotBackend}, sp::Subplot{PyPlotBackend})
# function _initialize_subplot(plt::Plot{PyPlotBackend}, sp::Subplot{PyPlotBackend})
function py_init_subplot(plt::Plot{PyPlotBackend}, sp::Subplot{PyPlotBackend})
fig = plt.o
proj = sp[:projection]
proj = (proj in (nothing,:none) ? nothing : string(proj))
@@ -364,7 +376,9 @@ end
# function _series_added(pkg::PyPlotBackend, plt::Plot, d::KW)
# TODO: change this to accept Subplot??
function _series_added(plt::Plot{PyPlotBackend}, series::Series)
# 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]
@@ -406,17 +420,49 @@ function _series_added(plt::Plot{PyPlotBackend}, series::Series)
# line plot
if st in (:path, :path3d, :steppre, :steppost)
if d[:linewidth] > 0
handle = ax[:plot](xyargs...;
label = d[:label],
zorder = plt.n,
color = py_linecolor(d),
linewidth = d[:linewidth],
linestyle = py_linestyle(st, d[:linestyle]),
solid_capstyle = "round",
# dash_capstyle = "round",
drawstyle = py_stepstyle(st)
)[1]
push!(handles, handle)
if d[:line_z] == nothing
handle = ax[:plot](xyargs...;
label = d[:label],
zorder = plt.n,
color = py_linecolor(d),
linewidth = py_dpi_scale(plt, d[:linewidth]),
linestyle = py_linestyle(st, d[:linestyle]),
solid_capstyle = "round",
drawstyle = py_stepstyle(st)
)[1]
push!(handles, handle)
else
# multicolored line segments
n = length(x) - 1
segments = Array(Any,n)
kw = KW(
:label => d[:label],
:zorder => plt.n,
:cmap => py_linecolormap(d),
:linewidth => py_dpi_scale(plt, d[:linewidth]),
:linestyle => py_linestyle(st, d[:linestyle])
)
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))]
end
lc = pyart3d.Line3DCollection(segments; kw...)
lc[:set_array](d[:line_z])
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))]
end
lc = pycollections.LineCollection(segments; kw...)
lc[:set_array](d[:line_z])
ax[:add_collection](lc)
lc
end
push!(handles, handle)
needs_colorbar = true
end
a = d[:arrow]
if a != nothing && !is3d(st) # TODO: handle 3d later
@@ -429,7 +475,7 @@ function _series_added(plt::Plot{PyPlotBackend}, series::Series)
:shrinkB => 0,
:edgecolor => py_linecolor(d),
:facecolor => py_linecolor(d),
:linewidth => d[:linewidth],
:linewidth => py_dpi_scale(plt, d[:linewidth]),
:linestyle => py_linestyle(st, d[:linestyle]),
)
add_arrows(x, y) do xyprev, xy
@@ -445,47 +491,47 @@ function _series_added(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 = plt.n,
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 == :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 = plt.n,
# 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 = plt.n,
color = py_linecolor(d),
edgecolor = py_linecolor(d),
linewidth = d[:linewidth],
align = "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 = plt.n,
# 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,
:scatter3d, :steppre, :steppost,
:bar, :sticks)
:bar)
extrakw = KW()
if d[:marker_z] == nothing
extrakw[:c] = py_color_fix(py_markercolor(d), x)
@@ -493,13 +539,13 @@ function _series_added(plt::Plot{PyPlotBackend}, series::Series)
extrakw[:c] = convert(Vector{Float64}, d[:marker_z])
extrakw[:cmap] = py_markercolormap(d)
clims = sp[:clims]
if isa(clims, Tuple) && length(clims) == 2
if is_2tuple(clims)
isfinite(clims[1]) && (extrakw[:vmin] = clims[1])
isfinite(clims[2]) && (extrakw[:vmax] = clims[2])
end
needs_colorbar = true
end
xyargs = if st in (:bar, :sticks) && !isvertical(d)
xyargs = if st == :bar && !isvertical(d)
(y, x)
else
xyargs
@@ -508,66 +554,66 @@ function _series_added(plt::Plot{PyPlotBackend}, series::Series)
label = d[:label],
zorder = plt.n + 0.5,
marker = py_marker(d[:markershape]),
s = d[:markersize] .^ 2,
s = py_dpi_scale(plt, d[:markersize] .^ 2),
edgecolors = py_markerstrokecolor(d),
linewidths = d[:markerstrokewidth],
linewidths = py_dpi_scale(plt, d[:markerstrokewidth]),
extrakw...
)
push!(handles, handle)
end
if st == :histogram
handle = ax[:hist](y;
label = d[:label],
zorder = plt.n,
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)
# if st == :histogram
# handle = ax[:hist](y;
# label = d[:label],
# zorder = plt.n,
# 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
# # 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 isa(clims, Tuple) && length(clims) == 2
isfinite(clims[1]) && (extrakw[:vmin] = clims[1])
isfinite(clims[2]) && (extrakw[:vmax] = clims[2])
end
handle = ax[:hist2d](x, y;
label = d[:label],
zorder = plt.n,
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
# 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 = plt.n,
# 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
# # 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 isa(clims, Tuple) && length(clims) == 2
if is_2tuple(clims)
isfinite(clims[1]) && (extrakw[:vmin] = clims[1])
isfinite(clims[2]) && (extrakw[:vmax] = clims[2])
end
@@ -575,7 +621,7 @@ function _series_added(plt::Plot{PyPlotBackend}, series::Series)
label = d[:label],
zorder = plt.n,
gridsize = d[:bins],
linewidths = d[:linewidth],
linewidths = py_dpi_scale(plt, d[:linewidth]),
edgecolors = py_linecolor(d),
cmap = py_fillcolormap(d), # applies to the pcolorfast object
extrakw...
@@ -584,17 +630,17 @@ function _series_added(plt::Plot{PyPlotBackend}, series::Series)
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 (: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'
@@ -602,7 +648,7 @@ function _series_added(plt::Plot{PyPlotBackend}, series::Series)
needs_colorbar = true
clims = sp[:clims]
if isa(clims, Tuple) && length(clims) == 2
if is_2tuple(clims)
isfinite(clims[1]) && (extrakw[:vmin] = clims[1])
isfinite(clims[2]) && (extrakw[:vmax] = clims[2])
end
@@ -615,7 +661,7 @@ function _series_added(plt::Plot{PyPlotBackend}, series::Series)
handle = ax[:contour](x, y, z, levelargs...;
label = d[:label],
zorder = plt.n,
linewidths = d[:linewidth],
linewidths = py_dpi_scale(plt, d[:linewidth]),
linestyles = py_linestyle(st, d[:linestyle]),
cmap = py_linecolormap(d),
extrakw...
@@ -648,7 +694,7 @@ function _series_added(plt::Plot{PyPlotBackend}, series::Series)
extrakw[:facecolors] = py_shading(d[:fillcolor], d[:marker_z], d[:fillalpha])
extrakw[:shade] = false
clims = sp[:clims]
if isa(clims, Tuple) && length(clims) == 2
if is_2tuple(clims)
isfinite(clims[1]) && (extrakw[:vmin] = clims[1])
isfinite(clims[2]) && (extrakw[:vmax] = clims[2])
end
@@ -662,7 +708,7 @@ function _series_added(plt::Plot{PyPlotBackend}, series::Series)
zorder = plt.n,
rstride = 1,
cstride = 1,
linewidth = d[:linewidth],
linewidth = py_dpi_scale(plt, d[:linewidth]),
edgecolor = py_linecolor(d),
extrakw...
)
@@ -690,7 +736,7 @@ function _series_added(plt::Plot{PyPlotBackend}, series::Series)
elseif typeof(z) <: AbstractVector
# tri-surface plot (http://matplotlib.org/mpl_toolkits/mplot3d/tutorial.html#tri-surface-plots)
clims = sp[:clims]
if isa(clims, Tuple) && length(clims) == 2
if is_2tuple(clims)
isfinite(clims[1]) && (extrakw[:vmin] = clims[1])
isfinite(clims[2]) && (extrakw[:vmax] = clims[2])
end
@@ -698,7 +744,7 @@ function _series_added(plt::Plot{PyPlotBackend}, series::Series)
label = d[:label],
zorder = plt.n,
cmap = py_fillcolormap(d),
linewidth = d[:linewidth],
linewidth = py_dpi_scale(plt, d[:linewidth]),
edgecolor = py_linecolor(d),
extrakw...
)
@@ -744,7 +790,7 @@ function _series_added(plt::Plot{PyPlotBackend}, series::Series)
end
clims = sp[:clims]
if isa(clims, Tuple) && length(clims) == 2
if is_2tuple(clims)
isfinite(clims[1]) && (extrakw[:vmin] = clims[1])
isfinite(clims[2]) && (extrakw[:vmax] = clims[2])
end
@@ -775,9 +821,9 @@ function _series_added(plt::Plot{PyPlotBackend}, series::Series)
patches = pypatches.pymember("PathPatch")(path;
label = d[:label],
zorder = plt.n,
edgecolor = py_markerstrokecolor(d),
facecolor = py_markercolor(d),
linewidth = d[:markerstrokewidth],
edgecolor = py_linecolor(d),
facecolor = py_fillcolor(d),
linewidth = py_dpi_scale(plt, d[:linewidth]),
fill = true
)
handle = ax[:add_patch](patches)
@@ -852,28 +898,28 @@ 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 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 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 _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
# --------------------------------------------------------------------------
@@ -912,7 +958,10 @@ function py_compute_axis_minval(axis::Axis)
minval = 1.0
sp = axis.sp
for series in series_list(axis.sp)
minval = min(minval, minimum(abs(series.d[axis[:letter]])))
v = series.d[axis[:letter]]
if !isempty(v)
minval = min(minval, minimum(abs(v)))
end
end
# now if the axis limits go to a smaller abs value, use that instead
@@ -963,19 +1012,31 @@ end
function _before_layout_calcs(plt::Plot{PyPlotBackend})
# update the specs
# update the fig
w, h = plt[:size]
fig = plt.o
fig[:set_size_inches](px2inch(w), px2inch(h), forward = true)
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]))
fig[:set_dpi](DPI)
fig[:set_dpi](dpi)
# resize the window
PyPlot.plt[:get_current_fig_manager]()[:resize](w, h)
# initialize subplots
for sp in plt.subplots
py_init_subplot(plt, sp)
end
# add the series
for series in plt.series_list
py_add_series(plt, series)
end
# update subplots
for sp in plt.subplots
# ax = getAxis(sp)
ax = sp.o
if ax == nothing
continue
@@ -989,16 +1050,16 @@ function _before_layout_calcs(plt::Plot{PyPlotBackend})
# title
if sp[:title] != ""
loc = lowercase(string(sp[:title_location]))
field = if loc == "left"
func = if loc == "left"
:_left_title
elseif loc == "right"
:_right_title
else
:title
end
ax[field][:set_text](sp[:title])
ax[field][:set_fontsize](sp[:titlefont].pointsize)
ax[field][:set_color](py_color(sp[:foreground_color_title]))
ax[func][:set_text](sp[:title])
ax[func][:set_fontsize](py_dpi_scale(plt, sp[:titlefont].pointsize))
ax[func][:set_color](py_color(sp[:foreground_color_title]))
# ax[:set_title](sp[:title], loc = loc)
end
@@ -1014,9 +1075,9 @@ function _before_layout_calcs(plt::Plot{PyPlotBackend})
if get(axis.d, :flip, false)
ax[Symbol("invert_", letter, "axis")]()
end
ax[axissym][:label][:set_fontsize](axis[:guidefont].pointsize)
ax[axissym][:label][:set_fontsize](py_dpi_scale(plt, axis[:guidefont].pointsize))
for lab in ax[Symbol("get_", letter, "ticklabels")]()
lab[:set_fontsize](axis[:tickfont].pointsize)
lab[:set_fontsize](py_dpi_scale(plt, axis[:tickfont].pointsize))
lab[:set_rotation](axis[:rotation])
end
if sp[:grid]
@@ -1094,7 +1155,7 @@ function py_add_annotations(sp::Subplot{PyPlotBackend}, x, y, val::PlotText)
horizontalalignment = val.font.halign == :hcenter ? "center" : string(val.font.halign),
verticalalignment = val.font.valign == :vcenter ? "center" : string(val.font.valign),
rotation = val.font.rotation * 180 / π,
size = val.font.pointsize,
size = py_dpi_scale(sp.plt, val.font.pointsize),
zorder = 999
)
end
@@ -1137,7 +1198,7 @@ function py_add_legend(plt::Plot, sp::Subplot, ax)
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=4)
PyPlot.plt[:Line2D]((0,1),(0,0), color=py_fillcolor(series.d), linewidth=py_dpi_scale(plt, 4))
else
series.d[:serieshandle][1]
end)
@@ -1151,7 +1212,7 @@ function py_add_legend(plt::Plot, sp::Subplot, ax)
labels,
loc = get(_pyplot_legend_pos, leg, "best"),
scatterpoints = 1,
fontsize = sp[:legendfont].pointsize
fontsize = py_dpi_scale(plt, sp[:legendfont].pointsize)
# framealpha = 0.6
)
leg[:set_zorder](1000)
@@ -1224,7 +1285,7 @@ for (mime, fmt) in _pyplot_mimeformats
# figsize = map(px2inch, plt[:size]),
facecolor = fig.o["get_facecolor"](),
edgecolor = "none",
dpi = DPI
dpi = plt[:dpi]
)
end
end
+53 -182
View File
@@ -4,12 +4,12 @@
supported_args(::UnicodePlotsBackend) = merge_with_base_supported([
:label,
:legend,
:seriescolor, :seriesalpha,
:seriescolor,
:seriesalpha,
:linestyle,
:markershape,
:bins,
:title,
:window_title,
:guide, :lims,
])
supported_types(::UnicodePlotsBackend) = [
@@ -28,19 +28,19 @@ warnOnUnsupported_args(pkg::UnicodePlotsBackend, d::KW) = nothing
# --------------------------------------------------------------------------------------
function _initialize_backend(::UnicodePlotsBackend; kw...)
@eval begin
import UnicodePlots
export UnicodePlots
end
@eval begin
import UnicodePlots
export UnicodePlots
end
end
# -------------------------------
# convert_size_from_pixels(sz) =
# 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)
plt.o = []
for sp in plt.subplots
xaxis = sp[:xaxis]
yaxis = sp[:yaxis]
@@ -58,12 +58,14 @@ function rebuildUnicodePlot!(plt::Plot)
# create a plot window with xlim/ylim set, but the X/Y vectors are outside the bounds
width, height = plt[:size]
o = UnicodePlots.Plot(x, y;
canvas_type = isijulia() ? UnicodePlots.AsciiCanvas : UnicodePlots.BrailleCanvas
o = UnicodePlots.Plot(x, y, canvas_type;
width = width,
height = height,
title = sp[:title],
xlim = xlim,
ylim = ylim
ylim = ylim,
border = isijulia() ? :ascii : :solid
)
# set the axis labels
@@ -80,213 +82,82 @@ function rebuildUnicodePlot!(plt::Plot)
end
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)
#
# # figure out the plotting area xlim = [xmin, xmax] and ylim = [ymin, ymax]
# sargs = plt.seriesargs
# iargs = plt.attr
#
# # get the x/y limits
# if get(iargs, :xlims, :auto) == :auto
# xlim = [Inf, -Inf]
# for d in sargs
# _expand_limits(xlim, d[:x])
# end
# else
# xmin, xmax = iargs[:xlims]
# xlim = [xmin, xmax]
# end
#
# if get(iargs, :ylims, :auto) == :auto
# ylim = [Inf, -Inf]
# for d in sargs
# _expand_limits(ylim, d[:y])
# end
# else
# ymin, ymax = iargs[:ylims]
# ylim = [ymin, ymax]
# end
#
# # we set x/y to have a single point, since we need to create the plot with some data.
# # since this point is at the bottom left corner of the plot, it shouldn't actually be shown
# x = Float64[xlim[1]]
# y = Float64[ylim[1]]
#
# # create a plot window with xlim/ylim set, but the X/Y vectors are outside the bounds
# width, height = iargs[:size]
# o = UnicodePlots.Plot(x, y; width = width,
# height = height,
# title = iargs[:title],
# # labels = iargs[:legend],
# xlim = xlim,
# ylim = ylim)
#
# # set the axis labels
# UnicodePlots.xlabel!(o, iargs[:xguide])
# UnicodePlots.ylabel!(o, iargs[:yguide])
#
# # now use the ! functions to add to the plot
# for d in sargs
# addUnicodeSeries!(o, d, iargs[:legend] != :none, xlim, ylim)
# end
#
# # save the object
# plt.o = o
# end
# add a single series
function addUnicodeSeries!(o, d::KW, addlegend::Bool, xlim, ylim)
# get the function, or special handling for step/bar/hist
st = d[:seriestype]
# handle hline/vline separately
if st in (:hline,:vline)
for yi in d[:y]
if st == :hline
UnicodePlots.lineplot!(o, xlim, [yi,yi])
else
UnicodePlots.lineplot!(o, [yi,yi], ylim)
end
end
return
# elseif st == :bar
# UnicodePlots.barplot!(o, d[:x], d[:y])
# return
# elseif st == :histogram
# UnicodePlots.histogram!(o, d[:y], bins = d[:bins])
# return
elseif st == :histogram2d
if st == :histogram2d
UnicodePlots.densityplot!(o, d[:x], d[:y])
return
end
stepstyle = :post
if st == :path
func = UnicodePlots.lineplot!
elseif st == :scatter || d[:markershape] != :none
func = UnicodePlots.scatterplot!
elseif st == :steppost
func = UnicodePlots.stairs!
elseif st == :steppre
func = UnicodePlots.stairs!
stepstyle = :pre
else
error("Linestyle $st not supported by UnicodePlots")
end
if st == :path
func = UnicodePlots.lineplot!
elseif st == :scatter || d[:markershape] != :none
func = UnicodePlots.scatterplot!
else
error("Linestyle $st not supported by UnicodePlots")
end
# get the series data and label
x, y = [collect(float(d[s])) for s in (:x, :y)]
label = addlegend ? d[:label] : ""
# get the series data and label
x, y = [collect(float(d[s])) for s in (:x, :y)]
label = addlegend ? d[:label] : ""
# if we happen to pass in allowed color symbols, great... otherwise let UnicodePlots decide
color = d[:linecolor] in UnicodePlots.color_cycle ? d[:linecolor] : :auto
# if we happen to pass in allowed color symbols, great... otherwise let UnicodePlots decide
color = d[:linecolor] in UnicodePlots.color_cycle ? d[:linecolor] : :auto
# add the series
func(o, x, y; color = color, name = label, style = stepstyle)
# add the series
func(o, x, y; color = color, name = label)
end
# function handlePlotColors(::UnicodePlotsBackend, d::KW)
# # TODO: something special for unicodeplots, since it doesn't take kindly to people messing with its color palette
# d[:color_palette] = [RGB(0,0,0)]
# end
# -------------------------------
# function _create_plot(pkg::UnicodePlotsBackend, d::KW)
# plt = Plot(nothing, pkg, 0, d, KW[])
function _create_backend_figure(plt::Plot{UnicodePlotsBackend})
# do we want to give a new default size?
# if !haskey(plt.attr, :size) || plt.attr[:size] == default(:size)
# plt.attr[:size] = (60,20)
# end
w, h = plt[:size]
plt.attr[:size] = div(w, 10), div(h, 20)
plt.attr[:color_palette] = [RGB(0,0,0)]
nothing
# plt
end
# function _series_added(plt::Plot{UnicodePlotsBackend}, series::Series)
# d = series.d
# # TODO don't need these once the "bar" series recipe is done
# if d[:seriestype] in (:sticks, :bar)
# d = barHack(; d...)
# elseif d[:seriestype] == :histogram
# d = barHack(; histogramHack(; d...)...)
# end
# # push!(plt.seriesargs, d)
# # plt
# end
#
#
# function _update_plot_object(plt::Plot{UnicodePlotsBackend}, d::KW)
# for k in (:title, :xguide, :yguide, :xlims, :ylims)
# if haskey(d, k)
# plt.attr[k] = d[k]
# end
# end
# end
# -------------------------------
# since this is such a hack, it's only callable using `png`... should error during normal `writemime`
function png(plt::AbstractPlot{UnicodePlotsBackend}, fn::AbstractString)
fn = addExtension(fn, "png")
fn = addExtension(fn, "png")
# make some whitespace and show the plot
println("\n\n\n\n\n\n")
gui(plt)
# make some whitespace and show the plot
println("\n\n\n\n\n\n")
gui(plt)
# @osx_only begin
@compat @static if is_apple()
# BEGIN HACK
# @osx_only begin
@compat @static if is_apple()
# BEGIN HACK
# wait while the plot gets drawn
sleep(0.5)
# wait while the plot gets drawn
sleep(0.5)
# use osx screen capture when my terminal is maximized and cursor starts at the bottom (I know, right?)
# TODO: compute size of plot to adjust these numbers (or maybe implement something good??)
run(`screencapture -R50,600,700,420 $fn`)
# use osx screen capture when my terminal is maximized and cursor starts at the bottom (I know, right?)
# TODO: compute size of plot to adjust these numbers (or maybe implement something good??)
run(`screencapture -R50,600,700,420 $fn`)
# END HACK (phew)
return
end
# END HACK (phew)
return
end
error("Can only savepng on osx with UnicodePlots (though even then I wouldn't do it)")
error("Can only savepng on osx with UnicodePlots (though even then I wouldn't do it)")
end
# -------------------------------
# we don't do very much for subplots... just stack them vertically
# function _create_subplot(subplt::Subplot{UnicodePlotsBackend}, isbefore::Bool)
# isbefore && return false
# true
# end
function _display(plt::Plot{UnicodePlotsBackend})
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)
end
function _writemime(io::IO, ::MIME"text/plain", plt::Plot{UnicodePlotsBackend})
map(show, plt.o)
nothing
end
function _display(plt::Plot{UnicodePlotsBackend})
map(show, plt.o)
nothing
end
# function Base.display(::PlotsDisplay, subplt::Subplot{UnicodePlotsBackend})
# for plt in subplt.plts
# gui(plt)
# end
# end
+33
View File
@@ -1,6 +1,33 @@
abstract ColorScheme
Base.getindex(scheme::ColorScheme, i::Integer) = getColor(scheme, i)
export
cgrad
cgrad() = default_gradient()
function cgrad(arg, values = nothing; alpha = nothing, scale = :identity)
colors = ColorGradient(arg, alpha=alpha).colors
values = if values != nothing
values
elseif scale in (:log, :log10)
log10(linspace(1,10,30))
elseif scale == :log2
log2(linspace(1,2,30))
elseif scale == :ln
log(linspace(1,pi,30))
elseif scale in (:exp, :exp10)
(exp10(linspace(0,1,30)) - 1) / 9
else
linspace(0, 1, length(colors))
end
ColorGradient(colors, values)
end
# --------------------------------------------------------------
getColor(scheme::ColorScheme) = getColor(scheme, 1)
getColorVector(scheme::ColorScheme) = [getColor(scheme)]
@@ -103,6 +130,12 @@ immutable ColorGradient <: ColorScheme
end
end
Base.getindex(cs::ColorGradient, i::Integer) = getColor(cs, i)
Base.getindex(cs::ColorGradient, z::Number) = getColorZ(cs, z)
# create a gradient from a symbol (blues, reds, etc) and vector of boundary values
function ColorGradient{T<:Real}(s::Symbol, vals::AVec{T} = 0:0; kw...)
haskey(_gradients, s) || error("Invalid gradient symbol. Choose from: ", sort(collect(keys(_gradients))))
+2 -2
View File
@@ -303,8 +303,8 @@ PlotExample("Boxplot and Violin series recipes",
[:(begin
import RDatasets
singers = RDatasets.dataset("lattice", "singer")
violin(singers, :VoicePart, :Height, marker = (0.2, :blue, stroke(0)))
boxplot!(singers, :VoicePart, :Height, marker = (0.3, :orange, stroke(2)))
violin(singers, :VoicePart, :Height, line = 0, fill = (0.2, :blue))
boxplot!(singers, :VoicePart, :Height, line = (2,:black), fill = (0.3, :orange))
end)]
)
+43 -4
View File
@@ -101,19 +101,46 @@ end
Base.show(io::IO, layout::AbstractLayout) = print(io, "$(typeof(layout))$(size(layout))")
make_measure_hor(n::Number) = n * w
make_measure_hor(m::Measure) = m
make_measure_vert(n::Number) = n * h
make_measure_vert(m::Measure) = m
function bbox(x, y, w, h, oarg1::Symbol, originargs::Symbol...)
oargs = vcat(oarg1, originargs...)
orighor = :left
origver = :top
for oarg in oargs
if oarg in (:left, :right)
orighor = oarg
elseif oarg in (:top, :bottom)
origver = oarg
else
warn("Unused origin arg in bbox construction: $oarg")
end
end
bbox(x, y, w, h; h_anchor = orighor, v_anchor = origver)
end
# create a new bbox
function bbox(x, y, w, h; h_anchor = :left, v_anchor = :top)
function bbox(x, y, width, height; h_anchor = :left, v_anchor = :top)
x = make_measure_hor(x)
y = make_measure_vert(y)
width = make_measure_hor(width)
height = make_measure_vert(height)
left = if h_anchor == :left
x
else
x - w * (h_anchor == :right ? 1.0 : 0.5)
1w - x - width
end
top = if v_anchor == :top
y
else
y - h * (v_anchor == :bottom ? 1.0 : 0.5)
1h - y - height
end
BoundingBox(left, top, w, h)
BoundingBox(left, top, width, height)
end
# this is the available area for drawing everything in this layout... as percentages of total canvas
@@ -479,6 +506,8 @@ function build_layout(layout::GridLayout, n::Integer)
append!(subplots, sps)
merge!(spmap, m)
i += length(sps)
elseif isa(l, Subplot)
error("Subplot exists. Cannot re-use existing layout. Please make a new one.")
end
i >= n && break # only add n subplots
end
@@ -662,6 +691,16 @@ function link_axes!(layout::GridLayout, link::Symbol)
link_axes!(layout.grid[r,:], :yaxis)
end
end
if link == :square
sps = filter(l -> isa(l, Subplot), layout.grid)
if !isempty(sps)
base_axis = sps[1][:xaxis]
for sp in sps
link_axes!(base_axis, sp[:xaxis])
link_axes!(base_axis, sp[:yaxis])
end
end
end
if link == :all
link_axes!(layout.grid, :xaxis)
link_axes!(layout.grid, :yaxis)
+16 -1
View File
@@ -119,11 +119,13 @@ const _mimeformats = Dict(
"application/pdf" => "pdf",
"image/png" => "png",
"application/postscript" => "ps",
"image/svg+xml" => "svg"
"image/svg+xml" => "svg",
"text/plain" => "txt",
)
const _best_html_output_type = KW(
:pyplot => :png,
:unicodeplots => :txt,
)
# a backup for html... passes to svg or png depending on the html_output_format arg
@@ -138,6 +140,8 @@ function Base.writemime(io::IO, ::MIME"text/html", plt::Plot)
elseif output_type == :svg
# info("writing svg to html output")
writemime(io, MIME("image/svg+xml"), plt)
elseif output_type == :txt
writemime(io, MIME("text/plain"), plt)
else
error("only png or svg allowed. got: $output_type")
end
@@ -145,6 +149,12 @@ 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)
@@ -218,6 +228,11 @@ function setup_ijulia()
global _ijulia_output
Dict{Compat.ASCIIString, ByteString}(_ijulia_output[1] => sprint(writemime, _ijulia_output[1], plt))
end
# default text/plain passes to html... handles Interact issues
function Base.writemime(io::IO, m::MIME"text/plain", plt::Plot)
writemime(io, MIME("text/html"), plt)
end
end
set_ijulia_output("text/html")
end
+145 -79
View File
@@ -162,8 +162,11 @@ 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]
# @show st
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
@@ -172,6 +175,11 @@ function _apply_series_recipe(plt::Plot, d::KW)
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"
@@ -183,6 +191,18 @@ function _apply_series_recipe(plt::Plot, d::KW)
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])
@@ -205,12 +225,13 @@ function _apply_series_recipe(plt::Plot, d::KW)
else
# get a sub list of series for this seriestype
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
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
@@ -224,12 +245,15 @@ function _apply_series_recipe(plt::Plot, d::KW)
end
end
function command_idx(kw_list::AVec{KW}, kw::KW)
kw[:series_plotindex] - kw_list[1][:series_plotindex] + 1
end
# this is the core plotting function. recursively apply recipes to build
# 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 = plt.user_attr
d[:plot_object] = plt
# the grouping mechanism is a recipe on a GroupBy object
@@ -239,6 +263,40 @@ function _plot!(plt::Plot, d::KW, args...)
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
[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
# --------------------------------
# "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.
@@ -246,8 +304,6 @@ function _plot!(plt::Plot, d::KW, args...)
# 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[]
still_to_process = isempty(args) ? [] : [RecipeData(copy(d), args)]
while !isempty(still_to_process)
# grab the first in line to be processed and pass it through apply_recipe
@@ -272,7 +328,12 @@ function _plot!(plt::Plot, d::KW, args...)
# 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[: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
@@ -315,9 +376,6 @@ function _plot!(plt::Plot, d::KW, args...)
:label => "",
:primary => false,
)))
# don't allow something else to handle it
d[:smooth] = false
end
else
@@ -327,25 +385,62 @@ function _plot!(plt::Plot, d::KW, args...)
end
end
# merge in anything meant for plot/subplot/axis
for kw in kw_list
for (k,v) in kw
for defdict in (_plot_defaults,)
# _subplot_defaults,
# _axis_defaults,
# _axis_defaults_byletter)
if haskey(defdict, k)
d[k] = pop!(kw, k)
# don't allow something else to handle it
d[:smooth] = false
# --------------------------------
# "PLOT RECIPES"
# --------------------------------
# "plot recipe", which acts like a series type, and is processed before
# the plot layout is created, which allows for setting layouts and other plot-wide attributes.
# we get inputs which have been fully processed by "user recipes" and "type recipes",
# so we can expect standard vectors, surfaces, etc. No defaults have been set yet.
still_to_process = kw_list
kw_list = KW[]
while !isempty(still_to_process)
# 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
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)
# DD(d)
# create the layout and subplots from the inputs
plt.layout, plt.subplots, plt.spmap = build_layout(plt.attr)
@@ -361,6 +456,9 @@ function _plot!(plt::Plot, d::KW, args...)
# 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)
@@ -374,27 +472,24 @@ function _plot!(plt::Plot, d::KW, args...)
sp = Subplot(backend(), parent=parent)
sp.plt = plt
sp.attr[:relative_bbox] = bb
push!(plt.subplots, sp)
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.
# any series which belong to that subplot
# Subplot/Axis attributes set by a user/series recipe apply only to the
# Subplot object which they belong to.
# TODO: allow matrices to still apply to all subplots
sp_attrs = Dict{Subplot,Any}()
for (i,kw) in enumerate(kw_list)
# get the Subplot object to which the series belongs
sp = get(kw, :subplot, :auto)
sp = if sp == :auto
mod1(i,length(plt.subplots))
else
slice_arg(sp, i)
end
sp = kw[:subplot] = get_subplot(plt, sp)
# idx = get_subplot_index(plt, sp)
attr = KW()
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,
@@ -407,60 +502,31 @@ function _plot!(plt::Plot, d::KW, args...)
sp_attrs[sp] = attr
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
# override subplot/axis args. `sp_attrs` take precendence
for (idx,sp) in enumerate(plt.subplots)
# if we picked up any subplot-specific overrides, merge them here
attr = merge(d, get(sp_attrs, sp, KW()))
# DD(attr, "sp$idx")
_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])
# !!! 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 !!!
# this is it folks!
# TODO: we probably shouldn't use i for tracking series index, but rather explicitly track it in recipes
for (i,kw) in enumerate(kw_list)
command_idx = kw[:series_plotindex] - kw_list[1][:series_plotindex] + 1
# # get the Subplot object to which the series belongs
# sp = get(kw, :subplot, :auto)
# sp = if sp == :auto
# mod1(i,length(plt.subplots))
# else
# slice_arg(sp, i)
# end
# sp = kw[:subplot] = get_subplot(plt, sp)
# --------------------------------
# "SERIES RECIPES"
# --------------------------------
for kw in kw_list
sp = kw[:subplot]
idx = get_subplot_index(plt, sp)
# 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!(kw, :series_annotations, []))
if length(anns) > 0
x, y = kw[:x], kw[:y]
nx, ny, na = map(length, (x,y,anns))
n = max(nx, ny, na)
anns = [(x[mod1(i,nx)], y[mod1(i,ny)], text(anns[mod1(i,na)])) for i=1:n]
end
sp.attr[:annotations] = vcat(sp_anns, anns)
# we update subplot args in case something like the color palatte is part of the recipe
_update_subplot_args(plt, sp, kw, idx)
# # we update subplot args in case something like the color palatte is part of the recipe
# _update_subplot_args(plt, sp, kw, idx)
# set default values, select from attribute cycles, and generally set the final attributes
_add_defaults!(kw, plt, sp, command_idx)
_add_defaults!(kw, plt, sp, command_idx(kw_list,kw))
# now we have a fully specified series, with colors chosen. we must recursively handle
# series recipes, which dispatch on seriestype. If a backend does not natively support a seriestype,
@@ -471,12 +537,12 @@ function _plot!(plt::Plot, d::KW, args...)
_apply_series_recipe(plt, kw)
end
# --------------------------------
current(plt)
# note: lets ignore the show param and effectively use the semicolon at the end of the REPL statement
# # do we want to show it?
# if haskey(d, :show) && d[:show]
if get(d, :show, default(:show))
# do we want to force display?
if plt[:show]
gui()
end
+267 -298
View File
@@ -98,19 +98,21 @@ end
# ----------------------------------------------------------------------------------
abstract PlotRecipe
# abstract PlotRecipe
getRecipeXY(recipe::PlotRecipe) = Float64[], Float64[]
getRecipeArgs(recipe::PlotRecipe) = ()
# getRecipeXY(recipe::PlotRecipe) = Float64[], Float64[]
# getRecipeArgs(recipe::PlotRecipe) = ()
plot(recipe::PlotRecipe, args...; kw...) = plot(getRecipeXY(recipe)..., args...; getRecipeArgs(recipe)..., kw...)
plot!(recipe::PlotRecipe, args...; kw...) = plot!(getRecipeXY(recipe)..., args...; getRecipeArgs(recipe)..., kw...)
plot!(plt::Plot, recipe::PlotRecipe, args...; kw...) = plot!(getRecipeXY(recipe)..., args...; getRecipeArgs(recipe)..., kw...)
# plot(recipe::PlotRecipe, args...; kw...) = plot(getRecipeXY(recipe)..., args...; getRecipeArgs(recipe)..., kw...)
# plot!(recipe::PlotRecipe, args...; kw...) = plot!(getRecipeXY(recipe)..., args...; getRecipeArgs(recipe)..., kw...)
# plot!(plt::Plot, recipe::PlotRecipe, args...; kw...) = plot!(getRecipeXY(recipe)..., args...; getRecipeArgs(recipe)..., kw...)
num_series(x::AMat) = size(x,2)
num_series(x) = 1
RecipesBase.apply_recipe{T}(d::KW, ::Type{T}, plt::Plot) = throw(MethodError("Unmatched plot recipe: $T"))
# # if it's not a recipe, just do nothing and return the args
# function RecipesBase.apply_recipe(d::KW, args...; issubplot=false)
# if issubplot && !isempty(args) && !haskey(d, :n) && !haskey(d, :layout)
@@ -214,8 +216,20 @@ end
# end
# @deps sticks path
function hvline_limits(axis::Axis)
vmin, vmax = axis_limits(axis)
if vmin >= vmax
if isfinite(vmin)
vmax = vmin + 1
else
vmin, vmax = 0.0, 1.1
end
end
vmin, vmax
end
@recipe function f(::Type{Val{:hline}}, x, y, z)
xmin, xmax = axis_limits(d[:subplot][:xaxis])
xmin, xmax = hvline_limits(d[:subplot][:xaxis])
n = length(y)
newx = repmat(Float64[xmin, xmax, NaN], n)
newy = vec(Float64[yi for i=1:3,yi=y])
@@ -227,7 +241,7 @@ end
@deps hline path
@recipe function f(::Type{Val{:vline}}, x, y, z)
ymin, ymax = axis_limits(d[:subplot][:yaxis])
ymin, ymax = hvline_limits(d[:subplot][:yaxis])
n = length(y)
newx = vec(Float64[yi for i=1:3,yi=y])
newy = repmat(Float64[ymin, ymax, NaN], n)
@@ -338,10 +352,83 @@ end
@deps sticks path scatter
# ---------------------------------------------------------------------------
# bezier curves
# get the value of the curve point at position t
function bezier_value(pts::AVec, t::Real)
val = 0.0
n = length(pts)-1
for (i,p) in enumerate(pts)
val += p * binomial(n, i-1) * (1-t)^(n-i+1) * t^(i-1)
end
val
end
# create segmented bezier curves in place of line segments
@recipe function f(::Type{Val{:curves}}, x, y, z)
args = z != nothing ? (x,y,z) : (x,y)
newx, newy = zeros(0), zeros(0)
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
npoints = pop!(d, :npoints, 30)
# 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
for rng in iter_segments(args...)
length(rng) < 2 && continue
ts = linspace(0, 1, npoints)
nanappend!(newx, map(t -> bezier_value(cycle(x,rng), t), ts))
nanappend!(newy, map(t -> bezier_value(cycle(y,rng), t), ts))
if z != nothing
nanappend!(newz, map(t -> bezier_value(cycle(z,rng), t), ts))
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
# @show lzrng, sizeof(lzrng) map(t -> 1+floor(Int, t * (length(rng)-1)), ts)
# choose the line_z value of the control point just before this t
push!(newlz, 0.0)
append!(newlz, map(t -> lzrng[1+floor(Int, t * (length(rng)-1))], ts))
# lzrng = vcat()
# nanappend!(newlz, #map(t -> bezier_value(cycle(lz,rng), t), ts))
end
end
x := newx
y := newy
if z == nothing
seriestype := :path
else
seriestype := :path3d
z := newz
end
if fr != nothing
fillrange := newfr
end
if lz != nothing
line_z := newlz
linecolor := (isa(d[:linecolor], ColorGradient) ? d[:linecolor] : default_gradient())
end
# Plots.DD(d)
()
end
@deps curves path
# ---------------------------------------------------------------------------
# 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
@@ -365,36 +452,25 @@ end
# make fillto a vector... default fills to 0
fillto = d[:fillrange]
if fillto == nothing
fillto = zeros(1)
elseif isa(fillto, Number)
fillto = Float64[fillto]
fillto = 0
end
nf = length(fillto)
npts = 3ny + 1
heights = y
x = zeros(npts)
y = zeros(npts)
fillrng = zeros(npts)
# create the path in triplets. after the first bottom-left coord of the first bar:
# add the top-left, top-right, and bottom-right coords for each height
x[1] = edges[1]
y[1] = fillto[1]
fillrng[1] = fillto[1]
# create the bar shapes by adding x/y segments
xseg, yseg = Segments(), Segments()
for i=1:ny
idx = 3i
rng = idx-1:idx+1
fi = fillto[mod1(i,nf)]
x[rng] = [edges[i], edges[i+1], edges[i+1]]
y[rng] = [heights[i], heights[i], fi]
fillrng[rng] = [fi, fi, fi]
fi = cycle(fillto,i)
push!(xseg, edges[i], edges[i], edges[i+1], edges[i+1])
push!(yseg, y[i], fi, fi, y[i])
end
x := x
y := y
fillrange := fillrng
seriestype := :path
# switch back
if !isvertical(d)
xseg, yseg = yseg, xseg
end
x := xseg.pts
y := yseg.pts
seriestype := :shape
()
end
@deps bar path
@@ -489,6 +565,7 @@ centers(v::AVec) = v[1] + cumsum(diff(v))
x := centers(xedges)
y := centers(yedges)
z := Surface(counts)
linewidth := 0
seriestype := :heatmap
()
end
@@ -517,27 +594,25 @@ const _box_halfwidth = 0.4
notch_width(q2, q4, N) = 1.58 * (q4-q2)/sqrt(N)
# function apply_series_recipe(d::KW, ::Type{Val{:box}})
@recipe function f(::Type{Val{:boxplot}}, x, y, z; notch=false, range=1.5)
# Plots.dumpdict(d, "box before", true)
# create a list of shapes, where each shape is a single boxplot
shapes = Shape[]
groupby = extractGroupArgs(x)
outliers_y = Float64[]
outliers_x = Float64[]
delete!(d, :notch)
delete!(d, :range)
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))]
for (i, glabel) in enumerate(groupby.groupLabels)
# filter y values
values = d[:y][groupby.groupIds[i]]
# then compute quantiles
# compute quantiles
q1,q2,q3,q4,q5 = quantile(values, linspace(0,1,5))
# 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
@@ -546,8 +621,10 @@ notch_width(q2, q4, N) = 1.58 * (q4-q2)/sqrt(N)
# make the shape
center = discrete_value!(d[:subplot][:xaxis], glabel)[1]
l, m, r = center - _box_halfwidth, center, center + _box_halfwidth
# internal nodes for notches
L, R = center - 0.5 * _box_halfwidth, center + 0.5 * _box_halfwidth
# outliers
if Float64(range) != 0.0 # if the range is 0.0, the whiskers will extend to the data
limit = range*(q4-q2)
@@ -564,58 +641,144 @@ notch_width(q2, q4, N) = 1.58 * (q4-q2)/sqrt(N)
# using maximum and minimum values inside the limits
q1, q5 = extrema(inside)
end
# Box
xcoords = notch::Bool ? [
m, l, r, m, m, NaN, # lower T
l, l, L, R, r, r, l, NaN, # lower box
l, l, L, R, r, r, l, NaN, # upper box
m, l, r, m, m, NaN, # upper T
] : [
m, l, r, m, m, NaN, # lower T
l, l, r, r, l, NaN, # lower box
l, l, r, r, l, NaN, # upper box
m, l, r, m, m, NaN, # upper T
]
ycoords = notch::Bool ? [
q1, q1, q1, q1, q2, NaN, # lower T
q2, q3-n, q3, q3, q3-n, q2, q2, NaN, # lower box
q4, q3+n, q3, q3, q3+n, q4, q4, NaN, # upper box
q5, q5, q5, q5, q4, NaN, # upper T
] : [
q1, q1, q1, q1, q2, NaN, # lower T
q2, q3, q3, q2, q2, NaN, # lower box
q4, q3, q3, q4, q4, NaN, # upper box
q5, q5, q5, q5, q4, NaN, # upper T
]
push!(shapes, Shape(xcoords, ycoords))
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, 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
# d[:plotarg_overrides] = KW(:xticks => (1:length(shapes), groupby.groupLabels))
seriestype := :shape
# n = length(groupby.groupLabels)
# xticks --> (linspace(0.5,n-0.5,n), groupby.groupLabels)
# clean d
pop!(d, :notch)
pop!(d, :range)
# we want to set the fields directly inside series recipes... args are ignored
d[:x], d[:y] = Plots.shape_coords(shapes)
# Outliers
@series begin
seriestype := :scatter
markershape := :circle
x := outliers_x
y := outliers_y
label := ""
primary := false
seriestype := :scatter
markershape --> :circle
x := outliers_x
y := outliers_y
primary := false
()
end
() # expects a tuple returned
seriestype := :shape
x := xsegs.pts
y := ysegs.pts
()
end
# @recipe function f(::Type{Val{:boxplot}}, x, y, z; notch=false, range=1.5)
# # Plots.dumpdict(d, "box before", true)
# # create a list of shapes, where each shape is a single boxplot
# shapes = Shape[]
# groupby = extractGroupArgs(x)
# outliers_y = Float64[]
# outliers_x = Float64[]
# warning = false
# for (i, glabel) in enumerate(groupby.groupLabels)
# # filter y values
# values = d[:y][groupby.groupIds[i]]
# # then compute quantiles
# q1,q2,q3,q4,q5 = quantile(values, linspace(0,1,5))
# # notch
# n = notch_width(q2, q4, length(values))
# 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
# # internal nodes for notches
# L, R = center - 0.5 * _box_halfwidth, center + 0.5 * _box_halfwidth
# # 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
# xcoords = notch::Bool ? [
# m, l, r, m, m, NaN, # lower T
# l, l, L, R, r, r, l, NaN, # lower box
# l, l, L, R, r, r, l, NaN, # upper box
# m, l, r, m, m, NaN, # upper T
# ] : [
# m, l, r, m, m, NaN, # lower T
# l, l, r, r, l, NaN, # lower box
# l, l, r, r, l, NaN, # upper box
# m, l, r, m, m, NaN, # upper T
# ]
# ycoords = notch::Bool ? [
# q1, q1, q1, q1, q2, NaN, # lower T
# q2, q3-n, q3, q3, q3-n, q2, q2, NaN, # lower box
# q4, q3+n, q3, q3, q3+n, q4, q4, NaN, # upper box
# q5, q5, q5, q5, q4, NaN, # upper T
# ] : [
# q1, q1, q1, q1, q2, NaN, # lower T
# q2, q3, q3, q2, q2, NaN, # lower box
# q4, q3, q3, q4, q4, NaN, # upper box
# q5, q5, q5, q5, q4, NaN, # upper T
# ]
# push!(shapes, Shape(xcoords, ycoords))
# end
# # d[:plotarg_overrides] = KW(:xticks => (1:length(shapes), groupby.groupLabels))
# seriestype := :shape
# # n = length(groupby.groupLabels)
# # xticks --> (linspace(0.5,n-0.5,n), groupby.groupLabels)
# # clean d
# pop!(d, :notch)
# pop!(d, :range)
# # we want to set the fields directly inside series recipes... args are ignored
# d[:x], d[:y] = Plots.shape_coords(shapes)
# # Outliers
# @series begin
# seriestype := :scatter
# markershape --> :circle
# x := outliers_x
# y := outliers_y
# primary := false
# ()
# end
# () # expects a tuple returned
# end
@deps boxplot shape scatter
# ---------------------------------------------------------------------------
@@ -644,20 +807,13 @@ else
end
# function apply_series_recipe(d::KW, ::Type{Val{:violin}})
@recipe function f(::Type{Val{:violin}}, x, y, z; trim=true)
# dumpdict(d, "box before", true)
# TODO: add scatter series with outliers
# create a list of shapes, where each shape is a single boxplot
shapes = Shape[]
groupby = extractGroupArgs(d[:x])
for (i, glabel) in enumerate(groupby.groupLabels)
# get the edges and widths
y = d[:y][groupby.groupIds[i]]
widths, centers = violin_coords(y, trim=trim)
delete!(d, :trim)
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)
@@ -666,18 +822,14 @@ end
xcenter = discrete_value!(d[:subplot][:xaxis], glabel)[1]
xcoords = vcat(widths, -reverse(widths)) + xcenter
ycoords = vcat(centers, reverse(centers))
push!(shapes, Shape(xcoords, ycoords))
push!(xsegs, xcoords)
push!(ysegs, ycoords)
end
# d[:plotarg_overrides] = KW(:xticks => (1:length(shapes), groupby.groupLabels))
seriestype := :shape
# n = length(groupby.groupLabels)
# xticks --> (linspace(0.5,n-0.5,n), groupby.groupLabels)
# clean up d
pop!(d, :trim)
d[:x], d[:y] = shape_coords(shapes)
x := xsegs.pts
y := ysegs.pts
()
end
@deps violin shape
@@ -971,6 +1123,9 @@ end
# -------------------------------------------------
# TODO: everything below here should be either changed to a
# series recipe or moved to PlotRecipes
"Sparsity plot... heatmap of non-zero values of a matrix"
function spy{T<:Real}(z::AMat{T}; kw...)
@@ -987,189 +1142,3 @@ function abline!(plt::Plot, a, b; kw...)
end
abline!(args...; kw...) = abline!(current(), args...; kw...)
# =================================================
# Arc and chord diagrams
"Takes an adjacency matrix and returns source, destiny and weight lists"
function mat2list{T}(mat::AbstractArray{T,2})
nrow, ncol = size(mat) # rows are sources and columns are destinies
nosymmetric = !issym(mat) # plots only triu for symmetric matrices
nosparse = !issparse(mat) # doesn't plot zeros from a sparse matrix
L = length(mat)
source = Array(Int, L)
destiny = Array(Int, L)
weight = Array(T, L)
idx = 1
for i in 1:nrow, j in 1:ncol
value = mat[i, j]
if !isnan(value) && ( nosparse || value != zero(T) ) # TODO: deal with Nullable
if i < j
source[idx] = i
destiny[idx] = j
weight[idx] = value
idx += 1
elseif nosymmetric && (i > j)
source[idx] = i
destiny[idx] = j
weight[idx] = value
idx += 1
end
end
end
resize!(source, idx-1), resize!(destiny, idx-1), resize!(weight, idx-1)
end
# ---------------------------------------------------------------------------
# Arc Diagram
curvecolor(value, min, max, grad) = getColorZ(grad, (value-min)/(max-min))
"Plots a clockwise arc, from source to destiny, colored by weight"
function arc!(source, destiny, weight, min, max, grad)
radius = (destiny - source) / 2
arc = Plots.partialcircle(0, π, 30, radius)
x, y = Plots.unzip(arc)
plot!(x .+ radius .+ source, y, line = (curvecolor(weight, min, max, grad), 0.5, 2), legend=false)
end
"""
`arcdiagram(source, destiny, weight[, grad])`
Plots an arc diagram, form `source` to `destiny` (clockwise), using `weight` to determine the colors.
"""
function arcdiagram(source, destiny, weight; kargs...)
args = KW(kargs)
grad = pop!(args, :grad, ColorGradient([colorant"darkred", colorant"darkblue"]))
if length(source) == length(destiny) == length(weight)
vertices = unique(vcat(source, destiny))
sort!(vertices)
xmin, xmax = extrema(vertices)
plot(xlim=(xmin - 0.5, xmax + 0.5), legend=false)
wmin,wmax = extrema(weight)
for (i, j, value) in zip(source,destiny,weight)
arc!(i, j, value, wmin, wmax, grad)
end
scatter!(vertices, zeros(length(vertices)); legend=false, args...)
else
throw(ArgumentError("source, destiny and weight should have the same length"))
end
end
"""
`arcdiagram(mat[, grad])`
Plots an arc diagram from an adjacency matrix, form rows to columns (clockwise),
using the values on the matrix as weights to determine the colors.
Doesn't show edges with value zero if the input is sparse.
For simmetric matrices, only the upper triangular values are used.
"""
arcdiagram{T}(mat::AbstractArray{T,2}; kargs...) = arcdiagram(mat2list(mat)...; kargs...)
# ---------------------------------------------------------------------------
# Chord diagram
arcshape(θ1, θ2) = Shape(vcat(Plots.partialcircle(θ1, θ2, 15, 1.1),
reverse(Plots.partialcircle(θ1, θ2, 15, 0.9))))
colorlist(grad, ::Void) = :darkgray
function colorlist(grad, z)
zmin, zmax = extrema(z)
RGBA{Float64}[getColorZ(grad, (zi-zmin)/(zmax-zmin)) for zi in z]'
end
"""
`chorddiagram(source, destiny, weight[, grad, zcolor, group])`
Plots a chord diagram, form `source` to `destiny`,
using `weight` to determine the edge colors using `grad`.
`zcolor` or `group` can be used to determine the node colors.
"""
function chorddiagram(source, destiny, weight; kargs...)
args = KW(kargs)
grad = pop!(args, :grad, ColorGradient([colorant"darkred", colorant"darkblue"]))
zcolor= pop!(args, :zcolor, nothing)
group = pop!(args, :group, nothing)
if zcolor !== nothing && group !== nothing
throw(ErrorException("group and zcolor can not be used together."))
end
if length(source) == length(destiny) == length(weight)
plt = plot(xlim=(-2,2), ylim=(-2,2), legend=false, grid=false,
xticks=nothing, yticks=nothing,
xlim=(-1.2,1.2), ylim=(-1.2,1.2))
nodemin, nodemax = extrema(vcat(source, destiny))
weightmin, weightmax = extrema(weight)
A = 1.5π # Filled space
B = 0.5π # White space (empirical)
Δα = A / nodemax
Δβ = B / nodemax
δ = Δα + Δβ
for i in 1:length(source)
curve = BezierCurve(P2[ (cos((source[i ]-1)*δ + 0.5Δα), sin((source[i ]-1)*δ + 0.5Δα)), (0,0),
(cos((destiny[i]-1)*δ + 0.5Δα), sin((destiny[i]-1)*δ + 0.5Δα)) ])
plot!(curve_points(curve), line = (Plots.curvecolor(weight[i], weightmin, weightmax, grad), 1, 1))
end
if group === nothing
c = colorlist(grad, zcolor)
elseif length(group) == nodemax
idx = collect(0:(nodemax-1))
for g in group
plot!([arcshape(n*δ, n*δ + Δα) for n in idx[group .== g]]; args...)
end
return plt
else
throw(ErrorException("group should the ", nodemax, " elements."))
end
plot!([arcshape(n*δ, n*δ + Δα) for n in 0:(nodemax-1)]; mc=c, args...)
return plt
else
throw(ArgumentError("source, destiny and weight should have the same length"))
end
end
"""
`chorddiagram(mat[, grad, zcolor, group])`
Plots a chord diagram from an adjacency matrix,
using the values on the matrix as weights to determine edge colors.
Doesn't show edges with value zero if the input is sparse.
For simmetric matrices, only the upper triangular values are used.
`zcolor` or `group` can be used to determine the node colors.
"""
chorddiagram(mat::AbstractMatrix; kargs...) = chorddiagram(mat2list(mat)...; kargs...)
+2 -453
View File
@@ -7,7 +7,7 @@
typealias FuncOrFuncs @compat(Union{Function, AVec{Function}})
all3D(d::KW) = trueOrAllTrue(st -> st in (:contour, :heatmap, :surface, :wireframe, :contour3d), get(d, :seriestype, :none))
all3D(d::KW) = trueOrAllTrue(st -> st in (:contour, :heatmap, :surface, :wireframe, :contour3d, :image), get(d, :seriestype, :none))
# missing
convertToAnyVector(v::@compat(Void), d::KW) = Any[nothing], nothing
@@ -21,8 +21,7 @@ convertToAnyVector{T<:Number}(v::AVec{T}, d::KW) = Any[v], nothing
# string vector
convertToAnyVector{T<:@compat(AbstractString)}(v::AVec{T}, d::KW) = Any[v], nothing
# numeric matrix
function convertToAnyVector{T<:Number}(v::AMat{T}, d::KW)
function convertToAnyVector(v::AMat, d::KW)
if all3D(d)
Any[Surface(v)]
else
@@ -30,14 +29,6 @@ function convertToAnyVector{T<:Number}(v::AMat{T}, d::KW)
end, nothing
end
# other matrix... vector of columns
function convertToAnyVector(m::AMat, d::KW)
Any[begin
v = vec(m[:,i])
length(v) == 1 ? v[1] : v
end for i=1:size(m,2)], nothing
end
# function
convertToAnyVector(f::Function, d::KW) = Any[f], nothing
@@ -106,445 +97,3 @@ compute_xyz(x::Void, y::Void, z::FuncOrFuncs) = error("If you want to plot the f
compute_xyz(x::Void, y::Void, z::Void) = error("x/y/z are all nothing!")
# --------------------------------------------------------------------
# # create n=max(mx,my) series arguments. the shorter list is cycled through
# # note: everything should flow through this
# function build_series_args(plt::AbstractPlot, kw::KW) #, idxfilter)
# x, y, z = map(sym -> pop!(kw, sym, nothing), (:x, :y, :z))
# if nothing == x == y == z
# return [], nothing, nothing
# end
#
# xs, xmeta = convertToAnyVector(x, kw)
# ys, ymeta = convertToAnyVector(y, kw)
# zs, zmeta = convertToAnyVector(z, kw)
#
# fr = pop!(kw, :fillrange, nothing)
# fillranges, _ = if typeof(fr) <: Number
# ([fr],nothing)
# else
# convertToAnyVector(fr, kw)
# end
#
# mx = length(xs)
# my = length(ys)
# mz = length(zs)
# ret = Any[]
# for i in 1:max(mx, my, mz)
#
# # try to set labels using ymeta
# d = copy(kw)
# if !haskey(d, :label) && ymeta != nothing
# if isa(ymeta, Symbol)
# d[:label] = string(ymeta)
# elseif isa(ymeta, AVec{Symbol})
# d[:label] = string(ymeta[mod1(i,length(ymeta))])
# end
# end
#
# # build the series arg dict
# numUncounted = pop!(d, :numUncounted, 0)
# commandIndex = i + numUncounted
# n = plt.n + i
#
# dumpdict(d, "before getSeriesArgs")
# d = getSeriesArgs(plt.backend, getattr(plt, n), d, commandIndex, convertSeriesIndex(plt, n), n)
# dumpdict(d, "after getSeriesArgs")
#
# d[:x], d[:y], d[:z] = compute_xyz(xs[mod1(i,mx)], ys[mod1(i,my)], zs[mod1(i,mz)])
# st = d[:seriestype]
#
# # for seriestype `line`, need to sort by x values
# if st == :line
# # order by x
# indices = sortperm(d[:x])
# d[:x] = d[:x][indices]
# d[:y] = d[:y][indices]
# d[:seriestype] = :path
# end
#
# # special handling for missing x in box plot... all the same category
# if st == :box && xs[mod1(i,mx)] == nothing
# d[:x] = ones(Int, length(d[:y]))
# end
#
# # map functions to vectors
# if isa(d[:marker_z], Function)
# d[:marker_z] = map(d[:marker_z], d[:x])
# end
#
# # @show fillranges
# d[:fillrange] = fillranges[mod1(i,length(fillranges))]
# if isa(d[:fillrange], Function)
# d[:fillrange] = map(d[:fillrange], d[:x])
# end
#
# # handle error bars
# for esym in (:xerror, :yerror)
# if get(d, esym, nothing) != nothing
# # we make a copy of the KW and apply an errorbar recipe
# append!(ret, apply_series_recipe(copy(d), Val{esym}))
# end
# end
#
# # handle ribbons
# if get(d, :ribbon, nothing) != nothing
# rib = d[:ribbon]
# d[:fillrange] = (d[:y] - rib, d[:y] + rib)
# end
#
# # handle quiver plots
# # either a series of velocity vectors are passed in (`:quiver` keyword),
# # or we just add arrows to the path
#
# # if st == :quiver
# # d[:seriestype] = st = :path
# # d[:linewidth] = 0
# # end
# if get(d, :quiver, nothing) != nothing
# append!(ret, apply_series_recipe(copy(d), Val{:quiver}))
# elseif st == :quiver
# d[:seriestype] = st = :path
# d[:arrow] = arrow()
# end
#
# # now that we've processed a given series... optionally split into
# # multiple dicts through a recipe (for example, a box plot is split into component
# # parts... polygons, lines, and scatters)
# # note: we pass in a Val type (i.e. Val{:box}) so that we can dispatch on the seriestype
# kwlist = apply_series_recipe(d, Val{st})
# append!(ret, kwlist)
#
# # # add it to our series list
# # push!(ret, d)
# end
#
# ret, xmeta, ymeta
# end
#
#
# # --------------------------------------------------------------------
# # process_inputs
# # --------------------------------------------------------------------
#
# # These methods take a plot and the keyword arguments, and processes the input
# # arguments (x/y/z, group, etc), populating the KW dict with appropriate values.
#
# # --------------------------------------------------------------------
# # 0 arguments
# # --------------------------------------------------------------------
#
# # don't do anything
# function process_inputs(plt::AbstractPlot, d::KW)
# end
#
# # --------------------------------------------------------------------
# # 1 argument
# # --------------------------------------------------------------------
#
# function process_inputs(plt::AbstractPlot, d::KW, n::Integer)
# # d[:x], d[:y], d[:z] = zeros(0), zeros(0), zeros(0)
# d[:x] = d[:y] = d[:z] = n
# end
#
# # no special handling... assume x and z are nothing
# function process_inputs(plt::AbstractPlot, d::KW, y)
# d[:y] = y
# end
#
# # matrix... is it z or y?
# function process_inputs{T<:Number}(plt::AbstractPlot, d::KW, mat::AMat{T})
# if all3D(d)
# n,m = size(mat)
# d[:x], d[:y], d[:z] = 1:n, 1:m, mat
# else
# d[:y] = mat
# end
# end
#
# # images - grays
# function process_inputs{T<:Gray}(plt::AbstractPlot, d::KW, mat::AMat{T})
# d[:seriestype] = :image
# n,m = size(mat)
# d[:x], d[:y], d[:z] = 1:n, 1:m, Surface(mat)
# # handle images... when not supported natively, do a hack to use heatmap machinery
# if !nativeImagesSupported()
# d[:seriestype] = :heatmap
# d[:yflip] = true
# d[:z] = Surface(convert(Matrix{Float64}, mat.surf))
# d[:fillcolor] = ColorGradient([:black, :white])
# end
# end
#
# # images - colors
# function process_inputs{T<:Colorant}(plt::AbstractPlot, d::KW, mat::AMat{T})
# d[:seriestype] = :image
# n,m = size(mat)
# d[:x], d[:y], d[:z] = 1:n, 1:m, Surface(mat)
# # handle images... when not supported natively, do a hack to use heatmap machinery
# if !nativeImagesSupported()
# d[:yflip] = true
# imageHack(d)
# end
# end
#
#
# # plotting arbitrary shapes/polygons
# function process_inputs(plt::AbstractPlot, d::KW, shape::Shape)
# d[:x], d[:y] = shape_coords(shape)
# d[:seriestype] = :shape
# end
# function process_inputs(plt::AbstractPlot, d::KW, shapes::AVec{Shape})
# d[:x], d[:y] = shape_coords(shapes)
# d[:seriestype] = :shape
# end
# function process_inputs(plt::AbstractPlot, d::KW, shapes::AMat{Shape})
# x, y = [], []
# for j in 1:size(shapes, 2)
# tmpx, tmpy = shape_coords(vec(shapes[:,j]))
# push!(x, tmpx)
# push!(y, tmpy)
# end
# d[:x], d[:y] = x, y
# d[:seriestype] = :shape
# end
#
#
# # function without range... use the current range of the x-axis
# function process_inputs(plt::AbstractPlot, d::KW, f::FuncOrFuncs)
# process_inputs(plt, d, f, xmin(plt), xmax(plt))
# end
#
# # --------------------------------------------------------------------
# # 2 arguments
# # --------------------------------------------------------------------
#
# function process_inputs(plt::AbstractPlot, d::KW, x, y)
# d[:x], d[:y] = x, y
# end
#
# # if functions come first, just swap the order (not to be confused with parametric functions...
# # as there would be more than one function passed in)
# function process_inputs(plt::AbstractPlot, d::KW, f::FuncOrFuncs, x)
# @assert !(typeof(x) <: FuncOrFuncs) # otherwise we'd hit infinite recursion here
# process_inputs(plt, d, x, f)
# end
#
# # --------------------------------------------------------------------
# # 3 arguments
# # --------------------------------------------------------------------
#
# # no special handling... just pass them through
# function process_inputs(plt::AbstractPlot, d::KW, x, y, z)
# d[:x], d[:y], d[:z] = x, y, z
# end
#
# # 3d line or scatter
# function process_inputs(plt::AbstractPlot, d::KW, x::AVec, y::AVec, zvec::AVec)
# # default to path3d if we haven't set a 3d seriestype
# st = get(d, :seriestype, :none)
# if st == :scatter
# d[:seriestype] = :scatter3d
# elseif !(st in _3dTypes)
# d[:seriestype] = :path3d
# end
# d[:x], d[:y], d[:z] = x, y, zvec
# end
#
# # surface-like... function
# function process_inputs{TX,TY}(plt::AbstractPlot, d::KW, x::AVec{TX}, y::AVec{TY}, zf::Function)
# x = TX <: Number ? sort(x) : x
# y = TY <: Number ? sort(y) : y
# # x, y = sort(x), sort(y)
# d[:z] = Surface(zf, x, y) # TODO: replace with SurfaceFunction when supported
# d[:x], d[:y] = x, y
# end
#
# # surface-like... matrix grid
# function process_inputs{TX,TY,TZ}(plt::AbstractPlot, d::KW, x::AVec{TX}, y::AVec{TY}, zmat::AMat{TZ})
# # @assert size(zmat) == (length(x), length(y))
# # if TX <: Number && !issorted(x)
# # idx = sortperm(x)
# # x, zmat = x[idx], zmat[idx, :]
# # end
# # if TY <: Number && !issorted(y)
# # idx = sortperm(y)
# # y, zmat = y[idx], zmat[:, idx]
# # end
# d[:x], d[:y], d[:z] = x, y, Surface{Matrix{TZ}}(zmat)
# if !like_surface(get(d, :seriestype, :none))
# d[:seriestype] = :contour
# end
# end
#
# # surfaces-like... general x, y grid
# function process_inputs{T<:Number}(plt::AbstractPlot, d::KW, x::AMat{T}, y::AMat{T}, zmat::AMat{T})
# @assert size(zmat) == size(x) == size(y)
# # d[:x], d[:y], d[:z] = Any[x], Any[y], Surface{Matrix{Float64}}(zmat)
# d[:x], d[:y], d[:z] = map(Surface{Matrix{Float64}}, (x, y, zmat))
# if !like_surface(get(d, :seriestype, :none))
# d[:seriestype] = :contour
# end
# end
#
#
# # --------------------------------------------------------------------
# # Parametric functions
# # --------------------------------------------------------------------
#
# # special handling... xmin/xmax with function(s)
# function process_inputs(plt::AbstractPlot, d::KW, f::FuncOrFuncs, xmin::Number, xmax::Number)
# width = get(plt.attr, :size, (100,))[1]
# x = linspace(xmin, xmax, width)
# process_inputs(plt, d, x, f)
# end
#
# # special handling... xmin/xmax with parametric function(s)
# process_inputs{T<:Number}(plt::AbstractPlot, d::KW, fx::FuncOrFuncs, fy::FuncOrFuncs, u::AVec{T}) = process_inputs(plt, d, mapFuncOrFuncs(fx, u), mapFuncOrFuncs(fy, u))
# process_inputs{T<:Number}(plt::AbstractPlot, d::KW, u::AVec{T}, fx::FuncOrFuncs, fy::FuncOrFuncs) = process_inputs(plt, d, mapFuncOrFuncs(fx, u), mapFuncOrFuncs(fy, u))
# process_inputs(plt::AbstractPlot, d::KW, fx::FuncOrFuncs, fy::FuncOrFuncs, umin::Number, umax::Number, numPoints::Int = 1000) = process_inputs(plt, d, fx, fy, linspace(umin, umax, numPoints))
#
# # special handling... 3D parametric function(s)
# process_inputs{T<:Number}(plt::AbstractPlot, d::KW, fx::FuncOrFuncs, fy::FuncOrFuncs, fz::FuncOrFuncs, u::AVec{T}) = process_inputs(plt, d, mapFuncOrFuncs(fx, u), mapFuncOrFuncs(fy, u), mapFuncOrFuncs(fz, u))
# process_inputs{T<:Number}(plt::AbstractPlot, d::KW, u::AVec{T}, fx::FuncOrFuncs, fy::FuncOrFuncs, fz::FuncOrFuncs) = process_inputs(plt, d, mapFuncOrFuncs(fx, u), mapFuncOrFuncs(fy, u), mapFuncOrFuncs(fz, u))
# process_inputs(plt::AbstractPlot, d::KW, fx::FuncOrFuncs, fy::FuncOrFuncs, fz::FuncOrFuncs, umin::Number, umax::Number, numPoints::Int = 1000) = process_inputs(plt, d, fx, fy, fz, linspace(umin, umax, numPoints))
#
#
# # --------------------------------------------------------------------
# # Lists of tuples and FixedSizeArrays
# # --------------------------------------------------------------------
#
# # if we get an unhandled tuple, just splat it in
# function process_inputs(plt::AbstractPlot, d::KW, tup::Tuple)
# process_inputs(plt, d, tup...)
# end
#
# # (x,y) tuples
# function process_inputs{R1<:Number,R2<:Number}(plt::AbstractPlot, d::KW, xy::AVec{Tuple{R1,R2}})
# process_inputs(plt, d, unzip(xy)...)
# end
# function process_inputs{R1<:Number,R2<:Number}(plt::AbstractPlot, d::KW, xy::Tuple{R1,R2})
# process_inputs(plt, d, [xy[1]], [xy[2]])
# end
#
# # (x,y,z) tuples
# function process_inputs{R1<:Number,R2<:Number,R3<:Number}(plt::AbstractPlot, d::KW, xyz::AVec{Tuple{R1,R2,R3}})
# process_inputs(plt, d, unzip(xyz)...)
# end
# function process_inputs{R1<:Number,R2<:Number,R3<:Number}(plt::AbstractPlot, d::KW, xyz::Tuple{R1,R2,R3})
# process_inputs(plt, d, [xyz[1]], [xyz[2]], [xyz[3]])
# end
#
# # 2D FixedSizeArrays
# function process_inputs{T<:Number}(plt::AbstractPlot, d::KW, xy::AVec{FixedSizeArrays.Vec{2,T}})
# process_inputs(plt, d, unzip(xy)...)
# end
# function process_inputs{T<:Number}(plt::AbstractPlot, d::KW, xy::FixedSizeArrays.Vec{2,T})
# process_inputs(plt, d, [xy[1]], [xy[2]])
# end
#
# # 3D FixedSizeArrays
# function process_inputs{T<:Number}(plt::AbstractPlot, d::KW, xyz::AVec{FixedSizeArrays.Vec{3,T}})
# process_inputs(plt, d, unzip(xyz)...)
# end
# function process_inputs{T<:Number}(plt::AbstractPlot, d::KW, xyz::FixedSizeArrays.Vec{3,T})
# process_inputs(plt, d, [xyz[1]], [xyz[2]], [xyz[3]])
# end
#
# # --------------------------------------------------------------------
# # handle grouping
# # --------------------------------------------------------------------
#
# # function process_inputs(plt::AbstractPlot, d::KW, groupby::GroupBy, args...)
# # ret = Any[]
# # error("unfinished after series reorg")
# # for (i,glab) in enumerate(groupby.groupLabels)
# # # TODO: don't automatically overwrite labels
# # kwlist, xmeta, ymeta = process_inputs(plt, d, args...,
# # idxfilter = groupby.groupIds[i],
# # label = string(glab),
# # numUncounted = length(ret)) # we count the idx from plt.n + numUncounted + i
# # append!(ret, kwlist)
# # end
# # ret, nothing, nothing # TODO: handle passing meta through
# # end
# --------------------------------------------------------------------
# For DataFrame support. Imports DataFrames and defines the necessary methods which support them.
# --------------------------------------------------------------------
# function setup_dataframes()
# @require DataFrames begin
# # @eval begin
# # import DataFrames
#
# DFS = Union{Symbol, AbstractArray{Symbol}}
#
# function handle_dfs(df::DataFrames.AbstractDataFrame, d::KW, letter, dfs::DFS)
# if isa(dfs, Symbol)
# get!(d, Symbol(letter * "label"), string(dfs))
# collect(df[dfs])
# else
# get!(d, :label, reshape(dfs, 1, length(dfs)))
# Any[collect(df[s]) for s in dfs]
# end
# end
#
# function handle_group(df::DataFrames.AbstractDataFrame, d::KW)
# if haskey(d, :group)
# g = d[:group]
# if isa(g, Symbol)
# d[:group] = collect(df[g])
# end
# end
# end
#
# @recipe function plot(df::DataFrames.AbstractDataFrame, sy::DFS)
# handle_group(df, d)
# handle_dfs(df, d, "y", sy)
# end
#
# @recipe function plot(df::DataFrames.AbstractDataFrame, sx::DFS, sy::DFS)
# handle_group(df, d)
# x = handle_dfs(df, d, "x", sx)
# y = handle_dfs(df, d, "y", sy)
# x, y
# end
#
# @recipe function plot(df::DataFrames.AbstractDataFrame, sx::DFS, sy::DFS, sz::DFS)
# handle_group(df, d)
# x = handle_dfs(df, d, "x", sx)
# y = handle_dfs(df, d, "y", sy)
# z = handle_dfs(df, d, "z", sz)
# x, y, z
# end
#
# # get_data(df::DataFrames.AbstractDataFrame, arg::Symbol) = df[arg]
# # get_data(df::DataFrames.AbstractDataFrame, arg) = arg
# #
# # function process_inputs(plt::AbstractPlot, d::KW, df::DataFrames.AbstractDataFrame, args...)
# # # d[:dataframe] = df
# # process_inputs(plt, d, map(arg -> get_data(df, arg), args)...)
# # end
# #
# # # expecting the column name of a dataframe that was passed in... anything else should error
# # function extractGroupArgs(s::Symbol, df::DataFrames.AbstractDataFrame, args...)
# # if haskey(df, s)
# # return extractGroupArgs(df[s])
# # else
# # error("Got a symbol, and expected that to be a key in d[:dataframe]. s=$s d=$d")
# # end
# # end
#
# # function getDataFrameFromKW(d::KW)
# # get(d, :dataframe) do
# # error("Missing dataframe argument!")
# # end
# # end
#
# # # the conversion functions for when we pass symbols or vectors of symbols to reference dataframes
# # convertToAnyVector(s::Symbol, d::KW) = Any[getDataFrameFromKW(d)[s]], s
# # convertToAnyVector(v::AVec{Symbol}, d::KW) = (df = getDataFrameFromKW(d); Any[df[s] for s in v]), v
#
# end
# end
+12 -21
View File
@@ -180,15 +180,6 @@ end
newargs
end
# @recipe f(x, y, z) = SliceIt, apply_recipe(typeof(x), x), apply_recipe(typeof(y), y), apply_recipe(typeof(z), z)
# @recipe f(x, y) = SliceIt, apply_recipe(typeof(x), x), apply_recipe(typeof(y), y), nothing
# @recipe f(y) = SliceIt, nothing, apply_recipe(typeof(y), y), nothing
# # pass these through to the slicer
# @recipe f(x, y, z) = SliceIt, x, y, z
# @recipe f(x, y) = SliceIt, x, y, nothing
# @recipe f(y) = SliceIt, nothing, y, nothing
# # --------------------------------------------------------------------
# # 1 argument
@@ -292,22 +283,22 @@ end
# # 3d line or scatter
@recipe function f(x::AVec, y::AVec, z::AVec)
st = get(d, :seriestype, :none)
if st == :scatter
d[:seriestype] = :scatter3d
elseif !is3d(st)
d[:seriestype] = :path3d
end
# st = get(d, :seriestype, :none)
# if st == :scatter
# d[:seriestype] = :scatter3d
# elseif !is3d(st)
# d[:seriestype] = :path3d
# end
SliceIt, x, y, z
end
@recipe function f(x::AMat, y::AMat, z::AMat)
st = get(d, :seriestype, :none)
if size(x) == size(y) == size(z)
if !is3d(st)
seriestype := :path3d
end
end
# st = get(d, :seriestype, :none)
# if size(x) == size(y) == size(z)
# if !is3d(st)
# seriestype := :path3d
# end
# end
SliceIt, x, y, z
end
+78
View File
@@ -136,16 +136,94 @@ function imageHack(d::KW)
d[:z], d[:fillcolor] = replace_image_with_heatmap(d[:z].surf)
end
# ---------------------------------------------------------------
type Segments
pts::Vector{Float64}
end
Segments() = Segments(zeros(0))
function Base.push!(segments::Segments, vs...)
push!(segments.pts, NaN)
for v in vs
push!(segments.pts, v)
end
segments
end
function Base.push!(segments::Segments, vs::AVec)
push!(segments.pts, NaN)
for v in vs
push!(segments.pts, v)
end
segments
end
# -----------------------------------------------------
# helper to manage NaN-separated segments
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)
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)
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
# find the next NaN, and iend is the one before
while i <= itr.n + 1
if i > itr.n || anynan(i, itr.args...)
# done... array end or found NaN
iend = i-1
break
end
i += 1
end
# find the next non-NaN, and set itr.nextidx
while i <= itr.n
if !anynan(i, itr.args...)
break
end
i += 1
end
itr.nextidx = i
istart:iend, 0
end
# ------------------------------------------------------------------------------------
nop() = nothing
notimpl() = error("This has not been implemented yet")
Base.cycle(wrapper::InputWrapper, idx::Int) = wrapper.obj
Base.cycle(wrapper::InputWrapper, idx::AVec{Int}) = wrapper.obj
Base.cycle(v::AVec, idx::Int) = v[mod1(idx, length(v))]
Base.cycle(v::AMat, idx::Int) = size(v,1) == 1 ? v[1, mod1(idx, size(v,2))] : v[:, mod1(idx, size(v,2))]
Base.cycle(v, idx::Int) = v
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
makevec(v::AVec) = v
makevec{T}(v::T) = T[v]
+1 -1
View File
@@ -22,7 +22,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.1"
const _current_plots_version = v"0.7.3"
function image_comparison_tests(pkg::Symbol, idx::Int; debug = false, popup = isinteractive(), sigma = [1,1], eps = 1e-2)
+1 -1
View File
@@ -30,7 +30,7 @@ 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