@cdc/chart 4.23.1 → 4.23.3

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 (74) hide show
  1. package/dist/cdcchart.js +56289 -702
  2. package/examples/Barchart_with_negative.json +34 -0
  3. package/examples/area-chart.json +187 -0
  4. package/examples/big-small-test-bar.json +328 -0
  5. package/examples/big-small-test-line.json +328 -0
  6. package/examples/big-small-test-negative.json +328 -0
  7. package/examples/box-plot.json +1 -2
  8. package/examples/dynamic-legends.json +1 -1
  9. package/examples/example-bar-chart-nonnumeric.json +36 -0
  10. package/examples/example-bar-chart.json +36 -0
  11. package/examples/example-combo-bar-nonnumeric.json +105 -0
  12. package/examples/example-sparkline.json +76 -0
  13. package/examples/gallery/bar-chart-horizontal/horizontal-bar-chart.json +31 -172
  14. package/examples/gallery/bar-chart-vertical/vertical-bar-chart-categorical.json +1 -1
  15. package/examples/gallery/bar-chart-vertical/vertical-bar-chart-confidence.json +1 -0
  16. package/examples/gallery/bar-chart-vertical/vertical-bar-chart-with-confidence.json +96 -14
  17. package/examples/gallery/bar-chart-vertical/vertical-bar-chart.json +2 -2
  18. package/examples/gallery/line/line.json +1 -0
  19. package/examples/gallery/paired-bar/paired-bar-chart.json +65 -13
  20. package/examples/horizontal-chart-max-increase.json +38 -0
  21. package/examples/line-chart-max-increase.json +32 -0
  22. package/examples/line-chart-nonnumeric.json +32 -0
  23. package/examples/line-chart.json +21 -63
  24. package/examples/newdata.json +1 -1
  25. package/examples/planet-combo-example-config.json +143 -20
  26. package/examples/planet-deviation-config.json +168 -0
  27. package/examples/planet-deviation-data.json +38 -0
  28. package/examples/planet-example-config.json +139 -20
  29. package/examples/planet-example-data-max-increase.json +56 -0
  30. package/examples/planet-example-data-nonnumeric.json +56 -0
  31. package/examples/planet-example-data.json +9 -9
  32. package/examples/planet-pie-example-config-nonnumeric.json +30 -0
  33. package/examples/scatterplot-continuous.csv +17 -0
  34. package/examples/scatterplot.json +136 -0
  35. package/examples/sparkline-chart-nonnumeric.json +76 -0
  36. package/examples/stacked-vertical-bar-example-negative.json +154 -0
  37. package/examples/stacked-vertical-bar-example-nonnumerics.json +154 -0
  38. package/index.html +91 -0
  39. package/package.json +33 -24
  40. package/src/{CdcChart.tsx → CdcChart.jsx} +196 -124
  41. package/src/components/AreaChart.jsx +198 -0
  42. package/src/components/{BarChart.tsx → BarChart.jsx} +154 -122
  43. package/src/components/BoxPlot.jsx +101 -0
  44. package/src/components/{DataTable.tsx → DataTable.jsx} +109 -28
  45. package/src/components/DeviationBar.jsx +191 -0
  46. package/src/components/{EditorPanel.js → EditorPanel.jsx} +676 -157
  47. package/src/components/{Filters.js → Filters.jsx} +6 -11
  48. package/src/components/Legend.jsx +316 -0
  49. package/src/components/{LineChart.tsx → LineChart.jsx} +22 -26
  50. package/src/components/{LinearChart.tsx → LinearChart.jsx} +214 -91
  51. package/src/components/{PairedBarChart.tsx → PairedBarChart.jsx} +44 -78
  52. package/src/components/{PieChart.tsx → PieChart.jsx} +26 -44
  53. package/src/components/ScatterPlot.jsx +51 -0
  54. package/src/components/SparkLine.jsx +218 -0
  55. package/src/components/{useIntersectionObserver.tsx → useIntersectionObserver.jsx} +2 -2
  56. package/src/data/initial-state.js +51 -5
  57. package/src/hooks/useColorPalette.js +68 -0
  58. package/src/hooks/{useReduceData.ts → useReduceData.js} +26 -16
  59. package/src/hooks/useRightAxis.js +3 -1
  60. package/src/index.jsx +16 -0
  61. package/src/scss/DataTable.scss +22 -0
  62. package/src/scss/editor-panel.scss +5 -0
  63. package/src/scss/main.scss +30 -10
  64. package/src/test/CdcChart.test.jsx +6 -0
  65. package/vite.config.js +4 -0
  66. package/dist/495.js +0 -3
  67. package/dist/703.js +0 -1
  68. package/src/components/BoxPlot.js +0 -92
  69. package/src/components/Legend.js +0 -291
  70. package/src/components/SparkLine.js +0 -185
  71. package/src/hooks/useColorPalette.ts +0 -76
  72. package/src/index.html +0 -67
  73. package/src/index.tsx +0 -18
  74. /package/src/{context.tsx → ConfigContext.jsx} +0 -0
@@ -1,7 +1,6 @@
1
1
  import React, { useState, useEffect, useCallback } from 'react'
2
2
 
3
3
  // IE11
4
- import 'core-js/stable'
5
4
  import ResizeObserver from 'resize-observer-polyfill'
6
5
  import 'whatwg-fetch'
7
6
  import * as d3 from 'd3-array'
@@ -13,14 +12,14 @@ import { timeParse, timeFormat } from 'd3-time-format'
13
12
  import { format } from 'd3-format'
14
13
  import Papa from 'papaparse'
15
14
  import parse from 'html-react-parser'
16
- import { Base64 } from 'js-base64'
15
+ import 'react-tooltip/dist/react-tooltip.css'
17
16
 
18
17
  // Primary Components
19
- import Context from './context'
18
+ import ConfigContext from './ConfigContext'
20
19
  import PieChart from './components/PieChart'
21
20
  import LinearChart from './components/LinearChart'
22
21
 
23
- import { colorPalettesChart as colorPalettes } from '../../core/data/colorPalettes'
22
+ import { colorPalettesChart as colorPalettes, twoColorPalette } from '@cdc/core/data/colorPalettes'
24
23
 
25
24
  import { publish, subscribe, unsubscribe } from '@cdc/core/helpers/events'
26
25
 
@@ -33,46 +32,57 @@ import defaults from './data/initial-state'
33
32
  import EditorPanel from './components/EditorPanel'
34
33
  import Loading from '@cdc/core/components/Loading'
35
34
  import Filters from './components/Filters'
36
- import CoveMediaControls from '@cdc/core/helpers/CoveMediaControls'
35
+ import CoveMediaControls from '@cdc/core/components/CoveMediaControls'
37
36
 
38
- // helpers
37
+ // Helpers
39
38
  import numberFromString from '@cdc/core/helpers/numberFromString'
40
39
  import getViewport from '@cdc/core/helpers/getViewport'
41
40
  import { DataTransform } from '@cdc/core/helpers/DataTransform'
42
41
  import cacheBustingString from '@cdc/core/helpers/cacheBustingString'
42
+ import isNumber from '@cdc/core/helpers/isNumber'
43
+ import cleanData from '@cdc/core/helpers/cleanData'
43
44
 
44
45
  import './scss/main.scss'
45
46
 
46
- export default function CdcChart({ configUrl, config: configObj, isEditor = false, isDashboard = false, setConfig: setParentConfig, setEditing, hostname, link }: { configUrl?: string; config?: any; isEditor?: boolean; isDashboard?: boolean; setConfig?; setEditing?; hostname?; link?: any }) {
47
+ export default function CdcChart({ configUrl, config: configObj, isEditor = false, isDashboard = false, setConfig: setParentConfig, setEditing, hostname, link }) {
47
48
  const transform = new DataTransform()
48
- interface keyable {
49
- [key: string]: any
50
- }
51
-
52
- const [loading, setLoading] = useState<Boolean>(true)
53
- const [colorScale, setColorScale] = useState<any>(null)
54
- const [config, setConfig] = useState<keyable>({})
55
- const [stateData, setStateData] = useState<Array<Object>>(config.data || [])
56
- const [excludedData, setExcludedData] = useState<Array<Object>>()
57
- const [filteredData, setFilteredData] = useState<Array<Object>>()
58
- const [seriesHighlight, setSeriesHighlight] = useState<Array<String>>([])
59
- const [currentViewport, setCurrentViewport] = useState<String>('lg')
60
- const [dimensions, setDimensions] = useState<Array<Number>>([])
49
+ const [loading, setLoading] = useState(true)
50
+ const [colorScale, setColorScale] = useState(null)
51
+ const [config, setConfig] = useState({})
52
+ const [stateData, setStateData] = useState(config.data || [])
53
+ const [excludedData, setExcludedData] = useState()
54
+ const [filteredData, setFilteredData] = useState()
55
+ const [seriesHighlight, setSeriesHighlight] = useState([])
56
+ const [currentViewport, setCurrentViewport] = useState('lg')
57
+ const [dimensions, setDimensions] = useState([])
61
58
  const [externalFilters, setExternalFilters] = useState(null)
62
59
  const [container, setContainer] = useState()
63
60
  const [coveLoadedEventRan, setCoveLoadedEventRan] = useState(false)
64
61
  const [dynamicLegendItems, setDynamicLegendItems] = useState([])
65
- const [imageId, setImageId] = useState(`cove-${Math.random().toString(16).slice(-4)}`)
66
-
67
- const legendGlyphSize = 15
68
- const legendGlyphSizeHalf = legendGlyphSize / 2
62
+ const [imageId] = useState(`cove-${Math.random().toString(16).slice(-4)}`)
69
63
 
70
64
  // Destructure items from config for more readable JSX
71
- const { legend, title, description, visualizationType } = config
72
- const { barBorderClass, lineDatapointClass, contentClasses, innerContainerClasses, sparkLineStyles } = useDataVizClasses(config)
65
+ let { legend, title, description, visualizationType } = config
66
+
67
+ // set defaults on titles if blank AND only in editor
68
+ if (isEditor) {
69
+ if (!title || title === '') title = 'Chart Title'
70
+ }
71
+
72
+ if (config.table && (!config.table?.label || config.table?.label === '')) config.table.label = 'Data Table'
73
+
74
+ const { barBorderClass, lineDatapointClass, contentClasses, sparkLineStyles } = useDataVizClasses(config)
73
75
 
74
76
  const handleChartTabbing = config.showSidebar ? `#legend` : config?.title ? `#dataTableSection__${config.title.replace(/\s/g, '')}` : `#dataTableSection`
75
77
 
78
+ const sortAsc = (a, b) => {
79
+ return a.toString().localeCompare(b.toString(), 'en', { numeric: true })
80
+ }
81
+
82
+ const sortDesc = (a, b) => {
83
+ return b.toString().localeCompare(a.toString(), 'en', { numeric: true })
84
+ }
85
+
76
86
  const handleChartAriaLabels = (state, testing = false) => {
77
87
  if (testing) console.log(`handleChartAriaLabels Testing On:`, state)
78
88
  try {
@@ -89,7 +99,20 @@ export default function CdcChart({ configUrl, config: configObj, isEditor = fals
89
99
 
90
100
  return ariaLabel
91
101
  } catch (e) {
92
- console.error(e.message)
102
+ console.error('COVE: ', e.message) // eslint-disable-line
103
+ }
104
+ }
105
+
106
+ const handleLineType = lineType => {
107
+ switch (lineType) {
108
+ case 'dashed-sm':
109
+ return '5 5'
110
+ case 'dashed-md':
111
+ return '10 5'
112
+ case 'dashed-lg':
113
+ return '15 5'
114
+ default:
115
+ return 0
93
116
  }
94
117
  }
95
118
 
@@ -121,7 +144,7 @@ export default function CdcChart({ configUrl, config: configObj, isEditor = fals
121
144
  data = await fetch(response.dataUrl + `?v=${cacheBustingString()}`).then(response => response.json())
122
145
  }
123
146
  } catch {
124
- console.error(`Cannot parse URL: ${response.dataUrl}`)
147
+ console.error(`COVE: Cannot parse URL: ${response.dataUrl}`)
125
148
  data = []
126
149
  }
127
150
 
@@ -137,6 +160,9 @@ export default function CdcChart({ configUrl, config: configObj, isEditor = fals
137
160
  }
138
161
 
139
162
  let newConfig = { ...defaults, ...response }
163
+ if (newConfig.visualizationType === 'Box Plot') {
164
+ newConfig.legend.hide = true
165
+ }
140
166
  if (undefined === newConfig.table.show) newConfig.table.show = !isDashboard
141
167
  updateConfig(newConfig, data)
142
168
  }
@@ -189,7 +215,7 @@ export default function CdcChart({ configUrl, config: configObj, isEditor = fals
189
215
  newConfig.filters.forEach((filter, index) => {
190
216
  let filterValues = []
191
217
 
192
- filterValues = filter.orderedValues || generateValuesForFilter(filter.columnName, newExcludedData)
218
+ filterValues = filter.orderedValues || generateValuesForFilter(filter.columnName, newExcludedData).sort(filter.order === 'desc' ? sortDesc : sortAsc)
193
219
 
194
220
  newConfig.filters[index].values = filterValues
195
221
  // Initial filter should be active
@@ -219,11 +245,8 @@ export default function CdcChart({ configUrl, config: configObj, isEditor = fals
219
245
  }
220
246
 
221
247
  if (newConfig.visualizationType === 'Box Plot' && newConfig.series) {
222
- console.log('hit', newConfig)
223
-
224
- // stats
225
- let allKeys = data.map(d => d[newConfig.xAxis.dataKey])
226
- let allValues = data.map(d => Number(d[newConfig?.series[0]?.dataKey]))
248
+ let allKeys = newExcludedData ? newExcludedData.map(d => d[newConfig.xAxis.dataKey]) : data.map(d => d[newConfig.xAxis.dataKey])
249
+ let allValues = newExcludedData ? newExcludedData.map(d => Number(d[newConfig?.series[0]?.dataKey])) : data.map(d => Number(d[newConfig?.series[0]?.dataKey]))
227
250
 
228
251
  const uniqueArray = function (arrArg) {
229
252
  return arrArg.filter(function (elem, pos, arr) {
@@ -232,50 +255,75 @@ export default function CdcChart({ configUrl, config: configObj, isEditor = fals
232
255
  }
233
256
 
234
257
  const groups = uniqueArray(allKeys)
258
+ let tableData = []
235
259
  const plots = []
236
260
 
237
- console.log('d', data)
238
- console.log('newConfig', newConfig)
239
- console.log('groups', groups)
240
- console.log('allKeys', allKeys)
241
- console.log('allValues', allValues)
242
-
243
261
  // group specific statistics
244
262
  // prevent re-renders
245
- groups.map((g, index) => {
246
- if (!g) return
247
- // filter data by group
248
- let filteredData = data.filter(item => item[newConfig.xAxis.dataKey] === g)
249
- let filteredDataValues = filteredData.map(item => Number(item[newConfig?.series[0]?.dataKey]))
250
- console.log('g', g)
251
- console.log('item', filteredData)
252
- console.log('item', newConfig)
253
- // let filteredDataValues = filteredData.map(item => Number(item[newConfig.yAxis.dataKey]))
254
-
255
- const q1 = d3.quantile(filteredDataValues, 0.25)
256
- const q3 = d3.quantile(filteredDataValues, 0.75)
257
- const iqr = q3 - q1
258
- const lowerBounds = q1 - (q3 - q1) * 1.5
259
- const upperBounds = q3 + (q3 - q1) * 1.5
260
- const outliers = filteredDataValues.filter(v => v < lowerBounds || v > upperBounds)
261
- plots.push({
262
- columnCategory: g,
263
- columnMean: d3.mean(filteredDataValues),
264
- columnMedian: d3.median(filteredDataValues),
265
- columnFirstQuartile: q1,
266
- columnThirdQuartile: q3,
267
- columnMin: q1 - 1.5 * iqr,
268
- columnMax: q3 + 1.5 * iqr,
269
- columnIqr: iqr,
270
- columnOutliers: outliers,
271
- values: filteredDataValues
272
- })
263
+ if (!groups) return
264
+ groups.forEach((g, index) => {
265
+ try {
266
+ if (!g) throw new Error('No groups resolved in box plots')
267
+
268
+ // filter data by group
269
+ let filteredData = newExcludedData ? newExcludedData.filter(item => item[newConfig.xAxis.dataKey] === g) : data.filter(item => item[newConfig.xAxis.dataKey] === g)
270
+ let filteredDataValues = filteredData.map(item => Number(item[newConfig?.series[0]?.dataKey]))
271
+ // let filteredDataValues = filteredData.map(item => Number(item[newConfig.yAxis.dataKey]))
272
+
273
+ if (!filteredData) throw new Error('boxplots dont have data yet')
274
+ if (!plots) throw new Error('boxplots dont have plots yet')
275
+ if (newConfig.boxplot.firstQuartilePercentage === '') {
276
+ newConfig.boxplot.firstQuartilePercentage = 0
277
+ }
278
+
279
+ if (newConfig.boxplot.thirdQuartilePercentage === '') {
280
+ newConfig.boxplot.thirdQuartilePercentage = 0
281
+ }
282
+
283
+ const q1 = d3.quantile(filteredDataValues, parseFloat(newConfig.boxplot.firstQuartilePercentage) / 100)
284
+ const q3 = d3.quantile(filteredDataValues, parseFloat(newConfig.boxplot.thirdQuartilePercentage) / 100)
285
+ const iqr = q3 - q1
286
+ const lowerBounds = q1 - (q3 - q1) * 1.5
287
+ const upperBounds = q3 + (q3 - q1) * 1.5
288
+ const outliers = filteredDataValues.filter(v => v < lowerBounds || v > upperBounds)
289
+ let nonOutliers = filteredDataValues
290
+
291
+ nonOutliers = nonOutliers.filter(item => !outliers.includes(item))
292
+
293
+ plots.push({
294
+ columnCategory: g,
295
+ columnMax: Number(q3 + 1.5 * iqr).toFixed(newConfig.dataFormat.roundTo),
296
+ columnThirdQuartile: Number(q3).toFixed(newConfig.dataFormat.roundTo),
297
+ columnMedian: Number(d3.median(filteredDataValues)).toFixed(newConfig.dataFormat.roundTo),
298
+ columnFirstQuartile: q1.toFixed(newConfig.dataFormat.roundTo),
299
+ columnMin: Number(q1 - 1.5 * iqr).toFixed(newConfig.dataFormat.roundTo),
300
+ columnTotal: filteredDataValues.reduce((partialSum, a) => partialSum + a, 0),
301
+ columnSd: Number(d3.deviation(filteredDataValues)).toFixed(newConfig.dataFormat.roundTo),
302
+ columnMean: Number(d3.mean(filteredDataValues)).toFixed(newConfig.dataFormat.roundTo),
303
+ columnIqr: Number(iqr).toFixed(newConfig.dataFormat.roundTo),
304
+ columnOutliers: outliers,
305
+ values: filteredDataValues,
306
+ nonOutlierValues: nonOutliers
307
+ })
308
+ } catch (e) {
309
+ console.error('COVE: ', e.message) // eslint-disable-line
310
+ }
311
+ })
312
+
313
+ // make deep copy so we can remove some fields for data
314
+ // this appears to be the easiest option instead of running logic against the datatable cell...
315
+ tableData = JSON.parse(JSON.stringify(plots))
316
+ tableData.map(table => {
317
+ delete table.columnIqr
318
+ delete table.nonOutlierValues
319
+ return null // resolve eslint
273
320
  })
274
321
 
275
322
  // any other data we can add to boxplots
276
323
  newConfig.boxplot['allValues'] = allValues
277
324
  newConfig.boxplot['categories'] = groups
278
- newConfig.boxplot.push(...plots)
325
+ newConfig.boxplot.plots = plots
326
+ newConfig.boxplot.tableData = tableData
279
327
  }
280
328
 
281
329
  if (newConfig.visualizationType === 'Combo' && newConfig.series) {
@@ -291,7 +339,7 @@ export default function CdcChart({ configUrl, config: configObj, isEditor = fals
291
339
  })
292
340
  }
293
341
 
294
- if ((newConfig.visualizationType === 'Bar' && newConfig.orientation === 'horizontal') || newConfig.visualizationType === 'Paired Bar') {
342
+ if (((newConfig.visualizationType === 'Bar' || newConfig.visualizationType === 'Deviation Bar') && newConfig.orientation === 'horizontal') || newConfig.visualizationType === 'Paired Bar') {
295
343
  newConfig.runtime.xAxis = newConfig.yAxis
296
344
  newConfig.runtime.yAxis = newConfig.xAxis
297
345
  newConfig.runtime.horizontal = true
@@ -328,6 +376,7 @@ export default function CdcChart({ configUrl, config: configObj, isEditor = fals
328
376
 
329
377
  data.forEach(row => {
330
378
  const value = row[columnName]
379
+ //@ts-ignore
331
380
  if (value && false === values.includes(value)) {
332
381
  values.push(value)
333
382
  }
@@ -352,7 +401,7 @@ export default function CdcChart({ configUrl, config: configObj, isEditor = fals
352
401
  }
353
402
 
354
403
  // Observes changes to outermost container and changes viewport size in state
355
- const resizeObserver: ResizeObserver = new ResizeObserver(entries => {
404
+ const resizeObserver = new ResizeObserver(entries => {
356
405
  for (let entry of entries) {
357
406
  let { width, height } = entry.contentRect
358
407
  let newViewport = getViewport(width)
@@ -381,7 +430,7 @@ export default function CdcChart({ configUrl, config: configObj, isEditor = fals
381
430
  }
382
431
 
383
432
  setContainer(node)
384
- }, [])
433
+ }, []) // eslint-disable-line
385
434
 
386
435
  function isEmpty(obj) {
387
436
  return Object.keys(obj).length === 0
@@ -390,7 +439,7 @@ export default function CdcChart({ configUrl, config: configObj, isEditor = fals
390
439
  // Load data when component first mounts
391
440
  useEffect(() => {
392
441
  loadConfig()
393
- }, [])
442
+ }, []) // eslint-disable-line
394
443
 
395
444
  /**
396
445
  * When cove has a config and container ref publish the cove_loaded event.
@@ -400,7 +449,7 @@ export default function CdcChart({ configUrl, config: configObj, isEditor = fals
400
449
  publish('cove_loaded', { config: config })
401
450
  setCoveLoadedEventRan(true)
402
451
  }
403
- }, [container, config])
452
+ }, [container, config]) // eslint-disable-line
404
453
 
405
454
  /**
406
455
  * Handles filter change events outside of COVE
@@ -408,13 +457,13 @@ export default function CdcChart({ configUrl, config: configObj, isEditor = fals
408
457
  * Another useEffect listens to externalFilterChanges and updates the config.
409
458
  */
410
459
  useEffect(() => {
411
- const handleFilterData = (e: CustomEvent) => {
460
+ const handleFilterData = e => {
412
461
  let tmp = []
413
462
  tmp.push(e.detail)
414
463
  setExternalFilters(tmp)
415
464
  }
416
465
 
417
- subscribe('cove_filterData', (e: CustomEvent) => handleFilterData(e))
466
+ subscribe('cove_filterData', e => handleFilterData(e))
418
467
 
419
468
  return () => {
420
469
  unsubscribe('cove_filterData', handleFilterData)
@@ -443,19 +492,22 @@ export default function CdcChart({ configUrl, config: configObj, isEditor = fals
443
492
  setConfig(newConfigHere)
444
493
  setFilteredData(filterData(externalFilters, excludedData))
445
494
  }
446
- }, [externalFilters])
495
+ }, [externalFilters]) // eslint-disable-line
447
496
 
448
497
  // Load data when configObj data changes
449
498
  if (configObj) {
499
+ // eslint-disable-next-line react-hooks/rules-of-hooks
450
500
  useEffect(() => {
451
501
  loadConfig()
452
- }, [configObj.data])
502
+ }, [configObj.data]) // eslint-disable-line
453
503
  }
454
504
 
455
505
  // Generates color palette to pass to child chart component
456
506
  useEffect(() => {
457
507
  if (stateData && config.xAxis && config.runtime.seriesKeys) {
458
- let palette = config.customColors || colorPalettes[config.palette]
508
+ const configPalette = config.visualizationType === 'Paired Bar' || config.visualizationType === 'Deviation Bar' ? config.twoColor.palette : config.palette
509
+ const allPalettes = { ...colorPalettes, ...twoColorPalette }
510
+ let palette = config.customColors || allPalettes[configPalette]
459
511
  let numberOfKeys = config.runtime.seriesKeys.length
460
512
  let newColorScale
461
513
 
@@ -478,7 +530,7 @@ export default function CdcChart({ configUrl, config: configObj, isEditor = fals
478
530
  if (config && stateData && config.sortData) {
479
531
  stateData.sort(sortData)
480
532
  }
481
- }, [config, stateData])
533
+ }, [config, stateData]) // eslint-disable-line
482
534
 
483
535
  // Called on legend click, highlights/unhighlights the data series with the given label
484
536
  const highlight = label => {
@@ -523,7 +575,7 @@ export default function CdcChart({ configUrl, config: configObj, isEditor = fals
523
575
 
524
576
  const section = config.orientation === 'horizontal' ? 'yAxis' : 'xAxis'
525
577
 
526
- const parseDate = (dateString: string) => {
578
+ const parseDate = dateString => {
527
579
  let date = timeParse(config.runtime[section].dateParseFormat)(dateString)
528
580
  if (!date) {
529
581
  config.runtime.editorErrorMessage = `Error parsing date "${dateString}". Try reviewing your data and date parse settings in the X Axis section.`
@@ -533,48 +585,37 @@ export default function CdcChart({ configUrl, config: configObj, isEditor = fals
533
585
  }
534
586
  }
535
587
 
536
- const formatDate = (date: Date) => {
588
+ const formatDate = date => {
537
589
  return timeFormat(config.runtime[section].dateDisplayFormat)(date)
538
590
  }
539
591
 
540
- const DownloadButton = ({ data }: any, type = 'link') => {
541
- const fileName = `${config.title.substring(0, 50)}.csv`
592
+ // function calculates the width of given text and its font-size
593
+ function getTextWidth(text, font) {
594
+ const canvas = document.createElement('canvas')
595
+ const context = canvas.getContext('2d')
542
596
 
543
- const csvData = Papa.unparse(data)
597
+ context.font = font || getComputedStyle(document.body).font
544
598
 
545
- const saveBlob = () => {
546
- //@ts-ignore
547
- if (typeof window.navigator.msSaveBlob === 'function') {
548
- const dataBlob = new Blob([csvData], { type: 'text/csv;charset=utf-8;' })
549
- //@ts-ignore
550
- window.navigator.msSaveBlob(dataBlob, fileName)
551
- }
552
- }
553
-
554
- if (type === 'download') {
555
- return (
556
- <a download={fileName} onClick={saveBlob} href={`data:text/csv;base64,${Base64.encode(csvData)}`} aria-label='Download this data in a CSV file format.' className={`btn btn-download no-border`}>
557
- Download Data (CSV)
558
- </a>
559
- )
560
- } else {
561
- return (
562
- <a download={fileName} onClick={saveBlob} href={`data:text/csv;base64,${Base64.encode(csvData)}`} aria-label='Download this data in a CSV file format.' className={`btn no-border`}>
563
- Download Data (CSV)
564
- </a>
565
- )
566
- }
599
+ return Math.ceil(context.measureText(text).width)
567
600
  }
568
601
 
569
602
  // Format numeric data based on settings in config
570
603
  const formatNumber = (num, axis) => {
571
604
  // if num is NaN return num
572
605
  if (isNaN(num) || !num) return num
606
+ // Check if the input number is negative
607
+ const isNegative = num < 0
608
+
609
+ // If the input number is negative, take the absolute value
610
+ if (isNegative) {
611
+ num = Math.abs(num)
612
+ }
573
613
 
574
614
  // destructure dataFormat values
575
615
  let {
576
- dataFormat: { commas, abbreviated, roundTo, prefix, suffix, rightRoundTo, rightPrefix, rightSuffix }
616
+ dataFormat: { commas, abbreviated, roundTo, prefix, suffix, rightRoundTo, bottomRoundTo, rightPrefix, rightSuffix, bottomPrefix, bottomSuffix, bottomAbbreviated }
577
617
  } = config
618
+
578
619
  let formatSuffix = format('.2s')
579
620
 
580
621
  // check if value contains comma and remove it. later will add comma below.
@@ -582,14 +623,15 @@ export default function CdcChart({ configUrl, config: configObj, isEditor = fals
582
623
 
583
624
  let original = num
584
625
  let stringFormattingOptions
585
-
586
- if (axis !== 'right') {
626
+ if (axis === 'left') {
587
627
  stringFormattingOptions = {
588
628
  useGrouping: config.dataFormat.commas ? true : false,
589
629
  minimumFractionDigits: roundTo ? Number(roundTo) : 0,
590
630
  maximumFractionDigits: roundTo ? Number(roundTo) : 0
591
631
  }
592
- } else {
632
+ }
633
+
634
+ if (axis === 'right') {
593
635
  stringFormattingOptions = {
594
636
  useGrouping: config.dataFormat.rightCommas ? true : false,
595
637
  minimumFractionDigits: rightRoundTo ? Number(rightRoundTo) : 0,
@@ -597,6 +639,14 @@ export default function CdcChart({ configUrl, config: configObj, isEditor = fals
597
639
  }
598
640
  }
599
641
 
642
+ if (axis === 'bottom') {
643
+ stringFormattingOptions = {
644
+ useGrouping: config.dataFormat.bottomCommas ? true : false,
645
+ minimumFractionDigits: bottomRoundTo ? Number(bottomRoundTo) : 0,
646
+ maximumFractionDigits: bottomRoundTo ? Number(bottomRoundTo) : 0
647
+ }
648
+ }
649
+
600
650
  num = numberFromString(num)
601
651
 
602
652
  if (isNaN(num)) {
@@ -617,8 +667,11 @@ export default function CdcChart({ configUrl, config: configObj, isEditor = fals
617
667
  // Use commas also updates bars and the data table
618
668
  // We can't use commas when we're formatting the dataFormatted number
619
669
  // Example: commas -> 12,000; abbreviated -> 12k (correct); abbreviated & commas -> 12 (incorrect)
620
- if (axis === 'left' && commas && abbreviated) {
621
- num = num
670
+ //
671
+ // Edge case for small numbers with decimals
672
+ // - if roundTo undefined which means it is blank, then do not round
673
+ if ((axis === 'left' && commas && abbreviated) || (axis === 'bottom' && commas && abbreviated)) {
674
+ num = num // eslint-disable-line
622
675
  } else {
623
676
  num = num.toLocaleString('en-US', stringFormattingOptions)
624
677
  }
@@ -628,11 +681,11 @@ export default function CdcChart({ configUrl, config: configObj, isEditor = fals
628
681
  num = formatSuffix(parseFloat(num)).replace('G', 'B')
629
682
  }
630
683
 
631
- if (abbreviated && axis === 'bottom') {
684
+ if (bottomAbbreviated && axis === 'bottom') {
632
685
  num = formatSuffix(parseFloat(num)).replace('G', 'B')
633
686
  }
634
687
 
635
- if (prefix && axis !== 'right') {
688
+ if (prefix && axis === 'left') {
636
689
  result += prefix
637
690
  }
638
691
 
@@ -640,9 +693,13 @@ export default function CdcChart({ configUrl, config: configObj, isEditor = fals
640
693
  result += rightPrefix
641
694
  }
642
695
 
696
+ if (bottomPrefix && axis === 'bottom') {
697
+ result += bottomPrefix
698
+ }
699
+
643
700
  result += num
644
701
 
645
- if (suffix && axis !== 'right') {
702
+ if (suffix && axis === 'left') {
646
703
  result += suffix
647
704
  }
648
705
 
@@ -650,6 +707,13 @@ export default function CdcChart({ configUrl, config: configObj, isEditor = fals
650
707
  result += rightSuffix
651
708
  }
652
709
 
710
+ if (bottomSuffix && axis === 'bottom') {
711
+ result += bottomSuffix
712
+ }
713
+ if (isNegative) {
714
+ result = '-' + result
715
+ }
716
+
653
717
  return String(result)
654
718
  }
655
719
 
@@ -660,7 +724,10 @@ export default function CdcChart({ configUrl, config: configObj, isEditor = fals
660
724
  Line: <LinearChart />,
661
725
  Combo: <LinearChart />,
662
726
  Pie: <PieChart />,
663
- 'Box Plot': <LinearChart />
727
+ 'Box Plot': <LinearChart />,
728
+ 'Area Chart': <LinearChart />,
729
+ 'Scatter Plot': <LinearChart />,
730
+ 'Deviation Bar': <LinearChart />
664
731
  }
665
732
 
666
733
  const missingRequiredSections = () => {
@@ -692,7 +759,7 @@ export default function CdcChart({ configUrl, config: configObj, isEditor = fals
692
759
  <div className='cdc-chart-inner-container'>
693
760
  {/* Title */}
694
761
 
695
- {title && (
762
+ {title && config.showTitle && (
696
763
  <div role='heading' className={`chart-title ${config.theme} cove-component__header`} aria-level={2}>
697
764
  {config && <sup className='superTitle'>{parse(config.superTitle || '')}</sup>}
698
765
  <div>{parse(title)}</div>
@@ -706,7 +773,7 @@ export default function CdcChart({ configUrl, config: configObj, isEditor = fals
706
773
  {/* Visualization */}
707
774
  {config?.introText && <section className='introText'>{parse(config.introText)}</section>}
708
775
  <div
709
- style={{ marginBottom: config.legend.position !== 'bottom' && config.orientation === 'horizontal' ? `${config.runtime.xAxis.size}px` : '0px' }}
776
+ style={{ marginBottom: config.legend.position !== 'bottom' && currentViewport !== 'sm' && currentViewport !== 'xs' && config.orientation === 'horizontal' ? `${config.runtime.xAxis.size}px` : '0px' }}
710
777
  className={`chart-container ${config.legend.position === 'bottom' ? 'bottom' : ''}${config.legend.hide ? ' legend-hidden' : ''}${lineDatapointClass}${barBorderClass} ${contentClasses.join(' ')}`}
711
778
  >
712
779
  {/* All charts except sparkline */}
@@ -750,8 +817,8 @@ export default function CdcChart({ configUrl, config: configObj, isEditor = fals
750
817
  )
751
818
  }
752
819
 
753
- const getXAxisData = (d: any) => (config.runtime.xAxis.type === 'date' ? parseDate(d[config.runtime.originalXAxis.dataKey]).getTime() : d[config.runtime.originalXAxis.dataKey])
754
- const getYAxisData = (d: any, seriesKey: string) => d[seriesKey]
820
+ const getXAxisData = d => (config.runtime.xAxis.type === 'date' ? parseDate(d[config.runtime.originalXAxis.dataKey]).getTime() : d[config.runtime.originalXAxis.dataKey])
821
+ const getYAxisData = (d, seriesKey) => d[seriesKey]
755
822
 
756
823
  const contextValues = {
757
824
  getXAxisData,
@@ -785,7 +852,12 @@ export default function CdcChart({ configUrl, config: configObj, isEditor = fals
785
852
  dynamicLegendItems,
786
853
  setDynamicLegendItems,
787
854
  filterData,
788
- imageId
855
+ imageId,
856
+ handleLineType,
857
+ isNumber,
858
+ cleanData,
859
+ getTextWidth,
860
+ twoColorPalette
789
861
  }
790
862
 
791
863
  const classes = ['cdc-open-viz-module', 'type-chart', `${currentViewport}`, `font-${config.fontSize}`, `${config.theme}`]
@@ -795,10 +867,10 @@ export default function CdcChart({ configUrl, config: configObj, isEditor = fals
795
867
  isEditor && classes.push('isEditor')
796
868
 
797
869
  return (
798
- <Context.Provider value={contextValues}>
870
+ <ConfigContext.Provider value={contextValues}>
799
871
  <div className={`${classes.join(' ')}`} ref={outerContainerRef} data-lollipop={config.isLollipopChart} data-download-id={imageId}>
800
872
  {body}
801
873
  </div>
802
- </Context.Provider>
874
+ </ConfigContext.Provider>
803
875
  )
804
876
  }