
When building real-time data visualization applications, performance is often a critical factor—especially when dealing with large datasets and frequent updates. SciChart.js, our high-performance JavaScript Chart Library, is designed to handle demanding use cases, and with the help of scichart-react, an open-source React component wrapper for SciChart, integrating charts into React applications becomes even easier.
To showcase the power of SciChart.js in a React environment, we’ve created a performance demo featuring 100 interactive charts in a drag-and-drop, rearrangeable and resizable dashboard. This demo allows users to test various SciChart features in real time, measuring their impact on rendering speed and interactivity. A built-in toolbar provides controls to toggle effects, animations, and data updates, as well as report chart stats such as FPS, demonstrating how SciChart maintains smooth performance under different configurations.
Individual chart panels in the Dashboard can be dragged to reposition them, resized, maximised or minimised.
With the right settings, this dashboard achieves 60 FPS rendering of 100 JavaScript/React charts containing a total of up to 10,000,000 (ten million) data points, making it an excellent benchmark for high-performance web applications. This blog post walks through the key aspects of the demo, the optimizations used, and how SciChart.js ensures seamless visualization, even at scale.
Let’s get started.
Downloading the Drag & Drop Dashboard Source-Code
The source-code for this tutorial can be found in our SciChart.JS.Examples Github repository. Go ahead and clone the repo and navigate to the Sandbox/CustomerExamples/DragDropDashboardPerformanceDemo folder. From there, simply npm install
and npm start
and the application will start up in a browser.
What can you do with the demo?
This demo shows you how to create a drag & drop chart dashboard in react. It creates 100 charts and arranges them in a grid of 4×25 and allows you to scroll up/down to view all the charts.
Charts are updated in real-time and scrolled to simulate a real-time telemetry monitoring application. You can turn on/off various elements or features to see the effect on optimisation and performance in SciChart.js.
Architecture of the Drag & Drop React Chart Dashboard
Here’s how the demo is architected:
App.tsx
At the top level we have App.jsx. This component contains a number of components:
DraggableProvider
– a React Component to provide a drag & drop panel, as well as context object to store panel stateAppHeader
– Contains checkboxes to enable/disable optimization features, as well as reporting on chart count, total point count and FPS (frames per second)SciChartGroup
– part of the scichart-react library, groups charts into one with a single shared context.DraggablePanel
– Provides a single drag & drop panel that can contain a chart, allowing the user to reposition it by dragging the mouse.ChartPanel
– all logic for rendering a single chart and it’s settings is encapsulated here.
// App.tsx excerpt
return (
<DraggableProvider
style={{ height: "100vh", display: "flex", flexDirection: "column" }}
>
<AppHeader />
<div
className="App"
style={{
position: "relative",
height: "calc(100vh - 40px)",
overflowY: "auto",
overflowX: "hidden",
}}
>
<SciChartGroup>
{charts.map((spec, index) => (
<DraggablePanel key={index} positionable={spec} width="25%">
<ChartPanel
chartSpec={spec}
style={{
width: "100%",
height: "200px",
}}
/>
</DraggablePanel>
))}
</SciChartGroup>
</div>
</DraggableProvider>
);
}
1..N ChartPanel
components are created according to the charts
array in App.tsx
. This array is initialized with 100 ChartSpec
objects, each one representing a JavaScript chart panel, with various options to configure the chart. Look at the code at the top of App.tsx
to see how this initialization and setup works.
// App.tsx - 100 charts specification and state initialization
function AppContent() {
const { chartState, chartCount, pointCount, dataUpdateRate } =
useChartState();
// Initialize chart specs. 100 charts of varying types
const [charts, setCharts] = useState<ChartSpec[]>(() => {
const chartTypes = Object.values(ChartType);
const cols = 4;
return Array.from({ length: chartCount }, (_, index) => ({
chartType: chartTypes[index % chartTypes.length],
pointCount,
dataUpdateRate: dataUpdateRate,
chartTitle: `Chart ${index + 1}`,
position: {
left: `${(index % cols) * 25}%`,
top: Math.floor(index / cols) * 200,
},
drawLabels: chartState.drawLabels,
useNativeText: chartState.useNativeText,
reduceAxisElements: chartState.reduceAxisElements,
cacheLabels: chartState.cacheLabels,
hideOutOfView: chartState.hideOutOfView,
}));
});
How DraggablePanel.tsx Works
DraggablePanel
provides a <div>
which can be dragged to reposition around the screen. This code handles onMouseDown
, onMouseMove
, onMouseUp
and onMouseLeave
to record when the user drags the chart panel, and allows repositioning anywhere in the parent DraggableProvider
. Code here could be extended to provide more complex layout and docking operations, but for a proof of concept of drag-and-drop charts, it does the job!
// DraggablePanel.tsx excerpt
return (
<div
style={{
position: "absolute",
left: isDragged ? `${position.left}px` : position.left,
top: `${position.top}px`,
width,
cursor: isDragging ? "grabbing" : "grab",
zIndex,
border: "1px solid SteelBlue",
}}
onMouseDown={handleMouseDown}
onMouseMove={handleMouseMove}
onMouseUp={handleMouseUp}
onMouseLeave={handleMouseUp}
>
{children}
</div>
);
How ChartPanel.tsx Works
ChartPanel
is a React component which provides a single scichart.js chart, hosted by <SciChartReact/>
which handles full component lifecycle, initialization of the chart and deletion (teardown) on component unmount. The ChartPanel
code is simple, it accepts props which are passed to an initChart
function that sets up the SciChart chart.
export const ChartPanel: React.FC<ChartPanelProps> = ({ chartSpec, style }) => {
// Use chartSpec properties as part of the key to force re-creation when they change
const chartKey = `${chartSpec.drawLabels}-${chartSpec.useNativeText}-` +
`${chartSpec.reduceAxisElements}-${chartSpec.cacheLabels}-` +
`${chartSpec.hideOutOfView}`;
return (
<SciChartReact
key={chartKey}
initChart={async (rootElement) => initChart(rootElement, chartSpec)}
onDelete={(initResult) => initResult.onDeleteChart()}
style={style}
/>
);
};
Initializing the SciChart.js Charts
SciChart chart instantiation and configuration occurs in initChart.ts
. This performs the following operations:
- Creates a JavaScript chart using
SciChartSurface.create()
– one is created for eachDraggablePanel
with 100 charts total in the demo app - X and Y axis are created, with axis options with varying features that are enabled/disabled in order to demonstrate the performance impact of axis elements in multi-chart dashboards.
// initChart.ts excerpt, settings for optimal axis rendering // Depending on optimization flags, create optimal xAxis settings const axisOptions = { // When useNativeText, also use label Caching useNativeText: spec.useNativeText, useSharedCache: spec.cacheLabels, // Hide elements which add to the draw time, but are barely visible due to chart size drawMinorTickLines: !spec.reduceAxisElements, drawMinorGridLines: !spec.reduceAxisElements, drawMajorTickLines: !spec.reduceAxisElements, drawMajorGridLines: !spec.reduceAxisElements, drawMajorBands: !spec.reduceAxisElements, maxAutoTicks: spec.reduceAxisElements ? 5 : undefined, // Reduce number of labels on screen // Hide labels if specified drawLabels: spec.drawLabels, }; // Create X Axis sciChartSurface.xAxes.add( new NumericAxis(wasmContext, { ...axisOptions, labelPrecision: 0, }) ); // Create Y Axis const yAxis = new NumericAxis(wasmContext, { ...axisOptions, // Depending on optimized flag, create optimal xAxis settings growBy: new NumberRange(0.1, 0.1), axisAlignment: EAxisAlignment.Left, });
- Series creation is provided by
createRenderableSeries
, where thechartSpec.chartType
is used to determine which series or chart type to create: Line, Mountain, Scatter or Column. - The brand new
freezeWhenOutOfView
flag (new to SciChart.js v3.5.727) is set whenchartSpec.hideOutOfView
is true, allowing SciChart to pause/resume rendering of charts which are not currently in the viewport. This uses theIntersectionObserver
API to suspend/resume updates on aSciChartSurface
when it is not visible.// initChart.ts excerpt - using freezeWhenOutOfView flag // Create the SciChartSurface const { sciChartSurface, wasmContext } = await SciChartSurface.create(rootElement, { theme: new SciChartJsNavyTheme(), // NEW to SciChart.js 3.5.727! // Freezes drawing (but not data updates) on charts which are // outside the viewport. Drawing will be resumed when a chart // is scrolled into view. This is handled automatically internally and may // be enabled/disabled on a per-chart basis freezeWhenOutOfView: spec.hideOutOfView, } );
- Finally, realtime, dynamic updates to the chart we subscribe to
DataManager.getInstance().subscribeDataUpdate()
. This simulates a real-time incoming data feed with new data-points sent every ~16 ms, for a total maximum frame rate of 60 FPS. New data-points are added to SciChart usingdataSeries.append
and the chart xAxis is scrolled to show the latest N datapoints// initChart.ts excerpt. Subscribe to data updates and scroll the chart const unsubscribeDataUpdates = DataManager.getInstance( spec.dataUpdateRate ).subscribeDataUpdate((timestamp, xValues, yValues) => { // Append data to the chart if (xValues.length === 1) { dataSeries.append(xValues[0], yValues[0]); } else { dataSeries.appendRange(xValues, yValues); } // Scroll the xAxis sciChartSurface.xAxes.get(0).visibleRange = new NumberRange( xValues[xValues.length - 1] - spec.pointCount, xValues[xValues.length - 1] ); });
Tweaking the Chart Count Parameters
Chart count and state parameters are found in ChartStateContext.tsx
. Adjusting any of these variables will modify the app startup parameters. For example, if you wanted to try 300 charts you can update chartCount
. Or 30 charts with 2,000 points each modify pointCount
. dataUpdateRate
modifies the number of points appended per timer tick (1/60th second) to every chart.
// ChartStateContext.tsx excerpt - showing initial state
export function ChartStateProvider({
children,
}: {
children: React.ReactNode;
}) {
const [chartState, setChartState] = useState<ChartState>({
reduceAxisElements: true,
drawLabels: false,
useNativeText: true,
cacheLabels: true,
hideOutOfView: true,
});
const [pointCount] = useState(100); // pointcount per chart
const [chartCount] = useState(100); // number of charts
const [dataUpdateRate] = useState(10); // number of points per 1/60th second per chart
const handlePropertyChange = useCallback(
(propertyName: string, value: boolean) => {
setChartState((prev) => ({ ...prev, [propertyName]: value }));
},
[]
);
We incorporated modifying the pointCount
and dataUpdateRate
into a slider in the top of the app which is declared in AppHeader.tsx
. This lets you slide from 100 points per chart (total 10k data-points), to 1,000 to 10,000 and finally 100,000 (total of 10 million datapoints across 100 charts, with 60k new datapoints per second).
Yes, SciChart.js is powerful enough to render 100 charts, each with 100,000 points, for a total of 10 million data-points and 60k new points per second at an FPS of 50-60 in the browser.

Optimizing the App and Measuring Performance
OK so what can the React dashboard performance demo do?
First up, it creates 100 charts and arranges them in a grid of 4×25. These have varying chart types such as Line, Scatter plot, Mountain (Area) plot and Column chart.
Data is updated in real-time and the charts scroll as new data arrives. FPS is outputted to the top right of the browser, and you can toggle various options to measure the impact of performance optimizations on SciChart.js in a multi-chart dashboard scenario.
Optimization #1 – Freezing Charts that are out of View
You can scroll down or up in the browser to view all 100 charts. Charts out of view are automatically paused/resumed when the Freeze Charts out of View option is checked, which enables the IntersectionObserver
and sciChartSurface.suspendUpdates()
behaviour in initChart.ts
.
With 100 charts in the dashboard, only 20-30 are visible at any one time, so setting or unchecking Freeze Charts out of View has the following impact on performance:
- With Freeze Charts out of View: 57-60 FPS
- Without Freeze Charts out of View: 13 FPS
Wow! A huge difference. This goes to show that you should always optimize your rendering to pause drawing for charts outside of the viewport. Note that data updates are not paused, these will continue to stream into the chart and the chart will redraw the dataset once you bring it into view.
Using
IntersectionObserver
to suspend/resume charts that are out of the viewport has a dramatic effect on performance in large multi-chart scenarios. We are working on including this functionality into the library, with a new flag which will allow suspending of charts outside the viewport.
Optimization #2 – Axis Labels
In this example there are 100 charts in a React Dashboard, each chart has 2 axis total, and each axis by default will have up to 10 labels, or a total of up to 2,000 axis labels for 100 charts.
By default, SciChart.js renders labels using HTML5, which allows for a lot of customization, but not the best performance out of the box. This can be changed with a few simple tweaks.
Select the option Draw Labels. This will reload the app and show yAxis and xAxis labels on each chart panel.
Now, experiment with the Native Text and Cache Labels options. This parameters are passed through to initChart
and affect the setup or initialization of SciChart. These options dramatically change the performance with many charts are on the screen. Here is the impact on performance:
Draw Labels | Native Text | Cache Labels | FPS |
---|---|---|---|
False | – | – | 60 |
True | True | True | 60 |
True | True | False | 55 |
True | False | True | 10 |
True | False | False | 5.5 |
For multi-chart dashboards, or for where many labels are drawn, enabling
SciChartDefaults.useNativeText
andSciChartDefaults.useSharedCache
drammatically speeds up the rendering of SciChart. These properties are not enabled by default, so you will need to set them in your app. For single chart cases, or for few charts, they may not make much difference.
Optimization #3 – Reducing Axis Elements
The next optimization to cover is Reduce Axis Elements. With this option checked, the following options are passed to axis creation.
Each chart has 2 axis, and each axis could have 10 major gridlines, 50 minor gridlines, as well as 10 major and 50 minor ticks (small 2 pixel lines drawn outside of the chart). While aesthetically pleasing, using the defaults can result in up to 12,000 extra elements being drawn to the chart surfaces across 100 charts.
// initChart.ts excerpt. Reducing axis elements to improve performance
// Depending on optimization flags, create optimal xAxis settings
const axisOptions = {
// When useNativeText, also use label Caching
useNativeText: spec.useNativeText,
useSharedCache: spec.cacheLabels,
// Hide elements which add to the draw time, but are barely visible due to chart size
drawMinorTickLines: !spec.reduceAxisElements,
drawMinorGridLines: !spec.reduceAxisElements,
drawMajorTickLines: !spec.reduceAxisElements,
drawMajorGridLines: !spec.reduceAxisElements,
drawMajorBands: !spec.reduceAxisElements,
maxAutoTicks: spec.reduceAxisElements ? 5 : undefined, // Reduce number of labels on screen
// Hide labels if specified
drawLabels: spec.drawLabels,
};
// Create X Axis
sciChartSurface.xAxes.add(
new NumericAxis(wasmContext, {
...axisOptions,
labelPrecision: 0,
})
);
// Create Y Axis
const yAxis = new NumericAxis(wasmContext, {
...axisOptions,
// Depending on optimized flag, create optimal xAxis settings
growBy: new NumberRange(0.1, 0.1),
axisAlignment: EAxisAlignment.Left,
});
Select / unselect the Reduce Axis Elements option to see the effect on performance:
- With Reduce Axis Elements: 60 FPS
- Without Reduce Axis Elements: 30-40 FPS
When many charts are on screen, consider reducing the frequency of labels, axis gridlines, or even disabling elements by setting
axis.drawMinorGridLines
,axisDrawMinorTicks
,axisDrawMajorTicks
andaxis.drawMajorBands
equal to false.
Conclusion
This performance demo highlights just how powerful SciChart.js is when handling large-scale real-time data visualization in a React drag-and-drop dashboard. By leveraging scichart-react, an open-source React wrapper for SciChart, we were able to render 100 interactive charts, update thousands or even millions of data-points, and maintain 60 FPS under optimal conditions—all within the browser.
Through this experiment, we explored various performance optimizations, including freezing off-screen charts, enabling native text rendering, caching labels, and reducing axis elements. Each of these techniques significantly improves performance when dealing with large datasets and multiple charts in a single application.
If you’re interested in pushing SciChart.js further, check out these resources:
- SciChart.js Performance Tips – Learn best practices to optimize rendering performance.
- Getting Started with SciChart.js – A step-by-step guide to building high-performance charts.
- React Chart Tutorials – Learn how to integrate SciChart.js into React applications with scichart-react.
The source code for this demo is available in the SciChart.JS.Examples GitHub repository under Sandbox/CustomerExamples/DragDropDashboardPerformanceDemo
. Clone the repo, experiment with different optimizations, and explore how SciChart.js can elevate your real-time data visualization projects.
For any questions or to share your own SciChart-powered dashboards, join the discussion in the SciChart Forums or reach out to us directly.
Happy coding!
Recent Blogs