@cdc/chart 4.24.10 → 4.24.11
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 +34618 -33995
- package/examples/feature/boxplot/boxplot-data.json +88 -22
- package/examples/feature/boxplot/boxplot.json +540 -16
- package/examples/feature/boxplot/testing.csv +7 -7
- package/examples/feature/sankey/sankey-example-data.json +0 -1
- package/examples/private/test.json +20092 -0
- package/index.html +3 -3
- package/package.json +2 -2
- package/src/CdcChart.tsx +86 -86
- package/src/_stories/Chart.CustomColors.stories.tsx +19 -0
- package/src/_stories/Chart.DynamicSeries.stories.tsx +27 -0
- package/src/_stories/Chart.Legend.Gradient.stories.tsx +42 -1
- package/src/_stories/Chart.stories.tsx +7 -8
- package/src/_stories/ChartEditor.stories.tsx +27 -0
- package/src/_stories/ChartLine.Suppression.stories.tsx +25 -0
- package/src/_stories/ChartPrefixSuffix.stories.tsx +8 -0
- package/src/_stories/_mock/boxplot_multiseries.json +647 -0
- package/src/_stories/_mock/dynamic_series_bar_config.json +723 -0
- package/src/_stories/_mock/dynamic_series_config.json +979 -0
- package/{examples/feature/scatterplot/scatterplot.json → src/_stories/_mock/scatterplot_mock.json} +62 -92
- package/src/_stories/_mock/suppression_mock.json +1549 -0
- package/src/components/BarChart/components/BarChart.StackedVertical.tsx +43 -9
- package/src/components/BarChart/components/BarChart.Vertical.tsx +60 -42
- package/src/components/BarChart/helpers/index.ts +1 -2
- package/src/components/BoxPlot/BoxPlot.tsx +189 -0
- package/src/components/EditorPanel/EditorPanel.tsx +64 -62
- package/src/components/EditorPanel/components/Panels/Panel.Annotate.tsx +3 -3
- package/src/components/EditorPanel/components/Panels/Panel.BoxPlot.tsx +51 -6
- package/src/components/EditorPanel/components/Panels/Panel.Regions.tsx +40 -9
- package/src/components/EditorPanel/components/Panels/Panel.Sankey.tsx +3 -3
- package/src/components/EditorPanel/components/Panels/Panel.Series.tsx +121 -56
- package/src/components/EditorPanel/components/Panels/Panel.Visual.tsx +1 -2
- package/src/components/EditorPanel/useEditorPermissions.ts +15 -1
- package/src/components/Legend/Legend.Component.tsx +9 -10
- package/src/components/Legend/Legend.tsx +16 -16
- package/src/components/LineChart/helpers.ts +48 -43
- package/src/components/LineChart/index.tsx +88 -82
- package/src/components/LinearChart.tsx +17 -10
- package/src/components/Sankey/index.tsx +50 -32
- package/src/components/Sankey/sankey.scss +6 -5
- package/src/components/Sankey/useSankeyAlert.tsx +60 -0
- package/src/components/ScatterPlot/ScatterPlot.jsx +20 -4
- package/src/data/initial-state.js +3 -9
- package/src/hooks/useLegendClasses.ts +10 -23
- package/src/hooks/useMinMax.ts +27 -13
- package/src/hooks/useReduceData.ts +43 -10
- package/src/hooks/useScales.ts +56 -35
- package/src/hooks/useTooltip.tsx +54 -49
- package/src/scss/main.scss +0 -18
- package/src/types/ChartConfig.ts +6 -19
- package/src/types/ChartContext.ts +4 -1
- package/src/types/ForestPlot.ts +8 -0
- package/src/components/BoxPlot/BoxPlot.jsx +0 -111
package/index.html
CHANGED
|
@@ -62,19 +62,19 @@
|
|
|
62
62
|
<!-- <div class="react-container" data-config="/examples/feature/forest-plot/linear.json"></div> -->
|
|
63
63
|
<!-- <div class="react-container" data-config="/examples/feature/forest-plot/logarithmic.json"></div> -->
|
|
64
64
|
<!-- <div class="react-container" data-config="/examples/feature/forest-plot/forest-plot.json"></div> -->
|
|
65
|
-
<div class="react-container" data-config="/examples/feature/pie/planet-pie-example-config.json"></div>
|
|
65
|
+
<!-- <div class="react-container" data-config="/examples/feature/pie/planet-pie-example-config.json"></div> -->
|
|
66
66
|
<!-- <div class="react-container" data-config=https://www.cdc.gov/wcms/4.0/cdc-wp/data-presentation/examples/Line_Chart_Viz.json></div> -->
|
|
67
67
|
<!-- <div class="react-container" data-config=/examples/feature/regions/index.json></div> -->
|
|
68
68
|
<!-- <div class="react-container" data-config=https://www.cdc.gov/wcms/4.0/cdc-wp/data-presentation/examples/Line_Chart_Regions_Viz.json></div> -->
|
|
69
69
|
<!-- <div class="react-container" data-config=https://www.cdc.gov/wcms/4.0/cdc-wp/data-presentation/examples/Line_Chart_Regions_Viz.json></div> -->
|
|
70
70
|
<!-- <div class="react-container" data-config="/examples/feature/forecasting/forecasting.json"></div> -->
|
|
71
|
-
<div class="react-container" data-config="/examples/feature/forecasting/combo-forecasting.json"></div>
|
|
71
|
+
<!-- <div class="react-container" data-config="/examples/feature/forecasting/combo-forecasting.json"></div> -->
|
|
72
72
|
<!-- <div class="react-container" data-config="/examples/feature/forecasting/effective_reproduction.json"></div> -->
|
|
73
73
|
<!-- <div class="react-container" data-config="/examples/feature/area/area-chart-date.json"></div> -->
|
|
74
74
|
<!-- <div class="react-container" data-config="/examples/feature/area/area-chart-category.json"></div> -->
|
|
75
75
|
<!-- <div class="react-container" data-config="/examples/feature/scatterplot/scatterplot.json"></div> -->
|
|
76
76
|
<!-- <div class="react-container" data-config="/examples/feature/deviation/planet-deviation-config.json"></div> -->
|
|
77
|
-
|
|
77
|
+
<div class="react-container" data-config="/examples/feature/boxplot/boxplot.json"></div>
|
|
78
78
|
<!-- <div class="react-container" data-config="/examples/feature/combo/planet-combo-example-config.json"></div> -->
|
|
79
79
|
<!-- <div class="react-container" data-config="/examples/feature/combo/right-issues.json"></div> -->
|
|
80
80
|
<!-- <div class="react-container" data-config="/examples/region-issue.json"></div> -->
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@cdc/chart",
|
|
3
|
-
"version": "4.24.
|
|
3
|
+
"version": "4.24.11",
|
|
4
4
|
"description": "React component for visualizing tabular data in various types of charts",
|
|
5
5
|
"moduleName": "CdcChart",
|
|
6
6
|
"main": "dist/cdcchart",
|
|
@@ -61,7 +61,7 @@
|
|
|
61
61
|
"react": "^18.2.0",
|
|
62
62
|
"react-dom": "^18.2.0"
|
|
63
63
|
},
|
|
64
|
-
"gitHead": "
|
|
64
|
+
"gitHead": "9ab5ee9b2b0ef7321a66a2104be6ce8899ec3808",
|
|
65
65
|
"devDependencies": {
|
|
66
66
|
"@types/d3-sankey": "^0.12.4",
|
|
67
67
|
"resize-observer-polyfill": "^1.5.1"
|
package/src/CdcChart.tsx
CHANGED
|
@@ -35,7 +35,6 @@ import EditorPanel from './components/EditorPanel'
|
|
|
35
35
|
import { abbreviateNumber } from './helpers/abbreviateNumber'
|
|
36
36
|
import { handleChartTabbing } from './helpers/handleChartTabbing'
|
|
37
37
|
import { getQuartiles } from './helpers/getQuartiles'
|
|
38
|
-
import { sortAsc, sortDesc } from './helpers/sort'
|
|
39
38
|
import { handleChartAriaLabels } from './helpers/handleChartAriaLabels'
|
|
40
39
|
import { lineOptions } from './helpers/lineOptions'
|
|
41
40
|
import { handleLineType } from './helpers/handleLineType'
|
|
@@ -55,17 +54,16 @@ import { DataTransform } from '@cdc/core/helpers/DataTransform'
|
|
|
55
54
|
import cacheBustingString from '@cdc/core/helpers/cacheBustingString'
|
|
56
55
|
import isNumber from '@cdc/core/helpers/isNumber'
|
|
57
56
|
import coveUpdateWorker from '@cdc/core/helpers/coveUpdateWorker'
|
|
58
|
-
import { getQueryStringFilterValue } from '@cdc/core/helpers/queryStringUtils'
|
|
59
57
|
import { isConvertLineToBarGraph } from './helpers/isConvertLineToBarGraph'
|
|
60
|
-
import {
|
|
61
|
-
import { isLegendWrapViewport, isMobileHeightViewport } from '@cdc/core/helpers/viewports'
|
|
58
|
+
import { isLegendWrapViewport } from '@cdc/core/helpers/viewports'
|
|
62
59
|
|
|
63
60
|
import './scss/main.scss'
|
|
64
61
|
// load both then config below determines which to use
|
|
65
62
|
import DataTable from '@cdc/core/components/DataTable'
|
|
63
|
+
import type { TableConfig } from '@cdc/core/components/DataTable/types/TableConfig'
|
|
66
64
|
import { getFileExtension } from '@cdc/core/helpers/getFileExtension'
|
|
67
65
|
import Title from '@cdc/core/components/ui/Title'
|
|
68
|
-
import { ChartConfig } from './types/ChartConfig'
|
|
66
|
+
import { AllChartsConfig, ChartConfig } from './types/ChartConfig'
|
|
69
67
|
import { Label } from './types/Label'
|
|
70
68
|
import { type ViewportSize } from './types/ChartConfig'
|
|
71
69
|
import { isSolrCsv, isSolrJson } from '@cdc/core/helpers/isSolr'
|
|
@@ -74,6 +72,8 @@ import { filterVizData } from '@cdc/core/helpers/filterVizData'
|
|
|
74
72
|
import LegendWrapper from './components/LegendWrapper'
|
|
75
73
|
import _ from 'lodash'
|
|
76
74
|
import { addValuesToFilters } from '@cdc/core/helpers/addValuesToFilters'
|
|
75
|
+
import { Runtime } from '@cdc/core/types/Runtime'
|
|
76
|
+
import { Pivot } from '@cdc/core/types/Table'
|
|
77
77
|
|
|
78
78
|
interface CdcChartProps {
|
|
79
79
|
configUrl?: string
|
|
@@ -129,14 +129,6 @@ const CdcChart = ({
|
|
|
129
129
|
})
|
|
130
130
|
|
|
131
131
|
const { description, visualizationType } = config
|
|
132
|
-
let [width] = dimensions
|
|
133
|
-
const useVertical = config.orientation === 'vertical'
|
|
134
|
-
const useMobileVertical = config.heights?.mobileVertical && ['xs', 'xxs'].includes(currentViewport)
|
|
135
|
-
const responsiveVertical = useMobileVertical ? 'mobileVertical' : 'vertical'
|
|
136
|
-
const renderedOrientation = useVertical ? responsiveVertical : 'horizontal'
|
|
137
|
-
let height = config.aspectRatio ? width * config.aspectRatio : config?.heights?.[renderedOrientation]
|
|
138
|
-
if (config.visualizationType === 'Pie') height = config?.heights?.[renderedOrientation]
|
|
139
|
-
height = height + Number(config.orientation === 'horizontal' ? config.yAxis.size : config?.xAxis?.size) + 45
|
|
140
132
|
|
|
141
133
|
const legendRef = useRef(null)
|
|
142
134
|
const parentRef = useRef(null)
|
|
@@ -300,9 +292,6 @@ const CdcChart = ({
|
|
|
300
292
|
}
|
|
301
293
|
let newConfig = { ...defaults, ...response }
|
|
302
294
|
|
|
303
|
-
if (newConfig.visualizationType === 'Box Plot') {
|
|
304
|
-
newConfig.legend.hide = true
|
|
305
|
-
}
|
|
306
295
|
if (undefined === newConfig.table.show) newConfig.table.show = !isDashboard
|
|
307
296
|
|
|
308
297
|
newConfig.series.forEach(series => {
|
|
@@ -321,7 +310,7 @@ const CdcChart = ({
|
|
|
321
310
|
updateConfig(processedConfig, data)
|
|
322
311
|
}
|
|
323
312
|
|
|
324
|
-
const updateConfig = (_config, dataOverride?: any[]) => {
|
|
313
|
+
const updateConfig = (_config: AllChartsConfig, dataOverride?: any[]) => {
|
|
325
314
|
const newConfig = _.cloneDeep(_config)
|
|
326
315
|
let data = dataOverride || stateData
|
|
327
316
|
|
|
@@ -384,15 +373,15 @@ const CdcChart = ({
|
|
|
384
373
|
}
|
|
385
374
|
|
|
386
375
|
//Enforce default values that need to be calculated at runtime
|
|
387
|
-
newConfig.runtime = {}
|
|
388
|
-
newConfig.runtime.series = newConfig.dynamicSeries ? [] : newConfig.series
|
|
376
|
+
newConfig.runtime = {} as Runtime
|
|
377
|
+
newConfig.runtime.series = newConfig.dynamicSeries ? [] : _.cloneDeep(newConfig.series)
|
|
389
378
|
newConfig.runtime.seriesLabels = {}
|
|
390
379
|
newConfig.runtime.seriesLabelsAll = []
|
|
391
380
|
newConfig.runtime.originalXAxis = newConfig.xAxis
|
|
392
381
|
|
|
393
382
|
if (newConfig.dynamicSeries) {
|
|
394
383
|
let finalData = dataOverride || newConfig.formattedData || newConfig.data
|
|
395
|
-
if (finalData
|
|
384
|
+
if (finalData?.length) {
|
|
396
385
|
Object.keys(finalData[0]).forEach(seriesKey => {
|
|
397
386
|
if (
|
|
398
387
|
seriesKey !== newConfig.xAxis.dataKey &&
|
|
@@ -414,30 +403,42 @@ const CdcChart = ({
|
|
|
414
403
|
newConfig.runtime.seriesKeys = (dataOverride || data).map(d => d[newConfig.xAxis.dataKey])
|
|
415
404
|
newConfig.runtime.seriesLabelsAll = newConfig.runtime.seriesKeys
|
|
416
405
|
} else {
|
|
417
|
-
newConfig.
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
406
|
+
const finalData = dataOverride || newConfig.formattedData || newConfig.data
|
|
407
|
+
newConfig.runtime.seriesKeys = (newConfig.runtime.series || []).flatMap(series => {
|
|
408
|
+
if (series.dynamicCategory) {
|
|
409
|
+
_.remove(newConfig.runtime.seriesLabelsAll, label => label === series.dataKey)
|
|
410
|
+
_.remove(newConfig.runtime.series, s => s.dataKey === series.dataKey)
|
|
411
|
+
// grab the dynamic series keys from the data
|
|
412
|
+
const seriesKeys: string[] = _.uniq(finalData.map(d => d[series.dynamicCategory]))
|
|
413
|
+
// for each of those keys perform side effects
|
|
414
|
+
seriesKeys.forEach(dataKey => {
|
|
415
|
+
newConfig.runtime.seriesLabels[dataKey] = dataKey
|
|
416
|
+
newConfig.runtime.seriesLabelsAll.push(dataKey)
|
|
417
|
+
newConfig.runtime.series.push({
|
|
418
|
+
dataKey,
|
|
419
|
+
type: series.type,
|
|
420
|
+
lineType: series.lineType,
|
|
421
|
+
originalDataKey: series.dataKey,
|
|
422
|
+
dynamicCategory: series.dynamicCategory,
|
|
423
|
+
tooltip: true
|
|
424
|
+
})
|
|
422
425
|
})
|
|
423
|
-
|
|
426
|
+
// return the series keys
|
|
427
|
+
return seriesKeys
|
|
428
|
+
} else {
|
|
429
|
+
newConfig.runtime.seriesLabels[series.dataKey] = series.name || series.label || series.dataKey
|
|
430
|
+
newConfig.runtime.seriesLabelsAll.push(series.name || series.dataKey)
|
|
431
|
+
// return the series keys
|
|
432
|
+
return [series.dataKey]
|
|
433
|
+
}
|
|
434
|
+
})
|
|
424
435
|
}
|
|
425
436
|
|
|
426
437
|
if (newConfig.visualizationType === 'Box Plot' && newConfig.series) {
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
let allValues = newExcludedData
|
|
431
|
-
? newExcludedData.map(d => Number(d[newConfig?.series[0]?.dataKey]))
|
|
432
|
-
: data.map(d => Number(d[newConfig?.series[0]?.dataKey]))
|
|
433
|
-
|
|
434
|
-
const uniqueArray = function (arrArg) {
|
|
435
|
-
return arrArg.filter(function (elem, pos, arr) {
|
|
436
|
-
return arr.indexOf(elem) === pos
|
|
437
|
-
})
|
|
438
|
-
}
|
|
438
|
+
const combinedData = filteredData || data
|
|
439
|
+
let allKeys = combinedData.map(d => d[newConfig.xAxis.dataKey])
|
|
440
|
+
const groups = _.uniq(allKeys)
|
|
439
441
|
|
|
440
|
-
const groups = uniqueArray(allKeys)
|
|
441
442
|
let tableData: any[] = []
|
|
442
443
|
const plots: any[] = []
|
|
443
444
|
|
|
@@ -449,9 +450,8 @@ const CdcChart = ({
|
|
|
449
450
|
if (!g) throw new Error('No groups resolved in box plots')
|
|
450
451
|
|
|
451
452
|
// filter data by group
|
|
452
|
-
let filteredData =
|
|
453
|
-
|
|
454
|
-
: data.filter(item => item[newConfig.xAxis.dataKey] === g)
|
|
453
|
+
let filteredData = combinedData.filter(item => item[newConfig.xAxis.dataKey] === g)
|
|
454
|
+
|
|
455
455
|
let filteredDataValues: number[] = filteredData.map(item => Number(item[newConfig?.series[0]?.dataKey]))
|
|
456
456
|
|
|
457
457
|
// Sort the data for upcoming functions.
|
|
@@ -460,16 +460,18 @@ const CdcChart = ({
|
|
|
460
460
|
// ! - Notice d3.quantile doesn't work here, and we had to take a custom route.
|
|
461
461
|
const quartiles = getQuartiles(sortedData)
|
|
462
462
|
|
|
463
|
-
|
|
464
|
-
|
|
463
|
+
const getValuesBySeriesKey = () => {
|
|
464
|
+
const allSeriesKeys = newConfig.series.map(item => item?.dataKey)
|
|
465
|
+
const result = {}
|
|
466
|
+
allSeriesKeys.forEach(key => {
|
|
467
|
+
result[key] = filteredData.map(item => item[key])
|
|
468
|
+
})
|
|
465
469
|
|
|
466
|
-
|
|
467
|
-
newConfig.boxplot.firstQuartilePercentage = 0
|
|
470
|
+
return result
|
|
468
471
|
}
|
|
469
472
|
|
|
470
|
-
if (
|
|
471
|
-
|
|
472
|
-
}
|
|
473
|
+
if (!filteredData) throw new Error('boxplots dont have data yet')
|
|
474
|
+
if (!plots) throw new Error('boxplots dont have plots yet')
|
|
473
475
|
|
|
474
476
|
const q1 = quartiles.q1
|
|
475
477
|
const q3 = quartiles.q3
|
|
@@ -478,9 +480,7 @@ const CdcChart = ({
|
|
|
478
480
|
const upperBounds = q3 + (q3 - q1) * 1.5
|
|
479
481
|
|
|
480
482
|
const outliers = sortedData.filter(v => v < lowerBounds || v > upperBounds)
|
|
481
|
-
let nonOutliers = filteredDataValues
|
|
482
483
|
|
|
483
|
-
nonOutliers = nonOutliers.filter(item => !outliers.includes(item))
|
|
484
484
|
const minValue: number = d3.min<number>(filteredDataValues) || 0
|
|
485
485
|
const _colMin = d3.max<number>([minValue, q1 - 1.5 * iqr])
|
|
486
486
|
plots.push({
|
|
@@ -490,7 +490,7 @@ const CdcChart = ({
|
|
|
490
490
|
columnMedian: Number(d3.median(filteredDataValues)).toFixed(newConfig.dataFormat.roundTo),
|
|
491
491
|
columnFirstQuartile: q1.toFixed(newConfig.dataFormat.roundTo),
|
|
492
492
|
columnMin: _colMin,
|
|
493
|
-
|
|
493
|
+
columnCount: filteredData.length,
|
|
494
494
|
columnSd: Number(d3.deviation(filteredDataValues)).toFixed(newConfig.dataFormat.roundTo),
|
|
495
495
|
columnMean: Number(d3.mean(filteredDataValues)).toFixed(newConfig.dataFormat.roundTo),
|
|
496
496
|
columnIqr: Number(iqr).toFixed(newConfig.dataFormat.roundTo),
|
|
@@ -498,7 +498,7 @@ const CdcChart = ({
|
|
|
498
498
|
columnUpperBounds: d3.min([d3.max(sortedData), q1 + 1.5 * iqr]),
|
|
499
499
|
columnOutliers: outliers,
|
|
500
500
|
values: filteredDataValues,
|
|
501
|
-
|
|
501
|
+
keyValues: getValuesBySeriesKey()
|
|
502
502
|
})
|
|
503
503
|
} catch (e) {
|
|
504
504
|
console.error('COVE: ', e.message) // eslint-disable-line
|
|
@@ -516,8 +516,6 @@ const CdcChart = ({
|
|
|
516
516
|
return null // resolve eslint
|
|
517
517
|
})
|
|
518
518
|
|
|
519
|
-
// any other data we can add to boxplots
|
|
520
|
-
newConfig.boxplot['allValues'] = allValues
|
|
521
519
|
newConfig.boxplot['categories'] = groups
|
|
522
520
|
newConfig.boxplot.plots = plots
|
|
523
521
|
newConfig.boxplot.tableData = tableData
|
|
@@ -616,21 +614,6 @@ const CdcChart = ({
|
|
|
616
614
|
setConfig(newConfig)
|
|
617
615
|
}
|
|
618
616
|
|
|
619
|
-
// Gets filter values from dataset
|
|
620
|
-
const generateValuesForFilter = (columnName, data = this.state.data) => {
|
|
621
|
-
const values: any[] = []
|
|
622
|
-
|
|
623
|
-
data.forEach(row => {
|
|
624
|
-
const value = row[columnName]
|
|
625
|
-
//@ts-ignore
|
|
626
|
-
if (value && false === values.includes(value)) {
|
|
627
|
-
values.push(value)
|
|
628
|
-
}
|
|
629
|
-
})
|
|
630
|
-
|
|
631
|
-
return values
|
|
632
|
-
}
|
|
633
|
-
|
|
634
617
|
// Sorts data series for horizontal bar charts
|
|
635
618
|
const sortData = (a, b) => {
|
|
636
619
|
let sortKey =
|
|
@@ -653,7 +636,6 @@ const CdcChart = ({
|
|
|
653
636
|
const resizeObserver = new ResizeObserver(entries => {
|
|
654
637
|
for (let entry of entries) {
|
|
655
638
|
let { width, height } = entry.contentRect
|
|
656
|
-
let svgMarginWidth = 30
|
|
657
639
|
let editorWidth = 350
|
|
658
640
|
|
|
659
641
|
width = isEditor ? width - editorWidth : width
|
|
@@ -666,7 +648,7 @@ const CdcChart = ({
|
|
|
666
648
|
width = width - 2.5
|
|
667
649
|
}
|
|
668
650
|
|
|
669
|
-
width = width
|
|
651
|
+
width = width
|
|
670
652
|
|
|
671
653
|
setDimensions([width, height])
|
|
672
654
|
}
|
|
@@ -865,14 +847,15 @@ const CdcChart = ({
|
|
|
865
847
|
}
|
|
866
848
|
}
|
|
867
849
|
|
|
868
|
-
const formatDate = (date,
|
|
850
|
+
const formatDate = (date, i, ticks) => {
|
|
869
851
|
let formattedDate = timeFormat(config.runtime[section].dateDisplayFormat)(date)
|
|
870
852
|
// Handle the case where all months work with '%b.' except for May
|
|
871
853
|
if (config.runtime[section].dateDisplayFormat?.includes('%b.') && formattedDate.includes('May.')) {
|
|
872
854
|
formattedDate = formattedDate.replace(/May\./g, 'May')
|
|
873
855
|
}
|
|
874
856
|
// Show years only once
|
|
875
|
-
if (config.xAxis.showYearsOnce && config.runtime[section].dateDisplayFormat?.includes('%Y') &&
|
|
857
|
+
if (config.xAxis.showYearsOnce && config.runtime[section].dateDisplayFormat?.includes('%Y') && ticks) {
|
|
858
|
+
const prevDate = ticks[i - 1] ? ticks[i - 1].value : null
|
|
876
859
|
const prevFormattedDate = timeFormat(config.runtime[section].dateDisplayFormat)(prevDate)
|
|
877
860
|
const year = formattedDate.match(/\d{4}/)
|
|
878
861
|
const prevYear = prevFormattedDate.match(/\d{4}/)
|
|
@@ -1177,7 +1160,7 @@ const CdcChart = ({
|
|
|
1177
1160
|
<h3>Finish Configuring</h3>
|
|
1178
1161
|
<p>Set all required options to the left and confirm below to display a preview of the chart.</p>
|
|
1179
1162
|
<Button
|
|
1180
|
-
className='btn'
|
|
1163
|
+
className='btn btn-primary'
|
|
1181
1164
|
style={{ margin: '1em auto' }}
|
|
1182
1165
|
disabled={missingRequiredSections()}
|
|
1183
1166
|
onClick={e => confirmDone(e)}
|
|
@@ -1234,12 +1217,31 @@ const CdcChart = ({
|
|
|
1234
1217
|
// cleaning is deleting data we need in forecasting charts.
|
|
1235
1218
|
if (!Array.isArray(data)) return []
|
|
1236
1219
|
if (config.visualizationType === 'Forecasting') return data
|
|
1220
|
+
if (config.series?.some(series => !!series.dynamicCategory)) return data
|
|
1237
1221
|
return config?.xAxis?.dataKey ? transform.cleanData(data, config.xAxis.dataKey) : data
|
|
1238
1222
|
}
|
|
1239
1223
|
|
|
1240
|
-
|
|
1241
|
-
|
|
1242
|
-
|
|
1224
|
+
const getTableRuntimeData = () => {
|
|
1225
|
+
if (visualizationType === 'Sankey') return config?.data?.[0]?.tableData
|
|
1226
|
+
const data = filteredData || excludedData
|
|
1227
|
+
const dynamicSeries = config.series.find(series => !!series.dynamicCategory)
|
|
1228
|
+
if (!dynamicSeries) return data
|
|
1229
|
+
const usedColumns = Object.values(config.columns)
|
|
1230
|
+
.filter(col => col.dataTable)
|
|
1231
|
+
.map(col => col.name)
|
|
1232
|
+
.concat([dynamicSeries.dynamicCategory, dynamicSeries.dataKey])
|
|
1233
|
+
if (config.xAxis?.dataKey) usedColumns.push(config.xAxis.dataKey)
|
|
1234
|
+
return data.map(d => _.pick(d, usedColumns))
|
|
1235
|
+
}
|
|
1236
|
+
|
|
1237
|
+
const pivotDynamicSeries = (config: ChartConfig): TableConfig => {
|
|
1238
|
+
const tableConfig: TableConfig = _.cloneDeep(config)
|
|
1239
|
+
const dynamicSeries = tableConfig.series.find(series => !!series.dynamicCategory)
|
|
1240
|
+
if (dynamicSeries) {
|
|
1241
|
+
const pivot: Pivot = { columnName: dynamicSeries.dynamicCategory, valueColumns: [dynamicSeries.dataKey] }
|
|
1242
|
+
tableConfig.table.pivot = pivot
|
|
1243
|
+
}
|
|
1244
|
+
return tableConfig
|
|
1243
1245
|
}
|
|
1244
1246
|
|
|
1245
1247
|
// Prevent render if loading
|
|
@@ -1459,7 +1461,7 @@ const CdcChart = ({
|
|
|
1459
1461
|
config.visualizationType !== 'Sankey') ||
|
|
1460
1462
|
(config.visualizationType === 'Sankey' && config.table.show)) && (
|
|
1461
1463
|
<DataTable
|
|
1462
|
-
config={config}
|
|
1464
|
+
config={pivotDynamicSeries(config)}
|
|
1463
1465
|
rawData={
|
|
1464
1466
|
config.visualizationType === 'Sankey'
|
|
1465
1467
|
? config?.data?.[0]?.tableData
|
|
@@ -1467,15 +1469,11 @@ const CdcChart = ({
|
|
|
1467
1469
|
? filterVizData(config.filters, config.data)
|
|
1468
1470
|
: config.data
|
|
1469
1471
|
}
|
|
1470
|
-
runtimeData={
|
|
1471
|
-
config.visualizationType === 'Sankey'
|
|
1472
|
-
? config?.data?.[0]?.tableData
|
|
1473
|
-
: filteredData || excludedData
|
|
1474
|
-
}
|
|
1472
|
+
runtimeData={getTableRuntimeData()}
|
|
1475
1473
|
expandDataTable={config.table.expanded}
|
|
1476
1474
|
columns={config.columns}
|
|
1477
1475
|
displayDataAsText={displayDataAsText}
|
|
1478
|
-
displayGeoName={
|
|
1476
|
+
displayGeoName={name => name}
|
|
1479
1477
|
applyLegendToRow={applyLegendToRow}
|
|
1480
1478
|
tableTitle={config.table.label}
|
|
1481
1479
|
indexTitle={config.table.indexLabel}
|
|
@@ -1518,7 +1516,7 @@ const CdcChart = ({
|
|
|
1518
1516
|
debugSvg: isDebug,
|
|
1519
1517
|
dimensions,
|
|
1520
1518
|
dynamicLegendItems,
|
|
1521
|
-
excludedData
|
|
1519
|
+
excludedData,
|
|
1522
1520
|
formatDate,
|
|
1523
1521
|
formatNumber,
|
|
1524
1522
|
formatTooltipsDate,
|
|
@@ -1526,6 +1524,7 @@ const CdcChart = ({
|
|
|
1526
1524
|
getYAxisData,
|
|
1527
1525
|
handleChartAriaLabels,
|
|
1528
1526
|
handleLineType,
|
|
1527
|
+
handleChartTabbing,
|
|
1529
1528
|
highlight,
|
|
1530
1529
|
highlightReset,
|
|
1531
1530
|
imageId,
|
|
@@ -1537,6 +1536,7 @@ const CdcChart = ({
|
|
|
1537
1536
|
isEditor,
|
|
1538
1537
|
isNumber,
|
|
1539
1538
|
legend,
|
|
1539
|
+
legendId,
|
|
1540
1540
|
legendRef,
|
|
1541
1541
|
lineOptions,
|
|
1542
1542
|
loading,
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import type { Meta, StoryObj } from '@storybook/react'
|
|
2
|
+
import Chart from '../CdcChart'
|
|
3
|
+
import scatterPlotCustomColorConfig from './_mock/scatterplot_mock.json'
|
|
4
|
+
|
|
5
|
+
const meta: Meta<typeof Chart> = {
|
|
6
|
+
title: 'Components/Templates/Chart/Custom Colors',
|
|
7
|
+
component: Chart
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
type Story = StoryObj<typeof Chart>
|
|
11
|
+
|
|
12
|
+
export const ScatterPlot: Story = {
|
|
13
|
+
args: {
|
|
14
|
+
config: scatterPlotCustomColorConfig,
|
|
15
|
+
isEditor: false
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export default meta
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import DynamicSeriesConfig from './_mock/dynamic_series_config.json'
|
|
2
|
+
import DynamicSeriesBarConfig from './_mock/dynamic_series_bar_config.json'
|
|
3
|
+
import { Meta, StoryObj } from '@storybook/react'
|
|
4
|
+
import Chart from '../CdcChart'
|
|
5
|
+
|
|
6
|
+
const meta: Meta<typeof Chart> = {
|
|
7
|
+
title: 'Components/Templates/Chart/Dynamic Series',
|
|
8
|
+
component: Chart
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
type Story = StoryObj<typeof Chart>
|
|
12
|
+
|
|
13
|
+
export const Line: Story = {
|
|
14
|
+
args: {
|
|
15
|
+
config: DynamicSeriesConfig,
|
|
16
|
+
isEditor: false
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export const Bar: Story = {
|
|
21
|
+
args: {
|
|
22
|
+
config: DynamicSeriesBarConfig,
|
|
23
|
+
isEditor: false
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
export default meta
|
|
@@ -12,12 +12,18 @@ const meta: Meta<typeof Chart> = {
|
|
|
12
12
|
|
|
13
13
|
type Story = StoryObj<typeof Chart>
|
|
14
14
|
|
|
15
|
-
export const
|
|
15
|
+
export const Legend_Gradient_Smooth: Story = {
|
|
16
16
|
args: {
|
|
17
17
|
config: chartGradientConfig
|
|
18
18
|
}
|
|
19
19
|
}
|
|
20
20
|
|
|
21
|
+
export const Legend_Gradient_Linear_Blocks: Story = {
|
|
22
|
+
args: {
|
|
23
|
+
config: editConfigKeys(chartGradientConfig, [{ path: ['legend', 'subStyle'], value: 'linear blocks' }])
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
|
|
21
27
|
export const Labels_On_Line_Legend_On_Top: Story = {
|
|
22
28
|
args: {
|
|
23
29
|
config: editConfigKeys(chartGradientConfig, [{ path: ['yAxis', 'labelsAboveGridlines'], value: true }])
|
|
@@ -30,4 +36,39 @@ export const Legend_On_Right: Story = {
|
|
|
30
36
|
}
|
|
31
37
|
}
|
|
32
38
|
|
|
39
|
+
export const Legend_On_Right_With_Box: Story = {
|
|
40
|
+
args: {
|
|
41
|
+
config: editConfigKeys(SimplifiedLineConfig, [{ path: ['legend', 'hideBorder'], value: false }])
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
export const Legend_Gradient_With_box: Story = {
|
|
46
|
+
args: {
|
|
47
|
+
config: editConfigKeys(chartGradientConfig, [{ path: ['legend', 'hideBorder'], value: false }])
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
export const Legend_Gradient_With_Text: Story = {
|
|
52
|
+
args: {
|
|
53
|
+
config: editConfigKeys(chartGradientConfig, [
|
|
54
|
+
{ path: ['legend', 'title'], value: 'Title' },
|
|
55
|
+
{ path: ['legend', 'description'], value: 'Description' },
|
|
56
|
+
{ path: ['legend', 'hideBorder'], value: false }
|
|
57
|
+
])
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
export const Legend_Gradient_Wrapping_Label: Story = {
|
|
61
|
+
args: {
|
|
62
|
+
config: JSON.parse(
|
|
63
|
+
JSON.stringify(
|
|
64
|
+
editConfigKeys(chartGradientConfig, [
|
|
65
|
+
{ path: ['legend', 'title'], value: 'Title' },
|
|
66
|
+
{ path: ['legend', 'description'], value: 'Description' },
|
|
67
|
+
{ path: ['legend', 'hideBorder'], value: false }
|
|
68
|
+
])
|
|
69
|
+
).replace(/Data 1/g, 'This is a long string that should wrap')
|
|
70
|
+
)
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
|
|
33
74
|
export default meta
|
|
@@ -1,5 +1,4 @@
|
|
|
1
1
|
import type { Meta, StoryObj } from '@storybook/react'
|
|
2
|
-
import SuppressedConfig from './_mock/bar-chart-suppressed.json'
|
|
3
2
|
|
|
4
3
|
import Chart from '../CdcChart'
|
|
5
4
|
import lineChartTwoPointsRegressionTest from './_mock/line_chart_two_points_regression_test.json'
|
|
@@ -9,6 +8,7 @@ import forestPlot from '../../examples/feature/forest-plot/forest-plot.json'
|
|
|
9
8
|
import pairedBar from './_mock/paired-bar.json'
|
|
10
9
|
import horizontalBarConfig from './_mock/horizontal_bar.json'
|
|
11
10
|
import pieConfig from './_mock/pie_with_data.json'
|
|
11
|
+
import boxPlotConfig from './_mock/boxplot_multiseries.json'
|
|
12
12
|
|
|
13
13
|
const meta: Meta<typeof Chart> = {
|
|
14
14
|
title: 'Components/Templates/Chart',
|
|
@@ -37,13 +37,6 @@ export const Lollipop: Story = {
|
|
|
37
37
|
}
|
|
38
38
|
}
|
|
39
39
|
|
|
40
|
-
export const Suppression: Story = {
|
|
41
|
-
args: {
|
|
42
|
-
config: SuppressedConfig,
|
|
43
|
-
isEditor: false
|
|
44
|
-
}
|
|
45
|
-
}
|
|
46
|
-
|
|
47
40
|
export const Forest_Plot: Story = {
|
|
48
41
|
args: {
|
|
49
42
|
config: forestPlot
|
|
@@ -67,5 +60,11 @@ export const Paired_Bar: Story = {
|
|
|
67
60
|
config: pairedBar
|
|
68
61
|
}
|
|
69
62
|
}
|
|
63
|
+
export const BoxPlot_Multiseries: Story = {
|
|
64
|
+
args: {
|
|
65
|
+
config: boxPlotConfig,
|
|
66
|
+
isEditor: false
|
|
67
|
+
}
|
|
68
|
+
}
|
|
70
69
|
|
|
71
70
|
export default meta
|
|
@@ -4,6 +4,7 @@ import Chart from '../CdcChart'
|
|
|
4
4
|
import pieChartExample from './_mock/pie_config.json'
|
|
5
5
|
import pieData from './_mock/pie_data.json'
|
|
6
6
|
import urlFilterExample from './_mock/url_filter.json'
|
|
7
|
+
import mockScatterPlot from './_mock/scatterplot_mock.json'
|
|
7
8
|
|
|
8
9
|
const meta: Meta<typeof Chart> = {
|
|
9
10
|
title: 'Components/Templates/Chart/Editor',
|
|
@@ -30,4 +31,30 @@ export const Url_Filter: Story = {
|
|
|
30
31
|
}
|
|
31
32
|
}
|
|
32
33
|
|
|
34
|
+
export const ScatterPlot: Story = {
|
|
35
|
+
args: {
|
|
36
|
+
config: mockScatterPlot,
|
|
37
|
+
isEditor: true
|
|
38
|
+
},
|
|
39
|
+
play: async ({ canvasElement }) => {
|
|
40
|
+
const canvas = within(canvasElement)
|
|
41
|
+
const user = userEvent.setup()
|
|
42
|
+
// play is running before full rendering is complete so sleep function
|
|
43
|
+
// is needed to delay the execution.
|
|
44
|
+
// possible related bug: https://github.com/storybookjs/storybook/issues/18258
|
|
45
|
+
await sleep(3000)
|
|
46
|
+
const dateCategoryAccordion = await canvas.findByRole('button', { name: /Date\/Category Axis/i })
|
|
47
|
+
user.click(dateCategoryAccordion)
|
|
48
|
+
await sleep(1000)
|
|
49
|
+
const minValueInput = await canvas.findByLabelText('min value')
|
|
50
|
+
const maxValueInput = await canvas.findByLabelText('max value')
|
|
51
|
+
await user.type(minValueInput, '0')
|
|
52
|
+
await user.type(maxValueInput, '100')
|
|
53
|
+
await sleep(1000)
|
|
54
|
+
// Scroll to the top of the screen
|
|
55
|
+
user.click(dateCategoryAccordion)
|
|
56
|
+
await canvasElement.scrollTo(0, 0)
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
|
|
33
60
|
export default meta
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import type { Meta, StoryObj } from '@storybook/react'
|
|
2
|
+
import Chart from '../CdcChart'
|
|
3
|
+
import suppressionConfig from './_mock/suppression_mock.json'
|
|
4
|
+
import SuppressedConfig from './_mock/bar-chart-suppressed.json'
|
|
5
|
+
const meta: Meta<typeof Chart> = {
|
|
6
|
+
title: 'Components/Templates/Chart/Suppression',
|
|
7
|
+
component: Chart
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
type Story = StoryObj<typeof Chart>
|
|
11
|
+
|
|
12
|
+
export const SuppressedLines: Story = {
|
|
13
|
+
args: {
|
|
14
|
+
config: suppressionConfig,
|
|
15
|
+
isEditor: false
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
export const SuppressedBars: Story = {
|
|
19
|
+
args: {
|
|
20
|
+
config: SuppressedConfig,
|
|
21
|
+
isEditor: false
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
export default meta
|
|
@@ -3,6 +3,7 @@ import barConfig from './_mock/line_chart_two_points_new_chart.json'
|
|
|
3
3
|
import annotationConfig from './_mock/annotation_category_mock.json'
|
|
4
4
|
import areaPrefix from './_mock/annotation_category_mock.json'
|
|
5
5
|
import horizontalBarConfig from './_mock/horizontal_bar.json'
|
|
6
|
+
import scatterPlotConfig from './_mock/scatterplot_mock.json'
|
|
6
7
|
|
|
7
8
|
import Chart from '../CdcChart'
|
|
8
9
|
import { editConfigKeys } from '../helpers/configHelpers'
|
|
@@ -148,4 +149,11 @@ export const Top_Suffix_Above_Gridlines_With_Options: Story = {
|
|
|
148
149
|
}
|
|
149
150
|
}
|
|
150
151
|
|
|
152
|
+
export const ScatterPlot_Bottom_Commas: Story = {
|
|
153
|
+
args: {
|
|
154
|
+
config: editConfigKeys(scatterPlotConfig, [{ path: ['dataFormat', 'bottomCommas'], value: true }])
|
|
155
|
+
},
|
|
156
|
+
isEditor: true
|
|
157
|
+
}
|
|
158
|
+
|
|
151
159
|
export default meta
|