The wallet tracker got real charts this week. They were built on a machine with no screen, which forced two habits that turned out to be better than the usual way.
Decide the form before the color
Most bad charts start with a color palette. The better order is to ask what the reader has to do with the data, let that pick the chart type, and only then think about color.
Allocation by asset is "compare named things by size." That is a horizontal bar chart, and because the names have no natural order, every bar gets the same single hue. Coloring each bar darker where it is bigger would spend the only free visual channel re-encoding what the bar length already shows.
Holdings by chain is "what share of the whole is each part." That is one stacked bar, split by chain, and this time the segments genuinely need distinct hues because the chain is the identity.
Income by month is a value per period, so columns. Cumulative income is a running total, so a line with a light wash underneath. Progress toward the mining payout threshold is a single ratio against a limit, so a meter, not a chart.
Five forms, each picked by the job. Color came last, and by then most of it was already decided.
Compute the color decisions
The stacked bar needs up to eight distinguishable hues on a dark surface, readable by people with the common forms of color-vision deficiency, each clearing a contrast floor against the background.
Nobody can judge that by looking, and I could not look anyway. So I ran a validator instead: it simulates protanopia and deuteranopia, measures the perceptual distance between each adjacent pair, checks the lightness band, the chroma floor, and contrast against the exact surface color the app uses, and returns pass or fail per check.
The first palette I tried for an ordered ramp failed. Two middle steps were too close in lightness to tell apart. The fix was not to squint at it, it was to notice that the chart did not need an ordered ramp at all, because the categories were nominal. The categorical set passed on every check, with the worst adjacent pair still clear of the floor under both simulations.
The point is not the specific numbers. It is that "are these colors distinguishable" has a computable answer, and computing it is faster and more reliable than any amount of staring.
Then screenshot it anyway
The validator checks color. It does not check whether a label ran off the edge of the plot.
So after the build passed, I stood up the app with mock data and a throwaway login, drove a headless browser through the sign-in form, and took three screenshots: the full page at desktop width, a close-up with a bar hovered to prove the tooltip renders, and the full page at phone width with a check for horizontal overflow.
That pass caught two real bugs the build could not.
The axis tick calculation could stop below the largest value. The tallest bar and the line's end point then drew above the top of the plot and got clipped, taking their labels with them. The peak label on the column chart was simply missing. The end value on the line was cut in half. Both invisible in the code, obvious in a picture.
The header overflowed at 400 pixels wide, so the page scrolled sideways on a phone. Also invisible in the code.
Both were fixed and the screenshots re-run before anything was committed.
The two habits, stated plainly
Where a decision has a computable answer, compute it. Colorblind-safety is the clearest case, but the same applies to contrast ratios, tick rounding, and whether a label fits inside a bar. Taste is for the things that do not have a right answer.
Where a decision does not have a computable answer, look at the thing that actually rendered, not the code that was supposed to render it. A build passing means the code is valid. It does not mean the chart is right.
The rest of the checklist
For anyone building charts, the rules that shaped these:
- Bars no thicker than 24 pixels, rounded at the data end and square at the baseline.
- Lines at 2 pixels. Gridlines as solid hairlines, one shade off the surface, never dashed.
- Text in the text color, never the series color. A colored mark beside a label carries identity; coloring the label itself makes it illegible on the wrong background.
- One direct label per chart, on the extreme or the endpoint. A number on every point is noise.
- Every chart has a hover readout, keyboard focus, and a table view, so no value is reachable only by pointer or only by color.
- A legend whenever there are two or more series. Never for one, because the title already says what it is.
The charts are in the open-source wallet tracker, in one file, with no dependencies. The whole module is smaller than most charting libraries' type definitions.