histogram2d recipe; handle smoothing generically

This commit is contained in:
Thomas Breloff 2016-06-06 17:55:09 -04:00
parent cf5aed00e2
commit 56f398fb55
5 changed files with 149 additions and 88 deletions

View File

@ -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))

View File

@ -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)

View File

@ -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

View File

@ -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,

View File

@ -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