Command Palette

Search for a command to run...

Hectal
Course outline
Topic 19.4Advanced ~23 min · step 4 of 5

Frontend System Design: 3D Data and Maps

In one line

Design a 3D view of a lot of data, such as 100,000 bike rentals on a city map: draw them in one call (points or instancing), pick with the GPU or a spatial index, label only what matters, stream tiles, and know when a 2D map is the better answer.

0/5 · 0%

Think of it like this

A stadium crowd. You don't paint each fan separately; you print one picture of a fan and stamp it 80,000 times in different places. And you only write names under the few people the camera follows.

Words you'll meet

New words in this topic, in plain English. Come back here whenever one feels fuzzy.

instancing
Drawing one geometry many times in one draw call, each copy with its own transform (and colour).
GPU picking
Finding what is under the pointer by rendering ids as colours and reading one pixel back.
spatial index
A structure (grid, quadtree, BVH) that finds items near a point without checking them all.
tile
One square piece of a large map or dataset at one zoom level, loaded only when visible.

Step by step

01Measure three ways to draw

Run the first example and compare draw calls, triangles and vertex data. The status bar shows the last case: the 1,000 separate meshes.

02Pick with colours

The second example builds two InstancedMeshes with the same boxes: one shown, one in id colours used only for picking. setViewOffset crops the camera to one pixel, so the pick renders 1 × 1 pixels instead of the whole screen.

03Encode ids exactly

Ids are written with setRGB(…, THREE.LinearSRGBColorSpace) so no colour conversion changes them, and the target is read with readRenderTargetPixels. Id 0 means "nothing".

04Decide 2D or 3D

Before building, write the question the view must answer. If height carries no meaning, sketch the 2D version first.

Try it yourself

  1. 1

    Cheaper spheres

    In the first example, change SphereGeometry(0.2, 8, 6) to SphereGeometry(0.2, 6, 4). The instanced line drops to triangles 3600000: 36 per sphere instead of 80. Detail per item multiplies by the item count.

  2. 2

    Pick in the gaps

    In the second example, add [0.51, 0.5], [0.49, 0.5] to the list of points. Measured: GPU says 5152, raycaster says 5152 for the first, and nothing from both for the second, which falls in a gap between boxes. Both methods agree when both sample the pixel's centre: the raycaster uses x + 0.5, because x alone is the pixel's corner.

Code & diagrams

100,000 rentals, three ways New tab

Seeded positions and a fixed 300 × 150 canvas, so the counts are the same on every run and screen.

idle

Console

Run the scene to see its console.

Expected console output

Points                     draw calls 1 | triangles 0 | points 100000 | vertex data 1.1 MiB
InstancedMesh of spheres   draw calls 1 | triangles 8000000 | points 0 | vertex data 6.1 MiB
1,000 separate meshes      draw calls 955 | triangles 76400
GPU picking against the raycaster New tab

The canvas is a fixed 300 × 150, so the picked ids are the same on every screen.

idle

Console

Run the scene to see its console.

Expected console output

at 50% across, 50% down: GPU says 5150, raycaster says 5150
at 30% across, 70% down: GPU says 7421, raycaster says 7421
at 65% across, 60% down: GPU says 6375, raycaster says 6375
at 2% across, 5% down: GPU says nothing, raycaster says nothing

Break it on purpose

Errors are the best teachers. Make each change, read the error, guess what went wrong, then reveal the answer.

Break #1

Let colour management change the ids

In the second example, change THREE.LinearSRGBColorSpace to THREE.SRGBColorSpace in the setColorAt line.

terminal
$ Press Run
── what you'll see ──
at 50% across, 50% down: GPU says 514, raycaster says 5150 <- differ
at 30% across, 70% down: GPU says 1020, raycaster says 7421 <- differ
at 65% across, 60% down: GPU says 717, raycaster says 6375 <- differ
at 2% across, 5% down: GPU says nothing, raycaster says nothing
Every hit returns the wrong id.

Myth vs fact

Myth

More dimensions always show more.

Fact

3D hides items behind others and distorts size with distance. For many questions about data on a map, a 2D view is clearer, faster and more accessible.

Interview problem

The problem

Ardenvale rentals in 3D

Lumen wants a live 3D map of Ardenvale showing every bike rental of the last 24 hours (about 100,000), with hover details and a time slider.

You're given

  • 100,000 items, updating every minute
  • Hover shows the rental's details
  • Works on a mid phone
  • An accessible alternative

The interviewer follows up

01

The time slider makes the frame rate drop. Why, and what do you change?

02

How do you hover-pick without reading pixels every frame?

Pro corner

Extra depth for experienced readers. New to this? Skip it for now and come back later.

  • ▸

    Picking renders can run only on pointer events, at 1 × 1 pixels: cheap enough for 60 Hz hover. Read back asynchronously where you can (WebGL 2 with a fence, or WebGPU's mapAsync) so the read doesn't stall the frame.

  • ▸

    With InstancedMesh, update only the matrices that changed and set instanceMatrix.needsUpdate = true, or use BatchedMesh when items have different geometries (Topic 17.2).

Remember this

  1. 1

    The problem this solves: data scenes break the habits of product scenes. One mesh per item stops working at a few thousand items, labels drown each other, and picking by raycast slows down. This is the second system design round 3D frontend roles ask.

  2. 2

    One draw call for many things. The first example draws 100,000 rentals three ways. Points: 1 draw call, 100,000 points, 1.1 MiB of vertex data. An InstancedMesh of small spheres: 1 draw call but 8,000,000 triangles (80 per sphere) and 6.1 MiB, mostly one 4 × 4 matrix per instance. And 1,000 separate meshes, only a hundredth of the data: 955 draw calls (the other 45 were outside the view and culled). Choose points for dots, instancing when each item needs a shape.

  3. 3

    Picking. A raycast against 100,000 instances tests each one on the CPU. GPU picking draws every item in a unique "id colour" into a 1 × 1 render target under the pointer (camera.setViewOffset) and reads that one pixel: 3 bytes give up to 16.7 million ids. The second example picks among 10,000 boxes both ways and gets the same ids. Alternatives: a spatial index (a grid, a quadtree, or three-mesh-bvh) to test only nearby items.

  4. 4

    Labels and tiles. Show labels only for the nearest or most important few (HTML or CSS2D, Topic 8.3), and fade the rest. Large maps stream tiles: square pieces of the map at several zoom levels, loaded as the camera moves and dropped when off-screen (the idea behind map libraries and 3D Tiles).

  5. 5

    When 2D is better. 3D adds occlusion (items hide others), perspective (sizes lie about distance) and harder interaction. If the question is "where are rentals densest?", a 2D heat map or a choropleth answers it faster and more accessibly (the /data-viz course). Use 3D when height or the third dimension carries meaning.

Explain it without notes

01

How would you draw and pick 100,000 items in a browser?

02

When would you advise against a 3D data view?

Practice

01

Colour each rental by its hour of the day in the instanced version, without adding draw calls.

02

Hide every rental outside a time range chosen on a slider, without rebuilding any buffer.

Done when you can

  • You can draw 100,000 items in one draw call and say the cost

  • You can explain and build GPU picking

  • You know when a 2D view is the better design

  • You can design labels and tiles for a large scene