A chart made of the things it counts

Pebble Chart: Small Pictures, Big Numbers

What if you could just see the quantity on a bar chart? Take twenty dollars, for example; it is worth about 18 euro or 3,156 yen. The new "Pebble Chart" puts these numbers on a log scale base 1.1, and also contains a visual marker for every item it counts.

Approximately twenty US dollars represented by piles of bills and coins: USD 20, EUR 18, CNY 133, MXN 373, CAD 28, GBP 15, CHF 17, JPY 3,156, AUD 28.
Rounded one-unit equivalents using the saved 27 October 2026 exchange-rate inputs. Calculation inputs · Image credits.

Design options for the log scale

Twenty-eight Australian one-dollar coins stacked two ways, labeled Base 10 and Base 4. Base 10: rows hold 1, 10 and 100 pictures. One very large coin, a row of ten small coins, then seventeen tiny specks in the third row, which has room for 100. Base 4: rows hold 1, 4, 16 and 64 pictures. One large coin, a row of four, a row of sixteen, then seven specks in the fourth row, which has room for 64.
The same 28 Australian dollars laid out with size shrinking on (left) the standard log10 scale and (right) the 'perfect' scale for a pebble chart of log4.

Base 10 grows far too fast. The second row already holds ten pictures at a tenth of the size, and the third row has room for a hundred at a hundredth, though we only have seventeen coins left for it.

Base 4 is the case where the items fit perfectly, one, four, sixteen, each row exactly four times as many pictures at a quarter of the size, so every row fits neatly. But it still grows too quickly. The fourth row has room for 64, and we only have seven coins left for it.

Twenty-eight Australian one-dollar coins at base 1.1, laid on their side, growing left to right. Naive: eight columns of a single coin, then four columns of two, then four columns of three, shrinking to the right. Tuned: sixteen skipped slots are drawn as empty dashed circles on the left and cropped away. The coins start after them, squeezed to 0.7 of the width, in compact columns of three to five coins, ending with a single coin.
The same 28 coins at base 1.1, laid on their side. Dashed circles mark skipped slots that are left empty.

Base 1.1 grows gently, so the pictures shrink gradually. Used naively (top) it starts with a long run of rows holding a single coin and builds a very long pile.

By skipping the early rows (bottom) and squeezing the remaining columns together to 0.7 of their width, we get a more visually useful chart.

When every picture is different

In the currency chart above, a small set of front and back images were repeated, with random rotations. But every picture can actually be its own unique item. In the example below we show reports of people parking in bike lanes via Bike Bureau and the items show a photo of the actual vehicle counting towards the total. Clicking any item will bring you to the full report.

Vehicle chart preview. Open the interactive chart to select individual Bike Bureau reports.

Technical details

How the pictures fit

Each element is a picture in a square area, padded transparently to keep its proportions.

1. Size

Each row’s elements are smaller than the row below, so more fit across. At α = 1.1 they shrink to about 91% per row. Rows fill from the bottom; the last may be incomplete.

2. Vertical position

Stack the visible rows upward from the baseline, each as tall as its elements. The same sum gives the column height and places the count ticks. So the height of each bar can be slightly higher than the true height.

3. Horizontal position

Spread each row’s spare width equally between its elements, then squeeze their centers toward the column’s center so they overlap without distortion.

Equations

Size element size: sr = W / αr row capacity: cr = ⌊W / sr⌋ Vertical position row top: yr = B − (sr₀ + … + sr) Horizontal position spacing between neighbors: g = (W − ksr) / (k − 1) horizontal squeeze: x′ = qx

W: column width, in pixels.
r: row number, 0 at the bottom; r0 is the first visible row.
α: growth factor; here 1.1.
sr: element width and height in row r.
cr: elements that fit in row r.
B: baseline.
yr: top of row r.
k: elements placed in the row.
g: spare width between neighbors before squeezing (a lone element is centered).
x, x′: an element’s horizontal distance from the column center, before and after squeezing.
q: squeeze factor; 0.7 moves centers 30% closer.

Pre-rendering and click mapping

The browser never lays out thousands of separate pictures. Python does all the drawing ahead of time: each category’s pile is rendered once into a single image, so the page loads only a handful of files, one per bar, however many pictures each pile holds.

Alongside each image goes a small location table: for every row, its top, its element size, how many columns it has, the index of its first item, and where its columns start and how far apart they are. Hovering or clicking turns the pointer position into an item by location alone. Find the row from the vertical position (a binary search), then work out the column from the horizontal position, checking the neighboring columns because squeezed elements overlap. No per-picture handlers or hit regions are needed.

Only the item under the pointer gets a real, invisible link or button laid over it, so the original photograph loads only when someone clicks.

Build one

The Pebble Chart code is open sourced as part of the matviz python package. Here is an example where the images are stored in a 'cutouts' folder:

from pathlib import Path
from matviz import pebble_bar_chart

categories = [
    {"name": name, "label": name, "color": color,
     "items": [{"id": p.stem, "src": str(p)}
               for p in sorted(Path(folder).glob("*.png"))]}
    for name, color, folder in [
        ("Pink flowers", "#a65b7d", "cutouts/pink"),
        ("Brown flowers", "#927452", "cutouts/brown"),
    ]
]

pebble_bar_chart(
    categories, "collection.html",
    bar_width=150, log_base=1.1,
    item_offset=16, h_squeeze=0.7,
    show_count=True,
)

Python renders each column as one image, downsampling from a larger render to retain the smallest marks. The browser uses saved row geometry to link pictures to their source records.

A log scale you can see

Log scales are a big help to experts and a trap for everyone else. Early in the COVID pandemic, a survey found that most people couldn't answer simple questions about a chart of COVID deaths once it was drawn on a log axis1. The trouble is that a log axis hides how big the numbers are. The yen bar is only about twice as tall as the dollar bar, even though it stands for about 160 times as many things.

A pebble chart keeps that compression but stops hiding it. As a pile grows, its pieces shrink, so you can watch the scale squeeze. Twenty big bills sit on one side and a tall drift of tiny coins on the other. You don't need to know what a logarithm is to see that the second pile holds far more. The height gives the expert their log scale, and the pieces show the beginner what that scale has done to the numbers. One chart serves both readers.

The pictures do a second job, too. In the vehicle chart you get the gist before reading a single label: you can see what kinds of vehicles end up in bike lanes, not just how many. And every picture is a piece of evidence. A plain bar asks you to trust whoever drew it, but a pebble chart shows its work, down to the individual report you can click open and check for yourself.