@cdc/chart 4.23.6 → 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 +25700 -26736
- package/examples/feature/__data__/planet-example-data.json +1 -1
- package/examples/feature/combo/right-issues.json +1 -1
- package/examples/feature/forecasting/combo-forecasting.json +72 -46
- package/examples/feature/forecasting/effective_reproduction.json +57 -8
- package/examples/feature/forecasting/forecasting.json +12 -3
- package/examples/feature/line/line-chart.json +11 -11
- package/examples/gallery/bar-chart-vertical/vertical-bar-chart-categorical.json +167 -20
- package/index.html +6 -6
- package/package.json +2 -2
- package/src/CdcChart.jsx +56 -15
- package/src/components/AreaChart.jsx +22 -133
- package/src/components/BarChart.jsx +25 -15
- package/src/components/DataTable.jsx +5 -2
- package/src/components/EditorPanel.jsx +97 -77
- package/src/components/Forecasting.jsx +23 -86
- package/src/components/Legend.jsx +10 -8
- package/src/components/LineChart.jsx +31 -6
- package/src/components/LinearChart.jsx +408 -126
- package/src/components/Series.jsx +40 -4
- package/src/data/initial-state.js +7 -3
- package/src/hooks/useMinMax.js +3 -2
- package/src/hooks/useRightAxis.js +2 -1
- package/src/scss/main.scss +4 -17
- package/LICENSE +0 -201
|
@@ -412,7 +412,18 @@ const EditorPanel = () => {
|
|
|
412
412
|
|
|
413
413
|
const addNewSeries = seriesKey => {
|
|
414
414
|
let newSeries = config.series ? [...config.series] : []
|
|
415
|
-
|
|
415
|
+
let forecastingStages = Array.from(new Set(data.map(item => item[seriesKey])))
|
|
416
|
+
let forecastingStageArr = []
|
|
417
|
+
|
|
418
|
+
forecastingStages.forEach(stage => {
|
|
419
|
+
forecastingStageArr.push({ key: stage })
|
|
420
|
+
})
|
|
421
|
+
|
|
422
|
+
if (config.visualizationType === 'Forecasting') {
|
|
423
|
+
newSeries.push({ dataKey: seriesKey, type: config.visualizationType, stages: forecastingStageArr, stageColumn: seriesKey, axis: 'Left', tooltip: true })
|
|
424
|
+
} else {
|
|
425
|
+
newSeries.push({ dataKey: seriesKey, type: config.visualizationType, axis: 'Left', tooltip: true })
|
|
426
|
+
}
|
|
416
427
|
updateConfig({ ...config, series: newSeries }) // left axis series keys
|
|
417
428
|
}
|
|
418
429
|
|
|
@@ -624,16 +635,6 @@ const EditorPanel = () => {
|
|
|
624
635
|
})
|
|
625
636
|
}, [config.orientation])
|
|
626
637
|
|
|
627
|
-
// Set paired bars to be horizontal, even though that option doesn't display
|
|
628
|
-
useEffect(() => {
|
|
629
|
-
if (config.visualizationType === 'Paired Bar') {
|
|
630
|
-
updateConfig({
|
|
631
|
-
...config,
|
|
632
|
-
orientation: 'horizontal'
|
|
633
|
-
})
|
|
634
|
-
}
|
|
635
|
-
}, []) // eslint-disable-line
|
|
636
|
-
|
|
637
638
|
useEffect(() => {
|
|
638
639
|
if (config.orientation === 'horizontal') {
|
|
639
640
|
updateConfig({
|
|
@@ -643,13 +644,6 @@ const EditorPanel = () => {
|
|
|
643
644
|
}
|
|
644
645
|
}, [config.isLollipopChart, config.lollipopShape]) // eslint-disable-line
|
|
645
646
|
|
|
646
|
-
/// temporary force orientation untill we support Vartical deviaton bar
|
|
647
|
-
useEffect(() => {
|
|
648
|
-
if (config.visualizationType === 'Deviation Bar') {
|
|
649
|
-
updateConfig({ ...config, orientation: 'horizontal' })
|
|
650
|
-
}
|
|
651
|
-
}, [config.visualizationType])
|
|
652
|
-
|
|
653
647
|
const ExclusionsList = useCallback(() => {
|
|
654
648
|
const exclusions = [...config.exclusions.keys]
|
|
655
649
|
return (
|
|
@@ -671,8 +665,8 @@ const EditorPanel = () => {
|
|
|
671
665
|
}, [config]) // eslint-disable-line
|
|
672
666
|
|
|
673
667
|
const visSupportsTooltipLines = () => {
|
|
674
|
-
|
|
675
|
-
if (config.visualizationType
|
|
668
|
+
const chartsWithTooltipGuides = ['Combo', 'Forecasting', 'Area Chart', 'Line', 'Bar']
|
|
669
|
+
if (chartsWithTooltipGuides.includes(config.visualizationType)) return true
|
|
676
670
|
return false
|
|
677
671
|
}
|
|
678
672
|
|
|
@@ -831,7 +825,7 @@ const EditorPanel = () => {
|
|
|
831
825
|
'Box Plot',
|
|
832
826
|
'Combo',
|
|
833
827
|
'Deviation Bar',
|
|
834
|
-
|
|
828
|
+
'Forecasting',
|
|
835
829
|
'Line',
|
|
836
830
|
'Paired Bar',
|
|
837
831
|
'Pie',
|
|
@@ -1345,47 +1339,6 @@ const EditorPanel = () => {
|
|
|
1345
1339
|
<TextField type='text' value={config.boxplot.labels.outliers} fieldName='outliers' section='boxplot' subsection='labels' label='Outliers' updateField={updateField} />
|
|
1346
1340
|
{/* values */}
|
|
1347
1341
|
<TextField type='text' value={config.boxplot.labels.values} fieldName='values' section='boxplot' subsection='labels' label='Values' updateField={updateField} />
|
|
1348
|
-
<br />
|
|
1349
|
-
<h4 style={{ fontSize: '18px' }}>Percentages for Quartiles</h4>
|
|
1350
|
-
<TextField
|
|
1351
|
-
type='number'
|
|
1352
|
-
value={config.boxplot.firstQuartilePercentage ? config.boxplot.firstQuartilePercentage : 25}
|
|
1353
|
-
fieldName='firstQuartilePercentage'
|
|
1354
|
-
section='boxplot'
|
|
1355
|
-
label='Lower Quartile'
|
|
1356
|
-
max={100}
|
|
1357
|
-
updateField={updateField}
|
|
1358
|
-
tooltip={
|
|
1359
|
-
<Tooltip style={{ textTransform: 'none' }}>
|
|
1360
|
-
<Tooltip.Target>
|
|
1361
|
-
<Icon display='question' style={{ marginLeft: '0.5rem' }} />
|
|
1362
|
-
</Tooltip.Target>
|
|
1363
|
-
<Tooltip.Content>
|
|
1364
|
-
<p>Represented by bottom line of box. 25% of data are lower.</p>
|
|
1365
|
-
</Tooltip.Content>
|
|
1366
|
-
</Tooltip>
|
|
1367
|
-
}
|
|
1368
|
-
/>
|
|
1369
|
-
|
|
1370
|
-
<TextField
|
|
1371
|
-
type='number'
|
|
1372
|
-
value={config.boxplot.thirdQuartilePercentage ? config.boxplot.thirdQuartilePercentage : 75}
|
|
1373
|
-
fieldName='thirdQuartilePercentage'
|
|
1374
|
-
label='Upper Quartile'
|
|
1375
|
-
section='boxplot'
|
|
1376
|
-
max={100}
|
|
1377
|
-
updateField={updateField}
|
|
1378
|
-
tooltip={
|
|
1379
|
-
<Tooltip style={{ textTransform: 'none' }}>
|
|
1380
|
-
<Tooltip.Target>
|
|
1381
|
-
<Icon display='question' style={{ marginLeft: '0.5rem' }} />
|
|
1382
|
-
</Tooltip.Target>
|
|
1383
|
-
<Tooltip.Content>
|
|
1384
|
-
<p>Represented by top line of box. 25% of data are higher.</p>
|
|
1385
|
-
</Tooltip.Content>
|
|
1386
|
-
</Tooltip>
|
|
1387
|
-
}
|
|
1388
|
-
/>
|
|
1389
1342
|
</AccordionItemPanel>
|
|
1390
1343
|
</AccordionItem>
|
|
1391
1344
|
)}
|
|
@@ -1444,10 +1397,35 @@ const EditorPanel = () => {
|
|
|
1444
1397
|
</Tooltip>
|
|
1445
1398
|
}
|
|
1446
1399
|
/>
|
|
1400
|
+
{config.orientation === 'horizontal' && config.visualizationType !== 'Paired Bar' && <CheckBox value={config.isResponsiveTicks} fieldName='isResponsiveTicks' label='Use Responsive Ticks' updateField={updateField} />}
|
|
1401
|
+
{(config.orientation === 'vertical' || !config.isResponsiveTicks) && <TextField value={config.yAxis.tickRotation} type='number' min='0' section='yAxis' fieldName='tickRotation' label='Tick rotation (Degrees)' className='number-narrow' updateField={updateField} />}
|
|
1402
|
+
{config.isResponsiveTicks && config.orientation === 'horizontal' && config.visualizationType !== 'Paired Bar' && (
|
|
1403
|
+
<TextField
|
|
1404
|
+
value={config.xAxis.maxTickRotation}
|
|
1405
|
+
type='number'
|
|
1406
|
+
min='0'
|
|
1407
|
+
section='xAxis'
|
|
1408
|
+
fieldName='maxTickRotation'
|
|
1409
|
+
label='Max Tick Rotation'
|
|
1410
|
+
className='number-narrow'
|
|
1411
|
+
updateField={updateField}
|
|
1412
|
+
tooltip={
|
|
1413
|
+
<Tooltip style={{ textTransform: 'none' }}>
|
|
1414
|
+
<Tooltip.Target>
|
|
1415
|
+
<Icon display='question' style={{ marginLeft: '0.5rem', display: 'inline-block', whiteSpace: 'nowrap' }} />
|
|
1416
|
+
</Tooltip.Target>
|
|
1417
|
+
<Tooltip.Content>
|
|
1418
|
+
<p>Degrees ticks will be rotated if values overlap, especially in smaller viewports.</p>
|
|
1419
|
+
</Tooltip.Content>
|
|
1420
|
+
</Tooltip>
|
|
1421
|
+
}
|
|
1422
|
+
/>
|
|
1423
|
+
)}
|
|
1424
|
+
|
|
1447
1425
|
{/* Hiding this for now, not interested in moving the axis lines away from chart comp. right now. */}
|
|
1448
1426
|
{/* <TextField value={config.yAxis.axisPadding} type='number' max={10} min={0} section='yAxis' fieldName='axisPadding' label={'Axis Padding'} className='number-narrow' updateField={updateField} /> */}
|
|
1449
1427
|
{config.orientation === 'horizontal' && <TextField value={config.xAxis.labelOffset} section='xAxis' fieldName='labelOffset' label='Label offset' type='number' className='number-narrow' updateField={updateField} />}
|
|
1450
|
-
{config.orientation !== 'horizontal' && <CheckBox value={config.yAxis.gridLines} section='yAxis' fieldName='gridLines' label='
|
|
1428
|
+
{config.orientation !== 'horizontal' && <CheckBox value={config.yAxis.gridLines} section='yAxis' fieldName='gridLines' label='Show Gridlines' updateField={updateField} />}
|
|
1451
1429
|
<CheckBox value={config.yAxis.enablePadding} section='yAxis' fieldName='enablePadding' label='Add Padding to Value Axis Scale' updateField={updateField} />
|
|
1452
1430
|
{config.visualizationSubType === 'regular' && <CheckBox value={config.useLogScale} fieldName='useLogScale' label='use logarithmic scale' updateField={updateField} />}
|
|
1453
1431
|
</>
|
|
@@ -1524,7 +1502,7 @@ const EditorPanel = () => {
|
|
|
1524
1502
|
<>
|
|
1525
1503
|
<TextField value={config.xAxis.target} section='xAxis' fieldName='target' type='number' label='Deviation point' placeholder='Auto' updateField={updateField} />
|
|
1526
1504
|
<TextField value={config.xAxis.targetLabel || 'Target'} section='xAxis' fieldName='targetLabel' type='text' label='Deviation point Label' updateField={updateField} />
|
|
1527
|
-
<CheckBox value={config.xAxis.showTargetLabel} section='xAxis' fieldName='showTargetLabel' label='
|
|
1505
|
+
<CheckBox value={config.xAxis.showTargetLabel} section='xAxis' fieldName='showTargetLabel' label='Show Deviation point label' updateField={updateField} />
|
|
1528
1506
|
</>
|
|
1529
1507
|
)}
|
|
1530
1508
|
</>
|
|
@@ -1546,10 +1524,10 @@ const EditorPanel = () => {
|
|
|
1546
1524
|
{/* start: anchors */}
|
|
1547
1525
|
{visHasAnchors() && config.orientation !== 'horizontal' && (
|
|
1548
1526
|
<div className='edit-block'>
|
|
1549
|
-
<
|
|
1527
|
+
<span className='edit-label column-heading'>Anchors</span>
|
|
1550
1528
|
<Accordion allowZeroExpanded>
|
|
1551
1529
|
{config.yAxis?.anchors?.map((anchor, index) => (
|
|
1552
|
-
<AccordionItem className='series-item series-item--chart'>
|
|
1530
|
+
<AccordionItem className='series-item series-item--chart' key={`yaxis-anchors-2-${index}`}>
|
|
1553
1531
|
<AccordionItemHeading className='series-item__title'>
|
|
1554
1532
|
<>
|
|
1555
1533
|
<AccordionItemButton className={'accordion__button accordion__button'}>
|
|
@@ -1672,10 +1650,10 @@ const EditorPanel = () => {
|
|
|
1672
1650
|
|
|
1673
1651
|
{visHasAnchors() && config.orientation === 'horizontal' && (
|
|
1674
1652
|
<div className='edit-block'>
|
|
1675
|
-
<
|
|
1653
|
+
<span className='edit-label column-heading'>Anchors</span>
|
|
1676
1654
|
<Accordion allowZeroExpanded>
|
|
1677
1655
|
{config.xAxis?.anchors?.map((anchor, index) => (
|
|
1678
|
-
<AccordionItem className='series-item series-item--chart'>
|
|
1656
|
+
<AccordionItem className='series-item series-item--chart' key={`xaxis-anchors-${index}`}>
|
|
1679
1657
|
<AccordionItemHeading className='series-item__title'>
|
|
1680
1658
|
<>
|
|
1681
1659
|
<AccordionItemButton className={'accordion__button accordion__button'}>
|
|
@@ -2060,8 +2038,31 @@ const EditorPanel = () => {
|
|
|
2060
2038
|
<TextField value={config.dataFormat.bottomRoundTo} type='number' section='dataFormat' fieldName='bottomRoundTo' label='Round to decimal point' className='number-narrow' updateField={updateField} min={0} />
|
|
2061
2039
|
</>
|
|
2062
2040
|
)}
|
|
2041
|
+
{config.orientation === 'vertical' && config.visualizationType !== 'Paired Bar' && <CheckBox value={config.isResponsiveTicks} fieldName='isResponsiveTicks' label='Use Responsive Ticks' updateField={updateField} />}
|
|
2042
|
+
{(config.orientation === 'horizontal' || !config.isResponsiveTicks) && <TextField value={config.xAxis.tickRotation} type='number' min='0' section='xAxis' fieldName='tickRotation' label='Tick rotation (Degrees)' className='number-narrow' updateField={updateField} />}
|
|
2043
|
+
{config.orientation === 'vertical' && config.isResponsiveTicks && config.visualizationType !== 'Paired Bar' && (
|
|
2044
|
+
<TextField
|
|
2045
|
+
value={config.xAxis.maxTickRotation}
|
|
2046
|
+
type='number'
|
|
2047
|
+
min='0'
|
|
2048
|
+
section='xAxis'
|
|
2049
|
+
fieldName='maxTickRotation'
|
|
2050
|
+
label='Max Tick Rotation'
|
|
2051
|
+
className='number-narrow'
|
|
2052
|
+
updateField={updateField}
|
|
2053
|
+
tooltip={
|
|
2054
|
+
<Tooltip style={{ textTransform: 'none' }}>
|
|
2055
|
+
<Tooltip.Target>
|
|
2056
|
+
<Icon display='question' style={{ marginLeft: '0.5rem', display: 'inline-block', whiteSpace: 'nowrap' }} />
|
|
2057
|
+
</Tooltip.Target>
|
|
2058
|
+
<Tooltip.Content>
|
|
2059
|
+
<p>Degrees ticks will be rotated if values overlap, especially in smaller viewports.</p>
|
|
2060
|
+
</Tooltip.Content>
|
|
2061
|
+
</Tooltip>
|
|
2062
|
+
}
|
|
2063
|
+
/>
|
|
2064
|
+
)}
|
|
2063
2065
|
|
|
2064
|
-
{config.yAxis.labelPlacement !== 'Below Bar' && <TextField value={config.xAxis.tickRotation} type='number' min='0' section='xAxis' fieldName='tickRotation' label='Tick rotation (Degrees)' className='number-narrow' updateField={updateField} />}
|
|
2065
2066
|
{config.orientation === 'horizontal' ? (
|
|
2066
2067
|
<>
|
|
2067
2068
|
<CheckBox value={config.yAxis.hideAxis} section='yAxis' fieldName='hideAxis' label='Hide Axis' updateField={updateField} />
|
|
@@ -2167,10 +2168,10 @@ const EditorPanel = () => {
|
|
|
2167
2168
|
{/* anchors */}
|
|
2168
2169
|
{visHasAnchors() && config.orientation !== 'horizontal' && (
|
|
2169
2170
|
<div className='edit-block'>
|
|
2170
|
-
<
|
|
2171
|
+
<span className='edit-label column-heading'>Anchors</span>
|
|
2171
2172
|
<Accordion allowZeroExpanded>
|
|
2172
2173
|
{config.xAxis?.anchors?.map((anchor, index) => (
|
|
2173
|
-
<AccordionItem className='series-item series-item--chart'>
|
|
2174
|
+
<AccordionItem className='series-item series-item--chart' key={`xaxis-anchors-2-${index}`}>
|
|
2174
2175
|
<AccordionItemHeading className='series-item__title'>
|
|
2175
2176
|
<>
|
|
2176
2177
|
<AccordionItemButton className={'accordion__button accordion__button'}>
|
|
@@ -2293,10 +2294,10 @@ const EditorPanel = () => {
|
|
|
2293
2294
|
|
|
2294
2295
|
{visHasAnchors() && config.orientation === 'horizontal' && (
|
|
2295
2296
|
<div className='edit-block'>
|
|
2296
|
-
<
|
|
2297
|
+
<span className='edit-label column-heading'>Anchors</span>
|
|
2297
2298
|
<Accordion allowZeroExpanded>
|
|
2298
2299
|
{config.yAxis?.anchors?.map((anchor, index) => (
|
|
2299
|
-
<AccordionItem className='series-item series-item--chart'>
|
|
2300
|
+
<AccordionItem className='series-item series-item--chart' key={`accordion-yaxis-anchors-${index}`}>
|
|
2300
2301
|
<AccordionItemHeading className='series-item__title'>
|
|
2301
2302
|
<>
|
|
2302
2303
|
<AccordionItemButton className={'accordion__button accordion__button'}>
|
|
@@ -2483,9 +2484,9 @@ const EditorPanel = () => {
|
|
|
2483
2484
|
<label className='checkbox'>
|
|
2484
2485
|
<input
|
|
2485
2486
|
type='checkbox'
|
|
2486
|
-
checked={config.columns[val].
|
|
2487
|
+
checked={config.columns[val].commas}
|
|
2487
2488
|
onChange={event => {
|
|
2488
|
-
editColumn(val, '
|
|
2489
|
+
editColumn(val, 'commas', event.target.checked)
|
|
2489
2490
|
}}
|
|
2490
2491
|
/>
|
|
2491
2492
|
<span className='edit-label'>Add Commas to Numbers</span>
|
|
@@ -2500,7 +2501,7 @@ const EditorPanel = () => {
|
|
|
2500
2501
|
editColumn(val, 'dataTable', event.target.checked)
|
|
2501
2502
|
}}
|
|
2502
2503
|
/>
|
|
2503
|
-
<span className='edit-label'>
|
|
2504
|
+
<span className='edit-label'>Show in Data Table</span>
|
|
2504
2505
|
</label>
|
|
2505
2506
|
</li>
|
|
2506
2507
|
{/* disable for now */}
|
|
@@ -2996,7 +2997,7 @@ const EditorPanel = () => {
|
|
|
2996
2997
|
|
|
2997
2998
|
{config.visualizationType === 'Spark Line' && (
|
|
2998
2999
|
<div className='cove-accordion__panel-section checkbox-group'>
|
|
2999
|
-
<CheckBox value={config.visual?.border} section='visual' fieldName='border' label='
|
|
3000
|
+
<CheckBox value={config.visual?.border} section='visual' fieldName='border' label='Show Border' updateField={updateField} />
|
|
3000
3001
|
<CheckBox value={config.visual?.borderColorTheme} section='visual' fieldName='borderColorTheme' label='Use Border Color Theme' updateField={updateField} />
|
|
3001
3002
|
<CheckBox value={config.visual?.accent} section='visual' fieldName='accent' label='Use Accent Style' updateField={updateField} />
|
|
3002
3003
|
<CheckBox value={config.visual?.background} section='visual' fieldName='background' label='Use Theme Background Color' updateField={updateField} />
|
|
@@ -3015,6 +3016,25 @@ const EditorPanel = () => {
|
|
|
3015
3016
|
<CheckBox value={config.visual.horizontalHoverLine} fieldName='horizontalHoverLine' section='visual' label='Horizontal Hover Line' updateField={updateField} />
|
|
3016
3017
|
</>
|
|
3017
3018
|
)}
|
|
3019
|
+
|
|
3020
|
+
{
|
|
3021
|
+
<label>
|
|
3022
|
+
<span className='edit-label column-heading'>Tooltip Opacity</span>
|
|
3023
|
+
<input
|
|
3024
|
+
type='number'
|
|
3025
|
+
value={config.tooltips.opacity ? config.tooltips.opacity : 100}
|
|
3026
|
+
onChange={e =>
|
|
3027
|
+
updateConfig({
|
|
3028
|
+
...config,
|
|
3029
|
+
tooltips: {
|
|
3030
|
+
...config.tooltips,
|
|
3031
|
+
opacity: e.target.value
|
|
3032
|
+
}
|
|
3033
|
+
})
|
|
3034
|
+
}
|
|
3035
|
+
/>
|
|
3036
|
+
</label>
|
|
3037
|
+
}
|
|
3018
3038
|
</AccordionItemPanel>
|
|
3019
3039
|
</AccordionItem>
|
|
3020
3040
|
{/* Spark Line has no data table */}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import React, { useContext
|
|
1
|
+
import React, { useContext } from 'react'
|
|
2
2
|
|
|
3
3
|
// cdc
|
|
4
4
|
import ConfigContext from '../ConfigContext'
|
|
@@ -6,43 +6,21 @@ import ErrorBoundary from '@cdc/core/components/ErrorBoundary'
|
|
|
6
6
|
import { colorPalettesChart } from '@cdc/core/data/colorPalettes'
|
|
7
7
|
|
|
8
8
|
// visx & d3
|
|
9
|
-
import { useTooltipInPortal, defaultStyles } from '@visx/tooltip'
|
|
10
9
|
import { curveMonotoneX } from '@visx/curve'
|
|
11
|
-
import { Bar, Area, LinePath
|
|
10
|
+
import { Bar, Area, LinePath } from '@visx/shape'
|
|
12
11
|
import { Group } from '@visx/group'
|
|
13
12
|
|
|
14
|
-
const Forecasting = ({ xScale, yScale, height, width,
|
|
15
|
-
const { transformedData: data, rawData, config, seriesHighlight
|
|
13
|
+
const Forecasting = ({ xScale, yScale, height, width, handleTooltipMouseOver, handleTooltipMouseOff }) => {
|
|
14
|
+
const { transformedData: data, rawData, config, seriesHighlight } = useContext(ConfigContext)
|
|
16
15
|
const { xAxis, yAxis, legend, runtime } = config
|
|
17
16
|
const DEBUG = false
|
|
18
17
|
|
|
19
|
-
// sets the portal x/y for where tooltips should appear on the page.
|
|
20
|
-
const [chartPosition, setChartPosition] = useState(null)
|
|
21
|
-
useEffect(() => {
|
|
22
|
-
setChartPosition(chartRef.current.getBoundingClientRect())
|
|
23
|
-
}, [chartRef])
|
|
24
|
-
|
|
25
|
-
// a unique id is needed for tooltips.
|
|
26
|
-
const tooltip_id = `cdc-open-viz-tooltip-${config.runtime.uniqueId}`
|
|
27
|
-
|
|
28
|
-
// it appears we need to use TooltipInPortal.
|
|
29
|
-
const { TooltipInPortal } = useTooltipInPortal({
|
|
30
|
-
detectBounds: true,
|
|
31
|
-
// when tooltip containers are scrolled, this will correctly update the Tooltip position
|
|
32
|
-
scroll: true
|
|
33
|
-
})
|
|
34
|
-
|
|
35
|
-
const TooltipListItem = ({ item }) => {
|
|
36
|
-
const [label, value] = item
|
|
37
|
-
return label === config.xAxis.dataKey ? `${label}: ${value}` : `${label}: ${formatNumber(value, 'left')}`
|
|
38
|
-
}
|
|
39
|
-
|
|
40
18
|
return (
|
|
41
19
|
data && (
|
|
42
20
|
<ErrorBoundary component='ForecastingChart'>
|
|
43
|
-
<Group className='forecasting-items' key='forecasting-items-wrapper' left={yAxis.size}>
|
|
21
|
+
<Group className='forecasting-items' key='forecasting-items-wrapper' left={Number(yAxis.size)}>
|
|
44
22
|
{runtime.forecastingSeriesKeys?.map((group, index) => {
|
|
45
|
-
if (!group || !group.stages) return
|
|
23
|
+
if (!group || !group.stages) return false
|
|
46
24
|
return group.stages.map((stage, stageIndex) => {
|
|
47
25
|
const { behavior } = legend
|
|
48
26
|
const groupData = rawData.filter(d => d[group.stageColumn] === stage.key)
|
|
@@ -54,13 +32,24 @@ const Forecasting = ({ xScale, yScale, height, width, chartRef, handleTooltipMou
|
|
|
54
32
|
{group.confidenceIntervals?.map((ciGroup, ciGroupIndex) => {
|
|
55
33
|
const palette = colorPalettesChart[stage.color]
|
|
56
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
|
|
57
46
|
return (
|
|
58
47
|
<Group key={`forecasting-areas--stage-${stage.key.replaceAll(' ', '-')}--group-${stageIndex}-${ciGroupIndex}`}>
|
|
59
48
|
{/* prettier-ignore */}
|
|
60
49
|
<Area
|
|
61
50
|
curve={curveMonotoneX}
|
|
62
51
|
data={groupData}
|
|
63
|
-
fill={
|
|
52
|
+
fill={getFill()}
|
|
64
53
|
opacity={transparentArea ? 0.1 : 0.5}
|
|
65
54
|
x={d => xScale(Date.parse(d[xAxis.dataKey]))}
|
|
66
55
|
y0={d => yScale(d[ciGroup.low])}
|
|
@@ -70,26 +59,10 @@ const Forecasting = ({ xScale, yScale, height, width, chartRef, handleTooltipMou
|
|
|
70
59
|
{ciGroupIndex === 0 && (
|
|
71
60
|
<>
|
|
72
61
|
{/* prettier-ignore */}
|
|
73
|
-
<LinePath
|
|
74
|
-
data={groupData}
|
|
75
|
-
x={ d => xScale(Date.parse(d[xAxis.dataKey])) }
|
|
76
|
-
y={ d => yScale(d[ciGroup.high])}
|
|
77
|
-
curve={curveMonotoneX}
|
|
78
|
-
stroke={displayArea ? palette[2] : 'transparent'}
|
|
79
|
-
strokeWidth={1}
|
|
80
|
-
strokeOpacity={1}
|
|
81
|
-
/>
|
|
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} />
|
|
82
63
|
|
|
83
64
|
{/* prettier-ignore */}
|
|
84
|
-
<LinePath
|
|
85
|
-
data={groupData}
|
|
86
|
-
x={ d => xScale(Date.parse(d[xAxis.dataKey])) }
|
|
87
|
-
y={ d => yScale(d[ciGroup.low])}
|
|
88
|
-
curve={curveMonotoneX}
|
|
89
|
-
stroke={displayArea ? palette[2] : 'transparent'}
|
|
90
|
-
strokeWidth={1}
|
|
91
|
-
strokeOpacity={1}
|
|
92
|
-
/>
|
|
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} />
|
|
93
66
|
</>
|
|
94
67
|
)}
|
|
95
68
|
</Group>
|
|
@@ -99,46 +72,10 @@ const Forecasting = ({ xScale, yScale, height, width, chartRef, handleTooltipMou
|
|
|
99
72
|
)
|
|
100
73
|
})
|
|
101
74
|
})}
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
<TooltipInPortal key={Math.random()} top={tooltipData.dataYPosition + chartPosition?.top} left={tooltipData.dataXPosition + chartPosition?.left} style={defaultStyles}>
|
|
105
|
-
<ul
|
|
106
|
-
style={{
|
|
107
|
-
listStyle: 'none',
|
|
108
|
-
paddingLeft: 'unset',
|
|
109
|
-
fontFamily: 'sans-serif',
|
|
110
|
-
margin: 'auto',
|
|
111
|
-
lineHeight: '1rem'
|
|
112
|
-
}}
|
|
113
|
-
data-tooltip-id={tooltip_id}
|
|
114
|
-
>
|
|
115
|
-
{typeof tooltipData === 'object' &&
|
|
116
|
-
Object.entries(tooltipData.data).map((item, index) => (
|
|
117
|
-
<li style={{ padding: '2.5px 0' }} key={`li-${index}`}>
|
|
118
|
-
<TooltipListItem item={item} />
|
|
119
|
-
</li>
|
|
120
|
-
))}
|
|
121
|
-
</ul>
|
|
122
|
-
</TooltipInPortal>
|
|
123
|
-
)}
|
|
124
|
-
{config?.runtime?.forecastingSeriesKeys?.length > 0 && (config.visualizationType === 'Combo' || config.visualizationType === 'Forecasting') && (
|
|
125
|
-
<Group key='tooltip-hover-section'>
|
|
126
|
-
<Bar key={'bars'} width={Number(width)} height={Number(height)} fill={DEBUG ? 'red' : 'transparent'} fillOpacity={0.05} onMouseMove={e => handleTooltipMouseOver(e, data)} onMouseOut={handleTooltipMouseOff} />
|
|
127
|
-
</Group>
|
|
128
|
-
)}
|
|
129
|
-
</Group>
|
|
130
|
-
|
|
131
|
-
{showTooltip && tooltipData && config.visual.verticalHoverLine && (
|
|
132
|
-
<Group key='tooltipLine-vertical' className='vertical-tooltip-line'>
|
|
133
|
-
<Line from={{ x: tooltipData.dataXPosition - 10, y: 0 }} to={{ x: tooltipData.dataXPosition - 10, y: height }} stroke={'black'} strokeWidth={1} pointerEvents='none' strokeDasharray='5,5' className='vertical-tooltip-line' />
|
|
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} />
|
|
134
77
|
</Group>
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
{showTooltip && tooltipData && config.visual.horizontalHoverLine && (
|
|
138
|
-
<Group key='tooltipLine-horizontal' className='horizontal-tooltip-line' left={config.yAxis.size ? config.yAxis.size : 0}>
|
|
139
|
-
<Line from={{ x: 0, y: tooltipData.dataYPosition }} to={{ x: width, y: tooltipData.dataYPosition }} stroke={'black'} strokeWidth={1} pointerEvents='none' strokeDasharray='5,5' className='horizontal-tooltip-line' />
|
|
140
|
-
</Group>
|
|
141
|
-
)}
|
|
78
|
+
</Group>
|
|
142
79
|
</ErrorBoundary>
|
|
143
80
|
)
|
|
144
81
|
)
|
|
@@ -33,6 +33,8 @@ const Legend = () => {
|
|
|
33
33
|
|
|
34
34
|
const { innerClasses, containerClasses } = useLegendClasses(config)
|
|
35
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)
|
|
36
38
|
|
|
37
39
|
const createLegendLabels = defaultLabels => {
|
|
38
40
|
const colorCode = config.legend?.colorCode
|
|
@@ -51,7 +53,7 @@ const Legend = () => {
|
|
|
51
53
|
value: aboveColor
|
|
52
54
|
}
|
|
53
55
|
|
|
54
|
-
return [labelBelow, labelAbove]
|
|
56
|
+
return reverseLabels([labelBelow, labelAbove])
|
|
55
57
|
}
|
|
56
58
|
if (visualizationType === 'Bar' && visualizationSubType === 'regular' && colorCode && series?.length === 1) {
|
|
57
59
|
let palette = colorPalettes[config.palette]
|
|
@@ -76,7 +78,7 @@ const Legend = () => {
|
|
|
76
78
|
return newLabel
|
|
77
79
|
})
|
|
78
80
|
|
|
79
|
-
return uniqueLabels
|
|
81
|
+
return reverseLabels(uniqueLabels)
|
|
80
82
|
}
|
|
81
83
|
|
|
82
84
|
// get forecasting items inside of combo
|
|
@@ -103,7 +105,7 @@ const Legend = () => {
|
|
|
103
105
|
|
|
104
106
|
// loop through bars for now to meet requirements.
|
|
105
107
|
config.runtime.barSeriesKeys &&
|
|
106
|
-
config.runtime.barSeriesKeys.
|
|
108
|
+
config.runtime.barSeriesKeys.forEach((bar, index) => {
|
|
107
109
|
let colorValue = colorPalettes[config.palette][index] ? colorPalettes[config.palette][index] : '#ccc'
|
|
108
110
|
|
|
109
111
|
const newLabel = {
|
|
@@ -116,7 +118,7 @@ const Legend = () => {
|
|
|
116
118
|
seriesLabels.push(newLabel)
|
|
117
119
|
})
|
|
118
120
|
|
|
119
|
-
return seriesLabels
|
|
121
|
+
return reverseLabels(seriesLabels)
|
|
120
122
|
}
|
|
121
123
|
|
|
122
124
|
// DEV-4161: replaceable series name in the legend
|
|
@@ -147,17 +149,17 @@ const Legend = () => {
|
|
|
147
149
|
return newLabel
|
|
148
150
|
})
|
|
149
151
|
|
|
150
|
-
return uniqueLabels
|
|
152
|
+
return reverseLabels(uniqueLabels)
|
|
151
153
|
}
|
|
152
154
|
|
|
153
|
-
return defaultLabels
|
|
155
|
+
return reverseLabels(defaultLabels)
|
|
154
156
|
}
|
|
155
157
|
|
|
156
|
-
const isBottomOrSmallViewport = legend.position === 'bottom' ||
|
|
158
|
+
const isBottomOrSmallViewport = legend.position === 'bottom' || ['sm', 'xs', 'xxs'].includes(currentViewport)
|
|
157
159
|
|
|
158
160
|
const legendClasses = {
|
|
159
161
|
marginBottom: isBottomOrSmallViewport ? '15px' : '0px',
|
|
160
|
-
marginTop: isBottomOrSmallViewport && orientation === 'horizontal' ? `${config.runtime.xAxis.size}px` :
|
|
162
|
+
marginTop: isBottomOrSmallViewport && orientation === 'horizontal' ? `${config.yAxis.label && config.isResponsiveTicks ? config.dynamicMarginTop : config.runtime.xAxis.size}px` : `0px`
|
|
161
163
|
}
|
|
162
164
|
|
|
163
165
|
const { HighLightedBarUtils } = useHighlightedBars(config)
|
|
@@ -2,22 +2,23 @@ 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 } from '@visx/shape'
|
|
5
|
+
import { LinePath, Bar } 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
11
|
|
|
12
|
-
|
|
13
|
-
|
|
12
|
+
const LineChart = ({ xScale, yScale, getXAxisData, getYAxisData, xMax, yMax, handleTooltipMouseOver, handleTooltipMouseOff, showTooltip, seriesStyle = 'Line', svgRef, handleTooltipClick, tooltipData }) => {
|
|
13
|
+
// Not sure why there's a redraw here.
|
|
14
14
|
|
|
15
|
+
const { colorPalettes, transformedData: data, colorScale, seriesHighlight, config, formatNumber, formatDate, parseDate, isNumber, updateConfig, handleLineType, dashboardConfig } = useContext(ConfigContext)
|
|
15
16
|
const { yScaleRight } = useRightAxis({ config, yMax, data, updateConfig })
|
|
16
17
|
|
|
18
|
+
if (!handleTooltipMouseOver) return
|
|
17
19
|
const handleAxisFormating = (axis = 'left', label, value) => {
|
|
18
20
|
// if this is an x axis category/date value return without doing any formatting.
|
|
19
21
|
// if (label === config.runtime.xAxis.label) return value
|
|
20
|
-
|
|
21
22
|
axis = String(axis).toLocaleLowerCase()
|
|
22
23
|
if (label) {
|
|
23
24
|
return `${label}: ${formatNumber(value, axis)}`
|
|
@@ -25,6 +26,7 @@ export default function LineChart({ xScale, yScale, getXAxisData, getYAxisData,
|
|
|
25
26
|
return `${formatNumber(value, axis)}`
|
|
26
27
|
}
|
|
27
28
|
|
|
29
|
+
const DEBUG = false
|
|
28
30
|
return (
|
|
29
31
|
<ErrorBoundary component='LineChart'>
|
|
30
32
|
<Group left={config.runtime.yAxis.size ? parseInt(config.runtime.yAxis.size) : 66}>
|
|
@@ -35,6 +37,8 @@ export default function LineChart({ xScale, yScale, getXAxisData, getYAxisData,
|
|
|
35
37
|
const seriesData = config.series.filter(item => item.dataKey === seriesKey)
|
|
36
38
|
const seriesAxis = seriesData[0].axis ? seriesData[0].axis : 'left'
|
|
37
39
|
|
|
40
|
+
let displayArea = config.legend.behavior === 'highlight' || seriesHighlight.length === 0 || seriesHighlight.indexOf(seriesKey) !== -1
|
|
41
|
+
|
|
38
42
|
return (
|
|
39
43
|
<Group
|
|
40
44
|
key={`series-${seriesKey}`}
|
|
@@ -71,7 +75,10 @@ export default function LineChart({ xScale, yScale, getXAxisData, getYAxisData,
|
|
|
71
75
|
d[seriesKey] !== '' &&
|
|
72
76
|
d[seriesKey] !== null &&
|
|
73
77
|
isNumber(d[seriesKey]) && (
|
|
74
|
-
<Group key={`series-${seriesKey}-point-${dataIndex}`}>
|
|
78
|
+
<Group key={`series-${seriesKey}-point-${dataIndex}`} className='checkwidth'>
|
|
79
|
+
{/* tooltips */}
|
|
80
|
+
<Bar key={'bars'} width={Number(xMax)} height={Number(yMax)} fill={DEBUG ? 'red' : 'transparent'} fillOpacity={0.05} onMouseMove={e => handleTooltipMouseOver(e, data)} onMouseOut={handleTooltipMouseOff} onClick={e => handleTooltipClick(e, data)} />
|
|
81
|
+
|
|
75
82
|
{/* Render legend */}
|
|
76
83
|
<Text display={config.labels ? 'block' : 'none'} x={xScale(getXAxisData(d))} y={seriesAxis === 'Right' ? yScaleRight(getYAxisData(d, seriesKey)) : yScale(getYAxisData(d, seriesKey))} fill={'#000'} textAnchor='middle'>
|
|
77
84
|
{formatNumber(d[seriesKey], 'left')}
|
|
@@ -83,10 +90,26 @@ export default function LineChart({ xScale, yScale, getXAxisData, getYAxisData,
|
|
|
83
90
|
cx={Number(xScale(getXAxisData(d)))}
|
|
84
91
|
cy={seriesAxis === 'Right' ? yScaleRight(getYAxisData(d, seriesKey)) : yScale(getYAxisData(d, seriesKey))}
|
|
85
92
|
fill={colorScale ? colorScale(config.runtime.seriesLabels ? config.runtime.seriesLabels[seriesKey] : seriesKey) : '#000'}
|
|
86
|
-
style={{
|
|
93
|
+
style={{
|
|
94
|
+
fill: colorScale ? colorScale(config.runtime.seriesLabels ? config.runtime.seriesLabels[seriesKey] : seriesKey) : '#000'
|
|
95
|
+
}}
|
|
87
96
|
data-tooltip-html={tooltip}
|
|
88
97
|
data-tooltip-id={`cdc-open-viz-tooltip-${config.runtime.uniqueId}`}
|
|
89
98
|
/>
|
|
99
|
+
|
|
100
|
+
{/* circles that appear on hover */}
|
|
101
|
+
{/* todo: circle radii used here should be global with other circle radii */}
|
|
102
|
+
{/* {tooltipData && Object.entries(tooltipData.data).length > 0 && isNumber(tooltipData.data[seriesKey]) && config.lineDatapointStyle === 'hover' && config.series.filter(s => s.type === 'Line') && (
|
|
103
|
+
<circle
|
|
104
|
+
cx={config.xAxis.type === 'categorical' ? xScale(tooltipData.data[config.xAxis.dataKey]) : xScale(parseDate(tooltipData.data[config.xAxis.dataKey]))}
|
|
105
|
+
cy={yScale(tooltipData.data[seriesKey])}
|
|
106
|
+
r={4.5}
|
|
107
|
+
opacity={tooltipData[seriesKey] ? 1 : 0}
|
|
108
|
+
fillOpacity={1}
|
|
109
|
+
fill={displayArea ? (colorScale ? colorScale(config.runtime.seriesLabels ? config.runtime.seriesLabels[seriesKey] : seriesKey) : '#000') : 'transparent'}
|
|
110
|
+
style={{ filter: 'unset', opacity: 1 }}
|
|
111
|
+
/>
|
|
112
|
+
)} */}
|
|
90
113
|
</Group>
|
|
91
114
|
)
|
|
92
115
|
)
|
|
@@ -161,3 +184,5 @@ export default function LineChart({ xScale, yScale, getXAxisData, getYAxisData,
|
|
|
161
184
|
</ErrorBoundary>
|
|
162
185
|
)
|
|
163
186
|
}
|
|
187
|
+
|
|
188
|
+
export default LineChart
|