@cdc/chart 4.26.5 → 4.26.7
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 +20 -14
- package/dist/{cdcchart-DQ00cQCm.es.js → cdcchart-BWVIUcAF.es.js} +5 -55
- package/dist/cdcchart.js +36554 -34320
- package/examples/feature/heatmap/average-age-categorical.json +1 -1
- package/examples/feature/heatmap/calendar-heatmap.json +1 -1
- package/examples/feature/heatmap/sparse-aggregation.json +1 -1
- package/examples/private/bad-y-axis.json +543 -0
- package/examples/private/bar-chart-negative-value.json +455 -0
- package/examples/private/flueview.json +22051 -0
- package/package.json +3 -11
- package/src/CdcChart.tsx +7 -6
- package/src/CdcChartComponent.tsx +71 -18
- package/src/_stories/Chart.PieLabels.stories.tsx +47 -0
- package/src/_stories/Chart.SeriesColorAssignments.stories.tsx +183 -0
- package/src/_stories/Chart.YAxisAutoMaxStrategy.stories.tsx +52 -0
- package/src/_stories/Chart.YAxisFilterDomain.stories.tsx +90 -0
- package/src/_stories/Chart.smoke.stories.tsx +14 -1
- package/src/_stories/ChartAnnotation.smoke.stories.tsx +10 -0
- package/src/_stories/ChartPrefixSuffix.smoke.stories.tsx +28 -1
- package/src/_stories/_mock/annotation_event-line_mock.json +1064 -0
- package/src/_stories/_mock/heatmap-average-age-categorical.json +1 -1
- package/src/_stories/_mock/heatmap.json +1 -1
- package/src/components/Annotations/components/AnnotationDraggable.styles.css +22 -1
- package/src/components/Annotations/components/AnnotationDraggable.tsx +219 -119
- package/src/components/Annotations/tests/AnnotationDraggable.test.tsx +198 -0
- package/src/components/Axis/LeftAxis.tsx +3 -0
- package/src/components/Axis/LeftAxisGridlines.tsx +8 -1
- package/src/components/Axis/RightAxis.tsx +3 -0
- package/src/components/Axis/tests/AxisTickValues.test.tsx +86 -0
- package/src/components/BarChart/components/BarChart.Horizontal.tsx +1 -2
- package/src/components/BarChart/components/BarChart.StackedHorizontal.tsx +1 -1
- package/src/components/BarChart/components/BarChart.StackedVertical.tsx +1 -1
- package/src/components/BarChart/components/BarChart.Vertical.tsx +1 -2
- package/src/components/BarChart/helpers/getBarData.ts +28 -28
- package/src/components/BarChart/helpers/getBarHeights.ts +2 -2
- package/src/components/BarChart/helpers/tests/useBarChart.test.tsx +98 -0
- package/src/components/BarChart/helpers/useBarChart.ts +4 -5
- package/src/components/BoxPlot/BoxPlot.Horizontal.tsx +7 -6
- package/src/components/BoxPlot/BoxPlot.Vertical.tsx +9 -7
- package/src/components/BoxPlot/helpers/index.ts +4 -3
- package/src/components/Brush/BrushSelector.tsx +1 -1
- package/src/components/EditorPanel/EditorPanel.tsx +457 -165
- package/src/components/EditorPanel/components/Panels/Panel.Annotate.tsx +101 -52
- package/src/components/EditorPanel/components/Panels/Panel.General.tsx +16 -7
- package/src/components/EditorPanel/components/Panels/Panel.PatternSettings.tsx +3 -3
- package/src/components/EditorPanel/components/Panels/Panel.Series.tsx +19 -4
- package/src/components/EditorPanel/components/Panels/Panel.SmallMultiples.test.tsx +98 -0
- package/src/components/EditorPanel/components/Panels/Panel.SmallMultiples.tsx +10 -0
- package/src/components/EditorPanel/components/Panels/Panel.Visual.test.tsx +484 -0
- package/src/components/EditorPanel/components/Panels/Panel.Visual.tsx +3 -0
- package/src/components/EditorPanel/components/Panels/SeriesColorAssignments.tsx +262 -0
- package/src/components/EditorPanel/components/Panels/panelVisual.styles.css +32 -0
- package/src/components/EditorPanel/components/panels.scss +10 -0
- package/src/components/EditorPanel/editor-panel.scss +47 -0
- package/src/components/EditorPanel/helpers/getVisualizationTypeConfigUpdate.ts +6 -2
- package/src/components/EditorPanel/helpers/tests/getVisualizationTypeConfigUpdate.test.ts +20 -2
- package/src/components/EditorPanel/helpers/updateFieldRankByValue.ts +3 -3
- package/src/components/EditorPanel/useEditorPermissions.test.tsx +288 -11
- package/src/components/EditorPanel/useEditorPermissions.ts +46 -6
- package/src/components/Forecasting/Forecasting.tsx +1 -1
- package/src/components/HeatMap/components/HeatMap.tsx +22 -17
- package/src/components/HeatMap/components/HeatMapGradientLegend.tsx +29 -15
- package/src/components/HeatMap/heatmap.css +15 -1
- package/src/components/HeatMap/tests/HeatMap.test.tsx +39 -7
- package/src/components/Legend/Legend.Component.tsx +56 -61
- package/src/components/Legend/Legend.Suppression.tsx +3 -3
- package/src/components/Legend/LegendGroup/LegendGroup.tsx +0 -1
- package/src/components/Legend/helpers/createFormatLabels.test.ts +88 -0
- package/src/components/Legend/helpers/createFormatLabels.tsx +6 -1
- package/src/components/LineChart/helpers.test.ts +230 -1
- package/src/components/LineChart/helpers.ts +25 -14
- package/src/components/LineChart/index.tsx +23 -7
- package/src/components/LinearChart/tests/LinearChart.test.tsx +30 -0
- package/src/components/LinearChart.tsx +51 -15
- package/src/components/PieChart/PieChart.tsx +117 -10
- package/src/components/PieChart/helpers/labelPlacement.test.ts +88 -0
- package/src/components/PieChart/helpers/labelPlacement.ts +95 -0
- package/src/components/ScatterPlot/ScatterPlot.jsx +5 -13
- package/src/components/SmallMultiples/SmallMultipleTile.tsx +3 -0
- package/src/components/SmallMultiples/SmallMultiples.tsx +38 -9
- package/src/components/SmallMultiples/tests/SmallMultiples.test.tsx +170 -0
- package/src/data/initial-state.js +3 -0
- package/src/helpers/calculateLeftYAxisWidth.ts +9 -3
- package/src/helpers/categoryOrder.ts +60 -0
- package/src/helpers/colorAssignmentHelpers.ts +102 -0
- package/src/helpers/dataHelpers.ts +11 -1
- package/src/helpers/getAxisMaxOverride.ts +16 -0
- package/src/helpers/getBoxPlotConfig.ts +19 -12
- package/src/helpers/getCleanTopTickMax.ts +64 -0
- package/src/helpers/getColorScale.ts +18 -9
- package/src/helpers/getComboChartConfig.ts +0 -1
- package/src/helpers/getExcludedData.ts +0 -1
- package/src/helpers/getHasBoundarySuppression.ts +27 -0
- package/src/helpers/getMinMax.ts +25 -22
- package/src/helpers/getNewRuntime.ts +0 -2
- package/src/helpers/getPiePercent.ts +3 -3
- package/src/helpers/getYAxisDomainData.ts +38 -0
- package/src/helpers/getYAxisFinalizationEligibility.ts +40 -0
- package/src/helpers/hasSpacedInlineLabel.ts +10 -0
- package/src/helpers/isConvertLineToBarGraph.ts +2 -2
- package/src/helpers/seriesColumnSettings.ts +26 -7
- package/src/helpers/sizeHelpers.ts +0 -2
- package/src/helpers/smallMultiplesHelpers.ts +5 -0
- package/src/helpers/tests/calculateLeftYAxisWidth.test.ts +38 -0
- package/src/helpers/tests/categoryOrder.test.ts +57 -0
- package/src/helpers/tests/dataHelpers.test.ts +135 -0
- package/src/helpers/tests/getAxisMaxOverride.test.ts +36 -0
- package/src/helpers/tests/getCleanTopTickMax.test.ts +165 -0
- package/src/helpers/tests/getColorScale.test.ts +197 -0
- package/src/helpers/tests/getMinMax.test.ts +211 -0
- package/src/helpers/tests/getYAxisDomainData.test.ts +119 -0
- package/src/helpers/tests/getYAxisFinalizationEligibility.test.ts +185 -0
- package/src/helpers/tests/{getYAxisAutoPaddingMode.test.ts → hasSpacedInlineLabel.test.ts} +17 -32
- package/src/helpers/tests/seriesColumnSettings.test.ts +76 -2
- package/src/helpers/tests/smallMultiplesHelpers.test.ts +99 -0
- package/src/hooks/tests/useRightAxis.test.tsx +268 -0
- package/src/hooks/tests/useScales.test.tsx +302 -27
- package/src/hooks/tests/useTooltip.test.tsx +132 -0
- package/src/hooks/useRightAxis.ts +35 -25
- package/src/hooks/useScales.ts +45 -28
- package/src/hooks/useTooltip.tsx +17 -11
- package/src/store/chart.reducer.ts +0 -1
- package/src/test/CdcChart.dataTable.test.tsx +216 -0
- package/src/types/ChartConfig.ts +6 -1
- package/src/types/ChartContext.ts +3 -0
- package/src/helpers/getYAxisAutoPadding.ts +0 -55
- package/src/helpers/getYAxisAutoPaddingMode.ts +0 -17
- package/src/helpers/tests/getYAxisAutoPadding.test.ts +0 -37
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
import { DataItem, StyleProps, Style } from './LineChartProps'
|
|
2
2
|
import { PreliminaryDataItem } from '../../types/ChartConfig'
|
|
3
3
|
|
|
4
|
-
|
|
4
|
+
const isCalculable = value => !isNaN(parseFloat(value)) && isFinite(value)
|
|
5
|
+
|
|
5
6
|
export const createStyles = (props: StyleProps): Style[] => {
|
|
6
7
|
const {
|
|
7
8
|
preliminaryData,
|
|
@@ -42,24 +43,29 @@ export const createStyles = (props: StyleProps): Style[] => {
|
|
|
42
43
|
validPreliminaryData.find(pd => isEffectLine(pd, point))
|
|
43
44
|
|
|
44
45
|
const styles: Style[] = []
|
|
45
|
-
const createStyle = (lineStyle): Style =>
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
46
|
+
const createStyle = (lineStyle, customWeight?: number | string): Style => {
|
|
47
|
+
const parsedWeight = Number(customWeight)
|
|
48
|
+
const hasValidWeight =
|
|
49
|
+
customWeight !== '' && customWeight != null && Number.isFinite(parsedWeight) && parsedWeight >= 1
|
|
50
|
+
return {
|
|
51
|
+
stroke: stroke,
|
|
52
|
+
strokeWidth: hasValidWeight ? parsedWeight : strokeWidth,
|
|
53
|
+
strokeDasharray: lineStyle
|
|
54
|
+
}
|
|
55
|
+
}
|
|
50
56
|
|
|
51
57
|
data.forEach((d, index) => {
|
|
52
58
|
const matchingPd: PreliminaryDataItem = getMatchingPd(d)
|
|
53
59
|
|
|
54
60
|
let style: Style = matchingPd
|
|
55
|
-
? createStyle(handleLineType(matchingPd.style))
|
|
61
|
+
? createStyle(handleLineType(matchingPd.style), matchingPd.weight)
|
|
56
62
|
: createStyle(handleLineType(lineType))
|
|
57
63
|
|
|
58
64
|
styles.push(style)
|
|
59
65
|
|
|
60
66
|
// If matchingPd exists, update the previous style if there is a previous element
|
|
61
67
|
if (matchingPd && index > 0) {
|
|
62
|
-
styles[index - 1] = createStyle(handleLineType(matchingPd.style))
|
|
68
|
+
styles[index - 1] = createStyle(handleLineType(matchingPd.style), matchingPd.weight)
|
|
63
69
|
}
|
|
64
70
|
})
|
|
65
71
|
return styles as Style[]
|
|
@@ -96,7 +102,7 @@ export const filterCircles = (
|
|
|
96
102
|
if (
|
|
97
103
|
item[fc.column] === fc.value &&
|
|
98
104
|
fc.seriesKeys.includes(seriesKey) &&
|
|
99
|
-
item[valueKey] &&
|
|
105
|
+
isCalculable(item[valueKey]) &&
|
|
100
106
|
fc.style === 'Open Circles'
|
|
101
107
|
) {
|
|
102
108
|
const result = {
|
|
@@ -109,7 +115,7 @@ export const filterCircles = (
|
|
|
109
115
|
if (
|
|
110
116
|
(!fc.value || item[fc.column] === fc.value) &&
|
|
111
117
|
fc.seriesKeys.includes(seriesKey) &&
|
|
112
|
-
item[valueKey] &&
|
|
118
|
+
isCalculable(item[valueKey]) &&
|
|
113
119
|
fc.style === 'Filled Circles'
|
|
114
120
|
) {
|
|
115
121
|
const result = {
|
|
@@ -125,7 +131,6 @@ export const filterCircles = (
|
|
|
125
131
|
return filteredData
|
|
126
132
|
}
|
|
127
133
|
|
|
128
|
-
const isCalculable = value => !isNaN(parseFloat(value)) && isFinite(value)
|
|
129
134
|
const handleFirstIndex = ({
|
|
130
135
|
data,
|
|
131
136
|
seriesKey,
|
|
@@ -139,7 +144,8 @@ const handleFirstIndex = ({
|
|
|
139
144
|
const result = {
|
|
140
145
|
data: { '0': [] },
|
|
141
146
|
style: '',
|
|
142
|
-
color: ''
|
|
147
|
+
color: '',
|
|
148
|
+
weight: undefined
|
|
143
149
|
}
|
|
144
150
|
|
|
145
151
|
// If data is empty, return the empty result
|
|
@@ -160,6 +166,7 @@ const handleFirstIndex = ({
|
|
|
160
166
|
|
|
161
167
|
result.data[pairCount].push(modifiedItem)
|
|
162
168
|
result.style = suppressionData.style
|
|
169
|
+
result.weight = suppressionData.weight
|
|
163
170
|
result.color = dynamicCategory && modifiedItem ? colorScale(modifiedItem[dynamicCategory]) : ''
|
|
164
171
|
|
|
165
172
|
// Find the next calculable item index
|
|
@@ -190,7 +197,8 @@ const handleLastIndex = ({
|
|
|
190
197
|
const result = {
|
|
191
198
|
data: { '0': [] },
|
|
192
199
|
style: '',
|
|
193
|
-
color: ''
|
|
200
|
+
color: '',
|
|
201
|
+
weight: undefined
|
|
194
202
|
}
|
|
195
203
|
const lastIndexDataItem = data[data.length - 1]
|
|
196
204
|
|
|
@@ -213,6 +221,7 @@ const handleLastIndex = ({
|
|
|
213
221
|
lastAddedIndex = prevIndex
|
|
214
222
|
}
|
|
215
223
|
result.style = pd.style
|
|
224
|
+
result.weight = pd.weight
|
|
216
225
|
result.color = colorScale(modifiedItem[dynamicCategory])
|
|
217
226
|
}
|
|
218
227
|
})
|
|
@@ -232,7 +241,8 @@ const handleMiddleIndices = ({
|
|
|
232
241
|
let result = {
|
|
233
242
|
data: {},
|
|
234
243
|
style: '',
|
|
235
|
-
color: 'red'
|
|
244
|
+
color: 'red',
|
|
245
|
+
weight: undefined
|
|
236
246
|
}
|
|
237
247
|
|
|
238
248
|
//skip processing if data or preliminaryData is not an array
|
|
@@ -270,6 +280,7 @@ const handleMiddleIndices = ({
|
|
|
270
280
|
// Only add siblings to results if both siblings are found
|
|
271
281
|
if (siblingBefore && siblingAfter) {
|
|
272
282
|
result.style = pd.style
|
|
283
|
+
result.weight = pd.weight
|
|
273
284
|
result.color = colorScale(item[dynamicCategory])
|
|
274
285
|
result.data[pairCount++] = [siblingBefore, siblingAfter]
|
|
275
286
|
}
|
|
@@ -18,6 +18,7 @@ import { filterCircles, createStyles, createDataSegments } from './helpers'
|
|
|
18
18
|
import { getLabelPositionForDataPoint } from './helpers/labelPositioning'
|
|
19
19
|
import LineChartCircle from './components/LineChart.Circle'
|
|
20
20
|
import LineChartBumpCircle from './components/LineChart.BumpCircle'
|
|
21
|
+
import { getYAxisDomainData } from '../../helpers/getYAxisDomainData'
|
|
21
22
|
import isNumber from '@cdc/core/helpers/isNumber'
|
|
22
23
|
|
|
23
24
|
// Types
|
|
@@ -39,9 +40,25 @@ const LineChart = (props: LineChartProps) => {
|
|
|
39
40
|
yAxisWidth,
|
|
40
41
|
} = props
|
|
41
42
|
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
43
|
+
const {
|
|
44
|
+
colorScale,
|
|
45
|
+
config,
|
|
46
|
+
formatNumber,
|
|
47
|
+
handleLineType,
|
|
48
|
+
parseDate,
|
|
49
|
+
seriesHighlight,
|
|
50
|
+
tableData,
|
|
51
|
+
transformedData: data,
|
|
52
|
+
updateConfig,
|
|
53
|
+
yAxisDomainData
|
|
54
|
+
} = useContext<ChartContext>(ConfigContext)
|
|
55
|
+
const dataForRightAxis = getYAxisDomainData({
|
|
56
|
+
config,
|
|
57
|
+
data,
|
|
58
|
+
tableData,
|
|
59
|
+
fullEligibleDomainData: yAxisDomainData
|
|
60
|
+
})
|
|
61
|
+
const { yScaleRight } = useRightAxis({ config, yMax, data: dataForRightAxis, updateConfig })
|
|
45
62
|
const showSingleSeries = config.tooltips.singleSeries
|
|
46
63
|
if (!handleTooltipMouseOver) return
|
|
47
64
|
|
|
@@ -302,7 +319,7 @@ const LineChart = (props: LineChartProps) => {
|
|
|
302
319
|
? segment.color
|
|
303
320
|
: colorScale(config.runtime.seriesLabels[seriesKey])
|
|
304
321
|
}
|
|
305
|
-
strokeWidth={seriesData
|
|
322
|
+
strokeWidth={Number(segment.weight) || seriesData.weight || 2}
|
|
306
323
|
strokeOpacity={1}
|
|
307
324
|
shapeRendering='geometricPrecision'
|
|
308
325
|
strokeDasharray={handleLineType(segment.style)}
|
|
@@ -464,9 +481,7 @@ const LineChart = (props: LineChartProps) => {
|
|
|
464
481
|
break
|
|
465
482
|
}
|
|
466
483
|
}
|
|
467
|
-
if (!lastDatum || legend.position === 'right')
|
|
468
|
-
return <></>
|
|
469
|
-
}
|
|
484
|
+
if (!lastDatum || legend.position === 'right') return null
|
|
470
485
|
|
|
471
486
|
let labelText = config.runtime.seriesLabels[seriesKey] || seriesKey
|
|
472
487
|
// truncate labels longer that 10 chars
|
|
@@ -477,6 +492,7 @@ const LineChart = (props: LineChartProps) => {
|
|
|
477
492
|
|
|
478
493
|
return (
|
|
479
494
|
<Text
|
|
495
|
+
key={seriesKey}
|
|
480
496
|
display={
|
|
481
497
|
legend.behavior === 'highlight' ||
|
|
482
498
|
(seriesHighlight.length === 0 && !legend.dynamicLegend) ||
|
|
@@ -5,6 +5,7 @@ import LinearChart from '../../LinearChart'
|
|
|
5
5
|
import ConfigContext from '../../../ConfigContext'
|
|
6
6
|
import { createMockChartContext } from './mockConfigContext'
|
|
7
7
|
import forestPlotConfig from '../../../../examples/feature/forest-plot/forest-plot.json'
|
|
8
|
+
import * as suppressionHelpers from '../../../helpers/getHasBoundarySuppression'
|
|
8
9
|
|
|
9
10
|
// Mock ResizeObserver
|
|
10
11
|
vi.stubGlobal(
|
|
@@ -88,6 +89,35 @@ const sidePlacementYAxis = {
|
|
|
88
89
|
}
|
|
89
90
|
|
|
90
91
|
describe('LinearChart', () => {
|
|
92
|
+
describe('suppression domain data source', () => {
|
|
93
|
+
it('uses excludedData fallback for suppression detection when Y-axis domain is stable and yAxisDomainData is not provided', () => {
|
|
94
|
+
const suppressionSpy = vi.spyOn(suppressionHelpers, 'getHasBoundarySuppression').mockReturnValue(false)
|
|
95
|
+
const excludedRows = [{ Date: '2020-01-01', Cases: 'SUPP' }]
|
|
96
|
+
const tableRows = [{ Date: '2020-01-01', Cases: 12 }]
|
|
97
|
+
|
|
98
|
+
renderLinearChart(
|
|
99
|
+
{
|
|
100
|
+
visualizationType: 'Line',
|
|
101
|
+
yAxis: {
|
|
102
|
+
...createMockChartContext().config.yAxis,
|
|
103
|
+
filterDomainBehavior: 'stable'
|
|
104
|
+
}
|
|
105
|
+
},
|
|
106
|
+
{
|
|
107
|
+
transformedData: tableRows as any,
|
|
108
|
+
tableData: tableRows as any,
|
|
109
|
+
excludedData: excludedRows as any,
|
|
110
|
+
yAxisDomainData: undefined
|
|
111
|
+
}
|
|
112
|
+
)
|
|
113
|
+
|
|
114
|
+
expect(suppressionSpy).toHaveBeenCalled()
|
|
115
|
+
expect(suppressionSpy.mock.calls[0][0].rows).toBe(excludedRows)
|
|
116
|
+
|
|
117
|
+
suppressionSpy.mockRestore()
|
|
118
|
+
})
|
|
119
|
+
})
|
|
120
|
+
|
|
91
121
|
describe('rendering', () => {
|
|
92
122
|
it('renders without crashing', () => {
|
|
93
123
|
const { container } = renderLinearChart()
|
|
@@ -37,7 +37,10 @@ import { calcInitialHeight } from '../helpers/sizeHelpers'
|
|
|
37
37
|
import { calculateHorizontalBarCategoryLabelWidth } from '../helpers/calculateHorizontalBarCategoryLabelWidth'
|
|
38
38
|
import { calculateLeftYAxisWidth } from '../helpers/calculateLeftYAxisWidth'
|
|
39
39
|
import { getAxisLabelFontSize } from '../helpers/axisLabelFontSize'
|
|
40
|
-
import {
|
|
40
|
+
import { hasSpacedInlineLabel } from '../helpers/hasSpacedInlineLabel'
|
|
41
|
+
import { getYAxisDomainData, getYAxisFilterDomainBehavior } from '../helpers/getYAxisDomainData'
|
|
42
|
+
import { getHasBoundarySuppression } from '../helpers/getHasBoundarySuppression'
|
|
43
|
+
import { getExcludedData } from '../helpers/getExcludedData'
|
|
41
44
|
|
|
42
45
|
// Hooks
|
|
43
46
|
import useReduceData from '../hooks/useReduceData'
|
|
@@ -117,8 +120,11 @@ const LinearChart = forwardRef<SVGAElement, LinearChartProps>(({ parentHeight, p
|
|
|
117
120
|
legendRef,
|
|
118
121
|
parseDate,
|
|
119
122
|
parentRef,
|
|
123
|
+
excludedData,
|
|
120
124
|
tableData,
|
|
121
125
|
transformedData: data,
|
|
126
|
+
yAxisTickValues: sharedYAxisTickValues,
|
|
127
|
+
yAxisDomainData
|
|
122
128
|
} = useContext(ConfigContext)
|
|
123
129
|
|
|
124
130
|
// SVG accessibility: title/desc pattern
|
|
@@ -131,8 +137,29 @@ const LinearChart = forwardRef<SVGAElement, LinearChartProps>(({ parentHeight, p
|
|
|
131
137
|
const { inlineLabel } = config.yAxis
|
|
132
138
|
|
|
133
139
|
// HOOKS % STATES
|
|
134
|
-
const
|
|
135
|
-
|
|
140
|
+
const dataForMinMax = getYAxisDomainData({
|
|
141
|
+
config,
|
|
142
|
+
data,
|
|
143
|
+
tableData,
|
|
144
|
+
fullEligibleDomainData: yAxisDomainData
|
|
145
|
+
})
|
|
146
|
+
const isStableYAxisDomain = getYAxisFilterDomainBehavior(config) === 'stable'
|
|
147
|
+
const rawEligibleYAxisDomainData =
|
|
148
|
+
Array.isArray(config.yAxisDomainData) && config.yAxisDomainData.length > 0
|
|
149
|
+
? getExcludedData(config, config.yAxisDomainData)
|
|
150
|
+
: isStableYAxisDomain && Array.isArray(excludedData)
|
|
151
|
+
? excludedData
|
|
152
|
+
: tableData
|
|
153
|
+
const suppressionDomainData = getYAxisDomainData({
|
|
154
|
+
config,
|
|
155
|
+
data: tableData,
|
|
156
|
+
tableData,
|
|
157
|
+
fullEligibleDomainData: rawEligibleYAxisDomainData
|
|
158
|
+
})
|
|
159
|
+
const hasBoundarySuppression = getHasBoundarySuppression({
|
|
160
|
+
rows: suppressionDomainData,
|
|
161
|
+
config
|
|
162
|
+
})
|
|
136
163
|
const { minValue, maxValue, existPositiveValue, isAllLine } = useReduceData(config, dataForMinMax)
|
|
137
164
|
|
|
138
165
|
const { visSupportsSmallMultiples } = useEditorPermissions()
|
|
@@ -177,7 +204,7 @@ const LinearChart = forwardRef<SVGAElement, LinearChartProps>(({ parentHeight, p
|
|
|
177
204
|
const isLogarithmicAxis = config.yAxis.type === 'logarithmic'
|
|
178
205
|
const isForestPlot = visualizationType === 'Forest Plot'
|
|
179
206
|
const isDateTime = config.xAxis.type === 'date-time'
|
|
180
|
-
const
|
|
207
|
+
const usesSpacedInlineLabel = hasSpacedInlineLabel(config)
|
|
181
208
|
const tickLabelFontSize = isMobileFontViewport(vizViewport) ? TICK_LABEL_FONT_SIZE_SMALL : TICK_LABEL_FONT_SIZE
|
|
182
209
|
const axisLabelFontSize = getAxisLabelFontSize(vizViewport)
|
|
183
210
|
const GET_TEXT_WIDTH_FONT = `normal ${tickLabelFontSize}px Nunito, sans-serif`
|
|
@@ -255,14 +282,14 @@ const LinearChart = forwardRef<SVGAElement, LinearChartProps>(({ parentHeight, p
|
|
|
255
282
|
return () => observer.disconnect()
|
|
256
283
|
}, [axisBottomRef.current])
|
|
257
284
|
|
|
258
|
-
const { yScaleRight, hasRightAxis } = useRightAxis({ config, yMax, data })
|
|
285
|
+
const { yScaleRight, hasRightAxis, rightTickValues } = useRightAxis({ config, yMax, data: dataForMinMax })
|
|
259
286
|
|
|
260
287
|
// State for computed left-axis width - shared across all linear-chart types.
|
|
261
288
|
const [currentYAxisWidth, setCurrentYAxisWidth] = useState<number>(DEFAULT_LEFT_Y_AXIS_WIDTH)
|
|
262
289
|
const yAxisWidth = currentYAxisWidth
|
|
263
290
|
|
|
264
291
|
// Chart width calculation using the current y-axis width
|
|
265
|
-
const xMax = parentWidth - yAxisWidth - (hasRightAxis ? config.yAxis.rightAxisSize : 0)
|
|
292
|
+
const xMax = Math.max(0, parentWidth - yAxisWidth - (hasRightAxis ? config.yAxis.rightAxisSize : 0))
|
|
266
293
|
|
|
267
294
|
// Stabilize brush container dimensions when brushDynamicYAxis is enabled.
|
|
268
295
|
// Without this, y-axis rescaling on each brush change creates a feedback loop:
|
|
@@ -289,10 +316,13 @@ const LinearChart = forwardRef<SVGAElement, LinearChartProps>(({ parentHeight, p
|
|
|
289
316
|
xScaleAnnotation,
|
|
290
317
|
yScaleAnnotation,
|
|
291
318
|
min,
|
|
292
|
-
max
|
|
319
|
+
max,
|
|
320
|
+
yTickValues
|
|
293
321
|
} = useScales({
|
|
294
322
|
data,
|
|
295
|
-
tableData,
|
|
323
|
+
tableData: dataForMinMax,
|
|
324
|
+
yAxisDomainData: dataForMinMax,
|
|
325
|
+
hasBoundarySuppression,
|
|
296
326
|
config,
|
|
297
327
|
minValue,
|
|
298
328
|
maxValue,
|
|
@@ -301,9 +331,10 @@ const LinearChart = forwardRef<SVGAElement, LinearChartProps>(({ parentHeight, p
|
|
|
301
331
|
xAxisDataMapped,
|
|
302
332
|
yMax,
|
|
303
333
|
xMax,
|
|
304
|
-
|
|
334
|
+
hasSpacedInlineLabel: usesSpacedInlineLabel,
|
|
305
335
|
currentViewport: vizViewport
|
|
306
336
|
})
|
|
337
|
+
const effectiveYTickValues = sharedYAxisTickValues ?? yTickValues
|
|
307
338
|
|
|
308
339
|
// Consolidated tick formatters
|
|
309
340
|
const { handleLeftTickFormatting, handleBottomTickFormatting } = useTickFormatters({
|
|
@@ -358,6 +389,7 @@ const LinearChart = forwardRef<SVGAElement, LinearChartProps>(({ parentHeight, p
|
|
|
358
389
|
data,
|
|
359
390
|
yScale,
|
|
360
391
|
numTicks: handleNumTicks,
|
|
392
|
+
tickValues: effectiveYTickValues,
|
|
361
393
|
parentWidth,
|
|
362
394
|
tickLabelFont: GET_TEXT_WIDTH_FONT,
|
|
363
395
|
axisLabelFontSize,
|
|
@@ -369,6 +401,7 @@ const LinearChart = forwardRef<SVGAElement, LinearChartProps>(({ parentHeight, p
|
|
|
369
401
|
config,
|
|
370
402
|
data,
|
|
371
403
|
yScale,
|
|
404
|
+
effectiveYTickValues,
|
|
372
405
|
handleNumTicks,
|
|
373
406
|
parentWidth,
|
|
374
407
|
isHorizontal,
|
|
@@ -596,6 +629,8 @@ const LinearChart = forwardRef<SVGAElement, LinearChartProps>(({ parentHeight, p
|
|
|
596
629
|
<SmallMultiples
|
|
597
630
|
config={config}
|
|
598
631
|
data={data}
|
|
632
|
+
yAxisDomainData={dataForMinMax}
|
|
633
|
+
hasBoundarySuppression={hasBoundarySuppression}
|
|
599
634
|
svgRef={svgRef}
|
|
600
635
|
parentWidth={parentWidth}
|
|
601
636
|
parentHeight={parentHeight}
|
|
@@ -617,9 +652,8 @@ const LinearChart = forwardRef<SVGAElement, LinearChartProps>(({ parentHeight, p
|
|
|
617
652
|
onMouseMove={onMouseMove}
|
|
618
653
|
width={parentWidth}
|
|
619
654
|
height={isNoDataAvailable ? 1 : calculatedSvgHeight ?? parentHeight}
|
|
620
|
-
className={`linear ${config.animate ? 'animated' : ''} ${animatedChart && config.animate ? 'animate' : ''} ${
|
|
621
|
-
|
|
622
|
-
} ${isDraggingAnnotation && 'dragging-annotation'}`}
|
|
655
|
+
className={`linear ${config.animate ? 'animated' : ''} ${animatedChart && config.animate ? 'animate' : ''} ${debugSvg && 'debug'
|
|
656
|
+
} ${isDraggingAnnotation && 'dragging-annotation'}`}
|
|
623
657
|
role='img'
|
|
624
658
|
aria-label={a11y}
|
|
625
659
|
style={{ overflow: 'visible' }}
|
|
@@ -642,6 +676,7 @@ const LinearChart = forwardRef<SVGAElement, LinearChartProps>(({ parentHeight, p
|
|
|
642
676
|
xMax={xMax}
|
|
643
677
|
yAxisWidth={yAxisWidth}
|
|
644
678
|
numTicks={handleNumTicks}
|
|
679
|
+
tickValues={effectiveYTickValues}
|
|
645
680
|
axisLabelFontSize={axisLabelFontSize}
|
|
646
681
|
/>
|
|
647
682
|
)}
|
|
@@ -838,6 +873,7 @@ const LinearChart = forwardRef<SVGAElement, LinearChartProps>(({ parentHeight, p
|
|
|
838
873
|
xMax={xMax}
|
|
839
874
|
yAxisWidth={yAxisWidth}
|
|
840
875
|
numTicks={handleNumTicks}
|
|
876
|
+
tickValues={effectiveYTickValues}
|
|
841
877
|
tickLabelFontSize={tickLabelFontSize}
|
|
842
878
|
axisLabelFontSize={axisLabelFontSize}
|
|
843
879
|
handleLeftTickFormatting={handleLeftTickFormatting}
|
|
@@ -863,6 +899,7 @@ const LinearChart = forwardRef<SVGAElement, LinearChartProps>(({ parentHeight, p
|
|
|
863
899
|
yMax={yMax}
|
|
864
900
|
xMax={xMax}
|
|
865
901
|
yAxisWidth={yAxisWidth}
|
|
902
|
+
tickValues={rightTickValues}
|
|
866
903
|
tickLabelFontSize={tickLabelFontSize}
|
|
867
904
|
axisLabelFontSize={axisLabelFontSize}
|
|
868
905
|
/>
|
|
@@ -914,9 +951,8 @@ const LinearChart = forwardRef<SVGAElement, LinearChartProps>(({ parentHeight, p
|
|
|
914
951
|
tooltipData.dataXPosition &&
|
|
915
952
|
!config.tooltips.singleSeries && (
|
|
916
953
|
<>
|
|
917
|
-
<style>{`.tooltip {background-color: rgba(255,255,255, ${
|
|
918
|
-
|
|
919
|
-
}) !important;`}</style>
|
|
954
|
+
<style>{`.tooltip {background-color: rgba(255,255,255, ${config.tooltips.opacity / 100
|
|
955
|
+
}) !important;`}</style>
|
|
920
956
|
<style>{`.tooltip {max-width:300px} !important; word-wrap: break-word; `}</style>
|
|
921
957
|
<TooltipWithBounds
|
|
922
958
|
ref={tooltipRef}
|
|
@@ -4,7 +4,6 @@ import { animated, useTransition, to } from '@react-spring/web'
|
|
|
4
4
|
// visx
|
|
5
5
|
import { Pie } from '@visx/shape'
|
|
6
6
|
import { Group } from '@visx/group'
|
|
7
|
-
import { Text } from '@visx/text'
|
|
8
7
|
import { useTooltip, TooltipWithBounds } from '@visx/tooltip'
|
|
9
8
|
import { colorPalettesChart as colorPalettes } from '@cdc/core/data/colorPalettes'
|
|
10
9
|
import { getPaletteColors } from '@cdc/core/helpers/palettes/utils'
|
|
@@ -25,6 +24,8 @@ import ErrorBoundary from '@cdc/core/components/ErrorBoundary'
|
|
|
25
24
|
import { scaleOrdinal } from '@visx/scale'
|
|
26
25
|
import { getContrastColor } from '@cdc/core/helpers/cove/accessibility'
|
|
27
26
|
import { type TooltipDisplayData } from '../../helpers/tooltipHelpers'
|
|
27
|
+
import { getTextWidth } from '@cdc/core/helpers/getTextWidth'
|
|
28
|
+
import { getPieLabelPosition } from './helpers/labelPlacement'
|
|
28
29
|
|
|
29
30
|
type TooltipData = TooltipDisplayData
|
|
30
31
|
|
|
@@ -34,6 +35,38 @@ type PieChartProps = {
|
|
|
34
35
|
interactionLabel?: string
|
|
35
36
|
}
|
|
36
37
|
|
|
38
|
+
const ENHANCED_PIE_LABEL_FONT_SIZE = 13
|
|
39
|
+
const ENHANCED_PIE_LABEL_LINE_HEIGHT = 15
|
|
40
|
+
const ENHANCED_PIE_LABEL_HEIGHT = ENHANCED_PIE_LABEL_LINE_HEIGHT * 2
|
|
41
|
+
const ENHANCED_PIE_LABEL_FONT = `${ENHANCED_PIE_LABEL_FONT_SIZE}px sans-serif`
|
|
42
|
+
const ENHANCED_PIE_LABEL_MIN_ANGLE = 0.01
|
|
43
|
+
const LEGACY_PERCENT_LABEL_MIN_ANGLE = 0.1
|
|
44
|
+
const MIN_RADIUS_WITH_LABELS = 55
|
|
45
|
+
const MIN_LABEL_GUTTER = 96
|
|
46
|
+
const OUTSIDE_LABEL_OFFSET = 22
|
|
47
|
+
const LABEL_GUTTER_PADDING = OUTSIDE_LABEL_OFFSET + 16
|
|
48
|
+
const VERTICAL_LABEL_GUTTER = 44
|
|
49
|
+
|
|
50
|
+
const pieLabelTextWidthCache = new Map<string, number>()
|
|
51
|
+
const MAX_PIE_LABEL_TEXT_CACHE = 2000
|
|
52
|
+
|
|
53
|
+
const measurePieLabelText = (text: string) => {
|
|
54
|
+
const cached = pieLabelTextWidthCache.get(text)
|
|
55
|
+
if (cached !== undefined) return cached
|
|
56
|
+
|
|
57
|
+
const measured =
|
|
58
|
+
typeof document === 'undefined'
|
|
59
|
+
? Math.ceil(text.length * ENHANCED_PIE_LABEL_FONT_SIZE * 0.6)
|
|
60
|
+
: (getTextWidth(text, ENHANCED_PIE_LABEL_FONT) ??
|
|
61
|
+
Math.ceil(text.length * ENHANCED_PIE_LABEL_FONT_SIZE * 0.6))
|
|
62
|
+
|
|
63
|
+
// Avoid unbounded growth in long-lived sessions with many unique labels.
|
|
64
|
+
if (pieLabelTextWidthCache.size >= MAX_PIE_LABEL_TEXT_CACHE) pieLabelTextWidthCache.clear()
|
|
65
|
+
|
|
66
|
+
pieLabelTextWidthCache.set(text, measured)
|
|
67
|
+
return measured
|
|
68
|
+
}
|
|
69
|
+
|
|
37
70
|
const PieChart = React.forwardRef<SVGSVGElement, PieChartProps>((props, ref) => {
|
|
38
71
|
const { interactionLabel = '' } = props
|
|
39
72
|
const {
|
|
@@ -70,6 +103,7 @@ const PieChart = React.forwardRef<SVGSVGElement, PieChartProps>((props, ref) =>
|
|
|
70
103
|
const pivotKey = dataNeedsPivot ? 'pivotColumn' : undefined
|
|
71
104
|
const showPercentage = config.dataFormat.showPiePercent
|
|
72
105
|
const labelForCalcArea = 'Calculated Area'
|
|
106
|
+
const showCategoryPercentageLabels = Boolean(config.labels)
|
|
73
107
|
|
|
74
108
|
const _data = useMemo(() => {
|
|
75
109
|
let baseData = []
|
|
@@ -269,9 +303,25 @@ const PieChart = React.forwardRef<SVGSVGElement, PieChartProps>((props, ref) =>
|
|
|
269
303
|
|
|
270
304
|
let roundedPercentage = percentageToDisplay.toFixed(roundTo) + '%'
|
|
271
305
|
// add missing pie part
|
|
272
|
-
if (arc.data[config.xAxis.dataKey] === labelForCalcArea && config.dataFormat.showPiePercent) {
|
|
306
|
+
if (arc.data[config.runtime.xAxis.dataKey] === labelForCalcArea && config.dataFormat.showPiePercent) {
|
|
273
307
|
roundedPercentage = '**'
|
|
274
308
|
}
|
|
309
|
+
let categoryLabel = ''
|
|
310
|
+
let labelPosition = { placement: 'inside' as const, x: 0, y: 0, textAnchor: 'middle' as const }
|
|
311
|
+
|
|
312
|
+
if (showCategoryPercentageLabels) {
|
|
313
|
+
categoryLabel = String(arc.data[config.runtime.xAxis.dataKey] ?? '')
|
|
314
|
+
const labelWidth = Math.max(measurePieLabelText(categoryLabel), measurePieLabelText(roundedPercentage))
|
|
315
|
+
labelPosition = getPieLabelPosition({
|
|
316
|
+
startAngle: arc.startAngle,
|
|
317
|
+
endAngle: arc.endAngle,
|
|
318
|
+
innerRadius: radius - donutThickness,
|
|
319
|
+
outerRadius: radius,
|
|
320
|
+
labelWidth,
|
|
321
|
+
labelHeight: ENHANCED_PIE_LABEL_HEIGHT,
|
|
322
|
+
isDonut: config.pieType === 'Donut'
|
|
323
|
+
})
|
|
324
|
+
}
|
|
275
325
|
|
|
276
326
|
// Determine if this slice should be muted based on legend behavior
|
|
277
327
|
const isHighlighted =
|
|
@@ -279,6 +329,8 @@ const PieChart = React.forwardRef<SVGSVGElement, PieChartProps>((props, ref) =>
|
|
|
279
329
|
const shouldMute = config.legend.behavior === 'highlight' && seriesHighlight.length > 0 && !isHighlighted
|
|
280
330
|
const sliceOpacity = shouldMute ? 0.3 : 1
|
|
281
331
|
const textOpacity = shouldMute ? 0.3 : 1
|
|
332
|
+
const labelFill = labelPosition.placement === 'inside' ? textColor : '#000000'
|
|
333
|
+
const labelMinAngle = showCategoryPercentageLabels ? ENHANCED_PIE_LABEL_MIN_ANGLE : LEGACY_PERCENT_LABEL_MIN_ANGLE
|
|
282
334
|
|
|
283
335
|
return (
|
|
284
336
|
<Group key={key} className={`slice-${CSS.escape(String(key))}`}>
|
|
@@ -302,7 +354,7 @@ const PieChart = React.forwardRef<SVGSVGElement, PieChartProps>((props, ref) =>
|
|
|
302
354
|
/>
|
|
303
355
|
|
|
304
356
|
{/* ── the percentage label */}
|
|
305
|
-
{arc.endAngle - arc.startAngle >
|
|
357
|
+
{arc.endAngle - arc.startAngle > labelMinAngle && !showCategoryPercentageLabels && (
|
|
306
358
|
<animated.text
|
|
307
359
|
transform={to([styles.startAngle, styles.endAngle], (start: number, end: number) => {
|
|
308
360
|
const [x, y] = path.centroid({
|
|
@@ -321,6 +373,37 @@ const PieChart = React.forwardRef<SVGSVGElement, PieChartProps>((props, ref) =>
|
|
|
321
373
|
{roundedPercentage}
|
|
322
374
|
</animated.text>
|
|
323
375
|
)}
|
|
376
|
+
|
|
377
|
+
{arc.endAngle - arc.startAngle > labelMinAngle && showCategoryPercentageLabels && (
|
|
378
|
+
<animated.text
|
|
379
|
+
className={`pie-label pie-label--${labelPosition.placement}`}
|
|
380
|
+
transform={to([styles.startAngle, styles.endAngle], (start: number, end: number) => {
|
|
381
|
+
const midAngle = start + (end - start) / 2
|
|
382
|
+
const innerRadius = radius - donutThickness
|
|
383
|
+
const outsideOffset = OUTSIDE_LABEL_OFFSET
|
|
384
|
+
const labelRadius =
|
|
385
|
+
labelPosition.placement === 'inside'
|
|
386
|
+
? innerRadius + (radius - innerRadius) * 0.62
|
|
387
|
+
: radius + outsideOffset
|
|
388
|
+
const x = Math.sin(midAngle) * labelRadius
|
|
389
|
+
const y = -Math.cos(midAngle) * labelRadius
|
|
390
|
+
return `translate(${x},${y})`
|
|
391
|
+
})}
|
|
392
|
+
textAnchor={labelPosition.textAnchor}
|
|
393
|
+
pointerEvents='none'
|
|
394
|
+
fill={labelFill}
|
|
395
|
+
opacity={textOpacity}
|
|
396
|
+
style={{ fontSize: ENHANCED_PIE_LABEL_FONT_SIZE, fontFamily: 'sans-serif' }}
|
|
397
|
+
>
|
|
398
|
+
<title>{`${categoryLabel}: ${roundedPercentage}`}</title>
|
|
399
|
+
<tspan x={0} dy={-ENHANCED_PIE_LABEL_LINE_HEIGHT / 2}>
|
|
400
|
+
{categoryLabel}
|
|
401
|
+
</tspan>
|
|
402
|
+
<tspan x={0} dy={ENHANCED_PIE_LABEL_LINE_HEIGHT}>
|
|
403
|
+
{roundedPercentage}
|
|
404
|
+
</tspan>
|
|
405
|
+
</animated.text>
|
|
406
|
+
)}
|
|
324
407
|
</Group>
|
|
325
408
|
)
|
|
326
409
|
})
|
|
@@ -334,11 +417,35 @@ const PieChart = React.forwardRef<SVGSVGElement, PieChartProps>((props, ref) =>
|
|
|
334
417
|
}
|
|
335
418
|
|
|
336
419
|
const height = config.heights.vertical
|
|
337
|
-
|
|
338
|
-
|
|
420
|
+
const maxPieLabelWidth = useMemo(() => {
|
|
421
|
+
if (!showCategoryPercentageLabels) return 0
|
|
422
|
+
|
|
423
|
+
const roundTo = Number(config.dataFormat.roundTo) || 0
|
|
424
|
+
const percentExample = `${(100).toFixed(roundTo)}%`
|
|
425
|
+
|
|
426
|
+
return _data.reduce((widestLabel, datum) => {
|
|
427
|
+
const categoryLabel = String(datum[config.runtime.xAxis.dataKey] ?? '')
|
|
428
|
+
return Math.max(widestLabel, measurePieLabelText(categoryLabel), measurePieLabelText(percentExample))
|
|
429
|
+
}, 0)
|
|
430
|
+
}, [_data, config.dataFormat.roundTo, config.runtime.xAxis.dataKey, showCategoryPercentageLabels])
|
|
431
|
+
const maxHorizontalLabelGutter = showCategoryPercentageLabels
|
|
432
|
+
? Math.max(0, (width - MIN_RADIUS_WITH_LABELS * 2) / 2)
|
|
433
|
+
: 0
|
|
434
|
+
const labelGutter = showCategoryPercentageLabels
|
|
435
|
+
? Math.min(Math.max(maxPieLabelWidth + LABEL_GUTTER_PADDING, MIN_LABEL_GUTTER), maxHorizontalLabelGutter)
|
|
436
|
+
: 0
|
|
437
|
+
const availableDiameter = showCategoryPercentageLabels
|
|
438
|
+
? Math.min(width - labelGutter * 2, height - VERTICAL_LABEL_GUTTER * 2)
|
|
439
|
+
: Math.min(width, height)
|
|
440
|
+
|
|
441
|
+
const radius = showCategoryPercentageLabels
|
|
442
|
+
? Math.max(0, availableDiameter / 2)
|
|
443
|
+
: Math.min(width, height) / 2
|
|
444
|
+
const svgWidth = showCategoryPercentageLabels ? width : radius * 2
|
|
445
|
+
const svgLeftOffset = Math.max((props.parentWidth - svgWidth) / 2, 0)
|
|
339
446
|
const centerY = height / 2
|
|
340
|
-
const centerX =
|
|
341
|
-
const donutThickness = config.pieType === 'Donut' ? 75 : radius
|
|
447
|
+
const centerX = showCategoryPercentageLabels ? svgWidth / 2 : radius
|
|
448
|
+
const donutThickness = config.pieType === 'Donut' ? Math.min(75, radius * 0.65) : radius
|
|
342
449
|
|
|
343
450
|
useEffect(() => {
|
|
344
451
|
if (seriesHighlight.length > 0 && config.legend.behavior !== 'highlight') {
|
|
@@ -389,7 +496,7 @@ const PieChart = React.forwardRef<SVGSVGElement, PieChartProps>((props, ref) =>
|
|
|
389
496
|
<>
|
|
390
497
|
<ErrorBoundary component='PieChart'>
|
|
391
498
|
<svg
|
|
392
|
-
width={
|
|
499
|
+
width={svgWidth}
|
|
393
500
|
height={height}
|
|
394
501
|
className={getSvgClasses()}
|
|
395
502
|
role='img'
|
|
@@ -400,7 +507,7 @@ const PieChart = React.forwardRef<SVGSVGElement, PieChartProps>((props, ref) =>
|
|
|
400
507
|
handleChartMouseLeave()
|
|
401
508
|
}}
|
|
402
509
|
>
|
|
403
|
-
<Group top={centerY} left={
|
|
510
|
+
<Group top={centerY} left={centerX}>
|
|
404
511
|
{/* prettier-ignore */}
|
|
405
512
|
<Pie
|
|
406
513
|
data={filteredData || _data}
|
|
@@ -435,7 +542,7 @@ const PieChart = React.forwardRef<SVGSVGElement, PieChartProps>((props, ref) =>
|
|
|
435
542
|
}) !important`}</style>
|
|
436
543
|
<TooltipWithBounds
|
|
437
544
|
className={'tooltip cove-visualization'}
|
|
438
|
-
left={tooltipLeft +
|
|
545
|
+
left={tooltipLeft + svgLeftOffset}
|
|
439
546
|
top={tooltipTop}
|
|
440
547
|
>
|
|
441
548
|
<ul>
|