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;