# https://github.com/stevengj/PyPlot.jl immutable PyPlotPackage <: PlottingPackage end export pyplot pyplot() = backend(:pyplot) # ------------------------------- # supportedArgs(::PyPlotPackage) = setdiff(_allArgs, [:reg, :heatmap_c, :fill, :pos, :xlims, :ylims, :xticks, :yticks]) supportedArgs(::PyPlotPackage) = [ :annotation, # :args, :axis, :background_color, :color, :color_palette, :fillrange, :fillcolor, :foreground_color, :group, # :heatmap_c, # :kwargs, :label, :layout, :legend, :linestyle, :linetype, :linewidth, :markershape, :markercolor, :markersize, :n, :nbins, :nc, :nr, # :pos, # :reg, # :ribbon, :show, :size, :title, :windowtitle, :x, :xlabel, :xlims, :xticks, :y, :ylabel, :ylims, :yrightlabel, :yticks, :xscale, :yscale, :xflip, :yflip, :z, # :linkx, # :linky, # :linkfunc, ] supportedAxes(::PyPlotPackage) = _allAxes supportedTypes(::PyPlotPackage) = [:none, :line, :path, :step, :stepinverted, :sticks, :scatter, :heatmap, :hexbin, :hist, :bar, :hline, :vline] supportedStyles(::PyPlotPackage) = [:auto, :solid, :dash, :dot, :dashdot] supportedMarkers(::PyPlotPackage) = [:none, :auto, :rect, :ellipse, :diamond, :utriangle, :dtriangle, :cross, :xcross, :star1, :hexagon] supportedScales(::PyPlotPackage) = [:identity, :log, :log2, :log10] subplotSupported(::PyPlotPackage) = true # convert colorant to 4-tuple RGBA getPyPlotColor(c::Colorant) = map(f->float(f(c)), (red, green, blue, alpha)) getPyPlotColor(scheme::ColorScheme) = getPyPlotColor(getColor(scheme)) # getPyPlotColorMap(c::ColorGradient) = PyPlot.matplotlib[:colors][:ListedColormap](map(getPyPlotColor, getColorVector(c))) function getPyPlotColorMap(c::ColorGradient) pycolors.pymember("LinearSegmentedColormap")[:from_list]("tmp", map(getPyPlotColor, getColorVector(c))) end # get the style (solid, dashed, etc) function getPyPlotLineStyle(linetype::Symbol, linestyle::Symbol) linetype == :none && return " " linestyle == :solid && return "-" linestyle == :dash && return "--" linestyle == :dot && return ":" linestyle == :dashdot && return "-." warn("Unknown linestyle $linestyle") return "-" end # get the marker shape function getPyPlotMarker(marker::Symbol) marker == :none && return " " marker == :ellipse && return "o" marker == :rect && return "s" marker == :diamond && return "D" marker == :utriangle && return "^" marker == :dtriangle && return "v" marker == :cross && return "+" marker == :xcross && return "x" marker == :star1 && return "*" # marker == :star2 && return "*" marker == :hexagon && return "h" warn("Unknown marker $marker") return "o" end # pass through function getPyPlotMarker(marker::@compat(AbstractString)) @assert length(marker) == 1 marker end function getPyPlotDrawStyle(linetype::Symbol) linetype == :step && return "steps-post" linetype == :stepinverted && return "steps-pre" return "default" end immutable PyPlotFigWrapper fig end immutable PyPlotAxisWrapper ax end # addPyPlotAxis(fig, layout) = error("Only GridLayouts are supported with PyPlot") # function addPyPlotAxis(fig, layout::GridLayout, idx::Int) # end # get a reference to the correct axis function getLeftAxis(wrap::PyPlotFigWrapper) # @show wrap.fig.o[:axes] axes = wrap.fig.o[:axes] if isempty(axes) return wrap.fig.o[:add_subplot](111) end axes[1] end getLeftAxis(wrap::PyPlotAxisWrapper) = wrap.ax getLeftAxis(plt::Plot{PyPlotPackage}) = getLeftAxis(plt.o) getRightAxis(x) = getLeftAxis(x)[:twinx]() getAxis(plt::Plot{PyPlotPackage}, axis::Symbol) = (axis == :right ? getRightAxis : getLeftAxis)(plt) # left axis is PyPlot., right axis is "f.axes[0].twinx()." function getPyPlotFunction(plt::Plot, axis::Symbol, linetype::Symbol) # in the 2-axis case we need to get: [:] ax = getAxis(plt, axis) # if axis == :right # ax = getRightAxis(plt.o) ax[:set_ylabel](plt.initargs[:yrightlabel]) fmap = @compat Dict( :hist => :hist, :sticks => :bar, :bar => :bar, :heatmap => :hexbin, :hexbin => :hexbin, :scatter => :scatter ) return ax[get(fmap, linetype, :plot)] # return ax[linetype == :hist ? :hist : (linetype in (:sticks,:bar) ? :bar : (linetype in (:heatmap,:hexbin) ? :hexbin : :plot))] # end # # get the function # fmap = @compat Dict( # :hist => PyPlot.plt[:hist], # :sticks => PyPlot.bar, # :bar => PyPlot.bar, # :heatmap => PyPlot.hexbin, # :hexbin => PyPlot.hexbin, # :scatter => PyPlot.scatter # ) # return get(fmap, linetype, PyPlot.plot) # # return linetype == :hist ? PyPlot.plt[:hist] : (linetype in (:sticks,:bar) ? PyPlot.bar : (linetype in (:heatmap,:hexbin) ? PyPlot.hexbin : PyPlot.plot)) end function updateAxisColors(ax, fgcolor) for loc in ("bottom", "top", "left", "right") ax[:spines][loc][:set_color](fgcolor) end for axis in ("x", "y") ax[:tick_params](axis=axis, colors=fgcolor, which="both") end for axis in (:yaxis, :xaxis) ax[axis][:label][:set_color](fgcolor) end ax[:title][:set_color](fgcolor) end nop() = nothing makePyPlotCurrent(wrap::PyPlotFigWrapper) = PyPlot.figure(wrap.fig.o[:number]) makePyPlotCurrent(wrap::PyPlotAxisWrapper) = PyPlot.sca(wrap.ax.o) makePyPlotCurrent(plt::Plot{PyPlotPackage}) = makePyPlotCurrent(plt.o) # makePyPlotCurrent(plt::Plot) = PyPlot.figure(plt.o.o[:number]) function preparePlotUpdate(plt::Plot{PyPlotPackage}) makePyPlotCurrent(plt) end # ------------------------------------------------------------------ # TODO: # fillto # might have to use barHack/histogramHack?? # heatmap # subplot # reg # true or false, add a regression line for each line # pos # (Int,Int), move the enclosing window to this position # windowtitle # string or symbol, set the title of the enclosing windowtitle # screen # Integer, move enclosing window to this screen number (for multiscreen desktops) # show # true or false, show the plot (in case you don't want the window to pop up right away) function plot(pkg::PyPlotPackage; kw...) # create the figure d = Dict(kw) w,h = map(px2inch, d[:size]) bgcolor = getPyPlotColor(d[:background_color]) # standalone plots will create a figure, but not if part of a subplot (do it later) if haskey(d, :subplot) println("no wrap") wrap = nothing else println("yes wrap") wrap = PyPlotFigWrapper(PyPlot.figure(; figsize = (w,h), facecolor = bgcolor, dpi = 96)) end # num = wrap.o[:number] plt = Plot(wrap, pkg, 0, d, Dict[]) plt end function plot!(pkg::PyPlotPackage, plt::Plot; kw...) d = Dict(kw) # fig = plt.o ax = getAxis(plt, d[:axis]) lt = d[:linetype] if !(lt in supportedTypes(pkg)) error("linetype $(lt) is unsupported in PyPlot. Choose from: $(supportedTypes(pkg))") end if lt == :sticks d,_ = sticksHack(;d...) elseif lt == :scatter # d[:linetype] = :none if d[:markershape] == :none d[:markershape] = :ellipse end elseif lt in (:hline,:vline) linewidth = d[:linewidth] linecolor = getPyPlotColor(d[:color]) linestyle = getPyPlotLineStyle(lt, d[:linestyle]) for yi in d[:y] # func = (lt == :hline ? PyPlot.axhline : PyPlot.axvline) func = ax[lt == :hline ? :axhline : axvline] func(yi, linewidth=d[:linewidth], color=linecolor, linestyle=linestyle) end end lt = d[:linetype] extraargs = Dict() plotfunc = getPyPlotFunction(plt, d[:axis], lt) # we have different args depending on plot type if lt in (:hist, :sticks, :bar) # NOTE: this is unsupported because it does the wrong thing... it shifts the whole axis # extraargs[:bottom] = d[:fill] if lt == :hist extraargs[:bins] = d[:nbins] else extraargs[:linewidth] = (lt == :sticks ? 0.1 : 0.9) end elseif lt in (:heatmap, :hexbin) extraargs[:gridsize] = d[:nbins] else extraargs[:linestyle] = getPyPlotLineStyle(lt, d[:linestyle]) extraargs[:marker] = getPyPlotMarker(d[:markershape]) if lt == :scatter extraargs[:s] = d[:markersize]^2 #extraargs[:linewidths] = d[:linewidth] c = d[:markercolor] if isa(c, ColorGradient) && d[:z] != nothing extraargs[:c] = convert(Vector{Float64}, d[:z]) extraargs[:cmap] = getPyPlotColorMap(c) else extraargs[:c] = getPyPlotColor(c) end else extraargs[:markersize] = d[:markersize] extraargs[:markerfacecolor] = getPyPlotColor(d[:markercolor]) extraargs[:drawstyle] = getPyPlotDrawStyle(lt) end end # set these for all types # extraargs[:figure] = plt.o extraargs[:color] = getPyPlotColor(d[:color]) extraargs[:linewidth] = d[:linewidth] extraargs[:label] = d[:label] # do the plot if lt == :hist d[:serieshandle] = plotfunc(d[:y]; extraargs...)[1] elseif lt in (:scatter, :heatmap, :hexbin) d[:serieshandle] = plotfunc(d[:x], d[:y]; extraargs...) else d[:serieshandle] = plotfunc(d[:x], d[:y]; extraargs...)[1] end # this sets the bg color inside the grid # ax = getLeftAxis(plt) ax[:set_axis_bgcolor](getPyPlotColor(plt.initargs[:background_color])) fillrange = d[:fillrange] if fillrange != nothing fillcolor = getPyPlotColor(d[:fillcolor]) if typeof(fillrange) <: @compat(Union{Real, AVec}) ax[:fill_between](d[:x], fillrange, d[:y], facecolor = fillcolor) else ax[:fill_between](d[:x], fillrange..., facecolor = fillcolor) end end push!(plt.seriesargs, d) plt end # ----------------------------------------------------------------- function addPyPlotLims(ax, lims, isx::Bool) lims == :auto && return ltype = limsType(lims) if ltype == :limits # (isx ? PyPlot.xlim : PyPlot.ylim)(lims...) # @show isx, lims, ax ax[isx ? :set_xlim : :set_ylim](lims...) else error("Invalid input for $(isx ? "xlims" : "ylims"): ", lims) end end function addPyPlotTicks(ax, ticks, isx::Bool) ticks == :auto && return if ticks == :none ticks = zeros(0) end ttype = ticksType(ticks) if ttype == :ticks # (isx ? PyPlot.xticks : PyPlot.yticks)(ticks) ax[isx ? :set_xticks : :set_yticks](ticks) elseif ttype == :ticks_and_labels # (isx ? PyPlot.xticks : PyPlot.yticks)(ticks...) ax[isx ? :set_xticks : :set_yticks](ticks...) else error("Invalid input for $(isx ? "xticks" : "yticks"): ", ticks) end end function updatePlotItems(plt::Plot{PyPlotPackage}, d::Dict) figorax = plt.o ax = getLeftAxis(figorax) # PyPlot.sca(ax) # title and axis labels haskey(d, :title) && PyPlot.title(d[:title]) # haskey(d, :xlabel) && PyPlot.xlabel(d[:xlabel]) haskey(d, :xlabel) && ax[:set_xlabel](d[:xlabel]) if haskey(d, :ylabel) # ax = getLeftAxis(figorax) ax[:set_ylabel](d[:ylabel]) end if haskey(d, :yrightlabel) rightax = getRightAxis(figorax) rightax[:set_ylabel](d[:yrightlabel]) end # scales haskey(d, :xscale) && applyPyPlotScale(ax, d[:xscale], true) haskey(d, :yscale) && applyPyPlotScale(ax, d[:yscale], false) # limits and ticks haskey(d, :xlims) && addPyPlotLims(ax, d[:xlims], true) haskey(d, :ylims) && addPyPlotLims(ax, d[:ylims], false) haskey(d, :xticks) && addPyPlotTicks(ax, d[:xticks], true) haskey(d, :yticks) && addPyPlotTicks(ax, d[:yticks], false) if get(d, :xflip, false) ax[:invert_xaxis]() end if get(d, :yflip, false) ax[:invert_yaxis]() end end function applyPyPlotScale(ax, scaleType::Symbol, isx::Bool) func = ax[isx ? :set_xscale : :set_yscale] scaleType == :identity && return func("linear") scaleType == :log && return func("log", basex = e, basey = e) scaleType == :log2 && return func("log", basex = 2, basey = 2) scaleType == :log10 && return func("log", basex = 10, basey = 10) warn("Unhandled scaleType: ", scaleType) end # ----------------------------------------------------------------- function createPyPlotAnnotationObject(plt::Plot{PyPlotPackage}, x, y, val::@compat(AbstractString)) ax = getLeftAxis(plt) ax[:annotate](val, xy = (x,y)) end function addAnnotations{X,Y,V}(plt::Plot{PyPlotPackage}, anns::AVec{@compat(Tuple{X,Y,V})}) for ann in anns createPyPlotAnnotationObject(plt, ann...) end end # ----------------------------------------------------------------- # create the underlying object (each backend will do this differently) function buildSubplotObject!(subplt::Subplot{PyPlotPackage}, isbefore::Bool) l = subplt.layout isa(l, GridLayout) || error("Unsupported layout ", l) iargs = subplt.initargs[1] w,h = map(px2inch, iargs[:size]) bgcolor = getPyPlotColor(iargs[:background_color]) n, m = nrows(l), ncols(l) fig, axes = PyPlot.subplots(n, m, sharex = get(iargs,:linkx,false), sharey = get(iargs,:linky,false), figsize = (w,h), facecolor = bgcolor, dpi = 96) # @show axes @assert length(axes) == length(subplt.plts) axes = vec(reshape(axes, n, m)') for (i,plt) in enumerate(subplt.plts) plt.o = PyPlotAxisWrapper(axes[i]) end # @show fig axes subplt.o = PyPlotFigWrapper(fig) true # # TODO: set plt.o = PyPlotAxisWrapper(ax) for each plot # for (i,(r,c)) in enumerate(subplt.layout) # plt = subplt.plts[i] # plt.o = PyPlotAxisWrapper(subplt.o.fig.o[:add_subplot]()) # # return wrap.fig.o[:add_subplot](111) end function handleLinkInner(plt::Plot{PyPlotPackage}, isx::Bool) if isx plot!(plt, xticks=zeros(0), xlabel="") else plot!(plt, yticks=zeros(0), ylabel="") end end function expandLimits!(lims, plt::Plot{PyPlotPackage}, isx::Bool) pltlims = plt.o.ax[isx ? :get_xbound : :get_ybound]() expandLimits!(lims, pltlims) end # ----------------------------------------------------------------- # function addPyPlotLegend(plt::Plot) function addPyPlotLegend(plt::Plot, ax) if plt.initargs[:legend] # gotta do this to ensure both axes are included args = filter(x -> !(x[:linetype] in (:hist,:hexbin,:heatmap,:hline,:vline)), plt.seriesargs) if length(args) > 0 # PyPlot.legend([d[:serieshandle] for d in args], [d[:label] for d in args], loc="best") ax[:legend]([d[:serieshandle] for d in args], [d[:label] for d in args], loc="best") end end end function finalizePlot(plt::Plot{PyPlotPackage}) wrap = plt.o ax = getLeftAxis(plt) addPyPlotLegend(plt, ax) updateAxisColors(ax, getPyPlotColor(plt.initargs[:foreground_color])) PyPlot.draw() end function Base.writemime(io::IO, m::MIME"image/png", plt::Plot{PyPlotPackage}) # wrap = plt.o # # addPyPlotLegend(plt) # # ax = fig.o[:axes][1] # ax = getLeftAxis(plt) # addPyPlotLegend(plt, ax) # updateAxisColors(ax, getPyPlotColor(plt.initargs[:foreground_color])) finalizePlot(plt) writemime(io, m, plt.o.fig) end function Base.display(::PlotsDisplay, plt::Plot{PyPlotPackage}) # wrap = plt.o # # addPyPlotLegend(plt) # # ax = fig.o[:axes][1] # ax = getLeftAxis(plt) # addPyPlotLegend(plt, ax) # updateAxisColors(ax, getPyPlotColor(plt.initargs[:foreground_color])) # # wrap.fig.o[:show]() # PyPlot.draw() # display(wrap.fig) finalizePlot(plt) end function finalizePlot(subplt::Subplot{PyPlotPackage}) fig = subplt.o.fig for (i,plt) in enumerate(subplt.plts) # fig.o[:axes][i] = getLeftAxis(plt) finalizePlot(plt) end end function Base.display(::PlotsDisplay, subplt::Subplot{PyPlotPackage}) # for plt in subplt.plts # finalizePlot(plt) # end finalizePlot(subplt) display(subplt.o.fig) end function Base.writemime(io::IO, m::MIME"image/png", subplt::Subplot{PyPlotPackage}) # wrap = plt.o # # addPyPlotLegend(plt) # # ax = fig.o[:axes][1] # ax = getLeftAxis(plt) # addPyPlotLegend(plt, ax) # updateAxisColors(ax, getPyPlotColor(plt.initargs[:foreground_color])) # for plt in subplt.plts # finalizePlot(plt) # end finalizePlot(subplt) writemime(io, m, subplt.o.fig) end