@cdc/chart 4.26.3 → 4.26.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CONFIG.md +169 -0
- package/README.md +42 -17
- package/dist/cdcchart.js +34725 -33730
- package/examples/feature/bar/additional-column-tooltip.json +3 -3
- package/examples/feature/deviation/planet-deviation-config.json +2 -2
- package/examples/feature/paired-bar/paired-bar-example.json +2 -2
- package/examples/feature/tests-case-rate/case-rate-example-config.json +2 -2
- package/examples/feature/tests-covid/covid-confidence-example-config.json +2 -2
- package/examples/feature/tests-covid/covid-example-config.json +2 -2
- package/examples/feature/tests-cutoff/cutoff-example-config.json +2 -2
- package/examples/feature/tests-non-numerics/planet-pie-example-config-nonnumeric.json +2 -2
- package/examples/metadata-variables.json +1 -1
- package/examples/minimal-example.json +24 -0
- package/examples/private/test2.json +35012 -0
- package/package.json +4 -4
- package/src/CdcChartComponent.tsx +9 -6
- package/src/_stories/{Chart.HTMLInDataTable.stories.tsx → Chart.HTMLInDataTable.smoke.stories.tsx} +10 -0
- package/src/_stories/Chart.Minimal.smoke.stories.tsx +25 -0
- package/src/_stories/{Chart.SmallMultiples.stories.tsx → Chart.SmallMultiples.smoke.stories.tsx} +17 -1
- package/src/_stories/{Chart.stories.tsx → Chart.smoke.stories.tsx} +12 -0
- package/src/_stories/{Chart.tooltip.stories.tsx → Chart.tooltip.smoke.stories.tsx} +68 -0
- package/src/_stories/ChartBar.Editor.stories.tsx +88 -85
- package/src/_stories/ChartEditor.Editor.stories.tsx +23 -0
- package/src/_stories/ChartLine.DashedDates.smoke.stories.tsx +23 -0
- package/src/_stories/_mock/line_chart_dashed_dates.json +276 -0
- package/src/_stories/_mock/paired-bar-abbr.json +1 -1
- package/src/_stories/_mock/paired-bar.json +1 -1
- package/src/components/Annotations/components/AnnotationDraggable.tsx +1 -1
- package/src/components/AreaChart/components/AreaChart.Stacked.jsx +10 -2
- package/src/components/Axis/BottomAxis.tsx +1 -1
- package/src/components/Axis/LeftAxis.tsx +2 -4
- package/src/components/Axis/LeftAxisGridlines.tsx +1 -3
- package/src/components/BarChart/components/BarChart.Horizontal.tsx +9 -5
- package/src/components/BarChart/components/BarChart.StackedHorizontal.tsx +9 -5
- package/src/components/BarChart/components/BarChart.StackedVertical.tsx +9 -5
- package/src/components/BarChart/components/BarChart.Vertical.tsx +9 -6
- package/src/components/BarChart/components/BarChart.tsx +2 -6
- package/src/components/BarChart/helpers/useBarChart.ts +1 -1
- package/src/components/BoxPlot/BoxPlot.Vertical.tsx +3 -3
- package/src/components/EditorPanel/EditorPanel.tsx +633 -908
- package/src/components/EditorPanel/EditorPanelContext.ts +1 -1
- package/src/components/EditorPanel/components/Panels/Panel.Annotate.tsx +216 -165
- package/src/components/EditorPanel/components/Panels/Panel.PatternSettings.tsx +168 -116
- package/src/components/EditorPanel/components/Panels/Panel.Regions.tsx +154 -109
- package/src/components/EditorPanel/components/Panels/Panel.Sankey.tsx +3 -3
- package/src/components/EditorPanel/components/Panels/Panel.Series.tsx +12 -26
- package/src/components/EditorPanel/useEditorPermissions.ts +1 -10
- package/src/components/Forecasting/Forecasting.tsx +2 -2
- package/src/components/HorizonChart/HorizonChart.tsx +10 -2
- package/src/components/HoverLine/HoverLine.tsx +6 -8
- package/src/components/Legend/Legend.Component.tsx +4 -4
- package/src/components/Legend/LegendGroup/LegendGroup.styles.css +5 -0
- package/src/components/Legend/LegendGroup/LegendGroup.tsx +3 -1
- package/src/components/Legend/helpers/index.ts +0 -2
- package/src/components/LineChart/LineChartProps.ts +1 -0
- package/src/components/LineChart/components/LineChart.BumpCircle.tsx +3 -2
- package/src/components/LineChart/index.tsx +11 -4
- package/src/components/LinearChart/VisualizationRenderer.tsx +11 -1
- package/src/components/LinearChart/tests/LinearChart.test.tsx +147 -0
- package/src/components/LinearChart.tsx +98 -41
- package/src/components/PairedBarChart.jsx +2 -2
- package/src/components/PieChart/PieChart.tsx +11 -9
- package/src/components/Regions/components/Regions.tsx +30 -20
- package/src/components/ScatterPlot/ScatterPlot.jsx +19 -17
- package/src/components/SmallMultiples/SmallMultipleTile.tsx +1 -0
- package/src/components/Sparkline/components/SparkLine.tsx +1 -1
- package/src/components/WarmingStripes/WarmingStripes.tsx +5 -3
- package/src/data/initial-state.js +1 -0
- package/src/helpers/calculateLeftYAxisWidth.ts +193 -0
- package/src/helpers/tests/tooltipHelpers.test.ts +151 -0
- package/src/helpers/tooltipHelpers.ts +176 -0
- package/src/hooks/useHighlightedBars.ts +9 -16
- package/src/hooks/useProgrammaticTooltip.ts +16 -2
- package/src/hooks/useSmallMultipleSynchronization.ts +3 -4
- package/src/hooks/useTooltip.tsx +167 -38
- package/src/test/CdcChart.test.jsx +24 -0
- package/src/types/ChartConfig.ts +1 -0
- package/LICENSE +0 -201
- package/examples/chart-regression-2.json +0 -2360
- package/examples/feature/__data__/age-adjusted-rates.json +0 -1486
- package/examples/feature/__data__/city-temperature.json +0 -2198
- package/examples/feature/bar/Barchart_with_negative.json +0 -34
- package/examples/feature/boxplot/box-plot-data.json +0 -71
- package/examples/feature/boxplot/boxplot-data.json +0 -156
- package/examples/feature/combo/combochart-categories_are_numbers .json +0 -18
- package/examples/feature/forest-plot/log.json +0 -26
- package/examples/feature/tests-date-exclusions/date-exclusions-data.json +0 -162
- package/examples/region-issue.json +0 -2065
- package/examples/xaxis.json +0 -493
- /package/examples/{data → __data__}/data-with-metadata.json +0 -0
- /package/examples/feature/bar/{tall-data.json → __data__/tall-data.json} +0 -0
- /package/examples/feature/deviation/{planet-deviation-data.json → __data__/planet-deviation-data.json} +0 -0
- /package/examples/feature/paired-bar/{paired-bar-data.json → __data__/paired-bar-data.json} +0 -0
- /package/examples/feature/paired-bar/{paired-bar-formatted.json → __data__/paired-bar-formatted.json} +0 -0
- /package/examples/feature/tests-case-rate/{case-rate-example-data.json → __data__/case-rate-example-data.json} +0 -0
- /package/examples/feature/tests-covid/{covid-example-data-confidence.json → __data__/covid-example-data-confidence.json} +0 -0
- /package/examples/feature/tests-covid/{covid-example-data.json → __data__/covid-example-data.json} +0 -0
- /package/examples/feature/tests-cutoff/{cutoff-example-data.json → __data__/cutoff-example-data.json} +0 -0
- /package/examples/feature/tests-non-numerics/{planet-example-data-nonnumeric.json → __data__/planet-example-data-nonnumeric.json} +0 -0
- /package/src/_stories/{Chart.Anchors.stories.tsx → Chart.Anchors.smoke.stories.tsx} +0 -0
- /package/src/_stories/{Chart.BoxPlot.stories.tsx → Chart.BoxPlot.smoke.stories.tsx} +0 -0
- /package/src/_stories/{Chart.CI.stories.tsx → Chart.CI.smoke.stories.tsx} +0 -0
- /package/src/_stories/{Chart.Combo.stories.tsx → Chart.Combo.smoke.stories.tsx} +0 -0
- /package/src/_stories/{Chart.CustomColors.stories.tsx → Chart.CustomColors.smoke.stories.tsx} +0 -0
- /package/src/_stories/{Chart.Defaults.stories.tsx → Chart.Defaults.smoke.stories.tsx} +0 -0
- /package/src/_stories/{Chart.DynamicSeries.stories.tsx → Chart.DynamicSeries.smoke.stories.tsx} +0 -0
- /package/src/_stories/{Chart.Filters.stories.tsx → Chart.Filters.smoke.stories.tsx} +0 -0
- /package/src/_stories/{Chart.Forecast.stories.tsx → Chart.Forecast.smoke.stories.tsx} +0 -0
- /package/src/_stories/{Chart.Legend.Gradient.stories.tsx → Chart.Legend.Gradient.smoke.stories.tsx} +0 -0
- /package/src/_stories/{Chart.Patterns.stories.tsx → Chart.Patterns.smoke.stories.tsx} +0 -0
- /package/src/_stories/{Chart.PreserveDecimals.stories.tsx → Chart.PreserveDecimals.smoke.stories.tsx} +0 -0
- /package/src/_stories/{Chart.Regions.Categorical.stories.tsx → Chart.Regions.Categorical.smoke.stories.tsx} +0 -0
- /package/src/_stories/{Chart.Regions.DateScale.stories.tsx → Chart.Regions.DateScale.smoke.stories.tsx} +0 -0
- /package/src/_stories/{Chart.Regions.DateTimeScale.stories.tsx → Chart.Regions.DateTimeScale.smoke.stories.tsx} +0 -0
- /package/src/_stories/{Chart.ScatterPlot.stories.tsx → Chart.ScatterPlot.smoke.stories.tsx} +0 -0
- /package/src/_stories/{Chart.SmallestLeftAxisMax.stories.tsx → Chart.SmallestLeftAxisMax.smoke.stories.tsx} +0 -0
- /package/src/_stories/{ChartAnnotation.stories.tsx → ChartAnnotation.smoke.stories.tsx} +0 -0
- /package/src/_stories/{ChartAxisLabels.stories.tsx → ChartAxisLabels.smoke.stories.tsx} +0 -0
- /package/src/_stories/{ChartAxisTitles.stories.tsx → ChartAxisTitles.smoke.stories.tsx} +0 -0
- /package/src/_stories/{ChartBrush.Matrix.Continuous.stories.tsx → ChartBrush.Matrix.Continuous.smoke.stories.tsx} +0 -0
- /package/src/_stories/{ChartBrush.Matrix.Date.stories.tsx → ChartBrush.Matrix.Date.smoke.stories.tsx} +0 -0
- /package/src/_stories/{ChartBrush.Matrix.DateTime.stories.tsx → ChartBrush.Matrix.DateTime.smoke.stories.tsx} +0 -0
- /package/src/_stories/{ChartBrush.stories.tsx → ChartBrush.smoke.stories.tsx} +0 -0
- /package/src/_stories/{ChartLine.QuadrantAngles.stories.tsx → ChartLine.QuadrantAngles.smoke.stories.tsx} +0 -0
- /package/src/_stories/{ChartLine.Suppression.stories.tsx → ChartLine.Suppression.smoke.stories.tsx} +0 -0
- /package/src/_stories/{ChartLine.Symbols.stories.tsx → ChartLine.Symbols.smoke.stories.tsx} +0 -0
- /package/src/_stories/{ChartPrefixSuffix.stories.tsx → ChartPrefixSuffix.smoke.stories.tsx} +0 -0
- /package/src/_stories/{TechAdoptionWithLinks.stories.tsx → TechAdoptionWithLinks.smoke.stories.tsx} +0 -0
|
@@ -0,0 +1,193 @@
|
|
|
1
|
+
import { getTextWidth } from '@cdc/core/helpers/getTextWidth'
|
|
2
|
+
import {
|
|
3
|
+
DEFAULT_TICK_LENGTH,
|
|
4
|
+
TICK_LABEL_MARGIN_RIGHT,
|
|
5
|
+
VALUE_ON_LINE_PADDING_NO_AXIS,
|
|
6
|
+
VALUE_ON_LINE_PADDING_WITH_AXIS
|
|
7
|
+
} from '../components/Axis/axis.constants'
|
|
8
|
+
|
|
9
|
+
type CalculateLeftYAxisWidthProps = {
|
|
10
|
+
config: any
|
|
11
|
+
data: any[]
|
|
12
|
+
yScale: any
|
|
13
|
+
numTicks: number
|
|
14
|
+
parentWidth: number
|
|
15
|
+
tickLabelFont: string
|
|
16
|
+
axisLabelFontSize: number
|
|
17
|
+
handleLeftTickFormatting: (tick: any, index: number, ticks: any[]) => string
|
|
18
|
+
categoryLabelSpace?: number
|
|
19
|
+
horizontalYAxisLabelSpace?: number
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
const AXIS_LABEL_PADDING = 5
|
|
23
|
+
const MIN_LEFT_AXIS_WIDTH = 0
|
|
24
|
+
const MAX_LEFT_AXIS_WIDTH_RATIO = 0.45
|
|
25
|
+
|
|
26
|
+
type TickValueProps = Omit<
|
|
27
|
+
CalculateLeftYAxisWidthProps,
|
|
28
|
+
'parentWidth' | 'tickLabelFont' | 'axisLabelFontSize' | 'handleLeftTickFormatting'
|
|
29
|
+
>
|
|
30
|
+
|
|
31
|
+
const getTickValues = ({ config, data, yScale, numTicks }: TickValueProps) => {
|
|
32
|
+
if (config.visualizationType === 'Forest Plot') {
|
|
33
|
+
return data.map((_, index) => index)
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
if (typeof yScale?.ticks === 'function') {
|
|
37
|
+
return yScale.ticks(numTicks)
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
if (typeof yScale?.domain === 'function') {
|
|
41
|
+
return yScale.domain()
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
return []
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
const getAxisLabelSpace = (config: any, axisLabelFontSize: number) => {
|
|
48
|
+
return !config.hideYAxisLabel && config.runtime.yAxis.label ? axisLabelFontSize + AXIS_LABEL_PADDING : 0
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
const measureWidestLabel = (labels: string[], tickLabelFont: string) => {
|
|
52
|
+
return labels.length ? Math.max(...labels.map(label => getTextWidth(label, tickLabelFont))) : 0
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
const clampYAxisWidth = (calculatedWidth: number, parentWidth: number) => {
|
|
56
|
+
const maxAllowedWidth = parentWidth * MAX_LEFT_AXIS_WIDTH_RATIO
|
|
57
|
+
return Math.max(MIN_LEFT_AXIS_WIDTH, Math.min(Math.ceil(calculatedWidth), maxAllowedWidth))
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
const formatMeasuredTickLabels = ({
|
|
61
|
+
tickValues,
|
|
62
|
+
handleLeftTickFormatting,
|
|
63
|
+
inlineLabel,
|
|
64
|
+
labelsAboveGridlines
|
|
65
|
+
}: {
|
|
66
|
+
tickValues: any[]
|
|
67
|
+
handleLeftTickFormatting: (tick: any, index: number, ticks: any[]) => string
|
|
68
|
+
inlineLabel?: string
|
|
69
|
+
labelsAboveGridlines?: boolean
|
|
70
|
+
}) => {
|
|
71
|
+
const inlineLabelHasNoSpace = !inlineLabel?.includes(' ')
|
|
72
|
+
|
|
73
|
+
return tickValues.map((tick, index, ticks) => {
|
|
74
|
+
const formatted = handleLeftTickFormatting(tick, index, ticks)
|
|
75
|
+
const isLastTick = index === ticks.length - 1
|
|
76
|
+
|
|
77
|
+
if (isLastTick && inlineLabel && (labelsAboveGridlines || inlineLabelHasNoSpace)) {
|
|
78
|
+
return `${formatted}${inlineLabel}`
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
return String(formatted ?? '')
|
|
82
|
+
})
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
const getHorizonChartWidth = ({
|
|
86
|
+
config,
|
|
87
|
+
parentWidth,
|
|
88
|
+
tickLabelFont,
|
|
89
|
+
axisLabelFontSize
|
|
90
|
+
}: Pick<CalculateLeftYAxisWidthProps, 'config' | 'parentWidth' | 'tickLabelFont' | 'axisLabelFontSize'>) => {
|
|
91
|
+
const seriesKeys =
|
|
92
|
+
config.runtime?.seriesKeys && config.runtime.seriesKeys.length > 0
|
|
93
|
+
? config.runtime.seriesKeys
|
|
94
|
+
: config.series?.map(series => series.dataKey) || []
|
|
95
|
+
const labels = seriesKeys.map(seriesKey => String(config.runtime?.seriesLabels?.[seriesKey] || seriesKey))
|
|
96
|
+
const labelWidth = config.yAxis.hideLabel ? 0 : measureWidestLabel(labels, tickLabelFont)
|
|
97
|
+
const calculatedWidth =
|
|
98
|
+
labelWidth +
|
|
99
|
+
DEFAULT_TICK_LENGTH +
|
|
100
|
+
TICK_LABEL_MARGIN_RIGHT +
|
|
101
|
+
getAxisLabelSpace(config, axisLabelFontSize) +
|
|
102
|
+
Number(config.yAxis.axisPadding || 0)
|
|
103
|
+
|
|
104
|
+
return clampYAxisWidth(calculatedWidth, parentWidth)
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
const getHorizontalAxisWidth = ({
|
|
108
|
+
config,
|
|
109
|
+
tickLabelWidth,
|
|
110
|
+
categoryLabelSpace,
|
|
111
|
+
horizontalYAxisLabelSpace
|
|
112
|
+
}: Pick<CalculateLeftYAxisWidthProps, 'config' | 'categoryLabelSpace' | 'horizontalYAxisLabelSpace'> & {
|
|
113
|
+
tickLabelWidth: number
|
|
114
|
+
}) => {
|
|
115
|
+
const horizontalTickWidth = tickLabelWidth + DEFAULT_TICK_LENGTH + TICK_LABEL_MARGIN_RIGHT
|
|
116
|
+
return Math.max(horizontalTickWidth + horizontalYAxisLabelSpace, categoryLabelSpace + horizontalYAxisLabelSpace)
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
const getCategoricalAxisWidth = ({
|
|
120
|
+
config,
|
|
121
|
+
baseWidth,
|
|
122
|
+
tickLabelFont
|
|
123
|
+
}: Pick<CalculateLeftYAxisWidthProps, 'config' | 'tickLabelFont'> & {
|
|
124
|
+
baseWidth: number
|
|
125
|
+
}) => {
|
|
126
|
+
if (config.yAxis.type !== 'categorical' || config.orientation !== 'vertical' || config.yAxis.hideLabel) {
|
|
127
|
+
return baseWidth
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
const categoryLabels = (config.yAxis.categories || []).map(category => String(category?.label || ''))
|
|
131
|
+
const categoryLabelWidth = measureWidestLabel(categoryLabels, tickLabelFont)
|
|
132
|
+
|
|
133
|
+
return Math.max(baseWidth, categoryLabelWidth + DEFAULT_TICK_LENGTH + TICK_LABEL_MARGIN_RIGHT)
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
export const calculateLeftYAxisWidth = ({
|
|
137
|
+
config,
|
|
138
|
+
data,
|
|
139
|
+
yScale,
|
|
140
|
+
numTicks,
|
|
141
|
+
parentWidth,
|
|
142
|
+
tickLabelFont,
|
|
143
|
+
axisLabelFontSize,
|
|
144
|
+
handleLeftTickFormatting,
|
|
145
|
+
categoryLabelSpace = 0,
|
|
146
|
+
horizontalYAxisLabelSpace = 0
|
|
147
|
+
}: CalculateLeftYAxisWidthProps): number => {
|
|
148
|
+
if (config.visualizationType === 'Horizon Chart') {
|
|
149
|
+
return getHorizonChartWidth({ config, parentWidth, tickLabelFont, axisLabelFontSize })
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
const tickValues = getTickValues({ config, data, yScale, numTicks })
|
|
153
|
+
const { hideAxis, inlineLabel, labelsAboveGridlines } = config.yAxis
|
|
154
|
+
const formattedTicks = formatMeasuredTickLabels({
|
|
155
|
+
tickValues,
|
|
156
|
+
handleLeftTickFormatting,
|
|
157
|
+
inlineLabel,
|
|
158
|
+
labelsAboveGridlines
|
|
159
|
+
})
|
|
160
|
+
const tickLabelWidth = config.yAxis.hideLabel ? 0 : measureWidestLabel(formattedTicks, tickLabelFont)
|
|
161
|
+
|
|
162
|
+
const valueOnLinePadding = hideAxis
|
|
163
|
+
? Math.abs(VALUE_ON_LINE_PADDING_NO_AXIS)
|
|
164
|
+
: Math.abs(VALUE_ON_LINE_PADDING_WITH_AXIS)
|
|
165
|
+
const tickPadding = labelsAboveGridlines ? valueOnLinePadding : TICK_LABEL_MARGIN_RIGHT + DEFAULT_TICK_LENGTH
|
|
166
|
+
const verticalAxisLabelSpace = getAxisLabelSpace(config, axisLabelFontSize)
|
|
167
|
+
const reservedTickLabelWidth = labelsAboveGridlines ? 0 : tickLabelWidth
|
|
168
|
+
const reservedTickPadding = labelsAboveGridlines || config.yAxis.hideLabel ? 0 : tickPadding
|
|
169
|
+
|
|
170
|
+
const defaultCalculatedWidth =
|
|
171
|
+
reservedTickLabelWidth + reservedTickPadding + verticalAxisLabelSpace + Number(config.yAxis.axisPadding || 0)
|
|
172
|
+
|
|
173
|
+
if (config.orientation === 'horizontal') {
|
|
174
|
+
const calculatedWidth = getHorizontalAxisWidth({
|
|
175
|
+
config,
|
|
176
|
+
tickLabelWidth,
|
|
177
|
+
categoryLabelSpace,
|
|
178
|
+
horizontalYAxisLabelSpace
|
|
179
|
+
})
|
|
180
|
+
|
|
181
|
+
return clampYAxisWidth(calculatedWidth, parentWidth)
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
const calculatedWidth = getCategoricalAxisWidth({
|
|
185
|
+
config,
|
|
186
|
+
baseWidth: defaultCalculatedWidth,
|
|
187
|
+
tickLabelFont
|
|
188
|
+
})
|
|
189
|
+
|
|
190
|
+
return clampYAxisWidth(calculatedWidth, parentWidth)
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
export default calculateLeftYAxisWidth
|
|
@@ -0,0 +1,151 @@
|
|
|
1
|
+
import { describe, expect, it } from 'vitest'
|
|
2
|
+
import {
|
|
3
|
+
buildTooltipListHtml,
|
|
4
|
+
getTooltipMarkerColor,
|
|
5
|
+
getTooltipMarkerShape,
|
|
6
|
+
shouldUseTooltipLegendMarkers,
|
|
7
|
+
tooltipHasMarkerColumn,
|
|
8
|
+
tooltipShouldUseMarkerColumn,
|
|
9
|
+
type TooltipRow
|
|
10
|
+
} from '../tooltipHelpers'
|
|
11
|
+
|
|
12
|
+
const buildConfig = overrides => ({
|
|
13
|
+
visualizationType: 'Line',
|
|
14
|
+
legend: {
|
|
15
|
+
hide: false,
|
|
16
|
+
style: 'circles',
|
|
17
|
+
groupBy: '',
|
|
18
|
+
patterns: {},
|
|
19
|
+
...overrides?.legend
|
|
20
|
+
},
|
|
21
|
+
runtime: {
|
|
22
|
+
seriesLabels: { cases: 'Cases', deaths: 'Deaths' },
|
|
23
|
+
seriesLabelsAll: ['Cases', 'Deaths'],
|
|
24
|
+
seriesKeys: ['cases', 'deaths'],
|
|
25
|
+
...overrides?.runtime
|
|
26
|
+
},
|
|
27
|
+
series: [{ dataKey: 'cases' }, { dataKey: 'deaths' }],
|
|
28
|
+
smallMultiples: {
|
|
29
|
+
mode: '',
|
|
30
|
+
...overrides?.smallMultiples
|
|
31
|
+
},
|
|
32
|
+
...overrides
|
|
33
|
+
})
|
|
34
|
+
|
|
35
|
+
describe('tooltipHelpers', () => {
|
|
36
|
+
it('enables marker rendering for supported multi-series charts with visible legends', () => {
|
|
37
|
+
expect(shouldUseTooltipLegendMarkers(buildConfig())).toBe(true)
|
|
38
|
+
})
|
|
39
|
+
|
|
40
|
+
it('allows small-multiple tiles to use markers when the source legend is visible', () => {
|
|
41
|
+
expect(shouldUseTooltipLegendMarkers(buildConfig({ legend: { hide: true, tooltipLegendVisible: true } }))).toBe(
|
|
42
|
+
true
|
|
43
|
+
)
|
|
44
|
+
})
|
|
45
|
+
|
|
46
|
+
it('disables markers when the source legend is hidden or the legend is in a special mode', () => {
|
|
47
|
+
expect(shouldUseTooltipLegendMarkers(buildConfig({ legend: { hide: true } }))).toBe(false)
|
|
48
|
+
expect(shouldUseTooltipLegendMarkers(buildConfig({ legend: { hide: true, tooltipLegendVisible: false } }))).toBe(
|
|
49
|
+
false
|
|
50
|
+
)
|
|
51
|
+
expect(shouldUseTooltipLegendMarkers(buildConfig({ legend: { style: 'gradient' } }))).toBe(false)
|
|
52
|
+
expect(shouldUseTooltipLegendMarkers(buildConfig({ legend: { groupBy: 'category' } }))).toBe(false)
|
|
53
|
+
expect(
|
|
54
|
+
shouldUseTooltipLegendMarkers(
|
|
55
|
+
buildConfig({
|
|
56
|
+
runtime: { seriesLabelsAll: ['Cases'], seriesKeys: ['cases'] },
|
|
57
|
+
series: [{ dataKey: 'cases' }]
|
|
58
|
+
})
|
|
59
|
+
)
|
|
60
|
+
).toBe(false)
|
|
61
|
+
})
|
|
62
|
+
|
|
63
|
+
it('disables markers for single-series small-multiple tiles even when the source legend is visible', () => {
|
|
64
|
+
expect(
|
|
65
|
+
shouldUseTooltipLegendMarkers(
|
|
66
|
+
buildConfig({
|
|
67
|
+
legend: { hide: true, tooltipLegendVisible: true },
|
|
68
|
+
runtime: { seriesLabelsAll: ['Cases'], seriesKeys: ['cases'] },
|
|
69
|
+
series: [{ dataKey: 'cases' }]
|
|
70
|
+
})
|
|
71
|
+
)
|
|
72
|
+
).toBe(false)
|
|
73
|
+
})
|
|
74
|
+
|
|
75
|
+
it('keeps patterned legends eligible and still uses the base series color', () => {
|
|
76
|
+
expect(shouldUseTooltipLegendMarkers(buildConfig({ legend: { patterns: { striped: { color: '#000' } } } }))).toBe(
|
|
77
|
+
true
|
|
78
|
+
)
|
|
79
|
+
})
|
|
80
|
+
|
|
81
|
+
it('resolves marker colors using the legend label mapping', () => {
|
|
82
|
+
const colorScale = value => ({ Cases: '#1f77b4', deaths: '#d62728' }[value] || null)
|
|
83
|
+
|
|
84
|
+
expect(getTooltipMarkerColor(buildConfig(), colorScale as any, 'cases')).toBe('#1f77b4')
|
|
85
|
+
expect(getTooltipMarkerColor(buildConfig(), colorScale as any, 'unknown')).toBe(null)
|
|
86
|
+
})
|
|
87
|
+
|
|
88
|
+
it('uses square markers only when the legend explicitly uses boxes', () => {
|
|
89
|
+
expect(getTooltipMarkerShape(buildConfig())).toBe('circle')
|
|
90
|
+
expect(getTooltipMarkerShape(buildConfig({ legend: { style: 'boxes' } }))).toBe('square')
|
|
91
|
+
})
|
|
92
|
+
|
|
93
|
+
it('only reserves a marker column when at least one series row has a marker', () => {
|
|
94
|
+
const rows: TooltipRow[] = [
|
|
95
|
+
{ key: 'Date', value: '2024-01-01', kind: 'heading' },
|
|
96
|
+
{ key: 'Cases', value: '10', kind: 'series', markerColor: '#1f77b4' },
|
|
97
|
+
{ key: 'Source', value: 'CDC', kind: 'extra' }
|
|
98
|
+
]
|
|
99
|
+
const rowsWithoutMarkers: TooltipRow[] = [
|
|
100
|
+
{ key: 'Date', value: '2024-01-01', kind: 'heading' },
|
|
101
|
+
{ key: 'Source', value: 'CDC', kind: 'extra' }
|
|
102
|
+
]
|
|
103
|
+
|
|
104
|
+
expect(tooltipHasMarkerColumn(rows)).toBe(true)
|
|
105
|
+
expect(tooltipHasMarkerColumn(rowsWithoutMarkers)).toBe(false)
|
|
106
|
+
})
|
|
107
|
+
|
|
108
|
+
it('suppresses the marker column when two series would render identical marker signatures', () => {
|
|
109
|
+
expect(
|
|
110
|
+
tooltipShouldUseMarkerColumn([
|
|
111
|
+
{ markerColor: '#f28e2b', markerShape: 'circle' },
|
|
112
|
+
{ markerColor: '#f28e2b', markerShape: 'circle' }
|
|
113
|
+
])
|
|
114
|
+
).toBe(false)
|
|
115
|
+
|
|
116
|
+
expect(
|
|
117
|
+
tooltipShouldUseMarkerColumn([
|
|
118
|
+
{ markerColor: '#f28e2b', markerShape: 'circle' },
|
|
119
|
+
{ markerColor: '#f28e2b', markerShape: 'square' }
|
|
120
|
+
])
|
|
121
|
+
).toBe(true)
|
|
122
|
+
})
|
|
123
|
+
|
|
124
|
+
it('renders aligned raw HTML rows only when the tooltip contains a marker', () => {
|
|
125
|
+
const withMarker = buildTooltipListHtml({
|
|
126
|
+
heading: 'Date: Jan 1, 2024',
|
|
127
|
+
bodyRows: [{ text: 'Cases: 10', markerColor: '#1f77b4', markerShape: 'square' }, { text: 'Source: CDC' }]
|
|
128
|
+
})
|
|
129
|
+
const withoutMarker = buildTooltipListHtml({
|
|
130
|
+
heading: 'Date: Jan 1, 2024',
|
|
131
|
+
bodyRows: [{ text: 'Source: CDC' }]
|
|
132
|
+
})
|
|
133
|
+
const ambiguousMarkers = buildTooltipListHtml({
|
|
134
|
+
heading: 'Date: Jan 1, 2024',
|
|
135
|
+
bodyRows: [
|
|
136
|
+
{ text: 'Dose 1: 10', markerColor: '#f28e2b', markerShape: 'circle' },
|
|
137
|
+
{ text: 'Dose 2: 20', markerColor: '#f28e2b', markerShape: 'circle' }
|
|
138
|
+
]
|
|
139
|
+
})
|
|
140
|
+
|
|
141
|
+
expect(withMarker).toContain('tooltip-body--marker-layout')
|
|
142
|
+
expect(withMarker).toContain('tooltip-marker-slot')
|
|
143
|
+
expect(withMarker).toContain('tooltip-marker-slot" aria-hidden="true"')
|
|
144
|
+
expect(withMarker).toContain('tooltip-marker-swatch')
|
|
145
|
+
expect(withMarker).toContain('tooltip-marker-swatch--square')
|
|
146
|
+
expect(withoutMarker).not.toContain('tooltip-body--marker-layout')
|
|
147
|
+
expect(withoutMarker).not.toContain('tooltip-marker-slot')
|
|
148
|
+
expect(ambiguousMarkers).not.toContain('tooltip-body--marker-layout')
|
|
149
|
+
expect(ambiguousMarkers).not.toContain('tooltip-marker-slot')
|
|
150
|
+
})
|
|
151
|
+
})
|
|
@@ -0,0 +1,176 @@
|
|
|
1
|
+
import { type ChartConfig } from '../types/ChartConfig'
|
|
2
|
+
import { type ColorScale } from '../types/ChartContext'
|
|
3
|
+
|
|
4
|
+
export type TooltipRowKind = 'heading' | 'series' | 'extra'
|
|
5
|
+
|
|
6
|
+
export type TooltipRow = {
|
|
7
|
+
key: string
|
|
8
|
+
value: string | number
|
|
9
|
+
axisPosition?: string
|
|
10
|
+
kind: TooltipRowKind
|
|
11
|
+
markerColor?: string | null
|
|
12
|
+
markerShape?: 'circle' | 'square'
|
|
13
|
+
seriesKey?: string
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
export type TooltipDisplayData = {
|
|
17
|
+
data: TooltipRow[]
|
|
18
|
+
dataXPosition: number
|
|
19
|
+
dataYPosition: number
|
|
20
|
+
useMarkerColumn: boolean
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
type RawTooltipBodyRow = {
|
|
24
|
+
text: string
|
|
25
|
+
markerColor?: string | null
|
|
26
|
+
markerShape?: 'circle' | 'square'
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
export type TooltipMarker = {
|
|
30
|
+
markerColor: string
|
|
31
|
+
markerShape: 'circle' | 'square'
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
const TOOLTIP_MARKER_SUPPORTED_TYPES = new Set(['Line', 'Area Chart', 'Combo', 'Bar', 'Scatter Plot'])
|
|
35
|
+
|
|
36
|
+
export const getTooltipMarkerShape = (config: ChartConfig): 'circle' | 'square' =>
|
|
37
|
+
config.legend?.style === 'boxes' ? 'square' : 'circle'
|
|
38
|
+
|
|
39
|
+
export const shouldUseTooltipLegendMarkers = (config: ChartConfig): boolean => {
|
|
40
|
+
const legendItemCount =
|
|
41
|
+
config.runtime?.seriesLabelsAll?.length || config.runtime?.seriesKeys?.length || config.series?.length || 0
|
|
42
|
+
const sourceLegendVisible = config.legend?.tooltipLegendVisible ?? !config.legend?.hide
|
|
43
|
+
|
|
44
|
+
return (
|
|
45
|
+
TOOLTIP_MARKER_SUPPORTED_TYPES.has(config.visualizationType) &&
|
|
46
|
+
legendItemCount > 1 &&
|
|
47
|
+
sourceLegendVisible &&
|
|
48
|
+
config.legend?.style !== 'gradient' &&
|
|
49
|
+
!config.legend?.groupBy
|
|
50
|
+
)
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
export const getTooltipMarkerColor = (
|
|
54
|
+
config: ChartConfig,
|
|
55
|
+
colorScale: ColorScale | undefined,
|
|
56
|
+
seriesKey?: string
|
|
57
|
+
): string | null => {
|
|
58
|
+
if (!colorScale || !seriesKey) return null
|
|
59
|
+
|
|
60
|
+
const colorInput = config.runtime?.seriesLabels?.[seriesKey] || seriesKey
|
|
61
|
+
const color = colorScale(colorInput)
|
|
62
|
+
return typeof color === 'string' ? color : null
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
export const getTooltipSeriesMarker = (
|
|
66
|
+
config: ChartConfig,
|
|
67
|
+
colorScale: ColorScale | undefined,
|
|
68
|
+
seriesKey?: string
|
|
69
|
+
): TooltipMarker | null => {
|
|
70
|
+
if (!shouldUseTooltipLegendMarkers(config)) {
|
|
71
|
+
return null
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
const markerColor = getTooltipMarkerColor(config, colorScale, seriesKey)
|
|
75
|
+
if (!markerColor) {
|
|
76
|
+
return null
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
return {
|
|
80
|
+
markerColor,
|
|
81
|
+
markerShape: getTooltipMarkerShape(config)
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
export const buildTooltipRow = (row: TooltipRow): TooltipRow => row
|
|
86
|
+
|
|
87
|
+
const getMarkerSignature = ({
|
|
88
|
+
markerColor,
|
|
89
|
+
markerShape = 'circle'
|
|
90
|
+
}: {
|
|
91
|
+
markerColor?: string | null
|
|
92
|
+
markerShape?: 'circle' | 'square'
|
|
93
|
+
}): string | null => {
|
|
94
|
+
if (!markerColor) return null
|
|
95
|
+
return `${markerColor}::${markerShape}`
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
export const tooltipShouldUseMarkerColumn = (
|
|
99
|
+
rows: Array<{ markerColor?: string | null; markerShape?: 'circle' | 'square' }>
|
|
100
|
+
): boolean => {
|
|
101
|
+
const signatures = rows.map(getMarkerSignature).filter(Boolean)
|
|
102
|
+
|
|
103
|
+
if (signatures.length === 0) {
|
|
104
|
+
return false
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
return new Set(signatures).size === signatures.length
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
export const tooltipHasMarkerColumn = (rows: TooltipRow[]): boolean =>
|
|
111
|
+
tooltipShouldUseMarkerColumn(rows.filter(row => row.kind === 'series'))
|
|
112
|
+
|
|
113
|
+
export const buildTooltipBodyRowHtml = ({
|
|
114
|
+
text,
|
|
115
|
+
markerColor,
|
|
116
|
+
markerShape = 'circle',
|
|
117
|
+
useMarkerColumn
|
|
118
|
+
}: RawTooltipBodyRow & {
|
|
119
|
+
useMarkerColumn: boolean
|
|
120
|
+
}): string => {
|
|
121
|
+
if (!useMarkerColumn) {
|
|
122
|
+
return `<li class="tooltip-body">${text}</li>`
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
const marker = markerColor
|
|
126
|
+
? `<span class="tooltip-marker-swatch tooltip-marker-swatch--${markerShape}" style="background-color: ${markerColor};" aria-hidden="true"></span>`
|
|
127
|
+
: ''
|
|
128
|
+
|
|
129
|
+
return `<li class="tooltip-body tooltip-body--marker-layout"><span class="tooltip-marker-slot" aria-hidden="true">${marker}</span><span class="tooltip-body-content">${text}</span></li>`
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
export const buildTooltipListHtml = ({
|
|
133
|
+
heading,
|
|
134
|
+
bodyRows
|
|
135
|
+
}: {
|
|
136
|
+
heading: string
|
|
137
|
+
bodyRows: RawTooltipBodyRow[]
|
|
138
|
+
}): string => {
|
|
139
|
+
const hasMarkerColumn = tooltipShouldUseMarkerColumn(bodyRows)
|
|
140
|
+
const renderedRows = bodyRows
|
|
141
|
+
.filter(row => row.text !== undefined && row.text !== null && row.text !== '')
|
|
142
|
+
.map(row => buildTooltipBodyRowHtml({ ...row, useMarkerColumn: hasMarkerColumn }))
|
|
143
|
+
.join('')
|
|
144
|
+
|
|
145
|
+
return `<ul><li class="tooltip-heading">${heading}</li>${renderedRows}</ul>`
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
export const buildSeriesTooltipListHtml = ({
|
|
149
|
+
config,
|
|
150
|
+
colorScale,
|
|
151
|
+
heading,
|
|
152
|
+
seriesKey,
|
|
153
|
+
seriesText,
|
|
154
|
+
extraRows = []
|
|
155
|
+
}: {
|
|
156
|
+
config: ChartConfig
|
|
157
|
+
colorScale?: ColorScale
|
|
158
|
+
heading: string
|
|
159
|
+
seriesKey?: string
|
|
160
|
+
seriesText: string
|
|
161
|
+
extraRows?: string[]
|
|
162
|
+
}): string => {
|
|
163
|
+
const marker = getTooltipSeriesMarker(config, colorScale, seriesKey)
|
|
164
|
+
|
|
165
|
+
return buildTooltipListHtml({
|
|
166
|
+
heading,
|
|
167
|
+
bodyRows: [
|
|
168
|
+
{
|
|
169
|
+
text: seriesText,
|
|
170
|
+
markerColor: marker?.markerColor,
|
|
171
|
+
markerShape: marker?.markerShape
|
|
172
|
+
},
|
|
173
|
+
...extraRows.filter(Boolean).map(text => ({ text }))
|
|
174
|
+
]
|
|
175
|
+
})
|
|
176
|
+
}
|
|
@@ -5,21 +5,14 @@ import { ChartConfig } from '../types/ChartConfig'
|
|
|
5
5
|
export const useHighlightedBars = (config: ChartConfig, updateConfig: (config) => void) => {
|
|
6
6
|
const { formatDate, parseDate } = useContext(ConfigContext)
|
|
7
7
|
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
highlightedBarValues = config.highlightedBarValues
|
|
17
|
-
highlightedSeriesValues = config.data.map(item => item[config.xAxis.dataKey])
|
|
18
|
-
} else {
|
|
19
|
-
highlightedSeries = [] // only allow single series for highlights
|
|
20
|
-
highlightedSeriesKey = ''
|
|
21
|
-
highlightedBarValues = []
|
|
22
|
-
highlightedSeriesValues = []
|
|
8
|
+
const highlightedSeries = config.series?.length > 0 && config.data ? config.series[0] : [] // only allow single series for highlights
|
|
9
|
+
const highlightedSeriesKey = config.series?.length > 0 && config.data ? config.series[0].dataKey : ''
|
|
10
|
+
const highlightedBarValues = config.series?.length > 0 && config.data ? config.highlightedBarValues : []
|
|
11
|
+
|
|
12
|
+
const getHighlightedSeriesValues = () => {
|
|
13
|
+
if (!(config.series?.length > 0 && config.data)) return []
|
|
14
|
+
|
|
15
|
+
return config.data.map(item => item[config.xAxis.dataKey])
|
|
23
16
|
}
|
|
24
17
|
|
|
25
18
|
const handleUpdateHighlightedBorderWidth = (e, index) => {
|
|
@@ -144,7 +137,7 @@ export const useHighlightedBars = (config: ChartConfig, updateConfig: (config) =
|
|
|
144
137
|
highlightedSeries,
|
|
145
138
|
highlightedSeriesKey,
|
|
146
139
|
highlightedBarValues,
|
|
147
|
-
|
|
140
|
+
getHighlightedSeriesValues,
|
|
148
141
|
handleUpdateHighlightedBar,
|
|
149
142
|
handleAddNewHighlightedBar,
|
|
150
143
|
handleRemoveHighlightedBar,
|
|
@@ -3,7 +3,9 @@ import { useRef, useImperativeHandle, ForwardedRef } from 'react'
|
|
|
3
3
|
interface UseProgrammaticTooltipProps {
|
|
4
4
|
svgRef: ForwardedRef<SVGAElement>
|
|
5
5
|
getCoordinateFromXValue: (xAxisValue: any) => number
|
|
6
|
+
getXValueFromCoordinate: (xCoordinate: number) => any
|
|
6
7
|
config: any
|
|
8
|
+
yAxisWidth: number
|
|
7
9
|
setPoint: (point: { x: number; y: number }) => void
|
|
8
10
|
setShowHoverLine: (show: boolean) => void
|
|
9
11
|
handleTooltipMouseOver: (event: MouseEvent, additionalChartData?: any) => void
|
|
@@ -18,7 +20,9 @@ interface UseProgrammaticTooltipProps {
|
|
|
18
20
|
export const useProgrammaticTooltip = ({
|
|
19
21
|
svgRef,
|
|
20
22
|
getCoordinateFromXValue,
|
|
23
|
+
getXValueFromCoordinate,
|
|
21
24
|
config,
|
|
25
|
+
yAxisWidth,
|
|
22
26
|
setPoint,
|
|
23
27
|
setShowHoverLine,
|
|
24
28
|
handleTooltipMouseOver,
|
|
@@ -62,7 +66,16 @@ export const useProgrammaticTooltip = ({
|
|
|
62
66
|
}
|
|
63
67
|
|
|
64
68
|
const pixelX = getCoordinateFromXValue(xAxisValue)
|
|
65
|
-
const
|
|
69
|
+
const resolvedXValue = Number.isFinite(pixelX) ? getXValueFromCoordinate(pixelX) : null
|
|
70
|
+
|
|
71
|
+
if (!Number.isFinite(pixelX) || resolvedXValue !== xAxisValue) {
|
|
72
|
+
hideTooltip()
|
|
73
|
+
setShowHoverLine(false)
|
|
74
|
+
setSynchronizedXValue?.(null)
|
|
75
|
+
return
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
const adjustedX = pixelX + yAxisWidth
|
|
66
79
|
|
|
67
80
|
const svgRect = internalSvgRef.current!.getBoundingClientRect()
|
|
68
81
|
|
|
@@ -103,7 +116,8 @@ export const useProgrammaticTooltip = ({
|
|
|
103
116
|
},
|
|
104
117
|
[
|
|
105
118
|
getCoordinateFromXValue,
|
|
106
|
-
|
|
119
|
+
getXValueFromCoordinate,
|
|
120
|
+
yAxisWidth,
|
|
107
121
|
config.visualizationType,
|
|
108
122
|
setPoint,
|
|
109
123
|
setShowHoverLine,
|
|
@@ -13,6 +13,7 @@ import ConfigContext from '../ConfigContext'
|
|
|
13
13
|
export const useSmallMultipleSynchronization = (
|
|
14
14
|
xMax: number,
|
|
15
15
|
yMax: number,
|
|
16
|
+
yAxisWidth: number,
|
|
16
17
|
getXValueFromCoordinate: (x: number) => any
|
|
17
18
|
) => {
|
|
18
19
|
const { config, handleSmallMultipleHover } = useContext(ConfigContext)
|
|
@@ -25,8 +26,6 @@ export const useSmallMultipleSynchronization = (
|
|
|
25
26
|
}
|
|
26
27
|
}
|
|
27
28
|
|
|
28
|
-
const yAxisSize = Number(config.yAxis.size || 0)
|
|
29
|
-
|
|
30
29
|
const onMouseMove = (event: any) => {
|
|
31
30
|
const svgRect = event.currentTarget.getBoundingClientRect()
|
|
32
31
|
const x = event.clientX - svgRect.left
|
|
@@ -34,10 +33,10 @@ export const useSmallMultipleSynchronization = (
|
|
|
34
33
|
|
|
35
34
|
// Only trigger synchronized tooltips when mouse is over the valid chart area
|
|
36
35
|
// (to the right of the Y-axis and within chart bounds)
|
|
37
|
-
const isOverChartArea = x >=
|
|
36
|
+
const isOverChartArea = x >= yAxisWidth && x <= yAxisWidth + xMax && y >= 0 && y <= yMax
|
|
38
37
|
|
|
39
38
|
if (isOverChartArea) {
|
|
40
|
-
const xAxisValue = getXValueFromCoordinate(x -
|
|
39
|
+
const xAxisValue = getXValueFromCoordinate(x - yAxisWidth)
|
|
41
40
|
if (xAxisValue !== null && xAxisValue !== undefined) {
|
|
42
41
|
handleSmallMultipleHover(xAxisValue, y)
|
|
43
42
|
return
|