@cdc/chart 4.23.6 → 4.23.8
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/dist/cdcchart.js +29981 -29995
- package/examples/feature/__data__/area-chart-date-apple.json +5122 -0
- package/examples/feature/__data__/city-temperature.json +2198 -0
- package/examples/feature/__data__/planet-example-data.json +1 -1
- package/examples/feature/area/area-chart-category.json +45 -45
- package/examples/feature/area/area-chart-date-apple.json +10376 -0
- package/examples/feature/area/area-chart-date-city-temperature.json +4528 -0
- package/examples/feature/area/area-chart-date.json +111 -3
- package/examples/feature/combo/right-issues.json +1 -1
- package/examples/feature/forecasting/combo-forecasting.json +72 -46
- package/examples/feature/forecasting/effective_reproduction.json +57 -8
- package/examples/feature/forecasting/forecasting.json +12 -3
- package/examples/feature/forest-plot/broken.json +700 -0
- package/examples/feature/forest-plot/data.csv +24 -0
- package/examples/feature/forest-plot/forest-plot.json +717 -0
- package/examples/feature/line/line-chart.json +11 -11
- package/examples/feature/pie/planet-pie-example-config.json +1 -1
- package/examples/gallery/bar-chart-vertical/vertical-bar-chart-categorical.json +167 -20
- package/examples/private/confidence_interval_test.json +248 -0
- package/examples/private/tooltip-issue.json +45275 -0
- package/index.html +13 -11
- package/package.json +4 -3
- package/src/CdcChart.jsx +78 -27
- package/src/components/AreaChart.jsx +65 -151
- package/src/components/BarChart.Horizontal.jsx +251 -0
- package/src/components/BarChart.StackedHorizontal.jsx +118 -0
- package/src/components/BarChart.StackedVertical.jsx +93 -0
- package/src/components/BarChart.Vertical.jsx +204 -0
- package/src/components/BarChart.jsx +17 -667
- package/src/components/BarChartType.jsx +15 -0
- package/src/components/BrushHandle.jsx +17 -0
- package/src/components/DataTable.jsx +67 -22
- package/src/components/EditorPanel.jsx +426 -358
- package/src/components/Forecasting.jsx +23 -86
- package/src/components/ForestPlot.jsx +191 -0
- package/src/components/ForestPlotSettings.jsx +508 -0
- package/src/components/Legend.jsx +10 -8
- package/src/components/LineChart.jsx +31 -6
- package/src/components/LinearChart.jsx +317 -230
- package/src/components/Series.jsx +40 -4
- package/src/data/initial-state.js +50 -3
- package/src/hooks/useBarChart.js +186 -0
- package/src/hooks/useEditorPermissions.js +218 -0
- package/src/hooks/useMinMax.js +18 -5
- package/src/hooks/useRightAxis.js +2 -1
- package/src/hooks/useScales.js +45 -2
- package/src/hooks/useTooltip.jsx +407 -0
- package/src/scss/main.scss +11 -17
package/index.html
CHANGED
|
@@ -34,16 +34,19 @@
|
|
|
34
34
|
-->
|
|
35
35
|
|
|
36
36
|
<!-- GENERIC CHART TYPES -->
|
|
37
|
-
|
|
38
|
-
<!-- <div class="react-container" data-config="/
|
|
37
|
+
<div class="react-container" data-config="/examples/private/tooltip-issue.json"></div>
|
|
38
|
+
<!-- <div class="react-container" data-config="https://cdc.gov/poxvirus/mpox/modules/data-viz/mpx-trends_1.json"></div> -->
|
|
39
|
+
<!-- <div class="react-container" data-config="/examples/private/mpox-bar-test-aug-31.json"></div> -->
|
|
40
|
+
<!-- <div class="react-container" data-config="/examples/feature/area/area-chart-date-city-temperature.json"></div> -->
|
|
41
|
+
<!-- <div class="react-container" data-config="/examples/feature/area/area-chart-date-apple.json"></div> -->
|
|
42
|
+
<!-- <div class="react-container" data-config="/examples/feature/forest-plot/broken.json"></div> -->
|
|
43
|
+
<!-- <div class="react-container" data-config="/examples/feature/forest-plot/forest-plot.json"></div> -->
|
|
39
44
|
<!-- <div class="react-container" data-config="/examples/feature/pie/planet-pie-example-config.json"></div> -->
|
|
40
45
|
<!-- <div class="react-container" data-config="/examples/feature/line/line-chart.json"></div> -->
|
|
41
|
-
<!-- <div class="react-container" data-config="/examples/feature/forecasting/index.json"></div> -->
|
|
42
46
|
<!-- <div class="react-container" data-config="/examples/feature/forecasting/forecasting.json"></div> -->
|
|
43
47
|
<!-- <div class="react-container" data-config="/examples/feature/forecasting/combo-forecasting.json"></div> -->
|
|
44
|
-
<!-- <div class="react-container" data-config="/examples/feature/forecasting/non-combo-forecasting.json"></div> -->
|
|
45
48
|
<!-- <div class="react-container" data-config="/examples/feature/forecasting/effective_reproduction.json"></div> -->
|
|
46
|
-
|
|
49
|
+
<div class="react-container" data-config="/examples/feature/area/area-chart-date.json"></div>
|
|
47
50
|
<!-- <div class="react-container" data-config="/examples/feature/area/area-chart-category.json"></div> -->
|
|
48
51
|
<!-- <div class="react-container" data-config="/examples/feature/scatterplot/scatterplot.json"></div> -->
|
|
49
52
|
<!-- <div class="react-container" data-config="/examples/feature/deviation/planet-deviation-config.json"></div> -->
|
|
@@ -55,10 +58,9 @@
|
|
|
55
58
|
<!-- BAR -->
|
|
56
59
|
<!-- <div class="react-container" data-config="/examples/feature/bar/planet-example-config.json"></div> -->
|
|
57
60
|
<!-- <div class="react-container" data-config="/examples/feature/bar/planet-chart-horizontal-example-config.json"></div> -->
|
|
58
|
-
<!-- <div class="react-container" data-config="/examples/feature/bar/new.json"></div> -->
|
|
59
61
|
<!-- <div class="react-container" data-config="/examples/feature/bar/example-bar-chart.json"></div> -->
|
|
60
62
|
<!-- <div class="react-container" data-config="/examples/feature/bar/horizontal-chart-max-increase.json"></div> -->
|
|
61
|
-
|
|
63
|
+
<div class="react-container" data-config="/examples/feature/bar/horizontal-chart.json"></div>
|
|
62
64
|
<!-- <div class="react-container" data-config="/examples/feature/bar/horizontal-stacked-bar-chart.json"></div> -->
|
|
63
65
|
<!-- <div class="react-container" data-config="/examples/feature/bar/planet-chart-horizontal-example-config.json"></div> -->
|
|
64
66
|
|
|
@@ -70,14 +72,14 @@
|
|
|
70
72
|
<!-- <div class="react-container" data-config="/examples/feature/tests-case-rate/case-rate-example-config.json"></div> -->
|
|
71
73
|
|
|
72
74
|
<!-- TESTS BIG SMALL-->
|
|
73
|
-
<!-- <div class="react-container" data-config="/examples/feature/tests-big-small/big-small-test-line.json"></div>
|
|
74
|
-
<!-- <div class="react-container" data-config="/examples/feature/tests-big-small/big-small-test-bar.json"></div>
|
|
75
|
+
<!-- <div class="react-container" data-config="/examples/feature/tests-big-small/big-small-test-line.json"></div> -->
|
|
76
|
+
<!-- <div class="react-container" data-config="/examples/feature/tests-big-small/big-small-test-bar.json"></div> -->
|
|
75
77
|
<!-- <div class="react-container" data-config="/examples/feature/tests-big-small/big-small-test-negative.json"></div> -->
|
|
76
78
|
<!-- <div class="react-container" data-config="/examples/feature/tests-big-small/line-chart-max-increase.json"></div> -->
|
|
77
79
|
|
|
78
80
|
<!-- TESTS NONNUMERICS -->
|
|
79
81
|
<!-- <div class="react-container" data-config="/examples/feature/tests-non-numerics/planet-pie-example-config-nonnumeric.json"></div> -->
|
|
80
|
-
<div class="react-container" data-config="/examples/feature/tests-non-numerics/example-combo-bar-nonnumeric.json"></div>
|
|
82
|
+
<!-- <div class="react-container" data-config="/examples/feature/tests-non-numerics/example-combo-bar-nonnumeric.json"></div> -->
|
|
81
83
|
<!-- <div class="react-container" data-config="/examples/feature/tests-non-numerics/example-bar-chart-nonnumeric.json"></div> -->
|
|
82
84
|
<!-- <div class="react-container" data-config="/examples/feature/tests-non-numerics/sparkline-chart-nonnumeric.json"></div> -->
|
|
83
85
|
<!-- <div class="react-container" data-config="/examples/feature/tests-non-numerics/stacked-vertical-bar-example-nonnumerics.json"></div> -->
|
|
@@ -110,7 +112,7 @@
|
|
|
110
112
|
<!-- <div class="react-container" data-config="/examples/gallery/bar-chart-vertical/vertical-bar-chart-stacked.json"></div> -->
|
|
111
113
|
<!-- <div class="react-container" data-config="/examples/gallery/bar-chart-vertical/vertical-bar-chart-confidence.json"></div> -->
|
|
112
114
|
<!-- <div class="react-container" data-config="/examples/gallery/bar-chart-vertical/vertical-bar-chart-confidence.json"></div> -->
|
|
113
|
-
|
|
115
|
+
<div class="react-container" data-config="/examples/gallery/bar-chart-vertical/vertical-bar-chart.json"></div>
|
|
114
116
|
|
|
115
117
|
<noscript>You need to enable JavaScript to run this app.</noscript>
|
|
116
118
|
</body>
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@cdc/chart",
|
|
3
|
-
"version": "4.23.
|
|
3
|
+
"version": "4.23.8",
|
|
4
4
|
"description": "React component for visualizing tabular data in various types of charts",
|
|
5
5
|
"moduleName": "CdcChart",
|
|
6
6
|
"main": "dist/cdcchart",
|
|
@@ -29,13 +29,14 @@
|
|
|
29
29
|
"@visx/axis": "^3.0.0",
|
|
30
30
|
"@visx/curve": "^3.0.0",
|
|
31
31
|
"@visx/event": "^3.0.1",
|
|
32
|
+
"@visx/glyph": "^3.3.0",
|
|
32
33
|
"@visx/gradient": "^3.0.0",
|
|
33
34
|
"@visx/group": "^3.0.0",
|
|
34
35
|
"@visx/legend": "^3.0.0",
|
|
35
36
|
"@visx/marker": "^3.0.0",
|
|
36
37
|
"@visx/mock-data": "^3.0.0",
|
|
37
38
|
"@visx/scale": "^3.0.0",
|
|
38
|
-
"@visx/shape": "^3.
|
|
39
|
+
"@visx/shape": "^3.3.0",
|
|
39
40
|
"@visx/stats": "^3.0.0",
|
|
40
41
|
"@visx/text": "^3.0.0",
|
|
41
42
|
"@visx/tooltip": "^3.0.0",
|
|
@@ -57,7 +58,7 @@
|
|
|
57
58
|
"react": "^18.2.0",
|
|
58
59
|
"react-dom": "^18.2.0"
|
|
59
60
|
},
|
|
60
|
-
"gitHead": "
|
|
61
|
+
"gitHead": "ba0a072a40c430baf121ad5ece0165f52a414b86",
|
|
61
62
|
"devDependencies": {
|
|
62
63
|
"resize-observer-polyfill": "^1.5.1"
|
|
63
64
|
}
|
package/src/CdcChart.jsx
CHANGED
|
@@ -21,10 +21,6 @@ import LinearChart from './components/LinearChart'
|
|
|
21
21
|
|
|
22
22
|
import { colorPalettesChart as colorPalettes, twoColorPalette } from '@cdc/core/data/colorPalettes'
|
|
23
23
|
|
|
24
|
-
import { publish, subscribe, unsubscribe } from '@cdc/core/helpers/events'
|
|
25
|
-
|
|
26
|
-
import useDataVizClasses from '@cdc/core/helpers/useDataVizClasses'
|
|
27
|
-
|
|
28
24
|
import SparkLine from './components/SparkLine'
|
|
29
25
|
import Legend from './components/Legend'
|
|
30
26
|
import defaults from './data/initial-state'
|
|
@@ -34,6 +30,8 @@ import Filters from '@cdc/core/components/Filters'
|
|
|
34
30
|
import MediaControls from '@cdc/core/components/MediaControls'
|
|
35
31
|
|
|
36
32
|
// Helpers
|
|
33
|
+
import { publish, subscribe, unsubscribe } from '@cdc/core/helpers/events'
|
|
34
|
+
import useDataVizClasses from '@cdc/core/helpers/useDataVizClasses'
|
|
37
35
|
import numberFromString from '@cdc/core/helpers/numberFromString'
|
|
38
36
|
import getViewport from '@cdc/core/helpers/getViewport'
|
|
39
37
|
import { DataTransform } from '@cdc/core/helpers/DataTransform'
|
|
@@ -96,7 +94,7 @@ export default function CdcChart({ configUrl, config: configObj, isEditor = fals
|
|
|
96
94
|
let legendMemo = useRef(new Map()) // map collection
|
|
97
95
|
let innerContainerRef = useRef()
|
|
98
96
|
|
|
99
|
-
if (isDebug) console.log('Chart config', config)
|
|
97
|
+
if (isDebug) console.log('Chart config, isEditor', config, isEditor)
|
|
100
98
|
|
|
101
99
|
const DataTable = config?.table?.showVertical ? DataTable_vert : DataTable_horiz
|
|
102
100
|
|
|
@@ -316,6 +314,10 @@ export default function CdcChart({ configUrl, config: configObj, isEditor = fals
|
|
|
316
314
|
}
|
|
317
315
|
if (undefined === newConfig.table.show) newConfig.table.show = !isDashboard
|
|
318
316
|
|
|
317
|
+
newConfig.series.map(series => {
|
|
318
|
+
if (!series.tooltip) series.tooltip = true
|
|
319
|
+
})
|
|
320
|
+
|
|
319
321
|
const processedConfig = { ...(await coveUpdateWorker(newConfig)) }
|
|
320
322
|
|
|
321
323
|
updateConfig(processedConfig, data)
|
|
@@ -393,7 +395,7 @@ export default function CdcChart({ configUrl, config: configObj, isEditor = fals
|
|
|
393
395
|
newConfig.runtime.seriesKeys = newConfig.series
|
|
394
396
|
? newConfig.series.map(series => {
|
|
395
397
|
newConfig.runtime.seriesLabels[series.dataKey] = series.label || series.dataKey
|
|
396
|
-
newConfig.runtime.seriesLabelsAll.push(series.label || series.dataKey)
|
|
398
|
+
newConfig.runtime.seriesLabelsAll.push(series.name || series.label || series.dataKey)
|
|
397
399
|
return series.dataKey
|
|
398
400
|
})
|
|
399
401
|
: []
|
|
@@ -559,16 +561,20 @@ export default function CdcChart({ configUrl, config: configObj, isEditor = fals
|
|
|
559
561
|
newConfig.runtime.areaSeriesKeys = []
|
|
560
562
|
|
|
561
563
|
newConfig.series.forEach(series => {
|
|
562
|
-
|
|
563
|
-
newConfig.runtime.areaSeriesKeys.push(series)
|
|
564
|
-
}
|
|
564
|
+
newConfig.runtime.areaSeriesKeys.push({ ...series, type: 'Area Chart' })
|
|
565
565
|
})
|
|
566
566
|
}
|
|
567
567
|
|
|
568
|
-
if ((
|
|
568
|
+
if ((newConfig.visualizationType === 'Bar' && newConfig.orientation === 'horizontal') || ['Deviation Bar', 'Paired Bar', 'Forest Plot'].includes(newConfig.visualizationType)) {
|
|
569
569
|
newConfig.runtime.xAxis = newConfig.yAxis
|
|
570
570
|
newConfig.runtime.yAxis = newConfig.xAxis
|
|
571
571
|
newConfig.runtime.horizontal = true
|
|
572
|
+
newConfig.orientation = 'horizontal'
|
|
573
|
+
} else if (['Box Plot', 'Scatter Plot', 'Area Chart'].includes(newConfig.visualizationType)) {
|
|
574
|
+
newConfig.runtime.xAxis = newConfig.xAxis
|
|
575
|
+
newConfig.runtime.yAxis = newConfig.yAxis
|
|
576
|
+
newConfig.runtime.horizontal = false
|
|
577
|
+
newConfig.orientation = 'vertical'
|
|
572
578
|
} else {
|
|
573
579
|
newConfig.runtime.xAxis = newConfig.xAxis
|
|
574
580
|
newConfig.runtime.yAxis = newConfig.yAxis
|
|
@@ -794,6 +800,21 @@ export default function CdcChart({ configUrl, config: configObj, isEditor = fals
|
|
|
794
800
|
} else {
|
|
795
801
|
newSeriesHighlight.push(newHighlight)
|
|
796
802
|
}
|
|
803
|
+
|
|
804
|
+
/**
|
|
805
|
+
* pushDataKeyBySeriesName
|
|
806
|
+
* - pushes series.dataKey into the series highlight based on the found series.name
|
|
807
|
+
* @param {String} value
|
|
808
|
+
*/
|
|
809
|
+
const pushDataKeyBySeriesName = value => {
|
|
810
|
+
let matchingSeries = config.series.filter(series => series.name === value.text)
|
|
811
|
+
if (matchingSeries?.length > 0) {
|
|
812
|
+
newSeriesHighlight.push(matchingSeries[0].dataKey)
|
|
813
|
+
}
|
|
814
|
+
}
|
|
815
|
+
|
|
816
|
+
pushDataKeyBySeriesName(label)
|
|
817
|
+
|
|
797
818
|
setSeriesHighlight(newSeriesHighlight)
|
|
798
819
|
}
|
|
799
820
|
|
|
@@ -852,13 +873,16 @@ export default function CdcChart({ configUrl, config: configObj, isEditor = fals
|
|
|
852
873
|
return num + unit
|
|
853
874
|
}
|
|
854
875
|
|
|
855
|
-
// Format numeric data based on settings in config
|
|
856
|
-
|
|
876
|
+
// Format numeric data based on settings in config OR from passed in settings for Additional Columns
|
|
877
|
+
// - use only for old horizontal data - newer formatNumber is in helper/formatNumber
|
|
878
|
+
const formatNumber = (num, axis, shouldAbbreviate = false, addColPrefix, addColSuffix, addColRoundTo) => {
|
|
857
879
|
// if num is NaN return num
|
|
858
880
|
if (isNaN(num) || !num) return num
|
|
859
881
|
// Check if the input number is negative
|
|
860
882
|
const isNegative = num < 0
|
|
861
883
|
|
|
884
|
+
if (axis === undefined || !axis) axis = 'left'
|
|
885
|
+
|
|
862
886
|
// If the input number is negative, take the absolute value
|
|
863
887
|
if (isNegative) {
|
|
864
888
|
num = Math.abs(num)
|
|
@@ -873,12 +897,21 @@ export default function CdcChart({ configUrl, config: configObj, isEditor = fals
|
|
|
873
897
|
if (String(num).indexOf(',') !== -1) num = num.replaceAll(',', '')
|
|
874
898
|
|
|
875
899
|
let original = num
|
|
876
|
-
let stringFormattingOptions
|
|
877
|
-
|
|
900
|
+
let stringFormattingOptions = {
|
|
901
|
+
useGrouping: commas ? true : false // for old chart data table to work right cant just leave this to undefined
|
|
902
|
+
}
|
|
903
|
+
if (axis === 'left' || axis === undefined) {
|
|
904
|
+
let roundToPlace
|
|
905
|
+
if (addColRoundTo !== undefined) {
|
|
906
|
+
// if its an Additional Column
|
|
907
|
+
roundToPlace = addColRoundTo ? Number(addColRoundTo) : 0
|
|
908
|
+
} else {
|
|
909
|
+
roundToPlace = roundTo ? Number(roundTo) : 0
|
|
910
|
+
}
|
|
878
911
|
stringFormattingOptions = {
|
|
879
|
-
useGrouping: config.dataFormat.commas ? true : false,
|
|
880
|
-
minimumFractionDigits:
|
|
881
|
-
maximumFractionDigits:
|
|
912
|
+
useGrouping: addColRoundTo ? true : config.dataFormat.commas ? true : false,
|
|
913
|
+
minimumFractionDigits: roundToPlace,
|
|
914
|
+
maximumFractionDigits: roundToPlace
|
|
882
915
|
}
|
|
883
916
|
}
|
|
884
917
|
|
|
@@ -937,8 +970,12 @@ export default function CdcChart({ configUrl, config: configObj, isEditor = fals
|
|
|
937
970
|
num = abbreviateNumber(parseFloat(num))
|
|
938
971
|
}
|
|
939
972
|
|
|
940
|
-
if (
|
|
941
|
-
result
|
|
973
|
+
if (addColPrefix && axis === 'left') {
|
|
974
|
+
result = addColPrefix + result
|
|
975
|
+
} else {
|
|
976
|
+
if (prefix && axis === 'left') {
|
|
977
|
+
result += prefix
|
|
978
|
+
}
|
|
942
979
|
}
|
|
943
980
|
|
|
944
981
|
if (rightPrefix && axis === 'right') {
|
|
@@ -949,10 +986,15 @@ export default function CdcChart({ configUrl, config: configObj, isEditor = fals
|
|
|
949
986
|
result += bottomPrefix
|
|
950
987
|
}
|
|
951
988
|
|
|
989
|
+
// combine prefix and num
|
|
952
990
|
result += num
|
|
953
991
|
|
|
954
|
-
if (
|
|
955
|
-
result +=
|
|
992
|
+
if (addColSuffix && axis === 'left') {
|
|
993
|
+
result += addColSuffix
|
|
994
|
+
} else {
|
|
995
|
+
if (suffix && axis === 'left') {
|
|
996
|
+
result += suffix
|
|
997
|
+
}
|
|
956
998
|
}
|
|
957
999
|
|
|
958
1000
|
if (rightSuffix && axis === 'right') {
|
|
@@ -980,11 +1022,13 @@ export default function CdcChart({ configUrl, config: configObj, isEditor = fals
|
|
|
980
1022
|
'Box Plot': <LinearChart />,
|
|
981
1023
|
'Area Chart': <LinearChart />,
|
|
982
1024
|
'Scatter Plot': <LinearChart />,
|
|
983
|
-
'Deviation Bar': <LinearChart
|
|
1025
|
+
'Deviation Bar': <LinearChart />,
|
|
1026
|
+
'Forest Plot': <LinearChart />
|
|
984
1027
|
}
|
|
985
1028
|
|
|
986
1029
|
const missingRequiredSections = () => {
|
|
987
1030
|
if (config.visualizationType === 'Forecasting') return false // skip required checks for now.
|
|
1031
|
+
if (config.visualizationType === 'Forest Plot') return false // skip required checks for now.
|
|
988
1032
|
if (config.visualizationType === 'Pie') {
|
|
989
1033
|
if (undefined === config?.yAxis.dataKey) {
|
|
990
1034
|
return true
|
|
@@ -1141,10 +1185,10 @@ export default function CdcChart({ configUrl, config: configObj, isEditor = fals
|
|
|
1141
1185
|
{config?.introText && <section className='introText'>{parse(config.introText)}</section>}
|
|
1142
1186
|
<div
|
|
1143
1187
|
style={{ marginBottom: config.legend.position !== 'bottom' && config.orientation === 'horizontal' ? `${config.runtime.xAxis.size}px` : '0px' }}
|
|
1144
|
-
className={`chart-container ${config.legend.position === 'bottom' ? 'bottom' : ''}${config.legend.hide ? ' legend-hidden' : ''}${lineDatapointClass}${barBorderClass} ${contentClasses.join(' ')}`}
|
|
1188
|
+
className={`chart-container p-relative ${config.legend.position === 'bottom' ? 'bottom' : ''}${config.legend.hide ? ' legend-hidden' : ''}${lineDatapointClass}${barBorderClass} ${contentClasses.join(' ')}`}
|
|
1145
1189
|
>
|
|
1146
1190
|
{/* All charts except sparkline */}
|
|
1147
|
-
{config.visualizationType !== 'Spark Line' && chartComponents[visualizationType]}
|
|
1191
|
+
{config.visualizationType !== 'Spark Line' && chartComponents[config.visualizationType]}
|
|
1148
1192
|
|
|
1149
1193
|
{/* Sparkline */}
|
|
1150
1194
|
{config.visualizationType === 'Spark Line' && (
|
|
@@ -1167,7 +1211,7 @@ export default function CdcChart({ configUrl, config: configObj, isEditor = fals
|
|
|
1167
1211
|
{isDashboard && config.table && config.table.show && config.table.showDataTableLink ? tableLink : link && link}
|
|
1168
1212
|
|
|
1169
1213
|
{/* Description */}
|
|
1170
|
-
{description && config.visualizationType !== 'Spark Line' && <div className='subtext'>{parse(description)}</div>}
|
|
1214
|
+
{description && config.visualizationType !== 'Spark Line' && <div className={'column ' + config.isResponsiveTicks ? 'subtext--responsive-ticks' : 'subtext'}>{parse(description)}</div>}
|
|
1171
1215
|
|
|
1172
1216
|
{/* buttons */}
|
|
1173
1217
|
<MediaControls.Section classes={['download-buttons']}>
|
|
@@ -1181,9 +1225,9 @@ export default function CdcChart({ configUrl, config: configObj, isEditor = fals
|
|
|
1181
1225
|
config={config}
|
|
1182
1226
|
rawData={config.data}
|
|
1183
1227
|
runtimeData={filteredData || excludedData}
|
|
1184
|
-
//navigationHandler={navigationHandler}
|
|
1228
|
+
//navigationHandler={navigationHandler} // do we need this? What does it do?
|
|
1185
1229
|
expandDataTable={config.table.expanded}
|
|
1186
|
-
//headerColor={general.headerColor}
|
|
1230
|
+
//headerColor={general.headerColor} // have this in map but not chart
|
|
1187
1231
|
columns={config.columns}
|
|
1188
1232
|
showDownloadButton={config.general.showDownloadButton}
|
|
1189
1233
|
runtimeLegend={dynamicLegendItems}
|
|
@@ -1204,6 +1248,7 @@ export default function CdcChart({ configUrl, config: configObj, isEditor = fals
|
|
|
1204
1248
|
outerContainerRef={outerContainerRef}
|
|
1205
1249
|
imageRef={imageId}
|
|
1206
1250
|
isDebug={isDebug}
|
|
1251
|
+
isEditor={isEditor}
|
|
1207
1252
|
/>
|
|
1208
1253
|
)}
|
|
1209
1254
|
{config?.footnotes && <section className='footnotes'>{parse(config.footnotes)}</section>}
|
|
@@ -1217,7 +1262,12 @@ export default function CdcChart({ configUrl, config: configObj, isEditor = fals
|
|
|
1217
1262
|
const getXAxisData = d => (config.runtime.xAxis.type === 'date' ? parseDate(d[config.runtime.originalXAxis.dataKey]).getTime() : d[config.runtime.originalXAxis.dataKey])
|
|
1218
1263
|
const getYAxisData = (d, seriesKey) => d[seriesKey]
|
|
1219
1264
|
|
|
1265
|
+
const capitalize = str => {
|
|
1266
|
+
return str.charAt(0).toUpperCase() + str.slice(1)
|
|
1267
|
+
}
|
|
1268
|
+
|
|
1220
1269
|
const contextValues = {
|
|
1270
|
+
capitalize,
|
|
1221
1271
|
getXAxisData,
|
|
1222
1272
|
getYAxisData,
|
|
1223
1273
|
config,
|
|
@@ -1256,6 +1306,7 @@ export default function CdcChart({ configUrl, config: configObj, isEditor = fals
|
|
|
1256
1306
|
isNumber,
|
|
1257
1307
|
getTextWidth,
|
|
1258
1308
|
twoColorPalette,
|
|
1309
|
+
isEditor,
|
|
1259
1310
|
isDebug,
|
|
1260
1311
|
setSharedFilter,
|
|
1261
1312
|
setSharedFilterValue,
|
|
@@ -1,46 +1,30 @@
|
|
|
1
|
-
import React, { useContext,
|
|
1
|
+
import React, { useContext, memo } from 'react'
|
|
2
2
|
|
|
3
3
|
// cdc
|
|
4
4
|
import ConfigContext from '../ConfigContext'
|
|
5
5
|
import ErrorBoundary from '@cdc/core/components/ErrorBoundary'
|
|
6
|
-
import { colorPalettesChart } from '@cdc/core/data/colorPalettes'
|
|
7
6
|
|
|
8
7
|
// visx & d3
|
|
9
8
|
import * as allCurves from '@visx/curve'
|
|
10
9
|
import { AreaClosed, LinePath, Bar } from '@visx/shape'
|
|
11
10
|
import { Group } from '@visx/group'
|
|
12
|
-
import { useTooltip, useTooltipInPortal, defaultStyles, Tooltip } from '@visx/tooltip'
|
|
13
|
-
import { localPoint } from '@visx/event'
|
|
14
11
|
import { bisector } from 'd3-array'
|
|
15
12
|
|
|
16
|
-
const
|
|
13
|
+
const AreaChart = ({ xScale, yScale, yMax, xMax, getXAxisData, getYAxisData, chartRef, handleTooltipMouseOver, handleTooltipMouseOff, tooltipData, isDebug, isBrush, brushData, children }) => {
|
|
17
14
|
// enable various console logs in the file
|
|
18
|
-
const DEBUG =
|
|
19
|
-
const [chartPosition, setChartPosition] = useState(null)
|
|
20
|
-
|
|
21
|
-
useEffect(() => {
|
|
22
|
-
setChartPosition(chartRef.current.getBoundingClientRect())
|
|
23
|
-
}, [chartRef])
|
|
15
|
+
const DEBUG = isDebug
|
|
24
16
|
|
|
25
17
|
// import data from context
|
|
26
|
-
|
|
27
|
-
const tooltip_id = `cdc-open-viz-tooltip-${config.runtime.uniqueId}`
|
|
18
|
+
let { transformedData: data, config, handleLineType, parseDate, formatDate, formatNumber, seriesHighlight, colorScale, rawData } = useContext(ConfigContext)
|
|
28
19
|
|
|
29
|
-
//
|
|
30
|
-
|
|
20
|
+
// use brush data if it is passed in AND if this is NOT a brush chart
|
|
21
|
+
data = !isBrush && undefined !== brushData && brushData.length ? brushData : data
|
|
31
22
|
|
|
32
|
-
|
|
33
|
-
// it appears we need to use TooltipInPortal.
|
|
34
|
-
const { TooltipInPortal } = useTooltipInPortal({
|
|
35
|
-
detectBounds: true,
|
|
36
|
-
// when tooltip containers are scrolled, this will correctly update the Tooltip position
|
|
37
|
-
scroll: true
|
|
38
|
-
})
|
|
23
|
+
if (isBrush && isDebug) console.log('###AREAchart BRUSH data, xScale, yScale, yMax, xMax', data, xScale, yScale, yMax, xMax)
|
|
39
24
|
|
|
40
25
|
// Draw transparent bars over the chart to get tooltip data
|
|
41
26
|
// Turn DEBUG on for additional context.
|
|
42
27
|
if (!data) return
|
|
43
|
-
let barThickness = xMax / data.length
|
|
44
28
|
|
|
45
29
|
// Tooltip helper for getting data to the closest date/category hovered.
|
|
46
30
|
const getXValueFromCoordinate = x => {
|
|
@@ -60,98 +44,60 @@ const CoveAreaChart = ({ xScale, yScale, yMax, xMax, chartRef }) => {
|
|
|
60
44
|
}
|
|
61
45
|
}
|
|
62
46
|
|
|
63
|
-
const
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
let formattedDate = formatDate(closestXScaleValue)
|
|
71
|
-
|
|
72
|
-
let yScaleValues
|
|
73
|
-
if (config.xAxis.type === 'categorical') {
|
|
74
|
-
yScaleValues = data.filter(d => d[config.xAxis.dataKey] === closestXScaleValue)
|
|
75
|
-
} else {
|
|
76
|
-
yScaleValues = data.filter(d => formatDate(parseDate(d[config.xAxis.dataKey])) === formattedDate)
|
|
77
|
-
}
|
|
78
|
-
|
|
79
|
-
let seriesToInclude = []
|
|
80
|
-
let yScaleMaxValues = []
|
|
81
|
-
let itemsToLoop = [config.runtime.xAxis.dataKey, ...config.runtime.seriesKeys]
|
|
82
|
-
|
|
83
|
-
itemsToLoop.map(seriesKey => {
|
|
84
|
-
if (!seriesKey) return
|
|
85
|
-
if (!yScaleValues[0]) return
|
|
86
|
-
for (const item of Object.entries(yScaleValues[0])) {
|
|
87
|
-
if (item[0] === seriesKey) {
|
|
88
|
-
// let userUpdatedSeriesName = config.series.filter(series => series.dataKey === item[0])?.[0]?.name
|
|
89
|
-
// if (userUpdatedSeriesName) item[0] = userUpdatedSeriesName
|
|
90
|
-
|
|
91
|
-
seriesToInclude.push(item)
|
|
92
|
-
}
|
|
93
|
-
}
|
|
47
|
+
const getXAxisDates = brushDataSet => {
|
|
48
|
+
if (undefined === brushDataSet || !brushDataSet) return
|
|
49
|
+
let XAxisBrushDates = []
|
|
50
|
+
brushDataSet.forEach(function convertDateTimeNumber(key, value, brushDataSet) {
|
|
51
|
+
let tmp = getXValueFromCoordinate(xScale(value))
|
|
52
|
+
let date = formatDate(tmp)
|
|
53
|
+
XAxisBrushDates.push(date)
|
|
94
54
|
})
|
|
95
|
-
|
|
96
|
-
// filter out the series that aren't added to the map.
|
|
97
|
-
seriesToInclude.map(series => yScaleMaxValues.push(Number(yScaleValues[0][series])))
|
|
98
|
-
if (!seriesToInclude) return
|
|
99
|
-
|
|
100
|
-
let tooltipDataFromSeries = Object.fromEntries(seriesToInclude) ? Object.fromEntries(seriesToInclude) : {}
|
|
101
|
-
|
|
102
|
-
let tooltipData = {}
|
|
103
|
-
tooltipData.data = tooltipDataFromSeries
|
|
104
|
-
tooltipData.dataXPosition = x + 20
|
|
105
|
-
tooltipData.dataYPosition = y - 100
|
|
106
|
-
|
|
107
|
-
let tooltipInformation = {
|
|
108
|
-
tooltipData: tooltipData,
|
|
109
|
-
tooltipTop: 0,
|
|
110
|
-
tooltipValues: yScaleValues,
|
|
111
|
-
tooltipLeft: x
|
|
112
|
-
}
|
|
113
|
-
|
|
114
|
-
showTooltip(tooltipInformation)
|
|
115
|
-
}
|
|
116
|
-
|
|
117
|
-
const TooltipListItem = ({ item }) => {
|
|
118
|
-
const [label, value] = item
|
|
119
|
-
return label === config.xAxis.dataKey ? `${label}: ${value}` : `${label}: ${formatNumber(value, 'left')}`
|
|
55
|
+
return XAxisBrushDates
|
|
120
56
|
}
|
|
121
57
|
|
|
122
58
|
const handleX = d => {
|
|
123
|
-
return config.xAxis.type === 'date' ? xScale(parseDate(d[config.xAxis.dataKey])) : xScale(d[config.xAxis.dataKey])
|
|
59
|
+
return config.xAxis.type === 'date' ? xScale(parseDate(d[config.xAxis.dataKey], false)) : xScale(d[config.xAxis.dataKey])
|
|
124
60
|
}
|
|
125
61
|
|
|
126
62
|
const handleY = (d, index, s = undefined) => {
|
|
127
|
-
return yScale(d[s.dataKey])
|
|
63
|
+
return isBrush ? yScale(d[s.dataKey]) / 4 : yScale(d[s.dataKey])
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
// prevents duplicate brush handles being rendered
|
|
67
|
+
const getFirstBrushHandleOnly = (children, index) => {
|
|
68
|
+
if (index === 0) {
|
|
69
|
+
return children
|
|
70
|
+
}
|
|
71
|
+
// else dont return the other brush handles
|
|
128
72
|
}
|
|
129
73
|
|
|
130
74
|
return (
|
|
131
75
|
data && (
|
|
132
|
-
<
|
|
133
|
-
<
|
|
134
|
-
{(config.
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
76
|
+
<svg>
|
|
77
|
+
<ErrorBoundary component='AreaChart'>
|
|
78
|
+
<Group className='area-chart' key='area-wrapper' left={Number(config.yAxis.size)} top={isBrush ? yMax * 1.3 : 0}>
|
|
79
|
+
{(config.runtime.areaSeriesKeys || config.series).map((s, index) => {
|
|
80
|
+
let seriesData = data.map(d => {
|
|
81
|
+
return {
|
|
82
|
+
[config.xAxis.dataKey]: d[config.xAxis.dataKey],
|
|
83
|
+
[s.dataKey]: d[s.dataKey]
|
|
84
|
+
}
|
|
85
|
+
})
|
|
86
|
+
|
|
87
|
+
let curveType = allCurves[s.lineType]
|
|
88
|
+
let transparentArea = config.legend.behavior === 'highlight' && seriesHighlight.length > 0 && seriesHighlight.indexOf(s.dataKey) === -1
|
|
89
|
+
let displayArea = config.legend.behavior === 'highlight' || seriesHighlight.length === 0 || seriesHighlight.indexOf(s.dataKey) !== -1
|
|
90
|
+
|
|
91
|
+
if (config.xAxis.type === 'date') {
|
|
92
|
+
data.map(d => xScale(parseDate(d[config.xAxis.dataKey])))
|
|
93
|
+
} else {
|
|
94
|
+
data.map(d => xScale(d[config.xAxis.dataKey]))
|
|
139
95
|
}
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
if (config.xAxis.type === 'date') {
|
|
147
|
-
data.map(d => xScale(parseDate(d[config.xAxis.dataKey])))
|
|
148
|
-
} else {
|
|
149
|
-
data.map(d => xScale(d[config.xAxis.dataKey]))
|
|
150
|
-
}
|
|
151
|
-
return (
|
|
152
|
-
<React.Fragment key={index}>
|
|
153
|
-
{/* prettier-ignore */}
|
|
154
|
-
<LinePath
|
|
96
|
+
|
|
97
|
+
return (
|
|
98
|
+
<React.Fragment key={index}>
|
|
99
|
+
{/* prettier-ignore */}
|
|
100
|
+
<LinePath
|
|
155
101
|
data={seriesData}
|
|
156
102
|
x={d => handleX(d)}
|
|
157
103
|
y={d => handleY(d, index, s)}
|
|
@@ -163,10 +109,10 @@ const CoveAreaChart = ({ xScale, yScale, yMax, xMax, chartRef }) => {
|
|
|
163
109
|
strokeDasharray={s.type ? handleLineType(s.type) : 0}
|
|
164
110
|
/>
|
|
165
111
|
|
|
166
|
-
|
|
167
|
-
|
|
112
|
+
{/* prettier-ignore */}
|
|
113
|
+
<AreaClosed
|
|
168
114
|
key={'area-chart'}
|
|
169
|
-
fill={
|
|
115
|
+
fill={displayArea ? colorScale ? colorScale(config.runtime.seriesLabels ? config.runtime.seriesLabels[s.dataKey] : s.dataKey) : '#000' : 'transparent'}
|
|
170
116
|
fillOpacity={transparentArea ? 0.25 : 0.5}
|
|
171
117
|
data={seriesData}
|
|
172
118
|
x={d => handleX(d)}
|
|
@@ -174,52 +120,20 @@ const CoveAreaChart = ({ xScale, yScale, yMax, xMax, chartRef }) => {
|
|
|
174
120
|
yScale={yScale}
|
|
175
121
|
curve={curveType}
|
|
176
122
|
strokeDasharray={s.type ? handleLineType(s.type) : 0}
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
/>
|
|
190
|
-
|
|
191
|
-
{/* circles that appear on hover */}
|
|
192
|
-
{tooltipData && Object.entries(tooltipData.data).length > 0 && (
|
|
193
|
-
<circle
|
|
194
|
-
cx={config.xAxis.type === 'categorical' ? xScale(tooltipData.data[config.xAxis.dataKey]) : xScale(parseDate(tooltipData.data[config.xAxis.dataKey]))}
|
|
195
|
-
cy={yScale(tooltipData.data[s.dataKey])}
|
|
196
|
-
r={4.5}
|
|
197
|
-
opacity={1}
|
|
198
|
-
fillOpacity={1}
|
|
199
|
-
fill={displayArea ? (colorScale ? colorScale(config.runtime.seriesLabels ? config.runtime.seriesLabels[s.dataKey] : s.dataKey) : '#000') : 'transparent'}
|
|
200
|
-
style={{ filter: 'unset', opacity: 1 }}
|
|
201
|
-
/>
|
|
202
|
-
)}
|
|
203
|
-
|
|
204
|
-
{tooltipData && Object.entries(tooltipData.data).length > 0 && (
|
|
205
|
-
<TooltipInPortal key={Math.random()} top={tooltipData.dataYPosition + chartPosition?.top} left={tooltipData.dataXPosition + chartPosition?.left} style={defaultStyles}>
|
|
206
|
-
<ul style={{ listStyle: 'none', paddingLeft: 'unset', fontFamily: 'sans-serif', margin: 'auto', lineHeight: '1rem' }} data-tooltip-id={tooltip_id}>
|
|
207
|
-
{typeof tooltipData === 'object' &&
|
|
208
|
-
Object.entries(tooltipData.data).map(item => (
|
|
209
|
-
<li style={{ padding: '2.5px 0' }}>
|
|
210
|
-
<TooltipListItem item={item} />
|
|
211
|
-
</li>
|
|
212
|
-
))}
|
|
213
|
-
</ul>
|
|
214
|
-
</TooltipInPortal>
|
|
215
|
-
)}
|
|
216
|
-
</React.Fragment>
|
|
217
|
-
)
|
|
218
|
-
})}
|
|
219
|
-
</Group>
|
|
220
|
-
</ErrorBoundary>
|
|
123
|
+
/>
|
|
124
|
+
{getFirstBrushHandleOnly(children, index)}
|
|
125
|
+
</React.Fragment>
|
|
126
|
+
)
|
|
127
|
+
})}
|
|
128
|
+
|
|
129
|
+
{/* Transparent bar for tooltips - disable if AreaChart is a brush */}
|
|
130
|
+
{/* prettier-ignore */}
|
|
131
|
+
{!isBrush && <Bar width={Number(xMax)} height={Number(yMax)} fill={DEBUG ? 'red' : 'transparent'} fillOpacity={0.05} style={DEBUG ? { stroke: 'black', strokeWidth: 2 } : {}} onMouseMove={e => handleTooltipMouseOver(e, rawData)} onMouseLeave={handleTooltipMouseOff} />}
|
|
132
|
+
</Group>
|
|
133
|
+
</ErrorBoundary>
|
|
134
|
+
</svg>
|
|
221
135
|
)
|
|
222
136
|
)
|
|
223
137
|
}
|
|
224
138
|
|
|
225
|
-
export default
|
|
139
|
+
export default memo(AreaChart)
|