mirror of
https://github.com/grafana/grafana.git
synced 2025-07-31 05:52:16 +08:00

* refactor(frontend): rename all @grafana/data/src imports to @grafana/data * feat(grafana-data): introduce internal entrypoint for sharing code only with grafana * feat(grafana-data): add test entrypoint for data test utils usage in core * refactor(frontend): update import paths to use grafana/data exports entrypoints * docs(grafana-data): update comment in internal/index.ts * refactor(frontend): prefer public namespaced exports over re-exporting via internal * chore(frontend): fix a couple more weird paths that typescript complains about
144 lines
4.2 KiB
TypeScript
144 lines
4.2 KiB
TypeScript
import { DataFrame, Field, FieldType, outerJoinDataFrames, TimeRange, applyNullInsertThreshold } from '@grafana/data';
|
|
import { NULL_EXPAND, NULL_REMOVE, NULL_RETAIN, nullToUndefThreshold } from '@grafana/data/internal';
|
|
import { GraphDrawStyle } from '@grafana/schema';
|
|
|
|
import { XYFieldMatchers } from './types';
|
|
|
|
function isVisibleBarField(f: Field) {
|
|
return (
|
|
f.type === FieldType.number && f.config.custom?.drawStyle === GraphDrawStyle.Bars && !f.config.custom?.hideFrom?.viz
|
|
);
|
|
}
|
|
|
|
export function getRefField(frame: DataFrame, refFieldName?: string | null) {
|
|
return frame.fields.find((field) => {
|
|
// note: getFieldDisplayName() would require full DF[]
|
|
return refFieldName != null ? field.name === refFieldName : field.type === FieldType.time;
|
|
});
|
|
}
|
|
|
|
// will mutate the DataFrame's fields' values
|
|
function applySpanNullsThresholds(frame: DataFrame, refFieldName?: string | null) {
|
|
const refField = getRefField(frame, refFieldName);
|
|
|
|
let refValues = refField?.values;
|
|
|
|
for (let i = 0; i < frame.fields.length; i++) {
|
|
let field = frame.fields[i];
|
|
|
|
if (field === refField || isVisibleBarField(field)) {
|
|
continue;
|
|
}
|
|
|
|
let spanNulls = field.config.custom?.spanNulls;
|
|
|
|
if (typeof spanNulls === 'number') {
|
|
if (spanNulls !== -1 && refValues) {
|
|
field.values = nullToUndefThreshold(refValues, field.values, spanNulls);
|
|
}
|
|
}
|
|
}
|
|
|
|
return frame;
|
|
}
|
|
|
|
export function preparePlotFrame(frames: DataFrame[], dimFields: XYFieldMatchers, timeRange?: TimeRange | null) {
|
|
let xField: Field;
|
|
loop: for (let frame of frames) {
|
|
for (let field of frame.fields) {
|
|
if (dimFields.x(field, frame, frames)) {
|
|
xField = field;
|
|
break loop;
|
|
}
|
|
}
|
|
}
|
|
|
|
// apply null insertions at interval
|
|
frames = frames.map((frame) => {
|
|
if (!xField?.state?.nullThresholdApplied) {
|
|
return applyNullInsertThreshold({
|
|
frame,
|
|
refFieldName: xField.name,
|
|
refFieldPseudoMin: timeRange?.from.valueOf(),
|
|
refFieldPseudoMax: timeRange?.to.valueOf(),
|
|
});
|
|
} else {
|
|
return frame;
|
|
}
|
|
});
|
|
|
|
let numBarSeries = frames.reduce(
|
|
(acc, frame) => acc + frame.fields.reduce((acc, field) => acc + (isVisibleBarField(field) ? 1 : 0), 0),
|
|
0
|
|
);
|
|
|
|
// to make bar widths of all series uniform (equal to narrowest bar series), find smallest distance between x points
|
|
let minXDelta = Infinity;
|
|
|
|
if (numBarSeries > 1) {
|
|
frames.forEach((frame) => {
|
|
if (!frame.fields.some(isVisibleBarField)) {
|
|
return;
|
|
}
|
|
|
|
const xVals = xField.values;
|
|
|
|
for (let i = 0; i < xVals.length; i++) {
|
|
if (i > 0) {
|
|
minXDelta = Math.min(minXDelta, xVals[i] - xVals[i - 1]);
|
|
}
|
|
}
|
|
});
|
|
}
|
|
|
|
let alignedFrame = outerJoinDataFrames({
|
|
frames,
|
|
joinBy: dimFields.x,
|
|
keep: dimFields.y,
|
|
keepOriginIndices: true,
|
|
|
|
// the join transformer force-deletes our state.displayName cache unless keepDisplayNames: true
|
|
// https://github.com/grafana/grafana/pull/31121
|
|
// https://github.com/grafana/grafana/pull/71806
|
|
keepDisplayNames: true,
|
|
|
|
// prevent minesweeper-expansion of nulls (gaps) when joining bars
|
|
// since bar width is determined from the minimum distance between non-undefined values
|
|
// (this strategy will still retain any original pre-join nulls, though)
|
|
nullMode: (field) => {
|
|
if (isVisibleBarField(field)) {
|
|
return NULL_RETAIN;
|
|
}
|
|
|
|
let spanNulls = field.config.custom?.spanNulls;
|
|
return spanNulls === true ? NULL_REMOVE : spanNulls === -1 ? NULL_RETAIN : NULL_EXPAND;
|
|
},
|
|
});
|
|
|
|
if (alignedFrame) {
|
|
alignedFrame = applySpanNullsThresholds(alignedFrame, xField!.name);
|
|
|
|
// append 2 null vals at minXDelta to bar series
|
|
if (minXDelta !== Infinity) {
|
|
alignedFrame.fields.forEach((f, fi) => {
|
|
let vals = f.values;
|
|
|
|
if (fi === 0) {
|
|
let lastVal = vals[vals.length - 1];
|
|
vals.push(lastVal + minXDelta, lastVal + 2 * minXDelta);
|
|
} else if (isVisibleBarField(f)) {
|
|
vals.push(null, null);
|
|
} else {
|
|
vals.push(undefined, undefined);
|
|
}
|
|
});
|
|
|
|
alignedFrame.length += 2;
|
|
}
|
|
|
|
return alignedFrame;
|
|
}
|
|
|
|
return null;
|
|
}
|