2018-04-26 04:58:42 -05:00
|
|
|
import $ from 'jquery';
|
2018-10-05 04:39:00 -05:00
|
|
|
import React, { PureComponent } from 'react';
|
2018-04-30 10:25:25 -05:00
|
|
|
import moment from 'moment';
|
2018-10-05 04:39:00 -05:00
|
|
|
import { withSize } from 'react-sizeme';
|
2018-04-26 04:58:42 -05:00
|
|
|
|
2018-05-01 06:27:25 -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';
|
2018-10-22 10:51:42 -05:00
|
|
|
|
2018-10-26 11:16:00 -05:00
|
|
|
import { RawTimeRange } from 'app/types/series';
|
2018-04-30 10:25:25 -05:00
|
|
|
import * as dateMath from 'app/core/utils/datemath';
|
2018-04-26 04:58:42 -05:00
|
|
|
import TimeSeries from 'app/core/time_series2';
|
|
|
|
|
2018-05-01 06:27:25 -05:00
|
|
|
import Legend from './Legend';
|
2018-11-15 07:56:52 -06:00
|
|
|
import { equal, intersect } from './utils/set';
|
2018-04-26 04:58:42 -05:00
|
|
|
|
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) {
|
2018-08-26 14:52:57 -05:00
|
|
|
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',
|
|
|
|
// },
|
|
|
|
};
|
|
|
|
|
2018-10-05 04:39:00 -05:00
|
|
|
interface GraphProps {
|
|
|
|
data: any[];
|
|
|
|
height?: string; // e.g., '200px'
|
|
|
|
id?: string;
|
2018-10-26 11:16:00 -05:00
|
|
|
range: RawTimeRange;
|
2018-10-05 04:39:00 -05:00
|
|
|
split?: boolean;
|
|
|
|
size?: { width: number; height: number };
|
2018-11-02 02:25:36 -05:00
|
|
|
userOptions?: any;
|
2018-11-05 16:36:58 -06:00
|
|
|
onChangeTime?: (range: RawTimeRange) => void;
|
2018-11-23 09:29:55 -06:00
|
|
|
onToggleSeries?: (alias: string, hiddenSeries: Set<string>) => void;
|
2018-10-05 04:39:00 -05:00
|
|
|
}
|
|
|
|
|
|
|
|
interface GraphState {
|
2018-11-15 07:56:52 -06:00
|
|
|
/**
|
|
|
|
* Type parameter refers to the `alias` property of a `TimeSeries`.
|
|
|
|
* Consequently, all series sharing the same alias will share visibility state.
|
|
|
|
*/
|
|
|
|
hiddenSeries: Set<string>;
|
2018-10-05 04:39:00 -05:00
|
|
|
showAllTimeSeries: boolean;
|
|
|
|
}
|
|
|
|
|
|
|
|
export class Graph extends PureComponent<GraphProps, GraphState> {
|
2018-11-05 16:36:58 -06:00
|
|
|
$el: any;
|
2018-11-15 07:56:52 -06:00
|
|
|
dynamicOptions = null;
|
2018-11-05 16:36:58 -06:00
|
|
|
|
2018-09-24 09:35:24 -05:00
|
|
|
state = {
|
2018-11-15 07:56:52 -06:00
|
|
|
hiddenSeries: new Set(),
|
2018-09-24 09:35:24 -05:00
|
|
|
showAllTimeSeries: false,
|
|
|
|
};
|
|
|
|
|
|
|
|
getGraphData() {
|
|
|
|
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-09-24 09:35:24 -05:00
|
|
|
}
|
|
|
|
|
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
|
|
|
}
|
|
|
|
|
2018-11-15 07:56:52 -06:00
|
|
|
componentDidUpdate(prevProps: GraphProps, prevState: GraphState) {
|
2018-04-26 04:58:42 -05:00
|
|
|
if (
|
|
|
|
prevProps.data !== this.props.data ||
|
2018-10-22 10:51:42 -05:00
|
|
|
prevProps.range !== this.props.range ||
|
2018-05-15 10:07:38 -05:00
|
|
|
prevProps.split !== this.props.split ||
|
2018-10-05 04:39:00 -05:00
|
|
|
prevProps.height !== this.props.height ||
|
2018-11-15 07:56:52 -06:00
|
|
|
(prevProps.size && prevProps.size.width !== this.props.size.width) ||
|
|
|
|
!equal(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);
|
|
|
|
}
|
|
|
|
|
|
|
|
onPlotSelected = (event, ranges) => {
|
|
|
|
if (this.props.onChangeTime) {
|
|
|
|
const range = {
|
|
|
|
from: moment(ranges.xaxis.from),
|
|
|
|
to: moment(ranges.xaxis.to),
|
|
|
|
};
|
|
|
|
this.props.onChangeTime(range);
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
2018-11-15 07:56:52 -06:00
|
|
|
getDynamicOptions() {
|
|
|
|
const { range, size } = this.props;
|
|
|
|
const ticks = (size.width || 0) / 100;
|
|
|
|
let { from, to } = range;
|
|
|
|
if (!moment.isMoment(from)) {
|
|
|
|
from = dateMath.parse(from, false);
|
|
|
|
}
|
|
|
|
if (!moment.isMoment(to)) {
|
|
|
|
to = dateMath.parse(to, true);
|
|
|
|
}
|
|
|
|
const min = from.valueOf();
|
|
|
|
const max = to.valueOf();
|
|
|
|
return {
|
|
|
|
xaxis: {
|
|
|
|
mode: 'time',
|
|
|
|
min: min,
|
|
|
|
max: max,
|
|
|
|
label: 'Datetime',
|
|
|
|
ticks: ticks,
|
|
|
|
timezone: 'browser',
|
|
|
|
timeformat: time_format(ticks, min, max),
|
|
|
|
},
|
|
|
|
};
|
|
|
|
}
|
|
|
|
|
2018-09-24 09:35:24 -05:00
|
|
|
onShowAllTimeSeries = () => {
|
|
|
|
this.setState(
|
|
|
|
{
|
|
|
|
showAllTimeSeries: true,
|
|
|
|
},
|
|
|
|
this.draw
|
|
|
|
);
|
|
|
|
};
|
|
|
|
|
2018-11-15 07:56:52 -06:00
|
|
|
onToggleSeries = (series: TimeSeries, exclusive: boolean) => {
|
|
|
|
this.setState((state, props) => {
|
2018-11-23 09:29:55 -06:00
|
|
|
const { data, onToggleSeries } = props;
|
2018-11-15 07:56:52 -06:00
|
|
|
const { hiddenSeries } = state;
|
2018-11-23 09:29:55 -06:00
|
|
|
|
2018-11-15 07:56:52 -06:00
|
|
|
// Deduplicate series as visibility tracks the alias property
|
|
|
|
const oneSeriesVisible = hiddenSeries.size === new Set(data.map(d => d.alias)).size - 1;
|
2018-11-23 09:29:55 -06:00
|
|
|
|
|
|
|
let nextHiddenSeries = new Set();
|
2018-11-15 07:56:52 -06:00
|
|
|
if (exclusive) {
|
2018-11-23 09:29:55 -06:00
|
|
|
if (hiddenSeries.has(series.alias) || !oneSeriesVisible) {
|
|
|
|
nextHiddenSeries = new Set(data.filter(d => d.alias !== series.alias).map(d => d.alias));
|
|
|
|
}
|
2018-11-15 07:56:52 -06:00
|
|
|
} else {
|
2018-11-23 09:29:55 -06:00
|
|
|
// Prune hidden series no longer part of those available from the most recent query
|
|
|
|
const availableSeries = new Set(data.map(d => d.alias));
|
|
|
|
nextHiddenSeries = intersect(new Set(hiddenSeries), availableSeries);
|
|
|
|
if (nextHiddenSeries.has(series.alias)) {
|
|
|
|
nextHiddenSeries.delete(series.alias);
|
|
|
|
} else {
|
|
|
|
nextHiddenSeries.add(series.alias);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if (onToggleSeries) {
|
|
|
|
onToggleSeries(series.alias, nextHiddenSeries);
|
2018-11-15 07:56:52 -06:00
|
|
|
}
|
|
|
|
return {
|
|
|
|
hiddenSeries: nextHiddenSeries,
|
|
|
|
};
|
|
|
|
}, this.draw);
|
|
|
|
};
|
|
|
|
|
2018-04-26 04:58:42 -05:00
|
|
|
draw() {
|
2018-11-15 07:56:52 -06:00
|
|
|
const { userOptions = {} } = this.props;
|
|
|
|
const { hiddenSeries } = this.state;
|
2018-09-24 09:35:24 -05:00
|
|
|
const data = this.getGraphData();
|
|
|
|
|
2018-08-04 04:47:04 -05:00
|
|
|
const $el = $(`#${this.props.id}`);
|
2018-10-22 10:51:42 -05:00
|
|
|
let series = [{ data: [[0, 0]] }];
|
|
|
|
|
|
|
|
if (data && data.length > 0) {
|
2018-11-15 07:56:52 -06:00
|
|
|
series = data.filter((ts: TimeSeries) => !hiddenSeries.has(ts.alias)).map((ts: TimeSeries) => ({
|
2018-10-22 10:51:42 -05:00
|
|
|
color: ts.color,
|
|
|
|
label: ts.label,
|
|
|
|
data: ts.getFlotPairs('null'),
|
|
|
|
}));
|
2018-04-26 04:58:42 -05:00
|
|
|
}
|
|
|
|
|
2018-11-15 07:56:52 -06:00
|
|
|
this.dynamicOptions = this.getDynamicOptions();
|
|
|
|
|
2018-04-26 04:58:42 -05:00
|
|
|
const options = {
|
|
|
|
...FLOT_OPTIONS,
|
2018-11-15 07:56:52 -06:00
|
|
|
...this.dynamicOptions,
|
2018-11-02 02:25:36 -05:00
|
|
|
...userOptions,
|
2018-04-26 04:58:42 -05:00
|
|
|
};
|
2018-11-15 07:56:52 -06:00
|
|
|
|
2018-04-26 04:58:42 -05:00
|
|
|
$.plot($el, series, options);
|
|
|
|
}
|
|
|
|
|
|
|
|
render() {
|
2018-11-16 12:21:13 -06:00
|
|
|
const { height = '100px', id = 'graph' } = this.props;
|
2018-11-15 07:56:52 -06:00
|
|
|
const { hiddenSeries } = this.state;
|
2018-09-24 09:35:24 -05:00
|
|
|
const data = this.getGraphData();
|
|
|
|
|
2018-05-01 06:27:25 -05:00
|
|
|
return (
|
2018-11-16 12:21:13 -06:00
|
|
|
<>
|
2018-10-22 10:51:42 -05:00
|
|
|
{this.props.data &&
|
|
|
|
this.props.data.length > MAX_NUMBER_OF_TIME_SERIES &&
|
2018-09-24 09:35:24 -05:00
|
|
|
!this.state.showAllTimeSeries && (
|
|
|
|
<div className="time-series-disclaimer">
|
|
|
|
<i className="fa fa-fw fa-warning disclaimer-icon" />
|
2018-09-28 05:58:01 -05:00
|
|
|
{`Showing only ${MAX_NUMBER_OF_TIME_SERIES} time series. `}
|
2018-09-24 09:35:24 -05:00
|
|
|
<span className="show-all-time-series" onClick={this.onShowAllTimeSeries}>{`Show all ${
|
|
|
|
this.props.data.length
|
|
|
|
}`}</span>
|
|
|
|
</div>
|
|
|
|
)}
|
2018-10-24 04:25:49 -05:00
|
|
|
<div id={id} className="explore-graph" style={{ height }} />
|
2018-11-15 07:56:52 -06:00
|
|
|
<Legend data={data} hiddenSeries={hiddenSeries} onToggleSeries={this.onToggleSeries} />
|
2018-11-16 12:21:13 -06:00
|
|
|
</>
|
2018-05-01 06:27:25 -05:00
|
|
|
);
|
2018-04-26 04:58:42 -05:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2018-10-05 04:39:00 -05:00
|
|
|
export default withSize()(Graph);
|