What's behind creating a colorful and playful bubble chart/graph?
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.
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.
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.
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).
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:
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
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)
}
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
})
}
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)
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)
}
- 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)
)
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)
- 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
- 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()
}))
}
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 = []
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
}
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))
}
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()
})
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.
Here's our chart:
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