🧊
ESSAY · DATA VIZ

Data Art in the Browser: From Raw JSON to Living Pixel Architecture

You pull a thousand numbers from an exchange API. They're ugly. They're overwhelming. They don't mean anything until you give them a body. Here's how BittCity turns a JSON blob into a skyline you can actually feel.

by ALEX · ~9 MIN READ

You call a crypto API. What comes back looks roughly like this:

[
  { "symbol": "BTC",
    "quote": { "USD": {
      "price": 95432.17,
      "percent_change_24h": 1.83,
      "market_cap": 1.879e12,
      // ...fifteen more fields
    }}},
  { "symbol": "ETH", ... },
  // ...97 more of these
]

Ninety-nine coins. Updating constantly. Each one a little bag of numbers. On its own, this is data. In a human brain, it is static.

That's the fundamental data visualization problem, and crypto has it worse than almost any other domain. Too many tickers, too fast, updating in real time, all fighting for your attention, most of them worthless, a few of them the difference between a vacation and a liquidation email.

The standard answers are tables and line charts. Both are useless past a certain density. A table of 99 rows and 8 columns is a Rorschach test. A chart with 99 overlapping lines is a hairball.

So. What if the data had a body?

WHY CITIES

There's a concept in cognitive science called ambient display: the idea that some information is best delivered not as numbers on a screen, but as a visual environment you glance at. A weather mobile in your hallway. A smart bulb that shifts color when your portfolio tanks. A fishtank whose lights pulse faster when the market's rallying.

Cities are almost embarrassingly good at this. Humans evolved to parse skylines in milliseconds. A skyline gives you scale (how tall?), density (how many?), lighting (what state?), and geography (what's where?) — all at a single glance. You don't have to read anything. You just see it.

For crypto, the mapping is obvious once you spot it. Each coin has a size (market cap). Each coin has a state (up, down, flat). Each coin has a rank. Map those to height, window color, and position — and you've got a visualization that delivers a week of numbers in one second of looking.

That's the premise of BittCity. Now let's talk about how it actually gets built.

THE PIPELINE, IN SEVEN UNSURPRISING STEPS

Every live data project has the same spine. This one is no different.

01
FETCHA serverless function hits CoinMarketCap's /cryptocurrency/listings/latest endpoint with limit=99. That's it. The entire top of the market in one HTTP call.
02
CACHEThe response goes into Upstash Redis with a 20-minute TTL. Why? Because a free-tier CMC API key has hard rate limits, and hitting the upstream on every visitor request is both expensive and pointless — prices don't change meaningfully in 30 seconds for this use case.
03
SERVEThe frontend fetch()s the cached JSON. If cache is warm, ~100ms. If it needs a refresh from CMC, ~800ms. Either way, the user sees data fast.
04
TRANSFORMEach coin object gets flattened: { Name, Ticker, Price, _24h, _7d, _30d, _90d, _cap, _r }. Underscore-prefixed keys are the numeric versions; non-prefixed keys are pre-formatted display strings. This small trick — always keeping display-formatted versions alongside the numbers — saves you from reformatting on every render.
05
SORTUser taps "7D %" → sort by _7d. Taps "MKT CAP" → sort by _cap. Taps rank → stable default order. The array reorders. That's all.
06
ASSIGN BUILDINGSEach coin gets a building height proportional to its market cap, clamped to min/max bounds so the chart stays readable even when Bitcoin's cap dwarfs everything. A zoom control shifts which market cap equals "maximum height" — useful when you want to see small caps without BTC eating the whole canvas.
07
RENDERAll 99 buildings, their windows, the mountains, the sky, the moon, the road, the occasional train or UFO — all drawn to a single <canvas> at 30 FPS with pixel-perfect rendering mode.

None of these steps are novel. What makes the result interesting is what happens inside step 7.

THE KEY TRICK: DETERMINISTIC RANDOMNESS

Here's a problem you hit immediately when building something like this. You want each building to look unique — different window patterns, different details, different roof textures. If every building looked identical you'd have Mondrian, not a city.

The naive solution is Math.random(). Roll dice for each window on each render. Except — every re-render, every timeframe switch, every sort, every data refresh — the whole city re-rolls. Windows blink on and off randomly. Building details shuffle. It looks broken.

A skyline should be stable. It should be the same city every time you look at it. Bitcoin's tower should always have the same window arrangement. You walk away, you come back, it's there.

The fix is a seeded pseudo-random number generator, where the seed is derived from something stable about each coin. In BittCity, it's basically ticker.charCodeAt(0) combined with a constant. Same ticker → same seed → same windows, forever.

function srand(seed) {
  let s = seed | 0;
  return () => {
    s = s * 1103515245 + 12345 & 0x7fffffff;
    return (s >>> 16) / 32768;
  };
}

That's a Linear Congruential Generator. It's from 1968. It is not cryptographically secure. For drawing pixel windows, it is absolutely perfect.

WHY THIS MATTERS Now BTC always has the same window pattern. ETH always has the same. When you sort, switch timeframes, or refresh — the city stays stable. It feels solid. This one change — deterministic seeds — is what separates "data viz that feels alive" from "data viz that feels glitchy."

CANVAS VS SVG VS DOM — A SHORT RANT

For 99 pixel buildings, each with ~30 windows, you're rendering roughly 3000 rectangles at 30fps. Your options:

BittCity uses canvas. For any data viz where you have more than ~50 animated elements, canvas is almost always the right answer. Set ctx.imageSmoothingEnabled = false and you get pixel-perfect rendering for free, which means you can zoom without blur — critical for this aesthetic.

MAKING IT FEEL ALIVE WITHOUT LYING ABOUT THE DATA

This is the hard part.

Static data is boring. A perfectly accurate skyline of coin market caps, rendered once and frozen, is a chart. You'd look at it for ten seconds and leave.

But if you over-animate the data — wiggle the buildings, strobe the windows, add motion for its own sake — you're lying. The price didn't just change. Don't tell the user's eyeballs it did.

The trick is to animate the environment, not the data. In BittCity:

None of this is data. All of it is motion. Your brain receives the "this is alive" signal without getting misleading information about the market. That's the central principle of ambient data display:

The world moves, the data sits still.

You can stare at the skyline for a minute and it feels dynamic. Nothing is lying to you. When Bitcoin actually moves, you'll notice — because everything else was still before.

PERFORMANCE GOTCHAS YOU WILL ABSOLUTELY HIT

If you try to build something like this, you will hit these in roughly this order:

  1. Garbage collection jank. Allocating new objects every frame causes GC pauses, which show up as stutters. Pre-allocate your arrays. Reuse objects. Your framerate will thank you.
  2. Retina scaling weirdness. On a 2× DPR screen, canvas is fuzzy unless you manually scale the backing buffer. Set canvas.width = rect.width * dpr, then ctx.scale(dpr, dpr) at init. Don't forget or everything looks Vaseline.
  3. Mobile viewport changes. Mobile browsers resize the viewport on scroll as the URL bar hides. Your canvas will shift and jitter unless you debounce resize events properly.
  4. Touch vs mouse events. Handle both, explicitly. Don't assume devicePixelRatio tells you everything. Test on an actual phone, not Chrome's device emulator.
  5. Loading state flicker. The moment data arrives, if you don't handle it carefully, the whole city "pops" into existence and feels broken. Fade from a neutral loading state. Twenty lines of code, massive UX payoff.

HOW TO START BUILDING SOMETHING LIKE THIS

You don't need BittCity's exact stack. A minimal setup:

That's the whole stack. Everything else is taste.


The interesting thing about data art isn't the tech — the tech is almost trivial. It's the decision. The decision to stop treating data as rows and start treating it as environment. A field you walk through. A city you glance at. A weather system you feel without ever reading a forecast.

Crypto markets are perfect for this because they're public, continuous, structurally emotional, and already loaded with metaphors about towers and altitude and climbing and falling. The metaphors were always there. Someone just had to render them.

If you want to see the full thesis in motion, it's running right here: the BittCity live skyline. If you want the cultural companion piece on why crypto interfaces all look the way they do: Web3 Aesthetics and the Retro Obsession. If you want to play the game built on the same canvas engine: Crypto Jump.

KEEP READING