@cdc/chart 4.24.10 → 4.24.11
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 +34618 -33995
- package/examples/feature/boxplot/boxplot-data.json +88 -22
- package/examples/feature/boxplot/boxplot.json +540 -16
- package/examples/feature/boxplot/testing.csv +7 -7
- package/examples/feature/sankey/sankey-example-data.json +0 -1
- package/examples/private/test.json +20092 -0
- package/index.html +3 -3
- package/package.json +2 -2
- package/src/CdcChart.tsx +86 -86
- package/src/_stories/Chart.CustomColors.stories.tsx +19 -0
- package/src/_stories/Chart.DynamicSeries.stories.tsx +27 -0
- package/src/_stories/Chart.Legend.Gradient.stories.tsx +42 -1
- package/src/_stories/Chart.stories.tsx +7 -8
- package/src/_stories/ChartEditor.stories.tsx +27 -0
- package/src/_stories/ChartLine.Suppression.stories.tsx +25 -0
- package/src/_stories/ChartPrefixSuffix.stories.tsx +8 -0
- package/src/_stories/_mock/boxplot_multiseries.json +647 -0
- package/src/_stories/_mock/dynamic_series_bar_config.json +723 -0
- package/src/_stories/_mock/dynamic_series_config.json +979 -0
- package/{examples/feature/scatterplot/scatterplot.json → src/_stories/_mock/scatterplot_mock.json} +62 -92
- package/src/_stories/_mock/suppression_mock.json +1549 -0
- package/src/components/BarChart/components/BarChart.StackedVertical.tsx +43 -9
- package/src/components/BarChart/components/BarChart.Vertical.tsx +60 -42
- package/src/components/BarChart/helpers/index.ts +1 -2
- package/src/components/BoxPlot/BoxPlot.tsx +189 -0
- package/src/components/EditorPanel/EditorPanel.tsx +64 -62
- package/src/components/EditorPanel/components/Panels/Panel.Annotate.tsx +3 -3
- package/src/components/EditorPanel/components/Panels/Panel.BoxPlot.tsx +51 -6
- package/src/components/EditorPanel/components/Panels/Panel.Regions.tsx +40 -9
- package/src/components/EditorPanel/components/Panels/Panel.Sankey.tsx +3 -3
- package/src/components/EditorPanel/components/Panels/Panel.Series.tsx +121 -56
- package/src/components/EditorPanel/components/Panels/Panel.Visual.tsx +1 -2
- package/src/components/EditorPanel/useEditorPermissions.ts +15 -1
- package/src/components/Legend/Legend.Component.tsx +9 -10
- package/src/components/Legend/Legend.tsx +16 -16
- package/src/components/LineChart/helpers.ts +48 -43
- package/src/components/LineChart/index.tsx +88 -82
- package/src/components/LinearChart.tsx +17 -10
- package/src/components/Sankey/index.tsx +50 -32
- package/src/components/Sankey/sankey.scss +6 -5
- package/src/components/Sankey/useSankeyAlert.tsx +60 -0
- package/src/components/ScatterPlot/ScatterPlot.jsx +20 -4
- package/src/data/initial-state.js +3 -9
- package/src/hooks/useLegendClasses.ts +10 -23
- package/src/hooks/useMinMax.ts +27 -13
- package/src/hooks/useReduceData.ts +43 -10
- package/src/hooks/useScales.ts +56 -35
- package/src/hooks/useTooltip.tsx +54 -49
- package/src/scss/main.scss +0 -18
- package/src/types/ChartConfig.ts +6 -19
- package/src/types/ChartContext.ts +4 -1
- package/src/types/ForestPlot.ts +8 -0
- package/src/components/BoxPlot/BoxPlot.jsx +0 -111
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import React, { useContext } from 'react'
|
|
1
|
+
import React, { useContext, useMemo } from 'react'
|
|
2
2
|
import ConfigContext from '../../../../ConfigContext'
|
|
3
3
|
|
|
4
4
|
// Core
|
|
@@ -7,10 +7,18 @@ import Check from '@cdc/core/assets/icon-check.svg'
|
|
|
7
7
|
import { approvedCurveTypes } from '@cdc/core/helpers/lineChartHelpers'
|
|
8
8
|
import { sequentialPalettes } from '@cdc/core/data/colorPalettes'
|
|
9
9
|
import Icon from '@cdc/core/components/ui/Icon'
|
|
10
|
+
import { Select } from '@cdc/core/components/EditorPanel/Inputs'
|
|
10
11
|
|
|
11
12
|
// Third Party
|
|
12
|
-
import {
|
|
13
|
+
import {
|
|
14
|
+
Accordion,
|
|
15
|
+
AccordionItem,
|
|
16
|
+
AccordionItemHeading,
|
|
17
|
+
AccordionItemPanel,
|
|
18
|
+
AccordionItemButton
|
|
19
|
+
} from 'react-accessible-accordion'
|
|
13
20
|
import { Draggable } from '@hello-pangea/dnd'
|
|
21
|
+
import Tooltip from '@cdc/core/components/ui/Tooltip'
|
|
14
22
|
|
|
15
23
|
const SeriesContext = React.createContext({})
|
|
16
24
|
|
|
@@ -48,7 +56,11 @@ const SeriesWrapper = props => {
|
|
|
48
56
|
updateConfig({ ...config, series })
|
|
49
57
|
}
|
|
50
58
|
|
|
51
|
-
return
|
|
59
|
+
return (
|
|
60
|
+
<SeriesContext.Provider value={{ updateSeries, supportedRightAxisTypes, getColumns, selectComponent }}>
|
|
61
|
+
{props.children}
|
|
62
|
+
</SeriesContext.Provider>
|
|
63
|
+
)
|
|
52
64
|
}
|
|
53
65
|
|
|
54
66
|
const SeriesDropdownLineType = props => {
|
|
@@ -245,7 +257,9 @@ const SeriesDropdownForecastColor = props => {
|
|
|
245
257
|
<InputSelect
|
|
246
258
|
key={`${stage}--${stageIndex}`}
|
|
247
259
|
initial='Select an option'
|
|
248
|
-
value={
|
|
260
|
+
value={
|
|
261
|
+
config.series?.[index].stages?.[stageIndex].color ? config.series?.[index].stages?.[stageIndex].color : 'Select'
|
|
262
|
+
}
|
|
249
263
|
label={`${stage.key} Series Color`}
|
|
250
264
|
onChange={event => {
|
|
251
265
|
const copyOfSeries = [...config.series] // copy the entire series array
|
|
@@ -315,55 +329,33 @@ const SeriesDropdownConfidenceInterval = props => {
|
|
|
315
329
|
<AccordionItemPanel>
|
|
316
330
|
<div className='input-group'>
|
|
317
331
|
<label htmlFor='showInTooltip'>Show In Tooltip</label>
|
|
318
|
-
<div
|
|
319
|
-
|
|
332
|
+
<div
|
|
333
|
+
className={'cove-input__checkbox--small'}
|
|
334
|
+
onClick={e => updateShowInTooltip(e, index, ciIndex)}
|
|
335
|
+
>
|
|
336
|
+
<div
|
|
337
|
+
className={`cove-input__checkbox-box${'blue' ? ' custom-color' : ''}`}
|
|
338
|
+
style={{ backgroundColor: '' }}
|
|
339
|
+
>
|
|
320
340
|
{showInTooltip && <Check className='' style={{ fill: '#025eaa' }} />}
|
|
321
341
|
</div>
|
|
322
|
-
<input
|
|
342
|
+
<input
|
|
343
|
+
className='cove-input--hidden'
|
|
344
|
+
type='checkbox'
|
|
345
|
+
name={'showInTooltip'}
|
|
346
|
+
checked={showInTooltip ? showInTooltip : false}
|
|
347
|
+
readOnly
|
|
348
|
+
/>
|
|
323
349
|
</div>
|
|
324
350
|
</div>
|
|
325
351
|
|
|
326
|
-
{/* <label>
|
|
327
|
-
High Label
|
|
328
|
-
<input
|
|
329
|
-
type='text'
|
|
330
|
-
key={`series-ci-high-label-${index}`}
|
|
331
|
-
value={series.confidenceIntervals[index]?.highLabel ? series.confidenceIntervals[index]?.highLabel : ''}
|
|
332
|
-
onChange={e => {
|
|
333
|
-
const copiedConfidenceArray = [...config.series[index].confidenceIntervals]
|
|
334
|
-
copiedConfidenceArray[ciIndex].highLabel = e.target.value
|
|
335
|
-
const copyOfSeries = [...config.series] // copy the entire series array
|
|
336
|
-
copyOfSeries[index] = { ...copyOfSeries[index], confidenceIntervals: copiedConfidenceArray }
|
|
337
|
-
updateConfig({
|
|
338
|
-
...config,
|
|
339
|
-
series: copyOfSeries
|
|
340
|
-
})
|
|
341
|
-
}}
|
|
342
|
-
/>
|
|
343
|
-
</label> */}
|
|
344
|
-
|
|
345
|
-
{/* <label>
|
|
346
|
-
Low label
|
|
347
|
-
<input
|
|
348
|
-
type='text'
|
|
349
|
-
key={`series-ci-high-label-${index}`}
|
|
350
|
-
value={series.confidenceIntervals[index]?.lowLabel ? series.confidenceIntervals[index]?.lowLabel : ''}
|
|
351
|
-
onChange={e => {
|
|
352
|
-
const copiedConfidenceArray = [...config.series[index].confidenceIntervals]
|
|
353
|
-
copiedConfidenceArray[ciIndex].lowLabel = e.target.value
|
|
354
|
-
const copyOfSeries = [...config.series] // copy the entire series array
|
|
355
|
-
copyOfSeries[index] = { ...copyOfSeries[index], confidenceIntervals: copiedConfidenceArray }
|
|
356
|
-
updateConfig({
|
|
357
|
-
...config,
|
|
358
|
-
series: copyOfSeries
|
|
359
|
-
})
|
|
360
|
-
}}
|
|
361
|
-
/>
|
|
362
|
-
</label> */}
|
|
363
|
-
|
|
364
352
|
<InputSelect
|
|
365
353
|
initial='Select an option'
|
|
366
|
-
value={
|
|
354
|
+
value={
|
|
355
|
+
config.series[index].confidenceIntervals[ciIndex].low
|
|
356
|
+
? config.series[index].confidenceIntervals[ciIndex].low
|
|
357
|
+
: 'Select'
|
|
358
|
+
}
|
|
367
359
|
label='Low Confidence Interval'
|
|
368
360
|
onChange={e => {
|
|
369
361
|
const copiedConfidenceArray = [...config.series[index].confidenceIntervals]
|
|
@@ -379,7 +371,11 @@ const SeriesDropdownConfidenceInterval = props => {
|
|
|
379
371
|
/>
|
|
380
372
|
<InputSelect
|
|
381
373
|
initial='Select an option'
|
|
382
|
-
value={
|
|
374
|
+
value={
|
|
375
|
+
config.series[index].confidenceIntervals[ciIndex].high
|
|
376
|
+
? config.series[index].confidenceIntervals[ciIndex].high
|
|
377
|
+
: 'Select'
|
|
378
|
+
}
|
|
383
379
|
label='High Confidence Interval'
|
|
384
380
|
onChange={e => {
|
|
385
381
|
const copiedConfidenceArray = [...config.series[index].confidenceIntervals]
|
|
@@ -399,7 +395,7 @@ const SeriesDropdownConfidenceInterval = props => {
|
|
|
399
395
|
})}
|
|
400
396
|
</Accordion>
|
|
401
397
|
<button
|
|
402
|
-
className='btn full-width'
|
|
398
|
+
className='btn btn-primary full-width'
|
|
403
399
|
onClick={e => {
|
|
404
400
|
e.preventDefault()
|
|
405
401
|
let copiedIndex = null
|
|
@@ -409,7 +405,10 @@ const SeriesDropdownConfidenceInterval = props => {
|
|
|
409
405
|
copiedIndex = []
|
|
410
406
|
}
|
|
411
407
|
const copyOfSeries = [...config.series] // copy the entire series array
|
|
412
|
-
copyOfSeries[index] = {
|
|
408
|
+
copyOfSeries[index] = {
|
|
409
|
+
...copyOfSeries[index],
|
|
410
|
+
confidenceIntervals: [...copiedIndex, { high: '', low: '' }]
|
|
411
|
+
} // update the nested array
|
|
413
412
|
updateConfig({
|
|
414
413
|
...config,
|
|
415
414
|
series: copyOfSeries
|
|
@@ -468,7 +467,19 @@ const SeriesInputWeight = props => {
|
|
|
468
467
|
const SeriesInputName = props => {
|
|
469
468
|
const { series, index: i } = props
|
|
470
469
|
const { config, updateConfig } = useContext(ConfigContext)
|
|
471
|
-
const adjustableNameSeriesTypes = [
|
|
470
|
+
const adjustableNameSeriesTypes = [
|
|
471
|
+
'Bump Chart',
|
|
472
|
+
'Bar',
|
|
473
|
+
'Line',
|
|
474
|
+
'Area Chart',
|
|
475
|
+
'Combo',
|
|
476
|
+
'Deviation',
|
|
477
|
+
'Paired',
|
|
478
|
+
'Scatter',
|
|
479
|
+
'dashed-sm',
|
|
480
|
+
'dashed-md',
|
|
481
|
+
'dashed-lg'
|
|
482
|
+
]
|
|
472
483
|
|
|
473
484
|
if (!adjustableNameSeriesTypes.includes(series.type)) return
|
|
474
485
|
|
|
@@ -532,7 +543,13 @@ const SeriesDisplayInTooltip = props => {
|
|
|
532
543
|
<div className={`cove-input__checkbox-box${'blue' ? ' custom-color' : ''}`} style={{ backgroundColor: '' }}>
|
|
533
544
|
{series.tooltip && <Check className='' style={{ fill: '#025eaa' }} />}
|
|
534
545
|
</div>
|
|
535
|
-
<input
|
|
546
|
+
<input
|
|
547
|
+
className='cove-input--hidden'
|
|
548
|
+
type='checkbox'
|
|
549
|
+
name={`series-tooltip--${index}`}
|
|
550
|
+
checked={series.tooltip ? series.tooltip : false}
|
|
551
|
+
readOnly
|
|
552
|
+
/>
|
|
536
553
|
</div>
|
|
537
554
|
</div>
|
|
538
555
|
</>
|
|
@@ -591,17 +608,32 @@ const SeriesButtonRemove = props => {
|
|
|
591
608
|
|
|
592
609
|
const SeriesItem = props => {
|
|
593
610
|
const { config } = useContext(ConfigContext)
|
|
594
|
-
|
|
611
|
+
const { updateSeries, getColumns } = useContext(SeriesContext)
|
|
595
612
|
const { series, getItemStyle, sortableItemStyles, chartsWithOptions, index: i } = props
|
|
596
|
-
|
|
613
|
+
const showDynamicCategory =
|
|
614
|
+
['Bar', 'Line'].includes(config.visualizationType) &&
|
|
615
|
+
!config.series.find(s => s.dynamicCategory && s.dataKey !== series.dataKey)
|
|
597
616
|
return (
|
|
598
617
|
<Draggable key={series.dataKey} draggableId={`draggableFilter-${series.dataKey}`} index={i}>
|
|
599
618
|
{(provided, snapshot) => (
|
|
600
|
-
<div
|
|
619
|
+
<div
|
|
620
|
+
key={i}
|
|
621
|
+
className={snapshot.isDragging ? 'currently-dragging' : ''}
|
|
622
|
+
style={getItemStyle(snapshot.isDragging, provided.draggableProps.style, sortableItemStyles)}
|
|
623
|
+
ref={provided.innerRef}
|
|
624
|
+
{...provided.draggableProps}
|
|
625
|
+
{...provided.dragHandleProps}
|
|
626
|
+
>
|
|
601
627
|
<Accordion allowZeroExpanded>
|
|
602
628
|
<AccordionItem className='series-item series-item--chart'>
|
|
603
629
|
<AccordionItemHeading className='series-item__title'>
|
|
604
|
-
<AccordionItemButton
|
|
630
|
+
<AccordionItemButton
|
|
631
|
+
className={
|
|
632
|
+
chartsWithOptions.includes(config.visualizationType)
|
|
633
|
+
? 'accordion__button'
|
|
634
|
+
: 'accordion__button hide-arrow'
|
|
635
|
+
}
|
|
636
|
+
>
|
|
605
637
|
<Icon display='move' size={15} style={{ cursor: 'default' }} />
|
|
606
638
|
{series.dataKey}
|
|
607
639
|
<Series.Button.Remove series={series} index={i} />
|
|
@@ -610,6 +642,30 @@ const SeriesItem = props => {
|
|
|
610
642
|
{chartsWithOptions.includes(config.visualizationType) && (
|
|
611
643
|
<AccordionItemPanel>
|
|
612
644
|
<Series.Input.Name series={series} index={i} />
|
|
645
|
+
{showDynamicCategory && (
|
|
646
|
+
<Select
|
|
647
|
+
label='Dynamic Category'
|
|
648
|
+
value={series.dynamicCategory}
|
|
649
|
+
options={['- Select - ', ...getColumns().filter(col => series.dataKey !== col)]}
|
|
650
|
+
updateField={(_section, _subsection, _fieldName, value) => {
|
|
651
|
+
if (value === '- Select -') value = ''
|
|
652
|
+
updateSeries(i, value, 'dynamicCategory')
|
|
653
|
+
}}
|
|
654
|
+
tooltip={
|
|
655
|
+
<Tooltip style={{ textTransform: 'none' }}>
|
|
656
|
+
<Tooltip.Target>
|
|
657
|
+
<Icon display='question' style={{ marginLeft: '0.5rem' }} />
|
|
658
|
+
</Tooltip.Target>
|
|
659
|
+
<Tooltip.Content>
|
|
660
|
+
<p>
|
|
661
|
+
This field is Optional. If you have a dynamic data series you can select the category
|
|
662
|
+
field here. You can only add one dynamic category per visualization.
|
|
663
|
+
</p>
|
|
664
|
+
</Tooltip.Content>
|
|
665
|
+
</Tooltip>
|
|
666
|
+
}
|
|
667
|
+
/>
|
|
668
|
+
)}
|
|
613
669
|
<Series.Input.Weight series={series} index={i} />
|
|
614
670
|
<Series.Dropdown.SeriesType series={series} index={i} />
|
|
615
671
|
<Series.Dropdown.AxisPosition series={series} index={i} />
|
|
@@ -630,7 +686,16 @@ const SeriesItem = props => {
|
|
|
630
686
|
const SeriesList = props => {
|
|
631
687
|
const { series, getItemStyle, sortableItemStyles, chartsWithOptions } = props
|
|
632
688
|
return series.map((series, i) => {
|
|
633
|
-
return
|
|
689
|
+
return (
|
|
690
|
+
<SeriesItem
|
|
691
|
+
getItemStyle={getItemStyle}
|
|
692
|
+
sortableItemStyles={sortableItemStyles}
|
|
693
|
+
chartsWithOptions={chartsWithOptions}
|
|
694
|
+
series={series}
|
|
695
|
+
index={i}
|
|
696
|
+
key={`series-list-${i}`}
|
|
697
|
+
/>
|
|
698
|
+
)
|
|
634
699
|
})
|
|
635
700
|
}
|
|
636
701
|
|
|
@@ -435,8 +435,7 @@ const PanelVisual: FC<PanelProps> = props => {
|
|
|
435
435
|
min={15}
|
|
436
436
|
/>
|
|
437
437
|
)}
|
|
438
|
-
{(
|
|
439
|
-
config.visualizationType === 'Combo') && (
|
|
438
|
+
{(config.orientation !== 'horizontal' || config.visualizationType === 'Combo') && (
|
|
440
439
|
<TextField
|
|
441
440
|
value={config.barThickness}
|
|
442
441
|
type='number'
|
|
@@ -44,6 +44,18 @@ export const useEditorPermissions = () => {
|
|
|
44
44
|
return true
|
|
45
45
|
}
|
|
46
46
|
|
|
47
|
+
const visSupportsDateCategoryAxisMin = () => {
|
|
48
|
+
const enabledCharts = ['Scatter Plot']
|
|
49
|
+
if (enabledCharts.includes(visualizationType)) return true
|
|
50
|
+
return false
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
const visSupportsDateCategoryAxisMax = () => {
|
|
54
|
+
const enabledCharts = ['Scatter Plot']
|
|
55
|
+
if (enabledCharts.includes(visualizationType)) return true
|
|
56
|
+
return false
|
|
57
|
+
}
|
|
58
|
+
|
|
47
59
|
const visSupportsSuperTitle = () => {
|
|
48
60
|
const disabledCharts = ['Spark Line']
|
|
49
61
|
if (disabledCharts.includes(visualizationType)) return false
|
|
@@ -88,7 +100,7 @@ export const useEditorPermissions = () => {
|
|
|
88
100
|
const visHasLegend = () => {
|
|
89
101
|
switch (visualizationType) {
|
|
90
102
|
case 'Box Plot':
|
|
91
|
-
return
|
|
103
|
+
return true
|
|
92
104
|
case 'Forest Plot':
|
|
93
105
|
return false
|
|
94
106
|
case 'Spark Line':
|
|
@@ -414,6 +426,8 @@ export const useEditorPermissions = () => {
|
|
|
414
426
|
visSupportsChartHeight,
|
|
415
427
|
visSupportsMobileChartHeight,
|
|
416
428
|
visSupportsDateCategoryAxis,
|
|
429
|
+
visSupportsDateCategoryAxisMin,
|
|
430
|
+
visSupportsDateCategoryAxisMax,
|
|
417
431
|
visSupportsDateCategoryAxisLabel,
|
|
418
432
|
visSupportsDateCategoryAxisLine,
|
|
419
433
|
visSupportsDateCategoryAxisTicks,
|
|
@@ -9,7 +9,7 @@ import { handleLineType } from '../../helpers/handleLineType'
|
|
|
9
9
|
import { getMarginTop, getGradientConfig, getMarginBottom } from './helpers/index'
|
|
10
10
|
import { Line } from '@visx/shape'
|
|
11
11
|
import { Label } from '../../types/Label'
|
|
12
|
-
import { ChartConfig } from '../../types/ChartConfig'
|
|
12
|
+
import { ChartConfig, ViewportSize } from '../../types/ChartConfig'
|
|
13
13
|
import { ColorScale } from '../../types/ChartContext'
|
|
14
14
|
import { forwardRef, useState } from 'react'
|
|
15
15
|
import LegendSuppression from './Legend.Suppression'
|
|
@@ -17,10 +17,12 @@ import LegendGradient from '@cdc/core/components/Legend/Legend.Gradient'
|
|
|
17
17
|
import { DimensionsType } from '@cdc/core/types/Dimensions'
|
|
18
18
|
import { isLegendWrapViewport } from '@cdc/core/helpers/viewports'
|
|
19
19
|
|
|
20
|
+
const LEGEND_PADDING = 30
|
|
21
|
+
|
|
20
22
|
export interface LegendProps {
|
|
21
23
|
colorScale: ColorScale
|
|
22
24
|
config: ChartConfig
|
|
23
|
-
currentViewport:
|
|
25
|
+
currentViewport: ViewportSize
|
|
24
26
|
formatLabels: (labels: Label[]) => Label[]
|
|
25
27
|
highlight: Function
|
|
26
28
|
highlightReset: Function
|
|
@@ -78,7 +80,7 @@ const Legend: React.FC<LegendProps> = forwardRef(
|
|
|
78
80
|
config={config}
|
|
79
81
|
{...getGradientConfig(config, formatLabels, colorScale)}
|
|
80
82
|
dimensions={dimensions}
|
|
81
|
-
|
|
83
|
+
parentPaddingToSubtract={legend.hideBorder ? 0 : LEGEND_PADDING}
|
|
82
84
|
/>
|
|
83
85
|
|
|
84
86
|
<LegendOrdinal scale={colorScale} itemDirection='row' labelMargin='0 20px 0 0' shapeMargin='0 10px 0'>
|
|
@@ -129,7 +131,7 @@ const Legend: React.FC<LegendProps> = forwardRef(
|
|
|
129
131
|
}}
|
|
130
132
|
role='button'
|
|
131
133
|
>
|
|
132
|
-
|
|
134
|
+
<>
|
|
133
135
|
{config.visualizationType === 'Line' && config.legend.style === 'lines' ? (
|
|
134
136
|
<svg width={40} height={25}>
|
|
135
137
|
<Line
|
|
@@ -141,17 +143,14 @@ const Legend: React.FC<LegendProps> = forwardRef(
|
|
|
141
143
|
/>
|
|
142
144
|
</svg>
|
|
143
145
|
) : (
|
|
144
|
-
|
|
146
|
+
<>
|
|
145
147
|
<LegendShape
|
|
146
148
|
shape={config.legend.style === 'boxes' ? 'square' : 'circle'}
|
|
147
|
-
viewport={currentViewport}
|
|
148
|
-
margin='0'
|
|
149
149
|
fill={label.value}
|
|
150
|
-
display={true}
|
|
151
150
|
/>
|
|
152
|
-
|
|
151
|
+
</>
|
|
153
152
|
)}
|
|
154
|
-
|
|
153
|
+
</>
|
|
155
154
|
|
|
156
155
|
<LegendLabel align='left' margin='0 0 0 4px'>
|
|
157
156
|
{label.text}
|
|
@@ -17,6 +17,7 @@ const Legend = forwardRef((props, ref) => {
|
|
|
17
17
|
transformedData: data,
|
|
18
18
|
currentViewport,
|
|
19
19
|
dimensions,
|
|
20
|
+
getTextWidth
|
|
20
21
|
} = useContext(ConfigContext)
|
|
21
22
|
if (!config.legend) return null
|
|
22
23
|
// create fn to reverse labels while legend is Bottom. Legend-right , legend-left works by default.
|
|
@@ -24,22 +25,21 @@ const Legend = forwardRef((props, ref) => {
|
|
|
24
25
|
const createLegendLabels = createFormatLabels(config, tableData, data, colorScale)
|
|
25
26
|
|
|
26
27
|
return (
|
|
27
|
-
|
|
28
|
-
<
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
)
|
|
28
|
+
<Fragment>
|
|
29
|
+
<LegendComponent
|
|
30
|
+
getTextWidth={getTextWidth}
|
|
31
|
+
dimensions={dimensions}
|
|
32
|
+
ref={ref}
|
|
33
|
+
skipId={props.skipId || 'legend'}
|
|
34
|
+
config={config}
|
|
35
|
+
colorScale={colorScale}
|
|
36
|
+
seriesHighlight={seriesHighlight}
|
|
37
|
+
highlight={highlight}
|
|
38
|
+
highlightReset={highlightReset}
|
|
39
|
+
currentViewport={currentViewport}
|
|
40
|
+
formatLabels={createLegendLabels}
|
|
41
|
+
/>
|
|
42
|
+
</Fragment>
|
|
43
43
|
)
|
|
44
44
|
})
|
|
45
45
|
|
|
@@ -92,8 +92,9 @@ export const filterCircles = (
|
|
|
92
92
|
|
|
93
93
|
const isCalculable = value => !isNaN(parseFloat(value)) && isFinite(value)
|
|
94
94
|
const handleFirstIndex = (data, seriesKey, preliminaryData) => {
|
|
95
|
+
let pairCount = '0'
|
|
95
96
|
const result = {
|
|
96
|
-
data: [],
|
|
97
|
+
data: { '0': [] },
|
|
97
98
|
style: ''
|
|
98
99
|
}
|
|
99
100
|
|
|
@@ -116,7 +117,7 @@ const handleFirstIndex = (data, seriesKey, preliminaryData) => {
|
|
|
116
117
|
if (suppressionData && suppressionData.style) {
|
|
117
118
|
// Modify first item and add to result
|
|
118
119
|
const modifiedItem = { ...firstIndexDataItem, [seriesKey]: 0 }
|
|
119
|
-
result.data.push(modifiedItem)
|
|
120
|
+
result.data[pairCount].push(modifiedItem)
|
|
120
121
|
result.style = suppressionData.style
|
|
121
122
|
|
|
122
123
|
// Find the next calculable item index
|
|
@@ -125,19 +126,20 @@ const handleFirstIndex = (data, seriesKey, preliminaryData) => {
|
|
|
125
126
|
nextIndex++
|
|
126
127
|
}
|
|
127
128
|
if (nextIndex < data.length) {
|
|
128
|
-
result.data.push(data[nextIndex])
|
|
129
|
+
result.data[pairCount].push(data[nextIndex])
|
|
129
130
|
}
|
|
130
131
|
} else {
|
|
131
132
|
// If no suppression, just add the first item
|
|
132
|
-
result.data.push(firstIndexDataItem)
|
|
133
|
+
result.data[pairCount].push(firstIndexDataItem)
|
|
133
134
|
}
|
|
134
135
|
|
|
135
136
|
return result
|
|
136
137
|
}
|
|
137
138
|
|
|
138
139
|
const handleLastIndex = (data, seriesKey, preliminaryData) => {
|
|
140
|
+
let pairCount = '0'
|
|
139
141
|
const result = {
|
|
140
|
-
data: [],
|
|
142
|
+
data: { '0': [] },
|
|
141
143
|
style: ''
|
|
142
144
|
}
|
|
143
145
|
let lastAddedIndex = -1 // Tracks the last index added to the result
|
|
@@ -152,7 +154,7 @@ const handleLastIndex = (data, seriesKey, preliminaryData) => {
|
|
|
152
154
|
) {
|
|
153
155
|
const lastIndex = data.length - 1
|
|
154
156
|
const modifiedItem = { ...data[lastIndex], [seriesKey]: 0 }
|
|
155
|
-
result.data.push(modifiedItem)
|
|
157
|
+
result.data[pairCount].push(modifiedItem)
|
|
156
158
|
|
|
157
159
|
// Find previous calculable item
|
|
158
160
|
let prevIndex = lastIndex - 1
|
|
@@ -160,7 +162,7 @@ const handleLastIndex = (data, seriesKey, preliminaryData) => {
|
|
|
160
162
|
prevIndex--
|
|
161
163
|
}
|
|
162
164
|
if (prevIndex >= 0 && lastAddedIndex !== prevIndex) {
|
|
163
|
-
result.data.push(data[prevIndex])
|
|
165
|
+
result.data[pairCount].push(data[prevIndex])
|
|
164
166
|
lastAddedIndex = prevIndex
|
|
165
167
|
}
|
|
166
168
|
result.style = pd.style
|
|
@@ -170,47 +172,48 @@ const handleLastIndex = (data, seriesKey, preliminaryData) => {
|
|
|
170
172
|
return result
|
|
171
173
|
}
|
|
172
174
|
|
|
173
|
-
function handleMiddleIndices(data, seriesKey,
|
|
174
|
-
|
|
175
|
-
|
|
175
|
+
function handleMiddleIndices(data, seriesKey, preliminaryData) {
|
|
176
|
+
// slice data to remove first and last object these no need for handleMiddleIndices
|
|
177
|
+
|
|
178
|
+
let result = {
|
|
179
|
+
data: {},
|
|
176
180
|
style: ''
|
|
177
181
|
}
|
|
182
|
+
// Variable to count the number of sibling pairs found
|
|
183
|
+
let pairCount = 1
|
|
184
|
+
|
|
185
|
+
// Loop through the data array to find each occurrence of the target value
|
|
186
|
+
data.forEach((item, index) => {
|
|
187
|
+
preliminaryData.forEach(pd => {
|
|
188
|
+
const targetValue = pd.value
|
|
189
|
+
if (item[seriesKey] === targetValue) {
|
|
190
|
+
let siblingBefore = null
|
|
191
|
+
let siblingAfter = null
|
|
192
|
+
|
|
193
|
+
// Find the nearest numeric sibling before the current index
|
|
194
|
+
for (let i = index - 1; i >= 0; i--) {
|
|
195
|
+
if (isCalculable(data[i][seriesKey])) {
|
|
196
|
+
siblingBefore = data[i]
|
|
197
|
+
break // Stop searching once a valid sibling is found
|
|
198
|
+
}
|
|
199
|
+
}
|
|
178
200
|
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
const matchingIndices = data.reduce((indices, item, index) => {
|
|
187
|
-
if (item[seriesKey] === targetValue && isValidMiddleIndex(index) && (!pd.column || pd.column === seriesKey)) {
|
|
188
|
-
indices.push(index)
|
|
189
|
-
}
|
|
190
|
-
return indices
|
|
191
|
-
}, [])
|
|
192
|
-
|
|
193
|
-
// Process each valid index
|
|
194
|
-
matchingIndices.forEach(i => {
|
|
195
|
-
result.style = pd.style
|
|
196
|
-
// Add previous object if calculable
|
|
197
|
-
if (isCalculable(data[i - 1][seriesKey])) {
|
|
198
|
-
result.data.push(data[i - 1])
|
|
199
|
-
}
|
|
201
|
+
// Find the nearest numeric sibling after the current index
|
|
202
|
+
for (let j = index + 1; j < data.length; j++) {
|
|
203
|
+
if (isCalculable(data[j][seriesKey])) {
|
|
204
|
+
siblingAfter = data[j]
|
|
205
|
+
break // Stop searching once a valid sibling is found
|
|
206
|
+
}
|
|
207
|
+
}
|
|
200
208
|
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
result.data.push(data[i + 1 + nextIndex])
|
|
209
|
+
// Only add siblings to results if both siblings are found
|
|
210
|
+
if (siblingBefore && siblingAfter) {
|
|
211
|
+
result.style = pd.style
|
|
212
|
+
result.data[pairCount++] = [siblingBefore, siblingAfter]
|
|
213
|
+
}
|
|
207
214
|
}
|
|
208
215
|
})
|
|
209
216
|
})
|
|
210
|
-
|
|
211
|
-
// Deduplicate entries
|
|
212
|
-
result.data = _.uniqWith(result.data, (a, b) => a[dataKey] === b[dataKey] && a[seriesKey] === b[seriesKey])
|
|
213
|
-
|
|
214
217
|
return result
|
|
215
218
|
}
|
|
216
219
|
|
|
@@ -221,7 +224,9 @@ export const createDataSegments = (data, seriesKey, preliminaryData, dataKey) =>
|
|
|
221
224
|
// Process the last index if necessary
|
|
222
225
|
const lastSegment = handleLastIndex(data, seriesKey, preliminaryData)
|
|
223
226
|
// Process the middle segment
|
|
224
|
-
const middleSegments = handleMiddleIndices(data, seriesKey,
|
|
227
|
+
const middleSegments = handleMiddleIndices(data, seriesKey, preliminaryData)
|
|
228
|
+
|
|
225
229
|
// Combine all segments into a single array
|
|
226
|
-
return [firstSegment, middleSegments, lastSegment]
|
|
230
|
+
return [firstSegment, middleSegments, lastSegment]
|
|
231
|
+
// return [firstSegment, middleSegments, lastSegment].filter(segment => segment.data.length > 0 && segment.style !== '')
|
|
227
232
|
}
|