ScatterPlot

Scatterplots are a LineChart alternative that allows for disconnected data points with configurable size and colors.

Screenshot of component ScatterPlot example

Charts rely on a DataSource to provide their data, and use a series of accessors to optionally control formatting. It is recommended to read the DataSource documentation first.

Usage

One common use of a ScatterPlot is to draw probabilistic sensor data, multidimensional signals, or signals which don't suit a 'line based' point-to-point relationship.

To use a ScatterPlot, the minimum requirements are similar to other chart types:

import {
ChartContainer,
ScatterPlot,
RealTimeSlicingDomain,
HorizontalAxis,
VerticalAxis
} from '@electricui/components-desktop-charts'
import { useMessageDataSource } from '@electricui/core-timeseries'
 
const OverviewPage = () => {
// structured
const sensorDataSource = useMessageDataSource('propellor')
 
return (
<React.Fragment>
<ChartContainer>
<ScatterPlot dataSource={sensorDataSource} />
<RealTimeSlicingDomain window={10000} />
<VerticalAxis />
<HorizontalAxis />
</ChartContainer>
</React.Fragment>
)
}

However, as ScatterPlot are often used for multidimensional, or non-timeseries data, some custom event processing will usually be required (or inbound data must be pre-structured) to provide suitable values for plotting.

Data Preparation

  • Use a Transformer pipeline to format the Event stream, or
  • Use accessor syntax with accessor, and optionally colorAccessor and sizeAccessor , or
  • Provide a formatted DataSource with structured fields:
    • Positioning of points with x and y and z members,
    • Color control by providing r, g and b members THREE.Color or string,
    • Size control with the size member.

Ultimately, positioning of the dots is achieved with x, y, z members in the Event.

Optionally format each point with size, r, g, b members.

const scatterEvent = {
x: data.servoA,
y: data.servoB,
z: time,
size: data.systemLoad,
r: 0.06, // Color channels are [0-1] floating point values
g: 153 / 255,
b: 96 / 255,
}

Axis and Domains

Use TimeAxis with RealTimeDomain for conventional one-dimensional data streamed from hardware.

Screenshot of component ScatterPlot axis-time

The simple numeric datasource is automatically handled.

import {
ChartContainer,
ScatterPlot,
RealTimeDomain,
TimeAxis,
VerticalAxis
} from '@electricui/components-desktop-charts'
import { useMessageDataSource } from '@electricui/core-timeseries'
 
const OverviewPage = () => {
const analogDataSource = useMessageDataSource('sine')
 
return (
<React.Fragment>
<ChartContainer>
<ScatterPlot dataSource={analogDataSource} />
<RealTimeDomain window={10000} />
<VerticalAxis />
<TimeAxis />
</ChartContainer>
</React.Fragment>
)
}

For multi-dimensional data, use HorizontalAxis and RealTimeSlicingDomain.

Screenshot of component ScatterPlot axis
import {
ChartContainer,
ScatterPlot,
RealTimeSlicingDomain,
VerticalAxis,
HorizontalAxis
} from '@electricui/components-desktop-charts'
import { useMessageDataSource } from '@electricui/core-timeseries'
import { coalesce } from '@electricui/dataflow'
import { useDataTransformer } from '@electricui/timeseries-react'
import { Colors } from '@blueprintjs/core'
 
const OverviewPage = () => {
const xDataSource = useMessageDataSource('lvdt_x')
const yDataSource = useMessageDataSource('lvdt_y')
 
const manipulatedDataSource = useDataTransformer(() => {
return coalesce({x: xDataSource, y: yDataSource})
})
 
return (
<React.Fragment>
<ChartContainer width={400} height={400}>
<ScatterPlot
dataSource={manipulatedDataSource}
color={Colors.BLUE5}
size={4}
/>
<RealTimeSlicingDomain
window={5000}
xMin={0}
xMax={1000}
yMin={0}
yMax={1000}
/>
<VerticalAxis />
<HorizontalAxis />
</ChartContainer>
</React.Fragment>
)
}

See the coalesce transformer documentation for notes on how DataSource pairs are being remapped to the x and y members required by ScatterPlot.

Geometry Cache size

The ScatterPlot component uses a statically declared internal buffer for geometry storage.

By default, this is internally managed, but can be overridden with the maxItems property to increase/reduce the potential RAM impact of the chart.

This is helpful when you know the incoming data stream is slow, only covers a short timespan, or if high bandwith data is being plotted (>10kps, >1M points).

Electric UI will internally allocate the internal ping-pong geometry buffers as deemed optimal for performance.

As such, the actual value may be some multiple of 4096 entries.

<ScatterPlot dataSource={analogDataSource} maxItems={6000} />

If maxItems isn't big enough to buffer for the selected domain, then users will see data dropping off the scatterplot.

Formatting

Global Color

For global colour configuration, the color property accepts a string or number.

Passing hex color="#0066cc locally is fine, but we recommend using centralised colors to maintain consistency.

Screenshot of component ScatterPlot color-global
import { Colors } from '@blueprintjs/core'
<ScatterPlot dataSource={sensorDS} color="#0066cc" />
<ScatterPlot dataSource={sensorDS} color={Colors.GREEN5} />

It can also accept a threejs Color.

Screenshot of component ScatterPlot color-global-three
import { Color } from 'three'
 
const OverviewPage = () => {
const col = new Color()
col.setHSL(0.1, 0.7, 0.6)
 
return (
<React.Fragment>
<ChartContainer>
<ScatterPlot dataSource={sensorDS} color={col} />
<RealTimeDomain window={10000} />
<VerticalAxis />
<TimeAxis />
</ChartContainer>
</React.Fragment>
)
}

Color Accessor

For per-point color control, use accessor syntax to return a string, hex value or threejs Color.

This can be used to highlight values of interest, or integrate another dimension of sensor data into the scatter point properties.

Screenshot of component ScatterPlot color-advanced
const OverviewPage = () => {
function colorFromValue(value: number): string {
if (value > WARN_HIGH) {
return Colors.RED3
} else if (value < WARN_LOW) {
return Colors.COBALT3
} else {
return Colors.GREEN5
}
}
 
return (
<React.Fragment>
<ChartContainer>
<ScatterPlot
dataSource={sensorDS}
colorAccessor={(data, time) => colorFromValue(data.y)}
/>
<RealTimeDomain window={10000} />
<VerticalAxis />
<TimeAxis />
</ChartContainer>
</React.Fragment>
)
}

For a more complex example which interpolates color values, look at this example:

Global Size

Rendered point size is globally configured by providing the size property with a positive number.

Screenshot of component ScatterPlot size-global
<ScatterPlot dataSource={sensorDS} size={1} />
<ScatterPlot dataSource={sensorDS} size={4} />
<ScatterPlot dataSource={sensorDS} size={8} />

Size Accessor

For per-point size control, use accessor syntax to return a number.

An example use-case is conveying the signal-strength or confidence values of rangefinder sensors through variable sizing.

Carefully consider the legibility of your resulting plot with variable sizing, and consider clamping the minimum and maximum point size to legible values

Screenshot of component ScatterPlot size-advanced

This example interpolates point sizing in a similar manner to the hue interpolation described in the Scatterplot Gradient Example.

const OverviewPage = () => {
 
function valueToSize(value: number): number {
// Normalise the raw value into 0-1 based on min/max
const valueInRange = invlerp(SENSITIVITY_MIN, SENSITIVITY_MAX, value)
// Re-map the 0-1 to a clamped size
const size = lerp(PT_SIZE_MIN, PT_SIZE_MAX, valueInRange)
return size
}
 
return (
<ChartContainer>
<ScatterPlot
dataSource={lidarDS}
color={Colors.BLUE5}
sizeAccessor={(data, time) =>
valueToSize(data.strength)
}
/>
<RealTimeDomain window={10000} />
<VerticalAxis />
<TimeAxis />
</ChartContainer>
)
}