From 56f398fb55173f48e4b7f83c301760b4a09023ee Mon Sep 17 00:00:00 2001 From: Thomas Breloff Date: Mon, 6 Jun 2016 17:55:09 -0400 Subject: [PATCH] histogram2d recipe; handle smoothing generically --- src/axes.jl | 2 +- src/backends/gr.jl | 80 ++++++++++++++++------------- src/layouts.jl | 12 ++--- src/plot.jl | 21 +++++++- src/recipes.jl | 122 ++++++++++++++++++++++++++++----------------- 5 files changed, 149 insertions(+), 88 deletions(-) diff --git a/src/axes.jl b/src/axes.jl index b5d6e304..4baf3279 100644 --- a/src/axes.jl +++ b/src/axes.jl @@ -182,7 +182,7 @@ function expand_extrema!(sp::Subplot, d::KW) if bw == nothing bw = d[:bar_width] = mean(diff(data)) end - @show data bw + # @show data bw axis = sp.attr[Symbol(dsym, :axis)] expand_extrema!(axis, maximum(data) + 0.5maximum(bw)) diff --git a/src/backends/gr.jl b/src/backends/gr.jl index 18fe28ef..9d39766a 100644 --- a/src/backends/gr.jl +++ b/src/backends/gr.jl @@ -34,12 +34,14 @@ supportedArgs(::GRBackend) = [ :orientation, :overwrite_figure, :polar, - :aspect_ratio + :aspect_ratio, + :normalize, :weights ] supportedAxes(::GRBackend) = _allAxes supportedTypes(::GRBackend) = [ :path, :steppre, :steppost, - :scatter, :histogram2d, :hexbin, + :scatter, + #:histogram2d, :hexbin, :sticks, # :hline, :vline, :heatmap, :pie, :image, @@ -647,19 +649,26 @@ function gr_display(sp::Subplot{GRBackend}, w, h, viewport_canvas) for axis_idx = 1:num_axes xmin, xmax, ymin, ymax = extrema[axis_idx,:] + + # NOTE: for log axes, the major_x and major_y - if non-zero (omit labels) - control the minor grid lines (1 = draw 9 minor grid lines, 2 = no minor grid lines) + # NOTE: for log axes, the x_tick and y_tick - if non-zero (omit axes) - only affect the output appearance (1 = nomal, 2 = scientiic notation) if scale & GR.OPTION_X_LOG == 0 # xmin, xmax = GR.adjustlimits(xmin, xmax) majorx = 5 xtick = GR.tick(xmin, xmax) / majorx else - xtick = majorx = 1 + # xtick = majorx = 1 + xtick = 2 # scientific notation + majorx = 2 # no minor grid lines end if scale & GR.OPTION_Y_LOG == 0 # ymin, ymax = GR.adjustlimits(ymin, ymax) majory = 5 ytick = GR.tick(ymin, ymax) / majory else - ytick = majory = 1 + # ytick = majory = 1 + ytick = 2 # scientific notation + majory = 2 # no minor grid lines end xorg = (scale & GR.OPTION_FLIP_X == 0) ? (xmin,xmax) : (xmax,xmin) yorg = (scale & GR.OPTION_FLIP_Y == 0) ? (ymin,ymax) : (ymax,ymin) @@ -692,12 +701,13 @@ function gr_display(sp::Subplot{GRBackend}, w, h, viewport_canvas) ticksize = -ticksize end if grid_flag - @show dark_bg, xtick, ytick, majorx, majory - if dark_bg - GR.grid(xtick * majorx, ytick * majory, 0, 0, 1, 1) - else - GR.grid(xtick, ytick, 0, 0, majorx, majory) - end + GR.grid(xtick, ytick, 0, 0, majorx, majory) + # @show dark_bg, xtick, ytick, majorx, majory + # if dark_bg + # GR.grid(xtick * majorx, ytick * majory, 0, 0, 1, 1) + # else + # GR.grid(xtick, ytick, 0, 0, majorx, majory) + # end end # TODO: this should be done for each axis separately GR.setlinecolorind(gr_getcolorind(xaxis[:foreground_color_axis])) @@ -927,32 +937,32 @@ function gr_display(sp::Subplot{GRBackend}, w, h, viewport_canvas) # end # end - # TODO: use recipe - elseif st in [:histogram2d, :hexbin] - E = zeros(length(d[:x]),2) - E[:,1] = d[:x] - E[:,2] = d[:y] - if isa(d[:bins], Tuple) - xbins, ybins = d[:bins] - else - xbins = ybins = d[:bins] - end - x, y, H = Base.hist2d(E, xbins, ybins) - maxh = maximum(H) - n, m = size(H) - counts = Int32[round(Int32, 1000 + 255 * H[n-i+1,j] / maxh) for i=1:n,j=1:m] - GR.cellarray(xmin, xmax, ymin, ymax, n, m, counts) + # # TODO: use recipe + # elseif st in [:histogram2d, :hexbin] + # E = zeros(length(d[:x]),2) + # E[:,1] = d[:x] + # E[:,2] = d[:y] + # if isa(d[:bins], Tuple) + # xbins, ybins = d[:bins] + # else + # xbins = ybins = d[:bins] + # end + # x, y, H = Base.hist2d(E, xbins, ybins) + # maxh = maximum(H) + # n, m = size(H) + # counts = Int32[round(Int32, 1000 + 255 * H[n-i+1,j] / maxh) for i=1:n,j=1:m] + # GR.cellarray(xmin, xmax, ymin, ymax, n, m, counts) - # NOTE: set viewport to the colorbar area, get character height, draw it, then reset viewport - GR.setviewport(viewport_plotarea[2] + 0.02, viewport_plotarea[2] + 0.05, viewport_plotarea[3], viewport_plotarea[4]) - # zmin, zmax = gr_getzlims(d, 0, maximum(counts), false) - zmin, zmax = gr_lims(zaxis, false, (0, maximum(counts))) - GR.setspace(zmin, zmax, 0, 90) - diag = sqrt((viewport_plotarea[2] - viewport_plotarea[1])^2 + (viewport_plotarea[4] - viewport_plotarea[3])^2) - charheight = max(0.016 * diag, 0.01) - GR.setcharheight(charheight) - GR.colormap() - GR.setviewport(viewport_plotarea[1], viewport_plotarea[2], viewport_plotarea[3], viewport_plotarea[4]) + # # NOTE: set viewport to the colorbar area, get character height, draw it, then reset viewport + # GR.setviewport(viewport_plotarea[2] + 0.02, viewport_plotarea[2] + 0.05, viewport_plotarea[3], viewport_plotarea[4]) + # # zmin, zmax = gr_getzlims(d, 0, maximum(counts), false) + # zmin, zmax = gr_lims(zaxis, false, (0, maximum(counts))) + # GR.setspace(zmin, zmax, 0, 90) + # diag = sqrt((viewport_plotarea[2] - viewport_plotarea[1])^2 + (viewport_plotarea[4] - viewport_plotarea[3])^2) + # charheight = max(0.016 * diag, 0.01) + # GR.setcharheight(charheight) + # GR.colormap() + # GR.setviewport(viewport_plotarea[1], viewport_plotarea[2], viewport_plotarea[3], viewport_plotarea[4]) elseif st == :contour x, y, z = d[:x], d[:y], transpose_z(d, d[:z].surf, false) diff --git a/src/layouts.jl b/src/layouts.jl index 9eedbd76..548f3cf7 100644 --- a/src/layouts.jl +++ b/src/layouts.jl @@ -513,7 +513,7 @@ rowsize(v) = isrow(v) ? length(v.args) : 1 function create_grid(expr::Expr) # cellsym = gensym(:cell) - @show expr + # @show expr if iscol(expr) create_grid_vcat(expr) # rowsizes = map(rowsize, expr.args) @@ -562,17 +562,17 @@ end function create_grid_vcat(expr::Expr) rowsizes = map(rowsize, expr.args) rmin, rmax = extrema(rowsizes) - @show rmin, rmax + # @show rmin, rmax if rmin > 0 && rmin == rmax # we have a grid... build the whole thing # note: rmin is the number of columns nr = length(expr.args) nc = rmin - @show nr, nc + # @show nr, nc body = Expr(:block) for r=1:nr arg = expr.args[r] - @show r, arg + # @show r, arg if isrow(arg) for (c,item) in enumerate(arg.args) push!(body.args, :(cell[$r,$c] = $(create_grid(item)))) @@ -581,7 +581,7 @@ function create_grid_vcat(expr::Expr) push!(body.args, :(cell[$r,1] = $(create_grid(arg)))) end end - @show body + # @show body :(let cell = GridLayout($nr, $nc) $body cell @@ -614,7 +614,7 @@ function create_grid_curly(expr::Expr) for (i,arg) in enumerate(expr.args[2:end]) add_layout_pct!(kw, arg, i, length(expr.args)-1) end - # @show kw + @show kw :(EmptyLayout(label = $(QuoteNode(s)), width = $(get(kw, :w, QuoteNode(:auto))), height = $(get(kw, :h, QuoteNode(:auto))))) end diff --git a/src/plot.jl b/src/plot.jl index 4367514d..f36d5a4d 100644 --- a/src/plot.jl +++ b/src/plot.jl @@ -278,6 +278,24 @@ function _plot!(plt::Plot, d::KW, args...) end end + # handle smoothing by adding a new series + if get(d, :smooth, false) + x, y = kw[:x], kw[:y] + β, α = convert(Matrix{Float64}, [x ones(length(x))]) \ convert(Vector{Float64}, y) + sx = [minimum(x), maximum(x)] + sy = β * sx + α + push!(kw_list, merge(copy(kw), KW( + :seriestype => :path, + :x => sx, + :y => sy, + :fillrange => nothing, + :label => "", + ))) + + # don't allow something else to handle it + d[:smooth] = false + end + else # args are non-empty, so there's still processing to do... add it back to the queue push!(still_to_process, recipedata) @@ -334,6 +352,7 @@ function _plot!(plt::Plot, d::KW, args...) # if !(get(kw, :seriestype, :none) in (:xerror, :yerror)) # plt.n += 1 # end + command_idx = kw[:series_plotindex] - kw_list[1][:series_plotindex] + 1 # get the Subplot object to which the series belongs sp = get(kw, :subplot, :auto) @@ -361,7 +380,7 @@ function _plot!(plt::Plot, d::KW, args...) _update_subplot_args(plt, sp, kw, idx) # set default values, select from attribute cycles, and generally set the final attributes - _add_defaults!(kw, plt, sp, i) + _add_defaults!(kw, plt, sp, command_idx) # 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, diff --git a/src/recipes.jl b/src/recipes.jl index eb4b2cc0..63b4ef5a 100644 --- a/src/recipes.jl +++ b/src/recipes.jl @@ -186,36 +186,7 @@ end # end -# midpoints = d[:x] -# heights = d[:y] -# fillrange = d[:fillrange] == nothing ? 0.0 : d[:fillrange] -# -# # estimate the edges -# dists = diff(midpoints) * 0.5 -# edges = zeros(length(midpoints)+1) -# for i in 1:length(edges) -# if i == 1 -# edge = midpoints[1] - dists[1] -# elseif i == length(edges) -# edge = midpoints[i-1] + dists[i-2] -# else -# edge = midpoints[i-1] + dists[i-1] -# end -# edges[i] = edge -# end -# -# x = Float64[] -# y = Float64[] -# for i in 1:length(heights) -# e1, e2 = edges[i:i+1] -# append!(x, [e1, e1, e2, e2]) -# append!(y, [fillrange, heights[i], heights[i], fillrange]) -# end -# -# d[:x] = x -# d[:y] = y -# d[:seriestype] = :path -# d[:fillrange] = fillrange +# --------------------------------------------------------------------------- # create a bar plot as a filled step function @recipe function f(::Type{Val{:bar}}, x, y, z) @@ -275,30 +246,91 @@ end () end +# --------------------------------------------------------------------------- +# Histograms + +# edges from number of bins +function calc_edges(v, bins::Integer) + vmin, vmax = extrema(v) + linspace(vmin, vmax, bins+1) +end + +# just pass through arrays +calc_edges(v, bins::AVec) = v + +# find the bucket index of this value +function bucket_index(vi, edges) + for (i,e) in enumerate(edges) + if vi <= e + return max(1,i-1) + end + end + return length(edges)-1 +end + +function my_hist(v, bins; normed = false, weights = nothing) + edges = calc_edges(v, bins) + counts = zeros(length(edges)-1) + + for (i,vi) in enumerate(v) + idx = bucket_index(vi, edges) + counts[idx] += (weights == nothing ? 1.0 : weights[i]) + end + + norm_denom = normed ? sum(counts) : 1.0 + if norm_denom == 0 + norm_denom = 1.0 + end + + edges, counts ./ norm_denom +end - # # x is edges - # for i=1:n - # gr_fillrect(series, x[i], x[i+1], 0, y[i]) - # end - # elseif length(x) == n - # # x is centers - # leftwidth = length(x) > 1 ? abs(0.5 * (x[2] - x[1])) : 0.5 - # for i=1:n - # rightwidth = (i == n ? leftwidth : abs(0.5 * (x[i+1] - x[i]))) - # gr_fillrect(series, x[i] - leftwidth, x[i] + rightwidth, 0, y[i]) - # end - # else - # error("gr_barplot: x must be same length as y (centers), or one more than y (edges).\n\t\tlength(x)=$(length(x)), length(y)=$(length(y))") - # end @recipe function f(::Type{Val{:histogram}}, x, y, z) - edges, counts = Base.hist(y, d[:bins]) + edges, counts = my_hist(y, d[:bins], normed = d[:normalize], weights = d[:weights]) d[:x] = edges d[:y] = counts d[:seriestype] = :bar () end +# --------------------------------------------------------------------------- +# Histogram 2D + +# if tuple, map out bins, otherwise use the same for both +calc_edges_2d(x, y, bins) = calc_edges(x, bins), calc_edges(y, bins) +calc_edges_2d{X,Y}(x, y, bins::Tuple{X,Y}) = calc_edges(x, bins[1]), calc_edges(y, bins[2]) + +# the 2D version +function my_hist_2d(x, y, bins; normed = false, weights = nothing) + xedges, yedges = calc_edges_2d(x, y, bins) + counts = zeros(length(yedges)-1, length(xedges)-1) + + for i=1:length(x) + r = bucket_index(y[i], yedges) + c = bucket_index(x[i], xedges) + counts[r,c] += (weights == nothing ? 1.0 : weights[i]) + end + + norm_denom = normed ? sum(counts) : 1.0 + if norm_denom == 0 + norm_denom = 1.0 + end + + xedges, yedges, counts ./ norm_denom +end + +centers(v::AVec) = v[1] + cumsum(diff(v)) + +@recipe function f(::Type{Val{:histogram2d}}, x, y, z) + xedges, yedges, counts = my_hist_2d(x, y, d[:bins], normed = d[:normalize], weights = d[:weights]) + d[:x] = centers(xedges) + d[:y] = centers(yedges) + d[:z] = Surface(counts) + d[:seriestype] = :heatmap + () +end + # --------------------------------------------------------------------------- # Box Plot