3D Data Visualisation

When Flat Charts Cannot Show the Full Picture


Most data visualisation belongs in 2D. Bar charts, line charts, scatter plots, heatmaps. They are faster to render, easier to read, and better supported by accessibility tools. A 3D bar chart is almost always worse than a 2D bar chart because the third dimension adds no information and makes comparison harder.

But some data genuinely benefits from spatial representation. When three or more variables correlate in ways that 2D projections flatten, when network structures have too many edge crossings in 2D, when the underlying data is inherently spatial. In these cases, Three.js provides the rendering backbone for interactive 3D exploration. The challenge is building visualisations that perform at scale, update in real-time, and remain usable rather than merely impressive.

3D Visualisation Types

Four common Three.js visualisation patterns rendered in WebGL. Each tab builds a different scene using the rendering technique discussed: InstancedMesh for scatter, displaced PlaneGeometry for surfaces, LineSegments for networks, and BoxGeometry for treemaps.

Visualisation Type Explorer
Three.js WebGLRenderer. Each type uses the rendering approach discussed in the page content.

The Constraint: Data Volume and Interactivity

A 3D scatter plot with 100 points is trivial. A 3D scatter plot with 100,000 points that updates every second, responds to hover and click, supports filtering and highlighting, and maintains 60fps on a laptop GPU is an engineering problem.

The real constraint: Three.js can render millions of vertices. The constraint is the interaction layer: raycasting against 100,000 points to detect hover, updating 100,000 positions when data changes, and keeping the JavaScript thread free enough to handle user input without jank. Volume is not the rendering problem. Volume is the interactivity problem.


The Naive Approach

Tutorial-grade 3D data visualisation works for demos with small, static datasets. It fails for production dashboards with live data feeds and thousands of data points.

Mesh per data point. A small sphere or cube per point. At 10,000 points, the scene has 10,000 objects, each requiring a separate draw call. Frame rates collapse.
Rebuild on update. Dispose all objects, create new ones when data changes. Garbage collection pauses cause visible stuttering during every update cycle.
Raycast all objects. Raycaster.intersectObjects() against every object on every mouse move. At 10,000 objects, this runs for several milliseconds per frame.
Colour-only encoding. All information encoded in hue alone. 8% of male users cannot distinguish the categories. No alternative channel for the same data dimension.

Rendering Architecture

Choosing the right rendering strategy is the highest-impact decision in 3D data visualisation. The three approaches serve different data shapes and interaction requirements.

Approach Draw calls (10K) Update cost Interaction Best for
THREE.Points 1 Low (buffer update) GPU picking or spatial index Large marker datasets
InstancedMesh 1 per geometry type Moderate (matrix update) Built-in raycasting Multi-shape categories
BufferGeometry 1 Low (vertex update) Custom Surfaces, terrain, meshes
Individual Mesh 10,000 High (create/dispose) Native raycasting Prototypes only

Points use a single geometry with a position buffer. 100,000 points render in one draw call. Custom shaders control size, colour, and opacity per point. The trade-off: points are always camera-facing and have limited shape options.

InstancedMesh renders distinct geometry per category (cubes for one type, spheres for another). One draw call per geometry type. Per-instance colour, position, and scale via instance attributes. Better than individual meshes by orders of magnitude.

BufferGeometry stores vertex data in Float32Arrays for surface plots, terrain, and continuous data. Update the arrays and flag for upload when data changes. No object creation or disposal.


Data Update Pipeline

When new data arrives (WebSocket, polling, user filter change), the update path determines whether the visualisation feels responsive or sluggish. The key is keeping the main thread free for rendering while data transforms happen elsewhere.

1

Transform in Worker. Map data values to positions, colours, sizes in a Web Worker. The main thread stays free for rendering.


2

Update buffers directly. Modify the Float32Array contents. Do not create new arrays. Transfer the buffer back to the main thread.


3

Flag for upload. Set attribute.needsUpdate = true. Three.js uploads only the changed data to the GPU on the next render.


4

Animate transitions. Smooth position changes using TWEEN or manual interpolation in the render loop. Users track how data has shifted.

For streaming data (sensor readings, live transactions), use a ring buffer pattern: pre-allocate arrays to the maximum expected size, write new data at the current index, wrap around when full. No allocations during runtime.


Interaction at Scale

Raycasting against 100,000 objects is expensive. Three alternative approaches each trade different things for performance.

GPU Picking

Render the scene to an offscreen buffer with each object in a unique colour. Read the pixel under the mouse to identify the object. One render pass, constant time regardless of object count. The fastest approach at scale.

Spatial Indexing

Build an octree or k-d tree from data positions. Query the tree for points near the mouse ray. Test only nearby candidates. Reduces intersection tests from N to log(N). Good for dynamic datasets.

Throttled Raycasting

Raycast on a timer (30Hz, not every mouse move). Cache the result. Show tooltips with a slight delay rather than frame-perfect tracking. Simple to implement, effective for moderate counts.


Visual Encoding

Effective 3D data visualisation uses multiple visual channels simultaneously. Relying on a single channel (colour alone) limits both information density and accessibility.

Position (x, y, z)
Primary encoding. Three continuous variables.
Colour
Categorical or continuous. Always pair with another channel.
Size
Point radius or instance scale. A fourth variable.
Shape
Sphere, cube, cone for categories. Limit to 4-5.
Opacity
De-emphasise filtered-out points while keeping context.

Multi-channel encoding is not just a design choice. It is an accessibility requirement. Colour vision deficiency affects 8% of males. If colour is the only way to distinguish categories, those users lose the information entirely. Pair colour with shape, pair colour with size, pair colour with labels. At least two channels per data dimension.


Visualisation Types

Different data shapes call for different visualisation approaches. Each type has specific rendering patterns and interaction requirements.

3D Scatter Plots

Three continuous variables mapped to x, y, z position. Useful for clustering analysis, correlation detection, and outlier identification. Add axis lines and grid planes using CSS2DRenderer for readable labels at any camera angle.

Surface Plots and Terrain

A grid of height values rendered as a continuous mesh. Colour encodes a third variable. Useful for performance landscapes, geographic terrain, and probability distributions. Built from PlaneBufferGeometry with modified vertices.

Hierarchical Treemaps

Nested rectangles with height encoding a value. Area for one metric, height for another. Useful for code complexity, financial portfolios, and organisational analysis. InstancedMesh for performance at high node counts.

Time-Series in 3D

Time on one axis, category on another, value on the third. A landscape showing how multiple categories evolve simultaneously. The 2D equivalent (overlapping line charts) becomes unreadable above 10-15 series.


Dashboard Integration

3D visualisations rarely stand alone. They sit alongside 2D charts, tables, and filters in a dashboard. The integration patterns determine whether the 3D component feels like part of the application or an isolated widget.

Shared filter state. When a user applies a filter in a 2D chart (date range, category selection), the 3D visualisation updates to show only matching data. Filters are application state, not component state.
Linked highlighting. Hovering a point in the 3D scatter highlights the corresponding row in a table or bar in a chart. And vice versa. Cross-component highlighting builds spatial understanding.
Drill-down. Clicking a cluster in the 3D view filters all other dashboard components to that subset. The 3D component becomes one view of the data, not the only view.

Pair every 3D visualisation with a data table for accessibility and precise value lookup. These integration patterns apply equally to real-time dashboards built with 2D visualisation libraries.

Example: customer segmentation. A SaaS company plots 40,000 accounts as a 3D scatter: monthly usage on X, revenue on Y, support tickets on Z. In 2D (usage vs revenue), the segments overlap. In 3D, a distinct cluster emerges: high-usage, high-revenue accounts with disproportionately many support tickets. That cluster turned out to be enterprise customers hitting API rate limits. The insight was invisible in every 2D projection. Rendered as an InstancedMesh with GPU picking, the entire dataset runs at 60fps on a mid-range laptop.


The Business Link

Insight over spectacle. 3D data visualisation is not about visual impressiveness. It is about revealing structure that 2D projections hide. When customer segments cluster in three-dimensional space, when supply chain networks have geographic depth, when simulation results vary across multiple parameters simultaneously: the investment in 3D rendering and interaction pays back through faster insight and better decisions.

The cost is real: 3D is harder to build, harder to make accessible, and harder to get right on mobile. Use it only when the third dimension carries genuine information. For everything else, a well-designed 2D chart communicates more clearly.


Visualise Data in 3D

We build 3D data visualisations for datasets that benefit from spatial representation. Scatter plots, network graphs, surface plots, and hierarchical structures: rendered with Three.js, optimised for performance, integrated with your existing dashboard architecture.

Let's talk about your data →
Graphic Swish