@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
package/package.json
CHANGED
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@cdc/chart",
|
|
3
|
-
"version": "4.26.
|
|
3
|
+
"version": "4.26.7",
|
|
4
4
|
"description": "React component for visualizing tabular data in various types of charts",
|
|
5
5
|
"license": "Apache-2.0",
|
|
6
6
|
"author": "Matthew Pallansch <mpallansch@adittech.com>",
|
|
7
7
|
"bugs": "https://github.com/CDCgov/cdc-open-viz/issues",
|
|
8
8
|
"dependencies": {
|
|
9
|
-
"@cdc/core": "^4.26.
|
|
9
|
+
"@cdc/core": "^4.26.7",
|
|
10
10
|
"@hello-pangea/dnd": "^16.2.0",
|
|
11
11
|
"@react-spring/web": "^9.7.5",
|
|
12
12
|
"@rollup/plugin-dsv": "^3.0.2",
|
|
@@ -14,14 +14,11 @@
|
|
|
14
14
|
"@visx/axis": "^3.12.0",
|
|
15
15
|
"@visx/brush": "^3.12.0",
|
|
16
16
|
"@visx/curve": "^3.12.0",
|
|
17
|
-
"@visx/drag": "^3.12.0",
|
|
18
17
|
"@visx/event": "^3.12.0",
|
|
19
18
|
"@visx/glyph": "^3.12.0",
|
|
20
|
-
"@visx/gradient": "^3.12.0",
|
|
21
19
|
"@visx/heatmap": "^3.12.0",
|
|
22
20
|
"@visx/legend": "^3.12.0",
|
|
23
21
|
"@visx/marker": "^3.12.0",
|
|
24
|
-
"@visx/mock-data": "^3.12.0",
|
|
25
22
|
"@visx/pattern": "^3.12.0",
|
|
26
23
|
"@visx/responsive": "^3.12.0",
|
|
27
24
|
"@visx/scale": "^3.12.0",
|
|
@@ -32,20 +29,16 @@
|
|
|
32
29
|
"@vitejs/plugin-react": "^5.1.2",
|
|
33
30
|
"chroma-js": "^3.1.2",
|
|
34
31
|
"d3-array": "^3.2.4",
|
|
35
|
-
"d3-format": "^3.1.2",
|
|
36
32
|
"d3-sankey": "^0.12.3",
|
|
37
33
|
"d3-time-format": "^4.1.0",
|
|
38
34
|
"dompurify": "^3.4.0",
|
|
39
35
|
"html-react-parser": "^5.2.3",
|
|
40
|
-
"js-base64": "^2.5.2",
|
|
41
36
|
"lodash": "^4.17.23",
|
|
42
|
-
"papaparse": "^5.5.2",
|
|
43
37
|
"react-accessible-accordion": "^5.0.1",
|
|
44
38
|
"react-icons": "^5.5.0",
|
|
45
39
|
"react-tooltip": "5.8.2-beta.3",
|
|
46
40
|
"resize-observer-polyfill": "^1.5.1",
|
|
47
41
|
"sass": "^1.89.2",
|
|
48
|
-
"use-debounce": "^10.1.0",
|
|
49
42
|
"vite": "^7.3.1",
|
|
50
43
|
"vite-plugin-css-injected-by-js": "^2.4.0",
|
|
51
44
|
"vite-plugin-svgr": "^4.2.0",
|
|
@@ -53,11 +46,10 @@
|
|
|
53
46
|
},
|
|
54
47
|
"devDependencies": {
|
|
55
48
|
"@types/d3-array": "^3.2.1",
|
|
56
|
-
"@types/d3-format": "^3.0.4",
|
|
57
49
|
"@types/d3-sankey": "^0.12.4",
|
|
58
50
|
"@types/d3-time-format": "^4.0.3"
|
|
59
51
|
},
|
|
60
|
-
"gitHead": "
|
|
52
|
+
"gitHead": "ca89fe0eb083a52a425fa5971bbe74716b5bb0f9",
|
|
61
53
|
"main": "dist/cdcchart",
|
|
62
54
|
"moduleName": "CdcChart",
|
|
63
55
|
"peerDependencies": {
|
package/src/CdcChart.tsx
CHANGED
|
@@ -4,10 +4,10 @@ import { ChartConfig } from './types/ChartConfig'
|
|
|
4
4
|
import { getFileExtension } from '@cdc/core/helpers/getFileExtension'
|
|
5
5
|
import { isSolrCsv, isSolrJson } from '@cdc/core/helpers/isSolr'
|
|
6
6
|
import { parseCsvWithQuotes } from '@cdc/core/helpers/parseCsvWithQuotes'
|
|
7
|
-
import
|
|
7
|
+
import { addCsvCacheBuster } from '@cdc/core/helpers/csvCacheBuster'
|
|
8
8
|
import { extractDataAndMetadata } from '@cdc/core/helpers/extractDataAndMetadata'
|
|
9
9
|
import Loading from '@cdc/core/components/Loading'
|
|
10
|
-
import
|
|
10
|
+
import isEqual from 'lodash/isEqual'
|
|
11
11
|
import EditorContext from '@cdc/core/contexts/EditorContext'
|
|
12
12
|
interface CdcChartProps {
|
|
13
13
|
configUrl?: string
|
|
@@ -90,7 +90,7 @@ const CdcChartWrapper: React.FC<CdcChartProps> = ({
|
|
|
90
90
|
}, [configUrl, loadConfig])
|
|
91
91
|
|
|
92
92
|
useEffect(() => {
|
|
93
|
-
if (!
|
|
93
|
+
if (!isEqual(prevFiltersRef.current, config.filters)) {
|
|
94
94
|
prevFiltersRef.current = config.filters
|
|
95
95
|
reloadFilteredData()
|
|
96
96
|
}
|
|
@@ -112,11 +112,12 @@ const parseCsv = (responseText: string, delimiter = '|') => {
|
|
|
112
112
|
|
|
113
113
|
const fetchAndParseData = async (url: string, ext: string) => {
|
|
114
114
|
try {
|
|
115
|
-
|
|
116
|
-
|
|
115
|
+
if (ext?.toLowerCase() === 'csv' || isSolrCsv(url)) {
|
|
116
|
+
const response = await fetch(addCsvCacheBuster(url))
|
|
117
117
|
const responseText = await response.text()
|
|
118
118
|
return { data: parseCsv(responseText), dataMetadata: {} }
|
|
119
|
-
} else if (ext === 'json' || isSolrJson(url)) {
|
|
119
|
+
} else if (ext?.toLowerCase() === 'json' || isSolrJson(url)) {
|
|
120
|
+
const response = await fetch(url)
|
|
120
121
|
const json = await response.json()
|
|
121
122
|
return extractDataAndMetadata(json)
|
|
122
123
|
}
|
|
@@ -101,6 +101,7 @@ import { prepareSmallMultiplesDataTable } from './helpers/smallMultiplesHelpers'
|
|
|
101
101
|
import { calcInitialHeight } from './helpers/sizeHelpers'
|
|
102
102
|
import { ensureSpecialChartAxisTypes } from './helpers/ensureSpecialChartAxisTypes'
|
|
103
103
|
import { findColumnConfigByName } from './helpers/seriesColumnSettings'
|
|
104
|
+
import { sortByCategoryOrder } from './helpers/categoryOrder'
|
|
104
105
|
|
|
105
106
|
// styles
|
|
106
107
|
import './scss/main.scss'
|
|
@@ -231,6 +232,7 @@ const CdcChart: React.FC<CdcChartProps> = ({
|
|
|
231
232
|
config.enableMarkupVariables,
|
|
232
233
|
config.markupVariables,
|
|
233
234
|
config.data,
|
|
235
|
+
config.dataMetadata,
|
|
234
236
|
config.filters,
|
|
235
237
|
title,
|
|
236
238
|
config.superTitle,
|
|
@@ -246,6 +248,10 @@ const CdcChart: React.FC<CdcChartProps> = ({
|
|
|
246
248
|
const processedIntroText = processedTextFields.introText
|
|
247
249
|
const processedLegacyFootnotes = processedTextFields.legacyFootnotes
|
|
248
250
|
const processedDescription = processedTextFields.description
|
|
251
|
+
const chartDataTableIsRendered =
|
|
252
|
+
Boolean(config.table?.show) &&
|
|
253
|
+
config.visualizationType !== 'Spark Line' &&
|
|
254
|
+
((Boolean(config.xAxis?.dataKey) && config.visualizationType !== 'Sankey') || config.visualizationType === 'Sankey')
|
|
249
255
|
// Note: Axis labels are processed within updateConfig to ensure they use the correct data
|
|
250
256
|
|
|
251
257
|
// set defaults on titles if blank AND only in editor
|
|
@@ -259,14 +265,30 @@ const CdcChart: React.FC<CdcChartProps> = ({
|
|
|
259
265
|
const legendId = useId()
|
|
260
266
|
|
|
261
267
|
const convertLineToBarGraph = isConvertLineToBarGraph(config, filteredData)
|
|
268
|
+
const xAxisDataKey = config.xAxis?.dataKey
|
|
269
|
+
const xAxisType = config.xAxis?.type
|
|
270
|
+
const xAxisCategoryOrderType = config.xAxis?.categoryOrderType
|
|
271
|
+
const xAxisCategoryOrder = config.xAxis?.categoryOrder
|
|
272
|
+
const categoryOrderConfig = useMemo(
|
|
273
|
+
() =>
|
|
274
|
+
({
|
|
275
|
+
xAxis: {
|
|
276
|
+
dataKey: xAxisDataKey,
|
|
277
|
+
type: xAxisType,
|
|
278
|
+
categoryOrderType: xAxisCategoryOrderType,
|
|
279
|
+
categoryOrder: xAxisCategoryOrder
|
|
280
|
+
}
|
|
281
|
+
} as Pick<ChartConfig, 'xAxis'>),
|
|
282
|
+
[xAxisDataKey, xAxisType, xAxisCategoryOrderType, xAxisCategoryOrder]
|
|
283
|
+
)
|
|
262
284
|
|
|
263
285
|
// Declaratively calculate series keys for pie charts based on filtered data
|
|
264
286
|
const pieSeriesKeys = useMemo(() => {
|
|
265
|
-
if (
|
|
287
|
+
if (visualizationType !== 'Pie' || !xAxisDataKey) return null
|
|
266
288
|
const data = filteredData?.length > 0 ? filteredData : excludedData
|
|
267
289
|
if (!data) return null
|
|
268
|
-
return uniq(data.map(d => d[
|
|
269
|
-
}, [
|
|
290
|
+
return uniq(sortByCategoryOrder(data, categoryOrderConfig).map(d => d[xAxisDataKey]))
|
|
291
|
+
}, [visualizationType, xAxisDataKey, categoryOrderConfig, filteredData, excludedData])
|
|
270
292
|
|
|
271
293
|
const prepareConfig = (loadedConfig: ChartConfig) => {
|
|
272
294
|
// Create defaults without version to avoid overriding legacy configs
|
|
@@ -454,13 +476,15 @@ const CdcChart: React.FC<CdcChartProps> = ({
|
|
|
454
476
|
if (newConfig.visualizationType === 'Pie') {
|
|
455
477
|
// Use the same data that will be passed to PieChart (after exclusions and filters)
|
|
456
478
|
const pieData = currentData.length > 0 ? currentData : newExcludedData
|
|
457
|
-
newConfig.runtime.seriesKeys = uniq(pieData.map(d => d[newConfig.xAxis.dataKey]))
|
|
479
|
+
newConfig.runtime.seriesKeys = uniq(sortByCategoryOrder(pieData, newConfig).map(d => d[newConfig.xAxis.dataKey]))
|
|
458
480
|
newConfig.runtime.seriesLabelsAll = newConfig.runtime.seriesKeys
|
|
459
481
|
newConfig.runtime.isPieChart = true // Flag to know when to use derived keys
|
|
460
482
|
} else if (newConfig.visualizationType === 'Radar') {
|
|
461
483
|
// Radar chart: seriesKeys are the entity names from xAxis.dataKey
|
|
462
484
|
const radarData = currentData.length > 0 ? currentData : newExcludedData
|
|
463
|
-
newConfig.runtime.seriesKeys = uniq(
|
|
485
|
+
newConfig.runtime.seriesKeys = uniq(
|
|
486
|
+
sortByCategoryOrder(radarData, newConfig).map(d => d[newConfig.xAxis.dataKey])
|
|
487
|
+
)
|
|
464
488
|
newConfig.runtime.seriesLabelsAll = newConfig.runtime.seriesKeys
|
|
465
489
|
} else {
|
|
466
490
|
const finalData = dataOverride || newConfig.formattedData || newConfig.data
|
|
@@ -480,7 +504,9 @@ const CdcChart: React.FC<CdcChartProps> = ({
|
|
|
480
504
|
lineType: series.lineType,
|
|
481
505
|
originalDataKey: series.dataKey,
|
|
482
506
|
dynamicCategory: series.dynamicCategory,
|
|
483
|
-
tooltip: true
|
|
507
|
+
tooltip: true,
|
|
508
|
+
weight: series.weight,
|
|
509
|
+
axis: series.axis
|
|
484
510
|
})
|
|
485
511
|
})
|
|
486
512
|
// return the series keys
|
|
@@ -565,7 +591,8 @@ const CdcChart: React.FC<CdcChartProps> = ({
|
|
|
565
591
|
...newConfig.legend,
|
|
566
592
|
position: newConfig.legend?.position || 'top',
|
|
567
593
|
style: newConfig.legend?.style || 'gradient',
|
|
568
|
-
subStyle:
|
|
594
|
+
subStyle:
|
|
595
|
+
newConfig.legend?.subStyle === 'smooth' ? 'linear blocks' : newConfig.legend?.subStyle || 'linear blocks'
|
|
569
596
|
}
|
|
570
597
|
newConfig.yAxis = {
|
|
571
598
|
...newConfig.yAxis,
|
|
@@ -769,6 +796,17 @@ const CdcChart: React.FC<CdcChartProps> = ({
|
|
|
769
796
|
return newConfig
|
|
770
797
|
}
|
|
771
798
|
|
|
799
|
+
const configDataDependency = configObj?.data?.length ? configObj.data : null
|
|
800
|
+
const configDataMetadataDependency = configObj?.dataMetadata ? JSON.stringify(configObj.dataMetadata) : null
|
|
801
|
+
const configExclusionsDependency = [
|
|
802
|
+
configObj?.xAxis?.type,
|
|
803
|
+
configObj?.xAxis?.dataKey,
|
|
804
|
+
configObj?.exclusions?.active,
|
|
805
|
+
configObj?.exclusions?.dateStart,
|
|
806
|
+
configObj?.exclusions?.dateEnd,
|
|
807
|
+
...(configObj?.exclusions?.keys || [])
|
|
808
|
+
].join('|')
|
|
809
|
+
|
|
772
810
|
useEffect(() => {
|
|
773
811
|
const load = async () => {
|
|
774
812
|
try {
|
|
@@ -790,7 +828,7 @@ const CdcChart: React.FC<CdcChartProps> = ({
|
|
|
790
828
|
}
|
|
791
829
|
|
|
792
830
|
load()
|
|
793
|
-
}, [
|
|
831
|
+
}, [configDataDependency, configDataMetadataDependency, configExclusionsDependency])
|
|
794
832
|
|
|
795
833
|
/**
|
|
796
834
|
* When cove has a config and container ref publish the cove_loaded event.
|
|
@@ -1041,6 +1079,7 @@ const CdcChart: React.FC<CdcChartProps> = ({
|
|
|
1041
1079
|
rightSuffix,
|
|
1042
1080
|
bottomPrefix,
|
|
1043
1081
|
bottomSuffix,
|
|
1082
|
+
bottomCommas,
|
|
1044
1083
|
bottomAbbreviated,
|
|
1045
1084
|
preserveOriginalDecimals
|
|
1046
1085
|
}
|
|
@@ -1136,7 +1175,7 @@ const CdcChart: React.FC<CdcChartProps> = ({
|
|
|
1136
1175
|
|
|
1137
1176
|
if (
|
|
1138
1177
|
(axis === 'left' && commas && abbreviated && shouldAbbreviate) ||
|
|
1139
|
-
(axis === 'bottom' &&
|
|
1178
|
+
(axis === 'bottom' && bottomCommas && bottomAbbreviated && shouldAbbreviate)
|
|
1140
1179
|
) {
|
|
1141
1180
|
num = num // eslint-disable-line
|
|
1142
1181
|
} else {
|
|
@@ -1226,9 +1265,14 @@ const CdcChart: React.FC<CdcChartProps> = ({
|
|
|
1226
1265
|
return config?.xAxis?.dataKey ? transform.cleanData(data, excludedKey, keysToClean) : data
|
|
1227
1266
|
}
|
|
1228
1267
|
|
|
1268
|
+
const orderedTableData = useMemo(
|
|
1269
|
+
() => sortByCategoryOrder(filteredData || excludedData, categoryOrderConfig),
|
|
1270
|
+
[filteredData, excludedData, categoryOrderConfig]
|
|
1271
|
+
)
|
|
1272
|
+
|
|
1229
1273
|
const getTableRuntimeData = () => {
|
|
1230
1274
|
if (visualizationType === 'Sankey') return config?.data?.[0]?.tableData
|
|
1231
|
-
const data =
|
|
1275
|
+
const data = orderedTableData
|
|
1232
1276
|
if (config.visualizationType === 'Pie' && !config.dataFormat?.showPiePercent) {
|
|
1233
1277
|
return getPiePercent(data, config?.yAxis?.dataKey)
|
|
1234
1278
|
}
|
|
@@ -1254,7 +1298,18 @@ const CdcChart: React.FC<CdcChartProps> = ({
|
|
|
1254
1298
|
}
|
|
1255
1299
|
|
|
1256
1300
|
// Transform and clean data for chart rendering
|
|
1257
|
-
const transformedData =
|
|
1301
|
+
const transformedData = sortByCategoryOrder(
|
|
1302
|
+
getTransformedData({ brushData: state.brushData, filteredData, excludedData, clean }),
|
|
1303
|
+
config
|
|
1304
|
+
)
|
|
1305
|
+
const configYAxisDomainData = (config as ChartConfig).yAxisDomainData
|
|
1306
|
+
const yAxisDomainData = useMemo(() => {
|
|
1307
|
+
if (Array.isArray(configYAxisDomainData) && configYAxisDomainData.length > 0) {
|
|
1308
|
+
return clean(getExcludedData(config, configYAxisDomainData))
|
|
1309
|
+
}
|
|
1310
|
+
|
|
1311
|
+
return clean(excludedData)
|
|
1312
|
+
}, [config, configYAxisDomainData, excludedData])
|
|
1258
1313
|
|
|
1259
1314
|
// Filter annotations to only those visible in current data view
|
|
1260
1315
|
const visibleAnnotations = getVisibleAnnotations(config.annotations, transformedData, config.xAxis?.dataKey)
|
|
@@ -1403,11 +1458,7 @@ const CdcChart: React.FC<CdcChartProps> = ({
|
|
|
1403
1458
|
{isDashboard && config.table && config.table.show && config.table.showDataTableLink
|
|
1404
1459
|
? tableLink
|
|
1405
1460
|
: link && link}
|
|
1406
|
-
{
|
|
1407
|
-
config.table.show &&
|
|
1408
|
-
config.visualizationType !== 'Spark Line' &&
|
|
1409
|
-
config.visualizationType !== 'Sankey') ||
|
|
1410
|
-
(config.visualizationType === 'Sankey' && config.table.show)
|
|
1461
|
+
{chartDataTableIsRendered
|
|
1411
1462
|
? (() => {
|
|
1412
1463
|
let dataTableConfig = pivotDynamicSeries(config)
|
|
1413
1464
|
let dataTableColumns = config.columns
|
|
@@ -1433,6 +1484,7 @@ const CdcChart: React.FC<CdcChartProps> = ({
|
|
|
1433
1484
|
<DataTable
|
|
1434
1485
|
key={config.table?.defaultSort?.column || ''}
|
|
1435
1486
|
config={dataTableConfig}
|
|
1487
|
+
dataConfig={config.dataKey ? datasets?.[config.dataKey] : undefined}
|
|
1436
1488
|
rawData={dataTableRawData}
|
|
1437
1489
|
runtimeData={dataTableRuntimeData}
|
|
1438
1490
|
expandDataTable={config.table.expanded}
|
|
@@ -1701,12 +1753,13 @@ const CdcChart: React.FC<CdcChartProps> = ({
|
|
|
1701
1753
|
setSharedFilter,
|
|
1702
1754
|
setSharedFilterValue,
|
|
1703
1755
|
svgRef,
|
|
1704
|
-
tableData:
|
|
1756
|
+
tableData: orderedTableData,
|
|
1705
1757
|
transformedData,
|
|
1706
1758
|
twoColorPalette,
|
|
1707
1759
|
unfilteredData: stateData,
|
|
1708
1760
|
updateConfig,
|
|
1709
|
-
visibleAnnotations
|
|
1761
|
+
visibleAnnotations,
|
|
1762
|
+
yAxisDomainData
|
|
1710
1763
|
}
|
|
1711
1764
|
|
|
1712
1765
|
return (
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
import type { Meta, StoryObj } from '@storybook/react-vite'
|
|
2
|
+
import { expect } from 'storybook/test'
|
|
3
|
+
import Chart from '../CdcChart'
|
|
4
|
+
import pieConfig from './_mock/pie_with_data.json'
|
|
5
|
+
import { editConfigKeys } from '@cdc/core/helpers/configHelpers'
|
|
6
|
+
import { assertVisualizationRendered, waitForPresence } from '@cdc/core/helpers/testing'
|
|
7
|
+
|
|
8
|
+
const meta: Meta<typeof Chart> = {
|
|
9
|
+
title: 'Components/Templates/Chart/Pie Labels',
|
|
10
|
+
component: Chart
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
type Story = StoryObj<typeof Chart>
|
|
14
|
+
|
|
15
|
+
export const CategoryPercentageLabels: Story = {
|
|
16
|
+
args: {
|
|
17
|
+
config: editConfigKeys(pieConfig, [
|
|
18
|
+
{ path: ['labels'], value: true },
|
|
19
|
+
{ path: ['legend', 'hide'], value: true }
|
|
20
|
+
]),
|
|
21
|
+
isEditor: true
|
|
22
|
+
},
|
|
23
|
+
play: async ({ canvasElement }) => {
|
|
24
|
+
await assertVisualizationRendered(canvasElement)
|
|
25
|
+
await waitForPresence('.pie-label', canvasElement)
|
|
26
|
+
expect(canvasElement.querySelector('line')).toBeNull()
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
export const DonutCategoryPercentageLabels: Story = {
|
|
31
|
+
args: {
|
|
32
|
+
config: editConfigKeys(pieConfig, [
|
|
33
|
+
{ path: ['labels'], value: true },
|
|
34
|
+
{ path: ['legend', 'hide'], value: true },
|
|
35
|
+
{ path: ['pieType'], value: 'Donut' }
|
|
36
|
+
]),
|
|
37
|
+
isEditor: true
|
|
38
|
+
},
|
|
39
|
+
play: async ({ canvasElement }) => {
|
|
40
|
+
await assertVisualizationRendered(canvasElement)
|
|
41
|
+
await waitForPresence('.pie-label--outside', canvasElement)
|
|
42
|
+
expect(canvasElement.querySelector('.pie-label--inside')).toBeNull()
|
|
43
|
+
expect(canvasElement.querySelector('line')).toBeNull()
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
export default meta
|
|
@@ -0,0 +1,183 @@
|
|
|
1
|
+
import type { Meta, StoryObj } from '@storybook/react-vite'
|
|
2
|
+
import Chart from '../CdcChartComponent'
|
|
3
|
+
import { assertVisualizationRendered } from '@cdc/core/helpers/testing'
|
|
4
|
+
import { expect, userEvent, waitFor, within } from 'storybook/test'
|
|
5
|
+
|
|
6
|
+
const meta: Meta<typeof Chart> = {
|
|
7
|
+
title: 'Components/Templates/Chart/Series Color Assignments',
|
|
8
|
+
component: Chart
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
type Story = StoryObj<typeof Chart>
|
|
12
|
+
|
|
13
|
+
const assignedColors = {
|
|
14
|
+
alpha: '#0057B7',
|
|
15
|
+
bravo: '#D94E5F',
|
|
16
|
+
charlie: '#5A8E3F'
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
const normalizeColor = (color: string) => {
|
|
20
|
+
const swatch = document.createElement('div')
|
|
21
|
+
swatch.style.color = color
|
|
22
|
+
document.body.appendChild(swatch)
|
|
23
|
+
const normalized = getComputedStyle(swatch).color
|
|
24
|
+
swatch.remove()
|
|
25
|
+
return normalized
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
const getVisiblePointColorsBySeries = (canvasElement: HTMLElement) => {
|
|
29
|
+
const groups = Array.from(canvasElement.querySelectorAll('.line-chart-group .visx-glyph-group')) as SVGGElement[]
|
|
30
|
+
const colorsBySeries = new Map<string, Set<string>>()
|
|
31
|
+
|
|
32
|
+
groups.forEach(group => {
|
|
33
|
+
const seriesIndex = group.getAttribute('data-seriesindex')
|
|
34
|
+
const glyph = group.querySelector('[fill]') as SVGElement | null
|
|
35
|
+
const fill = glyph?.getAttribute('fill')
|
|
36
|
+
|
|
37
|
+
if (!seriesIndex || !fill || fill === 'transparent') return
|
|
38
|
+
|
|
39
|
+
const seriesColors = colorsBySeries.get(seriesIndex) || new Set<string>()
|
|
40
|
+
seriesColors.add(normalizeColor(fill))
|
|
41
|
+
colorsBySeries.set(seriesIndex, seriesColors)
|
|
42
|
+
})
|
|
43
|
+
|
|
44
|
+
return colorsBySeries
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
const expectVisibleSeriesColors = async (
|
|
48
|
+
canvasElement: HTMLElement,
|
|
49
|
+
expected: Array<[seriesIndex: string, color: string]>
|
|
50
|
+
) => {
|
|
51
|
+
await waitFor(() => {
|
|
52
|
+
const colorsBySeries = getVisiblePointColorsBySeries(canvasElement)
|
|
53
|
+
const expectedSeries = expected.map(([seriesIndex]) => seriesIndex)
|
|
54
|
+
|
|
55
|
+
expect(Array.from(colorsBySeries.keys()).sort()).toEqual(expectedSeries.sort())
|
|
56
|
+
|
|
57
|
+
expected.forEach(([seriesIndex, color]) => {
|
|
58
|
+
expect(Array.from(colorsBySeries.get(seriesIndex) || [])).toEqual([normalizeColor(color)])
|
|
59
|
+
})
|
|
60
|
+
})
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
const expectLegendLabels = async (canvasElement: HTMLElement, expectedLabels: string[]) => {
|
|
64
|
+
await waitFor(() => {
|
|
65
|
+
const labels = Array.from(canvasElement.querySelectorAll('.legend-container [role="button"]')).map(item =>
|
|
66
|
+
item.textContent?.trim()
|
|
67
|
+
)
|
|
68
|
+
|
|
69
|
+
expect(labels).toEqual(expectedLabels)
|
|
70
|
+
})
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
const config = {
|
|
74
|
+
type: 'chart',
|
|
75
|
+
version: '4.26.5',
|
|
76
|
+
title: 'Series Color Assignments With Filtered Series',
|
|
77
|
+
visualizationType: 'Line',
|
|
78
|
+
visualizationSubType: 'regular',
|
|
79
|
+
orientation: 'vertical',
|
|
80
|
+
filterBehavior: 'Filter Change',
|
|
81
|
+
lineDatapointStyle: 'always show',
|
|
82
|
+
lineDatapointColor: 'Same as Line',
|
|
83
|
+
general: {
|
|
84
|
+
palette: {
|
|
85
|
+
name: 'qualitative_standard',
|
|
86
|
+
version: '2.0',
|
|
87
|
+
colorAssignmentMode: 'by-value',
|
|
88
|
+
colorAssignments: [
|
|
89
|
+
{ key: 'alpha', color: assignedColors.alpha },
|
|
90
|
+
{ key: 'bravo', color: assignedColors.bravo },
|
|
91
|
+
{ key: 'charlie', color: assignedColors.charlie }
|
|
92
|
+
]
|
|
93
|
+
}
|
|
94
|
+
},
|
|
95
|
+
legend: {
|
|
96
|
+
hide: false,
|
|
97
|
+
behavior: 'isolate',
|
|
98
|
+
dynamicLegend: false,
|
|
99
|
+
position: 'top',
|
|
100
|
+
style: 'circles',
|
|
101
|
+
unified: false
|
|
102
|
+
},
|
|
103
|
+
xAxis: {
|
|
104
|
+
dataKey: 'week',
|
|
105
|
+
type: 'date-time',
|
|
106
|
+
dateParseFormat: '%Y-%m-%d',
|
|
107
|
+
dateDisplayFormat: '%b. %-d',
|
|
108
|
+
label: 'Week',
|
|
109
|
+
sortDates: false
|
|
110
|
+
},
|
|
111
|
+
yAxis: {
|
|
112
|
+
label: 'Rate',
|
|
113
|
+
min: '0',
|
|
114
|
+
max: '50',
|
|
115
|
+
numTicks: '5',
|
|
116
|
+
gridLines: true
|
|
117
|
+
},
|
|
118
|
+
visual: {
|
|
119
|
+
lineDatapointSymbol: 'none',
|
|
120
|
+
verticalHoverLine: false,
|
|
121
|
+
horizontalHoverLine: false
|
|
122
|
+
},
|
|
123
|
+
tooltips: {
|
|
124
|
+
singleSeries: false
|
|
125
|
+
},
|
|
126
|
+
series: [
|
|
127
|
+
{ dataKey: 'alpha', name: 'Alpha', type: 'Line', axis: 'Left', tooltip: true },
|
|
128
|
+
{ dataKey: 'bravo', name: 'Bravo', type: 'Line', axis: 'Left', tooltip: true },
|
|
129
|
+
{ dataKey: 'charlie', name: 'Charlie', type: 'Line', axis: 'Left', tooltip: true }
|
|
130
|
+
],
|
|
131
|
+
filters: [
|
|
132
|
+
{
|
|
133
|
+
values: ['Alpha + Bravo', 'Bravo + Charlie'],
|
|
134
|
+
filterStyle: 'dropdown',
|
|
135
|
+
id: 1726688167657,
|
|
136
|
+
columnName: 'view',
|
|
137
|
+
active: 'Alpha + Bravo',
|
|
138
|
+
label: 'Visible series'
|
|
139
|
+
}
|
|
140
|
+
],
|
|
141
|
+
data: [
|
|
142
|
+
{ week: '2024-01-01', view: 'Alpha + Bravo', alpha: 12, bravo: 20, charlie: null },
|
|
143
|
+
{ week: '2024-01-08', view: 'Alpha + Bravo', alpha: 16, bravo: 23, charlie: null },
|
|
144
|
+
{ week: '2024-01-15', view: 'Alpha + Bravo', alpha: 21, bravo: 28, charlie: null },
|
|
145
|
+
{ week: '2024-01-22', view: 'Alpha + Bravo', alpha: 26, bravo: 31, charlie: null },
|
|
146
|
+
{ week: '2024-01-01', view: 'Bravo + Charlie', alpha: null, bravo: 20, charlie: 30 },
|
|
147
|
+
{ week: '2024-01-08', view: 'Bravo + Charlie', alpha: null, bravo: 23, charlie: 34 },
|
|
148
|
+
{ week: '2024-01-15', view: 'Bravo + Charlie', alpha: null, bravo: 28, charlie: 39 },
|
|
149
|
+
{ week: '2024-01-22', view: 'Bravo + Charlie', alpha: null, bravo: 31, charlie: 43 }
|
|
150
|
+
],
|
|
151
|
+
dataDescription: {
|
|
152
|
+
horizontal: false,
|
|
153
|
+
series: false
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
export const FilteredSeriesKeepAssignedColors: Story = {
|
|
158
|
+
args: {
|
|
159
|
+
config,
|
|
160
|
+
isEditor: false
|
|
161
|
+
},
|
|
162
|
+
play: async ({ canvasElement }) => {
|
|
163
|
+
await assertVisualizationRendered(canvasElement)
|
|
164
|
+
|
|
165
|
+
await expectVisibleSeriesColors(canvasElement, [
|
|
166
|
+
['0', assignedColors.alpha],
|
|
167
|
+
['1', assignedColors.bravo]
|
|
168
|
+
])
|
|
169
|
+
await expectLegendLabels(canvasElement, ['Alpha', 'Bravo'])
|
|
170
|
+
|
|
171
|
+
const canvas = within(canvasElement)
|
|
172
|
+
const filter = canvas.getByLabelText(/filter by visible series/i) as HTMLSelectElement
|
|
173
|
+
await userEvent.selectOptions(filter, 'Bravo + Charlie')
|
|
174
|
+
|
|
175
|
+
await expectVisibleSeriesColors(canvasElement, [
|
|
176
|
+
['1', assignedColors.bravo],
|
|
177
|
+
['2', assignedColors.charlie]
|
|
178
|
+
])
|
|
179
|
+
await expectLegendLabels(canvasElement, ['Bravo', 'Charlie'])
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
export default meta
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
import type { Meta, StoryObj } from '@storybook/react-vite'
|
|
2
|
+
import Chart from '../CdcChartComponent'
|
|
3
|
+
import { editConfigKeys } from '@cdc/core/helpers/configHelpers'
|
|
4
|
+
import smallestLeftAxisMaxConfig from './_mock/smallest_left_axis_max.json'
|
|
5
|
+
import { assertVisualizationRendered } from '@cdc/core/helpers/testing'
|
|
6
|
+
|
|
7
|
+
const meta: Meta<typeof Chart> = {
|
|
8
|
+
title: 'Components/Templates/Chart/Y Axis Auto Max Strategy',
|
|
9
|
+
component: Chart
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
type Story = StoryObj<typeof Chart>
|
|
13
|
+
|
|
14
|
+
const baseConfig = editConfigKeys(smallestLeftAxisMaxConfig, [
|
|
15
|
+
{ path: ['yAxis', 'smallestLeftAxisMax'], value: undefined },
|
|
16
|
+
{ path: ['yAxis', 'filterDomainBehavior'], value: 'dynamic' },
|
|
17
|
+
{ path: ['yAxis', 'numTicks'], value: 4 },
|
|
18
|
+
{ path: ['filters', '0', 'active'], value: 'Region A' }
|
|
19
|
+
])
|
|
20
|
+
|
|
21
|
+
export const Clean_Top_Tick_Strategy: Story = {
|
|
22
|
+
args: {
|
|
23
|
+
config: editConfigKeys(baseConfig, [{ path: ['yAxis', 'autoMaxStrategy'], value: 'clean-top-tick' }])
|
|
24
|
+
},
|
|
25
|
+
play: async ({ canvasElement }) => {
|
|
26
|
+
await assertVisualizationRendered(canvasElement)
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
export const Default_Strategy: Story = {
|
|
31
|
+
args: {
|
|
32
|
+
config: editConfigKeys(baseConfig, [{ path: ['yAxis', 'autoMaxStrategy'], value: 'default' }])
|
|
33
|
+
},
|
|
34
|
+
play: async ({ canvasElement }) => {
|
|
35
|
+
await assertVisualizationRendered(canvasElement)
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
export const Stable_Filter_Domain_With_Clean_Top_Tick: Story = {
|
|
40
|
+
args: {
|
|
41
|
+
config: editConfigKeys(baseConfig, [
|
|
42
|
+
{ path: ['yAxis', 'filterDomainBehavior'], value: 'stable' },
|
|
43
|
+
{ path: ['yAxis', 'autoMaxStrategy'], value: 'clean-top-tick' },
|
|
44
|
+
{ path: ['filters', '0', 'active'], value: 'Region B' }
|
|
45
|
+
])
|
|
46
|
+
},
|
|
47
|
+
play: async ({ canvasElement }) => {
|
|
48
|
+
await assertVisualizationRendered(canvasElement)
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
export default meta
|