github.com/thanos-io/thanos@v0.32.5/pkg/ui/react-app/src/pages/graph/Graph.tsx (about) 1 import $ from 'jquery'; 2 import React, { PureComponent } from 'react'; 3 import ReactResizeDetector from 'react-resize-detector'; 4 5 import { Legend } from './Legend'; 6 import { Metric, QueryParams, SampleValue, SampleHistogram } from '../../types/types'; 7 import { isPresent } from '../../utils'; 8 import { normalizeData, getOptions, toHoverColor } from './GraphHelpers'; 9 10 require('../../vendor/flot/jquery.flot'); 11 require('../../vendor/flot/jquery.flot.stack'); 12 require('../../vendor/flot/jquery.flot.time'); 13 require('../../vendor/flot/jquery.flot.crosshair'); 14 require('jquery.flot.tooltip'); 15 16 export interface GraphProps { 17 data: { 18 resultType: string; 19 result: Array<{ metric: Metric; values: SampleValue[]; histograms?: SampleHistogram[] }>; 20 }; 21 stacked: boolean; 22 useLocalTime: boolean; 23 queryParams: QueryParams | null; 24 } 25 26 export interface GraphSeries { 27 labels: { [key: string]: string }; 28 color: string; 29 data: (number | null)[][]; // [x,y][] 30 index: number; 31 } 32 33 interface GraphState { 34 chartData: GraphSeries[]; 35 } 36 37 class Graph extends PureComponent<GraphProps, GraphState> { 38 private chartRef = React.createRef<HTMLDivElement>(); 39 private $chart?: jquery.flot.plot; 40 private rafID = 0; 41 private selectedSeriesIndexes: number[] = []; 42 43 state = { 44 chartData: normalizeData(this.props), 45 }; 46 47 componentDidUpdate(prevProps: GraphProps): void { 48 const { data, stacked, useLocalTime } = this.props; 49 if (prevProps.data !== data) { 50 this.selectedSeriesIndexes = []; 51 this.setState({ chartData: normalizeData(this.props) }, this.plot); 52 } else if (prevProps.stacked !== stacked) { 53 this.setState({ chartData: normalizeData(this.props) }, () => { 54 if (this.selectedSeriesIndexes.length === 0) { 55 this.plot(); 56 } else { 57 this.plot(this.state.chartData.filter((_, i) => this.selectedSeriesIndexes.includes(i))); 58 } 59 }); 60 } 61 62 if (prevProps.useLocalTime !== useLocalTime) { 63 this.plot(); 64 } 65 } 66 67 componentDidMount(): void { 68 this.plot(); 69 } 70 71 componentWillUnmount(): void { 72 this.destroyPlot(); 73 } 74 75 plot = (data: GraphSeries[] = this.state.chartData) => { 76 if (!this.chartRef.current) { 77 return; 78 } 79 this.destroyPlot(); 80 81 this.$chart = $.plot($(this.chartRef.current), data, getOptions(this.props.stacked, this.props.useLocalTime)); 82 }; 83 84 destroyPlot = (): void => { 85 if (isPresent(this.$chart)) { 86 this.$chart.destroy(); 87 } 88 }; 89 90 plotSetAndDraw(data: GraphSeries[] = this.state.chartData): void { 91 if (isPresent(this.$chart)) { 92 this.$chart.setData(data); 93 this.$chart.draw(); 94 } 95 } 96 97 handleSeriesSelect = (selected: number[], selectedIndex: number): void => { 98 const { chartData } = this.state; 99 this.plot( 100 this.selectedSeriesIndexes.length === 1 && this.selectedSeriesIndexes.includes(selectedIndex) 101 ? chartData.map(toHoverColor(selectedIndex, this.props.stacked)) 102 : chartData.filter((_, i) => selected.includes(i)) // draw only selected 103 ); 104 this.selectedSeriesIndexes = selected; 105 }; 106 107 handleSeriesHover = (index: number) => () => { 108 if (this.rafID) { 109 cancelAnimationFrame(this.rafID); 110 } 111 this.rafID = requestAnimationFrame(() => { 112 this.plotSetAndDraw(this.state.chartData.map(toHoverColor(index, this.props.stacked))); 113 }); 114 }; 115 116 handleLegendMouseOut = (): void => { 117 cancelAnimationFrame(this.rafID); 118 this.plotSetAndDraw(); 119 }; 120 121 handleResize = (): void => { 122 if (isPresent(this.$chart)) { 123 this.plot(this.$chart.getData() as GraphSeries[]); 124 } 125 }; 126 127 render(): JSX.Element { 128 const { chartData } = this.state; 129 return ( 130 <div className="graph"> 131 <ReactResizeDetector handleWidth onResize={this.handleResize} skipOnMount /> 132 <div className="graph-chart" ref={this.chartRef} /> 133 <Legend 134 shouldReset={this.selectedSeriesIndexes.length === 0} 135 chartData={chartData} 136 onHover={this.handleSeriesHover} 137 onLegendMouseOut={this.handleLegendMouseOut} 138 onSeriesToggle={this.handleSeriesSelect} 139 /> 140 </div> 141 ); 142 } 143 } 144 145 export default Graph;