From 8a9e963d929d660fd9f9a0a3add25a3d61ff3b3a Mon Sep 17 00:00:00 2001 From: Lukas Hauertmann Date: Fri, 1 Nov 2019 12:03:59 +0100 Subject: [PATCH 1/6] Nonuniform heatmaps are now possible with the GR backend. At least for the cartesian case. For polar plots it's still not possible. --- src/backends/gr.jl | 69 +++++++++++++++++++++++++++++++++------------- 1 file changed, 50 insertions(+), 19 deletions(-) diff --git a/src/backends/gr.jl b/src/backends/gr.jl index 7aee8768..ef05550b 100644 --- a/src/backends/gr.jl +++ b/src/backends/gr.jl @@ -920,11 +920,26 @@ function gr_display(sp::Subplot{GRBackend}, w, h, viewport_canvas) outside_ticks = true for ax in (sp[:xaxis], sp[:yaxis]) v = series[ax[:letter]] - if length(v) > 1 && diff(collect(extrema(diff(v))))[1] > 1e-6*std(v) - @warn("GR: heatmap only supported with equally spaced data.") - end end - x, y = heatmap_edges(series[:x], sp[:xaxis][:scale]), heatmap_edges(series[:y], sp[:yaxis][:scale]) + fx, fy = scalefunc(sp[:xaxis][:scale]), scalefunc(sp[:yaxis][:scale]) + nx, ny = length(series[:x]), length(series[:y]) + z = series[:z] + use_midpoints = size(z) == (ny, nx) + use_edges = size(z) == (ny - 1, nx - 1) + if !use_midpoints && !use_edges + error("""Length of x & y does not match the size of z. + Must be either `size(z) == (length(y), length(x))` (x & y define midpoints) + or `size(z) == (length(y)+1, length(x)+1))` (x & y define edges).""") + end + x, y = if use_midpoints + x_diff, y_diff = diff(series[:x]) ./ 2, diff(series[:y]) ./ 2 + x = [ series[:x][1] - x_diff[1], (series[:x][1:end-1] .+ x_diff)..., series[:x][end] + x_diff[end] ] + y = [ series[:y][1] - y_diff[1], (series[:y][1:end-1] .+ y_diff)..., series[:y][end] + y_diff[end] ] + x, y + else + series[:x], series[:y] + end + x, y = map(fx, series[:x]), map(fy, series[:y]) xy_lims = x[1], x[end], y[1], y[end] expand_extrema!(sp[:xaxis], x) expand_extrema!(sp[:yaxis], y) @@ -1316,24 +1331,40 @@ function gr_display(sp::Subplot{GRBackend}, w, h, viewport_canvas) elseif st == :heatmap zmin, zmax = clims + nx, ny = length(series[:x]), length(series[:y]) + use_midpoints = length(z) == ny * nx if !ispolar(sp) - xmin, xmax, ymin, ymax = xy_lims - m, n = length(x), length(y) GR.setspace(zmin, zmax, 0, 90) - grad = isa(series[:fillcolor], ColorGradient) ? series[:fillcolor] : cgrad() - colors = [plot_color(grad[clamp((zi-zmin) / (zmax-zmin), 0, 1)], series[:fillalpha]) for zi=z] - rgba = map(c -> UInt32( round(UInt, alpha(c) * 255) << 24 + - round(UInt, blue(c) * 255) << 16 + - round(UInt, green(c) * 255) << 8 + - round(UInt, red(c) * 255) ), colors) - w, h = length(x), length(y) - GR.drawimage(xmin, xmax, ymax, ymin, w, h, rgba) + x, y = if use_midpoints + x_diff, y_diff = diff(series[:x]) ./ 2, diff(series[:y]) ./ 2 + x = [ series[:x][1] - x_diff[1], (series[:x][1:end-1] .+ x_diff)..., series[:x][end] + x_diff[end] ] + y = [ series[:y][1] - y_diff[1], (series[:y][1:end-1] .+ y_diff)..., series[:y][end] + y_diff[end] ] + x, y + else + series[:x], series[:y] + end + w, h = length(x) - 1, length(y) - 1 + z_normalized = map(x -> GR.jlgr.normalize_color(x, zmin, zmax), z) + colors = Int32[round(Int32, 1000 + _i * 255) for _i in z_normalized] + GR.nonuniformcellarray(x, y, w, h, colors) else - h, w = length(x), length(y) - z = reshape(z, h, w) - colors = Int32[round(Int32, 1000 + _i * 255) for _i in z'] - GR.setwindow(-1, 1, -1, 1) - GR.polarcellarray(0, 0, 0, 360, 0, 1, w, h, colors) + phimin, phimax = 0.0, 360.0 # nonuniform polar array is not yet supported in GR.jl + z_normalized = map(x -> GR.jlgr.normalize_color(x, zmin, zmax), z) + colors = Int32[round(Int32, 1000 + _i * 255) for _i in z_normalized] + xmin, xmax, ymin, ymax = xy_lims + rmax = data_lims[4] + GR.setwindow(-rmax, rmax, -rmax, rmax) + if ymin > 0 + @warn "'ymin[1] > 0' (rmin) is not yet supported." + end + @show series[:y][end] + if series[:y][end] != ny + @warn "Right now only the maximum value of y (r) is taken into account." + end + # GR.polarcellarray(0, 0, phimin, phimax, ymin, ymax, nx, ny, colors) + GR.polarcellarray(0, 0, phimin, phimax, 0, ymax, nx, ny, colors) + # Right now only the maximum value of y (r) is taken into account. + # This is certainly not perfect but nonuniform polar array is not yet supported in GR.jl end elseif st in (:path3d, :scatter3d) From 7c5b7b09c287c4e7e72650c3f125754e15445768 Mon Sep 17 00:00:00 2001 From: Lukas Hauertmann Date: Mon, 4 Nov 2019 21:29:35 +0100 Subject: [PATCH 2/6] Add keyword `isedges::Bool = false` to function `heatmap_edges` Add the keyword `isedges::Bool` to the functions `heatmap_edges` and `_heatmap_edges`. Default is `false`. If `true`, the functions treat the given vector `v` as edges and not as midpoints. --- src/utils.jl | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/src/utils.jl b/src/utils.jl index 0c2b4b80..bd127f6c 100644 --- a/src/utils.jl +++ b/src/utils.jl @@ -342,8 +342,9 @@ const _scale_base = Dict{Symbol, Real}( :ln => ℯ, ) -function _heatmap_edges(v::AVec) +function _heatmap_edges(v::AVec, isedges::Bool = false) length(v) == 1 && return v[1] .+ [-0.5, 0.5] + if isedges return v end vmin, vmax = ignorenan_extrema(v) extra_min = (v[2] - v[1]) / 2 extra_max = (v[end] - v[end - 1]) / 2 @@ -351,9 +352,9 @@ function _heatmap_edges(v::AVec) end "create an (n+1) list of the outsides of heatmap rectangles" -function heatmap_edges(v::AVec, scale::Symbol = :identity) +function heatmap_edges(v::AVec, scale::Symbol = :identity; isedges::Bool = false) f, invf = scalefunc(scale), invscalefunc(scale) - map(invf, _heatmap_edges(map(f,v))) + map(invf, _heatmap_edges(map(f,v), isedges)) end function convert_to_polar(theta, r, r_extrema = ignorenan_extrema(r)) From 00fd916595a0e31a2717a6f12e57f8d151f1ec6a Mon Sep 17 00:00:00 2001 From: Lukas Hauertmann Date: Mon, 4 Nov 2019 21:30:11 +0100 Subject: [PATCH 3/6] Remove code duplication Use the function `heatmap_edges` again --- src/backends/gr.jl | 22 ++++------------------ 1 file changed, 4 insertions(+), 18 deletions(-) diff --git a/src/backends/gr.jl b/src/backends/gr.jl index ef05550b..6fa33080 100644 --- a/src/backends/gr.jl +++ b/src/backends/gr.jl @@ -921,7 +921,6 @@ function gr_display(sp::Subplot{GRBackend}, w, h, viewport_canvas) for ax in (sp[:xaxis], sp[:yaxis]) v = series[ax[:letter]] end - fx, fy = scalefunc(sp[:xaxis][:scale]), scalefunc(sp[:yaxis][:scale]) nx, ny = length(series[:x]), length(series[:y]) z = series[:z] use_midpoints = size(z) == (ny, nx) @@ -931,15 +930,8 @@ function gr_display(sp::Subplot{GRBackend}, w, h, viewport_canvas) Must be either `size(z) == (length(y), length(x))` (x & y define midpoints) or `size(z) == (length(y)+1, length(x)+1))` (x & y define edges).""") end - x, y = if use_midpoints - x_diff, y_diff = diff(series[:x]) ./ 2, diff(series[:y]) ./ 2 - x = [ series[:x][1] - x_diff[1], (series[:x][1:end-1] .+ x_diff)..., series[:x][end] + x_diff[end] ] - y = [ series[:y][1] - y_diff[1], (series[:y][1:end-1] .+ y_diff)..., series[:y][end] + y_diff[end] ] - x, y - else - series[:x], series[:y] - end - x, y = map(fx, series[:x]), map(fy, series[:y]) + x, y = heatmap_edges(series[:x], sp[:xaxis][:scale]; isedges = use_edges), + heatmap_edges(series[:y], sp[:yaxis][:scale]; isedges = use_edges) xy_lims = x[1], x[end], y[1], y[end] expand_extrema!(sp[:xaxis], x) expand_extrema!(sp[:yaxis], y) @@ -1335,14 +1327,8 @@ function gr_display(sp::Subplot{GRBackend}, w, h, viewport_canvas) use_midpoints = length(z) == ny * nx if !ispolar(sp) GR.setspace(zmin, zmax, 0, 90) - x, y = if use_midpoints - x_diff, y_diff = diff(series[:x]) ./ 2, diff(series[:y]) ./ 2 - x = [ series[:x][1] - x_diff[1], (series[:x][1:end-1] .+ x_diff)..., series[:x][end] + x_diff[end] ] - y = [ series[:y][1] - y_diff[1], (series[:y][1:end-1] .+ y_diff)..., series[:y][end] + y_diff[end] ] - x, y - else - series[:x], series[:y] - end + x, y = heatmap_edges(series[:x], sp[:xaxis][:scale]; isedges = !use_midpoints), + heatmap_edges(series[:y], sp[:yaxis][:scale]; isedges = !use_midpoints) w, h = length(x) - 1, length(y) - 1 z_normalized = map(x -> GR.jlgr.normalize_color(x, zmin, zmax), z) colors = Int32[round(Int32, 1000 + _i * 255) for _i in z_normalized] From a728ed9a60f0b83da19c461d126c87d0524d4d4d Mon Sep 17 00:00:00 2001 From: Lukas Hauertmann Date: Tue, 5 Nov 2019 00:06:33 +0100 Subject: [PATCH 4/6] Add new method for `heatmap_edges` New method check input vectors for x and y in compatibility with the 2D input array z. It also decides whether x and y represend the midpoints or the egdes of the heatmap pixels. --- src/backends/gr.jl | 19 ++++--------------- src/utils.jl | 16 ++++++++++++++++ 2 files changed, 20 insertions(+), 15 deletions(-) diff --git a/src/backends/gr.jl b/src/backends/gr.jl index 6fa33080..50d67f16 100644 --- a/src/backends/gr.jl +++ b/src/backends/gr.jl @@ -921,17 +921,7 @@ function gr_display(sp::Subplot{GRBackend}, w, h, viewport_canvas) for ax in (sp[:xaxis], sp[:yaxis]) v = series[ax[:letter]] end - nx, ny = length(series[:x]), length(series[:y]) - z = series[:z] - use_midpoints = size(z) == (ny, nx) - use_edges = size(z) == (ny - 1, nx - 1) - if !use_midpoints && !use_edges - error("""Length of x & y does not match the size of z. - Must be either `size(z) == (length(y), length(x))` (x & y define midpoints) - or `size(z) == (length(y)+1, length(x)+1))` (x & y define edges).""") - end - x, y = heatmap_edges(series[:x], sp[:xaxis][:scale]; isedges = use_edges), - heatmap_edges(series[:y], sp[:yaxis][:scale]; isedges = use_edges) + x, y = heatmap_edges(series[:x], sp[:xaxis][:scale], series[:y], sp[:yaxis][:scale], size(series[:z])) xy_lims = x[1], x[end], y[1], y[end] expand_extrema!(sp[:xaxis], x) expand_extrema!(sp[:yaxis], y) @@ -1323,18 +1313,17 @@ function gr_display(sp::Subplot{GRBackend}, w, h, viewport_canvas) elseif st == :heatmap zmin, zmax = clims - nx, ny = length(series[:x]), length(series[:y]) - use_midpoints = length(z) == ny * nx if !ispolar(sp) GR.setspace(zmin, zmax, 0, 90) - x, y = heatmap_edges(series[:x], sp[:xaxis][:scale]; isedges = !use_midpoints), - heatmap_edges(series[:y], sp[:yaxis][:scale]; isedges = !use_midpoints) + x, y = heatmap_edges(series[:x], sp[:xaxis][:scale], series[:y], sp[:yaxis][:scale], size(series[:z])) w, h = length(x) - 1, length(y) - 1 z_normalized = map(x -> GR.jlgr.normalize_color(x, zmin, zmax), z) + z_normalized = map(x -> isnan(x) ? 256/255 : x, z_normalized) # results in color index = 1256 -> transparent colors = Int32[round(Int32, 1000 + _i * 255) for _i in z_normalized] GR.nonuniformcellarray(x, y, w, h, colors) else phimin, phimax = 0.0, 360.0 # nonuniform polar array is not yet supported in GR.jl + nx, ny = length(series[:x]), length(series[:y]) z_normalized = map(x -> GR.jlgr.normalize_color(x, zmin, zmax), z) colors = Int32[round(Int32, 1000 + _i * 255) for _i in z_normalized] xmin, xmax, ymin, ymax = xy_lims diff --git a/src/utils.jl b/src/utils.jl index bd127f6c..cd7bb414 100644 --- a/src/utils.jl +++ b/src/utils.jl @@ -357,6 +357,22 @@ function heatmap_edges(v::AVec, scale::Symbol = :identity; isedges::Bool = false map(invf, _heatmap_edges(map(f,v), isedges)) end +function heatmap_edges(x::AVec, xscale::Symbol, y::AVec, yscale::Symbol, z_size::Tuple{Int, Int}) + nx, ny = length(x), length(y) + # use_midpoints = z_size == (ny, nx) # This fails some tests, but would actually be + # the correct check, since (4, 3) != (3, 4) and a missleading plot is produced. + use_midpoints = prod(z_size) == (ny * nx) + use_edges = z_size == (ny - 1, nx - 1) + if !use_midpoints && !use_edges + error("""Length of x & y does not match the size of z. + Must be either `size(z) == (length(y), length(x))` (x & y define midpoints) + or `size(z) == (length(y)+1, length(x)+1))` (x & y define edges).""") + end + x, y = heatmap_edges(x, xscale; isedges = use_edges), + heatmap_edges(y, yscale; isedges = use_edges) + return x, y +end + function convert_to_polar(theta, r, r_extrema = ignorenan_extrema(r)) rmin, rmax = r_extrema r = (r .- rmin) ./ (rmax .- rmin) From 686ab1b51f59bbbe9ab2c1f15bb6f8986a7220af Mon Sep 17 00:00:00 2001 From: Lukas Hauertmann Date: Tue, 5 Nov 2019 11:12:46 +0100 Subject: [PATCH 5/6] `isedges` is now an arguement and not a keyword anymore --- src/utils.jl | 18 ++++++++++-------- 1 file changed, 10 insertions(+), 8 deletions(-) diff --git a/src/utils.jl b/src/utils.jl index cd7bb414..324aa2cb 100644 --- a/src/utils.jl +++ b/src/utils.jl @@ -344,7 +344,9 @@ const _scale_base = Dict{Symbol, Real}( function _heatmap_edges(v::AVec, isedges::Bool = false) length(v) == 1 && return v[1] .+ [-0.5, 0.5] - if isedges return v end + if isedges return v end + # `isedges = true` means that v is a vector which already describes edges + # and does not need to be extended. vmin, vmax = ignorenan_extrema(v) extra_min = (v[2] - v[1]) / 2 extra_max = (v[end] - v[end - 1]) / 2 @@ -352,24 +354,24 @@ function _heatmap_edges(v::AVec, isedges::Bool = false) end "create an (n+1) list of the outsides of heatmap rectangles" -function heatmap_edges(v::AVec, scale::Symbol = :identity; isedges::Bool = false) +function heatmap_edges(v::AVec, scale::Symbol = :identity, isedges::Bool = false) f, invf = scalefunc(scale), invscalefunc(scale) map(invf, _heatmap_edges(map(f,v), isedges)) end function heatmap_edges(x::AVec, xscale::Symbol, y::AVec, yscale::Symbol, z_size::Tuple{Int, Int}) nx, ny = length(x), length(y) - # use_midpoints = z_size == (ny, nx) # This fails some tests, but would actually be + # ismidpoints = z_size == (ny, nx) # This fails some tests, but would actually be # the correct check, since (4, 3) != (3, 4) and a missleading plot is produced. - use_midpoints = prod(z_size) == (ny * nx) - use_edges = z_size == (ny - 1, nx - 1) - if !use_midpoints && !use_edges + ismidpoints = prod(z_size) == (ny * nx) + isedges = z_size == (ny - 1, nx - 1) + if !ismidpoints && !isedges error("""Length of x & y does not match the size of z. Must be either `size(z) == (length(y), length(x))` (x & y define midpoints) or `size(z) == (length(y)+1, length(x)+1))` (x & y define edges).""") end - x, y = heatmap_edges(x, xscale; isedges = use_edges), - heatmap_edges(y, yscale; isedges = use_edges) + x, y = heatmap_edges(x, xscale, isedges), + heatmap_edges(y, yscale, isedges) return x, y end From 36558389b6fd6e792bbc504c416d7f6acde7af64 Mon Sep 17 00:00:00 2001 From: Lukas Hauertmann Date: Tue, 5 Nov 2019 11:51:34 +0100 Subject: [PATCH 6/6] remove debugging line --- src/backends/gr.jl | 1 - 1 file changed, 1 deletion(-) diff --git a/src/backends/gr.jl b/src/backends/gr.jl index 50d67f16..e7ab5209 100644 --- a/src/backends/gr.jl +++ b/src/backends/gr.jl @@ -1332,7 +1332,6 @@ function gr_display(sp::Subplot{GRBackend}, w, h, viewport_canvas) if ymin > 0 @warn "'ymin[1] > 0' (rmin) is not yet supported." end - @show series[:y][end] if series[:y][end] != ny @warn "Right now only the maximum value of y (r) is taken into account." end