Commit 3ce93050 by Ryan McKinley Committed by GitHub

GraphNG: replace bizcharts with uPlot for sparklines (#29632)

parent 0e0ab8c9
...@@ -6,7 +6,7 @@ import { DataFrame } from '../types/dataFrame'; ...@@ -6,7 +6,7 @@ import { DataFrame } from '../types/dataFrame';
* *
* To compare multiple frames use: * To compare multiple frames use:
* ``` * ```
* areArraysEqual(a, b, framesHaveSameStructure); * compareArrayValues(a, b, framesHaveSameStructure);
* ``` * ```
* NOTE: this does a shallow check on the FieldConfig properties, when using the query * NOTE: this does a shallow check on the FieldConfig properties, when using the query
* editor, this should be sufficient, however if applicaitons are mutating properties * editor, this should be sufficient, however if applicaitons are mutating properties
......
...@@ -2,7 +2,6 @@ import toString from 'lodash/toString'; ...@@ -2,7 +2,6 @@ import toString from 'lodash/toString';
import isEmpty from 'lodash/isEmpty'; import isEmpty from 'lodash/isEmpty';
import { getDisplayProcessor } from './displayProcessor'; import { getDisplayProcessor } from './displayProcessor';
import { getFlotPairs } from '../utils/flotPairs';
import { import {
DataFrame, DataFrame,
DisplayValue, DisplayValue,
...@@ -13,10 +12,10 @@ import { ...@@ -13,10 +12,10 @@ import {
FieldType, FieldType,
InterpolateFunction, InterpolateFunction,
LinkModel, LinkModel,
TimeRange,
TimeZone, TimeZone,
} from '../types'; } from '../types';
import { DataFrameView } from '../dataframe/DataFrameView'; import { DataFrameView } from '../dataframe/DataFrameView';
import { GraphSeriesValue } from '../types/graph';
import { GrafanaTheme } from '../types/theme'; import { GrafanaTheme } from '../types/theme';
import { reduceField, ReducerID } from '../transformations/fieldReducer'; import { reduceField, ReducerID } from '../transformations/fieldReducer';
import { ScopedVars } from '../types/ScopedVars'; import { ScopedVars } from '../types/ScopedVars';
...@@ -56,11 +55,18 @@ function getTitleTemplate(stats: string[]): string { ...@@ -56,11 +55,18 @@ function getTitleTemplate(stats: string[]): string {
return parts.join(' '); return parts.join(' ');
} }
export interface FieldSparkline {
y: Field; // Y values
x?: Field; // if this does not exist, use the index
timeRange?: TimeRange; // Optionally force an absolute time
highlightIndex?: number;
}
export interface FieldDisplay { export interface FieldDisplay {
name: string; // The field name (title is in display) name: string; // The field name (title is in display)
field: FieldConfig; field: FieldConfig;
display: DisplayValue; display: DisplayValue;
sparkline?: GraphSeriesValue[][]; sparkline?: FieldSparkline;
// Expose to the original values for delayed inspection (DataLinks etc) // Expose to the original values for delayed inspection (DataLinks etc)
view?: DataFrameView; view?: DataFrameView;
...@@ -185,15 +191,6 @@ export const getFieldDisplayValues = (options: GetFieldDisplayValuesOptions): Fi ...@@ -185,15 +191,6 @@ export const getFieldDisplayValues = (options: GetFieldDisplayValuesOptions): Fi
field, field,
reducers: calcs, // The stats to calculate reducers: calcs, // The stats to calculate
}); });
let sparkline: GraphSeriesValue[][] | undefined = undefined;
// Single sparkline for every reducer
if (options.sparkline && timeField) {
sparkline = getFlotPairs({
xField: timeField,
yField: series.fields[i],
});
}
for (const calc of calcs) { for (const calc of calcs) {
scopedVars[VAR_CALC] = { value: calc, text: calc }; scopedVars[VAR_CALC] = { value: calc, text: calc };
...@@ -203,6 +200,19 @@ export const getFieldDisplayValues = (options: GetFieldDisplayValuesOptions): Fi ...@@ -203,6 +200,19 @@ export const getFieldDisplayValues = (options: GetFieldDisplayValuesOptions): Fi
...scopedVars, ...scopedVars,
}); });
let sparkline: FieldSparkline | undefined = undefined;
if (options.sparkline) {
sparkline = {
y: series.fields[i],
x: timeField,
};
if (calc === ReducerID.last) {
sparkline.highlightIndex = sparkline.y.values.length - 1;
} else if (calc === ReducerID.first) {
sparkline.highlightIndex = 0;
}
}
values.push({ values.push({
name: calc, name: calc,
field: config, field: config,
......
import { Field, FieldType } from '../types';
import { FunctionalVector } from './FunctionalVector';
/**
* IndexVector is a simple vector implementation that returns the index value
* for each element in the vector. It is functionally equivolant a vector backed
* by an array with values: `[0,1,2,...,length-1]`
*/
export class IndexVector extends FunctionalVector<number> {
constructor(private len: number) {
super();
}
get length() {
return this.len;
}
get(index: number): number {
return index;
}
/**
* Returns a field representing the range [0 ... length-1]
*/
static newField(len: number): Field<number> {
return {
name: '',
values: new IndexVector(len),
type: FieldType.number,
config: {
min: 0,
max: len - 1,
},
};
}
}
...@@ -5,5 +5,6 @@ export * from './ConstantVector'; ...@@ -5,5 +5,6 @@ export * from './ConstantVector';
export * from './BinaryOperationVector'; export * from './BinaryOperationVector';
export * from './SortedVector'; export * from './SortedVector';
export * from './FormattedVector'; export * from './FormattedVector';
export * from './IndexVector';
export { vectorator } from './FunctionalVector'; export { vectorator } from './FunctionalVector';
...@@ -117,7 +117,7 @@ module.exports = { ...@@ -117,7 +117,7 @@ module.exports = {
minimize: isProductionBuild, minimize: isProductionBuild,
minimizer: isProductionBuild minimizer: isProductionBuild
? [ ? [
new TerserPlugin({ cache: false, parallel: false, sourceMap: false, exclude: /monaco|bizcharts/ }), new TerserPlugin({ cache: false, parallel: false, sourceMap: false, exclude: /monaco/ }),
new OptimizeCSSAssetsPlugin({}), new OptimizeCSSAssetsPlugin({}),
] ]
: [], : [],
......
...@@ -41,7 +41,6 @@ ...@@ -41,7 +41,6 @@
"@types/react-table": "7.0.12", "@types/react-table": "7.0.12",
"@types/slate": "0.47.1", "@types/slate": "0.47.1",
"@types/slate-react": "0.22.5", "@types/slate-react": "0.22.5",
"bizcharts": "^3.5.8",
"classnames": "2.2.6", "classnames": "2.2.6",
"d3": "5.15.0", "d3": "5.15.0",
"emotion": "10.0.27", "emotion": "10.0.27",
......
...@@ -4,6 +4,7 @@ import { BigValue, BigValueColorMode, BigValueGraphMode, BigValueJustifyMode, Bi ...@@ -4,6 +4,7 @@ import { BigValue, BigValueColorMode, BigValueGraphMode, BigValueJustifyMode, Bi
import { withCenteredStory } from '../../utils/storybook/withCenteredStory'; import { withCenteredStory } from '../../utils/storybook/withCenteredStory';
import mdx from './BigValue.mdx'; import mdx from './BigValue.mdx';
import { useTheme } from '../../themes'; import { useTheme } from '../../themes';
import { ArrayVector, FieldSparkline, FieldType } from '@grafana/data';
const getKnobs = () => { const getKnobs = () => {
return { return {
...@@ -37,17 +38,13 @@ export default { ...@@ -37,17 +38,13 @@ export default {
export const Basic = () => { export const Basic = () => {
const { value, title, colorMode, graphMode, height, width, color, textMode, justifyMode } = getKnobs(); const { value, title, colorMode, graphMode, height, width, color, textMode, justifyMode } = getKnobs();
const theme = useTheme(); const theme = useTheme();
const sparkline = { const sparkline: FieldSparkline = {
xMin: 0, y: {
xMax: 5, name: '',
data: [ values: new ArrayVector([1, 2, 3, 4, 3]),
[0, 10], type: FieldType.number,
[1, 20], config: {},
[2, 15], },
[3, 25],
[4, 5],
[5, 10],
],
}; };
return ( return (
......
// Library // Library
import React, { PureComponent } from 'react'; import React, { PureComponent } from 'react';
import { DisplayValue, GraphSeriesValue, DisplayValueAlignmentFactors, TextDisplayOptions } from '@grafana/data'; import { DisplayValue, DisplayValueAlignmentFactors, FieldSparkline, TextDisplayOptions } from '@grafana/data';
// Types // Types
import { Themeable } from '../../types'; import { Themeable } from '../../types';
import { buildLayout } from './BigValueLayout'; import { buildLayout } from './BigValueLayout';
import { FormattedValueDisplay } from '../FormattedValueDisplay/FormattedValueDisplay'; import { FormattedValueDisplay } from '../FormattedValueDisplay/FormattedValueDisplay';
export interface BigValueSparkline {
data: GraphSeriesValue[][];
xMin?: number | null;
xMax?: number | null;
yMin?: number | null;
yMax?: number | null;
highlightIndex?: number;
}
export enum BigValueColorMode { export enum BigValueColorMode {
Value = 'value', Value = 'value',
Background = 'background', Background = 'background',
...@@ -51,7 +42,7 @@ export interface Props extends Themeable { ...@@ -51,7 +42,7 @@ export interface Props extends Themeable {
/** Value displayed as Big Value */ /** Value displayed as Big Value */
value: DisplayValue; value: DisplayValue;
/** Sparkline values for showing a graph under/behind the value */ /** Sparkline values for showing a graph under/behind the value */
sparkline?: BigValueSparkline; sparkline?: FieldSparkline;
/** onClick handler for the value */ /** onClick handler for the value */
onClick?: React.MouseEventHandler<HTMLElement>; onClick?: React.MouseEventHandler<HTMLElement>;
/** Custom styling */ /** Custom styling */
......
import { Props, BigValueColorMode, BigValueGraphMode } from './BigValue'; import { Props, BigValueColorMode, BigValueGraphMode } from './BigValue';
import { buildLayout, StackedWithChartLayout, WideWithChartLayout } from './BigValueLayout'; import { buildLayout, StackedWithChartLayout, WideWithChartLayout } from './BigValueLayout';
import { getTheme } from '../../themes'; import { getTheme } from '../../themes';
import { ArrayVector, FieldType } from '@grafana/data';
function getProps(propOverrides?: Partial<Props>): Props { function getProps(propOverrides?: Partial<Props>): Props {
const props: Props = { const props: Props = {
...@@ -13,12 +14,12 @@ function getProps(propOverrides?: Partial<Props>): Props { ...@@ -13,12 +14,12 @@ function getProps(propOverrides?: Partial<Props>): Props {
numeric: 25, numeric: 25,
}, },
sparkline: { sparkline: {
data: [ y: {
[10, 10], name: '',
[10, 10], values: new ArrayVector([1, 2, 3, 4, 3]),
], type: FieldType.number,
xMin: 0, config: {},
xMax: 100, },
}, },
theme: getTheme(), theme: getTheme(),
}; };
......
// Libraries // Libraries
import React, { CSSProperties } from 'react'; import React, { CSSProperties } from 'react';
import tinycolor from 'tinycolor2'; import tinycolor from 'tinycolor2';
import { Chart, Geom } from 'bizcharts';
// Utils // Utils
import { formattedValueToString, DisplayValue, getColorForTheme } from '@grafana/data'; import { formattedValueToString, DisplayValue, getColorForTheme, FieldConfig } from '@grafana/data';
import { calculateFontSize } from '../../utils/measureText'; import { calculateFontSize } from '../../utils/measureText';
// Types // Types
import { BigValueColorMode, Props, BigValueJustifyMode, BigValueTextMode } from './BigValue'; import { BigValueColorMode, Props, BigValueJustifyMode, BigValueTextMode } from './BigValue';
import { getTextColorForBackground } from '../../utils'; import { getTextColorForBackground } from '../../utils';
import { DrawStyle, GraphFieldConfig } from '../uPlot/config';
import { Sparkline } from '../Sparkline/Sparkline';
const LINE_HEIGHT = 1.2; const LINE_HEIGHT = 1.2;
const MAX_TITLE_SIZE = 30; const MAX_TITLE_SIZE = 30;
...@@ -148,65 +149,12 @@ export abstract class BigValueLayout { ...@@ -148,65 +149,12 @@ export abstract class BigValueLayout {
} }
renderChart(): JSX.Element | null { renderChart(): JSX.Element | null {
const { sparkline } = this.props; const { sparkline, colorMode } = this.props;
if (!sparkline || sparkline.data.length === 0) { if (!sparkline || !sparkline.y) {
return null; return null;
} }
const data = sparkline.data.map(values => {
return { time: values[0], value: values[1], name: 'A' };
});
const scales = {
time: {
type: 'time',
min: sparkline.xMin,
max: sparkline.xMax,
},
value: {
min: sparkline.yMin,
max: sparkline.yMax,
},
};
if (sparkline.xMax && sparkline.xMin) {
// Having the last data point align with the edge of the panel looks good
// So if it's close adjust time.max to the last data point time
const timeDelta = sparkline.xMax - sparkline.xMin;
const lastDataPointTime = data[data.length - 1].time || 0;
const lastTimeDiffFromMax = Math.abs(sparkline.xMax - lastDataPointTime);
// if last data point is just 5% or lower from the edge adjust it
if (lastTimeDiffFromMax / timeDelta < 0.05) {
scales.time.max = lastDataPointTime;
}
}
return (
<Chart
height={this.chartHeight}
width={this.chartWidth}
data={data}
animate={false}
padding={[4, 0, 0, 0]}
scale={scales}
style={this.getChartStyles()}
>
{this.renderGeom()}
</Chart>
);
}
renderGeom(): JSX.Element {
const { colorMode } = this.props;
const lineStyle: any = {
opacity: 1,
fillOpacity: 1,
lineWidth: 2,
};
let fillColor: string; let fillColor: string;
let lineColor: string; let lineColor: string;
...@@ -224,16 +172,28 @@ export abstract class BigValueLayout { ...@@ -224,16 +172,28 @@ export abstract class BigValueLayout {
.toRgbString(); .toRgbString();
} }
lineStyle.stroke = lineColor; // The graph field configuration applied to Y values
const config: FieldConfig<GraphFieldConfig> = {
custom: {
drawStyle: DrawStyle.Line,
lineWidth: 1,
fillColor,
lineColor,
},
};
return ( return (
<> <div style={this.getChartStyles()}>
<Geom type="area" position="time*value" size={0} color={fillColor} style={lineStyle} shape="smooth" /> <Sparkline
<Geom type="line" position="time*value" size={1} color={lineColor} style={lineStyle} shape="smooth" /> height={this.chartHeight}
</> width={this.chartWidth}
sparkline={sparkline}
config={config}
theme={this.props.theme}
/>
</div>
); );
} }
getChartStyles(): CSSProperties { getChartStyles(): CSSProperties {
return { return {
position: 'absolute', position: 'absolute',
......
import React, { PureComponent } from 'react';
import {
compareDataFrameStructures,
DefaultTimeZone,
FieldSparkline,
IndexVector,
DataFrame,
FieldType,
getFieldColorModeForField,
FieldConfig,
} from '@grafana/data';
import { AxisPlacement, DrawStyle, GraphFieldConfig, PointVisibility } from '../uPlot/config';
import { UPlotConfigBuilder } from '../uPlot/config/UPlotConfigBuilder';
import { UPlotChart } from '../uPlot/Plot';
import { Themeable } from '../../types';
export interface Props extends Themeable {
width: number;
height: number;
config?: FieldConfig<GraphFieldConfig>;
sparkline: FieldSparkline;
}
interface State {
data: DataFrame;
configBuilder: UPlotConfigBuilder;
}
const defaultConfig: GraphFieldConfig = {
drawStyle: DrawStyle.Line,
showPoints: PointVisibility.Auto,
axisPlacement: AxisPlacement.Hidden,
};
export class Sparkline extends PureComponent<Props, State> {
constructor(props: Props) {
super(props);
const data = this.prepareData(props);
this.state = {
data,
configBuilder: this.prepareConfig(data, props),
};
}
componentDidUpdate(oldProps: Props) {
if (oldProps.sparkline !== this.props.sparkline) {
const data = this.prepareData(this.props);
if (!compareDataFrameStructures(this.state.data, data)) {
const configBuilder = this.prepareConfig(data, this.props);
this.setState({ data, configBuilder });
} else {
this.setState({ data });
}
}
}
prepareData(props: Props): DataFrame {
const { sparkline } = props;
const length = sparkline.y.values.length;
const yFieldConfig = {
...sparkline.y.config,
...this.props.config,
};
return {
refId: 'sparkline',
fields: [
sparkline.x ?? IndexVector.newField(length),
{
...sparkline.y,
config: yFieldConfig,
},
],
length,
};
}
prepareConfig(data: DataFrame, props: Props) {
const { theme } = this.props;
const builder = new UPlotConfigBuilder();
builder.setCursor({
show: true,
x: false, // no crosshairs
y: false,
});
// X is the first field in the alligned frame
const xField = data.fields[0];
builder.addScale({
scaleKey: 'x',
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.get(0), vals.get(vals.length - 1)];
}
return [0, sparkline.y.values.length - 1];
},
});
builder.addAxis({
scaleKey: 'x',
theme,
placement: AxisPlacement.Hidden,
});
for (let i = 0; i < data.fields.length; i++) {
const field = data.fields[i];
const config = field.config as FieldConfig<GraphFieldConfig>;
const customConfig: GraphFieldConfig = {
...defaultConfig,
...config.custom,
};
if (field === xField || field.type !== FieldType.number) {
continue;
}
const scaleKey = config.unit || '__fixed';
builder.addScale({ scaleKey, min: field.config.min, max: field.config.max });
builder.addAxis({
scaleKey,
theme,
placement: AxisPlacement.Hidden,
});
const colorMode = getFieldColorModeForField(field);
const seriesColor = colorMode.getCalculator(field, theme)(0, 0);
const pointsMode = customConfig.drawStyle === DrawStyle.Points ? PointVisibility.Always : customConfig.showPoints;
builder.addSeries({
scaleKey,
drawStyle: customConfig.drawStyle!,
lineColor: customConfig.lineColor ?? seriesColor,
lineWidth: customConfig.lineWidth,
lineInterpolation: customConfig.lineInterpolation,
showPoints: pointsMode,
pointSize: customConfig.pointSize,
pointColor: customConfig.pointColor ?? seriesColor,
fillOpacity: customConfig.fillOpacity,
fillColor: customConfig.fillColor ?? seriesColor,
});
}
return builder;
}
render() {
const { data, configBuilder } = this.state;
const { width, height, sparkline } = this.props;
return (
<UPlotChart
data={{
frame: data,
isGap: () => true, // any null is a gap
}}
config={configBuilder}
width={width}
height={height}
timeRange={sparkline.timeRange!}
timeZone={DefaultTimeZone}
/>
);
}
}
...@@ -63,7 +63,6 @@ export { Counter } from './Tabs/Counter'; ...@@ -63,7 +63,6 @@ export { Counter } from './Tabs/Counter';
export { export {
BigValue, BigValue,
BigValueColorMode, BigValueColorMode,
BigValueSparkline,
BigValueGraphMode, BigValueGraphMode,
BigValueJustifyMode, BigValueJustifyMode,
BigValueTextMode, BigValueTextMode,
......
...@@ -3,11 +3,13 @@ import { ScaleProps, UPlotScaleBuilder } from './UPlotScaleBuilder'; ...@@ -3,11 +3,13 @@ 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 '../config'; import { AxisPlacement } from '../config';
import { Cursor } from 'uplot';
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 cursor: Cursor | undefined;
hasLeftAxis = false; hasLeftAxis = false;
...@@ -28,6 +30,11 @@ export class UPlotConfigBuilder { ...@@ -28,6 +30,11 @@ export class UPlotConfigBuilder {
this.hasLeftAxis = true; this.hasLeftAxis = true;
} }
if (props.placement === AxisPlacement.Hidden) {
props.show = false;
props.size = 0;
}
this.axes[props.scaleKey] = new UPlotAxisBuilder(props); this.axes[props.scaleKey] = new UPlotAxisBuilder(props);
} }
...@@ -36,6 +43,10 @@ export class UPlotConfigBuilder { ...@@ -36,6 +43,10 @@ export class UPlotConfigBuilder {
return axis?.props.placement! ?? AxisPlacement.Left; return axis?.props.placement! ?? AxisPlacement.Left;
} }
setCursor(cursor?: Cursor) {
this.cursor = cursor;
}
addSeries(props: SeriesProps) { addSeries(props: SeriesProps) {
this.series.push(new UPlotSeriesBuilder(props)); this.series.push(new UPlotSeriesBuilder(props));
} }
...@@ -57,7 +68,9 @@ export class UPlotConfigBuilder { ...@@ -57,7 +68,9 @@ export class UPlotConfigBuilder {
config.scales = this.scales.reduce((acc, s) => { config.scales = this.scales.reduce((acc, s) => {
return { ...acc, ...s.getConfig() }; return { ...acc, ...s.getConfig() };
}, {}); }, {});
if (this.cursor) {
config.cursor = this.cursor;
}
return config; return config;
} }
} }
...@@ -77,21 +77,23 @@ export class UPlotSeriesBuilder extends PlotConfigBuilder<SeriesProps, Series> { ...@@ -77,21 +77,23 @@ export class UPlotSeriesBuilder extends PlotConfigBuilder<SeriesProps, Series> {
pointsConfig.points!.show = true; pointsConfig.points!.show = true;
} }
const areaConfig = let fillConfig: any | undefined;
fillOpacity !== undefined if (fillColor && fillOpacity !== 0) {
? { fillConfig = {
fill: tinycolor(fillColor) fill: fillOpacity
? tinycolor(fillColor)
.setAlpha(fillOpacity) .setAlpha(fillOpacity)
.toRgbString(), .toRgbString()
} : fillColor,
: { fill: undefined }; };
}
return { return {
scale: scaleKey, scale: scaleKey,
spanGaps: spanNulls, spanGaps: spanNulls,
...lineConfig, ...lineConfig,
...pointsConfig, ...pointsConfig,
...areaConfig, ...fillConfig,
}; };
} }
} }
...@@ -3,7 +3,7 @@ import uPlot, { Options, Series, Hooks } from 'uplot'; ...@@ -3,7 +3,7 @@ import uPlot, { Options, Series, Hooks } from 'uplot';
import { DataFrame, TimeRange, TimeZone } from '@grafana/data'; import { DataFrame, TimeRange, TimeZone } from '@grafana/data';
import { UPlotConfigBuilder } from './config/UPlotConfigBuilder'; import { UPlotConfigBuilder } from './config/UPlotConfigBuilder';
export type PlotSeriesConfig = Pick<Options, 'series' | 'scales' | 'axes'>; export type PlotSeriesConfig = Pick<Options, 'series' | 'scales' | 'axes' | 'cursor'>;
export type PlotPlugin = { export type PlotPlugin = {
id: string; id: string;
/** can mutate provided opts as necessary */ /** can mutate provided opts as necessary */
......
...@@ -2,7 +2,6 @@ import React, { PureComponent } from 'react'; ...@@ -2,7 +2,6 @@ import React, { PureComponent } from 'react';
import { import {
BigValue, BigValue,
BigValueGraphMode, BigValueGraphMode,
BigValueSparkline,
DataLinksContextMenu, DataLinksContextMenu,
VizRepeater, VizRepeater,
VizRepeaterRenderValueProps, VizRepeaterRenderValueProps,
...@@ -14,7 +13,6 @@ import { ...@@ -14,7 +13,6 @@ import {
getDisplayValueAlignmentFactors, getDisplayValueAlignmentFactors,
getFieldDisplayValues, getFieldDisplayValues,
PanelProps, PanelProps,
ReducerID,
} from '@grafana/data'; } from '@grafana/data';
import { config } from 'app/core/config'; import { config } from 'app/core/config';
...@@ -29,21 +27,9 @@ export class StatPanel extends PureComponent<PanelProps<StatPanelOptions>> { ...@@ -29,21 +27,9 @@ export class StatPanel extends PureComponent<PanelProps<StatPanelOptions>> {
const { timeRange, options } = this.props; const { timeRange, options } = this.props;
const { value, alignmentFactors, width, height, count } = valueProps; const { value, alignmentFactors, width, height, count } = valueProps;
const { openMenu, targetClassName } = menuProps; const { openMenu, targetClassName } = menuProps;
let sparkline: BigValueSparkline | undefined; let sparkline = value.sparkline;
if (sparkline) {
if (value.sparkline) { sparkline.timeRange = timeRange;
sparkline = {
data: value.sparkline,
xMin: timeRange.from.valueOf(),
xMax: timeRange.to.valueOf(),
yMin: value.field.min,
yMax: value.field.max,
};
const calc = options.reduceOptions.calcs[0];
if (calc === ReducerID.last) {
sparkline.highlightIndex = sparkline.data.length - 1;
}
} }
return ( return (
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment