diff --git a/packages/grafana-ui/src/components/uPlot/Plot.test.tsx b/packages/grafana-ui/src/components/uPlot/Plot.test.tsx
index 2e543ac5f46..999d1fd0d56 100644
--- a/packages/grafana-ui/src/components/uPlot/Plot.test.tsx
+++ b/packages/grafana-ui/src/components/uPlot/Plot.test.tsx
@@ -7,6 +7,7 @@ import uPlot from 'uplot';
import createMockRaf from 'mock-raf';
import { UPlotConfigBuilder } from './config/UPlotConfigBuilder';
import { preparePlotData } from './utils';
+import { SeriesProps } from './config/UPlotSeriesBuilder';
const mockRaf = createMockRaf();
const setDataMock = jest.fn();
@@ -52,7 +53,9 @@ const mockData = () => {
raw: { from: '1602673200000', to: '1602680400000' },
};
- return { data, timeRange, config: new UPlotConfigBuilder() };
+ const config = new UPlotConfigBuilder();
+ config.addSeries({} as SeriesProps);
+ return { data, timeRange, config };
};
describe('UPlotChart', () => {
@@ -159,14 +162,11 @@ describe('UPlotChart', () => {
expect(uPlot).toBeCalledTimes(1);
+ const nextConfig = new UPlotConfigBuilder();
+ nextConfig.addSeries({} as SeriesProps);
+
rerender(
-
+
);
expect(destroyMock).toBeCalledTimes(1);
@@ -190,11 +190,13 @@ describe('UPlotChart', () => {
act(() => {
mockRaf.step({ count: 1 });
});
+ const nextConfig = new UPlotConfigBuilder();
+ nextConfig.addSeries({} as SeriesProps);
rerender(
{
expect(uPlot).toBeCalledTimes(1);
expect(setSizeMock).toBeCalledTimes(1);
});
+
+ it('does not initialize plot when config and data are not in sync', () => {
+ const { data, timeRange, config } = mockData();
+
+ // 1 series in data, 2 series in config
+ config.addSeries({} as SeriesProps);
+
+ render(
+
+ );
+
+ // we wait 1 frame for plugins initialisation logic to finish
+ act(() => {
+ mockRaf.step({ count: 1 });
+ });
+
+ expect(destroyMock).toBeCalledTimes(0);
+ expect(uPlot).toBeCalledTimes(0);
+ });
+
+ it('does not reinitialize plot when config and data are not in sync', () => {
+ const { data, timeRange, config } = mockData();
+
+ // 1 series in data, 1 series in config
+ const { rerender } = render(
+
+ );
+
+ // we wait 1 frame for plugins initialisation logic to finish
+ act(() => {
+ mockRaf.step({ count: 1 });
+ });
+
+ const nextConfig = new UPlotConfigBuilder();
+ nextConfig.addSeries({} as SeriesProps);
+ nextConfig.addSeries({} as SeriesProps);
+
+ // 1 series in data, 2 series in config
+ rerender(
+
+ );
+
+ expect(destroyMock).toBeCalledTimes(0);
+ expect(uPlot).toBeCalledTimes(1);
+ });
});
});
diff --git a/packages/grafana-ui/src/components/uPlot/Plot.tsx b/packages/grafana-ui/src/components/uPlot/Plot.tsx
index d59f1a0fe22..796078d479d 100755
--- a/packages/grafana-ui/src/components/uPlot/Plot.tsx
+++ b/packages/grafana-ui/src/components/uPlot/Plot.tsx
@@ -31,6 +31,14 @@ export const UPlotChart: React.FC = (props) => {
return;
}
+ // 0. Exit if the data set length is different than number of series expected to render
+ // This may happen when GraphNG has not synced config yet with the aligned frame. Alignment happens before the render
+ // in the getDerivedStateFromProps, while the config creation happens in componentDidUpdate, causing one more render
+ // of the UPlotChart if the config needs to be updated.
+ if (currentConfig.current.series.length !== props.data.length) {
+ return;
+ }
+
// 1. When config is ready and there is no uPlot instance, create new uPlot and return
if (isConfigReady && !plotInstance.current) {
plotInstance.current = initializePlot(props.data, currentConfig.current, canvasRef.current);