barChartZoom.tsx 5.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175
  1. import {Component} from 'react';
  2. import {browserHistory} from 'react-router';
  3. import {Location} from 'history';
  4. import DataZoomInside from 'sentry/components/charts/components/dataZoomInside';
  5. import ToolBox from 'sentry/components/charts/components/toolBox';
  6. import {EChartChartReadyHandler, EChartDataZoomHandler} from 'sentry/types/echarts';
  7. import {callIfFunction} from 'sentry/utils/callIfFunction';
  8. type RenderProps = {
  9. dataZoom: ReturnType<typeof DataZoomInside>;
  10. onChartReady: EChartChartReadyHandler;
  11. onDataZoom: EChartDataZoomHandler;
  12. toolBox: ReturnType<typeof ToolBox>;
  13. };
  14. export type BarChartBucket = {
  15. end: number;
  16. start: number;
  17. };
  18. type Props = {
  19. /**
  20. * This is the list of bucket start and end ranges. This is used by the
  21. * component to determine the start and end of the zoom.
  22. */
  23. buckets: BarChartBucket[];
  24. /**
  25. * The children function that will receive the render props and return
  26. * a rendered chart.
  27. */
  28. children: (props: RenderProps) => React.ReactNode;
  29. location: Location;
  30. /**
  31. * This is the query parameter the end of the zoom will be propagated to.
  32. */
  33. paramEnd: string;
  34. /**
  35. * This is the query parameter the start of the zoom will be propagated to.
  36. */
  37. paramStart: string;
  38. /**
  39. * If you need the dataZoom control to control more than one chart.
  40. * you can provide a list of the axis indexes.
  41. */
  42. xAxisIndex: number[];
  43. /**
  44. * This is the minimum width of the zoom. If the targeted zoom area is
  45. * smaller than is specified by this parameter, the zoom will be cancelled
  46. * and the `onDataZoomCancelled` callback will be called.
  47. */
  48. minZoomWidth?: number;
  49. onChartReady?: EChartChartReadyHandler;
  50. onDataZoom?: EChartDataZoomHandler;
  51. /**
  52. * This callback is called when the zoom action was cancelled. It can happen
  53. * when `minZoomWidth` is specified and the user tries to zoom on an area
  54. * smaller than that.
  55. */
  56. onDataZoomCancelled?: () => void;
  57. /**
  58. *
  59. */
  60. onHistoryPush?: (start: number, end: number) => void;
  61. };
  62. class BarChartZoom extends Component<Props> {
  63. zooming: (() => void) | null = null;
  64. /**
  65. * Enable zoom immediately instead of having to toggle to zoom
  66. */
  67. handleChartReady = chart => {
  68. callIfFunction(this.props.onChartReady, chart);
  69. };
  70. /**
  71. * Chart event when *any* rendering+animation finishes
  72. *
  73. * `this.zooming` acts as a callback function so that
  74. * we can let the native zoom animation on the chart complete
  75. * before we update URL state and re-render
  76. */
  77. handleChartFinished = (_props, chart) => {
  78. if (typeof this.zooming === 'function') {
  79. this.zooming();
  80. this.zooming = null;
  81. }
  82. // This attempts to activate the area zoom toolbox feature
  83. const zoom = chart._componentsViews?.find(c => c._features && c._features.dataZoom);
  84. if (zoom && !zoom._features.dataZoom._isZoomActive) {
  85. // Calling dispatchAction will re-trigger handleChartFinished
  86. chart.dispatchAction({
  87. type: 'takeGlobalCursor',
  88. key: 'dataZoomSelect',
  89. dataZoomSelectActive: true,
  90. });
  91. }
  92. };
  93. handleDataZoom = (evt, chart) => {
  94. const model = chart.getModel();
  95. const {startValue, endValue} = model._payload.batch[0];
  96. // Both of these values should not be null, but we include it just in case.
  97. // These values are null when the user uses the toolbox included in ECharts
  98. // to navigate back through zoom history, but we hide it below.
  99. if (startValue !== null && endValue !== null) {
  100. const {buckets, location, paramStart, paramEnd, minZoomWidth, onHistoryPush} =
  101. this.props;
  102. const {start} = buckets[startValue];
  103. const {end} = buckets[endValue];
  104. if (minZoomWidth === undefined || end - start > minZoomWidth) {
  105. const target = {
  106. pathname: location.pathname,
  107. query: {
  108. ...location.query,
  109. [paramStart]: start,
  110. [paramEnd]: end,
  111. },
  112. };
  113. if (onHistoryPush) {
  114. onHistoryPush(start, end);
  115. } else {
  116. browserHistory.push(target);
  117. }
  118. } else {
  119. // Dispatch the restore action here to stop ECharts from zooming
  120. chart.dispatchAction({type: 'restore'});
  121. callIfFunction(this.props.onDataZoomCancelled);
  122. }
  123. } else {
  124. // Dispatch the restore action here to stop ECharts from zooming
  125. chart.dispatchAction({type: 'restore'});
  126. callIfFunction(this.props.onDataZoomCancelled);
  127. }
  128. callIfFunction(this.props.onDataZoom, evt, chart);
  129. };
  130. render() {
  131. const {children, xAxisIndex} = this.props;
  132. const renderProps = {
  133. onChartReady: this.handleChartReady,
  134. onFinished: this.handleChartFinished,
  135. dataZoom: DataZoomInside({xAxisIndex}),
  136. // We must include data zoom in the toolbox for the zoom to work,
  137. // but we do not want to show the toolbox components.
  138. toolBox: ToolBox(
  139. {},
  140. {
  141. dataZoom: {
  142. title: {
  143. zoom: '',
  144. back: '',
  145. },
  146. iconStyle: {
  147. borderWidth: 0,
  148. color: 'transparent',
  149. opacity: 0,
  150. },
  151. },
  152. }
  153. ),
  154. onDataZoom: this.handleDataZoom,
  155. };
  156. return children(renderProps);
  157. }
  158. }
  159. export default BarChartZoom;