@cdc/chart 4.22.11 → 4.23.2

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 (65) hide show
  1. package/dist/cdcchart.js +54569 -16
  2. package/examples/Barchart_with_negative.json +34 -0
  3. package/examples/box-plot-data.json +71 -0
  4. package/examples/box-plot.csv +5 -0
  5. package/examples/box-plot.json +124 -0
  6. package/examples/dynamic-legends.json +1 -1
  7. package/examples/example-bar-chart-nonnumeric.json +36 -0
  8. package/examples/example-bar-chart.json +33 -0
  9. package/examples/example-combo-bar-nonnumeric.json +105 -0
  10. package/examples/gallery/bar-chart-vertical/combo-line-chart.json +3 -1
  11. package/examples/gallery/bar-chart-vertical/vertical-bar-chart-categorical.json +1 -1
  12. package/examples/gallery/bar-chart-vertical/vertical-bar-chart.json +86 -17
  13. package/examples/gallery/paired-bar/paired-bar-chart.json +65 -13
  14. package/examples/line-chart-nonnumeric.json +32 -0
  15. package/examples/line-chart.json +21 -63
  16. package/examples/new-data.csv +17 -0
  17. package/examples/newdata.json +90 -0
  18. package/examples/planet-combo-example-config.json +143 -20
  19. package/examples/planet-example-data-nonnumeric.json +56 -0
  20. package/examples/planet-example-data.json +2 -2
  21. package/examples/planet-pie-example-config-nonnumeric.json +30 -0
  22. package/examples/scatterplot-continuous.csv +17 -0
  23. package/examples/{private/yaxis-test.json → scatterplot.json} +53 -50
  24. package/examples/sparkline-chart-nonnumeric.json +76 -0
  25. package/examples/stacked-vertical-bar-example-negative.json +154 -0
  26. package/examples/stacked-vertical-bar-example-nonnumerics.json +154 -0
  27. package/{src/index.html → index.html} +18 -11
  28. package/package.json +29 -22
  29. package/src/{CdcChart.tsx → CdcChart.jsx} +193 -119
  30. package/src/components/BarChart.jsx +517 -0
  31. package/src/components/BoxPlot.jsx +88 -0
  32. package/src/components/{DataTable.tsx → DataTable.jsx} +125 -32
  33. package/src/components/{EditorPanel.js → EditorPanel.jsx} +376 -115
  34. package/src/components/Filters.jsx +125 -0
  35. package/src/components/Legend.jsx +303 -0
  36. package/src/components/{LineChart.tsx → LineChart.jsx} +87 -22
  37. package/src/components/{LinearChart.tsx → LinearChart.jsx} +172 -113
  38. package/src/components/{PairedBarChart.tsx → PairedBarChart.jsx} +46 -79
  39. package/src/components/{PieChart.tsx → PieChart.jsx} +29 -34
  40. package/src/components/ScatterPlot.jsx +48 -0
  41. package/src/components/{SparkLine.js → SparkLine.jsx} +49 -18
  42. package/src/components/useIntersectionObserver.jsx +29 -0
  43. package/src/data/initial-state.js +44 -8
  44. package/src/hooks/{useColorPalette.ts → useColorPalette.js} +10 -28
  45. package/src/hooks/{useReduceData.ts → useReduceData.js} +27 -13
  46. package/src/hooks/useRightAxis.js +3 -1
  47. package/src/index.jsx +16 -0
  48. package/src/scss/DataTable.scss +23 -1
  49. package/src/scss/main.scss +83 -32
  50. package/vite.config.js +4 -0
  51. package/examples/private/filters.json +0 -170
  52. package/examples/private/line-test-data.json +0 -22
  53. package/examples/private/line-test-two.json +0 -210
  54. package/examples/private/line-test.json +0 -102
  55. package/examples/private/new.json +0 -48800
  56. package/examples/private/newtest.csv +0 -101
  57. package/examples/private/shawn.json +0 -1106
  58. package/examples/private/test.json +0 -10124
  59. package/examples/private/yaxis-testing.csv +0 -27
  60. package/examples/private/yaxis.json +0 -28
  61. package/src/components/BarChart.tsx +0 -579
  62. package/src/components/Legend.js +0 -284
  63. package/src/components/useIntersectionObserver.tsx +0 -27
  64. package/src/index.tsx +0 -18
  65. /package/src/{context.tsx → ConfigContext.jsx} +0 -0
package/package.json CHANGED
@@ -1,11 +1,15 @@
1
1
  {
2
2
  "name": "@cdc/chart",
3
- "version": "4.22.11",
3
+ "version": "4.23.2",
4
4
  "description": "React component for visualizing tabular data in various types of charts",
5
+ "moduleName": "CdcChart",
5
6
  "main": "dist/cdcchart",
7
+ "type": "module",
6
8
  "scripts": {
7
- "start": "npx webpack serve --mode development -c ../../webpack.config.js",
8
- "build": "npx webpack --mode production --env packageName=CdcChart --env folderName=chart -c ../../webpack.config.js",
9
+ "start": "vite --open",
10
+ "build": "vite build",
11
+ "preview": "vite preview",
12
+ "graph": "nx graph",
9
13
  "prepublishOnly": "lerna run --scope @cdc/chart build"
10
14
  },
11
15
  "repository": {
@@ -19,36 +23,39 @@
19
23
  },
20
24
  "license": "Apache-2.0",
21
25
  "dependencies": {
22
- "@cdc/core": "^1.1.4",
23
- "@visx/axis": "^1.0.0",
24
- "@visx/curve": "^1.0.0",
25
- "@visx/gradient": "^1.0.0",
26
- "@visx/group": "^1.0.0",
27
- "@visx/legend": "^1.1.0",
28
- "@visx/marker": "^1.1.0",
29
- "@visx/mock-data": "^1.0.0",
30
- "@visx/scale": "^1.0.0",
31
- "@visx/shape": "^1.0.0",
32
- "@visx/tooltip": "^1.1.0",
26
+ "@hello-pangea/dnd": "^16.2.0",
27
+ "@visx/axis": "^3.0.0",
28
+ "@visx/curve": "^3.0.0",
29
+ "@visx/gradient": "^3.0.0",
30
+ "@visx/group": "^3.0.0",
31
+ "@visx/legend": "^3.0.0",
32
+ "@visx/marker": "^3.0.0",
33
+ "@visx/mock-data": "^3.0.0",
34
+ "@visx/scale": "^3.0.0",
35
+ "@visx/shape": "^3.0.0",
36
+ "@visx/stats": "^3.0.0",
37
+ "@visx/text": "^3.0.0",
38
+ "@visx/tooltip": "^3.0.0",
33
39
  "chroma-js": "^2.1.2",
34
40
  "d3-array": "^2.8.0",
41
+ "d3-format": "^3.1.0",
35
42
  "d3-time-format": "^3.0.0",
36
- "html-react-parser": "1.4.9",
43
+ "html-react-parser": "^3.0.8",
37
44
  "js-base64": "^2.5.2",
38
45
  "papaparse": "^5.3.0",
39
46
  "react-accessible-accordion": "^3.3.4",
40
47
  "react-spring": "^8.0.27",
41
48
  "react-table": "^7.5.0",
42
- "react-tooltip": "4.2.8",
49
+ "react-tooltip": "5.8.2-beta.3",
43
50
  "use-debounce": "^6.0.1",
44
51
  "whatwg-fetch": "^3.6.2"
45
52
  },
46
53
  "peerDependencies": {
47
- "react": ">=16.8",
48
- "react-dom": ">=16"
54
+ "react": "^18.2.0",
55
+ "react-dom": "^18.2.0"
49
56
  },
50
- "resolutions": {
51
- "@types/react": "17.x"
52
- },
53
- "gitHead": "9768d1ea0e2383044977d988e33531bcdfe33ea6"
57
+ "gitHead": "cd4216f47b1c41bfbc1de3b704f70c52cc7293c2",
58
+ "devDependencies": {
59
+ "resize-observer-polyfill": "^1.5.1"
60
+ }
54
61
  }
@@ -1,19 +1,22 @@
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'
6
+ import * as d3 from 'd3-array'
7
7
 
8
8
  // External Libraries
9
9
  import { scaleOrdinal } from '@visx/scale'
10
10
  import ParentSize from '@visx/responsive/lib/components/ParentSize'
11
11
  import { timeParse, timeFormat } from 'd3-time-format'
12
+ import { format } from 'd3-format'
12
13
  import Papa from 'papaparse'
13
14
  import parse from 'html-react-parser'
15
+ import { Base64 } from 'js-base64'
16
+ import 'react-tooltip/dist/react-tooltip.css'
14
17
 
15
18
  // Primary Components
16
- import Context from './context'
19
+ import ConfigContext from './ConfigContext'
17
20
  import PieChart from './components/PieChart'
18
21
  import LinearChart from './components/LinearChart'
19
22
 
@@ -29,39 +32,42 @@ import DataTable from './components/DataTable'
29
32
  import defaults from './data/initial-state'
30
33
  import EditorPanel from './components/EditorPanel'
31
34
  import Loading from '@cdc/core/components/Loading'
35
+ import Filters from './components/Filters'
36
+ import CoveMediaControls from '@cdc/core/components/CoveMediaControls'
32
37
 
33
- // helpers
38
+ // Helpers
34
39
  import numberFromString from '@cdc/core/helpers/numberFromString'
35
40
  import getViewport from '@cdc/core/helpers/getViewport'
36
41
  import { DataTransform } from '@cdc/core/helpers/DataTransform'
37
42
  import cacheBustingString from '@cdc/core/helpers/cacheBustingString'
43
+ import isNumber from '@cdc/core/helpers/isNumber'
44
+ import cleanData from '@cdc/core/helpers/cleanData'
38
45
 
39
46
  import './scss/main.scss'
40
47
 
41
- 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 }) {
48
+ export default function CdcChart({ configUrl, config: configObj, isEditor = false, isDashboard = false, setConfig: setParentConfig, setEditing, hostname, link }) {
42
49
  const transform = new DataTransform()
43
50
 
44
- interface keyable {
45
- [key: string]: any
46
- }
47
-
48
- const [loading, setLoading] = useState<Boolean>(true)
49
- const [colorScale, setColorScale] = useState<any>(null)
50
- const [config, setConfig] = useState<keyable>({})
51
- const [stateData, setStateData] = useState<Array<Object>>(config.data || [])
52
- const [excludedData, setExcludedData] = useState<Array<Object>>()
53
- const [filteredData, setFilteredData] = useState<Array<Object>>()
54
- const [seriesHighlight, setSeriesHighlight] = useState<Array<String>>([])
55
- const [currentViewport, setCurrentViewport] = useState<String>('lg')
56
- const [dimensions, setDimensions] = useState<Array<Number>>([])
51
+ const [loading, setLoading] = useState(true)
52
+ const [colorScale, setColorScale] = useState(null)
53
+ const [config, setConfig] = useState({})
54
+ const [stateData, setStateData] = useState(config.data || [])
55
+ const [excludedData, setExcludedData] = useState()
56
+ const [filteredData, setFilteredData] = useState()
57
+ const [seriesHighlight, setSeriesHighlight] = useState([])
58
+ const [currentViewport, setCurrentViewport] = useState('lg')
59
+ const [dimensions, setDimensions] = useState([])
57
60
  const [externalFilters, setExternalFilters] = useState(null)
58
61
  const [container, setContainer] = useState()
59
62
  const [coveLoadedEventRan, setCoveLoadedEventRan] = useState(false)
60
63
  const [dynamicLegendItems, setDynamicLegendItems] = useState([])
64
+ const [imageId] = useState(`cove-${Math.random().toString(16).slice(-4)}`)
61
65
 
62
66
  const legendGlyphSize = 15
63
67
  const legendGlyphSizeHalf = legendGlyphSize / 2
64
68
 
69
+ // Destructure items from config for more readable JSX
70
+ const { legend, title, description, visualizationType } = config
65
71
  const { barBorderClass, lineDatapointClass, contentClasses, innerContainerClasses, sparkLineStyles } = useDataVizClasses(config)
66
72
 
67
73
  const handleChartTabbing = config.showSidebar ? `#legend` : config?.title ? `#dataTableSection__${config.title.replace(/\s/g, '')}` : `#dataTableSection`
@@ -211,6 +217,71 @@ export default function CdcChart({ configUrl, config: configObj, isEditor = fals
211
217
  : []
212
218
  }
213
219
 
220
+ if (newConfig.visualizationType === 'Box Plot' && newConfig.series) {
221
+ // stats
222
+ let allKeys = data.map(d => d[newConfig.xAxis.dataKey])
223
+ let allValues = data.map(d => Number(d[newConfig?.series[0]?.dataKey]))
224
+
225
+ const uniqueArray = function (arrArg) {
226
+ return arrArg.filter(function (elem, pos, arr) {
227
+ return arr.indexOf(elem) === pos
228
+ })
229
+ }
230
+
231
+ const groups = uniqueArray(allKeys)
232
+ let tableData = []
233
+ const plots = []
234
+
235
+ // group specific statistics
236
+ // prevent re-renders
237
+ groups.forEach((g, index) => {
238
+ if (!g) return
239
+ // filter data by group
240
+ let filteredData = data.filter(item => item[newConfig.xAxis.dataKey] === g)
241
+ let filteredDataValues = filteredData.map(item => Number(item[newConfig?.series[0]?.dataKey]))
242
+ // let filteredDataValues = filteredData.map(item => Number(item[newConfig.yAxis.dataKey]))
243
+ const q1 = d3.quantile(filteredDataValues, parseFloat(newConfig.boxplot.firstQuartilePercentage) / 100)
244
+ const q3 = d3.quantile(filteredDataValues, parseFloat(newConfig.boxplot.thirdQuartilePercentage) / 100)
245
+ const iqr = q3 - q1
246
+ const lowerBounds = q1 - (q3 - q1) * 1.5
247
+ const upperBounds = q3 + (q3 - q1) * 1.5
248
+ const outliers = filteredDataValues.filter(v => v < lowerBounds || v > upperBounds)
249
+ let nonOutliers = filteredDataValues
250
+
251
+ nonOutliers = nonOutliers.filter(item => !outliers.includes(item))
252
+
253
+ plots.push({
254
+ columnCategory: g,
255
+ columnMax: Number(q3 + 1.5 * iqr).toFixed(2),
256
+ columnThirdQuartile: q3.toFixed(2),
257
+ columnMedian: d3.median(filteredDataValues),
258
+ columnFirstQuartile: q1.toFixed(2),
259
+ columnMin: Number(q1 - 1.5 * iqr).toFixed(2),
260
+ columnCount: filteredDataValues.reduce((partialSum, a) => partialSum + a, 0),
261
+ columnSd: d3.deviation(filteredDataValues).toFixed(2),
262
+ columnMean: d3.mean(filteredDataValues).toFixed(2),
263
+ columnIqr: iqr.toFixed(2),
264
+ columnOutliers: outliers,
265
+ values: filteredDataValues,
266
+ nonOutlierValues: nonOutliers
267
+ })
268
+ })
269
+
270
+ // make deep copy so we can remove some fields for data
271
+ // this appears to be the easiest option instead of running logic against the datatable cell...
272
+ tableData = JSON.parse(JSON.stringify(plots))
273
+ tableData.map(table => {
274
+ delete table.columnIqr
275
+ delete table.nonOutlierValues
276
+ })
277
+
278
+ // any other data we can add to boxplots
279
+ newConfig.boxplot['allValues'] = allValues
280
+ newConfig.boxplot['categories'] = groups
281
+ newConfig.boxplot.plots = plots
282
+ newConfig.boxplot.tableData = tableData
283
+ }
284
+
214
285
  if (newConfig.visualizationType === 'Combo' && newConfig.series) {
215
286
  newConfig.runtime.barSeriesKeys = []
216
287
  newConfig.runtime.lineSeriesKeys = []
@@ -261,6 +332,7 @@ export default function CdcChart({ configUrl, config: configObj, isEditor = fals
261
332
 
262
333
  data.forEach(row => {
263
334
  const value = row[columnName]
335
+ //@ts-ignore
264
336
  if (value && false === values.includes(value)) {
265
337
  values.push(value)
266
338
  }
@@ -285,7 +357,7 @@ export default function CdcChart({ configUrl, config: configObj, isEditor = fals
285
357
  }
286
358
 
287
359
  // Observes changes to outermost container and changes viewport size in state
288
- const resizeObserver: ResizeObserver = new ResizeObserver(entries => {
360
+ const resizeObserver = new ResizeObserver(entries => {
289
361
  for (let entry of entries) {
290
362
  let { width, height } = entry.contentRect
291
363
  let newViewport = getViewport(width)
@@ -341,13 +413,13 @@ export default function CdcChart({ configUrl, config: configObj, isEditor = fals
341
413
  * Another useEffect listens to externalFilterChanges and updates the config.
342
414
  */
343
415
  useEffect(() => {
344
- const handleFilterData = (e: CustomEvent) => {
416
+ const handleFilterData = e => {
345
417
  let tmp = []
346
418
  tmp.push(e.detail)
347
419
  setExternalFilters(tmp)
348
420
  }
349
421
 
350
- subscribe('cove_filterData', (e: CustomEvent) => handleFilterData(e))
422
+ subscribe('cove_filterData', e => handleFilterData(e))
351
423
 
352
424
  return () => {
353
425
  unsubscribe('cove_filterData', handleFilterData)
@@ -380,6 +452,7 @@ export default function CdcChart({ configUrl, config: configObj, isEditor = fals
380
452
 
381
453
  // Load data when configObj data changes
382
454
  if (configObj) {
455
+ // eslint-disable-next-line react-hooks/rules-of-hooks
383
456
  useEffect(() => {
384
457
  loadConfig()
385
458
  }, [configObj.data])
@@ -456,7 +529,7 @@ export default function CdcChart({ configUrl, config: configObj, isEditor = fals
456
529
 
457
530
  const section = config.orientation === 'horizontal' ? 'yAxis' : 'xAxis'
458
531
 
459
- const parseDate = (dateString: string) => {
532
+ const parseDate = dateString => {
460
533
  let date = timeParse(config.runtime[section].dateParseFormat)(dateString)
461
534
  if (!date) {
462
535
  config.runtime.editorErrorMessage = `Error parsing date "${dateString}". Try reviewing your data and date parse settings in the X Axis section.`
@@ -466,32 +539,75 @@ export default function CdcChart({ configUrl, config: configObj, isEditor = fals
466
539
  }
467
540
  }
468
541
 
469
- const formatDate = (date: Date) => {
542
+ const formatDate = date => {
470
543
  return timeFormat(config.runtime[section].dateDisplayFormat)(date)
471
544
  }
472
545
 
546
+ const DownloadButton = ({ data }, type = 'link') => {
547
+ const fileName = `${config.title.substring(0, 50)}.csv`
548
+
549
+ const csvData = Papa.unparse(data)
550
+
551
+ const saveBlob = () => {
552
+ if (typeof window.navigator.msSaveBlob === 'function') {
553
+ const dataBlob = new Blob([csvData], { type: 'text/csv;charset=utf-8;' })
554
+ window.navigator.msSaveBlob(dataBlob, fileName)
555
+ }
556
+ }
557
+
558
+ if (type === 'download') {
559
+ return (
560
+ <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`}>
561
+ Download Data (CSV)
562
+ </a>
563
+ )
564
+ } else {
565
+ return (
566
+ <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`}>
567
+ Download Data (CSV)
568
+ </a>
569
+ )
570
+ }
571
+ }
572
+
573
+ // function calculates the width of given text and its font-size
574
+ function getTextWidth(text, font) {
575
+ const canvas = document.createElement('canvas')
576
+ const context = canvas.getContext('2d')
577
+
578
+ context.font = font || getComputedStyle(document.body).font
579
+
580
+ return Math.ceil(context.measureText(text).width)
581
+ }
582
+
473
583
  // Format numeric data based on settings in config
474
584
  const formatNumber = (num, axis) => {
475
- // check if value contains comma and remove it. later will add comma below.
476
- if (String(num).indexOf(',') !== -1) num = num.replaceAll(',', '')
477
585
  // if num is NaN return num
478
586
  if (isNaN(num) || !num) return num
479
587
 
588
+ // destructure dataFormat values
589
+ let {
590
+ dataFormat: { commas, abbreviated, roundTo, prefix, suffix, rightRoundTo, rightPrefix, rightSuffix }
591
+ } = config
592
+ let formatSuffix = format('.2s')
593
+
594
+ // check if value contains comma and remove it. later will add comma below.
595
+ if (String(num).indexOf(',') !== -1) num = num.replaceAll(',', '')
596
+
480
597
  let original = num
481
- let prefix = config.dataFormat.prefix
482
598
  let stringFormattingOptions
483
599
 
484
600
  if (axis !== 'right') {
485
601
  stringFormattingOptions = {
486
602
  useGrouping: config.dataFormat.commas ? true : false,
487
- minimumFractionDigits: config.dataFormat.roundTo ? Number(config.dataFormat.roundTo) : 0,
488
- maximumFractionDigits: config.dataFormat.roundTo ? Number(config.dataFormat.roundTo) : 0
603
+ minimumFractionDigits: roundTo ? Number(roundTo) : 0,
604
+ maximumFractionDigits: roundTo ? Number(roundTo) : 0
489
605
  }
490
606
  } else {
491
607
  stringFormattingOptions = {
492
608
  useGrouping: config.dataFormat.rightCommas ? true : false,
493
- minimumFractionDigits: config.dataFormat.rightRoundTo ? Number(config.dataFormat.rightRoundTo) : 0,
494
- maximumFractionDigits: config.dataFormat.rightRoundTo ? Number(config.dataFormat.rightRoundTo) : 0
609
+ minimumFractionDigits: rightRoundTo ? Number(rightRoundTo) : 0,
610
+ maximumFractionDigits: rightRoundTo ? Number(rightRoundTo) : 0
495
611
  }
496
612
  }
497
613
 
@@ -510,109 +626,56 @@ export default function CdcChart({ configUrl, config: configObj, isEditor = fals
510
626
  num = cutoff
511
627
  }
512
628
  }
513
- num = num.toLocaleString('en-US', stringFormattingOptions)
514
629
 
630
+ // When we're formatting the left axis
631
+ // Use commas also updates bars and the data table
632
+ // We can't use commas when we're formatting the dataFormatted number
633
+ // Example: commas -> 12,000; abbreviated -> 12k (correct); abbreviated & commas -> 12 (incorrect)
634
+ if ((axis === 'left' && commas && abbreviated) || (axis === 'bottom' && commas && abbreviated)) {
635
+ num = num
636
+ } else {
637
+ num = num.toLocaleString('en-US', stringFormattingOptions)
638
+ }
515
639
  let result = ''
516
640
 
641
+ if (abbreviated && axis === 'left') {
642
+ num = formatSuffix(parseFloat(num)).replace('G', 'B')
643
+ }
644
+
645
+ if (abbreviated && axis === 'bottom') {
646
+ num = formatSuffix(parseFloat(num)).replace('G', 'B')
647
+ }
648
+
517
649
  if (prefix && axis !== 'right') {
518
650
  result += prefix
519
651
  }
520
652
 
521
- if (config.dataFormat.rightPrefix && axis === 'right') {
522
- result += config.dataFormat.rightPrefix
653
+ if (rightPrefix && axis === 'right') {
654
+ result += rightPrefix
523
655
  }
524
656
 
525
657
  result += num
526
658
 
527
- if (config.dataFormat.suffix && axis !== 'right') {
528
- result += config.dataFormat.suffix
659
+ if (suffix && axis !== 'right') {
660
+ result += suffix
529
661
  }
530
662
 
531
- if (config.dataFormat.rightSuffix && axis === 'right') {
532
- result += config.dataFormat.rightSuffix
663
+ if (rightSuffix && axis === 'right') {
664
+ result += rightSuffix
533
665
  }
534
666
 
535
667
  return String(result)
536
668
  }
537
669
 
538
- // Destructure items from config for more readable JSX
539
- const { legend, title, description, visualizationType } = config
540
-
541
670
  // Select appropriate chart type
542
671
  const chartComponents = {
543
672
  'Paired Bar': <LinearChart />,
544
673
  Bar: <LinearChart />,
545
674
  Line: <LinearChart />,
546
675
  Combo: <LinearChart />,
547
- Pie: <PieChart />
548
- }
549
-
550
- const Filters = () => {
551
- const changeFilterActive = (index, value) => {
552
- let newFilters = config.filters
553
-
554
- newFilters[index].active = value
555
-
556
- setConfig({ ...config, filters: newFilters })
557
-
558
- setFilteredData(filterData(newFilters, excludedData))
559
- }
560
-
561
- const announceChange = text => {}
562
-
563
- let filterList = ''
564
- if (config.filters) {
565
- filterList = config.filters.map((singleFilter, index) => {
566
- const values = []
567
- const sortAsc = (a, b) => {
568
- return a.toString().localeCompare(b.toString(), 'en', { numeric: true })
569
- }
570
-
571
- const sortDesc = (a, b) => {
572
- return b.toString().localeCompare(a.toString(), 'en', { numeric: true })
573
- }
574
-
575
- if (!singleFilter.order || singleFilter.order === '') {
576
- singleFilter.order = 'asc'
577
- }
578
-
579
- if (singleFilter.order === 'desc') {
580
- singleFilter.values = singleFilter.values.sort(sortDesc)
581
- }
582
-
583
- if (singleFilter.order === 'asc') {
584
- singleFilter.values = singleFilter.values.sort(sortAsc)
585
- }
586
-
587
- singleFilter.values.forEach((filterOption, index) => {
588
- values.push(
589
- <option key={index} value={filterOption}>
590
- {filterOption}
591
- </option>
592
- )
593
- })
594
-
595
- return (
596
- <div className='single-filter' key={index}>
597
- <label htmlFor={`filter-${index}`}>{singleFilter.label}</label>
598
- <select
599
- id={`filter-${index}`}
600
- className='filter-select'
601
- data-index='0'
602
- value={singleFilter.active}
603
- onChange={val => {
604
- changeFilterActive(index, val.target.value)
605
- announceChange(`Filter ${singleFilter.label} value has been changed to ${val.target.value}, please reference the data table to see updated values.`)
606
- }}
607
- >
608
- {values}
609
- </select>
610
- </div>
611
- )
612
- })
613
- }
614
-
615
- return <section className='filters-section'>{filterList}</section>
676
+ Pie: <PieChart />,
677
+ 'Box Plot': <LinearChart />,
678
+ 'Scatter Plot': <LinearChart />
616
679
  }
617
680
 
618
681
  const missingRequiredSections = () => {
@@ -656,11 +719,10 @@ export default function CdcChart({ configUrl, config: configObj, isEditor = fals
656
719
  {/* Filters */}
657
720
  {config.filters && !externalFilters && <Filters />}
658
721
  {/* Visualization */}
659
- {config?.introText && <section className="introText">{parse(config.introText)}</section>}
722
+ {config?.introText && <section className='introText'>{parse(config.introText)}</section>}
660
723
  <div
661
- className={`chart-container ${config.legend.position==='bottom'? "bottom":""
662
- }${config.legend.hide ? " legend-hidden" : ""
663
- }${lineDatapointClass}${barBorderClass} ${contentClasses.join(' ')}`}
724
+ style={{ marginBottom: config.legend.position !== 'bottom' && config.orientation === 'horizontal' ? `${config.runtime.xAxis.size}px` : '0px' }}
725
+ className={`chart-container ${config.legend.position === 'bottom' ? 'bottom' : ''}${config.legend.hide ? ' legend-hidden' : ''}${lineDatapointClass}${barBorderClass} ${contentClasses.join(' ')}`}
664
726
  >
665
727
  {/* All charts except sparkline */}
666
728
  {config.visualizationType !== 'Spark Line' && chartComponents[visualizationType]}
@@ -686,18 +748,25 @@ export default function CdcChart({ configUrl, config: configObj, isEditor = fals
686
748
  {link && link}
687
749
  {/* Description */}
688
750
  {description && config.visualizationType !== 'Spark Line' && <div className='subtext'>{parse(description)}</div>}
689
- {/* Data Table */}
690
751
 
752
+ {/* buttons */}
753
+ <CoveMediaControls.Section classes={['download-buttons']}>
754
+ {config.table.showDownloadImgButton && <CoveMediaControls.Button text='Download Image' title='Download Chart as Image' type='image' state={config} elementToCapture={imageId} />}
755
+ {config.table.showDownloadPdfButton && <CoveMediaControls.Button text='Download PDF' title='Download Chart as PDF' type='pdf' state={config} elementToCapture={imageId} />}
756
+ </CoveMediaControls.Section>
757
+
758
+ {/* Data Table */}
691
759
  {config.xAxis.dataKey && config.table.show && config.visualizationType !== 'Spark Line' && <DataTable />}
692
760
  {config?.footnotes && <section className='footnotes'>{parse(config.footnotes)}</section>}
761
+ {/* show pdf or image button */}
693
762
  </div>
694
763
  )}
695
764
  </>
696
765
  )
697
766
  }
698
767
 
699
- const getXAxisData = (d: any) => (config.runtime.xAxis.type === 'date' ? parseDate(d[config.runtime.originalXAxis.dataKey]).getTime() : d[config.runtime.originalXAxis.dataKey])
700
- const getYAxisData = (d: any, seriesKey: string) => d[seriesKey]
768
+ const getXAxisData = d => (config.runtime.xAxis.type === 'date' ? parseDate(d[config.runtime.originalXAxis.dataKey]).getTime() : d[config.runtime.originalXAxis.dataKey])
769
+ const getYAxisData = (d, seriesKey) => d[seriesKey]
701
770
 
702
771
  const contextValues = {
703
772
  getXAxisData,
@@ -729,7 +798,12 @@ export default function CdcChart({ configUrl, config: configObj, isEditor = fals
729
798
  legend,
730
799
  setSeriesHighlight,
731
800
  dynamicLegendItems,
732
- setDynamicLegendItems
801
+ setDynamicLegendItems,
802
+ filterData,
803
+ isNumber,
804
+ cleanData,
805
+ imageId,
806
+ getTextWidth
733
807
  }
734
808
 
735
809
  const classes = ['cdc-open-viz-module', 'type-chart', `${currentViewport}`, `font-${config.fontSize}`, `${config.theme}`]
@@ -739,10 +813,10 @@ export default function CdcChart({ configUrl, config: configObj, isEditor = fals
739
813
  isEditor && classes.push('isEditor')
740
814
 
741
815
  return (
742
- <Context.Provider value={contextValues}>
743
- <div className={`${classes.join(' ')}`} ref={outerContainerRef} data-lollipop={config.isLollipopChart}>
816
+ <ConfigContext.Provider value={contextValues}>
817
+ <div className={`${classes.join(' ')}`} ref={outerContainerRef} data-lollipop={config.isLollipopChart} data-download-id={imageId}>
744
818
  {body}
745
819
  </div>
746
- </Context.Provider>
820
+ </ConfigContext.Provider>
747
821
  )
748
822
  }