Plots.jl/src/backends/pyplot.jl

1117 lines
34 KiB
Julia
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# 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,
:linetype,
: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,
:y, :ylabel, :ylims, :yticks, :yscale, :yflip,
:axis, :yrightlabel,
:z, :zlabel, :zlims, :zticks, :zscale, :zflip,
:z,
:tickfont, :guidefont, :legendfont,
:grid, :legend, :colorbar,
:marker_z, :levels,
:xerror, :yerror,
:ribbon, :quiver,
:orientation,
:overwrite_figure,
:polar,
:normalize, :weights, :contours, :aspect_ratio
]
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,
: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
# --------------------------------------------------------------------------------------
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"))
end
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(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
function getPyPlotMarker(marker::Shape)
n = length(marker.vertices)
mat = zeros(n+1,2)
for (i,vert) in enumerate(marker.vertices)
mat[i,1] = vert[1]
mat[i,2] = vert[2]
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, 2)
codes = zeros(UInt8, n)
lastnan = true
for i=1:n
mat[i,1] = x[i]
mat[i,2] = y[i]
nan = !isfinite(x[i]) || !isfinite(y[i])
codes[i] = if nan
_path_CLOSEPOLY
else
lastnan ? _path_MOVETO : _path_LINETO
end
lastnan = nan
end
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(linetype::Symbol)
linetype == :steppost && return "steps-post"
linetype == :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
# ---------------------------------------------------------------------------
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(lt -> lt in _3dTypes, get(d, :linetype, :none))
push!(wrap.kwargs, (:projection, "3d"))
end
end
# ---------------------------------------------------------------------------
function _create_plot(pkg::PyPlotBackend, d::KW)
# create the figure
# standalone plots will create a figure, but not if part of a subplot (do it later)
if haskey(d, :subplot)
wrap = nothing
else
wrap = PyPlotAxisWrapper(nothing, nothing, pyplot_figure(d), [])
pyplot_3d_setup!(wrap, d)
if get(d, :polar, false)
push!(wrap.kwargs, (:polar, true))
end
end
plt = Plot(wrap, pkg, 0, d, 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)
lt = d[:linetype]
if !(lt in supportedTypes(pkg))
error("linetype $(lt) is unsupported in PyPlot. Choose from: $(supportedTypes(pkg))")
end
# 3D plots have a different underlying Axes object in PyPlot
if lt 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])
x, y, z = d[:x], d[:y], d[:z]
xyargs = (lt 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 lt 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(lt, d[:linestyle]),
drawstyle = getPyPlotStepStyle(lt)
)[1]
push!(handles, handle)
end
end
if lt == :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 lt == :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 && lt 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 lt 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 lt == :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 lt == :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 lt == :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 lt in (:hline,:vline)
for yi in d[:y]
func = ax[lt == :hline ? :axhline : :axvline]
handle = func(yi;
linewidth=d[:linewidth],
color=pylinecolor(d),
linestyle=getPyPlotLineStyle(lt, d[:linestyle])
)
push!(handles, handle)
end
end
if lt in (:contour, :contour3d)
z = z.surf'
needs_colorbar = true
if lt == :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(lt, d[:linestyle]),
cmap = pylinecolormap(d),
extrakw...
)
push!(handles, handle)
# contour fills
# if lt == :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 lt 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]))
z = z'
end
if lt == :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[lt == :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 linetype=$lt")
end
end
if lt == :heatmap
x, y, z = heatmap_edges(x), heatmap_edges(y), 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 lt == :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 lt == :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 && lt != :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(ds, dimension, axis)
lo, hi = Inf, -Inf
for d in ds
d[:axis] == axis || continue
v = 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
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.seriesargs, :x, axis)...)
end
if pargs[:ylims] == :auto
ax[pargs[:polar] ? :set_rlim : :set_ylim](minmaxseries(plt.seriesargs, :y, axis)...)
end
if pargs[:zlims] == :auto && haskey(ax, :set_zlim)
ax[:set_zlim](minmaxseries(plt.seriesargs, :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.seriesargs[i]
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, d[:axis])
plt
end
function setxyz!{X,Y,Z}(plt::Plot{PyPlotBackend}, xyz::Tuple{X,Y,Z}, i::Integer)
d = plt.seriesargs[i]
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, d[:axis])
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)
figorax = plt.o
ax = getLeftAxis(figorax)
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)
# 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
# handle each axis in turn
for letter in ("x", "y", "z")
axis, scale, lims, ticks, flip, lab = axis_symbols(letter, "axis", "scale", "lims", "ticks", "flip", "label")
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)
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
# -----------------------------------------------------------------
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::SubplotLayout, 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"
)
# 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
args = filter(x -> !(x[:linetype] in (
:hist,:density,:hexbin,:hist2d,:hline,:vline,
:contour,:contour3d,:surface,:wireframe,
:heatmap,:path3d,:scatter3d, :pie
)), plt.seriesargs)
args = filter(x -> x[:label] != "", args)
if length(args) > 0
leg = ax[:legend]([d[:serieshandle][1] for d in args],
[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})
ax = getLeftAxis(plt)
addPyPlotLegend(plt, ax)
updateAxisColors(ax, plt.plotargs)
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