@grafana/ui
Version:
Grafana Components Library
478 lines (475 loc) • 17.5 kB
JavaScript
import { isNumber } from 'lodash';
import uPlot from 'uplot';
import { FieldType, formattedValueToString, getDisplayProcessor, getFieldColorModeForField, getFieldSeriesColor, FieldColorModeId, getFieldDisplayName, DashboardCursorSync } from '@grafana/data';
import { AxisPlacement, VisibilityMode, GraphDrawStyle, ScaleDirection, ScaleOrientation, StackingMode, AxisColorMode, GraphGradientMode, GraphTransform, GraphThresholdsStyleMode } from '@grafana/schema';
import { UPlotConfigBuilder } from '../../components/uPlot/config/UPlotConfigBuilder.mjs';
import { getScaleGradientFn } from '../../components/uPlot/config/gradientFills.mjs';
import { preparePlotData2, getStackingGroups } from '../../components/uPlot/utils.mjs';
import { buildScaleKey } from '../GraphNG/utils.mjs';
"use strict";
const IEC_UNITS = /* @__PURE__ */ new Set([
"bytes",
"bits",
"kbytes",
"mbytes",
"gbytes",
"tbytes",
"pbytes",
"binBps",
"binbps",
"KiBs",
"Kibits",
"MiBs",
"Mibits",
"GiBs",
"Gibits",
"TiBs",
"Tibits",
"PiBs",
"Pibits"
]);
const BIN_INCRS = Array(53);
for (let i = 0; i < BIN_INCRS.length; i++) {
BIN_INCRS[i] = 2 ** i;
}
const defaultFormatter = (v, decimals = 1) => v == null ? "-" : v.toFixed(decimals);
const defaultConfig = {
drawStyle: GraphDrawStyle.Line,
showPoints: VisibilityMode.Auto,
axisPlacement: AxisPlacement.Auto
};
const preparePlotConfigBuilder = ({
frame,
theme,
timeZones,
getTimeRange,
sync,
allFrames,
renderers,
tweakScale = (opts) => opts,
tweakAxis = (opts) => opts
}) => {
var _a, _b, _c, _d, _e, _f, _g, _h, _i, _j, _k, _l, _m, _n, _o, _p, _q, _r, _s, _t, _u, _v;
const eventsScope = "__global_";
const builder = new UPlotConfigBuilder(timeZones[0]);
let alignedFrame;
builder.setPrepData((frames) => {
alignedFrame = frames[0];
return preparePlotData2(frames[0], builder.getStackingGroups());
});
const xField = frame.fields[0];
if (!xField) {
return builder;
}
const xScaleKey = "x";
let yScaleKey = "";
const xFieldAxisPlacement = ((_a = xField.config.custom) == null ? void 0 : _a.axisPlacement) !== AxisPlacement.Hidden ? AxisPlacement.Bottom : AxisPlacement.Hidden;
const xFieldAxisShow = ((_b = xField.config.custom) == null ? void 0 : _b.axisPlacement) !== AxisPlacement.Hidden;
if (xField.type === FieldType.time) {
builder.addScale({
scaleKey: xScaleKey,
orientation: ScaleOrientation.Horizontal,
direction: ScaleDirection.Right,
isTime: true,
range: () => {
const r = getTimeRange();
return [r.from.valueOf(), r.to.valueOf()];
}
});
const filterTicks = timeZones.length > 1 ? (u, splits) => {
return splits.map((v, i) => i < 2 ? null : v);
} : void 0;
for (let i = 0; i < timeZones.length; i++) {
const timeZone = timeZones[i];
builder.addAxis({
scaleKey: xScaleKey,
isTime: true,
placement: xFieldAxisPlacement,
show: xFieldAxisShow,
label: (_c = xField.config.custom) == null ? void 0 : _c.axisLabel,
timeZone,
theme,
grid: { show: i === 0 && ((_d = xField.config.custom) == null ? void 0 : _d.axisGridShow) },
filter: filterTicks
});
}
if (timeZones.length > 1) {
builder.addHook("drawAxes", (u) => {
u.ctx.save();
u.ctx.fillStyle = theme.colors.text.primary;
u.ctx.textAlign = "left";
u.ctx.textBaseline = "bottom";
let i = 0;
u.axes.forEach((a) => {
if (a.side === 2) {
let cssBaseline = a._pos + a._size;
u.ctx.fillText(timeZones[i], u.bbox.left, cssBaseline * uPlot.pxRatio);
i++;
}
});
u.ctx.restore();
});
}
} else {
builder.addScale({
scaleKey: xScaleKey,
orientation: ScaleOrientation.Horizontal,
direction: ScaleDirection.Right,
range: (u, dataMin, dataMax) => {
var _a2, _b2;
return [(_a2 = xField.config.min) != null ? _a2 : dataMin, (_b2 = xField.config.max) != null ? _b2 : dataMax];
}
});
builder.addAxis({
scaleKey: xScaleKey,
placement: xFieldAxisPlacement,
show: xFieldAxisShow,
label: (_e = xField.config.custom) == null ? void 0 : _e.axisLabel,
theme,
grid: { show: (_f = xField.config.custom) == null ? void 0 : _f.axisGridShow },
formatValue: (v, decimals) => formattedValueToString(xField.display(v, decimals))
});
}
let customRenderedFields = (_g = renderers == null ? void 0 : renderers.flatMap((r) => Object.values(r.fieldMap).filter((name) => r.indicesOnly.indexOf(name) === -1))) != null ? _g : [];
let indexByName;
for (let i = 1; i < frame.fields.length; i++) {
const field = frame.fields[i];
const config = {
...field.config,
custom: {
...defaultConfig,
...field.config.custom
}
};
const customConfig = config.custom;
if (field === xField || field.type !== FieldType.number && field.type !== FieldType.enum) {
continue;
}
let fmt = (_h = field.display) != null ? _h : defaultFormatter;
if (((_j = (_i = field.config.custom) == null ? void 0 : _i.stacking) == null ? void 0 : _j.mode) === StackingMode.Percent) {
fmt = getDisplayProcessor({
field: {
...field,
config: {
...field.config,
unit: "percentunit"
}
},
theme
});
}
const scaleKey = buildScaleKey(config, field.type);
const colorMode = getFieldColorModeForField(field);
const scaleColor = getFieldSeriesColor(field, theme);
const seriesColor = scaleColor.color;
builder.addScale(
tweakScale(
{
scaleKey,
orientation: ScaleOrientation.Vertical,
direction: ScaleDirection.Up,
distribution: (_k = customConfig.scaleDistribution) == null ? void 0 : _k.type,
log: (_l = customConfig.scaleDistribution) == null ? void 0 : _l.log,
linearThreshold: (_m = customConfig.scaleDistribution) == null ? void 0 : _m.linearThreshold,
min: field.config.min,
max: field.config.max,
softMin: customConfig.axisSoftMin,
softMax: customConfig.axisSoftMax,
centeredZero: customConfig.axisCenteredZero,
range: ((_n = customConfig.stacking) == null ? void 0 : _n.mode) === StackingMode.Percent ? (u, dataMin, dataMax) => {
dataMin = dataMin < 0 ? -1 : 0;
dataMax = dataMax > 0 ? 1 : 0;
return [dataMin, dataMax];
} : field.type === FieldType.enum ? (u, dataMin, dataMax) => {
let len = field.config.type.enum.text.length;
return [-1, len];
} : void 0,
decimals: field.config.decimals
},
field
)
);
if (!yScaleKey) {
yScaleKey = scaleKey;
}
if (customConfig.axisPlacement !== AxisPlacement.Hidden) {
let axisColor;
if (customConfig.axisColorMode === AxisColorMode.Series) {
if (colorMode.isByValue && ((_o = field.config.custom) == null ? void 0 : _o.gradientMode) === GraphGradientMode.Scheme && colorMode.id === FieldColorModeId.Thresholds) {
axisColor = getScaleGradientFn(1, theme, colorMode, field.config.thresholds);
} else {
axisColor = seriesColor;
}
}
const axisDisplayOptions = {
border: {
show: customConfig.axisBorderShow || false,
width: 1 / devicePixelRatio,
stroke: axisColor || theme.colors.text.primary
},
ticks: {
show: customConfig.axisBorderShow || false,
stroke: axisColor || theme.colors.text.primary
},
color: axisColor || theme.colors.text.primary
};
let incrs;
let values;
let splits;
if (IEC_UNITS.has(config.unit)) {
incrs = BIN_INCRS;
} else if (field.type === FieldType.enum) {
let text = field.config.type.enum.text;
splits = text.map((v, i2) => i2);
values = text;
}
builder.addAxis(
tweakAxis(
{
scaleKey,
label: customConfig.axisLabel,
size: customConfig.axisWidth,
placement: (_p = customConfig.axisPlacement) != null ? _p : AxisPlacement.Auto,
formatValue: (v, decimals) => formattedValueToString(fmt(v, decimals)),
theme,
grid: { show: customConfig.axisGridShow },
decimals: field.config.decimals,
distr: (_q = customConfig.scaleDistribution) == null ? void 0 : _q.type,
splits,
values,
incrs,
...axisDisplayOptions
},
field
)
);
}
const showPoints = customConfig.drawStyle === GraphDrawStyle.Points ? VisibilityMode.Always : customConfig.showPoints;
let pointsFilter = () => null;
if (customConfig.spanNulls !== true) {
pointsFilter = (u, seriesIdx, show, gaps) => {
let filtered = [];
let series = u.series[seriesIdx];
if (!show && gaps && gaps.length) {
const [firstIdx, lastIdx] = series.idxs;
const xData = u.data[0];
const yData = u.data[seriesIdx];
const firstPos = Math.round(u.valToPos(xData[firstIdx], "x", true));
const lastPos = Math.round(u.valToPos(xData[lastIdx], "x", true));
if (gaps[0][0] === firstPos) {
filtered.push(firstIdx);
}
for (let i2 = 0; i2 < gaps.length; i2++) {
let thisGap = gaps[i2];
let nextGap = gaps[i2 + 1];
if (nextGap && thisGap[1] === nextGap[0]) {
let approxIdx = u.posToIdx(thisGap[1], true);
if (yData[approxIdx] == null) {
for (let j = 1; j < 100; j++) {
if (yData[approxIdx + j] != null) {
approxIdx += j;
break;
}
if (yData[approxIdx - j] != null) {
approxIdx -= j;
break;
}
}
}
filtered.push(approxIdx);
}
}
if (gaps[gaps.length - 1][1] === lastPos) {
filtered.push(lastIdx);
}
}
return filtered.length ? filtered : null;
};
}
let { fillOpacity } = customConfig;
let pathBuilder = null;
let pointsBuilder = null;
if ((_r = field.state) == null ? void 0 : _r.origin) {
if (!indexByName) {
indexByName = getNamesToFieldIndex(frame, allFrames);
}
const originFrame = allFrames[field.state.origin.frameIndex];
const originField = originFrame == null ? void 0 : originFrame.fields[field.state.origin.fieldIndex];
const dispName = getFieldDisplayName(originField != null ? originField : field, originFrame, allFrames);
if (customRenderedFields.indexOf(dispName) >= 0) {
pathBuilder = () => null;
pointsBuilder = () => void 0;
} else if (customConfig.transform === GraphTransform.Constant) {
const defaultBuilder = uPlot.paths.linear();
pathBuilder = (u, seriesIdx) => {
const _data = u._data;
const r = getTimeRange();
let xData = [r.from.valueOf(), r.to.valueOf()];
let firstY = _data[seriesIdx].find((v) => v != null);
let yData = [firstY, firstY];
let fauxData = _data.slice();
fauxData[0] = xData;
fauxData[seriesIdx] = yData;
return defaultBuilder(
{
...u,
_data: fauxData
},
seriesIdx,
0,
1
);
};
}
if (customConfig.fillBelowTo) {
const fillBelowToField = frame.fields.find(
(f) => {
var _a2;
return customConfig.fillBelowTo === f.name || customConfig.fillBelowTo === ((_a2 = f.config) == null ? void 0 : _a2.displayNameFromDS) || customConfig.fillBelowTo === getFieldDisplayName(f, frame, allFrames);
}
);
const fillBelowDispName = fillBelowToField ? getFieldDisplayName(fillBelowToField, frame, allFrames) : customConfig.fillBelowTo;
const t = indexByName.get(dispName);
const b = indexByName.get(fillBelowDispName);
if (isNumber(b) && isNumber(t)) {
builder.addBand({
series: [t, b],
fill: void 0
// using null will have the band use fill options from `t`
});
if (!fillOpacity) {
fillOpacity = 35;
}
} else {
fillOpacity = 0;
}
}
}
let dynamicSeriesColor = void 0;
if (colorMode.id === FieldColorModeId.Thresholds) {
dynamicSeriesColor = (seriesIdx) => getFieldSeriesColor(alignedFrame.fields[seriesIdx], theme).color;
}
builder.addSeries({
pathBuilder,
pointsBuilder,
scaleKey,
showPoints,
pointsFilter,
colorMode,
fillOpacity,
theme,
dynamicSeriesColor,
drawStyle: customConfig.drawStyle,
lineColor: (_s = customConfig.lineColor) != null ? _s : seriesColor,
lineWidth: customConfig.lineWidth,
lineInterpolation: customConfig.lineInterpolation,
lineStyle: customConfig.lineStyle,
barAlignment: customConfig.barAlignment,
barWidthFactor: customConfig.barWidthFactor,
barMaxWidth: customConfig.barMaxWidth,
pointSize: customConfig.pointSize,
spanNulls: customConfig.spanNulls || false,
show: !((_t = customConfig.hideFrom) == null ? void 0 : _t.viz),
gradientMode: customConfig.gradientMode,
thresholds: config.thresholds,
hardMin: field.config.min,
hardMax: field.config.max,
softMin: customConfig.axisSoftMin,
softMax: customConfig.axisSoftMax,
// The following properties are not used in the uPlot config, but are utilized as transport for legend config
dataFrameFieldIndex: (_u = field.state) == null ? void 0 : _u.origin
});
if (customConfig.thresholdsStyle && config.thresholds) {
const thresholdDisplay = (_v = customConfig.thresholdsStyle.mode) != null ? _v : GraphThresholdsStyleMode.Off;
if (thresholdDisplay !== GraphThresholdsStyleMode.Off) {
builder.addThresholds({
config: customConfig.thresholdsStyle,
thresholds: config.thresholds,
scaleKey,
theme,
hardMin: field.config.min,
hardMax: field.config.max,
softMin: customConfig.axisSoftMin,
softMax: customConfig.axisSoftMax
});
}
}
}
let stackingGroups = getStackingGroups(frame);
builder.setStackingGroups(stackingGroups);
renderers == null ? void 0 : renderers.forEach((r) => {
if (!indexByName) {
indexByName = getNamesToFieldIndex(frame, allFrames);
}
let fieldIndices = {};
for (let key in r.fieldMap) {
let dispName = r.fieldMap[key];
fieldIndices[key] = indexByName.get(dispName);
}
r.init(builder, fieldIndices);
});
builder.scaleKeys = [xScaleKey, yScaleKey];
const hoverProximityPx = 15;
let cursor = {
// this scans left and right from cursor position to find nearest data index with value != null
// TODO: do we want to only scan past undefined values, but halt at explicit null values?
dataIdx: (self, seriesIdx, hoveredIdx, cursorXVal) => {
let seriesData = self.data[seriesIdx];
if (seriesData[hoveredIdx] == null) {
let nonNullLft = null, nonNullRgt = null, i;
i = hoveredIdx;
while (nonNullLft == null && i-- > 0) {
if (seriesData[i] != null) {
nonNullLft = i;
}
}
i = hoveredIdx;
while (nonNullRgt == null && i++ < seriesData.length) {
if (seriesData[i] != null) {
nonNullRgt = i;
}
}
let xVals = self.data[0];
let curPos = self.valToPos(cursorXVal, "x");
let rgtPos = nonNullRgt == null ? Infinity : self.valToPos(xVals[nonNullRgt], "x");
let lftPos = nonNullLft == null ? -Infinity : self.valToPos(xVals[nonNullLft], "x");
let lftDelta = curPos - lftPos;
let rgtDelta = rgtPos - curPos;
if (lftDelta <= rgtDelta) {
if (lftDelta <= hoverProximityPx) {
hoveredIdx = nonNullLft;
}
} else {
if (rgtDelta <= hoverProximityPx) {
hoveredIdx = nonNullRgt;
}
}
}
return hoveredIdx;
}
};
if (sync && sync() !== DashboardCursorSync.Off) {
cursor.sync = {
key: eventsScope,
scales: [xScaleKey, null]
};
}
builder.setCursor(cursor);
return builder;
};
function getNamesToFieldIndex(frame, allFrames) {
const originNames = /* @__PURE__ */ new Map();
frame.fields.forEach((field, i) => {
var _a, _b;
const origin = (_a = field.state) == null ? void 0 : _a.origin;
if (origin) {
const origField = (_b = allFrames[origin.frameIndex]) == null ? void 0 : _b.fields[origin.fieldIndex];
if (origField) {
originNames.set(getFieldDisplayName(origField, allFrames[origin.frameIndex], allFrames), i);
}
}
});
return originNames;
}
export { getNamesToFieldIndex, preparePlotConfigBuilder };
//# sourceMappingURL=utils.mjs.map