@cdc/chart 4.23.8 → 4.23.10

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 (34) hide show
  1. package/dist/cdcchart.js +44114 -44410
  2. package/examples/feature/__data__/area-chart-date-apple.json +1 -5073
  3. package/examples/feature/area/area-chart-date-apple.json +73 -10316
  4. package/examples/feature/area/area-chart-date-city-temperature.json +204 -80
  5. package/examples/{private/confidence_interval_test.json → feature/area/area-chart-stacked.json} +65 -74
  6. package/examples/feature/filters/bar-filter.json +5027 -0
  7. package/examples/feature/legend-highlights/highlights.json +567 -0
  8. package/index.html +28 -7
  9. package/package.json +3 -2
  10. package/src/{CdcChart.jsx → CdcChart.tsx} +77 -71
  11. package/src/components/AreaChart.Stacked.jsx +73 -0
  12. package/src/components/AreaChart.jsx +24 -26
  13. package/src/components/BarChart.StackedVertical.jsx +2 -0
  14. package/src/components/DeviationBar.jsx +67 -13
  15. package/src/components/EditorPanel.jsx +493 -454
  16. package/src/components/Forecasting.jsx +5 -5
  17. package/src/components/Legend.jsx +17 -8
  18. package/src/components/LineChart.Circle.tsx +108 -0
  19. package/src/components/{LineChart.jsx → LineChart.tsx} +10 -42
  20. package/src/components/LinearChart.jsx +460 -443
  21. package/src/components/PieChart.jsx +54 -25
  22. package/src/components/Series.jsx +63 -17
  23. package/src/components/SparkLine.jsx +7 -19
  24. package/src/data/initial-state.js +10 -1
  25. package/src/hooks/useEditorPermissions.js +87 -24
  26. package/src/hooks/useLegendClasses.js +14 -11
  27. package/src/hooks/useReduceData.js +6 -1
  28. package/src/hooks/useScales.js +2 -2
  29. package/src/hooks/useTooltip.jsx +21 -8
  30. package/src/scss/legend.scss +206 -0
  31. package/src/scss/main.scss +25 -24
  32. package/examples/private/tooltip-issue.json +0 -45275
  33. package/src/components/DataTable.jsx +0 -374
  34. /package/src/{components → hooks}/useIntersectionObserver.jsx +0 -0
@@ -41,8 +41,8 @@ import coveUpdateWorker from '@cdc/core/helpers/coveUpdateWorker'
41
41
 
42
42
  import './scss/main.scss'
43
43
  // load both then config below determines which to use
44
- import DataTable_horiz from './components/DataTable'
45
- import DataTable_vert from '@cdc/core/components/DataTable'
44
+ import DataTable from '@cdc/core/components/DataTable'
45
+ import { getFileExtension } from '@cdc/core/helpers/getFileExtension'
46
46
 
47
47
  const generateColorsArray = (color = '#000000', special = false) => {
48
48
  let colorObj = chroma(color)
@@ -78,17 +78,17 @@ export default function CdcChart({ configUrl, config: configObj, isEditor = fals
78
78
  const transform = new DataTransform()
79
79
  const [loading, setLoading] = useState(true)
80
80
  const [colorScale, setColorScale] = useState(null)
81
- const [config, setConfig] = useState({})
81
+ const [config, setConfig] = useState<any>({})
82
82
  const [stateData, setStateData] = useState(config.data || [])
83
- const [excludedData, setExcludedData] = useState()
84
- const [filteredData, setFilteredData] = useState()
85
- const [seriesHighlight, setSeriesHighlight] = useState([])
83
+ const [excludedData, setExcludedData] = useState<Record<string, number>[] | undefined>(undefined)
84
+ const [filteredData, setFilteredData] = useState<Record<string, any>[] | undefined>(undefined)
85
+ const [seriesHighlight, setSeriesHighlight] = useState<any[]>([])
86
86
  const [currentViewport, setCurrentViewport] = useState('lg')
87
- const [dimensions, setDimensions] = useState([])
88
- const [externalFilters, setExternalFilters] = useState(null)
87
+ const [dimensions, setDimensions] = useState<[number?, number?]>([])
88
+ const [externalFilters, setExternalFilters] = useState<any[]>()
89
89
  const [container, setContainer] = useState()
90
90
  const [coveLoadedEventRan, setCoveLoadedEventRan] = useState(false)
91
- const [dynamicLegendItems, setDynamicLegendItems] = useState([])
91
+ const [dynamicLegendItems, setDynamicLegendItems] = useState<any[]>([])
92
92
  const [imageId] = useState(`cove-${Math.random().toString(16).slice(-4)}`)
93
93
 
94
94
  let legendMemo = useRef(new Map()) // map collection
@@ -96,8 +96,6 @@ export default function CdcChart({ configUrl, config: configObj, isEditor = fals
96
96
 
97
97
  if (isDebug) console.log('Chart config, isEditor', config, isEditor)
98
98
 
99
- const DataTable = config?.table?.showVertical ? DataTable_vert : DataTable_horiz
100
-
101
99
  // Destructure items from config for more readable JSX
102
100
  let { legend, title, description, visualizationType } = config
103
101
 
@@ -164,12 +162,10 @@ export default function CdcChart({ configUrl, config: configObj, isEditor = fals
164
162
  })
165
163
  .join('')}`
166
164
 
167
- let data
165
+ let data: any[] = []
168
166
 
169
167
  try {
170
- const regex = /(?:\.([^.]+))?$/
171
-
172
- const ext = regex.exec(dataUrl.pathname)[1]
168
+ const ext = getFileExtension(dataUrl.pathname)
173
169
  if ('csv' === ext) {
174
170
  data = await fetch(dataUrlFinal)
175
171
  .then(response => response.text())
@@ -250,15 +246,13 @@ export default function CdcChart({ configUrl, config: configObj, isEditor = fals
250
246
  let response = configObj || (await (await fetch(configUrl)).json())
251
247
 
252
248
  // If data is included through a URL, fetch that and store
253
- let data = response.formattedData || response.data || {}
249
+ let data: any[] = response.data || []
254
250
 
255
251
  const urlFilters = response.filters ? (response.filters.filter(filter => filter.type === 'url').length > 0 ? true : false) : false
256
252
 
257
253
  if (response.dataUrl && !urlFilters) {
258
254
  try {
259
- const regex = /(?:\.([^.]+))?$/
260
-
261
- const ext = regex.exec(response.dataUrl)[1]
255
+ const ext = getFileExtension(response.dataUrl)
262
256
  if ('csv' === ext) {
263
257
  data = await fetch(response.dataUrl + `?v=${cacheBustingString()}`)
264
258
  .then(response => response.text())
@@ -289,11 +283,11 @@ export default function CdcChart({ configUrl, config: configObj, isEditor = fals
289
283
  console.error(`COVE: Cannot parse URL: ${response.dataUrl}`) // eslint-disable-line
290
284
  data = []
291
285
  }
286
+ }
292
287
 
293
- if (response.dataDescription) {
294
- data = transform.autoStandardize(data)
295
- data = transform.developerStandardize(data, response.dataDescription)
296
- }
288
+ if (response.dataDescription) {
289
+ data = transform.autoStandardize(data)
290
+ data = transform.developerStandardize(data, response.dataDescription)
297
291
  }
298
292
 
299
293
  if (data) {
@@ -323,7 +317,7 @@ export default function CdcChart({ configUrl, config: configObj, isEditor = fals
323
317
  updateConfig(processedConfig, data)
324
318
  }
325
319
 
326
- const updateConfig = (newConfig, dataOverride = undefined) => {
320
+ const updateConfig = (newConfig, dataOverride?: any[]) => {
327
321
  let data = dataOverride || stateData
328
322
 
329
323
  // Deeper copy
@@ -333,7 +327,7 @@ export default function CdcChart({ configUrl, config: configObj, isEditor = fals
333
327
  }
334
328
  })
335
329
 
336
- let newExcludedData
330
+ let newExcludedData: any[] = []
337
331
 
338
332
  if (newConfig.exclusions && newConfig.exclusions.active) {
339
333
  if (newConfig.xAxis.type === 'categorical' && newConfig.exclusions.keys?.length > 0) {
@@ -365,7 +359,7 @@ export default function CdcChart({ configUrl, config: configObj, isEditor = fals
365
359
  setExcludedData(newExcludedData)
366
360
 
367
361
  // After data is grabbed, loop through and generate filter column values if there are any
368
- let currentData
362
+ let currentData: any[] = []
369
363
  if (newConfig.filters) {
370
364
  newConfig.filters.forEach((filter, index) => {
371
365
  let filterValues = []
@@ -394,8 +388,8 @@ export default function CdcChart({ configUrl, config: configObj, isEditor = fals
394
388
  } else {
395
389
  newConfig.runtime.seriesKeys = newConfig.series
396
390
  ? newConfig.series.map(series => {
397
- newConfig.runtime.seriesLabels[series.dataKey] = series.label || series.dataKey
398
- newConfig.runtime.seriesLabelsAll.push(series.name || series.label || series.dataKey)
391
+ newConfig.runtime.seriesLabels[series.dataKey] = series.name || series.label || series.dataKey
392
+ newConfig.runtime.seriesLabelsAll.push(series.name || series.dataKey)
399
393
  return series.dataKey
400
394
  })
401
395
  : []
@@ -412,8 +406,8 @@ export default function CdcChart({ configUrl, config: configObj, isEditor = fals
412
406
  }
413
407
 
414
408
  const groups = uniqueArray(allKeys)
415
- let tableData = []
416
- const plots = []
409
+ let tableData: any[] = []
410
+ const plots: any[] = []
417
411
 
418
412
  /**
419
413
  * Calculates the first quartile (q1) and third quartile (q3) from an array of integers or decimals.
@@ -449,13 +443,13 @@ export default function CdcChart({ configUrl, config: configObj, isEditor = fals
449
443
  // group specific statistics
450
444
  // prevent re-renders
451
445
  if (!groups) return
452
- groups.forEach((g, index) => {
446
+ groups.forEach(g => {
453
447
  try {
454
448
  if (!g) throw new Error('No groups resolved in box plots')
455
449
 
456
450
  // filter data by group
457
451
  let filteredData = newExcludedData ? newExcludedData.filter(item => item[newConfig.xAxis.dataKey] === g) : data.filter(item => item[newConfig.xAxis.dataKey] === g)
458
- let filteredDataValues = filteredData.map(item => Number(item[newConfig?.series[0]?.dataKey]))
452
+ let filteredDataValues: number[] = filteredData.map(item => Number(item[newConfig?.series[0]?.dataKey]))
459
453
 
460
454
  // Sort the data for upcoming functions.
461
455
  let sortedData = filteredDataValues.sort((a, b) => a - b)
@@ -484,19 +478,20 @@ export default function CdcChart({ configUrl, config: configObj, isEditor = fals
484
478
  let nonOutliers = filteredDataValues
485
479
 
486
480
  nonOutliers = nonOutliers.filter(item => !outliers.includes(item))
487
-
481
+ const minValue: number = d3.min<number>(filteredDataValues) || 0
482
+ const _colMin = d3.max<number>([minValue, q1 - 1.5 * iqr])
488
483
  plots.push({
489
484
  columnCategory: g,
490
485
  columnMax: d3.min([d3.max(filteredDataValues), q1 + 1.5 * iqr]),
491
486
  columnThirdQuartile: Number(q3).toFixed(newConfig.dataFormat.roundTo),
492
487
  columnMedian: Number(d3.median(filteredDataValues)).toFixed(newConfig.dataFormat.roundTo),
493
488
  columnFirstQuartile: q1.toFixed(newConfig.dataFormat.roundTo),
494
- columnMin: d3.max([d3.min(filteredDataValues), q1 - 1.5 * iqr]),
489
+ columnMin: _colMin,
495
490
  columnTotal: filteredDataValues.reduce((partialSum, a) => partialSum + a, 0),
496
491
  columnSd: Number(d3.deviation(filteredDataValues)).toFixed(newConfig.dataFormat.roundTo),
497
492
  columnMean: Number(d3.mean(filteredDataValues)).toFixed(newConfig.dataFormat.roundTo),
498
493
  columnIqr: Number(iqr).toFixed(newConfig.dataFormat.roundTo),
499
- columnLowerBounds: d3.max([d3.min(filteredDataValues), q1 - 1.5 * iqr]),
494
+ columnLowerBounds: _colMin,
500
495
  columnUpperBounds: d3.min([d3.max(sortedData), q1 + 1.5 * iqr]),
501
496
  columnOutliers: outliers,
502
497
  values: filteredDataValues,
@@ -511,10 +506,10 @@ export default function CdcChart({ configUrl, config: configObj, isEditor = fals
511
506
  // this appears to be the easiest option instead of running logic against the datatable cell...
512
507
  tableData = JSON.parse(JSON.stringify(plots))
513
508
  tableData.map(table => {
514
- delete table.columnIqr
515
- delete table.nonOutlierValues
516
- delete table.columnLowerBounds
517
- delete table.columnUpperBounds
509
+ table.columnIqr = undefined
510
+ table.nonOutlierValues = undefined
511
+ table.columnLowerBounds = undefined
512
+ table.columnUpperBounds = undefined
518
513
  return null // resolve eslint
519
514
  })
520
515
 
@@ -570,7 +565,7 @@ export default function CdcChart({ configUrl, config: configObj, isEditor = fals
570
565
  newConfig.runtime.yAxis = newConfig.xAxis
571
566
  newConfig.runtime.horizontal = true
572
567
  newConfig.orientation = 'horizontal'
573
- } else if (['Box Plot', 'Scatter Plot', 'Area Chart'].includes(newConfig.visualizationType)) {
568
+ } else if (['Box Plot', 'Scatter Plot', 'Area Chart', 'Line', 'Forecasting'].includes(newConfig.visualizationType)) {
574
569
  newConfig.runtime.xAxis = newConfig.xAxis
575
570
  newConfig.runtime.yAxis = newConfig.yAxis
576
571
  newConfig.runtime.horizontal = false
@@ -587,7 +582,7 @@ export default function CdcChart({ configUrl, config: configObj, isEditor = fals
587
582
  }
588
583
 
589
584
  const filterData = (filters, data) => {
590
- let filteredData = []
585
+ let filteredData: any[] = []
591
586
 
592
587
  data.forEach(row => {
593
588
  let add = true
@@ -607,7 +602,7 @@ export default function CdcChart({ configUrl, config: configObj, isEditor = fals
607
602
 
608
603
  // Gets filter values from dataset
609
604
  const generateValuesForFilter = (columnName, data = this.state.data) => {
610
- const values = []
605
+ const values: any[] = []
611
606
 
612
607
  data.forEach(row => {
613
608
  const value = row[columnName]
@@ -697,7 +692,7 @@ export default function CdcChart({ configUrl, config: configObj, isEditor = fals
697
692
  */
698
693
  useEffect(() => {
699
694
  const handleFilterData = e => {
700
- let tmp = []
695
+ let tmp: any[] = []
701
696
  tmp.push(e.detail)
702
697
  setExternalFilters(tmp)
703
698
  }
@@ -773,10 +768,10 @@ export default function CdcChart({ configUrl, config: configObj, isEditor = fals
773
768
 
774
769
  // Called on legend click, highlights/unhighlights the data series with the given label
775
770
  const highlight = label => {
776
- const newSeriesHighlight = []
771
+ const newSeriesHighlight: any[] = []
777
772
 
778
773
  // If we're highlighting all the series, reset them
779
- if (seriesHighlight.length + 1 === config.runtime.seriesKeys.length && !config.legend.dynamicLegend && config.visualizationType !== 'Forecasting') {
774
+ if (seriesHighlight.length + 1 === config.runtime.seriesKeys.length && config.visualizationType !== 'Forecasting') {
780
775
  highlightReset()
781
776
  return
782
777
  }
@@ -806,25 +801,28 @@ export default function CdcChart({ configUrl, config: configObj, isEditor = fals
806
801
  * - pushes series.dataKey into the series highlight based on the found series.name
807
802
  * @param {String} value
808
803
  */
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
- }
804
+ // const pushDataKeyBySeriesName = value => {
805
+ // let matchingSeries = config.series.filter(series => series.name === value.text)
806
+ // if (matchingSeries?.length > 0) {
807
+ // newSeriesHighlight.push(matchingSeries[0].dataKey)
808
+ // }
809
+ // }
815
810
 
816
- pushDataKeyBySeriesName(label)
811
+ // pushDataKeyBySeriesName(label)
817
812
 
818
813
  setSeriesHighlight(newSeriesHighlight)
819
814
  }
820
815
 
821
816
  // Called on reset button click, unhighlights all data series
822
817
  const highlightReset = () => {
823
- if (config.legend.dynamicLegend && dynamicLegendItems) {
824
- setSeriesHighlight(dynamicLegendItems.map(item => item.text))
825
- } else {
826
- setSeriesHighlight([])
818
+ try {
819
+ const legend = document.getElementById('legend')
820
+ if (!legend) throw new Error('No legend available to set previous focus on.')
821
+ legend.focus()
822
+ } catch (e) {
823
+ console.error('COVE:', e.message)
827
824
  }
825
+ setSeriesHighlight([])
828
826
  }
829
827
 
830
828
  const section = config.orientation === 'horizontal' ? 'yAxis' : 'xAxis'
@@ -849,7 +847,10 @@ export default function CdcChart({ configUrl, config: configObj, isEditor = fals
849
847
  function getTextWidth(text, font) {
850
848
  const canvas = document.createElement('canvas')
851
849
  const context = canvas.getContext('2d')
852
-
850
+ if (!context) {
851
+ console.error('2d context not found')
852
+ return
853
+ }
853
854
  context.font = font || getComputedStyle(document.body).font
854
855
 
855
856
  return Math.ceil(context.measureText(text).width)
@@ -875,6 +876,7 @@ export default function CdcChart({ configUrl, config: configObj, isEditor = fals
875
876
 
876
877
  // Format numeric data based on settings in config OR from passed in settings for Additional Columns
877
878
  // - use only for old horizontal data - newer formatNumber is in helper/formatNumber
879
+ // TODO: we should combine various formatNumber functions across this project.
878
880
  const formatNumber = (num, axis, shouldAbbreviate = false, addColPrefix, addColSuffix, addColRoundTo) => {
879
881
  // if num is NaN return num
880
882
  if (isNaN(num) || !num) return num
@@ -897,7 +899,7 @@ export default function CdcChart({ configUrl, config: configObj, isEditor = fals
897
899
  if (String(num).indexOf(',') !== -1) num = num.replaceAll(',', '')
898
900
 
899
901
  let original = num
900
- let stringFormattingOptions = {
902
+ let stringFormattingOptions: any = {
901
903
  useGrouping: commas ? true : false // for old chart data table to work right cant just leave this to undefined
902
904
  }
903
905
  if (axis === 'left' || axis === undefined) {
@@ -1144,8 +1146,10 @@ export default function CdcChart({ configUrl, config: configObj, isEditor = fals
1144
1146
  }
1145
1147
  }
1146
1148
 
1149
+ // TODO: should be part of the DataTransform class.
1147
1150
  const clean = data => {
1148
1151
  // cleaning is deleting data we need in forecasting charts.
1152
+ if (!Array.isArray(data)) return []
1149
1153
  if (config.visualizationType === 'Forecasting') return data
1150
1154
  return config?.xAxis?.dataKey ? transform.cleanData(data, config.xAxis.dataKey) : data
1151
1155
  }
@@ -1180,9 +1184,9 @@ export default function CdcChart({ configUrl, config: configObj, isEditor = fals
1180
1184
  Skip Over Chart Container
1181
1185
  </a>
1182
1186
  {/* Filters */}
1183
- {config.filters && !externalFilters && <Filters config={config} setConfig={setConfig} setFilteredData={setFilteredData} filteredData={filteredData} excludedData={excludedData} filterData={filterData} isNumber={isNumber} dimensions={dimensions} />}
1187
+ {config.filters && !externalFilters && <Filters config={config} setConfig={setConfig} setFilteredData={setFilteredData} filteredData={filteredData} excludedData={excludedData} filterData={filterData} dimensions={dimensions} />}
1184
1188
  {/* Visualization */}
1185
- {config?.introText && <section className='introText'>{parse(config.introText)}</section>}
1189
+ {config?.introText && config.visualizationType !== 'Spark Line' && <section className='introText'>{parse(config.introText)}</section>}
1186
1190
  <div
1187
1191
  style={{ marginBottom: config.legend.position !== 'bottom' && config.orientation === 'horizontal' ? `${config.runtime.xAxis.size}px` : '0px' }}
1188
1192
  className={`chart-container p-relative ${config.legend.position === 'bottom' ? 'bottom' : ''}${config.legend.hide ? ' legend-hidden' : ''}${lineDatapointClass}${barBorderClass} ${contentClasses.join(' ')}`}
@@ -1193,16 +1197,19 @@ export default function CdcChart({ configUrl, config: configObj, isEditor = fals
1193
1197
  {/* Sparkline */}
1194
1198
  {config.visualizationType === 'Spark Line' && (
1195
1199
  <>
1196
- {description && <div className='subtext'>{parse(description)}</div>}
1197
- <div style={sparkLineStyles}>
1198
- <ParentSize>
1199
- {parent => (
1200
- <>
1201
- <SparkLine width={parent.width} height={parent.height} />
1202
- </>
1203
- )}
1204
- </ParentSize>
1200
+ {config?.introText && (
1201
+ <section className='introText' style={{ padding: '0px 0 35px' }}>
1202
+ {parse(config.introText)}
1203
+ </section>
1204
+ )}
1205
+ <div style={{ height: `100px`, width: `100%`, ...sparkLineStyles }}>
1206
+ <ParentSize>{parent => <SparkLine width={parent.width} height={parent.height} />}</ParentSize>
1205
1207
  </div>
1208
+ {description && (
1209
+ <div className='subtext' style={{ padding: '35px 0 15px' }}>
1210
+ {parse(description)}
1211
+ </div>
1212
+ )}
1206
1213
  </>
1207
1214
  )}
1208
1215
  {!config.legend.hide && config.visualizationType !== 'Spark Line' && <Legend />}
@@ -1225,9 +1232,7 @@ export default function CdcChart({ configUrl, config: configObj, isEditor = fals
1225
1232
  config={config}
1226
1233
  rawData={config.data}
1227
1234
  runtimeData={filteredData || excludedData}
1228
- //navigationHandler={navigationHandler} // do we need this? What does it do?
1229
1235
  expandDataTable={config.table.expanded}
1230
- //headerColor={general.headerColor} // have this in map but not chart
1231
1236
  columns={config.columns}
1232
1237
  showDownloadButton={config.general.showDownloadButton}
1233
1238
  runtimeLegend={dynamicLegendItems}
@@ -1247,6 +1252,7 @@ export default function CdcChart({ configUrl, config: configObj, isEditor = fals
1247
1252
  innerContainerRef={innerContainerRef}
1248
1253
  outerContainerRef={outerContainerRef}
1249
1254
  imageRef={imageId}
1255
+ colorScale={colorScale}
1250
1256
  isDebug={isDebug}
1251
1257
  isEditor={isEditor}
1252
1258
  />
@@ -0,0 +1,73 @@
1
+ import React, { useContext, memo } from 'react'
2
+
3
+ // cdc
4
+ import ConfigContext from '../ConfigContext'
5
+ import ErrorBoundary from '@cdc/core/components/ErrorBoundary'
6
+
7
+ // visx & d3
8
+ import * as allCurves from '@visx/curve'
9
+ import { Bar, AreaStack } from '@visx/shape'
10
+ import { Group } from '@visx/group'
11
+ import { approvedCurveTypes } from '@cdc/core/helpers/lineChartHelpers'
12
+
13
+ const AreaChartStacked = ({ xScale, yScale, yMax, xMax, handleTooltipMouseOver, handleTooltipMouseOff, isDebug, isBrush }) => {
14
+ // import data from context
15
+ let { transformedData: data, config, seriesHighlight, colorScale, rawData } = useContext(ConfigContext)
16
+
17
+ // Draw transparent bars over the chart to get tooltip data
18
+ // Turn DEBUG on for additional context.
19
+ if (!data) return
20
+
21
+ const handleDateCategory = value => {
22
+ if (config.xAxis.type === 'categorical') return xScale(value)
23
+ if (config.xAxis.type === 'date') {
24
+ let date = new Date(value)
25
+ return xScale(date)
26
+ }
27
+ }
28
+
29
+ const strokeWidth = 2
30
+
31
+ return (
32
+ data && (
33
+ <svg height={Number(yMax)}>
34
+ <ErrorBoundary component='AreaChartStacked'>
35
+ <Group className='area-chart' key='area-wrapper' left={Number(config.yAxis.size) + strokeWidth / 2} height={Number(yMax)} style={{ overflow: 'hidden' }}>
36
+ <AreaStack
37
+ data={data}
38
+ keys={config.runtime.areaSeriesKeys.map(s => s.dataKey) || config.series.map(s => s.dataKey)}
39
+ x0={d => handleDateCategory(d.data[config.xAxis.dataKey])}
40
+ y0={d => Number(yScale(d[0]))}
41
+ y1={d => Number(yScale(d[1]))}
42
+ curve={allCurves[approvedCurveTypes[config.stackedAreaChartLineType]]}
43
+ >
44
+ {({ stacks, path }) => {
45
+ return stacks.map((stack, stackIndex) => {
46
+ let transparentArea = config.legend.behavior === 'highlight' && seriesHighlight.length > 0 && seriesHighlight.indexOf(stack.key) === -1
47
+ let displayArea = config.legend.behavior === 'highlight' || seriesHighlight.length === 0 || seriesHighlight.indexOf(stack.key) !== -1
48
+
49
+ return (
50
+ // prettier-ignore
51
+ <path
52
+ key={stack.key}
53
+ d={path(stack) || ''}
54
+ strokeWidth={2}
55
+ stroke={displayArea ? colorScale ? colorScale(config.runtime.seriesLabels ? config.runtime.seriesLabels[stack.key] : stack.key) : '#000' : 'transparent'}
56
+ fillOpacity={transparentArea ? 0.25 : 0.5}
57
+ fill={displayArea ? colorScale ? colorScale(config.runtime.seriesLabels ? config.runtime.seriesLabels[stack.key] : stack.key) : '#000' : 'transparent'}
58
+ />
59
+ )
60
+ })
61
+ }}
62
+ </AreaStack>
63
+
64
+ {/* prettier-ignore */}
65
+ {!isBrush && <Bar width={Number(xMax)} height={Number(yMax)} fill={isDebug ? 'red' : 'transparent'} fillOpacity={0.05} style={isDebug ? { stroke: 'black', strokeWidth: 2 } : {}} onMouseMove={e => handleTooltipMouseOver(e, rawData)} onMouseLeave={handleTooltipMouseOff} />}
66
+ </Group>
67
+ </ErrorBoundary>
68
+ </svg>
69
+ )
70
+ )
71
+ }
72
+
73
+ export default memo(AreaChartStacked)
@@ -10,10 +10,8 @@ import { AreaClosed, LinePath, Bar } from '@visx/shape'
10
10
  import { Group } from '@visx/group'
11
11
  import { bisector } from 'd3-array'
12
12
 
13
- const AreaChart = ({ xScale, yScale, yMax, xMax, getXAxisData, getYAxisData, chartRef, handleTooltipMouseOver, handleTooltipMouseOff, tooltipData, isDebug, isBrush, brushData, children }) => {
14
- // enable various console logs in the file
15
- const DEBUG = isDebug
16
-
13
+ const AreaChart = props => {
14
+ const { xScale, yScale, yMax, xMax, handleTooltipMouseOver, handleTooltipMouseOff, isDebug, isBrush, brushData, children } = props
17
15
  // import data from context
18
16
  let { transformedData: data, config, handleLineType, parseDate, formatDate, formatNumber, seriesHighlight, colorScale, rawData } = useContext(ConfigContext)
19
17
 
@@ -23,7 +21,7 @@ const AreaChart = ({ xScale, yScale, yMax, xMax, getXAxisData, getYAxisData, cha
23
21
  if (isBrush && isDebug) console.log('###AREAchart BRUSH data, xScale, yScale, yMax, xMax', data, xScale, yScale, yMax, xMax)
24
22
 
25
23
  // Draw transparent bars over the chart to get tooltip data
26
- // Turn DEBUG on for additional context.
24
+ // Turn isDebug on for additional context.
27
25
  if (!data) return
28
26
 
29
27
  // Tooltip helper for getting data to the closest date/category hovered.
@@ -98,29 +96,29 @@ const AreaChart = ({ xScale, yScale, yMax, xMax, getXAxisData, getYAxisData, cha
98
96
  <React.Fragment key={index}>
99
97
  {/* prettier-ignore */}
100
98
  <LinePath
101
- data={seriesData}
102
- x={d => handleX(d)}
103
- y={d => handleY(d, index, s)}
104
- stroke={displayArea ? colorScale ? colorScale(config.runtime.seriesLabels ? config.runtime.seriesLabels[s.dataKey] : s.dataKey) : '#000' : 'transparent'}
105
- strokeWidth={2}
106
- strokeOpacity={1}
107
- shapeRendering='geometricPrecision'
108
- curve={curveType}
109
- strokeDasharray={s.type ? handleLineType(s.type) : 0}
110
- />
99
+ data={seriesData}
100
+ x={d => handleX(d)}
101
+ y={d => handleY(d, index, s)}
102
+ stroke={displayArea ? colorScale ? colorScale(config.runtime.seriesLabels ? config.runtime.seriesLabels[s.dataKey] : s.dataKey) : '#000' : 'transparent'}
103
+ strokeWidth={2}
104
+ strokeOpacity={1}
105
+ shapeRendering='geometricPrecision'
106
+ curve={curveType}
107
+ strokeDasharray={s.type ? handleLineType(s.type) : 0}
108
+ />
111
109
 
112
110
  {/* prettier-ignore */}
113
111
  <AreaClosed
114
- key={'area-chart'}
115
- fill={displayArea ? colorScale ? colorScale(config.runtime.seriesLabels ? config.runtime.seriesLabels[s.dataKey] : s.dataKey) : '#000' : 'transparent'}
116
- fillOpacity={transparentArea ? 0.25 : 0.5}
117
- data={seriesData}
118
- x={d => handleX(d)}
119
- y={d => handleY(d, index, s)}
120
- yScale={yScale}
121
- curve={curveType}
122
- strokeDasharray={s.type ? handleLineType(s.type) : 0}
123
- />
112
+ key={'area-chart'}
113
+ fill={displayArea ? colorScale ? colorScale(config.runtime.seriesLabels ? config.runtime.seriesLabels[s.dataKey] : s.dataKey) : '#000' : 'transparent'}
114
+ fillOpacity={transparentArea ? 0.25 : 0.5}
115
+ data={seriesData}
116
+ x={d => handleX(d)}
117
+ y={d => handleY(d, index, s)}
118
+ yScale={yScale}
119
+ curve={curveType}
120
+ strokeDasharray={s.type ? handleLineType(s.type) : 0}
121
+ />
124
122
  {getFirstBrushHandleOnly(children, index)}
125
123
  </React.Fragment>
126
124
  )
@@ -128,7 +126,7 @@ const AreaChart = ({ xScale, yScale, yMax, xMax, getXAxisData, getYAxisData, cha
128
126
 
129
127
  {/* Transparent bar for tooltips - disable if AreaChart is a brush */}
130
128
  {/* 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} />}
129
+ {!isBrush && <Bar width={Number(xMax)} height={Number(yMax)} fill={isDebug ? 'red' : 'transparent'} fillOpacity={0.05} style={isDebug ? { stroke: 'black', strokeWidth: 2 } : {}} onMouseMove={e => handleTooltipMouseOver(e, rawData)} onMouseLeave={handleTooltipMouseOff} />}
132
130
  </Group>
133
131
  </ErrorBoundary>
134
132
  </svg>
@@ -27,6 +27,8 @@ const BarChartStackedVertical = props => {
27
27
  const xAxisValue = config.runtime.xAxis.type === 'date' ? formatDate(parseDate(data[bar.index][config.runtime.xAxis.dataKey])) : data[bar.index][config.runtime.xAxis.dataKey]
28
28
  const yAxisValue = formatNumber(bar.bar ? bar.bar.data[bar.key] : 0, 'left')
29
29
 
30
+ if(!yAxisValue) return <></>
31
+
30
32
  const style = applyRadius(barStack.index)
31
33
  let yAxisTooltip = config.runtime.yAxis.label ? `${config.runtime.yAxis.label}: ${yAxisValue}` : yAxisValue
32
34
  const xAxisTooltip = config.runtime.xAxis.label ? `${config.runtime.xAxis.label}: ${xAxisValue}` : xAxisValue