Skip to content

Instantly share code, notes, and snippets.

@jkrumbiegel
Created January 28, 2026 12:43
Show Gist options
  • Select an option

  • Save jkrumbiegel/777fbe21a6b9d65e712aee8c534a2306 to your computer and use it in GitHub Desktop.

Select an option

Save jkrumbiegel/777fbe21a6b9d65e712aee8c534a2306 to your computer and use it in GitHub Desktop.
Makie Table widget
using GLMakie
##
@recipe TextTable (origin,) begin
text = String[;;] # size = (rows, columns)
cell_padding = Vec4f(5, 5, 2, 2)
colsize = Makie.automatic # could be a vector
rowsize = Makie.automatic # could be a vector
default_colsize = 70
default_rowsize = 20
cell_color = :white # could be a matrix
text_color = :black # could be a matrix
end
function Makie.plot!(t::TextTable)
map!(
t,
[:text, :origin, :cell_padding, :rowsize, :colsize, :default_rowsize, :default_colsize, :cell_color, :text_color],
[:text_vec, :positions, :row_edges, :col_edges, :markersizes, :cell_colors, :text_colors]
) do text, origin, cell_padding, rowsize, colsize, default_rowsize, default_colsize, cell_color, text_color
sz = size(text)
actual_colsizes = colsize === Makie.automatic ? fill(default_colsize + cell_padding[1] + cell_padding[2], sz[2]) : colsize
actual_rowsizes = rowsize === Makie.automatic ? fill(default_rowsize + cell_padding[3] + cell_padding[4], sz[1]) : rowsize
col_edges = cumsum([0; actual_colsizes])
row_edges = cumsum([0; -actual_rowsizes])
positions = [Point3f(origin..., 0) + Point3f(col_edges[idx[2]], row_edges[idx[1]], idx[1] + idx[2]) for idx in vec(CartesianIndices(text))]
markersizes = [Vec2f(actual_colsizes[idx[2]], actual_rowsizes[idx[1]]) for idx in vec(CartesianIndices(text))]
cell_colors = cell_color isa AbstractMatrix ? vec(cell_color) : fill(cell_color, prod(sz))
text_colors = text_color isa AbstractMatrix ? vec(text_color) : fill(text_color, prod(sz))
return vec(text), positions, row_edges, col_edges, markersizes, cell_colors, text_colors
end
map!(t, [:cell_padding], [:text_offset]) do cell_padding
return (Vec2f(cell_padding[1], -cell_padding[4]),)
end
map!(t, [:origin, :row_edges, :col_edges], [:gridsegments]) do origin, row_edges, col_edges
gridsegments = Point2f[]
for re in row_edges
push!(gridsegments, origin + Point2f(col_edges[1], re), origin + Point2f(col_edges[end], re))
end
for ce in col_edges
push!(gridsegments, origin + Point2f(ce, row_edges[1]), origin + Point2f(ce, row_edges[end]))
end
return (gridsegments,)
end
scatter!(t, t.positions, marker = BezierPath([MoveTo(0, 0), LineTo(1, 0), LineTo(1, -1), LineTo(0, -1), ClosePath()]), markersize = t.markersizes,
color = t.cell_colors)
linesegments!(t, t.gridsegments, overdraw = true, color = :gray90)
text!(t, t.positions; text = t.text_vec, align = (:left, :top), offset = t.text_offset, color = t.text_colors)
return t
end
function auto_cell_sizes!(t::TextTable)
txt = t.plots[3]::Makie.Text
bbs = reshape(Makie.string_boundingboxes(txt), size(t.text[]))
ws = [Vec2f(b.widths) for b in bbs]
pd::Vec4f = t.cell_padding[]
max_widths = vec(maximum(w -> w[1], ws; dims = 1)) .+ pd[1] .+ pd[2]
max_heights = vec(maximum(w -> w[2], ws; dims = 2)) .+ pd[3] .+ pd[4]
update!(t, colsize = max_widths, rowsize = max_heights)
end
s = Scene(camera = campixel!)
topleft = lift(s.viewport) do vp
return Point2f(vp.origin[1], vp.origin[2] + vp.widths[2])
end
tt = texttable!(
s,
topleft;
text = string.(rand(names(Base), 30, 30)),
cell_color = ones(RGBf, 30, 30),
)
auto_cell_sizes!(tt)
scroll_offset = Observable(Vec2f(0, 0))
on(s.events.scroll) do scr
colwidth = tt.col_edges[][end]
rowheight = abs(tt.row_edges[][end])
available_size = s.viewport[].widths
max_offset = (1, -1) .* min.(0, available_size .- (colwidth, rowheight))
scroll_offset[] = clamp.(scroll_offset[] .+ (1, -1) .* 30 .* scr, (max_offset[1], 0), (0, max_offset[2]))
end
on(scroll_offset) do off
translate!(s, off)
end
me = addmouseevents!(s, tt)
function mousepos_to_indices(pos)
p = pos .- scroll_offset[] .- tt.origin[]
re = tt.row_edges[]
ce = tt.col_edges[]
i_row = findfirst(eachindex(re)[1:end-1]) do i
re[i] >= p[2] > re[i+1]
end
i_row === nothing && return nothing
i_col = findfirst(eachindex(ce)[1:end-1]) do i
ce[i] <= p[1] < ce[i+1]
end
i_col === nothing && return nothing
return i_row, i_col
end
onmouserightclick(me) do event
p = mousepos_to_indices(event.data)
p === nothing && return
cell_colors = copy(tt.cell_color[])
cell_colors[p...] = rand(RGBf)
tt.cell_color = cell_colors
end
onmouseleftclick(me) do event
p = mousepos_to_indices(event.data)
p === nothing && return
strs = copy(tt.text[])
strs[p...] = string(rand(names(Base)))
tt.text[] = strs
return
end
s
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment