Drawing histograms


When I last showed how one might draw lots of different kinds of charts in Lua Carousel, I mentioned that histograms were still an open question. I think I have a reasonable way now to work in support for them.

A suitable point of take-off is the `boxes` chart I showed earlier.

function boxes(data, i, x, y, w)
  local w = optional(w, data, i, 5)
  local y = y(data, i)
  rect('fill', vx(x(data, i)-w/2), vy(y), scale(w), vy(0) - vy(y))
end

You call it like this:

color(0.7,0.7,1)
plot(data, boxes, col(1), col(2), 4)

on some test data like this:

data = {}
for i=1,16 do
  table.insert(data, {i*5, rand(60), rand(60)})
end

And you get a picture like this:

(I'm glossing over 40 lines of code, but they're almost identical to my previous post. You can see them at the bottom.)

A histogram is similar, but shows bars in clusters. Here's a new chart type:

function hbox(data, i, cluster, ncluster, y, w)
  local w = optional(w, data, i, 5)
  local clusterw = ncluster*w
  local x = (i-1)*clusterw + cluster*w
  local y = y(data, i)
  rect('fill', vx(x), vy(y), scale(w), vy(0) - vy(y))
end

This draws just one bar in each cluster, but it lets you string clusters together. For example, consider some example data containing 4 different (labeled) measures in each row:

data = {}
for i=1,3 do
  table.insert(data, {a=rand(30), b=rand(30), c=rand(30), d=rand(30)})
end

(A realistic example might name the measures say 'revenue', 'costs' and 'profit' rather than a/b/c/d.)

We can plot this data as a histogram like so:

color(0.7,0.7,1)
plot(data, hbox, 1,5, col('a'))
color(0.5,0.8,0.8)
plot(data, hbox, 2,5, col('b'))
color(0.6,0.9,0.6)
plot(data, hbox, 3,5, col('c'))
color(1,0.7,0.7)
plot(data, hbox, 4,5, col('d'))

Each set of bars gets an offset from 1 to 4, and 5 is left empty between clusters. The result looks like the image up top:For stacking bars atop one another we need to make hbox stateful as follows:

function hbox(data, i, cluster, ncluster, y, w)
  local w = optional(w, data, i, 5)
  local clusterw = ncluster*w
  local x = (i-1)*clusterw + cluster*w
  local y = y(data, i)
  local base = data[i].offsets[cluster] or 0
  y = y + base
  rect('fill', vx(x), vy(y), scale(w), vy(base) - vy(y))
  data[i].offsets[cluster] = y
end

We're accumulating each stack as we render it, which allows us to draw clusters like this:

function clear_stack(data)
  for i=1,#data do data[i].offsets = {} end
end
clear_stack(data)
color(0.7,0.7,1)
plot(data, hbox, 1,4, col('a'))
color(0.5,0.85,85)
plot(data, hbox, 1,4, col('b'))
color(0.6,0.9,0.6)
plot(data, hbox, 2,4, col('c'))
color(1,0.7,0.7)
plot(data, hbox, 3,4, col('d'))

Repeated calls to `plot(..., 1,4, ...)` will continue to stack up until an intervening call to `clear_stack()`. The result looks like this:

Here's all of the code for drawing histograms with stacked support, including the usual Carousel abbreviations I use. Importantly, we had to make no changes to helpers like `plot` and `col` from the previous post.

g = love.graphics
line, rect = g.line, g.rectangle
color = g.setColor
rand = math.random
-- decide how large a 80:60 image you can fit
H = Safe_height-Menu_bottom
if Safe_width/80 < H/60 then
  -- landscape
  v = {left=50, right=Safe_width-50}
  local h = (v.right-v.left)/80*60
  v.top = Menu_bottom + (H-h)/2
  v.bottom = v.top + h
else
  -- portrait
  v = {top=Menu_bottom+50, bottom=Safe_height-50}
  local w = (v.bottom-v.top)/60*80
  v.left = (Safe_width-w)/2
  v.right = v.left + w
end
-- at this point, 80/(v.right-v.left) == 60/(v.bottom-v.top)
line(v.left, v.top, v.left, v.bottom)
line(v.left, v.bottom, v.right, v.bottom)
function vx(x) return v.left + scale(x) end
function vy(y) return v.bottom - scale(y) end
function scale(d) return d/80*(v.right-v.left) end
function col(c)
  return function(data, i) return data[i][c] end
end
function plot(data, f, ...)
  for i=1,#data do f(data, i, ...) end
end
function optional(f, data, i, default)
  if f == nil then return default end
  if type(f) == 'function' then return f(data, i) end
  return f
end
data = {}
for i=1,3 do
  table.insert(data, {a=rand(30), b=rand(30), c=rand(30), d=rand(30)})
end
function clear_stack(data)
  for i=1,#data do data[i].offsets = {} end
end
function hbox(data, i, cluster, ncluster, y, w)
  local w = optional(w, data, i, 5)
  local clusterw = ncluster*w
  local x = (i-1)*clusterw + cluster*w
  local y = y(data, i)
  local base = data[i].offsets[cluster] or 0
  y = y + base
  rect('fill', vx(x), vy(y), scale(w), vy(base) - vy(y))
  data[i].offsets[cluster] = y
end
clear_stack(data)
color(0.7,0.7,1)
plot(data, hbox, 1,4, col('a'))
color(0.5,0.85,85)
plot(data, hbox, 1,4, col('b'))
color(0.6,0.9,0.6)
plot(data, hbox, 2,4, col('c'))
color(1,0.7,0.7)
plot(data, hbox, 3,4, col('d'))

Get Lua Carousel

Leave a comment

Log in with itch.io to leave a comment.