Graph.tsx 4.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191
  1. import $ from 'jquery';
  2. import React, { PureComponent } from 'react';
  3. import moment from 'moment';
  4. import { withSize } from 'react-sizeme';
  5. import 'vendor/flot/jquery.flot';
  6. import 'vendor/flot/jquery.flot.time';
  7. import { Range } from 'app/types/explore';
  8. import * as dateMath from 'app/core/utils/datemath';
  9. import TimeSeries from 'app/core/time_series2';
  10. import Legend from './Legend';
  11. const MAX_NUMBER_OF_TIME_SERIES = 20;
  12. // Copied from graph.ts
  13. function time_format(ticks, min, max) {
  14. if (min && max && ticks) {
  15. const range = max - min;
  16. const secPerTick = range / ticks / 1000;
  17. const oneDay = 86400000;
  18. const oneYear = 31536000000;
  19. if (secPerTick <= 45) {
  20. return '%H:%M:%S';
  21. }
  22. if (secPerTick <= 7200 || range <= oneDay) {
  23. return '%H:%M';
  24. }
  25. if (secPerTick <= 80000) {
  26. return '%m/%d %H:%M';
  27. }
  28. if (secPerTick <= 2419200 || range <= oneYear) {
  29. return '%m/%d';
  30. }
  31. return '%Y-%m';
  32. }
  33. return '%H:%M';
  34. }
  35. const FLOT_OPTIONS = {
  36. legend: {
  37. show: false,
  38. },
  39. series: {
  40. lines: {
  41. linewidth: 1,
  42. zero: false,
  43. },
  44. shadowSize: 0,
  45. },
  46. grid: {
  47. minBorderMargin: 0,
  48. markings: [],
  49. backgroundColor: null,
  50. borderWidth: 0,
  51. // hoverable: true,
  52. clickable: true,
  53. color: '#a1a1a1',
  54. margin: { left: 0, right: 0 },
  55. labelMarginX: 0,
  56. },
  57. // selection: {
  58. // mode: 'x',
  59. // color: '#666',
  60. // },
  61. // crosshair: {
  62. // mode: 'x',
  63. // },
  64. };
  65. interface GraphProps {
  66. data: any[];
  67. height?: string; // e.g., '200px'
  68. id?: string;
  69. loading?: boolean;
  70. range: Range;
  71. split?: boolean;
  72. size?: { width: number; height: number };
  73. }
  74. interface GraphState {
  75. showAllTimeSeries: boolean;
  76. }
  77. export class Graph extends PureComponent<GraphProps, GraphState> {
  78. state = {
  79. showAllTimeSeries: false,
  80. };
  81. getGraphData() {
  82. const { data } = this.props;
  83. return this.state.showAllTimeSeries ? data : data.slice(0, MAX_NUMBER_OF_TIME_SERIES);
  84. }
  85. componentDidMount() {
  86. this.draw();
  87. }
  88. componentDidUpdate(prevProps: GraphProps) {
  89. if (
  90. prevProps.data !== this.props.data ||
  91. prevProps.range !== this.props.range ||
  92. prevProps.split !== this.props.split ||
  93. prevProps.height !== this.props.height ||
  94. (prevProps.size && prevProps.size.width !== this.props.size.width)
  95. ) {
  96. this.draw();
  97. }
  98. }
  99. onShowAllTimeSeries = () => {
  100. this.setState(
  101. {
  102. showAllTimeSeries: true,
  103. },
  104. this.draw
  105. );
  106. };
  107. draw() {
  108. const { range, size } = this.props;
  109. const data = this.getGraphData();
  110. const $el = $(`#${this.props.id}`);
  111. let series = [{ data: [[0, 0]] }];
  112. if (data && data.length > 0) {
  113. series = data.map((ts: TimeSeries) => ({
  114. color: ts.color,
  115. label: ts.label,
  116. data: ts.getFlotPairs('null'),
  117. }));
  118. }
  119. const ticks = (size.width || 0) / 100;
  120. let { from, to } = range;
  121. if (!moment.isMoment(from)) {
  122. from = dateMath.parse(from, false);
  123. }
  124. if (!moment.isMoment(to)) {
  125. to = dateMath.parse(to, true);
  126. }
  127. const min = from.valueOf();
  128. const max = to.valueOf();
  129. const dynamicOptions = {
  130. xaxis: {
  131. mode: 'time',
  132. min: min,
  133. max: max,
  134. label: 'Datetime',
  135. ticks: ticks,
  136. timeformat: time_format(ticks, min, max),
  137. },
  138. };
  139. const options = {
  140. ...FLOT_OPTIONS,
  141. ...dynamicOptions,
  142. };
  143. $.plot($el, series, options);
  144. }
  145. render() {
  146. const { height = '100px', id = 'graph', loading = false } = this.props;
  147. const data = this.getGraphData();
  148. return (
  149. <div className="panel-container">
  150. {loading && <div className="explore-graph__loader" />}
  151. {this.props.data &&
  152. this.props.data.length > MAX_NUMBER_OF_TIME_SERIES &&
  153. !this.state.showAllTimeSeries && (
  154. <div className="time-series-disclaimer">
  155. <i className="fa fa-fw fa-warning disclaimer-icon" />
  156. {`Showing only ${MAX_NUMBER_OF_TIME_SERIES} time series. `}
  157. <span className="show-all-time-series" onClick={this.onShowAllTimeSeries}>{`Show all ${
  158. this.props.data.length
  159. }`}</span>
  160. </div>
  161. )}
  162. <div id={id} className="explore-graph" style={{ height }} />
  163. <Legend data={data} />
  164. </div>
  165. );
  166. }
  167. }
  168. export default withSize()(Graph);