@cdc/chart 4.24.2 → 4.24.4
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 +47933 -36918
- package/examples/chart-regression-1.json +378 -0
- package/examples/chart-regression-2.json +2360 -0
- package/examples/feature/filters/url-filter.json +1076 -0
- package/examples/feature/line/line-chart.json +362 -37
- package/examples/feature/regions/index.json +50 -4
- package/examples/feature/sankey/sankey-example-data.json +1364 -0
- package/examples/feature/sankey/sankey_chart_data.csv +20 -0
- package/examples/gallery/bar-chart-vertical/vertical-bar-chart-stacked.json +306 -19
- package/examples/region-issue.json +2065 -0
- package/examples/sparkline.json +868 -0
- package/examples/test.json +5409 -0
- package/index.html +130 -123
- package/package.json +4 -2
- package/src/CdcChart.tsx +178 -94
- package/src/_stories/ChartEditor.stories.tsx +14 -3
- package/src/_stories/_mock/url_filter.json +1076 -0
- package/src/components/AreaChart/components/AreaChart.Stacked.jsx +2 -1
- package/src/components/AreaChart/components/AreaChart.jsx +2 -1
- package/src/components/BarChart/components/BarChart.Horizontal.tsx +46 -63
- package/src/components/BarChart/components/BarChart.StackedHorizontal.tsx +36 -56
- package/src/components/BarChart/components/BarChart.StackedVertical.tsx +32 -39
- package/src/components/BarChart/components/BarChart.Vertical.tsx +44 -59
- package/src/components/BoxPlot/BoxPlot.jsx +2 -1
- package/src/components/DeviationBar.jsx +3 -3
- package/src/components/EditorPanel/EditorPanel.tsx +1684 -1564
- package/src/components/EditorPanel/components/Panels/Panel.Regions.tsx +1 -1
- package/src/components/EditorPanel/components/Panels/Panel.Sankey.tsx +107 -0
- package/src/components/EditorPanel/components/Panels/Panel.Series.tsx +48 -4
- package/src/components/EditorPanel/components/Panels/Panel.Visual.tsx +41 -0
- package/src/components/EditorPanel/components/Panels/index.tsx +9 -7
- package/src/components/EditorPanel/components/panels.scss +11 -0
- package/src/components/EditorPanel/editor-panel.scss +0 -724
- package/src/components/EditorPanel/useEditorPermissions.js +40 -14
- package/src/components/Legend/Legend.Component.tsx +43 -63
- package/src/components/Legend/Legend.tsx +8 -4
- package/src/components/LineChart/LineChartProps.ts +1 -0
- package/src/components/LineChart/helpers.ts +2 -2
- package/src/components/LineChart/index.tsx +7 -7
- package/src/components/LinearChart.jsx +11 -31
- package/src/components/PairedBarChart.jsx +6 -10
- package/src/components/PieChart/PieChart.tsx +3 -3
- package/src/components/Regions/components/Regions.tsx +120 -78
- package/src/components/Sankey/index.tsx +434 -0
- package/src/components/Sankey/sankey.scss +153 -0
- package/src/components/Sankey/types/index.ts +16 -0
- package/src/components/ScatterPlot/ScatterPlot.jsx +1 -0
- package/src/components/Sparkline/{SparkLine.jsx → components/SparkLine.tsx} +14 -30
- package/src/components/Sparkline/index.scss +3 -0
- package/src/components/Sparkline/index.tsx +1 -1
- package/src/components/ZoomBrush.tsx +2 -1
- package/src/data/initial-state.js +46 -2
- package/src/helpers/computeMarginBottom.ts +2 -1
- package/src/helpers/tests/computeMarginBottom.test.ts +2 -1
- package/src/hooks/useBarChart.js +5 -2
- package/src/hooks/useScales.ts +47 -18
- package/src/hooks/useTooltip.tsx +9 -8
- package/src/scss/main.scss +33 -29
- package/src/types/ChartConfig.ts +32 -14
- package/src/types/ChartContext.ts +7 -0
|
@@ -14,18 +14,19 @@ export const useEditorPermissions = () => {
|
|
|
14
14
|
'Combo',
|
|
15
15
|
'Deviation Bar',
|
|
16
16
|
'Forecasting',
|
|
17
|
-
'Forest Plot',
|
|
17
|
+
// 'Forest Plot',
|
|
18
18
|
'Line',
|
|
19
19
|
'Paired Bar',
|
|
20
20
|
'Pie',
|
|
21
21
|
'Scatter Plot',
|
|
22
|
-
'Spark Line'
|
|
22
|
+
'Spark Line',
|
|
23
|
+
'Sankey'
|
|
23
24
|
]
|
|
24
25
|
|
|
25
26
|
const headerColors = ['theme-blue', 'theme-purple', 'theme-brown', 'theme-teal', 'theme-pink', 'theme-orange', 'theme-slate', 'theme-indigo', 'theme-cyan', 'theme-green', 'theme-amber']
|
|
26
27
|
|
|
27
28
|
const visSupportsDateCategoryAxis = () => {
|
|
28
|
-
const disabledCharts = ['Forest Plot']
|
|
29
|
+
const disabledCharts = ['Forest Plot', 'Sankey']
|
|
29
30
|
if (disabledCharts.includes(visualizationType)) return false
|
|
30
31
|
return true
|
|
31
32
|
}
|
|
@@ -43,13 +44,13 @@ export const useEditorPermissions = () => {
|
|
|
43
44
|
}
|
|
44
45
|
|
|
45
46
|
const visHasLabelOnData = () => {
|
|
46
|
-
const disabledCharts = ['Area Chart', 'Box Plot', 'Pie', 'Scatter Plot', 'Forest Plot', 'Spark Line']
|
|
47
|
+
const disabledCharts = ['Area Chart', 'Box Plot', 'Pie', 'Scatter Plot', 'Forest Plot', 'Spark Line', 'Sankey']
|
|
47
48
|
if (disabledCharts.includes(visualizationType)) return false
|
|
48
49
|
return true
|
|
49
50
|
}
|
|
50
51
|
|
|
51
52
|
const visCanAnimate = () => {
|
|
52
|
-
const disabledCharts = ['Area Chart', 'Scatter Plot', 'Box Plot', 'Forest Plot', 'Spark Line']
|
|
53
|
+
const disabledCharts = ['Area Chart', 'Scatter Plot', 'Box Plot', 'Forest Plot', 'Spark Line', 'Sankey']
|
|
53
54
|
if (disabledCharts.includes(visualizationType)) return false
|
|
54
55
|
return true
|
|
55
56
|
}
|
|
@@ -62,6 +63,8 @@ export const useEditorPermissions = () => {
|
|
|
62
63
|
return false
|
|
63
64
|
case 'Spark Line':
|
|
64
65
|
return false
|
|
66
|
+
case 'Sankey':
|
|
67
|
+
return false
|
|
65
68
|
default:
|
|
66
69
|
return true
|
|
67
70
|
}
|
|
@@ -107,6 +110,8 @@ export const useEditorPermissions = () => {
|
|
|
107
110
|
|
|
108
111
|
const visHasDataCutoff = () => {
|
|
109
112
|
switch (visualizationType) {
|
|
113
|
+
case 'Sankey':
|
|
114
|
+
return false
|
|
110
115
|
case 'Forest Plot':
|
|
111
116
|
return false
|
|
112
117
|
case 'Box Plot':
|
|
@@ -120,8 +125,13 @@ export const useEditorPermissions = () => {
|
|
|
120
125
|
}
|
|
121
126
|
}
|
|
122
127
|
|
|
128
|
+
const visHasSelectableLegendValues = !['Box Plot', 'Forest Plot', 'Spark Line'].includes(visualizationType)
|
|
129
|
+
const visHasLegendAxisAlign = () => {
|
|
130
|
+
return visualizationType === 'Bar' && visualizationSubType === 'stacked' && config.legend.behavior === 'isolate'
|
|
131
|
+
}
|
|
132
|
+
|
|
123
133
|
const visSupportsTooltipOpacity = () => {
|
|
124
|
-
const disabledCharts = ['Spark Line']
|
|
134
|
+
const disabledCharts = ['Spark Line', 'Sankey']
|
|
125
135
|
if (disabledCharts.includes(visualizationType)) return false
|
|
126
136
|
return true
|
|
127
137
|
}
|
|
@@ -133,19 +143,19 @@ export const useEditorPermissions = () => {
|
|
|
133
143
|
}
|
|
134
144
|
|
|
135
145
|
const visSupportsSequentialPallete = () => {
|
|
136
|
-
const disabledCharts = ['Paired Bar', 'Deviation Bar', 'Forest Plot', 'Forecasting']
|
|
146
|
+
const disabledCharts = ['Paired Bar', 'Deviation Bar', 'Forest Plot', 'Forecasting', 'Sankey']
|
|
137
147
|
if (disabledCharts.includes(visualizationType)) return false
|
|
138
148
|
return true
|
|
139
149
|
}
|
|
140
150
|
|
|
141
151
|
const visSupportsNonSequentialPallete = () => {
|
|
142
|
-
const disabledCharts = ['Paired Bar', 'Deviation Bar', 'Forest Plot', 'Forecasting']
|
|
152
|
+
const disabledCharts = ['Paired Bar', 'Deviation Bar', 'Forest Plot', 'Forecasting', 'Sankey']
|
|
143
153
|
if (disabledCharts.includes(visualizationType)) return false
|
|
144
154
|
return true
|
|
145
155
|
}
|
|
146
156
|
|
|
147
157
|
const visSupportsReverseColorPalette = () => {
|
|
148
|
-
const disabledCharts = ['Forest Plot', 'Paired Bar', 'Deviation Bar']
|
|
158
|
+
const disabledCharts = ['Forest Plot', 'Paired Bar', 'Deviation Bar', 'Sankey']
|
|
149
159
|
if (disabledCharts.includes(visualizationType)) return false
|
|
150
160
|
return true
|
|
151
161
|
}
|
|
@@ -187,7 +197,7 @@ export const useEditorPermissions = () => {
|
|
|
187
197
|
}
|
|
188
198
|
|
|
189
199
|
const visSupportsRegions = () => {
|
|
190
|
-
const disabledCharts = ['Forest Plot', 'Pie', 'Paired Bar', 'Spark Line']
|
|
200
|
+
const disabledCharts = ['Forest Plot', 'Pie', 'Paired Bar', 'Spark Line', 'Sankey']
|
|
191
201
|
if (disabledCharts.includes(visualizationType)) return false
|
|
192
202
|
return true
|
|
193
203
|
}
|
|
@@ -205,7 +215,7 @@ export const useEditorPermissions = () => {
|
|
|
205
215
|
}
|
|
206
216
|
|
|
207
217
|
const visSupportsFilters = () => {
|
|
208
|
-
const disabledCharts = ['Forest Plot']
|
|
218
|
+
const disabledCharts = ['Forest Plot', 'Sankey']
|
|
209
219
|
if (disabledCharts.includes(visualizationType)) return false
|
|
210
220
|
return true
|
|
211
221
|
}
|
|
@@ -258,7 +268,7 @@ export const useEditorPermissions = () => {
|
|
|
258
268
|
}
|
|
259
269
|
|
|
260
270
|
const visSupportsLeftValueAxis = () => {
|
|
261
|
-
const disabledCharts = ['Spark Line']
|
|
271
|
+
const disabledCharts = ['Spark Line', 'Sankey']
|
|
262
272
|
if (disabledCharts.includes(visualizationType)) return false
|
|
263
273
|
return true
|
|
264
274
|
}
|
|
@@ -270,13 +280,13 @@ export const useEditorPermissions = () => {
|
|
|
270
280
|
}
|
|
271
281
|
|
|
272
282
|
const visSupportsDateCategoryHeight = () => {
|
|
273
|
-
const disabledCharts = ['Spark Line']
|
|
283
|
+
const disabledCharts = ['Spark Line', 'Sankey']
|
|
274
284
|
if (disabledCharts.includes(visualizationType)) return false
|
|
275
285
|
return true
|
|
276
286
|
}
|
|
277
287
|
|
|
278
288
|
const visSupportsDateCategoryAxisPadding = () => {
|
|
279
|
-
return config.xAxis.type === 'date'
|
|
289
|
+
return config.xAxis.type === 'date-time'
|
|
280
290
|
}
|
|
281
291
|
|
|
282
292
|
const visSupportsReactTooltip = () => {
|
|
@@ -285,6 +295,19 @@ export const useEditorPermissions = () => {
|
|
|
285
295
|
}
|
|
286
296
|
}
|
|
287
297
|
|
|
298
|
+
const visSupportsPreliminaryData = () => {
|
|
299
|
+
// check if Line added in Combo
|
|
300
|
+
const lineExist = config?.series.some(item => ['Line', 'dashed-sm', 'dashed-md', 'dashed-lg'].includes(item?.type))
|
|
301
|
+
if (visualizationType === 'Line') {
|
|
302
|
+
return true
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
if (visualizationType === 'Combo' && lineExist) {
|
|
306
|
+
return true
|
|
307
|
+
}
|
|
308
|
+
return false
|
|
309
|
+
}
|
|
310
|
+
|
|
288
311
|
return {
|
|
289
312
|
enabledChartTypes,
|
|
290
313
|
headerColors,
|
|
@@ -295,6 +318,7 @@ export const useEditorPermissions = () => {
|
|
|
295
318
|
visHasLabelOnData,
|
|
296
319
|
visHasDataSuppression,
|
|
297
320
|
visHasLegend,
|
|
321
|
+
visHasLegendAxisAlign,
|
|
298
322
|
visHasBrushChart,
|
|
299
323
|
visHasNumbersOnBars,
|
|
300
324
|
visSupportsBarSpace,
|
|
@@ -312,6 +336,7 @@ export const useEditorPermissions = () => {
|
|
|
312
336
|
visSupportsFootnotes,
|
|
313
337
|
visSupportsLeftValueAxis,
|
|
314
338
|
visSupportsNonSequentialPallete,
|
|
339
|
+
visSupportsPreliminaryData,
|
|
315
340
|
visSupportsRankByValue,
|
|
316
341
|
visSupportsRegions,
|
|
317
342
|
visSupportsResponsiveTicks,
|
|
@@ -319,6 +344,7 @@ export const useEditorPermissions = () => {
|
|
|
319
344
|
visSupportsSequentialPallete,
|
|
320
345
|
visSupportsSuperTitle,
|
|
321
346
|
visSupportsTooltipLines,
|
|
347
|
+
visHasSelectableLegendValues,
|
|
322
348
|
visSupportsTooltipOpacity,
|
|
323
349
|
visSupportsValueAxisGridLines,
|
|
324
350
|
visSupportsValueAxisLabels,
|
|
@@ -1,16 +1,15 @@
|
|
|
1
1
|
import parse from 'html-react-parser'
|
|
2
2
|
import { LegendOrdinal, LegendItem, LegendLabel } from '@visx/legend'
|
|
3
3
|
import LegendCircle from '@cdc/core/components/LegendCircle'
|
|
4
|
-
|
|
4
|
+
import Button from '@cdc/core/components/elements/Button'
|
|
5
5
|
import useLegendClasses from '../../hooks/useLegendClasses'
|
|
6
6
|
import { useHighlightedBars } from '../../hooks/useHighlightedBars'
|
|
7
7
|
import { handleLineType } from '../../helpers/handleLineType'
|
|
8
8
|
import { Line } from '@visx/shape'
|
|
9
|
-
import { scaleOrdinal } from '@visx/scale'
|
|
10
9
|
import { Label } from '../../types/Label'
|
|
11
10
|
import { ChartConfig } from '../../types/ChartConfig'
|
|
12
11
|
import { ColorScale } from '../../types/ChartContext'
|
|
13
|
-
import {
|
|
12
|
+
import { forwardRef } from 'react'
|
|
14
13
|
|
|
15
14
|
interface LegendProps {
|
|
16
15
|
config: ChartConfig
|
|
@@ -18,42 +17,17 @@ interface LegendProps {
|
|
|
18
17
|
seriesHighlight: string[]
|
|
19
18
|
highlight: Function
|
|
20
19
|
highlightReset: Function
|
|
21
|
-
currentViewport:
|
|
20
|
+
currentViewport: 'lg' | 'md' | 'sm' | 'xs' | 'xxs'
|
|
22
21
|
formatLabels: (labels: Label[]) => Label[]
|
|
22
|
+
ref: React.Ref<() => void>
|
|
23
|
+
skipId: string
|
|
23
24
|
}
|
|
24
25
|
|
|
25
26
|
/* eslint-disable jsx-a11y/no-noninteractive-tabindex, jsx-a11y/no-static-element-interactions */
|
|
26
|
-
const Legend: React.FC<LegendProps> = ({ config, colorScale, seriesHighlight, highlight, highlightReset, currentViewport, formatLabels }) => {
|
|
27
|
+
const Legend: React.FC<LegendProps> = forwardRef(({ config, colorScale, seriesHighlight, highlight, highlightReset, currentViewport, formatLabels, skipId = 'legend' }, ref) => {
|
|
27
28
|
const { innerClasses, containerClasses } = useLegendClasses(config)
|
|
28
29
|
const { runtime, orientation, legend } = config
|
|
29
30
|
if (!legend) return null
|
|
30
|
-
// create fn to reverse labels while legend is Bottom. Legend-right , legend-left works by default.
|
|
31
|
-
const displayScale = scaleOrdinal({
|
|
32
|
-
domain: config.suppressedData?.map(d => d.label),
|
|
33
|
-
range: ['none'],
|
|
34
|
-
unknown: 'block'
|
|
35
|
-
})
|
|
36
|
-
|
|
37
|
-
const renderDashes = style => {
|
|
38
|
-
const dashCount = style === 'Dashed Small' ? 3 : 2
|
|
39
|
-
const dashClass = `dashes ${style.toLowerCase().replace(' ', '-')}`
|
|
40
|
-
|
|
41
|
-
return (
|
|
42
|
-
<div className={dashClass}>
|
|
43
|
-
{Array.from({ length: dashCount }, (_, i) => (
|
|
44
|
-
<span key={i}>-</span>
|
|
45
|
-
))}
|
|
46
|
-
</div>
|
|
47
|
-
)
|
|
48
|
-
}
|
|
49
|
-
const renderDashesOrCircle = style => {
|
|
50
|
-
if (['Dashed Small', 'Dashed Medium', 'Dashed Large'].includes(style)) {
|
|
51
|
-
return renderDashes(style)
|
|
52
|
-
} else if (style === 'Open Circles') {
|
|
53
|
-
return <div className='dashes open-circles'></div>
|
|
54
|
-
}
|
|
55
|
-
}
|
|
56
|
-
|
|
57
31
|
const isBottomOrSmallViewport = legend.position === 'bottom' || ['sm', 'xs', 'xxs'].includes(currentViewport)
|
|
58
32
|
|
|
59
33
|
const legendClasses = {
|
|
@@ -64,11 +38,13 @@ const Legend: React.FC<LegendProps> = ({ config, colorScale, seriesHighlight, hi
|
|
|
64
38
|
const { HighLightedBarUtils } = useHighlightedBars(config)
|
|
65
39
|
|
|
66
40
|
let highLightedLegendItems = HighLightedBarUtils.findDuplicates(config.highlightedBarValues)
|
|
41
|
+
const fontSize = ['sm', 'xs', 'xxs'].includes(currentViewport) ? { fontSize: '11px' } : null
|
|
67
42
|
|
|
68
43
|
return (
|
|
69
|
-
<aside style={legendClasses} id='legend' className={containerClasses.join(' ')} role='region' aria-label='legend' tabIndex={0}>
|
|
70
|
-
{legend.label && <
|
|
71
|
-
{legend.description && <p>{parse(legend.description)}</p>}
|
|
44
|
+
<aside ref={ref} style={legendClasses} id={skipId || 'legend'} className={containerClasses.join(' ')} role='region' aria-label='legend' tabIndex={0}>
|
|
45
|
+
{legend.label && <h3>{parse(legend.label)}</h3>}
|
|
46
|
+
{legend.description && <p style={fontSize}>{parse(legend.description)}</p>}
|
|
47
|
+
|
|
72
48
|
<LegendOrdinal scale={colorScale} itemDirection='row' labelMargin='0 20px 0 0' shapeMargin='0 10px 0'>
|
|
73
49
|
{labels => {
|
|
74
50
|
return (
|
|
@@ -101,27 +77,31 @@ const Legend: React.FC<LegendProps> = ({ config, colorScale, seriesHighlight, hi
|
|
|
101
77
|
className={className.join(' ')}
|
|
102
78
|
tabIndex={0}
|
|
103
79
|
key={`legend-quantile-${i}`}
|
|
104
|
-
|
|
80
|
+
onKeyDown={e => {
|
|
105
81
|
if (e.key === 'Enter') {
|
|
82
|
+
e.preventDefault()
|
|
106
83
|
highlight(label)
|
|
107
84
|
}
|
|
108
85
|
}}
|
|
109
|
-
onClick={
|
|
86
|
+
onClick={e => {
|
|
87
|
+
e.preventDefault()
|
|
110
88
|
highlight(label)
|
|
111
89
|
}}
|
|
90
|
+
role='button'
|
|
112
91
|
>
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
<
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
<
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
92
|
+
<div>
|
|
93
|
+
{config.visualizationType === 'Line' && config.legend.lineMode ? (
|
|
94
|
+
<svg width={40} height={20}>
|
|
95
|
+
<Line from={{ x: 10, y: 10 }} to={{ x: 40, y: 10 }} stroke={label.value} strokeWidth={2} strokeDasharray={handleLineType(config.series[i]?.type ? config.series[i]?.type : '')} />
|
|
96
|
+
</svg>
|
|
97
|
+
) : (
|
|
98
|
+
<div style={{ display: 'flex', flexDirection: 'column' }}>
|
|
99
|
+
<LegendCircle viewport={currentViewport} margin='0' fill={label.value} display={true} />
|
|
100
|
+
</div>
|
|
101
|
+
)}
|
|
102
|
+
</div>
|
|
103
|
+
|
|
104
|
+
<LegendLabel style={fontSize} align='left' margin='0 0 0 4px'>
|
|
125
105
|
{label.text}
|
|
126
106
|
</LegendLabel>
|
|
127
107
|
</LegendItem>
|
|
@@ -142,12 +122,14 @@ const Legend: React.FC<LegendProps> = ({ config, colorScale, seriesHighlight, hi
|
|
|
142
122
|
className={className}
|
|
143
123
|
tabIndex={0}
|
|
144
124
|
key={`legend-quantile-${i}`}
|
|
145
|
-
|
|
125
|
+
onKeyDown={e => {
|
|
146
126
|
if (e.key === 'Enter') {
|
|
127
|
+
e.preventDefault()
|
|
147
128
|
highlight(bar.legendLabel)
|
|
148
129
|
}
|
|
149
130
|
}}
|
|
150
|
-
onClick={
|
|
131
|
+
onClick={e => {
|
|
132
|
+
e.preventDefault()
|
|
151
133
|
highlight(bar.legendLabel)
|
|
152
134
|
}}
|
|
153
135
|
>
|
|
@@ -158,15 +140,10 @@ const Legend: React.FC<LegendProps> = ({ config, colorScale, seriesHighlight, hi
|
|
|
158
140
|
</LegendItem>
|
|
159
141
|
)
|
|
160
142
|
})}
|
|
161
|
-
{seriesHighlight.length > 0 && (
|
|
162
|
-
<button className={`legend-reset ${config.theme}`} onClick={labels => highlightReset(labels)} tabIndex={0}>
|
|
163
|
-
Reset
|
|
164
|
-
</button>
|
|
165
|
-
)}
|
|
166
143
|
</div>
|
|
167
144
|
|
|
168
145
|
<>
|
|
169
|
-
{config?.preliminaryData?.some(pd => pd.label) && config.visualizationType
|
|
146
|
+
{config?.preliminaryData?.some(pd => pd.label) && ['Line', 'Combo'].includes(config.visualizationType) && (
|
|
170
147
|
<>
|
|
171
148
|
<hr></hr>
|
|
172
149
|
<div className={config.legend.singleRow && isBottomOrSmallViewport ? 'legend-container__inner bottom single-row' : ''}>
|
|
@@ -174,11 +151,9 @@ const Legend: React.FC<LegendProps> = ({ config, colorScale, seriesHighlight, hi
|
|
|
174
151
|
return (
|
|
175
152
|
<>
|
|
176
153
|
{pd.label && (
|
|
177
|
-
<div
|
|
178
|
-
<svg
|
|
179
|
-
|
|
180
|
-
</svg>
|
|
181
|
-
<span style={{}}> {pd.label}</span>
|
|
154
|
+
<div key={index} className='legend-preliminary'>
|
|
155
|
+
<svg>{pd.style.includes('Dashed') ? <Line from={{ x: 10, y: 10 }} to={{ x: 40, y: 10 }} stroke={'#000'} strokeWidth={2} strokeDasharray={handleLineType(pd.style)} /> : <circle r={6} strokeWidth={2} stroke={'#000'} cx={22} cy={10} fill='transparent' />}</svg>
|
|
156
|
+
<span> {pd.label}</span>
|
|
182
157
|
</div>
|
|
183
158
|
)}
|
|
184
159
|
</>
|
|
@@ -192,8 +167,13 @@ const Legend: React.FC<LegendProps> = ({ config, colorScale, seriesHighlight, hi
|
|
|
192
167
|
)
|
|
193
168
|
}}
|
|
194
169
|
</LegendOrdinal>
|
|
170
|
+
{seriesHighlight.length > 0 && (
|
|
171
|
+
<Button onClick={labels => highlightReset(labels)} style={{ marginTop: '1rem' }}>
|
|
172
|
+
Reset
|
|
173
|
+
</Button>
|
|
174
|
+
)}
|
|
195
175
|
</aside>
|
|
196
176
|
)
|
|
197
|
-
}
|
|
177
|
+
})
|
|
198
178
|
|
|
199
179
|
export default Legend
|
|
@@ -1,10 +1,10 @@
|
|
|
1
|
-
import { useContext } from 'react'
|
|
1
|
+
import { useContext, forwardRef } from 'react'
|
|
2
2
|
import ConfigContext from '../../ConfigContext'
|
|
3
3
|
import LegendComponent from './Legend.Component'
|
|
4
4
|
import { createFormatLabels } from './helpers/createFormatLabels'
|
|
5
5
|
|
|
6
6
|
/* eslint-disable jsx-a11y/no-noninteractive-tabindex, jsx-a11y/no-static-element-interactions */
|
|
7
|
-
const Legend = () => {
|
|
7
|
+
const Legend = forwardRef((props, ref) => {
|
|
8
8
|
// prettier-ignore
|
|
9
9
|
const {
|
|
10
10
|
config,
|
|
@@ -22,7 +22,11 @@ const Legend = () => {
|
|
|
22
22
|
|
|
23
23
|
const createLegendLabels = createFormatLabels(config, tableData, data, colorScale)
|
|
24
24
|
|
|
25
|
-
return
|
|
26
|
-
|
|
25
|
+
return (
|
|
26
|
+
!['Box Plot', 'Pie'].includes(config.visualizationType) && (
|
|
27
|
+
<LegendComponent ref={ref} skipId={props.skipId || 'legend'} config={config} colorScale={colorScale} seriesHighlight={seriesHighlight} highlight={highlight} highlightReset={highlightReset} currentViewport={currentViewport} formatLabels={createLegendLabels} />
|
|
28
|
+
)
|
|
29
|
+
)
|
|
30
|
+
})
|
|
27
31
|
|
|
28
32
|
export default Legend
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { type PreliminaryDataItem, DataItem, StyleProps, Style } from './LineChartProps'
|
|
2
2
|
|
|
3
3
|
export const createStyles = (props: StyleProps): Style[] => {
|
|
4
|
-
const { preliminaryData, data, stroke, handleLineType, lineType, seriesKey } = props
|
|
4
|
+
const { preliminaryData, data, stroke, strokeWidth, handleLineType, lineType, seriesKey } = props
|
|
5
5
|
|
|
6
6
|
const validPreliminaryData: PreliminaryDataItem[] = preliminaryData.filter(pd => pd.seriesKey && pd.column && pd.value && pd.type && pd.style)
|
|
7
7
|
const getMatchingPd = (point: DataItem): PreliminaryDataItem => validPreliminaryData.find(pd => pd.seriesKey === seriesKey && point[pd.column] === pd.value && pd.type === 'effect' && pd.style !== 'Open Circles')
|
|
@@ -9,7 +9,7 @@ export const createStyles = (props: StyleProps): Style[] => {
|
|
|
9
9
|
let styles: Style[] = []
|
|
10
10
|
const createStyle = (lineStyle): Style => ({
|
|
11
11
|
stroke: stroke,
|
|
12
|
-
strokeWidth:
|
|
12
|
+
strokeWidth: strokeWidth,
|
|
13
13
|
strokeDasharray: lineStyle
|
|
14
14
|
})
|
|
15
15
|
|
|
@@ -52,7 +52,7 @@ const LineChart = (props: LineChartProps) => {
|
|
|
52
52
|
|
|
53
53
|
return (
|
|
54
54
|
<ErrorBoundary component='LineChart'>
|
|
55
|
-
<Group left={config.runtime.yAxis.size
|
|
55
|
+
<Group left={config.runtime.yAxis.size}>
|
|
56
56
|
{' '}
|
|
57
57
|
{/* left - expects a number not a string */}
|
|
58
58
|
{(config.runtime.lineSeriesKeys || config.runtime.seriesKeys).map((seriesKey, index) => {
|
|
@@ -62,7 +62,7 @@ const LineChart = (props: LineChartProps) => {
|
|
|
62
62
|
let displayArea = legend.behavior === 'highlight' || seriesHighlight.length === 0 || seriesHighlight.indexOf(seriesKey) !== -1
|
|
63
63
|
const circleData = filterCircles(config.preliminaryData, rawData, seriesKey)
|
|
64
64
|
// styles for preliminary Data items
|
|
65
|
-
let styles = createStyles({ preliminaryData: config.preliminaryData, data: tableData, stroke: colorScale(config.runtime.seriesLabels[seriesKey]), handleLineType, lineType, seriesKey })
|
|
65
|
+
let styles = createStyles({ preliminaryData: config.preliminaryData, data: tableData, stroke: colorScale(config.runtime.seriesLabels[seriesKey]), strokeWidth: seriesData[0].weight || 2, handleLineType, lineType, seriesKey })
|
|
66
66
|
|
|
67
67
|
let xPos = d => {
|
|
68
68
|
return xScale(getXAxisData(d)) + (xScale.bandwidth ? xScale.bandwidth() / 2 : 0)
|
|
@@ -166,7 +166,7 @@ const LineChart = (props: LineChartProps) => {
|
|
|
166
166
|
{config?.preliminaryData?.some(d => d.value && d.column) ? (
|
|
167
167
|
<SplitLinePath
|
|
168
168
|
curve={allCurves[seriesData[0].lineType]}
|
|
169
|
-
segments={(config.xAxis.type === 'date'
|
|
169
|
+
segments={(config.xAxis.type === 'date-time'
|
|
170
170
|
? data.sort((d1, d2) => {
|
|
171
171
|
let x1 = getXAxisData(d1)
|
|
172
172
|
let x2 = getXAxisData(d2)
|
|
@@ -190,7 +190,7 @@ const LineChart = (props: LineChartProps) => {
|
|
|
190
190
|
<LinePath
|
|
191
191
|
curve={allCurves[seriesData[0].lineType]}
|
|
192
192
|
data={
|
|
193
|
-
config.xAxis.type === 'date'
|
|
193
|
+
config.xAxis.type === 'date-time'
|
|
194
194
|
? data.sort((d1, d2) => {
|
|
195
195
|
let x1 = getXAxisData(d1)
|
|
196
196
|
let x2 = getXAxisData(d2)
|
|
@@ -203,7 +203,7 @@ const LineChart = (props: LineChartProps) => {
|
|
|
203
203
|
x={d => xPos(d)}
|
|
204
204
|
y={d => (seriesAxis === 'Right' ? yScaleRight(getYAxisData(d, seriesKey)) : yScale(Number(getYAxisData(d, seriesKey))))}
|
|
205
205
|
stroke={colorScale(config.runtime.seriesLabels[seriesKey])}
|
|
206
|
-
strokeWidth={2}
|
|
206
|
+
strokeWidth={seriesData[0].weight || 2}
|
|
207
207
|
strokeOpacity={1}
|
|
208
208
|
shapeRendering='geometricPrecision'
|
|
209
209
|
strokeDasharray={lineType ? handleLineType(lineType) : 0}
|
|
@@ -216,14 +216,14 @@ const LineChart = (props: LineChartProps) => {
|
|
|
216
216
|
|
|
217
217
|
{/* circles for preliminaryData data */}
|
|
218
218
|
{circleData.map((d, i) => {
|
|
219
|
-
return <circle key={i} cx={xPos(d)} cy={yScale(Number(getYAxisData(d, seriesKey)))} r={6} strokeWidth={2} stroke={colorScale ? colorScale(config.runtime.seriesLabels[seriesKey]) : '#000'} fill='#fff' />
|
|
219
|
+
return <circle key={i} cx={xPos(d)} cy={seriesAxis === 'Right' ? yScaleRight(getYAxisData(d, seriesKey)) : yScale(Number(getYAxisData(d, seriesKey)))} r={6} strokeWidth={seriesData[0].weight || 2} stroke={colorScale ? colorScale(config.runtime.seriesLabels[seriesKey]) : '#000'} fill='#fff' />
|
|
220
220
|
})}
|
|
221
221
|
|
|
222
222
|
{/* ANIMATED LINE */}
|
|
223
223
|
{config.animate && (
|
|
224
224
|
<LinePath
|
|
225
225
|
className='animation'
|
|
226
|
-
curve={seriesData.lineType}
|
|
226
|
+
curve={allCurves[seriesData[0].lineType]}
|
|
227
227
|
data={data}
|
|
228
228
|
x={d => xPos(d)}
|
|
229
229
|
y={d => (seriesAxis === 'Right' ? yScaleRight(getYAxisData(d, seriesKey)) : yScale(Number(getYAxisData(d, seriesKey))))}
|
|
@@ -7,6 +7,7 @@ import { Line, Bar } from '@visx/shape'
|
|
|
7
7
|
import { Text } from '@visx/text'
|
|
8
8
|
import { Tooltip as ReactTooltip } from 'react-tooltip'
|
|
9
9
|
import { useTooltip, TooltipWithBounds } from '@visx/tooltip'
|
|
10
|
+
import { isDateScale } from '@cdc/core/helpers/cove/date'
|
|
10
11
|
|
|
11
12
|
// CDC Components
|
|
12
13
|
import { AreaChart, AreaChartStacked } from './AreaChart'
|
|
@@ -27,7 +28,7 @@ import Regions from './Regions'
|
|
|
27
28
|
import useMinMax from '../hooks/useMinMax'
|
|
28
29
|
import useReduceData from '../hooks/useReduceData'
|
|
29
30
|
import useRightAxis from '../hooks/useRightAxis'
|
|
30
|
-
import useScales from '../hooks/useScales'
|
|
31
|
+
import useScales, { getTickValues } from '../hooks/useScales'
|
|
31
32
|
import useTopAxis from '../hooks/useTopAxis'
|
|
32
33
|
import { useTooltip as useCoveTooltip } from '../hooks/useTooltip'
|
|
33
34
|
import { useEditorPermissions } from './EditorPanel/useEditorPermissions'
|
|
@@ -36,32 +37,10 @@ import { useEditorPermissions } from './EditorPanel/useEditorPermissions'
|
|
|
36
37
|
import ZoomBrush from './ZoomBrush'
|
|
37
38
|
|
|
38
39
|
const LinearChart = props => {
|
|
39
|
-
const {
|
|
40
|
-
isEditor,
|
|
41
|
-
isDashboard,
|
|
42
|
-
computeMarginBottom,
|
|
43
|
-
transformedData: data,
|
|
44
|
-
dimensions,
|
|
45
|
-
config,
|
|
46
|
-
parseDate,
|
|
47
|
-
formatDate,
|
|
48
|
-
currentViewport,
|
|
49
|
-
formatNumber,
|
|
50
|
-
handleChartAriaLabels,
|
|
51
|
-
updateConfig,
|
|
52
|
-
handleLineType,
|
|
53
|
-
rawData,
|
|
54
|
-
capitalize,
|
|
55
|
-
setSharedFilter,
|
|
56
|
-
setSharedFilterValue,
|
|
57
|
-
getTextWidth,
|
|
58
|
-
isDebug
|
|
59
|
-
} = useContext(ConfigContext)
|
|
40
|
+
const { transformedData: data, dimensions, config, parseDate, formatDate, currentViewport, formatNumber, handleChartAriaLabels, updateConfig, handleLineType, getTextWidth } = useContext(ConfigContext)
|
|
60
41
|
// todo: start destructuring this file for conciseness
|
|
61
42
|
const { visualizationType, visualizationSubType, orientation, xAxis, yAxis, runtime, debugSvg } = config
|
|
62
43
|
|
|
63
|
-
const getDate = d => new Date(d[config.xAxis.dataKey])
|
|
64
|
-
|
|
65
44
|
// configure width
|
|
66
45
|
let [width] = dimensions
|
|
67
46
|
if (config && config.legend && !config.legend.hide && config.legend.position !== 'bottom' && ['lg', 'md'].includes(currentViewport)) {
|
|
@@ -99,7 +78,7 @@ const LinearChart = props => {
|
|
|
99
78
|
})
|
|
100
79
|
|
|
101
80
|
// getters & functions
|
|
102
|
-
const getXAxisData = d => (config.runtime.xAxis
|
|
81
|
+
const getXAxisData = d => (isDateScale(config.runtime.xAxis) ? parseDate(d[config.runtime.originalXAxis.dataKey]).getTime() : d[config.runtime.originalXAxis.dataKey])
|
|
103
82
|
const getYAxisData = (d, seriesKey) => d[seriesKey]
|
|
104
83
|
const xAxisDataMapped = config.brush.active && config.brush.data?.length ? config.brush.data.map(d => getXAxisData(d)) : data.map(d => getXAxisData(d))
|
|
105
84
|
const section = config.orientation === 'horizontal' || config.visualizationType === 'Forest Plot' ? 'yAxis' : 'xAxis'
|
|
@@ -122,7 +101,7 @@ const LinearChart = props => {
|
|
|
122
101
|
|
|
123
102
|
if (config.data && !config.data[index] && visualizationType === 'Forest Plot') return
|
|
124
103
|
if (config.visualizationType === 'Forest Plot') return config.data[index][config.xAxis.dataKey]
|
|
125
|
-
if (runtime.yAxis
|
|
104
|
+
if (isDateScale(runtime.yAxis)) return formatDate(parseDate(tick))
|
|
126
105
|
if (orientation === 'vertical') return formatNumber(tick, 'left', shouldAbbreviate)
|
|
127
106
|
return tick
|
|
128
107
|
}
|
|
@@ -133,7 +112,7 @@ const LinearChart = props => {
|
|
|
133
112
|
tick = 0
|
|
134
113
|
}
|
|
135
114
|
|
|
136
|
-
if (runtime.xAxis
|
|
115
|
+
if (isDateScale(runtime.xAxis) && config.visualizationType !== 'Forest Plot') return formatDate(tick)
|
|
137
116
|
if (orientation === 'horizontal' && config.visualizationType !== 'Forest Plot') return formatNumber(tick, 'left', shouldAbbreviate)
|
|
138
117
|
if (config.xAxis.type === 'continuous' && config.visualizationType !== 'Forest Plot') return formatNumber(tick, 'bottom', shouldAbbreviate)
|
|
139
118
|
if (config.visualizationType === 'Forest Plot') return formatNumber(tick, 'left', config.dataFormat.abbreviated, config.runtime.xAxis.prefix, config.runtime.xAxis.suffix, Number(config.dataFormat.roundTo))
|
|
@@ -388,7 +367,7 @@ const LinearChart = props => {
|
|
|
388
367
|
{/* X axis */}
|
|
389
368
|
{visualizationType !== 'Paired Bar' && visualizationType !== 'Spark Line' && (
|
|
390
369
|
<AxisBottom
|
|
391
|
-
top={runtime.horizontal && config.visualizationType !== 'Forest Plot' ? Number(heightHorizontal) + Number(config.xAxis.axisPadding) : config.visualizationType === 'Forest Plot' ? yMax + Number(config.xAxis.axisPadding) : yMax
|
|
370
|
+
top={runtime.horizontal && config.visualizationType !== 'Forest Plot' ? Number(heightHorizontal) + Number(config.xAxis.axisPadding) : config.visualizationType === 'Forest Plot' ? yMax + Number(config.xAxis.axisPadding) : yMax}
|
|
392
371
|
left={config.visualizationType !== 'Forest Plot' ? Number(runtime.yAxis.size) : 0}
|
|
393
372
|
label={runtime.xAxis.label}
|
|
394
373
|
tickFormat={handleBottomTickFormatting}
|
|
@@ -396,6 +375,7 @@ const LinearChart = props => {
|
|
|
396
375
|
stroke='#333'
|
|
397
376
|
numTicks={countNumOfTicks('xAxis')}
|
|
398
377
|
tickStroke='#333'
|
|
378
|
+
tickValues={config.xAxis.manual ? getTickValues(xAxisDataMapped, xScale, config.xAxis.type === 'date-time' ? countNumOfTicks('xAxis') : config.xAxis.manualStep) : undefined}
|
|
399
379
|
>
|
|
400
380
|
{props => {
|
|
401
381
|
const axisCenter = config.visualizationType !== 'Forest Plot' ? (props.axisToPoint.x - props.axisFromPoint.x) / 2 : dimensions[0] / 2
|
|
@@ -502,7 +482,7 @@ const LinearChart = props => {
|
|
|
502
482
|
)}
|
|
503
483
|
{visualizationType === 'Paired Bar' && (
|
|
504
484
|
<>
|
|
505
|
-
<AxisBottom top={yMax} left={Number(runtime.yAxis.size)} label={runtime.xAxis.label} tickFormat={runtime.xAxis
|
|
485
|
+
<AxisBottom top={yMax} left={Number(runtime.yAxis.size)} label={runtime.xAxis.label} tickFormat={isDateScale(runtime.xAxis) ? formatDate : formatNumber} scale={g1xScale} stroke='#333' tickStroke='#333' numTicks={runtime.xAxis.numTicks || undefined}>
|
|
506
486
|
{props => {
|
|
507
487
|
return (
|
|
508
488
|
<Group className='bottom-axis'>
|
|
@@ -529,7 +509,7 @@ const LinearChart = props => {
|
|
|
529
509
|
top={yMax}
|
|
530
510
|
left={Number(runtime.yAxis.size)}
|
|
531
511
|
label={runtime.xAxis.label}
|
|
532
|
-
tickFormat={runtime.xAxis
|
|
512
|
+
tickFormat={isDateScale(runtime.xAxis) ? formatDate : runtime.xAxis.dataKey !== 'Year' ? formatNumber : tick => tick}
|
|
533
513
|
scale={g2xScale}
|
|
534
514
|
stroke='#333'
|
|
535
515
|
tickStroke='#333'
|
|
@@ -708,7 +688,7 @@ const LinearChart = props => {
|
|
|
708
688
|
newX = yAxis
|
|
709
689
|
}
|
|
710
690
|
|
|
711
|
-
let anchorPosition = newX
|
|
691
|
+
let anchorPosition = isDateScale(newX) ? xScale(parseDate(anchor.value, false)) : xScale(anchor.value)
|
|
712
692
|
|
|
713
693
|
// have to move up
|
|
714
694
|
// const padding = orientation === 'horizontal' ? Number(config.xAxis.size) : Number(config.yAxis.size)
|
|
@@ -5,7 +5,7 @@ import { scaleLinear } from '@visx/scale'
|
|
|
5
5
|
import { Text } from '@visx/text'
|
|
6
6
|
|
|
7
7
|
import ConfigContext from '../ConfigContext'
|
|
8
|
-
import
|
|
8
|
+
import { getContrastColor } from '@cdc/core/helpers/cove/accessibility'
|
|
9
9
|
|
|
10
10
|
const PairedBarChart = ({ width, height, originalWidth }) => {
|
|
11
11
|
const { config, colorScale, transformedData: data, formatNumber, seriesHighlight, getTextWidth } = useContext(ConfigContext)
|
|
@@ -47,15 +47,8 @@ const PairedBarChart = ({ width, height, originalWidth }) => {
|
|
|
47
47
|
})
|
|
48
48
|
|
|
49
49
|
// Set label color
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
if (groupOne.color && chroma.contrast(labelColor, groupOne.color) < 4.9) {
|
|
53
|
-
groupOne.labelColor = '#FFFFFF'
|
|
54
|
-
}
|
|
55
|
-
|
|
56
|
-
if (groupTwo.color && chroma.contrast(labelColor, groupTwo.color) < 4.9) {
|
|
57
|
-
groupTwo.labelColor = '#FFFFFF'
|
|
58
|
-
}
|
|
50
|
+
groupOne.labelColor = groupOne.color ? getContrastColor('#000', groupOne.color) : '#000'
|
|
51
|
+
groupTwo.labelColor = groupTwo.color ? getContrastColor('#000', groupTwo.color) : '#000'
|
|
59
52
|
|
|
60
53
|
const label = config.yAxis.label ? `${config.yAxis.label}: ` : ''
|
|
61
54
|
|
|
@@ -87,6 +80,7 @@ const PairedBarChart = ({ width, height, originalWidth }) => {
|
|
|
87
80
|
`}
|
|
88
81
|
</style>
|
|
89
82
|
<svg id='cdc-visualization__paired-bar-chart' width={originalWidth} height={height} viewBox={`0 0 ${width + Number(config.runtime.yAxis.size)} ${height}`} role='img' tabIndex={0}>
|
|
83
|
+
<title>{`Paired bar chart graphic with the title ${config.title ? config.title : 'No Title Found'}`}</title>
|
|
90
84
|
<Group top={0} left={Number(config.xAxis.size)}>
|
|
91
85
|
{data
|
|
92
86
|
.filter(item => config.series[0].dataKey === groupOne.dataKey)
|
|
@@ -122,6 +116,7 @@ const PairedBarChart = ({ width, height, originalWidth }) => {
|
|
|
122
116
|
strokeWidth={borderWidth}
|
|
123
117
|
opacity={transparentBar ? 0.5 : 1}
|
|
124
118
|
display={displayBar ? 'block' : 'none'}
|
|
119
|
+
tabIndex={-1}
|
|
125
120
|
/>
|
|
126
121
|
{config.yAxis.displayNumbersOnBar && displayBar && (
|
|
127
122
|
<Text textAnchor={textFits ? 'start' : 'end'} dx={textFits ? 5 : -5} verticalAnchor='middle' x={halfWidth - barWidth} y={y + config.barHeight / 2} fill={textFits ? groupOne.labelColor : '#000'}>
|
|
@@ -173,6 +168,7 @@ const PairedBarChart = ({ width, height, originalWidth }) => {
|
|
|
173
168
|
stroke='#333'
|
|
174
169
|
opacity={transparentBar ? 0.5 : 1}
|
|
175
170
|
display={displayBar ? 'block' : 'none'}
|
|
171
|
+
tabIndex={-1}
|
|
176
172
|
/>
|
|
177
173
|
{config.yAxis.displayNumbersOnBar && displayBar && (
|
|
178
174
|
<Text textAnchor={isTextFits ? 'end' : 'start'} dx={isTextFits ? -5 : 5} verticalAnchor='middle' x={halfWidth + barWidth} y={y + config.barHeight / 2} fill={isTextFits ? groupTwo.labelColor : '#000'}>
|