@cdc/chart 4.23.5 → 4.23.7
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 +29320 -28775
- package/examples/feature/__data__/planet-example-data.json +15 -16
- package/examples/feature/bar/new.json +561 -0
- package/examples/feature/combo/right-issues.json +190 -0
- package/examples/feature/forecasting/combo-forecasting.json +271 -0
- package/examples/feature/forecasting/effective_reproduction.json +57 -8
- package/examples/feature/forecasting/forecasting.json +5334 -0
- package/examples/feature/forecasting/index.json +203 -0
- package/examples/feature/line/line-chart.json +12 -12
- package/examples/gallery/bar-chart-vertical/vertical-bar-chart-categorical.json +167 -20
- package/examples/gallery/line/line.json +173 -1
- package/index.html +14 -8
- package/package.json +2 -2
- package/src/CdcChart.jsx +104 -26
- package/src/components/AreaChart.jsx +23 -149
- package/src/components/BarChart.jsx +87 -15
- package/src/components/DataTable.jsx +35 -14
- package/src/components/EditorPanel.jsx +1829 -1954
- package/src/components/Forecasting.jsx +84 -0
- package/src/components/Legend.jsx +191 -275
- package/src/components/LineChart.jsx +34 -7
- package/src/components/LinearChart.jsx +510 -101
- package/src/components/Series.jsx +554 -0
- package/src/components/SparkLine.jsx +3 -3
- package/src/data/initial-state.js +13 -5
- package/src/hooks/useMinMax.js +37 -0
- package/src/hooks/useRightAxis.js +9 -2
- package/src/hooks/useScales.js +7 -13
- package/src/scss/main.scss +4 -17
- package/LICENSE +0 -201
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
import React, { useContext } from 'react'
|
|
2
|
+
|
|
3
|
+
// cdc
|
|
4
|
+
import ConfigContext from '../ConfigContext'
|
|
5
|
+
import ErrorBoundary from '@cdc/core/components/ErrorBoundary'
|
|
6
|
+
import { colorPalettesChart } from '@cdc/core/data/colorPalettes'
|
|
7
|
+
|
|
8
|
+
// visx & d3
|
|
9
|
+
import { curveMonotoneX } from '@visx/curve'
|
|
10
|
+
import { Bar, Area, LinePath } from '@visx/shape'
|
|
11
|
+
import { Group } from '@visx/group'
|
|
12
|
+
|
|
13
|
+
const Forecasting = ({ xScale, yScale, height, width, handleTooltipMouseOver, handleTooltipMouseOff }) => {
|
|
14
|
+
const { transformedData: data, rawData, config, seriesHighlight } = useContext(ConfigContext)
|
|
15
|
+
const { xAxis, yAxis, legend, runtime } = config
|
|
16
|
+
const DEBUG = false
|
|
17
|
+
|
|
18
|
+
return (
|
|
19
|
+
data && (
|
|
20
|
+
<ErrorBoundary component='ForecastingChart'>
|
|
21
|
+
<Group className='forecasting-items' key='forecasting-items-wrapper' left={Number(yAxis.size)}>
|
|
22
|
+
{runtime.forecastingSeriesKeys?.map((group, index) => {
|
|
23
|
+
if (!group || !group.stages) return false
|
|
24
|
+
return group.stages.map((stage, stageIndex) => {
|
|
25
|
+
const { behavior } = legend
|
|
26
|
+
const groupData = rawData.filter(d => d[group.stageColumn] === stage.key)
|
|
27
|
+
let transparentArea = behavior === 'highlight' && seriesHighlight.length > 0 && seriesHighlight.indexOf(stage.key) === -1
|
|
28
|
+
let displayArea = behavior === 'highlight' || seriesHighlight.length === 0 || seriesHighlight.indexOf(stage.key) !== -1
|
|
29
|
+
|
|
30
|
+
return (
|
|
31
|
+
<Group className={`forecasting-areas-combo-${index}`} key={`forecasting-areas--stage-${stage.key.replaceAll(' ', '-')}-${index}`}>
|
|
32
|
+
{group.confidenceIntervals?.map((ciGroup, ciGroupIndex) => {
|
|
33
|
+
const palette = colorPalettesChart[stage.color]
|
|
34
|
+
|
|
35
|
+
const getFill = () => {
|
|
36
|
+
if (displayArea) return palette[ciGroupIndex] ? palette[ciGroupIndex] : 'transparent'
|
|
37
|
+
return 'transparent'
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
const getStroke = () => {
|
|
41
|
+
if (displayArea) return palette[2] ? palette[2] : 'transparent'
|
|
42
|
+
return 'transparent'
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
if (ciGroup.high === '' || ciGroup.low === '') return
|
|
46
|
+
return (
|
|
47
|
+
<Group key={`forecasting-areas--stage-${stage.key.replaceAll(' ', '-')}--group-${stageIndex}-${ciGroupIndex}`}>
|
|
48
|
+
{/* prettier-ignore */}
|
|
49
|
+
<Area
|
|
50
|
+
curve={curveMonotoneX}
|
|
51
|
+
data={groupData}
|
|
52
|
+
fill={getFill()}
|
|
53
|
+
opacity={transparentArea ? 0.1 : 0.5}
|
|
54
|
+
x={d => xScale(Date.parse(d[xAxis.dataKey]))}
|
|
55
|
+
y0={d => yScale(d[ciGroup.low])}
|
|
56
|
+
y1={d => yScale(d[ciGroup.high])}
|
|
57
|
+
/>
|
|
58
|
+
|
|
59
|
+
{ciGroupIndex === 0 && (
|
|
60
|
+
<>
|
|
61
|
+
{/* prettier-ignore */}
|
|
62
|
+
<LinePath data={groupData} x={d => Number(xScale(Date.parse(d[xAxis.dataKey])))} y={d => Number(yScale(d[ciGroup.high]))} curve={curveMonotoneX} stroke={getStroke()} strokeWidth={1} strokeOpacity={1} />
|
|
63
|
+
|
|
64
|
+
{/* prettier-ignore */}
|
|
65
|
+
<LinePath data={groupData} x={d => Number(xScale(Date.parse(d[xAxis.dataKey])))} y={d => Number(yScale(d[ciGroup.low]))} curve={curveMonotoneX} stroke={getStroke()} strokeWidth={1} strokeOpacity={1} />
|
|
66
|
+
</>
|
|
67
|
+
)}
|
|
68
|
+
</Group>
|
|
69
|
+
)
|
|
70
|
+
})}
|
|
71
|
+
</Group>
|
|
72
|
+
)
|
|
73
|
+
})
|
|
74
|
+
})}
|
|
75
|
+
<Group key='tooltip-hover-section'>
|
|
76
|
+
<Bar key={'bars'} width={Number(width)} height={Number(height)} fill={DEBUG ? 'red' : 'transparent'} fillOpacity={0.05} onMouseMove={e => handleTooltipMouseOver(e, data)} onMouseOut={handleTooltipMouseOff} />
|
|
77
|
+
</Group>
|
|
78
|
+
</Group>
|
|
79
|
+
</ErrorBoundary>
|
|
80
|
+
)
|
|
81
|
+
)
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
export default Forecasting
|
|
@@ -7,84 +7,38 @@ import LegendCircle from '@cdc/core/components/LegendCircle'
|
|
|
7
7
|
import useLegendClasses from './../hooks/useLegendClasses'
|
|
8
8
|
import { useHighlightedBars } from '../hooks/useHighlightedBars'
|
|
9
9
|
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
const { innerClasses, containerClasses } = useLegendClasses(config)
|
|
14
|
-
|
|
15
|
-
useEffect(() => {
|
|
16
|
-
if (dynamicLegendItems.length === 0) return
|
|
17
|
-
|
|
18
|
-
let itemsToHighlight = dynamicLegendItems.map(item => item.text)
|
|
19
|
-
|
|
20
|
-
setSeriesHighlight(itemsToHighlight)
|
|
10
|
+
// * FILE REVIEW *
|
|
11
|
+
// TODO: fix eslint-disable jsxa11y issues
|
|
21
12
|
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
rawData.map(dataItem => {
|
|
26
|
-
let tmp = {}
|
|
27
|
-
colsToKeep.map(col => {
|
|
28
|
-
tmp[col] = isNaN(dataItem[col]) ? dataItem[col] : dataItem[col]
|
|
29
|
-
return null
|
|
30
|
-
})
|
|
31
|
-
return tmp
|
|
32
|
-
})
|
|
33
|
-
|
|
34
|
-
colsToKeep.map(col => {
|
|
35
|
-
tmpLabels[col] = col
|
|
36
|
-
return null
|
|
37
|
-
})
|
|
38
|
-
|
|
39
|
-
if (dynamicLegendItems.length > 0) {
|
|
40
|
-
setConfig({
|
|
41
|
-
...config,
|
|
42
|
-
runtime: {
|
|
43
|
-
...config.runtime,
|
|
44
|
-
seriesKeys: colsToKeep,
|
|
45
|
-
seriesLabels: tmpLabels
|
|
46
|
-
}
|
|
47
|
-
})
|
|
48
|
-
}
|
|
49
|
-
}, [dynamicLegendItems]) // eslint-disable-line
|
|
50
|
-
|
|
51
|
-
useEffect(() => {
|
|
52
|
-
if (dynamicLegendItems.length === 0) {
|
|
53
|
-
// loop through all labels and add keys
|
|
54
|
-
let resetSeriesNames = [...config.runtime.seriesLabelsAll]
|
|
55
|
-
let tmpLabels = []
|
|
56
|
-
config.runtime.seriesLabelsAll.map(item => {
|
|
57
|
-
resetSeriesNames.map(col => {
|
|
58
|
-
tmpLabels[col] = col
|
|
59
|
-
return null
|
|
60
|
-
})
|
|
61
|
-
return null
|
|
62
|
-
})
|
|
13
|
+
// * ADDITIONAL NOTES *
|
|
14
|
+
// > recently removed dynamic legend items as they weren't used
|
|
15
|
+
// > recently removed boxplots, they don't provide any legend settings
|
|
63
16
|
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
17
|
+
/* eslint-disable jsx-a11y/no-noninteractive-tabindex, jsx-a11y/no-static-element-interactions */
|
|
18
|
+
const Legend = () => {
|
|
19
|
+
// prettier-ignore
|
|
20
|
+
const {
|
|
21
|
+
config,
|
|
22
|
+
legend,
|
|
23
|
+
colorScale,
|
|
24
|
+
seriesHighlight,
|
|
25
|
+
highlight,
|
|
26
|
+
twoColorPalette,
|
|
27
|
+
tableData,
|
|
28
|
+
highlightReset,
|
|
29
|
+
transformedData: data,
|
|
30
|
+
colorPalettes,
|
|
31
|
+
currentViewport
|
|
32
|
+
} = useContext(ConfigContext)
|
|
74
33
|
|
|
75
|
-
const
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
setSeriesHighlight(newLegendItemsText)
|
|
80
|
-
}
|
|
81
|
-
const handleDynamicLegendChange = e => {
|
|
82
|
-
setDynamicLegendItems([...dynamicLegendItems, JSON.parse(e.target.value)])
|
|
83
|
-
}
|
|
34
|
+
const { innerClasses, containerClasses } = useLegendClasses(config)
|
|
35
|
+
const { visualizationType, visualizationSubType, series, runtime, orientation } = config
|
|
36
|
+
// create fn to reverse labels while legend is Bottom. Legend-right , legend-left works by default.
|
|
37
|
+
const reverseLabels = labels => (config.legend.reverseLabelOrder && config.legend.position === 'bottom' ? labels.reverse() : labels)
|
|
84
38
|
|
|
85
39
|
const createLegendLabels = defaultLabels => {
|
|
86
40
|
const colorCode = config.legend?.colorCode
|
|
87
|
-
if (
|
|
41
|
+
if (visualizationType === 'Deviation Bar') {
|
|
88
42
|
const [belowColor, aboveColor] = twoColorPalette[config.twoColor.palette]
|
|
89
43
|
const labelBelow = {
|
|
90
44
|
datum: 'X',
|
|
@@ -99,22 +53,22 @@ const Legend = () => {
|
|
|
99
53
|
value: aboveColor
|
|
100
54
|
}
|
|
101
55
|
|
|
102
|
-
return [labelBelow, labelAbove]
|
|
56
|
+
return reverseLabels([labelBelow, labelAbove])
|
|
103
57
|
}
|
|
104
|
-
if (
|
|
58
|
+
if (visualizationType === 'Bar' && visualizationSubType === 'regular' && colorCode && series?.length === 1) {
|
|
105
59
|
let palette = colorPalettes[config.palette]
|
|
106
60
|
|
|
107
61
|
while (tableData.length > palette.length) {
|
|
108
62
|
palette = palette.concat(palette)
|
|
109
63
|
}
|
|
110
64
|
palette = palette.slice(0, data.length)
|
|
111
|
-
//store
|
|
65
|
+
//store unique values to Set by colorCode
|
|
112
66
|
const set = new Set()
|
|
113
67
|
|
|
114
68
|
tableData.forEach(d => set.add(d[colorCode]))
|
|
115
69
|
|
|
116
|
-
// create labels with
|
|
117
|
-
const
|
|
70
|
+
// create labels with unique values
|
|
71
|
+
const uniqueLabels = Array.from(set).map((val, i) => {
|
|
118
72
|
const newLabel = {
|
|
119
73
|
datum: val,
|
|
120
74
|
index: i,
|
|
@@ -124,226 +78,188 @@ const Legend = () => {
|
|
|
124
78
|
return newLabel
|
|
125
79
|
})
|
|
126
80
|
|
|
127
|
-
return
|
|
81
|
+
return reverseLabels(uniqueLabels)
|
|
128
82
|
}
|
|
129
|
-
return defaultLabels
|
|
130
|
-
}
|
|
131
83
|
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
const marginBottom = isBottomOrSmallViewport ? '15px' : '0px'
|
|
84
|
+
// get forecasting items inside of combo
|
|
85
|
+
if (runtime?.forecastingSeriesKeys?.length > 0) {
|
|
86
|
+
let seriesLabels = []
|
|
136
87
|
|
|
137
|
-
|
|
88
|
+
//store unique values to Set by colorCode
|
|
138
89
|
|
|
139
|
-
|
|
90
|
+
// loop through each stage/group/area on the chart and create a label
|
|
91
|
+
config.runtime?.forecastingSeriesKeys?.map((outerGroup, index) => {
|
|
92
|
+
return outerGroup?.stages?.map((stage, index) => {
|
|
93
|
+
let colorValue = colorPalettes[stage.color]?.[2] ? colorPalettes[stage.color]?.[2] : '#ccc'
|
|
140
94
|
|
|
141
|
-
|
|
95
|
+
const newLabel = {
|
|
96
|
+
datum: stage.key,
|
|
97
|
+
index: index,
|
|
98
|
+
text: stage.key,
|
|
99
|
+
value: colorValue
|
|
100
|
+
}
|
|
142
101
|
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
{legend.label && <h2>{parse(legend.label)}</h2>}
|
|
147
|
-
{legend.description && <p>{parse(legend.description)}</p>}
|
|
148
|
-
<LegendOrdinal scale={colorScale} itemDirection='row' labelMargin='0 20px 0 0' shapeMargin='0 10px 0'>
|
|
149
|
-
{labels => (
|
|
150
|
-
<div className={innerClasses.join(' ')}>
|
|
151
|
-
{createLegendLabels(labels).map((label, i) => {
|
|
152
|
-
let className = 'legend-item'
|
|
153
|
-
let itemName = label.datum
|
|
154
|
-
|
|
155
|
-
// Filter excluded data keys from legend
|
|
156
|
-
if (config.exclusions.active && config.exclusions.keys?.includes(itemName)) {
|
|
157
|
-
return null
|
|
158
|
-
}
|
|
159
|
-
|
|
160
|
-
if (config.runtime.seriesLabels) {
|
|
161
|
-
let index = config.runtime.seriesLabelsAll.indexOf(itemName)
|
|
162
|
-
itemName = config.runtime.seriesKeys[index]
|
|
163
|
-
}
|
|
164
|
-
|
|
165
|
-
if (seriesHighlight.length > 0 && false === seriesHighlight.includes(itemName)) {
|
|
166
|
-
className += ' inactive'
|
|
167
|
-
}
|
|
168
|
-
|
|
169
|
-
return (
|
|
170
|
-
<LegendItem
|
|
171
|
-
className={className}
|
|
172
|
-
tabIndex={0}
|
|
173
|
-
key={`legend-quantile-${i}`}
|
|
174
|
-
onKeyPress={e => {
|
|
175
|
-
if (e.key === 'Enter') {
|
|
176
|
-
highlight(label)
|
|
177
|
-
}
|
|
178
|
-
}}
|
|
179
|
-
onClick={() => {
|
|
180
|
-
highlight(label)
|
|
181
|
-
}}
|
|
182
|
-
>
|
|
183
|
-
<LegendCircle fill={label.value} />
|
|
184
|
-
<LegendLabel align='left' margin='0 0 0 4px'>
|
|
185
|
-
{label.text}
|
|
186
|
-
</LegendLabel>
|
|
187
|
-
</LegendItem>
|
|
188
|
-
)
|
|
189
|
-
})}
|
|
190
|
-
|
|
191
|
-
{highLightedLegendItems.map((bar, i) => {
|
|
192
|
-
// if duplicates only return first item
|
|
193
|
-
let className = 'legend-item'
|
|
194
|
-
let itemName = bar.legendLabel
|
|
195
|
-
|
|
196
|
-
if (!itemName) return
|
|
197
|
-
if (seriesHighlight.length > 0 && false === seriesHighlight.includes(itemName)) {
|
|
198
|
-
className += ' inactive'
|
|
199
|
-
}
|
|
200
|
-
return (
|
|
201
|
-
<LegendItem
|
|
202
|
-
className={className}
|
|
203
|
-
tabIndex={0}
|
|
204
|
-
key={`legend-quantile-${i}`}
|
|
205
|
-
onKeyPress={e => {
|
|
206
|
-
if (e.key === 'Enter') {
|
|
207
|
-
highlight(bar.legendLabel)
|
|
208
|
-
}
|
|
209
|
-
}}
|
|
210
|
-
onClick={() => {
|
|
211
|
-
highlight(bar.legendLabel)
|
|
212
|
-
}}
|
|
213
|
-
>
|
|
214
|
-
<LegendCircle fill='transparent' borderColor={bar.color ? bar.color : `rgba(255, 102, 1)`} />{' '}
|
|
215
|
-
<LegendLabel align='left' margin='0 0 0 4px'>
|
|
216
|
-
{bar.legendLabel ? bar.legendLabel : bar.value}
|
|
217
|
-
</LegendLabel>
|
|
218
|
-
</LegendItem>
|
|
219
|
-
)
|
|
220
|
-
})}
|
|
221
|
-
{seriesHighlight.length > 0 && (
|
|
222
|
-
<button className={`legend-reset ${config.theme}`} onClick={labels => highlightReset(labels)} tabIndex={0}>
|
|
223
|
-
Reset
|
|
224
|
-
</button>
|
|
225
|
-
)}
|
|
226
|
-
</div>
|
|
227
|
-
)}
|
|
228
|
-
</LegendOrdinal>
|
|
229
|
-
</aside>
|
|
230
|
-
) : (
|
|
231
|
-
<aside id='legend' className={containerClasses.join(' ')} role='region' aria-label='legend' tabIndex={0}>
|
|
232
|
-
{config.boxplot.legend.displayHowToReadText && <h3>{config.boxplot.legend.howToReadText}</h3>}
|
|
233
|
-
</aside>
|
|
234
|
-
)
|
|
235
|
-
return (
|
|
236
|
-
config.visualizationType !== 'Box Plot' && (
|
|
237
|
-
<aside id='legend' className={containerClasses.join(' ')} role='region' aria-label='legend' tabIndex={0}>
|
|
238
|
-
{legend.label && <h2>{parse(legend.label)}</h2>}
|
|
239
|
-
{legend.description && <p>{parse(legend.description)}</p>}
|
|
102
|
+
seriesLabels.push(newLabel)
|
|
103
|
+
})
|
|
104
|
+
})
|
|
240
105
|
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
dynamicLegendItems.length !== config.runtime.seriesLabelsAll.length
|
|
246
|
-
) {
|
|
247
|
-
// legend items are equal to series length
|
|
248
|
-
return (
|
|
249
|
-
<select className='dynamic-legend-dropdown' onChange={e => handleDynamicLegendChange(e)}>
|
|
250
|
-
<option className={'all'} tabIndex={0} value={JSON.stringify({ text: config.legend.dynamicLegendDefaultText })}>
|
|
251
|
-
{config.legend.dynamicLegendDefaultText}
|
|
252
|
-
</option>
|
|
253
|
-
{labels.map((label, i) => {
|
|
254
|
-
let className = 'legend-item'
|
|
255
|
-
let itemName = label.datum
|
|
256
|
-
let inDynamicList = false
|
|
257
|
-
|
|
258
|
-
// Filter excluded data keys from legend
|
|
259
|
-
if (config.exclusions.active && config.exclusions.keys?.includes(itemName)) {
|
|
260
|
-
return null
|
|
261
|
-
}
|
|
106
|
+
// loop through bars for now to meet requirements.
|
|
107
|
+
config.runtime.barSeriesKeys &&
|
|
108
|
+
config.runtime.barSeriesKeys.forEach((bar, index) => {
|
|
109
|
+
let colorValue = colorPalettes[config.palette][index] ? colorPalettes[config.palette][index] : '#ccc'
|
|
262
110
|
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
111
|
+
const newLabel = {
|
|
112
|
+
datum: bar,
|
|
113
|
+
index: index,
|
|
114
|
+
text: bar,
|
|
115
|
+
value: colorValue
|
|
116
|
+
}
|
|
267
117
|
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
}
|
|
118
|
+
seriesLabels.push(newLabel)
|
|
119
|
+
})
|
|
271
120
|
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
inDynamicList = true
|
|
275
|
-
}
|
|
276
|
-
return null
|
|
277
|
-
})
|
|
121
|
+
return reverseLabels(seriesLabels)
|
|
122
|
+
}
|
|
278
123
|
|
|
279
|
-
|
|
280
|
-
|
|
124
|
+
// DEV-4161: replaceable series name in the legend
|
|
125
|
+
const hasNewSeriesName = config.series.map(s => s.name).filter(item => item).length > 0
|
|
126
|
+
if (hasNewSeriesName) {
|
|
127
|
+
let palette = colorPalettes[config.palette]
|
|
281
128
|
|
|
282
|
-
|
|
129
|
+
while (tableData.length > palette.length) {
|
|
130
|
+
palette = palette.concat(palette)
|
|
131
|
+
}
|
|
283
132
|
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
</option>
|
|
288
|
-
)
|
|
289
|
-
})}
|
|
290
|
-
</select>
|
|
291
|
-
)
|
|
292
|
-
} else {
|
|
293
|
-
return config.legend.dynamicLegendItemLimitMessage
|
|
294
|
-
}
|
|
295
|
-
}}
|
|
296
|
-
</LegendOrdinal>
|
|
133
|
+
palette = palette.slice(0, data.length)
|
|
134
|
+
//store unique values to Set by colorCode
|
|
135
|
+
const set = new Set()
|
|
297
136
|
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
let itemName = label.text
|
|
302
|
-
let palette = colorPalettes[config.palette]
|
|
137
|
+
config.series.forEach(d => {
|
|
138
|
+
set.add(d['name'] ? d['name'] : d['dataKey'])
|
|
139
|
+
})
|
|
303
140
|
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
141
|
+
// create labels with unique values
|
|
142
|
+
const uniqueLabels = Array.from(set).map((val, i) => {
|
|
143
|
+
const newLabel = {
|
|
144
|
+
datum: val,
|
|
145
|
+
index: i,
|
|
146
|
+
text: val,
|
|
147
|
+
value: palette[i]
|
|
148
|
+
}
|
|
149
|
+
return newLabel
|
|
150
|
+
})
|
|
308
151
|
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
itemName = config.runtime.seriesKeys[index]
|
|
312
|
-
}
|
|
152
|
+
return reverseLabels(uniqueLabels)
|
|
153
|
+
}
|
|
313
154
|
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
155
|
+
return reverseLabels(defaultLabels)
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
const isBottomOrSmallViewport = legend.position === 'bottom' || ['sm', 'xs', 'xxs'].includes(currentViewport)
|
|
159
|
+
|
|
160
|
+
const legendClasses = {
|
|
161
|
+
marginBottom: isBottomOrSmallViewport ? '15px' : '0px',
|
|
162
|
+
marginTop: isBottomOrSmallViewport && orientation === 'horizontal' ? `${config.yAxis.label && config.isResponsiveTicks ? config.dynamicMarginTop : config.runtime.xAxis.size}px` : `0px`
|
|
163
|
+
}
|
|
317
164
|
|
|
318
|
-
|
|
319
|
-
className.push('inactive')
|
|
320
|
-
}
|
|
165
|
+
const { HighLightedBarUtils } = useHighlightedBars(config)
|
|
321
166
|
|
|
167
|
+
let highLightedLegendItems = HighLightedBarUtils.findDuplicates(config.highlightedBarValues)
|
|
168
|
+
|
|
169
|
+
if (!legend) return null
|
|
170
|
+
|
|
171
|
+
return (
|
|
172
|
+
config.visualizationType !== 'Box Plot' && (
|
|
173
|
+
<aside style={legendClasses} id='legend' className={containerClasses.join(' ')} role='region' aria-label='legend' tabIndex={0}>
|
|
174
|
+
{legend.label && <h2>{parse(legend.label)}</h2>}
|
|
175
|
+
{legend.description && <p>{parse(legend.description)}</p>}
|
|
176
|
+
<LegendOrdinal scale={colorScale} itemDirection='row' labelMargin='0 20px 0 0' shapeMargin='0 10px 0'>
|
|
177
|
+
{labels => {
|
|
322
178
|
return (
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
179
|
+
<div className={innerClasses.join(' ')}>
|
|
180
|
+
{createLegendLabels(labels).map((label, i) => {
|
|
181
|
+
let className = 'legend-item'
|
|
182
|
+
let itemName = label.datum
|
|
183
|
+
|
|
184
|
+
// Filter excluded data keys from legend
|
|
185
|
+
if (config.exclusions.active && config.exclusions.keys?.includes(itemName)) {
|
|
186
|
+
return null
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
if (runtime.seriesLabels) {
|
|
190
|
+
let index = config.runtime.seriesLabelsAll.indexOf(itemName)
|
|
191
|
+
itemName = config.runtime.seriesKeys[index]
|
|
192
|
+
|
|
193
|
+
if (runtime?.forecastingSeriesKeys?.length > 0) {
|
|
194
|
+
itemName = label.text
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
if (seriesHighlight.length > 0 && false === seriesHighlight.includes(itemName)) {
|
|
199
|
+
className += ' inactive'
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
return (
|
|
203
|
+
<LegendItem
|
|
204
|
+
className={className}
|
|
205
|
+
tabIndex={0}
|
|
206
|
+
key={`legend-quantile-${i}`}
|
|
207
|
+
onKeyPress={e => {
|
|
208
|
+
if (e.key === 'Enter') {
|
|
209
|
+
highlight(label)
|
|
210
|
+
}
|
|
211
|
+
}}
|
|
212
|
+
onClick={() => {
|
|
213
|
+
highlight(label)
|
|
214
|
+
}}
|
|
215
|
+
>
|
|
216
|
+
<LegendCircle fill={label.value} />
|
|
217
|
+
<LegendLabel align='left' margin='0 0 0 4px'>
|
|
218
|
+
{label.text}
|
|
219
|
+
</LegendLabel>
|
|
220
|
+
</LegendItem>
|
|
221
|
+
)
|
|
222
|
+
})}
|
|
223
|
+
|
|
224
|
+
{highLightedLegendItems.map((bar, i) => {
|
|
225
|
+
// if duplicates only return first item
|
|
226
|
+
let className = 'legend-item'
|
|
227
|
+
let itemName = bar.legendLabel
|
|
228
|
+
|
|
229
|
+
if (!itemName) return false
|
|
230
|
+
if (seriesHighlight.length > 0 && false === seriesHighlight.includes(itemName)) {
|
|
231
|
+
className += ' inactive'
|
|
232
|
+
}
|
|
233
|
+
return (
|
|
234
|
+
<LegendItem
|
|
235
|
+
className={className}
|
|
236
|
+
tabIndex={0}
|
|
237
|
+
key={`legend-quantile-${i}`}
|
|
238
|
+
onKeyPress={e => {
|
|
239
|
+
if (e.key === 'Enter') {
|
|
240
|
+
highlight(bar.legendLabel)
|
|
241
|
+
}
|
|
242
|
+
}}
|
|
243
|
+
onClick={() => {
|
|
244
|
+
highlight(bar.legendLabel)
|
|
245
|
+
}}
|
|
246
|
+
>
|
|
247
|
+
<LegendCircle fill='transparent' borderColor={bar.color ? bar.color : `rgba(255, 102, 1)`} />{' '}
|
|
248
|
+
<LegendLabel align='left' margin='0 0 0 4px'>
|
|
249
|
+
{bar.legendLabel ? bar.legendLabel : bar.value}
|
|
250
|
+
</LegendLabel>
|
|
251
|
+
</LegendItem>
|
|
252
|
+
)
|
|
253
|
+
})}
|
|
254
|
+
{seriesHighlight.length > 0 && (
|
|
255
|
+
<button className={`legend-reset ${config.theme}`} onClick={labels => highlightReset(labels)} tabIndex={0}>
|
|
256
|
+
Reset
|
|
335
257
|
</button>
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
</>
|
|
258
|
+
)}
|
|
259
|
+
</div>
|
|
339
260
|
)
|
|
340
|
-
}
|
|
341
|
-
</
|
|
342
|
-
{seriesHighlight.length < dynamicLegendItems.length && (
|
|
343
|
-
<button className={`legend-reset legend-reset--dynamic ${config.theme}`} onClick={highlightReset} tabIndex={0}>
|
|
344
|
-
Reset
|
|
345
|
-
</button>
|
|
346
|
-
)}
|
|
261
|
+
}}
|
|
262
|
+
</LegendOrdinal>
|
|
347
263
|
</aside>
|
|
348
264
|
)
|
|
349
265
|
)
|