Merge remote-tracking branch 'JuliaPlots/master'

This commit is contained in:
Michael K. Borregaard 2017-02-08 17:22:33 +01:00
commit 9804debe86
12 changed files with 639 additions and 32 deletions

View File

@ -1,4 +1,4 @@
__precompile__(false) __precompile__(true)
module Plots module Plots

View File

@ -29,6 +29,7 @@ const _arg_desc = KW(
:z => "Various. Input data. Third Dimension. May be wrapped by a `Surface` for surface and heatmap types.", :z => "Various. Input data. Third Dimension. May be wrapped by a `Surface` for surface and heatmap types.",
:marker_z => "AbstractVector, Function `f(x,y,z) -> z_value`, or nothing. z-values for each series data point, which correspond to the color to be used from a markercolor gradient.", :marker_z => "AbstractVector, Function `f(x,y,z) -> z_value`, or nothing. z-values for each series data point, which correspond to the color to be used from a markercolor gradient.",
:line_z => "AbstractVector, Function `f(x,y,z) -> z_value`, or nothing. z-values for each series line segment, which correspond to the color to be used from a linecolor gradient. Note that for N points, only the first N-1 values are used (one per line-segment).", :line_z => "AbstractVector, Function `f(x,y,z) -> z_value`, or nothing. z-values for each series line segment, which correspond to the color to be used from a linecolor gradient. Note that for N points, only the first N-1 values are used (one per line-segment).",
:fill_z => "Matrix{Float64} of the same size as z matrix, which specifies the color of the 3D surface; the default value is `nothing`.",
:levels => "Integer, NTuple{2,Integer}. Number of levels (or x-levels/y-levels) for a contour type.", :levels => "Integer, NTuple{2,Integer}. Number of levels (or x-levels/y-levels) for a contour type.",
:orientation => "Symbol. Horizontal or vertical orientation for bar types. Values `:h`, `:hor`, `:horizontal` correspond to horizontal (sideways, anchored to y-axis), and `:v`, `:vert`, and `:vertical` correspond to vertical (the default).", :orientation => "Symbol. Horizontal or vertical orientation for bar types. Values `:h`, `:hor`, `:horizontal` correspond to horizontal (sideways, anchored to y-axis), and `:v`, `:vert`, and `:vertical` correspond to vertical (the default).",
:bar_position => "Symbol. Choose from `:overlay` (default), `:stack`. (warning: May not be implemented fully)", :bar_position => "Symbol. Choose from `:overlay` (default), `:stack`. (warning: May not be implemented fully)",

View File

@ -188,6 +188,7 @@ const _series_defaults = KW(
:z => nothing, # depth for contour, surface, etc :z => nothing, # depth for contour, surface, etc
:marker_z => nothing, # value for color scale :marker_z => nothing, # value for color scale
:line_z => nothing, :line_z => nothing,
:fill_z => nothing,
:levels => 15, :levels => 15,
:orientation => :vertical, :orientation => :vertical,
:bar_position => :overlay, # for bar plots and histograms: could also be stack (stack up) or dodge (side by side) :bar_position => :overlay, # for bar plots and histograms: could also be stack (stack up) or dodge (side by side)
@ -431,6 +432,7 @@ add_aliases(:zguide, :zlabel, :zlab, :zl)
add_aliases(:zlims, :zlim, :zlimit, :zlimits) add_aliases(:zlims, :zlim, :zlimit, :zlimits)
add_aliases(:zticks, :ztick) add_aliases(:zticks, :ztick)
add_aliases(:zrotation, :zrot, :zr) add_aliases(:zrotation, :zrot, :zr)
add_aliases(:fill_z, :fillz, :fz, :surfacecolor, :surfacecolour, :sc, :surfcolor, :surfcolour)
add_aliases(:legend, :leg, :key) add_aliases(:legend, :leg, :key)
add_aliases(:colorbar, :cb, :cbar, :colorkey) add_aliases(:colorbar, :cb, :cbar, :colorkey)
add_aliases(:clims, :clim, :cbarlims, :cbar_lims, :climits, :color_limits) add_aliases(:clims, :clim, :cbarlims, :cbar_lims, :climits, :color_limits)

View File

@ -276,6 +276,7 @@ end
@init_backend GR @init_backend GR
@init_backend GLVisualize @init_backend GLVisualize
@init_backend PGFPlots @init_backend PGFPlots
@init_backend InspectDR
# --------------------------------------------------------- # ---------------------------------------------------------

531
src/backends/inspectdr.jl Normal file
View File

@ -0,0 +1,531 @@
# https://github.com/ma-laforge/InspectDR.jl
#=TODO:
Tweak scale factor for width & other sizes
Not supported by InspectDR:
:foreground_color_grid
:foreground_color_border
:polar,
Add in functionality to Plots.jl:
:aspect_ratio,
=#
# ---------------------------------------------------------------------------
#TODO: remove features
const _inspectdr_attr = merge_with_base_supported([
:annotations,
:background_color_legend, :background_color_inside, :background_color_outside,
:foreground_color_grid, :foreground_color_legend, :foreground_color_title,
:foreground_color_axis, :foreground_color_border, :foreground_color_guide, :foreground_color_text,
:label,
:linecolor, :linestyle, :linewidth, :linealpha,
:markershape, :markercolor, :markersize, :markeralpha,
:markerstrokewidth, :markerstrokecolor, :markerstrokealpha,
:markerstrokestyle, #Causes warning not to have it... what is this?
:fillcolor, :fillalpha, #:fillrange,
# :bins, :bar_width, :bar_edges, :bar_position,
:title, :title_location, :titlefont,
:window_title,
:guide, :lims, :scale, #:ticks, :flip, :rotation,
:tickfont, :guidefont, :legendfont,
:grid, :legend, #:colorbar,
# :marker_z,
# :line_z,
# :levels,
# :ribbon, :quiver, :arrow,
# :orientation,
:overwrite_figure,
:polar,
# :normalize, :weights,
# :contours, :aspect_ratio,
:match_dimensions,
# :clims,
# :inset_subplots,
:dpi,
# :colorbar_title,
])
const _inspectdr_style = [:auto, :solid, :dash, :dot, :dashdot]
const _inspectdr_seriestype = [
:path, :scatter, :shape #, :steppre, :steppost
]
#see: _allMarkers, _shape_keys
const _inspectdr_marker = Symbol[
:none, :auto,
:circle, :rect, :diamond,
:cross, :xcross,
:utriangle, :dtriangle, :rtriangle, :ltriangle,
:pentagon, :hexagon, :heptagon, :octagon,
:star4, :star5, :star6, :star7, :star8,
:vline, :hline, :+, :x,
]
const _inspectdr_scale = [:identity, :ln, :log2, :log10]
is_marker_supported(::InspectDRBackend, shape::Shape) = true
_inspectdr_to_pixels(bb::BoundingBox) =
InspectDR.BoundingBox(to_pixels(left(bb)), to_pixels(right(bb)), to_pixels(top(bb)), to_pixels(bottom(bb)))
#Do we avoid Map to avoid possible pre-comile issues?
function _inspectdr_mapglyph(s::Symbol)
s == :rect && return :square
return s
end
function _inspectdr_mapglyph(s::Shape)
x, y = coords(s)
return InspectDR.GlyphPolyline(x, y)
end
# py_marker(markers::AVec) = map(py_marker, markers)
function _inspectdr_mapglyph(markers::AVec)
warn("Vectors of markers are currently unsupported in InspectDR.")
_inspectdr_mapglyph(markers[1])
end
_inspectdr_mapglyphsize(v::Real) = v
function _inspectdr_mapglyphsize(v::Vector)
warn("Vectors of marker sizes are currently unsupported in InspectDR.")
_inspectdr_mapglyphsize(v[1])
end
_inspectdr_mapcolor(v::Colorant) = v
function _inspectdr_mapcolor(g::PlotUtils.ColorGradient)
warn("Color gradients are currently unsupported in InspectDR.")
#Pick middle color:
_inspectdr_mapcolor(g.colors[div(1+end,2)])
end
function _inspectdr_mapcolor(v::AVec)
warn("Vectors of colors are currently unsupported in InspectDR.")
#Pick middle color:
_inspectdr_mapcolor(v[div(1+end,2)])
end
#Hack: suggested point size does not seem adequate relative to plot size, for some reason.
_inspectdr_mapptsize(v) = 1.5*v
function _inspectdr_add_annotations(plot, x, y, val)
#What kind of annotation is this?
end
#plot::InspectDR.Plot2D
function _inspectdr_add_annotations(plot, x, y, val::PlotText)
vmap = Dict{Symbol, Symbol}(:top=>:t, :bottom=>:b) #:vcenter
hmap = Dict{Symbol, Symbol}(:left=>:l, :right=>:r) #:hcenter
align = Symbol(get(vmap, val.font.valign, :c), get(hmap, val.font.halign, :c))
fnt = InspectDR.Font(val.font.family, val.font.pointsize,
color =_inspectdr_mapcolor(val.font.color)
)
ann = InspectDR.atext(val.str, x=x, y=y,
font=fnt, angle=val.font.rotation, align=align
)
InspectDR.add(plot, ann)
return
end
# ---------------------------------------------------------------------------
function _inspectdr_getscale(s::Symbol, yaxis::Bool)
#TODO: Support :asinh, :sqrt
kwargs = yaxis? (:tgtmajor=>8, :tgtminor=>2): () #More grid lines on y-axis
if :log2 == s
return InspectDR.AxisScale(:log2; kwargs...)
elseif :log10 == s
return InspectDR.AxisScale(:log10; kwargs...)
elseif :ln == s
return InspectDR.AxisScale(:ln; kwargs...)
else #identity
return InspectDR.AxisScale(:lin; kwargs...)
end
end
# ---------------------------------------------------------------------------
function add_backend_string(::InspectDRBackend)
"""
if !Plots.is_installed("InspectDR")
Pkg.add("InspectDR")
end
"""
end
function _initialize_backend(::InspectDRBackend; kw...)
@eval begin
import InspectDR
export InspectDR
#Glyph used when plotting "Shape"s:
const INSPECTDR_GLYPH_SHAPE = InspectDR.GlyphPolyline(
2*InspectDR.GLYPH_SQUARE.x, InspectDR.GLYPH_SQUARE.y
)
type InspecDRPlotRef
mplot::Union{Void, InspectDR.Multiplot}
gui::Union{Void, InspectDR.GtkPlot}
end
_inspectdr_getmplot(::Any) = nothing
_inspectdr_getmplot(r::InspecDRPlotRef) = r.mplot
_inspectdr_getgui(::Any) = nothing
_inspectdr_getgui(gplot::InspectDR.GtkPlot) = (gplot.destroyed? nothing: gplot)
_inspectdr_getgui(r::InspecDRPlotRef) = _inspectdr_getgui(r.gui)
end
end
# ---------------------------------------------------------------------------
# Create the window/figure for this backend.
function _create_backend_figure(plt::Plot{InspectDRBackend})
mplot = _inspectdr_getmplot(plt.o)
gplot = _inspectdr_getgui(plt.o)
#:overwrite_figure: want to reuse current figure
if plt[:overwrite_figure] && mplot != nothing
mplot.subplots = [] #Reset
if gplot != nothing #Ensure still references current plot
gplot.src = mplot
end
else #want new one:
mplot = InspectDR.Multiplot()
gplot = nothing #Will be created later
end
#break link with old subplots
for sp in plt.subplots
sp.o = nothing
end
return InspecDRPlotRef(mplot, gplot)
end
# ---------------------------------------------------------------------------
# # this is called early in the pipeline, use it to make the plot current or something
# function _prepare_plot_object(plt::Plot{InspectDRBackend})
# end
# ---------------------------------------------------------------------------
# Set up the subplot within the backend object.
function _initialize_subplot(plt::Plot{InspectDRBackend}, sp::Subplot{InspectDRBackend})
plot = sp.o
#Don't do anything without a "subplot" object: Will process later.
if nothing == plot; return; end
plot.data = []
plot.markers = [] #Clear old markers
plot.atext = [] #Clear old annotation
plot.apline = [] #Clear old poly lines
return plot
end
# ---------------------------------------------------------------------------
# Add one series to the underlying backend object.
# Called once per series
# NOTE: Seems to be called when user calls plot()... even if backend
# plot, sp.o has not yet been constructed...
function _series_added(plt::Plot{InspectDRBackend}, series::Series)
st = series[:seriestype]
sp = series[:subplot]
plot = sp.o
#Don't do anything without a "subplot" object: Will process later.
if nothing == plot; return; end
_vectorize(v) = isa(v, Vector)? v: collect(v) #InspectDR only supports vectors
x = _vectorize(series[:x]); y = _vectorize(series[:y])
#No support for polar grid... but can still perform polar transformation:
if ispolar(sp)
Θ = x; r = y
x = r.*cos(Θ); y = r.*sin(Θ)
end
# doesn't handle mismatched x/y - wrap data (pyplot behaviour):
nx = length(x); ny = length(y)
if nx < ny
series[:x] = Float64[x[mod1(i,nx)] for i=1:ny]
elseif ny > nx
series[:y] = Float64[y[mod1(i,ny)] for i=1:nx]
end
#= TODO: Eventually support
series[:fillcolor] #I think this is fill under line
zorder = series[:series_plotindex]
For st in :shape:
zorder = series[:series_plotindex],
=#
if st in (:shape,)
nmax = 0
for (i,rng) in enumerate(iter_segments(x, y))
nmax = i
if length(rng) > 1
linewidth = series[:linewidth]
linecolor = _inspectdr_mapcolor(cycle(series[:linecolor], i))
fillcolor = _inspectdr_mapcolor(cycle(series[:fillcolor], i))
line = InspectDR.line(
style=:solid, width=linewidth, color=linecolor
)
apline = InspectDR.PolylineAnnotation(
x[rng], y[rng], line=line, fillcolor=fillcolor
)
push!(plot.apline, apline)
end
end
i = (nmax >= 2? div(nmax, 2): nmax) #Must pick one set of colors for legend
if i > 1 #Add dummy waveform for legend entry:
linewidth = series[:linewidth]
linecolor = _inspectdr_mapcolor(cycle(series[:linecolor], i))
fillcolor = _inspectdr_mapcolor(cycle(series[:fillcolor], i))
wfrm = InspectDR.add(plot, Float64[], Float64[], id=series[:label])
wfrm.line = InspectDR.line(
style=:none, width=linewidth, #linewidth affects glyph
)
wfrm.glyph = InspectDR.glyph(
shape = INSPECTDR_GLYPH_SHAPE, size = 8,
color = linecolor, fillcolor = fillcolor
)
end
elseif st in (:path, :scatter) #, :steppre, :steppost)
#NOTE: In Plots.jl, :scatter plots have 0-linewidths (I think).
linewidth = series[:linewidth]
#More efficient & allows some support for markerstrokewidth:
_style = (0==linewidth? :none: series[:linestyle])
wfrm = InspectDR.add(plot, x, y, id=series[:label])
wfrm.line = InspectDR.line(
style = _style,
width = series[:linewidth],
color = series[:linecolor],
)
#InspectDR does not control markerstrokewidth independently.
if :none == _style
#Use this property only if no line is displayed:
wfrm.line.width = series[:markerstrokewidth]
end
wfrm.glyph = InspectDR.glyph(
shape = _inspectdr_mapglyph(series[:markershape]),
size = _inspectdr_mapglyphsize(series[:markersize]),
color = _inspectdr_mapcolor(series[:markerstrokecolor]),
fillcolor = _inspectdr_mapcolor(series[:markercolor]),
)
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)
_inspectdr_add_annotations(plot, xi, yi, PlotText(str, fnt))
end
return
end
# ---------------------------------------------------------------------------
# When series data is added/changed, this callback can do dynamic updates to the backend object.
# note: if the backend rebuilds the plot from scratch on display, then you might not do anything here.
function _series_updated(plt::Plot{InspectDRBackend}, series::Series)
#Nothing to do
end
# ---------------------------------------------------------------------------
function _inspectdr_setupsubplot(sp::Subplot{InspectDRBackend})
const gridon = InspectDR.GridRect(vmajor=true, hmajor=true)
const gridoff = InspectDR.GridRect()
const plot = sp.o
const strip = plot.strips[1] #Only 1 strip supported with Plots.jl
#No independent control of grid???
strip.grid = sp[:grid]? gridon: gridoff
xaxis = sp[:xaxis]; yaxis = sp[:yaxis]
plot.xscale = _inspectdr_getscale(xaxis[:scale], false)
strip.yscale = _inspectdr_getscale(yaxis[:scale], true)
xmin, xmax = axis_limits(xaxis)
ymin, ymax = axis_limits(yaxis)
if ispolar(sp)
#Plots.jl appears to give (xmin,xmax) ≜ (Θmin,Θmax) & (ymin,ymax) ≜ (rmin,rmax)
rmax = max(abs(ymin), abs(ymax))
xmin, xmax = -rmax, rmax
ymin, ymax = -rmax, rmax
end
plot.xext = InspectDR.PExtents1D() #reset
strip.yext = InspectDR.PExtents1D() #reset
plot.xext_full = InspectDR.PExtents1D(xmin, xmax)
strip.yext_full = InspectDR.PExtents1D(ymin, ymax)
a = plot.annotation
a.title = sp[:title]
a.xlabel = xaxis[:guide]; a.ylabels = [yaxis[:guide]]
l = plot.layout
l.frame.fillcolor = _inspectdr_mapcolor(sp[:background_color_subplot])
l.framedata.fillcolor = _inspectdr_mapcolor(sp[:background_color_inside])
l.framedata.line.color = _inspectdr_mapcolor(xaxis[:foreground_color_axis])
l.fnttitle = InspectDR.Font(sp[:titlefont].family,
_inspectdr_mapptsize(sp[:titlefont].pointsize),
color = _inspectdr_mapcolor(sp[:foreground_color_title])
)
#Cannot independently control fonts of axes with InspectDR:
l.fntaxlabel = InspectDR.Font(xaxis[:guidefont].family,
_inspectdr_mapptsize(xaxis[:guidefont].pointsize),
color = _inspectdr_mapcolor(xaxis[:foreground_color_guide])
)
l.fntticklabel = InspectDR.Font(xaxis[:tickfont].family,
_inspectdr_mapptsize(xaxis[:tickfont].pointsize),
color = _inspectdr_mapcolor(xaxis[:foreground_color_text])
)
leg = l.legend
leg.enabled = (sp[:legend] != :none)
#leg.width = 150 #TODO: compute???
leg.font = InspectDR.Font(sp[:legendfont].family,
_inspectdr_mapptsize(sp[:legendfont].pointsize),
color = _inspectdr_mapcolor(sp[:foreground_color_legend])
)
leg.frame.fillcolor = _inspectdr_mapcolor(sp[:background_color_legend])
end
# called just before updating layout bounding boxes... in case you need to prep
# for the calcs
function _before_layout_calcs(plt::Plot{InspectDRBackend})
const mplot = _inspectdr_getmplot(plt.o)
if nothing == mplot; return; end
mplot.title = plt[:plot_title]
if "" == mplot.title
#Don't use window_title... probably not what you want.
#mplot.title = plt[:window_title]
end
mplot.frame.fillcolor = _inspectdr_mapcolor(plt[:background_color_outside])
resize!(mplot.subplots, length(plt.subplots))
nsubplots = length(plt.subplots)
for (i, sp) in enumerate(plt.subplots)
if !isassigned(mplot.subplots, i)
mplot.subplots[i] = InspectDR.Plot2D()
end
sp.o = mplot.subplots[i]
plot = sp.o
_initialize_subplot(plt, sp)
_inspectdr_setupsubplot(sp)
graphbb = _inspectdr_to_pixels(plotarea(sp))
plot.plotbb = InspectDR.plotbounds(plot.layout, graphbb)
# add the annotations
for ann in sp[:annotations]
_inspectdr_add_annotations(plot, ann...)
end
end
#Do not yet support absolute plot positionning.
#Just try to make things look more-or less ok:
if nsubplots <= 1
mplot.ncolumns = 1
elseif nsubplots <= 4
mplot.ncolumns = 2
elseif nsubplots <= 6
mplot.ncolumns = 3
elseif nsubplots <= 12
mplot.ncolumns = 4
else
mplot.ncolumns = 5
end
for series in plt.series_list
_series_added(plt, series)
end
return
end
# ----------------------------------------------------------------
# Set the (left, top, right, bottom) minimum padding around the plot area
# to fit ticks, tick labels, guides, colorbars, etc.
function _update_min_padding!(sp::Subplot{InspectDRBackend})
plot = sp.o
if !isa(plot, InspectDR.Plot2D); return sp.minpad; end
#Computing plotbounds with 0-BoundingBox returns required padding:
bb = InspectDR.plotbounds(plot.layout, InspectDR.BoundingBox(0,0,0,0))
#NOTE: plotbounds always pads for titles, legends, etc. even if not in use.
#TODO: possibly zero-out items not in use??
# add in the user-specified margin to InspectDR padding:
leftpad = abs(bb.xmin)*px + sp[:left_margin]
toppad = abs(bb.ymin)*px + sp[:top_margin]
rightpad = abs(bb.xmax)*px + sp[:right_margin]
bottompad = abs(bb.ymax)*px + sp[:bottom_margin]
sp.minpad = (leftpad, toppad, rightpad, bottompad)
end
# ----------------------------------------------------------------
# Override this to update plot items (title, xlabel, etc), and add annotations (d[:annotations])
function _update_plot_object(plt::Plot{InspectDRBackend})
mplot = _inspectdr_getmplot(plt.o)
if nothing == mplot; return; end
#TODO: should plotbb be computed here??
gplot = _inspectdr_getgui(plt.o)
if nothing == gplot; return; end
gplot.src = mplot #Ensure still references current plot
InspectDR.refresh(gplot)
return
end
# ----------------------------------------------------------------
const _inspectdr_mimeformats_dpi = Dict(
"image/png" => "png"
)
const _inspectdr_mimeformats_nodpi = Dict(
"image/svg+xml" => "svg",
"application/eps" => "eps",
"image/eps" => "eps",
# "application/postscript" => "ps", #TODO: support once Cairo supports PSSurface
"application/pdf" => "pdf"
)
_inspectdr_show(io::IO, mime::MIME, ::Void, w, h) =
throw(ErrorException("Cannot show(::IO, ...) plot - not yet generated"))
function _inspectdr_show(io::IO, mime::MIME, mplot, w, h)
InspectDR._show(io, mime, mplot, Float64(w), Float64(h))
end
for (mime, fmt) in _inspectdr_mimeformats_dpi
@eval function _show(io::IO, mime::MIME{Symbol($mime)}, plt::Plot{InspectDRBackend})
dpi = plt[:dpi]#TODO: support
_inspectdr_show(io, mime, _inspectdr_getmplot(plt.o), plt[:size]...)
end
end
for (mime, fmt) in _inspectdr_mimeformats_nodpi
@eval function _show(io::IO, mime::MIME{Symbol($mime)}, plt::Plot{InspectDRBackend})
_inspectdr_show(io, mime, _inspectdr_getmplot(plt.o), plt[:size]...)
end
end
_show(io::IO, mime::MIME"text/plain", plt::Plot{InspectDRBackend}) = nothing #Don't show
# ----------------------------------------------------------------
# Display/show the plot (open a GUI window, or browser page, for example).
function _display(plt::Plot{InspectDRBackend})
mplot = _inspectdr_getmplot(plt.o)
if nothing == mplot; return; end
gplot = _inspectdr_getgui(plt.o)
if nothing == gplot
gplot = display(InspectDR.GtkDisplay(), mplot)
else
#redundant... Plots.jl will call _update_plot_object:
#InspectDR.refresh(gplot)
end
plt.o = InspecDRPlotRef(mplot, gplot)
return gplot
end

View File

@ -3,7 +3,7 @@
# significant contributions by: @pkofod # significant contributions by: @pkofod
const _pgfplots_attr = merge_with_base_supported([ const _pgfplots_attr = merge_with_base_supported([
# :annotations, :annotations,
# :background_color_legend, # :background_color_legend,
:background_color_inside, :background_color_inside,
# :background_color_outside, # :background_color_outside,
@ -27,12 +27,12 @@ const _pgfplots_attr = merge_with_base_supported([
# :ribbon, :quiver, :arrow, # :ribbon, :quiver, :arrow,
# :orientation, # :orientation,
# :overwrite_figure, # :overwrite_figure,
# :polar, :polar,
# :normalize, :weights, :contours, # :normalize, :weights, :contours,
:aspect_ratio, :aspect_ratio,
# :match_dimensions, # :match_dimensions,
]) ])
const _pgfplots_seriestype = [:path, :path3d, :scatter, :steppre, :stepmid, :steppost, :histogram2d, :ysticks, :xsticks, :contour] const _pgfplots_seriestype = [:path, :path3d, :scatter, :steppre, :stepmid, :steppost, :histogram2d, :ysticks, :xsticks, :contour, :shape]
const _pgfplots_style = [:auto, :solid, :dash, :dot, :dashdot, :dashdotdot] const _pgfplots_style = [:auto, :solid, :dash, :dot, :dashdot, :dashdotdot]
const _pgfplots_marker = [:none, :auto, :circle, :rect, :diamond, :utriangle, :dtriangle, :cross, :xcross, :star5, :pentagon] #vcat(_allMarkers, Shape) const _pgfplots_marker = [:none, :auto, :circle, :rect, :diamond, :utriangle, :dtriangle, :cross, :xcross, :star5, :pentagon] #vcat(_allMarkers, Shape)
const _pgfplots_scale = [:identity, :ln, :log2, :log10] const _pgfplots_scale = [:identity, :ln, :log2, :log10]
@ -136,6 +136,20 @@ function pgf_marker(d::KW)
}""" }"""
end end
function pgf_add_annotation!(o,x,y,val)
# Construct the style string.
# Currently supports color and orientation
halign = val.font.halign == :hcenter ? "" : string(val.font.halign)
cstr,a = pgf_color(val.font.color)
push!(o, PGFPlots.Plots.Node(val.str, # Annotation Text
x, y,
style="""
$halign,
color=$cstr, draw opacity=$(convert(Float16,a)),
rotate=$(val.font.rotation)
"""))
end
# -------------------------------------------------------------------------------------- # --------------------------------------------------------------------------------------
function pgf_series(sp::Subplot, series::Series) function pgf_series(sp::Subplot, series::Series)
@ -147,7 +161,7 @@ function pgf_series(sp::Subplot, series::Series)
push!(style, pgf_linestyle(d)) push!(style, pgf_linestyle(d))
push!(style, pgf_marker(d)) push!(style, pgf_marker(d))
if d[:fillrange] != nothing if d[:fillrange] != nothing || st in (:shape,)
push!(style, pgf_fillstyle(d)) push!(style, pgf_fillstyle(d))
end end
@ -211,6 +225,9 @@ function pgf_axis(sp::Subplot, letter)
# axis guide # axis guide
kw[Symbol(letter,:label)] = axis[:guide] kw[Symbol(letter,:label)] = axis[:guide]
# Add ticklabel rotations
push!(style, "$(letter)ticklabel style={rotate = $(axis[:rotation])}")
# flip/reverse? # flip/reverse?
axis[:flip] && push!(style, "$letter dir=reverse") axis[:flip] && push!(style, "$letter dir=reverse")
@ -249,8 +266,11 @@ end
function _update_plot_object(plt::Plot{PGFPlotsBackend}) function _update_plot_object(plt::Plot{PGFPlotsBackend})
plt.o = PGFPlots.Axis[] plt.o = PGFPlots.Axis[]
# Obtain the total height of the plot by extracting the maximal bottom
# coordinate from the bounding box.
total_height = bottom(bbox(plt.layout))
for sp in plt.subplots for sp in plt.subplots
# first build the PGFPlots.Axis object # first build the PGFPlots.Axis object
style = ["unbounded coords=jump"] style = ["unbounded coords=jump"]
kw = KW() kw = KW()
@ -265,10 +285,12 @@ function _update_plot_object(plt::Plot{PGFPlotsBackend})
# bounding box values are in mm # bounding box values are in mm
# note: bb origin is top-left, pgf is bottom-left # note: bb origin is top-left, pgf is bottom-left
# A round on 2 decimal places should be enough precision for 300 dpi
# plots.
bb = bbox(sp) bb = bbox(sp)
push!(style, """ push!(style, """
xshift = $(left(bb).value)mm, xshift = $(left(bb).value)mm,
yshift = $((height(bb) - (bottom(bb))).value)mm, yshift = $(round((total_height - (bottom(bb))).value,2))mm,
axis background/.style={fill=$(pgf_color(sp[:background_color_inside])[1])} axis background/.style={fill=$(pgf_color(sp[:background_color_inside])[1])}
""") """)
kw[:width] = "$(width(bb).value)mm" kw[:width] = "$(width(bb).value)mm"
@ -288,19 +310,34 @@ function _update_plot_object(plt::Plot{PGFPlotsBackend})
kw[:legendPos] = _pgfplots_legend_pos[legpos] kw[:legendPos] = _pgfplots_legend_pos[legpos]
end end
o = PGFPlots.Axis(; style = style, kw...) axisf = PGFPlots.Axis
if sp[:projection] == :polar
axisf = PGFPlots.PolarAxis
end
o = axisf(; style = style, kw...)
# add the series object to the PGFPlots.Axis # add the series object to the PGFPlots.Axis
for series in series_list(sp) for series in series_list(sp)
push!(o, pgf_series(sp, series)) push!(o, pgf_series(sp, series))
# add series annotations
anns = series[:series_annotations]
for (xi,yi,str,fnt) in EachAnn(anns, series[:x], series[:y])
pgf_add_annotation!(o, xi, yi, PlotText(str, fnt))
end
end end
# add the annotations
for ann in sp[:annotations]
pgf_add_annotation!(o,ann...)
end
# add the PGFPlots.Axis to the list # add the PGFPlots.Axis to the list
push!(plt.o, o) push!(plt.o, o)
end end
end end
function _show(io::IO, mime::MIME"image/svg+xml", plt::Plot{PGFPlotsBackend}) function _show(io::IO, mime::MIME"image/svg+xml", plt::Plot{PGFPlotsBackend})
show(io, mime, plt.o) show(io, mime, plt.o)
end end

View File

@ -20,7 +20,7 @@ const _plotly_attr = merge_with_base_supported([
:guide, :lims, :ticks, :scale, :flip, :rotation, :guide, :lims, :ticks, :scale, :flip, :rotation,
:tickfont, :guidefont, :legendfont, :tickfont, :guidefont, :legendfont,
:grid, :legend, :colorbar, :grid, :legend, :colorbar,
:marker_z, :levels, :marker_z, :fill_z, :levels,
:ribbon, :quiver, :ribbon, :quiver,
:orientation, :orientation,
# :overwrite_figure, # :overwrite_figure,
@ -371,6 +371,7 @@ end
plotly_colorscale(c, α) = plotly_colorscale(cgrad(alpha=α), α) plotly_colorscale(c, α) = plotly_colorscale(cgrad(alpha=α), α)
# plotly_colorscale(c, alpha = nothing) = plotly_colorscale(cgrad(), alpha) # plotly_colorscale(c, alpha = nothing) = plotly_colorscale(cgrad(), alpha)
const _plotly_markers = KW( const _plotly_markers = KW(
:rect => "square", :rect => "square",
:xcross => "x", :xcross => "x",
@ -488,6 +489,9 @@ function plotly_series(plt::Plot, series::Series)
d_out[:contours] = KW(:x => wirelines, :y => wirelines, :z => wirelines) d_out[:contours] = KW(:x => wirelines, :y => wirelines, :z => wirelines)
else else
d_out[:colorscale] = plotly_colorscale(series[:fillcolor], series[:fillalpha]) d_out[:colorscale] = plotly_colorscale(series[:fillcolor], series[:fillalpha])
if series[:fill_z] != nothing
d_out[:surfacecolor] = plotly_surface_data(series, series[:fill_z])
end
end end
elseif st == :pie elseif st == :pie

View File

@ -102,8 +102,18 @@ _show(io::IO, ::MIME"image/png", plt::Plot{PlotlyJSBackend}) = plotlyjs_save_hac
_show(io::IO, ::MIME"application/pdf", plt::Plot{PlotlyJSBackend}) = plotlyjs_save_hack(io, plt, "pdf") _show(io::IO, ::MIME"application/pdf", plt::Plot{PlotlyJSBackend}) = plotlyjs_save_hack(io, plt, "pdf")
_show(io::IO, ::MIME"image/eps", plt::Plot{PlotlyJSBackend}) = plotlyjs_save_hack(io, plt, "eps") _show(io::IO, ::MIME"image/eps", plt::Plot{PlotlyJSBackend}) = plotlyjs_save_hack(io, plt, "eps")
function write_temp_html(plt::Plot{PlotlyJSBackend})
filename = string(tempname(), ".html")
savefig(plt, filename)
filename
end
function _display(plt::Plot{PlotlyJSBackend}) function _display(plt::Plot{PlotlyJSBackend})
display(plt.o) if get(ENV, "PLOTS_USE_ATOM_PLOTPANE", true) in (true, 1, "1", "true", "yes")
display(plt.o)
else
standalone_html_window(plt)
end
end end

View File

@ -18,8 +18,7 @@ const _pyplot_attr = merge_with_base_supported([
:guide, :lims, :ticks, :scale, :flip, :rotation, :guide, :lims, :ticks, :scale, :flip, :rotation,
:tickfont, :guidefont, :legendfont, :tickfont, :guidefont, :legendfont,
:grid, :legend, :colorbar, :grid, :legend, :colorbar,
:marker_z, :marker_z, :line_z, :fill_z,
:line_z,
:levels, :levels,
:ribbon, :quiver, :arrow, :ribbon, :quiver, :arrow,
:orientation, :orientation,
@ -56,6 +55,10 @@ function add_backend_string(::PyPlotBackend)
withenv("PYTHON" => "") do withenv("PYTHON" => "") do
Pkg.build("PyPlot") Pkg.build("PyPlot")
end end
import Conda
Conda.add("qt=4.8.5")
# now restart julia!
""" """
end end
@ -82,7 +85,17 @@ function _initialize_backend(::PyPlotBackend)
const pycollections = PyPlot.pywrap(PyPlot.pyimport("matplotlib.collections")) const pycollections = PyPlot.pywrap(PyPlot.pyimport("matplotlib.collections"))
const pyart3d = PyPlot.pywrap(PyPlot.pyimport("mpl_toolkits.mplot3d.art3d")) const pyart3d = PyPlot.pywrap(PyPlot.pyimport("mpl_toolkits.mplot3d.art3d"))
end end
if is_linux()
@eval begin
# avoid Conda update that causes Segfault with qt >=4.8.6 on Ubuntu https://github.com/JuliaPy/PyPlot.jl/issues/234
import Conda
kw = Conda._installed_packages_dict()
if (!haskey(kw,"qt") || (qt_version=get(kw,"qt",0)[1]!=v"4.8.5"))
print("\n If the code has a Segmentation fault error switch to qt v4.8.5 by pasting the following code into julia: \n \n")
print(add_backend_string(PyPlotBackend()))
end
end
end
# we don't want every command to update the figure # we don't want every command to update the figure
PyPlot.ioff() PyPlot.ioff()
end end
@ -124,8 +137,8 @@ end
py_colormap(c) = py_colormap(cgrad()) py_colormap(c) = py_colormap(cgrad())
function py_shading(c, z, α=nothing) function py_shading(c, z)
cmap = py_colormap(c, α) cmap = py_colormap(c)
ls = pycolors.pymember("LightSource")(270,45) ls = pycolors.pymember("LightSource")(270,45)
ls[:shade](z, cmap, vert_exag=0.1, blend_mode="soft") ls[:shade](z, cmap, vert_exag=0.1, blend_mode="soft")
end end
@ -668,21 +681,16 @@ function py_add_series(plt::Plot{PyPlotBackend}, series::Series)
x = repmat(x', length(y), 1) x = repmat(x', length(y), 1)
y = repmat(y, 1, length(series[:x])) y = repmat(y, 1, length(series[:x]))
end end
# z = z'
z = transpose_z(series, z) z = transpose_z(series, z)
if st == :surface if st == :surface
if series[:marker_z] != nothing if series[:fill_z] != nothing
extrakw[:facecolors] = py_shading(series[:fillcolor], series[:marker_z], series[:fillalpha]) # the surface colors are different than z-value
extrakw[:facecolors] = py_shading(series[:fillcolor], transpose_z(series, series[:fill_z].surf))
extrakw[:shade] = false extrakw[:shade] = false
clims = sp[:clims]
if is_2tuple(clims)
isfinite(clims[1]) && (extrakw[:vmin] = clims[1])
isfinite(clims[2]) && (extrakw[:vmax] = clims[2])
end
else else
extrakw[:cmap] = py_fillcolormap(series) extrakw[:cmap] = py_fillcolormap(series)
needs_colorbar = true
end end
needs_colorbar = true
end end
handle = ax[st == :surface ? :plot_surface : :plot_wireframe](x, y, z; handle = ax[st == :surface ? :plot_surface : :plot_wireframe](x, y, z;
label = series[:label], label = series[:label],

View File

@ -27,7 +27,7 @@ function open_browser_window(filename::AbstractString)
return run(`xdg-open $(filename)`) return run(`xdg-open $(filename)`)
end end
@static if is_windows() @static if is_windows()
return run(`$(ENV["COMSPEC"]) /c start $(filename)`) return run(`$(ENV["COMSPEC"]) /c start "" "$(filename)"`)
end end
warn("Unknown OS... cannot open browser window.") warn("Unknown OS... cannot open browser window.")
end end

View File

@ -302,6 +302,10 @@ function setup_atom()
Media.render(pane, Atom.div(".fill", Atom.HTML(stringmime(MIME("text/html"), plt)))) Media.render(pane, Atom.div(".fill", Atom.HTML(stringmime(MIME("text/html"), plt))))
plt[:size] = sz plt[:size] = sz
end end
# special handling for PlotlyJS
function Media.render(pane::Atom.PlotPane, plt::Plot{PlotlyJSBackend})
display(Plots.PlotsDisplay(), plt)
end
else else
# #
function Media.render(pane::Atom.PlotPane, plt::Plot) function Media.render(pane::Atom.PlotPane, plt::Plot)
@ -317,11 +321,5 @@ function setup_atom()
s = "PlotPane turned off. The plotly and plotlyjs backends cannot render in the PlotPane due to javascript issues." s = "PlotPane turned off. The plotly and plotlyjs backends cannot render in the PlotPane due to javascript issues."
Media.render(pane, Atom.div(Atom.HTML(s))) Media.render(pane, Atom.div(Atom.HTML(s)))
end end
# special handling for PlotlyJS to pass through to that render method
function Media.render(pane::Atom.PlotPane, plt::Plot{PlotlyJSBackend})
Plots.prepare_output(plt)
Media.render(pane, plt.o)
end
end end
end end

View File

@ -257,12 +257,23 @@ end
# # 1 argument # # 1 argument
# # -------------------------------------------------------------------- # # --------------------------------------------------------------------
# helper function to ensure relevant attributes are wrapped by Surface
function wrap_surfaces(d::KW)
if haskey(d, :fill_z)
v = d[:fill_z]
if !isa(v, Surface)
d[:fill_z] = Surface(v)
end
end
end
@recipe f(n::Integer) = is3d(get(d,:seriestype,:path)) ? (SliceIt, n, n, n) : (SliceIt, n, n, nothing) @recipe f(n::Integer) = is3d(get(d,:seriestype,:path)) ? (SliceIt, n, n, n) : (SliceIt, n, n, nothing)
# return a surface if this is a 3d plot, otherwise let it be sliced up # return a surface if this is a 3d plot, otherwise let it be sliced up
@recipe function f{T<:Union{Integer,AbstractFloat}}(mat::AMat{T}) @recipe function f{T<:Union{Integer,AbstractFloat}}(mat::AMat{T})
if all3D(d) if all3D(d)
n,m = size(mat) n,m = size(mat)
wrap_surfaces(d)
SliceIt, 1:m, 1:n, Surface(mat) SliceIt, 1:m, 1:n, Surface(mat)
else else
SliceIt, nothing, mat, nothing SliceIt, nothing, mat, nothing
@ -274,6 +285,7 @@ end
if all3D(d) if all3D(d)
mat = fmt.data mat = fmt.data
n,m = size(mat) n,m = size(mat)
wrap_surfaces(d)
SliceIt, 1:m, 1:n, Formatted(Surface(mat), fmt.formatter) SliceIt, 1:m, 1:n, Formatted(Surface(mat), fmt.formatter)
else else
SliceIt, nothing, fmt, nothing SliceIt, nothing, fmt, nothing
@ -391,6 +403,7 @@ end
# seriestype := :path3d # seriestype := :path3d
# end # end
# end # end
wrap_surfaces(d)
SliceIt, x, y, z SliceIt, x, y, z
end end
@ -400,6 +413,7 @@ end
@recipe function f(x::AVec, y::AVec, zf::Function) @recipe function f(x::AVec, y::AVec, zf::Function)
# x = X <: Number ? sort(x) : x # x = X <: Number ? sort(x) : x
# y = Y <: Number ? sort(y) : y # y = Y <: Number ? sort(y) : y
wrap_surfaces(d)
SliceIt, x, y, Surface(zf, x, y) # TODO: replace with SurfaceFunction when supported SliceIt, x, y, Surface(zf, x, y) # TODO: replace with SurfaceFunction when supported
end end
@ -410,6 +424,7 @@ end
if !like_surface(get(d, :seriestype, :none)) if !like_surface(get(d, :seriestype, :none))
d[:seriestype] = :contour d[:seriestype] = :contour
end end
wrap_surfaces(d)
SliceIt, x, y, Surface(z) SliceIt, x, y, Surface(z)
end end