TimeSeries & BarChart: refactor stacking (#47373)
Co-authored-by: Zoltán Bedi <zoltan.bedi@gmail.com> Co-authored-by: Dominik Prokop <dominik.prokop@grafana.com> Co-authored-by: Ryan McKinley <ryantxu@gmail.com>
This commit is contained in:
co-authored by
Zoltán Bedi
Dominik Prokop
Ryan McKinley
parent
3456793e0f
commit
dfdfe3f428
File diff suppressed because it is too large
Load Diff
@@ -82,7 +82,7 @@ function sameProps(prevProps: any, nextProps: any, propsToDiff: Array<string | P
|
|||||||
*/
|
*/
|
||||||
export interface GraphNGState {
|
export interface GraphNGState {
|
||||||
alignedFrame: DataFrame;
|
alignedFrame: DataFrame;
|
||||||
alignedData: AlignedData;
|
alignedData?: AlignedData;
|
||||||
config?: UPlotConfigBuilder;
|
config?: UPlotConfigBuilder;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -98,7 +98,9 @@ export class GraphNG extends React.Component<GraphNGProps, GraphNGState> {
|
|||||||
|
|
||||||
constructor(props: GraphNGProps) {
|
constructor(props: GraphNGProps) {
|
||||||
super(props);
|
super(props);
|
||||||
this.state = this.prepState(props);
|
let state = this.prepState(props);
|
||||||
|
state.alignedData = state.config!.prepData!([state.alignedFrame]) as AlignedData;
|
||||||
|
this.state = state;
|
||||||
this.plotInstance = React.createRef();
|
this.plotInstance = React.createRef();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -131,7 +133,6 @@ export class GraphNG extends React.Component<GraphNGProps, GraphNGState> {
|
|||||||
|
|
||||||
state = {
|
state = {
|
||||||
alignedFrame,
|
alignedFrame,
|
||||||
alignedData: config!.prepData!([alignedFrame]) as AlignedData,
|
|
||||||
config,
|
config,
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -229,12 +230,13 @@ export class GraphNG extends React.Component<GraphNGProps, GraphNGState> {
|
|||||||
|
|
||||||
if (shouldReconfig) {
|
if (shouldReconfig) {
|
||||||
newState.config = this.props.prepConfig(newState.alignedFrame, this.props.frames, this.getTimeRange);
|
newState.config = this.props.prepConfig(newState.alignedFrame, this.props.frames, this.getTimeRange);
|
||||||
newState.alignedData = newState.config.prepData!([newState.alignedFrame]) as AlignedData;
|
|
||||||
pluginLog('GraphNG', false, 'config recreated', newState.config);
|
pluginLog('GraphNG', false, 'config recreated', newState.config);
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
newState && this.setState(newState);
|
newState.alignedData = newState.config!.prepData!([newState.alignedFrame]) as AlignedData;
|
||||||
|
|
||||||
|
this.setState(newState);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -255,7 +257,7 @@ export class GraphNG extends React.Component<GraphNGProps, GraphNGState> {
|
|||||||
{(vizWidth: number, vizHeight: number) => (
|
{(vizWidth: number, vizHeight: number) => (
|
||||||
<UPlotChart
|
<UPlotChart
|
||||||
config={config}
|
config={config}
|
||||||
data={alignedData}
|
data={alignedData!}
|
||||||
width={vizWidth}
|
width={vizWidth}
|
||||||
height={vizHeight}
|
height={vizHeight}
|
||||||
timeRange={timeRange}
|
timeRange={timeRange}
|
||||||
|
|||||||
@@ -58,22 +58,6 @@ Object {
|
|||||||
"values": [Function],
|
"values": [Function],
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
"bands": Array [
|
|
||||||
Object {
|
|
||||||
"dir": -1,
|
|
||||||
"series": Array [
|
|
||||||
2,
|
|
||||||
1,
|
|
||||||
],
|
|
||||||
},
|
|
||||||
Object {
|
|
||||||
"dir": -1,
|
|
||||||
"series": Array [
|
|
||||||
4,
|
|
||||||
3,
|
|
||||||
],
|
|
||||||
},
|
|
||||||
],
|
|
||||||
"cursor": Object {
|
"cursor": Object {
|
||||||
"dataIdx": [Function],
|
"dataIdx": [Function],
|
||||||
"drag": Object {
|
"drag": Object {
|
||||||
|
|||||||
@@ -20,7 +20,7 @@ import {
|
|||||||
import { UPlotConfigBuilder } from '../uPlot/config/UPlotConfigBuilder';
|
import { UPlotConfigBuilder } from '../uPlot/config/UPlotConfigBuilder';
|
||||||
import { UPlotChart } from '../uPlot/Plot';
|
import { UPlotChart } from '../uPlot/Plot';
|
||||||
import { Themeable2 } from '../../types';
|
import { Themeable2 } from '../../types';
|
||||||
import { preparePlotData } from '../uPlot/utils';
|
import { preparePlotData2, getStackingGroups } from '../uPlot/utils';
|
||||||
import { preparePlotFrame } from './utils';
|
import { preparePlotFrame } from './utils';
|
||||||
import { isEqual } from 'lodash';
|
import { isEqual } from 'lodash';
|
||||||
|
|
||||||
@@ -51,7 +51,7 @@ export class Sparkline extends PureComponent<SparklineProps, State> {
|
|||||||
const alignedDataFrame = preparePlotFrame(props.sparkline, props.config);
|
const alignedDataFrame = preparePlotFrame(props.sparkline, props.config);
|
||||||
|
|
||||||
this.state = {
|
this.state = {
|
||||||
data: preparePlotData([alignedDataFrame]),
|
data: preparePlotData2(alignedDataFrame, getStackingGroups(alignedDataFrame)),
|
||||||
alignedDataFrame,
|
alignedDataFrame,
|
||||||
configBuilder: this.prepareConfig(alignedDataFrame),
|
configBuilder: this.prepareConfig(alignedDataFrame),
|
||||||
};
|
};
|
||||||
@@ -65,7 +65,7 @@ export class Sparkline extends PureComponent<SparklineProps, State> {
|
|||||||
|
|
||||||
return {
|
return {
|
||||||
...state,
|
...state,
|
||||||
data: preparePlotData([frame]),
|
data: preparePlotData2(frame, getStackingGroups(frame)),
|
||||||
alignedDataFrame: frame,
|
alignedDataFrame: frame,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -19,7 +19,7 @@ export class UnthemedTimeSeries extends React.Component<TimeSeriesProps> {
|
|||||||
|
|
||||||
prepConfig = (alignedFrame: DataFrame, allFrames: DataFrame[], getTimeRange: () => TimeRange) => {
|
prepConfig = (alignedFrame: DataFrame, allFrames: DataFrame[], getTimeRange: () => TimeRange) => {
|
||||||
const { eventBus, sync } = this.context as PanelContext;
|
const { eventBus, sync } = this.context as PanelContext;
|
||||||
const { theme, timeZone, legend, renderers, tweakAxis, tweakScale } = this.props;
|
const { theme, timeZone, renderers, tweakAxis, tweakScale } = this.props;
|
||||||
|
|
||||||
return preparePlotConfigBuilder({
|
return preparePlotConfigBuilder({
|
||||||
frame: alignedFrame,
|
frame: alignedFrame,
|
||||||
@@ -29,7 +29,6 @@ export class UnthemedTimeSeries extends React.Component<TimeSeriesProps> {
|
|||||||
eventBus,
|
eventBus,
|
||||||
sync,
|
sync,
|
||||||
allFrames,
|
allFrames,
|
||||||
legend,
|
|
||||||
renderers,
|
renderers,
|
||||||
tweakScale,
|
tweakScale,
|
||||||
tweakAxis,
|
tweakAxis,
|
||||||
|
|||||||
@@ -23,10 +23,9 @@ import {
|
|||||||
VisibilityMode,
|
VisibilityMode,
|
||||||
ScaleDirection,
|
ScaleDirection,
|
||||||
ScaleOrientation,
|
ScaleOrientation,
|
||||||
VizLegendOptions,
|
|
||||||
StackingMode,
|
StackingMode,
|
||||||
} from '@grafana/schema';
|
} from '@grafana/schema';
|
||||||
import { collectStackingGroups, INTERNAL_NEGATIVE_Y_PREFIX, orderIdsByCalcs, preparePlotData } from '../uPlot/utils';
|
import { getStackingGroups, preparePlotData2 } from '../uPlot/utils';
|
||||||
import uPlot from 'uplot';
|
import uPlot from 'uplot';
|
||||||
import { buildScaleKey } from '../GraphNG/utils';
|
import { buildScaleKey } from '../GraphNG/utils';
|
||||||
|
|
||||||
@@ -40,7 +39,6 @@ const defaultConfig: GraphFieldConfig = {
|
|||||||
|
|
||||||
export const preparePlotConfigBuilder: UPlotConfigPrepFn<{
|
export const preparePlotConfigBuilder: UPlotConfigPrepFn<{
|
||||||
sync?: () => DashboardCursorSync;
|
sync?: () => DashboardCursorSync;
|
||||||
legend?: VizLegendOptions;
|
|
||||||
}> = ({
|
}> = ({
|
||||||
frame,
|
frame,
|
||||||
theme,
|
theme,
|
||||||
@@ -50,13 +48,12 @@ export const preparePlotConfigBuilder: UPlotConfigPrepFn<{
|
|||||||
sync,
|
sync,
|
||||||
allFrames,
|
allFrames,
|
||||||
renderers,
|
renderers,
|
||||||
legend,
|
|
||||||
tweakScale = (opts) => opts,
|
tweakScale = (opts) => opts,
|
||||||
tweakAxis = (opts) => opts,
|
tweakAxis = (opts) => opts,
|
||||||
}) => {
|
}) => {
|
||||||
const builder = new UPlotConfigBuilder(timeZone);
|
const builder = new UPlotConfigBuilder(timeZone);
|
||||||
|
|
||||||
builder.setPrepData((prepData) => preparePlotData(prepData, undefined, legend));
|
builder.setPrepData((frames) => preparePlotData2(frames[0], builder.getStackingGroups()));
|
||||||
|
|
||||||
// X is the first field in the aligned frame
|
// X is the first field in the aligned frame
|
||||||
const xField = frame.fields[0];
|
const xField = frame.fields[0];
|
||||||
@@ -122,8 +119,6 @@ export const preparePlotConfigBuilder: UPlotConfigPrepFn<{
|
|||||||
let customRenderedFields =
|
let customRenderedFields =
|
||||||
renderers?.flatMap((r) => Object.values(r.fieldMap).filter((name) => r.indicesOnly.indexOf(name) === -1)) ?? [];
|
renderers?.flatMap((r) => Object.values(r.fieldMap).filter((name) => r.indicesOnly.indexOf(name) === -1)) ?? [];
|
||||||
|
|
||||||
const stackingGroups: Map<string, number[]> = new Map();
|
|
||||||
|
|
||||||
let indexByName: Map<string, number> | undefined;
|
let indexByName: Map<string, number> | undefined;
|
||||||
|
|
||||||
for (let i = 1; i < frame.fields.length; i++) {
|
for (let i = 1; i < frame.fields.length; i++) {
|
||||||
@@ -328,20 +323,11 @@ export const preparePlotConfigBuilder: UPlotConfigPrepFn<{
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
collectStackingGroups(field, stackingGroups, seriesIndex);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (stackingGroups.size !== 0) {
|
let stackingGroups = getStackingGroups(frame);
|
||||||
for (const [group, seriesIds] of stackingGroups.entries()) {
|
|
||||||
const seriesIdxs = orderIdsByCalcs({ ids: seriesIds, legend, frame });
|
builder.setStackingGroups(stackingGroups);
|
||||||
for (let j = seriesIdxs.length - 1; j > 0; j--) {
|
|
||||||
builder.addBand({
|
|
||||||
series: [seriesIdxs[j], seriesIdxs[j - 1]],
|
|
||||||
dir: group.startsWith(INTERNAL_NEGATIVE_Y_PREFIX) ? 1 : -1,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// hook up custom/composite renderers
|
// hook up custom/composite renderers
|
||||||
renderers?.forEach((r) => {
|
renderers?.forEach((r) => {
|
||||||
|
|||||||
@@ -6,7 +6,7 @@ import { GraphFieldConfig, GraphDrawStyle } from '@grafana/schema';
|
|||||||
import uPlot from 'uplot';
|
import uPlot from 'uplot';
|
||||||
import createMockRaf from 'mock-raf';
|
import createMockRaf from 'mock-raf';
|
||||||
import { UPlotConfigBuilder } from './config/UPlotConfigBuilder';
|
import { UPlotConfigBuilder } from './config/UPlotConfigBuilder';
|
||||||
import { preparePlotData } from './utils';
|
import { preparePlotData2, getStackingGroups } from './utils';
|
||||||
import { SeriesProps } from './config/UPlotSeriesBuilder';
|
import { SeriesProps } from './config/UPlotSeriesBuilder';
|
||||||
|
|
||||||
const mockRaf = createMockRaf();
|
const mockRaf = createMockRaf();
|
||||||
@@ -55,7 +55,7 @@ const mockData = () => {
|
|||||||
|
|
||||||
const config = new UPlotConfigBuilder();
|
const config = new UPlotConfigBuilder();
|
||||||
config.addSeries({} as SeriesProps);
|
config.addSeries({} as SeriesProps);
|
||||||
return { data: [data], timeRange, config };
|
return { data: data, timeRange, config };
|
||||||
};
|
};
|
||||||
|
|
||||||
describe('UPlotChart', () => {
|
describe('UPlotChart', () => {
|
||||||
@@ -75,7 +75,7 @@ describe('UPlotChart', () => {
|
|||||||
|
|
||||||
const { unmount } = render(
|
const { unmount } = render(
|
||||||
<UPlotChart
|
<UPlotChart
|
||||||
data={preparePlotData(data)} // mock
|
data={preparePlotData2(data, getStackingGroups(data))} // mock
|
||||||
config={config}
|
config={config}
|
||||||
timeRange={timeRange}
|
timeRange={timeRange}
|
||||||
width={100}
|
width={100}
|
||||||
@@ -94,7 +94,7 @@ describe('UPlotChart', () => {
|
|||||||
|
|
||||||
const { rerender } = render(
|
const { rerender } = render(
|
||||||
<UPlotChart
|
<UPlotChart
|
||||||
data={preparePlotData(data)} // mock
|
data={preparePlotData2(data, getStackingGroups(data))} // mock
|
||||||
config={config}
|
config={config}
|
||||||
timeRange={timeRange}
|
timeRange={timeRange}
|
||||||
width={100}
|
width={100}
|
||||||
@@ -104,11 +104,11 @@ describe('UPlotChart', () => {
|
|||||||
|
|
||||||
expect(uPlot).toBeCalledTimes(1);
|
expect(uPlot).toBeCalledTimes(1);
|
||||||
|
|
||||||
data[0].fields[1].values.set(0, 1);
|
data.fields[1].values.set(0, 1);
|
||||||
|
|
||||||
rerender(
|
rerender(
|
||||||
<UPlotChart
|
<UPlotChart
|
||||||
data={preparePlotData(data)} // changed
|
data={preparePlotData2(data, getStackingGroups(data))} // changed
|
||||||
config={config}
|
config={config}
|
||||||
timeRange={timeRange}
|
timeRange={timeRange}
|
||||||
width={100}
|
width={100}
|
||||||
@@ -124,7 +124,13 @@ describe('UPlotChart', () => {
|
|||||||
it('skips uPlot intialization for width and height equal 0', async () => {
|
it('skips uPlot intialization for width and height equal 0', async () => {
|
||||||
const { data, timeRange, config } = mockData();
|
const { data, timeRange, config } = mockData();
|
||||||
const { queryAllByTestId } = render(
|
const { queryAllByTestId } = render(
|
||||||
<UPlotChart data={preparePlotData(data)} config={config} timeRange={timeRange} width={0} height={0} />
|
<UPlotChart
|
||||||
|
data={preparePlotData2(data, getStackingGroups(data))}
|
||||||
|
config={config}
|
||||||
|
timeRange={timeRange}
|
||||||
|
width={0}
|
||||||
|
height={0}
|
||||||
|
/>
|
||||||
);
|
);
|
||||||
|
|
||||||
expect(queryAllByTestId('uplot-main-div')).toHaveLength(1);
|
expect(queryAllByTestId('uplot-main-div')).toHaveLength(1);
|
||||||
@@ -136,7 +142,7 @@ describe('UPlotChart', () => {
|
|||||||
|
|
||||||
const { rerender } = render(
|
const { rerender } = render(
|
||||||
<UPlotChart
|
<UPlotChart
|
||||||
data={preparePlotData(data)} // frame
|
data={preparePlotData2(data, getStackingGroups(data))} // frame
|
||||||
config={config}
|
config={config}
|
||||||
timeRange={timeRange}
|
timeRange={timeRange}
|
||||||
width={100}
|
width={100}
|
||||||
@@ -150,7 +156,13 @@ describe('UPlotChart', () => {
|
|||||||
nextConfig.addSeries({} as SeriesProps);
|
nextConfig.addSeries({} as SeriesProps);
|
||||||
|
|
||||||
rerender(
|
rerender(
|
||||||
<UPlotChart data={preparePlotData(data)} config={nextConfig} timeRange={timeRange} width={100} height={100} />
|
<UPlotChart
|
||||||
|
data={preparePlotData2(data, getStackingGroups(data))}
|
||||||
|
config={nextConfig}
|
||||||
|
timeRange={timeRange}
|
||||||
|
width={100}
|
||||||
|
height={100}
|
||||||
|
/>
|
||||||
);
|
);
|
||||||
|
|
||||||
expect(destroyMock).toBeCalledTimes(1);
|
expect(destroyMock).toBeCalledTimes(1);
|
||||||
@@ -162,7 +174,7 @@ describe('UPlotChart', () => {
|
|||||||
|
|
||||||
const { rerender } = render(
|
const { rerender } = render(
|
||||||
<UPlotChart
|
<UPlotChart
|
||||||
data={preparePlotData(data)} // frame
|
data={preparePlotData2(data, getStackingGroups(data))} // frame
|
||||||
config={config}
|
config={config}
|
||||||
timeRange={timeRange}
|
timeRange={timeRange}
|
||||||
width={100}
|
width={100}
|
||||||
@@ -173,7 +185,7 @@ describe('UPlotChart', () => {
|
|||||||
// we wait 1 frame for plugins initialisation logic to finish
|
// we wait 1 frame for plugins initialisation logic to finish
|
||||||
rerender(
|
rerender(
|
||||||
<UPlotChart
|
<UPlotChart
|
||||||
data={preparePlotData(data)} // frame
|
data={preparePlotData2(data, getStackingGroups(data))} // frame
|
||||||
config={config}
|
config={config}
|
||||||
timeRange={timeRange}
|
timeRange={timeRange}
|
||||||
width={200}
|
width={200}
|
||||||
|
|||||||
@@ -15,7 +15,7 @@ import { ScaleProps, UPlotScaleBuilder } from './UPlotScaleBuilder';
|
|||||||
import { SeriesProps, UPlotSeriesBuilder } from './UPlotSeriesBuilder';
|
import { SeriesProps, UPlotSeriesBuilder } from './UPlotSeriesBuilder';
|
||||||
import { AxisProps, UPlotAxisBuilder } from './UPlotAxisBuilder';
|
import { AxisProps, UPlotAxisBuilder } from './UPlotAxisBuilder';
|
||||||
import { AxisPlacement } from '@grafana/schema';
|
import { AxisPlacement } from '@grafana/schema';
|
||||||
import { pluginLog } from '../utils';
|
import { getStackingBands, pluginLog, StackingGroup } from '../utils';
|
||||||
import { getThresholdsDrawHook, UPlotThresholdOptions } from './UPlotThresholds';
|
import { getThresholdsDrawHook, UPlotThresholdOptions } from './UPlotThresholds';
|
||||||
|
|
||||||
const cursorDefaults: Cursor = {
|
const cursorDefaults: Cursor = {
|
||||||
@@ -33,12 +33,14 @@ const cursorDefaults: Cursor = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
type PrepData = (frames: DataFrame[]) => AlignedData | FacetedData;
|
type PrepData = (frames: DataFrame[]) => AlignedData | FacetedData;
|
||||||
|
type PreDataStacked = (frames: DataFrame[], stackingGroups: StackingGroup[]) => AlignedData | FacetedData;
|
||||||
|
|
||||||
export class UPlotConfigBuilder {
|
export class UPlotConfigBuilder {
|
||||||
private series: UPlotSeriesBuilder[] = [];
|
private series: UPlotSeriesBuilder[] = [];
|
||||||
private axes: Record<string, UPlotAxisBuilder> = {};
|
private axes: Record<string, UPlotAxisBuilder> = {};
|
||||||
private scales: UPlotScaleBuilder[] = [];
|
private scales: UPlotScaleBuilder[] = [];
|
||||||
private bands: Band[] = [];
|
private bands: Band[] = [];
|
||||||
|
private stackingGroups: StackingGroup[] = [];
|
||||||
private cursor: Cursor | undefined;
|
private cursor: Cursor | undefined;
|
||||||
private select: uPlot.Select | undefined;
|
private select: uPlot.Select | undefined;
|
||||||
private hasLeftAxis = false;
|
private hasLeftAxis = false;
|
||||||
@@ -143,6 +145,14 @@ export class UPlotConfigBuilder {
|
|||||||
this.bands.push(band);
|
this.bands.push(band);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
setStackingGroups(groups: StackingGroup[]) {
|
||||||
|
this.stackingGroups = groups;
|
||||||
|
}
|
||||||
|
|
||||||
|
getStackingGroups() {
|
||||||
|
return this.stackingGroups;
|
||||||
|
}
|
||||||
|
|
||||||
setTooltipInterpolator(interpolator: PlotTooltipInterpolator) {
|
setTooltipInterpolator(interpolator: PlotTooltipInterpolator) {
|
||||||
this.tooltipInterpolator = interpolator;
|
this.tooltipInterpolator = interpolator;
|
||||||
}
|
}
|
||||||
@@ -151,10 +161,10 @@ export class UPlotConfigBuilder {
|
|||||||
return this.tooltipInterpolator;
|
return this.tooltipInterpolator;
|
||||||
}
|
}
|
||||||
|
|
||||||
setPrepData(prepData: PrepData) {
|
setPrepData(prepData: PreDataStacked) {
|
||||||
this.prepData = (frames) => {
|
this.prepData = (frames) => {
|
||||||
this.frames = frames;
|
this.frames = frames;
|
||||||
return prepData(frames);
|
return prepData(frames, this.getStackingGroups());
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -221,6 +231,14 @@ export class UPlotConfigBuilder {
|
|||||||
config.tzDate = this.tzDate;
|
config.tzDate = this.tzDate;
|
||||||
config.padding = this.padding;
|
config.padding = this.padding;
|
||||||
|
|
||||||
|
if (this.stackingGroups.length) {
|
||||||
|
this.stackingGroups.forEach((group) => {
|
||||||
|
getStackingBands(group).forEach((band) => {
|
||||||
|
this.addBand(band);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
if (this.bands.length) {
|
if (this.bands.length) {
|
||||||
config.bands = this.bands;
|
config.bands = this.bands;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
import { orderIdsByCalcs, preparePlotData, timeFormatToTemplate } from './utils';
|
import { getStackingGroups, preparePlotData2, timeFormatToTemplate } from './utils';
|
||||||
import { FieldType, MutableDataFrame } from '@grafana/data';
|
import { FieldType, MutableDataFrame } from '@grafana/data';
|
||||||
import { GraphTransform, StackingMode } from '@grafana/schema';
|
import { BarAlignment, GraphDrawStyle, GraphTransform, LineInterpolation, StackingMode } from '@grafana/schema';
|
||||||
|
import Units from 'ol/proj/Units';
|
||||||
|
|
||||||
describe('timeFormatToTemplate', () => {
|
describe('timeFormatToTemplate', () => {
|
||||||
it.each`
|
it.each`
|
||||||
@@ -16,7 +17,7 @@ describe('timeFormatToTemplate', () => {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('preparePlotData', () => {
|
describe('preparePlotData2', () => {
|
||||||
const df = new MutableDataFrame({
|
const df = new MutableDataFrame({
|
||||||
fields: [
|
fields: [
|
||||||
{ name: 'time', type: FieldType.time, values: [9997, 9998, 9999] },
|
{ name: 'time', type: FieldType.time, values: [9997, 9998, 9999] },
|
||||||
@@ -27,7 +28,7 @@ describe('preparePlotData', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it('creates array from DataFrame', () => {
|
it('creates array from DataFrame', () => {
|
||||||
expect(preparePlotData([df])).toMatchInlineSnapshot(`
|
expect(preparePlotData2(df, getStackingGroups(df))).toMatchInlineSnapshot(`
|
||||||
Array [
|
Array [
|
||||||
Array [
|
Array [
|
||||||
9997,
|
9997,
|
||||||
@@ -63,7 +64,7 @@ describe('preparePlotData', () => {
|
|||||||
{ name: 'c', values: [20, 20, 20], config: { custom: { transform: GraphTransform.NegativeY } } },
|
{ name: 'c', values: [20, 20, 20], config: { custom: { transform: GraphTransform.NegativeY } } },
|
||||||
],
|
],
|
||||||
});
|
});
|
||||||
expect(preparePlotData([df])).toMatchInlineSnapshot(`
|
expect(preparePlotData2(df, getStackingGroups(df))).toMatchInlineSnapshot(`
|
||||||
Array [
|
Array [
|
||||||
Array [
|
Array [
|
||||||
9997,
|
9997,
|
||||||
@@ -104,7 +105,7 @@ describe('preparePlotData', () => {
|
|||||||
{ name: 'i', values: [20, undefined, 20, 20], config: { custom: { transform: GraphTransform.NegativeY } } },
|
{ name: 'i', values: [20, undefined, 20, 20], config: { custom: { transform: GraphTransform.NegativeY } } },
|
||||||
],
|
],
|
||||||
});
|
});
|
||||||
expect(preparePlotData([df])).toMatchInlineSnapshot(`
|
expect(preparePlotData2(df, getStackingGroups(df))).toMatchInlineSnapshot(`
|
||||||
Array [
|
Array [
|
||||||
Array [
|
Array [
|
||||||
9997,
|
9997,
|
||||||
@@ -178,7 +179,7 @@ describe('preparePlotData', () => {
|
|||||||
{ name: 'c', values: [20, 20, 20] },
|
{ name: 'c', values: [20, 20, 20] },
|
||||||
],
|
],
|
||||||
});
|
});
|
||||||
expect(preparePlotData([df])).toMatchInlineSnapshot(`
|
expect(preparePlotData2(df, getStackingGroups(df))).toMatchInlineSnapshot(`
|
||||||
Array [
|
Array [
|
||||||
Array [
|
Array [
|
||||||
9997,
|
9997,
|
||||||
@@ -226,7 +227,7 @@ describe('preparePlotData', () => {
|
|||||||
},
|
},
|
||||||
],
|
],
|
||||||
});
|
});
|
||||||
expect(preparePlotData([df])).toMatchInlineSnapshot(`
|
expect(preparePlotData2(df, getStackingGroups(df))).toMatchInlineSnapshot(`
|
||||||
Array [
|
Array [
|
||||||
Array [
|
Array [
|
||||||
9997,
|
9997,
|
||||||
@@ -273,7 +274,7 @@ describe('preparePlotData', () => {
|
|||||||
},
|
},
|
||||||
],
|
],
|
||||||
});
|
});
|
||||||
expect(preparePlotData([df])).toMatchInlineSnapshot(`
|
expect(preparePlotData2(df, getStackingGroups(df))).toMatchInlineSnapshot(`
|
||||||
Array [
|
Array [
|
||||||
Array [
|
Array [
|
||||||
9997,
|
9997,
|
||||||
@@ -329,7 +330,7 @@ describe('preparePlotData', () => {
|
|||||||
},
|
},
|
||||||
],
|
],
|
||||||
});
|
});
|
||||||
expect(preparePlotData([df])).toMatchInlineSnapshot(`
|
expect(preparePlotData2(df, getStackingGroups(df))).toMatchInlineSnapshot(`
|
||||||
Array [
|
Array [
|
||||||
Array [
|
Array [
|
||||||
9997,
|
9997,
|
||||||
@@ -397,7 +398,7 @@ describe('preparePlotData', () => {
|
|||||||
],
|
],
|
||||||
});
|
});
|
||||||
|
|
||||||
expect(preparePlotData([df])).toMatchInlineSnapshot(`
|
expect(preparePlotData2(df, getStackingGroups(df))).toMatchInlineSnapshot(`
|
||||||
Array [
|
Array [
|
||||||
Array [
|
Array [
|
||||||
9997,
|
9997,
|
||||||
@@ -472,7 +473,7 @@ describe('preparePlotData', () => {
|
|||||||
],
|
],
|
||||||
});
|
});
|
||||||
|
|
||||||
expect(preparePlotData([df])).toMatchInlineSnapshot(`
|
expect(preparePlotData2(df, getStackingGroups(df))).toMatchInlineSnapshot(`
|
||||||
Array [
|
Array [
|
||||||
Array [
|
Array [
|
||||||
9997,
|
9997,
|
||||||
@@ -507,239 +508,330 @@ describe('preparePlotData', () => {
|
|||||||
]
|
]
|
||||||
`);
|
`);
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('ignores nullish-only stacks', () => {
|
|
||||||
test('single stacking group', () => {
|
|
||||||
const df = new MutableDataFrame({
|
|
||||||
fields: [
|
|
||||||
{ name: 'time', type: FieldType.time, values: [9997, 9998, 9999] },
|
|
||||||
{
|
|
||||||
name: 'a',
|
|
||||||
values: [-10, null, 10],
|
|
||||||
config: { custom: { stacking: { mode: StackingMode.Normal, group: 'stackA' } } },
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: 'b',
|
|
||||||
values: [10, null, null],
|
|
||||||
config: { custom: { stacking: { mode: StackingMode.Normal, group: 'stackA' } } },
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: 'c',
|
|
||||||
values: [20, undefined, 20],
|
|
||||||
config: { custom: { stacking: { mode: StackingMode.Normal, group: 'stackA' } } },
|
|
||||||
},
|
|
||||||
],
|
|
||||||
});
|
|
||||||
|
|
||||||
expect(preparePlotData([df])).toMatchInlineSnapshot(`
|
|
||||||
Array [
|
|
||||||
Array [
|
|
||||||
9997,
|
|
||||||
9998,
|
|
||||||
9999,
|
|
||||||
],
|
|
||||||
Array [
|
|
||||||
-10,
|
|
||||||
null,
|
|
||||||
10,
|
|
||||||
],
|
|
||||||
Array [
|
|
||||||
0,
|
|
||||||
null,
|
|
||||||
10,
|
|
||||||
],
|
|
||||||
Array [
|
|
||||||
20,
|
|
||||||
null,
|
|
||||||
30,
|
|
||||||
],
|
|
||||||
]
|
|
||||||
`);
|
|
||||||
});
|
|
||||||
test('multiple stacking groups', () => {
|
|
||||||
const df = new MutableDataFrame({
|
|
||||||
fields: [
|
|
||||||
{ name: 'time', type: FieldType.time, values: [9997, 9998, 9999] },
|
|
||||||
{
|
|
||||||
name: 'a',
|
|
||||||
values: [-10, undefined, 10],
|
|
||||||
config: { custom: { stacking: { mode: StackingMode.Normal, group: 'stackA' } } },
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: 'b',
|
|
||||||
values: [10, undefined, 10],
|
|
||||||
config: { custom: { stacking: { mode: StackingMode.Normal, group: 'stackA' } } },
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: 'c',
|
|
||||||
values: [20, undefined, 20],
|
|
||||||
config: { custom: { stacking: { mode: StackingMode.Normal, group: 'stackA' } } },
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: 'd',
|
|
||||||
values: [1, 2, null],
|
|
||||||
config: { custom: { stacking: { mode: StackingMode.Normal, group: 'stackB' } } },
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: 'e',
|
|
||||||
values: [1, 2, null],
|
|
||||||
config: { custom: { stacking: { mode: StackingMode.Normal, group: 'stackB' } } },
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: 'f',
|
|
||||||
values: [1, 2, null],
|
|
||||||
config: { custom: { stacking: { mode: StackingMode.Normal, group: 'stackB' } } },
|
|
||||||
},
|
|
||||||
],
|
|
||||||
});
|
|
||||||
|
|
||||||
expect(preparePlotData([df])).toMatchInlineSnapshot(`
|
|
||||||
Array [
|
|
||||||
Array [
|
|
||||||
9997,
|
|
||||||
9998,
|
|
||||||
9999,
|
|
||||||
],
|
|
||||||
Array [
|
|
||||||
-10,
|
|
||||||
null,
|
|
||||||
10,
|
|
||||||
],
|
|
||||||
Array [
|
|
||||||
0,
|
|
||||||
null,
|
|
||||||
20,
|
|
||||||
],
|
|
||||||
Array [
|
|
||||||
20,
|
|
||||||
null,
|
|
||||||
40,
|
|
||||||
],
|
|
||||||
Array [
|
|
||||||
1,
|
|
||||||
2,
|
|
||||||
null,
|
|
||||||
],
|
|
||||||
Array [
|
|
||||||
2,
|
|
||||||
4,
|
|
||||||
null,
|
|
||||||
],
|
|
||||||
Array [
|
|
||||||
3,
|
|
||||||
6,
|
|
||||||
null,
|
|
||||||
],
|
|
||||||
]
|
|
||||||
`);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
describe('with legend sorted', () => {
|
|
||||||
it('should affect when single group', () => {
|
|
||||||
const df = new MutableDataFrame({
|
|
||||||
fields: [
|
|
||||||
{ name: 'time', type: FieldType.time, values: [9997, 9998, 9999] },
|
|
||||||
{
|
|
||||||
name: 'a',
|
|
||||||
values: [-10, 20, 10],
|
|
||||||
state: { calcs: { max: 20 } },
|
|
||||||
config: { custom: { stacking: { mode: StackingMode.Normal, group: 'stackA' } } },
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: 'b',
|
|
||||||
values: [10, 10, 10],
|
|
||||||
state: { calcs: { max: 10 } },
|
|
||||||
config: { custom: { stacking: { mode: StackingMode.Normal, group: 'stackA' } } },
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: 'c',
|
|
||||||
values: [20, 20, 20],
|
|
||||||
state: { calcs: { max: 20 } },
|
|
||||||
config: { custom: { stacking: { mode: StackingMode.Normal, group: 'stackA' } } },
|
|
||||||
},
|
|
||||||
],
|
|
||||||
});
|
|
||||||
|
|
||||||
expect(preparePlotData([df], undefined, { sortBy: 'Max', sortDesc: false } as any)).toMatchInlineSnapshot(`
|
|
||||||
Array [
|
|
||||||
Array [
|
|
||||||
9997,
|
|
||||||
9998,
|
|
||||||
9999,
|
|
||||||
],
|
|
||||||
Array [
|
|
||||||
0,
|
|
||||||
30,
|
|
||||||
20,
|
|
||||||
],
|
|
||||||
Array [
|
|
||||||
10,
|
|
||||||
10,
|
|
||||||
10,
|
|
||||||
],
|
|
||||||
Array [
|
|
||||||
20,
|
|
||||||
50,
|
|
||||||
40,
|
|
||||||
],
|
|
||||||
]
|
|
||||||
`);
|
|
||||||
expect(preparePlotData([df], undefined, { sortBy: 'Max', sortDesc: true } as any)).toMatchInlineSnapshot(`
|
|
||||||
Array [
|
|
||||||
Array [
|
|
||||||
9997,
|
|
||||||
9998,
|
|
||||||
9999,
|
|
||||||
],
|
|
||||||
Array [
|
|
||||||
-10,
|
|
||||||
20,
|
|
||||||
10,
|
|
||||||
],
|
|
||||||
Array [
|
|
||||||
20,
|
|
||||||
50,
|
|
||||||
40,
|
|
||||||
],
|
|
||||||
Array [
|
|
||||||
10,
|
|
||||||
40,
|
|
||||||
30,
|
|
||||||
],
|
|
||||||
]
|
|
||||||
`);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('orderIdsByCalcs', () => {
|
describe('auto stacking groups', () => {
|
||||||
const ids = [1, 2, 3, 4];
|
test('split on stacking mode', () => {
|
||||||
const frame = new MutableDataFrame({
|
const df = new MutableDataFrame({
|
||||||
fields: [
|
fields: [
|
||||||
{ name: 'time', type: FieldType.time, values: [9997, 9998, 9999] },
|
{ name: 'time', type: FieldType.time, values: [0, 1, 2] },
|
||||||
{ name: 'a', values: [-10, 20, 10], state: { calcs: { min: -10 } } },
|
{
|
||||||
{ name: 'b', values: [20, 20, 20], state: { calcs: { min: 20 } } },
|
name: 'b',
|
||||||
{ name: 'c', values: [10, 10, 10], state: { calcs: { min: 10 } } },
|
values: [1, 2, 3],
|
||||||
{ name: 'd', values: [30, 30, 30] },
|
config: { custom: { stacking: { mode: StackingMode.Percent } } },
|
||||||
],
|
},
|
||||||
|
{
|
||||||
|
name: 'c',
|
||||||
|
values: [4, 5, 6],
|
||||||
|
config: { custom: { stacking: { mode: StackingMode.Normal } } },
|
||||||
|
},
|
||||||
|
],
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(getStackingGroups(df)).toMatchInlineSnapshot(`
|
||||||
|
Array [
|
||||||
|
Object {
|
||||||
|
"dir": 1,
|
||||||
|
"series": Array [
|
||||||
|
1,
|
||||||
|
],
|
||||||
|
},
|
||||||
|
Object {
|
||||||
|
"dir": 1,
|
||||||
|
"series": Array [
|
||||||
|
2,
|
||||||
|
],
|
||||||
|
},
|
||||||
|
]
|
||||||
|
`);
|
||||||
});
|
});
|
||||||
|
|
||||||
it.each([
|
test('split pos/neg', () => {
|
||||||
{ legend: undefined },
|
// since we expect most series to be Pos, we try to bail early when scanning all values
|
||||||
{ legend: { sortBy: 'Min' } },
|
// as soon as we find a value >= 0, it's assumed Pos, else Neg
|
||||||
{ legend: { sortDesc: false } },
|
|
||||||
{ legend: {} },
|
const df = new MutableDataFrame({
|
||||||
{ sortBy: 'Mik', sortDesc: true },
|
fields: [
|
||||||
])('should return without ordering if legend option is %o', (legend: any) => {
|
{ name: 'time', type: FieldType.time, values: [0, 1, 2] },
|
||||||
const result = orderIdsByCalcs({ ids, frame, legend });
|
{
|
||||||
expect(result).toEqual([1, 2, 3, 4]);
|
name: 'a',
|
||||||
|
values: [-1, null, -3],
|
||||||
|
config: { custom: { stacking: { mode: StackingMode.Normal } } },
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'b',
|
||||||
|
values: [1, 2, 3],
|
||||||
|
config: { custom: { stacking: { mode: StackingMode.Normal } } },
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'c',
|
||||||
|
values: [0, 0, 0],
|
||||||
|
config: { custom: { stacking: { mode: StackingMode.Normal } } },
|
||||||
|
},
|
||||||
|
],
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(getStackingGroups(df)).toMatchInlineSnapshot(`
|
||||||
|
Array [
|
||||||
|
Object {
|
||||||
|
"dir": -1,
|
||||||
|
"series": Array [
|
||||||
|
1,
|
||||||
|
3,
|
||||||
|
],
|
||||||
|
},
|
||||||
|
Object {
|
||||||
|
"dir": 1,
|
||||||
|
"series": Array [
|
||||||
|
2,
|
||||||
|
],
|
||||||
|
},
|
||||||
|
]
|
||||||
|
`);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should order the ids based on the frame stat', () => {
|
test('split pos/neg with NegY', () => {
|
||||||
const resultDesc = orderIdsByCalcs({ ids, frame, legend: { sortBy: 'Min', sortDesc: true } as any });
|
const df = new MutableDataFrame({
|
||||||
expect(resultDesc).toEqual([4, 2, 3, 1]);
|
fields: [
|
||||||
const resultAsc = orderIdsByCalcs({ ids, frame, legend: { sortBy: 'Min', sortDesc: false } as any });
|
{ name: 'time', type: FieldType.time, values: [0, 1, 2] },
|
||||||
expect(resultAsc).toEqual([1, 3, 2, 4]);
|
{
|
||||||
|
name: 'a',
|
||||||
|
values: [-1, null, -3],
|
||||||
|
config: { custom: { stacking: { mode: StackingMode.Normal }, transform: GraphTransform.NegativeY } },
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'b',
|
||||||
|
values: [1, 2, 3],
|
||||||
|
config: { custom: { stacking: { mode: StackingMode.Normal } } },
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'c',
|
||||||
|
values: [0, 0, 0],
|
||||||
|
config: { custom: { stacking: { mode: StackingMode.Normal } } },
|
||||||
|
},
|
||||||
|
],
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(getStackingGroups(df)).toMatchInlineSnapshot(`
|
||||||
|
Array [
|
||||||
|
Object {
|
||||||
|
"dir": 1,
|
||||||
|
"series": Array [
|
||||||
|
1,
|
||||||
|
2,
|
||||||
|
],
|
||||||
|
},
|
||||||
|
Object {
|
||||||
|
"dir": -1,
|
||||||
|
"series": Array [
|
||||||
|
3,
|
||||||
|
],
|
||||||
|
},
|
||||||
|
]
|
||||||
|
`);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('split on drawStyle, lineInterpolation, barAlignment', () => {
|
||||||
|
const df = new MutableDataFrame({
|
||||||
|
fields: [
|
||||||
|
{ name: 'time', type: FieldType.time, values: [0, 1, 2] },
|
||||||
|
{
|
||||||
|
name: 'a',
|
||||||
|
values: [1, 2, 3],
|
||||||
|
config: {
|
||||||
|
custom: {
|
||||||
|
drawStyle: GraphDrawStyle.Bars,
|
||||||
|
barAlignment: BarAlignment.After,
|
||||||
|
stacking: { mode: StackingMode.Normal },
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'b',
|
||||||
|
values: [1, 2, 3],
|
||||||
|
config: {
|
||||||
|
custom: {
|
||||||
|
drawStyle: GraphDrawStyle.Bars,
|
||||||
|
barAlignment: BarAlignment.Before,
|
||||||
|
stacking: { mode: StackingMode.Normal },
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'c',
|
||||||
|
values: [1, 2, 3],
|
||||||
|
config: {
|
||||||
|
custom: {
|
||||||
|
drawStyle: GraphDrawStyle.Line,
|
||||||
|
lineInterpolation: LineInterpolation.Linear,
|
||||||
|
stacking: { mode: StackingMode.Normal },
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'd',
|
||||||
|
values: [1, 2, 3],
|
||||||
|
config: {
|
||||||
|
custom: {
|
||||||
|
drawStyle: GraphDrawStyle.Line,
|
||||||
|
lineInterpolation: LineInterpolation.Smooth,
|
||||||
|
stacking: { mode: StackingMode.Normal },
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'e',
|
||||||
|
values: [1, 2, 3],
|
||||||
|
config: { custom: { drawStyle: GraphDrawStyle.Points, stacking: { mode: StackingMode.Normal } } },
|
||||||
|
},
|
||||||
|
],
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(getStackingGroups(df)).toMatchInlineSnapshot(`
|
||||||
|
Array [
|
||||||
|
Object {
|
||||||
|
"dir": 1,
|
||||||
|
"series": Array [
|
||||||
|
1,
|
||||||
|
],
|
||||||
|
},
|
||||||
|
Object {
|
||||||
|
"dir": 1,
|
||||||
|
"series": Array [
|
||||||
|
2,
|
||||||
|
],
|
||||||
|
},
|
||||||
|
Object {
|
||||||
|
"dir": 1,
|
||||||
|
"series": Array [
|
||||||
|
3,
|
||||||
|
],
|
||||||
|
},
|
||||||
|
Object {
|
||||||
|
"dir": 1,
|
||||||
|
"series": Array [
|
||||||
|
4,
|
||||||
|
],
|
||||||
|
},
|
||||||
|
Object {
|
||||||
|
"dir": 1,
|
||||||
|
"series": Array [
|
||||||
|
5,
|
||||||
|
],
|
||||||
|
},
|
||||||
|
]
|
||||||
|
`);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('split on axis & units (scaleKey)', () => {
|
||||||
|
const df = new MutableDataFrame({
|
||||||
|
fields: [
|
||||||
|
{ name: 'time', type: FieldType.time, values: [0, 1, 2] },
|
||||||
|
{
|
||||||
|
name: 'a',
|
||||||
|
values: [1, 2, 3],
|
||||||
|
config: { custom: { stacking: { mode: StackingMode.Normal } }, unit: Units.FEET },
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'b',
|
||||||
|
values: [1, 2, 3],
|
||||||
|
config: { custom: { stacking: { mode: StackingMode.Normal } }, unit: Units.DEGREES },
|
||||||
|
},
|
||||||
|
],
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(getStackingGroups(df)).toMatchInlineSnapshot(`
|
||||||
|
Array [
|
||||||
|
Object {
|
||||||
|
"dir": 1,
|
||||||
|
"series": Array [
|
||||||
|
1,
|
||||||
|
],
|
||||||
|
},
|
||||||
|
Object {
|
||||||
|
"dir": 1,
|
||||||
|
"series": Array [
|
||||||
|
2,
|
||||||
|
],
|
||||||
|
},
|
||||||
|
]
|
||||||
|
`);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('split on explicit stacking group & mode & pos/neg w/NegY', () => {
|
||||||
|
const df = new MutableDataFrame({
|
||||||
|
fields: [
|
||||||
|
{ name: 'time', type: FieldType.time, values: [0, 1, 2] },
|
||||||
|
{
|
||||||
|
name: 'a',
|
||||||
|
values: [1, 2, 3],
|
||||||
|
config: { custom: { stacking: { mode: StackingMode.Normal, group: 'A' } } },
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'b',
|
||||||
|
values: [1, 2, 3],
|
||||||
|
config: { custom: { stacking: { mode: StackingMode.Normal, group: 'A' } } },
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'c',
|
||||||
|
values: [1, 2, 3],
|
||||||
|
config: { custom: { stacking: { mode: StackingMode.Percent, group: 'A' } } },
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'd',
|
||||||
|
values: [1, 2, 3],
|
||||||
|
config: { custom: { stacking: { mode: StackingMode.Normal, group: 'B' } } },
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'e',
|
||||||
|
values: [1, 2, 3],
|
||||||
|
config: { custom: { stacking: { mode: StackingMode.Percent, group: 'B' } } },
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'e',
|
||||||
|
values: [1, 2, 3],
|
||||||
|
config: {
|
||||||
|
custom: { stacking: { mode: StackingMode.Percent, group: 'B' }, transform: GraphTransform.NegativeY },
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(getStackingGroups(df)).toMatchInlineSnapshot(`
|
||||||
|
Array [
|
||||||
|
Object {
|
||||||
|
"dir": 1,
|
||||||
|
"series": Array [
|
||||||
|
1,
|
||||||
|
2,
|
||||||
|
],
|
||||||
|
},
|
||||||
|
Object {
|
||||||
|
"dir": 1,
|
||||||
|
"series": Array [
|
||||||
|
3,
|
||||||
|
],
|
||||||
|
},
|
||||||
|
Object {
|
||||||
|
"dir": 1,
|
||||||
|
"series": Array [
|
||||||
|
4,
|
||||||
|
],
|
||||||
|
},
|
||||||
|
Object {
|
||||||
|
"dir": 1,
|
||||||
|
"series": Array [
|
||||||
|
5,
|
||||||
|
],
|
||||||
|
},
|
||||||
|
Object {
|
||||||
|
"dir": -1,
|
||||||
|
"series": Array [
|
||||||
|
6,
|
||||||
|
],
|
||||||
|
},
|
||||||
|
]
|
||||||
|
`);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -1,12 +1,11 @@
|
|||||||
import { DataFrame, ensureTimeField, Field, FieldType } from '@grafana/data';
|
import { DataFrame, ensureTimeField, FieldType } from '@grafana/data';
|
||||||
import { GraphFieldConfig, GraphTransform, StackingMode, VizLegendOptions } from '@grafana/schema';
|
import { BarAlignment, GraphDrawStyle, GraphTransform, LineInterpolation, StackingMode } from '@grafana/schema';
|
||||||
import { orderBy } from 'lodash';
|
|
||||||
import uPlot, { AlignedData, Options, PaddingSide } from 'uplot';
|
import uPlot, { AlignedData, Options, PaddingSide } from 'uplot';
|
||||||
import { attachDebugger } from '../../utils';
|
import { attachDebugger } from '../../utils';
|
||||||
import { createLogger } from '../../utils/logger';
|
import { createLogger } from '../../utils/logger';
|
||||||
|
import { buildScaleKey } from '../GraphNG/utils';
|
||||||
|
|
||||||
const ALLOWED_FORMAT_STRINGS_REGEX = /\b(YYYY|YY|MMMM|MMM|MM|M|DD|D|WWWW|WWW|HH|H|h|AA|aa|a|mm|m|ss|s|fff)\b/g;
|
const ALLOWED_FORMAT_STRINGS_REGEX = /\b(YYYY|YY|MMMM|MMM|MM|M|DD|D|WWWW|WWW|HH|H|h|AA|aa|a|mm|m|ss|s|fff)\b/g;
|
||||||
export const INTERNAL_NEGATIVE_Y_PREFIX = '__internalNegY';
|
|
||||||
|
|
||||||
export function timeFormatToTemplate(f: string) {
|
export function timeFormatToTemplate(f: string) {
|
||||||
return f.replace(ALLOWED_FORMAT_STRINGS_REGEX, (match) => `{${match}}`);
|
return f.replace(ALLOWED_FORMAT_STRINGS_REGEX, (match) => `{${match}}`);
|
||||||
@@ -41,115 +40,225 @@ interface StackMeta {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/** @internal */
|
/** @internal */
|
||||||
export function preparePlotData(
|
export interface StackingGroup {
|
||||||
frames: DataFrame[],
|
series: number[];
|
||||||
onStackMeta?: (meta: StackMeta) => void,
|
dir: StackDirection;
|
||||||
legend?: VizLegendOptions
|
|
||||||
): AlignedData {
|
|
||||||
const frame = frames[0];
|
|
||||||
const result: any[] = [];
|
|
||||||
const stackingGroups: Map<string, number[]> = new Map();
|
|
||||||
let seriesIndex = 0;
|
|
||||||
|
|
||||||
for (let i = 0; i < frame.fields.length; i++) {
|
|
||||||
const f = frame.fields[i];
|
|
||||||
|
|
||||||
if (f.type === FieldType.time) {
|
|
||||||
result.push(ensureTimeField(f).values.toArray());
|
|
||||||
seriesIndex++;
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
collectStackingGroups(f, stackingGroups, seriesIndex);
|
|
||||||
const customConfig: GraphFieldConfig = f.config.custom || {};
|
|
||||||
|
|
||||||
const values = f.values.toArray();
|
|
||||||
|
|
||||||
if (customConfig.transform === GraphTransform.NegativeY) {
|
|
||||||
result.push(values.map((v) => (v == null ? v : v * -1)));
|
|
||||||
} else if (customConfig.transform === GraphTransform.Constant) {
|
|
||||||
result.push(new Array(values.length).fill(values[0]));
|
|
||||||
} else {
|
|
||||||
result.push(values);
|
|
||||||
}
|
|
||||||
seriesIndex++;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Stacking
|
|
||||||
if (stackingGroups.size !== 0) {
|
|
||||||
const byPct = frame.fields[1].config.custom?.stacking?.mode === StackingMode.Percent;
|
|
||||||
const dataLength = result[0].length;
|
|
||||||
const alignedTotals = Array(stackingGroups.size);
|
|
||||||
alignedTotals[0] = null;
|
|
||||||
|
|
||||||
// array or stacking groups
|
|
||||||
for (const [_, seriesIds] of stackingGroups.entries()) {
|
|
||||||
const seriesIdxs = orderIdsByCalcs({ ids: seriesIds, legend, frame });
|
|
||||||
const noValueStack = Array(dataLength).fill(true);
|
|
||||||
const groupTotals = byPct ? Array(dataLength).fill(0) : null;
|
|
||||||
|
|
||||||
if (byPct) {
|
|
||||||
for (let j = 0; j < seriesIdxs.length; j++) {
|
|
||||||
const currentlyStacking = result[seriesIdxs[j]];
|
|
||||||
|
|
||||||
for (let k = 0; k < dataLength; k++) {
|
|
||||||
const v = currentlyStacking[k];
|
|
||||||
groupTotals![k] += v == null ? 0 : +v;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const acc = Array(dataLength).fill(0);
|
|
||||||
|
|
||||||
for (let j = 0; j < seriesIdxs.length; j++) {
|
|
||||||
let seriesIdx = seriesIdxs[j];
|
|
||||||
|
|
||||||
alignedTotals[seriesIdx] = groupTotals;
|
|
||||||
|
|
||||||
const currentlyStacking = result[seriesIdx];
|
|
||||||
|
|
||||||
for (let k = 0; k < dataLength; k++) {
|
|
||||||
const v = currentlyStacking[k];
|
|
||||||
if (v != null && noValueStack[k]) {
|
|
||||||
noValueStack[k] = false;
|
|
||||||
}
|
|
||||||
acc[k] += v == null ? 0 : v / (byPct ? groupTotals![k] : 1);
|
|
||||||
}
|
|
||||||
|
|
||||||
result[seriesIdx] = acc.slice().map((v, i) => (noValueStack[i] ? null : v));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
onStackMeta &&
|
|
||||||
onStackMeta({
|
|
||||||
totals: alignedTotals as AlignedData,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
return result as AlignedData;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export function collectStackingGroups(f: Field, groups: Map<string, number[]>, seriesIdx: number) {
|
/** @internal */
|
||||||
const customConfig = f.config.custom;
|
const enum StackDirection {
|
||||||
if (!customConfig) {
|
Pos = 1,
|
||||||
return;
|
Neg = -1,
|
||||||
}
|
}
|
||||||
if (
|
|
||||||
customConfig.stacking?.mode !== StackingMode.None &&
|
|
||||||
customConfig.stacking?.group &&
|
|
||||||
!customConfig.hideFrom?.viz
|
|
||||||
) {
|
|
||||||
const group =
|
|
||||||
customConfig.transform === GraphTransform.NegativeY
|
|
||||||
? `${INTERNAL_NEGATIVE_Y_PREFIX}-${customConfig.stacking.group}`
|
|
||||||
: customConfig.stacking.group;
|
|
||||||
|
|
||||||
if (!groups.has(group)) {
|
// generates bands between adjacent group series
|
||||||
groups.set(group, [seriesIdx]);
|
/** @internal */
|
||||||
} else {
|
export function getStackingBands(group: StackingGroup) {
|
||||||
groups.set(group, groups.get(group)!.concat(seriesIdx));
|
let bands: uPlot.Band[] = [];
|
||||||
|
let { series, dir } = group;
|
||||||
|
let lastIdx = series.length - 1;
|
||||||
|
|
||||||
|
let rSeries = series.slice().reverse();
|
||||||
|
|
||||||
|
rSeries.forEach((si, i) => {
|
||||||
|
if (i !== lastIdx) {
|
||||||
|
let nextIdx = rSeries[i + 1];
|
||||||
|
bands.push({
|
||||||
|
series: [si, nextIdx],
|
||||||
|
// fill direction is inverted from stack direction
|
||||||
|
dir: (-1 * dir) as 1 | -1,
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
return bands;
|
||||||
|
}
|
||||||
|
|
||||||
|
// expects an AlignedFrame
|
||||||
|
/** @internal */
|
||||||
|
export function getStackingGroups(frame: DataFrame) {
|
||||||
|
let groups: Map<string, StackingGroup> = new Map();
|
||||||
|
|
||||||
|
frame.fields.forEach(({ config, values }, i) => {
|
||||||
|
// skip x or time field
|
||||||
|
if (i === 0) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
let { custom } = config;
|
||||||
|
|
||||||
|
if (custom == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: currently all AlignedFrame fields end up in uplot series & data, even custom.hideFrom?.viz
|
||||||
|
// ideally hideFrom.viz fields would be excluded so we can remove this
|
||||||
|
if (custom.hideFrom?.viz) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
let { stacking } = custom;
|
||||||
|
|
||||||
|
if (stacking == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
let { mode: stackingMode, group: stackingGroup } = stacking;
|
||||||
|
|
||||||
|
// not stacking
|
||||||
|
if (stackingMode === StackingMode.None) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// will this be stacked up or down after any transforms applied
|
||||||
|
let vals = values.toArray();
|
||||||
|
let transform = custom.transform;
|
||||||
|
let stackDir =
|
||||||
|
transform === GraphTransform.Constant
|
||||||
|
? vals[0] > 0
|
||||||
|
? StackDirection.Pos
|
||||||
|
: StackDirection.Neg
|
||||||
|
: transform === GraphTransform.NegativeY
|
||||||
|
? vals.some((v) => v > 0)
|
||||||
|
? StackDirection.Neg
|
||||||
|
: StackDirection.Pos
|
||||||
|
: vals.some((v) => v > 0)
|
||||||
|
? StackDirection.Pos
|
||||||
|
: StackDirection.Neg;
|
||||||
|
|
||||||
|
let drawStyle = custom.drawStyle as GraphDrawStyle;
|
||||||
|
let drawStyle2 =
|
||||||
|
drawStyle === GraphDrawStyle.Bars
|
||||||
|
? (custom.barAlignment as BarAlignment)
|
||||||
|
: drawStyle === GraphDrawStyle.Line
|
||||||
|
? (custom.lineInterpolation as LineInterpolation)
|
||||||
|
: null;
|
||||||
|
|
||||||
|
let stackKey = `${stackDir}|${stackingMode}|${stackingGroup}|${buildScaleKey(config)}|${drawStyle}|${drawStyle2}`;
|
||||||
|
|
||||||
|
let group = groups.get(stackKey);
|
||||||
|
|
||||||
|
if (group == null) {
|
||||||
|
group = {
|
||||||
|
series: [],
|
||||||
|
dir: stackDir,
|
||||||
|
};
|
||||||
|
|
||||||
|
groups.set(stackKey, group);
|
||||||
|
}
|
||||||
|
|
||||||
|
group.series.push(i);
|
||||||
|
});
|
||||||
|
|
||||||
|
return [...groups.values()];
|
||||||
|
}
|
||||||
|
|
||||||
|
/** @internal */
|
||||||
|
export function preparePlotData2(
|
||||||
|
frame: DataFrame,
|
||||||
|
stackingGroups: StackingGroup[],
|
||||||
|
onStackMeta?: (meta: StackMeta) => void
|
||||||
|
) {
|
||||||
|
let data = Array(frame.fields.length) as AlignedData;
|
||||||
|
|
||||||
|
let dataLen = frame.length;
|
||||||
|
let zeroArr = Array(dataLen).fill(0);
|
||||||
|
let accums = Array.from({ length: stackingGroups.length }, () => zeroArr.slice());
|
||||||
|
|
||||||
|
frame.fields.forEach((field, i) => {
|
||||||
|
let vals = field.values.toArray();
|
||||||
|
|
||||||
|
if (i === 0) {
|
||||||
|
if (field.type === FieldType.time) {
|
||||||
|
data[i] = ensureTimeField(field).values.toArray();
|
||||||
|
} else {
|
||||||
|
data[i] = vals;
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
let { custom } = field.config;
|
||||||
|
|
||||||
|
if (!custom || custom.hideFrom?.viz) {
|
||||||
|
data[i] = vals;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// apply transforms
|
||||||
|
if (custom.transform === GraphTransform.Constant) {
|
||||||
|
vals = Array(vals.length).fill(vals[0]);
|
||||||
|
} else {
|
||||||
|
vals = vals.slice();
|
||||||
|
|
||||||
|
if (custom.transform === GraphTransform.NegativeY) {
|
||||||
|
for (let i = 0; i < vals.length; i++) {
|
||||||
|
if (vals[i] != null) {
|
||||||
|
vals[i] *= -1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let stackingMode = custom.stacking?.mode;
|
||||||
|
|
||||||
|
if (!stackingMode || stackingMode === StackingMode.None) {
|
||||||
|
data[i] = vals;
|
||||||
|
} else {
|
||||||
|
let stackIdx = stackingGroups.findIndex((group) => group.series.indexOf(i) > -1);
|
||||||
|
|
||||||
|
let accum = accums[stackIdx];
|
||||||
|
let stacked = (data[i] = Array(dataLen));
|
||||||
|
|
||||||
|
for (let i = 0; i < dataLen; i++) {
|
||||||
|
let v = vals[i];
|
||||||
|
|
||||||
|
if (v != null) {
|
||||||
|
stacked[i] = accum[i] += v;
|
||||||
|
} else {
|
||||||
|
stacked[i] = v; // we may want to coerce to 0 here
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
if (onStackMeta) {
|
||||||
|
let accumsBySeriesIdx = data.map((vals, i) => {
|
||||||
|
let stackIdx = stackingGroups.findIndex((group) => group.series.indexOf(i) > -1);
|
||||||
|
return stackIdx !== -1 ? accums[stackIdx] : vals;
|
||||||
|
});
|
||||||
|
|
||||||
|
onStackMeta({
|
||||||
|
totals: accumsBySeriesIdx as AlignedData,
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// re-compute by percent
|
||||||
|
frame.fields.forEach((field, i) => {
|
||||||
|
if (i === 0 || field.config.custom?.hideFrom?.viz) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
let stackingMode = field.config.custom?.stacking?.mode;
|
||||||
|
|
||||||
|
if (stackingMode === StackingMode.Percent) {
|
||||||
|
let stackIdx = stackingGroups.findIndex((group) => group.series.indexOf(i) > -1);
|
||||||
|
let accum = accums[stackIdx];
|
||||||
|
let group = stackingGroups[stackIdx];
|
||||||
|
|
||||||
|
let stacked = data[i];
|
||||||
|
|
||||||
|
for (let i = 0; i < dataLen; i++) {
|
||||||
|
let v = stacked[i];
|
||||||
|
|
||||||
|
if (v != null) {
|
||||||
|
// v / accum will always be pos, so properly (re)sign by group stacking dir
|
||||||
|
stacked[i] = group.dir * (v / accum[i]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
return data;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -215,23 +324,3 @@ export const pluginLogger = createLogger('uPlot');
|
|||||||
export const pluginLog = pluginLogger.logger;
|
export const pluginLog = pluginLogger.logger;
|
||||||
// pluginLogger.enable();
|
// pluginLogger.enable();
|
||||||
attachDebugger('graphng', undefined, pluginLogger);
|
attachDebugger('graphng', undefined, pluginLogger);
|
||||||
|
|
||||||
type OrderIdsByCalcsOptions = {
|
|
||||||
legend?: VizLegendOptions;
|
|
||||||
ids: number[];
|
|
||||||
frame: DataFrame;
|
|
||||||
};
|
|
||||||
export function orderIdsByCalcs({ legend, ids, frame }: OrderIdsByCalcsOptions) {
|
|
||||||
if (!legend?.sortBy || legend.sortDesc == null) {
|
|
||||||
return ids;
|
|
||||||
}
|
|
||||||
const orderedIds = orderBy<number>(
|
|
||||||
ids,
|
|
||||||
(id) => {
|
|
||||||
return frame.fields[id].state?.calcs?.[legend.sortBy!.toLowerCase()];
|
|
||||||
},
|
|
||||||
legend.sortDesc ? 'desc' : 'asc'
|
|
||||||
);
|
|
||||||
|
|
||||||
return orderedIds;
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -11,7 +11,7 @@ import {
|
|||||||
VizTextDisplayOptions,
|
VizTextDisplayOptions,
|
||||||
VizLegendOptions,
|
VizLegendOptions,
|
||||||
} from '@grafana/schema';
|
} from '@grafana/schema';
|
||||||
import { preparePlotData } from '../../../../../packages/grafana-ui/src/components/uPlot/utils';
|
import { preparePlotData2, StackingGroup } from '../../../../../packages/grafana-ui/src/components/uPlot/utils';
|
||||||
import { alpha } from '@grafana/data/src/themes/colorManipulator';
|
import { alpha } from '@grafana/data/src/themes/colorManipulator';
|
||||||
import { formatTime } from '@grafana/ui/src/components/uPlot/config/UPlotAxisBuilder';
|
import { formatTime } from '@grafana/ui/src/components/uPlot/config/UPlotAxisBuilder';
|
||||||
|
|
||||||
@@ -564,16 +564,11 @@ export function getConfig(opts: BarsOptions, theme: GrafanaTheme2) {
|
|||||||
|
|
||||||
let alignedTotals: AlignedData | null = null;
|
let alignedTotals: AlignedData | null = null;
|
||||||
|
|
||||||
function prepData(frames: DataFrame[]) {
|
function prepData(frames: DataFrame[], stackingGroups: StackingGroup[]) {
|
||||||
alignedTotals = null;
|
alignedTotals = null;
|
||||||
|
return preparePlotData2(frames[0], stackingGroups, ({ totals }) => {
|
||||||
return preparePlotData(
|
alignedTotals = totals;
|
||||||
frames,
|
});
|
||||||
({ totals }) => {
|
|
||||||
alignedTotals = totals;
|
|
||||||
},
|
|
||||||
opts.legend
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
|
|||||||
@@ -25,9 +25,9 @@ import {
|
|||||||
StackingMode,
|
StackingMode,
|
||||||
VizLegendOptions,
|
VizLegendOptions,
|
||||||
} from '@grafana/schema';
|
} from '@grafana/schema';
|
||||||
import { collectStackingGroups, orderIdsByCalcs } from '../../../../../packages/grafana-ui/src/components/uPlot/utils';
|
|
||||||
import { orderBy } from 'lodash';
|
import { orderBy } from 'lodash';
|
||||||
import { findField } from 'app/features/dimensions';
|
import { findField } from 'app/features/dimensions';
|
||||||
|
import { getStackingGroups } from '@grafana/ui/src/components/uPlot/utils';
|
||||||
|
|
||||||
function getBarCharScaleOrientation(orientation: VizOrientation) {
|
function getBarCharScaleOrientation(orientation: VizOrientation) {
|
||||||
if (orientation === VizOrientation.Vertical) {
|
if (orientation === VizOrientation.Vertical) {
|
||||||
@@ -156,7 +156,6 @@ export const preparePlotConfigBuilder: UPlotConfigPrepFn<BarChartOptionsEX> = ({
|
|||||||
|
|
||||||
let seriesIndex = 0;
|
let seriesIndex = 0;
|
||||||
const legendOrdered = isLegendOrdered(legend);
|
const legendOrdered = isLegendOrdered(legend);
|
||||||
const stackingGroups: Map<string, number[]> = new Map();
|
|
||||||
|
|
||||||
// iterate the y values
|
// iterate the y values
|
||||||
for (let i = 1; i < frame.fields.length; i++) {
|
for (let i = 1; i < frame.fields.length; i++) {
|
||||||
@@ -237,20 +236,11 @@ export const preparePlotConfigBuilder: UPlotConfigPrepFn<BarChartOptionsEX> = ({
|
|||||||
grid: { show: customConfig.axisGridShow },
|
grid: { show: customConfig.axisGridShow },
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
collectStackingGroups(field, stackingGroups, seriesIndex);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (stackingGroups.size !== 0) {
|
let stackingGroups = getStackingGroups(frame);
|
||||||
for (const [_, seriesIds] of stackingGroups.entries()) {
|
|
||||||
const seriesIdxs = orderIdsByCalcs({ ids: seriesIds, legend, frame });
|
builder.setStackingGroups(stackingGroups);
|
||||||
for (let j = seriesIdxs.length - 1; j > 0; j--) {
|
|
||||||
builder.addBand({
|
|
||||||
series: [seriesIdxs[j], seriesIdxs[j - 1]],
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return builder;
|
return builder;
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -31,7 +31,7 @@ import { getConfig, TimelineCoreOptions } from './timeline';
|
|||||||
import { VizLegendOptions, AxisPlacement, ScaleDirection, ScaleOrientation } from '@grafana/schema';
|
import { VizLegendOptions, AxisPlacement, ScaleDirection, ScaleOrientation } from '@grafana/schema';
|
||||||
import { TimelineFieldConfig, TimelineOptions } from './types';
|
import { TimelineFieldConfig, TimelineOptions } from './types';
|
||||||
import { PlotTooltipInterpolator } from '@grafana/ui/src/components/uPlot/types';
|
import { PlotTooltipInterpolator } from '@grafana/ui/src/components/uPlot/types';
|
||||||
import { preparePlotData } from '../../../../../packages/grafana-ui/src/components/uPlot/utils';
|
import { preparePlotData2, getStackingGroups } from '../../../../../packages/grafana-ui/src/components/uPlot/utils';
|
||||||
import uPlot from 'uplot';
|
import uPlot from 'uplot';
|
||||||
|
|
||||||
const defaultConfig: TimelineFieldConfig = {
|
const defaultConfig: TimelineFieldConfig = {
|
||||||
@@ -153,7 +153,7 @@ export const preparePlotConfigBuilder: UPlotConfigPrepFn<TimelineOptions> = ({
|
|||||||
|
|
||||||
builder.setTooltipInterpolator(interpolateTooltip);
|
builder.setTooltipInterpolator(interpolateTooltip);
|
||||||
|
|
||||||
builder.setPrepData(preparePlotData);
|
builder.setPrepData((frames) => preparePlotData2(frames[0], getStackingGroups(frames[0])));
|
||||||
|
|
||||||
builder.setCursor(coreConfig.cursor);
|
builder.setCursor(coreConfig.cursor);
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user