Merge remote-tracking branch 'tbreloff/master'

This commit is contained in:
Michael K. Borregaard 2016-12-16 10:10:39 +01:00
commit 08c9e627ab
28 changed files with 1353 additions and 791 deletions

View File

@ -2,10 +2,9 @@
language: julia language: julia
os: os:
- linux - linux
- osx # - osx
julia: julia:
- 0.5 - 0.5
- nightly
matrix: matrix:
allow_failures: allow_failures:
- julia: nightly - julia: nightly

37
NEWS.md
View File

@ -12,6 +12,43 @@
## 0.9 (current master/dev) ## 0.9 (current master/dev)
#### 0.9.5
- added dependency on PlotThemes
- set_theme --> theme
- remove Compat from REQUIRE
- warning for DataFrames without StatPlots
- closeall exported and implemented for gr/pyplot
- fix DateTime recipe
- reset theme with theme(:none)
- fix link_axes! for nested subplots
- fix plotly lims for log scale
#### 0.9.4
- optimizations surrounding Subplot.series_list
- better Atom support, support plotlyjs
- gr:
- gks_wstype defaults and gr_set_output
- heatmap uses GR.drawimage
- histogram2d puts NaN for zeros
- improved support of NaN in heatmaps
- rebuilt spy recipes to output scatters with marker_z set
- deprecate png support in plotly... point to plotlyjs
- fixes:
- axis widen with lims set
- reset_extrema, setxyz
- bar plot widen
- better tick padding
- consistent tick rotation
- consistent aspect_ratio
- pyplot dpi
- plotly horizontal bars
- handle series attributes when combining subplots
- gr images transposed
- converted Date/DateTime to new type recipe approach for arrays
- issues closed include: #505 #513 #479 #523 #526 #529
#### 0.9.3 #### 0.9.3
- support pdf and eps in plotlyjs backend - support pdf and eps in plotlyjs backend

View File

@ -1,9 +1,9 @@
julia 0.5- julia 0.5
RecipesBase RecipesBase
PlotUtils PlotUtils
PlotThemes
Reexport Reexport
Compat
FixedSizeArrays FixedSizeArrays
Measures Measures
Showoff Showoff

View File

@ -1,40 +1,27 @@
__precompile__()
module Plots module Plots
# using Compat
using Reexport using Reexport
# @reexport using Colors
# using Requires
using FixedSizeArrays using FixedSizeArrays
@reexport using RecipesBase @reexport using RecipesBase
using Base.Meta using Base.Meta
@reexport using PlotUtils @reexport using PlotUtils
@reexport using PlotThemes
import Showoff import Showoff
export export
AbstractPlot,
Plot,
Subplot,
AbstractLayout,
GridLayout,
grid, grid,
EmptyLayout,
bbox, bbox,
plotarea, plotarea,
@layout, @layout,
AVec,
AMat,
KW, KW,
wrap, wrap,
set_theme, theme,
add_theme,
plot, plot,
plot!, plot!,
update!, attr!,
current, current,
default, default,
@ -70,6 +57,8 @@ export
savefig, savefig,
png, png,
gui, gui,
inline,
closeall,
backend, backend,
backends, backends,
@ -77,12 +66,10 @@ export
backend_object, backend_object,
add_backend, add_backend,
aliases, aliases,
# dataframes,
Shape, Shape,
text, text,
font, font,
Axis,
stroke, stroke,
brush, brush,
Surface, Surface,
@ -94,11 +81,12 @@ export
Animation, Animation,
frame, frame,
gif, gif,
mov,
mp4,
animate,
@animate, @animate,
@gif, @gif,
spy,
test_examples, test_examples,
iter_segments, iter_segments,
coords, coords,
@ -110,9 +98,7 @@ export
center, center,
P2, P2,
P3, P3,
BezierCurve, BezierCurve
curve_points,
directed_curve
# --------------------------------------------------------- # ---------------------------------------------------------
@ -206,28 +192,29 @@ yflip!(flip::Bool = true; kw...) = plot!(; yflip = flip
xaxis!(args...; kw...) = plot!(; xaxis = args, kw...) xaxis!(args...; kw...) = plot!(; xaxis = args, kw...)
yaxis!(args...; kw...) = plot!(; yaxis = args, kw...) yaxis!(args...; kw...) = plot!(; yaxis = args, kw...)
title!(plt::Plot, s::AbstractString; kw...) = plot!(plt; title = s, kw...) let PlotOrSubplot = Union{Plot, Subplot}
xlabel!(plt::Plot, s::AbstractString; kw...) = plot!(plt; xlabel = s, kw...) title!(plt::PlotOrSubplot, s::AbstractString; kw...) = plot!(plt; title = s, kw...)
ylabel!(plt::Plot, s::AbstractString; kw...) = plot!(plt; ylabel = s, kw...) xlabel!(plt::PlotOrSubplot, s::AbstractString; kw...) = plot!(plt; xlabel = s, kw...)
xlims!{T<:Real,S<:Real}(plt::Plot, lims::Tuple{T,S}; kw...) = plot!(plt; xlims = lims, kw...) ylabel!(plt::PlotOrSubplot, s::AbstractString; kw...) = plot!(plt; ylabel = s, kw...)
ylims!{T<:Real,S<:Real}(plt::Plot, lims::Tuple{T,S}; kw...) = plot!(plt; ylims = lims, kw...) xlims!{T<:Real,S<:Real}(plt::PlotOrSubplot, lims::Tuple{T,S}; kw...) = plot!(plt; xlims = lims, kw...)
zlims!{T<:Real,S<:Real}(plt::Plot, lims::Tuple{T,S}; kw...) = plot!(plt; zlims = lims, kw...) ylims!{T<:Real,S<:Real}(plt::PlotOrSubplot, lims::Tuple{T,S}; kw...) = plot!(plt; ylims = lims, kw...)
xlims!(plt::Plot, xmin::Real, xmax::Real; kw...) = plot!(plt; xlims = (xmin,xmax), kw...) zlims!{T<:Real,S<:Real}(plt::PlotOrSubplot, lims::Tuple{T,S}; kw...) = plot!(plt; zlims = lims, kw...)
ylims!(plt::Plot, ymin::Real, ymax::Real; kw...) = plot!(plt; ylims = (ymin,ymax), kw...) xlims!(plt::PlotOrSubplot, xmin::Real, xmax::Real; kw...) = plot!(plt; xlims = (xmin,xmax), kw...)
zlims!(plt::Plot, zmin::Real, zmax::Real; kw...) = plot!(plt; zlims = (zmin,zmax), kw...) ylims!(plt::PlotOrSubplot, ymin::Real, ymax::Real; kw...) = plot!(plt; ylims = (ymin,ymax), kw...)
xticks!{T<:Real}(plt::Plot, ticks::AVec{T}; kw...) = plot!(plt; xticks = ticks, kw...) zlims!(plt::PlotOrSubplot, zmin::Real, zmax::Real; kw...) = plot!(plt; zlims = (zmin,zmax), kw...)
yticks!{T<:Real}(plt::Plot, ticks::AVec{T}; kw...) = plot!(plt; yticks = ticks, kw...) xticks!{T<:Real}(plt::PlotOrSubplot, ticks::AVec{T}; kw...) = plot!(plt; xticks = ticks, kw...)
xticks!{T<:Real,S<:AbstractString}(plt::Plot, yticks!{T<:Real}(plt::PlotOrSubplot, ticks::AVec{T}; kw...) = plot!(plt; yticks = ticks, kw...)
ticks::AVec{T}, labels::AVec{S}; kw...) = plot!(plt; xticks = (ticks,labels), kw...) xticks!{T<:Real,S<:AbstractString}(plt::PlotOrSubplot,
yticks!{T<:Real,S<:AbstractString}(plt::Plot, ticks::AVec{T}, labels::AVec{S}; kw...) = plot!(plt; xticks = (ticks,labels), kw...)
ticks::AVec{T}, labels::AVec{S}; kw...) = plot!(plt; yticks = (ticks,labels), kw...) yticks!{T<:Real,S<:AbstractString}(plt::PlotOrSubplot,
annotate!(plt::Plot, anns...; kw...) = plot!(plt; annotation = anns, kw...) ticks::AVec{T}, labels::AVec{S}; kw...) = plot!(plt; yticks = (ticks,labels), kw...)
annotate!{T<:Tuple}(plt::Plot, anns::AVec{T}; kw...) = plot!(plt; annotation = anns, kw...) annotate!(plt::PlotOrSubplot, anns...; kw...) = plot!(plt; annotation = anns, kw...)
xflip!(plt::Plot, flip::Bool = true; kw...) = plot!(plt; xflip = flip, kw...) annotate!{T<:Tuple}(plt::PlotOrSubplot, anns::AVec{T}; kw...) = plot!(plt; annotation = anns, kw...)
yflip!(plt::Plot, flip::Bool = true; kw...) = plot!(plt; yflip = flip, kw...) xflip!(plt::PlotOrSubplot, flip::Bool = true; kw...) = plot!(plt; xflip = flip, kw...)
xaxis!(plt::Plot, args...; kw...) = plot!(plt; xaxis = args, kw...) yflip!(plt::PlotOrSubplot, flip::Bool = true; kw...) = plot!(plt; yflip = flip, kw...)
yaxis!(plt::Plot, args...; kw...) = plot!(plt; yaxis = args, kw...) xaxis!(plt::PlotOrSubplot, args...; kw...) = plot!(plt; xaxis = args, kw...)
yaxis!(plt::PlotOrSubplot, args...; kw...) = plot!(plt; yaxis = args, kw...)
end
# --------------------------------------------------------- # ---------------------------------------------------------
@ -247,11 +234,4 @@ end
# --------------------------------------------------------- # ---------------------------------------------------------
# if VERSION >= v"0.4.0-dev+5512"
# include("precompile.jl")
# _precompile_()
# end
# ---------------------------------------------------------
end # module end # module

View File

@ -1,62 +1,109 @@
immutable Animation immutable Animation
dir::String dir::String
frames::Vector{String} frames::Vector{String}
end end
function Animation() function Animation()
tmpdir = convert(String, mktempdir()) tmpdir = convert(String, mktempdir())
Animation(tmpdir, String[]) Animation(tmpdir, String[])
end end
function frame{P<:AbstractPlot}(anim::Animation, plt::P=current()) function frame{P<:AbstractPlot}(anim::Animation, plt::P=current())
i = length(anim.frames) + 1 i = length(anim.frames) + 1
filename = @sprintf("%06d.png", i) filename = @sprintf("%06d.png", i)
png(plt, joinpath(anim.dir, filename)) png(plt, joinpath(anim.dir, filename))
push!(anim.frames, filename) push!(anim.frames, filename)
end end
giffn() = (isijulia() ? "tmp.gif" : tempname()*".gif")
movfn() = (isijulia() ? "tmp.mov" : tempname()*".mov")
mp4fn() = (isijulia() ? "tmp.mp4" : tempname()*".mp4")
type FrameIterator
itr
every::Int
kw
end
FrameIterator(itr; every=1, kw...) = FrameIterator(itr, every, kw)
"""
Animate from an iterator which returns the plot args each iteration.
"""
function animate(fitr::FrameIterator, fn = giffn(); kw...)
anim = Animation()
for (i, plotargs) in enumerate(fitr.itr)
if mod1(i, fitr.every) == 1
plot(wraptuple(plotargs)...; fitr.kw...)
frame(anim)
end
end
gif(anim, fn; kw...)
end
# most things will implement this
function animate(obj, fn = giffn(); every=1, fps=20, loop=0, kw...)
animate(FrameIterator(obj, every, kw), fn; fps=fps, loop=loop)
end
# ----------------------------------------------- # -----------------------------------------------
"Wraps the location of an animated gif so that it can be displayed" "Wraps the location of an animated gif so that it can be displayed"
immutable AnimatedGif immutable AnimatedGif
filename::String filename::String
end end
function gif(anim::Animation, fn = (isijulia() ? "tmp.gif" : tempname()*".gif"); fps::Integer = 20, loop::Integer = 0) file_extension(fn) = Base.Filesystem.splitext(fn)[2][2:end]
fn = abspath(fn)
try gif(anim::Animation, fn = giffn(); kw...) = buildanimation(anim.dir, fn; kw...)
mov(anim::Animation, fn = movfn(); kw...) = buildanimation(anim.dir, fn; kw...)
mp4(anim::Animation, fn = mp4fn(); kw...) = buildanimation(anim.dir, fn; kw...)
# high quality const _imagemagick_initialized = Ref(false)
speed = round(Int, 100 / fps)
file = joinpath(Pkg.dir("ImageMagick"), "deps","deps.jl") function buildanimation(animdir::AbstractString, fn::AbstractString;
if isfile(file) && !haskey(ENV, "MAGICK_CONFIGURE_PATH") fps::Integer = 20, loop::Integer = 0)
include(file) fn = abspath(fn)
try
if !_imagemagick_initialized[]
file = joinpath(Pkg.dir("ImageMagick"), "deps","deps.jl")
if isfile(file) && !haskey(ENV, "MAGICK_CONFIGURE_PATH")
include(file)
end
_imagemagick_initialized[] = true
end
# prefix = get(ENV, "MAGICK_CONFIGURE_PATH", "")
# high quality
speed = round(Int, 100 / fps)
run(`convert -delay $speed -loop $loop $(joinpath(animdir, "*.png")) -alpha off $fn`)
catch err
warn("""Tried to create gif using convert (ImageMagick), but got error: $err
ImageMagick can be installed by executing `Pkg.add("ImageMagick")`
Will try ffmpeg, but it's lower quality...)""")
# low quality
run(`ffmpeg -v 0 -framerate $fps -loop $loop -i $(animdir)/%06d.png -y $fn`)
# run(`ffmpeg -v warning -i "fps=$fps,scale=320:-1:flags=lanczos"`)
end end
# prefix = get(ENV, "MAGICK_CONFIGURE_PATH", "")
run(`convert -delay $speed -loop $loop $(joinpath(anim.dir, "*.png")) -alpha off $fn`)
catch err info("Saved animation to ", fn)
warn("""Tried to create gif using convert (ImageMagick), but got error: $err AnimatedGif(fn)
ImageMagick can be installed by executing `Pkg.add("ImageMagick")`
Will try ffmpeg, but it's lower quality...)""")
# low quality
run(`ffmpeg -v 0 -framerate $fps -loop $loop -i $(anim.dir)/%06d.png -y $fn`)
# run(`ffmpeg -v warning -i "fps=$fps,scale=320:-1:flags=lanczos"`)
end
info("Saved animation to ", fn)
AnimatedGif(fn)
end end
# write out html to view the gif... note the rand call which is a hack so the image doesn't get cached # write out html to view the gif... note the rand call which is a hack so the image doesn't get cached
function Base.show(io::IO, ::MIME"text/html", agif::AnimatedGif) function Base.show(io::IO, ::MIME"text/html", agif::AnimatedGif)
write(io, "<img src=\"$(relpath(agif.filename))?$(rand())>\" />") ext = file_extension(agif.filename)
write(io, if ext == "gif"
"<img src=\"$(relpath(agif.filename))?$(rand())>\" />"
elseif ext in ("mov", "mp4")
"<video controls><source src=\"$(relpath(agif.filename))?$(rand())>\" type=\"video/$ext\"></video>"
else
error("Cannot show animation with extension $ext: $agif")
end)
end end
@ -122,7 +169,7 @@ Example:
``` ```
""" """
macro gif(forloop::Expr, args...) macro gif(forloop::Expr, args...)
_animate(forloop, args...; callgif = true) _animate(forloop, args...; callgif = true)
end end
""" """
@ -131,13 +178,13 @@ Collect one frame per for-block iteration and return an `Animation` object.
Example: Example:
``` ```
p = plot(1) p = plot(1)
anim = @animate for x=0:0.1:5 anim = @animate for x=0:0.1:5
push!(p, 1, sin(x)) push!(p, 1, sin(x))
end end
gif(anim) gif(anim)
``` ```
""" """
macro animate(forloop::Expr, args...) macro animate(forloop::Expr, args...)
_animate(forloop, args...) _animate(forloop, args...)
end end

View File

@ -19,7 +19,7 @@ const _arg_desc = KW(
:markersize => "Number or AbstractVector. Size (radius pixels) of the markers.", :markersize => "Number or AbstractVector. Size (radius pixels) of the markers.",
:markerstrokestyle => "Symbol. Style of the marker stroke (border). Choose from $(_allStyles)", :markerstrokestyle => "Symbol. Style of the marker stroke (border). Choose from $(_allStyles)",
:markerstrokewidth => "Number. Width of the marker stroke (border. in pixels)", :markerstrokewidth => "Number. Width of the marker stroke (border. in pixels)",
:markerstrokecolor => "Color Type. Color of the marker stroke (border). `:match` will take the value from `:seriescolor`.", :markerstrokecolor => "Color Type. Color of the marker stroke (border). `:match` will take the value from `:foreground_color_subplot`.",
:markerstrokealpha => "Number in [0,1]. The alpha/opacity override for the marker stroke (border). `nothing` (the default) means it will take the alpha value of markerstrokecolor.", :markerstrokealpha => "Number in [0,1]. The alpha/opacity override for the marker stroke (border). `nothing` (the default) means it will take the alpha value of markerstrokecolor.",
:bins => "Integer, NTuple{2,Integer}, AbstractVector. For histogram-types, defines the number of bins, or the edges, of the histogram.", :bins => "Integer, NTuple{2,Integer}, AbstractVector. For histogram-types, defines the number of bins, or the edges, of the histogram.",
:smooth => "Bool. Add a regression line?", :smooth => "Bool. Add a regression line?",
@ -85,7 +85,7 @@ const _arg_desc = KW(
:grid => "Bool. Show the grid lines?", :grid => "Bool. Show the grid lines?",
:annotations => "(x,y,text) tuple(s). Can be a single tuple or a list of them. Text can be String or PlotText (created with `text(args...)`) Add one-off text annotations at the x,y coordinates.", :annotations => "(x,y,text) tuple(s). Can be a single tuple or a list of them. Text can be String or PlotText (created with `text(args...)`) Add one-off text annotations at the x,y coordinates.",
:projection => "Symbol or String. '3d' or 'polar'", :projection => "Symbol or String. '3d' or 'polar'",
:aspect_ratio => "Symbol (:equal) or Number (width to height ratio of plot area).", :aspect_ratio => "Symbol (:equal) or Number. Plot area is resized so that 1 y-unit is the same size as `apect_ratio` x-units.",
:margin => "Measure (multiply by `mm`, `px`, etc). Base for individual margins... not directly used. Specifies the extra padding around subplots.", :margin => "Measure (multiply by `mm`, `px`, etc). Base for individual margins... not directly used. Specifies the extra padding around subplots.",
:left_margin => "Measure (multiply by `mm`, `px`, etc) or `:match` (matches `:margin`). Specifies the extra padding to the left of the subplot.", :left_margin => "Measure (multiply by `mm`, `px`, etc) or `:match` (matches `:margin`). Specifies the extra padding to the left of the subplot.",
:top_margin => "Measure (multiply by `mm`, `px`, etc) or `:match` (matches `:margin`). Specifies the extra padding on the top of the subplot.", :top_margin => "Measure (multiply by `mm`, `px`, etc) or `:match` (matches `:margin`). Specifies the extra padding on the top of the subplot.",

View File

@ -123,6 +123,16 @@ const _markerAliases = Dict{Symbol,Symbol}(
:dtri => :dtriangle, :dtri => :dtriangle,
:downtri => :dtriangle, :downtri => :dtriangle,
:downtriangle => :dtriangle, :downtriangle => :dtriangle,
:> => :rtriangle,
:rt => :rtriangle,
:rtri => :rtriangle,
:righttri => :rtriangle,
:righttriangle => :rtriangle,
:< => :ltriangle,
:lt => :ltriangle,
:ltri => :ltriangle,
:lighttri => :ltriangle,
:lighttriangle => :ltriangle,
# :+ => :cross, # :+ => :cross,
:plus => :cross, :plus => :cross,
# :x => :xcross, # :x => :xcross,
@ -165,7 +175,7 @@ const _series_defaults = KW(
:markershape => :none, :markershape => :none,
:markercolor => :match, :markercolor => :match,
:markeralpha => nothing, :markeralpha => nothing,
:markersize => 6, :markersize => 4,
:markerstrokestyle => :solid, :markerstrokestyle => :solid,
:markerstrokewidth => 1, :markerstrokewidth => 1,
:markerstrokecolor => :match, :markerstrokecolor => :match,
@ -194,7 +204,7 @@ const _series_defaults = KW(
:match_dimensions => false, # do rows match x (true) or y (false) for heatmap/image/spy? see issue 196 :match_dimensions => false, # do rows match x (true) or y (false) for heatmap/image/spy? see issue 196
# this ONLY effects whether or not the z-matrix is transposed for a heatmap display! # this ONLY effects whether or not the z-matrix is transposed for a heatmap display!
:subplot => :auto, # which subplot(s) does this series belong to? :subplot => :auto, # which subplot(s) does this series belong to?
:series_annotations => [], # a list of annotations which apply to the coordinates of this series :series_annotations => nothing, # a list of annotations which apply to the coordinates of this series
:primary => true, # when true, this "counts" as a series for color selection, etc. the main use is to allow :primary => true, # when true, this "counts" as a series for color selection, etc. the main use is to allow
# one logical series to be broken up (path and markers, for example) # one logical series to be broken up (path and markers, for example)
:hover => nothing, # text to display when hovering over the data points :hover => nothing, # text to display when hovering over the data points
@ -353,6 +363,7 @@ add_aliases(:seriescolor, :c, :color, :colour)
add_aliases(:linecolor, :lc, :lcolor, :lcolour, :linecolour) add_aliases(:linecolor, :lc, :lcolor, :lcolour, :linecolour)
add_aliases(:markercolor, :mc, :mcolor, :mcolour, :markercolour) add_aliases(:markercolor, :mc, :mcolor, :mcolour, :markercolour)
add_aliases(:markerstrokecolor, :msc, :mscolor, :mscolour, :markerstrokecolour) add_aliases(:markerstrokecolor, :msc, :mscolor, :mscolour, :markerstrokecolour)
add_aliases(:markerstrokewidth, :msw, :mswidth)
add_aliases(:fillcolor, :fc, :fcolor, :fcolour, :fillcolour) add_aliases(:fillcolor, :fc, :fcolor, :fcolour, :fillcolour)
add_aliases(:background_color, :bg, :bgcolor, :bg_color, :background, add_aliases(:background_color, :bg, :bgcolor, :bg_color, :background,
@ -387,7 +398,7 @@ add_aliases(:foreground_color_guide, :fg_guide, :fgguide, :fgcolor_guide, :fg_co
# alphas # alphas
add_aliases(:seriesalpha, :alpha, :α, :opacity) add_aliases(:seriesalpha, :alpha, :α, :opacity)
add_aliases(:linealpha, :la, :lalpha, :lα, :lineopacity, :lopacity) add_aliases(:linealpha, :la, :lalpha, :lα, :lineopacity, :lopacity)
add_aliases(:makeralpha, :ma, :malpha, :mα, :makeropacity, :mopacity) add_aliases(:markeralpha, :ma, :malpha, :mα, :markeropacity, :mopacity)
add_aliases(:markerstrokealpha, :msa, :msalpha, :msα, :markerstrokeopacity, :msopacity) add_aliases(:markerstrokealpha, :msa, :msalpha, :msα, :markerstrokeopacity, :msopacity)
add_aliases(:fillalpha, :fa, :falpha, :fα, :fillopacity, :fopacity) add_aliases(:fillalpha, :fa, :falpha, :fα, :fillopacity, :fopacity)
@ -439,7 +450,7 @@ add_aliases(:match_dimensions, :transpose, :transpose_z)
add_aliases(:subplot, :sp, :subplt, :splt) add_aliases(:subplot, :sp, :subplt, :splt)
add_aliases(:projection, :proj) add_aliases(:projection, :proj)
add_aliases(:title_location, :title_loc, :titleloc, :title_position, :title_pos, :titlepos, :titleposition, :title_align, :title_alignment) 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(:series_annotations, :series_ann, :seriesann, :series_anns, :seriesanns, :series_annotation, :text, :txt, :texts, :txts)
add_aliases(:html_output_format, :format, :fmt, :html_format) add_aliases(:html_output_format, :format, :fmt, :html_format)
add_aliases(:orientation, :direction, :dir) add_aliases(:orientation, :direction, :dir)
add_aliases(:inset_subplots, :inset, :floating) add_aliases(:inset_subplots, :inset, :floating)
@ -691,6 +702,11 @@ function preprocessArgs!(d::KW)
end end
delete!(d, :fill) delete!(d, :fill)
# handle series annotations
if haskey(d, :series_annotations)
d[:series_annotations] = series_annotations(wraptuple(d[:series_annotations])...)
end
# convert into strokes and brushes # convert into strokes and brushes
if haskey(d, :arrow) if haskey(d, :arrow)
@ -841,6 +857,7 @@ function convertLegendValue(val::Symbol)
end end
convertLegendValue(val::Bool) = val ? :best : :none convertLegendValue(val::Bool) = val ? :best : :none
convertLegendValue(val::Void) = :none convertLegendValue(val::Void) = :none
convertLegendValue{S<:Real, T<:Real}(v::Tuple{S,T}) = v
convertLegendValue(v::AbstractArray) = map(convertLegendValue, v) convertLegendValue(v::AbstractArray) = map(convertLegendValue, v)
# ----------------------------------------------------------------------------- # -----------------------------------------------------------------------------
@ -1028,8 +1045,14 @@ end
# ----------------------------------------------------------------------------- # -----------------------------------------------------------------------------
function _update_subplot_periphery(sp::Subplot, anns::AVec) function _update_subplot_periphery(sp::Subplot, anns::AVec)
# extend annotations # extend annotations, and ensure we always have a (x,y,PlotText) tuple
sp.attr[:annotations] = vcat(anns, sp[:annotations]) newanns = vcat(anns, sp[:annotations])
for (i,ann) in enumerate(newanns)
x,y,tmp = ann
ptxt = isa(tmp, PlotText) ? tmp : text(tmp)
newanns[i] = (x,y,ptxt)
end
sp.attr[:annotations] = newanns
# handle legend/colorbar # handle legend/colorbar
sp.attr[:legend] = convertLegendValue(sp.attr[:legend]) sp.attr[:legend] = convertLegendValue(sp.attr[:legend])
@ -1093,7 +1116,7 @@ function _update_axis(axis::Axis, d_in::KW, letter::Symbol, subplot_index::Int)
end end
# update the axis # update the axis
update!(axis, args...; kw...) attr!(axis, args...; kw...)
return return
end end

View File

@ -29,7 +29,7 @@ function Axis(sp::Subplot, letter::Symbol, args...; kw...)
d[:discrete_values] = [] d[:discrete_values] = []
# update the defaults # update the defaults
update!(Axis([sp], d), args...; kw...) attr!(Axis([sp], d), args...; kw...)
end end
function get_axis(sp::Subplot, letter::Symbol) function get_axis(sp::Subplot, letter::Symbol)
@ -83,7 +83,7 @@ function process_axis_arg!(d::KW, arg, letter = "")
end end
# update an Axis object with magic args and keywords # update an Axis object with magic args and keywords
function update!(axis::Axis, args...; kw...) function attr!(axis::Axis, args...; kw...)
# first process args # first process args
d = axis.d d = axis.d
for arg in args for arg in args
@ -381,6 +381,9 @@ function axis_limits(axis::Axis, should_widen::Bool = default_should_widen(axis)
if amax <= amin && isfinite(amin) if amax <= amin && isfinite(amin)
amax = amin + 1.0 amax = amin + 1.0
end end
if !isfinite(amin) && !isfinite(amax)
amin, amax = 0.0, 1.0
end
if should_widen if should_widen
widen(amin, amax) widen(amin, amax)
else else

View File

@ -51,6 +51,21 @@ _before_layout_calcs(plt::Plot) = nothing
title_padding(sp::Subplot) = sp[:title] == "" ? 0mm : sp[:titlefont].pointsize * pt title_padding(sp::Subplot) = sp[:title] == "" ? 0mm : sp[:titlefont].pointsize * pt
guide_padding(axis::Axis) = axis[:guide] == "" ? 0mm : axis[:guidefont].pointsize * pt guide_padding(axis::Axis) = axis[:guide] == "" ? 0mm : axis[:guidefont].pointsize * pt
"Returns the (width,height) of a text label."
function text_size(lablen::Int, sz::Number, rot::Number = 0)
# we need to compute the size of the ticks generically
# this means computing the bounding box and then getting the width/height
# note:
ptsz = sz * pt
width = 0.8lablen * ptsz
# now compute the generalized "height" after rotation as the "opposite+adjacent" of 2 triangles
height = abs(sind(rot)) * width + abs(cosd(rot)) * ptsz
width = abs(sind(rot+90)) * width + abs(cosd(rot+90)) * ptsz
width, height
end
text_size(lab::AbstractString, sz::Number, rot::Number = 0) = text_size(length(lab), sz, rot)
# account for the size/length/rotation of tick labels # account for the size/length/rotation of tick labels
function tick_padding(axis::Axis) function tick_padding(axis::Axis)
ticks = get_ticks(axis) ticks = get_ticks(axis)
@ -58,19 +73,24 @@ function tick_padding(axis::Axis)
0mm 0mm
else else
vals, labs = ticks vals, labs = ticks
ptsz = axis[:tickfont].pointsize * pt isempty(labs) && return 0mm
# ptsz = axis[:tickfont].pointsize * pt
# we need to compute the size of the ticks generically
# this means computing the bounding box and then getting the width/height
longest_label = maximum(length(lab) for lab in labs) longest_label = maximum(length(lab) for lab in labs)
labelwidth = 0.8longest_label * ptsz
# generalize by "rotating" y labels # generalize by "rotating" y labels
rot = axis[:rotation] + (axis[:letter] == :y ? 90 : 0) rot = axis[:rotation] + (axis[:letter] == :y ? 90 : 0)
# now compute the generalized "height" after rotation as the "opposite+adjacent" of 2 triangles # # we need to compute the size of the ticks generically
hgt = abs(sind(rot)) * labelwidth + abs(cosd(rot)) * ptsz + 1mm # # this means computing the bounding box and then getting the width/height
hgt # labelwidth = 0.8longest_label * ptsz
#
#
# # now compute the generalized "height" after rotation as the "opposite+adjacent" of 2 triangles
# hgt = abs(sind(rot)) * labelwidth + abs(cosd(rot)) * ptsz + 1mm
# hgt
# get the height of the rotated label
text_size(longest_label, axis[:tickfont].pointsize, rot)[2]
end end
end end

File diff suppressed because it is too large Load Diff

View File

@ -30,6 +30,7 @@ const _gr_attr = merge_with_base_supported([
:normalize, :weights, :normalize, :weights,
:inset_subplots, :inset_subplots,
:bar_width, :bar_width,
:arrow,
]) ])
const _gr_seriestype = [ const _gr_seriestype = [
:path, :scatter, :path, :scatter,
@ -131,7 +132,7 @@ gr_set_textcolor(c) = GR.settextcolorind(gr_getcolorind(cycle(c,1)))
# draw line segments, splitting x/y into contiguous/finite segments # draw line segments, splitting x/y into contiguous/finite segments
# note: this can be used for shapes by passing func `GR.fillarea` # note: this can be used for shapes by passing func `GR.fillarea`
function gr_polyline(x, y, func = GR.polyline) function gr_polyline(x, y, func = GR.polyline; arrowside=:none)
iend = 0 iend = 0
n = length(x) n = length(x)
while iend < n-1 while iend < n-1
@ -159,6 +160,12 @@ function gr_polyline(x, y, func = GR.polyline)
# if we found a start and end, draw the line segment, otherwise we're done # if we found a start and end, draw the line segment, otherwise we're done
if istart > 0 && iend > 0 if istart > 0 && iend > 0
func(x[istart:iend], y[istart:iend]) func(x[istart:iend], y[istart:iend])
if arrowside in (:head,:both)
GR.drawarrow(x[iend-1], y[iend-1], x[iend], y[iend])
end
if arrowside in (:tail,:both)
GR.drawarrow(x[istart+1], y[istart+1], x[istart], y[istart])
end
else else
break break
end end
@ -263,11 +270,13 @@ end
# draw ONE Shape # draw ONE Shape
function gr_draw_marker(xi, yi, msize, shape::Shape) function gr_draw_marker(xi, yi, msize, shape::Shape)
sx, sy = shape_coords(shape) sx, sy = coords(shape)
# convert to ndc coords (percentages of window)
GR.selntran(0) GR.selntran(0)
xi, yi = GR.wctondc(xi, yi) xi, yi = GR.wctondc(xi, yi)
GR.fillarea(xi + sx * 0.0015msize, ms_ndc_x, ms_ndc_y = gr_pixels_to_ndc(msize, msize)
yi + sy * 0.0015msize) GR.fillarea(xi .+ sx .* ms_ndc_x,
yi .+ sy .* ms_ndc_y)
GR.selntran(1) GR.selntran(1)
end end
@ -281,10 +290,11 @@ end
# draw the markers, one at a time # draw the markers, one at a time
function gr_draw_markers(series::Series, x, y, msize, mz) function gr_draw_markers(series::Series, x, y, msize, mz)
shape = series[:markershape] shapes = series[:markershape]
if shape != :none if shapes != :none
for i=1:length(x) for i=1:length(x)
msi = cycle(msize, i) msi = cycle(msize, i)
shape = cycle(shapes, i)
cfunc = isa(shape, Shape) ? gr_set_fillcolor : gr_set_markercolor cfunc = isa(shape, Shape) ? gr_set_fillcolor : gr_set_markercolor
cfuncind = isa(shape, Shape) ? GR.setfillcolorind : GR.setmarkercolorind cfuncind = isa(shape, Shape) ? GR.setfillcolorind : GR.setmarkercolorind
@ -349,6 +359,14 @@ function gr_set_font(f::Font; halign = f.halign, valign = f.valign,
GR.settextalign(gr_halign[halign], gr_valign[valign]) GR.settextalign(gr_halign[halign], gr_valign[valign])
end end
function gr_nans_to_infs!(z)
for (i,zi) in enumerate(z)
if zi == NaN
z[i] = Inf
end
end
end
# -------------------------------------------------------------------------------------- # --------------------------------------------------------------------------------------
# viewport plot area # viewport plot area
@ -356,6 +374,9 @@ end
# values are [xmin, xmax, ymin, ymax]. they range [0,1]. # values are [xmin, xmax, ymin, ymax]. they range [0,1].
const viewport_plotarea = zeros(4) const viewport_plotarea = zeros(4)
# the size of the current plot in pixels
const gr_plot_size = zeros(2)
function gr_viewport_from_bbox(bb::BoundingBox, w, h, viewport_canvas) function gr_viewport_from_bbox(bb::BoundingBox, w, h, viewport_canvas)
viewport = zeros(4) viewport = zeros(4)
viewport[1] = viewport_canvas[2] * (left(bb) / w) viewport[1] = viewport_canvas[2] * (left(bb) / w)
@ -410,6 +431,13 @@ gr_view_ycenter() = 0.5 * (viewport_plotarea[3] + viewport_plotarea[4])
gr_view_xdiff() = viewport_plotarea[2] - viewport_plotarea[1] gr_view_xdiff() = viewport_plotarea[2] - viewport_plotarea[1]
gr_view_ydiff() = viewport_plotarea[4] - viewport_plotarea[3] gr_view_ydiff() = viewport_plotarea[4] - viewport_plotarea[3]
function gr_pixels_to_ndc(x_pixels, y_pixels)
w,h = gr_plot_size
totx = w * gr_view_xdiff()
toty = h * gr_view_ydiff()
x_pixels / totx, y_pixels / toty
end
# -------------------------------------------------------------------------------------- # --------------------------------------------------------------------------------------
@ -437,6 +465,7 @@ function gr_display(plt::Plot)
# compute the viewport_canvas, normalized to the larger dimension # compute the viewport_canvas, normalized to the larger dimension
viewport_canvas = Float64[0,1,0,1] viewport_canvas = Float64[0,1,0,1]
w, h = plt[:size] w, h = plt[:size]
gr_plot_size[:] = [w, h]
if w > h if w > h
ratio = float(h) / w ratio = float(h) / w
msize = display_width_ratio * w msize = display_width_ratio * w
@ -534,16 +563,16 @@ function gr_display(sp::Subplot{GRBackend}, w, h, viewport_canvas)
# set the scale flags and window # set the scale flags and window
xmin, xmax, ymin, ymax = data_lims xmin, xmax, ymin, ymax = data_lims
scale = 0 scaleop = 0
xtick, ytick = 1, 1 xtick, ytick = 1, 1
if xmax > xmin && ymax > ymin 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 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) # 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)
xaxis[:scale] == :log10 && (scale |= GR.OPTION_X_LOG) xaxis[:scale] == :log10 && (scaleop |= GR.OPTION_X_LOG)
yaxis[:scale] == :log10 && (scale |= GR.OPTION_Y_LOG) yaxis[:scale] == :log10 && (scaleop |= GR.OPTION_Y_LOG)
xaxis[:flip] && (scale |= GR.OPTION_FLIP_X) xaxis[:flip] && (scaleop |= GR.OPTION_FLIP_X)
yaxis[:flip] && (scale |= GR.OPTION_FLIP_Y) yaxis[:flip] && (scaleop |= GR.OPTION_FLIP_Y)
if scale & GR.OPTION_X_LOG == 0 if scaleop & GR.OPTION_X_LOG == 0
majorx = 1 #5 majorx = 1 #5
xtick = GR.tick(xmin, xmax) / majorx xtick = GR.tick(xmin, xmax) / majorx
else else
@ -551,7 +580,7 @@ function gr_display(sp::Subplot{GRBackend}, w, h, viewport_canvas)
xtick = 2 # scientific notation xtick = 2 # scientific notation
majorx = 2 # no minor grid lines majorx = 2 # no minor grid lines
end end
if scale & GR.OPTION_Y_LOG == 0 if scaleop & GR.OPTION_Y_LOG == 0
majory = 1 #5 majory = 1 #5
ytick = GR.tick(ymin, ymax) / majory ytick = GR.tick(ymin, ymax) / majory
else else
@ -562,7 +591,7 @@ function gr_display(sp::Subplot{GRBackend}, w, h, viewport_canvas)
# NOTE: setwindow sets the "data coordinate" limits of the current "viewport" # NOTE: setwindow sets the "data coordinate" limits of the current "viewport"
GR.setwindow(xmin, xmax, ymin, ymax) GR.setwindow(xmin, xmax, ymin, ymax)
GR.setscale(scale) GR.setscale(scaleop)
end end
# draw the axes # draw the axes
@ -585,8 +614,8 @@ function gr_display(sp::Subplot{GRBackend}, w, h, viewport_canvas)
# GR.setlinetype(GR.LINETYPE_DOTTED) # GR.setlinetype(GR.LINETYPE_DOTTED)
if sp[:grid] if sp[:grid]
GR.grid3d(xtick, 0, ztick, xmin, ymin, zmin, 2, 0, 2) GR.grid3d(xtick, 0, ztick, xmin, ymax, zmin, 2, 0, 2)
GR.grid3d(0, ytick, 0, xmax, ymin, zmin, 0, 2, 0) GR.grid3d(0, ytick, 0, xmin, ymax, zmin, 0, 2, 0)
end end
GR.axes3d(xtick, 0, ztick, xmin, ymin, zmin, 2, 0, 2, -ticksize) GR.axes3d(xtick, 0, ztick, xmin, ymin, zmin, 2, 0, 2, -ticksize)
GR.axes3d(0, ytick, 0, xmax, ymin, zmin, 0, 2, 0, ticksize) GR.axes3d(0, ytick, 0, xmax, ymin, zmin, 0, 2, 0, ticksize)
@ -722,12 +751,16 @@ function gr_display(sp::Subplot{GRBackend}, w, h, viewport_canvas)
x, y, z = series[:x], series[:y], series[:z] x, y, z = series[:x], series[:y], series[:z]
frng = series[:fillrange] frng = series[:fillrange]
# add custom frame shapes to markershape?
series_annotations_shapes!(series)
# -------------------------------------------------------
# recompute data # recompute data
if typeof(z) <: Surface if typeof(z) <: Surface
if st == :heatmap # if st == :heatmap
expand_extrema!(sp[:xaxis], (x[1]-0.5*(x[2]-x[1]), x[end]+0.5*(x[end]-x[end-1]))) # expand_extrema!(sp[:xaxis], (x[1]-0.5*(x[2]-x[1]), x[end]+0.5*(x[end]-x[end-1])))
expand_extrema!(sp[:yaxis], (y[1]-0.5*(y[2]-y[1]), y[end]+0.5*(y[end]-y[end-1]))) # expand_extrema!(sp[:yaxis], (y[1]-0.5*(y[2]-y[1]), y[end]+0.5*(y[end]-y[end-1])))
end # end
z = vec(transpose_z(series, z.surf, false)) z = vec(transpose_z(series, z.surf, false))
elseif ispolar(sp) elseif ispolar(sp)
if frng != nothing if frng != nothing
@ -756,7 +789,8 @@ function gr_display(sp::Subplot{GRBackend}, w, h, viewport_canvas)
# draw the line(s) # draw the line(s)
if st == :path if st == :path
gr_set_line(series[:linewidth], series[:linestyle], series[:linecolor]) #, series[:linealpha]) gr_set_line(series[:linewidth], series[:linestyle], series[:linecolor]) #, series[:linealpha])
gr_polyline(x, y) arrowside = isa(series[:arrow], Arrow) ? series[:arrow].side : :none
gr_polyline(x, y; arrowside = arrowside)
end end
end end
@ -808,16 +842,19 @@ function gr_display(sp::Subplot{GRBackend}, w, h, viewport_canvas)
cmap && gr_colorbar(sp) cmap && gr_colorbar(sp)
elseif st == :heatmap elseif st == :heatmap
# z = vec(transpose_z(series, z.surf, false))
zmin, zmax = gr_lims(zaxis, true) zmin, zmax = gr_lims(zaxis, true)
clims = sp[:clims] clims = sp[:clims]
if is_2tuple(clims) if is_2tuple(clims)
isfinite(clims[1]) && (zmin = clims[1]) isfinite(clims[1]) && (zmin = clims[1])
isfinite(clims[2]) && (zmax = clims[2]) isfinite(clims[2]) && (zmax = clims[2])
end end
GR.setspace(zmin, zmax, 0, 90) grad = isa(series[:fillcolor], ColorGradient) ? series[:fillcolor] : cgrad()
# GR.surface(x, y, z, GR.OPTION_COLORED_MESH) colors = [grad[clamp((zi-zmin) / (zmax-zmin), 0, 1)] for zi=z]
GR.surface(x, y, z, GR.OPTION_HEATMAP) rgba = map(c -> UInt32( round(Int, alpha(c) * 255) << 24 +
round(Int, blue(c) * 255) << 16 +
round(Int, green(c) * 255) << 8 +
round(Int, red(c) * 255) ), colors)
GR.drawimage(xmin, xmax, ymax, ymin, length(x), length(y), rgba)
cmap && gr_colorbar(sp) cmap && gr_colorbar(sp)
elseif st in (:path3d, :scatter3d) elseif st in (:path3d, :scatter3d)
@ -904,8 +941,8 @@ function gr_display(sp::Subplot{GRBackend}, w, h, viewport_canvas)
elseif st == :image elseif st == :image
img = series[:z].surf z = transpose_z(series, series[:z].surf, true)
h, w = size(img) h, w = size(z)
if eltype(z) <: Colors.AbstractGray if eltype(z) <: Colors.AbstractGray
grey = round(UInt8, float(z) * 255) grey = round(UInt8, float(z) * 255)
rgba = map(c -> UInt32( 0xff000000 + Int(c)<<16 + Int(c)<<8 + Int(c) ), grey) rgba = map(c -> UInt32( 0xff000000 + Int(c)<<16 + Int(c)<<8 + Int(c) ), grey)
@ -918,6 +955,13 @@ function gr_display(sp::Subplot{GRBackend}, w, h, viewport_canvas)
GR.drawimage(xmin, xmax, ymax, ymin, w, h, rgba) GR.drawimage(xmin, xmax, ymax, ymin, w, h, rgba)
end end
# this is all we need to add the series_annotations text
anns = series[:series_annotations]
for (xi,yi,str,fnt) in EachAnn(anns, x, y)
gr_set_font(fnt)
gr_text(GR.wctondc(xi, yi)..., str)
end
GR.restorestate() GR.restorestate()
end end
@ -1025,22 +1069,25 @@ const _gr_mimeformats = Dict(
) )
const _gr_wstype_default = @static if is_linux() const _gr_wstype_default = @static if is_linux()
"cairox11" "x11"
# "cairox11"
elseif is_apple() elseif is_apple()
"quartz" "quartz"
else else
"windows" "use_default"
end end
const _gr_wstype = Ref(get(ENV, "GKS_WSTYPE", _gr_wstype_default))
gr_set_output(wstype::String) = (_gr_wstype[] = wstype)
for (mime, fmt) in _gr_mimeformats for (mime, fmt) in _gr_mimeformats
@eval function _show(io::IO, ::MIME{Symbol($mime)}, plt::Plot{GRBackend}) @eval function _show(io::IO, ::MIME{Symbol($mime)}, plt::Plot{GRBackend})
GR.emergencyclosegks() GR.emergencyclosegks()
filepath = tempname() * "." * $fmt filepath = tempname() * "." * $fmt
withenv("GKS_WSTYPE" => $fmt, # $fmt == "png" ? "cairopng" : $fmt, ENV["GKS_WSTYPE"] = $fmt
"GKS_FILEPATH" => filepath) do ENV["GKS_FILEPATH"] = filepath
gr_display(plt) gr_display(plt)
GR.emergencyclosegks() GR.emergencyclosegks()
end
write(io, readstring(filepath)) write(io, readstring(filepath))
rm(filepath) rm(filepath)
end end
@ -1050,18 +1097,20 @@ function _display(plt::Plot{GRBackend})
if plt[:display_type] == :inline if plt[:display_type] == :inline
GR.emergencyclosegks() GR.emergencyclosegks()
filepath = tempname() * ".pdf" filepath = tempname() * ".pdf"
withenv("GKS_WSTYPE" => "pdf", ENV["GKS_WSTYPE"] = "pdf"
"GKS_FILEPATH" => filepath) do ENV["GKS_FILEPATH"] = filepath
gr_display(plt) gr_display(plt)
GR.emergencyclosegks() GR.emergencyclosegks()
end
content = string("\033]1337;File=inline=1;preserveAspectRatio=0:", base64encode(open(readbytes, filepath)), "\a") content = string("\033]1337;File=inline=1;preserveAspectRatio=0:", base64encode(open(readbytes, filepath)), "\a")
println(content) println(content)
rm(filepath) rm(filepath)
else else
withenv("GKS_WSTYPE" => get(ENV, "GKS_WSTYPE", _gr_wstype_default), ENV["GKS_DOUBLE_BUF"] = true
"GKS_DOUBLE_BUF" => get(ENV ,"GKS_DOUBLE_BUF", "true")) do if _gr_wstype[] != "use_default"
gr_display(plt) ENV["GKS_WSTYPE"] = _gr_wstype[]
end end
gr_display(plt)
end end
end end
closeall(::GRBackend) = GR.emergencyclosegks()

View File

@ -30,6 +30,7 @@ const _plotly_attr = merge_with_base_supported([
:aspect_ratio, :aspect_ratio,
:hover, :hover,
:inset_subplots, :inset_subplots,
:bar_width,
]) ])
const _plotly_seriestype = [ const _plotly_seriestype = [
@ -56,6 +57,7 @@ end
const _plotly_js_path = joinpath(dirname(@__FILE__), "..", "..", "deps", "plotly-latest.min.js") const _plotly_js_path = joinpath(dirname(@__FILE__), "..", "..", "deps", "plotly-latest.min.js")
const _plotly_js_path_remote = "https://cdn.plot.ly/plotly-latest.min.js"
function _initialize_backend(::PlotlyBackend; kw...) function _initialize_backend(::PlotlyBackend; kw...)
@eval begin @eval begin
@ -92,6 +94,20 @@ end
# ---------------------------------------------------------------- # ----------------------------------------------------------------
const _plotly_legend_pos = KW(
:right => [1., 0.5],
:left => [0., 0.5],
:top => [0.5, 1.],
:bottom => [0.5, 0.],
:bottomleft => [0., 0.],
:bottomright => [1., 0.],
:topright => [1., 1.],
:topleft => [0., 1.]
)
plotly_legend_pos(pos::Symbol) = get(_plotly_legend_pos, pos, [1.,1.])
plotly_legend_pos{S<:Real, T<:Real}(v::Tuple{S,T}) = v
function plotly_font(font::Font, color = font.color) function plotly_font(font::Font, color = font.color)
KW( KW(
:family => font.family, :family => font.family,
@ -100,6 +116,7 @@ function plotly_font(font::Font, color = font.color)
) )
end end
function plotly_annotation_dict(x, y, val; xref="paper", yref="paper") function plotly_annotation_dict(x, y, val; xref="paper", yref="paper")
KW( KW(
:text => val, :text => val,
@ -162,14 +179,17 @@ function plotly_apply_aspect_ratio(sp::Subplot, plotarea, pcts)
if aspect_ratio == :equal if aspect_ratio == :equal
aspect_ratio = 1.0 aspect_ratio = 1.0
end end
xmin,xmax = axis_limits(sp[:xaxis])
ymin,ymax = axis_limits(sp[:yaxis])
want_ratio = ((xmax-xmin) / (ymax-ymin)) / aspect_ratio
parea_ratio = width(plotarea) / height(plotarea) parea_ratio = width(plotarea) / height(plotarea)
if aspect_ratio > parea_ratio if want_ratio > parea_ratio
# need to shrink y # need to shrink y
ratio = parea_ratio / aspect_ratio ratio = parea_ratio / want_ratio
pcts[2], pcts[4] = shrink_by(pcts[2], pcts[4], ratio) pcts[2], pcts[4] = shrink_by(pcts[2], pcts[4], ratio)
elseif aspect_ratio < parea_ratio elseif want_ratio < parea_ratio
# need to shrink x # need to shrink x
ratio = aspect_ratio / parea_ratio ratio = want_ratio / parea_ratio
pcts[1], pcts[3] = shrink_by(pcts[1], pcts[3], ratio) pcts[1], pcts[3] = shrink_by(pcts[1], pcts[3], ratio)
end end
pcts pcts
@ -214,7 +234,7 @@ function plotly_axis(axis::Axis, sp::Subplot)
# lims # lims
lims = axis[:lims] lims = axis[:lims]
if lims != :auto && limsType(lims) == :limits if lims != :auto && limsType(lims) == :limits
ax[:range] = lims ax[:range] = map(scalefunc(axis[:scale]), lims)
end end
# flip # flip
@ -289,17 +309,32 @@ function plotly_layout(plt::Plot)
# legend # legend
d_out[:showlegend] = sp[:legend] != :none d_out[:showlegend] = sp[:legend] != :none
xpos,ypos = plotly_legend_pos(sp[:legend])
if sp[:legend] != :none if sp[:legend] != :none
d_out[:legend] = KW( d_out[:legend] = KW(
:bgcolor => rgba_string(sp[:background_color_legend]), :bgcolor => rgba_string(sp[:background_color_legend]),
:bordercolor => rgba_string(sp[:foreground_color_legend]), :bordercolor => rgba_string(sp[:foreground_color_legend]),
:font => plotly_font(sp[:legendfont], sp[:foreground_color_legend]), :font => plotly_font(sp[:legendfont], sp[:foreground_color_legend]),
:x => xpos,
:y => ypos
) )
end end
# annotations # annotations
append!(d_out[:annotations], KW[plotly_annotation_dict(ann...; xref = "x$spidx", yref = "y$spidx") for ann in sp[:annotations]]) append!(d_out[:annotations], KW[plotly_annotation_dict(ann...; xref = "x$spidx", yref = "y$spidx") for ann in sp[:annotations]])
# series_annotations
for series in series_list(sp)
anns = series[:series_annotations]
for (xi,yi,str,fnt) in EachAnn(anns, series[:x], series[:y])
push!(d_out[:annotations], plotly_annotation_dict(
xi,
yi,
PlotText(str,fnt); xref = "x$spidx", yref = "y$spidx")
)
end
end
# # arrows # # arrows
# for sargs in seriesargs # for sargs in seriesargs
# a = sargs[:arrow] # a = sargs[:arrow]
@ -359,7 +394,7 @@ function plotly_close_shapes(x, y)
xs, ys = nansplit(x), nansplit(y) xs, ys = nansplit(x), nansplit(y)
for i=1:length(xs) for i=1:length(xs)
shape = Shape(xs[i], ys[i]) shape = Shape(xs[i], ys[i])
xs[i], ys[i] = shape_coords(shape) xs[i], ys[i] = coords(shape)
end end
nanvcat(xs), nanvcat(ys) nanvcat(xs), nanvcat(ys)
end end
@ -420,8 +455,11 @@ function plotly_series(plt::Plot, series::Series)
elseif st == :bar elseif st == :bar
d_out[:type] = "bar" d_out[:type] = "bar"
d_out[:x], d_out[:y] = x, y d_out[:x], d_out[:y], d_out[:orientation] = if isvertical(series)
d_out[:orientation] = isvertical(series) ? "v" : "h" x, y, "v"
else
y, x, "h"
end
d_out[:marker] = KW(:color => rgba_string(series[:fillcolor])) d_out[:marker] = KW(:color => rgba_string(series[:fillcolor]))
elseif st == :heatmap elseif st == :heatmap
@ -592,8 +630,12 @@ end
# ---------------------------------------------------------------- # ----------------------------------------------------------------
const _use_remote = Ref(false)
function html_head(plt::Plot{PlotlyBackend}) function html_head(plt::Plot{PlotlyBackend})
"<script src=\"$(joinpath(dirname(@__FILE__),"..","..","deps","plotly-latest.min.js"))\"></script>" jsfilename = _use_remote[] ? _plotly_js_path_remote : _plotly_js_path
# "<script src=\"$(joinpath(dirname(@__FILE__),"..","..","deps","plotly-latest.min.js"))\"></script>"
"<script src=\"$jsfilename\"></script>"
end end
function html_body(plt::Plot{PlotlyBackend}, style = nothing) function html_body(plt::Plot{PlotlyBackend}, style = nothing)
@ -624,7 +666,8 @@ end
function _show(io::IO, ::MIME"image/png", plt::Plot{PlotlyBackend}) function _show(io::IO, ::MIME"image/png", plt::Plot{PlotlyBackend})
show_png_from_html(io, plt) # show_png_from_html(io, plt)
error("png output from the plotly backend is not supported. Please use plotlyjs instead.")
end end
function _show(io::IO, ::MIME"image/svg+xml", plt::Plot{PlotlyBackend}) function _show(io::IO, ::MIME"image/svg+xml", plt::Plot{PlotlyBackend})

View File

@ -40,7 +40,11 @@ end
function _create_backend_figure(plt::Plot{PlotlyJSBackend}) function _create_backend_figure(plt::Plot{PlotlyJSBackend})
PlotlyJS.plot() if !isplotnull() && plt[:overwrite_figure] && isa(current().o, PlotlyJS.SyncPlot)
PlotlyJS.SyncPlot(PlotlyJS.Plot(), current().o.view)
else
PlotlyJS.plot()
end
end end
@ -59,7 +63,7 @@ function _series_updated(plt::Plot{PlotlyJSBackend}, series::Series)
kw = KW(xsym => (series.d[:x],), ysym => (series.d[:y],)) kw = KW(xsym => (series.d[:x],), ysym => (series.d[:y],))
z = series[:z] z = series[:z]
if z != nothing if z != nothing
kw[:z] = (transpose_z(series, series[:z].surf, false),) kw[:z] = (isa(z,Surface) ? transpose_z(series, series[:z].surf, false) : z,)
end end
PlotlyJS.restyle!( PlotlyJS.restyle!(
plt.o, plt.o,
@ -82,7 +86,11 @@ end
# ---------------------------------------------------------------- # ----------------------------------------------------------------
function _show(io::IO, ::MIME"image/svg+xml", plt::Plot{PlotlyJSBackend}) function _show(io::IO, ::MIME"image/svg+xml", plt::Plot{PlotlyJSBackend})
show(io, MIME("text/html"), plt.o) if Plots.isijulia()
print(io, PlotlyJS.html_body(plt.o))
else
show(io, MIME("text/html"), plt.o)
end
end end
function plotlyjs_save_hack(io::IO, plt::Plot{PlotlyJSBackend}, ext::String) function plotlyjs_save_hack(io::IO, plt::Plot{PlotlyJSBackend}, ext::String)
@ -97,3 +105,10 @@ _show(io::IO, ::MIME"image/eps", plt::Plot{PlotlyJSBackend}) = plotlyjs_save_hac
function _display(plt::Plot{PlotlyJSBackend}) function _display(plt::Plot{PlotlyJSBackend})
display(plt.o) display(plt.o)
end end
function closeall(::PlotlyJSBackend)
if !isplotnull() && isa(current().o, PlotlyJS.SyncPlot)
close(current().o)
end
end

View File

@ -64,7 +64,7 @@ function _initialize_backend(::PyPlotBackend)
# problem: https://github.com/tbreloff/Plots.jl/issues/308 # problem: https://github.com/tbreloff/Plots.jl/issues/308
# solution: hack from @stevengj: https://github.com/stevengj/PyPlot.jl/pull/223#issuecomment-229747768 # solution: hack from @stevengj: https://github.com/stevengj/PyPlot.jl/pull/223#issuecomment-229747768
otherdisplays = splice!(Base.Multimedia.displays, 2:length(Base.Multimedia.displays)) otherdisplays = splice!(Base.Multimedia.displays, 2:length(Base.Multimedia.displays))
import PyPlot import PyPlot, PyCall
import LaTeXStrings: latexstring import LaTeXStrings: latexstring
append!(Base.Multimedia.displays, otherdisplays) append!(Base.Multimedia.displays, otherdisplays)
@ -117,7 +117,9 @@ py_color(grad::ColorGradient) = py_color(grad.colors)
function py_colormap(grad::ColorGradient) function py_colormap(grad::ColorGradient)
pyvals = [(z, py_color(grad[z])) for z in grad.values] pyvals = [(z, py_color(grad[z])) for z in grad.values]
pycolors.LinearSegmentedColormap[:from_list]("tmp", pyvals) cm = pycolors.LinearSegmentedColormap[:from_list]("tmp", pyvals)
cm[:set_bad](color=(0,0,0,0.0), alpha=0.0)
cm
end end
py_colormap(c) = py_colormap(cgrad()) py_colormap(c) = py_colormap(cgrad())
@ -140,7 +142,7 @@ function py_linestyle(seriestype::Symbol, linestyle::Symbol)
end end
function py_marker(marker::Shape) function py_marker(marker::Shape)
x, y = shape_coords(marker) x, y = coords(marker)
n = length(x) n = length(x)
mat = zeros(n+1,2) mat = zeros(n+1,2)
for i=1:n for i=1:n
@ -246,6 +248,12 @@ function labelfunc(scale::Symbol, backend::PyPlotBackend)
end end
end end
function py_mask_nans(z)
# PyPlot.pywrap(pynp.ma[:masked_invalid](PyPlot.pywrap(z)))
PyCall.pycall(pynp.ma[:masked_invalid], Any, z)
# pynp.ma[:masked_where](pynp.isnan(z),z)
end
# --------------------------------------------------------------------------- # ---------------------------------------------------------------------------
function fix_xy_lengths!(plt::Plot{PyPlotBackend}, series::Series) function fix_xy_lengths!(plt::Plot{PyPlotBackend}, series::Series)
@ -437,6 +445,9 @@ function py_add_series(plt::Plot{PyPlotBackend}, series::Series)
error("Only numbers and vectors are supported with levels keyword") error("Only numbers and vectors are supported with levels keyword")
end end
# add custom frame shapes to markershape?
series_annotations_shapes!(series, :xy)
# for each plotting command, optionally build and add a series handle to the list # for each plotting command, optionally build and add a series handle to the list
# line plot # line plot
@ -552,16 +563,46 @@ function py_add_series(plt::Plot{PyPlotBackend}, series::Series)
else else
xyargs xyargs
end end
handle = ax[:scatter](xyargs...;
label = series[:label], if isa(series[:markershape], AbstractVector{Shape})
zorder = series[:series_plotindex] + 0.5, # this section will create one scatter per data point to accomodate the
marker = py_marker(series[:markershape]), # vector of shapes
s = py_dpi_scale(plt, series[:markersize] .^ 2), handle = []
edgecolors = py_markerstrokecolor(series), x,y = xyargs
linewidths = py_dpi_scale(plt, series[:markerstrokewidth]), shapes = series[:markershape]
extrakw... msc = py_markerstrokecolor(series)
) lw = py_dpi_scale(plt, series[:markerstrokewidth])
push!(handles, handle) for i=1:length(y)
extrakw[:c] = if series[:marker_z] == nothing
py_color_fix(py_color(cycle(series[:markercolor],i)), x)
else
extrakw[:c]
end
push!(handle, ax[:scatter](cycle(x,i), cycle(y,i);
label = series[:label],
zorder = series[:series_plotindex] + 0.5,
marker = py_marker(cycle(shapes,i)),
s = py_dpi_scale(plt, cycle(series[:markersize],i) .^ 2),
edgecolors = msc,
linewidths = lw,
extrakw...
))
end
push!(handles, handle)
else
# do a normal scatter plot
handle = ax[:scatter](xyargs...;
label = series[:label],
zorder = series[:series_plotindex] + 0.5,
marker = py_marker(series[:markershape]),
s = py_dpi_scale(plt, series[:markersize] .^ 2),
edgecolors = py_markerstrokecolor(series),
linewidths = py_dpi_scale(plt, series[:markerstrokewidth]),
extrakw...
)
push!(handles, handle)
end
end end
if st == :hexbin if st == :hexbin
@ -730,12 +771,11 @@ function py_add_series(plt::Plot{PyPlotBackend}, series::Series)
end end
clims = sp[:clims] clims = sp[:clims]
if is_2tuple(clims) zmin, zmax = extrema(z)
isfinite(clims[1]) && (extrakw[:vmin] = clims[1]) extrakw[:vmin] = (is_2tuple(clims) && isfinite(clims[1])) ? clims[1] : zmin
isfinite(clims[2]) && (extrakw[:vmax] = clims[2]) extrakw[:vmax] = (is_2tuple(clims) && isfinite(clims[2])) ? clims[2] : zmax
end
handle = ax[:pcolormesh](x, y, z; handle = ax[:pcolormesh](x, y, py_mask_nans(z);
label = series[:label], label = series[:label],
zorder = series[:series_plotindex], zorder = series[:series_plotindex],
cmap = py_fillcolormap(series), cmap = py_fillcolormap(series),
@ -763,16 +803,6 @@ function py_add_series(plt::Plot{PyPlotBackend}, series::Series)
push!(handle, ax[:add_patch](patches)) push!(handle, ax[:add_patch](patches))
end end
end end
# path = py_path(x, y)
# patches = pypatches.pymember("PathPatch")(path;
# label = series[:label],
# zorder = series[:series_plotindex],
# edgecolor = py_linecolor(series),
# facecolor = py_fillcolor(series),
# linewidth = py_dpi_scale(plt, series[:linewidth]),
# fill = true
# )
# handle = ax[:add_patch](patches)
push!(handles, handle) push!(handles, handle)
end end
@ -842,6 +872,12 @@ function py_add_series(plt::Plot{PyPlotBackend}, series::Series)
) )
push!(handles, handle) push!(handles, handle)
end end
# this is all we need to add the series_annotations text
anns = series[:series_annotations]
for (xi,yi,str,fnt) in EachAnn(anns, x, y)
py_add_annotations(sp, xi, yi, PlotText(str, fnt))
end
end end
# -------------------------------------------------------------------------- # --------------------------------------------------------------------------
@ -1217,3 +1253,5 @@ for (mime, fmt) in _pyplot_mimeformats
) )
end end
end end
closeall(::PyPlotBackend) = PyPlot.plt[:close]("all")

View File

@ -16,7 +16,8 @@ const _unicodeplots_seriestype = [
:path, :scatter, :path, :scatter,
# :bar, # :bar,
:shape, :shape,
:histogram2d :histogram2d,
:spy
] ]
const _unicodeplots_style = [:auto, :solid] const _unicodeplots_style = [:auto, :solid]
const _unicodeplots_marker = [:none, :auto, :circle] const _unicodeplots_marker = [:none, :auto, :circle]
@ -44,6 +45,18 @@ end
# ------------------------------- # -------------------------------
const _canvas_type = Ref(:auto)
function _canvas_map()
KW(
:braille => UnicodePlots.BrailleCanvas,
:ascii => UnicodePlots.AsciiCanvas,
:block => UnicodePlots.BlockCanvas,
:dot => UnicodePlots.DotCanvas,
:density => UnicodePlots.DensityCanvas,
)
end
# do all the magic here... build it all at once, since we need to know about all the series at the very beginning # 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, width, height) function rebuildUnicodePlot!(plt::Plot, width, height)
@ -65,7 +78,27 @@ function rebuildUnicodePlot!(plt::Plot, width, height)
y = Float64[ylim[1]] y = Float64[ylim[1]]
# create a plot window with xlim/ylim set, but the X/Y vectors are outside the bounds # create a plot window with xlim/ylim set, but the X/Y vectors are outside the bounds
canvas_type = isijulia() ? UnicodePlots.AsciiCanvas : UnicodePlots.BrailleCanvas ct = _canvas_type[]
canvas_type = if ct == :auto
isijulia() ? UnicodePlots.AsciiCanvas : UnicodePlots.BrailleCanvas
else
_canvas_map()[ct]
end
# special handling for spy
if length(sp.series_list) == 1
series = sp.series_list[1]
if series[:seriestype] == :spy
push!(plt.o, UnicodePlots.spy(
series[:z].surf,
width = width,
height = height,
title = sp[:title],
canvas = canvas_type
))
continue
end
end
# # make it a bar canvas if plotting bar # # make it a bar canvas if plotting bar
# if any(series -> series[:seriestype] == :bar, series_list(sp)) # if any(series -> series[:seriestype] == :bar, series_list(sp))

View File

@ -23,21 +23,24 @@ immutable Shape
# end # end
end end
Shape(verts::AVec) = Shape(unzip(verts)...) Shape(verts::AVec) = Shape(unzip(verts)...)
Shape(s::Shape) = deepcopy(s)
get_xs(shape::Shape) = shape.x get_xs(shape::Shape) = shape.x
get_ys(shape::Shape) = shape.y get_ys(shape::Shape) = shape.y
vertices(shape::Shape) = collect(zip(shape.x, shape.y)) vertices(shape::Shape) = collect(zip(shape.x, shape.y))
#deprecated
@deprecate shape_coords coords
function shape_coords(shape::Shape) function coords(shape::Shape)
shape.x, shape.y shape.x, shape.y
end end
function shape_coords(shapes::AVec{Shape}) function coords(shapes::AVec{Shape})
length(shapes) == 0 && return zeros(0), zeros(0) length(shapes) == 0 && return zeros(0), zeros(0)
xs = map(get_xs, shapes) xs = map(get_xs, shapes)
ys = map(get_ys, shapes) ys = map(get_ys, shapes)
x, y = map(copy, shape_coords(shapes[1])) x, y = map(copy, coords(shapes[1]))
for shape in shapes[2:end] for shape in shapes[2:end]
nanappend!(x, shape.x) nanappend!(x, shape.x)
nanappend!(y, shape.y) nanappend!(y, shape.y)
@ -72,13 +75,13 @@ function makestar(n; offset = -0.5, radius = 1.0)
z2 = z1 + π / (n) z2 = z1 + π / (n)
outercircle = partialcircle(z1, z1 + 2π, n+1, radius) outercircle = partialcircle(z1, z1 + 2π, n+1, radius)
innercircle = partialcircle(z2, z2 + 2π, n+1, 0.4radius) innercircle = partialcircle(z2, z2 + 2π, n+1, 0.4radius)
Shape(weave(outercircle, innercircle)[1:end-2]) Shape(weave(outercircle, innercircle))
end end
"create a shape by picking points around the unit circle. `n` is the number of point/sides, `offset` is the starting angle" "create a shape by picking points around the unit circle. `n` is the number of point/sides, `offset` is the starting angle"
function makeshape(n; offset = -0.5, radius = 1.0) function makeshape(n; offset = -0.5, radius = 1.0)
z = offset * π z = offset * π
Shape(partialcircle(z, z + 2π, n+1, radius)[1:end-1]) Shape(partialcircle(z, z + 2π, n+1, radius))
end end
@ -88,7 +91,7 @@ function makecross(; offset = -0.5, radius = 1.0)
outercircle = partialcircle(z1, z1 + 2π, 9, radius) outercircle = partialcircle(z1, z1 + 2π, 9, radius)
innercircle = partialcircle(z2, z2 + 2π, 5, 0.5radius) innercircle = partialcircle(z2, z2 + 2π, 5, 0.5radius)
Shape(weave(outercircle, innercircle, Shape(weave(outercircle, innercircle,
ordering=Vector[outercircle,innercircle,outercircle])[1:end-2]) ordering=Vector[outercircle,innercircle,outercircle]))
end end
@ -110,6 +113,8 @@ const _shape_keys = Symbol[
:xcross, :xcross,
:utriangle, :utriangle,
:dtriangle, :dtriangle,
:rtriangle,
:ltriangle,
:pentagon, :pentagon,
:heptagon, :heptagon,
:octagon, :octagon,
@ -127,8 +132,10 @@ const _shapes = KW(
:circle => makeshape(20), :circle => makeshape(20),
:rect => makeshape(4, offset=-0.25), :rect => makeshape(4, offset=-0.25),
:diamond => makeshape(4), :diamond => makeshape(4),
:utriangle => makeshape(3), :utriangle => makeshape(3, offset=0.5),
:dtriangle => makeshape(3, offset=0.5), :dtriangle => makeshape(3, offset=-0.5),
:rtriangle => makeshape(3, offset=0.0),
:ltriangle => makeshape(3, offset=1.0),
:pentagon => makeshape(5), :pentagon => makeshape(5),
:hexagon => makeshape(6), :hexagon => makeshape(6),
:heptagon => makeshape(7), :heptagon => makeshape(7),
@ -143,12 +150,14 @@ for n in [4,5,6,7,8]
_shapes[Symbol("star$n")] = makestar(n) _shapes[Symbol("star$n")] = makestar(n)
end end
Shape(k::Symbol) = deepcopy(_shapes[k])
# ----------------------------------------------------------------------- # -----------------------------------------------------------------------
# uses the centroid calculation from https://en.wikipedia.org/wiki/Centroid#Centroid_of_polygon # uses the centroid calculation from https://en.wikipedia.org/wiki/Centroid#Centroid_of_polygon
function center(shape::Shape) function center(shape::Shape)
x, y = shape_coords(shape) x, y = coords(shape)
n = length(x) n = length(x)
A, Cx, Cy = 0.0, 0.0, 0.0 A, Cx, Cy = 0.0, 0.0, 0.0
for i=1:n for i=1:n
@ -166,7 +175,7 @@ function center(shape::Shape)
end end
function Base.scale!(shape::Shape, x::Real, y::Real = x, c = center(shape)) function Base.scale!(shape::Shape, x::Real, y::Real = x, c = center(shape))
sx, sy = shape_coords(shape) sx, sy = coords(shape)
cx, cy = c cx, cy = c
for i=1:length(sx) for i=1:length(sx)
sx[i] = (sx[i] - cx) * x + cx sx[i] = (sx[i] - cx) * x + cx
@ -177,11 +186,11 @@ end
function Base.scale(shape::Shape, x::Real, y::Real = x, c = center(shape)) function Base.scale(shape::Shape, x::Real, y::Real = x, c = center(shape))
shapecopy = deepcopy(shape) shapecopy = deepcopy(shape)
scale!(shape, x, y, c) scale!(shapecopy, x, y, c)
end end
function translate!(shape::Shape, x::Real, y::Real = x) function translate!(shape::Shape, x::Real, y::Real = x)
sx, sy = shape_coords(shape) sx, sy = coords(shape)
for i=1:length(sx) for i=1:length(sx)
sx[i] += x sx[i] += x
sy[i] += y sy[i] += y
@ -191,7 +200,7 @@ end
function translate(shape::Shape, x::Real, y::Real = x) function translate(shape::Shape, x::Real, y::Real = x)
shapecopy = deepcopy(shape) shapecopy = deepcopy(shape)
translate!(shape, x, y) translate!(shapecopy, x, y)
end end
function rotate_x(x::Real, y::Real, Θ::Real, centerx::Real, centery::Real) function rotate_x(x::Real, y::Real, Θ::Real, centerx::Real, centery::Real)
@ -208,7 +217,7 @@ function rotate(x::Real, y::Real, θ::Real, c = center(shape))
end end
function rotate!(shape::Shape, Θ::Real, c = center(shape)) function rotate!(shape::Shape, Θ::Real, c = center(shape))
x, y = shape_coords(shape) x, y = coords(shape)
cx, cy = c cx, cy = c
for i=1:length(x) for i=1:length(x)
xi = rotate_x(x[i], y[i], Θ, cx, cy) xi = rotate_x(x[i], y[i], Θ, cx, cy)
@ -226,7 +235,7 @@ end
# ----------------------------------------------------------------------- # -----------------------------------------------------------------------
immutable Font type Font
family::AbstractString family::AbstractString
pointsize::Int pointsize::Int
halign::Symbol halign::Symbol
@ -283,6 +292,17 @@ function font(args...)
Font(family, pointsize, halign, valign, rotation, color) Font(family, pointsize, halign, valign, rotation, color)
end end
function scalefontsize(k::Symbol, factor::Number)
f = default(k)
f.pointsize = round(Int, factor * f.pointsize)
default(k, f)
end
function scalefontsizes(factor::Number)
for k in (:titlefont, :guidefont, :tickfont, :legendfont)
scalefontsize(k, factor)
end
end
"Wrap a string with font info" "Wrap a string with font info"
immutable PlotText immutable PlotText
str::AbstractString str::AbstractString
@ -296,11 +316,7 @@ function text(str, args...)
PlotText(string(str), font(args...)) PlotText(string(str), font(args...))
end end
Base.length(t::PlotText) = length(t.str)
annotations(::Void) = []
annotations(anns::AVec) = anns
annotations(anns) = Any[anns]
# ----------------------------------------------------------------------- # -----------------------------------------------------------------------
@ -314,9 +330,9 @@ immutable Stroke
end end
function stroke(args...; alpha = nothing) function stroke(args...; alpha = nothing)
width = nothing width = 1
color = nothing color = :black
style = nothing style = :solid
for arg in args for arg in args
T = typeof(arg) T = typeof(arg)
@ -350,8 +366,8 @@ immutable Brush
end end
function brush(args...; alpha = nothing) function brush(args...; alpha = nothing)
size = nothing size = 1
color = nothing color = :black
for arg in args for arg in args
T = typeof(arg) T = typeof(arg)
@ -376,6 +392,109 @@ end
# ----------------------------------------------------------------------- # -----------------------------------------------------------------------
type SeriesAnnotations
strs::AbstractVector # the labels/names
font::Font
baseshape::Nullable
scalefactor::Tuple
end
function series_annotations(strs::AbstractVector, args...)
fnt = font()
shp = Nullable{Any}()
scalefactor = (1,1)
for arg in args
if isa(arg, Shape) || (isa(arg, AbstractVector) && eltype(arg) == Shape)
shp = Nullable(arg)
elseif isa(arg, Font)
fnt = arg
elseif isa(arg, Symbol) && haskey(_shapes, arg)
shp = _shapes[arg]
elseif isa(arg, Number)
scalefactor = (arg,arg)
elseif is_2tuple(arg)
scalefactor = arg
else
warn("Unused SeriesAnnotations arg: $arg ($(typeof(arg)))")
end
end
# if scalefactor != 1
# for s in get(shp)
# scale!(s, scalefactor, scalefactor, (0,0))
# end
# end
SeriesAnnotations(strs, fnt, shp, scalefactor)
end
series_annotations(anns::SeriesAnnotations) = anns
series_annotations(::Void) = nothing
function series_annotations_shapes!(series::Series, scaletype::Symbol = :pixels)
anns = series[:series_annotations]
# msw,msh = anns.scalefactor
# ms = series[:markersize]
# msw,msh = if isa(ms, AbstractVector)
# 1,1
# elseif is_2tuple(ms)
# ms
# else
# ms,ms
# end
# @show msw msh
if anns != nothing && !isnull(anns.baseshape)
# we use baseshape to overwrite the markershape attribute
# with a list of custom shapes for each
msw,msh = anns.scalefactor
msize = Float64[]
shapes = Shape[begin
str = cycle(anns.strs,i)
# get the width and height of the string (in mm)
sw, sh = text_size(str, anns.font.pointsize)
# how much to scale the base shape?
# note: it's a rough assumption that the shape fills the unit box [-1,-1,1,1],
# so we scale the length-2 shape by 1/2 the total length
scalar = (backend() == PyPlotBackend() ? 1.7 : 1.0)
xscale = 0.5to_pixels(sw) * scalar
yscale = 0.5to_pixels(sh) * scalar
# we save the size of the larger direction to the markersize list,
# and then re-scale a copy of baseshape to match the w/h ratio
maxscale = max(xscale, yscale)
push!(msize, maxscale)
baseshape = cycle(get(anns.baseshape),i)
shape = scale(baseshape, msw*xscale/maxscale, msh*yscale/maxscale, (0,0))
end for i=1:length(anns.strs)]
series[:markershape] = shapes
series[:markersize] = msize
end
return
end
type EachAnn
anns
x
y
end
Base.start(ea::EachAnn) = 1
Base.done(ea::EachAnn, i) = ea.anns == nothing || isempty(ea.anns.strs) || i > length(ea.y)
function Base.next(ea::EachAnn, i)
tmp = cycle(ea.anns.strs,i)
str,fnt = if isa(tmp, PlotText)
tmp.str, tmp.font
else
tmp, ea.anns.font
end
((cycle(ea.x,i), cycle(ea.y,i), str, fnt), i+1)
end
annotations(::Void) = []
annotations(anns::AVec) = anns
annotations(anns) = Any[anns]
annotations(sa::SeriesAnnotations) = sa
# -----------------------------------------------------------------------
"type which represents z-values for colors and sizes (and anything else that might come up)" "type which represents z-values for colors and sizes (and anything else that might come up)"
immutable ZValues immutable ZValues
values::Vector{Float64} values::Vector{Float64}
@ -452,19 +571,25 @@ Base.eltype{T}(vol::Volume{T}) = T
# style is :open or :closed (for now) # style is :open or :closed (for now)
immutable Arrow immutable Arrow
style::Symbol style::Symbol
side::Symbol # :head (default), :tail, or :both
headlength::Float64 headlength::Float64
headwidth::Float64 headwidth::Float64
end end
function arrow(args...) function arrow(args...)
style = :simple style = :simple
side = :head
headlength = 0.3 headlength = 0.3
headwidth = 0.3 headwidth = 0.3
setlength = false setlength = false
for arg in args for arg in args
T = typeof(arg) T = typeof(arg)
if T == Symbol if T == Symbol
style = arg if arg in (:head, :tail, :both)
side = arg
else
style = arg
end
elseif T <: Number elseif T <: Number
# first we apply to both, but if there's more, then only change width after the first number # first we apply to both, but if there's more, then only change width after the first number
headwidth = Float64(arg) headwidth = Float64(arg)
@ -478,7 +603,7 @@ function arrow(args...)
warn("Skipped arrow arg $arg") warn("Skipped arrow arg $arg")
end end
end end
Arrow(style, headlength, headwidth) Arrow(style, side, headlength, headwidth)
end end
@ -508,54 +633,34 @@ end
# ----------------------------------------------------------------------- # -----------------------------------------------------------------------
type BezierCurve{T <: FixedSizeArrays.Vec} type BezierCurve{T <: FixedSizeArrays.Vec}
control_points::Vector{T} control_points::Vector{T}
end end
function (bc::BezierCurve)(t::Real) function (bc::BezierCurve)(t::Real)
p = zero(P2) p = zero(P2)
n = length(bc.control_points)-1 n = length(bc.control_points)-1
for i in 0:n for i in 0:n
p += bc.control_points[i+1] * binomial(n, i) * (1-t)^(n-i) * t^i p += bc.control_points[i+1] * binomial(n, i) * (1-t)^(n-i) * t^i
end end
p p
end end
Base.mean(x::Real, y::Real) = 0.5*(x+y) Base.mean(x::Real, y::Real) = 0.5*(x+y)
Base.mean{N,T<:Real}(ps::FixedSizeArrays.Vec{N,T}...) = sum(ps) / length(ps) Base.mean{N,T<:Real}(ps::FixedSizeArrays.Vec{N,T}...) = sum(ps) / length(ps)
curve_points(curve::BezierCurve, n::Integer = 30; range = [0,1]) = map(curve, linspace(range..., n)) @deprecate curve_points coords
coords(curve::BezierCurve, n::Integer = 30; range = [0,1]) = map(curve, linspace(range..., n))
# build a BezierCurve which leaves point p vertically upwards and arrives point q vertically upwards. # build a BezierCurve which leaves point p vertically upwards and arrives point q vertically upwards.
# may create a loop if necessary. Assumes the view is [0,1] # may create a loop if necessary. Assumes the view is [0,1]
function directed_curve(p::P2, q::P2; xview = 0:1, yview = 0:1) function directed_curve(args...; kw...)
mn = mean(p, q) error("directed_curve has been moved to PlotRecipes")
diff = q - p
minx, maxx = minimum(xview), maximum(xview)
miny, maxy = minimum(yview), maximum(yview)
diffpct = P2(diff[1] / (maxx - minx),
diff[2] / (maxy - miny))
# these points give the initial/final "rise"
# vertical_offset = P2(0, (maxy - miny) * max(0.03, min(abs(0.5diffpct[2]), 1.0)))
vertical_offset = P2(0, max(0.15, 0.5norm(diff)))
upper_control = p + vertical_offset
lower_control = q - vertical_offset
# try to figure out when to loop around vs just connecting straight
# TODO: choose loop direction based on sign of p[1]??
# x_close_together = abs(diffpct[1]) <= 0.05
p_is_higher = diff[2] <= 0
inside_control_points = if p_is_higher
# add curve points which will create a loop
sgn = mn[1] < 0.5 * (maxx + minx) ? -1 : 1
inside_offset = P2(0.3 * (maxx - minx), 0)
additional_offset = P2(sgn * diff[1], 0) # make it even loopier
[upper_control + sgn * (inside_offset + max(0, additional_offset)),
lower_control + sgn * (inside_offset + max(0, -additional_offset))]
else
[]
end end
BezierCurve([p, upper_control, inside_control_points..., lower_control, q]) function extrema_plus_buffer(v, buffmult = 0.2)
vmin,vmax = extrema(v)
vdiff = vmax-vmin
buffer = vdiff * buffmult
vmin - buffer, vmax + buffer
end end

View File

@ -40,7 +40,7 @@ PlotExample("Colors",
"Access predefined palettes (or build your own with the `colorscheme` method). Line/marker colors are auto-generated from the plot's palette, unless overridden. Set the `z` argument to turn on series gradients.", "Access predefined palettes (or build your own with the `colorscheme` method). Line/marker colors are auto-generated from the plot's palette, unless overridden. Set the `z` argument to turn on series gradients.",
[:(begin [:(begin
y = rand(100) y = rand(100)
plot(0:10:100,rand(11,4),lab="lines",w=3,palette=:grays,fill=(0,:auto), α=0.6) plot(0:10:100,rand(11,4),lab="lines",w=3,palette=:grays,fill=0, α=0.6)
scatter!(y, zcolor=abs(y-.5), m=(:heat,0.8,stroke(1,:green)), ms=10*abs(y-0.5)+4, lab="grad") scatter!(y, zcolor=abs(y-.5), m=(:heat,0.8,stroke(1,:green)), ms=10*abs(y-0.5)+4, lab="grad")
end)] end)]
), ),

View File

@ -96,6 +96,32 @@ function Base.show(io::IO, bbox::BoundingBox)
print(io, "BBox{l,t,r,b,w,h = $(left(bbox)),$(top(bbox)), $(right(bbox)),$(bottom(bbox)), $(width(bbox)),$(height(bbox))}") print(io, "BBox{l,t,r,b,w,h = $(left(bbox)),$(top(bbox)), $(right(bbox)),$(bottom(bbox)), $(width(bbox)),$(height(bbox))}")
end end
# -----------------------------------------------------------
# points combined by x/y, pct, and length
type MixedMeasures
xy::Float64
pct::Float64
len::AbsoluteLength
end
function resolve_mixed(mix::MixedMeasures, sp::Subplot, letter::Symbol)
xy = mix.xy
pct = mix.pct
if mix.len != 0mm
f = (letter == :x ? width : height)
totlen = f(plotarea(sp))
@show totlen
pct += mix.len / totlen
end
if pct != 0
amin, amax = axis_limits(sp[Symbol(letter,:axis)])
xy += pct * (amax-amin)
end
xy
end
# ----------------------------------------------------------- # -----------------------------------------------------------
# AbstractLayout # AbstractLayout
@ -692,9 +718,22 @@ function link_axes!(axes::Axis...)
end end
end end
# figure out which subplots to link
function link_subplots(a::AbstractArray{AbstractLayout}, axissym::Symbol)
subplots = []
for l in a
if isa(l, Subplot)
push!(subplots, l)
elseif isa(l, GridLayout) && size(l) == (1,1)
push!(subplots, l[1,1])
end
end
subplots
end
# for some vector or matrix of layouts, filter only the Subplots and link those axes # for some vector or matrix of layouts, filter only the Subplots and link those axes
function link_axes!(a::AbstractArray{AbstractLayout}, axissym::Symbol) function link_axes!(a::AbstractArray{AbstractLayout}, axissym::Symbol)
subplots = filter(l -> isa(l, Subplot), a) subplots = link_subplots(a, axissym)
axes = [sp.attr[axissym] for sp in subplots] axes = [sp.attr[axissym] for sp in subplots]
if length(axes) > 0 if length(axes) > 0
link_axes!(axes...) link_axes!(axes...)

View File

@ -52,6 +52,16 @@ function tex(plt::Plot, fn::AbstractString)
end end
tex(fn::AbstractString) = tex(current(), fn) tex(fn::AbstractString) = tex(current(), fn)
function html(plt::Plot, fn::AbstractString)
fn = addExtension(fn, "html")
io = open(fn, "w")
_use_remote[] = true
show(io, MIME("text/html"), plt)
_use_remote[] = false
close(io)
end
html(fn::AbstractString) = html(current(), fn)
# ---------------------------------------------------------------- # ----------------------------------------------------------------
@ -63,6 +73,7 @@ const _savemap = Dict(
"ps" => ps, "ps" => ps,
"eps" => eps, "eps" => eps,
"tex" => tex, "tex" => tex,
"html" => html,
) )
function getExtension(fn::AbstractString) function getExtension(fn::AbstractString)
@ -111,6 +122,13 @@ savefig(fn::AbstractString) = savefig(current(), fn)
gui(plt::Plot = current()) = display(PlotsDisplay(), plt) gui(plt::Plot = current()) = display(PlotsDisplay(), plt)
# IJulia only... inline display
function inline(plt::Plot = current())
isijulia() || error("inline() is IJulia-only")
Main.IJulia.clear_output(true)
display(Main.IJulia.InlineDisplay(), plt)
end
function Base.display(::PlotsDisplay, plt::Plot) function Base.display(::PlotsDisplay, plt::Plot)
prepare_output(plt) prepare_output(plt)
_display(plt) _display(plt)
@ -119,6 +137,13 @@ end
# override the REPL display to open a gui window # override the REPL display to open a gui window
Base.display(::Base.REPL.REPLDisplay, ::MIME"text/plain", plt::Plot) = gui(plt) Base.display(::Base.REPL.REPLDisplay, ::MIME"text/plain", plt::Plot) = gui(plt)
_do_plot_show(plt, showval::Bool) = showval && gui(plt)
function _do_plot_show(plt, showval::Symbol)
showval == :gui && gui(plt)
showval in (:inline,:ijulia) && inline(plt)
end
# --------------------------------------------------------- # ---------------------------------------------------------
const _mimeformats = Dict( const _mimeformats = Dict(
@ -172,6 +197,8 @@ for mime in keys(_mimeformats)
end end
end end
closeall() = closeall(backend())
# --------------------------------------------------------- # ---------------------------------------------------------
# A backup, if no PNG generation is defined, is to try to make a PDF and use FileIO to convert # A backup, if no PNG generation is defined, is to try to make a PDF and use FileIO to convert

View File

@ -283,7 +283,11 @@ function _subplot_setup(plt::Plot, d::KW, kw_list::Vector{KW})
# override subplot/axis args. `sp_attrs` take precendence # override subplot/axis args. `sp_attrs` take precendence
for (idx,sp) in enumerate(plt.subplots) for (idx,sp) in enumerate(plt.subplots)
attr = merge(d, get(sp_attrs, sp, KW())) attr = if !haskey(d, :subplot) || d[:subplot] == idx
merge(d, get(sp_attrs, sp, KW()))
else
get(sp_attrs, sp, KW())
end
_update_subplot_args(plt, sp, attr, idx, false) _update_subplot_args(plt, sp, attr, idx, false)
end end
@ -333,14 +337,17 @@ function _prepare_annotations(sp::Subplot, d::KW)
# strip out series annotations (those which are based on series x/y coords) # strip out series annotations (those which are based on series x/y coords)
# and add them to the subplot attr # and add them to the subplot attr
sp_anns = annotations(sp[:annotations]) sp_anns = annotations(sp[:annotations])
anns = annotations(pop!(d, :series_annotations, [])) # series_anns = annotations(pop!(d, :series_annotations, []))
if length(anns) > 0 # if isa(series_anns, SeriesAnnotations)
x, y = d[:x], d[:y] # series_anns.x = d[:x]
nx, ny, na = map(length, (x,y,anns)) # series_anns.y = d[:y]
n = max(nx, ny, na) # elseif length(series_anns) > 0
anns = [(x[mod1(i,nx)], y[mod1(i,ny)], text(anns[mod1(i,na)])) for i=1:n] # x, y = d[:x], d[:y]
end # nx, ny, na = map(length, (x,y,series_anns))
sp.attr[:annotations] = vcat(sp_anns, anns) # n = max(nx, ny, na)
# series_anns = [(x[mod1(i,nx)], y[mod1(i,ny)], text(series_anns[mod1(i,na)])) for i=1:n]
# end
# sp.attr[:annotations] = vcat(sp_anns, series_anns)
end end
function _expand_subplot_extrema(sp::Subplot, d::KW, st::Symbol) function _expand_subplot_extrema(sp::Subplot, d::KW, st::Symbol)

View File

@ -89,8 +89,16 @@ function plot(plt1::Plot, plts_tail::Plot...; kw...)
plt.o = _create_backend_figure(plt) plt.o = _create_backend_figure(plt)
plt.init = true plt.init = true
series_attr = KW()
for (k,v) in d
if haskey(_series_defaults, k)
series_attr[k] = pop!(d,k)
end
end
# create the layout and initialize the subplots # create the layout and initialize the subplots
plt.layout, plt.subplots, plt.spmap = build_layout(layout, num_sp, copy(plts)) plt.layout, plt.subplots, plt.spmap = build_layout(layout, num_sp, copy(plts))
cmdidx = 1
for (idx, sp) in enumerate(plt.subplots) for (idx, sp) in enumerate(plt.subplots)
_initialize_subplot(plt, sp) _initialize_subplot(plt, sp)
serieslist = series_list(sp) serieslist = series_list(sp)
@ -100,8 +108,11 @@ function plot(plt1::Plot, plts_tail::Plot...; kw...)
sp.plt = plt sp.plt = plt
sp.attr[:subplot_index] = idx sp.attr[:subplot_index] = idx
for series in serieslist for series in serieslist
merge!(series.d, series_attr)
_add_defaults!(series.d, plt, sp, cmdidx)
push!(plt.series_list, series) push!(plt.series_list, series)
_series_added(plt, series) _series_added(plt, series)
cmdidx += 1
end end
end end
@ -115,9 +126,7 @@ function plot(plt1::Plot, plts_tail::Plot...; kw...)
# finish up # finish up
current(plt) current(plt)
if get(d, :show, default(:show)) _do_plot_show(plt, get(d, :show, default(:show)))
gui()
end
plt plt
end end
@ -150,6 +159,11 @@ end
function _plot!(plt::Plot, d::KW, args::Tuple) function _plot!(plt::Plot, d::KW, args::Tuple)
d[:plot_object] = plt d[:plot_object] = plt
if !isempty(args) && !isdefined(Main, :StatPlots) &&
first(split(string(typeof(args[1])), ".")) == "DataFrames"
warn("You're trying to plot a DataFrame, but this functionality is provided by StatPlots")
end
# -------------------------------- # --------------------------------
# "USER RECIPES" # "USER RECIPES"
# -------------------------------- # --------------------------------
@ -218,9 +232,10 @@ function _plot!(plt::Plot, d::KW, args::Tuple)
current(plt) current(plt)
# do we want to force display? # do we want to force display?
if plt[:show] # if plt[:show]
gui(plt) # gui(plt)
end # end
_do_plot_show(plt, plt[:show])
plt plt
end end

View File

@ -93,57 +93,11 @@ end
# ---------------------------------------------------------------------------------- # ----------------------------------------------------------------------------------
num_series(x::AMat) = size(x,2) num_series(x::AMat) = size(x,2)
num_series(x) = 1 num_series(x) = 1
RecipesBase.apply_recipe{T}(d::KW, ::Type{T}, plt::Plot) = throw(MethodError("Unmatched plot recipe: $T")) RecipesBase.apply_recipe{T}(d::KW, ::Type{T}, plt::Plot) = throw(MethodError("Unmatched plot recipe: $T"))
# # TODO: remove when StatPlots is ready
# if is_installed("DataFrames")
# @eval begin
# import DataFrames
# # if it's one symbol, set the guide and return the column
# function handle_dfs(df::DataFrames.AbstractDataFrame, d::KW, letter, sym::Symbol)
# get!(d, Symbol(letter * "guide"), string(sym))
# collect(df[sym])
# end
# # if it's an array of symbols, set the labels and return a Vector{Any} of columns
# function handle_dfs(df::DataFrames.AbstractDataFrame, d::KW, letter, syms::AbstractArray{Symbol})
# get!(d, :label, reshape(syms, 1, length(syms)))
# Any[collect(df[s]) for s in syms]
# end
# # for anything else, no-op
# function handle_dfs(df::DataFrames.AbstractDataFrame, d::KW, letter, anything)
# anything
# end
# # handle grouping by DataFrame column
# function extractGroupArgs(group::Symbol, df::DataFrames.AbstractDataFrame, args...)
# extractGroupArgs(collect(df[group]))
# end
# # if a DataFrame is the first arg, lets swap symbols out for columns
# @recipe function f(df::DataFrames.AbstractDataFrame, args...)
# # if any of these attributes are symbols, swap out for the df column
# for k in (:fillrange, :line_z, :marker_z, :markersize, :ribbon, :weights, :xerror, :yerror)
# if haskey(d, k) && isa(d[k], Symbol)
# d[k] = collect(df[d[k]])
# end
# end
# # return a list of new arguments
# tuple(Any[handle_dfs(df, d, (i==1 ? "x" : i==2 ? "y" : "z"), arg) for (i,arg) in enumerate(args)]...)
# end
# end
# end
# --------------------------------------------------------------------------- # ---------------------------------------------------------------------------
@ -511,6 +465,11 @@ centers(v::AVec) = 0.5 * (v[1:end-1] + v[2:end])
xedges, yedges, counts = my_hist_2d(x, y, d[:bins], xedges, yedges, counts = my_hist_2d(x, y, d[:bins],
normed = d[:normalize], normed = d[:normalize],
weights = d[:weights]) weights = d[:weights])
for (i,c) in enumerate(counts)
if c == 0
counts[i] = NaN
end
end
x := centers(xedges) x := centers(xedges)
y := centers(yedges) y := centers(yedges)
z := Surface(counts) z := Surface(counts)
@ -536,179 +495,6 @@ end
# note: don't add dependencies because this really isn't a drop-in replacement # note: don't add dependencies because this really isn't a drop-in replacement
# # TODO: move boxplots and violin plots to StatPlots when it's ready
# # ---------------------------------------------------------------------------
# # Box Plot
# const _box_halfwidth = 0.4
# notch_width(q2, q4, N) = 1.58 * (q4-q2)/sqrt(N)
# @recipe function f(::Type{Val{:boxplot}}, x, y, z; notch=false, range=1.5)
# xsegs, ysegs = Segments(), Segments()
# glabels = sort(collect(unique(x)))
# warning = false
# outliers_x, outliers_y = zeros(0), zeros(0)
# for (i,glabel) in enumerate(glabels)
# # filter y
# values = y[filter(i -> cycle(x,i) == glabel, 1:length(y))]
# # compute quantiles
# q1,q2,q3,q4,q5 = quantile(values, linspace(0,1,5))
# # notch
# n = notch_width(q2, q4, length(values))
# # warn on inverted notches?
# if notch && !warning && ( (q2>(q3-n)) || (q4<(q3+n)) )
# warn("Boxplot's notch went outside hinges. Set notch to false.")
# warning = true # Show the warning only one time
# end
# # make the shape
# center = discrete_value!(d[:subplot][:xaxis], glabel)[1]
# hw = d[:bar_width] == nothing ? _box_halfwidth : 0.5cycle(d[:bar_width], i)
# l, m, r = center - hw, center, center + hw
# # internal nodes for notches
# L, R = center - 0.5 * hw, center + 0.5 * hw
# # outliers
# if Float64(range) != 0.0 # if the range is 0.0, the whiskers will extend to the data
# limit = range*(q4-q2)
# inside = Float64[]
# for value in values
# if (value < (q2 - limit)) || (value > (q4 + limit))
# push!(outliers_y, value)
# push!(outliers_x, center)
# else
# push!(inside, value)
# end
# end
# # change q1 and q5 to show outliers
# # using maximum and minimum values inside the limits
# q1, q5 = extrema(inside)
# end
# # Box
# if notch
# push!(xsegs, m, l, r, m, m) # lower T
# push!(xsegs, l, l, L, R, r, r, l) # lower box
# push!(xsegs, l, l, L, R, r, r, l) # upper box
# push!(xsegs, m, l, r, m, m) # upper T
# push!(ysegs, q1, q1, q1, q1, q2) # lower T
# push!(ysegs, q2, q3-n, q3, q3, q3-n, q2, q2) # lower box
# push!(ysegs, q4, q3+n, q3, q3, q3+n, q4, q4) # upper box
# push!(ysegs, q5, q5, q5, q5, q4) # upper T
# else
# push!(xsegs, m, l, r, m, m) # lower T
# push!(xsegs, l, l, r, r, l) # lower box
# push!(xsegs, l, l, r, r, l) # upper box
# push!(xsegs, m, l, r, m, m) # upper T
# push!(ysegs, q1, q1, q1, q1, q2) # lower T
# push!(ysegs, q2, q3, q3, q2, q2) # lower box
# push!(ysegs, q4, q3, q3, q4, q4) # upper box
# push!(ysegs, q5, q5, q5, q5, q4) # upper T
# end
# end
# # Outliers
# @series begin
# seriestype := :scatter
# markershape := :circle
# markercolor := d[:fillcolor]
# markeralpha := d[:fillalpha]
# markerstrokecolor := d[:linecolor]
# markerstrokealpha := d[:linealpha]
# x := outliers_x
# y := outliers_y
# primary := false
# ()
# end
# seriestype := :shape
# x := xsegs.pts
# y := ysegs.pts
# ()
# end
# @deps boxplot shape scatter
# # ---------------------------------------------------------------------------
# # Violin Plot
# const _violin_warned = [false]
# # if the user has KernelDensity installed, use this for violin plots.
# # otherwise, just use a histogram
# if is_installed("KernelDensity")
# @eval import KernelDensity
# @eval function violin_coords(y; trim::Bool=false)
# kd = KernelDensity.kde(y, npoints = 200)
# if trim
# xmin, xmax = extrema(y)
# inside = Bool[ xmin <= x <= xmax for x in kd.x]
# return(kd.density[inside], kd.x[inside])
# end
# kd.density, kd.x
# end
# else
# @eval function violin_coords(y; trim::Bool=false)
# if !_violin_warned[1]
# warn("Install the KernelDensity package for best results.")
# _violin_warned[1] = true
# end
# edges, widths = my_hist(y, 10)
# centers = 0.5 * (edges[1:end-1] + edges[2:end])
# ymin, ymax = extrema(y)
# vcat(0.0, widths, 0.0), vcat(ymin, centers, ymax)
# end
# end
# @recipe function f(::Type{Val{:violin}}, x, y, z; trim=true)
# xsegs, ysegs = Segments(), Segments()
# glabels = sort(collect(unique(x)))
# for glabel in glabels
# widths, centers = violin_coords(y[filter(i -> cycle(x,i) == glabel, 1:length(y))], trim=trim)
# isempty(widths) && continue
# # normalize
# widths = _box_halfwidth * widths / maximum(widths)
# # make the violin
# xcenter = discrete_value!(d[:subplot][:xaxis], glabel)[1]
# xcoords = vcat(widths, -reverse(widths)) + xcenter
# ycoords = vcat(centers, reverse(centers))
# push!(xsegs, xcoords)
# push!(ysegs, ycoords)
# end
# seriestype := :shape
# x := xsegs.pts
# y := ysegs.pts
# ()
# end
# @deps violin shape
# # ---------------------------------------------------------------------------
# # density
# @recipe function f(::Type{Val{:density}}, x, y, z; trim=false)
# newx, newy = violin_coords(y, trim=trim)
# if isvertical(d)
# newx, newy = newy, newx
# end
# x := newx
# y := newy
# seriestype := :path
# ()
# end
# @deps density path
# --------------------------------------------------------------------------- # ---------------------------------------------------------------------------
# contourf - filled contours # contourf - filled contours
@ -740,8 +526,7 @@ end
function error_coords(xorig, yorig, ebar) function error_coords(xorig, yorig, ebar)
# init empty x/y, and zip errors if passed Tuple{Vector,Vector} # init empty x/y, and zip errors if passed Tuple{Vector,Vector}
x, y = zeros(0), zeros(0) x, y = Array(float_extended_type(xorig), 0), Array(Float64, 0)
# for each point, create a line segment from the bottom to the top of the errorbar # for each point, create a line segment from the bottom to the top of the errorbar
for i = 1:max(length(xorig), length(yorig)) for i = 1:max(length(xorig), length(yorig))
xi = cycle(xorig, i) xi = cycle(xorig, i)
@ -947,15 +732,52 @@ end
# series recipe or moved to PlotRecipes # series recipe or moved to PlotRecipes
"Sparsity plot... heatmap of non-zero values of a matrix" # "Sparsity plot... heatmap of non-zero values of a matrix"
function spy{T<:Real}(z::AMat{T}; kw...) # function spy{T<:Real}(z::AMat{T}; kw...)
mat = map(zi->float(zi!=0), z)' # mat = map(zi->float(zi!=0), z)'
xn, yn = size(mat) # xn, yn = size(mat)
heatmap(mat; leg=false, yflip=true, aspect_ratio=:equal, # heatmap(mat; leg=false, yflip=true, aspect_ratio=:equal,
xlim=(0.5, xn+0.5), ylim=(0.5, yn+0.5), # xlim=(0.5, xn+0.5), ylim=(0.5, yn+0.5),
kw...) # kw...)
# end
# Only allow matrices through, and make it seriestype :spy so the backend can
# optionally handle it natively.
@userplot Spy
@recipe function f(g::Spy)
@assert length(g.args) == 1 && typeof(g.args[1]) <: AbstractMatrix
seriestype := :spy
mat = g.args[1]
n,m = size(mat)
Plots.SliceIt, 1:m, 1:n, Surface(mat)
end end
@recipe function f(::Type{Val{:spy}}, x,y,z)
yflip := true
aspect_ratio := 1
rs, cs, zs = findnz(z.surf)
xlim := extrema(cs)
ylim := extrema(rs)
if d[:markershape] == :none
markershape := :circle
end
if d[:markersize] == default(:markersize)
markersize := 1
end
markerstrokewidth := 0
marker_z := zs
label := ""
x := cs
y := rs
z := nothing
seriestype := :scatter
()
end
# -------------------------------------------------
"Adds a+bx... straight line over the current plot" "Adds a+bx... straight line over the current plot"
function abline!(plt::Plot, a, b; kw...) function abline!(plt::Plot, a, b; kw...)
plot!(plt, [extrema(plt)...], x -> b + a*x; kw...) plot!(plt, [extrema(plt)...], x -> b + a*x; kw...)
@ -967,14 +789,16 @@ abline!(args...; kw...) = abline!(current(), args...; kw...)
# ------------------------------------------------- # -------------------------------------------------
# Dates # Dates
@recipe function f{T<:AbstractArray{Date}}(::Type{T}, dts::T) @recipe f(::Type{Date}, dt::Date) = (dt -> convert(Int,dt), dt -> string(convert(Date,dt)))
date_formatter = dt -> string(convert(Date, dt)) @recipe f(::Type{DateTime}, dt::DateTime) = (dt -> convert(Int,dt), dt -> string(convert(DateTime,dt)))
xformatter := date_formatter
map(dt->convert(Int,dt), dts)
end
@recipe function f{T<:AbstractArray{DateTime}}(::Type{T}, dts::T) # -------------------------------------------------
date_formatter = dt -> string(convert(DateTime, dt)) # Complex Numbers
xformatter := date_formatter
map(dt->convert(Int,dt), dts) @userplot ComplexPlot
@recipe function f(cp::ComplexPlot)
xguide --> "Real Part"
yguide --> "Imaginary Part"
seriestype --> :scatter
real(cp.args[1]), imag(cp.args[1])
end end

View File

@ -42,8 +42,8 @@ convertToAnyVector(v::Volume, d::KW) = Any[v], nothing
# # vector of OHLC # # vector of OHLC
# convertToAnyVector(v::AVec{OHLC}, d::KW) = Any[v], nothing # convertToAnyVector(v::AVec{OHLC}, d::KW) = Any[v], nothing
# dates # # dates
convertToAnyVector{D<:Union{Date,DateTime}}(dts::AVec{D}, d::KW) = Any[dts], nothing # convertToAnyVector{D<:Union{Date,DateTime}}(dts::AVec{D}, d::KW) = Any[dts], nothing
# list of things (maybe other vectors, functions, or something else) # list of things (maybe other vectors, functions, or something else)
function convertToAnyVector(v::AVec, d::KW) function convertToAnyVector(v::AVec, d::KW)
@ -322,18 +322,18 @@ end
@recipe function f(shape::Shape) @recipe function f(shape::Shape)
seriestype --> :shape seriestype --> :shape
shape_coords(shape) coords(shape)
end end
@recipe function f(shapes::AVec{Shape}) @recipe function f(shapes::AVec{Shape})
seriestype --> :shape seriestype --> :shape
shape_coords(shapes) coords(shapes)
end end
@recipe function f(shapes::AMat{Shape}) @recipe function f(shapes::AMat{Shape})
seriestype --> :shape seriestype --> :shape
for j in 1:size(shapes,2) for j in 1:size(shapes,2)
@series shape_coords(vec(shapes[:,j])) @series coords(vec(shapes[:,j]))
end end
end end

View File

@ -1,65 +1,40 @@
const _invisible = RGBA(0,0,0,0) function theme(s::Symbol; kw...)
# reset?
if s == :none || s == :default
PlotUtils._default_gradient[] = :inferno
default(;
bg = :white,
bglegend = :match,
bginside = :match,
bgoutside = :match,
fg = :auto,
fglegend = :match,
fggrid = :match,
fgaxis = :match,
fgtext = :match,
fgborder = :match,
fgguide = :match,
palette = :auto
)
return
end
const _themes = KW( # update the default gradient and other defaults
:default => KW( thm = PlotThemes._themes[s]
:bg => :white, if thm.gradient != nothing
:bglegend => :match, PlotUtils._default_gradient[] = PlotThemes.gradient_name(s)
:bginside => :match, end
:bgoutside => :match, default(;
:fg => :auto, bg = thm.bg_secondary,
:fglegend => :match, bginside = thm.bg_primary,
:fggrid => :match, fg = thm.lines,
:fgaxis => :match, fgtext = thm.text,
:fgtext => :match, fgguide = thm.text,
:fgborder => :match, fglegend = thm.text,
:fgguide => :match, palette = thm.palette,
kw...
) )
)
function add_theme(sym::Symbol, theme::KW)
_themes[sym] = theme
end end
# add a new theme, using an existing theme as the base @deprecate set_theme(s) theme(s)
function add_theme(sym::Symbol;
base = :default, # start with this theme
bg = _themes[base][:bg],
bglegend = _themes[base][:bglegend],
bginside = _themes[base][:bginside],
bgoutside = _themes[base][:bgoutside],
fg = _themes[base][:fg],
fglegend = _themes[base][:fglegend],
fggrid = _themes[base][:fggrid],
fgaxis = _themes[base][:fgaxis],
fgtext = _themes[base][:fgtext],
fgborder = _themes[base][:fgborder],
fgguide = _themes[base][:fgguide],
kw...)
_themes[sym] = merge(KW(
:bg => bg,
:bglegend => bglegend,
:bginside => bginside,
:bgoutside => bgoutside,
:fg => fg,
:fglegend => fglegend,
:fggrid => fggrid,
:fgaxis => fgaxis,
:fgtext => fgtext,
:fgborder => fgborder,
:fgguide => fgguide,
), KW(kw))
end
add_theme(:ggplot2,
bglegend = :lightgray,
bginside = :lightgray,
fg = :black,
fggrid = :white,
fgborder = _invisible,
fgaxis = _invisible
)
function set_theme(sym::Symbol)
default(; _themes[sym]...)
end

View File

@ -90,10 +90,18 @@ end
# ----------------------------------------------------------------------- # -----------------------------------------------------------------------
Base.getindex(plt::Plot, i::Integer) = plt.subplots[i] Base.getindex(plt::Plot, i::Integer) = plt.subplots[i]
Base.length(plt::Plot) = length(plt.subplots)
Base.endof(plt::Plot) = length(plt)
Base.getindex(plt::Plot, r::Integer, c::Integer) = plt.layout[r,c] Base.getindex(plt::Plot, r::Integer, c::Integer) = plt.layout[r,c]
Base.size(plt::Plot) = size(plt.layout)
Base.size(plt::Plot, i::Integer) = size(plt.layout)[i]
Base.ndims(plt::Plot) = 2
# attr(plt::Plot, k::Symbol) = plt.attr[k] # attr(plt::Plot, k::Symbol) = plt.attr[k]
# attr!(plt::Plot, v, k::Symbol) = (plt.attr[k] = v) # attr!(plt::Plot, v, k::Symbol) = (plt.attr[k] = v)
Base.getindex(sp::Subplot, i::Integer) = series_list(sp)[i] Base.getindex(sp::Subplot, i::Integer) = series_list(sp)[i]
Base.endof(sp::Subplot) = length(series_list(sp))
# ----------------------------------------------------------------------- # -----------------------------------------------------------------------

View File

@ -228,25 +228,34 @@ function Base.next(itr::SegmentsIterator, nextidx::Int)
istart:iend, i istart:iend, i
end end
# Find minimal type that can contain NaN and x
# To allow use of NaN separated segments with categorical x axis
float_extended_type{T}(x::AbstractArray{T}) = Union{T,Float64}
float_extended_type{T<:Real}(x::AbstractArray{T}) = Float64
# ------------------------------------------------------------------------------------ # ------------------------------------------------------------------------------------
nop() = nothing nop() = nothing
notimpl() = error("This has not been implemented yet") notimpl() = error("This has not been implemented yet")
Base.cycle(wrapper::InputWrapper, idx::Int) = wrapper.obj isnothing(x::Void) = true
Base.cycle(wrapper::InputWrapper, idx::AVec{Int}) = wrapper.obj isnothing(x) = false
Base.cycle(v::AVec, idx::Int) = v[mod1(idx, length(v))] cycle(wrapper::InputWrapper, idx::Int) = wrapper.obj
Base.cycle(v::AMat, idx::Int) = size(v,1) == 1 ? v[1, mod1(idx, size(v,2))] : v[:, mod1(idx, size(v,2))] cycle(wrapper::InputWrapper, idx::AVec{Int}) = wrapper.obj
Base.cycle(v, idx::Int) = v
Base.cycle(v::AVec, indices::AVec{Int}) = map(i -> cycle(v,i), indices) cycle(v::AVec, idx::Int) = v[mod1(idx, length(v))]
Base.cycle(v::AMat, indices::AVec{Int}) = map(i -> cycle(v,i), indices) 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, indices::AVec{Int}) = fill(v, length(indices)) cycle(v, idx::Int) = v
Base.cycle(grad::ColorGradient, idx::Int) = cycle(grad.colors, idx) cycle(v::AVec, indices::AVec{Int}) = map(i -> cycle(v,i), indices)
Base.cycle(grad::ColorGradient, indices::AVec{Int}) = cycle(grad.colors, indices) cycle(v::AMat, indices::AVec{Int}) = map(i -> cycle(v,i), indices)
cycle(v, indices::AVec{Int}) = fill(v, length(indices))
cycle(grad::ColorGradient, idx::Int) = cycle(grad.colors, idx)
cycle(grad::ColorGradient, indices::AVec{Int}) = cycle(grad.colors, indices)
makevec(v::AVec) = v makevec(v::AVec) = v
makevec{T}(v::T) = T[v] makevec{T}(v::T) = T[v]
@ -460,7 +469,7 @@ ok(tup::Tuple) = ok(tup...)
# compute one side of a fill range from a ribbon # compute one side of a fill range from a ribbon
function make_fillrange_side(y, rib) function make_fillrange_side(y, rib)
frs = zeros(length(y)) frs = zeros(length(y))
for (i, (yi, ri)) in enumerate(zip(y, cycle(rib))) for (i, (yi, ri)) in enumerate(zip(y, Base.cycle(rib)))
frs[i] = yi + ri frs[i] = yi + ri
end end
frs frs
@ -490,6 +499,8 @@ zlims(sp_idx::Int = 1) = zlims(current(), sp_idx)
# --------------------------------------------------------------- # ---------------------------------------------------------------
makekw(; kw...) = KW(kw)
wraptuple(x::Tuple) = x wraptuple(x::Tuple) = x
wraptuple(x) = (x,) wraptuple(x) = (x,)
@ -714,28 +725,28 @@ Base.push!(series::Series, xi, yi, zi) = (push_x!(series,xi); push_y!(series,yi)
# ------------------------------------------------------- # -------------------------------------------------------
function update!(series::Series; kw...) function attr!(series::Series; kw...)
d = KW(kw) d = KW(kw)
preprocessArgs!(d) preprocessArgs!(d)
for (k,v) in d for (k,v) in d
if haskey(_series_defaults, k) if haskey(_series_defaults, k)
series[k] = v series[k] = v
else else
warn("unused key $k in series update") warn("unused key $k in series attr")
end end
end end
_series_updated(series[:subplot].plt, series) _series_updated(series[:subplot].plt, series)
series series
end end
function update!(sp::Subplot; kw...) function attr!(sp::Subplot; kw...)
d = KW(kw) d = KW(kw)
preprocessArgs!(d) preprocessArgs!(d)
for (k,v) in d for (k,v) in d
if haskey(_subplot_defaults, k) if haskey(_subplot_defaults, k)
sp[k] = v sp[k] = v
else else
warn("unused key $k in subplot update") warn("unused key $k in subplot attr")
end end
end end
sp sp

View File

@ -24,7 +24,7 @@ default(size=(500,300))
# TODO: use julia's Condition type and the wait() and notify() functions to initialize a Window, then wait() on a condition that # 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) # is referenced in a button press callback (the button clicked callback will call notify() on that condition)
const _current_plots_version = v"0.9.4" const _current_plots_version = v"0.9.6"
function image_comparison_tests(pkg::Symbol, idx::Int; debug = false, popup = isinteractive(), sigma = [1,1], eps = 1e-2) function image_comparison_tests(pkg::Symbol, idx::Int; debug = false, popup = isinteractive(), sigma = [1,1], eps = 1e-2)

View File

@ -67,7 +67,7 @@ facts("UnicodePlots") do
@fact backend() --> Plots.UnicodePlotsBackend() @fact backend() --> Plots.UnicodePlotsBackend()
# lets just make sure it runs without error # lets just make sure it runs without error
@fact isa(plot(rand(10)), Plot) --> true @fact isa(plot(rand(10)), Plots.Plot) --> true
end end
@ -75,7 +75,7 @@ end
facts("Axes") do facts("Axes") do
p = plot() p = plot()
axis = p.subplots[1][:xaxis] axis = p.subplots[1][:xaxis]
@fact typeof(axis) --> Axis @fact typeof(axis) --> Plots.Axis
@fact Plots.discrete_value!(axis, "HI") --> (0.5, 1) @fact Plots.discrete_value!(axis, "HI") --> (0.5, 1)
@fact Plots.discrete_value!(axis, :yo) --> (1.5, 2) @fact Plots.discrete_value!(axis, :yo) --> (1.5, 2)
@fact extrema(axis) --> (0.5,1.5) @fact extrema(axis) --> (0.5,1.5)