I made the one as you requested.
source code on github
here’s working demo: https://stackoverflow-angular-3d-chart.surge.sh/
This involved several intricate steps.
I couldn’t go any deeper from this answer because every part that I mentioned here could be hours worth tutorial. These are what I’ve felt interesting when I was working on it.
Used Stacks
EDIT: the stackblitz code is now outdated. I’ve used the most recent version for each package.
- Three.js r143
- D3.js v7.6.1
- Angular.js v14
Getting Circle Grid
- experiment note on ObservableHQ: https://observablehq.com/@rabelais/circle-inside-grids
First I’ve experimented on SVG with D3.js to get proper circle grid.
It seemed daunting but turned out very simple. I’ve slightly modified Midpoint circle algorithm to fill box grids in circular shape. It is little different from filling grids in 3d space; 2d space has top left corner as beginning of everything. In 3d space, everything starts from center.
const midPointX = gridWidth / 2;
const midPointY = gridHeight / 2;
const { midPointX, midPointY, radius } = config;
const getCollision = ({ x, y }) => {
return (midPointX - x) ** 2 + (midPointY - y) ** 2 - radius ** 2 > 0;
}
Calculating Gaps
d3’s scale band supports automatic calculation of gaps and content size in responsive environment.
const scaleBandX = d3
.scaleBand()
.domain(d3.range(0, config.gridWidth))
.range([config.margin, config.svgWidth - config.margin * 2])
.paddingInner(0.2);
const scaleBandY = d3
.scaleBand()
.domain(d3.range(0, config.gridHeight))
.range([config.margin, config.svgHeight - config.margin * 2])
.paddingInner(0.2);
scaleBandX.bandwidth(); // width of box in 2d space
scaleBandY.bandwidth(); // height of box in 2d space
scaleBandX(boxIndex); // x position of box in 2d space with gap
scaleBandY(boxIndex); // y position of box in 2d space with gap
as D3 assumes vector calculation as normal, it was pretty easy to apply the very same method in 3D.
Expressing on 3D space
I’ve used Three.js to express everything in 3D. The app is running on Angular per request but it does not matter which frontend framework is used.
Everything about expressing 2d bar chart on 3d is very trivial. However, the dimension is different from 2d; the positions have to be swapped.
// code to make a single bar mesh
makeBar(d: typeof gridData[0]) {
// length and height is swapped. because camera is looking from 90 degree angle by default.
const geo = new T.BoxGeometry(d.w, d.l, d.h, 32, 32);
const mat = new T.MeshPhysicalMaterial({ color: 'red' });
const mesh = new T.Mesh(geo, mat);
mesh.position.x = d.x;
// z and y is also swapped. because of the same reason.
mesh.position.z = d.y;
mesh.position.y = d.z;
return mesh;
}
then each element is assigned as 3d Group, to make them centered altogether.
EDIT: color scheme was missing. it is now added.