Removing lots of series can be slow

Author: VimisoCreated Jan 19, 2026Updated Jan 19, 2026

v5 - removing lots of multiple series is slow. The functionality is when I change timeframe I want to load new plots to fit that timeframe. These plots are split over multiple series so that they have their own unique lines. Performance is fine drawing them and the chart is very responsive once they're drawn, even when there are hundreds or even thousands. However, when it comes to tearing them down using removeSeries it takes a while, this blocks the main thread in my app until they've finished being removed - which is not ideal. But I kind of have to remove them because there might be more or less plot available on a new timeframe. Here's the code in a React context:

javascript
const useIndicators = ({
  indicatorDefs,
  chartRef,
  indicatorSeriesRef,
  startDate,
  endDate,
  symbolId,
  getIndicatorColor,
}) => {
  const [isIndicatorsLoading, setIsIndicatorsLoading] = useState(false);

  const applyIndicators = useCallback(
    async (interval, instances, defsOverride = null) => {
      setIsIndicatorsLoading(true);

      try {
        const targetInstances = Array.isArray(instances) ? instances : [];
        const sourceIndicators = Array.isArray(defsOverride) ? defsOverride : indicatorDefs;
        const targetDefinitions = targetInstances
          .map((instance) => ({
            instance,
            definition: sourceIndicators.find((indicator) => indicator.label === instance.label),
          }))
          .filter((entry) => entry.definition);
        if (targetDefinitions.length === 0) {
          return;
        }

        await Promise.all(
          targetDefinitions.map(async ({ instance, definition }) => {
            const indicator = definition;
            const existingSeries = indicatorSeriesRef.current.get(instance.id);
            const seriesList = Array.isArray(existingSeries)
              ? existingSeries
              : existingSeries
                ? [existingSeries]
                : [];

            try {
              if (!chartRef.current) {
                return;
              }

              if (seriesList.length > 0) {
                seriesList.forEach((series) => chartRef.current.removeSeries(series));
                indicatorSeriesRef.current.delete(instance.id);
              }

              const response = await api.post(indicator.endpoint, {
                interval,
                start_date: startDate,
                end_date: endDate,
                symbol_id: symbolId,
                settings: instance.settings ?? {},
              });

              const payload = Array.isArray(response.data) ? response.data : [];

              let dataSets = [];
              if (payload.length > 0 && !Array.isArray(payload[0]) && payload[0]?.points) {
                dataSets = payload;
              } else if (Array.isArray(payload[0])) {
                dataSets = payload.map((points) => ({ points }));
              } else {
                dataSets = [{ points: payload }];
              }

              const nextSeriesList = [];
              while (nextSeriesList.length < dataSets.length) {
                const series = chartRef.current.addSeries(indicator.seriesType, {
                  title: dataSets.length > 1 ? `${indicator.label}` : indicator.label,
                  color: getIndicatorColor(indicator),
                  lineWidth: indicator.lineWidth ?? 2,
                  lineStyle: indicator.lineStyle ?? LineStyle.SparseDotted,
                  crosshairMarkerVisible: false,
                  priceLineVisible: false,
                  lastValueVisible: true,
                });
                nextSeriesList.push(series);
              }

              indicatorSeriesRef.current.set(instance.id, nextSeriesList);
              console.log('indicator update', instance.id, nextSeriesList.length);

              const lineStyleMap = {
                solid: LineStyle.Solid,
                dashed: LineStyle.Dashed,
                dotted: LineStyle.Dotted,
                sparse_dotted: LineStyle.SparseDotted,
              };

              dataSets.forEach((dataSet, index) => {
                const points = Array.isArray(dataSet?.points) ? dataSet.points : [];
                const indicatorData = points.map((point) => ({
                  time: toChartTimeSeconds(point.time),
                  value: point.price,
                }));
                const lineType = dataSet?.style?.line_type;
                const lineStyle =
                  lineStyleMap[lineType] ?? indicator.lineStyle ?? LineStyle.SparseDotted;
                const isLast = index === dataSets.length - 1;
                const showPrice = dataSet?.style?.show_price_last_only
                  ? isLast
                  : dataSet?.style?.show_price;
                const showLabel = dataSet?.style?.show_label_last_only
                  ? isLast
                  : dataSet?.style?.show_label;
                nextSeriesList[index].applyOptions({
                  lineStyle,
                  color: getIndicatorColor({
                    ...indicator,
                    settings: instance.settings ?? {},
                  }),
                  lastValueVisible: showPrice ?? true,
                  title: showLabel === false ? '' : indicator.label,
                });
                nextSeriesList[index].setData(indicatorData);
              });
            } catch (error) {
              const existing = indicatorSeriesRef.current.get(instance.id) || [];
              existing.forEach((series) => series.setData([]));
            }
          })
        );
      } finally {
        setIsIndicatorsLoading(false);
      }
    },
    [chartRef, indicatorDefs, indicatorSeriesRef, startDate, endDate, symbolId, getIndicatorColor],
  );

  return { applyIndicators, isIndicatorsLoading };
};

...specifically, you can see I am removing the series in this snippet:

javascript
if (seriesList.length > 0) {
  seriesList.forEach((series) => chartRef.current.removeSeries(series));
  indicatorSeriesRef.current.delete(instance.id);
}

...before creating fresh ones. It all works well, but that loop does take 2-4 seconds, which blocks other aspects of my app a little longer than I'd like. I've read that in previous versions there was something like:

"Remove Series without Animation: Use series.remove(false) to skip animations, which saves time when clearing multiple series, particularly if you are switching timeframes."

...but I'm now sure that function exists in v5.

Any ideas, or approaches, regarding how I can speed this process up?

Source: tradingview/lightweight-charts