HTML Charts with JavaScript (Pt. 2)
Tags: javascript, datascience, typescript, node
Published: 2023-03-06 08:32:47

Hi again :)

I recently brought to you the first part of this series of [HTML Charts with JavaScript](https://dev.to/lightningchart/html-charts-with-javascript-pt-1-4d72).

Today, I'll continue with three new charts you can create with basic HTML code. 

This tutorial is not only perfect for new developers but also for those who are in data visualization and need to create simple yet stunning charting applications.

Let's see the charts:

###- 3D Scatter Charts

![3D-Scatter-Chart](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/y9ih8gker5taa1l634zc.jpg)

In a 3D scatter chart, data points are represented by markers or symbols that are positioned in three-dimensional space based on the values of the three variables being plotted. 

The X, Y, and Z-axis represent the three variables being plotted, and each data point has a corresponding X, Y, and Z value that determines its position in 3D space.<br>

###- 3D Box Series

![3D-Box-Series](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/7z5gdr05wf479vy07ppf.jpg)

In a 3D JavaScript box series, each box represents a single data point and is positioned based on its x, y, and z coordinates. 

The length, width, and height of the box can be used to represent additional variables, allowing for the visualization of complex relationships between multiple data dimensions.

The 3D nature of the box series allows for a more immersive and interactive data exploration experience, as users can rotate and zoom in on the visualization to gain different perspectives and insights.

###- 3D Chunked Surface Grid Chart

![3D-Chunked-Surface-Grid-Chart](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/l0bt5a2x14yidvzy152z.jpg)

The Chunked Surface Grid Chart breaks down a large set of data points into smaller chunks or tiles, which are then rendered in a three-dimensional grid. 

Each tile represents a subset of the data, and its color or height represents a variable being plotted. 

The 3D nature of the Chunked Surface Grid Chart allows for the visualization of complex relationships between multiple variables, such as time, location, and temperature.

**But let's start getting our template ready...**

---

## Template Setup

1. Please, [download the project template](https://lightningchart.com/wp-content/uploads/templates/HTML-Charts-with-JavaScript-Scatter-Box-Series-&-Surface-Grid.zip) that is provided in this article.

2. You will see a file tree like this one:


![3D-HTML-Charts-File-Tree](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/50dyfzu1hmf8y8butg6b.jpg)


In this example we won’t need Node JS to compile our templates. We only need the HTML files and have a web browser.

---

## HTML Template

In the three HTML files, we will see the same structure:
1. Head
2. Style
3. Body

- In the **Head section**, we will specify the properties of the page.

<br>

- In the **Style section** we will specify the style properties (CSS) for the template.

<br>

- In the **Body section**, we will embed our JavaScript code.

For HTML, we'll use the `<script>` tag to work with client-side JavaScript. 

Also, notice that the HTML template is very simplified but you can edit it and make it more complex as you like.

---

### 3D Scatter Chart

To import the IIFE files

```HTML
<script src="xydata.iife.js"></script>
    <script src="lcjs.iife.js"></script>
```
The IIFE file (Immediate Invoked Function Expression), contains all the Lightning Chart functions and properties that we need to create charts.

Extracting required parts from LightningChart JS:

```javascript
const {
            lightningChart,
            SolidFill,
            ColorRGBA,
            PointStyle3D,
            Themes
        } = lcjs

const {
                createWaterDropDataGenerator
        } = xydata
```

### Let's start the chart object

```javascript
const chart3D = lightningChart().Chart3D({
            disableAnimations: true,
            theme: Themes.darkGold,
        })
            .setTitle('3D Scatter Chart')
```

### Set titles to each axis

```javascript
chart3D.getDefaultAxisX().setTitle('Axis X')
        chart3D.getDefaultAxisY().setTitle('Axis Y')
        chart3D.getDefaultAxisZ().setTitle('Axis Z')
```
The `getDefaultAxis`, allow us to set UI properties to a specific axis.

<br>
### Point Series for Y coordinates

```javascript
const pointSeriesMaxCoords = chart3D.addPointSeries()
            .setPointStyle(new PointStyle3D.Triangulated({
                fillStyle: new SolidFill({ color: ColorRGBA(224, 152, 0) }),
                size: 10,
                shape: 'sphere'
            }))
            .setName('Max coords')
```

- **[addPointSeries]:** This is a method for adding a new PointSeries to the chart. 

This series type visualizes a list of Points (pair of X and Y coordinates), with configurable markers over each coordinate. 

`PointSeries` is optimized for massive amounts of data - here are some reference specs to give you an idea:

1. A static data set in tens of millions range is rendered in a matter of seconds.

2. With streaming data, even millions of data points can be streamed in every second, while retaining an interactive document. Read more about this in the [pointSeries documentation](https://lightningchart.com/lightningchart-js-api-documentation/v3.4.0/classes/pointseries.html).



- **[setPointStyle]:**
Configuration for UI style of 3D points. Read more about this in the [setPointStyle documentation](https://lightningchart.com/lightningchart-js-api-documentation/v3.4.0/classes/pointlineseries3d.html#setpointstyle).

<br>

- **[Triangulated]:** A style class used to specify style of 3D points rendering as triangulated Cubes.

<br>

### Heatmap data

Here we'll generate all the heatmap data to represent t he number of points scattered along the XZ plane.

```javascript
createWaterDropDataGenerator()
            .setRows( rows )
            .setColumns( columns )
            .generate()
            .then( data => {
```

- **Water drops data generator.** It generates a [data grid](https://lightningchart.com/js-charts/datagrid/) that contains "water droplets". 

Water droplets are like dots of the most exposed area in the generated heatmap data. The generated data range depends on the `WaterDropDataOptions`.

More about WaterDropDataOptions in [GitHub](https://arction.github.io/xydata/globals.html#createwaterdropdatagenerator).

<br>

### Data

**Data** refers to a number of the type Matrix number[][], that can be read as data[row][column].

It generates a **value** amount of points along this XZ coordinate, with the Y coordinate range based on the **value**.

```javascript
.then( data => {
    
                for ( let row = 0; row < rows; row ++ ) {
                    for ( let column = 0; column < columns; column ++ ) {
                        const value = data[row][column]
                        // Generate 'value' amount of points along this XZ coordinate,
                        // with the Y coordinate range based on 'value'.
                        const pointsAmount = Math.ceil( value / 100 )
                        const yMin = 0
                        const yMax = value
                        for ( let iPoint = 0; iPoint < pointsAmount; iPoint ++ ) {
                            const y = yMin + Math.random() * (yMax - yMin)
                            pointSeriesOtherCoords.add({ x: row, z: column, y })
                            totalPointsAmount ++
                        }
                        pointSeriesMaxCoords.add({ x: row, z: column, y: yMax })
                        totalPointsAmount ++
                    }
                }
```

---

## 3D Box Series

To import the IIFE files

```HTML
<script src="xydata.iife.js"></script>
    <script src="lcjs.iife.js"></script>
```
The IIFE file (Immediate Invoked Function Expression), contains all the Lightning Chart functions and properties that we need to create charts.

Extracting required parts from LightningChart JS:

```javascript
const {
            lightningChart,
            AxisScrollStrategies,
            PalettedFill,
            ColorRGBA,
            LUT,
            UILayoutBuilders,
            UIOrigins,
            UIElementBuilders,
            Themes
        } = lcjs

        const {
                createWaterDropDataGenerator
        } = xydata
```

### Let's start the chart object

```javascript
const chart3D = lightningChart().Chart3D( {
            disableAnimations: true,
            theme: Themes.darkGold,
        } )
            .setTitle( 'BoxSeries3D with rounded edges enabled' )
```

### Set titles to each axis

```javascript
chart3D.getDefaultAxisY()
            .setScrollStrategy( AxisScrollStrategies.expansion )
            .setTitle( 'Height' )

        chart3D.getDefaultAxisX()
            .setTitle( 'X' )

        chart3D.getDefaultAxisZ()
            .setTitle( 'Z' )
```

The **getDefaultAxis**, allow us to set UI properties to a specific axis.

**ScrollStrategies** can be used to establish the behavior of the axis scrolling. Read more about it in the [axisScrollStrategies documentation](https://lightningchart.com/lightningchart-js-api-documentation/v3.4.0/globals.html#axisscrollstrategies).

### PalletedFill

Now, let's set up the `PalettedFill` to dynamically colored boxes by an associated `value` property:

```javascript
const lut = new LUT( {
            steps: [
                { value: 0, color: ColorRGBA( 0, 0, 0 ) },
                { value: 30, color: ColorRGBA( 255, 255, 0 ) },
                { value: 45, color: ColorRGBA( 255, 204, 0 ) },
                { value: 60, color: ColorRGBA( 255, 128, 0 ) },
                { value: 100, color: ColorRGBA( 255, 0, 0 ) }
            ],
            interpolate: true
        } )
```

**LUT instances**, like all LCJS-style classes, are immutable, which means that their setters do not modify the actual object, but instead return an entirely new modified object.

**LUT Properties:**
- **Steps:** List of color steps (color + numeric value pair).
- **Dither:** true enables automatic linear interpolation between color steps.

### Specify edge roundness

For applications with massive amounts of small Boxes, it is wise to disable for performance benefits.

```javascript
boxSeries
            .setFillStyle( new PalettedFill( { lut, lookUpProperty: 'y' } ) )
            .setRoundedEdges( 0.4 )
```

### Creating height map data

```javascript
createWaterDropDataGenerator()
            .setRows( resolution )
            .setColumns( resolution )
            .generate()
            .then( waterdropData => {
                let t = 0
                const step = () => {
                    const result = []
                    for ( let x = 0; x < resolution; x++ ) {
                        for ( let y = 0; y < resolution; y++ ) {
                            const s = 1
                            const height = Math.max(
                                waterdropData[y][x] +
                                50 * Math.sin( ( t + x * .50 ) * Math.PI / resolution ) +
                                20 * Math.sin( ( t + y * 1.0 ) * Math.PI / resolution ), 0 )
                            const box = {
                                xCenter: x,
                                yCenter: height / 2,
                                zCenter: y,
                                xSize: s,
                                ySize: height,
                                zSize: s,
                                // Specify an ID for each Box in order to modify it during later frames, instead of making new Boxes.
                                id: String( result.length ),
                            }
                            result.push( box )
                        }
                    }
                
                    boxSeries
                        .invalidateData( result )
                
                    t += 0.1
                    requestAnimationFrame( step )
                }
                step()
            })
```

The resolution variable is equal to 10. The loop will create 10 boxes (0 -9), assigning a height value depending of the number of the box. Each box is stored in the result array object

### InvalidateData

Method for invalidating Box data. Accepts an Array of BoxDataCentered objects. Properties that must be defined for each NEW Box:
- "xCenter", "yCenter", "zCenter" | coordinates of Box in Axis values.

<br>

- "xSize", "ySize", "zSize" | size of Box in Axis values.
( if altering a previously created Box, these are not necessary )

**Optional properties:**

- **id:** If supplied, the Box can be altered afterwards by supplying different data with the same "id".

<br>

- **color:** If supplied, the Box will be coloured with that color, but only when the BoxSeries is styled as IndividualPointFill.

<br>

- **value:** Look-up value to be used when the BoxSeries is styled as PalettedFill.

Learn more about it in the [invalidateData documentation](https://lightningchart.com/lightningchart-js-api-documentation/v3.4.0/classes/boxseries3d.html#invalidatedata).

---

## 3D Chunked Surface Grid Chart

To import the IIFE files

```HTML
<script src="xydata.iife.js"></script>
    <script src="lcjs.iife.js"></script>
```
The IIFE file (Immediate Invoked Function Expression), contains all the Lightning Chart functions and properties that we need to create charts.

Extracting required parts from LightningChart JS:

```javascript
const {
            lightningChart,
            LUT,
            ColorRGBA,
            PalettedFill,
            emptyLine,
            LegendBoxBuilders,
            ColorShadingStyles,
            Themes
        } = lcjs

        const {
            createWaterDropDataGenerator
        } = xydata
```

### Starting the chart object

```javascript
const chart = lightningChart().Chart3D({
            disableAnimations: true,
            theme: Themes.darkGold,
        })
```

### Adding the surface grid to the chart object:

```javascript
const surfaceGrid = chart.addSurfaceGridSeries({
            columns: COLUMNS,
            rows: ROWS,
        })
```

### X & Z limits

```javascript
const COLUMNS = 2000
        const ROWS = 2000
        const CHUNK_SIZE = 1000
```

### Color properties

```javascript
.setColorShadingStyle(new ColorShadingStyles.Phong())
            .setFillStyle(
                new PalettedFill({
                    lookUpProperty: 'y',
                    lut: new LUT({
                        interpolate: false,
                        steps: [
                            { value: 0, label: '0', color: ColorRGBA(0, 0, 0) },
                            { value: 15, label: '15', color: ColorRGBA(0, 255, 0) },
                            { value: 30, label: '30', color: ColorRGBA(255, 0, 0) },
                            { value: 40, label: '40', color: ColorRGBA(0, 0, 255) },
                            { value: 50, label: '50', color: ColorRGBA(255, 255, 0) },
                            { value: 75, label: '75', color: ColorRGBA(0, 255, 255) },
                        ],
                    }),
                }),
            )
            .setWireframeStyle(emptyLine)
```

- **Color Shading Styles:** use for choosing from the different options for coloring the shading styles of a 3D series. See the [colorShadingStyles documentation](https://lightningchart.com/lightningchart-js-api-documentation/v3.4.0/globals.html#colorshadingstyles).

LUT instances, like all LCJS-style classes, are immutable, which means that their setters do not modify the actual object, but instead return an entirely new modified object.

**LUT Properties:**
- **Steps:** List of color steps (color + numeric value pair).
- **Dither:** true enables automatic linear interpolation between color steps.

- **setWireFrameStyle:** Wireframe is a line grid that highlights the edges of each cell of the 3D surface. Read more about in the [setWireFrameStyle documentation](https://lightningchart.com/lightningchart-js-api-documentation/v3.4.0/classes/surfacegridseries3d.html#setwireframestyle).

### Adding horizontal legend boxes to the chart

```javascript
const legend = chart.addLegendBox(LegendBoxBuilders.HorizontalLegendBox).add(chart)
            // Dispose example UI elements automatically if they take too much space. This is to avoid bad UI on mobile / etc. devices.
            .setAutoDispose({
                type: 'max-width',
                maxWidth: 0.80,
            })
```


![LegendBox](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/rb4k6e6uri2yvnw8s68v.jpg)

### Load dataset in one "chunk" at a time. 

Chunk refers to a smaller sub set of the entire data set. Loading large data sets in parts is extremely efficient in terms of memory usage and application usability.

```javascript
;(async () => {
            const chunks = []
            for (let column = 0; column < COLUMNS; column += CHUNK_SIZE) {
                for (let row = 0; row < ROWS; row += CHUNK_SIZE) {
                    chunks.push({column, row})
                }
            }
```

In this chart, we used the [`createWaterDropDataGenerator`] and [`invalidateHeightmap`] functions that were used in the previous chart.

```javascript
const chunkData = await createWaterDropDataGenerator()
                    .setColumns(CHUNK_SIZE)
                    .setRows(CHUNK_SIZE)
                    .setWaterDrops(waterdropOptions)
                    .generate()
            
                surfaceGrid.invalidateHeightMap({
                    iColumn: chunk.column,
                    iRow: chunk.row,
                    values: chunkData,
                })
```

We need the number of columns, rows and options. The main difference is that the values are created randomly:

```javascript
const waterdropOptions = new Array(waterDropsCount).fill(0).map((_) => ({
                    rowNormalized: rand(0.0, 1.0),
                    columnNormalized: rand(0.0, 1.0),
                    amplitude: rand(5, 60),
                }))
```

---

## Conclusion

Now we've finished this series of HTML charts with JS. You can see the first part where we built an [HTML template for 2D charts](https://dev.to/lightningchart/html-charts-with-javascript-pt-1-38a2).

In this article, we learned about using 3D charts and how libraries (in this case LC JS) can help us fasten the development of charting applications.

As you saw, LightningChart JS is a fully customizable library that requries single lines of code to change properties that benefit end-users and their experience when using our(your) application.

If you're in Data Science or Data Visualization or are just a data viz enthusiast, developing high-performance plotting apps with familiar languages can help you a lot :).

Also, there's a bunch of charts available in the LC JS gallery. Check them out and let me know if there's any + "insert_here_technology" that you'd like me to write about!

See you in the next article!

**Written by:**
Omar Urbano | Software Engineer & Technical Writer
[Find me on LinkedIn](https://www.linkedin.com/in/omarurbanocuellar/)