barChartZoom.tsx 5.1 KB

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