Explore: Replaces TimeSeries with GraphSeriesXY (#18475)

* Wip: Compiles and runs

* WIP: Logs Graph partially working

* Refactor: Adds GraphSeriesToggler

* Refactor: Adds tickDecimals to YAxis

* Refactor: Adds TimeZone and PlotSelection to Graph

* Refactor: Makes the graphResult work in Explore

* Refactor: Adds ExploreGraphPanel that is used by Logs and Explore

* Fix: Fixes strange behaviour with ExploreMode not beeing changed

* Fix: Adds onSelectionChanged to GraphWithLegend

* Refactor: Cleans up unused comments

* ExploreGraph: Disable colorpicker
This commit is contained in:
Hugo Häggmark
2019-08-13 07:32:43 +02:00
committed by GitHub
parent 8fd153edb7
commit 4b3440325e
30 changed files with 550 additions and 997 deletions

View File

@@ -4,16 +4,20 @@ import React, { PureComponent } from 'react';
import uniqBy from 'lodash/uniqBy';
// Types
import { TimeRange, GraphSeriesXY } from '@grafana/data';
import { TimeRange, GraphSeriesXY, AbsoluteTimeRange, TimeZone, DefaultTimeZone } from '@grafana/data';
export interface GraphProps {
series: GraphSeriesXY[];
timeRange: TimeRange; // NOTE: we should aim to make `time` a property of the axis, not force it for all graphs
timeZone: TimeZone; // NOTE: we should aim to make `time` a property of the axis, not force it for all graphs
showLines?: boolean;
showPoints?: boolean;
showBars?: boolean;
width: number;
height: number;
isStacked?: boolean;
lineWidth?: number;
onSelectionChanged?: (range: AbsoluteTimeRange) => void;
}
export class Graph extends PureComponent<GraphProps> {
@@ -21,9 +25,12 @@ export class Graph extends PureComponent<GraphProps> {
showLines: true,
showPoints: false,
showBars: false,
isStacked: false,
lineWidth: 1,
};
element: HTMLElement | null = null;
$element: any;
componentDidUpdate() {
this.draw();
@@ -31,14 +38,32 @@ export class Graph extends PureComponent<GraphProps> {
componentDidMount() {
this.draw();
if (this.element) {
this.$element = $(this.element);
this.$element.bind('plotselected', this.onPlotSelected);
}
}
componentWillUnmount() {
this.$element.unbind('plotselected', this.onPlotSelected);
}
onPlotSelected = (event: JQueryEventObject, ranges: { xaxis: { from: number; to: number } }) => {
const { onSelectionChanged } = this.props;
if (onSelectionChanged) {
onSelectionChanged({
from: ranges.xaxis.from,
to: ranges.xaxis.to,
});
}
};
draw() {
if (this.element === null) {
return;
}
const { width, series, timeRange, showLines, showBars, showPoints } = this.props;
const { width, series, timeRange, showLines, showBars, showPoints, isStacked, lineWidth, timeZone } = this.props;
if (!width) {
return;
@@ -49,10 +74,16 @@ export class Graph extends PureComponent<GraphProps> {
const max = timeRange.to.valueOf();
const yaxes = uniqBy(
series.map(s => {
const index = s.yAxis ? s.yAxis.index : 1;
const min = s.yAxis && !isNaN(s.yAxis.min as number) ? s.yAxis.min : null;
const tickDecimals = s.yAxis && !isNaN(s.yAxis.tickDecimals as number) ? s.yAxis.tickDecimals : null;
return {
show: true,
index: s.yAxis,
position: s.yAxis === 1 ? 'left' : 'right',
index,
position: index === 1 ? 'left' : 'right',
min,
tickDecimals,
};
}),
yAxisConfig => yAxisConfig.index
@@ -62,9 +93,10 @@ export class Graph extends PureComponent<GraphProps> {
show: false,
},
series: {
stack: isStacked,
lines: {
show: showLines,
linewidth: 1,
linewidth: lineWidth,
zero: false,
},
points: {
@@ -78,7 +110,7 @@ export class Graph extends PureComponent<GraphProps> {
fill: 1,
barWidth: 1,
zero: false,
lineWidth: 0,
lineWidth: lineWidth,
},
shadowSize: 0,
},
@@ -89,6 +121,7 @@ export class Graph extends PureComponent<GraphProps> {
label: 'Datetime',
ticks: ticks,
timeformat: timeFormat(ticks, min, max),
timezone: timeZone ? timeZone : DefaultTimeZone,
},
yaxes,
grid: {
@@ -102,6 +135,10 @@ export class Graph extends PureComponent<GraphProps> {
margin: { left: 0, right: 0 },
labelMarginX: 0,
},
selection: {
mode: 'x',
color: '#666',
},
};
try {
@@ -113,9 +150,10 @@ export class Graph extends PureComponent<GraphProps> {
}
render() {
const { height } = this.props;
return (
<div className="graph-panel">
<div className="graph-panel__chart" ref={e => (this.element = e)} />
<div className="graph-panel__chart" ref={e => (this.element = e)} style={{ height }} />
</div>
);
}

View File

@@ -74,7 +74,7 @@ GraphWithLegendStories.add('default', () => {
.map(s => s.trim())
.indexOf(s.label.split('-')[0]) > -1
) {
s.yAxis = 2;
s.yAxis = { index: 2 };
}
return s;

View File

@@ -2,7 +2,7 @@
import _ from 'lodash';
import React from 'react';
import { css } from 'emotion';
import { GraphSeriesValue } from '@grafana/data';
import { GraphSeriesValue, AbsoluteTimeRange } from '@grafana/data';
import { Graph, GraphProps } from './Graph';
import { LegendRenderOptions, LegendItem, LegendDisplayMode } from '../Legend/Legend';
@@ -18,10 +18,11 @@ export interface GraphWithLegendProps extends GraphProps, LegendRenderOptions {
displayMode: LegendDisplayMode;
sortLegendBy?: string;
sortLegendDesc?: boolean;
onSeriesColorChange: SeriesColorChangeHandler;
onSeriesColorChange?: SeriesColorChangeHandler;
onSeriesAxisToggle?: SeriesAxisToggleHandler;
onSeriesToggle?: (label: string, event: React.MouseEvent<HTMLElement>) => void;
onToggleSort: (sortBy: string) => void;
onSelectionChanged?: (range: AbsoluteTimeRange) => void;
}
const getGraphWithLegendStyles = ({ placement }: GraphWithLegendProps) => ({
@@ -67,6 +68,10 @@ export const GraphWithLegend: React.FunctionComponent<GraphWithLegendProps> = (p
onToggleSort,
hideEmpty,
hideZero,
isStacked,
lineWidth,
onSelectionChanged,
timeZone,
} = props;
const { graphContainer, wrapper, legendContainer } = getGraphWithLegendStyles(props);
@@ -78,7 +83,7 @@ export const GraphWithLegend: React.FunctionComponent<GraphWithLegendProps> = (p
label: s.label,
color: s.color,
isVisible: s.isVisible,
yAxis: s.yAxis,
yAxis: s.yAxis.index,
displayValues: s.info || [],
},
]);
@@ -90,12 +95,16 @@ export const GraphWithLegend: React.FunctionComponent<GraphWithLegendProps> = (p
<Graph
series={series.filter(s => !!s.isVisible)}
timeRange={timeRange}
timeZone={timeZone}
showLines={showLines}
showPoints={showPoints}
showBars={showBars}
width={width}
height={height}
key={isLegendVisible ? 'legend-visible' : 'legend-invisible'}
isStacked={isStacked}
lineWidth={lineWidth}
onSelectionChanged={onSelectionChanged}
/>
</div>

View File

@@ -1,7 +1,6 @@
import { GraphWithLegendProps } from './GraphWithLegend';
import { LegendDisplayMode } from '../Legend/Legend';
import { dateTime } from '@grafana/data';
// import { LegendList } from '../Legend/LegendList';
import { dateTime, DefaultTimeZone } from '@grafana/data';
export const mockGraphWithLegendData = ({
displayMode,
@@ -1099,7 +1098,9 @@ export const mockGraphWithLegendData = ({
{ title: 'max', text: '18.42', numeric: 18.427101844163694 },
],
isVisible: true,
yAxis: 1,
yAxis: {
index: 1,
},
},
{
label: 'B-series',
@@ -2191,7 +2192,9 @@ export const mockGraphWithLegendData = ({
{ title: 'max', text: '18.42', numeric: 18.427101844163694 },
],
isVisible: true,
yAxis: 1,
yAxis: {
index: 1,
},
},
{
label: 'C-series',
@@ -3283,7 +3286,9 @@ export const mockGraphWithLegendData = ({
{ title: 'max', text: '18.42', numeric: 18.427101844163694 },
],
isVisible: true,
yAxis: 1,
yAxis: {
index: 1,
},
},
],
timeRange: {
@@ -3313,4 +3318,5 @@ export const mockGraphWithLegendData = ({
},
onToggleSort: () => {},
displayMode: displayMode || LegendDisplayMode.List,
timeZone: DefaultTimeZone,
});

View File

@@ -1,5 +1,5 @@
import { ComponentClass, ComponentType } from 'react';
import { LoadingState, DataFrame, TimeRange } from '@grafana/data';
import { LoadingState, DataFrame, TimeRange, TimeZone } from '@grafana/data';
import { ScopedVars, DataQueryRequest, DataQueryError, LegacyResponseData } from './datasource';
import { PluginMeta, GrafanaPlugin } from './plugin';
@@ -27,6 +27,7 @@ export interface PanelProps<T = any> {
// TODO: annotation?: PanelData;
timeRange: TimeRange;
timeZone: TimeZone;
options: T;
onOptionsChange: (options: T) => void;
renderCounter: number;

View File

@@ -169,7 +169,7 @@ export const getFieldDisplayValues = (options: GetFieldDisplayValuesOptions): Fi
timeColumn < 0
? undefined
: getFlotPairs({
series,
rows: series.rows,
xIndex: timeColumn,
yIndex: i,
nullValueMode: NullValueMode.Null,

View File

@@ -1,12 +1,10 @@
import { getFlotPairs } from './flotPairs';
describe('getFlotPairs', () => {
const series = {
fields: [],
rows: [[1, 100, 'a'], [2, 200, 'b'], [3, 300, 'c']],
};
const rows = [[1, 100, 'a'], [2, 200, 'b'], [3, 300, 'c']];
it('should get X and y', () => {
const pairs = getFlotPairs({ series, xIndex: 0, yIndex: 1 });
const pairs = getFlotPairs({ rows, xIndex: 0, yIndex: 1 });
expect(pairs.length).toEqual(3);
expect(pairs[0].length).toEqual(2);
@@ -15,7 +13,7 @@ describe('getFlotPairs', () => {
});
it('should work with strings', () => {
const pairs = getFlotPairs({ series, xIndex: 0, yIndex: 2 });
const pairs = getFlotPairs({ rows, xIndex: 0, yIndex: 2 });
expect(pairs.length).toEqual(3);
expect(pairs[0].length).toEqual(2);

View File

@@ -1,16 +1,14 @@
// Types
import { NullValueMode, DataFrame, GraphSeriesValue } from '@grafana/data';
import { NullValueMode, GraphSeriesValue } from '@grafana/data';
export interface FlotPairsOptions {
series: DataFrame;
rows: any[][];
xIndex: number;
yIndex: number;
nullValueMode?: NullValueMode;
}
export function getFlotPairs({ series, xIndex, yIndex, nullValueMode }: FlotPairsOptions): GraphSeriesValue[][] {
const rows = series.rows;
export function getFlotPairs({ rows, xIndex, yIndex, nullValueMode }: FlotPairsOptions): GraphSeriesValue[][] {
const ignoreNulls = nullValueMode === NullValueMode.Ignore;
const nullAsZero = nullValueMode === NullValueMode.AsZero;