@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.
Files changed (48) hide show
  1. package/dist/cdcchart.js +29981 -29995
  2. package/examples/feature/__data__/area-chart-date-apple.json +5122 -0
  3. package/examples/feature/__data__/city-temperature.json +2198 -0
  4. package/examples/feature/__data__/planet-example-data.json +1 -1
  5. package/examples/feature/area/area-chart-category.json +45 -45
  6. package/examples/feature/area/area-chart-date-apple.json +10376 -0
  7. package/examples/feature/area/area-chart-date-city-temperature.json +4528 -0
  8. package/examples/feature/area/area-chart-date.json +111 -3
  9. package/examples/feature/combo/right-issues.json +1 -1
  10. package/examples/feature/forecasting/combo-forecasting.json +72 -46
  11. package/examples/feature/forecasting/effective_reproduction.json +57 -8
  12. package/examples/feature/forecasting/forecasting.json +12 -3
  13. package/examples/feature/forest-plot/broken.json +700 -0
  14. package/examples/feature/forest-plot/data.csv +24 -0
  15. package/examples/feature/forest-plot/forest-plot.json +717 -0
  16. package/examples/feature/line/line-chart.json +11 -11
  17. package/examples/feature/pie/planet-pie-example-config.json +1 -1
  18. package/examples/gallery/bar-chart-vertical/vertical-bar-chart-categorical.json +167 -20
  19. package/examples/private/confidence_interval_test.json +248 -0
  20. package/examples/private/tooltip-issue.json +45275 -0
  21. package/index.html +13 -11
  22. package/package.json +4 -3
  23. package/src/CdcChart.jsx +78 -27
  24. package/src/components/AreaChart.jsx +65 -151
  25. package/src/components/BarChart.Horizontal.jsx +251 -0
  26. package/src/components/BarChart.StackedHorizontal.jsx +118 -0
  27. package/src/components/BarChart.StackedVertical.jsx +93 -0
  28. package/src/components/BarChart.Vertical.jsx +204 -0
  29. package/src/components/BarChart.jsx +17 -667
  30. package/src/components/BarChartType.jsx +15 -0
  31. package/src/components/BrushHandle.jsx +17 -0
  32. package/src/components/DataTable.jsx +67 -22
  33. package/src/components/EditorPanel.jsx +426 -358
  34. package/src/components/Forecasting.jsx +23 -86
  35. package/src/components/ForestPlot.jsx +191 -0
  36. package/src/components/ForestPlotSettings.jsx +508 -0
  37. package/src/components/Legend.jsx +10 -8
  38. package/src/components/LineChart.jsx +31 -6
  39. package/src/components/LinearChart.jsx +317 -230
  40. package/src/components/Series.jsx +40 -4
  41. package/src/data/initial-state.js +50 -3
  42. package/src/hooks/useBarChart.js +186 -0
  43. package/src/hooks/useEditorPermissions.js +218 -0
  44. package/src/hooks/useMinMax.js +18 -5
  45. package/src/hooks/useRightAxis.js +2 -1
  46. package/src/hooks/useScales.js +45 -2
  47. package/src/hooks/useTooltip.jsx +407 -0
  48. 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
- <!-- <div class="react-container" data-config="/examples/private/datatable-issue.json"></div> -->
38
- <!-- <div class="react-container" data-config="/examples/feature/filters/filter-testing.json"></div> -->
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
- <!-- <div class="react-container" data-config="/examples/feature/area/area-chart-date.json"></div> -->
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
- <!-- <div class="react-container" data-config="/examples/feature/bar/horizontal-chart.json"></div> -->
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
- <!-- <div class="react-container" data-config="/examples/gallery/bar-chart-vertical/vertical-bar-chart.json"></div> -->
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.6",
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.0.0",
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": "aaed0388b487adfeb3e7e278b4ce74df09cbaade",
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
- if (series.type === 'Area Chart') {
563
- newConfig.runtime.areaSeriesKeys.push(series)
564
- }
564
+ newConfig.runtime.areaSeriesKeys.push({ ...series, type: 'Area Chart' })
565
565
  })
566
566
  }
567
567
 
568
- if (((newConfig.visualizationType === 'Bar' || newConfig.visualizationType === 'Deviation Bar') && newConfig.orientation === 'horizontal') || newConfig.visualizationType === 'Paired Bar') {
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
- const formatNumber = (num, axis, shouldAbbreviate = false) => {
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
- if (axis === 'left') {
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: roundTo ? Number(roundTo) : 0,
881
- maximumFractionDigits: roundTo ? Number(roundTo) : 0
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 (prefix && axis === 'left') {
941
- result += prefix
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 (suffix && axis === 'left') {
955
- result += suffix
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, useEffect, useState } from 'react'
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 CoveAreaChart = ({ xScale, yScale, yMax, xMax, chartRef }) => {
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 = false
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
- const { transformedData: data, config, handleLineType, parseDate, formatDate, formatNumber, seriesHighlight, colorScale } = useContext(ConfigContext)
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
- // import tooltip helpers
30
- const { tooltipData, showTooltip, hideTooltip } = useTooltip()
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
- // here we're inside of the svg,
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 handleMouseOver = (e, data) => {
64
- // get the svg coordinates of the mouse
65
- // and get the closest values
66
- const eventSvgCoords = localPoint(e)
67
- const { x, y } = eventSvgCoords
68
-
69
- let closestXScaleValue = getXValueFromCoordinate(x)
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
- <ErrorBoundary component='AreaChart'>
133
- <Group className='area-chart' key='area-wrapper' left={Number(config.yAxis.size)}>
134
- {(config.runtime.areaSeriesKeys || config.runtime.seriesKeys).map((s, index) => {
135
- let seriesData = data.map(d => {
136
- return {
137
- [config.xAxis.dataKey]: d[config.xAxis.dataKey],
138
- [s.dataKey]: d[s.dataKey]
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
- let curveType = allCurves[s.lineType]
143
- let transparentArea = config.legend.behavior === 'highlight' && seriesHighlight.length > 0 && seriesHighlight.indexOf(s.dataKey) === -1
144
- let displayArea = config.legend.behavior === 'highlight' || seriesHighlight.length === 0 || seriesHighlight.indexOf(s.dataKey) !== -1
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
- {/* prettier-ignore */}
167
- <AreaClosed
112
+ {/* prettier-ignore */}
113
+ <AreaClosed
168
114
  key={'area-chart'}
169
- fill={ displayArea ? colorScale ? colorScale(config.runtime.seriesLabels ? config.runtime.seriesLabels[s.dataKey] : s.dataKey) : '#000' : 'transparent'}
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
- {/* Transparent bar for tooltips */}
180
- {/* prettier-ignore */}
181
- <Bar
182
- width={ Number(xMax)}
183
- height={ Number(yMax)}
184
- fill={DEBUG ? 'red' : 'transparent'}
185
- fillOpacity={0.05}
186
- style={DEBUG ? { stroke: 'black', strokeWidth: 2 } : {}}
187
- onMouseMove={e => handleMouseOver(e, data)}
188
- onMouseOut={hideTooltip}
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 CoveAreaChart
139
+ export default memo(AreaChart)