# https://github.com/stevengj/PyPlot.jl supportedArgs(::PyPlotBackend) = [ :annotation, :background_color, :foreground_color, :color_palette, :background_color_legend, :background_color_inside, :background_color_outside, :foreground_color_legend, :foreground_color_grid, :foreground_color_axis, :foreground_color_text, :foreground_color_border, :group, :label, :seriestype, :seriescolor, :seriesalpha, :linecolor, :linestyle, :linewidth, :linealpha, :markershape, :markercolor, :markersize, :markeralpha, :markerstrokewidth, :markerstrokecolor, :markerstrokealpha, :fillrange, :fillcolor, :fillalpha, :bins, :n, :nc, :nr, :layout, :smooth, :title, :windowtitle, :show, :size, :x, :xlabel, :xlims, :xticks, :xscale, :xflip, :xrotation, :y, :ylabel, :ylims, :yticks, :yscale, :yflip, :yrotation, :axis, :yrightlabel, :z, :zlabel, :zlims, :zticks, :zscale, :zflip, :zrotation, :z, :tickfont, :guidefont, :legendfont, :grid, :legend, :colorbar, :marker_z, :levels, :xerror, :yerror, :ribbon, :quiver, :arrow, :orientation, :overwrite_figure, :polar, :normalize, :weights, :contours, :aspect_ratio, :match_dimensions, :subplot, ] supportedAxes(::PyPlotBackend) = _allAxes supportedTypes(::PyPlotBackend) = [ :none, :line, :path, :steppre, :steppost, :shape, :scatter, :hist2d, :hexbin, :hist, :density, :bar, :sticks, #:box, :violin, :quiver, :hline, :vline, :heatmap, :pie, :image, :contour, :contour3d, :path3d, :scatter3d, :surface, :wireframe ] supportedStyles(::PyPlotBackend) = [:auto, :solid, :dash, :dot, :dashdot] supportedMarkers(::PyPlotBackend) = vcat(_allMarkers, Shape) supportedScales(::PyPlotBackend) = [:identity, :ln, :log2, :log10] subplotSupported(::PyPlotBackend) = true nativeImagesSupported(::PyPlotBackend) = true # -------------------------------------------------------------------------------------- function _initialize_backend(::PyPlotBackend) @eval begin import PyPlot export PyPlot const pycolors = PyPlot.pywrap(PyPlot.pyimport("matplotlib.colors")) const pypath = PyPlot.pywrap(PyPlot.pyimport("matplotlib.path")) const mplot3d = PyPlot.pywrap(PyPlot.pyimport("mpl_toolkits.mplot3d")) const pypatches = PyPlot.pywrap(PyPlot.pyimport("matplotlib.patches")) const pyfont = PyPlot.pywrap(PyPlot.pyimport("matplotlib.font_manager")) const pyticker = PyPlot.pywrap(PyPlot.pyimport("matplotlib.ticker")) const pycmap = PyPlot.pywrap(PyPlot.pyimport("matplotlib.cm")) const pynp = PyPlot.pywrap(PyPlot.pyimport("numpy")) const pytransforms = PyPlot.pywrap(PyPlot.pyimport("matplotlib.transforms")) end PyPlot.ioff() if !isa(Base.Multimedia.displays[end], Base.REPL.REPLDisplay) PyPlot.ioff() # stops wierd behavior of displaying incomplete graphs in IJulia # # TODO: how the hell can I use PyQt4?? # "pyqt4"=>:qt_pyqt4 # PyPlot.backend[1] = "pyqt4" # PyPlot.gui[1] = :qt_pyqt4 # PyPlot.switch_backend("Qt4Agg") # only turn on the gui if we want it if PyPlot.gui != :none PyPlot.pygui(true) end end end # ------------------------------- # convert colorant to 4-tuple RGBA getPyPlotColor(c::Colorant, α=nothing) = map(f->float(f(convertColor(c,α))), (red, green, blue, alpha)) getPyPlotColor(cvec::ColorVector, α=nothing) = map(getPyPlotColor, convertColor(cvec, α).v) getPyPlotColor(scheme::ColorScheme, α=nothing) = getPyPlotColor(convertColor(getColor(scheme), α)) getPyPlotColor(c, α=nothing) = getPyPlotColor(convertColor(c, α)) function getPyPlotColorMap(c::ColorGradient, α=nothing) pyvals = [(v, getPyPlotColor(getColorZ(c, v), α)) for v in c.values] pycolors.pymember("LinearSegmentedColormap")[:from_list]("tmp", pyvals) end # convert vectors and ColorVectors to standard ColorGradients # TODO: move this logic to colors.jl and keep a barebones wrapper for pyplot getPyPlotColorMap(cv::ColorVector, α=nothing) = getPyPlotColorMap(ColorGradient(cv.v), α) getPyPlotColorMap(v::AVec, α=nothing) = getPyPlotColorMap(ColorGradient(v), α) # anything else just gets a bluesred gradient getPyPlotColorMap(c, α=nothing) = getPyPlotColorMap(default_gradient(), α) function getPyPlotCustomShading(c, z, α=nothing) cmap = getPyPlotColorMap(c, α) # sm = pycmap.pymember("ScalarMappable")(cmap = cmap) # sm[:set_array](z) # sm ls = pycolors.pymember("LightSource")(270,45) ls[:shade](z, cmap, vert_exag=0.1, blend_mode="soft") end # get the style (solid, dashed, etc) function getPyPlotLineStyle(seriestype::Symbol, linestyle::Symbol) seriestype == :none && return " " linestyle == :solid && return "-" linestyle == :dash && return "--" linestyle == :dot && return ":" linestyle == :dashdot && return "-." warn("Unknown linestyle $linestyle") return "-" end function getPyPlotMarker(marker::Shape) x, y = shape_coords(marker) n = length(x) mat = zeros(n+1,2) for i=1:n mat[i,1] = x[i] mat[i,2] = y[i] end mat[n+1,:] = mat[1,:] pypath.pymember("Path")(mat) end const _path_MOVETO = UInt8(1) const _path_LINETO = UInt8(2) const _path_CLOSEPOLY = UInt8(79) # see http://matplotlib.org/users/path_tutorial.html # and http://matplotlib.org/api/path_api.html#matplotlib.path.Path function buildPyPlotPath(x, y) n = length(x) mat = zeros(n+1, 2) codes = zeros(UInt8, n+1) lastnan = true for i=1:n mat[i,1] = x[i] mat[i,2] = y[i] nan = !ok(x[i], y[i]) codes[i] = if nan _path_CLOSEPOLY else lastnan ? _path_MOVETO : _path_LINETO end lastnan = nan end codes[n+1] = _path_CLOSEPOLY pypath.pymember("Path")(mat, codes) 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 == :star5 && return "*" marker == :pentagon && return "p" marker == :hexagon && return "h" marker == :octagon && return "8" haskey(_shapes, marker) && return getPyPlotMarker(_shapes[marker]) warn("Unknown marker $marker") return "o" end # getPyPlotMarker(markers::AVec) = map(getPyPlotMarker, markers) function getPyPlotMarker(markers::AVec) warn("Vectors of markers are currently unsupported in PyPlot: $markers") getPyPlotMarker(markers[1]) end # pass through function getPyPlotMarker(marker::AbstractString) @assert length(marker) == 1 marker end function getPyPlotStepStyle(seriestype::Symbol) seriestype == :steppost && return "steps-post" seriestype == :steppre && return "steps-pre" return "default" end # untested... return a FontProperties object from a Plots.Font function getPyPlotFont(font::Font) pyfont.pymember("FontProperties")( family = font.family, size = font.size ) end function get_locator_and_formatter(vals::AVec) pyticker.pymember("FixedLocator")(1:length(vals)), pyticker.pymember("FixedFormatter")(vals) end function add_pyfixedformatter(cbar, vals::AVec) cbar[:locator], cbar[:formatter] = get_locator_and_formatter(vals) cbar[:update_ticks]() end # --------------------------------------------------------------------------- # Figure utils -- F*** matplotlib for making me work so hard to figure this crap out # the drawing surface canvas(fig) = fig[:canvas] # the object controlling draw commands renderer(fig) = canvas(fig)[:get_renderer]() # draw commands... paint the screen (probably updating internals too) drawfig(fig) = fig[:draw](renderer(fig)) drawax(ax) = ax[:draw](renderer(ax[:get_figure]())) # bbox(obj) = obj[:get_window_extent](renderer(obj[:get_figure]())) # pos(obj) = obj[:get_position]() # # merge a list of bounding boxes together to become the area that surrounds them all # bbox_union(bboxes) = pytransforms.Bbox[:union](bboxes) function py_bbox_pct(obj) pybbox_pixels = obj[:get_window_extent]() fig = obj[:get_figure]() pybbox_pct = pybbox_pixels[:inverse_transformed](fig[:transFigure]) left, right, bottom, top = pybbox_pct[:get_points]() BoundingBox(left, bottom, right, top) end # bbox_from_pyplot(obj) = function py_bbox_ticks(ax, letter) # fig = ax[:get_figure]() # @show fig labels = ax[symbol("get_"*letter*"ticklabels")]() # @show labels # bboxes = [] bbox_union = BoundingBox() for lab in labels # @show lab,lab[:get_text]() bbox = py_bbox_pct(lab) bbox_union = bbox_union + bbox # @show bbox_union end bbox_union end function py_bbox_axislabel(ax, letter) pyaxis_label = ax[symbol("get_"*letter*"axis")]()[:label] py_bbox_pct(pyaxis_label) end # get a bounding box for the whole axis function py_bbox_axis(ax, letter) py_bbox_ticks(ax, letter) + py_bbox_axislabel(ax, letter) end # get a bounding box for the title area function py_bbox_title(ax) py_bbox_pct(ax[:title]) end xaxis_height(sp::Subplot{PyPlotBackend}) = height(py_bbox_axis(sp.o,"x")) yaxis_width(sp::Subplot{PyPlotBackend}) = width(py_bbox_axis(sp.o,"y")) title_height(sp::Subplot{PyPlotBackend}) = height(py_bbox_title(sp.o)) # note: this overrides the default version to allow for labels that stick out the sides # bounding box (relative to canvas) for plot area # note: we assume the x axis is on the left, and y axis is on the bottom # TODO: really the padding below should be part of the freespace calc, and we should probably # cache the plotarea bbox while we're doing that (need to add plotarea field to Subplot) function plotarea_bbox(sp::Subplot{PyPlotBackend}) ax = sp.o plot_bb = py_bbox_pct(ax) xbb = py_bbox_axis(ax, "x") ybb = py_bbox_axis(ax, "y") titbb = py_bbox_title(ax) items = [xbb, ybb, titbb] # TODO: add in margin/padding from sp.attr leftpad = max(0, left(plot_bb) - minimum(map(left, items))) bottompad = max(0, bottom(plot_bb) - minimum(map(bottom, items))) rightpad = max(0, maximum(map(right, items)) - right(plot_bb)) toppad = max(0, maximum(map(top, items)) - top(plot_bb)) # crop(bbox(sp), BoundingBox(yaxis_width(sp), xaxis_height(sp), 1, 1 - title_height(sp))) crop(bbox(sp), BoundingBox(leftpad, bottompad, 1 - rightpad, 1 - toppad)) end # --------------------------------------------------------------------------- # function used_width(sp::Subplot{PyPlotBackend}) # ax = sp.o # width(py_bbox_axis(ax,"y")) # end # # function used_height(sp::Subplot{PyPlotBackend}) # ax = sp.o # height(py_bbox_axis(ax,"x")) + height(py_bbox_title(ax)) # end # # bounding box (relative to canvas) for plot area # function plotarea_bbox(sp::Subplot{PyPlotBackend}) # crop(bbox(sp), BoundingBox()) # end function update_position!(sp::Subplot{PyPlotBackend}) ax = sp.o bb = plotarea_bbox(sp) ax[:set_position]([f(bb) for f in (left, bottom, width, height)]) end # each backend should set up the subplot here function _initialize_subplot(plt::Plot{PyPlotBackend}, sp::Subplot{PyPlotBackend}) fig = plt.o # add a new axis, and force it to create a new one by setting a distinct label ax = fig[:add_axes]([0,0,1,1], label = string(gensym())) for axis in (:xaxis, :yaxis) ax[axis][:_autolabelpos] = false end sp.o = ax end # --------------------------------------------------------------------------- # these can probably be removed eventually... right now they're just keeping things working before cleanup # getAxis(plt::Plot{PyPlotBackend}, subplot::Subplot = plt.subplots[1]) = subplot.o getAxis(sp::Subplot) = sp.o function getAxis(plt::Plot{PyPlotBackend}, series::Series) sp = get_subplot(plt, get(series.d, :subplot, 1)) getAxis(sp) end # getLeftAxis(plt::Plot{PyPlotBackend}, subplot::Subplot = plt.subplots[1]) = getAxis(plt, subplot) getfig(o) = o # --------------------------------------------------------------------------- # type PyPlotAxisWrapper # ax # rightax # fig # kwargs # for add_subplot # end # # getfig(wrap::PyPlotAxisWrapper) = wrap.fig # # # # # get a reference to the correct axis # function getLeftAxis(wrap::PyPlotAxisWrapper) # if wrap.ax == nothing # axes = wrap.fig.o[:axes] # if isempty(axes) # return wrap.fig.o[:add_subplot](111; wrap.kwargs...) # end # axes[1] # else # wrap.ax # end # end # # function getRightAxis(wrap::PyPlotAxisWrapper) # if wrap.rightax == nothing # wrap.rightax = getLeftAxis(wrap)[:twinx]() # end # wrap.rightax # end # # getLeftAxis(plt::Plot{PyPlotBackend}) = getLeftAxis(plt.o) # getRightAxis(plt::Plot{PyPlotBackend}) = getRightAxis(plt.o) # # getAxis(plt::Plot{PyPlotBackend}, axis::Symbol) = (axis == :right ? getRightAxis : getLeftAxis)(plt) function handleSmooth(plt::Plot{PyPlotBackend}, ax, d::KW, smooth::Bool) if smooth xs, ys = regressionXY(d[:x], d[:y]) ax[:plot](xs, ys, # linestyle = getPyPlotLineStyle(:path, :dashdot), color = getPyPlotColor(d[:linecolor]), linewidth = 2 ) end end handleSmooth(plt::Plot{PyPlotBackend}, ax, d::KW, smooth::Real) = handleSmooth(plt, ax, d, true) # --------------------------------------------------------------------------- # makePyPlotCurrent(wrap::PyPlotAxisWrapper) = wrap.ax == nothing ? PyPlot.figure(wrap.fig.o[:number]) : nothing # makePyPlotCurrent(plt::Plot{PyPlotBackend}) = plt.o == nothing ? nothing : makePyPlotCurrent(plt.o) # # # function _before_add_series(plt::Plot{PyPlotBackend}) # makePyPlotCurrent(plt) # end # ------------------------------------------------------------------ function pyplot_figure(plotargs::KW) w,h = map(px2inch, plotargs[:size]) # reuse the current figure? fig = if plotargs[:overwrite_figure] PyPlot.gcf() else PyPlot.figure() end # update the specs fig[:set_size_inches](w, h, forward = true) fig[:set_facecolor](getPyPlotColor(plotargs[:background_color_outside])) fig[:set_dpi](DPI) # fig[:set_tight_layout](true) # clear the figure PyPlot.clf() # resize the window PyPlot.plt[:get_current_fig_manager]()[:resize](plotargs[:size]...) fig end function pyplot_3d_setup!(wrap, d) if trueOrAllTrue(st -> st in _3dTypes, get(d, :seriestype, :none)) push!(wrap.kwargs, (:projection, "3d")) end end # --------------------------------------------------------------------------- function _create_backend_figure(plt::Plot{PyPlotBackend}) fig = pyplot_figure(plt.plotargs) # TODO: handle 3d and polar fig end # function _create_backend_figure(plt::Plot) # if haskey(plt.plotargs, :subplot) # wrap = nothing # else # wrap = PyPlotAxisWrapper(nothing, nothing, pyplot_figure(plt.plotargs), []) # pyplot_3d_setup!(wrap, plt.plotargs) # if get(plt.plotargs, :polar, false) # push!(wrap.kwargs, (:polar, true)) # end # end # wrap # # plt = Plot(wrap, pkg, 0, plt.plotargs, KW[]) # # plt # end # --------------------------------------------------------------------------- function fix_xy_lengths!(plt::Plot{PyPlotBackend}, d::KW) x, y = d[:x], d[:y] nx, ny = length(x), length(y) if !isa(get(d, :z, nothing), Surface) && nx != ny if nx < ny d[:x] = Float64[x[mod1(i,nx)] for i=1:ny] else d[:y] = Float64[y[mod1(i,ny)] for i=1:nx] end end end # total hack due to PyPlot bug (see issue #145). # hack: duplicate the color vector when the total rgba fields is the same as the series length function color_fix(c, x) if (typeof(c) <: AbstractArray && length(c)*4 == length(x)) || (typeof(c) <: Tuple && length(x) == 4) vcat(c, c) else c end end pylinecolor(d::KW) = getPyPlotColor(d[:linecolor], d[:linealpha]) pymarkercolor(d::KW) = getPyPlotColor(d[:markercolor], d[:markeralpha]) pymarkerstrokecolor(d::KW) = getPyPlotColor(d[:markerstrokecolor], d[:markerstrokealpha]) pyfillcolor(d::KW) = getPyPlotColor(d[:fillcolor], d[:fillalpha]) pylinecolormap(d::KW) = getPyPlotColorMap(d[:linecolor], d[:linealpha]) pymarkercolormap(d::KW) = getPyPlotColorMap(d[:markercolor], d[:markeralpha]) pyfillcolormap(d::KW) = getPyPlotColorMap(d[:fillcolor], d[:fillalpha]) # function _add_series(pkg::PyPlotBackend, plt::Plot, d::KW) function _add_series(plt::Plot{PyPlotBackend}, series::Series) d = series.d st = d[:seriestype] if !(st in supportedTypes(plt.backend)) error("seriestype $(st) is unsupported in PyPlot. Choose from: $(supportedTypes(plt.backend))") end # 3D plots have a different underlying Axes object in PyPlot # TODO: BUG: this adds to kwargs but never changes anything... source of subplot(wireframe(...)) bug? if st in _3dTypes && isempty(plt.o.kwargs) push!(plt.o.kwargs, (:projection, "3d")) end # PyPlot doesn't handle mismatched x/y fix_xy_lengths!(plt, d) # ax = getAxis(plt, d[:axis]) ax = getAxis(plt, series) x, y, z = d[:x], d[:y], d[:z] @show typeof((x,y,z)) xyargs = (st in _3dTypes ? (x,y,z) : (x,y)) # handle zcolor and get c/cmap extrakw = KW() # holds references to any python object representing the matplotlib series handles = [] needs_colorbar = false discrete_colorbar_values = nothing # pass in an integer value as an arg, but a levels list as a keyword arg levels = d[:levels] levelargs = if isscalar(levels) (levels) elseif isvector(levels) extrakw[:levels] = levels () else error("Only numbers and vectors are supported with levels keyword") end # for each plotting command, optionally build and add a series handle to the list # line plot if st in (:path, :line, :scatter, :path3d, :scatter3d, :steppre, :steppost) if d[:linewidth] > 0 handle = ax[:plot](xyargs...; label = d[:label], zorder = plt.n, color = pylinecolor(d), linewidth = d[:linewidth], linestyle = getPyPlotLineStyle(st, d[:linestyle]), solid_capstyle = "round", # dash_capstyle = "round", drawstyle = getPyPlotStepStyle(st) )[1] push!(handles, handle) a = d[:arrow] if a != nothing && !is3d(d) # TODO: handle 3d later if typeof(a) != Arrow warn("Unexpected type for arrow: $(typeof(a))") else arrowprops = KW( :arrowstyle => "simple,head_length=$(a.headlength),head_width=$(a.headwidth)", :shrinkA => 0, :shrinkB => 0, :edgecolor => pylinecolor(d), :facecolor => pylinecolor(d), :linewidth => d[:linewidth], :linestyle => getPyPlotLineStyle(st, d[:linestyle]), ) add_arrows(x, y) do xyprev, xy ax[:annotate]("", xytext = (0.001xyprev[1] + 0.999xy[1], 0.001xyprev[2] + 0.999xy[2]), xy = xy, arrowprops = arrowprops ) end end end end end if st == :bar extrakw[isvertical(d) ? :width : :height] = 0.9 handle = ax[isvertical(d) ? :bar : :barh](x, y; label = d[:label], zorder = plt.n, color = pyfillcolor(d), edgecolor = pylinecolor(d), linewidth = d[:linewidth], align = "center", extrakw... )[1] push!(handles, handle) end if st == :sticks extrakw[isvertical(d) ? :width : :height] = 0.0 handle = ax[isvertical(d) ? :bar : :barh](x, y; label = d[:label], zorder = plt.n, color = pylinecolor(d), edgecolor = pylinecolor(d), linewidth = d[:linewidth], align = "center", extrakw... )[1] push!(handles, handle) end # add markers? if d[:markershape] != :none && st in (:path, :line, :scatter, :path3d, :scatter3d, :steppre, :steppost, :bar, :sticks) extrakw = KW() if d[:marker_z] == nothing extrakw[:c] = color_fix(pymarkercolor(d), x) else extrakw[:c] = convert(Vector{Float64}, d[:marker_z]) extrakw[:cmap] = pymarkercolormap(d) needs_colorbar = true end xyargs = if st in (:bar, :sticks) && !isvertical(d) (y, x) else xyargs end handle = ax[:scatter](xyargs...; label = d[:label], zorder = plt.n + 0.5, marker = getPyPlotMarker(d[:markershape]), s = d[:markersize] .^ 2, edgecolors = pymarkerstrokecolor(d), linewidths = d[:markerstrokewidth], extrakw... ) push!(handles, handle) end if st == :hist handle = ax[:hist](y; label = d[:label], zorder = plt.n, color = pyfillcolor(d), edgecolor = pylinecolor(d), linewidth = d[:linewidth], bins = d[:bins], normed = d[:normalize], weights = d[:weights], orientation = (isvertical(d) ? "vertical" : "horizontal"), histtype = (d[:bar_position] == :stack ? "barstacked" : "bar") )[1] push!(handles, handle) end if st == :hist2d handle = ax[:hist2d](x, y; label = d[:label], zorder = plt.n, bins = d[:bins], normed = d[:normalize], weights = d[:weights], cmap = pyfillcolormap(d) # applies to the pcolorfast object )[4] push!(handles, handle) needs_colorbar = true end if st == :hexbin handle = ax[:hexbin](x, y; label = d[:label], zorder = plt.n, gridsize = d[:bins], linewidths = d[:linewidth], edgecolors = pylinecolor(d), cmap = pyfillcolormap(d) # applies to the pcolorfast object ) push!(handles, handle) needs_colorbar = true end if st in (:hline,:vline) for yi in d[:y] func = ax[st == :hline ? :axhline : :axvline] handle = func(yi; linewidth=d[:linewidth], color=pylinecolor(d), linestyle=getPyPlotLineStyle(st, d[:linestyle]) ) push!(handles, handle) end end if st in (:contour, :contour3d) # z = z.surf' z = transpose_z(d, z.surf) needs_colorbar = true if st == :contour3d extrakw[:extend3d] = true end # contour lines handle = ax[:contour](x, y, z, levelargs...; label = d[:label], zorder = plt.n, linewidths = d[:linewidth], linestyles = getPyPlotLineStyle(st, d[:linestyle]), cmap = pylinecolormap(d), extrakw... ) push!(handles, handle) # contour fills # if st == :contour handle = ax[:contourf](x, y, z, levelargs...; label = d[:label], zorder = plt.n + 0.5, cmap = pyfillcolormap(d), extrakw... ) push!(handles, handle) # end end if st in (:surface, :wireframe) if typeof(z) <: AbstractMatrix || typeof(z) <: Surface x, y, z = map(Array, (x,y,z)) if !ismatrix(x) || !ismatrix(y) x = repmat(x', length(y), 1) y = repmat(y, 1, length(d[:x])) end # z = z' z = transpose_z(d, z) if st == :surface if d[:marker_z] != nothing extrakw[:facecolors] = getPyPlotCustomShading(d[:fillcolor], d[:marker_z], d[:fillalpha]) extrakw[:shade] = false else extrakw[:cmap] = pyfillcolormap(d) needs_colorbar = true end end handle = ax[st == :surface ? :plot_surface : :plot_wireframe](x, y, z; label = d[:label], zorder = plt.n, rstride = 1, cstride = 1, linewidth = d[:linewidth], edgecolor = pylinecolor(d), extrakw... ) push!(handles, handle) # contours on the axis planes if d[:contours] for (zdir,mat) in (("x",x), ("y",y), ("z",z)) offset = (zdir == "y" ? maximum : minimum)(mat) handle = ax[:contourf](x, y, z, levelargs...; zdir = zdir, cmap = pyfillcolormap(d), offset = (zdir == "y" ? maximum : minimum)(mat) # where to draw the contour plane ) push!(handles, handle) needs_colorbar = true end end # no colorbar if we are creating a surface LightSource if haskey(extrakw, :facecolors) needs_colorbar = false end elseif typeof(z) <: AbstractVector # tri-surface plot (http://matplotlib.org/mpl_toolkits/mplot3d/tutorial.html#tri-surface-plots) handle = ax[:plot_trisurf](x, y, z; label = d[:label], zorder = plt.n, cmap = pyfillcolormap(d), linewidth = d[:linewidth], edgecolor = pylinecolor(d) ) push!(handles, handle) needs_colorbar = true else error("Unsupported z type $(typeof(z)) for seriestype=$st") end end if st == :image img = Array(transpose_z(d, z.surf)) z = if eltype(img) <: Colors.AbstractGray float(img) elseif eltype(img) <: Colorant map(c -> Float64[red(c),green(c),blue(c)], img) else z # hopefully it's in a data format that will "just work" with imshow end handle = ax[:imshow](z; zorder = plt.n ) push!(handles, handle) end if st == :heatmap x, y, z = heatmap_edges(x), heatmap_edges(y), transpose_z(d, z.surf) if !(eltype(z) <: Number) z, discrete_colorbar_values = indices_and_unique_values(z) end handle = ax[:pcolormesh](x, y, z; label = d[:label], zorder = plt.n, cmap = pyfillcolormap(d), edgecolors = (d[:linewidth] > 0 ? pylinecolor(d) : "face") ) push!(handles, handle) needs_colorbar = true end if st == :shape path = buildPyPlotPath(x, y) patches = pypatches.pymember("PathPatch")(path; label = d[:label], zorder = plt.n, edgecolor = pymarkerstrokecolor(d), facecolor = pymarkercolor(d), linewidth = d[:markerstrokewidth], fill = true ) handle = ax[:add_patch](patches) push!(handles, handle) end if st == :pie handle = ax[:pie](y; # label = d[:label], # colors = # a vector of colors? labels = x ) push!(handles, handle) end d[:serieshandle] = handles # smoothing handleSmooth(plt, ax, d, d[:smooth]) # add the colorbar legend if needs_colorbar && plt.plotargs[:colorbar] != :none # cbar = PyPlot.colorbar(handles[end], ax=ax) # do we need a discrete colorbar? if discrete_colorbar_values == nothing PyPlot.colorbar(handles[end], ax=ax) else # add_pyfixedformatter(cbar, discrete_colorbar_values) locator, formatter = get_locator_and_formatter(discrete_colorbar_values) vals = 1:length(discrete_colorbar_values) PyPlot.colorbar(handles[end], ax = ax, ticks = locator, format = formatter, boundaries = vcat(0, vals + 0.5), values = vals ) end end # this sets the bg color inside the grid ax[:set_axis_bgcolor](getPyPlotColor(plt.plotargs[:background_color_inside])) # handle area filling fillrange = d[:fillrange] if fillrange != nothing && st != :contour f, dim1, dim2 = if isvertical(d) :fill_between, x, y else :fill_betweenx, y, x end args = if typeof(fillrange) <: Union{Real, AVec} dim1, fillrange, dim2 else dim1, fillrange... end handle = ax[f](args...; zorder = plt.n, facecolor = pyfillcolor(d), linewidths = 0 ) push!(handles, handle) end # push!(plt.seriesargs, d) # plt end # ----------------------------------------------------------------- # # given a dimension (:x, :y, or :z), loop over the seriesargs KWs to find the min/max of the underlying data # function minmaxseries(series_list, dimension, axis) # lo, hi = Inf, -Inf # for series in series_list # series.d[:axis] == axis || continue # v = series.d[dimension] # if length(v) > 0 # vlo, vhi = extrema(v) # lo = min(lo, vlo) # hi = max(hi, vhi) # end # end # if lo == hi # hi = if lo == 0 # 1e-6 # else # hi + min(abs(1e-2hi), 1e-6) # end # end # lo, hi # end # # # TODO: this needs to handle one-sided fixed limits # # TODO: this should be handled within the Axis type # function set_lims!(plt::Plot{PyPlotBackend}, axis::Symbol) # ax = getAxis(plt, axis) # pargs = plt.plotargs # if pargs[:xlims] == :auto # ax[pargs[:polar] ? :set_tlim : :set_xlim](minmaxseries(plt.series_list, :x, axis)...) # end # if pargs[:ylims] == :auto # ax[pargs[:polar] ? :set_rlim : :set_ylim](minmaxseries(plt.series_list, :y, axis)...) # end # if pargs[:zlims] == :auto && haskey(ax, :set_zlim) # ax[:set_zlim](minmaxseries(plt.series_list, :z, axis)...) # end # end # -------------------------------------------------------------------------- # TODO: d[:serieshandle] should really be a list of handles... then we should set # the x/y data for each handle (for example, plot and scatter) function setxy!{X,Y}(plt::Plot{PyPlotBackend}, xy::Tuple{X,Y}, i::Integer) d = plt.series_list[i].d d[:x], d[:y] = xy for handle in d[:serieshandle] try handle[:set_data](xy...) catch handle[:set_offsets](hcat(xy...)) end end # set_lims!(plt, plt.series_list[i]) plt end function setxyz!{X,Y,Z}(plt::Plot{PyPlotBackend}, xyz::Tuple{X,Y,Z}, i::Integer) d = plt.series_list[i].d d[:x], d[:y], d[:z] = xyz for handle in d[:serieshandle] handle[:set_data](d[:x], d[:y]) handle[:set_3d_properties](d[:z]) end # set_lims!(plt, plt.series_list[i]) plt end # -------------------------------------------------------------------------- function addPyPlotLims(ax, lims, letter) lims == :auto && return ltype = limsType(lims) if ltype == :limits setf = ax[symbol("set_", letter, "lim")] l1, l2 = lims if isfinite(l1) letter == "x" ? setf(left = l1) : setf(bottom = l1) end if isfinite(l2) letter == "x" ? setf(right = l2) : setf(top = l2) end else error("Invalid input for $letter: ", lims) end end function addPyPlotTicks(ax, ticks, letter) ticks == :auto && return if ticks == :none || ticks == nothing ticks = zeros(0) end ttype = ticksType(ticks) tickfunc = symbol("set_", letter, "ticks") labfunc = symbol("set_", letter, "ticklabels") if ttype == :ticks ax[tickfunc](ticks) elseif ttype == :ticks_and_labels ax[tickfunc](ticks[1]) ax[labfunc](ticks[2]) else error("Invalid input for $(isx ? "xticks" : "yticks"): ", ticks) end end function applyPyPlotScale(ax, scaleType::Symbol, letter) func = ax[symbol("set_", letter, "scale")] scaleType == :identity && return func("linear") scaleType == :ln && 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 updateAxisColors(ax, d::KW) guidecolor = getPyPlotColor(d[:foreground_color_guide]) for (loc, spine) in ax[:spines] spine[:set_color](getPyPlotColor(d[:foreground_color_border])) end for letter in ("x", "y", "z") axis = axis_symbol(letter, "axis") if haskey(ax, axis) ax[:tick_params](axis=letter, which="both", colors=getPyPlotColor(d[:foreground_color_axis]), labelcolor=getPyPlotColor(d[:foreground_color_text])) ax[axis][:label][:set_color](guidecolor) end end ax[:title][:set_color](guidecolor) end # function usingRightAxis(plt::Plot{PyPlotBackend}) # any(args -> args[:axis] in (:right,:auto), plt.seriesargs) # end # -------------------------------------------------------------------------- function _update_plot(plt::Plot{PyPlotBackend}, d::KW) # @show d # figorax = plt.o # ax = getLeftAxis(figorax) for sp in plt.subplots ax = getAxis(sp) # ticksz = get(d, :tickfont, plt.plotargs[:tickfont]).pointsize guidesz = get(d, :guidefont, plt.plotargs[:guidefont]).pointsize # title haskey(d, :title) && ax[:set_title](d[:title]) ax[:title][:set_fontsize](guidesz) # axes = [ax] # # handle right y axis # axes = [getLeftAxis(figorax)] # if usingRightAxis(plt) # push!(axes, getRightAxis(figorax)) # if get(d, :yrightlabel, "") != "" # rightax = getRightAxis(figorax) # rightax[:set_ylabel](d[:yrightlabel]) # end # end for letter in ("x", "y", "z") axissym = symbol(letter*"axis") axis = plt.plotargs[axissym] # @show axis haskey(ax, axissym) || continue applyPyPlotScale(ax, axis[:scale], letter) addPyPlotLims(ax, axis[:lims], letter) addPyPlotTicks(ax, get_ticks(axis), letter) ax[symbol("set_", letter, "label")](axis[:label]) if get(axis.d, :flip, false) ax[symbol("invert_", letter, "axis")]() end # for tmpax in axes tmpax = ax tmpax[axissym][:label][:set_fontsize](axis[:guidefont].pointsize) for lab in tmpax[symbol("get_", letter, "ticklabels")]() lab[:set_fontsize](axis[:tickfont].pointsize) lab[:set_rotation](axis[:rotation]) end if get(d, :grid, false) fgcolor = getPyPlotColor(plt.plotargs[:foreground_color_grid]) tmpax[axissym][:grid](true, color = fgcolor) tmpax[:set_axisbelow](true) end # end # @show "" end # # handle each axis in turn # for letter in ("x", "y", "z") # axis, scale, lims, ticks, flip, lab, rotation = # axis_symbols(letter, "axis", "scale", "lims", "ticks", "flip", "label", "rotation") # haskey(ax, axis) || continue # haskey(d, scale) && applyPyPlotScale(ax, d[scale], letter) # haskey(d, lims) && addPyPlotLims(ax, d[lims], letter) # haskey(d, ticks) && addPyPlotTicks(ax, d[ticks], letter) # haskey(d, lab) && ax[symbol("set_", letter, "label")](d[lab]) # if get(d, flip, false) # ax[symbol("invert_", letter, "axis")]() # end # for tmpax in axes # tmpax[axis][:label][:set_fontsize](guidesz) # for lab in tmpax[symbol("get_", letter, "ticklabels")]() # lab[:set_fontsize](ticksz) # haskey(d, rotation) && lab[:set_rotation](d[rotation]) # end # if get(d, :grid, false) # fgcolor = getPyPlotColor(plt.plotargs[:foreground_color_grid]) # tmpax[axis][:grid](true, color = fgcolor) # tmpax[:set_axisbelow](true) # end # end # end # do we want to change the aspect ratio? aratio = get(d, :aspect_ratio, :none) if aratio != :none ax[:set_aspect](isa(aratio, Symbol) ? string(aratio) : aratio, anchor = "C") end end end # ----------------------------------------------------------------- # TODO: these should apply to a Subplot, NOT a Plot # function createPyPlotAnnotationObject(plt::Plot{PyPlotBackend}, x, y, val::@compat(AbstractString)) # ax = getLeftAxis(plt) # ax[:annotate](val, xy = (x,y)) # end # # # function createPyPlotAnnotationObject(plt::Plot{PyPlotBackend}, x, y, val::PlotText) # ax = getLeftAxis(plt) # ax[:annotate](val.str, # xy = (x,y), # family = val.font.family, # color = getPyPlotColor(val.font.color), # horizontalalignment = val.font.halign == :hcenter ? "center" : string(val.font.halign), # verticalalignment = val.font.valign == :vcenter ? "center" : string(val.font.valign), # rotation = val.font.rotation * 180 / π, # size = val.font.pointsize # ) # end # # function _add_annotations{X,Y,V}(plt::Plot{PyPlotBackend}, anns::AVec{@compat(Tuple{X,Y,V})}) # for ann in anns # createPyPlotAnnotationObject(plt, ann...) # end # end # ----------------------------------------------------------------- # function _create_subplot(subplt::Subplot{PyPlotBackend}, isbefore::Bool) # l = subplt.layout # plotargs = getplotargs(subplt, 1) # fig = pyplot_figure(plotargs) # # nr = nrows(l) # for (i,(r,c)) in enumerate(l) # # add the plot to the figure # nc = ncols(l, r) # fakeidx = (r-1) * nc + c # ax = fig[:add_subplot](nr, nc, fakeidx) # # subplt.plts[i].o = PyPlotAxisWrapper(ax, nothing, fig, []) # pyplot_3d_setup!(subplt.plts[i].o, plotargs) # end # # subplt.o = PyPlotAxisWrapper(nothing, nothing, fig, []) # pyplot_3d_setup!(subplt.o, plotargs) # true # end # # # this will be called internally, when creating a subplot from existing plots # # NOTE: if I ever need to "Rebuild a "ubplot from individual Plot's"... this is what I should use! # function subplot(plts::AVec{Plot{PyPlotBackend}}, layout::AbstractLayout, d::KW) # validateSubplotSupported() # # p = length(layout) # n = sum([plt.n for plt in plts]) # # pkg = PyPlotBackend() # newplts = Plot{PyPlotBackend}[begin # plt.plotargs[:subplot] = true # _create_plot(pkg, plt.plotargs) # end for plt in plts] # # subplt = Subplot(nothing, newplts, PyPlotBackend(), p, n, layout, d, true, false, false, (r,c) -> (nothing,nothing)) # # _preprocess_subplot(subplt, d) # _create_subplot(subplt, true) # # for (i,plt) in enumerate(plts) # for seriesargs in plt.seriesargs # _add_series_subplot(newplts[i], seriesargs) # end # end # # _postprocess_subplot(subplt, d) # # subplt # end function _remove_axis(plt::Plot{PyPlotBackend}, isx::Bool) if isx plot!(plt, xticks=zeros(0), xlabel="") else plot!(plt, yticks=zeros(0), ylabel="") end end function _expand_limits(lims, plt::Plot{PyPlotBackend}, isx::Bool) pltlims = plt.o.ax[isx ? :get_xbound : :get_ybound]() _expand_limits(lims, pltlims) end # ----------------------------------------------------------------- const _pyplot_legend_pos = KW( :right => "right", :left => "center left", :top => "upper center", :bottom => "lower center", :bottomleft => "lower left", :bottomright => "lower right", :topright => "upper right", :topleft => "upper left" ) # function addPyPlotLegend(plt::Plot) function addPyPlotLegend(plt::Plot, ax) leg = plt.plotargs[:legend] if leg != :none # gotta do this to ensure both axes are included labels = [] handles = [] for series in plt.series_list if series.d[:label] != "" && !(series.d[:seriestype] in ( :hist,:density,:hexbin,:hist2d,:hline,:vline, :contour,:contour3d,:surface,:wireframe, :heatmap,:path3d,:scatter3d, :pie, :image)) push!(handles, series.d[:serieshandle][1]) push!(labels, series.d[:label]) end end # args = filter(x -> !(x.d[:seriestype] in ( # :hist,:density,:hexbin,:hist2d,:hline,:vline, # :contour,:contour3d,:surface,:wireframe, # :heatmap,:path3d,:scatter3d, :pie, :image # )), plt.series_list) # args = filter(x -> x[:label] != "", args) # if length(args) > 0 if !isempty(handles) leg = ax[:legend](handles, #[d[:serieshandle][1] for d in args], labels, #[d[:label] for d in args], loc = get(_pyplot_legend_pos, leg, "best"), scatterpoints = 1, fontsize = plt.plotargs[:legendfont].pointsize # framealpha = 0.6 ) leg[:set_zorder](1000) fgcolor = getPyPlotColor(plt.plotargs[:foreground_color_legend]) for txt in leg[:get_texts]() PyPlot.plt[:setp](txt, color = fgcolor) end # set some legend properties frame = leg[:get_frame]() frame[:set_facecolor](getPyPlotColor(plt.plotargs[:background_color_legend])) frame[:set_edgecolor](fgcolor) end end end # ----------------------------------------------------------------- function finalizePlot(plt::Plot{PyPlotBackend}) for sp in plt.subplots # ax = getLeftAxis(plt) ax = getAxis(sp) addPyPlotLegend(plt, ax) updateAxisColors(ax, plt.plotargs) end drawfig(plt.o) update_bboxes!(plt.layout) update_position!(plt.layout) PyPlot.draw() end # function finalizePlot(subplt::Subplot{PyPlotBackend}) # fig = subplt.o.fig # for (i,plt) in enumerate(subplt.plts) # ax = getLeftAxis(plt) # addPyPlotLegend(plt, ax) # updateAxisColors(ax, plt.plotargs) # end # # fig[:tight_layout]() # PyPlot.draw() # end # ----------------------------------------------------------------- # NOTE: to bring up a GUI window in IJulia, need some extra steps function Base.display(::PlotsDisplay, plt::AbstractPlot{PyPlotBackend}) finalizePlot(plt) if isa(Base.Multimedia.displays[end], Base.REPL.REPLDisplay) display(getfig(plt.o)) else # # PyPlot.ion() # PyPlot.figure(getfig(plt.o).o[:number]) # PyPlot.draw_if_interactive() # # PyPlot.ioff() end # PyPlot.plt[:show](block=false) getfig(plt.o)[:show]() end const _pyplot_mimeformats = Dict( "application/eps" => "eps", "image/eps" => "eps", "application/pdf" => "pdf", "image/png" => "png", "application/postscript" => "ps", "image/svg+xml" => "svg" ) for (mime, fmt) in _pyplot_mimeformats @eval function Base.writemime(io::IO, ::MIME{symbol($mime)}, plt::AbstractPlot{PyPlotBackend}) finalizePlot(plt) fig = getfig(plt.o) fig.o["canvas"][:print_figure]( io, format=$fmt, # bbox_inches = "tight", # figsize = map(px2inch, plt.plotargs[:size]), facecolor = fig.o["get_facecolor"](), edgecolor = "none", dpi = DPI ) end end