refactor out utils, experiment with getting highlightIndex working
This commit is contained in:
@@ -190,10 +190,61 @@ export const getFieldDisplayValues = (options: GetFieldDisplayValuesOptions): Fi
|
||||
y: dataFrame.fields[i],
|
||||
x: timeField,
|
||||
};
|
||||
if (calc === ReducerID.last) {
|
||||
sparkline.highlightIndex = sparkline.y.values.length - 1;
|
||||
} else if (calc === ReducerID.first) {
|
||||
sparkline.highlightIndex = 0;
|
||||
let highlightIdx: number | undefined = (() => {
|
||||
switch (calc) {
|
||||
case ReducerID.last:
|
||||
return sparkline.y.values.length - 1;
|
||||
case ReducerID.first:
|
||||
return 0;
|
||||
case ReducerID.lastNotNull: {
|
||||
for (let k = sparkline.y.values.length - 1; k >= 0; k--) {
|
||||
const v = sparkline.y.values[k];
|
||||
if (v !== null && v !== undefined && !Number.isNaN(v)) {
|
||||
return k;
|
||||
}
|
||||
}
|
||||
return;
|
||||
}
|
||||
case ReducerID.firstNotNull: {
|
||||
for (let k = 0; k < sparkline.y.values.length; k++) {
|
||||
const v = sparkline.y.values[k];
|
||||
if (v !== null && v !== undefined && !Number.isNaN(v)) {
|
||||
return k;
|
||||
}
|
||||
}
|
||||
return;
|
||||
}
|
||||
case ReducerID.min: {
|
||||
let minIdx = -1;
|
||||
let prevMin = Infinity;
|
||||
for (let k = 0; k < sparkline.y.values.length; k++) {
|
||||
const v = sparkline.y.values[k];
|
||||
if (v !== null && v !== undefined && !Number.isNaN(v) && v < prevMin) {
|
||||
prevMin = v;
|
||||
minIdx = k;
|
||||
}
|
||||
}
|
||||
return minIdx >= 0 ? minIdx : undefined;
|
||||
}
|
||||
case ReducerID.max: {
|
||||
let maxIdx = -1;
|
||||
let prevMax = -Infinity;
|
||||
for (let k = 0; k < sparkline.y.values.length; k++) {
|
||||
const v = sparkline.y.values[k];
|
||||
if (v !== null && v !== undefined && !Number.isNaN(v) && v > prevMax) {
|
||||
prevMax = v;
|
||||
maxIdx = k;
|
||||
}
|
||||
}
|
||||
return maxIdx >= 0 ? maxIdx : undefined;
|
||||
}
|
||||
default:
|
||||
return;
|
||||
}
|
||||
})();
|
||||
|
||||
if (typeof highlightIdx === 'number') {
|
||||
sparkline.highlightIndex = highlightIdx;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -248,9 +248,6 @@ export function RadialGauge(props: RadialGaugeProps) {
|
||||
}
|
||||
|
||||
if (displayValue.sparkline) {
|
||||
if (props.timeRange && displayValue.sparkline.x?.type === FieldType.time) {
|
||||
displayValue.sparkline.timeRange = props.timeRange;
|
||||
}
|
||||
sparklineElement = (
|
||||
<RadialSparkline
|
||||
sparkline={displayValue.sparkline}
|
||||
|
||||
@@ -1,29 +1,13 @@
|
||||
import React, { memo } from 'react';
|
||||
|
||||
import {
|
||||
DataFrame,
|
||||
FieldConfig,
|
||||
FieldSparkline,
|
||||
FieldType,
|
||||
getFieldColorModeForField,
|
||||
GrafanaTheme2,
|
||||
nullToValue,
|
||||
} from '@grafana/data';
|
||||
import {
|
||||
AxisPlacement,
|
||||
GraphDrawStyle,
|
||||
GraphFieldConfig,
|
||||
VisibilityMode,
|
||||
ScaleDirection,
|
||||
ScaleOrientation,
|
||||
} from '@grafana/schema';
|
||||
import { FieldConfig, FieldSparkline } from '@grafana/data';
|
||||
import { GraphFieldConfig } from '@grafana/schema';
|
||||
|
||||
import { Themeable2 } from '../../types/theme';
|
||||
import { UPlotChart } from '../uPlot/Plot';
|
||||
import { UPlotConfigBuilder } from '../uPlot/config/UPlotConfigBuilder';
|
||||
import { preparePlotData2, getStackingGroups } from '../uPlot/utils';
|
||||
|
||||
import { getYRange, preparePlotFrame } from './utils';
|
||||
import { prepareSeries, prepareConfig } from './utils';
|
||||
|
||||
export interface SparklineProps extends Themeable2 {
|
||||
width: number;
|
||||
@@ -32,106 +16,11 @@ export interface SparklineProps extends Themeable2 {
|
||||
sparkline: FieldSparkline;
|
||||
}
|
||||
|
||||
const defaultConfig: GraphFieldConfig = {
|
||||
drawStyle: GraphDrawStyle.Line,
|
||||
showPoints: VisibilityMode.Auto,
|
||||
axisPlacement: AxisPlacement.Hidden,
|
||||
pointSize: 2,
|
||||
};
|
||||
|
||||
const prepareConfig = (sparkline: FieldSparkline, dataFrame: DataFrame, theme: GrafanaTheme2): UPlotConfigBuilder => {
|
||||
const builder = new UPlotConfigBuilder();
|
||||
|
||||
builder.setCursor({
|
||||
show: false,
|
||||
x: false,
|
||||
y: false,
|
||||
});
|
||||
|
||||
// X is the first field in the aligned frame
|
||||
const xField = dataFrame.fields[0];
|
||||
builder.addScale({
|
||||
scaleKey: 'x',
|
||||
orientation: ScaleOrientation.Horizontal,
|
||||
direction: ScaleDirection.Right,
|
||||
isTime: false,
|
||||
range: () => {
|
||||
if (sparkline.x) {
|
||||
if (sparkline.timeRange && sparkline.x.type === FieldType.time) {
|
||||
return [sparkline.timeRange.from.valueOf(), sparkline.timeRange.to.valueOf()];
|
||||
}
|
||||
const vals = sparkline.x.values;
|
||||
return [vals[0], vals[vals.length - 1]];
|
||||
}
|
||||
return [0, sparkline.y.values.length - 1];
|
||||
},
|
||||
});
|
||||
|
||||
builder.addAxis({
|
||||
scaleKey: 'x',
|
||||
theme,
|
||||
placement: AxisPlacement.Hidden,
|
||||
});
|
||||
|
||||
for (let i = 0; i < dataFrame.fields.length; i++) {
|
||||
const field = dataFrame.fields[i];
|
||||
const config: FieldConfig<GraphFieldConfig> = field.config;
|
||||
const customConfig: GraphFieldConfig = {
|
||||
...defaultConfig,
|
||||
...config.custom,
|
||||
};
|
||||
|
||||
if (field === xField || field.type !== FieldType.number) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const scaleKey = config.unit || '__fixed';
|
||||
builder.addScale({
|
||||
scaleKey,
|
||||
orientation: ScaleOrientation.Vertical,
|
||||
direction: ScaleDirection.Up,
|
||||
range: () => getYRange(field, dataFrame),
|
||||
});
|
||||
|
||||
builder.addAxis({
|
||||
scaleKey,
|
||||
theme,
|
||||
placement: AxisPlacement.Hidden,
|
||||
});
|
||||
|
||||
const colorMode = getFieldColorModeForField(field);
|
||||
const seriesColor = colorMode.getCalculator(field, theme)(0, 0);
|
||||
const pointsMode =
|
||||
customConfig.drawStyle === GraphDrawStyle.Points ? VisibilityMode.Always : customConfig.showPoints;
|
||||
|
||||
builder.addSeries({
|
||||
pxAlign: false,
|
||||
scaleKey,
|
||||
theme,
|
||||
colorMode,
|
||||
thresholds: config.thresholds,
|
||||
drawStyle: customConfig.drawStyle!,
|
||||
lineColor: customConfig.lineColor ?? seriesColor,
|
||||
lineWidth: customConfig.lineWidth,
|
||||
lineInterpolation: customConfig.lineInterpolation,
|
||||
showPoints: pointsMode,
|
||||
pointSize: customConfig.pointSize,
|
||||
fillOpacity: customConfig.fillOpacity,
|
||||
fillColor: customConfig.fillColor,
|
||||
lineStyle: customConfig.lineStyle,
|
||||
gradientMode: customConfig.gradientMode,
|
||||
spanNulls: customConfig.spanNulls,
|
||||
});
|
||||
}
|
||||
|
||||
return builder;
|
||||
};
|
||||
|
||||
export const Sparkline: React.FC<SparklineProps> = memo((props) => {
|
||||
const { sparkline, config: fieldConfig, theme, width, height } = props;
|
||||
const alignedDataFrame = nullToValue(preparePlotFrame(sparkline, fieldConfig));
|
||||
// do not render sparklines for fields with 1 or less values - this can cause an infinite loop in uPlot
|
||||
if (alignedDataFrame.fields.some((f) => f.values.length <= 1)) {
|
||||
|
||||
const { frame: alignedDataFrame, warning } = prepareSeries(sparkline, fieldConfig);
|
||||
if (warning) {
|
||||
return null;
|
||||
}
|
||||
|
||||
@@ -140,4 +29,5 @@ export const Sparkline: React.FC<SparklineProps> = memo((props) => {
|
||||
|
||||
return <UPlotChart data={data} config={configBuilder} width={width} height={height} />;
|
||||
});
|
||||
|
||||
Sparkline.displayName = 'Sparkline';
|
||||
|
||||
@@ -1,16 +1,29 @@
|
||||
import { Range } from 'uplot';
|
||||
|
||||
import {
|
||||
applyNullInsertThreshold,
|
||||
DataFrame,
|
||||
Field,
|
||||
FieldConfig,
|
||||
FieldSparkline,
|
||||
FieldType,
|
||||
getFieldColorModeForField,
|
||||
GrafanaTheme2,
|
||||
isLikelyAscendingVector,
|
||||
nullToValue,
|
||||
sortDataFrame,
|
||||
applyNullInsertThreshold,
|
||||
Field,
|
||||
} from '@grafana/data';
|
||||
import { GraphFieldConfig } from '@grafana/schema';
|
||||
import { t } from '@grafana/i18n';
|
||||
import {
|
||||
AxisPlacement,
|
||||
GraphDrawStyle,
|
||||
GraphFieldConfig,
|
||||
VisibilityMode,
|
||||
ScaleDirection,
|
||||
ScaleOrientation,
|
||||
} from '@grafana/schema';
|
||||
|
||||
import { UPlotConfigBuilder } from '../uPlot/config/UPlotConfigBuilder';
|
||||
|
||||
/** @internal
|
||||
* Given a sparkline config returns a DataFrame ready to be turned into Plot data set
|
||||
@@ -85,3 +98,129 @@ export function getYRange(field: Field, alignedFrame: DataFrame): Range.MinMax {
|
||||
|
||||
return [min, max];
|
||||
}
|
||||
|
||||
// const HIGHLIGHT_IDX_POINT_SIZE = 6;
|
||||
|
||||
const defaultConfig: GraphFieldConfig = {
|
||||
drawStyle: GraphDrawStyle.Line,
|
||||
showPoints: VisibilityMode.Auto,
|
||||
axisPlacement: AxisPlacement.Hidden,
|
||||
pointSize: 2,
|
||||
};
|
||||
|
||||
export const prepareSeries = (
|
||||
sparkline: FieldSparkline,
|
||||
fieldConfig?: FieldConfig<GraphFieldConfig>
|
||||
): { frame: DataFrame; warning?: string } => {
|
||||
const frame = nullToValue(preparePlotFrame(sparkline, fieldConfig));
|
||||
if (frame.fields.some((f) => f.values.length <= 1)) {
|
||||
return {
|
||||
warning: t(
|
||||
'grafana-ui.components.sparkline.warning.too-few-values',
|
||||
'Sparkline requires at least two values to render.'
|
||||
),
|
||||
frame,
|
||||
};
|
||||
}
|
||||
return { frame };
|
||||
};
|
||||
|
||||
export const prepareConfig = (
|
||||
sparkline: FieldSparkline,
|
||||
dataFrame: DataFrame,
|
||||
theme: GrafanaTheme2
|
||||
): UPlotConfigBuilder => {
|
||||
const builder = new UPlotConfigBuilder();
|
||||
// const rangePad = HIGHLIGHT_IDX_POINT_SIZE / 2;
|
||||
|
||||
builder.setCursor({
|
||||
show: false,
|
||||
x: false, // no crosshairs
|
||||
y: false,
|
||||
});
|
||||
|
||||
// X is the first field in the aligned frame
|
||||
const xField = dataFrame.fields[0];
|
||||
builder.addScale({
|
||||
scaleKey: 'x',
|
||||
orientation: ScaleOrientation.Horizontal,
|
||||
direction: ScaleDirection.Right,
|
||||
isTime: false, // xField.type === FieldType.time,
|
||||
range: () => {
|
||||
if (sparkline.x) {
|
||||
if (sparkline.timeRange && sparkline.x.type === FieldType.time) {
|
||||
return [sparkline.timeRange.from.valueOf(), sparkline.timeRange.to.valueOf()];
|
||||
}
|
||||
const vals = sparkline.x.values;
|
||||
return [vals[0], vals[vals.length - 1]];
|
||||
}
|
||||
return [0, sparkline.y.values.length - 1];
|
||||
},
|
||||
});
|
||||
|
||||
builder.addAxis({
|
||||
scaleKey: 'x',
|
||||
theme,
|
||||
placement: AxisPlacement.Hidden,
|
||||
});
|
||||
|
||||
for (let i = 0; i < dataFrame.fields.length; i++) {
|
||||
const field = dataFrame.fields[i];
|
||||
const config: FieldConfig<GraphFieldConfig> = field.config;
|
||||
const customConfig: GraphFieldConfig = {
|
||||
...defaultConfig,
|
||||
...config.custom,
|
||||
};
|
||||
|
||||
if (field === xField || field.type !== FieldType.number) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const scaleKey = config.unit || '__fixed';
|
||||
builder.addScale({
|
||||
scaleKey,
|
||||
orientation: ScaleOrientation.Vertical,
|
||||
direction: ScaleDirection.Up,
|
||||
range: () => getYRange(field, dataFrame),
|
||||
});
|
||||
|
||||
builder.addAxis({
|
||||
scaleKey,
|
||||
theme,
|
||||
placement: AxisPlacement.Hidden,
|
||||
});
|
||||
|
||||
const colorMode = getFieldColorModeForField(field);
|
||||
const seriesColor = colorMode.getCalculator(field, theme)(0, 0);
|
||||
// const hasHighlightIndex = typeof sparkline.highlightIndex === 'number';
|
||||
// if (hasHighlightIndex) {
|
||||
// builder.setPadding([rangePad, rangePad, rangePad, rangePad]);
|
||||
// }
|
||||
const pointsMode =
|
||||
customConfig.drawStyle === GraphDrawStyle.Points // || hasHighlightIndex
|
||||
? VisibilityMode.Always
|
||||
: customConfig.showPoints;
|
||||
|
||||
builder.addSeries({
|
||||
pxAlign: false,
|
||||
scaleKey,
|
||||
theme,
|
||||
colorMode,
|
||||
thresholds: config.thresholds,
|
||||
drawStyle: customConfig.drawStyle!,
|
||||
lineColor: customConfig.lineColor ?? seriesColor,
|
||||
lineWidth: customConfig.lineWidth,
|
||||
lineInterpolation: customConfig.lineInterpolation,
|
||||
showPoints: pointsMode,
|
||||
// pointSize: hasHighlightIndex ? HIGHLIGHT_IDX_POINT_SIZE : customConfig.pointSize,
|
||||
// pointsFilter: hasHighlightIndex ? [sparkline.highlightIndex!] : undefined,
|
||||
fillOpacity: customConfig.fillOpacity,
|
||||
fillColor: customConfig.fillColor,
|
||||
lineStyle: customConfig.lineStyle,
|
||||
gradientMode: customConfig.gradientMode,
|
||||
spanNulls: customConfig.spanNulls,
|
||||
});
|
||||
}
|
||||
|
||||
return builder;
|
||||
};
|
||||
|
||||
@@ -9,20 +9,20 @@ import { config } from '@grafana/runtime';
|
||||
import { importPanelPlugin } from 'app/features/plugins/importPanelPlugin';
|
||||
|
||||
export const panelsToCheckFirst = [
|
||||
// 'timeseries',
|
||||
// 'barchart',
|
||||
'timeseries',
|
||||
'barchart',
|
||||
'gauge',
|
||||
// 'stat',
|
||||
// 'piechart',
|
||||
// 'bargauge',
|
||||
// 'table',
|
||||
// 'state-timeline',
|
||||
// 'status-history',
|
||||
// 'logs',
|
||||
// 'candlestick',
|
||||
// 'flamegraph',
|
||||
// 'traces',
|
||||
// 'nodeGraph',
|
||||
'stat',
|
||||
'piechart',
|
||||
'bargauge',
|
||||
'table',
|
||||
'state-timeline',
|
||||
'status-history',
|
||||
'logs',
|
||||
'candlestick',
|
||||
'flamegraph',
|
||||
'traces',
|
||||
'nodeGraph',
|
||||
];
|
||||
|
||||
export async function getAllSuggestions(
|
||||
|
||||
@@ -8693,6 +8693,13 @@
|
||||
"aria-label-default": "Pick a color",
|
||||
"aria-label-selected-color": "{{colorLabel}} color"
|
||||
},
|
||||
"components": {
|
||||
"sparkline": {
|
||||
"warning": {
|
||||
"too-few-values": "Sparkline requires at least two values to render."
|
||||
}
|
||||
}
|
||||
},
|
||||
"confirm-button": {
|
||||
"aria-label-delete": "Delete",
|
||||
"cancel": "Cancel",
|
||||
|
||||
Reference in New Issue
Block a user