diff --git a/src/args.jl b/src/args.jl index e9ae5c0a..68af6ebd 100644 --- a/src/args.jl +++ b/src/args.jl @@ -51,6 +51,7 @@ like_line(seriestype::Symbol) = seriestype in (:line, :path, :steppre, :ste like_surface(seriestype::Symbol) = seriestype in (:contour, :contour3d, :heatmap, :surface, :wireframe, :image) is3d(seriestype::Symbol) = seriestype in _3dTypes +is3d(series::Series) = is3d(series.d) is3d(d::KW) = trueOrAllTrue(is3d, d[:seriestype]) const _allStyles = [:auto, :solid, :dash, :dot, :dashdot, :dashdotdot] diff --git a/src/backends.jl b/src/backends.jl index acc87b80..e8ceec80 100644 --- a/src/backends.jl +++ b/src/backends.jl @@ -53,10 +53,10 @@ _update_plot(pkg::AbstractBackend, plt::Plot, d::KW) = error("_update_plot($pkg # don't do anything as a default _create_backend_figure(plt::Plot) = nothing -_before_add_series(plt::Plot) = nothing -_add_series(plt::Plot) = nothing -_add_annotations{X,Y,V}(plt::Plot, anns::AVec{Tuple{X,Y,V}}) = nothing -_update_plot_pos_size(plt::AbstractPlot, d::KW) = nothing +_before_update(plt::Plot) = nothing +_series_added(plt::Plot) = nothing +# _add_annotations{X,Y,V}(plt::Plot, anns::AVec{Tuple{X,Y,V}}) = nothing +# _update_plot_pos_size(plt::AbstractPlot, d::KW) = nothing # --------------------------------------------------------- diff --git a/src/backends/bokeh.jl b/src/backends/bokeh.jl index 2844443d..9ff99ad6 100644 --- a/src/backends/bokeh.jl +++ b/src/backends/bokeh.jl @@ -79,13 +79,6 @@ function _initialize_backend(::BokehBackend; kw...) end end -# make255(x) = round(Int, 255 * x) - -# function bokehcolor(c::Colorant) -# @sprintf("rgba(%d, %d, %d, %1.3f)", [make255(f(c)) for f in [red,green,blue]]..., alpha(c)) -# end -# bokehcolor(cs::ColorScheme) = bokehcolor(getColor(cs)) - const _glyphtypes = KW( :ellipse => :Circle, @@ -154,8 +147,8 @@ function _create_backend_figure(plt::Plot{BokehBackend}) end -# function _add_series(::BokehBackend, plt::Plot, d::KW) -function _add_series(plt::Plot{BokehBackend}, series::Series) +# function _series_added(::BokehBackend, plt::Plot, d::KW) +function _series_added(plt::Plot{BokehBackend}, series::Series) bdata = Dict{Symbol, Vector}(:x => collect(series.d[:x]), :y => collect(series.d[:y])) glyph = Bokeh.Bokehjs.Glyph( @@ -180,9 +173,6 @@ end function _update_plot(plt::Plot{BokehBackend}, d::KW) end -function _update_plot_pos_size(plt::AbstractPlot{BokehBackend}, d::KW) -end - # ---------------------------------------------------------------- # accessors for x/y data @@ -199,29 +189,9 @@ end # end -# ---------------------------------------------------------------- - -function _add_annotations{X,Y,V}(plt::Plot{BokehBackend}, anns::AVec{@compat(Tuple{X,Y,V})}) - for ann in anns - # TODO: add the annotation to the plot - end -end # ---------------------------------------------------------------- -# function _create_subplot(subplt::Subplot{BokehBackend}, isbefore::Bool) -# # TODO: build the underlying Subplot object. this is where you might layout the panes within a GUI window, for example -# -# end - - -function _expand_limits(lims, plt::Plot{BokehBackend}, isx::Bool) - # TODO: call expand limits for each plot data -end - -function _remove_axis(plt::Plot{BokehBackend}, isx::Bool) - # TODO: if plot is inner subplot, might need to remove ticks or axis labels -end # ---------------------------------------------------------------- diff --git a/src/backends/gadfly.jl b/src/backends/gadfly.jl index dc9defe2..4c1790ed 100644 --- a/src/backends/gadfly.jl +++ b/src/backends/gadfly.jl @@ -580,8 +580,8 @@ end # plot one data series -# function _add_series(::GadflyBackend, plt::Plot, d::KW) -function _add_series(plt::Plot{GadflyBackend}, series::Series) +# function _series_added(::GadflyBackend, plt::Plot, d::KW) +function _series_added(plt::Plot{GadflyBackend}, series::Series) # first clear out the temporary layer gplt = getGadflyContext(plt) if gplt.layers[1].geom.tag == :remove diff --git a/src/backends/glvisualize.jl b/src/backends/glvisualize.jl index 0274209b..e654e373 100644 --- a/src/backends/glvisualize.jl +++ b/src/backends/glvisualize.jl @@ -95,8 +95,8 @@ function _create_backend_figure(plt::Plot{GLVisualizeBackend}) end -# function _add_series(::GLVisualizeBackend, plt::Plot, d::KW) -function _add_series(plt::Plot{GLVisualizeBackend}, series::Series) +# function _series_added(::GLVisualizeBackend, plt::Plot, d::KW) +function _series_added(plt::Plot{GLVisualizeBackend}, series::Series) # TODO: add one series to the underlying package # push!(plt.seriesargs, d) # TODO: this should be moved to the display method? diff --git a/src/backends/gr.jl b/src/backends/gr.jl index 32122271..263cd76e 100644 --- a/src/backends/gr.jl +++ b/src/backends/gr.jl @@ -873,7 +873,7 @@ end # Plot(nothing, pkg, 0, d, KW[]) # end -# function _add_series(::GRBackend, plt::Plot, d::KW) +# function _series_added(::GRBackend, plt::Plot, d::KW) # push!(plt.seriesargs, d) # plt # end diff --git a/src/backends/immerse.jl b/src/backends/immerse.jl index a974448b..a1bab77b 100644 --- a/src/backends/immerse.jl +++ b/src/backends/immerse.jl @@ -42,13 +42,13 @@ end # # plot one data series -# function _add_series(::ImmerseBackend, plt::Plot, d::KW) +# function _series_added(::ImmerseBackend, plt::Plot, d::KW) # addGadflySeries!(plt, d) # push!(plt.seriesargs, d) # plt # end -function _add_series(plt::Plot{ImmerseBackend}, series::Series) +function _series_added(plt::Plot{ImmerseBackend}, series::Series) addGadflySeries!(plt, series.d) end diff --git a/src/backends/pgfplots.jl b/src/backends/pgfplots.jl index 315f51bd..96a25299 100644 --- a/src/backends/pgfplots.jl +++ b/src/backends/pgfplots.jl @@ -238,7 +238,7 @@ end # end -# function _add_series(::PGFPlotsBackend, plt::Plot, d::KW) +# function _series_added(::PGFPlotsBackend, plt::Plot, d::KW) # # TODO: add one series to the underlying package # push!(plt.seriesargs, d) # plt diff --git a/src/backends/plotly.jl b/src/backends/plotly.jl index 807003a6..19cecc45 100644 --- a/src/backends/plotly.jl +++ b/src/backends/plotly.jl @@ -91,7 +91,7 @@ end # end -# function _add_series(::PlotlyBackend, plt::Plot, d::KW) +# function _series_added(::PlotlyBackend, plt::Plot, d::KW) # # TODO: add one series to the underlying package # push!(plt.seriesargs, d) # plt diff --git a/src/backends/plotlyjs.jl b/src/backends/plotlyjs.jl index 12d2ba3f..2e6b2d65 100644 --- a/src/backends/plotlyjs.jl +++ b/src/backends/plotlyjs.jl @@ -113,7 +113,7 @@ function _create_backend_figure(plt::Plot{PlotlyJSBackend}) end -function _add_series(plt::Plot{PlotlyJSBackend}, series::Series) +function _series_added(plt::Plot{PlotlyJSBackend}, series::Series) d = series.d syncplot = plt.o diff --git a/src/backends/pyplot.jl b/src/backends/pyplot.jl index 6308d5cc..8540f4e7 100644 --- a/src/backends/pyplot.jl +++ b/src/backends/pyplot.jl @@ -223,295 +223,7 @@ function add_pyfixedformatter(cbar, vals::AVec) 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 - -get_extents(obj) = obj[:get_window_extent]()[:get_points]() - -function py_bbox_fig(fig) - fl, fr, fb, ft = get_extents(fig) - BoundingBox(0px, 0px, (fr-fl)*px, (ft-fb)*px) -end -py_bbox_fig(plt::Plot) = py_bbox_fig(plt.o) - -# compute a bounding box (with origin top-left), however pyplot gives coords with origin bottom-left -function py_bbox(obj) - fl, fr, fb, ft = get_extents(obj[:get_figure]()) - # @show fl, fr, fb, ft - l, r, b, t = get_extents(obj) - # @show l, r, b, t, obj - BoundingBox(l*px, (ft-t)*px, (r-l)*px, (t-b)*px) -end - -# get the bounding box of the union of the objects -function py_bbox(v::AVec) - bbox_union = defaultbox - for obj in v - bbox_union = bbox_union + py_bbox(obj) - end - bbox_union -end - -function py_bbox_ticks(ax, letter) - # fig = ax[:get_figure]() - # @show fig - labels = ax[symbol("get_"*letter*"ticklabels")]() - py_bbox(labels) - # # @show labels - # # bboxes = [] - # bbox_union = defaultbox - # for lab in labels - # # @show lab,lab[:get_text]() - # bbox = py_bbox(lab) - # bbox_union = bbox_union + bbox - # # @show letter,bbox bbox_union - # # @show bbox_union - # end - # bbox_union -end - -function py_bbox_axislabel(ax, letter) - pyaxis_label = ax[symbol("get_"*letter*"axis")]()[:label] - py_bbox(pyaxis_label) -end - -# get a bounding box for the whole axis -function py_bbox_axis(ax, letter) - ticks = py_bbox_ticks(ax, letter) - labels = py_bbox_axislabel(ax, letter) - # letter == "x" && @show ticks labels ticks+labels - ticks + labels -end - -# get a bounding box for the title area -function py_bbox_title(ax) - bb = defaultbox - for s in (:title, :_left_title, :_right_title) - bb = bb + py_bbox(ax[s]) - end - bb -end - -# TODO: need to compute each of these by subtracting the plotarea position from -# the most extreme guide/axis in each direction. Can pass method (left,top,right,bottom) -# and aggregation (minimum or maximum) into a method to do this. - -min_padding_left(layout::Subplot{PyPlotBackend}) = compute_min_padding(layout, left, 1) -min_padding_top(layout::Subplot{PyPlotBackend}) = compute_min_padding(layout, top, 1) -min_padding_right(layout::Subplot{PyPlotBackend}) = compute_min_padding(layout, right, -1) -min_padding_bottom(layout::Subplot{PyPlotBackend}) = compute_min_padding(layout, bottom,-1) - -const _cbar_width = 5mm - -# loop over the guides and axes and compute how far they "stick out" from the plot area, -# so that we know the minimum padding we need to avoid cropping and overlapping text. -# `func` is one of (left,top,right,bottom), and we multiply by 1 or -1 depending on direction -function compute_min_padding(sp::Subplot{PyPlotBackend}, func::Function, mult::Number) - ax = sp.o - ax == nothing && return 0mm - plotbb = py_bbox(ax) - # @show func, mult plotbb - # @show func, py_bbox_axis(ax, "x") - # TODO: this should initialize to the margin from sp.attr - padding = 1mm - for bb in (py_bbox_axis(ax, "x"), - py_bbox_axis(ax, "y"), - py_bbox_title(ax)) - diff = func(plotbb) - func(bb) - # @show diff,bb - if ispositive(width(bb)) && ispositive(height(bb)) - padding = max(padding, mult * diff) - end - # @show padding - end - - if func == right && haskey(sp.attr, :cbar_ax) - # TODO: need bounding box of colorbar labels - bb = py_bbox(sp.attr[:cbar_handle][:ax][:get_yticklabels]()) - # bb = py_bbox(sp.attr[:cbar_ax][:get_ticklabels]()) - # bb = BoundingBox(0mm,0mm,15mm,15mm) - sp.attr[:cbar_width] = _cbar_width + width(bb) + 1mm - padding = padding + sp.attr[:cbar_width] - end - - # if func == top - # titlebbox = py_bbox_title(ax) - # padding = max(padding, height(titlebbox)) - # @show titlebbox height(titlebbox),padding - # end - - padding -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(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(0mm, left(plot_bb) - minimum(map(left, items))) -# bottompad = max(0mm, bottom(plot_bb) - minimum(map(bottom, items))) -# rightpad = max(0mm, maximum(map(right, items)) - right(plot_bb)) -# toppad = max(0mm, maximum(map(top, items)) - top(plot_bb)) -# crop(bbox(sp), BoundingBox(leftpad, bottompad, -# width(sp) - rightpad - leftpad, -# height(sp) - toppad - bottompad)) -# end - -# --------------------------------------------------------------------------- - -# TODO: add this (and _cbar_width) to layouts.jl? -function bbox_to_pcts(bb::BoundingBox, figw, figh, flipy = true) - mms = Float64[f(bb).value for f in (left,bottom,width,height)] - if flipy - mms[2] = figh.value - mms[2] # flip y when origin in bottom-left - end - mms ./ Float64[figw.value, figh.value, figw.value, figh.value] -end - - -function update_position!(sp::Subplot{PyPlotBackend}) - ax = sp.o - ax == nothing && return - figw, figh = size(py_bbox_fig(sp.plt)) - - # plot_bb = plotarea_bbox(sp) - # plot_bb = sp.plotarea - # @show sp.bbox plot_bb - # l = float(left(plot_bb) / px) / figw - # b = float(bottom(plot_bb) / px) / figh - # w = float(width(plot_bb) / px) / figw - # mms = Float64[f(plot_bb).value for f in (left, bottom, width, height)] - # - # mms[2] = figh.value - mms[2] - # # @show mms - # pcts = mms ./ Float64[figw.value, figh.value, figw.value, figh.value] - pcts = bbox_to_pcts(sp.plotarea, figw, figh) - # @show pcts - ax[:set_position](pcts) - - # set the cbar position if there is one - # @show sp.attr[:cbar_ax] - if haskey(sp.attr, :cbar_ax) - cbw = sp.attr[:cbar_width] - # cb_bbox = BoundingBox(figw - cbw, 0.1figh, cbw, figh - 0.2pct) - # this is the bounding box of just the colors of the colorbar (not labels) - # TODO: use buffers from sp.attr, or maybe the layouts? - cb_bbox = BoundingBox(right(sp.bbox)-cbw+1mm, top(sp.bbox)+2mm, _cbar_width-1mm, height(sp.bbox)-4mm) - pcts = bbox_to_pcts(cb_bbox, figw, figh) - # @show cbw cb_bbox pcts - sp.attr[:cbar_ax][:set_position](pcts) - end -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 - proj = sp.attr[:projection] - ax = fig[:add_axes]( - [0,0,1,1], - label = string(gensym()), - projection = (proj in (nothing,:none) ? nothing : string(proj)) - ) - - # @show proj, ax - # projection = - # ax = fig[:add_subplot](111, projection = _py_projections[sp.attr[:projection]]) - # 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) - - +# TODO: smoothing should be moved into the SliceIt method, should not touch backends function handleSmooth(plt::Plot{PyPlotBackend}, ax, d::KW, smooth::Bool) if smooth xs, ys = regressionXY(d[:x], d[:y]) @@ -526,72 +238,6 @@ handleSmooth(plt::Plot{PyPlotBackend}, ax, d::KW, smooth::Real) = handleSmooth(p # --------------------------------------------------------------------------- -# 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) @@ -624,28 +270,253 @@ 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) +# --------------------------------------------------------------------------- + +# TODO: these can probably be removed eventually... right now they're just keeping things working before cleanup + +getAxis(sp::Subplot) = sp.o + +function getAxis(plt::Plot{PyPlotBackend}, series::Series) + sp = get_subplot(plt, get(series.d, :subplot, 1)) + getAxis(sp) +end + +getfig(o) = o + +# --------------------------------------------------------------------------- +# 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]())) + +# get a vector [left, right, bottom, top] in PyPlot coords (origin is bottom-left!) +get_extents(obj) = obj[:get_window_extent]()[:get_points]() + +# bounding box of the figure +function py_bbox_fig(fig) + fl, fr, fb, ft = get_extents(fig) + BoundingBox(0px, 0px, (fr-fl)*px, (ft-fb)*px) +end +py_bbox_fig(plt::Plot) = py_bbox_fig(plt.o) + +# compute a bounding box (with origin top-left), however pyplot gives coords with origin bottom-left +function py_bbox(obj) + fl, fr, fb, ft = get_extents(obj[:get_figure]()) + l, r, b, t = get_extents(obj) + BoundingBox(l*px, (ft-t)*px, (r-l)*px, (t-b)*px) +end + +# get the bounding box of the union of the objects +function py_bbox(v::AVec) + bbox_union = defaultbox + for obj in v + bbox_union = bbox_union + py_bbox(obj) + end + bbox_union +end + +# bounding box: union of axis tick labels +function py_bbox_ticks(ax, letter) + labels = ax[symbol("get_"*letter*"ticklabels")]() + py_bbox(labels) +end + +# bounding box: axis guide +function py_bbox_axislabel(ax, letter) + pyaxis_label = ax[symbol("get_"*letter*"axis")]()[:label] + py_bbox(pyaxis_label) +end + +# bounding box: union of axis ticks and guide +function py_bbox_axis(ax, letter) + ticks = py_bbox_ticks(ax, letter) + labels = py_bbox_axislabel(ax, letter) + # letter == "x" && @show ticks labels ticks+labels + ticks + labels +end + +# bounding box: axis title +function py_bbox_title(ax) + bb = defaultbox + for s in (:title, :_left_title, :_right_title) + bb = bb + py_bbox(ax[s]) + end + bb +end + +# --------------------------------------------------------------------------- + +# # TODO: remove these after _min_padding is done +# +# # need to compute each of these by subtracting the plotarea position from +# # the most extreme guide/axis in each direction. Can pass method (left,top,right,bottom) +# # and aggregation (minimum or maximum) into a method to do this. +# +# min_padding_left(layout::Subplot{PyPlotBackend}) = compute_min_padding(layout, left, 1) +# min_padding_top(layout::Subplot{PyPlotBackend}) = compute_min_padding(layout, top, 1) +# min_padding_right(layout::Subplot{PyPlotBackend}) = compute_min_padding(layout, right, -1) +# min_padding_bottom(layout::Subplot{PyPlotBackend}) = compute_min_padding(layout, bottom,-1) +# +# +# # loop over the guides and axes and compute how far they "stick out" from the plot area, +# # so that we know the minimum padding we need to avoid cropping and overlapping text. +# # `func` is one of (left,top,right,bottom), and we multiply by 1 or -1 depending on direction +# function compute_min_padding(sp::Subplot{PyPlotBackend}, func::Function, mult::Number) +# ax = sp.o +# ax == nothing && return 0mm +# plotbb = py_bbox(ax) +# # @show func, mult plotbb +# # @show func, py_bbox_axis(ax, "x") +# # TODO: this should initialize to the margin from sp.attr +# padding = 1mm +# for bb in (py_bbox_axis(ax, "x"), +# py_bbox_axis(ax, "y"), +# py_bbox_title(ax)) +# diff = func(plotbb) - func(bb) +# # @show diff,bb +# if ispositive(width(bb)) && ispositive(height(bb)) +# padding = max(padding, mult * diff) +# end +# # @show padding +# end +# +# if func == right && haskey(sp.attr, :cbar_ax) +# # get width of colorbar labels and add to width of colorbar +# bb = py_bbox(sp.attr[:cbar_handle][:ax][:get_yticklabels]()) +# sp.attr[:cbar_width] = _cbar_width + width(bb) + 1mm +# padding = padding + sp.attr[:cbar_width] +# end +# +# # if func == top +# # titlebbox = py_bbox_title(ax) +# # padding = max(padding, height(titlebbox)) +# # @show titlebbox height(titlebbox),padding +# # end +# +# padding +# 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 + +# --------------------------------------------------------------------------- + +# Create the window/figure for this backend. +function _create_backend_figure(plt::Plot{PyPlotBackend}) + pyplot_figure(plt.plotargs) +end + +# Set up the subplot within the backend object. +function _initialize_subplot(plt::Plot{PyPlotBackend}, sp::Subplot{PyPlotBackend}) + fig = plt.o + proj = sp.attr[:projection] + proj = (proj in (nothing,:none) ? nothing : string(proj)) + + # 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()), + projection = proj + ) + sp.o = ax +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{PyPlotBackend}) + ax = sp.o + ax == nothing && return sp.minpad + plotbb = py_bbox(ax) + + # TODO: this should initialize to the margin from sp.attr + # figure out how much the axis components and title "stick out" from the plot area + leftpad = toppad = rightpad = bottompad = 1mm + for bb in (py_bbox_axis(ax, "x"), py_bbox_axis(ax, "y"), py_bbox_title(ax)) + if ispositive(width(bb)) && ispositive(height(bb)) + leftpad = max(leftpad, left(plotbb) - left(bb)) + toppad = max(toppad, top(plotbb) - top(bb)) + rightpad = max(rightpad, right(bb) - right(plotbb)) + bottompad = max(bottompad, bottom(bb) - bottom(plotbb)) + end + end + + # optionally add the width of colorbar labels and colorbar to rightpad + if haskey(sp.attr, :cbar_ax) + bb = py_bbox(sp.attr[:cbar_handle][:ax][:get_yticklabels]()) + sp.attr[:cbar_width] = _cbar_width + width(bb) + 1mm + rightpad = rightpad + sp.attr[:cbar_width] + end + + sp.minpad = (leftpad, toppad, rightpad, bottompad) +end + +# Use the bounding boxes (and methods left/top/right/bottom/width/height) `sp.bbox` and `sp.plotarea` to +# position the subplot in the backend. +function _update_position!(sp::Subplot{PyPlotBackend}) + ax = sp.o + ax == nothing && return + figw, figh = size(py_bbox_fig(sp.plt)) + pcts = bbox_to_pcts(sp.plotarea, figw, figh) + ax[:set_position](pcts) + + # set the cbar position if there is one + if haskey(sp.attr, :cbar_ax) + cbw = sp.attr[:cbar_width] + # this is the bounding box of just the colors of the colorbar (not labels) + cb_bbox = BoundingBox(right(sp.bbox)-cbw+1mm, top(sp.bbox)+2mm, _cbar_width-1mm, height(sp.bbox)-4mm) + pcts = bbox_to_pcts(cb_bbox, figw, figh) + sp.attr[:cbar_ax][:set_position](pcts) + end +end + +# --------------------------------------------------------------------------- + + +# function _series_added(pkg::PyPlotBackend, plt::Plot, d::KW) # TODO: change this to accept Subplot?? -function _add_series(plt::Plot{PyPlotBackend}, series::Series) +function _series_added(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 @@ -668,8 +539,6 @@ function _add_series(plt::Plot{PyPlotBackend}, series::Series) error("Only numbers and vectors are supported with levels keyword") end - # @show ax d - # for each plotting command, optionally build and add a series handle to the list # line plot @@ -688,7 +557,7 @@ function _add_series(plt::Plot{PyPlotBackend}, series::Series) push!(handles, handle) a = d[:arrow] - if a != nothing && !is3d(d) # TODO: handle 3d later + if a != nothing && !is3d(st) # TODO: handle 3d later if typeof(a) != Arrow warn("Unexpected type for arrow: $(typeof(a))") else @@ -982,9 +851,7 @@ function _add_series(plt::Plot{PyPlotBackend}, series::Series) # add the colorbar legend sp = d[:subplot] if needs_colorbar && sp.attr[:colorbar] != :none - # cbar = PyPlot.colorbar(handles[end], ax=ax) - - # do we need a discrete colorbar? + # add keyword args for a discrete colorbar handle = handles[end] kw = KW() if discrete_colorbar_values != nothing @@ -995,25 +862,12 @@ function _add_series(plt::Plot{PyPlotBackend}, series::Series) kw[:boundaries] = vcat(0, kw[:values] + 0.5) end + # create and store the colorbar object (handle) and the axis that it is drawn on. + # note: the colorbar axis is positioned independently from the subplot axis fig = plt.o cbax = fig[:add_axes]([0.8,0.1,0.03,0.8]) sp.attr[:cbar_handle] = fig[:colorbar](handle, cax = cbax, kw...) sp.attr[:cbar_ax] = cbax - - # 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 @@ -1040,84 +894,13 @@ function _add_series(plt::Plot{PyPlotBackend}, series::Series) ) 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 - -# # set the lims for each axis using the axis extrema, overriding if needed -# # TODO: most of this is the same for all backends! -# function set_lims!(sp::Subplot{PyPlotBackend}) -# ax = sp.o -# ax == nothing && return -# for letter in (:x, :y, :z) -# axis = sp.attr[symbol(letter,:axis)] -# # get the extrema -# lims = copy(axis[:extrema]) -# # if d[:lims] != :auto, update lim when `isfinite` -# lims_override = axis[:lims] -# if lims_override != :auto -# if isfinite(lims_override[1]) -# lims[1] = lims_override[1] -# end -# if isfinite(lims_override[2]) -# lims[2] = lims_override[2] -# end -# end -# # ax[:set_<>lim](lims) -# # TODO: check for polar, set tlim/rlim instead -# func = symbol(:set_, letter, :lim) -# if haskey(ax, func) -# ax[func](lims...) -# end -# end -# end - function set_lims!(sp::Subplot{PyPlotBackend}, axis::Axis) lims = copy(axis[:extrema]) - # if d[:lims] != :auto, update lim when `isfinite` lims_override = axis[:lims] if lims_override != :auto if isfinite(lims_override[1]) @@ -1127,7 +910,8 @@ function set_lims!(sp::Subplot{PyPlotBackend}, axis::Axis) lims[2] = lims_override[2] end end - # TODO: check for polar, set tlim/rlim instead + + # TODO: check for polar, do set_tlim/set_rlim instead # pyplot's set_xlim (or y/z) method: sp.o[symbol(:set_, axis[:letter], :lim)](lims...) @@ -1147,40 +931,50 @@ function update_limits!(sp::Subplot{PyPlotBackend}, series::Series, letters) end end -function setxy!{X,Y}(plt::Plot{PyPlotBackend}, xy::Tuple{X,Y}, i::Integer) - series = plt.series_list[i] +function _series_updated(plt::Plot{PyPlotBackend}, series::Series) d = series.d - d[:x], d[:y] = xy for handle in d[:serieshandle] - try - handle[:set_data](xy...) - catch - handle[:set_offsets](hcat(xy...)) + if is3d(series) + handle[:set_data](d[:x], d[:y]) + handle[:set_3d_properties](d[:z]) + else + try + handle[:set_data](d[:x], d[:y]) + catch + handle[:set_offsets](hcat(d[:x], d[:y])) + end end end - # set_lims!(plt, plt.series_list[i]) - # expand_extrema!(axis, d[letter]) - # set_lims!(d[:subplot]) - update_limits!(d[:subplot], series, (:x,:y)) - plt + update_limits!(d[:subplot], series, is3d(series) ? (:x,:y,:z) : (:x,:y)) end - -function setxyz!{X,Y,Z}(plt::Plot{PyPlotBackend}, xyz::Tuple{X,Y,Z}, i::Integer) - # d = plt.series_list[i].d - series = plt.series_list[i] - d = series.d - d[:x], d[:y], d[:z] = xyz - # 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]) - # set_lims!(d[:subplot]) - update_limits!(d[:subplot], series, (:x,:y,:z)) - plt -end +# function setxy!{X,Y}(plt::Plot{PyPlotBackend}, xy::Tuple{X,Y}, i::Integer) +# series = plt.series_list[i] +# d = series.d +# d[:x], d[:y] = xy +# for handle in d[:serieshandle] +# try +# handle[:set_data](xy...) +# catch +# handle[:set_offsets](hcat(xy...)) +# end +# end +# update_limits!(d[:subplot], series, (:x,:y)) +# plt +# end +# +# +# function setxyz!{X,Y,Z}(plt::Plot{PyPlotBackend}, xyz::Tuple{X,Y,Z}, i::Integer) +# series = plt.series_list[i] +# d = series.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 +# update_limits!(d[:subplot], series, (:x,:y,:z)) +# plt +# end # -------------------------------------------------------------------------- @@ -1204,12 +998,7 @@ end function addPyPlotTicks(ax, ticks, letter) ticks == :auto && return axis = ax[symbol(letter,"axis")] - # tickfunc = symbol("set_", letter, "ticks") - # labfunc = symbol("set_", letter, "ticklabels") if ticks == :none || ticks == nothing - # ax[][:set_major_locator] - # ax[tickfunc]([]) - # ax[labfunc]([]) kw = KW() for dir in (:top,:bottom,:left,:right) kw[dir] = kw[symbol(:label,dir)] = "off" @@ -1220,11 +1009,8 @@ function addPyPlotTicks(ax, ticks, letter) ttype = ticksType(ticks) if ttype == :ticks - # ax[tickfunc](ticks) axis[:set_ticks](ticks) elseif ttype == :ticks_and_labels - # ax[tickfunc](ticks[1]) - # ax[labfunc](ticks[2]) axis[:set_ticks](ticks[1]) axis[:set_ticklabels](ticks[2]) else @@ -1246,37 +1032,26 @@ function updateAxisColors(ax, a::Axis) for (loc, spine) in ax[:spines] spine[:set_color](getPyPlotColor(a[:foreground_color_border])) end - # for letter in ("x", "y", "z") - axissym = symbol(a[:letter], :axis) - if haskey(ax, axissym) - ax[:tick_params](axis=string(a[:letter]), which="both", - colors=getPyPlotColor(a[:foreground_color_axis]), - labelcolor=getPyPlotColor(a[:foreground_color_text])) - ax[axissym][:label][:set_color](getPyPlotColor(a[:foreground_color_guide])) - end - # end + axissym = symbol(a[:letter], :axis) + if haskey(ax, axissym) + ax[:tick_params](axis=string(a[:letter]), which="both", + colors=getPyPlotColor(a[:foreground_color_axis]), + labelcolor=getPyPlotColor(a[:foreground_color_text])) + ax[axissym][:label][:set_color](getPyPlotColor(a[:foreground_color_guide])) + end 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 attr = sp.attr ax = getAxis(sp) if ax == nothing continue end - # ticksz = get(d, :tickfont, plt.plotargs[:tickfont]).pointsize - # guidesz = get(d, :guidefont, attr[:guidefont]).pointsize # add the annotations for ann in attr[:annotations] @@ -1295,29 +1070,14 @@ function _update_plot(plt::Plot{PyPlotBackend}, d::KW) end ax[field][:set_text](attr[:title]) ax[field][:set_fontsize](attr[:titlefont].pointsize) - # ax[field][:set_color](getPyPlotColor(attr[:titlefont].color)) ax[field][:set_color](getPyPlotColor(attr[:foreground_color_title])) ax[:set_title](attr[:title], loc = loc) - # TODO: set other font attributes end - # axes = [ax] - # # handle right y axis - # axes = [getLeftAxis(figorax)] - # if usingRightAxis(plt) - # push!(axes, getRightAxis(figorax)) - # if get(attr, :yrightlabel, "") != "" - # rightax = getRightAxis(figorax) - # rightax[:set_ylabel](attr[:yrightlabel]) - # end - # end - + # axis attributes for letter in (:x, :y, :z) axissym = symbol(letter, :axis) axis = attr[axissym] - # @show axis - # DD(axis.attr, "updateplot") - # @show haskey(ax, axissym) haskey(ax, axissym) || continue applyPyPlotScale(ax, axis[:scale], letter) addPyPlotLims(ax, axis[:lims], letter) @@ -1326,50 +1086,19 @@ function _update_plot(plt::Plot{PyPlotBackend}, d::KW) 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(attr, :grid, false) - fgcolor = getPyPlotColor(attr[:foreground_color_grid]) - tmpax[axissym][:grid](true, color = fgcolor) - tmpax[:set_axisbelow](true) - end - # end - # @show "" + ax[axissym][:label][:set_fontsize](axis[:guidefont].pointsize) + for lab in ax[symbol("get_", letter, "ticklabels")]() + lab[:set_fontsize](axis[:tickfont].pointsize) + lab[:set_rotation](axis[:rotation]) + end + if get(attr, :grid, false) + fgcolor = getPyPlotColor(attr[:foreground_color_grid]) + ax[axissym][:grid](true, color = fgcolor) + ax[:set_axisbelow](true) + end 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(attr, scale) && applyPyPlotScale(ax, attr[scale], letter) - # haskey(attr, lims) && addPyPlotLims(ax, attr[lims], letter) - # haskey(attr, ticks) && addPyPlotTicks(ax, attr[ticks], letter) - # haskey(attr, lab) && ax[symbol("set_", letter, "label")](attr[lab]) - # if get(attr, 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(attr, rotation) && lab[:set_rotation](attr[rotation]) - # end - # if get(attr, :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? + # aspect ratio aratio = get(attr, :aspect_ratio, :none) if aratio != :none ax[:set_aspect](isa(aratio, Symbol) ? string(aratio) : aratio, anchor = "C") @@ -1380,8 +1109,6 @@ end # ----------------------------------------------------------------- -# TODO: these should apply to a Subplot, NOT a Plot - function createPyPlotAnnotationObject(sp::Subplot{PyPlotBackend}, x, y, val::@compat(AbstractString)) ax = sp.o ax[:annotate](val, xy = (x,y)) @@ -1401,79 +1128,21 @@ function createPyPlotAnnotationObject(sp::Subplot{PyPlotBackend}, x, y, val::Plo ) end -# function _add_annotations(sp::Subplot{PyPlotBackend}, anns::AVec) -# for ann in anns -# createPyPlotAnnotationObject(sp, 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) +# function _remove_axis(plt::Plot{PyPlotBackend}, isx::Bool) +# if isx +# plot!(plt, xticks=zeros(0), xlabel="") +# else +# plot!(plt, yticks=zeros(0), ylabel="") # 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 +# function _expand_limits(lims, plt::Plot{PyPlotBackend}, isx::Bool) +# pltlims = plt.o.ax[isx ? :get_xbound : :get_ybound]() +# _expand_limits(lims, pltlims) # 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( @@ -1487,8 +1156,6 @@ const _pyplot_legend_pos = KW( :topleft => "upper left" ) -# function addPyPlotLegend(plt::Plot) -# function addPyPlotLegend(plt::Plot, ax) function addPyPlotLegend(plt::Plot, sp::Subplot, ax) leg = sp.attr[:legend] if leg != :none @@ -1506,16 +1173,9 @@ function addPyPlotLegend(plt::Plot, sp::Subplot, ax) 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], + leg = ax[:legend](handles, + labels, loc = get(_pyplot_legend_pos, leg, "best"), scatterpoints = 1, fontsize = sp.attr[:legendfont].pointsize @@ -1538,6 +1198,7 @@ end # ----------------------------------------------------------------- +# add legend, update colors and positions, then draw function finalizePlot(plt::Plot{PyPlotBackend}) for sp in plt.subplots # ax = getLeftAxis(plt) @@ -1551,36 +1212,19 @@ function finalizePlot(plt::Plot{PyPlotBackend}) drawfig(plt.o) plt.layout.bbox = py_bbox_fig(plt) update_child_bboxes!(plt.layout) - update_position!(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 - # ----------------------------------------------------------------- +# display/output -# 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 diff --git a/src/backends/qwt.jl b/src/backends/qwt.jl index cc76d733..efdc0af9 100644 --- a/src/backends/qwt.jl +++ b/src/backends/qwt.jl @@ -137,8 +137,8 @@ function _create_backend_figure(plt::Plot{QwtBackend}) # plt end -# function _add_series(::QwtBackend, plt::Plot, d::KW) -function _add_series(plt::Plot{QwtBackend}, series::Series) +# function _series_added(::QwtBackend, plt::Plot, d::KW) +function _series_added(plt::Plot{QwtBackend}, series::Series) d = adjustQwtKeywords(plt, false; series.d...) fixcolors(d) dumpdict(d,"\n\n!!! plot!") diff --git a/src/backends/template.jl b/src/backends/template.jl index 1e41ef10..10580287 100644 --- a/src/backends/template.jl +++ b/src/backends/template.jl @@ -1,84 +1,74 @@ -# TODO: find/replace all [PkgName] with CamelCase, all [pkgname] with lowercase +# TODO: find/replace all [PkgName] with CamelCase -# [WEBSITE] +# [ADD BACKEND WEBSITE] function _initialize_backend(::[PkgName]AbstractBackend; kw...) - @eval begin - import [PkgName] - export [PkgName] - # TODO: other initialization that needs to be eval-ed - end - # TODO: other initialization + @eval begin + import [PkgName] + export [PkgName] + # todo: other initialization that needs to be eval-ed + end + # todo: other initialization end # --------------------------------------------------------------------------- +# Create the window/figure for this backend. function _create_backend_figure(plt::Plot{[PkgName]Backend}) - # TODO: create the window/figure for this backend nothing end - -function _add_series(plt::Plot{[PkgName]Backend}, series::Series) - # TODO: add one series to the underlying package +# Set up the subplot within the backend object. +function _initialize_subplot(plt::Plot{PyPlotBackend}, sp::Subplot{PyPlotBackend}) end -# function _add_annotations{X,Y,V}(plt::Plot{[PkgName]AbstractBackend}, anns::AVec{@compat(Tuple{X,Y,V})}) -# for ann in anns -# # TODO: add the annotation to the plot -# end -# 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{[PkgName]Backend}) + sp.minpad = (20mm, 5mm, 2mm, 10mm) +end + +# Use the bounding boxes (and methods left/top/right/bottom/width/height) `sp.bbox` and `sp.plotarea` to +# position the subplot in the backend. +function _update_position!(sp::Subplot{[PkgName]Backend}) +end # ---------------------------------------------------------------- -function _before_update_plot(plt::Plot{[PkgName]AbstractBackend}) +# This is called before series processing... use it if you need to make the backend object current or something. +function _before_update(plt::Plot{[PkgName]AbstractBackend}) end -# TODO: override this to update plot items (title, xlabel, etc) after creation + +# Add one series to the underlying backend object. +function _series_added(plt::Plot{[PkgName]Backend}, series::Series) +end + + +# Override this to update plot items (title, xlabel, etc), and add annotations (d[:annotations]) function _update_plot(plt::Plot{[PkgName]AbstractBackend}, d::KW) end -function _update_plot_pos_size(plt::AbstractPlot{[PkgName]AbstractBackend}, d::KW) +# ---------------------------------------------------------------- + +# 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{[PkgName]AbstractBackend}, series::Series) end # ---------------------------------------------------------------- -# accessors for x/y data - -# function getxy(plt::Plot{[PkgName]AbstractBackend}, i::Int) -# # TODO: return a tuple of (x, y) vectors -# end -# -# function setxy!{X,Y}(plt::Plot{[PkgName]AbstractBackend}, xy::Tuple{X,Y}, i::Integer) -# # TODO: set the plot data from the (x,y) tuple -# plt -# end - -# ---------------------------------------------------------------- - -# function _create_subplot(subplt::Subplot{[PkgName]AbstractBackend}, isbefore::Bool) -# # TODO: build the underlying Subplot object. this is where you might layout the panes within a GUI window, for example -# end - -# function _expand_limits(lims, plt::Plot{[PkgName]AbstractBackend}, isx::Bool) -# # TODO: call expand limits for each plot data -# end -# -# function _remove_axis(plt::Plot{[PkgName]AbstractBackend}, isx::Bool) -# # TODO: if plot is inner subplot, might need to remove ticks or axis labels -# end - -# ---------------------------------------------------------------- - +# Write a png to io. You could define methods for: + # "application/eps" => "eps", + # "image/eps" => "eps", + # "application/pdf" => "pdf", + # "image/png" => "png", + # "application/postscript" => "ps", + # "image/svg+xml" => "svg" function Base.writemime(io::IO, ::MIME"image/png", plt::AbstractPlot{[PkgName]AbstractBackend}) - # TODO: write a png to io end +# Display/show the plot (open a GUI window, or browser page, for example). function Base.display(::PlotsDisplay, plt::Plot{[PkgName]AbstractBackend}) - # TODO: display/show the plot end - -# function Base.display(::PlotsDisplay, plt::Subplot{[PkgName]AbstractBackend}) -# # TODO: display/show the subplot -# end diff --git a/src/backends/unicodeplots.jl b/src/backends/unicodeplots.jl index ca218544..6800e841 100644 --- a/src/backends/unicodeplots.jl +++ b/src/backends/unicodeplots.jl @@ -195,7 +195,7 @@ function _create_backend_figure(plt::Plot{UnicodePlotsBackend}) # plt end -function _add_series(plt::Plot{UnicodePlotsBackend}, series::Series) +function _series_added(plt::Plot{UnicodePlotsBackend}, series::Series) d = series.d # TODO don't need these once the "bar" series recipe is done if d[:seriestype] in (:sticks, :bar) diff --git a/src/backends/winston.jl b/src/backends/winston.jl index 97f0637b..c0a43fc8 100644 --- a/src/backends/winston.jl +++ b/src/backends/winston.jl @@ -96,7 +96,7 @@ end :star5 => "asterisk" ) -function _before_add_series(plt::Plot{WinstonBackend}) +function _before_update(plt::Plot{WinstonBackend}) Winston.ghf(plt.o) end @@ -127,7 +127,7 @@ function getWinstonItems(plt::Plot) window, canvas, wplt end -function _add_series(plt::Plot{WinstonBackend}, series::Series) +function _series_added(plt::Plot{WinstonBackend}, series::Series) d = series.d window, canvas, wplt = getWinstonItems(plt) diff --git a/src/layouts.jl b/src/layouts.jl index 9c8f5c41..c9060612 100644 --- a/src/layouts.jl +++ b/src/layouts.jl @@ -5,6 +5,8 @@ const px = AbsoluteLength(0.254) const pct = Length{:pct, Float64}(1.0) +const _cbar_width = 5mm + Base.(:.*)(m::Measure, n::Number) = m * n Base.(:.*)(n::Number, m::Measure) = m * n Base.(:-)(m::Measure, a::AbstractArray) = map(ai -> m - ai, a) @@ -65,6 +67,15 @@ function crop(parent::BoundingBox, child::BoundingBox) BoundingBox(l, t, w, h) end +# convert a bounding box from absolute coords to percentages... returns an array of percentages of figure size: [left, bottom, width, height] +function bbox_to_pcts(bb::BoundingBox, figw, figh, flipy = true) + mms = Float64[f(bb).value for f in (left,bottom,width,height)] + if flipy + mms[2] = figh.value - mms[2] # flip y when origin in bottom-left + end + mms ./ Float64[figw.value, figh.value, figw.value, figh.value] +end + function Base.show(io::IO, bbox::BoundingBox) print(io, "BBox{l,t,r,b,w,h = $(left(bbox)),$(top(bbox)), $(right(bbox)),$(bottom(bbox)), $(width(bbox)),$(height(bbox))}") end @@ -93,7 +104,7 @@ padding_w(layout::AbstractLayout) = left_padding(layout) + right_padding(layout) padding_h(layout::AbstractLayout) = bottom_padding(layout) + top_padding(layout) padding(layout::AbstractLayout) = (padding_w(layout), padding_h(layout)) -update_position!(layout::AbstractLayout) = nothing +_update_position!(layout::AbstractLayout) = nothing update_child_bboxes!(layout::AbstractLayout) = nothing width(layout::AbstractLayout) = width(layout.bbox) @@ -105,6 +116,11 @@ attr(layout::AbstractLayout, k::Symbol, v) = get(layout.attr, k, v) attr!(layout::AbstractLayout, v, k::Symbol) = (layout.attr[k] = v) hasattr(layout::AbstractLayout, k::Symbol) = haskey(layout.attr, k) +leftpad(layout::AbstractLayout) = 0mm +toppad(layout::AbstractLayout) = 0mm +rightpad(layout::AbstractLayout) = 0mm +bottompad(layout::AbstractLayout) = 0mm + # ----------------------------------------------------------- # RootLayout @@ -138,6 +154,7 @@ Base.getindex(layout::EmptyLayout, r::Int, c::Int) = nothing # nested, gridded layout with optional size percentages type GridLayout <: AbstractLayout parent::AbstractLayout + minpad::Tuple # leftpad, toppad, rightpad, bottompad bbox::BoundingBox grid::Matrix{AbstractLayout} # Nested layouts. Each position is a AbstractLayout, which allows for arbitrary recursion widths::Vector{Measure} @@ -155,6 +172,7 @@ function GridLayout(dims...; grid = Matrix{AbstractLayout}(dims...) layout = GridLayout( parent, + (20mm, 5mm, 2mm, 10mm), defaultbox, grid, Measure[w*pct for w in widths], @@ -173,13 +191,39 @@ function Base.setindex!(layout::GridLayout, v, r::Int, c::Int) layout.grid[r,c] = v end -min_padding_left(layout::GridLayout) = maximum(map(min_padding_left, layout.grid[:,1])) -min_padding_top(layout::GridLayout) = maximum(map(min_padding_top, layout.grid[1,:])) -min_padding_right(layout::GridLayout) = maximum(map(min_padding_right, layout.grid[:,end])) -min_padding_bottom(layout::GridLayout) = maximum(map(min_padding_bottom, layout.grid[end,:])) +leftpad(layout::GridLayout) = layout.minpad[1] +toppad(layout::GridLayout) = layout.minpad[2] +rightpad(layout::GridLayout) = layout.minpad[3] +bottompad(layout::GridLayout) = layout.minpad[4] + +# min_padding_left(layout::GridLayout) = maximum(map(min_padding_left, layout.grid[:,1])) +# min_padding_top(layout::GridLayout) = maximum(map(min_padding_top, layout.grid[1,:])) +# min_padding_right(layout::GridLayout) = maximum(map(min_padding_right, layout.grid[:,end])) +# min_padding_bottom(layout::GridLayout) = maximum(map(min_padding_bottom, layout.grid[end,:])) -update_position!(layout::GridLayout) = map(update_position!, layout.grid) +# leftpad, toppad, rightpad, bottompad +function _update_min_padding!(layout::GridLayout) + # minpad_matrix = map(_update_min_padding!, layout.grid) + # nr,nc = size(layout) + # leftpad = maximum([minpad_matrix[r,1][1] for r=1:nr]) + # toppad = maximum([minpad_matrix[1,c][2] for c=1:nc]) + # rightpad = maximum([minpad_matrix[r,end][3] for r=1:nr]) + # bottompad = maximum([minpad_matrix[end,c][4] for c=1:nc]) + map(_update_min_padding!, layout.grid) + layout.minpad = ( + maximum(map(leftpad, layout.grid[:,1])), + maximum(map(toppad, layout.grid[1,:])), + maximum(map(rightpad, layout.grid[:,end])), + maximum(map(bottompad, layout.grid[end,:])) + ) + # layout.minpad = (leftpad, toppad, rightpad, bottompad) +end + + +function _update_position!(layout::GridLayout) + map(_update_position!, layout.grid) +end # recursively compute the bounding boxes for the layout and plotarea (relative to canvas!) @@ -187,10 +231,16 @@ function update_child_bboxes!(layout::GridLayout) nr, nc = size(layout) # create a matrix for each minimum padding direction - minpad_left = map(min_padding_left, layout.grid) - minpad_top = map(min_padding_top, layout.grid) - minpad_right = map(min_padding_right, layout.grid) - minpad_bottom = map(min_padding_bottom, layout.grid) + _update_min_padding!(layout) + # minpad_left = map(l -> l.minpad[1], layout.grid) + # minpad_left = map(min_padding_left, layout.grid) + # minpad_top = map(min_padding_top, layout.grid) + # minpad_right = map(min_padding_right, layout.grid) + # minpad_bottom = map(min_padding_bottom, layout.grid) + minpad_left = map(leftpad, layout.grid) + minpad_top = map(toppad, layout.grid) + minpad_right = map(rightpad, layout.grid) + minpad_bottom = map(bottompad, layout.grid) # @show minpad_left minpad_top minpad_right minpad_bottom # get the max horizontal (left and right) padding over columns, diff --git a/src/plot.jl b/src/plot.jl index 11491cc4..ecb0902f 100644 --- a/src/plot.jl +++ b/src/plot.jl @@ -94,9 +94,7 @@ end # natively by the backend function _apply_series_recipe(plt::Plot, d::KW) st = d[:seriestype] - # dumpdict(d, "apply_series_recipe", true) if st in supportedTypes() - # println("adding series!!") # getting ready to add the series... last update to subplot from anything # that might have been added during series recipes @@ -134,7 +132,7 @@ function _apply_series_recipe(plt::Plot, d::KW) warnOnUnsupported(plt.backend, d) series = Series(d) push!(plt.series_list, series) - _add_series(plt, series) + _series_added(plt, series) else # get a sub list of series for this seriestype @@ -158,7 +156,7 @@ end # note: at entry, we only have those preprocessed args which were passed in... no default values yet function _plot!(plt::Plot, d::KW, args...) # just in case the backend needs to set up the plot (make it current or something) - _before_add_series(plt) + _before_update(plt) # first apply any args for the subplots for (idx,sp) in enumerate(plt.subplots) @@ -172,15 +170,6 @@ function _plot!(plt::Plot, d::KW, args...) args = tuple(extractGroupArgs(d[:group], args...), args...) end - # # initialize the annotations list with what was passed in - # # TODO: there must be cleaner way to handle this! - # anns = annotations(get(d, :annotation, NTuple{3}[])) - # if typeof(anns) <: AVec{PlotText} - # anns = NTuple{3}[] - # else - # delete!(d, :annotation) - # end - # for plotting recipes, swap out the args and update the parameter dictionary # we are keeping a queue of series that still need to be processed. @@ -212,7 +201,6 @@ function _plot!(plt::Plot, d::KW, args...) # if there was a grouping, filter the data here _filter_input_data!(kw) - # @show typeof((kw[:x], kw[:y], kw[:z])) # map marker_z if it's a Function if isa(get(kw, :marker_z, nothing), Function) @@ -250,38 +238,9 @@ function _plot!(plt::Plot, d::KW, args...) # !!! note: at this point, kw_list is fully decomposed into individual series... one KW per series !!! - # # TODO: move annotations into subplot update - # # now include any annotations which were added during recipes - # for kw in kw_list - # append!(anns, annotations(pop!(kw, :annotation, []))) - # end - # # @show anns - - - # for kw in kw_list - # @show typeof((kw[:x], kw[:y], kw[:z])) - # end - - # # merge plot args... this is where we combine all the plot args from the user and - # # from the recipes... axis info, colors, etc - # # TODO: why do i need to check for the subplot key? - # # if !haskey(d, :subplot) - # # for kw in vcat(kw_list, d) - # for kw in kw_list - # _update_subplot_args(plt, kw[:subplot], kw) - # # _add_plotargs!(plt, kw) - # end - # # handlePlotColors(plt.backend, plt.plotargs) - # # end - - # for kw in kw_list - # @show typeof((kw[:x], kw[:y], kw[:z])) - # end - # this is it folks! # TODO: we probably shouldn't use i for tracking series index, but rather explicitly track it in recipes for (i,kw) in enumerate(kw_list) - # DD(kw, "series $i before") if !(get(kw, :seriestype, :none) in (:xerror, :yerror)) plt.n += 1 end @@ -309,16 +268,10 @@ function _plot!(plt::Plot, d::KW, args...) sp.attr[:annotations] = vcat(sp_anns, anns) # we update subplot args in case something like the color palatte is part of the recipe - # DD(sp.attr[:xaxis].d, "before USA $i") - # DD(kw, "kw") _update_subplot_args(plt, sp, kw, idx) - # DD(sp.attr[:xaxis].d, "after USA $i") # set default values, select from attribute cycles, and generally set the final attributes _add_defaults!(kw, plt, sp, i) - # DD(kw, "series $i after") - - # # now we have a fully specified series, with colors chosen. we must recursively handle # series recipes, which dispatch on seriestype. If a backend does not natively support a seriestype, @@ -326,14 +279,9 @@ function _plot!(plt::Plot, d::KW, args...) # For example, a histogram is just a bar plot with binned data, a bar plot is really a filled step plot, # and a step plot is really just a path. So any backend that supports drawing a path will implicitly # be able to support step, bar, and histogram plots (and any recipes that use those components). - # DD(sp.attr[:xaxis].d, "before $i") _apply_series_recipe(plt, kw) - # DD(sp.attr[:xaxis].d, "after $i") end - # # now that we're done adding all the series, add the annotations - # _add_annotations(plt, anns) - # TODO just need to pass plt... and we should do all non-series updates here _update_plot(plt, plt.plotargs) @@ -348,18 +296,6 @@ function _plot!(plt::Plot, d::KW, args...) plt end -# # handle the grouping -# function _add_series(plt::Plot, d::KW, groupby::GroupBy, args...) -# starting_n = plt.n -# for (i, glab) in enumerate(groupby.groupLabels) -# tmpd = copy(d) -# tmpd[:numUncounted] = plt.n - starting_n -# _add_series(plt, tmpd, nothing, args...; -# idxfilter = groupby.groupIds[i], -# grouplabel = string(glab)) -# end -# end - function _replace_linewidth(d::KW) # get a good default linewidth... 0 for surface and heatmaps @@ -368,63 +304,6 @@ function _replace_linewidth(d::KW) end end -# # no grouping -# function _add_series(plt::Plot, d::KW, args...; -# idxfilter = nothing, -# grouplabel = "") -# -# # get the list of dictionaries, one per series -# dumpdict(d, "before process_inputs") -# process_inputs(plt, d, args...) -# dumpdict(d, "after process_inputs") -# -# if idxfilter != nothing -# # add the group name as the label if there isn't one passed in -# get!(d, :label, grouplabel) -# # filter the data -# filter_data!(d, idxfilter) -# end -# # dumpdict(d,"",true) -# -# seriesArgList, xmeta, ymeta = build_series_args(plt, d) #, idxfilter) -# # seriesArgList, xmeta, ymeta = build_series_args(plt, groupargs..., args...; d...) -# -# # # if we were able to extract guide information from the series inputs, then update the plot -# # # @show xmeta, ymeta -# # updateDictWithMeta(d, plt.plotargs, xmeta, true) -# # updateDictWithMeta(d, plt.plotargs, ymeta, false) -# -# -# # function _add_series(plt::Plot, ds::) -# # now we can plot the series -# for (i,di) in enumerate(seriesArgList) -# plt.n += 1 -# -# if !stringsSupported() && di[:seriestype] != :pie -# setTicksFromStringVector(plt, d, di, "x") -# setTicksFromStringVector(plt, d, di, "y") -# setTicksFromStringVector(plt, d, di, "z") -# end -# -# # remove plot args -# for k in keys(_plot_defaults) -# delete!(di, k) -# end -# -# # merge in plotarg_overrides -# plotarg_overrides = pop!(di, :plotarg_overrides, nothing) -# if plotarg_overrides != nothing -# merge!(plt.plotargs, plotarg_overrides) -# end -# # dumpdict(plt.plotargs, "pargs", true) -# -# dumpdict(di, "Series $i") -# -# _replace_linewidth(di) -# -# _add_series(plt.backend, plt, di) -# end -# end # -------------------------------------------------------------------- @@ -482,54 +361,11 @@ end # end -# -------------------------------------------------------------------- - -# TODO: remove -# # should we update the x/y label given the meta info during input slicing? -# function updateDictWithMeta(d::KW, plotargs::KW, meta::Symbol, isx::Bool) -# lsym = isx ? :xguide : :yguide -# if plotargs[lsym] == default(lsym) -# d[lsym] = string(meta) -# end -# end -# updateDictWithMeta(d::KW, plotargs::KW, meta, isx::Bool) = nothing - # -------------------------------------------------------------------- annotations(::Void) = [] annotations(anns::AVec) = anns annotations(anns) = Any[anns] -# annotations{X,Y,V}(v::AVec{@compat(Tuple{X,Y,V})}) = v -# annotations{X,Y,V}(t::@compat(Tuple{X,Y,V})) = [t] -# annotations(v::AVec{PlotText}) = v -# annotations(v::AVec) = map(PlotText, v) -# annotations(anns) = error("Expecting a tuple (or vector of tuples) for annotations: ", -# "(x, y, annotation)\n got: $(typeof(anns))") - -# function annotations(plt::Plot, anns) -# anns = annotations(anns) -# # if we just have a list of PlotText objects, then create (x,y,text) tuples -# if typeof(anns) <: AVec{PlotText} -# x, y = plt[plt.n] -# anns = Tuple{Float64,Float64,PlotText}[(x[i], y[i], t) for (i,t) in enumerate(anns)] -# end -# anns -# end - - -# function _add_annotations(plt::Plot, d::KW) -# anns = annotations(get(d, :annotation, nothing)) -# if !isempty(anns) -# -# # if we just have a list of PlotText objects, then create (x,y,text) tuples -# if typeof(anns) <: AVec{PlotText} -# x, y = plt[plt.n] -# anns = Tuple{Float64,Float64,PlotText}[(x[i], y[i], t) for (i,t) in enumerate(anns)] -# end -# -# _add_annotations(plt, anns) -# end -# end # -------------------------------------------------------------------- diff --git a/src/subplots.jl b/src/subplots.jl index ae25236b..e8652ded 100644 --- a/src/subplots.jl +++ b/src/subplots.jl @@ -1,7 +1,15 @@ function Subplot{T<:AbstractBackend}(::T; parent = RootLayout()) - Subplot{T}(parent, defaultbox, defaultbox, KW(), nothing, nothing) + Subplot{T}( + parent, + (20mm, 5mm, 2mm, 10mm), + defaultbox, + defaultbox, + KW(), + nothing, + nothing + ) end plotarea!(sp::Subplot, bbox::BoundingBox) = (sp.plotarea = bbox) @@ -11,6 +19,10 @@ Base.size(sp::Subplot) = (1,1) Base.length(sp::Subplot) = 1 Base.getindex(sp::Subplot, r::Int, c::Int) = sp +leftpad(sp::Subplot) = sp.minpad[1] +toppad(sp::Subplot) = sp.minpad[2] +rightpad(sp::Subplot) = sp.minpad[3] +bottompad(sp::Subplot) = sp.minpad[4] get_subplot(plt::Plot, sp::Subplot) = sp get_subplot(plt::Plot, i::Integer) = plt.subplots[i] diff --git a/src/types.jl b/src/types.jl index d8e0e209..3012704f 100644 --- a/src/types.jl +++ b/src/types.jl @@ -32,6 +32,7 @@ end # a single subplot type Subplot{T<:AbstractBackend} <: AbstractLayout parent::AbstractLayout + minpad::Tuple # leftpad, toppad, rightpad, bottompad bbox::BoundingBox # the canvas area which is available to this subplot plotarea::BoundingBox # the part where the data goes attr::KW # args specific to this subplot diff --git a/src/utils.jl b/src/utils.jl index bca36d9a..93ea4dbc 100644 --- a/src/utils.jl +++ b/src/utils.jl @@ -481,14 +481,44 @@ function getxyz(plt::Plot, i::Integer) end function setxy!{X,Y}(plt::Plot, xy::Tuple{X,Y}, i::Integer) - d = plt.series_list[i].d - d[:x], d[:y] = xy + series = plt.series_list[i] + series.d[:x], series.d[:y] = xy + _series_updated(plt, series) end function setxyz!{X,Y,Z}(plt::Plot, xyz::Tuple{X,Y,Z}, i::Integer) - d = plt.series_list[i].d - d[:x], d[:y], d[:z] = xyz + series = plt.series_list[i] + series.d[:x], series.d[:y], series.d[:z] = xyz + _series_updated(plt, series) end - +# +# +# function setxy!{X,Y}(plt::Plot{PyPlotBackend}, xy::Tuple{X,Y}, i::Integer) +# series = plt.series_list[i] +# d = series.d +# d[:x], d[:y] = xy +# for handle in d[:serieshandle] +# try +# handle[:set_data](xy...) +# catch +# handle[:set_offsets](hcat(xy...)) +# end +# end +# update_limits!(d[:subplot], series, (:x,:y)) +# plt +# end +# +# +# function setxyz!{X,Y,Z}(plt::Plot{PyPlotBackend}, xyz::Tuple{X,Y,Z}, i::Integer) +# series = plt.series_list[i] +# d = series.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 +# update_limits!(d[:subplot], series, (:x,:y,:z)) +# plt +# end # ------------------------------------------------------- # indexing notation