@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.
- package/dist/cdcchart.js +54569 -16
- package/examples/Barchart_with_negative.json +34 -0
- package/examples/box-plot-data.json +71 -0
- package/examples/box-plot.csv +5 -0
- package/examples/box-plot.json +124 -0
- package/examples/dynamic-legends.json +1 -1
- package/examples/example-bar-chart-nonnumeric.json +36 -0
- package/examples/example-bar-chart.json +33 -0
- package/examples/example-combo-bar-nonnumeric.json +105 -0
- package/examples/gallery/bar-chart-vertical/combo-line-chart.json +3 -1
- package/examples/gallery/bar-chart-vertical/vertical-bar-chart-categorical.json +1 -1
- package/examples/gallery/bar-chart-vertical/vertical-bar-chart.json +86 -17
- package/examples/gallery/paired-bar/paired-bar-chart.json +65 -13
- package/examples/line-chart-nonnumeric.json +32 -0
- package/examples/line-chart.json +21 -63
- package/examples/new-data.csv +17 -0
- package/examples/newdata.json +90 -0
- package/examples/planet-combo-example-config.json +143 -20
- package/examples/planet-example-data-nonnumeric.json +56 -0
- package/examples/planet-example-data.json +2 -2
- package/examples/planet-pie-example-config-nonnumeric.json +30 -0
- package/examples/scatterplot-continuous.csv +17 -0
- package/examples/{private/yaxis-test.json → scatterplot.json} +53 -50
- package/examples/sparkline-chart-nonnumeric.json +76 -0
- package/examples/stacked-vertical-bar-example-negative.json +154 -0
- package/examples/stacked-vertical-bar-example-nonnumerics.json +154 -0
- package/{src/index.html → index.html} +18 -11
- package/package.json +29 -22
- package/src/{CdcChart.tsx → CdcChart.jsx} +193 -119
- package/src/components/BarChart.jsx +517 -0
- package/src/components/BoxPlot.jsx +88 -0
- package/src/components/{DataTable.tsx → DataTable.jsx} +125 -32
- package/src/components/{EditorPanel.js → EditorPanel.jsx} +376 -115
- package/src/components/Filters.jsx +125 -0
- package/src/components/Legend.jsx +303 -0
- package/src/components/{LineChart.tsx → LineChart.jsx} +87 -22
- package/src/components/{LinearChart.tsx → LinearChart.jsx} +172 -113
- package/src/components/{PairedBarChart.tsx → PairedBarChart.jsx} +46 -79
- package/src/components/{PieChart.tsx → PieChart.jsx} +29 -34
- package/src/components/ScatterPlot.jsx +48 -0
- package/src/components/{SparkLine.js → SparkLine.jsx} +49 -18
- package/src/components/useIntersectionObserver.jsx +29 -0
- package/src/data/initial-state.js +44 -8
- package/src/hooks/{useColorPalette.ts → useColorPalette.js} +10 -28
- package/src/hooks/{useReduceData.ts → useReduceData.js} +27 -13
- package/src/hooks/useRightAxis.js +3 -1
- package/src/index.jsx +16 -0
- package/src/scss/DataTable.scss +23 -1
- package/src/scss/main.scss +83 -32
- package/vite.config.js +4 -0
- package/examples/private/filters.json +0 -170
- package/examples/private/line-test-data.json +0 -22
- package/examples/private/line-test-two.json +0 -210
- package/examples/private/line-test.json +0 -102
- package/examples/private/new.json +0 -48800
- package/examples/private/newtest.csv +0 -101
- package/examples/private/shawn.json +0 -1106
- package/examples/private/test.json +0 -10124
- package/examples/private/yaxis-testing.csv +0 -27
- package/examples/private/yaxis.json +0 -28
- package/src/components/BarChart.tsx +0 -579
- package/src/components/Legend.js +0 -284
- package/src/components/useIntersectionObserver.tsx +0 -27
- package/src/index.tsx +0 -18
- /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.
|
|
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": "
|
|
8
|
-
"build": "
|
|
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
|
-
"@
|
|
23
|
-
"@visx/axis": "^
|
|
24
|
-
"@visx/curve": "^
|
|
25
|
-
"@visx/gradient": "^
|
|
26
|
-
"@visx/group": "^
|
|
27
|
-
"@visx/legend": "^
|
|
28
|
-
"@visx/marker": "^
|
|
29
|
-
"@visx/mock-data": "^
|
|
30
|
-
"@visx/scale": "^
|
|
31
|
-
"@visx/shape": "^
|
|
32
|
-
"@visx/
|
|
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": "
|
|
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": "
|
|
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": "
|
|
48
|
-
"react-dom": "
|
|
54
|
+
"react": "^18.2.0",
|
|
55
|
+
"react-dom": "^18.2.0"
|
|
49
56
|
},
|
|
50
|
-
"
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
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
|
|
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
|
-
//
|
|
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 }
|
|
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
|
-
|
|
45
|
-
|
|
46
|
-
}
|
|
47
|
-
|
|
48
|
-
const [
|
|
49
|
-
const [
|
|
50
|
-
const [
|
|
51
|
-
const [
|
|
52
|
-
const [
|
|
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
|
|
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 =
|
|
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',
|
|
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 =
|
|
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 =
|
|
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:
|
|
488
|
-
maximumFractionDigits:
|
|
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:
|
|
494
|
-
maximumFractionDigits:
|
|
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 (
|
|
522
|
-
result +=
|
|
653
|
+
if (rightPrefix && axis === 'right') {
|
|
654
|
+
result += rightPrefix
|
|
523
655
|
}
|
|
524
656
|
|
|
525
657
|
result += num
|
|
526
658
|
|
|
527
|
-
if (
|
|
528
|
-
result +=
|
|
659
|
+
if (suffix && axis !== 'right') {
|
|
660
|
+
result += suffix
|
|
529
661
|
}
|
|
530
662
|
|
|
531
|
-
if (
|
|
532
|
-
result +=
|
|
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=
|
|
722
|
+
{config?.introText && <section className='introText'>{parse(config.introText)}</section>}
|
|
660
723
|
<div
|
|
661
|
-
|
|
662
|
-
}${config.legend.hide ?
|
|
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 =
|
|
700
|
-
const getYAxisData = (d
|
|
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
|
-
<
|
|
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
|
-
</
|
|
820
|
+
</ConfigContext.Provider>
|
|
747
821
|
)
|
|
748
822
|
}
|