@cdc/chart 4.25.8 → 4.25.11
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/.claude/settings.local.json +9 -0
- package/dist/{cdcchart-1a1724a1.es.js → cdcchart-dgT_1dIT.es.js} +136 -151
- package/dist/cdcchart.js +44236 -40355
- package/examples/feature/__data__/planet-example-data.json +0 -30
- package/examples/feature/boxplot/valid-boxplot.csv +38 -17
- package/examples/grouped-bar-test.json +400 -0
- package/examples/private/DEV-11825.json +573 -0
- package/examples/private/d.json +382 -0
- package/examples/private/example-2.json +49784 -0
- package/examples/private/f2.json +1 -0
- package/examples/private/f4.json +1577 -0
- package/examples/private/forecast.json +1180 -0
- package/examples/private/lollipop.json +468 -0
- package/examples/private/na.json +913 -0
- package/examples/private/new.json +48756 -0
- package/examples/private/pie-chart-legend.json +904 -0
- package/examples/private/test-data.csv +28 -0
- package/examples/suppressed_tooltip.json +480 -0
- package/index.html +2 -133
- package/package.json +25 -7
- package/src/CdcChart.tsx +9 -13
- package/src/CdcChartComponent.tsx +403 -92
- package/src/_stories/Chart.Anchors.stories.tsx +2 -2
- package/src/_stories/Chart.BoxPlot.stories.tsx +1 -1
- package/src/_stories/Chart.CI.stories.tsx +1 -1
- package/src/_stories/Chart.Combo.stories.tsx +18 -0
- package/src/_stories/Chart.CustomColors.stories.tsx +1 -1
- package/src/_stories/Chart.DynamicSeries.stories.tsx +2 -2
- package/src/_stories/Chart.Filters.stories.tsx +2 -2
- package/src/_stories/Chart.Forecast.stories.tsx +36 -0
- package/src/_stories/Chart.HTMLInDataTable.stories.tsx +520 -0
- package/src/_stories/Chart.Legend.Gradient.stories.tsx +2 -2
- package/src/_stories/Chart.Patterns.stories.tsx +20 -0
- package/src/_stories/Chart.PreserveDecimals.stories.tsx +220 -0
- package/src/_stories/Chart.ScatterPlot.stories.tsx +1 -1
- package/src/_stories/Chart.SmallMultiples.stories.tsx +47 -0
- package/src/_stories/Chart.stories.tsx +8 -5
- package/src/_stories/Chart.tooltip.stories.tsx +1 -1
- package/src/_stories/ChartAnnotation.stories.tsx +7 -4
- package/src/_stories/ChartAxisLabels.stories.tsx +2 -2
- package/src/_stories/ChartAxisTitles.stories.tsx +2 -2
- package/src/_stories/ChartBar.Editor.stories.tsx +3580 -0
- package/src/_stories/ChartEditor.Editor.stories.tsx +658 -0
- package/src/_stories/ChartEditor.stories.tsx +59 -60
- package/src/_stories/ChartLine.Suppression.stories.tsx +1 -1
- package/src/_stories/ChartLine.Symbols.stories.tsx +1 -1
- package/src/_stories/ChartPrefixSuffix.stories.tsx +2 -2
- package/src/_stories/_mock/combo.json +451 -0
- package/src/_stories/_mock/editor-test-configs.json +376 -0
- package/src/_stories/_mock/editor-test-datasets.json +477 -0
- package/src/_stories/_mock/editor-tests/bar-chart-editor-test.json +255 -0
- package/src/_stories/_mock/editor-tests/bar-chart-general-test.json +267 -0
- package/src/_stories/_mock/editor-tests/bar-chart-test.json +237 -0
- package/src/_stories/_mock/forecast_combo_with_gaps.json +913 -0
- package/src/_stories/_mock/pie_config.json +257 -62
- package/src/_stories/_mock/small_multiples/small_multiples_bars.json +1944 -0
- package/src/_stories/_mock/small_multiples/small_multiples_big_data_bars.json +1114 -0
- package/src/_stories/_mock/small_multiples/small_multiples_lines.json +2646 -0
- package/src/_stories/_mock/small_multiples/small_multiples_lines_colors.json +1305 -0
- package/src/_stories/_mock/small_multiples/small_multiples_stacked_bars.json +1936 -0
- package/src/_stories/_mock/stacked-pattern-test.json +520 -0
- package/src/components/Annotations/components/AnnotationDraggable.tsx +1 -0
- package/src/components/Annotations/components/AnnotationDropdown.tsx +1 -1
- package/src/components/Annotations/components/findNearestDatum.ts +6 -41
- package/src/components/AreaChart/components/AreaChart.Stacked.jsx +10 -6
- package/src/components/AreaChart/index.tsx +1 -2
- package/src/components/BarChart/components/BarChart.Horizontal.tsx +161 -22
- package/src/components/BarChart/components/BarChart.StackedHorizontal.tsx +138 -5
- package/src/components/BarChart/components/BarChart.StackedVertical.tsx +215 -73
- package/src/components/BarChart/components/BarChart.Vertical.tsx +155 -22
- package/src/components/BarChart/helpers/index.ts +43 -4
- package/src/components/BarChart/helpers/lollipopColors.ts +27 -0
- package/src/components/BarChart/helpers/useBarChart.ts +25 -3
- package/src/components/BoxPlot/BoxPlot.Vertical.tsx +2 -1
- package/src/components/BoxPlot/helpers/index.ts +3 -3
- package/src/components/Brush/BrushChart.tsx +1 -1
- package/src/components/DeviationBar.jsx +9 -6
- package/src/components/EditorPanel/EditorPanel.tsx +563 -229
- package/src/components/EditorPanel/EditorPanelContext.ts +3 -0
- package/src/components/EditorPanel/components/Panels/Panel.Annotate.tsx +96 -111
- package/src/components/EditorPanel/components/Panels/Panel.General.tsx +19 -1
- package/src/components/EditorPanel/components/Panels/Panel.PatternSettings.tsx +461 -0
- package/src/components/EditorPanel/components/Panels/Panel.Series.tsx +80 -67
- package/src/components/EditorPanel/components/Panels/Panel.SmallMultiples.tsx +422 -0
- package/src/components/EditorPanel/components/Panels/Panel.Visual.tsx +188 -139
- package/src/components/EditorPanel/components/Panels/index.tsx +5 -1
- package/src/components/EditorPanel/components/Panels/panelVisual.styles.css +0 -8
- package/src/components/EditorPanel/editor-panel.scss +0 -20
- package/src/components/EditorPanel/helpers/updateFieldRankByValue.ts +49 -48
- package/src/components/EditorPanel/useEditorPermissions.ts +7 -15
- package/src/components/Forecasting/Forecasting.tsx +175 -27
- package/src/components/ForestPlot/ForestPlot.tsx +11 -7
- package/src/components/ForestPlot/ForestPlotProps.ts +1 -1
- package/src/components/Legend/Legend.Component.tsx +114 -14
- package/src/components/Legend/helpers/createFormatLabels.tsx +230 -171
- package/src/components/Legend/helpers/getLegendClasses.ts +0 -1
- package/src/components/LegendWrapper.tsx +1 -1
- package/src/components/LineChart/LineChartProps.ts +0 -3
- package/src/components/LineChart/components/LineChart.Circle.tsx +2 -2
- package/src/components/LineChart/helpers.ts +1 -1
- package/src/components/LineChart/index.tsx +38 -15
- package/src/components/LinearChart.tsx +96 -84
- package/src/components/PairedBarChart.jsx +6 -4
- package/src/components/PieChart/PieChart.tsx +170 -54
- package/src/components/Regions/components/Regions.tsx +3 -24
- package/src/components/Sankey/components/Sankey.tsx +7 -1
- package/src/components/Sankey/types/index.ts +1 -1
- package/src/components/ScatterPlot/ScatterPlot.jsx +32 -4
- package/src/components/SmallMultiples/SmallMultipleTile.tsx +198 -0
- package/src/components/SmallMultiples/SmallMultiples.css +32 -0
- package/src/components/SmallMultiples/SmallMultiples.tsx +271 -0
- package/src/components/SmallMultiples/index.ts +2 -0
- package/src/data/initial-state.js +327 -293
- package/src/helpers/buildForecastPaletteMappings.ts +112 -0
- package/src/helpers/buildForecastPaletteOptions.ts +71 -0
- package/src/helpers/getColorScale.ts +82 -8
- package/src/{hooks/useMinMax.ts → helpers/getMinMax.ts} +14 -7
- package/src/helpers/getNewRuntime.ts +1 -1
- package/src/helpers/getTransformedData.ts +1 -1
- package/src/helpers/getYAxisAutoPadding.ts +53 -0
- package/src/helpers/smallMultiplesHelpers.ts +529 -0
- package/src/hooks/useChartHoverAnalytics.tsx +44 -0
- package/src/hooks/useProgrammaticTooltip.ts +96 -0
- package/src/hooks/useReduceData.ts +105 -70
- package/src/hooks/useScales.ts +88 -34
- package/src/hooks/useSmallMultipleSynchronization.ts +59 -0
- package/src/hooks/useTooltip.tsx +116 -29
- package/src/index.jsx +0 -2
- package/src/scss/main.scss +13 -80
- package/src/store/chart.actions.ts +2 -0
- package/src/store/chart.reducer.ts +5 -1
- package/src/test/CdcChart.test.jsx +8 -3
- package/src/types/ChartConfig.ts +53 -11
- package/src/types/ChartContext.ts +4 -0
- package/vite.config.js +1 -1
- package/vitest.config.ts +16 -0
- package/src/_stories/_mock/pie_data.json +0 -218
- package/src/components/AreaChart/components/AreaChart.jsx +0 -109
- package/src/coreStyles_chart.scss +0 -3
- package/src/helpers/configHelpers.ts +0 -28
- package/src/helpers/generateColorsArray.ts +0 -8
- package/src/helpers/sort.ts +0 -7
- package/src/hooks/useActiveElement.js +0 -19
- package/src/hooks/useChartClasses.js +0 -41
- package/src/hooks/useColorPalette.js +0 -76
|
@@ -7,10 +7,18 @@ import { Group } from '@visx/group'
|
|
|
7
7
|
import { Text } from '@visx/text'
|
|
8
8
|
import { useTooltip, TooltipWithBounds } from '@visx/tooltip'
|
|
9
9
|
import { colorPalettesChart as colorPalettes } from '@cdc/core/data/colorPalettes'
|
|
10
|
+
import { getPaletteColors } from '@cdc/core/helpers/palettes/utils'
|
|
11
|
+
import { getColorPaletteVersion } from '@cdc/core/helpers/getColorPaletteVersion'
|
|
12
|
+
import {
|
|
13
|
+
v2ColorDistribution,
|
|
14
|
+
divergentColorDistribution,
|
|
15
|
+
colorblindColorDistribution
|
|
16
|
+
} from '@cdc/core/helpers/palettes/colorDistributions'
|
|
10
17
|
|
|
11
18
|
// cove
|
|
12
|
-
import ConfigContext from '../../ConfigContext'
|
|
19
|
+
import ConfigContext, { ChartDispatchContext } from '../../ConfigContext'
|
|
13
20
|
import { useTooltip as useCoveTooltip } from '../../hooks/useTooltip'
|
|
21
|
+
import { useChartHoverAnalytics } from '../../hooks/useChartHoverAnalytics'
|
|
14
22
|
import useIntersectionObserver from '../../hooks/useIntersectionObserver'
|
|
15
23
|
import { handleChartAriaLabels } from '../../helpers/handleChartAriaLabels'
|
|
16
24
|
import ErrorBoundary from '@cdc/core/components/ErrorBoundary'
|
|
@@ -25,7 +33,14 @@ type TooltipData = {
|
|
|
25
33
|
dataYPosition: number
|
|
26
34
|
}
|
|
27
35
|
|
|
28
|
-
|
|
36
|
+
type PieChartProps = {
|
|
37
|
+
parentWidth?: number
|
|
38
|
+
parentHeight?: number
|
|
39
|
+
interactionLabel?: string
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
const PieChart = React.forwardRef<SVGSVGElement, PieChartProps>((props, ref) => {
|
|
43
|
+
const { interactionLabel = '' } = props
|
|
29
44
|
const {
|
|
30
45
|
transformedData: data,
|
|
31
46
|
config,
|
|
@@ -34,13 +49,22 @@ const PieChart = props => {
|
|
|
34
49
|
seriesHighlight,
|
|
35
50
|
isDraggingAnnotation
|
|
36
51
|
} = useContext(ConfigContext)
|
|
52
|
+
const dispatch = useContext(ChartDispatchContext)
|
|
37
53
|
const { tooltipData, showTooltip, hideTooltip, tooltipOpen, tooltipLeft, tooltipTop } = useTooltip<TooltipData>()
|
|
38
54
|
const { handleTooltipMouseOver, handleTooltipMouseOff, TooltipListItem } = useCoveTooltip({
|
|
39
55
|
xScale: false,
|
|
40
56
|
yScale: false,
|
|
41
57
|
showTooltip,
|
|
42
|
-
hideTooltip
|
|
58
|
+
hideTooltip,
|
|
59
|
+
interactionLabel
|
|
60
|
+
})
|
|
61
|
+
|
|
62
|
+
// Analytics tracking for chart hover
|
|
63
|
+
const { handleChartMouseEnter, handleChartMouseLeave } = useChartHoverAnalytics({
|
|
64
|
+
config,
|
|
65
|
+
interactionLabel
|
|
43
66
|
})
|
|
67
|
+
|
|
44
68
|
const [filteredData, setFilteredData] = useState(undefined)
|
|
45
69
|
const [animatedPie, setAnimatePie] = useState(false)
|
|
46
70
|
const pivotColumns = Object.values(config.columns).filter(column => column.showInViz)
|
|
@@ -90,42 +114,100 @@ const PieChart = props => {
|
|
|
90
114
|
}
|
|
91
115
|
|
|
92
116
|
return baseData
|
|
93
|
-
}, [
|
|
117
|
+
}, [
|
|
118
|
+
data,
|
|
119
|
+
dataNeedsPivot,
|
|
120
|
+
showPercentage,
|
|
121
|
+
config.yAxis.dataKey,
|
|
122
|
+
config.xAxis.dataKey,
|
|
123
|
+
config.runtime.yAxis.dataKey,
|
|
124
|
+
config.runtime.xAxis.dataKey
|
|
125
|
+
])
|
|
126
|
+
|
|
127
|
+
// Helper function to determine enhanced distribution type and apply it
|
|
128
|
+
const applyEnhancedColorDistribution = (config, palette, numberOfKeys) => {
|
|
129
|
+
const version = getColorPaletteVersion(config)
|
|
130
|
+
const configPalette = config.general?.palette?.name || config.palette
|
|
131
|
+
|
|
132
|
+
// Skip enhanced distribution if not v2, too many keys, or wrong palette length
|
|
133
|
+
if (version !== 2 || numberOfKeys > 9 || palette.length !== 9) {
|
|
134
|
+
return palette.slice(0, numberOfKeys)
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
const isSequential = configPalette && configPalette.includes('sequential')
|
|
138
|
+
const isDivergent = configPalette && configPalette.includes('divergent')
|
|
139
|
+
const isColorblindSafe =
|
|
140
|
+
configPalette && (configPalette.includes('colorblindsafe') || configPalette.includes('qualitative_standard'))
|
|
141
|
+
|
|
142
|
+
// Determine which distribution to use based on palette type
|
|
143
|
+
let distributionMap = null
|
|
144
|
+
if (isDivergent) {
|
|
145
|
+
distributionMap = divergentColorDistribution
|
|
146
|
+
} else if (isColorblindSafe) {
|
|
147
|
+
distributionMap = colorblindColorDistribution
|
|
148
|
+
} else if (isSequential) {
|
|
149
|
+
distributionMap = v2ColorDistribution
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
if (distributionMap && distributionMap[numberOfKeys]) {
|
|
153
|
+
const distributionIndices = distributionMap[numberOfKeys]
|
|
154
|
+
return distributionIndices.map((index: number) => palette[index])
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
return palette.slice(0, numberOfKeys)
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
// Helper function to extract keys from data
|
|
161
|
+
const extractDataKeys = (data, dataKey) => {
|
|
162
|
+
const keys = {}
|
|
163
|
+
data.forEach(d => {
|
|
164
|
+
if (!keys[d[dataKey]]) keys[d[dataKey]] = true
|
|
165
|
+
})
|
|
166
|
+
return Object.keys(keys)
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
// Helper function to create color scale for pie charts
|
|
170
|
+
const createPieColorScale = (data, config, isPercentageMode = false, labelForCalcArea = null) => {
|
|
171
|
+
const dataKeys = extractDataKeys(data, config.xAxis.dataKey)
|
|
172
|
+
const domainKeys = isPercentageMode ? dataKeys.filter(k => k !== labelForCalcArea) : dataKeys
|
|
173
|
+
const numberOfKeys = domainKeys.length
|
|
174
|
+
|
|
175
|
+
let palette = getPaletteColors(config, colorPalettes)
|
|
176
|
+
palette = applyEnhancedColorDistribution(config, palette, numberOfKeys)
|
|
177
|
+
|
|
178
|
+
const unknownColor = isPercentageMode
|
|
179
|
+
? getComputedStyle(document.documentElement).getPropertyValue('--cool-gray-10').trim()
|
|
180
|
+
: null
|
|
181
|
+
|
|
182
|
+
return scaleOrdinal({
|
|
183
|
+
domain: domainKeys,
|
|
184
|
+
range: palette,
|
|
185
|
+
unknown: unknownColor
|
|
186
|
+
})
|
|
187
|
+
}
|
|
94
188
|
|
|
95
189
|
const _colorScale = useMemo(() => {
|
|
190
|
+
// Always use the full _data for color scale to ensure legend shows all items
|
|
96
191
|
if (dataNeedsPivot) {
|
|
97
|
-
|
|
98
|
-
_data.forEach(d => {
|
|
99
|
-
if (!keys[d[config.xAxis.dataKey]]) keys[d[config.xAxis.dataKey]] = true
|
|
100
|
-
})
|
|
101
|
-
const numberOfKeys = Object.entries(keys).length
|
|
102
|
-
let palette = config.customColors || colorPalettes[config.palette]
|
|
103
|
-
palette = palette.slice(0, numberOfKeys)
|
|
104
|
-
return scaleOrdinal({
|
|
105
|
-
domain: Object.keys(keys),
|
|
106
|
-
range: palette,
|
|
107
|
-
unknown: null
|
|
108
|
-
})
|
|
192
|
+
return createPieColorScale(_data, config)
|
|
109
193
|
}
|
|
110
194
|
|
|
111
195
|
if (showPercentage) {
|
|
112
|
-
|
|
113
|
-
_data.forEach(d => {
|
|
114
|
-
keys[d[config.xAxis.dataKey]] = true
|
|
115
|
-
})
|
|
116
|
-
// take out Calculated Area so it falls back to `unknown`
|
|
117
|
-
const domainKeys = Object.keys(keys).filter(k => k !== labelForCalcArea)
|
|
118
|
-
|
|
119
|
-
const basePalette = (config.customColors || colorPalettes[config.palette]).slice(0, domainKeys.length)
|
|
120
|
-
const COOL_GRAY_90 = getComputedStyle(document.documentElement).getPropertyValue('--cool-gray-10').trim()
|
|
121
|
-
return scaleOrdinal({
|
|
122
|
-
domain: domainKeys,
|
|
123
|
-
range: basePalette,
|
|
124
|
-
unknown: COOL_GRAY_90
|
|
125
|
-
})
|
|
196
|
+
return createPieColorScale(_data, config, true, labelForCalcArea)
|
|
126
197
|
}
|
|
127
|
-
|
|
128
|
-
|
|
198
|
+
|
|
199
|
+
// Handle normal pie chart case
|
|
200
|
+
return createPieColorScale(_data, config)
|
|
201
|
+
}, [
|
|
202
|
+
_data,
|
|
203
|
+
dataNeedsPivot,
|
|
204
|
+
showPercentage,
|
|
205
|
+
config.xAxis.dataKey,
|
|
206
|
+
config.general?.palette?.name,
|
|
207
|
+
config.general?.palette?.isReversed,
|
|
208
|
+
config.general?.palette?.customColors,
|
|
209
|
+
config.palette
|
|
210
|
+
])
|
|
129
211
|
|
|
130
212
|
const triggerRef = useRef()
|
|
131
213
|
const dataRef = useIntersectionObserver(triggerRef, {
|
|
@@ -137,9 +219,9 @@ const PieChart = props => {
|
|
|
137
219
|
const element = document.querySelector('.isEditor')
|
|
138
220
|
if (element) {
|
|
139
221
|
// parent element is visible
|
|
140
|
-
setAnimatePie(
|
|
222
|
+
setAnimatePie(true)
|
|
141
223
|
}
|
|
142
|
-
})
|
|
224
|
+
}, [])
|
|
143
225
|
|
|
144
226
|
useEffect(() => {
|
|
145
227
|
if (dataRef?.isIntersecting && config.animate && !animatedPie) {
|
|
@@ -178,14 +260,22 @@ const PieChart = props => {
|
|
|
178
260
|
roundedPercentage = '**'
|
|
179
261
|
}
|
|
180
262
|
|
|
263
|
+
// Determine if this slice should be muted based on legend behavior
|
|
264
|
+
const isHighlighted =
|
|
265
|
+
seriesHighlight.length === 0 || seriesHighlight.indexOf(arc.data[config.runtime.xAxis.dataKey]) !== -1
|
|
266
|
+
const shouldMute = config.legend.behavior === 'highlight' && seriesHighlight.length > 0 && !isHighlighted
|
|
267
|
+
const sliceOpacity = shouldMute ? 0.3 : 1
|
|
268
|
+
const textOpacity = shouldMute ? 0.3 : 1
|
|
269
|
+
|
|
181
270
|
return (
|
|
182
271
|
<Group key={key} className={`slice-${key}`}>
|
|
183
272
|
{/* ── the slice */}
|
|
184
273
|
<animated.path
|
|
185
|
-
d={to([styles.startAngle, styles.endAngle], (start, end) =>
|
|
274
|
+
d={to([styles.startAngle, styles.endAngle], (start: number, end: number) =>
|
|
186
275
|
path({ ...arc, startAngle: start, endAngle: end })
|
|
187
276
|
)}
|
|
188
277
|
fill={colorScale(key)}
|
|
278
|
+
opacity={sliceOpacity}
|
|
189
279
|
onMouseEnter={e =>
|
|
190
280
|
onHover(e, {
|
|
191
281
|
data: arc.data,
|
|
@@ -201,7 +291,7 @@ const PieChart = props => {
|
|
|
201
291
|
{/* ── the percentage label */}
|
|
202
292
|
{arc.endAngle - arc.startAngle > 0.1 && (
|
|
203
293
|
<animated.text
|
|
204
|
-
transform={to([styles.startAngle, styles.endAngle], (start, end) => {
|
|
294
|
+
transform={to([styles.startAngle, styles.endAngle], (start: number, end: number) => {
|
|
205
295
|
const [x, y] = path.centroid({
|
|
206
296
|
...arc,
|
|
207
297
|
startAngle: start,
|
|
@@ -212,6 +302,7 @@ const PieChart = props => {
|
|
|
212
302
|
textAnchor='middle'
|
|
213
303
|
pointerEvents='none'
|
|
214
304
|
fill={textColor}
|
|
305
|
+
opacity={textOpacity}
|
|
215
306
|
>
|
|
216
307
|
{/** compute text inside the spring callback */}
|
|
217
308
|
{roundedPercentage}
|
|
@@ -252,6 +343,27 @@ const PieChart = props => {
|
|
|
252
343
|
}
|
|
253
344
|
}, [seriesHighlight]) // eslint-disable-line
|
|
254
345
|
|
|
346
|
+
// Update the context colorScale when the pie chart's colorScale changes
|
|
347
|
+
// This ensures the Legend component uses the same colors as the pie chart
|
|
348
|
+
const prevColorScaleRef = useRef<{ domain: string; range: string } | null>(null)
|
|
349
|
+
|
|
350
|
+
useEffect(() => {
|
|
351
|
+
if (_colorScale && config.visualizationType === 'Pie') {
|
|
352
|
+
// Only dispatch if the domain or range has actually changed
|
|
353
|
+
const currentDomain = JSON.stringify(_colorScale.domain())
|
|
354
|
+
const currentRange = JSON.stringify(_colorScale.range())
|
|
355
|
+
const colorScaleKey = `${currentDomain}|${currentRange}`
|
|
356
|
+
const prevKey = prevColorScaleRef.current
|
|
357
|
+
? `${prevColorScaleRef.current.domain}|${prevColorScaleRef.current.range}`
|
|
358
|
+
: null
|
|
359
|
+
|
|
360
|
+
if (colorScaleKey !== prevKey) {
|
|
361
|
+
prevColorScaleRef.current = { domain: currentDomain, range: currentRange }
|
|
362
|
+
dispatch({ type: 'SET_COLOR_SCALE', payload: _colorScale })
|
|
363
|
+
}
|
|
364
|
+
}
|
|
365
|
+
}, [_colorScale, config.visualizationType, dispatch])
|
|
366
|
+
|
|
255
367
|
const getSvgClasses = () => {
|
|
256
368
|
let classes = ['animated-pie', 'group']
|
|
257
369
|
if (config.animate === false || animatedPie) {
|
|
@@ -269,26 +381,31 @@ const PieChart = props => {
|
|
|
269
381
|
className={getSvgClasses()}
|
|
270
382
|
role='img'
|
|
271
383
|
aria-label={handleChartAriaLabels(config)}
|
|
384
|
+
onMouseEnter={handleChartMouseEnter}
|
|
385
|
+
onMouseLeave={() => {
|
|
386
|
+
handleTooltipMouseOff()
|
|
387
|
+
handleChartMouseLeave()
|
|
388
|
+
}}
|
|
272
389
|
>
|
|
273
390
|
<Group top={centerY} left={radius}>
|
|
274
391
|
{/* prettier-ignore */}
|
|
275
392
|
<Pie
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
393
|
+
data={filteredData || _data}
|
|
394
|
+
pieValue={d => parseFloat(d[pivotKey || config.runtime.yAxis.dataKey])}
|
|
395
|
+
pieSortValues={() => -1}
|
|
396
|
+
innerRadius={radius - donutThickness}
|
|
397
|
+
outerRadius={radius}
|
|
398
|
+
>
|
|
399
|
+
{pie => (
|
|
400
|
+
<AnimatedPie
|
|
401
|
+
{...pie}
|
|
402
|
+
getKey={d => d.data[config.runtime.xAxis.dataKey]}
|
|
403
|
+
colorScale={_colorScale}
|
|
404
|
+
onHover={handleTooltipMouseOver}
|
|
405
|
+
onLeave={handleTooltipMouseOff}
|
|
406
|
+
/>
|
|
407
|
+
)}
|
|
408
|
+
</Pie>
|
|
292
409
|
</Group>
|
|
293
410
|
</svg>
|
|
294
411
|
<div ref={triggerRef} />
|
|
@@ -304,7 +421,6 @@ const PieChart = props => {
|
|
|
304
421
|
config.tooltips.opacity / 100
|
|
305
422
|
}) !important`}</style>
|
|
306
423
|
<TooltipWithBounds
|
|
307
|
-
key={Math.random()}
|
|
308
424
|
className={'tooltip cdc-open-viz-module'}
|
|
309
425
|
left={tooltipLeft + centerX - radius}
|
|
310
426
|
top={tooltipTop}
|
|
@@ -319,6 +435,6 @@ const PieChart = props => {
|
|
|
319
435
|
</ErrorBoundary>
|
|
320
436
|
</>
|
|
321
437
|
)
|
|
322
|
-
}
|
|
438
|
+
})
|
|
323
439
|
|
|
324
440
|
export default PieChart
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import React, {
|
|
1
|
+
import React, { useContext } from 'react'
|
|
2
2
|
import ConfigContext from '../../../ConfigContext'
|
|
3
3
|
import { ChartContext } from '../../../types/ChartContext'
|
|
4
4
|
import { Text } from '@visx/text'
|
|
@@ -10,27 +10,10 @@ type RegionsProps = {
|
|
|
10
10
|
yMax: number
|
|
11
11
|
barWidth?: number
|
|
12
12
|
totalBarsInGroup?: number
|
|
13
|
-
handleTooltipMouseOff: MouseEventHandler<SVGElement>
|
|
14
|
-
handleTooltipMouseOver: MouseEventHandler<SVGElement>
|
|
15
|
-
handleTooltipClick: MouseEventHandler<SVGElement>
|
|
16
|
-
tooltipData: unknown
|
|
17
|
-
showTooltip: Function
|
|
18
|
-
hideTooltip: Function
|
|
19
13
|
}
|
|
20
14
|
|
|
21
15
|
// TODO: should regions be removed on categorical axis?
|
|
22
|
-
const Regions: React.FC<RegionsProps> = ({
|
|
23
|
-
xScale,
|
|
24
|
-
barWidth = 0,
|
|
25
|
-
totalBarsInGroup = 1,
|
|
26
|
-
yMax,
|
|
27
|
-
handleTooltipMouseOff,
|
|
28
|
-
handleTooltipMouseOver,
|
|
29
|
-
handleTooltipClick,
|
|
30
|
-
tooltipData,
|
|
31
|
-
showTooltip,
|
|
32
|
-
hideTooltip
|
|
33
|
-
}) => {
|
|
16
|
+
const Regions: React.FC<RegionsProps> = ({ xScale, barWidth = 0, totalBarsInGroup = 1, yMax }) => {
|
|
34
17
|
const { parseDate, config } = useContext<ChartContext>(ConfigContext)
|
|
35
18
|
|
|
36
19
|
const { runtime, regions, visualizationType, orientation, xAxis } = config
|
|
@@ -181,11 +164,7 @@ const Regions: React.FC<RegionsProps> = ({
|
|
|
181
164
|
fill='red'
|
|
182
165
|
className='regions regions-group--line zzz'
|
|
183
166
|
key={region.label}
|
|
184
|
-
|
|
185
|
-
onMouseLeave={handleTooltipMouseOff}
|
|
186
|
-
handleTooltipClick={handleTooltipClick}
|
|
187
|
-
tooltipData={JSON.stringify(tooltipData)}
|
|
188
|
-
showTooltip={showTooltip}
|
|
167
|
+
pointerEvents='none'
|
|
189
168
|
>
|
|
190
169
|
<HighlightedArea />
|
|
191
170
|
<Text x={from + width / 2} y={5} fill={region.color} verticalAnchor='start' textAnchor='middle'>
|
|
@@ -9,7 +9,7 @@ import { Text } from '@visx/text'
|
|
|
9
9
|
// Cdc
|
|
10
10
|
import './../sankey.scss'
|
|
11
11
|
import 'react-tooltip/dist/react-tooltip.css'
|
|
12
|
-
import ConfigContext from '
|
|
12
|
+
import ConfigContext from '../../../ConfigContext'
|
|
13
13
|
import type { ChartContext } from '../../../types/ChartContext'
|
|
14
14
|
import type { SankeyNode, SankeyProps } from '../types'
|
|
15
15
|
import useSankeyAlert from '../useSankeyAlert'
|
|
@@ -171,6 +171,7 @@ const Sankey = ({ width, height, runtime }: SankeyProps) => {
|
|
|
171
171
|
fill={nodeColor}
|
|
172
172
|
fillOpacity={opacityValue}
|
|
173
173
|
rx={sankeyConfig.rxValue}
|
|
174
|
+
onMouseEnter={() => {}}
|
|
174
175
|
// todo: move enable tooltips to sankey
|
|
175
176
|
data-tooltip-html={data.tooltips && config.enableTooltips && tooltipID !== '' ? sankeyToolTip : null}
|
|
176
177
|
data-tooltip-id={`cdc-open-viz-tooltip-${runtime.uniqueId}-sankey`}
|
|
@@ -204,6 +205,7 @@ const Sankey = ({ width, height, runtime }: SankeyProps) => {
|
|
|
204
205
|
className='node-text'
|
|
205
206
|
style={{ pointerEvents: 'auto', cursor: 'pointer' }} // Enable pointer events
|
|
206
207
|
onClick={() => handleNodeClick(node.id)}
|
|
208
|
+
onMouseEnter={() => {}}
|
|
207
209
|
data-tooltip-html={data.tooltips && config.enableTooltips && tooltipID !== '' ? sankeyToolTip : null}
|
|
208
210
|
data-tooltip-id={`cdc-open-viz-tooltip-${runtime.uniqueId}-sankey`}
|
|
209
211
|
>
|
|
@@ -220,6 +222,7 @@ const Sankey = ({ width, height, runtime }: SankeyProps) => {
|
|
|
220
222
|
textAnchor='start'
|
|
221
223
|
style={{ pointerEvents: 'auto', cursor: 'pointer' }} // Enable pointer events
|
|
222
224
|
onClick={() => handleNodeClick(node.id)}
|
|
225
|
+
onMouseEnter={() => {}}
|
|
223
226
|
data-tooltip-html={data.tooltips && config.enableTooltips && tooltipID !== '' ? sankeyToolTip : null}
|
|
224
227
|
data-tooltip-id={`cdc-open-viz-tooltip-${runtime.uniqueId}-sankey`}
|
|
225
228
|
>
|
|
@@ -237,6 +240,7 @@ const Sankey = ({ width, height, runtime }: SankeyProps) => {
|
|
|
237
240
|
verticalAnchor='start'
|
|
238
241
|
style={{ pointerEvents: 'auto', cursor: 'pointer' }} // Enable pointer events
|
|
239
242
|
onClick={() => handleNodeClick(node.id)}
|
|
243
|
+
onMouseEnter={() => {}}
|
|
240
244
|
data-tooltip-html={data.tooltips && config.enableTooltips && tooltipID !== '' ? sankeyToolTip : null}
|
|
241
245
|
data-tooltip-id={`cdc-open-viz-tooltip-${runtime.uniqueId}-sankey`}
|
|
242
246
|
>
|
|
@@ -248,6 +252,7 @@ const Sankey = ({ width, height, runtime }: SankeyProps) => {
|
|
|
248
252
|
<Text
|
|
249
253
|
style={{ pointerEvents: 'auto', cursor: 'pointer' }} // Enable pointer events
|
|
250
254
|
onClick={() => handleNodeClick(node.id)}
|
|
255
|
+
onMouseEnter={() => {}}
|
|
251
256
|
data-tooltip-html={data.tooltips && config.enableTooltips && tooltipID !== '' ? sankeyToolTip : null}
|
|
252
257
|
data-tooltip-id={`cdc-open-viz-tooltip-${runtime.uniqueId}-sankey`}
|
|
253
258
|
x={node.x0! + textPositionHorizontal}
|
|
@@ -270,6 +275,7 @@ const Sankey = ({ width, height, runtime }: SankeyProps) => {
|
|
|
270
275
|
textAnchor='start'
|
|
271
276
|
style={{ pointerEvents: 'auto', cursor: 'pointer' }} // Enable pointer events
|
|
272
277
|
onClick={() => handleNodeClick(node.id)}
|
|
278
|
+
onMouseEnter={() => {}}
|
|
273
279
|
data-tooltip-html={data.tooltips && config.enableTooltips && tooltipID !== '' ? sankeyToolTip : null}
|
|
274
280
|
data-tooltip-id={`cdc-open-viz-tooltip-${runtime.uniqueId}-sankey`}
|
|
275
281
|
>
|
|
@@ -1,7 +1,9 @@
|
|
|
1
|
-
import React, { useContext } from 'react'
|
|
1
|
+
import React, { useContext, useState } from 'react'
|
|
2
2
|
import ConfigContext from '../../ConfigContext'
|
|
3
3
|
import { Group } from '@visx/group'
|
|
4
4
|
import { formatNumber as formatColNumber } from '@cdc/core/helpers/cove/number'
|
|
5
|
+
import { publishAnalyticsEvent } from '@cdc/core/helpers/metrics/helpers'
|
|
6
|
+
import { getVizTitle, getVizSubType } from '@cdc/core/helpers/metrics/utils'
|
|
5
7
|
|
|
6
8
|
const ScatterPlot = ({ xScale, yScale }) => {
|
|
7
9
|
const {
|
|
@@ -10,12 +12,17 @@ const ScatterPlot = ({ xScale, yScale }) => {
|
|
|
10
12
|
tableData,
|
|
11
13
|
formatNumber,
|
|
12
14
|
seriesHighlight,
|
|
13
|
-
colorPalettes
|
|
15
|
+
colorPalettes,
|
|
16
|
+
colorScale,
|
|
17
|
+
interactionLabel
|
|
14
18
|
} = useContext(ConfigContext)
|
|
15
19
|
|
|
16
20
|
// TODO: copied from line chart should probably be a constant somewhere.
|
|
17
21
|
const circleRadii = 4.5
|
|
18
22
|
const hasMultipleSeries = Object.keys(config.runtime.seriesLabels).length > 1
|
|
23
|
+
|
|
24
|
+
// Track current hover for analytics
|
|
25
|
+
const [currentHover, setCurrentHover] = useState({ dataIndex: null, seriesKey: null })
|
|
19
26
|
// tooltips for additional columns
|
|
20
27
|
const additionalColumns = Object.entries(config.columns)
|
|
21
28
|
.filter(([_, value]) => value.tooltips)
|
|
@@ -52,14 +59,14 @@ const ScatterPlot = ({ xScale, yScale }) => {
|
|
|
52
59
|
return config.runtime.seriesKeys.map((s, index) => {
|
|
53
60
|
const transparentArea = config.legend.behavior === 'highlight' && seriesHighlight.length > 0 && seriesHighlight.indexOf(s) === -1
|
|
54
61
|
const displayArea = config.legend.behavior === 'highlight' || seriesHighlight.length === 0 || seriesHighlight.indexOf(s) !== -1
|
|
55
|
-
const seriesColor = config?.customColors ? config.customColors[index] : config.
|
|
62
|
+
const seriesColor = config?.general?.palette?.customColors ? config.general.palette.customColors[index] : colorScale(config.runtime.seriesLabels?.[s] || s)
|
|
56
63
|
|
|
57
64
|
let pointStyles = {
|
|
58
65
|
filter: 'unset',
|
|
59
66
|
opacity: 1,
|
|
60
67
|
stroke: displayArea ? 'black' : ''
|
|
61
68
|
}
|
|
62
|
-
if (item[s]==='') {
|
|
69
|
+
if (item[s] === '') {
|
|
63
70
|
return <> </>
|
|
64
71
|
}
|
|
65
72
|
|
|
@@ -77,6 +84,27 @@ const ScatterPlot = ({ xScale, yScale }) => {
|
|
|
77
84
|
data-tooltip-html={handleTooltip(item, s, dataIndex)}
|
|
78
85
|
data-tooltip-id={`cdc-open-viz-tooltip-${config.runtime.uniqueId}`}
|
|
79
86
|
tabIndex={-1}
|
|
87
|
+
onMouseEnter={() => {
|
|
88
|
+
// Track hover analytics event if this is a new hover
|
|
89
|
+
if ((currentHover.dataIndex !== dataIndex || currentHover.seriesKey !== s)) {
|
|
90
|
+
const seriesName = config.runtime.seriesLabels?.[s] || s
|
|
91
|
+
const safeSeriesName = String(seriesName).replace(/[^a-zA-Z0-9]/g, '_')
|
|
92
|
+
const xAxisValue = item[config.xAxis.dataKey]
|
|
93
|
+
const yAxisValue = item[s]
|
|
94
|
+
|
|
95
|
+
publishAnalyticsEvent({
|
|
96
|
+
vizType: config?.type,
|
|
97
|
+
vizSubType: getVizSubType(config),
|
|
98
|
+
eventType: `chart_hover`,
|
|
99
|
+
eventAction: 'hover',
|
|
100
|
+
eventLabel: interactionLabel || 'unknown',
|
|
101
|
+
vizTitle: getVizTitle(config),
|
|
102
|
+
series: seriesName,
|
|
103
|
+
specifics: `series: ${String(safeSeriesName).toLowerCase()}, ${config.xAxis.dataKey}: ${xAxisValue}, ${s}: ${yAxisValue}`
|
|
104
|
+
})
|
|
105
|
+
setCurrentHover({ dataIndex, seriesKey: s })
|
|
106
|
+
}
|
|
107
|
+
}}
|
|
80
108
|
/>
|
|
81
109
|
)
|
|
82
110
|
})
|