Creating A Bubble Chart With NodeJS

Omar Urbano | LightningChart - Aug 16 '22 - - Dev Community

What's behind creating a colorful and playful bubble chart/graph?

JS-Bubble-Chart

Scatter Charts

If you have any experience or background in finance or statistics, you'll easily detect that a bubble graph is simply a variation of a scatter chart where the bubbles size is a 3D representation of the data magnitude.

Here's a normal scatter chart built using the PointSeries property of the LightningChart JS library.

This "basic" scattergram is perfect for visualizing the relationship between variables.

Simple Scatter Chart

And here's how a scatter graph with 1 million data points looks like. Ideally, these advanced variations come with interactivity features to zoom in/out such a large amount of data.

1 million data points scatter chart

But scatter series also support 3D rendering. The main capability of this example is to constantly process new data as the processing happens in real-time.

3D real-time scatter chart

Scatter charts are also used in science and medical research. Here's the Flow Cytometry scatter series variation.

(Flow cytometry is a common technique in research and clinical practices. It allows to measure cells in heterogeneous samples).

Flow Cytometry Chart

And with that explanation, let's code a bubble chart :)


Setting Up The Project's Template

To follow this tutorial, please download the RAR project template. You can download the template here.

1.After downloading, open the folder in Visual Studio, and you'll see a file tree like this:

File tree

2.Now, let's open a new terminal and as usual in a Node JS project, we will have to run our NPM Install command.

That'd be all for our initial setup.


CHART.ts

Inside this file, there will be code for creating our application, configuring animations, and formatting data.

1.Declare the constant lcjs that will refer to our @arction/lcjs library.

2.Extract required classes from lcjs:

// Import LightningChartJS
const lcjs = require('@arction/lcjs')

// Extract required parts from LightningChartJS.
const {
    lightningChart,
    SolidFill,
    SolidLine,
    ColorRGBA,
    emptyFill,
    FontSettings,
    AutoCursorModes,
    Animator,
    AnimationEasings,
    UIDraggingModes,
    UIOrigins,
    ColorPalettes,
    AxisTickStrategies,
    emptyLine,
    Themes
} = lcjs
Enter fullscreen mode Exit fullscreen mode


3.Now let's define the text and color settings:

// Define colors to configure chart and bubbles.
const colors = {
    background: ColorRGBA(255, 255, 255),
    graphBackground: ColorRGBA(220, 255, 255),
    title: ColorRGBA(0, 100, 0),
    subTitle: ColorRGBA(0, 100, 0),
    bubbleBorder: ColorRGBA(0, 0, 0),
    bubbleFillPalette: ColorPalettes.fullSpectrum(100)
}
Enter fullscreen mode Exit fullscreen mode

Note: Inside the colors class we will store common properties for easier access later.


4.Now we have to define the font settings:

// Define font settings.
const fonts = {
    subTitle:new FontSettings({
        size: 15,
    }),
        title: new FontSettings({
        size: 40,
        weight: 400
    })
}
Enter fullscreen mode Exit fullscreen mode


5.Creating the chart object:

// Create chart with customized settings
const chart = lightningChart()
    .ChartXY({
        theme: Themes.lavender,        
    })
    .setTitle('Custom Styled Chart')
    .setTitleMarginTop(6)
    .setTitleMarginBottom(0)
    .setPadding({ left: 5, right: 5, top: 30, bottom: 30 })
    .setAutoCursorMode(AutoCursorModes.disabled)
    .setMouseInteractions(false)
Enter fullscreen mode Exit fullscreen mode

Within the chart object, we need to define three properties, theme, cursor mode, and the mouse interactions.

Theme:
the lcjs library has several default implementations and can be accessed by Themes. More about themes in the documentation.

setAutoCursorMode:
allows to define the behavior of the AutoCursor in the chart.
Supported behaviors include AutoCursor disabled, onHover, and snapToClosest. More about this here.

setMouseInteractions:
Set mouse interactions enabled. Implementations should update the mouse-interactions of any Shapes they may contain here.

6.Adding the axes to the chart:

// Get axes.
const axes = {
    bottom: chart.getDefaultAxisX(),
    left: chart.getDefaultAxisY(),
    top: chart.addAxisX(true),
    right: chart.addAxisY(true).setChartInteractions(false)
}
Enter fullscreen mode Exit fullscreen mode
getDefaultAxisX / AxisY:
Get the axis object. With this object, we can add more properties to the specific axis.

addAxisX / addAxisY:
this will return the axis created.

setChartInteractions:
this will set all the mouse interactions in the chart within the axis at once.

7.Adding the UIelement to the chart:

chart.addUIElement(undefined, chart.uiScale)
    .setPosition({ x: 50, y: 93 })
    .setOrigin(UIOrigins.Center)
    .setText('- With Bubbles -')
    .setTextFont(fonts.subTitle)
    .setDraggingMode(UIDraggingModes.notDraggable)
    .setBackground((bg) => bg
        .setFillStyle(emptyFill)
        .setStrokeStyle(emptyLine)
    )
Enter fullscreen mode Exit fullscreen mode

With the addUIElement we can create a single text element to the chart. In this case, we are creating and formatting the subtitle.

And you'll notice that almost all the properties are very familiar to CSS properties.

setDraggingMode makes non-draggable the text element. If the property is removed, then the subtitle element will be draggable.


8.Configure the axes:

// Axis mutator.
const overrideAxis = (axis) => axis
    .setTickStrategy(AxisTickStrategies.Empty)
    .setTitleMargin(0)
    .setMouseInteractions(undefined)
Enter fullscreen mode Exit fullscreen mode
AxisTickStrategies:

Collection of available AxisTickStrategies. AxisTickStrategies modify logic of drawing Axis Ticks and formatting to better suit different user applications. For example, a DateTime Axis is created by selecting AxisTickStrategies.DateTime. More about this in the documentation.

setMouseInteractions:
Set if mouse and cursor interactions should be disabled during scrolling animations for the chart's series.

Parameters:
state: boolean.

True if mouse and cursor interactions should be disabled during scrolling animations, false if not.


9.Configuring the bubbles:

[axes.bottom, axes.left].forEach(axis => axis.setInterval(-100, 100).setScrollStrategy(undefined))
// Ratio between bubble ellipse width / height.
const bubbleWidthHeightRatio = {
    x: window.innerHeight / window.innerWidth,
    y: 1
}

// Create instance of ellipse series to draw bubbles.
const ellipseSeries = chart.addEllipseSeries()
let bubbleCount = 0
Enter fullscreen mode Exit fullscreen mode
setInterval:
Set axis scale interval.

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

const bubbleWidthHeightRatio:

This constant will help to have an initial ratio size for axes X and Y. These values will be calculated for each bubble later.

addEllipseSeries:
Method for adding a new EllipseSeries to the chart. This series type visualizes a collection of ellipses.

bubbleDragHandler:
const bubbleDragHandler = (figure, event, button, startLocation, delta) => {
    const prevDimensions = figure.getDimensions()
    figure.setDimensions(Object.assign(prevDimensions, {
        x: prevDimensions.x + delta.x * figure.scale.x.getPixelSize(),
        y: prevDimensions.y + delta.y * figure.scale.y.getPixelSize()
    }))
}
Enter fullscreen mode Exit fullscreen mode

In order to simulate the drag of each bubble, we will need to obtain the interval and the size of the pixels of the scale direction.

To obtain the interval, the dimension of each axis will be obtained from the figure object and also we will sum the delta value to this result.

Delta is a variable that will contain the central value within the chart.

To adjust for interval changes, multiply the above result by the pixel size in each scaling direction.

To get the pixel size on each axis, the getPixelSize() function must be used.

Create resizeBubble array and sizeArray to store the values separately.

const resizeBubble = []
const sizeArray = []
Enter fullscreen mode Exit fullscreen mode



10.Now, let's add the bubbles

const addBubble = (pos, size) => {
    const radius = size * 2.5
    const borderThickness = 1 + size * 1.0

    const color = colors.bubbleFillPalette(Math.round(Math.random() * 99))
    const fillStyle = new SolidFill({ color })
    const strokeStyle = new SolidLine({ fillStyle: colors.bubbleBorder, thickness: borderThickness })

    const figure = ellipseSeries.add({
        x: pos.x,
        y: pos.y,
        radiusX: radius * bubbleWidthHeightRatio.x,
        radiusY: radius * bubbleWidthHeightRatio.y
    })
        .setFillStyle(fillStyle)
        .setStrokeStyle(strokeStyle)

    // Make draggable by mouse.
    figure.onMouseDrag(bubbleDragHandler)
    bubbleCount++
    return figure
}
Enter fullscreen mode Exit fullscreen mode

The addBubble function will create the bubbles by collecting values from all previous constants and functions the we worked before.

The pos(position) and size parameters, will be provided when this function is executed.

The Radius constant is equal to the size value. The multiplication works as a ratio increaser, you can increase or decrease it by removing or modifying the value of this.

Color, fillStyle, and strokeStyle, are UI properties that use values from the colors class that we created at the beginning of the project.

The figure object will execute the function addEllipseSeries, this function is provided by the LC library, and it creates ellipses figures for an XY chart.

Inside the figure constant, we will encapsulate the position and radius properties for each bubble.

Finally, the UI properties and the Drag handler function will be added to the bubble.

const addRandomBubble = () => {
    const pos = {
        x: Math.random() * 200 - 100,
        y: Math.random() * 200 - 100
    }
    const size = 1 + Math.random() * 7.0
    sizeArray.push(size)
    resizeBubble.push(addBubble(pos, size))
}
Enter fullscreen mode Exit fullscreen mode

The addBubble function will be executed by the addRandomBubble.

This function will create random position and size values.

If you want to use real data from a JSON object or database, you can modify this function.

// Amount of bubbles to render.
const bubbleMaxCount = 100

// Animate bubbles creation.
Animator(() => undefined)(2.5 * 1000, AnimationEasings.ease)([[0, bubbleMaxCount]], ([nextBubbleCount]) => {
    while (bubbleCount < nextBubbleCount)
        addRandomBubble()
})
Enter fullscreen mode Exit fullscreen mode

To finish with the code, we will need to execute the Animator function from the LC library.

The AnimationEasings collector, will help us to work with some values for the animation process. You can learn more about this property here.

Note: The multiplication values will affect the display speed of each bubble.

bubbleMaxCount will be the limit of how many bubbles we could create on the chart.

As long as nextBubbleCount is less than bubbleCount, a new bubble will be created.


NPM Start

Finally, we need to run the npm start command in a new terminal and the chart will be allocated in the local server http://localhost:8080/.

Control + click to follow the route.

local host

Here's our chart:

Bubble-Chart-with-NodeJS-and-LightningChart


As seen, bubble charts (and generally scatter charts), are great ways to visualize data and compare different variables.

Scattergrams are easy to understand and can be used to quickly identify patterns and trends.

Essentially, Bubble charts tend to be visually appealing and can be used to highlight specific data points.

Feel free to replicate this tutorial with your own data values.

See you in the next article!


Written by:
Omar Urbano | Software Engineer
Find me on LinkedIn

. . . . . . . . . . . . . . . .