@cdc/chart 4.23.11 → 4.24.1
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 +30220 -29764
- package/examples/feature/bar/additional-column-tooltip.json +446 -0
- package/examples/feature/bar/tall-data.json +98 -0
- package/examples/feature/forest-plot/forest-plot.json +63 -19
- package/examples/feature/forest-plot/linear.json +52 -3
- package/examples/feature/forest-plot/log.json +26 -0
- package/examples/feature/forest-plot/logarithmic.json +0 -35
- package/examples/feature/line/line-chart-preliminary.json +346 -0
- package/examples/feature/scatterplot/scatterplot.json +272 -33
- package/examples/private/chart-t.json +3740 -0
- package/examples/private/combo.json +369 -0
- package/examples/private/epi-data.csv +13 -0
- package/examples/private/epi-data.json +62 -0
- package/examples/private/epi.json +403 -0
- package/examples/private/occupancy.json +109283 -0
- package/examples/private/prod-line-config.json +401 -0
- package/examples/private/region-data.json +822 -0
- package/examples/private/region-testing.json +312 -0
- package/examples/private/scaling.json +45325 -0
- package/examples/private/testing-data.json +1739 -0
- package/examples/private/testing.json +816 -0
- package/index.html +7 -7
- package/package.json +2 -2
- package/src/CdcChart.tsx +29 -210
- package/src/ConfigContext.tsx +6 -0
- package/src/_stories/ChartEditor.stories.tsx +22 -0
- package/src/_stories/ChartLine.preliminary.tsx +19 -0
- package/src/_stories/_mock/pie_config.json +191 -0
- package/src/_stories/_mock/pie_data.json +218 -0
- package/src/_stories/_mock/preliminary_mock.json +346 -0
- package/src/components/{AreaChart.Stacked.jsx → AreaChart/components/AreaChart.Stacked.jsx} +2 -2
- package/src/components/{AreaChart.jsx → AreaChart/components/AreaChart.jsx} +1 -1
- package/src/components/AreaChart/index.tsx +4 -0
- package/src/components/{BarChart.Horizontal.tsx → BarChart/components/BarChart.Horizontal.tsx} +8 -8
- package/src/components/{BarChart.StackedHorizontal.tsx → BarChart/components/BarChart.StackedHorizontal.tsx} +37 -7
- package/src/components/BarChart/components/BarChart.StackedVertical.tsx +106 -0
- package/src/components/{BarChart.Vertical.tsx → BarChart/components/BarChart.Vertical.tsx} +41 -57
- package/src/components/BarChart/components/BarChart.jsx +39 -0
- package/src/components/{BarChartType.jsx → BarChart/components/BarChartType.jsx} +0 -2
- package/src/components/BarChart/components/context.tsx +13 -0
- package/src/components/BarChart/index.tsx +3 -0
- package/src/components/{BoxPlot.jsx → BoxPlot/BoxPlot.jsx} +1 -1
- package/src/components/BoxPlot/index.tsx +3 -0
- package/src/components/{EditorPanel.jsx → EditorPanel/EditorPanel.tsx} +667 -851
- package/src/components/EditorPanel/components/Panel.DateHighlighting.tsx +109 -0
- package/src/components/{ForestPlotSettings.jsx → EditorPanel/components/Panel.ForestPlotSettings.tsx} +87 -166
- package/src/components/EditorPanel/components/Panel.Regions.tsx +168 -0
- package/src/components/{Series.jsx → EditorPanel/components/Panel.Series.tsx} +1 -1
- package/src/components/EditorPanel/components/PanelProps.ts +3 -0
- package/src/components/EditorPanel/components/Panels.tsx +13 -0
- package/src/components/EditorPanel/components/panels.scss +72 -0
- package/src/components/EditorPanel/editor-panel.scss +751 -0
- package/src/components/EditorPanel/index.tsx +3 -0
- package/src/{hooks → components/EditorPanel}/useEditorPermissions.js +29 -2
- package/src/components/{Forecasting.jsx → Forecasting/Forecasting.jsx} +1 -1
- package/src/components/Forecasting/index.tsx +3 -0
- package/src/components/ForestPlot/ForestPlot.tsx +254 -0
- package/src/components/ForestPlot/ForestPlotProps.ts +7 -0
- package/src/components/ForestPlot/index.tsx +1 -209
- package/src/components/{Legend.jsx → Legend/Legend.tsx} +150 -113
- package/src/components/Legend/index.tsx +3 -0
- package/src/components/LineChart/LineChartProps.ts +29 -0
- package/src/components/LineChart/{LineChart.Circle.tsx → components/LineChart.Circle.tsx} +12 -3
- package/src/components/LineChart/helpers.ts +45 -0
- package/src/components/LineChart/index.tsx +20 -8
- package/src/components/LinearChart.jsx +52 -69
- package/src/components/{PieChart.jsx → PieChart/PieChart.tsx} +16 -7
- package/src/components/PieChart/index.tsx +3 -0
- package/src/components/Regions/components/Regions.tsx +135 -0
- package/src/components/Regions/index.tsx +3 -0
- package/src/components/{ScatterPlot.jsx → ScatterPlot/ScatterPlot.jsx} +3 -3
- package/src/components/ScatterPlot/index.tsx +3 -0
- package/src/components/{SparkLine.jsx → Sparkline/SparkLine.jsx} +2 -2
- package/src/components/Sparkline/index.tsx +3 -0
- package/src/data/initial-state.js +5 -6
- package/src/helpers/abbreviateNumber.ts +17 -0
- package/src/helpers/computeMarginBottom.ts +55 -0
- package/src/helpers/filterData.ts +18 -0
- package/src/helpers/generateColorsArray.ts +8 -0
- package/src/helpers/getQuartiles.ts +30 -0
- package/src/helpers/handleChartAriaLabels.ts +19 -0
- package/src/helpers/handleLineType.ts +18 -0
- package/src/helpers/lineOptions.ts +18 -0
- package/src/helpers/sort.ts +7 -0
- package/src/helpers/tests/computeMarginBottom.test.ts +20 -0
- package/src/hooks/useBarChart.js +7 -6
- package/src/hooks/useScales.ts +1 -1
- package/src/hooks/{useTooltip.jsx → useTooltip.tsx} +23 -21
- package/src/scss/main.scss +67 -3
- package/src/types/ChartConfig.ts +158 -23
- package/src/types/ChartContext.ts +26 -10
- package/src/types/ForestPlot.ts +7 -14
- package/examples/feature/scatterplot/scatterplot-continuous.csv +0 -17
- package/src/ConfigContext.jsx +0 -5
- package/src/components/BarChart.StackedVertical.tsx +0 -91
- package/src/components/BarChart.jsx +0 -30
- package/src/components/ForestPlot/Readme.md +0 -0
- package/src/scss/LinearChart.scss +0 -0
- package/src/scss/editor-panel.scss +0 -745
- package/src/scss/legend.scss +0 -206
- package/src/scss/mixins.scss +0 -0
- package/src/scss/variables.scss +0 -1
- package/src/types/ChartProps.ts +0 -7
|
@@ -1,43 +1,43 @@
|
|
|
1
|
-
import
|
|
2
|
-
import ConfigContext from '
|
|
1
|
+
import { useContext } from 'react'
|
|
2
|
+
import ConfigContext from '../../ConfigContext'
|
|
3
3
|
import parse from 'html-react-parser'
|
|
4
4
|
import { LegendOrdinal, LegendItem, LegendLabel } from '@visx/legend'
|
|
5
5
|
import LegendCircle from '@cdc/core/components/LegendCircle'
|
|
6
6
|
|
|
7
|
-
import useLegendClasses from '
|
|
8
|
-
import { useHighlightedBars } from '
|
|
7
|
+
import useLegendClasses from '../../hooks/useLegendClasses'
|
|
8
|
+
import { useHighlightedBars } from '../../hooks/useHighlightedBars'
|
|
9
|
+
import { handleLineType } from '../../helpers/handleLineType'
|
|
9
10
|
import { Line } from '@visx/shape'
|
|
10
|
-
import { sequentialPalettes } from '@cdc/core/data/colorPalettes'
|
|
11
|
+
import { colorPalettesChart as colorPalettes, sequentialPalettes, twoColorPalette } from '@cdc/core/data/colorPalettes'
|
|
11
12
|
import { scaleOrdinal } from '@visx/scale'
|
|
12
13
|
import { FaStar } from 'react-icons/fa'
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
14
|
+
import { Text } from '@visx/text'
|
|
15
|
+
import { Group } from '@visx/group'
|
|
16
|
+
|
|
17
|
+
type Label = {
|
|
18
|
+
datum: string
|
|
19
|
+
index: number
|
|
20
|
+
text: string
|
|
21
|
+
value: string
|
|
22
|
+
icon?: any
|
|
23
|
+
}
|
|
20
24
|
|
|
21
25
|
/* eslint-disable jsx-a11y/no-noninteractive-tabindex, jsx-a11y/no-static-element-interactions */
|
|
22
26
|
const Legend = () => {
|
|
23
27
|
// prettier-ignore
|
|
24
28
|
const {
|
|
25
29
|
config,
|
|
26
|
-
legend,
|
|
27
30
|
colorScale,
|
|
28
31
|
seriesHighlight,
|
|
29
32
|
highlight,
|
|
30
|
-
twoColorPalette,
|
|
31
33
|
tableData,
|
|
32
34
|
highlightReset,
|
|
33
35
|
transformedData: data,
|
|
34
|
-
|
|
35
|
-
currentViewport,
|
|
36
|
-
handleLineType
|
|
36
|
+
currentViewport
|
|
37
37
|
} = useContext(ConfigContext)
|
|
38
38
|
|
|
39
39
|
const { innerClasses, containerClasses } = useLegendClasses(config)
|
|
40
|
-
const { visualizationType, visualizationSubType, series, runtime, orientation } = config
|
|
40
|
+
const { visualizationType, visualizationSubType, series, runtime, orientation, legend } = config
|
|
41
41
|
// create fn to reverse labels while legend is Bottom. Legend-right , legend-left works by default.
|
|
42
42
|
const reverseLabels = labels => (config.legend.reverseLabelOrder && config.legend.position === 'bottom' ? labels.reverse() : labels)
|
|
43
43
|
const displayScale = scaleOrdinal({
|
|
@@ -46,7 +46,26 @@ const Legend = () => {
|
|
|
46
46
|
unknown: 'block'
|
|
47
47
|
})
|
|
48
48
|
|
|
49
|
-
const
|
|
49
|
+
const renderDashes = style => {
|
|
50
|
+
const dashCount = style === 'Dashed Small' ? 3 : 2
|
|
51
|
+
const dashClass = `dashes ${style.toLowerCase().replace(' ', '-')}`
|
|
52
|
+
|
|
53
|
+
return (
|
|
54
|
+
<div className={dashClass}>
|
|
55
|
+
{Array.from({ length: dashCount }, (_, i) => (
|
|
56
|
+
<span key={i}>-</span>
|
|
57
|
+
))}
|
|
58
|
+
</div>
|
|
59
|
+
)
|
|
60
|
+
}
|
|
61
|
+
const renderDashesOrCircle = style => {
|
|
62
|
+
if (['Dashed Small', 'Dashed Medium', 'Dashed Large'].includes(style)) {
|
|
63
|
+
return renderDashes(style)
|
|
64
|
+
} else if (style === 'Open Circles') {
|
|
65
|
+
return <div className='dashes open-circles'></div>
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
const createLegendLabels = (defaultLabels): Label[] => {
|
|
50
69
|
const colorCode = config.legend?.colorCode
|
|
51
70
|
if (visualizationType === 'Deviation Bar') {
|
|
52
71
|
const [belowColor, aboveColor] = twoColorPalette[config.twoColor.palette]
|
|
@@ -96,7 +115,6 @@ const Legend = () => {
|
|
|
96
115
|
let seriesLabels = []
|
|
97
116
|
|
|
98
117
|
//store unique values to Set by colorCode
|
|
99
|
-
|
|
100
118
|
// loop through each stage/group/area on the chart and create a label
|
|
101
119
|
config.runtime?.forecastingSeriesKeys?.map((outerGroup, index) => {
|
|
102
120
|
return outerGroup?.stages?.map((stage, index) => {
|
|
@@ -166,11 +184,7 @@ const Legend = () => {
|
|
|
166
184
|
const lastIndex = defaultLabels.length - 1
|
|
167
185
|
let newLabels = []
|
|
168
186
|
|
|
169
|
-
config.suppressedData?.forEach(({ label, icon
|
|
170
|
-
const dataExists = data.some(d => {
|
|
171
|
-
return runtime.seriesKeys.some(column => d[column] === value)
|
|
172
|
-
})
|
|
173
|
-
|
|
187
|
+
config.suppressedData?.forEach(({ label, icon }, index) => {
|
|
174
188
|
if (label && icon) {
|
|
175
189
|
const newLabel = {
|
|
176
190
|
datum: label,
|
|
@@ -208,97 +222,120 @@ const Legend = () => {
|
|
|
208
222
|
<LegendOrdinal scale={colorScale} itemDirection='row' labelMargin='0 20px 0 0' shapeMargin='0 10px 0'>
|
|
209
223
|
{labels => {
|
|
210
224
|
return (
|
|
211
|
-
|
|
212
|
-
{
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
225
|
+
<>
|
|
226
|
+
<div className={innerClasses.join(' ')}>
|
|
227
|
+
{createLegendLabels(labels).map((label, i) => {
|
|
228
|
+
let className = ['legend-item', `legend-text--${label.text.replace(' ', '').toLowerCase()}`]
|
|
229
|
+
let itemName = label.datum
|
|
230
|
+
|
|
231
|
+
// Filter excluded data keys from legend
|
|
232
|
+
if (config.exclusions.active && config.exclusions.keys?.includes(itemName)) {
|
|
233
|
+
return null
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
if (runtime.seriesLabels) {
|
|
237
|
+
let index = config.runtime.seriesLabelsAll.indexOf(itemName)
|
|
238
|
+
itemName = config.runtime.seriesKeys[index]
|
|
239
|
+
|
|
240
|
+
if (runtime?.forecastingSeriesKeys?.length > 0) {
|
|
241
|
+
itemName = label.text
|
|
242
|
+
}
|
|
227
243
|
}
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
244
|
+
|
|
245
|
+
if (seriesHighlight.length > 0 && false === seriesHighlight.includes(itemName)) {
|
|
246
|
+
className.push('inactive')
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
return (
|
|
250
|
+
<LegendItem
|
|
251
|
+
className={className.join(' ')}
|
|
252
|
+
tabIndex={0}
|
|
253
|
+
key={`legend-quantile-${i}`}
|
|
254
|
+
onKeyPress={e => {
|
|
255
|
+
if (e.key === 'Enter') {
|
|
256
|
+
highlight(label)
|
|
257
|
+
}
|
|
258
|
+
}}
|
|
259
|
+
onClick={() => {
|
|
241
260
|
highlight(label)
|
|
242
|
-
}
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
261
|
+
}}
|
|
262
|
+
>
|
|
263
|
+
{config.visualizationType === 'Line' && config.legend.lineMode ? (
|
|
264
|
+
<svg width={40} height={20}>
|
|
265
|
+
<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 : '')} />
|
|
266
|
+
</svg>
|
|
267
|
+
) : (
|
|
268
|
+
<div style={{ display: 'flex', flexDirection: 'column' }}>
|
|
269
|
+
<LegendCircle margin='0' fill={label.value} display={displayScale(label.datum)} />
|
|
270
|
+
<div style={{ marginTop: '2px', marginRight: '6px' }}>{label.icon}</div>
|
|
271
|
+
</div>
|
|
272
|
+
)}
|
|
273
|
+
|
|
274
|
+
<LegendLabel align='left' margin='0 0 0 4px'>
|
|
275
|
+
{label.text}
|
|
276
|
+
</LegendLabel>
|
|
277
|
+
</LegendItem>
|
|
278
|
+
)
|
|
279
|
+
})}
|
|
280
|
+
|
|
281
|
+
{highLightedLegendItems.map((bar, i) => {
|
|
282
|
+
// if duplicates only return first item
|
|
283
|
+
let className = 'legend-item'
|
|
284
|
+
let itemName = bar.legendLabel
|
|
285
|
+
|
|
286
|
+
if (!itemName) return false
|
|
287
|
+
if (seriesHighlight.length > 0 && false === seriesHighlight.includes(itemName)) {
|
|
288
|
+
className += ' inactive'
|
|
289
|
+
}
|
|
290
|
+
return (
|
|
291
|
+
<LegendItem
|
|
292
|
+
className={className}
|
|
293
|
+
tabIndex={0}
|
|
294
|
+
key={`legend-quantile-${i}`}
|
|
295
|
+
onKeyPress={e => {
|
|
296
|
+
if (e.key === 'Enter') {
|
|
297
|
+
highlight(bar.legendLabel)
|
|
298
|
+
}
|
|
299
|
+
}}
|
|
300
|
+
onClick={() => {
|
|
282
301
|
highlight(bar.legendLabel)
|
|
283
|
-
}
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
+
}}
|
|
303
|
+
>
|
|
304
|
+
<LegendCircle fill='transparent' borderColor={bar.color ? bar.color : `rgba(255, 102, 1)`} />{' '}
|
|
305
|
+
<LegendLabel align='left' margin='0 0 0 4px'>
|
|
306
|
+
{bar.legendLabel ? bar.legendLabel : bar.value}
|
|
307
|
+
</LegendLabel>
|
|
308
|
+
</LegendItem>
|
|
309
|
+
)
|
|
310
|
+
})}
|
|
311
|
+
{seriesHighlight.length > 0 && (
|
|
312
|
+
<button className={`legend-reset ${config.theme}`} onClick={labels => highlightReset(labels)} tabIndex={0}>
|
|
313
|
+
Reset
|
|
314
|
+
</button>
|
|
315
|
+
)}
|
|
316
|
+
</div>
|
|
317
|
+
<>
|
|
318
|
+
{config.preliminaryData.some(pd => pd.label) && (
|
|
319
|
+
<>
|
|
320
|
+
<hr></hr>
|
|
321
|
+
<div className={config.legend.singleRow && isBottomOrSmallViewport ? 'legend-container__inner bottom single-row' : 'dash-left'}>
|
|
322
|
+
{config.preliminaryData.map((pd, index) => {
|
|
323
|
+
return (
|
|
324
|
+
<div className='dash-container' key={index}>
|
|
325
|
+
{pd.label && (
|
|
326
|
+
<>
|
|
327
|
+
<div className='dash-inner'>{renderDashesOrCircle(pd.style)}</div>
|
|
328
|
+
<div style={{ marginLeft: '7px' }}>{pd.label}</div>
|
|
329
|
+
</>
|
|
330
|
+
)}
|
|
331
|
+
</div>
|
|
332
|
+
)
|
|
333
|
+
})}
|
|
334
|
+
</div>
|
|
335
|
+
</>
|
|
336
|
+
)}
|
|
337
|
+
</>
|
|
338
|
+
</>
|
|
302
339
|
)
|
|
303
340
|
}}
|
|
304
341
|
</LegendOrdinal>
|
|
@@ -15,3 +15,32 @@ export type LineChartProps = {
|
|
|
15
15
|
handleTooltipClick: Function
|
|
16
16
|
tooltipData: any
|
|
17
17
|
}
|
|
18
|
+
|
|
19
|
+
export interface PreliminaryDataItem {
|
|
20
|
+
style: string
|
|
21
|
+
type: string
|
|
22
|
+
column: string
|
|
23
|
+
value: string
|
|
24
|
+
seriesKey: string
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
export interface DataItem {
|
|
28
|
+
[key: string]: any
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
export interface Config {
|
|
32
|
+
preliminaryData: PreliminaryDataItem[] | []
|
|
33
|
+
}
|
|
34
|
+
export interface StyleProps {
|
|
35
|
+
preliminaryData: PreliminaryDataItem[]
|
|
36
|
+
rawData: DataItem[]
|
|
37
|
+
stroke: string
|
|
38
|
+
handleLineType: Function
|
|
39
|
+
lineType: string
|
|
40
|
+
seriesKey: 'string'
|
|
41
|
+
}
|
|
42
|
+
export interface Style {
|
|
43
|
+
stroke: string
|
|
44
|
+
strokeWidth: number
|
|
45
|
+
strokeDasharray: string
|
|
46
|
+
}
|
|
@@ -1,9 +1,10 @@
|
|
|
1
1
|
import React from 'react'
|
|
2
2
|
import chroma from 'chroma-js'
|
|
3
|
-
import { type ChartConfig } from '
|
|
3
|
+
import { type ChartConfig } from '../../../types/ChartConfig'
|
|
4
4
|
|
|
5
5
|
// todo: change this config obj to ChartConfig once its created
|
|
6
6
|
type LineChartCircleProps = {
|
|
7
|
+
circleData: object[]
|
|
7
8
|
config: ChartConfig
|
|
8
9
|
data: object[]
|
|
9
10
|
d?: Object
|
|
@@ -23,7 +24,7 @@ type LineChartCircleProps = {
|
|
|
23
24
|
}
|
|
24
25
|
|
|
25
26
|
const LineChartCircle = (props: LineChartCircleProps) => {
|
|
26
|
-
const { config, d, displayArea, seriesKey, tooltipData, xScale, yScale, colorScale, parseDate, yScaleRight, data } = props
|
|
27
|
+
const { config, d, displayArea, seriesKey, tooltipData, xScale, yScale, colorScale, parseDate, yScaleRight, data, circleData } = props
|
|
27
28
|
const { lineDatapointStyle } = config
|
|
28
29
|
const filtered = config?.series.filter(s => s.dataKey === seriesKey)?.[0]
|
|
29
30
|
// If we're not showing the circle, simply return
|
|
@@ -46,8 +47,11 @@ const LineChartCircle = (props: LineChartCircleProps) => {
|
|
|
46
47
|
}
|
|
47
48
|
return color
|
|
48
49
|
}
|
|
49
|
-
|
|
50
50
|
if (lineDatapointStyle === 'always show') {
|
|
51
|
+
const isMatch = circleData?.some(cd => cd[config.xAxis.dataKey] === d[config.xAxis.dataKey] && cd[seriesKey] === d[seriesKey])
|
|
52
|
+
if (isMatch) {
|
|
53
|
+
return <></>
|
|
54
|
+
}
|
|
51
55
|
return (
|
|
52
56
|
<circle
|
|
53
57
|
cx={config.xAxis.type === 'categorical' ? xScale(d[config.xAxis.dataKey]) : xScale(parseDate(d[config.xAxis.dataKey]))}
|
|
@@ -84,6 +88,11 @@ const LineChartCircle = (props: LineChartCircleProps) => {
|
|
|
84
88
|
let seriesIndex = config.runtime.seriesLabelsAll.indexOf(hoveredXValue)
|
|
85
89
|
|
|
86
90
|
if (isNaN(hoveredSeriesValue)) return <></>
|
|
91
|
+
const isMatch = circleData?.some(cd => cd[config.xAxis.dataKey] === hoveredXValue)
|
|
92
|
+
|
|
93
|
+
if (isMatch) {
|
|
94
|
+
return <></>
|
|
95
|
+
}
|
|
87
96
|
return (
|
|
88
97
|
<circle
|
|
89
98
|
cx={config.xAxis.type === 'categorical' ? xScale(hoveredXValue) : xScale(parseDate(hoveredXValue))}
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import { type PreliminaryDataItem, DataItem, StyleProps, Style } from './LineChartProps'
|
|
2
|
+
|
|
3
|
+
export const createStyles = (props: StyleProps): Style[] => {
|
|
4
|
+
const { preliminaryData, rawData, stroke, handleLineType, lineType, seriesKey } = props
|
|
5
|
+
|
|
6
|
+
const validPreliminaryData: PreliminaryDataItem[] = preliminaryData.filter(pd => pd.seriesKey && pd.column && pd.value && pd.type && pd.style)
|
|
7
|
+
const getMatchingPd = (point: DataItem): PreliminaryDataItem => validPreliminaryData.find(pd => pd.seriesKey === seriesKey && point[pd.column] === pd.value && pd.type === 'effect' && pd.style !== 'Open Circles')
|
|
8
|
+
|
|
9
|
+
let styles: Style[] = []
|
|
10
|
+
const createStyle = (lineStyle): Style => ({
|
|
11
|
+
stroke: stroke,
|
|
12
|
+
strokeWidth: 2,
|
|
13
|
+
strokeDasharray: lineStyle
|
|
14
|
+
})
|
|
15
|
+
|
|
16
|
+
rawData.forEach((d, index) => {
|
|
17
|
+
let matchingPd: PreliminaryDataItem = getMatchingPd(d)
|
|
18
|
+
let style: Style = matchingPd ? createStyle(handleLineType(matchingPd.style)) : createStyle(handleLineType(lineType))
|
|
19
|
+
|
|
20
|
+
styles.push(style)
|
|
21
|
+
|
|
22
|
+
// If matchingPd exists, update the previous style if there is a previous element
|
|
23
|
+
if (matchingPd && index > 0) {
|
|
24
|
+
styles[index - 1] = createStyle(handleLineType(matchingPd.style))
|
|
25
|
+
}
|
|
26
|
+
})
|
|
27
|
+
return styles as Style[]
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
export const filterCircles = (preliminaryData: PreliminaryDataItem[], data: DataItem[], seriesKey: string): DataItem[] => {
|
|
31
|
+
// Filter and map preliminaryData to get circlesFiltered
|
|
32
|
+
const circlesFiltered = preliminaryData.filter(item => item.style === 'Open Circles' && item.type === 'effect').map(item => ({ column: item.column, value: item.value, seriesKey: item.seriesKey }))
|
|
33
|
+
|
|
34
|
+
let filteredData: DataItem[] = []
|
|
35
|
+
|
|
36
|
+
// Process data to find matching items
|
|
37
|
+
data.forEach(item => {
|
|
38
|
+
if (circlesFiltered.some(d => item[d.column] === d.value && d.seriesKey === seriesKey)) {
|
|
39
|
+
// Add current item
|
|
40
|
+
filteredData.push(item)
|
|
41
|
+
}
|
|
42
|
+
})
|
|
43
|
+
|
|
44
|
+
return filteredData
|
|
45
|
+
}
|
|
@@ -2,13 +2,14 @@ import React, { useContext } from 'react'
|
|
|
2
2
|
|
|
3
3
|
import * as allCurves from '@visx/curve'
|
|
4
4
|
import { Group } from '@visx/group'
|
|
5
|
-
import { LinePath, Bar } from '@visx/shape'
|
|
5
|
+
import { LinePath, Bar, SplitLinePath } from '@visx/shape'
|
|
6
6
|
import { Text } from '@visx/text'
|
|
7
7
|
|
|
8
8
|
import ErrorBoundary from '@cdc/core/components/ErrorBoundary'
|
|
9
9
|
import ConfigContext from '../../ConfigContext'
|
|
10
10
|
import useRightAxis from '../../hooks/useRightAxis'
|
|
11
|
-
import
|
|
11
|
+
import { filterCircles, createStyles } from './helpers'
|
|
12
|
+
import LineChartCircle from './components/LineChart.Circle'
|
|
12
13
|
|
|
13
14
|
// types
|
|
14
15
|
import { type ChartContext } from '../../types/ChartContext'
|
|
@@ -41,6 +42,7 @@ const LineChart = (props: LineChartProps) => {
|
|
|
41
42
|
tableData,
|
|
42
43
|
transformedData: data,
|
|
43
44
|
updateConfig,
|
|
45
|
+
rawData
|
|
44
46
|
} = useContext<ChartContext>(ConfigContext)
|
|
45
47
|
const { yScaleRight } = useRightAxis({ config, yMax, data, updateConfig })
|
|
46
48
|
if (!handleTooltipMouseOver) return
|
|
@@ -57,8 +59,10 @@ const LineChart = (props: LineChartProps) => {
|
|
|
57
59
|
let lineType = config.series.filter(item => item.dataKey === seriesKey)[0].type
|
|
58
60
|
const seriesData = config.series.filter(item => item.dataKey === seriesKey)
|
|
59
61
|
const seriesAxis = seriesData[0].axis ? seriesData[0].axis : 'left'
|
|
60
|
-
|
|
61
62
|
let displayArea = legend.behavior === 'highlight' || seriesHighlight.length === 0 || seriesHighlight.indexOf(seriesKey) !== -1
|
|
63
|
+
const circleData = filterCircles(config.preliminaryData, rawData, seriesKey)
|
|
64
|
+
// styles for preliminary Data items
|
|
65
|
+
let styles = createStyles({ preliminaryData: config.preliminaryData, rawData, stroke: colorScale(config.runtime.seriesLabels[seriesKey]), handleLineType, lineType, seriesKey })
|
|
62
66
|
|
|
63
67
|
return (
|
|
64
68
|
<Group
|
|
@@ -93,6 +97,7 @@ const LineChart = (props: LineChartProps) => {
|
|
|
93
97
|
|
|
94
98
|
{(lineDatapointStyle === 'hidden' || lineDatapointStyle === 'always show') && (
|
|
95
99
|
<LineChartCircle
|
|
100
|
+
circleData={circleData}
|
|
96
101
|
data={data}
|
|
97
102
|
d={d}
|
|
98
103
|
config={config}
|
|
@@ -114,7 +119,7 @@ const LineChart = (props: LineChartProps) => {
|
|
|
114
119
|
})}
|
|
115
120
|
<>
|
|
116
121
|
{lineDatapointStyle === 'hover' && (
|
|
117
|
-
<LineChartCircle data={data} config={config} seriesKey={seriesKey} displayArea={displayArea} tooltipData={tooltipData} xScale={xScale} yScale={yScale} colorScale={colorScale} parseDate={parseDate} yScaleRight={yScaleRight} seriesAxis={seriesAxis} />
|
|
122
|
+
<LineChartCircle circleData={circleData} data={data} config={config} seriesKey={seriesKey} displayArea={displayArea} tooltipData={tooltipData} xScale={xScale} yScale={yScale} colorScale={colorScale} parseDate={parseDate} yScaleRight={yScaleRight} seriesAxis={seriesAxis} />
|
|
118
123
|
)}
|
|
119
124
|
</>
|
|
120
125
|
{/* STANDARD LINE */}
|
|
@@ -122,15 +127,22 @@ const LineChart = (props: LineChartProps) => {
|
|
|
122
127
|
curve={allCurves[seriesData[0].lineType]}
|
|
123
128
|
data={data}
|
|
124
129
|
x={d => xScale(getXAxisData(d))}
|
|
125
|
-
y={d => (seriesAxis === 'Right' ? yScaleRight(getYAxisData(d, seriesKey)) : yScale(getYAxisData(d, seriesKey)))}
|
|
126
|
-
stroke={colorScale
|
|
130
|
+
y={d => (seriesAxis === 'Right' ? yScaleRight(getYAxisData(d, seriesKey)) : yScale(Number(getYAxisData(d, seriesKey))))}
|
|
131
|
+
stroke={colorScale(config.runtime.seriesLabels[seriesKey])}
|
|
127
132
|
strokeWidth={2}
|
|
128
133
|
strokeOpacity={1}
|
|
134
|
+
shapeRendering='geometricPrecision'
|
|
129
135
|
strokeDasharray={lineType ? handleLineType(lineType) : 0}
|
|
130
136
|
defined={(item, i) => {
|
|
131
137
|
return item[seriesKey] !== '' && item[seriesKey] !== null && item[seriesKey] !== undefined
|
|
132
138
|
}}
|
|
133
139
|
/>
|
|
140
|
+
|
|
141
|
+
{/* circles for preliminaryData data */}
|
|
142
|
+
{circleData.map((d, i) => {
|
|
143
|
+
return <circle key={i} cx={xScale(getXAxisData(d))} cy={yScale(Number(getYAxisData(d, seriesKey)))} r={6} strokeWidth={2} stroke={colorScale ? colorScale(config.runtime.seriesLabels[seriesKey]) : '#000'} fill='#fff' />
|
|
144
|
+
})}
|
|
145
|
+
|
|
134
146
|
{/* ANIMATED LINE */}
|
|
135
147
|
{config.animate && (
|
|
136
148
|
<LinePath
|
|
@@ -138,14 +150,14 @@ const LineChart = (props: LineChartProps) => {
|
|
|
138
150
|
curve={seriesData.lineType}
|
|
139
151
|
data={data}
|
|
140
152
|
x={d => xScale(getXAxisData(d))}
|
|
141
|
-
y={d => (seriesAxis === 'Right' ? yScaleRight(getYAxisData(d, seriesKey)) : yScale(getYAxisData(d, seriesKey)))}
|
|
153
|
+
y={d => (seriesAxis === 'Right' ? yScaleRight(getYAxisData(d, seriesKey)) : yScale(Number(getYAxisData(d, seriesKey))))}
|
|
142
154
|
stroke='#fff'
|
|
143
155
|
strokeWidth={3}
|
|
144
156
|
strokeOpacity={1}
|
|
145
157
|
shapeRendering='geometricPrecision'
|
|
146
158
|
strokeDasharray={lineType ? handleLineType(lineType) : 0}
|
|
147
159
|
defined={(item, i) => {
|
|
148
|
-
return
|
|
160
|
+
return item[seriesKey] !== '' && item[seriesKey] !== null && item[seriesKey] !== undefined
|
|
149
161
|
}}
|
|
150
162
|
/>
|
|
151
163
|
)}
|