Skip to main content

Interactive Waterfall (Spectral) Chart

The Interactive Waterfall Chart is a scientific visualization that stacks multiple spectral line series in a 3D-like perspective. Each series represents a slice of data at a point in time or frequency, creating the appearance of a waterfall of spectra. Hover and click interactions let users select and compare individual slices.

tip

The JavaScript Interactive Waterfall Chart Example can be found in the SciChart.JS Examples Suite on GitHub, or in the live demo at scichart.com/demo.

Above: The JavaScript Interactive Waterfall Spectral Chart example from the SciChart.js Demo

How the Waterfall Effect Works

SciChart.js has no single "waterfall series" type. The depth-stacking effect is achieved by:

  1. Creating one FastLineRenderableSeries per slice, each bound to its own X and Y axis.
  2. Setting overrideOffset on each axis to shift it by a fixed pixel amount, nudging every successive series further back in the visual stack.
  3. Hiding all axes except the frontmost one, so the chart reads cleanly while still using multiple coordinate systems internally.
// Demonstrates how to create a Spectral Waterfall Chart using SciChart.js.
// The waterfall effect is achieved by giving each series its own X and Y axis
// and using overrideOffset to shift each axis so the series appear stacked in depth.
const {
SciChartSurface,
NumericAxis,
FastLineRenderableSeries,
XyDataSeries,
EAxisAlignment,
EXyDirection,
NumberRange,
MouseWheelZoomModifier,
ZoomExtentsModifier,
ZoomPanModifier,
SeriesSelectionModifier,
SciChartJsNavyTheme,
} = SciChart;
// or, for npm: import { SciChartSurface, ... } from "scichart"

const { sciChartSurface, wasmContext } = await SciChartSurface.create(divElementId, {
theme: new SciChartJsNavyTheme(),
disableAspect: true,
});

const seriesCount = 30;
const spectraSize = 200;

for (let i = 0; i < seriesCount; i++) {
// Each series gets its own Y axis, offset upward to create the depth effect.
// overrideOffset shifts the axis position, stacking series behind one another.
const yAxis = new NumericAxis(wasmContext, {
id: `Y${i}`,
axisAlignment: EAxisAlignment.Left,
visibleRange: new NumberRange(-50, 60),
isVisible: i === seriesCount - 1, // only the last (frontmost) axis is visible
overrideOffset: 3 * -i, // each axis is offset 3px upward
});
sciChartSurface.yAxes.add(yAxis);

// Each series also gets its own X axis, offset to the right for the depth effect.
const xAxis = new NumericAxis(wasmContext, {
id: `X${i}`,
axisAlignment: EAxisAlignment.Bottom,
growBy: new NumberRange(0, 0.2),
isVisible: i === seriesCount - 1, // only the last (frontmost) axis is visible
overrideOffset: 2 * i, // each axis is offset 2px to the right
});
sciChartSurface.xAxes.add(xAxis);

const { xValues, yValues } = generateSpectralData(i, spectraSize);
sciChartSurface.renderableSeries.add(
new FastLineRenderableSeries(wasmContext, {
id: `S${i}`,
xAxisId: `X${i}`,
yAxisId: `Y${i}`,
stroke: "#64BAE4",
strokeThickness: 1,
dataSeries: new XyDataSeries(wasmContext, {
xValues,
yValues,
dataSeriesName: `Spectra ${i}`,
}),
})
);
}

// Selection modifier highlights hovered / clicked series
sciChartSurface.chartModifiers.add(
new SeriesSelectionModifier({
enableHover: true,
enableSelection: true,
onHoverChanged: (args) => {
args.allSeries.forEach((s) => {
s.stroke = s.isHovered || s.isSelected ? "#FFBE93" : "#64BAE4";
s.strokeThickness = s.isHovered || s.isSelected ? 3 : 1;
});
},
onSelectionChanged: (args) => {
args.allSeries.forEach((s) => {
s.stroke = s.isSelected ? "White" : s.isHovered ? "#FFBE93" : "#64BAE4";
s.strokeThickness = s.isSelected || s.isHovered ? 3 : 1;
});
},
}),
new ZoomPanModifier({ enableZoom: true, xyDirection: EXyDirection.XDirection }),
new MouseWheelZoomModifier({ xyDirection: EXyDirection.XDirection }),
new ZoomExtentsModifier({ xyDirection: EXyDirection.XDirection })
);

In the code above:

  • overrideOffset: 3 * -i on each Y axis shifts it 3 px upward per slice, moving each series higher than the previous one.
  • overrideOffset: 2 * i on each X axis shifts it 2 px to the right per slice, adding the horizontal perspective tilt.
  • isVisible: i === seriesCount - 1 shows only the frontmost axis so tick marks and labels appear only once.
  • SeriesSelectionModifier with enableHover and enableSelection highlights individual slices on hover and click.
  • Zoom and pan are restricted to the X direction so the depth offset is never accidentally distorted.

The overrideOffset Property

overrideOffset lets you bypass SciChart's automatic axis layout and position an axis at an exact pixel offset from its default location. This is what makes the 3D stacking possible: by incrementing the offset for each successive axis, you physically move each layer back in the chart.

tip

For a deeper explanation of overrideOffset and how it interacts with SciChart's layout system, see Axis Offset and overrideOffset.

Series Selection and Highlighting

Attach a SeriesSelectionModifier to enable hover and click highlighting across all series:

new SeriesSelectionModifier({
enableHover: true,
enableSelection: true,
onHoverChanged: (args) => {
args.allSeries.forEach((s) => {
s.stroke = s.isHovered || s.isSelected ? "#FFBE93" : "#64BAE4";
s.strokeThickness = s.isHovered || s.isSelected ? 3 : 1;
});
},
onSelectionChanged: (args) => {
args.allSeries.forEach((s) => {
s.stroke = s.isSelected ? "White" : s.isHovered ? "#FFBE93" : "#64BAE4";
s.strokeThickness = s.isSelected || s.isHovered ? 3 : 1;
});
},
})

The full SciChart.JS Examples interactive version extends this further with a draggable cross-section annotation and two linked detail charts — see the source on GitHub for the complete implementation.

See Also