Created
January 28, 2026 12:43
-
-
Save jkrumbiegel/777fbe21a6b9d65e712aee8c534a2306 to your computer and use it in GitHub Desktop.
Makie Table widget
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| 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