import {RefObject, useCallback, useMemo, useRef} from 'react'; import styled from '@emotion/styled'; import {EChartsOption} from 'echarts'; import moment from 'moment'; import {Button} from 'sentry/components/button'; import {IconDelete, IconZoom} from 'sentry/icons'; import {space} from 'sentry/styles/space'; import {EChartBrushEndHandler, ReactEchartsRef} from 'sentry/types/echarts'; import {MetricRange} from 'sentry/utils/metrics'; import theme from 'sentry/utils/theme'; import {DateTimeObject} from '../../components/charts/utils'; interface AbsolutePosition { height: string; left: string; top: string; width: string; } export interface FocusArea { position: AbsolutePosition; range: MetricRange; widgetIndex: number; } interface UseFocusAreaBrushOptions { widgetIndex: number; isDisabled?: boolean; } type BrushEndResult = Parameters[0]; export function useFocusAreaBrush( chartRef: RefObject, focusArea: FocusArea | null, onAdd: (area: FocusArea) => void, onRemove: () => void, onZoom: (range: DateTimeObject) => void, {widgetIndex, isDisabled = false}: UseFocusAreaBrushOptions ) { const hasFocusArea = useMemo( () => focusArea && focusArea.widgetIndex === widgetIndex, [focusArea, widgetIndex] ); const isDrawingRef = useRef(false); const onBrushEnd = useCallback( (brushEnd: BrushEndResult) => { if (isDisabled) { return; } const rect = brushEnd.areas[0]; if (!rect) { return; } const chartWidth = chartRef.current?.getEchartsInstance().getWidth() ?? 100; onAdd({ widgetIndex, position: getPosition(brushEnd, chartWidth), range: getMetricRange(brushEnd), }); // Remove brush from echarts immediately after adding the focus area // since brushes get added to all charts in the group by default and then randomly // render in the wrong place chartRef.current?.getEchartsInstance().dispatchAction({ type: 'brush', brushType: 'clear', areas: [], }); isDrawingRef.current = false; }, [chartRef, isDisabled, onAdd, widgetIndex] ); const startBrush = useCallback(() => { if (hasFocusArea) { return; } chartRef.current?.getEchartsInstance().dispatchAction({ type: 'takeGlobalCursor', key: 'brush', brushOption: { brushType: 'rect', }, }); isDrawingRef.current = true; }, [chartRef, hasFocusArea]); const handleRemove = useCallback(() => { onRemove(); }, [onRemove]); const handleZoomIn = useCallback(() => { onZoom({ period: null, ...focusArea?.range, }); handleRemove(); }, [focusArea, handleRemove, onZoom]); const brushOptions = useMemo(() => { return { onBrushEnd, toolBox: { show: false, }, brush: { toolbox: ['rect'], xAxisIndex: 0, brushStyle: { borderWidth: 2, borderColor: theme.purple300, color: 'transparent', }, inBrush: { opacity: 1, }, outOfBrush: { opacity: 1, }, z: 10, } as EChartsOption['brush'], }; }, [onBrushEnd]); if (hasFocusArea) { return { overlay: ( ), isDrawingRef, startBrush, options: {}, }; } return { overlay: null, isDrawingRef, startBrush, options: brushOptions, }; } function BrushRectOverlay({rect, onZoom, onRemove}) { if (!rect) { return null; } const {top, left, width, height} = rect.position; return (