Compare commits
8 Commits
sriram/SQL
...
fastfrwrd/
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
65e740fe9c | ||
|
|
7f100bf104 | ||
|
|
76dfb8fbea | ||
|
|
19135016f9 | ||
|
|
6271b56247 | ||
|
|
7aa58f690a | ||
|
|
3e08e784c5 | ||
|
|
f00b83f3b6 |
@@ -833,11 +833,6 @@
|
||||
"count": 13
|
||||
}
|
||||
},
|
||||
"packages/grafana-ui/src/components/Sparkline/Sparkline.tsx": {
|
||||
"react-prefer-function-component/react-prefer-function-component": {
|
||||
"count": 1
|
||||
}
|
||||
},
|
||||
"packages/grafana-ui/src/components/StatsPicker/StatsPicker.story.tsx": {
|
||||
"react-prefer-function-component/react-prefer-function-component": {
|
||||
"count": 1
|
||||
|
||||
@@ -190,10 +190,62 @@ 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;
|
||||
// TODO: #112977 enable more reducers for highlight index
|
||||
// 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;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -2,7 +2,13 @@ import { css, cx } from '@emotion/css';
|
||||
import { isNumber } from 'lodash';
|
||||
import { useId } from 'react';
|
||||
|
||||
import { DisplayValueAlignmentFactors, FieldDisplay, getDisplayProcessor, GrafanaTheme2 } from '@grafana/data';
|
||||
import {
|
||||
DisplayValueAlignmentFactors,
|
||||
FieldDisplay,
|
||||
getDisplayProcessor,
|
||||
GrafanaTheme2,
|
||||
TimeRange,
|
||||
} from '@grafana/data';
|
||||
import { t } from '@grafana/i18n';
|
||||
|
||||
import { useStyles2, useTheme2 } from '../../themes/ThemeContext';
|
||||
@@ -66,6 +72,7 @@ export interface RadialGaugeProps {
|
||||
showScaleLabels?: boolean;
|
||||
/** For data links */
|
||||
onClick?: React.MouseEventHandler<HTMLElement>;
|
||||
timeRange?: TimeRange;
|
||||
}
|
||||
|
||||
export type RadialGradientMode = 'none' | 'auto';
|
||||
|
||||
@@ -1,30 +1,127 @@
|
||||
import { render } from '@testing-library/react';
|
||||
import { render, screen } from '@testing-library/react';
|
||||
|
||||
import { createTheme, FieldSparkline, FieldType } from '@grafana/data';
|
||||
import { createTheme, Field, FieldSparkline, FieldType } from '@grafana/data';
|
||||
|
||||
import { Sparkline } from './Sparkline';
|
||||
|
||||
describe('Sparkline', () => {
|
||||
it('should render without throwing an error', () => {
|
||||
const sparkline: FieldSparkline = {
|
||||
x: {
|
||||
name: 'x',
|
||||
values: [1679839200000, 1680444000000, 1681048800000, 1681653600000, 1682258400000],
|
||||
type: FieldType.time,
|
||||
config: {},
|
||||
},
|
||||
y: {
|
||||
name: 'y',
|
||||
values: [1, 2, 3, 4, 5],
|
||||
type: FieldType.number,
|
||||
config: {},
|
||||
state: {
|
||||
range: { min: 1, max: 5, delta: 1 },
|
||||
describe('renders without error', () => {
|
||||
const numField = (name: string, numVals: number): Field => ({
|
||||
name,
|
||||
values: Array.from({ length: numVals }, (_, i) => i + 1),
|
||||
type: FieldType.number,
|
||||
config: {},
|
||||
state:
|
||||
numVals > 0
|
||||
? {
|
||||
range: { min: 1, max: numVals, delta: numVals - 1 },
|
||||
}
|
||||
: {},
|
||||
});
|
||||
|
||||
const startTime = 1679839200000;
|
||||
const timeField = (name: string, numVals: number): Field => ({
|
||||
name,
|
||||
values: Array.from({ length: numVals }, (_, i) => startTime + (i + 1) * 1000),
|
||||
type: FieldType.time,
|
||||
config: {},
|
||||
state:
|
||||
numVals > 0
|
||||
? {
|
||||
range: { min: 1, max: numVals, delta: numVals - 1 },
|
||||
}
|
||||
: {},
|
||||
});
|
||||
|
||||
it.each<{ description: string; input: FieldSparkline; warning?: boolean }>([
|
||||
{
|
||||
description: 'x=time, y=number, 5 values',
|
||||
input: {
|
||||
x: timeField('x', 5),
|
||||
y: numField('y', 5),
|
||||
},
|
||||
},
|
||||
};
|
||||
expect(() =>
|
||||
render(<Sparkline width={800} height={600} theme={createTheme()} sparkline={sparkline} />)
|
||||
).not.toThrow();
|
||||
{
|
||||
description: 'x=time, y=number, 1 value',
|
||||
input: {
|
||||
x: timeField('x', 1),
|
||||
y: numField('y', 1),
|
||||
},
|
||||
warning: true,
|
||||
},
|
||||
{
|
||||
description: 'x=time, y=number, 0 values',
|
||||
input: {
|
||||
x: timeField('x', 0),
|
||||
y: numField('y', 0),
|
||||
},
|
||||
warning: true,
|
||||
},
|
||||
{
|
||||
description: 'x=time (unordered), y=number, 5 values',
|
||||
input: {
|
||||
x: {
|
||||
...timeField('x', 5),
|
||||
values: timeField('x', 5).values.reverse(),
|
||||
},
|
||||
y: timeField('y', 5),
|
||||
},
|
||||
warning: true,
|
||||
},
|
||||
{
|
||||
description: 'x=number, y=number, 5 values',
|
||||
input: {
|
||||
x: numField('x', 5),
|
||||
y: numField('y', 5),
|
||||
},
|
||||
},
|
||||
{
|
||||
description: 'x=number, y=number, 1 value',
|
||||
input: {
|
||||
x: numField('x', 1),
|
||||
y: numField('y', 1),
|
||||
},
|
||||
warning: true,
|
||||
},
|
||||
{
|
||||
description: 'x=number, y=number, 0 values',
|
||||
input: {
|
||||
x: numField('x', 0),
|
||||
y: numField('y', 0),
|
||||
},
|
||||
warning: true,
|
||||
},
|
||||
{
|
||||
description: 'y=number, 5 values',
|
||||
input: {
|
||||
y: numField('y', 5),
|
||||
},
|
||||
},
|
||||
{
|
||||
description: 'y=number, 1 value',
|
||||
input: {
|
||||
y: numField('y', 1),
|
||||
},
|
||||
warning: true,
|
||||
},
|
||||
{
|
||||
description: 'y=number, 0 values',
|
||||
input: {
|
||||
y: numField('y', 0),
|
||||
},
|
||||
warning: true,
|
||||
},
|
||||
])('does not throw for "$description"', ({ input, warning }) => {
|
||||
expect(() =>
|
||||
render(<Sparkline width={800} height={600} theme={createTheme()} sparkline={input} />)
|
||||
).not.toThrow();
|
||||
|
||||
if (warning) {
|
||||
expect(screen.getByRole('alert')).toBeInTheDocument();
|
||||
} else {
|
||||
expect(screen.queryByRole('alert')).not.toBeInTheDocument();
|
||||
expect(screen.getByTestId('uplot-main-div')).toBeInTheDocument();
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -1,32 +1,18 @@
|
||||
import { isEqual } from 'lodash';
|
||||
import { PureComponent } from 'react';
|
||||
import { AlignedData, Range } from 'uplot';
|
||||
import { css } from '@emotion/css';
|
||||
import React, { memo } from 'react';
|
||||
|
||||
import {
|
||||
compareDataFrameStructures,
|
||||
DataFrame,
|
||||
Field,
|
||||
FieldConfig,
|
||||
FieldSparkline,
|
||||
FieldType,
|
||||
getFieldColorModeForField,
|
||||
nullToValue,
|
||||
} from '@grafana/data';
|
||||
import {
|
||||
AxisPlacement,
|
||||
GraphDrawStyle,
|
||||
GraphFieldConfig,
|
||||
VisibilityMode,
|
||||
ScaleDirection,
|
||||
ScaleOrientation,
|
||||
} from '@grafana/schema';
|
||||
import { colorManipulator, FieldConfig, FieldSparkline, GrafanaTheme2 } from '@grafana/data';
|
||||
import { Trans } from '@grafana/i18n';
|
||||
import { GraphFieldConfig } from '@grafana/schema';
|
||||
|
||||
import { useStyles2 } from '../../themes/ThemeContext';
|
||||
import { Themeable2 } from '../../types/theme';
|
||||
import { Icon } from '../Icon/Icon';
|
||||
import { Tooltip } from '../Tooltip/Tooltip';
|
||||
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;
|
||||
@@ -35,169 +21,65 @@ export interface SparklineProps extends Themeable2 {
|
||||
sparkline: FieldSparkline;
|
||||
}
|
||||
|
||||
interface State {
|
||||
data: AlignedData;
|
||||
alignedDataFrame: DataFrame;
|
||||
configBuilder: UPlotConfigBuilder;
|
||||
}
|
||||
const CompactAlert = ({ children, width }: { width: number; children: string | React.ReactElement }) => {
|
||||
const styles = useStyles2(getCompactAlertStyles);
|
||||
|
||||
const defaultConfig: GraphFieldConfig = {
|
||||
drawStyle: GraphDrawStyle.Line,
|
||||
showPoints: VisibilityMode.Auto,
|
||||
axisPlacement: AxisPlacement.Hidden,
|
||||
pointSize: 2,
|
||||
return (
|
||||
<div role="alert" style={{ width, display: 'flex', justifyContent: 'center' }}>
|
||||
{width >= 400 ? (
|
||||
<div role="alert" className={styles.content}>
|
||||
<Icon className={styles.icon} name="exclamation-triangle" />
|
||||
{children}
|
||||
</div>
|
||||
) : (
|
||||
<Tooltip content={children} placement="top">
|
||||
<div className={styles.content}>
|
||||
<Icon className={styles.icon} size="lg" name="exclamation-triangle" />
|
||||
<Trans i18nKey="grafana-ui.components.sparkline.alert.title">Cannot render sparkline</Trans>
|
||||
</div>
|
||||
</Tooltip>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
/** @internal */
|
||||
export class Sparkline extends PureComponent<SparklineProps, State> {
|
||||
constructor(props: SparklineProps) {
|
||||
super(props);
|
||||
const getCompactAlertStyles = (theme: GrafanaTheme2) => ({
|
||||
content: css({
|
||||
margin: theme.spacing(1),
|
||||
fontSize: theme.typography.bodySmall.fontSize,
|
||||
fontWeight: theme.typography.fontWeightMedium,
|
||||
padding: theme.spacing(0.5, 1),
|
||||
color: theme.colors.warning.contrastText,
|
||||
background: colorManipulator.alpha(theme.colors.warning.main, 0.85),
|
||||
borderRadius: theme.shape.radius.default,
|
||||
display: 'flex',
|
||||
justifyContent: 'center',
|
||||
|
||||
const alignedDataFrame = preparePlotFrame(props.sparkline, props.config);
|
||||
|
||||
this.state = {
|
||||
data: preparePlotData2(alignedDataFrame, getStackingGroups(alignedDataFrame)),
|
||||
alignedDataFrame,
|
||||
configBuilder: this.prepareConfig(alignedDataFrame),
|
||||
};
|
||||
}
|
||||
|
||||
static getDerivedStateFromProps(props: SparklineProps, state: State) {
|
||||
const _frame = preparePlotFrame(props.sparkline, props.config);
|
||||
const frame = nullToValue(_frame);
|
||||
if (!frame) {
|
||||
return { ...state };
|
||||
}
|
||||
|
||||
return {
|
||||
...state,
|
||||
data: preparePlotData2(frame, getStackingGroups(frame)),
|
||||
alignedDataFrame: frame,
|
||||
};
|
||||
}
|
||||
|
||||
componentDidUpdate(prevProps: SparklineProps, prevState: State) {
|
||||
const { alignedDataFrame } = this.state;
|
||||
|
||||
if (!alignedDataFrame) {
|
||||
return;
|
||||
}
|
||||
|
||||
let rebuildConfig = false;
|
||||
|
||||
if (prevProps.sparkline !== this.props.sparkline) {
|
||||
const isStructureChanged = !compareDataFrameStructures(this.state.alignedDataFrame, prevState.alignedDataFrame);
|
||||
const isRangeChanged = !isEqual(
|
||||
alignedDataFrame.fields[1].state?.range,
|
||||
prevState.alignedDataFrame.fields[1].state?.range
|
||||
);
|
||||
rebuildConfig = isStructureChanged || isRangeChanged;
|
||||
} else {
|
||||
rebuildConfig = !isEqual(prevProps.config, this.props.config);
|
||||
}
|
||||
|
||||
if (rebuildConfig) {
|
||||
this.setState({ configBuilder: this.prepareConfig(alignedDataFrame) });
|
||||
}
|
||||
}
|
||||
|
||||
getYRange(field: Field): Range.MinMax {
|
||||
return getYRange(field, this.state.alignedDataFrame);
|
||||
}
|
||||
|
||||
prepareConfig(data: DataFrame) {
|
||||
const { theme } = this.props;
|
||||
const builder = new UPlotConfigBuilder();
|
||||
|
||||
builder.setCursor({
|
||||
show: false,
|
||||
x: false, // no crosshairs
|
||||
y: false,
|
||||
});
|
||||
|
||||
// X is the first field in the alligned frame
|
||||
const xField = data.fields[0];
|
||||
builder.addScale({
|
||||
scaleKey: 'x',
|
||||
orientation: ScaleOrientation.Horizontal,
|
||||
direction: ScaleDirection.Right,
|
||||
isTime: false, //xField.type === FieldType.time,
|
||||
range: () => {
|
||||
const { sparkline } = this.props;
|
||||
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];
|
||||
'& a': {
|
||||
color: theme.colors.warning.contrastText,
|
||||
textDecoration: 'underline',
|
||||
'&:hover': {
|
||||
textDecoration: 'none',
|
||||
},
|
||||
});
|
||||
},
|
||||
}),
|
||||
icon: css({
|
||||
marginRight: theme.spacing(1),
|
||||
}),
|
||||
});
|
||||
|
||||
builder.addAxis({
|
||||
scaleKey: 'x',
|
||||
theme,
|
||||
placement: AxisPlacement.Hidden,
|
||||
});
|
||||
export const Sparkline: React.FC<SparklineProps> = memo((props) => {
|
||||
const { sparkline, config: fieldConfig, theme, width, height } = props;
|
||||
|
||||
for (let i = 0; i < data.fields.length; i++) {
|
||||
const field = data.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: () => this.getYRange(field),
|
||||
});
|
||||
|
||||
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;
|
||||
const { frame: alignedDataFrame, warning } = prepareSeries(sparkline, fieldConfig);
|
||||
if (warning) {
|
||||
return <CompactAlert width={width}>{warning}</CompactAlert>;
|
||||
}
|
||||
|
||||
render() {
|
||||
const { data, configBuilder } = this.state;
|
||||
const { width, height } = this.props;
|
||||
return <UPlotChart data={data} config={configBuilder} width={width} height={height} />;
|
||||
}
|
||||
}
|
||||
const data = preparePlotData2(alignedDataFrame, getStackingGroups(alignedDataFrame));
|
||||
const configBuilder = prepareConfig(sparkline, alignedDataFrame, theme);
|
||||
|
||||
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,141 @@ export function getYRange(field: Field, alignedFrame: DataFrame): Range.MinMax {
|
||||
|
||||
return [min, max];
|
||||
}
|
||||
|
||||
// TODO: #112977 enable highlight index
|
||||
// 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,
|
||||
};
|
||||
}
|
||||
if (sparkline.x && !isLikelyAscendingVector(sparkline.x.values)) {
|
||||
return {
|
||||
warning: t(
|
||||
'grafana-ui.components.sparkline.warning.x-not-ascending',
|
||||
"The data in your Sparkline's x series must be sorted in ascending order."
|
||||
),
|
||||
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);
|
||||
// TODO: #112977 enable highlight index and adjust padding accordingly
|
||||
// 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,
|
||||
// TODO: #112977 enable highlight index
|
||||
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;
|
||||
};
|
||||
|
||||
@@ -8693,6 +8693,17 @@
|
||||
"aria-label-default": "Pick a color",
|
||||
"aria-label-selected-color": "{{colorLabel}} color"
|
||||
},
|
||||
"components": {
|
||||
"sparkline": {
|
||||
"alert": {
|
||||
"title": "Cannot render sparkline"
|
||||
},
|
||||
"warning": {
|
||||
"too-few-values": "Sparkline requires at least two values to render.",
|
||||
"x-not-ascending": "The data in your Sparkline's x series must be sorted in ascending order."
|
||||
}
|
||||
}
|
||||
},
|
||||
"confirm-button": {
|
||||
"aria-label-delete": "Delete",
|
||||
"cancel": "Cancel",
|
||||
|
||||
Reference in New Issue
Block a user