Build a mesh 3D bar chart using SVG
Published: 2022-08-16 11:46:38

> First, you can check out the results of my application in production: [Playground of SSR-Contributions-svg](https://contribution.catsjuice.com/).

## Draw a basic cube
Let's start with one of the most basic cube.

![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/my5j1a6wa4cfog7hax0k.jpg)

Think of it as a combination of multiple geometric figures placed in a [Cartesian coordinate system](https://en.wikipedia.org/wiki/Cartesian_coordinate_system)，it may looks like(Note that in the coordinate system of svg, the positive direction of the y-axis is downwards):

![Basic cube](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/8k7g49mnz62t7o4whth5.jpg)

Define the basic concepts of the vertices and faces as shown in the diagram below. We use vertex O as the origin of the cube.

![Basic concepts](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/f6dgde23kxc9uulxo8xn.jpg)

Here, the perspectives is actually decided by the top face's shape. So we need to modify the perspectives by **defining the length of the two diagonals in the horizontal and vertical directions**.


![Control perspectives](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/7phdcx1ytyqogil6ntik.jpg)

Then we can get the coordinates of all the points(Suppose the coordinates of the origin **O** are 'ox', 'oy'):


![Points coordinates](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/ut87xhr63uyati3x7t1b.jpg)

Start coding with defining variables(write inside the `<script>` tag of a html file):


```javascript
const sizeX = 20;
const ratio = 2;
const sizeY = sizeX / ratio;
const height = 40;
const ox = 100;
const oy = 100;
const pA = [ox - sizeX, oy + sizeY];
const pB = [ox, oy + 2 * sizeY];
const pC = [ox + sizeX, oy + sizeY];
const pD = [ox - sizeX, oy - height + sizeY];
const pE = [ox, oy - height + 2 * sizeY];
const pF = [ox + sizeX, oy - height + sizeY];
const pG = [ox, oy - height];
const pO = [ox, oy];
const face_l = [pE, pD, pA, pB];
const face_r = [pE, pF, pC, pB];
const face_t = [pE, pD, pG, pF];
```

We will draw the geometric surface via the path tag of svg，so first define a method for generating critical paths to make it easier to write the code:

```js
function d(points) {
    const raw = points
     .map((point) => `${point[0]} ${point[1]}`)
     .join(' ');
    return "M" + raw + "z";
}
```

Then we can get code of svg, and write it to document:

```js
const svg = `<svg width="200" height="200">
    <path d="${d(face_l)}" stroke="#000" fill="transparent" />
    <path d="${d(face_r)}" stroke="#000" fill="transparent" />
    <path d="${d(face_t)}" stroke="#000" fill="transparent" />
</svg>`;
document.write(svg);
```

---

## Draw a grid of cubes

Define a grid by specify rowNum and colNum:

![Mesh](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/39ihpshsadthyi7y1raf.jpg)

Assume that the origin of svg lies at 0, 0 of the grid(yellow block), we have to move whole grid horizontally. And set `maxBarHeight = 100`.

The grid width should be : `sizeX * (rowNum + colNum)` , the height should be: `sizeY * (rowNum + colNum) + maxBarHeight` , the whole grid's `translateX` and `translateY` should be : `translateX = rowNum * sizeX` , `translateY = maxBarHeight`.

Define a function to draw basic cube(Just like mentioned above, but `ox`, `oy`, `height` should be passed as function's parameters):

```js
function cube(ox, oy, height) {
    const pO = [ox, oy];
    const pA = [ox - sizeX, oy + sizeY];
    const pB = [ox, oy + 2 * sizeY];
    const pC = [ox + sizeX, oy + sizeY];
    const pD = [ox - sizeX, oy - height + sizeY];
    const pE = [ox, oy - height + 2 * sizeY];
    const pF = [ox + sizeX, oy - height + sizeY];
    const pG = [ox, oy - height];
    const face_l = [pE, pD, pA, pB];
    const face_r = [pE, pF, pC, pB];
    const face_t = [pE, pD, pG, pF];
    
    return `<g>
        <path d="${d(face_l)}" stroke="#000" fill="#555" />
        <path d="${d(face_r)}" stroke="#000" fill="#888" />
        <path d="${d(face_t)}" stroke="#000" fill="#aaa" />
    </g>`;
}
```

Define a function to coordinate `rowIndex` and `colIndex` to pixels :

```js
function coordIndex(rowIndex, colIndex) {
    return [
        (rowIndex - colIndex) * sizeX, 
        (rowIndex + colIndex) * sizeY
    ];
}
```

Generate random data:

```js
const colNum = 6; // num of cols
const rowNum = 4; // num of rows
const cubes = [];
for (let i = 0; i < colNum; i++) {
    for (let j = 0; j < rowNum; j++) {
        cubes.push([
            i, 
            j, 
            Math.floor(Math.random() * maxBarHeight)
        ]);
    }
}
```

Generate svg code:

```js
// render
const svg = `<svg width="${svgWidth}" height="${svgHeight}">
    <g transform="translate(${translateX}, ${translateY})">
    ${cubes.map((opt) => cube(
        ...coordIndex(...opt), 
        opt[2]
    )).join('')}
    </g>
</svg>`;
```

## Final codes on codepen:

[https://codepen.io/catsjuice/pen/MWVqNdQ](https://codepen.io/catsjuice/pen/MWVqNdQ)