@cdc/chart 4.24.9 → 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/LICENSE +201 -0
- package/dist/cdcchart.js +45911 -41739
- 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 +4 -4
- package/package.json +2 -2
- package/src/CdcChart.tsx +209 -188
- 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 +74 -0
- package/src/_stories/Chart.stories.tsx +30 -3
- package/src/_stories/ChartAxisLabels.stories.tsx +20 -0
- package/src/_stories/ChartAxisTitles.stories.tsx +53 -0
- package/src/_stories/ChartEditor.stories.tsx +27 -0
- package/src/_stories/ChartLine.Suppression.stories.tsx +25 -0
- package/src/_stories/ChartPrefixSuffix.stories.tsx +159 -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/src/_stories/_mock/horizontal_bar.json +257 -0
- package/src/_stories/_mock/large_x_axis_labels.json +261 -0
- package/src/_stories/_mock/paired-bar.json +262 -0
- package/src/_stories/_mock/pie_with_data.json +255 -0
- package/{examples/feature/scatterplot/scatterplot.json → src/_stories/_mock/scatterplot_mock.json} +62 -92
- package/src/_stories/_mock/simplified_line.json +1510 -0
- package/src/_stories/_mock/suppression_mock.json +1549 -0
- package/src/components/Annotations/components/AnnotationDraggable.tsx +0 -3
- package/src/components/Annotations/components/AnnotationDropdown.tsx +1 -1
- package/src/components/Axis/Categorical.Axis.tsx +22 -4
- package/src/components/BarChart/components/BarChart.Horizontal.tsx +95 -16
- package/src/components/BarChart/components/BarChart.StackedHorizontal.tsx +41 -17
- package/src/components/BarChart/components/BarChart.StackedVertical.tsx +43 -9
- package/src/components/BarChart/components/BarChart.Vertical.tsx +123 -47
- package/src/components/BarChart/helpers/index.ts +23 -5
- package/src/components/BoxPlot/BoxPlot.tsx +189 -0
- package/src/components/BrushChart.tsx +3 -2
- package/src/components/DeviationBar.jsx +58 -8
- package/src/components/EditorPanel/EditorPanel.tsx +127 -102
- package/src/components/EditorPanel/components/Panels/Panel.Annotate.tsx +11 -28
- package/src/components/EditorPanel/components/Panels/Panel.BoxPlot.tsx +51 -6
- package/src/components/EditorPanel/components/Panels/Panel.General.tsx +21 -4
- 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 +296 -35
- package/src/components/EditorPanel/components/panels.scss +4 -6
- package/src/components/EditorPanel/editor-panel.scss +0 -8
- package/src/components/EditorPanel/helpers/tests/updateFieldRankByValue.test.ts +38 -0
- package/src/components/EditorPanel/helpers/updateFieldRankByValue.ts +42 -0
- package/src/components/EditorPanel/useEditorPermissions.ts +16 -1
- package/src/components/ForestPlot/ForestPlot.tsx +2 -3
- package/src/components/ForestPlot/ForestPlotProps.ts +2 -0
- package/src/components/Legend/Legend.Component.tsx +23 -24
- package/src/components/Legend/Legend.Suppression.tsx +25 -20
- package/src/components/Legend/Legend.tsx +16 -18
- package/src/components/Legend/helpers/index.ts +16 -19
- package/src/components/LegendWrapper.tsx +3 -1
- package/src/components/LineChart/components/LineChart.Circle.tsx +10 -0
- package/src/components/LineChart/helpers.ts +48 -43
- package/src/components/LineChart/index.tsx +88 -82
- package/src/components/LinearChart.tsx +747 -562
- package/src/components/PairedBarChart.jsx +50 -10
- package/src/components/PieChart/PieChart.tsx +1 -6
- package/src/components/Regions/components/Regions.tsx +33 -19
- 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/components/ZoomBrush.tsx +25 -6
- package/src/coreStyles_chart.scss +3 -0
- package/src/data/initial-state.js +8 -10
- package/src/helpers/configHelpers.ts +28 -0
- package/src/helpers/handleRankByValue.ts +15 -0
- package/src/helpers/sizeHelpers.ts +25 -0
- package/src/helpers/tests/handleRankByValue.test.ts +37 -0
- package/src/helpers/tests/sizeHelpers.test.ts +80 -0
- package/src/hooks/useColorPalette.js +10 -2
- package/src/hooks/useLegendClasses.ts +13 -22
- package/src/hooks/useMinMax.ts +27 -13
- package/src/hooks/useReduceData.ts +43 -10
- package/src/hooks/useScales.ts +87 -38
- package/src/hooks/useTooltip.tsx +62 -53
- package/src/index.jsx +1 -0
- package/src/scss/DataTable.scss +5 -4
- package/src/scss/main.scss +57 -70
- package/src/types/ChartConfig.ts +43 -34
- package/src/types/ChartContext.ts +22 -15
- package/src/types/ForestPlot.ts +8 -0
- package/src/_stories/Chart.Legend.Gradient.tsx +0 -19
- package/src/_stories/ChartBrush.stories.tsx +0 -19
- package/src/components/BoxPlot/BoxPlot.jsx +0 -111
- package/src/components/LinearChart.jsx +0 -817
|
@@ -9,17 +9,20 @@ 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
|
-
import { forwardRef } from 'react'
|
|
14
|
+
import { forwardRef, useState } from 'react'
|
|
15
15
|
import LegendSuppression from './Legend.Suppression'
|
|
16
16
|
import LegendGradient from '@cdc/core/components/Legend/Legend.Gradient'
|
|
17
17
|
import { DimensionsType } from '@cdc/core/types/Dimensions'
|
|
18
|
+
import { isLegendWrapViewport } from '@cdc/core/helpers/viewports'
|
|
19
|
+
|
|
20
|
+
const LEGEND_PADDING = 30
|
|
18
21
|
|
|
19
22
|
export interface LegendProps {
|
|
20
23
|
colorScale: ColorScale
|
|
21
24
|
config: ChartConfig
|
|
22
|
-
currentViewport:
|
|
25
|
+
currentViewport: ViewportSize
|
|
23
26
|
formatLabels: (labels: Label[]) => Label[]
|
|
24
27
|
highlight: Function
|
|
25
28
|
highlightReset: Function
|
|
@@ -27,7 +30,6 @@ export interface LegendProps {
|
|
|
27
30
|
seriesHighlight: string[]
|
|
28
31
|
skipId: string
|
|
29
32
|
dimensions: DimensionsType // for responsive width legend
|
|
30
|
-
getTextWidth: (text: string, font: string) => string
|
|
31
33
|
}
|
|
32
34
|
|
|
33
35
|
/* eslint-disable jsx-a11y/no-noninteractive-tabindex, jsx-a11y/no-static-element-interactions */
|
|
@@ -42,24 +44,21 @@ const Legend: React.FC<LegendProps> = forwardRef(
|
|
|
42
44
|
currentViewport,
|
|
43
45
|
formatLabels,
|
|
44
46
|
skipId = 'legend',
|
|
45
|
-
dimensions
|
|
46
|
-
getTextWidth
|
|
47
|
+
dimensions
|
|
47
48
|
},
|
|
48
49
|
ref
|
|
49
50
|
) => {
|
|
50
51
|
const { innerClasses, containerClasses } = useLegendClasses(config)
|
|
51
52
|
const { runtime, legend } = config
|
|
52
53
|
|
|
54
|
+
const [hasSuppression, setHasSuppression] = useState(false)
|
|
55
|
+
|
|
53
56
|
const isBottomOrSmallViewport =
|
|
54
|
-
legend?.position === 'bottom' || (
|
|
57
|
+
legend?.position === 'bottom' || (isLegendWrapViewport(currentViewport) && !legend.hide)
|
|
55
58
|
|
|
56
59
|
const legendClasses = {
|
|
57
|
-
marginBottom: getMarginBottom(
|
|
58
|
-
|
|
59
|
-
marginTop:
|
|
60
|
-
isBottomOrSmallViewport && config.orientation === 'horizontal'
|
|
61
|
-
? `${config.yAxis.label && config.isResponsiveTicks ? config.dynamicMarginTop : config.runtime.xAxis.size}px`
|
|
62
|
-
: getMarginTop(isBottomOrSmallViewport, config.brush.active, legend)
|
|
60
|
+
marginBottom: getMarginBottom(config, hasSuppression),
|
|
61
|
+
marginTop: getMarginTop(isBottomOrSmallViewport, config)
|
|
63
62
|
}
|
|
64
63
|
|
|
65
64
|
const { HighLightedBarUtils } = useHighlightedBars(config)
|
|
@@ -78,11 +77,10 @@ const Legend: React.FC<LegendProps> = forwardRef(
|
|
|
78
77
|
{legend.label && <h3>{parse(legend.label)}</h3>}
|
|
79
78
|
{legend.description && <p>{parse(legend.description)}</p>}
|
|
80
79
|
<LegendGradient
|
|
81
|
-
getTextWidth={getTextWidth}
|
|
82
80
|
config={config}
|
|
83
81
|
{...getGradientConfig(config, formatLabels, colorScale)}
|
|
84
82
|
dimensions={dimensions}
|
|
85
|
-
|
|
83
|
+
parentPaddingToSubtract={legend.hideBorder ? 0 : LEGEND_PADDING}
|
|
86
84
|
/>
|
|
87
85
|
|
|
88
86
|
<LegendOrdinal scale={colorScale} itemDirection='row' labelMargin='0 20px 0 0' shapeMargin='0 10px 0'>
|
|
@@ -133,9 +131,9 @@ const Legend: React.FC<LegendProps> = forwardRef(
|
|
|
133
131
|
}}
|
|
134
132
|
role='button'
|
|
135
133
|
>
|
|
136
|
-
|
|
134
|
+
<>
|
|
137
135
|
{config.visualizationType === 'Line' && config.legend.style === 'lines' ? (
|
|
138
|
-
<svg width={40} height={
|
|
136
|
+
<svg width={40} height={25}>
|
|
139
137
|
<Line
|
|
140
138
|
from={{ x: 10, y: 10 }}
|
|
141
139
|
to={{ x: 40, y: 10 }}
|
|
@@ -145,17 +143,14 @@ const Legend: React.FC<LegendProps> = forwardRef(
|
|
|
145
143
|
/>
|
|
146
144
|
</svg>
|
|
147
145
|
) : (
|
|
148
|
-
|
|
146
|
+
<>
|
|
149
147
|
<LegendShape
|
|
150
148
|
shape={config.legend.style === 'boxes' ? 'square' : 'circle'}
|
|
151
|
-
viewport={currentViewport}
|
|
152
|
-
margin='0'
|
|
153
149
|
fill={label.value}
|
|
154
|
-
display={true}
|
|
155
150
|
/>
|
|
156
|
-
|
|
151
|
+
</>
|
|
157
152
|
)}
|
|
158
|
-
|
|
153
|
+
</>
|
|
159
154
|
|
|
160
155
|
<LegendLabel align='left' margin='0 0 0 4px'>
|
|
161
156
|
{label.text}
|
|
@@ -204,7 +199,11 @@ const Legend: React.FC<LegendProps> = forwardRef(
|
|
|
204
199
|
})}
|
|
205
200
|
</div>
|
|
206
201
|
|
|
207
|
-
<LegendSuppression
|
|
202
|
+
<LegendSuppression
|
|
203
|
+
config={config}
|
|
204
|
+
isBottomOrSmallViewport={isBottomOrSmallViewport}
|
|
205
|
+
setHasSuppression={setHasSuppression}
|
|
206
|
+
/>
|
|
208
207
|
</>
|
|
209
208
|
)
|
|
210
209
|
}}
|
|
@@ -7,11 +7,11 @@ interface LegendProps {
|
|
|
7
7
|
isBottomOrSmallViewport: boolean
|
|
8
8
|
}
|
|
9
9
|
|
|
10
|
-
const LegendSuppression: React.FC<LegendProps> = ({ config, isBottomOrSmallViewport }) => {
|
|
10
|
+
const LegendSuppression: React.FC<LegendProps> = ({ config, isBottomOrSmallViewport, setHasSuppression }) => {
|
|
11
11
|
const { preliminaryData, visualizationType, visualizationSubType, legend } = config
|
|
12
12
|
|
|
13
13
|
const hasOpenCircleEffects = () =>
|
|
14
|
-
preliminaryData?.some(pd => pd.label && pd.type === 'effect' && pd.style
|
|
14
|
+
preliminaryData?.some(pd => pd.label && pd.type === 'effect' && pd.style !== 'Filled Circles') &&
|
|
15
15
|
['Line', 'Combo'].includes(visualizationType)
|
|
16
16
|
|
|
17
17
|
const shouldShowSuppressedLabels = () =>
|
|
@@ -100,6 +100,13 @@ const LegendSuppression: React.FC<LegendProps> = ({ config, isBottomOrSmallViewp
|
|
|
100
100
|
const getLegendContainerClass = () =>
|
|
101
101
|
legend.singleRow && isBottomOrSmallViewport ? 'legend-container__inner bottom single-row' : ''
|
|
102
102
|
|
|
103
|
+
const shouldShowSuppressedInfo = () =>
|
|
104
|
+
!config.legend.hideSuppressionLink &&
|
|
105
|
+
config.visualizationSubType !== 'stacked' &&
|
|
106
|
+
preliminaryData?.some(pd => pd.label && pd.type === 'suppression' && pd.value && (pd?.style || pd.symbol))
|
|
107
|
+
|
|
108
|
+
setHasSuppression(shouldShowSuppressedInfo())
|
|
109
|
+
|
|
103
110
|
return (
|
|
104
111
|
<React.Fragment>
|
|
105
112
|
{hasOpenCircleEffects() && (
|
|
@@ -115,24 +122,22 @@ const LegendSuppression: React.FC<LegendProps> = ({ config, isBottomOrSmallViewp
|
|
|
115
122
|
<div className={getLegendContainerClass()}>{renderSuppressedItems()}</div>
|
|
116
123
|
</React.Fragment>
|
|
117
124
|
)}
|
|
118
|
-
{
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
<
|
|
122
|
-
|
|
123
|
-
<
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
</div>
|
|
135
|
-
)}
|
|
125
|
+
{shouldShowSuppressedInfo() && (
|
|
126
|
+
<div className='legend-container__outer definition-link'>
|
|
127
|
+
<Icon alt='info-icon' display='info' />
|
|
128
|
+
<p>
|
|
129
|
+
This chart contains
|
|
130
|
+
<a // prettier-ignore
|
|
131
|
+
onClick={handleLinkClick}
|
|
132
|
+
data-tooltip-content='Data is suppressed to maintain statistical reliability. This occurs when the number of respondents or reported values does not meet the minimum reporting threshold.'
|
|
133
|
+
data-tooltip-id='my-tooltip'
|
|
134
|
+
href='no-router-link'
|
|
135
|
+
>
|
|
136
|
+
suppressed data
|
|
137
|
+
</a>
|
|
138
|
+
</p>
|
|
139
|
+
</div>
|
|
140
|
+
)}
|
|
136
141
|
|
|
137
142
|
<ReactTooltip // prettier-ignore
|
|
138
143
|
id='my-tooltip'
|
|
@@ -17,7 +17,7 @@ const Legend = forwardRef((props, ref) => {
|
|
|
17
17
|
transformedData: data,
|
|
18
18
|
currentViewport,
|
|
19
19
|
dimensions,
|
|
20
|
-
getTextWidth
|
|
20
|
+
getTextWidth
|
|
21
21
|
} = useContext(ConfigContext)
|
|
22
22
|
if (!config.legend) return null
|
|
23
23
|
// create fn to reverse labels while legend is Bottom. Legend-right , legend-left works by default.
|
|
@@ -25,23 +25,21 @@ const Legend = forwardRef((props, ref) => {
|
|
|
25
25
|
const createLegendLabels = createFormatLabels(config, tableData, data, colorScale)
|
|
26
26
|
|
|
27
27
|
return (
|
|
28
|
-
|
|
29
|
-
<
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
</Fragment>
|
|
44
|
-
)
|
|
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>
|
|
45
43
|
)
|
|
46
44
|
})
|
|
47
45
|
|
|
@@ -1,8 +1,3 @@
|
|
|
1
|
-
export const getMarginTop = (isBottomOrSmallViewport, isBrushActive, legend) => {
|
|
2
|
-
if (!isBottomOrSmallViewport) return '0px'
|
|
3
|
-
if (isBrushActive && legend.position === 'bottom') return '35px'
|
|
4
|
-
}
|
|
5
|
-
|
|
6
1
|
export const getGradientConfig = (config, formatLabels, colorScale) => {
|
|
7
2
|
const defaultValue = [{ datum: '', index: 0, text: '', value: '' }]
|
|
8
3
|
|
|
@@ -15,21 +10,23 @@ export const getGradientConfig = (config, formatLabels, colorScale) => {
|
|
|
15
10
|
return { colors, labels }
|
|
16
11
|
}
|
|
17
12
|
|
|
18
|
-
export const
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
const isLegendTop = config.legend?.position === 'top' && !config.legend.hide
|
|
22
|
-
|
|
23
|
-
let marginBottom = '0px'
|
|
24
|
-
if (isLegendTop && !isSuppressedActive) {
|
|
25
|
-
marginBottom = config.legend.hideBorder.topBottom ? '15px' : '25px'
|
|
26
|
-
}
|
|
27
|
-
if (isLegendTop && isSuppressedActive) {
|
|
28
|
-
marginBottom = '75px'
|
|
13
|
+
export const getMarginTop = (isBottomOrSmallViewport, config) => {
|
|
14
|
+
if (!isBottomOrSmallViewport) {
|
|
15
|
+
return '0px'
|
|
29
16
|
}
|
|
30
|
-
if (isBottomOrSmallViewport &&
|
|
31
|
-
|
|
17
|
+
if (isBottomOrSmallViewport && config.brush?.active) {
|
|
18
|
+
return '35px'
|
|
32
19
|
}
|
|
20
|
+
return '20px'
|
|
21
|
+
}
|
|
22
|
+
export const getMarginBottom = (config, hasSuppression) => {
|
|
23
|
+
const isLegendTop = config.legend?.position === 'top' && !config.legend.hide
|
|
24
|
+
|
|
25
|
+
let marginBottom = 0
|
|
26
|
+
|
|
27
|
+
if (isLegendTop) marginBottom = config.legend.hideBorder.topBottom ? 15 : 25
|
|
28
|
+
|
|
29
|
+
if (hasSuppression) marginBottom += 40
|
|
33
30
|
|
|
34
|
-
return marginBottom
|
|
31
|
+
return `${marginBottom}px`
|
|
35
32
|
}
|
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
import React, { useContext } from 'react'
|
|
2
2
|
import ConfigContext from '../ConfigContext'
|
|
3
3
|
|
|
4
|
+
import { isLegendWrapViewport } from '@cdc/core/helpers/viewports'
|
|
5
|
+
|
|
4
6
|
type LegendWrapperProps = {
|
|
5
7
|
children: React.ReactNode
|
|
6
8
|
}
|
|
@@ -13,7 +15,7 @@ const LegendWrapper: React.FC<LegendWrapperProps> = props => {
|
|
|
13
15
|
const getLegendWrappingClasses = () => {
|
|
14
16
|
let classes = ['legend-wrapper', 'd-flex', 'flex-nowrap', 'w-100']
|
|
15
17
|
const { legend } = config
|
|
16
|
-
if (legend.position === 'bottom' || legend.position === 'top' ||
|
|
18
|
+
if (legend.position === 'bottom' || legend.position === 'top' || isLegendWrapViewport(currentViewport)) {
|
|
17
19
|
classes = classes.filter(item => item !== 'flex-nowrap')
|
|
18
20
|
classes.push('flex-wrap')
|
|
19
21
|
}
|
|
@@ -147,10 +147,17 @@ const LineChartCircle = (props: LineChartCircleProps) => {
|
|
|
147
147
|
|
|
148
148
|
if (mode === 'ISOLATED_POINTS') {
|
|
149
149
|
const drawIsolatedPoints = (currentIndex, seriesKey) => {
|
|
150
|
+
let isMatch = false
|
|
150
151
|
const currentPoint = data[currentIndex]
|
|
151
152
|
const previousPoint = currentIndex > 0 ? data[currentIndex - 1] : null
|
|
152
153
|
const nextPoint = currentIndex < data.length - 1 ? data[currentIndex + 1] : null
|
|
153
154
|
let res = false
|
|
155
|
+
// check if isolated points has overlap with circle effect
|
|
156
|
+
circleData.forEach(item => {
|
|
157
|
+
if (item?.data[seriesKey] === currentPoint[seriesKey]) {
|
|
158
|
+
isMatch = true
|
|
159
|
+
}
|
|
160
|
+
})
|
|
154
161
|
|
|
155
162
|
// Handle the first point in the array
|
|
156
163
|
if (currentIndex === 0 && nextPoint && !nextPoint[seriesKey]) {
|
|
@@ -171,6 +178,9 @@ const LineChartCircle = (props: LineChartCircleProps) => {
|
|
|
171
178
|
res = true
|
|
172
179
|
}
|
|
173
180
|
}
|
|
181
|
+
if (isMatch) {
|
|
182
|
+
res = false
|
|
183
|
+
}
|
|
174
184
|
|
|
175
185
|
return res
|
|
176
186
|
}
|
|
@@ -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
|
}
|