import {useState} from 'react'; import * as React from 'react'; import { ChartMargin, DataType, COLOR_VISION_SINGLE_ITEM, useTheme, useChartPositions, LINE_HEIGHT, } from '@shopify/polaris-viz-core'; import type { Dimensions, DataGroup, BoundingRect, XAxisOptions, } from '@shopify/polaris-viz-core'; import {ChartElements} from '../ChartElements'; import { Annotations, checkAvailableAnnotations, YAxisAnnotations, } from '../Annotations'; import {sortBarChartData} from '../../utilities/sortBarChartData'; import {getVerticalBarChartTooltipPosition} from '../../utilities/getVerticalBarChartTooltipPosition'; import type {TooltipPosition, TooltipPositionParams} from '../TooltipWrapper'; import { TooltipHorizontalOffset, TooltipVerticalOffset, TooltipWrapper, TOOLTIP_POSITION_DEFAULT_RETURN, } from '../TooltipWrapper'; import type { AnnotationLookupTable, RenderLegendContent, RenderTooltipContentData, } from '../../types'; import {XAxis} from '../XAxis'; import {useThemeSeriesColorsForDataGroup} from '../../hooks/useThemeSeriesColorsForDataGroup'; import {useColorVisionEvents, useReducedLabelIndexes} from '../../hooks'; import {HorizontalGridLines} from '../HorizontalGridLines'; import {YAxis} from '../YAxis'; import {LegendContainer, useLegend} from '../LegendContainer'; import {ANNOTATIONS_LABELS_OFFSET} from '../../constants'; import {useDualAxisTicks} from './hooks/useDualAxisTicks'; import {useDualAxisTicksWidth} from './hooks/useDualAxisTickWidths'; import {useDualAxisScale} from './hooks/useDualAxisScale'; import {useXScale} from './hooks/useXScale'; import {ComboBarChart, ComboLineChart, AxisLabel} from './components'; import {useSplitDataForCharts} from './hooks/useSplitDataForCharts'; import {useComboChartTooltipContent} from './hooks/useComboChartTooltipContent'; import {useComboChartPositions} from './hooks/useComboChartPositions'; export interface ChartProps { annotationsLookupTable: AnnotationLookupTable; data: DataGroup[]; renderTooltipContent(data: RenderTooltipContentData): React.ReactNode; showLegend: boolean; theme: string; xAxisOptions: Required; dimensions?: Dimensions; renderLegendContent?: RenderLegendContent; } export function Chart({ annotationsLookupTable, data, dimensions, renderTooltipContent, showLegend, theme, xAxisOptions, renderLegendContent, }: ChartProps) { const selectedTheme = useTheme(theme); useColorVisionEvents(); const colors = useThemeSeriesColorsForDataGroup(data, selectedTheme); const [xAxisHeight, setXAxisHeight] = useState(LINE_HEIGHT); const [svgRef, setSvgRef] = useState(null); const [activeIndex, setActiveIndex] = useState(null); const [annotationsHeight, setAnnotationsHeight] = useState(0); const {legend, setLegendDimensions, height, width} = useLegend({ colors, data, dimensions, showLegend, }); const {drawableHeight, chartYPosition, xAxisBounds, yAxisBounds} = useChartPositions({ annotationsHeight, height, width, xAxisHeight, yAxisWidth: 0, }); const annotationsDrawableHeight = chartYPosition + drawableHeight + ANNOTATIONS_LABELS_OFFSET; const { doBothChartsContainMixedValues, doesOneChartContainAllNegativeValues, primaryTicks, primaryAxis, secondaryTicks, secondaryAxis, shouldPlaceZeroInMiddleOfChart, ticksBetweenZeroAndMax, yScale, } = useDualAxisTicks({ data, drawableHeight, }); const {leftTickWidth, rightTickWidth} = useDualAxisTicksWidth( primaryTicks, secondaryTicks, ); const {barYScale, lineYScale, primaryYScale, secondaryYScale} = useDualAxisScale({ doesOneChartContainAllNegativeValues, doBothChartsContainMixedValues, drawableHeight: annotationsDrawableHeight, primaryAxis, secondaryAxis, yScale, shouldPlaceZeroInMiddleOfChart, ticksBetweenZeroAndMax, }); const {chartXPosition, drawableWidth, leftAxis, rightAxis} = useComboChartPositions({ leftTickWidth, primaryAxis, rightTickWidth, secondaryAxis, width, }); const { barChartData, barChartColors, lineChartColors, lineChartData, barChartIndexOffset, lineChartIndexOffset, } = useSplitDataForCharts(data, colors); const {xScale, labels} = useXScale({drawableWidth, data, xAxisOptions}); const reducedLabelIndexes = useReducedLabelIndexes({ dataLength: labels.length, }); const hideXAxis = false; const labelWidth = drawableWidth / labels.length; const chartBounds: BoundingRect = { width, height, x: chartXPosition, y: chartYPosition, }; const getTooltipMarkup = useComboChartTooltipContent({ renderTooltipContent, data, seriesColors: colors, }); const {hasXAxisAnnotations, hasYAxisAnnotations} = checkAvailableAnnotations( annotationsLookupTable, ); return ( {selectedTheme.grid.showHorizontalLines ? ( ) : null} {hideXAxis ? null : ( )} {primaryAxis.name != null && ( )} {hasXAxisAnnotations && ( )} {hasYAxisAnnotations && ( )} {secondaryAxis.name != null && ( )} setActiveIndex(index)} parentRef={svgRef} /> {showLegend && ( )} ); function formatPositionForTooltip(index: number | null): TooltipPosition { if (index == null) { return TOOLTIP_POSITION_DEFAULT_RETURN; } const sortedData = sortBarChartData(labels, barChartData.series); const xPosition = xScale(index) ?? 0; const sortedDataPos = sortedData[index].map((num) => Math.abs(num ?? 0)); const highestValuePos = Math.max(...sortedDataPos); const x = xPosition + chartXPosition; const y = barYScale(highestValuePos) + (ChartMargin.Top as number); return { x, y: Math.abs(y), position: { horizontal: TooltipHorizontalOffset.Left, vertical: TooltipVerticalOffset.Above, }, activeIndex: index, }; } function getTooltipPosition({ event, index, eventType, }: TooltipPositionParams): TooltipPosition { return getVerticalBarChartTooltipPosition({ tooltipPosition: {event, index, eventType}, chartXPosition, formatPositionForTooltip, maxIndex: labels.length - 1, step: labelWidth, yMin: ChartMargin.Top, yMax: drawableHeight + Number(ChartMargin.Bottom) + xAxisHeight, }); } }