HTML Charts with JavaScript (Pt. 1)
Tags: javascript, tutorial, typescript, node
Published: 2023-03-06 08:32:40

Hi again!

Previously, I wrote an article about creating HTML charts and their easy implementation using JS charting frameworks but the article was limited to [creating 2D charts in HTML with LightningChart JS](https://lightningchart.com/js-charts/frameworks/html/).

Today, I bring you something more interesting: how to create 3D charts with basic HTML and the "LCJS" (LightningChart JS) library.

LightningCharts can be used as embed code, so it can be very useful if you don't have enough experience with advanced web development (MVC with C#, Java, Angular, etc.).

Today, we will create three charts:

### - 3D Box Series Spectrogram

![3D-Box-Series-Spectrogram](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/wdvwhhbj7gkheb01tfsi.jpg)
Simply put, an spectrogram is a visualization of sound and it plots frequency, time, and amplitude. 

The frequencies are indicated by the color and height. That is very clear in the spectrogram above. 

So, just think about it for a second, you can actually create this chart just using basic HTML and a JS framework. 

This chart is typically used for acoustics, music, physics, and engineering fields.

### - 3D Ellipsoid

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

On the other hand, we're also creating a more scientific chart. The ellipsoid is frequently used in scientific fields as mathematics, physics, or engineering. 

An ellipsoid is a sphere fed by several data points whose sum of distances is known as "forci".

In Astronomy, problems about celestial bodies are explained using ellipsoid charts. FYI: another type of chart used to describe celestial bodies' phenomena are the [N-body simulations](https://lightningchart.com/blog/nbody-simulation-data-visualization/).

### - 3D Line Series

![3D-Line-Series](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/4vhuz239okjekrsho9sl.jpg)

Finally, we're creating a more common visualization: a line series. 

A line series is simply a visualization that connects data points with lines and in this case, in a three-dimension way. That is, for instance, plotting time, value, and an additional axis (depth). 

Let's now jump to implementation.

## Setting up our template

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

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


![HTML-3D-Charts-filte-Tree](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/misburcc33i245oc7ywn.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.

Let’s code.


## 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.

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

- 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 Box Series Spectrogram

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.

Setting `PalettedFill` to dynamically color boxes by an associated '`value`' property:

```javascript
const lut = new LUT( {
            steps: [
                { value: 15, 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 ) }
            ],
            units: 'dB',
            interpolate: true
        } )
        const paletteFill = new PalettedFill( { lut, lookUpProperty: 'y' } )
```

The `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.

#### Creating the chart 3D object and configure the Axes:

```javascript
const chart3D = lightningChart().Chart3D({
            disableAnimations: true,
            theme: Themes.darkGold,
        })
            .setTitle( '3D Box Series Spectrogram' )
            .setBoundingBox( { x: 1, y: 1, z: 2 } )
        chart3D.setCameraLocation( initialCameraLocation )
        chart3D.getDefaultAxisY()
            .setScrollStrategy( AxisScrollStrategies.expansion )
            .setInterval( 0, 100 )
            .setTitle( 'Power spectrum P(f)' )
        chart3D.getDefaultAxisX()
            .setTitle( 'Frequency (Hz)' )
        chart3D.getDefaultAxisZ()
            .setTitle( 'Time' )
            .setInterval( 0, -dataHistoryLength )
            .setScrollStrategy( AxisScrollStrategies.progressive )
```
We can access to the Axes by using the `[getDefaultAxisY - X]` property.

- `ScrollStrategy`: Specify ScrollStrategy of the Axis. This decides where the Axis scrolls based on current view and series boundaries.

- `Strategies`: different strategies are available. Check them all in the [Strategies documentation](https://lightningchart.com/lightningchart-js-api-documentation/v3.4.0/globals.html#axisscrollstrategies).


- `Themes`: LightningChart JS has several themes available. For more details, check the [Themes documentation](https://lightningchart.com/lightningchart-js-api-documentation/v3.4.0/interfaces/themes.html).

#### DataGrid

Now we need to create a data grid of Boxes that we can mutate without creating new Boxes for a better performance. 

```javascript
const boxGrid = []
        for ( let sampleIndex = 0; sampleIndex < dataHistoryLength; sampleIndex++ ) {
            const sampleBoxIDs = []
            for ( let i = 0; i < dataSampleSize; i++ ) {
                const id = sampleIndex * dataSampleSize + i
                // Add empty Box to series.
                boxSeries.invalidateData( [{
                    id,
                    yMin: 0,
                    yMax: 0,
                    zMin: 0,
                    zMax: 0,
                    // Box X coordinates don't have to change afterwards.
                    xMin: i,
                    xMax: i + 1.0
                }] )
                sampleBoxIDs.push( id )
            }
            boxGrid.push( sampleBoxIDs )
        }
```

#### Spectrum

```javascript
createSpectrumDataGenerator()
            .setSampleSize( dataSampleSize )
            .setNumberOfSamples( dataHistoryLength )
            .setVariation( 5.0 )
            .generate()
            .setStreamRepeat( true )
            .setStreamInterval( 1000 / 60 )
            .setStreamBatchSize( 1 )
            .toStream()
            // Scale Y values from [0.0, 1.0] to [0.0, 80]
            .map( sample => sample.map( y => y * 80 ) )
            // Map Y values to a Row of Boxes.
            .forEach( sample => {
                const infiniteStreamingDataEnabled = toggleStreamingCheckBox.getOn()
                const addSample = infiniteStreamingDataEnabled || sampleIndex < dataHistoryLength
```

The Spectrum data generator generates rows of random numbers that can be used to mimic spectrum data. Generated data is between 0 and 1.
To create a new instance of Spectrum data generator use `createSpectrumDataGenerator()` property.

#### UI controls


![UI-control](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/t39jboqeeqis2ii3spqh.jpg)

In a charting application, UI controls refer to the graphical elements that allow users to interact with and customize charts. 

These controls enable users to modify the appearance and behavior of charts to suit their needs and preferences. 

Common UI controls include buttons, sliders, checkboxes, drop-down menus, and text boxes. 

In this case, we'll use radio button selectors to control two main functions: _Disable animations_ and _infinite data stream_ option.

Is important to have UI controls in our application as they provide a user-friendly way of navigating and manipulating complex data sets.

This makes it easier for end-users to analyze and interpret the data while improving the user experience and overall effectiveness of the app.

- **UI control to stop the spectrum generator and leave the static chart.**

```javascript
const group = chart3D.addUIElement( UILayoutBuilders.Column
            .setBackground( UIBackgrounds.Rectangle )
        )
        group
            .setPosition( { x: 0, y: 100 } )
            .setOrigin( UIOrigins.LeftTop )
            .setMargin( 10 )
            .setPadding( 4 )
            // Dispose example UI elements automatically if they take too much space. This is to avoid bad UI on mobile / etc. devices.
            .setAutoDispose({
                type: 'max-height',
                maxHeight: 0.30,
            })
```

- **UI control to toggle between infinite streaming data and static amount of data.**

```javascript
const handleStreamingToggled = ( state ) => {
            toggleStreamingCheckBox.setText( state ? 'Disable infinite streaming data' : 'Enable infinite streaming data' )
            if ( toggleStreamingCheckBox.getOn() !== state ) {
                toggleStreamingCheckBox.setOn( state )
            }
        }
        const toggleStreamingCheckBox = group.addElement( UIElementBuilders.CheckBox )
        toggleStreamingCheckBox.onSwitch(( _, state ) => handleStreamingToggled( state ) )
        handleStreamingToggled( true )
```

## 3D Ellipsoid

- **importing the IIFE files:**

```javascript
<script src="lcjs.iife.js"></script>
```

- **Extracting required parts from LightningCharts JS:**

```javascript
const {
            lightningChart,
            PointStyle3D,
            ColorShadingStyles,
            Themes
        } = lcjs
```

- **Creating the chart 3D object and configuring the Axes:**

```javascript
const chart3D = lightningChart().Chart3D({
            theme: Themes.darkGold,
        }).setTitle('3D scatter data set and confidence ellipsoid')
        chart3D.forEachAxis((axis) => axis.setInterval(-1.8, 1.8, false, true))
```

- **Creating an instance for the scatter and ellipsoid:**

```javascript
const scatterSeries = chart3D.addPointSeries().setName('Scatter series')
        const confidenceEllipsoidSeries = chart3D.addPointSeries().setName('Confidence ellipsoid')
```

- **Creating random data for the scatter series:**

```javascript
const xSize = 1.2
        const ySize = 1.2
        const zSize = 1.5
        const pi2 = Math.PI * 2
        const data = new Array(1000).fill(0).map((_) => {
            const u = Math.random()
            const v = Math.random()
            const theta = u * pi2
            const phi = Math.acos(2.0 * v - 1.0)
            const r = Math.cbrt(Math.random())
            const sinTheta = Math.sin(theta)
            const cosTheta = Math.cos(theta)
            const sinPhi = Math.sin(phi)
            const cosPhi = Math.cos(phi)
            const x = xSize * r * sinPhi * cosTheta
            const y = ySize * r * sinPhi * sinTheta
            const z = zSize * r * cosPhi
            return { x, y, z }
        })
```

**_Note:_** the mathematics here may be challenging. For additional help on those, read more about the [SpherePointPicking](https://mathworld.wolfram.com/SpherePointPicking.html) and more about [how to generate those random points](https://karthikkaranth.me/blog/generating-random-points-in-a-sphere/#better-choice-of-spherical-coordinates).

Applying the examples to create coordinate points in an sphere, the process will create an array with 1000 points.

```javascript
scatterSeries.add(data)
```

Once we have the array data object, we can add it to the scatterSeries instance.

- **Creating the Ellipsoid**

```javascript
confidenceEllipsoidSeries
            .setDepthTestEnabled(false)
            .setColorShadingStyle(new ColorShadingStyles.Simple())
            .setPointStyle(
                new PointStyle3D.Triangulated({
                    // Ellipsoid is rendered as sphere with individual sizes along X, Y and Z axes.
                    shape: 'sphere',
                    size: { x: xSize * 2, y: ySize * 2, z: zSize * 2 },
                    fillStyle: scatterSeries.getPointStyle().getFillStyle().setA(50),
                }),
            )
            .add({ x: 0, y: 0, z: 0 })
```

The Ellipsoid uses the limits specified in the xSize, ySize, zSize constants.

The color of the ellipsoid will be the same obtained in the Scatter series, adding the alpha property to 50%.

## 3D Line Series

- **Importing the iife files.**

```javascript
<script src="lcjs.iife.js"></script>
    <script src="xydata.iife.js"></script>
```

- **Extracting required parts from LightningCharts JS:**

```javascript
const {
            lightningChart,
            AxisTickStrategies,
            Themes
        } = lcjs

        // Extract required parts from xyData.
        const {
            createProgressiveTraceGenerator
        } = xydata
```

- **Creating the chart object and adding properties to the axes:**

```javascript
const chart3D = lightningChart().Chart3D({
            theme: Themes.darkGold,
        })
            // Set 3D bounding box dimensions to highlight X Axis. 
            .setBoundingBox({ x: 1.0, y: 0.5, z: 0.4 })

        // Set Axis titles
        chart3D.getDefaultAxisX().setTitle('Axis X')
        chart3D.getDefaultAxisY().setTitle('Axis Y')
        chart3D.getDefaultAxisZ().setTitle('')
```

- **Creating the Series that will be displayed and the amount of data per series:**

```javascript
const seriesConf = [
            {
                name: 'Series A',
                dataAmount: 50,
            },
            {
                name: 'Series B',
                dataAmount: 50
            },
            {
                name: 'Series C',
                dataAmount: 50
            },
        ]
```

- **Creating the series and generating the data:**

```javascript
seriesConf.forEach((conf, iSeries) => {
            const seriesName = conf.name || ''
            const seriesDataAmount = conf.dataAmount || 100
            const seriesZ = iSeries

            const series = chart3D.addPointLineSeries()
                .setName(seriesName)
```

For each series in the `SeriesConf` array, the name and data amount will be taken from same array.

The series Z will be the number of the current series in the loop.

- **`createProgressiveTraceGenerator`**:

A progressive trace data generator. 
- Generates point data that has a progressive X axis. 
- The data is always derived from the previous point.
- The number of points will be specified in the seriesConf array.

```javascript
const series = chart3D.addPointLineSeries()
                .setName(seriesName)
        
            createProgressiveTraceGenerator()
                .setNumberOfPoints(seriesDataAmount)
                .generate()
                .toPromise()
                .then((data) => {
                    // Map XY data to XYZ data.
                    return data.map((xy) => ({
                        x: xy.x,
                        y: xy.y,
                        z: seriesZ
                    }))
                })
```
The data will be splitted into 3 members, one for each series.
Each class in the batch will contain the x, y and z values.

```javascript
.then((data) => {
                    // Stream data into series very quickly.
                    
                    setInterval(() => {
                        const batch = data.splice(0, 3)
                       
                        if (batch.length > 0) {
                            console.log(batch)
                            series.add(batch)
                            totalDataAmount += batch.length
                            chart3D.setTitle(`3D Line Series (${totalDataAmount} data points)`)
                        }
                    }, 30)
                })
```

![X-Y-Z-VALUES](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/peqkqu2f017gt2bfyf5s.jpg)

## Conclusion

As we have seen, generating 3D graphics is possible embedded within a simple HTML template.

The look and feel properties are mostly provided by Lightning Charts, which reduces the time spent developing a style sheet.

In this exercise, we didn't use Node JS or some other framework that uses type script.

So if you need to work with advanced charts, but only have a traditional website, you shouldn't have a problem making use of the [LightningChart JS library](https://lightningchart.com/js-charts/).

**This is a first part of the 3D charts articles series**, [check out the second part here](https://dev.to/lightningchart/html-charts-with-javascript-pt-2-55ki).

See you in the next article!

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