Mobile
A Squarified Treemap by Hand, Because Charting Packages Cannot Drill Down
K M Shahriar Hossain DEV Community
1 views
Helm's storage tool shows your disk as a treemap: every folder a rectangle,
every rectangle sized by how much space it takes. Click one and you are inside
it, looking at its children, with a breadcrumb trail back out. It goes from a
494 GB volume down to a single file.
I drew it by hand. Not because the packages are bad, but because the thing that
makes a treemap useful for disk space is the part they do not expose.
What the packages give you, and what they don't
Search for a Flutter treemap and you will find several that work. Hand them a
list of values, get back a coloured rectangle layout. For a dashboard showing
revenue by region, that is the whole job.
A disk browser needs four things beyond that, and each one reaches into the
layout rather than sitting on top of it:
Hit-testing that returns the node, not a coordinate. A tap has to resolve
to which folder, at whatever depth you are currently at.
Navigation as a first-class state. Diving into a rectangle re-lays out the
entire canvas from a new root, and the breadcrumb has to be able to climb back.
Labels that adapt. A rectangle 400 px wide gets a name and a size. One
20 px wide gets nothing, because a clipped half-word is worse than blank space.
Layout over a subtree, not a list. The input is not fifty values, it is a
tree with a million nodes, and you lay out one level at a time.
Any one of those is a fork of the package. All four means you are writing it
anyway, and you would rather own it than fight an abstraction built for a
different problem.
Squarified, and why it is worth the trouble
The naive treemap slices the rectangle repeatedly along one axis. It is easy and
it produces slivers — long thin shapes that are impossible to read, impossible
to tap, and misleading, because human eyes judge area badly when the aspect
ratio is extreme.
The squarified algorithm keeps rectangles as close to square as it can. The idea
is simple enough to hold in your head: sort children largest first, then add
them one at a time to the current row while doing so improves the worst aspect
ratio in that row. The moment adding one makes the worst ratio worse, close the
row, and start a new one in the remaining space.
That is the entire algorithm. Sort descending, accumulate greedily, close the
row when the worst ratio stops improving, recurse into the space that is left.
Two implementation notes that cost me time:
Sort descending or the greedy step is meaningless. The whole method assumes
you place the biggest item first. Feed it unsorted input and it still produces
a layout — a bad one, with no error to tell you why.
Guard against zero. Empty folders, and the remaining space after the last
row, both produce zero-width or zero-height rectangles, which turn into
divide-by-zero in the ratio computation and NaN in the layout. NaN
propagates silently through a layout pass and you get an empty canvas with no
exception, which is a genuinely unpleasant thing to debug.
Making it fast enough over a real disk
Scanning a 347 GB volume walks several hundred thousand files. Two things keep
that from freezing the UI, and neither is about the treemap itself.
Scan off the main isolate. Walking a filesystem is not CPU-heavy so much as
relentless — hundreds of thousands of stat calls. Done on the main isolate it
blocks the frame loop, and the app appears hung exactly while it is doing the
work the user asked for.
Lay out one level at a time. The tree has a million nodes; the screen shows
a few dozen rectangles. There is no reason to lay out anything but the current
level's children. Diving in is a fresh layout over a smaller subtree, which is
why it stays instant no matter how deep you go — the work is bounded by what is
visible, not by what exists.
The second point is the one that also solves the label problem. Because you only
lay out the visible level, you know each rectangle's real pixel size at paint
time, so deciding whether a name fits is a measurement rather than a guess.
The bit that has nothing to do with drawing
The hardest part of Helm's storage tool is not the treemap. It is making the
numbers agree with the operating system.
macOS reports purgeable space — snapshots and caches the system will reclaim
when it needs to. If you sum file sizes and ignore purgeable, your total
disagrees with About This Mac, and when a user sees two numbers they believe the
one from Apple. Correctly, too: yours is the one that is wrong.
So the categories have to be disjoint buckets that reconcile to the volume's
reported capacity, purgeable included as its own slice rather than quietly
dropped or quietly counted as used. A beautiful visualisation of numbers the
user does not trust is worth nothing, and trust here is a single comparison
against a system dialog they already know.
Was it worth writing?
For a dashboard, no — use a package. For anything where the treemap is the
interface rather than a picture of the data, you will end up writing it, because
drill-down, hit-testing and adaptive labels all live below the API surface that
a general charting package exposes.
The reward is that it is genuinely the fastest way to answer "what is eating my
disk". A list of the largest folders makes you read and compare. A treemap makes
the answer the biggest thing on screen.
Helm is free and MIT, and the treemap is in
lib/tools/storage/ui/widgets/treemap.dart if you want to read it rather than
reimplement it: github.com/devShakib015/helm.
Originally published at devshakib.jumyn.com. I write about Flutter, Dart and the parts of shipping that are genuinely awkward — and publish the packages that came out of them at pub.dev/publishers/jumyn.com.
Read original: https://dev.to/devshakib/a-squarified-treemap-by-hand-because-charting-packages-cannot-drill-down-353g
← Previous
The DNS Field That Could Run Anything as Root
Next →
Your Flutter 404 Page Is Probably Crashing, and Your Server Is Probably Lying About It
Related
What It Actually Takes to Run a Cross-Border Marketplace: Six Years of Shpper
Mobile
0
DEV Community
I kept failing interviews on delivery, so I built a practice coach that scores eye contact
Mobile
0
DEV Community
JSON to Kotlin data class: Gson vs Moshi vs kotlinx.serialization annotations
Mobile
3
Dev.to (EN Zone)
I'm 12. I paused KODA Browser to try something I've never done before.
Mobile
3
DEV Community
Comments0
No comments yet — be the first