2019-11-27 07:51:40 -08:00
|
|
|
import $ from 'jquery';
|
|
|
|
import React, { PureComponent } from 'react';
|
|
|
|
import ReactResizeDetector from 'react-resize-detector';
|
|
|
|
|
2019-12-03 11:20:45 -08:00
|
|
|
import { Legend } from './Legend';
|
2019-11-27 07:51:40 -08:00
|
|
|
import { Metric, QueryParams } from '../types/types';
|
|
|
|
import { isPresent } from '../utils/func';
|
|
|
|
import { normalizeData, getOptions, toHoverColor } from './GraphHelpers';
|
|
|
|
|
|
|
|
require('flot');
|
|
|
|
require('flot/source/jquery.flot.crosshair');
|
|
|
|
require('flot/source/jquery.flot.legend');
|
|
|
|
require('flot/source/jquery.flot.time');
|
|
|
|
require('flot/source/jquery.canvaswrapper');
|
|
|
|
require('jquery.flot.tooltip');
|
|
|
|
|
|
|
|
export interface GraphProps {
|
|
|
|
data: {
|
|
|
|
resultType: string;
|
|
|
|
result: Array<{ metric: Metric; values: [number, string][] }>;
|
|
|
|
};
|
|
|
|
stacked: boolean;
|
|
|
|
queryParams: QueryParams | null;
|
|
|
|
}
|
|
|
|
|
|
|
|
export interface GraphSeries {
|
|
|
|
labels: { [key: string]: string };
|
|
|
|
color: string;
|
|
|
|
data: (number | null)[][]; // [x,y][]
|
|
|
|
index: number;
|
|
|
|
}
|
|
|
|
|
|
|
|
interface GraphState {
|
|
|
|
chartData: GraphSeries[];
|
|
|
|
}
|
|
|
|
|
|
|
|
class Graph extends PureComponent<GraphProps, GraphState> {
|
|
|
|
private chartRef = React.createRef<HTMLDivElement>();
|
|
|
|
private $chart?: jquery.flot.plot;
|
|
|
|
private rafID = 0;
|
2019-12-03 11:20:45 -08:00
|
|
|
private selectedSeriesIndexes: number[] = [];
|
2019-11-27 07:51:40 -08:00
|
|
|
|
|
|
|
state = {
|
|
|
|
chartData: normalizeData(this.props),
|
|
|
|
};
|
|
|
|
|
|
|
|
componentDidUpdate(prevProps: GraphProps) {
|
|
|
|
const { data, stacked } = this.props;
|
2019-12-03 11:20:45 -08:00
|
|
|
if (prevProps.data !== data) {
|
|
|
|
this.selectedSeriesIndexes = [];
|
|
|
|
this.setState({ chartData: normalizeData(this.props) }, this.plot);
|
|
|
|
} else if (prevProps.stacked !== stacked) {
|
|
|
|
this.setState({ chartData: normalizeData(this.props) }, () => {
|
|
|
|
if (this.selectedSeriesIndexes.length === 0) {
|
|
|
|
this.plot();
|
|
|
|
} else {
|
|
|
|
this.plot(this.state.chartData.filter((_, i) => this.selectedSeriesIndexes.includes(i)));
|
|
|
|
}
|
|
|
|
});
|
2019-11-27 07:51:40 -08:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2019-12-03 11:20:45 -08:00
|
|
|
componentDidMount() {
|
|
|
|
this.plot();
|
|
|
|
}
|
|
|
|
|
2019-11-27 07:51:40 -08:00
|
|
|
componentWillUnmount() {
|
|
|
|
this.destroyPlot();
|
|
|
|
}
|
|
|
|
|
2019-12-03 11:20:45 -08:00
|
|
|
plot = (data: GraphSeries[] = this.state.chartData) => {
|
2019-11-27 07:51:40 -08:00
|
|
|
if (!this.chartRef.current) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
this.destroyPlot();
|
|
|
|
|
2019-12-03 11:20:45 -08:00
|
|
|
this.$chart = $.plot($(this.chartRef.current), data, getOptions(this.props.stacked));
|
2019-11-27 07:51:40 -08:00
|
|
|
};
|
|
|
|
|
|
|
|
destroyPlot = () => {
|
|
|
|
if (isPresent(this.$chart)) {
|
|
|
|
this.$chart.destroy();
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
|
|
|
plotSetAndDraw(data: GraphSeries[] = this.state.chartData) {
|
|
|
|
if (isPresent(this.$chart)) {
|
|
|
|
this.$chart.setData(data);
|
|
|
|
this.$chart.draw();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2019-12-03 11:20:45 -08:00
|
|
|
handleSeriesSelect = (selected: number[], selectedIndex: number) => {
|
|
|
|
const { chartData } = this.state;
|
|
|
|
this.plot(
|
|
|
|
this.selectedSeriesIndexes.length === 1 && this.selectedSeriesIndexes.includes(selectedIndex)
|
|
|
|
? chartData.map(toHoverColor(selectedIndex, this.props.stacked))
|
|
|
|
: chartData.filter((_, i) => selected.includes(i)) // draw only selected
|
2019-11-27 07:51:40 -08:00
|
|
|
);
|
2019-12-03 11:20:45 -08:00
|
|
|
this.selectedSeriesIndexes = selected;
|
2019-11-27 07:51:40 -08:00
|
|
|
};
|
|
|
|
|
|
|
|
handleSeriesHover = (index: number) => () => {
|
|
|
|
if (this.rafID) {
|
|
|
|
cancelAnimationFrame(this.rafID);
|
|
|
|
}
|
|
|
|
this.rafID = requestAnimationFrame(() => {
|
|
|
|
this.plotSetAndDraw(this.state.chartData.map(toHoverColor(index, this.props.stacked)));
|
|
|
|
});
|
|
|
|
};
|
|
|
|
|
|
|
|
handleLegendMouseOut = () => {
|
|
|
|
cancelAnimationFrame(this.rafID);
|
|
|
|
this.plotSetAndDraw();
|
|
|
|
};
|
|
|
|
|
2019-12-03 11:20:45 -08:00
|
|
|
handleResize = () => {
|
|
|
|
if (isPresent(this.$chart)) {
|
|
|
|
this.plot(this.$chart.getData() as GraphSeries[]);
|
|
|
|
}
|
|
|
|
};
|
2019-11-27 07:51:40 -08:00
|
|
|
|
2019-12-03 11:20:45 -08:00
|
|
|
render() {
|
|
|
|
const { chartData } = this.state;
|
2019-11-27 07:51:40 -08:00
|
|
|
return (
|
|
|
|
<div className="graph">
|
2019-12-03 11:20:45 -08:00
|
|
|
<ReactResizeDetector handleWidth onResize={this.handleResize} skipOnMount />
|
2019-11-27 07:51:40 -08:00
|
|
|
<div className="graph-chart" ref={this.chartRef} />
|
2019-12-03 11:20:45 -08:00
|
|
|
<Legend
|
|
|
|
shouldReset={this.selectedSeriesIndexes.length === 0}
|
|
|
|
chartData={chartData}
|
|
|
|
onHover={this.handleSeriesHover}
|
|
|
|
onLegendMouseOut={this.handleLegendMouseOut}
|
|
|
|
onSeriesToggle={this.handleSeriesSelect}
|
|
|
|
/>
|
2019-11-27 07:51:40 -08:00
|
|
|
</div>
|
|
|
|
);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
export default Graph;
|