Lattice Grid

blog

Treemap of Disk Usage by Folder

Building…
Loading a live grid…

If you are the storage admin, the question is not how full the volume is. It is which folders take the space, and how much of it nobody has touched in years, so you know what to archive before you buy more disk.

A treemap answers that. Each folder is a tile whose area is its size, and the tiles sit inside a branch for their age band. The chart above is bound to the grid beneath it, one row per folder, so filtering the rows redraws the chart. Filter the Age column to Over 2 years and only the stale folders are left on the canvas.

Why a treemap and not a pie

A pie cannot hold thirty folders and cannot nest them. A treemap spends its whole area on the sizes, so a few large folders dominate at once, and it nests, so each folder keeps its place inside its age band.

The chart over a grid

The chart reads the grid’s rows, and the tree it draws is the grid’s own grouping. Group the grid by the age band and then the folder, and the treemap has one branch per band with a tile per folder inside it. The measure, y, sizes the tiles. Each branch takes its own colour, and the legend names the age bands. Here is the example in plain JavaScript, with sample rows inline in place of your own.

import { createGrid } from '@toclocoinc/lattice-grid';
import '@toclocoinc/lattice-grid/css';
import { createChart } from '@toclocoinc/lattice-grid/modules/charts';

const rows = [
  { id: 1, path: '/shares/it/backups', gb: 910, modified: '2026-11-12', age: 'Under 1 year' },
  { id: 2, path: '/shares/design/renders', gb: 640, modified: '2026-11-04', age: 'Under 1 year' },
  { id: 3, path: '/shares/engineering/releases', gb: 345, modified: '2025-07-15', age: '1 to 2 years' },
  { id: 4, path: '/shares/support/call-recordings', gb: 690, modified: '2024-06-11', age: 'Over 2 years' },
  { id: 5, path: '/shares/finance/archive', gb: 412, modified: '2023-02-14', age: 'Over 2 years' },
];
const gridEl = document.createElement('div');
const chartEl = document.createElement('div');
gridEl.style.height = '240px';
chartEl.style.height = '360px';
document.body.append(gridEl, chartEl);

const grid = createGrid(gridEl, {
  rowKey: 'id', rows, groupDefaultExpanded: false,
  columns: [
    { field: 'age', title: 'Age', filter: { type: 'set' }, group: { enabled: true, index: 0 } },
    { field: 'path', title: 'Folder', group: { enabled: true, index: 1 } },
    { field: 'gb', title: 'Size (GB)', type: 'number', total: 'sum' },
    { field: 'modified', title: 'Last modified' },
  ],
});
createChart({ grid, container: chartEl, type: 'treemap', y: 'gb',
  title: 'Disk usage by folder, grouped by age' });

The same thing in React

The React adapter is a factory, so you build the component once at module scope. The ref’s grid is the real grid, which is what the chart binds to.

import React, { useEffect, useRef } from 'react';
import { createGrid } from '@toclocoinc/lattice-grid';
import '@toclocoinc/lattice-grid/css';
import { createLatticeGrid } from '@toclocoinc/lattice-grid/modules/react';
import { createChart } from '@toclocoinc/lattice-grid/modules/charts';

const LatticeGrid = createLatticeGrid({ React, createGrid });
const rows = [
  { id: 1, path: '/shares/it/backups', gb: 910, modified: '2026-11-12', age: 'Under 1 year' },
  { id: 2, path: '/shares/design/renders', gb: 640, modified: '2026-11-04', age: 'Under 1 year' },
  { id: 3, path: '/shares/engineering/releases', gb: 345, modified: '2025-07-15', age: '1 to 2 years' },
  { id: 4, path: '/shares/support/call-recordings', gb: 690, modified: '2024-06-11', age: 'Over 2 years' },
];
const columns = [
  { field: 'age', title: 'Age', filter: { type: 'set' }, group: { enabled: true, index: 0 } },
  { field: 'path', title: 'Folder', group: { enabled: true, index: 1 } },
  { field: 'gb', title: 'Size (GB)', type: 'number', total: 'sum' },
];

export function DiskTreemap() {
  const gridRef = useRef(null);
  const chartRef = useRef(null);
  useEffect(() => {
    const chart = createChart({ grid: gridRef.current.grid, container: chartRef.current,
      type: 'treemap', y: 'gb' });
    return () => chart.destroy();
  }, []);
  return (<>
    <LatticeGrid ref={gridRef} rowKey="id" columns={columns} rows={rows}
      groupDefaultExpanded={false} style={{ height: 200 }} />
    <div ref={chartRef} style={{ height: 320 }} />
  </>);
}

The options used

These are the rows of the charts reference for the options in the listings above.

Option Type What it does
grid Grid Required. The grid to read.
container Element or string Required. Where to draw.
type string 'treemap' here, nested tiles sized by the measure.
y string The measure column. Here it is the folder size, which sets each tile’s area.
title string Drawn above the plot.

The tree itself comes from the grid’s grouping, one level per grouped column in order, so x is not used.

Reading the canvas

The bigger the tile, the more space the folder takes, and the branch it sits in is its age. In the example, the Over 2 years branch holds several large folders that nobody has opened in that long, which is the space to archive first. A big tile in the Under 1 year branch is large but in use, so it is a question for the next purchase and not for a clean-up.

Next

See the treemap beside the other part-to-whole charts in the bar, line and pie family demo, read the full list of chart types on the charts page, or follow the how-to for a chart that follows the grid.

Read next

  • Sunburst Chart of Spend by Department

    Bind a sunburst chart to a grid of spend by department, cost centre and vendor: three rings in one chart, and filtering the rows redraws them.

  • Donut Chart of Budget Used Against Remaining

    Bind a donut chart to a grid of budget lines: show spend by category with the remaining budget in the centre, and filter the rows to redraw both.

  • Pie Chart of Revenue Share by Channel

    Bind a pie chart to a grid of revenue by channel: show each channel as a share of the quarter, and filter the rows to redraw the slices.

All posts RSS