From de616dfdf001be7fc11ab6b935da986c5911f4bf Mon Sep 17 00:00:00 2001 From: Thomas Breloff Date: Mon, 6 Jun 2016 15:34:51 -0400 Subject: [PATCH] update_min_padding fix; shape support in GR; GR attr fixes; image extrema and fixes; hline/vline recipes; nobigs closes #303 --- src/backends.jl | 10 +-- src/backends/gr.jl | 125 ++++++++++++++++++++-------------- src/backends/pyplot.jl | 2 +- src/examples.jl | 4 +- src/plot.jl | 7 +- src/recipes.jl | 149 ++++++----------------------------------- src/series_args.jl | 6 +- 7 files changed, 115 insertions(+), 188 deletions(-) diff --git a/src/backends.jl b/src/backends.jl index 4d3d0155..4ba64565 100644 --- a/src/backends.jl +++ b/src/backends.jl @@ -54,16 +54,16 @@ _series_updated(plt::Plot, series::Series) = nothing _before_layout_calcs(plt::Plot) = nothing -title_padding(sp::Subplot) = sp.attr[:title] == "" ? 0mm : sp.attr[:titlefont].pointsize * pt +title_padding(sp::Subplot) = sp[:title] == "" ? 0mm : sp[:titlefont].pointsize * pt guide_padding(axis::Axis) = axis[:guide] == "" ? 0mm : axis[:guidefont].pointsize * pt # 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) - leftpad = 10mm + sp.attr[:left_margin] + guide_padding(sp.attr[:yaxis]) - toppad = 2mm + sp.attr[:top_margin] + title_padding(sp) - rightpad = 3mm + sp.attr[:right_margin] - bottompad = 5mm + sp.attr[:bottom_margin] + guide_padding(sp.attr[:xaxis]) + leftpad = 10mm + sp[:left_margin] + guide_padding(sp[:yaxis]) + toppad = 2mm + sp[:top_margin] + title_padding(sp) + rightpad = 3mm + sp[:right_margin] + bottompad = 5mm + sp[:bottom_margin] + guide_padding(sp[:xaxis]) # @show (leftpad, toppad, rightpad, bottompad) sp.minpad = (leftpad, toppad, rightpad, bottompad) end diff --git a/src/backends/gr.jl b/src/backends/gr.jl index 39401eb9..18fe28ef 100644 --- a/src/backends/gr.jl +++ b/src/backends/gr.jl @@ -41,8 +41,10 @@ supportedTypes(::GRBackend) = [ :path, :steppre, :steppost, :scatter, :histogram2d, :hexbin, :sticks, - :hline, :vline, :heatmap, :pie, :image, #:ohlc, - :contour, :path3d, :scatter3d, :surface, :wireframe + # :hline, :vline, + :heatmap, :pie, :image, + :contour, :path3d, :scatter3d, :surface, :wireframe, + :shape ] supportedStyles(::GRBackend) = [:auto, :solid, :dash, :dot, :dashdot, :dashdotdot] supportedMarkers(::GRBackend) = vcat(_allMarkers, Shape) @@ -162,7 +164,8 @@ function gr_polymarker(d, x, y) end # draw line segments, splitting x/y into contiguous/finite segments -function gr_polyline(x, y) +# note: this can be used for shapes by passing func `GR.fillarea` +function gr_polyline(x, y, func = GR.polyline) iend = 0 n = length(x) while iend < n-1 @@ -189,7 +192,7 @@ function gr_polyline(x, y) # if we found a start and end, draw the line segment, otherwise we're done if istart > 0 && iend > 0 - GR.polyline(x[istart:iend], y[istart:iend]) + func(x[istart:iend], y[istart:iend]) else break end @@ -268,9 +271,9 @@ end # using the axis extrema and limit overrides, return the min/max value for this axis -gr_x_axislims(sp::Subplot) = axis_limits(sp.attr[:xaxis], true) -gr_y_axislims(sp::Subplot) = axis_limits(sp.attr[:yaxis], true) -gr_z_axislims(sp::Subplot) = axis_limits(sp.attr[:zaxis], true) +gr_x_axislims(sp::Subplot) = axis_limits(sp[:xaxis], true) +gr_y_axislims(sp::Subplot) = axis_limits(sp[:yaxis], true) +gr_z_axislims(sp::Subplot) = axis_limits(sp[:zaxis], true) gr_xy_axislims(sp::Subplot) = gr_x_axislims(sp)..., gr_y_axislims(sp)... function gr_lims(axis::Axis, adjust::Bool, expand = nothing) @@ -351,6 +354,18 @@ end # end # end +function gr_set_line(w, style, c) + GR.setlinetype(gr_linetype[style]) + GR.setlinewidth(w) + GR.setlinecolorind(gr_getcolorind(c)) +end + + + +function gr_set_fill(c) + GR.setfillcolorind(gr_getcolorind(c)) + GR.setfillintstyle(GR.INTSTYLE_SOLID) +end # -------------------------------------------------------------------------------------- @@ -392,7 +407,7 @@ function gr_display(plt::Plot) # compute the viewport_canvas, normalized to the larger dimension viewport_canvas = Float64[0,1,0,1] - w, h = plt.attr[:size] + w, h = plt[:size] if w > h ratio = float(h) / w msize = display_width_ratio * w @@ -410,7 +425,7 @@ function gr_display(plt::Plot) end # fill in the viewport_canvas background - gr_fill_viewport(viewport_canvas, plt.attr[:background_color_outside]) + gr_fill_viewport(viewport_canvas, plt[:background_color_outside]) # @show "PLOT SETUP" plt.layout.bbox ratio viewport_canvas # subplots: @@ -426,10 +441,10 @@ function gr_display(sp::Subplot{GRBackend}, w, h, viewport_canvas) # the viewports for this subplot viewport_subplot = gr_viewport_from_bbox(bbox(sp), w, h, viewport_canvas) viewport_plotarea = gr_viewport_from_bbox(plotarea(sp), w, h, viewport_canvas) - # @show "SUBPLOT",sp.attr[:subplot_index] bbox(sp) plotarea(sp) viewport_subplot viewport_plotarea + # @show "SUBPLOT",sp[:subplot_index] bbox(sp) plotarea(sp) viewport_subplot viewport_plotarea # fill in the plot area background - bg = getColor(sp.attr[:background_color_inside]) + bg = getColor(sp[:background_color_inside]) gr_fill_viewport(viewport_plotarea, bg) # # # # c = getColor(d[:background_color_inside]) dark_bg = 0.21 * bg.r + 0.72 * bg.g + 0.07 * bg.b < 0.9 @@ -499,7 +514,7 @@ function gr_display(sp::Subplot{GRBackend}, w, h, viewport_canvas) # # gr_fill_viewport(viewport_plotarea, sp.attr[:background_color_inside]) num_axes = 1 - grid_flag = sp.attr[:grid] + grid_flag = sp[:grid] # reduced from before... set some flags based on the series in this subplot # TODO: can these be generic flags? @@ -616,9 +631,9 @@ function gr_display(sp::Subplot{GRBackend}, w, h, viewport_canvas) GR.setviewport(viewport_plotarea[1], viewport_plotarea[2], viewport_plotarea[3], viewport_plotarea[4]) # these are the Axis objects, which hold scale, lims, etc - xaxis = sp.attr[:xaxis] - yaxis = sp.attr[:yaxis] - zaxis = sp.attr[:zaxis] + xaxis = sp[:xaxis] + yaxis = sp[:yaxis] + zaxis = sp[:zaxis] scale = 0 xaxis[:scale] == :log10 && (scale |= GR.OPTION_X_LOG) @@ -671,7 +686,7 @@ function gr_display(sp::Subplot{GRBackend}, w, h, viewport_canvas) if axes_2d GR.setlinewidth(1) # GR.setlinetype(GR.LINETYPE_DOTTED) - GR.setlinecolorind(gr_getcolorind(sp.attr[:foreground_color_grid])) + GR.setlinecolorind(gr_getcolorind(sp[:foreground_color_grid])) ticksize = 0.0075 * diag if outside_ticks ticksize = -ticksize @@ -697,11 +712,11 @@ function gr_display(sp::Subplot{GRBackend}, w, h, viewport_canvas) end end - if sp.attr[:title] != "" + if sp[:title] != "" GR.savestate() GR.settextalign(GR.TEXT_HALIGN_CENTER, GR.TEXT_VALIGN_TOP) - GR.settextcolorind(gr_getcolorind(sp.attr[:foreground_color_title])) - GR.text(0.5 * (viewport_plotarea[1] + viewport_plotarea[2]), viewport_subplot[4], sp.attr[:title]) + GR.settextcolorind(gr_getcolorind(sp[:foreground_color_title])) + GR.text(0.5 * (viewport_plotarea[1] + viewport_plotarea[2]), viewport_subplot[4], sp[:title]) GR.restorestate() end if xaxis[:guide] != "" @@ -751,7 +766,7 @@ function gr_display(sp::Subplot{GRBackend}, w, h, viewport_canvas) GR.savestate() xmin, xmax, ymin, ymax = extrema[gr_getaxisind(d),:] GR.setwindow(xmin, xmax, ymin, ymax) - if st in [:path, :line, :steppre, :steppost, :sticks, :hline, :vline, :polar] + if st in [:path, :steppre, :steppost, :sticks, :polar] GR.setlinetype(gr_linetype[d[:linestyle]]) GR.setlinewidth(d[:linewidth]) GR.setlinecolorind(gr_getcolorind(d[:linecolor])) @@ -902,15 +917,15 @@ function gr_display(sp::Subplot{GRBackend}, w, h, viewport_canvas) # # GR.fillrect(x[i-1], x[i], ymin, y[i]) # # end - # TODO: use recipe - elseif st in [:hline, :vline] - for xy in d[:y] - if st == :hline - gr_polyline([xmin, xmax], [xy, xy]) - else - gr_polyline([xy, xy], [ymin, ymax]) - end - end + # # TODO: use recipe + # elseif st in [:hline, :vline] + # for xy in d[:y] + # if st == :hline + # gr_polyline([xmin, xmax], [xy, xy]) + # else + # gr_polyline([xy, xy], [ymin, ymax]) + # end + # end # TODO: use recipe elseif st in [:histogram2d, :hexbin] @@ -1088,6 +1103,18 @@ function gr_display(sp::Subplot{GRBackend}, w, h, viewport_canvas) end GR.selntran(1) + elseif st == :shape + # TODO: use GR.fillarea(x, y) similar to pie (extract shape drawing and re-use in pie!) + # TODO: while we're at it, make a pie series recipe?? + gr_set_line(d[:markerstrokewidth], :solid, d[:markerstrokecolor]) + gr_set_fill(d[:markercolor]) + + # draw the shapes + gr_polyline(d[:x], d[:y]) + gr_polyline(d[:x], d[:y], GR.fillarea) + + + elseif st == :image img = d[:z].surf w, h = size(img) @@ -1100,13 +1127,13 @@ function gr_display(sp::Subplot{GRBackend}, w, h, viewport_canvas) round(Int, green(c) * 255) << 8 + round(Int, red(c) * 255) ), img) end - GR.drawimage(xmin, xmax, ymin, ymax, w, h, rgba) + GR.drawimage(xmin, xmax, ymax, ymin, w, h, rgba) end GR.restorestate() end - if sp.attr[:legend] != :none #&& any(legend) == true + if sp[:legend] != :none #&& any(legend) == true GR.savestate() GR.selntran(0) GR.setscale(0) @@ -1134,7 +1161,7 @@ function gr_display(sp::Subplot{GRBackend}, w, h, viewport_canvas) ypos = viewport_plotarea[4] - 0.06 dy = 0.03 * sqrt((viewport_plotarea[2] - viewport_plotarea[1])^2 + (viewport_plotarea[4] - viewport_plotarea[3])^2) GR.setfillintstyle(GR.INTSTYLE_SOLID) - GR.setfillcolorind(gr_getcolorind(sp.attr[:background_color_legend])) + GR.setfillcolorind(gr_getcolorind(sp[:background_color_legend])) GR.fillrect(xpos - 0.08, xpos + w + 0.02, ypos + dy, ypos - dy * n) GR.setlinetype(1) GR.setlinewidth(1) @@ -1170,7 +1197,7 @@ function gr_display(sp::Subplot{GRBackend}, w, h, viewport_canvas) lab = d[:label] end GR.settextalign(GR.TEXT_HALIGN_LEFT, GR.TEXT_VALIGN_HALF) - GR.settextcolorind(gr_getcolorind(sp.attr[:foreground_color_legend])) + GR.settextcolorind(gr_getcolorind(sp[:foreground_color_legend])) GR.text(xpos, ypos, lab) ypos -= dy end @@ -1179,26 +1206,22 @@ function gr_display(sp::Subplot{GRBackend}, w, h, viewport_canvas) GR.restorestate() end - if haskey(sp.attr, :annotations) - GR.savestate() - for ann in sp.attr[:annotations] - x, y, val = ann - x, y = GR.wctondc(x, y) - alpha = val.font.rotation - family = lowercase(val.font.family) - GR.setcharheight(0.7 * val.font.pointsize / sp.plt.attr[:size][2]) - GR.setcharup(sin(val.font.rotation), cos(val.font.rotation)) - if haskey(gr_font_family, family) - GR.settextfontprec(100 + gr_font_family[family], GR.TEXT_PRECISION_STRING) - end - GR.settextcolorind(gr_getcolorind(val.font.color)) - GR.settextalign(gr_halign[val.font.halign], gr_valign[val.font.valign]) - GR.text(x, y, val.str) + GR.savestate() + for ann in sp[:annotations] + x, y, val = ann + x, y = GR.wctondc(x, y) + alpha = val.font.rotation + family = lowercase(val.font.family) + GR.setcharheight(0.7 * val.font.pointsize / sp.plt[:size][2]) + GR.setcharup(sin(val.font.rotation), cos(val.font.rotation)) + if haskey(gr_font_family, family) + GR.settextfontprec(100 + gr_font_family[family], GR.TEXT_PRECISION_STRING) end - GR.restorestate() + GR.settextcolorind(gr_getcolorind(val.font.color)) + GR.settextalign(gr_halign[val.font.halign], gr_valign[val.font.valign]) + GR.text(x, y, val.str) end - - # update && GR.updatews() + GR.restorestate() end diff --git a/src/backends/pyplot.jl b/src/backends/pyplot.jl index a63df531..bbd0fb3e 100644 --- a/src/backends/pyplot.jl +++ b/src/backends/pyplot.jl @@ -732,7 +732,7 @@ function _series_added(plt::Plot{PyPlotBackend}, series::Series) # expand extrema... handle is AxesImage object xmin, xmax, ymax, ymin = handle[:get_extent]() expand_extrema!(sp, xmin, xmax, ymin, ymax) - sp[:yaxis].d[:flip] = true + # sp[:yaxis].d[:flip] = true end if st == :heatmap diff --git a/src/examples.jl b/src/examples.jl index 1a500427..1ed5ea27 100644 --- a/src/examples.jl +++ b/src/examples.jl @@ -315,8 +315,8 @@ PlotExample("Boxplot and Violin series recipes", [:(begin import RDatasets singers = RDatasets.dataset("lattice", "singer") - boxplot(singers, :VoicePart, :Height, marker = (0.3, :orange, stroke(2))) - violin!(singers, :VoicePart, :Height, marker = (0.2, :blue, stroke(0))) + violin(singers, :VoicePart, :Height, marker = (0.2, :blue, stroke(0))) + boxplot!(singers, :VoicePart, :Height, marker = (0.3, :orange, stroke(2))) end)] ) diff --git a/src/plot.jl b/src/plot.jl index 88c7bb7c..4367514d 100644 --- a/src/plot.jl +++ b/src/plot.jl @@ -162,7 +162,12 @@ function _apply_series_recipe(plt::Plot, d::KW) end # adjust extrema and discrete info - if !(st in (:pie, :image, :histogram, :histogram2d)) + if st == :image + w, h = size(d[:z]) + expand_extrema!(sp[:xaxis], (0,w)) + expand_extrema!(sp[:yaxis], (0,h)) + sp[:yaxis].d[:flip] = true + elseif !(st in (:pie, :histogram, :histogram2d)) expand_extrema!(sp, d) end diff --git a/src/recipes.jl b/src/recipes.jl index 2858372b..eb4b2cc0 100644 --- a/src/recipes.jl +++ b/src/recipes.jl @@ -30,17 +30,13 @@ macro userplot(expr::Expr) typename = expr.args[2] funcname = Symbol(lowercase(string(typename))) funcname2 = Symbol(funcname, "!") - # @show typename funcname expr # return a code block with the type definition and convenience plotting methods - ret = esc(quote + esc(quote $expr $funcname(args...; kw...) = plot($typename(args...); kw...) $funcname2(args...; kw...) = plot!($typename(args...); kw...) end) - # dump(ret,20) - # @show ret - ret end # ---------------------------------------------------------------------------------- @@ -62,7 +58,6 @@ plot!(plt::Plot, recipe::PlotRecipe, args...; kw...) = plot!(getRecipeXY(recipe) num_series(x::AMat) = size(x,2) num_series(x) = 1 -# _apply_recipe(d::KW, kw::KW) = () # if it's not a recipe, just do nothing and return the args function RecipesBase.apply_recipe(d::KW, args...; issubplot=false) @@ -125,127 +120,6 @@ if is_installed("DataFrames") end end -# macro kw(k, v) -# esc(:(get!(d, $k, $v))) -# end -# -# function _is_arrow_tuple(expr::Expr) -# expr.head == :tuple && -# isa(expr.args[1], Expr) && -# expr.args[1].head == :(-->) -# end -# -# function _equals_Symbol(arg::Symbol, sym::Symbol) -# arg == sym -# end -# function _equals_Symbol(arg::Expr, sym::Symbol) -# arg.head == :quote && arg.args[1] == sym -# end -# -# # TODO: when this is moved out of Plots, also move the replacement of key aliases to just after the _apply_recipe calls -# function replace_recipe_arrows!(expr::Expr) -# for (i,e) in enumerate(expr.args) -# if isa(e,Expr) -# -# # process trailing flags, like: -# # a --> b, :quiet, :force -# quiet, require, force = false, false, false -# if _is_arrow_tuple(e) -# for flag in e.args -# if _equals_Symbol(flag, :quiet) -# quiet = true -# elseif _equals_Symbol(flag, :require) -# require = true -# elseif _equals_Symbol(flag, :force) -# force = true -# end -# end -# e = e.args[1] -# end -# -# # we are going to recursively swap out `a --> b, flags...` commands -# if e.head == :(-->) -# k, v = e.args -# keyexpr = :(get(Plots._keyAliases, $k, $k)) -# -# set_expr = if force -# # forced override user settings -# :(d[$keyexpr] = $v) -# else -# # if the user has set this keyword, use theirs -# :(get!(d, $keyexpr, $v)) -# end -# -# expr.args[i] = if quiet -# # quietly ignore keywords which are not supported -# :($keyexpr in supportedArgs() ? $set_expr : nothing) -# elseif require -# # error when not supported by the backend -# :($keyexpr in supportedArgs() ? $set_expr : error("In recipe: required keyword ", $k, " is not supported by backend $(backend_name())")) -# else -# set_expr -# end -# -# # @show quiet, force, expr.args[i] -# -# elseif e.head != :call -# # we want to recursively replace the arrows, but not inside function calls -# # as this might include things like Dict(1=>2) -# replace_recipe_arrows!(e) -# end -# end -# end -# end -# -# -# macro recipe(funcexpr::Expr) -# lhs, body = funcexpr.args -# -# if !(funcexpr.head in (:(=), :function)) -# error("Must wrap a valid function call!") -# end -# if !(isa(lhs, Expr) && lhs.head == :call) -# error("Expected `lhs = ...` with lhs as a call Expr... got: $lhs") -# end -# -# # for parametric definitions, take the "curly" expression and add the func -# front = lhs.args[1] -# func = :(Plots._apply_recipe) -# if isa(front, Expr) && front.head == :curly -# front.args[1] = func -# func = front -# end -# -# # get the arg list, stripping out any keyword parameters into a -# # bunch of get!(kw, key, value) lines -# args = lhs.args[2:end] -# kw_body = Expr(:block) -# if isa(args[1], Expr) && args[1].head == :parameters -# for kwpair in args[1].args -# k, v = kwpair.args -# push!(kw_body.args, :(get!(kw, $(QuoteNode(k)), $v))) -# end -# args = args[2:end] -# end -# -# # replace all the key => value lines with argument setting logic -# replace_recipe_arrows!(body) -# -# # now build a function definition for _apply_recipe, wrapping the return value in a tuple if needed -# esc(quote -# function $func(d::KW, kw::KW, $(args...); issubplot=false) -# $kw_body -# ret = $body -# if typeof(ret) <: Tuple -# ret -# else -# (ret,) -# end -# end -# end) -# end -# - # --------------------------------------------------------------------------- @@ -285,6 +159,27 @@ end () end +@recipe function f(::Type{Val{:hline}}, x, y, z) + xmin, xmax = axis_limits(d[:subplot][:xaxis]) + n = length(y) + newx = repmat(Float64[xmin, xmax, NaN], n) + newy = vec(Float64[yi for i=1:3,yi=y]) + d[:x], d[:y] = newx, newy + d[:seriestype] = :path + () +end + +@recipe function f(::Type{Val{:vline}}, x, y, z) + ymin, ymax = axis_limits(d[:subplot][:yaxis]) + n = length(y) + newx = vec(Float64[yi for i=1:3,yi=y]) + newy = repmat(Float64[ymin, ymax, NaN], n) + d[:x], d[:y] = newx, newy + d[:seriestype] = :path + () +end + + # # create a path from steps # @recipe function f(::Type{Val{:steppre}}, x, y, z) # diff --git a/src/series_args.jl b/src/series_args.jl index c22a80f1..29fc1b63 100644 --- a/src/series_args.jl +++ b/src/series_args.jl @@ -87,11 +87,15 @@ compute_z(x, y, z::AbstractMatrix) = Surface(z) compute_z(x, y, z::Void) = nothing compute_z(x, y, z) = copy(z) +nobigs(v::AVec{BigFloat}) = map(Float64, v) +nobigs(v::AVec{BigInt}) = map(Int64, v) +nobigs(v) = v + @noinline function compute_xyz(x, y, z) x = compute_x(x,y,z) y = compute_y(x,y,z) z = compute_z(x,y,z) - x, y, z + nobigs(x), nobigs(y), nobigs(z) end # not allowed