@cdc/chart 4.26.1 → 4.26.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/CLAUDE.local.md +79 -0
- package/dist/{cdcchart-dgT_1dIT.es.js → cdcchart-DQ00cQCm.es.js} +1 -20
- package/dist/cdcchart.js +45357 -43655
- package/examples/default.json +378 -0
- package/examples/feature/__data__/horizon-chart-data.json +373 -0
- package/examples/feature/annotations/index.json +3 -6
- package/examples/feature/horizon/horizon-chart.json +395 -0
- package/examples/line-chart-states.json +1085 -0
- package/examples/private/123.json +694 -0
- package/examples/private/anchor-issue.json +4094 -0
- package/examples/private/backwards-slider.json +10430 -0
- package/examples/private/georgia.csv +160 -0
- package/examples/private/timeline-data.json +1 -0
- package/examples/private/timeline.json +389 -0
- package/examples/radar-chart-simple.json +133 -0
- package/examples/radar-chart.json +148 -0
- package/index.html +1 -31
- package/package.json +57 -59
- package/src/CdcChartComponent.tsx +99 -18
- package/src/_stories/Chart.Anchors.stories.tsx +10 -0
- package/src/_stories/Chart.BoxPlot.stories.tsx +7 -0
- package/src/_stories/Chart.CI.stories.tsx +13 -0
- package/src/_stories/Chart.Combo.stories.tsx +17 -0
- package/src/_stories/Chart.CustomColors.stories.tsx +4 -0
- package/src/_stories/Chart.DynamicSeries.stories.tsx +19 -0
- package/src/_stories/Chart.Filters.stories.tsx +4 -0
- package/src/_stories/Chart.Forecast.stories.tsx +4 -0
- package/src/_stories/Chart.HTMLInDataTable.stories.tsx +22 -0
- package/src/_stories/Chart.Legend.Gradient.stories.tsx +28 -0
- package/src/_stories/Chart.Patterns.stories.tsx +4 -0
- package/src/_stories/Chart.PreserveDecimals.stories.tsx +25 -0
- package/src/_stories/Chart.Regions.Categorical.stories.tsx +13 -0
- package/src/_stories/Chart.Regions.DateScale.stories.tsx +19 -0
- package/src/_stories/Chart.Regions.DateTimeScale.stories.tsx +25 -10
- package/src/_stories/Chart.ScatterPlot.stories.tsx +4 -0
- package/src/_stories/Chart.SmallMultiples.stories.tsx +16 -0
- package/src/_stories/Chart.stories.tsx +37 -0
- package/src/_stories/Chart.tooltip.stories.tsx +7 -0
- package/src/_stories/ChartAnnotation.stories.tsx +10 -0
- package/src/_stories/ChartAxisLabels.stories.tsx +4 -0
- package/src/_stories/ChartAxisTitles.stories.tsx +10 -0
- package/src/_stories/ChartBrush.Matrix.Continuous.stories.tsx +41 -0
- package/src/_stories/ChartBrush.Matrix.Date.stories.tsx +114 -0
- package/src/_stories/ChartBrush.Matrix.DateTime.stories.tsx +78 -0
- package/src/_stories/ChartBrush.stories.tsx +7 -0
- package/src/_stories/ChartEditor.stories.tsx +7 -0
- package/src/_stories/ChartLine.QuadrantAngles.stories.tsx +89 -0
- package/src/_stories/ChartLine.Suppression.stories.tsx +7 -0
- package/src/_stories/ChartLine.Symbols.stories.tsx +4 -0
- package/src/_stories/ChartPrefixSuffix.stories.tsx +46 -1
- package/src/_stories/TechAdoptionWithLinks.stories.tsx +7 -0
- package/src/_stories/_mock/brush_continuous.json +86 -0
- package/src/_stories/_mock/brush_date_large.json +176 -0
- package/src/_stories/_mock/line_chart_angle_near_zero_fall.json +195 -0
- package/src/_stories/_mock/line_chart_angle_near_zero_rise.json +195 -0
- package/src/_stories/_mock/line_chart_angle_q1_steep_upward.json +195 -0
- package/src/_stories/_mock/line_chart_angle_q2_gentle_downward.json +195 -0
- package/src/_stories/_mock/line_chart_angle_q3_steep_downward.json +195 -0
- package/src/_stories/_mock/line_chart_angle_q4_gentle_upward.json +195 -0
- package/src/_stories/_mock/line_chart_quadrant_angles.json +264 -0
- package/src/components/Annotations/components/AnnotationDraggable.styles.css +11 -17
- package/src/components/Annotations/components/AnnotationDraggable.tsx +240 -116
- package/src/components/Annotations/components/AnnotationDropdown.styles.css +1 -2
- package/src/components/Annotations/components/AnnotationDropdown.tsx +8 -12
- package/src/components/Annotations/components/AnnotationList.styles.css +4 -10
- package/src/components/Annotations/components/AnnotationList.tsx +5 -4
- package/src/components/Annotations/components/findNearestDatum.ts +75 -85
- package/src/components/Annotations/helpers/getVisibleAnnotations.ts +38 -0
- package/src/components/Axis/BottomAxis.tsx +270 -0
- package/src/components/Axis/LeftAxis.tsx +404 -0
- package/src/components/Axis/LeftAxisGridlines.tsx +77 -0
- package/src/components/Axis/PairedBarAxis.tsx +186 -0
- package/src/components/Axis/README.md +94 -0
- package/src/components/Axis/RightAxis.tsx +108 -0
- package/src/components/Axis/axis.constants.ts +21 -0
- package/src/components/Axis/index.ts +7 -0
- package/src/components/BarChart/components/BarChart.tsx +7 -1
- package/src/components/Brush/BrushSelector.tsx +154 -22
- package/src/components/Brush/MiniChartPreview.tsx +138 -21
- package/src/components/EditorPanel/EditorPanel.tsx +25 -11
- package/src/components/EditorPanel/components/Panels/Panel.Annotate.tsx +60 -22
- package/src/components/EditorPanel/components/Panels/Panel.General.tsx +81 -1
- package/src/components/EditorPanel/components/Panels/Panel.PatternSettings.tsx +1 -1
- package/src/components/EditorPanel/components/Panels/Panel.Radar.tsx +353 -0
- package/src/components/EditorPanel/components/Panels/Panel.Series.tsx +0 -1
- package/src/components/EditorPanel/components/Panels/Panel.Visual.tsx +21 -1
- package/src/components/EditorPanel/components/Panels/index.tsx +2 -0
- package/src/components/EditorPanel/useEditorPermissions.ts +55 -26
- package/src/components/HorizonChart/HorizonChart.tsx +131 -0
- package/src/components/HorizonChart/components/HorizonBand.tsx +160 -0
- package/src/components/HorizonChart/helpers/calculateHorizonBands.ts +27 -0
- package/src/components/HorizonChart/helpers/getHorizonLayerColors.ts +40 -0
- package/src/components/HorizonChart/index.tsx +3 -0
- package/src/components/Legend/Legend.Component.tsx +52 -4
- package/src/components/Legend/Legend.tsx +1 -1
- package/src/components/Legend/LegendValueRange.tsx +77 -0
- package/src/components/Legend/helpers/createFormatLabels.tsx +13 -0
- package/src/components/Legend/helpers/generateValueRanges.ts +92 -0
- package/src/components/LineChart/helpers/README.md +292 -0
- package/src/components/LineChart/helpers/labelPositioning.test.ts +245 -0
- package/src/components/LineChart/helpers/labelPositioning.ts +304 -0
- package/src/components/LineChart/index.tsx +44 -8
- package/src/components/LinearChart/README.md +109 -0
- package/src/components/LinearChart/VisualizationRenderer.tsx +267 -0
- package/src/components/LinearChart/linearChart.constants.ts +84 -0
- package/src/components/LinearChart/tests/LinearChart.test.tsx +201 -0
- package/src/components/LinearChart/tests/mockConfigContext.ts +129 -0
- package/src/components/LinearChart/utils/tickFormatting.ts +146 -0
- package/src/components/LinearChart.tsx +250 -1059
- package/src/components/PieChart/PieChart.tsx +1 -1
- package/src/components/RadarChart/RadarAxis.tsx +78 -0
- package/src/components/RadarChart/RadarChart.tsx +298 -0
- package/src/components/RadarChart/RadarGrid.tsx +64 -0
- package/src/components/RadarChart/RadarPolygon.tsx +91 -0
- package/src/components/RadarChart/helpers.ts +83 -0
- package/src/components/RadarChart/index.tsx +3 -0
- package/src/components/WarmingStripes/WarmingStripes.tsx +95 -25
- package/src/data/initial-state.js +14 -1
- package/src/helpers/getExcludedData.ts +4 -0
- package/src/helpers/handleChartAriaLabels.ts +19 -19
- package/src/helpers/handleLineType.ts +22 -18
- package/src/hooks/useProgrammaticTooltip.ts +23 -2
- package/src/hooks/useScales.ts +7 -0
- package/src/hooks/useTooltip.tsx +3 -0
- package/src/scss/main.scss +5 -0
- package/src/selectors/README.md +68 -0
- package/src/store/chart.reducer.ts +2 -0
- package/src/types/ChartConfig.ts +18 -0
- package/src/types/ChartContext.ts +1 -0
- package/src/types/Horizon.ts +64 -0
- package/preview.html +0 -1616
- package/src/components/Annotations/components/helpers/index.tsx +0 -46
package/package.json
CHANGED
|
@@ -1,84 +1,82 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@cdc/chart",
|
|
3
|
-
"version": "4.26.
|
|
3
|
+
"version": "4.26.2",
|
|
4
4
|
"description": "React component for visualizing tabular data in various types of charts",
|
|
5
|
-
"moduleName": "CdcChart",
|
|
6
|
-
"main": "dist/cdcchart",
|
|
7
|
-
"type": "module",
|
|
8
|
-
"scripts": {
|
|
9
|
-
"start": "vite --open",
|
|
10
|
-
"build": "vite build",
|
|
11
|
-
"preview": "vite preview",
|
|
12
|
-
"graph": "nx graph",
|
|
13
|
-
"prepublishOnly": "lerna run --scope @cdc/chart build",
|
|
14
|
-
"test": "vitest run --reporter verbose",
|
|
15
|
-
"test-watch": "vitest watch --reporter verbose",
|
|
16
|
-
"test-watch:ui": "vitest --ui"
|
|
17
|
-
},
|
|
18
|
-
"repository": {
|
|
19
|
-
"type": "git",
|
|
20
|
-
"url": "git+https://github.com/CDCgov/cdc-open-viz",
|
|
21
|
-
"directory": "packages/chart"
|
|
22
|
-
},
|
|
23
|
-
"author": "Matthew Pallansch <mpallansch@adittech.com>",
|
|
24
|
-
"bugs": {
|
|
25
|
-
"url": "https://github.com/CDCgov/cdc-open-viz/issues"
|
|
26
|
-
},
|
|
27
5
|
"license": "Apache-2.0",
|
|
6
|
+
"author": "Matthew Pallansch <mpallansch@adittech.com>",
|
|
7
|
+
"bugs": "https://github.com/CDCgov/cdc-open-viz/issues",
|
|
28
8
|
"dependencies": {
|
|
29
|
-
"@cdc/core": "^4.26.
|
|
9
|
+
"@cdc/core": "^4.26.2",
|
|
30
10
|
"@hello-pangea/dnd": "^16.2.0",
|
|
31
11
|
"@react-spring/web": "^9.7.5",
|
|
32
12
|
"@rollup/plugin-dsv": "^3.0.2",
|
|
33
|
-
"@visx/annotation": "^3.
|
|
34
|
-
"@visx/axis": "3.12.0",
|
|
13
|
+
"@visx/annotation": "^3.12.0",
|
|
14
|
+
"@visx/axis": "^3.12.0",
|
|
35
15
|
"@visx/brush": "^3.12.0",
|
|
36
|
-
"@visx/curve": "3.12.0",
|
|
16
|
+
"@visx/curve": "^3.12.0",
|
|
37
17
|
"@visx/drag": "^3.12.0",
|
|
38
|
-
"@visx/event": "3.12.0",
|
|
39
|
-
"@visx/glyph": "3.12.0",
|
|
40
|
-
"@visx/gradient": "3.12.0",
|
|
41
|
-
"@visx/legend": "3.12.0",
|
|
42
|
-
"@visx/marker": "3.12.0",
|
|
43
|
-
"@visx/mock-data": "3.12.0",
|
|
44
|
-
"@visx/pattern": "^3.
|
|
45
|
-
"@visx/responsive": "^
|
|
46
|
-
"@visx/scale": "3.12.0",
|
|
47
|
-
"@visx/shape": "3.12.0",
|
|
48
|
-
"@visx/stats": "3.12.0",
|
|
49
|
-
"@visx/text": "3.12.0",
|
|
50
|
-
"@visx/tooltip": "3.12.0",
|
|
51
|
-
"@vitejs/plugin-react": "^
|
|
52
|
-
"chroma-js": "3.1.2",
|
|
53
|
-
"d3-array": "3.2.4",
|
|
54
|
-
"d3-format": "^3.1.
|
|
18
|
+
"@visx/event": "^3.12.0",
|
|
19
|
+
"@visx/glyph": "^3.12.0",
|
|
20
|
+
"@visx/gradient": "^3.12.0",
|
|
21
|
+
"@visx/legend": "^3.12.0",
|
|
22
|
+
"@visx/marker": "^3.12.0",
|
|
23
|
+
"@visx/mock-data": "^3.12.0",
|
|
24
|
+
"@visx/pattern": "^3.12.0",
|
|
25
|
+
"@visx/responsive": "^3.12.0",
|
|
26
|
+
"@visx/scale": "^3.12.0",
|
|
27
|
+
"@visx/shape": "^3.12.0",
|
|
28
|
+
"@visx/stats": "^3.12.0",
|
|
29
|
+
"@visx/text": "^3.12.0",
|
|
30
|
+
"@visx/tooltip": "^3.12.0",
|
|
31
|
+
"@vitejs/plugin-react": "^5.1.2",
|
|
32
|
+
"chroma-js": "^3.1.2",
|
|
33
|
+
"d3-array": "^3.2.4",
|
|
34
|
+
"d3-format": "^3.1.2",
|
|
55
35
|
"d3-sankey": "^0.12.3",
|
|
56
|
-
"d3-time-format": "4.1.0",
|
|
57
|
-
"dompurify": "^3.1
|
|
58
|
-
"html-react-parser": "5.2.3",
|
|
36
|
+
"d3-time-format": "^4.1.0",
|
|
37
|
+
"dompurify": "^3.3.1",
|
|
38
|
+
"html-react-parser": "^5.2.3",
|
|
59
39
|
"js-base64": "^2.5.2",
|
|
60
|
-
"lodash": "^4.17.
|
|
61
|
-
"papaparse": "5.5.2",
|
|
40
|
+
"lodash": "^4.17.23",
|
|
41
|
+
"papaparse": "^5.5.2",
|
|
62
42
|
"react-accessible-accordion": "^5.0.1",
|
|
63
|
-
"react-icons": "5.5.0",
|
|
43
|
+
"react-icons": "^5.5.0",
|
|
64
44
|
"react-tooltip": "5.8.2-beta.3",
|
|
65
45
|
"resize-observer-polyfill": "^1.5.1",
|
|
66
46
|
"sass": "^1.89.2",
|
|
67
|
-
"use-debounce": "^
|
|
68
|
-
"vite": "^
|
|
47
|
+
"use-debounce": "^10.1.0",
|
|
48
|
+
"vite": "^7.3.1",
|
|
69
49
|
"vite-plugin-css-injected-by-js": "^2.4.0",
|
|
70
50
|
"vite-plugin-svgr": "^4.2.0",
|
|
71
|
-
"whatwg-fetch": "3.6.20"
|
|
72
|
-
},
|
|
73
|
-
"peerDependencies": {
|
|
74
|
-
"react": "^18.2.0",
|
|
75
|
-
"react-dom": "^18.2.0"
|
|
51
|
+
"whatwg-fetch": "^3.6.20"
|
|
76
52
|
},
|
|
77
|
-
"gitHead": "7e3b27098c4eb7a24bc9c3654ad53f88d6419f16",
|
|
78
53
|
"devDependencies": {
|
|
79
54
|
"@types/d3-array": "^3.2.1",
|
|
80
55
|
"@types/d3-format": "^3.0.4",
|
|
81
56
|
"@types/d3-sankey": "^0.12.4",
|
|
82
57
|
"@types/d3-time-format": "^4.0.3"
|
|
83
|
-
}
|
|
58
|
+
},
|
|
59
|
+
"gitHead": "be3413e8e1149abf94225108f86a7910f56e0616",
|
|
60
|
+
"main": "dist/cdcchart",
|
|
61
|
+
"moduleName": "CdcChart",
|
|
62
|
+
"peerDependencies": {
|
|
63
|
+
"react": "^18.2.0",
|
|
64
|
+
"react-dom": "^18.2.0"
|
|
65
|
+
},
|
|
66
|
+
"repository": {
|
|
67
|
+
"type": "git",
|
|
68
|
+
"url": "git+https://github.com/CDCgov/cdc-open-viz",
|
|
69
|
+
"directory": "packages/chart"
|
|
70
|
+
},
|
|
71
|
+
"scripts": {
|
|
72
|
+
"build": "vite build",
|
|
73
|
+
"graph": "nx graph",
|
|
74
|
+
"prepublishOnly": "lerna run --scope @cdc/chart build",
|
|
75
|
+
"preview": "vite preview",
|
|
76
|
+
"start": "vite --open",
|
|
77
|
+
"test": "vitest run --reporter verbose",
|
|
78
|
+
"test-watch": "vitest watch --reporter verbose",
|
|
79
|
+
"test-watch:ui": "vitest --ui"
|
|
80
|
+
},
|
|
81
|
+
"type": "module"
|
|
84
82
|
}
|
|
@@ -22,15 +22,16 @@ import { Runtime } from '@cdc/core/types/Runtime'
|
|
|
22
22
|
import { Label } from './types/Label'
|
|
23
23
|
// External Libraries
|
|
24
24
|
import ParentSize from '@visx/responsive/lib/components/ParentSize'
|
|
25
|
-
import { timeParse
|
|
25
|
+
import { timeParse } from 'd3-time-format'
|
|
26
26
|
import parse from 'html-react-parser'
|
|
27
27
|
import _ from 'lodash'
|
|
28
28
|
// Primary Components
|
|
29
29
|
import ConfigContext, { ChartDispatchContext } from './ConfigContext'
|
|
30
30
|
import PieChart from './components/PieChart'
|
|
31
|
+
import RadarChart from './components/RadarChart'
|
|
31
32
|
import SankeyChart from './components/Sankey'
|
|
32
33
|
import LinearChart from './components/LinearChart'
|
|
33
|
-
import { isDateScale } from '@cdc/core/helpers/cove/date'
|
|
34
|
+
import { isDateScale, formatDate as coreFormatDate } from '@cdc/core/helpers/cove/date'
|
|
34
35
|
|
|
35
36
|
import { twoColorPalette } from '@cdc/core/data/colorPalettes'
|
|
36
37
|
import { filterChartColorPalettes } from '@cdc/core/helpers/filterColorPalettes'
|
|
@@ -53,6 +54,7 @@ import Loading from '@cdc/core/components/Loading'
|
|
|
53
54
|
import Filters from '@cdc/core/components/Filters'
|
|
54
55
|
import MediaControls from '@cdc/core/components/MediaControls'
|
|
55
56
|
import Annotation from './components/Annotations'
|
|
57
|
+
import { getVisibleAnnotations } from './components/Annotations/helpers/getVisibleAnnotations'
|
|
56
58
|
// Core Helpers
|
|
57
59
|
import { DataTransform } from '@cdc/core/helpers/DataTransform'
|
|
58
60
|
import { isLegendWrapViewport } from '@cdc/core/helpers/viewports'
|
|
@@ -133,7 +135,8 @@ const CdcChart: React.FC<CdcChartProps> = ({
|
|
|
133
135
|
coveLoadedEventRan,
|
|
134
136
|
imageId,
|
|
135
137
|
seriesHighlight,
|
|
136
|
-
colorScale
|
|
138
|
+
colorScale,
|
|
139
|
+
brushData
|
|
137
140
|
} = state
|
|
138
141
|
const { description, visualizationType } = config
|
|
139
142
|
const svgRef = useRef(null)
|
|
@@ -263,8 +266,39 @@ const CdcChart: React.FC<CdcChartProps> = ({
|
|
|
263
266
|
delete defaultsWithoutPalette.general?.palette
|
|
264
267
|
}
|
|
265
268
|
|
|
269
|
+
// Override palette defaults for Line charts specifically
|
|
270
|
+
if (loadedConfig?.visualizationType === 'Line' && !loadedConfig?.general?.palette) {
|
|
271
|
+
if (!defaultsWithoutPalette.general) {
|
|
272
|
+
defaultsWithoutPalette.general = {}
|
|
273
|
+
}
|
|
274
|
+
defaultsWithoutPalette.general.palette = {
|
|
275
|
+
isReversed: false,
|
|
276
|
+
version: '2.0',
|
|
277
|
+
name: 'divergent_blue_cyan'
|
|
278
|
+
}
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
// Override palette defaults for Horizon Chart specifically
|
|
282
|
+
if (loadedConfig?.visualizationType === 'Horizon Chart' && !loadedConfig?.general?.palette) {
|
|
283
|
+
if (!defaultsWithoutPalette.general) {
|
|
284
|
+
defaultsWithoutPalette.general = {}
|
|
285
|
+
}
|
|
286
|
+
defaultsWithoutPalette.general.palette = {
|
|
287
|
+
isReversed: false,
|
|
288
|
+
version: '2.0',
|
|
289
|
+
name: 'sequential_blue'
|
|
290
|
+
}
|
|
291
|
+
}
|
|
292
|
+
|
|
266
293
|
let newConfig = { ...defaultsWithoutPalette, ...loadedConfig }
|
|
267
294
|
|
|
295
|
+
// Ensure Horizon Chart has enough palette colors for all layers
|
|
296
|
+
if (newConfig.visualizationType === 'Horizon Chart') {
|
|
297
|
+
const numLayers = newConfig.horizon?.numLayers ?? 4
|
|
298
|
+
const currentCount = _.get(newConfig, 'general.paletteColorCount', 4)
|
|
299
|
+
_.set(newConfig, 'general.paletteColorCount', Math.max(currentCount, numLayers))
|
|
300
|
+
}
|
|
301
|
+
|
|
268
302
|
_.defaultsDeep(newConfig, {
|
|
269
303
|
table: { showVertical: false }
|
|
270
304
|
})
|
|
@@ -385,6 +419,11 @@ const CdcChart: React.FC<CdcChartProps> = ({
|
|
|
385
419
|
newConfig.runtime.seriesKeys = _.uniq(pieData.map(d => d[newConfig.xAxis.dataKey]))
|
|
386
420
|
newConfig.runtime.seriesLabelsAll = newConfig.runtime.seriesKeys
|
|
387
421
|
newConfig.runtime.isPieChart = true // Flag to know when to use derived keys
|
|
422
|
+
} else if (newConfig.visualizationType === 'Radar') {
|
|
423
|
+
// Radar chart: seriesKeys are the entity names from xAxis.dataKey
|
|
424
|
+
const radarData = currentData.length > 0 ? currentData : newExcludedData
|
|
425
|
+
newConfig.runtime.seriesKeys = _.uniq(radarData.map(d => d[newConfig.xAxis.dataKey]))
|
|
426
|
+
newConfig.runtime.seriesLabelsAll = newConfig.runtime.seriesKeys
|
|
388
427
|
} else {
|
|
389
428
|
const finalData = dataOverride || newConfig.formattedData || newConfig.data
|
|
390
429
|
newConfig.runtime.seriesKeys = (newConfig.runtime.series || []).flatMap(series => {
|
|
@@ -458,6 +497,22 @@ const CdcChart: React.FC<CdcChartProps> = ({
|
|
|
458
497
|
newConfig.visualizationSubType = 'stacked'
|
|
459
498
|
}
|
|
460
499
|
|
|
500
|
+
if (newConfig.visualizationType === 'Horizon Chart' && newConfig.series) {
|
|
501
|
+
// Apply horizon defaults if not set
|
|
502
|
+
newConfig.horizon = {
|
|
503
|
+
numLayers: 4,
|
|
504
|
+
mode: 'offset', // Always offset for now, mirror hidden from UI
|
|
505
|
+
bandGap: 15,
|
|
506
|
+
bottomPadding: 15,
|
|
507
|
+
...newConfig.horizon
|
|
508
|
+
}
|
|
509
|
+
|
|
510
|
+
// Set categorical as default xAxis type for horizon charts if not already set
|
|
511
|
+
if (!newConfig.xAxis.type) {
|
|
512
|
+
newConfig.xAxis.type = 'categorical'
|
|
513
|
+
}
|
|
514
|
+
}
|
|
515
|
+
|
|
461
516
|
if (isHorizontalVariant) {
|
|
462
517
|
// For horizontal charts, axes are swapped, so processedYAxis goes to runtime.xAxis and vice versa
|
|
463
518
|
const horizontalXAxisSource = _.cloneDeep((newConfig.yAxis as any)?.yAxis || newConfig.yAxis)
|
|
@@ -618,7 +673,7 @@ const CdcChart: React.FC<CdcChartProps> = ({
|
|
|
618
673
|
if (newData) {
|
|
619
674
|
newConfig.data = newData
|
|
620
675
|
}
|
|
621
|
-
} else if (newConfig.formattedData) {
|
|
676
|
+
} else if (newConfig.formattedData && Array.isArray(newConfig.formattedData)) {
|
|
622
677
|
newConfig.data = newConfig.formattedData
|
|
623
678
|
} else if (newConfig.dataDescription) {
|
|
624
679
|
// For dashboard contexts, get data from datasets if config.data is undefined
|
|
@@ -654,7 +709,7 @@ const CdcChart: React.FC<CdcChartProps> = ({
|
|
|
654
709
|
updateConfig(preparedConfig, preppedData.data)
|
|
655
710
|
}
|
|
656
711
|
} catch (err) {
|
|
657
|
-
console.error('Could not Load!')
|
|
712
|
+
console.error('Could not Load!', err)
|
|
658
713
|
}
|
|
659
714
|
}
|
|
660
715
|
|
|
@@ -746,6 +801,16 @@ const CdcChart: React.FC<CdcChartProps> = ({
|
|
|
746
801
|
}
|
|
747
802
|
}, [config, stateData]) // eslint-disable-line
|
|
748
803
|
|
|
804
|
+
// Clear brush selection when brush slider is disabled
|
|
805
|
+
useEffect(() => {
|
|
806
|
+
const isBrushDisabled = !config?.xAxis?.brushActive
|
|
807
|
+
const hasBrushData = Array.isArray(brushData) && brushData.length > 0
|
|
808
|
+
|
|
809
|
+
if (isBrushDisabled && hasBrushData) {
|
|
810
|
+
dispatch({ type: 'SET_BRUSH_DATA', payload: [] })
|
|
811
|
+
}
|
|
812
|
+
}, [config?.xAxis?.brushActive, brushData])
|
|
813
|
+
|
|
749
814
|
// Updates runtime axis labels when config or data changes when using markup variables
|
|
750
815
|
useEffect(() => {
|
|
751
816
|
if (
|
|
@@ -844,15 +909,11 @@ const CdcChart: React.FC<CdcChartProps> = ({
|
|
|
844
909
|
const formatDate = (date, i, ticks) => {
|
|
845
910
|
const displayFormat =
|
|
846
911
|
config.runtime[section].dateDisplayFormat || config.runtime[section].dateParseFormat || '%Y-%m-%d'
|
|
847
|
-
let formattedDate =
|
|
848
|
-
// Handle the case where all months work with '%b.' except for May
|
|
849
|
-
if (displayFormat?.includes('%b.') && formattedDate.includes('May.')) {
|
|
850
|
-
formattedDate = formattedDate.replace(/May\./g, 'May')
|
|
851
|
-
}
|
|
912
|
+
let formattedDate = coreFormatDate(displayFormat, date)
|
|
852
913
|
// Show years only once
|
|
853
914
|
if (config.xAxis.showYearsOnce && displayFormat?.includes('%Y') && ticks) {
|
|
854
915
|
const prevDate = ticks[i - 1] ? ticks[i - 1].value : null
|
|
855
|
-
const prevFormattedDate =
|
|
916
|
+
const prevFormattedDate = coreFormatDate(displayFormat, prevDate)
|
|
856
917
|
const year = formattedDate.match(/\d{4}/)
|
|
857
918
|
const prevYear = prevFormattedDate.match(/\d{4}/)
|
|
858
919
|
if (year && prevYear && year[0] === prevYear[0]) {
|
|
@@ -863,7 +924,7 @@ const CdcChart: React.FC<CdcChartProps> = ({
|
|
|
863
924
|
}
|
|
864
925
|
|
|
865
926
|
const formatTooltipsDate = date => {
|
|
866
|
-
return
|
|
927
|
+
return coreFormatDate(config.tooltips.dateDisplayFormat, date)
|
|
867
928
|
}
|
|
868
929
|
|
|
869
930
|
// Format numeric data based on settings in config OR from passed in settings for Additional Columns
|
|
@@ -1118,6 +1179,12 @@ const CdcChart: React.FC<CdcChartProps> = ({
|
|
|
1118
1179
|
return tableConfig
|
|
1119
1180
|
}
|
|
1120
1181
|
|
|
1182
|
+
// Transform and clean data for chart rendering
|
|
1183
|
+
const transformedData = getTransformedData({ brushData: state.brushData, filteredData, excludedData, clean })
|
|
1184
|
+
|
|
1185
|
+
// Filter annotations to only those visible in current data view
|
|
1186
|
+
const visibleAnnotations = getVisibleAnnotations(config.annotations, transformedData, config.xAxis?.dataKey)
|
|
1187
|
+
|
|
1121
1188
|
// Prevent render if loading
|
|
1122
1189
|
let body = <Loading />
|
|
1123
1190
|
|
|
@@ -1209,7 +1276,7 @@ const CdcChart: React.FC<CdcChartProps> = ({
|
|
|
1209
1276
|
/>
|
|
1210
1277
|
)}
|
|
1211
1278
|
<SkipTo skipId={handleChartTabbing(config, legendId)} skipMessage='Skip Over Chart Container' />
|
|
1212
|
-
{
|
|
1279
|
+
{visibleAnnotations.length > 0 && (
|
|
1213
1280
|
<SkipTo
|
|
1214
1281
|
skipId={handleChartTabbing(config, legendId)}
|
|
1215
1282
|
skipMessage={`Skip over annotations`}
|
|
@@ -1239,7 +1306,7 @@ const CdcChart: React.FC<CdcChartProps> = ({
|
|
|
1239
1306
|
{/* All charts with LinearChart */}
|
|
1240
1307
|
{filteredData &&
|
|
1241
1308
|
filteredData.length > 0 &&
|
|
1242
|
-
!['Spark Line', 'Line', 'Sankey', 'Pie', '
|
|
1309
|
+
!['Spark Line', 'Line', 'Sankey', 'Pie', 'Radar'].includes(config.visualizationType) && (
|
|
1243
1310
|
<div ref={parentRef} style={{ width: `100%` }}>
|
|
1244
1311
|
<ParentSize>
|
|
1245
1312
|
{parent => (
|
|
@@ -1261,6 +1328,19 @@ const CdcChart: React.FC<CdcChartProps> = ({
|
|
|
1261
1328
|
)}
|
|
1262
1329
|
</ParentSize>
|
|
1263
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
|
+
)}
|
|
1264
1344
|
{/* Line Chart */}
|
|
1265
1345
|
{filteredData &&
|
|
1266
1346
|
filteredData.length > 0 &&
|
|
@@ -1382,7 +1462,7 @@ const CdcChart: React.FC<CdcChartProps> = ({
|
|
|
1382
1462
|
return (
|
|
1383
1463
|
<DataTable
|
|
1384
1464
|
/* changing the "key" will force the table to re-render
|
|
1385
|
-
|
|
1465
|
+
when the default sort changes while editing */
|
|
1386
1466
|
key={dataTableDefaultSortBy}
|
|
1387
1467
|
config={dataTableConfig}
|
|
1388
1468
|
rawData={dataTableRawData}
|
|
@@ -1432,7 +1512,7 @@ const CdcChart: React.FC<CdcChartProps> = ({
|
|
|
1432
1512
|
</MediaControls.Section>
|
|
1433
1513
|
</div>
|
|
1434
1514
|
)}
|
|
1435
|
-
{
|
|
1515
|
+
{visibleAnnotations.length > 0 && <Annotation.Dropdown />}
|
|
1436
1516
|
{/* show pdf or image button */}
|
|
1437
1517
|
{processedLegacyFootnotes && (
|
|
1438
1518
|
<section className='footnotes pt-2 mt-4'>{parse(processedLegacyFootnotes)}</section>
|
|
@@ -1505,10 +1585,11 @@ const CdcChart: React.FC<CdcChartProps> = ({
|
|
|
1505
1585
|
setSharedFilterValue,
|
|
1506
1586
|
svgRef,
|
|
1507
1587
|
tableData: filteredData || excludedData,
|
|
1508
|
-
transformedData
|
|
1588
|
+
transformedData,
|
|
1509
1589
|
twoColorPalette,
|
|
1510
1590
|
unfilteredData: stateData,
|
|
1511
|
-
updateConfig
|
|
1591
|
+
updateConfig,
|
|
1592
|
+
visibleAnnotations
|
|
1512
1593
|
}
|
|
1513
1594
|
|
|
1514
1595
|
return (
|
|
@@ -3,6 +3,7 @@ import { Meta, Story } from '@storybook/react-vite'
|
|
|
3
3
|
import Chart from '../CdcChartComponent'
|
|
4
4
|
import exampleComboBarNonNumeric from './../../examples/feature/tests-date-exclusions/date-exclusions-config.json'
|
|
5
5
|
import { editConfigKeys } from '@cdc/core/helpers/configHelpers'
|
|
6
|
+
import { assertVisualizationRendered } from '@cdc/core/helpers/testing'
|
|
6
7
|
|
|
7
8
|
export default {
|
|
8
9
|
title: 'Components/Templates/Chart/Anchors',
|
|
@@ -15,17 +16,26 @@ export const Anchor_DateLinear: Story = {
|
|
|
15
16
|
args: {
|
|
16
17
|
config: exampleComboBarNonNumeric,
|
|
17
18
|
isEditor: false
|
|
19
|
+
},
|
|
20
|
+
play: async ({ canvasElement }) => {
|
|
21
|
+
await assertVisualizationRendered(canvasElement)
|
|
18
22
|
}
|
|
19
23
|
}
|
|
20
24
|
|
|
21
25
|
export const Anchor_Categorical: Story = {
|
|
22
26
|
args: {
|
|
23
27
|
config: editConfigKeys(exampleComboBarNonNumeric, [{ path: ['xAxis', 'type'], value: 'categorical' }])
|
|
28
|
+
},
|
|
29
|
+
play: async ({ canvasElement }) => {
|
|
30
|
+
await assertVisualizationRendered(canvasElement)
|
|
24
31
|
}
|
|
25
32
|
}
|
|
26
33
|
|
|
27
34
|
export const Anchor_Date_Time: Story = {
|
|
28
35
|
args: {
|
|
29
36
|
config: editConfigKeys(exampleComboBarNonNumeric, [{ path: ['xAxis', 'type'], value: 'date-time' }])
|
|
37
|
+
},
|
|
38
|
+
play: async ({ canvasElement }) => {
|
|
39
|
+
await assertVisualizationRendered(canvasElement)
|
|
30
40
|
}
|
|
31
41
|
}
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import type { Meta, StoryObj } from '@storybook/react-vite'
|
|
2
2
|
import boxPlotConfig from './_mock/boxplot_multiseries.json'
|
|
3
3
|
import Chart from '../CdcChartComponent'
|
|
4
|
+
import { assertVisualizationRendered } from '@cdc/core/helpers/testing'
|
|
4
5
|
|
|
5
6
|
const meta: Meta<typeof Chart> = {
|
|
6
7
|
title: 'Components/Templates/Chart/Box Plot',
|
|
@@ -17,6 +18,9 @@ export const BoxPlot_Vertical: Story = {
|
|
|
17
18
|
title: 'Vertical Multiseries Box Plot',
|
|
18
19
|
isEditor: false
|
|
19
20
|
}
|
|
21
|
+
},
|
|
22
|
+
play: async ({ canvasElement }) => {
|
|
23
|
+
await assertVisualizationRendered(canvasElement)
|
|
20
24
|
}
|
|
21
25
|
}
|
|
22
26
|
|
|
@@ -29,6 +33,9 @@ export const BoxPlot_Horizontal: Story = {
|
|
|
29
33
|
yAxis: { ...boxPlotConfig.yAxis, labelPlacement: 'Above Bar' }
|
|
30
34
|
},
|
|
31
35
|
isEditor: false
|
|
36
|
+
},
|
|
37
|
+
play: async ({ canvasElement }) => {
|
|
38
|
+
await assertVisualizationRendered(canvasElement)
|
|
32
39
|
}
|
|
33
40
|
}
|
|
34
41
|
|
|
@@ -4,6 +4,7 @@ import lineChartDynamicCI from './_mock/line_chart_dynamic_ci.json'
|
|
|
4
4
|
import lineChartNonDynamicCI from './_mock/line_chart_non_dynamic_ci.json'
|
|
5
5
|
|
|
6
6
|
import Chart from '../CdcChartComponent'
|
|
7
|
+
import { assertVisualizationRendered } from '@cdc/core/helpers/testing'
|
|
7
8
|
|
|
8
9
|
const meta: Meta<typeof Chart> = {
|
|
9
10
|
title: 'Components/Templates/Chart/Confidence Intervals',
|
|
@@ -14,6 +15,9 @@ export const bar_chart_with_labels: Story = {
|
|
|
14
15
|
args: {
|
|
15
16
|
config: barChartCiLabels,
|
|
16
17
|
isEditor: false
|
|
18
|
+
},
|
|
19
|
+
play: async ({ canvasElement }) => {
|
|
20
|
+
await assertVisualizationRendered(canvasElement)
|
|
17
21
|
}
|
|
18
22
|
}
|
|
19
23
|
export const bar_chart_horizontal_labels: Story = {
|
|
@@ -24,6 +28,9 @@ export const bar_chart_horizontal_labels: Story = {
|
|
|
24
28
|
yAxis: { ...barChartCiLabels.yAxis, displayNumbersOnBar: true }
|
|
25
29
|
},
|
|
26
30
|
isEditor: false
|
|
31
|
+
},
|
|
32
|
+
play: async ({ canvasElement }) => {
|
|
33
|
+
await assertVisualizationRendered(canvasElement)
|
|
27
34
|
}
|
|
28
35
|
}
|
|
29
36
|
|
|
@@ -31,6 +38,9 @@ export const line_Chart_Dynamic_Confidence_Intervals: Story = {
|
|
|
31
38
|
args: {
|
|
32
39
|
config: lineChartDynamicCI,
|
|
33
40
|
isEditor: false
|
|
41
|
+
},
|
|
42
|
+
play: async ({ canvasElement }) => {
|
|
43
|
+
await assertVisualizationRendered(canvasElement)
|
|
34
44
|
}
|
|
35
45
|
}
|
|
36
46
|
|
|
@@ -38,6 +48,9 @@ export const line_Chart_Non_Dynamic_Confidence_Intervals: Story = {
|
|
|
38
48
|
args: {
|
|
39
49
|
config: lineChartNonDynamicCI,
|
|
40
50
|
isEditor: false
|
|
51
|
+
},
|
|
52
|
+
play: async ({ canvasElement }) => {
|
|
53
|
+
await assertVisualizationRendered(canvasElement)
|
|
41
54
|
}
|
|
42
55
|
}
|
|
43
56
|
export default meta
|
|
@@ -2,6 +2,8 @@
|
|
|
2
2
|
import React from 'react'
|
|
3
3
|
import { Meta, Story } from '@storybook/react'
|
|
4
4
|
import CdcChart from '@cdc/chart/src/CdcChart'
|
|
5
|
+
import { editConfigKeys } from '@cdc/core/helpers/configHelpers'
|
|
6
|
+
import { assertVisualizationRendered } from '@cdc/core/helpers/testing'
|
|
5
7
|
import comboChartConfig from './_mock/combo.json'
|
|
6
8
|
|
|
7
9
|
export default {
|
|
@@ -16,3 +18,18 @@ ComboChart.args = {
|
|
|
16
18
|
config: comboChartConfig,
|
|
17
19
|
isEditor: true
|
|
18
20
|
}
|
|
21
|
+
ComboChart.play = async ({ canvasElement }) => {
|
|
22
|
+
await assertVisualizationRendered(canvasElement)
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
export const ComboChartWithBrush = Template.bind({})
|
|
26
|
+
ComboChartWithBrush.args = {
|
|
27
|
+
config: editConfigKeys(comboChartConfig, [
|
|
28
|
+
{ path: ['xAxis', 'brushActive'], value: true },
|
|
29
|
+
{ path: ['xAxis', 'brushDefaultRecentDateCount'], value: 12 }
|
|
30
|
+
]),
|
|
31
|
+
isEditor: true
|
|
32
|
+
}
|
|
33
|
+
ComboChartWithBrush.play = async ({ canvasElement }) => {
|
|
34
|
+
await assertVisualizationRendered(canvasElement)
|
|
35
|
+
}
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import type { Meta, StoryObj } from '@storybook/react-vite'
|
|
2
2
|
import Chart from '../CdcChartComponent'
|
|
3
3
|
import scatterPlotCustomColorConfig from './_mock/scatterplot_mock.json'
|
|
4
|
+
import { assertVisualizationRendered } from '@cdc/core/helpers/testing'
|
|
4
5
|
|
|
5
6
|
const meta: Meta<typeof Chart> = {
|
|
6
7
|
title: 'Components/Templates/Chart/Custom Colors',
|
|
@@ -13,6 +14,9 @@ export const ScatterPlot: Story = {
|
|
|
13
14
|
args: {
|
|
14
15
|
config: scatterPlotCustomColorConfig,
|
|
15
16
|
isEditor: false
|
|
17
|
+
},
|
|
18
|
+
play: async ({ canvasElement }) => {
|
|
19
|
+
await assertVisualizationRendered(canvasElement)
|
|
16
20
|
}
|
|
17
21
|
}
|
|
18
22
|
|
|
@@ -3,6 +3,7 @@ import DynamicSeriesBarConfig from './_mock/dynamic_series_bar_config.json'
|
|
|
3
3
|
import DynamicSeriesSuppression from './_mock/dynamic_series_suppression_mock.json'
|
|
4
4
|
import { Meta, StoryObj } from '@storybook/react-vite'
|
|
5
5
|
import Chart from '../CdcChartComponent'
|
|
6
|
+
import { assertVisualizationRendered } from '@cdc/core/helpers/testing'
|
|
6
7
|
|
|
7
8
|
const meta: Meta<typeof Chart> = {
|
|
8
9
|
title: 'Components/Templates/Chart/Dynamic Series',
|
|
@@ -23,6 +24,9 @@ export const LineShowPoints: Story = {
|
|
|
23
24
|
lineDatapointStyle: 'always show'
|
|
24
25
|
},
|
|
25
26
|
isEditor: false
|
|
27
|
+
},
|
|
28
|
+
play: async ({ canvasElement }) => {
|
|
29
|
+
await assertVisualizationRendered(canvasElement)
|
|
26
30
|
}
|
|
27
31
|
}
|
|
28
32
|
|
|
@@ -30,6 +34,9 @@ export const LineSuppression: Story = {
|
|
|
30
34
|
args: {
|
|
31
35
|
config: DynamicSeriesSuppression,
|
|
32
36
|
isEditor: false
|
|
37
|
+
},
|
|
38
|
+
play: async ({ canvasElement }) => {
|
|
39
|
+
await assertVisualizationRendered(canvasElement)
|
|
33
40
|
}
|
|
34
41
|
}
|
|
35
42
|
|
|
@@ -37,6 +44,9 @@ export const LineHoverPoints: Story = {
|
|
|
37
44
|
args: {
|
|
38
45
|
config: DynamicSeriesConfig,
|
|
39
46
|
isEditor: false
|
|
47
|
+
},
|
|
48
|
+
play: async ({ canvasElement }) => {
|
|
49
|
+
await assertVisualizationRendered(canvasElement)
|
|
40
50
|
}
|
|
41
51
|
}
|
|
42
52
|
|
|
@@ -44,6 +54,9 @@ export const Bar_Vertical: Story = {
|
|
|
44
54
|
args: {
|
|
45
55
|
config: DynamicSeriesBarConfig,
|
|
46
56
|
isEditor: true
|
|
57
|
+
},
|
|
58
|
+
play: async ({ canvasElement }) => {
|
|
59
|
+
await assertVisualizationRendered(canvasElement)
|
|
47
60
|
}
|
|
48
61
|
}
|
|
49
62
|
|
|
@@ -51,6 +64,9 @@ export const Bar_Horizontal: Story = {
|
|
|
51
64
|
args: {
|
|
52
65
|
config: { ...DynamicSeriesBarConfig, orientation: 'horizontal' },
|
|
53
66
|
isEditor: false
|
|
67
|
+
},
|
|
68
|
+
play: async ({ canvasElement }) => {
|
|
69
|
+
await assertVisualizationRendered(canvasElement)
|
|
54
70
|
}
|
|
55
71
|
}
|
|
56
72
|
|
|
@@ -62,6 +78,9 @@ export const Legend_Order: Story = {
|
|
|
62
78
|
legend: { ...DynamicSeriesBarConfig, order: 'desc' }
|
|
63
79
|
},
|
|
64
80
|
isEditor: false
|
|
81
|
+
},
|
|
82
|
+
play: async ({ canvasElement }) => {
|
|
83
|
+
await assertVisualizationRendered(canvasElement)
|
|
65
84
|
}
|
|
66
85
|
}
|
|
67
86
|
|
|
@@ -2,6 +2,7 @@ import type { Meta, StoryObj } from '@storybook/react-vite'
|
|
|
2
2
|
import Chart from '../CdcChartComponent'
|
|
3
3
|
import { editConfigKeys } from '@cdc/core/helpers/configHelpers'
|
|
4
4
|
import scatterPlotDownloadImage from './_mock/scatterplot-image-download.json'
|
|
5
|
+
import { assertVisualizationRendered } from '@cdc/core/helpers/testing'
|
|
5
6
|
|
|
6
7
|
const meta: Meta<typeof Chart> = {
|
|
7
8
|
title: 'Components/Templates/Chart/Filters',
|
|
@@ -13,6 +14,9 @@ type Story = StoryObj<typeof Chart>
|
|
|
13
14
|
export const Tab_Simple: Story = {
|
|
14
15
|
args: {
|
|
15
16
|
config: editConfigKeys(scatterPlotDownloadImage, [{ path: ['filters', '0', 'filterStyle'], value: 'tab-simple' }])
|
|
17
|
+
},
|
|
18
|
+
play: async ({ canvasElement }) => {
|
|
19
|
+
await assertVisualizationRendered(canvasElement)
|
|
16
20
|
}
|
|
17
21
|
}
|
|
18
22
|
|
|
@@ -2,6 +2,7 @@ import type { Meta, StoryObj } from '@storybook/react-vite'
|
|
|
2
2
|
|
|
3
3
|
import Chart from '../CdcChart'
|
|
4
4
|
import forecastComboWithGaps from './_mock/forecast_combo_with_gaps.json'
|
|
5
|
+
import { assertVisualizationRendered } from '@cdc/core/helpers/testing'
|
|
5
6
|
|
|
6
7
|
const meta: Meta<typeof Chart> = {
|
|
7
8
|
title: 'Components/Templates/Chart/Forecast Chart',
|
|
@@ -30,6 +31,9 @@ export const Forecast_Combo_With_Gaps: Story = {
|
|
|
30
31
|
'A combo chart with forecast confidence intervals that demonstrates proper gap handling. The forecast areas and lines are split into separate segments when gaps are detected, preventing misleading connections across missing data periods. Invalid data points (NA values) are automatically filtered out.'
|
|
31
32
|
}
|
|
32
33
|
}
|
|
34
|
+
},
|
|
35
|
+
play: async ({ canvasElement }) => {
|
|
36
|
+
await assertVisualizationRendered(canvasElement)
|
|
33
37
|
}
|
|
34
38
|
}
|
|
35
39
|
|