Demonstrates how to use multiple styles on a single series on React Charts using SciChart.js, High Performance JavaScript Charts. This uses a RenderDataTransform to split the data so that we can draw the selected points using additional customised drawingProviders. This means that modifiers still see a single series with the original data.
import {
BaseRenderDataTransform,
NumberRange,
RenderPassData,
IPointSeries,
XyDataSeries,
SciChartSurface,
NumericAxis,
TrianglePointMarker,
EllipsePointMarker,
PointMarkerDrawingProvider,
IPointMarker,
FastColumnRenderableSeries,
GradientParams,
Point,
ColumnSeriesDrawingProvider,
DataPointSelectionModifier,
LineSeriesDrawingProvider,
FastLineRenderableSeries,
ILineSeriesDrawingProviderProperties,
ELineDrawMode,
OhlcPointSeriesResampled,
IOhlcPointSeries,
NativeTextAnnotation,
ECoordinateMode,
EHorizontalAnchorPoint,
EVerticalAnchorPoint,
vectorToArrayViewF64,
} from "scichart";
import { appTheme } from "./appTheme";
/**
* This transform turns xy data into ohlc. Unselected points are in y (close).
* Selected points in low for pointmarkers, and selected plus points either side in high for lines.
* If you only need this for points or columns, you could transform to Xyy instead
*/
class SplitRenderDataTransform extends BaseRenderDataTransform<OhlcPointSeriesResampled> {
protected createPointSeries(): OhlcPointSeriesResampled {
return new OhlcPointSeriesResampled(this.wasmContext, new NumberRange(0, 0));
}
protected runTransformInternal(renderPassData: RenderPassData): IPointSeries {
const { xValues: oldX, yValues: oldY, indexes: oldI, resampled } = renderPassData.pointSeries;
// this.pointSeries is the target. Clear the existing values
const { xValues, yValues, highValues, lowValues, indexes } = this.pointSeries;
// Resampled buffers already cover the visible range; raw buffers need the index range.
const iStart = resampled ? 0 : renderPassData.indexRange.min;
const iEnd = resampled ? oldX.size() - 1 : renderPassData.indexRange?.max;
const length = iEnd - iStart + 1;
// Resize before creating array views: a later allocation would invalidate those views.
xValues.resizeFast(length);
yValues.resizeFast(length);
highValues.resizeFast(length);
lowValues.resizeFast(length);
indexes.resizeFast(length);
// Views provide fast access to the native source and target buffers.
const oldXView = vectorToArrayViewF64(oldX, this.wasmContext);
const oldYView = vectorToArrayViewF64(oldY, this.wasmContext);
const oldIndexView = vectorToArrayViewF64(oldI, this.wasmContext);
const xView = vectorToArrayViewF64(xValues, this.wasmContext);
const yView = vectorToArrayViewF64(yValues, this.wasmContext);
const highView = vectorToArrayViewF64(highValues, this.wasmContext);
const lowView = vectorToArrayViewF64(lowValues, this.wasmContext);
const indexView = vectorToArrayViewF64(indexes, this.wasmContext);
const ds = this.parentSeries.dataSeries as XyDataSeries;
let prevSelected = false;
let iOut = 0;
for (let i = iStart; i <= iEnd; i++) {
const index = resampled ? oldIndexView[i] : i;
const md = ds.getMetadataAt(index);
xView[iOut] = oldXView[i];
indexView[iOut] = oldIndexView[i];
let nextSelected = false;
if (i < iEnd) {
const nextmd = ds.getMetadataAt(index + 1);
nextSelected = nextmd.isSelected;
}
yView[iOut] = md.isSelected ? NaN : oldYView[i];
// For pointmarkers we just need the point itself
lowView[iOut] = md.isSelected ? oldYView[i] : NaN;
// need points either side of the selected value for the line to draw.
highView[iOut] = prevSelected || md.isSelected || nextSelected ? oldYView[i] : NaN;
prevSelected = md.isSelected;
iOut++;
}
return this.pointSeries;
}
}
export const drawExample = async (rootElement: string | HTMLDivElement) => {
// Create a SciChartSurface
const { sciChartSurface, wasmContext } = await SciChartSurface.create(rootElement, {
theme: appTheme.SciChartJsTheme,
});
// Create X,Y Axis
sciChartSurface.xAxes.add(new NumericAxis(wasmContext, { growBy: new NumberRange(0.05, 0.05) }));
sciChartSurface.yAxes.add(new NumericAxis(wasmContext, { growBy: new NumberRange(0, 0.05) }));
// Column series with different gradient fill for selected columns
const xValues = Array.from({ length: 20 }, (x, i) => i);
const colyValues = xValues.map(() => 10 + Math.random() * 40);
const colmetadata = xValues.map(() => ({
isSelected: Math.random() < 0.3,
}));
const columnSeries = new FastColumnRenderableSeries(wasmContext, {
fillLinearGradient: new GradientParams(new Point(0, 0), new Point(0, 1), [
{ color: appTheme.MutedRed, offset: 0 },
{ color: appTheme.MutedTeal, offset: 1 },
]),
dataSeries: new XyDataSeries(wasmContext, {
xValues,
yValues: colyValues,
metadata: colmetadata,
containsNaN: true,
}),
stroke: "transparent",
});
// We cannot use a paletteProvider to change a gradient fill, so we have to use a second drawingProvider
const selectedColDP = new ColumnSeriesDrawingProvider(
wasmContext,
columnSeries,
// configure this to draw using the selected points
(ps) => (ps as IOhlcPointSeries).lowValues
);
selectedColDP.getProperties = (parentSeries) => {
const { stroke, strokeThickness, fill } = parentSeries;
return {
opacity: 1,
// Opacity setting does not currently apply to the gradient colors, so we have to apply it individually
fillLinearGradient: new GradientParams(new Point(0, 0), new Point(0, 1), [
{ color: appTheme.MutedRed + "88", offset: 0 },
{ color: appTheme.MutedSkyBlue + "88", offset: 1 },
]),
stroke,
strokeThickness,
fill,
};
};
columnSeries.drawingProviders.push(selectedColDP);
columnSeries.renderDataTransform = new SplitRenderDataTransform(
columnSeries,
wasmContext,
columnSeries.drawingProviders
);
sciChartSurface.renderableSeries.add(columnSeries);
const lineyValues = xValues.map((x) => 30 + x + x * Math.random());
const linemetadata = xValues.map(() => ({
isSelected: Math.random() < 0.3,
}));
// Line series with different pointmarker and dashed line for selected sections
const lineSeries = new FastLineRenderableSeries(wasmContext, {
dataSeries: new XyDataSeries(wasmContext, {
xValues,
yValues: lineyValues,
metadata: linemetadata,
containsNaN: true,
}),
pointMarker: new EllipsePointMarker(wasmContext, {
width: 14,
height: 14,
strokeThickness: 0,
fill: appTheme.VividSkyBlue,
}),
stroke: appTheme.VividTeal,
strokeThickness: 3,
drawNaNAs: ELineDrawMode.DiscontinuousLine,
});
const trianglePM = new TrianglePointMarker(wasmContext, {
width: 15,
height: 15,
strokeThickness: 0,
fill: appTheme.VividOrange,
});
// Additional line drawing for selected segments
const selectedLineDP = new LineSeriesDrawingProvider(
wasmContext,
lineSeries,
(ps) => (ps as IOhlcPointSeries).highValues
);
// Make this drawingProvider use dashed lines
selectedLineDP.getProperties = (parentSeries) => {
const { stroke, strokeThickness, isDigitalLine, lineType, drawNaNAs } = parentSeries;
return {
stroke,
strokeThickness,
strokeDashArray: [3, 4],
isDigitalLine,
lineType,
drawNaNAs,
containsNaN: true,
} as ILineSeriesDrawingProviderProperties;
};
// Add this as the first drawingProviders so it draws behind all pointmarkers
lineSeries.drawingProviders.unshift(selectedLineDP);
// Additional point drawing for selected points
const triangleDP = new PointMarkerDrawingProvider(
wasmContext,
lineSeries,
(ps) => (ps as IOhlcPointSeries).lowValues
);
triangleDP.getProperties = () => {
return { pointMarker: trianglePM as IPointMarker };
};
lineSeries.drawingProviders.push(triangleDP);
// Apply the transform to all the drawingProviders
lineSeries.renderDataTransform = new SplitRenderDataTransform(lineSeries, wasmContext, lineSeries.drawingProviders);
sciChartSurface.renderableSeries.add(lineSeries);
sciChartSurface.annotations.add(
new NativeTextAnnotation({
xCoordinateMode: ECoordinateMode.Pixel,
yCoordinateMode: ECoordinateMode.Pixel,
x1: 20,
y1: 20,
horizontalAnchorPoint: EHorizontalAnchorPoint.Left,
verticalAnchorPoint: EVerticalAnchorPoint.Top,
text: "Selected points are styled differently. Click and drag to change the selection",
textColor: appTheme.ForegroundColor,
fontSize: 16,
opacity: 0.77,
})
);
// Optional: Add Interactivity Modifiers
sciChartSurface.chartModifiers.add(
new DataPointSelectionModifier({
allowClickSelect: true,
onSelectionChanged: () => {
lineSeries.renderDataTransform.requiresTransform = true;
columnSeries.renderDataTransform.requiresTransform = true;
},
})
);
sciChartSurface.zoomExtents();
return { sciChartSurface, wasmContext };
};
This example demonstrates how to create a multi-style series in SciChart.js using React. It shows how to display charts where selected data points are styled differently from the rest, by leveraging a custom RenderDataTransform (see SplitRenderDataTransform) and multiple drawing providers. The example uses high-performance series such as FastLineRenderableSeries and FastColumnRenderableSeries, and integrates interactive modifiers like DataPointSelectionModifier to enable dynamic selection.
The core technical concept is the use of a custom RenderDataTransform to split the original data into segments that can be rendered with multiple visual styles. This is achieved by processing each data point and, based on custom metadata (i.e. whether metadata.isSelected is true), assigning different values for drawing providers. The transform is applied to both the line and column series to determine which segments should display different styles. For more details, see the RenderDataTransforms API Documentation.
The example highlights several advanced features:
GradientParams API to differentiate between selected and unselected columns. Learn more from the GradientParams TypeDoc API.DataPointSelectionModifier allows users to click and drag to change the selection on the chart, which then triggers a re-transform of the render data. This interactivity is further detailed in the Data Point Selection documentation example.This example is fully integrated with React through the <SciChartReact/> component. The implementation follows best practices for React integration by encapsulating the chart initialization within a dedicated React component and leveraging the WebAssembly wasmContext for optimal performance. Developers can explore more about efficient React integration in the React Charts with SciChart.js: Introducing “SciChart React”. Additionally, performance is optimized by using fast-rendering series and handling data updates efficiently with the render data transform.
This comprehensive approach combining real-time data transformation, custom drawing customization, and interactive components provides a robust template for developers looking to create dynamic and performant charts using SciChart.js in React.










