@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.
Files changed (53) hide show
  1. package/dist/cdcchart.js +34618 -33995
  2. package/examples/feature/boxplot/boxplot-data.json +88 -22
  3. package/examples/feature/boxplot/boxplot.json +540 -16
  4. package/examples/feature/boxplot/testing.csv +7 -7
  5. package/examples/feature/sankey/sankey-example-data.json +0 -1
  6. package/examples/private/test.json +20092 -0
  7. package/index.html +3 -3
  8. package/package.json +2 -2
  9. package/src/CdcChart.tsx +86 -86
  10. package/src/_stories/Chart.CustomColors.stories.tsx +19 -0
  11. package/src/_stories/Chart.DynamicSeries.stories.tsx +27 -0
  12. package/src/_stories/Chart.Legend.Gradient.stories.tsx +42 -1
  13. package/src/_stories/Chart.stories.tsx +7 -8
  14. package/src/_stories/ChartEditor.stories.tsx +27 -0
  15. package/src/_stories/ChartLine.Suppression.stories.tsx +25 -0
  16. package/src/_stories/ChartPrefixSuffix.stories.tsx +8 -0
  17. package/src/_stories/_mock/boxplot_multiseries.json +647 -0
  18. package/src/_stories/_mock/dynamic_series_bar_config.json +723 -0
  19. package/src/_stories/_mock/dynamic_series_config.json +979 -0
  20. package/{examples/feature/scatterplot/scatterplot.json → src/_stories/_mock/scatterplot_mock.json} +62 -92
  21. package/src/_stories/_mock/suppression_mock.json +1549 -0
  22. package/src/components/BarChart/components/BarChart.StackedVertical.tsx +43 -9
  23. package/src/components/BarChart/components/BarChart.Vertical.tsx +60 -42
  24. package/src/components/BarChart/helpers/index.ts +1 -2
  25. package/src/components/BoxPlot/BoxPlot.tsx +189 -0
  26. package/src/components/EditorPanel/EditorPanel.tsx +64 -62
  27. package/src/components/EditorPanel/components/Panels/Panel.Annotate.tsx +3 -3
  28. package/src/components/EditorPanel/components/Panels/Panel.BoxPlot.tsx +51 -6
  29. package/src/components/EditorPanel/components/Panels/Panel.Regions.tsx +40 -9
  30. package/src/components/EditorPanel/components/Panels/Panel.Sankey.tsx +3 -3
  31. package/src/components/EditorPanel/components/Panels/Panel.Series.tsx +121 -56
  32. package/src/components/EditorPanel/components/Panels/Panel.Visual.tsx +1 -2
  33. package/src/components/EditorPanel/useEditorPermissions.ts +15 -1
  34. package/src/components/Legend/Legend.Component.tsx +9 -10
  35. package/src/components/Legend/Legend.tsx +16 -16
  36. package/src/components/LineChart/helpers.ts +48 -43
  37. package/src/components/LineChart/index.tsx +88 -82
  38. package/src/components/LinearChart.tsx +17 -10
  39. package/src/components/Sankey/index.tsx +50 -32
  40. package/src/components/Sankey/sankey.scss +6 -5
  41. package/src/components/Sankey/useSankeyAlert.tsx +60 -0
  42. package/src/components/ScatterPlot/ScatterPlot.jsx +20 -4
  43. package/src/data/initial-state.js +3 -9
  44. package/src/hooks/useLegendClasses.ts +10 -23
  45. package/src/hooks/useMinMax.ts +27 -13
  46. package/src/hooks/useReduceData.ts +43 -10
  47. package/src/hooks/useScales.ts +56 -35
  48. package/src/hooks/useTooltip.tsx +54 -49
  49. package/src/scss/main.scss +0 -18
  50. package/src/types/ChartConfig.ts +6 -19
  51. package/src/types/ChartContext.ts +4 -1
  52. package/src/types/ForestPlot.ts +8 -0
  53. 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 { Accordion, AccordionItem, AccordionItemHeading, AccordionItemPanel, AccordionItemButton } from 'react-accessible-accordion'
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 <SeriesContext.Provider value={{ updateSeries, supportedRightAxisTypes, getColumns, selectComponent }}>{props.children}</SeriesContext.Provider>
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={config.series?.[index].stages?.[stageIndex].color ? config.series?.[index].stages?.[stageIndex].color : 'Select'}
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 className={'cove-input__checkbox--small'} onClick={e => updateShowInTooltip(e, index, ciIndex)}>
319
- <div className={`cove-input__checkbox-box${'blue' ? ' custom-color' : ''}`} style={{ backgroundColor: '' }}>
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 className='cove-input--hidden' type='checkbox' name={'showInTooltip'} checked={showInTooltip ? showInTooltip : false} readOnly />
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={config.series[index].confidenceIntervals[ciIndex].low ? config.series[index].confidenceIntervals[ciIndex].low : 'Select'}
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={config.series[index].confidenceIntervals[ciIndex].high ? config.series[index].confidenceIntervals[ciIndex].high : 'Select'}
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] = { ...copyOfSeries[index], confidenceIntervals: [...copiedIndex, { high: '', low: '' }] } // update the nested array
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 = ['Bump Chart', 'Bar', 'Line', 'Area Chart', 'Combo', 'Deviation', 'Paired', 'Scatter', 'dashed-sm', 'dashed-md', 'dashed-lg']
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 className='cove-input--hidden' type='checkbox' name={`series-tooltip--${index}`} checked={series.tooltip ? series.tooltip : false} readOnly />
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 key={i} className={snapshot.isDragging ? 'currently-dragging' : ''} style={getItemStyle(snapshot.isDragging, provided.draggableProps.style, sortableItemStyles)} ref={provided.innerRef} {...provided.draggableProps} {...provided.dragHandleProps}>
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 className={chartsWithOptions.includes(config.visualizationType) ? 'accordion__button' : 'accordion__button hide-arrow'}>
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 <SeriesItem getItemStyle={getItemStyle} sortableItemStyles={sortableItemStyles} chartsWithOptions={chartsWithOptions} series={series} index={i} key={`series-list-${i}`} />
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
- {((config.visualizationType === 'Bar' && config.orientation !== 'horizontal') ||
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 false
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: 'lg' | 'md' | 'sm' | 'xs' | 'xxs'
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
- currentViewport={currentViewport}
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
- <div className='d-flex justify-content-center align-items-center'>
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
- <div className='d-flex flex-column mt-1'>
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
- </div>
151
+ </>
153
152
  )}
154
- </div>
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
- !['Box Plot'].includes(config.visualizationType) && (
28
- <Fragment>
29
- <LegendComponent
30
- dimensions={dimensions}
31
- ref={ref}
32
- skipId={props.skipId || 'legend'}
33
- config={config}
34
- colorScale={colorScale}
35
- seriesHighlight={seriesHighlight}
36
- highlight={highlight}
37
- highlightReset={highlightReset}
38
- currentViewport={currentViewport}
39
- formatLabels={createLegendLabels}
40
- />
41
- </Fragment>
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, dataKey, preliminaryData) {
174
- const result = {
175
- data: [],
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
- const isValidMiddleIndex = index => index > 0 && index < data.length - 1
180
-
181
- preliminaryData?.forEach(pd => {
182
- if (pd.type === 'effect' || pd.hideLineStyle) return
183
- const targetValue = pd.value
184
-
185
- // Find all indices
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
- // Find and add the next calculable object
202
- const nextIndex = data
203
- .slice(i + 1)
204
- .findIndex(item => item[seriesKey] !== targetValue && isCalculable(item[seriesKey]))
205
- if (nextIndex !== -1) {
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, dataKey, preliminaryData)
227
+ const middleSegments = handleMiddleIndices(data, seriesKey, preliminaryData)
228
+
225
229
  // Combine all segments into a single array
226
- return [firstSegment, middleSegments, lastSegment].filter(segment => segment.data.length > 0 && segment.style !== '')
230
+ return [firstSegment, middleSegments, lastSegment]
231
+ // return [firstSegment, middleSegments, lastSegment].filter(segment => segment.data.length > 0 && segment.style !== '')
227
232
  }