@cdc/chart 4.24.5 → 4.24.9
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 +44197 -38258
- package/examples/cases-year.json +13379 -0
- package/examples/feature/annotations/index.json +542 -0
- package/examples/gallery/bar-chart-vertical/combo-line-chart.json +76 -15
- package/examples/gallery/bar-chart-vertical/vertical-bar-chart-stacked.json +5 -5
- package/examples/xaxis.json +493 -0
- package/index.html +20 -10
- package/package.json +5 -4
- package/src/CdcChart.tsx +462 -172
- package/src/_stories/Chart.Legend.Gradient.tsx +19 -0
- package/src/_stories/Chart.stories.tsx +18 -171
- package/src/_stories/ChartAnnotation.stories.tsx +32 -0
- package/src/_stories/_mock/annotation_category_mock.json +473 -0
- package/src/_stories/_mock/annotation_date-linear_mock.json +530 -0
- package/{examples/feature/line/line-chart.json → src/_stories/_mock/annotation_date-time_mock.json} +150 -69
- package/src/_stories/_mock/legend.gradient_mock.json +236 -0
- package/src/_stories/_mock/line_chart_two_points_new_chart.json +128 -0
- package/src/_stories/_mock/line_chart_two_points_regression_test.json +127 -0
- package/src/_stories/_mock/lollipop.json +171 -0
- package/src/components/Annotations/components/AnnotationDraggable.styles.css +31 -0
- package/src/components/Annotations/components/AnnotationDraggable.tsx +207 -0
- package/src/components/Annotations/components/AnnotationDropdown.styles.css +14 -0
- package/src/components/Annotations/components/AnnotationDropdown.tsx +72 -0
- package/src/components/Annotations/components/AnnotationList.styles.css +45 -0
- package/src/components/Annotations/components/AnnotationList.tsx +42 -0
- package/src/components/Annotations/components/findNearestDatum.ts +138 -0
- package/src/components/Annotations/components/helpers/index.tsx +46 -0
- package/src/components/Annotations/index.tsx +13 -0
- package/src/components/AreaChart/components/AreaChart.Stacked.jsx +1 -1
- package/src/components/AreaChart/components/AreaChart.jsx +1 -1
- package/src/components/Axis/Categorical.Axis.tsx +145 -0
- package/src/components/BarChart/components/BarChart.Horizontal.tsx +47 -44
- package/src/components/BarChart/components/BarChart.StackedHorizontal.tsx +0 -1
- package/src/components/BarChart/components/BarChart.StackedVertical.tsx +11 -14
- package/src/components/BarChart/components/BarChart.Vertical.tsx +67 -30
- package/src/components/BarChart/helpers/index.ts +91 -0
- package/src/components/BrushChart.tsx +205 -0
- package/src/components/EditorPanel/EditorPanel.tsx +1794 -403
- package/src/components/EditorPanel/components/Panels/Panel.Annotate.tsx +320 -0
- package/src/components/EditorPanel/components/Panels/Panel.General.tsx +282 -18
- package/src/components/EditorPanel/components/Panels/Panel.Sankey.tsx +43 -8
- package/src/components/EditorPanel/components/Panels/Panel.Series.tsx +4 -4
- package/src/components/EditorPanel/components/Panels/Panel.Visual.tsx +4 -13
- package/src/components/EditorPanel/components/Panels/index.tsx +3 -1
- package/src/components/EditorPanel/components/panels.scss +4 -0
- package/src/components/EditorPanel/editor-panel.scss +35 -3
- package/src/components/EditorPanel/{useEditorPermissions.js → useEditorPermissions.ts} +105 -17
- package/src/components/Legend/Legend.Component.tsx +185 -194
- package/src/components/Legend/Legend.Suppression.tsx +146 -0
- package/src/components/Legend/Legend.tsx +21 -5
- package/src/components/Legend/helpers/createFormatLabels.tsx +1 -1
- package/src/components/Legend/helpers/index.ts +35 -0
- package/src/components/LegendWrapper.tsx +26 -0
- package/src/components/LineChart/LineChartProps.ts +1 -15
- package/src/components/LineChart/components/LineChart.BumpCircle.tsx +103 -0
- package/src/components/LineChart/components/LineChart.Circle.tsx +47 -8
- package/src/components/LineChart/helpers.ts +72 -14
- package/src/components/LineChart/index.tsx +117 -42
- package/src/components/LinearChart.jsx +179 -136
- package/src/components/LinearChart.tsx +1366 -0
- package/src/components/PairedBarChart.jsx +9 -9
- package/src/components/PieChart/PieChart.tsx +75 -18
- package/src/components/Sankey/index.tsx +89 -30
- package/src/components/ScatterPlot/ScatterPlot.jsx +22 -8
- package/src/components/Sparkline/components/SparkLine.tsx +2 -2
- package/src/components/ZoomBrush.tsx +90 -44
- package/src/data/initial-state.js +25 -7
- package/src/helpers/handleChartTabbing.ts +8 -0
- package/src/helpers/isConvertLineToBarGraph.ts +4 -0
- package/src/hooks/{useBarChart.js → useBarChart.ts} +2 -40
- package/src/hooks/useColorScale.ts +1 -1
- package/src/hooks/useLegendClasses.ts +68 -0
- package/src/hooks/useMinMax.ts +12 -7
- package/src/hooks/useScales.ts +58 -26
- package/src/hooks/useTooltip.tsx +135 -25
- package/src/scss/DataTable.scss +2 -1
- package/src/scss/main.scss +128 -28
- package/src/types/ChartConfig.ts +83 -10
- package/src/types/ChartContext.ts +14 -4
- package/tests-examples/helpers/testZeroValue.test.ts +30 -0
- package/LICENSE +0 -201
- package/src/components/BrushHandle.jsx +0 -17
- package/src/components/LineChart/index.scss +0 -1
- package/src/helpers/filterData.ts +0 -18
- package/src/helpers/tests/computeMarginBottom.test.ts +0 -21
- package/src/hooks/useLegendClasses.js +0 -31
- /package/src/hooks/{useReduceData.js → useReduceData.ts} +0 -0
|
@@ -1,16 +1,20 @@
|
|
|
1
1
|
import parse from 'html-react-parser'
|
|
2
2
|
import { LegendOrdinal, LegendItem, LegendLabel } from '@visx/legend'
|
|
3
|
-
import
|
|
3
|
+
import LegendShape from '@cdc/core/components/LegendShape'
|
|
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
|
+
|
|
9
|
+
import { getMarginTop, getGradientConfig, getMarginBottom } from './helpers/index'
|
|
9
10
|
import { Line } from '@visx/shape'
|
|
10
11
|
import { Label } from '../../types/Label'
|
|
11
12
|
import { ChartConfig } from '../../types/ChartConfig'
|
|
12
13
|
import { ColorScale } from '../../types/ChartContext'
|
|
13
14
|
import { forwardRef } from 'react'
|
|
15
|
+
import LegendSuppression from './Legend.Suppression'
|
|
16
|
+
import LegendGradient from '@cdc/core/components/Legend/Legend.Gradient'
|
|
17
|
+
import { DimensionsType } from '@cdc/core/types/Dimensions'
|
|
14
18
|
|
|
15
19
|
export interface LegendProps {
|
|
16
20
|
colorScale: ColorScale
|
|
@@ -22,210 +26,197 @@ export interface LegendProps {
|
|
|
22
26
|
ref: React.Ref<() => void>
|
|
23
27
|
seriesHighlight: string[]
|
|
24
28
|
skipId: string
|
|
29
|
+
dimensions: DimensionsType // for responsive width legend
|
|
30
|
+
getTextWidth: (text: string, font: string) => string
|
|
25
31
|
}
|
|
26
32
|
|
|
27
33
|
/* eslint-disable jsx-a11y/no-noninteractive-tabindex, jsx-a11y/no-static-element-interactions */
|
|
28
|
-
const Legend: React.FC<LegendProps> = forwardRef(
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
34
|
+
const Legend: React.FC<LegendProps> = forwardRef(
|
|
35
|
+
(
|
|
36
|
+
{
|
|
37
|
+
config,
|
|
38
|
+
colorScale,
|
|
39
|
+
seriesHighlight,
|
|
40
|
+
highlight,
|
|
41
|
+
highlightReset,
|
|
42
|
+
currentViewport,
|
|
43
|
+
formatLabels,
|
|
44
|
+
skipId = 'legend',
|
|
45
|
+
dimensions,
|
|
46
|
+
getTextWidth
|
|
47
|
+
},
|
|
48
|
+
ref
|
|
49
|
+
) => {
|
|
50
|
+
const { innerClasses, containerClasses } = useLegendClasses(config)
|
|
51
|
+
const { runtime, legend } = config
|
|
52
|
+
|
|
53
|
+
const isBottomOrSmallViewport =
|
|
54
|
+
legend?.position === 'bottom' || (['sm', 'xs', 'xxs'].includes(currentViewport) && !legend.hide)
|
|
55
|
+
|
|
56
|
+
const legendClasses = {
|
|
57
|
+
marginBottom: getMarginBottom(isBottomOrSmallViewport, config),
|
|
58
|
+
|
|
59
|
+
marginTop:
|
|
60
|
+
isBottomOrSmallViewport && config.orientation === 'horizontal'
|
|
61
|
+
? `${config.yAxis.label && config.isResponsiveTicks ? config.dynamicMarginTop : config.runtime.xAxis.size}px`
|
|
62
|
+
: getMarginTop(isBottomOrSmallViewport, config.brush.active, legend)
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
const { HighLightedBarUtils } = useHighlightedBars(config)
|
|
66
|
+
let highLightedLegendItems = HighLightedBarUtils.findDuplicates(config.highlightedBarValues)
|
|
67
|
+
if (!legend) return null
|
|
68
|
+
return (
|
|
69
|
+
<aside
|
|
70
|
+
ref={ref}
|
|
71
|
+
style={legendClasses}
|
|
72
|
+
id={skipId || 'legend'}
|
|
73
|
+
className={containerClasses.join(' ')}
|
|
74
|
+
role='region'
|
|
75
|
+
aria-label='legend'
|
|
76
|
+
tabIndex={0}
|
|
77
|
+
>
|
|
78
|
+
{legend.label && <h3>{parse(legend.label)}</h3>}
|
|
79
|
+
{legend.description && <p>{parse(legend.description)}</p>}
|
|
80
|
+
<LegendGradient
|
|
81
|
+
getTextWidth={getTextWidth}
|
|
82
|
+
config={config}
|
|
83
|
+
{...getGradientConfig(config, formatLabels, colorScale)}
|
|
84
|
+
dimensions={dimensions}
|
|
85
|
+
currentViewport={currentViewport}
|
|
86
|
+
/>
|
|
87
|
+
|
|
88
|
+
<LegendOrdinal scale={colorScale} itemDirection='row' labelMargin='0 20px 0 0' shapeMargin='0 10px 0'>
|
|
89
|
+
{labels => {
|
|
90
|
+
return (
|
|
91
|
+
<>
|
|
92
|
+
<div className={innerClasses.join(' ')}>
|
|
93
|
+
{formatLabels(labels as Label[]).map((label, i) => {
|
|
94
|
+
let className = ['legend-item', `legend-text--${label.text.replace(' ', '').toLowerCase()}`]
|
|
95
|
+
let itemName = label.datum
|
|
96
|
+
|
|
97
|
+
// Filter excluded data keys from legend
|
|
98
|
+
if (config.exclusions.active && config.exclusions.keys?.includes(itemName)) {
|
|
99
|
+
return null
|
|
100
|
+
}
|
|
49
101
|
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
<>
|
|
54
|
-
<div className={innerClasses.join(' ')}>
|
|
55
|
-
{formatLabels(labels as Label[]).map((label, i) => {
|
|
56
|
-
let className = ['legend-item', `legend-text--${label.text.replace(' ', '').toLowerCase()}`]
|
|
57
|
-
let itemName = label.datum
|
|
102
|
+
if (runtime.seriesLabels) {
|
|
103
|
+
let index = config.runtime.seriesLabelsAll.indexOf(itemName)
|
|
104
|
+
itemName = config.runtime.seriesKeys[index]
|
|
58
105
|
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
106
|
+
if (runtime?.forecastingSeriesKeys?.length > 0) {
|
|
107
|
+
itemName = label.text
|
|
108
|
+
}
|
|
109
|
+
}
|
|
63
110
|
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
111
|
+
if (seriesHighlight.length > 0 && false === seriesHighlight.includes(itemName)) {
|
|
112
|
+
className.push('inactive')
|
|
113
|
+
}
|
|
67
114
|
|
|
68
|
-
if (
|
|
69
|
-
|
|
115
|
+
if (config.legend.style === 'gradient') {
|
|
116
|
+
return <></>
|
|
70
117
|
}
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
118
|
+
|
|
119
|
+
return (
|
|
120
|
+
<LegendItem
|
|
121
|
+
className={className.join(' ')}
|
|
122
|
+
tabIndex={0}
|
|
123
|
+
key={`legend-quantile-${i}`}
|
|
124
|
+
onKeyDown={e => {
|
|
125
|
+
if (e.key === 'Enter') {
|
|
126
|
+
e.preventDefault()
|
|
127
|
+
highlight(label)
|
|
128
|
+
}
|
|
129
|
+
}}
|
|
130
|
+
onClick={e => {
|
|
84
131
|
e.preventDefault()
|
|
85
132
|
highlight(label)
|
|
86
|
-
}
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
<
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
133
|
+
}}
|
|
134
|
+
role='button'
|
|
135
|
+
>
|
|
136
|
+
<div>
|
|
137
|
+
{config.visualizationType === 'Line' && config.legend.style === 'lines' ? (
|
|
138
|
+
<svg width={40} height={20}>
|
|
139
|
+
<Line
|
|
140
|
+
from={{ x: 10, y: 10 }}
|
|
141
|
+
to={{ x: 40, y: 10 }}
|
|
142
|
+
stroke={label.value}
|
|
143
|
+
strokeWidth={2}
|
|
144
|
+
strokeDasharray={handleLineType(config.series[i]?.type ? config.series[i]?.type : '')}
|
|
145
|
+
/>
|
|
146
|
+
</svg>
|
|
147
|
+
) : (
|
|
148
|
+
<div style={{ display: 'flex', flexDirection: 'column' }}>
|
|
149
|
+
<LegendShape
|
|
150
|
+
shape={config.legend.style === 'boxes' ? 'square' : 'circle'}
|
|
151
|
+
viewport={currentViewport}
|
|
152
|
+
margin='0'
|
|
153
|
+
fill={label.value}
|
|
154
|
+
display={true}
|
|
155
|
+
/>
|
|
156
|
+
</div>
|
|
157
|
+
)}
|
|
158
|
+
</div>
|
|
159
|
+
|
|
160
|
+
<LegendLabel align='left' margin='0 0 0 4px'>
|
|
161
|
+
{label.text}
|
|
162
|
+
</LegendLabel>
|
|
163
|
+
</LegendItem>
|
|
164
|
+
)
|
|
165
|
+
})}
|
|
166
|
+
|
|
167
|
+
{highLightedLegendItems.map((bar, i) => {
|
|
168
|
+
// if duplicates only return first item
|
|
169
|
+
let className = 'legend-item'
|
|
170
|
+
let itemName = bar.legendLabel
|
|
171
|
+
|
|
172
|
+
if (!itemName) return false
|
|
173
|
+
if (seriesHighlight.length > 0 && false === seriesHighlight.includes(itemName)) {
|
|
174
|
+
className += ' inactive'
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
return (
|
|
178
|
+
<LegendItem
|
|
179
|
+
className={className}
|
|
180
|
+
tabIndex={0}
|
|
181
|
+
key={`legend-quantile-${i}`}
|
|
182
|
+
onKeyDown={e => {
|
|
183
|
+
if (e.key === 'Enter') {
|
|
184
|
+
e.preventDefault()
|
|
185
|
+
highlight(bar.legendLabel)
|
|
186
|
+
}
|
|
187
|
+
}}
|
|
188
|
+
onClick={e => {
|
|
129
189
|
e.preventDefault()
|
|
130
190
|
highlight(bar.legendLabel)
|
|
131
|
-
}
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
{config?.preliminaryData?.some(pd => pd.label && pd.type === 'effect' && pd.style) && ['Line', 'Combo'].includes(config.visualizationType) && (
|
|
149
|
-
<>
|
|
150
|
-
<hr></hr>
|
|
151
|
-
<div className={config.legend.singleRow && isBottomOrSmallViewport ? 'legend-container__inner bottom single-row' : ''}>
|
|
152
|
-
{config?.preliminaryData?.map((pd, index) => {
|
|
153
|
-
return (
|
|
154
|
-
<>
|
|
155
|
-
{pd.label && pd.type === 'effect' && pd.style && (
|
|
156
|
-
<div key={index} className='legend-preliminary'>
|
|
157
|
-
<span className={pd.symbol}>{pd.lineCode}</span>
|
|
158
|
-
<p> {pd.label}</p>
|
|
159
|
-
</div>
|
|
160
|
-
)}
|
|
161
|
-
</>
|
|
162
|
-
)
|
|
163
|
-
})}
|
|
164
|
-
</div>
|
|
165
|
-
</>
|
|
166
|
-
)}
|
|
167
|
-
{!config.legend.hideSuppressedLabels &&
|
|
168
|
-
config?.preliminaryData?.some(pd => pd.label && pd.displayLegend && pd.type === 'suppression' && pd.value && (pd?.style || pd.symbol)) &&
|
|
169
|
-
((config.visualizationType === 'Bar' && config.visualizationSubType === 'regular') || config.visualizationType === 'Line' || config.visualizationType === 'Combo') && (
|
|
170
|
-
<>
|
|
171
|
-
<hr></hr>
|
|
172
|
-
<div className={config.legend.singleRow && isBottomOrSmallViewport ? 'legend-container__inner bottom single-row' : ''}>
|
|
173
|
-
{config?.preliminaryData?.map(
|
|
174
|
-
(pd, index) =>
|
|
175
|
-
pd.displayLegend &&
|
|
176
|
-
pd.type === 'suppression' && (
|
|
177
|
-
<>
|
|
178
|
-
{config.visualizationType === 'Bar' && (
|
|
179
|
-
<>
|
|
180
|
-
<div key={index + 'Bar'} className={`legend-preliminary ${pd.symbol}`}>
|
|
181
|
-
<span className={pd.symbol}>{pd.iconCode}</span>
|
|
182
|
-
<p className={pd.type}>{pd.label}</p>
|
|
183
|
-
</div>
|
|
184
|
-
</>
|
|
185
|
-
)}
|
|
186
|
-
{config.visualizationType === 'Line' && (
|
|
187
|
-
<>
|
|
188
|
-
<div key={index + 'Line'} className={`legend-preliminary `}>
|
|
189
|
-
<span>{pd.lineCode}</span>
|
|
190
|
-
<p className={pd.type}>{pd.label}</p>
|
|
191
|
-
</div>
|
|
192
|
-
</>
|
|
193
|
-
)}
|
|
194
|
-
{config.visualizationType === 'Combo' && (
|
|
195
|
-
<>
|
|
196
|
-
{pd.symbol && pd.iconCode && (
|
|
197
|
-
<div key={index + 'Combo'} className={`legend-preliminary ${pd.symbol}`}>
|
|
198
|
-
<span className={pd.symbol}>{pd.iconCode}</span>
|
|
199
|
-
<p className={pd.type}>{pd.label}</p>
|
|
200
|
-
</div>
|
|
201
|
-
)}
|
|
202
|
-
|
|
203
|
-
{pd.style && pd.lineCode && (
|
|
204
|
-
<div key={index + 'Combo'} className='legend-preliminary'>
|
|
205
|
-
<span>{pd.lineCode}</span>
|
|
206
|
-
<p>{pd.label}</p>
|
|
207
|
-
</div>
|
|
208
|
-
)}
|
|
209
|
-
</>
|
|
210
|
-
)}
|
|
211
|
-
</>
|
|
212
|
-
)
|
|
213
|
-
)}
|
|
214
|
-
</div>
|
|
215
|
-
</>
|
|
216
|
-
)}
|
|
191
|
+
}}
|
|
192
|
+
>
|
|
193
|
+
<LegendShape
|
|
194
|
+
shape={config.legend.style === 'boxes' ? 'square' : 'circle'}
|
|
195
|
+
style={{ borderRadius: '0px' }}
|
|
196
|
+
fill='transparent'
|
|
197
|
+
borderColor={bar.color ? bar.color : `rgba(255, 102, 1)`}
|
|
198
|
+
/>{' '}
|
|
199
|
+
<LegendLabel align='left' margin='0 0 0 4px'>
|
|
200
|
+
{bar.legendLabel ? bar.legendLabel : bar.value}
|
|
201
|
+
</LegendLabel>
|
|
202
|
+
</LegendItem>
|
|
203
|
+
)
|
|
204
|
+
})}
|
|
205
|
+
</div>
|
|
206
|
+
|
|
207
|
+
<LegendSuppression config={config} isBottomOrSmallViewport={isBottomOrSmallViewport} />
|
|
217
208
|
</>
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
209
|
+
)
|
|
210
|
+
}}
|
|
211
|
+
</LegendOrdinal>
|
|
212
|
+
{seriesHighlight.length > 0 && (
|
|
213
|
+
<Button onClick={labels => highlightReset(labels)} style={{ marginTop: '1rem' }}>
|
|
214
|
+
Reset
|
|
215
|
+
</Button>
|
|
216
|
+
)}
|
|
217
|
+
</aside>
|
|
218
|
+
)
|
|
219
|
+
}
|
|
220
|
+
)
|
|
230
221
|
|
|
231
222
|
export default Legend
|
|
@@ -0,0 +1,146 @@
|
|
|
1
|
+
import React from 'react'
|
|
2
|
+
import { ChartConfig } from '../../types/ChartConfig'
|
|
3
|
+
import Icon from '@cdc/core/components/ui/Icon'
|
|
4
|
+
import { Tooltip as ReactTooltip } from 'react-tooltip'
|
|
5
|
+
interface LegendProps {
|
|
6
|
+
config: ChartConfig
|
|
7
|
+
isBottomOrSmallViewport: boolean
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
const LegendSuppression: React.FC<LegendProps> = ({ config, isBottomOrSmallViewport }) => {
|
|
11
|
+
const { preliminaryData, visualizationType, visualizationSubType, legend } = config
|
|
12
|
+
|
|
13
|
+
const hasOpenCircleEffects = () =>
|
|
14
|
+
preliminaryData?.some(pd => pd.label && pd.type === 'effect' && pd.style === 'Open Circles') &&
|
|
15
|
+
['Line', 'Combo'].includes(visualizationType)
|
|
16
|
+
|
|
17
|
+
const shouldShowSuppressedLabels = () =>
|
|
18
|
+
!legend.hideSuppressedLabels &&
|
|
19
|
+
preliminaryData?.some(
|
|
20
|
+
pd => pd.label && pd.displayLegend && pd.type === 'suppression' && pd.value && (pd?.style || pd.symbol)
|
|
21
|
+
) &&
|
|
22
|
+
((visualizationType === 'Bar' && visualizationSubType === 'regular') ||
|
|
23
|
+
visualizationType === 'Line' ||
|
|
24
|
+
visualizationType === 'Combo')
|
|
25
|
+
|
|
26
|
+
const renderEffectItems = () =>
|
|
27
|
+
preliminaryData?.map(
|
|
28
|
+
(pd, index) =>
|
|
29
|
+
pd.label &&
|
|
30
|
+
pd.type === 'effect' &&
|
|
31
|
+
pd.style && (
|
|
32
|
+
<div key={index} className='legend-preliminary'>
|
|
33
|
+
<span className={pd.symbol}>{pd.lineCode}</span>
|
|
34
|
+
<p>{pd.label}</p>
|
|
35
|
+
</div>
|
|
36
|
+
)
|
|
37
|
+
)
|
|
38
|
+
const handleLinkClick = event => {
|
|
39
|
+
// prevent defintion link to change URl
|
|
40
|
+
event.preventDefault()
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
const renderSuppressedItems = () => {
|
|
44
|
+
const getStyle = displayGray => {
|
|
45
|
+
if (displayGray) {
|
|
46
|
+
return {
|
|
47
|
+
color: '#777772'
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
return null
|
|
51
|
+
}
|
|
52
|
+
return preliminaryData?.map((pd, index) => {
|
|
53
|
+
if (!pd.displayLegend || pd.type !== 'suppression') return null
|
|
54
|
+
|
|
55
|
+
const baseClass = 'legend-preliminary'
|
|
56
|
+
const itemKey = index + visualizationType
|
|
57
|
+
|
|
58
|
+
if (visualizationType === 'Bar') {
|
|
59
|
+
return (
|
|
60
|
+
<div style={getStyle(pd.displayGray)} key={itemKey} className={`${baseClass} ${pd.symbol}`}>
|
|
61
|
+
<span className={pd.symbol}>{pd.iconCode}</span>
|
|
62
|
+
<p className={pd.type}>{pd.label}</p>
|
|
63
|
+
</div>
|
|
64
|
+
)
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
if (visualizationType === 'Line') {
|
|
68
|
+
return (
|
|
69
|
+
<div style={getStyle(pd.displayGray)} key={itemKey} className={baseClass}>
|
|
70
|
+
<span>{pd.lineCode}</span>
|
|
71
|
+
<p className={pd.type}>{pd.label}</p>
|
|
72
|
+
</div>
|
|
73
|
+
)
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
if (visualizationType === 'Combo') {
|
|
77
|
+
return (
|
|
78
|
+
<React.Fragment>
|
|
79
|
+
{pd.symbol && pd.iconCode && (
|
|
80
|
+
<div style={getStyle(pd.displayGray)} key={itemKey} className={`${baseClass} ${pd.symbol}`}>
|
|
81
|
+
<span className={pd.symbol}>{pd.iconCode}</span>
|
|
82
|
+
<p className={pd.type}>{pd.label}</p>
|
|
83
|
+
</div>
|
|
84
|
+
)}
|
|
85
|
+
|
|
86
|
+
{pd.style && pd.lineCode && (
|
|
87
|
+
<div style={getStyle(pd.displayGray)} key={itemKey} className={baseClass}>
|
|
88
|
+
<span>{pd.lineCode}</span>
|
|
89
|
+
<p>{pd.label}</p>
|
|
90
|
+
</div>
|
|
91
|
+
)}
|
|
92
|
+
</React.Fragment>
|
|
93
|
+
)
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
return null
|
|
97
|
+
})
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
const getLegendContainerClass = () =>
|
|
101
|
+
legend.singleRow && isBottomOrSmallViewport ? 'legend-container__inner bottom single-row' : ''
|
|
102
|
+
|
|
103
|
+
return (
|
|
104
|
+
<React.Fragment>
|
|
105
|
+
{hasOpenCircleEffects() && (
|
|
106
|
+
<React.Fragment>
|
|
107
|
+
<hr />
|
|
108
|
+
<div className={getLegendContainerClass()}>{renderEffectItems()}</div>
|
|
109
|
+
</React.Fragment>
|
|
110
|
+
)}
|
|
111
|
+
|
|
112
|
+
{shouldShowSuppressedLabels() && (
|
|
113
|
+
<React.Fragment>
|
|
114
|
+
<hr />
|
|
115
|
+
<div className={getLegendContainerClass()}>{renderSuppressedItems()}</div>
|
|
116
|
+
</React.Fragment>
|
|
117
|
+
)}
|
|
118
|
+
{!config.legend.hideSuppressionLink &&
|
|
119
|
+
config.visualizationSubType !== 'stacked' &&
|
|
120
|
+
preliminaryData?.some(pd => pd.label && pd.type === 'suppression' && pd.value && (pd?.style || pd.symbol)) && (
|
|
121
|
+
<div className='legend-container__outer definition-link'>
|
|
122
|
+
<Icon alt='info-icon' display='info' />
|
|
123
|
+
<p>
|
|
124
|
+
This chart contains
|
|
125
|
+
<a // prettier-ignore
|
|
126
|
+
onClick={handleLinkClick}
|
|
127
|
+
data-tooltip-content='Data is suppressed to maintain statistical reliability. This occurs when the number of respondents or reported values does not meet the minimum reporting threshold.'
|
|
128
|
+
data-tooltip-id='my-tooltip'
|
|
129
|
+
href='no-router-link'
|
|
130
|
+
>
|
|
131
|
+
suppressed data
|
|
132
|
+
</a>
|
|
133
|
+
</p>
|
|
134
|
+
</div>
|
|
135
|
+
)}
|
|
136
|
+
|
|
137
|
+
<ReactTooltip // prettier-ignore
|
|
138
|
+
id='my-tooltip'
|
|
139
|
+
variant='light'
|
|
140
|
+
style={{ background: `rgba(255,255,255, ${config.tooltips.opacity / 100})`, color: 'black', maxWidth: '100%' }}
|
|
141
|
+
/>
|
|
142
|
+
</React.Fragment>
|
|
143
|
+
)
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
export default LegendSuppression
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { useContext, forwardRef } from 'react'
|
|
1
|
+
import { useContext, forwardRef, Fragment } from 'react'
|
|
2
2
|
import ConfigContext from '../../ConfigContext'
|
|
3
3
|
import LegendComponent from './Legend.Component'
|
|
4
4
|
import { createFormatLabels } from './helpers/createFormatLabels'
|
|
@@ -7,6 +7,7 @@ import { createFormatLabels } from './helpers/createFormatLabels'
|
|
|
7
7
|
const Legend = forwardRef((props, ref) => {
|
|
8
8
|
// prettier-ignore
|
|
9
9
|
const {
|
|
10
|
+
// prettier-ignore
|
|
10
11
|
config,
|
|
11
12
|
colorScale,
|
|
12
13
|
seriesHighlight,
|
|
@@ -14,17 +15,32 @@ const Legend = forwardRef((props, ref) => {
|
|
|
14
15
|
tableData,
|
|
15
16
|
highlightReset,
|
|
16
17
|
transformedData: data,
|
|
17
|
-
currentViewport
|
|
18
|
+
currentViewport,
|
|
19
|
+
dimensions,
|
|
20
|
+
getTextWidth,
|
|
18
21
|
} = useContext(ConfigContext)
|
|
19
|
-
|
|
20
22
|
if (!config.legend) return null
|
|
21
23
|
// create fn to reverse labels while legend is Bottom. Legend-right , legend-left works by default.
|
|
22
24
|
|
|
23
25
|
const createLegendLabels = createFormatLabels(config, tableData, data, colorScale)
|
|
24
26
|
|
|
25
27
|
return (
|
|
26
|
-
!['Box Plot'
|
|
27
|
-
<
|
|
28
|
+
!['Box Plot'].includes(config.visualizationType) && (
|
|
29
|
+
<Fragment>
|
|
30
|
+
<LegendComponent
|
|
31
|
+
getTextWidth={getTextWidth}
|
|
32
|
+
dimensions={dimensions}
|
|
33
|
+
ref={ref}
|
|
34
|
+
skipId={props.skipId || 'legend'}
|
|
35
|
+
config={config}
|
|
36
|
+
colorScale={colorScale}
|
|
37
|
+
seriesHighlight={seriesHighlight}
|
|
38
|
+
highlight={highlight}
|
|
39
|
+
highlightReset={highlightReset}
|
|
40
|
+
currentViewport={currentViewport}
|
|
41
|
+
formatLabels={createLegendLabels}
|
|
42
|
+
/>
|
|
43
|
+
</Fragment>
|
|
28
44
|
)
|
|
29
45
|
)
|
|
30
46
|
})
|
|
@@ -9,7 +9,7 @@ export const createFormatLabels =
|
|
|
9
9
|
(defaultLabels: Label[]): Label[] => {
|
|
10
10
|
const { visualizationType, visualizationSubType, series, runtime } = config
|
|
11
11
|
|
|
12
|
-
const reverseLabels = labels => (config.legend.reverseLabelOrder && config.legend
|
|
12
|
+
const reverseLabels = labels => (config.legend.reverseLabelOrder && config.legend?.position === 'bottom' ? labels.reverse() : labels)
|
|
13
13
|
const colorCode = config.legend?.colorCode
|
|
14
14
|
if (visualizationType === 'Deviation Bar') {
|
|
15
15
|
const [belowColor, aboveColor] = twoColorPalette[config.twoColor.palette]
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
export const getMarginTop = (isBottomOrSmallViewport, isBrushActive, legend) => {
|
|
2
|
+
if (!isBottomOrSmallViewport) return '0px'
|
|
3
|
+
if (isBrushActive && legend.position === 'bottom') return '35px'
|
|
4
|
+
}
|
|
5
|
+
|
|
6
|
+
export const getGradientConfig = (config, formatLabels, colorScale) => {
|
|
7
|
+
const defaultValue = [{ datum: '', index: 0, text: '', value: '' }]
|
|
8
|
+
|
|
9
|
+
const formatted = formatLabels(defaultValue)
|
|
10
|
+
const colors = config.legend.colorCode ? formatted.map(label => label?.value) : colorScale?.range() ?? []
|
|
11
|
+
const labels = config.legend.colorCode
|
|
12
|
+
? formatted.map(label => label?.text || label?.datum)
|
|
13
|
+
: colorScale?.domain() ?? []
|
|
14
|
+
|
|
15
|
+
return { colors, labels }
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
export const getMarginBottom = (isBottomOrSmallViewport, config) => {
|
|
19
|
+
const isSuppressedActive = config.preliminaryData.some(pd => pd.label) && !config.legend.hideSuppressionLink
|
|
20
|
+
|
|
21
|
+
const isLegendTop = config.legend?.position === 'top' && !config.legend.hide
|
|
22
|
+
|
|
23
|
+
let marginBottom = '0px'
|
|
24
|
+
if (isLegendTop && !isSuppressedActive) {
|
|
25
|
+
marginBottom = config.legend.hideBorder.topBottom ? '15px' : '25px'
|
|
26
|
+
}
|
|
27
|
+
if (isLegendTop && isSuppressedActive) {
|
|
28
|
+
marginBottom = '75px'
|
|
29
|
+
}
|
|
30
|
+
if (isBottomOrSmallViewport && isSuppressedActive) {
|
|
31
|
+
marginBottom = '45px'
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
return marginBottom
|
|
35
|
+
}
|