grafana/public/app/features/explore/Graph.tsx

280 lines
7.0 KiB
TypeScript
Raw Normal View History

2018-04-26 04:58:42 -05:00
import $ from 'jquery';
import React, { PureComponent } from 'react';
import difference from 'lodash/difference';
2018-04-26 04:58:42 -05:00
import 'vendor/flot/jquery.flot';
import 'vendor/flot/jquery.flot.time';
2018-11-05 16:36:58 -06:00
import 'vendor/flot/jquery.flot.selection';
2018-11-06 04:07:12 -06:00
import 'vendor/flot/jquery.flot.stack';
import { TimeZone, AbsoluteTimeRange, GraphLegend, LegendItem, LegendDisplayMode } from '@grafana/ui';
2018-04-26 04:58:42 -05:00
import TimeSeries from 'app/core/time_series2';
2018-09-26 08:18:46 -05:00
const MAX_NUMBER_OF_TIME_SERIES = 20;
2018-04-26 04:58:42 -05:00
// Copied from graph.ts
function time_format(ticks, min, max) {
if (min && max && ticks) {
const range = max - min;
const secPerTick = range / ticks / 1000;
const oneDay = 86400000;
const oneYear = 31536000000;
2018-04-26 04:58:42 -05:00
if (secPerTick <= 45) {
return '%H:%M:%S';
}
if (secPerTick <= 7200 || range <= oneDay) {
return '%H:%M';
}
if (secPerTick <= 80000) {
return '%m/%d %H:%M';
}
if (secPerTick <= 2419200 || range <= oneYear) {
return '%m/%d';
}
return '%Y-%m';
}
return '%H:%M';
}
const FLOT_OPTIONS = {
legend: {
show: false,
},
series: {
lines: {
linewidth: 1,
zero: false,
},
shadowSize: 0,
},
grid: {
minBorderMargin: 0,
markings: [],
backgroundColor: null,
borderWidth: 0,
// hoverable: true,
clickable: true,
color: '#a1a1a1',
margin: { left: 0, right: 0 },
labelMarginX: 0,
},
2018-11-05 16:36:58 -06:00
selection: {
mode: 'x',
color: '#666',
},
2018-04-26 04:58:42 -05:00
// crosshair: {
// mode: 'x',
// },
};
interface GraphProps {
data: any[];
height?: number;
width?: number;
id?: string;
range: AbsoluteTimeRange;
timeZone: TimeZone;
split?: boolean;
userOptions?: any;
onChangeTime?: (range: AbsoluteTimeRange) => void;
onToggleSeries?: (alias: string, hiddenSeries: string[]) => void;
}
interface GraphState {
/**
* Type parameter refers to the `alias` property of a `TimeSeries`.
* Consequently, all series sharing the same alias will share visibility state.
*/
hiddenSeries: string[];
showAllTimeSeries: boolean;
}
export class Graph extends PureComponent<GraphProps, GraphState> {
2018-11-05 16:36:58 -06:00
$el: any;
dynamicOptions = null;
2018-11-05 16:36:58 -06:00
state = {
hiddenSeries: [],
showAllTimeSeries: false,
};
getGraphData(): TimeSeries[] {
const { data } = this.props;
2018-09-26 08:18:46 -05:00
return this.state.showAllTimeSeries ? data : data.slice(0, MAX_NUMBER_OF_TIME_SERIES);
}
2018-04-26 04:58:42 -05:00
componentDidMount() {
this.draw();
2018-11-05 16:36:58 -06:00
this.$el = $(`#${this.props.id}`);
this.$el.bind('plotselected', this.onPlotSelected);
2018-04-26 04:58:42 -05:00
}
componentDidUpdate(prevProps: GraphProps, prevState: GraphState) {
2018-04-26 04:58:42 -05:00
if (
prevProps.data !== this.props.data ||
prevProps.range !== this.props.range ||
prevProps.split !== this.props.split ||
prevProps.height !== this.props.height ||
prevProps.width !== this.props.width ||
prevState.hiddenSeries !== this.state.hiddenSeries
2018-04-26 04:58:42 -05:00
) {
this.draw();
}
}
2018-11-05 16:36:58 -06:00
componentWillUnmount() {
this.$el.unbind('plotselected', this.onPlotSelected);
}
TimePicker: New time picker dropdown & custom range UI (#16811) * feat: Add new picker to DashNavTimeControls * chore: noImplicitAny limit reached * chore: noImplicityAny fix * chore: Add momentUtc helper to avoid the isUtc conditionals * chore: Move getRaw from Explore's time picker to grafana/ui utils and rename to getRawRange * feat: Use helper functions to convert utc to browser time * fix: Dont Select current value when pressing tab when using Time Picker * fix: Add tabIndex to time range inputs so tab works smoothly and prevent mouseDown event to propagate to react-select * fix: Add spacing to custom range labels * fix: Updated snapshot * fix: Re-adding getRaw() temporary to fix the build * fix: Disable scroll event in Popper when we're using the TimePicker so the popup wont "follow" the menu * fix: Move all "Last xxxx" quick ranges to the menu and show a "UTC" text when applicable * fix: Add zoom functionality * feat: Add logic to mark selected option as active * fix: Add tooltip to zoom button * fix: lint fix after rebase * chore: Remove old time picker from DashNav * TimePicker: minor design update * chore: Move all time picker quick ranges to the menu * fix: Remove the popover border-right, since the quick ranges are gone * chore: Remove function not in use * Fix: Close time picker on resize event * Fix: Remove border bottom * Fix: Use fa icons on prev/next arrows * Fix: Pass ref from TimePicker to TimePickerOptionGroup so the popover will align as it should * Fix: time picker ui adjustments to get better touch area on buttons * Fix: Dont increase line height on large screens * TimePicker: style updates * Fix: Add more prominent colors for selected dates and fade out dates in previous/next month * TimePicker: style updates2 * TimePicker: Big refactorings and style changes * Removed use of Popper not sure we need that here? * Made active selected item in the list have the "selected" checkmark * Changed design of popover * Changed design of and implementation of the Custom selection in the dropdown it did not feel like a item you could select like the rest now the list is just a normal list * TimePicker: Refactoring & style changes * TimePicker: use same date format everywhere * TimePicker: Calendar style updates * TimePicker: fixed unit test * fixed unit test * TimeZone: refactoring time zone type * TimePicker: refactoring * TimePicker: finally to UTC to work * TimePicker: better way to handle calendar utc dates * TimePicker: Fixed tooltip issues * Updated snapshot * TimePicker: moved tooltip from DashNavControls into TimePicker
2019-06-24 07:39:59 -05:00
onPlotSelected = (event: JQueryEventObject, ranges) => {
const { onChangeTime } = this.props;
if (onChangeTime) {
this.props.onChangeTime({
from: ranges.xaxis.from,
to: ranges.xaxis.to,
});
2018-11-05 16:36:58 -06:00
}
};
getDynamicOptions() {
const { range, width, timeZone } = this.props;
const ticks = (width || 0) / 100;
const min = range.from;
const max = range.to;
return {
xaxis: {
mode: 'time',
min: min,
max: max,
label: 'Datetime',
ticks: ticks,
TimePicker: New time picker dropdown & custom range UI (#16811) * feat: Add new picker to DashNavTimeControls * chore: noImplicitAny limit reached * chore: noImplicityAny fix * chore: Add momentUtc helper to avoid the isUtc conditionals * chore: Move getRaw from Explore's time picker to grafana/ui utils and rename to getRawRange * feat: Use helper functions to convert utc to browser time * fix: Dont Select current value when pressing tab when using Time Picker * fix: Add tabIndex to time range inputs so tab works smoothly and prevent mouseDown event to propagate to react-select * fix: Add spacing to custom range labels * fix: Updated snapshot * fix: Re-adding getRaw() temporary to fix the build * fix: Disable scroll event in Popper when we're using the TimePicker so the popup wont "follow" the menu * fix: Move all "Last xxxx" quick ranges to the menu and show a "UTC" text when applicable * fix: Add zoom functionality * feat: Add logic to mark selected option as active * fix: Add tooltip to zoom button * fix: lint fix after rebase * chore: Remove old time picker from DashNav * TimePicker: minor design update * chore: Move all time picker quick ranges to the menu * fix: Remove the popover border-right, since the quick ranges are gone * chore: Remove function not in use * Fix: Close time picker on resize event * Fix: Remove border bottom * Fix: Use fa icons on prev/next arrows * Fix: Pass ref from TimePicker to TimePickerOptionGroup so the popover will align as it should * Fix: time picker ui adjustments to get better touch area on buttons * Fix: Dont increase line height on large screens * TimePicker: style updates * Fix: Add more prominent colors for selected dates and fade out dates in previous/next month * TimePicker: style updates2 * TimePicker: Big refactorings and style changes * Removed use of Popper not sure we need that here? * Made active selected item in the list have the "selected" checkmark * Changed design of popover * Changed design of and implementation of the Custom selection in the dropdown it did not feel like a item you could select like the rest now the list is just a normal list * TimePicker: Refactoring & style changes * TimePicker: use same date format everywhere * TimePicker: Calendar style updates * TimePicker: fixed unit test * fixed unit test * TimeZone: refactoring time zone type * TimePicker: refactoring * TimePicker: finally to UTC to work * TimePicker: better way to handle calendar utc dates * TimePicker: Fixed tooltip issues * Updated snapshot * TimePicker: moved tooltip from DashNavControls into TimePicker
2019-06-24 07:39:59 -05:00
timezone: timeZone,
timeformat: time_format(ticks, min, max),
},
};
}
onShowAllTimeSeries = () => {
this.setState(
{
showAllTimeSeries: true,
},
this.draw
);
};
2018-04-26 04:58:42 -05:00
draw() {
const { userOptions = {} } = this.props;
const { hiddenSeries } = this.state;
const data = this.getGraphData();
const $el = $(`#${this.props.id}`);
let series = [{ data: [[0, 0]] }];
if (data && data.length > 0) {
series = data
.filter((ts: TimeSeries) => hiddenSeries.indexOf(ts.alias) === -1)
.map((ts: TimeSeries) => ({
color: ts.color,
label: ts.label,
data: ts.getFlotPairs('null'),
}));
2018-04-26 04:58:42 -05:00
}
this.dynamicOptions = this.getDynamicOptions();
2018-04-26 04:58:42 -05:00
const options = {
...FLOT_OPTIONS,
...this.dynamicOptions,
...userOptions,
2018-04-26 04:58:42 -05:00
};
2018-04-26 04:58:42 -05:00
$.plot($el, series, options);
}
getLegendItems = (): LegendItem[] => {
const { hiddenSeries } = this.state;
const data = this.getGraphData();
return data.map(series => {
return {
label: series.alias,
color: series.color,
isVisible: hiddenSeries.indexOf(series.alias) === -1,
yAxis: 1,
};
});
};
onSeriesToggle(label: string, event: React.MouseEvent<HTMLElement>) {
// This implementation is more or less a copy of GraphPanel's logic.
// TODO: we need to use Graph's panel controller or split it into smaller
// controllers to remove code duplication. Right now we cant easily use that, since Explore
// is not using SeriesData for graph yet
const exclusive = event.ctrlKey || event.metaKey || event.shiftKey;
this.setState((state, props) => {
const { data, onToggleSeries } = props;
let nextHiddenSeries: string[] = [];
if (exclusive) {
// Toggling series with key makes the series itself to toggle
if (state.hiddenSeries.indexOf(label) > -1) {
nextHiddenSeries = state.hiddenSeries.filter(series => series !== label);
} else {
nextHiddenSeries = state.hiddenSeries.concat([label]);
}
} else {
// Toggling series with out key toggles all the series but the clicked one
const allSeriesLabels = data.map(series => series.label);
if (state.hiddenSeries.length + 1 === allSeriesLabels.length) {
nextHiddenSeries = [];
} else {
nextHiddenSeries = difference(allSeriesLabels, [label]);
}
}
if (onToggleSeries) {
onToggleSeries(label, nextHiddenSeries);
}
return {
hiddenSeries: nextHiddenSeries,
};
});
}
render() {
const { height = 100, id = 'graph' } = this.props;
return (
<>
{this.props.data && this.props.data.length > MAX_NUMBER_OF_TIME_SERIES && !this.state.showAllTimeSeries && (
<div className="time-series-disclaimer">
<i className="fa fa-fw fa-warning disclaimer-icon" />
{`Showing only ${MAX_NUMBER_OF_TIME_SERIES} time series. `}
<span className="show-all-time-series" onClick={this.onShowAllTimeSeries}>{`Show all ${
this.props.data.length
}`}</span>
</div>
)}
<div id={id} className="explore-graph" style={{ height }} />
<GraphLegend
items={this.getLegendItems()}
displayMode={LegendDisplayMode.List}
placement="under"
onLabelClick={(item, event) => {
this.onSeriesToggle(item.label, event);
}}
/>
</>
);
2018-04-26 04:58:42 -05:00
}
}
export default Graph;