mirror of
https://github.com/grafana/grafana.git
synced 2025-02-25 18:55:37 -06:00
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:
@@ -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>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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>
|
||||
|
||||
|
||||
@@ -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,
|
||||
});
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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;
|
||||
|
||||
|
||||
Reference in New Issue
Block a user