Files
2024-02-15 17:01:50 +00:00

175 lines
5.1 KiB
TypeScript

import {Component} from 'react';
import {browserHistory} from 'react-router';
import type {Location} from 'history';
import DataZoomInside from 'sentry/components/charts/components/dataZoomInside';
import ToolBox from 'sentry/components/charts/components/toolBox';
import type {EChartChartReadyHandler, EChartDataZoomHandler} from 'sentry/types/echarts';
type RenderProps = {
dataZoom: ReturnType<typeof DataZoomInside>;
onChartReady: EChartChartReadyHandler;
onDataZoom: EChartDataZoomHandler;
toolBox: ReturnType<typeof ToolBox>;
};
export type BarChartBucket = {
end: number;
start: number;
};
type Props = {
/**
* This is the list of bucket start and end ranges. This is used by the
* component to determine the start and end of the zoom.
*/
buckets: BarChartBucket[];
/**
* The children function that will receive the render props and return
* a rendered chart.
*/
children: (props: RenderProps) => React.ReactNode;
location: Location;
/**
* This is the query parameter the end of the zoom will be propagated to.
*/
paramEnd: string;
/**
* This is the query parameter the start of the zoom will be propagated to.
*/
paramStart: string;
/**
* If you need the dataZoom control to control more than one chart.
* you can provide a list of the axis indexes.
*/
xAxisIndex: number[];
/**
* This is the minimum width of the zoom. If the targeted zoom area is
* smaller than is specified by this parameter, the zoom will be cancelled
* and the `onDataZoomCancelled` callback will be called.
*/
minZoomWidth?: number;
onChartReady?: EChartChartReadyHandler;
onDataZoom?: EChartDataZoomHandler;
/**
* This callback is called when the zoom action was cancelled. It can happen
* when `minZoomWidth` is specified and the user tries to zoom on an area
* smaller than that.
*/
onDataZoomCancelled?: () => void;
/**
*
*/
onHistoryPush?: (start: number, end: number) => void;
};
class BarChartZoom extends Component<Props> {
zooming: (() => void) | null = null;
/**
* Enable zoom immediately instead of having to toggle to zoom
*/
handleChartReady = chart => {
this.props.onChartReady?.(chart);
};
/**
* Chart event when *any* rendering+animation finishes
*
* `this.zooming` acts as a callback function so that
* we can let the native zoom animation on the chart complete
* before we update URL state and re-render
*/
handleChartFinished = (_props, chart) => {
if (typeof this.zooming === 'function') {
this.zooming();
this.zooming = null;
}
// This attempts to activate the area zoom toolbox feature
const zoom = chart._componentsViews?.find(c => c._features?.dataZoom);
if (zoom && !zoom._features.dataZoom._isZoomActive) {
// Calling dispatchAction will re-trigger handleChartFinished
chart.dispatchAction({
type: 'takeGlobalCursor',
key: 'dataZoomSelect',
dataZoomSelectActive: true,
});
}
};
handleDataZoom = (evt, chart) => {
const model = chart.getModel();
const {startValue, endValue} = model._payload.batch[0];
// Both of these values should not be null, but we include it just in case.
// These values are null when the user uses the toolbox included in ECharts
// to navigate back through zoom history, but we hide it below.
if (startValue !== null && endValue !== null) {
const {buckets, location, paramStart, paramEnd, minZoomWidth, onHistoryPush} =
this.props;
const {start} = buckets[startValue];
const {end} = buckets[endValue];
if (minZoomWidth === undefined || end - start > minZoomWidth) {
const target = {
pathname: location.pathname,
query: {
...location.query,
[paramStart]: start,
[paramEnd]: end,
},
};
if (onHistoryPush) {
onHistoryPush(start, end);
} else {
browserHistory.push(target);
}
} else {
// Dispatch the restore action here to stop ECharts from zooming
chart.dispatchAction({type: 'restore'});
this.props.onDataZoomCancelled?.();
}
} else {
// Dispatch the restore action here to stop ECharts from zooming
chart.dispatchAction({type: 'restore'});
this.props.onDataZoomCancelled?.();
}
this.props.onDataZoom?.(evt, chart);
};
render() {
const {children, xAxisIndex} = this.props;
const renderProps = {
onChartReady: this.handleChartReady,
onFinished: this.handleChartFinished,
dataZoom: DataZoomInside({xAxisIndex}),
// We must include data zoom in the toolbox for the zoom to work,
// but we do not want to show the toolbox components.
toolBox: ToolBox(
{},
{
dataZoom: {
title: {
zoom: '',
back: '',
},
iconStyle: {
borderWidth: 0,
color: 'transparent',
opacity: 0,
},
},
}
),
onDataZoom: this.handleDataZoom,
};
return children(renderProps);
}
}
export default BarChartZoom;