@cdc/chart 4.26.2 → 4.26.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.
- package/LICENSE +201 -0
- package/dist/cdcchart.js +35674 -32430
- package/examples/data/data-with-metadata.json +10 -0
- package/examples/feature/pie/planet-pie-example-config.json +2 -1
- package/examples/metadata-variables.json +58 -0
- package/package.json +3 -3
- package/src/CdcChart.tsx +8 -4
- package/src/CdcChartComponent.tsx +321 -288
- package/src/_stories/Chart.CustomColors.stories.tsx +74 -0
- package/src/_stories/Chart.Defaults.stories.tsx +95 -0
- package/src/_stories/Chart.SmallestLeftAxisMax.stories.tsx +64 -0
- package/src/_stories/Chart.stories.tsx +36 -2
- package/src/_stories/ChartBar.Editor.stories.tsx +97 -38
- package/src/_stories/ChartBrush.Editor.stories.tsx +11 -25
- package/src/_stories/ChartEditor.Editor.stories.tsx +1 -1
- package/src/_stories/_mock/paired-bar-abbr.json +421 -0
- package/src/_stories/_mock/pie_custom_colors.json +268 -0
- package/src/_stories/_mock/smallest_left_axis_max.json +104 -0
- package/src/components/Annotations/components/AnnotationDraggable.styles.css +10 -10
- package/src/components/Annotations/components/AnnotationDropdown.styles.css +1 -1
- package/src/components/Annotations/components/AnnotationList.styles.css +11 -11
- package/src/components/Axis/BottomAxis.tsx +10 -3
- package/src/components/Axis/PairedBarAxis.tsx +10 -4
- package/src/components/BarChart/components/BarChart.Horizontal.tsx +12 -28
- package/src/components/BarChart/components/BarChart.StackedHorizontal.tsx +12 -30
- package/src/components/BarChart/components/BarChart.StackedVertical.tsx +12 -31
- package/src/components/BarChart/components/BarChart.Vertical.tsx +12 -28
- package/src/components/BarChart/helpers/getPatternUrl.ts +94 -0
- package/src/components/BarChart/helpers/tests/getPatternUrl.test.ts +134 -0
- package/src/components/BarChart/helpers/useBarChart.ts +3 -0
- package/src/components/Brush/BrushSelector.tsx +2 -1
- package/src/components/Brush/MiniChartPreview.tsx +21 -26
- package/src/components/EditorPanel/EditorPanel.tsx +56 -43
- package/src/components/EditorPanel/components/Panels/Panel.Annotate.tsx +9 -9
- package/src/components/EditorPanel/components/Panels/Panel.ForestPlotSettings.tsx +0 -78
- package/src/components/EditorPanel/components/Panels/Panel.General.tsx +39 -1
- package/src/components/EditorPanel/components/Panels/Panel.PatternSettings.tsx +24 -42
- package/src/components/EditorPanel/components/Panels/Panel.Series.tsx +83 -2
- package/src/components/EditorPanel/components/Panels/Panel.Visual.tsx +45 -42
- package/src/components/EditorPanel/editor-panel.scss +1 -1
- package/src/components/ForestPlot/ForestPlot.tsx +26 -22
- package/src/components/Legend/LegendGroup/LegendGroup.styles.css +4 -4
- package/src/components/Legend/helpers/createFormatLabels.tsx +3 -2
- package/src/components/LinearChart/tests/LinearChart.test.tsx +77 -0
- package/src/components/LinearChart/tests/mockConfigContext.ts +2 -0
- package/src/components/LinearChart.tsx +26 -6
- package/src/components/PieChart/PieChart.tsx +19 -4
- package/src/components/RadarChart/RadarChart.tsx +1 -1
- package/src/components/Regions/components/Regions.tsx +6 -6
- package/src/components/Sankey/components/Sankey.tsx +3 -3
- package/src/components/Sankey/sankey.scss +1 -1
- package/src/components/SmallMultiples/SmallMultiples.css +5 -5
- package/src/components/Sparkline/index.scss +4 -2
- package/src/components/WarmingStripes/WarmingStripesGradientLegend.css +8 -8
- package/src/data/initial-state.js +23 -14
- package/src/data/legacy-defaults.ts +18 -0
- package/src/helpers/abbreviateNumber.ts +24 -17
- package/src/helpers/getChartPatternId.ts +17 -0
- package/src/helpers/getMinMax.ts +16 -2
- package/src/helpers/seriesColumnSettings.ts +114 -0
- package/src/helpers/tests/countNumOfTicks.test.ts +77 -0
- package/src/helpers/tests/seriesColumnSettings.test.ts +84 -0
- package/src/hooks/useRightAxis.ts +14 -0
- package/src/hooks/useScales.ts +92 -56
- package/src/hooks/useTooltip.tsx +20 -3
- package/src/scss/main.scss +152 -79
- package/src/test/CdcChart.test.jsx +2 -2
- package/src/types/ChartConfig.ts +4 -0
- package/tests/fixtures/chart-config-with-metadata.json +29 -0
- package/tests/fixtures/data-with-metadata.json +10 -0
|
@@ -5,7 +5,7 @@ import ResizeObserver from 'resize-observer-polyfill'
|
|
|
5
5
|
import 'whatwg-fetch'
|
|
6
6
|
// Core components
|
|
7
7
|
import fetchRemoteData from '@cdc/core/helpers/fetchRemoteData'
|
|
8
|
-
import
|
|
8
|
+
import { VisualizationContainer, VisualizationContent } from '@cdc/core/components/Layout'
|
|
9
9
|
import Confirm from '@cdc/core/components/elements/Confirm'
|
|
10
10
|
import Error from '@cdc/core/components/elements/Error'
|
|
11
11
|
import SkipTo from '@cdc/core/components/elements/SkipTo'
|
|
@@ -24,7 +24,21 @@ import { Label } from './types/Label'
|
|
|
24
24
|
import ParentSize from '@visx/responsive/lib/components/ParentSize'
|
|
25
25
|
import { timeParse } from 'd3-time-format'
|
|
26
26
|
import parse from 'html-react-parser'
|
|
27
|
-
import
|
|
27
|
+
import cloneDeep from 'lodash/cloneDeep'
|
|
28
|
+
import defaultsDeep from 'lodash/defaultsDeep'
|
|
29
|
+
import lodashDefaults from 'lodash/defaults'
|
|
30
|
+
import findKey from 'lodash/findKey'
|
|
31
|
+
import forEach from 'lodash/forEach'
|
|
32
|
+
import get from 'lodash/get'
|
|
33
|
+
import isEmpty from 'lodash/isEmpty'
|
|
34
|
+
import isEqual from 'lodash/isEqual'
|
|
35
|
+
import isString from 'lodash/isString'
|
|
36
|
+
import kebabCase from 'lodash/kebabCase'
|
|
37
|
+
import pick from 'lodash/pick'
|
|
38
|
+
import remove from 'lodash/remove'
|
|
39
|
+
import set from 'lodash/set'
|
|
40
|
+
import uniq from 'lodash/uniq'
|
|
41
|
+
import xor from 'lodash/xor'
|
|
28
42
|
// Primary Components
|
|
29
43
|
import ConfigContext, { ChartDispatchContext } from './ConfigContext'
|
|
30
44
|
import PieChart from './components/PieChart'
|
|
@@ -40,6 +54,7 @@ import SparkLine from './components/Sparkline'
|
|
|
40
54
|
import Legend from './components/Legend'
|
|
41
55
|
import WarmingStripesGradientLegend from './components/WarmingStripes/WarmingStripesGradientLegend'
|
|
42
56
|
import defaults from './data/initial-state'
|
|
57
|
+
import { LEGACY_CHART_DEFAULTS } from './data/legacy-defaults'
|
|
43
58
|
import EditorPanel from './components/EditorPanel'
|
|
44
59
|
import { abbreviateNumber } from './helpers/abbreviateNumber'
|
|
45
60
|
import { handleChartTabbing } from './helpers/handleChartTabbing'
|
|
@@ -57,6 +72,7 @@ import Annotation from './components/Annotations'
|
|
|
57
72
|
import { getVisibleAnnotations } from './components/Annotations/helpers/getVisibleAnnotations'
|
|
58
73
|
// Core Helpers
|
|
59
74
|
import { DataTransform } from '@cdc/core/helpers/DataTransform'
|
|
75
|
+
import { backfillDefaults } from '@cdc/core/helpers/backfillDefaults'
|
|
60
76
|
import { isLegendWrapViewport } from '@cdc/core/helpers/viewports'
|
|
61
77
|
import { missingRequiredSections } from '@cdc/core/helpers/missingRequiredSections'
|
|
62
78
|
import { filterVizData } from '@cdc/core/helpers/filterVizData'
|
|
@@ -90,6 +106,8 @@ import { Datasets } from '@cdc/core/types/DataSet'
|
|
|
90
106
|
import { publishAnalyticsEvent } from '@cdc/core/helpers/metrics/helpers'
|
|
91
107
|
import cloneConfig from '@cdc/core/helpers/cloneConfig'
|
|
92
108
|
import { getVizTitle, getVizSubType } from '@cdc/core/helpers/metrics/utils'
|
|
109
|
+
import { ENABLE_CHART_MAP_TP5_TREATMENT, ENABLE_CHART_VISUAL_SETTINGS } from '@cdc/core/helpers/constants'
|
|
110
|
+
import CalloutFlag from '@cdc/core/assets/callout-flag.svg?url'
|
|
93
111
|
|
|
94
112
|
interface CdcChartProps {
|
|
95
113
|
config?: ChartConfig
|
|
@@ -174,36 +192,32 @@ const CdcChart: React.FC<CdcChartProps> = ({
|
|
|
174
192
|
}
|
|
175
193
|
}
|
|
176
194
|
|
|
195
|
+
const markupOptions = {
|
|
196
|
+
isEditor,
|
|
197
|
+
filters: config.filters || [],
|
|
198
|
+
locale: config.locale,
|
|
199
|
+
dataMetadata: config.dataMetadata
|
|
200
|
+
}
|
|
201
|
+
|
|
177
202
|
return {
|
|
178
203
|
title: title
|
|
179
|
-
? processMarkupVariables(title, config.data || [], config.markupVariables,
|
|
180
|
-
isEditor,
|
|
181
|
-
filters: config.filters || []
|
|
182
|
-
}).processedContent
|
|
204
|
+
? processMarkupVariables(title, config.data || [], config.markupVariables, markupOptions).processedContent
|
|
183
205
|
: title,
|
|
184
206
|
superTitle: config.superTitle
|
|
185
|
-
? processMarkupVariables(config.superTitle, config.data || [], config.markupVariables,
|
|
186
|
-
|
|
187
|
-
filters: config.filters || []
|
|
188
|
-
}).processedContent
|
|
207
|
+
? processMarkupVariables(config.superTitle, config.data || [], config.markupVariables, markupOptions)
|
|
208
|
+
.processedContent
|
|
189
209
|
: config.superTitle,
|
|
190
210
|
introText: config.introText
|
|
191
|
-
? processMarkupVariables(config.introText, config.data || [], config.markupVariables,
|
|
192
|
-
|
|
193
|
-
filters: config.filters || []
|
|
194
|
-
}).processedContent
|
|
211
|
+
? processMarkupVariables(config.introText, config.data || [], config.markupVariables, markupOptions)
|
|
212
|
+
.processedContent
|
|
195
213
|
: config.introText,
|
|
196
214
|
legacyFootnotes: config.legacyFootnotes
|
|
197
|
-
? processMarkupVariables(config.legacyFootnotes, config.data || [], config.markupVariables,
|
|
198
|
-
|
|
199
|
-
filters: config.filters || []
|
|
200
|
-
}).processedContent
|
|
215
|
+
? processMarkupVariables(config.legacyFootnotes, config.data || [], config.markupVariables, markupOptions)
|
|
216
|
+
.processedContent
|
|
201
217
|
: config.legacyFootnotes,
|
|
202
218
|
description: config.description
|
|
203
|
-
? processMarkupVariables(config.description, config.data || [], config.markupVariables,
|
|
204
|
-
|
|
205
|
-
filters: config.filters || []
|
|
206
|
-
}).processedContent
|
|
219
|
+
? processMarkupVariables(config.description, config.data || [], config.markupVariables, markupOptions)
|
|
220
|
+
.processedContent
|
|
207
221
|
: config.description
|
|
208
222
|
}
|
|
209
223
|
}, [
|
|
@@ -237,10 +251,6 @@ const CdcChart: React.FC<CdcChartProps> = ({
|
|
|
237
251
|
const { lineDatapointClass, contentClasses, sparkLineStyles } = useDataVizClasses(config)
|
|
238
252
|
const legendId = useId()
|
|
239
253
|
|
|
240
|
-
const hasDateAxis =
|
|
241
|
-
(config.xAxis || config.yAxis) && ['date-time', 'date'].includes((config.xAxis || config.yAxis).type)
|
|
242
|
-
const dataTableDefaultSortBy = hasDateAxis && config.xAxis.dataKey
|
|
243
|
-
|
|
244
254
|
const convertLineToBarGraph = isConvertLineToBarGraph(config, filteredData)
|
|
245
255
|
|
|
246
256
|
// Declaratively calculate series keys for pie charts based on filtered data
|
|
@@ -248,7 +258,7 @@ const CdcChart: React.FC<CdcChartProps> = ({
|
|
|
248
258
|
if (config.visualizationType !== 'Pie' || !config.xAxis?.dataKey) return null
|
|
249
259
|
const data = filteredData?.length > 0 ? filteredData : excludedData
|
|
250
260
|
if (!data) return null
|
|
251
|
-
return
|
|
261
|
+
return uniq(data.map(d => d[config.xAxis.dataKey]))
|
|
252
262
|
}, [config.visualizationType, config.xAxis?.dataKey, filteredData, excludedData])
|
|
253
263
|
|
|
254
264
|
const prepareConfig = (loadedConfig: ChartConfig) => {
|
|
@@ -295,18 +305,18 @@ const CdcChart: React.FC<CdcChartProps> = ({
|
|
|
295
305
|
// Ensure Horizon Chart has enough palette colors for all layers
|
|
296
306
|
if (newConfig.visualizationType === 'Horizon Chart') {
|
|
297
307
|
const numLayers = newConfig.horizon?.numLayers ?? 4
|
|
298
|
-
const currentCount =
|
|
299
|
-
|
|
308
|
+
const currentCount = get(newConfig, 'general.paletteColorCount', 4)
|
|
309
|
+
set(newConfig, 'general.paletteColorCount', Math.max(currentCount, numLayers))
|
|
300
310
|
}
|
|
301
311
|
|
|
302
|
-
|
|
312
|
+
defaultsDeep(newConfig, {
|
|
303
313
|
table: { showVertical: false }
|
|
304
314
|
})
|
|
305
315
|
|
|
306
|
-
|
|
316
|
+
set(newConfig, 'table.show', get(newConfig, 'table.show', !isDashboard))
|
|
307
317
|
|
|
308
|
-
|
|
309
|
-
|
|
318
|
+
forEach(newConfig.series, series => {
|
|
319
|
+
lodashDefaults(series, {
|
|
310
320
|
tooltip: true,
|
|
311
321
|
axis: 'Left'
|
|
312
322
|
})
|
|
@@ -325,15 +335,18 @@ const CdcChart: React.FC<CdcChartProps> = ({
|
|
|
325
335
|
let processedYAxis = targetConfig.yAxis?.label
|
|
326
336
|
|
|
327
337
|
if (targetConfig.enableMarkupVariables && targetConfig.markupVariables?.length) {
|
|
338
|
+
const axisMarkupOptions = {
|
|
339
|
+
isEditor,
|
|
340
|
+
filters: targetConfig.filters || [],
|
|
341
|
+
locale: targetConfig.locale,
|
|
342
|
+
dataMetadata: targetConfig.dataMetadata
|
|
343
|
+
}
|
|
328
344
|
if (targetConfig.xAxis?.label) {
|
|
329
345
|
processedXAxis = processMarkupVariables(
|
|
330
346
|
targetConfig.xAxis.label,
|
|
331
347
|
dataSource || [],
|
|
332
348
|
targetConfig.markupVariables,
|
|
333
|
-
|
|
334
|
-
isEditor,
|
|
335
|
-
filters: targetConfig.filters || []
|
|
336
|
-
}
|
|
349
|
+
axisMarkupOptions
|
|
337
350
|
).processedContent
|
|
338
351
|
}
|
|
339
352
|
if (targetConfig.yAxis?.label) {
|
|
@@ -341,10 +354,7 @@ const CdcChart: React.FC<CdcChartProps> = ({
|
|
|
341
354
|
targetConfig.yAxis.label,
|
|
342
355
|
dataSource || [],
|
|
343
356
|
targetConfig.markupVariables,
|
|
344
|
-
|
|
345
|
-
isEditor,
|
|
346
|
-
filters: targetConfig.filters || []
|
|
347
|
-
}
|
|
357
|
+
axisMarkupOptions
|
|
348
358
|
).processedContent
|
|
349
359
|
}
|
|
350
360
|
}
|
|
@@ -376,12 +386,17 @@ const CdcChart: React.FC<CdcChartProps> = ({
|
|
|
376
386
|
const { processedXAxis, processedYAxis, runtimeXAxisLabel, runtimeYAxisLabel, isHorizontalVariant } =
|
|
377
387
|
getProcessedAxisLabels(newConfig, data || [])
|
|
378
388
|
|
|
379
|
-
//
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
389
|
+
// Backfill missing properties from defaults, respecting legacy values
|
|
390
|
+
backfillDefaults(newConfig, defaults, LEGACY_CHART_DEFAULTS)
|
|
391
|
+
|
|
392
|
+
// Auto-populate table.defaultSort for date-axis charts if not already set by user
|
|
393
|
+
const hasDateAxisType = ['date-time', 'date'].includes(newConfig.xAxis?.type)
|
|
394
|
+
if (hasDateAxisType && newConfig.xAxis?.dataKey && !newConfig.table?.defaultSort?.column) {
|
|
395
|
+
newConfig.table = {
|
|
396
|
+
...newConfig.table,
|
|
397
|
+
defaultSort: { column: newConfig.xAxis.dataKey, sortDirection: 'desc' }
|
|
383
398
|
}
|
|
384
|
-
}
|
|
399
|
+
}
|
|
385
400
|
|
|
386
401
|
const newExcludedData: any[] = getExcludedData(newConfig, dataOverride || stateData)
|
|
387
402
|
dispatch({ type: 'SET_EXCLUDED_DATA', payload: newExcludedData })
|
|
@@ -408,7 +423,7 @@ const CdcChart: React.FC<CdcChartProps> = ({
|
|
|
408
423
|
const shouldPreserveError = existingErrorMessage && !isPieChartValidationError
|
|
409
424
|
|
|
410
425
|
newConfig.runtime = {} as Runtime
|
|
411
|
-
newConfig.runtime.series =
|
|
426
|
+
newConfig.runtime.series = cloneDeep(newConfig.series)
|
|
412
427
|
newConfig.runtime.seriesLabels = {}
|
|
413
428
|
newConfig.runtime.seriesLabelsAll = []
|
|
414
429
|
newConfig.runtime.originalXAxis = newConfig.xAxis
|
|
@@ -416,22 +431,22 @@ const CdcChart: React.FC<CdcChartProps> = ({
|
|
|
416
431
|
if (newConfig.visualizationType === 'Pie') {
|
|
417
432
|
// Use the same data that will be passed to PieChart (after exclusions and filters)
|
|
418
433
|
const pieData = currentData.length > 0 ? currentData : newExcludedData
|
|
419
|
-
newConfig.runtime.seriesKeys =
|
|
434
|
+
newConfig.runtime.seriesKeys = uniq(pieData.map(d => d[newConfig.xAxis.dataKey]))
|
|
420
435
|
newConfig.runtime.seriesLabelsAll = newConfig.runtime.seriesKeys
|
|
421
436
|
newConfig.runtime.isPieChart = true // Flag to know when to use derived keys
|
|
422
437
|
} else if (newConfig.visualizationType === 'Radar') {
|
|
423
438
|
// Radar chart: seriesKeys are the entity names from xAxis.dataKey
|
|
424
439
|
const radarData = currentData.length > 0 ? currentData : newExcludedData
|
|
425
|
-
newConfig.runtime.seriesKeys =
|
|
440
|
+
newConfig.runtime.seriesKeys = uniq(radarData.map(d => d[newConfig.xAxis.dataKey]))
|
|
426
441
|
newConfig.runtime.seriesLabelsAll = newConfig.runtime.seriesKeys
|
|
427
442
|
} else {
|
|
428
443
|
const finalData = dataOverride || newConfig.formattedData || newConfig.data
|
|
429
444
|
newConfig.runtime.seriesKeys = (newConfig.runtime.series || []).flatMap(series => {
|
|
430
445
|
if (series.dynamicCategory) {
|
|
431
|
-
|
|
432
|
-
|
|
446
|
+
remove(newConfig.runtime.seriesLabelsAll, label => label === series.dataKey)
|
|
447
|
+
remove(newConfig.runtime.series, s => s.dataKey === series.dataKey)
|
|
433
448
|
// grab the dynamic series keys from the data
|
|
434
|
-
const seriesKeys: string[] =
|
|
449
|
+
const seriesKeys: string[] = uniq(finalData.map(d => d[series.dynamicCategory]))
|
|
435
450
|
// for each of those keys perform side effects
|
|
436
451
|
seriesKeys.forEach(dataKey => {
|
|
437
452
|
newConfig.runtime.seriesLabels[dataKey] = dataKey
|
|
@@ -515,8 +530,8 @@ const CdcChart: React.FC<CdcChartProps> = ({
|
|
|
515
530
|
|
|
516
531
|
if (isHorizontalVariant) {
|
|
517
532
|
// For horizontal charts, axes are swapped, so processedYAxis goes to runtime.xAxis and vice versa
|
|
518
|
-
const horizontalXAxisSource =
|
|
519
|
-
const horizontalYAxisSource =
|
|
533
|
+
const horizontalXAxisSource = cloneDeep((newConfig.yAxis as any)?.yAxis || newConfig.yAxis)
|
|
534
|
+
const horizontalYAxisSource = cloneDeep((newConfig.xAxis as any)?.xAxis || newConfig.xAxis)
|
|
520
535
|
newConfig.runtime.xAxis = {
|
|
521
536
|
...horizontalXAxisSource,
|
|
522
537
|
label: runtimeXAxisLabel ?? horizontalXAxisSource?.label
|
|
@@ -659,7 +674,8 @@ const CdcChart: React.FC<CdcChartProps> = ({
|
|
|
659
674
|
if (newConfig.dataUrl && !urlFilters) {
|
|
660
675
|
// handle urls with spaces in the name.
|
|
661
676
|
if (newConfig.dataUrl) newConfig.dataUrl = `${newConfig.dataUrl}`
|
|
662
|
-
let newData = await fetchRemoteData(newConfig.dataUrl)
|
|
677
|
+
let { data: newData, dataMetadata } = await fetchRemoteData(newConfig.dataUrl)
|
|
678
|
+
newConfig.dataMetadata = dataMetadata
|
|
663
679
|
|
|
664
680
|
if (newConfig.vegaConfig) {
|
|
665
681
|
newData = extractCoveData(updateVegaData(newConfig.vegaConfig, newData))
|
|
@@ -720,7 +736,7 @@ const CdcChart: React.FC<CdcChartProps> = ({
|
|
|
720
736
|
* When cove has a config and container ref publish the cove_loaded event.
|
|
721
737
|
*/
|
|
722
738
|
useEffect(() => {
|
|
723
|
-
if (container && !isLoading && !
|
|
739
|
+
if (container && !isLoading && !isEmpty(config) && !coveLoadedEventRan) {
|
|
724
740
|
publish('cove_loaded', { config: config })
|
|
725
741
|
dispatch({ type: 'SET_LOADED_EVENT', payload: true })
|
|
726
742
|
}
|
|
@@ -775,7 +791,7 @@ const CdcChart: React.FC<CdcChartProps> = ({
|
|
|
775
791
|
}, [externalFilters]) // eslint-disable-line
|
|
776
792
|
|
|
777
793
|
// Declaratively update runtime series keys for pie charts when derived value changes
|
|
778
|
-
if (config.runtime?.isPieChart && pieSeriesKeys && !
|
|
794
|
+
if (config.runtime?.isPieChart && pieSeriesKeys && !isEqual(pieSeriesKeys, config.runtime?.seriesKeys)) {
|
|
779
795
|
const newConfig = {
|
|
780
796
|
...config,
|
|
781
797
|
runtime: {
|
|
@@ -815,7 +831,7 @@ const CdcChart: React.FC<CdcChartProps> = ({
|
|
|
815
831
|
useEffect(() => {
|
|
816
832
|
if (
|
|
817
833
|
!config?.runtime ||
|
|
818
|
-
|
|
834
|
+
isEmpty(config.runtime) ||
|
|
819
835
|
(!config.runtime.xAxis && !config.runtime.yAxis) ||
|
|
820
836
|
!config.markupVariables?.length
|
|
821
837
|
) {
|
|
@@ -825,7 +841,7 @@ const CdcChart: React.FC<CdcChartProps> = ({
|
|
|
825
841
|
const dataSource = (stateData && stateData.length ? stateData : config.data) || []
|
|
826
842
|
const { runtimeXAxisLabel, runtimeYAxisLabel, isHorizontalVariant } = getProcessedAxisLabels(config, dataSource)
|
|
827
843
|
|
|
828
|
-
const runtimeClone =
|
|
844
|
+
const runtimeClone = cloneDeep(config.runtime)
|
|
829
845
|
|
|
830
846
|
if (!runtimeClone?.xAxis || !runtimeClone?.yAxis) {
|
|
831
847
|
return
|
|
@@ -864,9 +880,9 @@ const CdcChart: React.FC<CdcChartProps> = ({
|
|
|
864
880
|
return handleShowAll()
|
|
865
881
|
}
|
|
866
882
|
|
|
867
|
-
const newHighlight =
|
|
883
|
+
const newHighlight = findKey(config.runtime.seriesLabels, v => v === label.datum) || label.datum
|
|
868
884
|
|
|
869
|
-
const newSeriesHighlight =
|
|
885
|
+
const newSeriesHighlight = xor(seriesHighlight, [newHighlight])
|
|
870
886
|
dispatch({ type: 'SET_SERIES_HIGHLIGHT', payload: newSeriesHighlight })
|
|
871
887
|
}
|
|
872
888
|
// Called on reset button click, unhighlights all data series
|
|
@@ -909,11 +925,11 @@ const CdcChart: React.FC<CdcChartProps> = ({
|
|
|
909
925
|
const formatDate = (date, i, ticks) => {
|
|
910
926
|
const displayFormat =
|
|
911
927
|
config.runtime[section].dateDisplayFormat || config.runtime[section].dateParseFormat || '%Y-%m-%d'
|
|
912
|
-
let formattedDate = coreFormatDate(displayFormat, date)
|
|
928
|
+
let formattedDate = coreFormatDate(displayFormat, date, config.locale)
|
|
913
929
|
// Show years only once
|
|
914
930
|
if (config.xAxis.showYearsOnce && displayFormat?.includes('%Y') && ticks) {
|
|
915
931
|
const prevDate = ticks[i - 1] ? ticks[i - 1].value : null
|
|
916
|
-
const prevFormattedDate = coreFormatDate(displayFormat, prevDate)
|
|
932
|
+
const prevFormattedDate = coreFormatDate(displayFormat, prevDate, config.locale)
|
|
917
933
|
const year = formattedDate.match(/\d{4}/)
|
|
918
934
|
const prevYear = prevFormattedDate.match(/\d{4}/)
|
|
919
935
|
if (year && prevYear && year[0] === prevYear[0]) {
|
|
@@ -924,7 +940,7 @@ const CdcChart: React.FC<CdcChartProps> = ({
|
|
|
924
940
|
}
|
|
925
941
|
|
|
926
942
|
const formatTooltipsDate = date => {
|
|
927
|
-
return coreFormatDate(config.tooltips.dateDisplayFormat, date)
|
|
943
|
+
return coreFormatDate(config.tooltips.dateDisplayFormat, date, config.locale)
|
|
928
944
|
}
|
|
929
945
|
|
|
930
946
|
// Format numeric data based on settings in config OR from passed in settings for Additional Columns
|
|
@@ -1066,16 +1082,16 @@ const CdcChart: React.FC<CdcChartProps> = ({
|
|
|
1066
1082
|
) {
|
|
1067
1083
|
num = num // eslint-disable-line
|
|
1068
1084
|
} else {
|
|
1069
|
-
num = num.toLocaleString(
|
|
1085
|
+
num = num.toLocaleString(config.locale, stringFormattingOptions)
|
|
1070
1086
|
}
|
|
1071
1087
|
let result = ''
|
|
1072
1088
|
|
|
1073
1089
|
if (abbreviated && axis === 'left' && shouldAbbreviate) {
|
|
1074
|
-
num = abbreviateNumber(parseFloat(num))
|
|
1090
|
+
num = abbreviateNumber(parseFloat(num), config.locale)
|
|
1075
1091
|
}
|
|
1076
1092
|
|
|
1077
1093
|
if (bottomAbbreviated && axis === 'bottom' && shouldAbbreviate) {
|
|
1078
|
-
num = abbreviateNumber(parseFloat(num))
|
|
1094
|
+
num = abbreviateNumber(parseFloat(num), config.locale)
|
|
1079
1095
|
}
|
|
1080
1096
|
|
|
1081
1097
|
if (addColPrefix && axis === 'left') {
|
|
@@ -1143,8 +1159,8 @@ const CdcChart: React.FC<CdcChartProps> = ({
|
|
|
1143
1159
|
if (!Array.isArray(data)) return []
|
|
1144
1160
|
if (config.visualizationType === 'Forecasting') return data
|
|
1145
1161
|
// specify keys that needs to be cleaned to render chart and skip rest
|
|
1146
|
-
const CIkeys: string[] = Object.values(
|
|
1147
|
-
const seriesKeys: string[] =
|
|
1162
|
+
const CIkeys: string[] = Object.values(get(config, 'confidenceKeys', {})) as string[]
|
|
1163
|
+
const seriesKeys: string[] = get(config, 'series', []).map((s: any) => s.dataKey)
|
|
1148
1164
|
const keysToClean: string[] = [...(seriesKeys ?? []), ...(CIkeys ?? [])]
|
|
1149
1165
|
|
|
1150
1166
|
// key that does not need to be cleaned
|
|
@@ -1166,7 +1182,7 @@ const CdcChart: React.FC<CdcChartProps> = ({
|
|
|
1166
1182
|
.map(col => col.name)
|
|
1167
1183
|
.concat([dynamicSeries.dynamicCategory, dynamicSeries.dataKey])
|
|
1168
1184
|
if (config.xAxis?.dataKey) usedColumns.push(config.xAxis.dataKey)
|
|
1169
|
-
return data.map(d =>
|
|
1185
|
+
return data.map(d => pick(d, usedColumns))
|
|
1170
1186
|
}
|
|
1171
1187
|
|
|
1172
1188
|
const pivotDynamicSeries = (config: ChartConfig): TableConfig => {
|
|
@@ -1184,18 +1200,51 @@ const CdcChart: React.FC<CdcChartProps> = ({
|
|
|
1184
1200
|
|
|
1185
1201
|
// Filter annotations to only those visible in current data view
|
|
1186
1202
|
const visibleAnnotations = getVisibleAnnotations(config.annotations, transformedData, config.xAxis?.dataKey)
|
|
1203
|
+
const isTp5Treatment = ENABLE_CHART_MAP_TP5_TREATMENT && config.visual?.tp5Treatment
|
|
1204
|
+
const visualSettingClasses = new Set([
|
|
1205
|
+
'component--has-border-color-theme',
|
|
1206
|
+
'component--has-accent',
|
|
1207
|
+
'component--has-background',
|
|
1208
|
+
'component--hide-background-color'
|
|
1209
|
+
])
|
|
1210
|
+
const tp5Classes = new Set(['component--tp5-treatment', 'component--tp5-treatment-background'])
|
|
1211
|
+
const bodyClasses = contentClasses.filter(className => {
|
|
1212
|
+
if (!ENABLE_CHART_VISUAL_SETTINGS && visualSettingClasses.has(className)) return false
|
|
1213
|
+
if (!ENABLE_CHART_MAP_TP5_TREATMENT && tp5Classes.has(className)) return false
|
|
1214
|
+
return true
|
|
1215
|
+
})
|
|
1216
|
+
if (config.visualizationType === 'Spark Line' && config.visual?.background) {
|
|
1217
|
+
bodyClasses.push('component--has-background')
|
|
1218
|
+
}
|
|
1219
|
+
if (config.visualizationType === 'Spark Line' && config.visual?.hideBackgroundColor) {
|
|
1220
|
+
bodyClasses.push('component--hide-background-color')
|
|
1221
|
+
}
|
|
1222
|
+
if (isTp5Treatment && !bodyClasses.includes('no-borders')) bodyClasses.push('no-borders')
|
|
1223
|
+
const chartTitle = (
|
|
1224
|
+
<Title
|
|
1225
|
+
showTitle={config.showTitle}
|
|
1226
|
+
isDashboard={isDashboard}
|
|
1227
|
+
title={title}
|
|
1228
|
+
superTitle={processedSuperTitle}
|
|
1229
|
+
titleStyle={isTp5Treatment ? 'small' : config.titleStyle}
|
|
1230
|
+
classes={['chart-title', `${config.theme}`, 'cove-visualization__title', isTp5Treatment ? '' : 'mb-3']}
|
|
1231
|
+
style={undefined}
|
|
1232
|
+
config={config}
|
|
1233
|
+
/>
|
|
1234
|
+
)
|
|
1187
1235
|
|
|
1188
1236
|
// Prevent render if loading
|
|
1189
1237
|
let body = <Loading />
|
|
1190
1238
|
|
|
1191
1239
|
const makeClassName = string => {
|
|
1192
|
-
if (!
|
|
1240
|
+
if (!isString(string)) return undefined
|
|
1193
1241
|
|
|
1194
|
-
return
|
|
1242
|
+
return kebabCase(string)
|
|
1195
1243
|
}
|
|
1196
1244
|
const getChartWrapperClasses = () => {
|
|
1197
1245
|
const isLegendOnBottom = legend?.position === 'bottom' || isLegendWrapViewport(currentViewport)
|
|
1198
|
-
const classes = ['chart-container', 'p-relative']
|
|
1246
|
+
const classes = ['chart-container', 'visualization-container', 'p-relative']
|
|
1247
|
+
const visualSettingClasses = ['component--has-border-color-theme', 'component--has-accent']
|
|
1199
1248
|
if (legend?.position) {
|
|
1200
1249
|
if (isLegendWrapViewport(currentViewport) && legend?.position !== 'top') {
|
|
1201
1250
|
classes.push('legend-bottom')
|
|
@@ -1204,16 +1253,23 @@ const CdcChart: React.FC<CdcChartProps> = ({
|
|
|
1204
1253
|
}
|
|
1205
1254
|
}
|
|
1206
1255
|
if (legend?.hide) classes.push('legend-hidden')
|
|
1256
|
+
if (contentClasses.includes('sparkline')) classes.push('sparkline')
|
|
1207
1257
|
if (lineDatapointClass) classes.push(lineDatapointClass)
|
|
1208
1258
|
if (!config.barHasBorder) classes.push('chart-bar--no-border')
|
|
1209
1259
|
if (config.xAxis.brushActive && dashboardConfig?.type === 'dashboard' && (!isLegendOnBottom || legend.hide))
|
|
1210
1260
|
classes.push('dashboard-brush')
|
|
1211
|
-
|
|
1261
|
+
|
|
1262
|
+
if (!ENABLE_CHART_VISUAL_SETTINGS) {
|
|
1263
|
+
const filtered = classes.filter(className => !visualSettingClasses.includes(className))
|
|
1264
|
+
if (!filtered.includes('no-borders')) filtered.push('no-borders')
|
|
1265
|
+
return filtered
|
|
1266
|
+
}
|
|
1267
|
+
|
|
1212
1268
|
return classes
|
|
1213
1269
|
}
|
|
1214
1270
|
|
|
1215
1271
|
const getChartSubTextClasses = () => {
|
|
1216
|
-
const classes = ['subtext
|
|
1272
|
+
const classes = ['subtext']
|
|
1217
1273
|
const isLegendOnBottom = legend?.position === 'bottom' || isLegendWrapViewport(currentViewport)
|
|
1218
1274
|
|
|
1219
1275
|
if (config.isResponsiveTicks) classes.push('subtext--responsive-ticks ')
|
|
@@ -1228,211 +1284,34 @@ const CdcChart: React.FC<CdcChartProps> = ({
|
|
|
1228
1284
|
)
|
|
1229
1285
|
body = (
|
|
1230
1286
|
<>
|
|
1231
|
-
{
|
|
1232
|
-
|
|
1233
|
-
|
|
1234
|
-
|
|
1235
|
-
|
|
1236
|
-
|
|
1237
|
-
|
|
1238
|
-
|
|
1239
|
-
|
|
1240
|
-
|
|
1241
|
-
|
|
1242
|
-
|
|
1243
|
-
|
|
1244
|
-
|
|
1245
|
-
|
|
1246
|
-
|
|
1247
|
-
|
|
1248
|
-
|
|
1249
|
-
|
|
1250
|
-
|
|
1251
|
-
|
|
1252
|
-
|
|
1253
|
-
|
|
1254
|
-
|
|
1255
|
-
|
|
1256
|
-
const hasError = errorMessage && typeof errorMessage === 'string' && errorMessage.trim() !== ''
|
|
1257
|
-
const shouldShow = undefined === config.newViz && isEditor && config.runtime && hasError
|
|
1258
|
-
return shouldShow ? <Error errorMessage={errorMessage} /> : null
|
|
1259
|
-
})()} */}
|
|
1260
|
-
|
|
1261
|
-
{/* Visualization Wrapper */}
|
|
1262
|
-
<div className={getChartWrapperClasses().join(' ')}>
|
|
1263
|
-
{/* Intro Text/Message */}
|
|
1264
|
-
{processedIntroText && config.visualizationType !== 'Spark Line' && (
|
|
1265
|
-
<section className={`introText mb-4`}>{parse(processedIntroText)}</section>
|
|
1266
|
-
)}
|
|
1267
|
-
|
|
1268
|
-
{/* Filters */}
|
|
1269
|
-
{config.filters && !externalFilters && config.visualizationType !== 'Spark Line' && (
|
|
1270
|
-
<Filters
|
|
1271
|
-
config={config}
|
|
1272
|
-
setFilters={setFilters}
|
|
1273
|
-
excludedData={excludedData}
|
|
1274
|
-
dimensions={dimensions}
|
|
1275
|
-
interactionLabel={interactionLabel}
|
|
1276
|
-
/>
|
|
1277
|
-
)}
|
|
1278
|
-
<SkipTo skipId={handleChartTabbing(config, legendId)} skipMessage='Skip Over Chart Container' />
|
|
1279
|
-
{visibleAnnotations.length > 0 && (
|
|
1280
|
-
<SkipTo
|
|
1281
|
-
skipId={handleChartTabbing(config, legendId)}
|
|
1282
|
-
skipMessage={`Skip over annotations`}
|
|
1283
|
-
key={`skip-annotations`}
|
|
1284
|
-
/>
|
|
1285
|
-
)}
|
|
1286
|
-
<LegendWrapper>
|
|
1287
|
-
<div
|
|
1288
|
-
className={
|
|
1289
|
-
legend.hide || isLegendWrapViewport(currentViewport)
|
|
1290
|
-
? 'w-100'
|
|
1291
|
-
: legend.position === 'bottom' ||
|
|
1292
|
-
legend.position === 'top' ||
|
|
1293
|
-
visualizationType === 'Sankey' ||
|
|
1294
|
-
visualizationType === 'Spark Line'
|
|
1295
|
-
? 'w-100'
|
|
1296
|
-
: 'w-75'
|
|
1297
|
-
}
|
|
1298
|
-
>
|
|
1299
|
-
{/* Check if there is data to display */}
|
|
1300
|
-
{(!filteredData || filteredData.length === 0) && (
|
|
1301
|
-
<div className='no-data-message' style={{ padding: '2rem', textAlign: 'center', color: '#666' }}>
|
|
1302
|
-
{config.chartMessage?.noData || 'No Data Available'}
|
|
1303
|
-
</div>
|
|
1304
|
-
)}
|
|
1305
|
-
|
|
1306
|
-
{/* All charts with LinearChart */}
|
|
1307
|
-
{filteredData &&
|
|
1308
|
-
filteredData.length > 0 &&
|
|
1309
|
-
!['Spark Line', 'Line', 'Sankey', 'Pie', 'Radar'].includes(config.visualizationType) && (
|
|
1310
|
-
<div ref={parentRef} style={{ width: `100%` }}>
|
|
1311
|
-
<ParentSize>
|
|
1312
|
-
{parent => (
|
|
1313
|
-
<LinearChart ref={svgRef} parentWidth={parent.width} parentHeight={parent.height} />
|
|
1314
|
-
)}
|
|
1315
|
-
</ParentSize>
|
|
1316
|
-
</div>
|
|
1317
|
-
)}
|
|
1318
|
-
|
|
1319
|
-
{filteredData && filteredData.length > 0 && config.visualizationType === 'Pie' && (
|
|
1320
|
-
<ParentSize className='justify-content-center d-flex' style={{ width: `100%` }}>
|
|
1321
|
-
{parent => (
|
|
1322
|
-
<PieChart
|
|
1323
|
-
ref={svgRef}
|
|
1324
|
-
parentWidth={parent.width}
|
|
1325
|
-
parentHeight={parent.height}
|
|
1326
|
-
interactionLabel={interactionLabel}
|
|
1327
|
-
/>
|
|
1328
|
-
)}
|
|
1329
|
-
</ParentSize>
|
|
1330
|
-
)}
|
|
1331
|
-
{/* Radar Chart */}
|
|
1332
|
-
{filteredData && filteredData.length > 0 && config.visualizationType === 'Radar' && (
|
|
1333
|
-
<ParentSize className='justify-content-center d-flex' style={{ width: `100%` }}>
|
|
1334
|
-
{parent => (
|
|
1335
|
-
<RadarChart
|
|
1336
|
-
ref={svgRef}
|
|
1337
|
-
parentWidth={parent.width}
|
|
1338
|
-
parentHeight={parent.height}
|
|
1339
|
-
interactionLabel={interactionLabel}
|
|
1340
|
-
/>
|
|
1341
|
-
)}
|
|
1342
|
-
</ParentSize>
|
|
1343
|
-
)}
|
|
1344
|
-
{/* Line Chart */}
|
|
1345
|
-
{filteredData &&
|
|
1346
|
-
filteredData.length > 0 &&
|
|
1347
|
-
config.visualizationType === 'Line' &&
|
|
1348
|
-
(convertLineToBarGraph ? (
|
|
1349
|
-
<div ref={parentRef} style={{ width: `100%` }}>
|
|
1350
|
-
<ParentSize>
|
|
1351
|
-
{parent => (
|
|
1352
|
-
<LinearChart ref={svgRef} parentWidth={parent.width} parentHeight={parent.height} />
|
|
1353
|
-
)}
|
|
1354
|
-
</ParentSize>
|
|
1355
|
-
</div>
|
|
1356
|
-
) : (
|
|
1357
|
-
<div ref={parentRef} style={{ width: '100%' }}>
|
|
1358
|
-
<ParentSize>
|
|
1359
|
-
{parent => {
|
|
1360
|
-
const labelMargin = 120
|
|
1361
|
-
const widthReduction =
|
|
1362
|
-
config.showLineSeriesLabels &&
|
|
1363
|
-
(config.legend.position !== 'right' || config.legend.hide)
|
|
1364
|
-
? labelMargin
|
|
1365
|
-
: 0
|
|
1366
|
-
return (
|
|
1367
|
-
<LinearChart
|
|
1368
|
-
ref={svgRef}
|
|
1369
|
-
parentWidth={parent.width - widthReduction}
|
|
1370
|
-
parentHeight={parent.height}
|
|
1371
|
-
/>
|
|
1372
|
-
)
|
|
1373
|
-
}}
|
|
1374
|
-
</ParentSize>
|
|
1375
|
-
</div>
|
|
1376
|
-
))}
|
|
1377
|
-
{/* Sparkline */}
|
|
1378
|
-
{config.visualizationType === 'Spark Line' && (
|
|
1379
|
-
<>
|
|
1380
|
-
<Filters
|
|
1381
|
-
config={config}
|
|
1382
|
-
setFilters={setFilters}
|
|
1383
|
-
excludedData={excludedData}
|
|
1384
|
-
dimensions={dimensions}
|
|
1385
|
-
interactionLabel={interactionLabel}
|
|
1386
|
-
/>
|
|
1387
|
-
{processedIntroText && (
|
|
1388
|
-
<section className='introText mb-4' style={{ padding: '0px 0 35px' }}>
|
|
1389
|
-
{parse(processedIntroText)}
|
|
1390
|
-
</section>
|
|
1391
|
-
)}
|
|
1392
|
-
<div style={{ height: `100px`, width: `100%`, ...sparkLineStyles }}>
|
|
1393
|
-
<ParentSize>{parent => <SparkLine width={parent.width} height={parent.height} />}</ParentSize>
|
|
1394
|
-
</div>
|
|
1395
|
-
{description && (
|
|
1396
|
-
<div className='subtext' style={{ padding: '35px 0 15px' }}>
|
|
1397
|
-
{parse(description)}
|
|
1398
|
-
</div>
|
|
1399
|
-
)}
|
|
1400
|
-
</>
|
|
1401
|
-
)}
|
|
1402
|
-
{/* Sankey */}
|
|
1403
|
-
{config.visualizationType === 'Sankey' && (
|
|
1404
|
-
<ParentSize aria-hidden='true'>
|
|
1405
|
-
{parent => <SankeyChart runtime={config.runtime} width={parent.width} height={parent.height} />}
|
|
1406
|
-
</ParentSize>
|
|
1407
|
-
)}
|
|
1408
|
-
</div>
|
|
1409
|
-
{/* Legend */}
|
|
1410
|
-
{!config.legend.hide &&
|
|
1411
|
-
config.visualizationType !== 'Spark Line' &&
|
|
1412
|
-
config.visualizationType !== 'Sankey' &&
|
|
1413
|
-
!(config.visualizationType === 'Warming Stripes' && config.legend?.style === 'gradient') &&
|
|
1414
|
-
!(config.visualizationType === 'Warming Stripes' && config.smallMultiples?.mode) && (
|
|
1415
|
-
<Legend
|
|
1416
|
-
ref={legendRef}
|
|
1417
|
-
skipId={handleChartTabbing(config, legendId)}
|
|
1418
|
-
interactionLabel={interactionLabel}
|
|
1419
|
-
/>
|
|
1420
|
-
)}
|
|
1421
|
-
{config.visualizationType === 'Warming Stripes' &&
|
|
1422
|
-
config.legend?.style === 'gradient' &&
|
|
1423
|
-
!config.smallMultiples?.mode && <WarmingStripesGradientLegend />}
|
|
1424
|
-
</LegendWrapper>
|
|
1425
|
-
{/* Link */}
|
|
1287
|
+
{config.newViz && <Confirm updateConfig={updateConfig} config={config} />}
|
|
1288
|
+
{!missingRequiredSections(config) && !config.newViz && (
|
|
1289
|
+
<VisualizationContent
|
|
1290
|
+
innerClassName={`type-${makeClassName(config.visualizationType)}`}
|
|
1291
|
+
innerProps={{ 'aria-label': handleChartAriaLabels(config), tabIndex: 0 }}
|
|
1292
|
+
bodyClassName={bodyClasses.join(' ')}
|
|
1293
|
+
bodyWrapClassName={isTp5Treatment ? 'cdc-callout d-flex flex-column tp5-chart-callout' : ''}
|
|
1294
|
+
filters={
|
|
1295
|
+
config.filters?.length > 0 && !externalFilters && config.visualizationType !== 'Spark Line' ? (
|
|
1296
|
+
<Filters
|
|
1297
|
+
config={config}
|
|
1298
|
+
setFilters={setFilters}
|
|
1299
|
+
excludedData={excludedData}
|
|
1300
|
+
dimensions={dimensions}
|
|
1301
|
+
interactionLabel={interactionLabel}
|
|
1302
|
+
/>
|
|
1303
|
+
) : undefined
|
|
1304
|
+
}
|
|
1305
|
+
bodySubtext={
|
|
1306
|
+
processedDescription && config.visualizationType !== 'Spark Line' ? (
|
|
1307
|
+
<div className={getChartSubTextClasses().join(' ')}>{parse(processedDescription)}</div>
|
|
1308
|
+
) : null
|
|
1309
|
+
}
|
|
1310
|
+
bodyFooter={
|
|
1311
|
+
<>
|
|
1426
1312
|
{isDashboard && config.table && config.table.show && config.table.showDataTableLink
|
|
1427
1313
|
? tableLink
|
|
1428
1314
|
: link && link}
|
|
1429
|
-
{/* Description */}
|
|
1430
|
-
|
|
1431
|
-
{processedDescription && config.visualizationType !== 'Spark Line' && (
|
|
1432
|
-
<div className={getChartSubTextClasses().join(' ')}>{parse(processedDescription)}</div>
|
|
1433
|
-
)}
|
|
1434
|
-
|
|
1435
|
-
{/* Data Table */}
|
|
1436
1315
|
{(config.xAxis.dataKey &&
|
|
1437
1316
|
config.table.show &&
|
|
1438
1317
|
config.visualizationType !== 'Spark Line' &&
|
|
@@ -1461,15 +1340,12 @@ const CdcChart: React.FC<CdcChartProps> = ({
|
|
|
1461
1340
|
|
|
1462
1341
|
return (
|
|
1463
1342
|
<DataTable
|
|
1464
|
-
|
|
1465
|
-
when the default sort changes while editing */
|
|
1466
|
-
key={dataTableDefaultSortBy}
|
|
1343
|
+
key={config.table?.defaultSort?.column || ''}
|
|
1467
1344
|
config={dataTableConfig}
|
|
1468
1345
|
rawData={dataTableRawData}
|
|
1469
1346
|
runtimeData={dataTableRuntimeData}
|
|
1470
1347
|
expandDataTable={config.table.expanded}
|
|
1471
1348
|
columns={dataTableColumns}
|
|
1472
|
-
defaultSortBy={dataTableDefaultSortBy}
|
|
1473
1349
|
displayGeoName={name => name}
|
|
1474
1350
|
applyLegendToRow={applyLegendToRow}
|
|
1475
1351
|
tableTitle={config.table.label}
|
|
@@ -1482,6 +1358,7 @@ const CdcChart: React.FC<CdcChartProps> = ({
|
|
|
1482
1358
|
showDownloadImgButton={config.table.showDownloadImgButton}
|
|
1483
1359
|
showDownloadPdfButton={config.table.showDownloadPdfButton}
|
|
1484
1360
|
includeContextInDownload={config.table?.includeContextInDownload}
|
|
1361
|
+
hasSubtextAbove={Boolean(processedDescription && config.visualizationType !== 'Spark Line')}
|
|
1485
1362
|
interactionLabel={interactionLabel}
|
|
1486
1363
|
/>
|
|
1487
1364
|
)
|
|
@@ -1513,21 +1390,177 @@ const CdcChart: React.FC<CdcChartProps> = ({
|
|
|
1513
1390
|
</div>
|
|
1514
1391
|
)}
|
|
1515
1392
|
{visibleAnnotations.length > 0 && <Annotation.Dropdown />}
|
|
1516
|
-
{/* show pdf or image button */}
|
|
1517
1393
|
{processedLegacyFootnotes && (
|
|
1518
1394
|
<section className='footnotes pt-2 mt-4'>{parse(processedLegacyFootnotes)}</section>
|
|
1519
1395
|
)}
|
|
1520
|
-
|
|
1396
|
+
</>
|
|
1397
|
+
}
|
|
1398
|
+
header={isTp5Treatment ? null : chartTitle}
|
|
1399
|
+
messageIsIntroText={config.visualizationType !== 'Spark Line' && !!processedIntroText}
|
|
1400
|
+
message={config.visualizationType !== 'Spark Line' && processedIntroText ? parse(processedIntroText) : null}
|
|
1401
|
+
footer={
|
|
1521
1402
|
<FootnotesStandAlone
|
|
1522
1403
|
config={configObj.footnotes}
|
|
1523
1404
|
filters={config.filters?.filter(f => f.filterFootnotes)}
|
|
1524
1405
|
markupVariables={config.markupVariables}
|
|
1525
1406
|
enableMarkupVariables={config.enableMarkupVariables}
|
|
1526
1407
|
data={config.data}
|
|
1408
|
+
dataMetadata={config.dataMetadata}
|
|
1527
1409
|
/>
|
|
1410
|
+
}
|
|
1411
|
+
>
|
|
1412
|
+
{isTp5Treatment && <img src={CalloutFlag} alt='' className='cdc-callout__flag' aria-hidden='true' />}
|
|
1413
|
+
{isTp5Treatment && chartTitle}
|
|
1414
|
+
<div className={getChartWrapperClasses().join(' ')}>
|
|
1415
|
+
<SkipTo skipId={handleChartTabbing(config, legendId)} skipMessage='Skip Over Chart Container' />
|
|
1416
|
+
{visibleAnnotations.length > 0 && (
|
|
1417
|
+
<SkipTo
|
|
1418
|
+
skipId={handleChartTabbing(config, legendId)}
|
|
1419
|
+
skipMessage={`Skip over annotations`}
|
|
1420
|
+
key={`skip-annotations`}
|
|
1421
|
+
/>
|
|
1422
|
+
)}
|
|
1423
|
+
<LegendWrapper>
|
|
1424
|
+
<div
|
|
1425
|
+
className={
|
|
1426
|
+
legend.hide || isLegendWrapViewport(currentViewport)
|
|
1427
|
+
? 'w-100'
|
|
1428
|
+
: legend.position === 'bottom' ||
|
|
1429
|
+
legend.position === 'top' ||
|
|
1430
|
+
visualizationType === 'Sankey' ||
|
|
1431
|
+
visualizationType === 'Spark Line'
|
|
1432
|
+
? 'w-100'
|
|
1433
|
+
: 'w-75'
|
|
1434
|
+
}
|
|
1435
|
+
>
|
|
1436
|
+
{/* Check if there is data to display */}
|
|
1437
|
+
{(!filteredData || filteredData.length === 0) && (
|
|
1438
|
+
<div className='no-data-message' style={{ padding: '2rem', textAlign: 'center', color: '#666' }}>
|
|
1439
|
+
{config.chartMessage?.noData || 'No Data Available'}
|
|
1440
|
+
</div>
|
|
1441
|
+
)}
|
|
1442
|
+
|
|
1443
|
+
{/* All charts with LinearChart */}
|
|
1444
|
+
{filteredData &&
|
|
1445
|
+
filteredData.length > 0 &&
|
|
1446
|
+
!['Spark Line', 'Line', 'Sankey', 'Pie', 'Radar'].includes(config.visualizationType) && (
|
|
1447
|
+
<div ref={parentRef} style={{ width: `100%` }}>
|
|
1448
|
+
<ParentSize>
|
|
1449
|
+
{parent => (
|
|
1450
|
+
<LinearChart ref={svgRef} parentWidth={parent.width} parentHeight={parent.height} />
|
|
1451
|
+
)}
|
|
1452
|
+
</ParentSize>
|
|
1453
|
+
</div>
|
|
1454
|
+
)}
|
|
1455
|
+
|
|
1456
|
+
{filteredData && filteredData.length > 0 && config.visualizationType === 'Pie' && (
|
|
1457
|
+
<ParentSize className='justify-content-center d-flex' style={{ width: `100%` }}>
|
|
1458
|
+
{parent => (
|
|
1459
|
+
<PieChart
|
|
1460
|
+
ref={svgRef}
|
|
1461
|
+
parentWidth={parent.width}
|
|
1462
|
+
parentHeight={parent.height}
|
|
1463
|
+
interactionLabel={interactionLabel}
|
|
1464
|
+
/>
|
|
1465
|
+
)}
|
|
1466
|
+
</ParentSize>
|
|
1467
|
+
)}
|
|
1468
|
+
{/* Radar Chart */}
|
|
1469
|
+
{filteredData && filteredData.length > 0 && config.visualizationType === 'Radar' && (
|
|
1470
|
+
<ParentSize className='justify-content-center d-flex' style={{ width: `100%` }}>
|
|
1471
|
+
{parent => (
|
|
1472
|
+
<RadarChart
|
|
1473
|
+
ref={svgRef}
|
|
1474
|
+
parentWidth={parent.width}
|
|
1475
|
+
parentHeight={parent.height}
|
|
1476
|
+
interactionLabel={interactionLabel}
|
|
1477
|
+
/>
|
|
1478
|
+
)}
|
|
1479
|
+
</ParentSize>
|
|
1480
|
+
)}
|
|
1481
|
+
{/* Line Chart */}
|
|
1482
|
+
{filteredData &&
|
|
1483
|
+
filteredData.length > 0 &&
|
|
1484
|
+
config.visualizationType === 'Line' &&
|
|
1485
|
+
(convertLineToBarGraph ? (
|
|
1486
|
+
<div ref={parentRef} style={{ width: `100%` }}>
|
|
1487
|
+
<ParentSize>
|
|
1488
|
+
{parent => (
|
|
1489
|
+
<LinearChart ref={svgRef} parentWidth={parent.width} parentHeight={parent.height} />
|
|
1490
|
+
)}
|
|
1491
|
+
</ParentSize>
|
|
1492
|
+
</div>
|
|
1493
|
+
) : (
|
|
1494
|
+
<div ref={parentRef} style={{ width: '100%' }}>
|
|
1495
|
+
<ParentSize>
|
|
1496
|
+
{parent => {
|
|
1497
|
+
const labelMargin = 120
|
|
1498
|
+
const widthReduction =
|
|
1499
|
+
config.showLineSeriesLabels && (config.legend.position !== 'right' || config.legend.hide)
|
|
1500
|
+
? labelMargin
|
|
1501
|
+
: 0
|
|
1502
|
+
return (
|
|
1503
|
+
<LinearChart
|
|
1504
|
+
ref={svgRef}
|
|
1505
|
+
parentWidth={parent.width - widthReduction}
|
|
1506
|
+
parentHeight={parent.height}
|
|
1507
|
+
/>
|
|
1508
|
+
)
|
|
1509
|
+
}}
|
|
1510
|
+
</ParentSize>
|
|
1511
|
+
</div>
|
|
1512
|
+
))}
|
|
1513
|
+
{/* Sparkline */}
|
|
1514
|
+
{config.visualizationType === 'Spark Line' && (
|
|
1515
|
+
<>
|
|
1516
|
+
<Filters
|
|
1517
|
+
config={config}
|
|
1518
|
+
setFilters={setFilters}
|
|
1519
|
+
excludedData={excludedData}
|
|
1520
|
+
dimensions={dimensions}
|
|
1521
|
+
interactionLabel={interactionLabel}
|
|
1522
|
+
/>
|
|
1523
|
+
{processedIntroText && (
|
|
1524
|
+
<section className='introText' style={{ padding: '0px 0 35px' }}>
|
|
1525
|
+
{parse(processedIntroText)}
|
|
1526
|
+
</section>
|
|
1527
|
+
)}
|
|
1528
|
+
<div style={{ height: `100px`, width: `100%`, ...sparkLineStyles }}>
|
|
1529
|
+
<ParentSize>{parent => <SparkLine width={parent.width} height={parent.height} />}</ParentSize>
|
|
1530
|
+
</div>
|
|
1531
|
+
{processedDescription && (
|
|
1532
|
+
<div className='subtext' style={{ padding: '35px 0 1.5rem' }}>
|
|
1533
|
+
{parse(processedDescription)}
|
|
1534
|
+
</div>
|
|
1535
|
+
)}
|
|
1536
|
+
</>
|
|
1537
|
+
)}
|
|
1538
|
+
{/* Sankey */}
|
|
1539
|
+
{config.visualizationType === 'Sankey' && (
|
|
1540
|
+
<ParentSize aria-hidden='true'>
|
|
1541
|
+
{parent => <SankeyChart runtime={config.runtime} width={parent.width} height={parent.height} />}
|
|
1542
|
+
</ParentSize>
|
|
1543
|
+
)}
|
|
1544
|
+
</div>
|
|
1545
|
+
{/* Legend */}
|
|
1546
|
+
{!config.legend.hide &&
|
|
1547
|
+
config.visualizationType !== 'Spark Line' &&
|
|
1548
|
+
config.visualizationType !== 'Sankey' &&
|
|
1549
|
+
!(config.visualizationType === 'Warming Stripes' && config.legend?.style === 'gradient') &&
|
|
1550
|
+
!(config.visualizationType === 'Warming Stripes' && config.smallMultiples?.mode) && (
|
|
1551
|
+
<Legend
|
|
1552
|
+
ref={legendRef}
|
|
1553
|
+
skipId={handleChartTabbing(config, legendId)}
|
|
1554
|
+
interactionLabel={interactionLabel}
|
|
1555
|
+
/>
|
|
1556
|
+
)}
|
|
1557
|
+
{config.visualizationType === 'Warming Stripes' &&
|
|
1558
|
+
config.legend?.style === 'gradient' &&
|
|
1559
|
+
!config.smallMultiples?.mode && <WarmingStripesGradientLegend />}
|
|
1560
|
+
</LegendWrapper>
|
|
1528
1561
|
</div>
|
|
1529
|
-
|
|
1530
|
-
|
|
1562
|
+
</VisualizationContent>
|
|
1563
|
+
)}
|
|
1531
1564
|
</>
|
|
1532
1565
|
)
|
|
1533
1566
|
}
|
|
@@ -1595,16 +1628,16 @@ const CdcChart: React.FC<CdcChartProps> = ({
|
|
|
1595
1628
|
return (
|
|
1596
1629
|
<ConfigContext.Provider value={contextValues}>
|
|
1597
1630
|
<ChartDispatchContext.Provider value={dispatch}>
|
|
1598
|
-
<
|
|
1631
|
+
<VisualizationContainer
|
|
1599
1632
|
config={config}
|
|
1600
1633
|
isEditor={isEditor}
|
|
1601
1634
|
currentViewport={currentViewport}
|
|
1602
1635
|
ref={outerContainerRef}
|
|
1603
1636
|
imageId={imageId}
|
|
1604
|
-
|
|
1637
|
+
editorPanel={!isLoading ? <EditorPanel datasets={datasets} /> : null}
|
|
1605
1638
|
>
|
|
1606
1639
|
{body}
|
|
1607
|
-
</
|
|
1640
|
+
</VisualizationContainer>
|
|
1608
1641
|
</ChartDispatchContext.Provider>
|
|
1609
1642
|
</ConfigContext.Provider>
|
|
1610
1643
|
)
|