diff --git a/src/Plots.jl b/src/Plots.jl index 3515e75b..c957ab8a 100644 --- a/src/Plots.jl +++ b/src/Plots.jl @@ -1,4 +1,4 @@ -__precompile__(false) +__precompile__(true) module Plots diff --git a/src/arg_desc.jl b/src/arg_desc.jl index b4503c1e..4efbf1af 100644 --- a/src/arg_desc.jl +++ b/src/arg_desc.jl @@ -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.", :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).", +: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.", :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)", diff --git a/src/args.jl b/src/args.jl index 119dd125..8f82fec0 100644 --- a/src/args.jl +++ b/src/args.jl @@ -188,6 +188,7 @@ const _series_defaults = KW( :z => nothing, # depth for contour, surface, etc :marker_z => nothing, # value for color scale :line_z => nothing, + :fill_z => nothing, :levels => 15, :orientation => :vertical, :bar_position => :overlay, # for bar plots and histograms: could also be stack (stack up) or dodge (side by side) @@ -431,6 +432,7 @@ add_aliases(:zguide, :zlabel, :zlab, :zl) add_aliases(:zlims, :zlim, :zlimit, :zlimits) add_aliases(:zticks, :ztick) add_aliases(:zrotation, :zrot, :zr) +add_aliases(:fill_z, :fillz, :fz, :surfacecolor, :surfacecolour, :sc, :surfcolor, :surfcolour) add_aliases(:legend, :leg, :key) add_aliases(:colorbar, :cb, :cbar, :colorkey) add_aliases(:clims, :clim, :cbarlims, :cbar_lims, :climits, :color_limits) diff --git a/src/backends.jl b/src/backends.jl index 16fcad2c..779b91cc 100644 --- a/src/backends.jl +++ b/src/backends.jl @@ -276,6 +276,7 @@ end @init_backend GR @init_backend GLVisualize @init_backend PGFPlots +@init_backend InspectDR # --------------------------------------------------------- diff --git a/src/backends/inspectdr.jl b/src/backends/inspectdr.jl new file mode 100644 index 00000000..72034473 --- /dev/null +++ b/src/backends/inspectdr.jl @@ -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 diff --git a/src/backends/pgfplots.jl b/src/backends/pgfplots.jl index b42e8629..de4d4a6a 100644 --- a/src/backends/pgfplots.jl +++ b/src/backends/pgfplots.jl @@ -3,7 +3,7 @@ # significant contributions by: @pkofod const _pgfplots_attr = merge_with_base_supported([ - # :annotations, + :annotations, # :background_color_legend, :background_color_inside, # :background_color_outside, @@ -27,12 +27,12 @@ const _pgfplots_attr = merge_with_base_supported([ # :ribbon, :quiver, :arrow, # :orientation, # :overwrite_figure, - # :polar, + :polar, # :normalize, :weights, :contours, :aspect_ratio, # :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_marker = [:none, :auto, :circle, :rect, :diamond, :utriangle, :dtriangle, :cross, :xcross, :star5, :pentagon] #vcat(_allMarkers, Shape) const _pgfplots_scale = [:identity, :ln, :log2, :log10] @@ -136,6 +136,20 @@ function pgf_marker(d::KW) }""" 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) @@ -147,7 +161,7 @@ function pgf_series(sp::Subplot, series::Series) push!(style, pgf_linestyle(d)) push!(style, pgf_marker(d)) - if d[:fillrange] != nothing + if d[:fillrange] != nothing || st in (:shape,) push!(style, pgf_fillstyle(d)) end @@ -211,6 +225,9 @@ function pgf_axis(sp::Subplot, letter) # axis guide kw[Symbol(letter,:label)] = axis[:guide] + # Add ticklabel rotations + push!(style, "$(letter)ticklabel style={rotate = $(axis[:rotation])}") + # flip/reverse? axis[:flip] && push!(style, "$letter dir=reverse") @@ -249,8 +266,11 @@ end function _update_plot_object(plt::Plot{PGFPlotsBackend}) 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 - # first build the PGFPlots.Axis object + # first build the PGFPlots.Axis object style = ["unbounded coords=jump"] kw = KW() @@ -265,10 +285,12 @@ function _update_plot_object(plt::Plot{PGFPlotsBackend}) # bounding box values are in mm # 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) push!(style, """ 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])} """) kw[:width] = "$(width(bb).value)mm" @@ -288,19 +310,34 @@ function _update_plot_object(plt::Plot{PGFPlotsBackend}) kw[:legendPos] = _pgfplots_legend_pos[legpos] 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 for series in series_list(sp) 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 + # add the annotations + for ann in sp[:annotations] + pgf_add_annotation!(o,ann...) + end + + # add the PGFPlots.Axis to the list push!(plt.o, o) end end - function _show(io::IO, mime::MIME"image/svg+xml", plt::Plot{PGFPlotsBackend}) show(io, mime, plt.o) end diff --git a/src/backends/plotly.jl b/src/backends/plotly.jl index a70ddf60..bbdd29b9 100644 --- a/src/backends/plotly.jl +++ b/src/backends/plotly.jl @@ -20,7 +20,7 @@ const _plotly_attr = merge_with_base_supported([ :guide, :lims, :ticks, :scale, :flip, :rotation, :tickfont, :guidefont, :legendfont, :grid, :legend, :colorbar, - :marker_z, :levels, + :marker_z, :fill_z, :levels, :ribbon, :quiver, :orientation, # :overwrite_figure, @@ -371,6 +371,7 @@ end plotly_colorscale(c, α) = plotly_colorscale(cgrad(alpha=α), α) # plotly_colorscale(c, alpha = nothing) = plotly_colorscale(cgrad(), alpha) + const _plotly_markers = KW( :rect => "square", :xcross => "x", @@ -488,6 +489,9 @@ function plotly_series(plt::Plot, series::Series) d_out[:contours] = KW(:x => wirelines, :y => wirelines, :z => wirelines) else 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 elseif st == :pie diff --git a/src/backends/plotlyjs.jl b/src/backends/plotlyjs.jl index 6d4ad145..0a2ad219 100644 --- a/src/backends/plotlyjs.jl +++ b/src/backends/plotlyjs.jl @@ -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"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}) - 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 diff --git a/src/backends/pyplot.jl b/src/backends/pyplot.jl index b1f7e2c0..68f45be9 100644 --- a/src/backends/pyplot.jl +++ b/src/backends/pyplot.jl @@ -18,8 +18,7 @@ const _pyplot_attr = merge_with_base_supported([ :guide, :lims, :ticks, :scale, :flip, :rotation, :tickfont, :guidefont, :legendfont, :grid, :legend, :colorbar, - :marker_z, - :line_z, + :marker_z, :line_z, :fill_z, :levels, :ribbon, :quiver, :arrow, :orientation, @@ -56,6 +55,10 @@ function add_backend_string(::PyPlotBackend) withenv("PYTHON" => "") do Pkg.build("PyPlot") end + import Conda + Conda.add("qt=4.8.5") + + # now restart julia! """ end @@ -82,7 +85,17 @@ function _initialize_backend(::PyPlotBackend) const pycollections = PyPlot.pywrap(PyPlot.pyimport("matplotlib.collections")) const pyart3d = PyPlot.pywrap(PyPlot.pyimport("mpl_toolkits.mplot3d.art3d")) 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 PyPlot.ioff() end @@ -124,8 +137,8 @@ end py_colormap(c) = py_colormap(cgrad()) -function py_shading(c, z, α=nothing) - cmap = py_colormap(c, α) +function py_shading(c, z) + cmap = py_colormap(c) ls = pycolors.pymember("LightSource")(270,45) ls[:shade](z, cmap, vert_exag=0.1, blend_mode="soft") end @@ -668,21 +681,16 @@ function py_add_series(plt::Plot{PyPlotBackend}, series::Series) x = repmat(x', length(y), 1) y = repmat(y, 1, length(series[:x])) end - # z = z' z = transpose_z(series, z) if st == :surface - if series[:marker_z] != nothing - extrakw[:facecolors] = py_shading(series[:fillcolor], series[:marker_z], series[:fillalpha]) + if series[:fill_z] != nothing + # the surface colors are different than z-value + extrakw[:facecolors] = py_shading(series[:fillcolor], transpose_z(series, series[:fill_z].surf)) 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 extrakw[:cmap] = py_fillcolormap(series) - needs_colorbar = true end + needs_colorbar = true end handle = ax[st == :surface ? :plot_surface : :plot_wireframe](x, y, z; label = series[:label], diff --git a/src/backends/web.jl b/src/backends/web.jl index 2fd4ae6e..6781314e 100644 --- a/src/backends/web.jl +++ b/src/backends/web.jl @@ -27,7 +27,7 @@ function open_browser_window(filename::AbstractString) return run(`xdg-open $(filename)`) end @static if is_windows() - return run(`$(ENV["COMSPEC"]) /c start $(filename)`) + return run(`$(ENV["COMSPEC"]) /c start "" "$(filename)"`) end warn("Unknown OS... cannot open browser window.") end diff --git a/src/output.jl b/src/output.jl index ee9972ab..f3efd812 100644 --- a/src/output.jl +++ b/src/output.jl @@ -302,6 +302,10 @@ function setup_atom() Media.render(pane, Atom.div(".fill", Atom.HTML(stringmime(MIME("text/html"), plt)))) plt[:size] = sz end + # special handling for PlotlyJS + function Media.render(pane::Atom.PlotPane, plt::Plot{PlotlyJSBackend}) + display(Plots.PlotsDisplay(), plt) + end else # 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." Media.render(pane, Atom.div(Atom.HTML(s))) 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 diff --git a/src/series.jl b/src/series.jl index 02e9eca2..f7233c1a 100644 --- a/src/series.jl +++ b/src/series.jl @@ -257,12 +257,23 @@ end # # 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) # 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}) if all3D(d) n,m = size(mat) + wrap_surfaces(d) SliceIt, 1:m, 1:n, Surface(mat) else SliceIt, nothing, mat, nothing @@ -274,6 +285,7 @@ end if all3D(d) mat = fmt.data n,m = size(mat) + wrap_surfaces(d) SliceIt, 1:m, 1:n, Formatted(Surface(mat), fmt.formatter) else SliceIt, nothing, fmt, nothing @@ -391,6 +403,7 @@ end # seriestype := :path3d # end # end + wrap_surfaces(d) SliceIt, x, y, z end @@ -400,6 +413,7 @@ end @recipe function f(x::AVec, y::AVec, zf::Function) # x = X <: Number ? sort(x) : x # y = Y <: Number ? sort(y) : y + wrap_surfaces(d) SliceIt, x, y, Surface(zf, x, y) # TODO: replace with SurfaceFunction when supported end @@ -410,6 +424,7 @@ end if !like_surface(get(d, :seriestype, :none)) d[:seriestype] = :contour end + wrap_surfaces(d) SliceIt, x, y, Surface(z) end