Pre loader

Creating a React Drag & Drop Chart Dashboard Performance Demo with 100 Charts

Categories

Creating a React Drag & Drop Chart Dashboard Performance Demo with 100 Charts

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 state
  • AppHeader – 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 each DraggablePanel 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 the chartSpec.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 when chartSpec.hideOutOfView is true, allowing SciChart to pause/resume rendering of charts which are not currently in the viewport. This uses the IntersectionObserver API to suspend/resume updates on a SciChartSurface 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 using dataSeries.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.

Realtime React Chart Dashboard Performance Demo
Drag the DataRate Slider to change from 100 points to 1k, 10k or 100k per chart – for a total of 10 million datapoints across all charts

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.

Realtime React Charts Drag & Drop Dashboard Performance Demo with 100 charts

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.

React Dashboard Performance Optimization - Freeze Charts out of View

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.

React Dashboard Performance Optimization - Axis optimization controls

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 LabelsNative TextCache LabelsFPS
False60
TrueTrueTrue60
TrueTrueFalse55
TrueFalseTrue10
TrueFalseFalse5.5

For multi-chart dashboards, or for where many labels are drawn, enabling SciChartDefaults.useNativeText and SciChartDefaults.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.

React Dashboard Performance Optimization - Reduce Axis Elements

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 and axis.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:

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!

By Andrew Burnett-Thompson | Feb 12, 2025
CEO / Founder of SciChart. Masters (MEng) and PhD in Electronics & Signal Processing.Follow me on LinkedIn for more SciChart content, or twitter at @drandrewbt.

Leave a Reply