@cdc/chart 4.24.7 → 4.24.9

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 (51) hide show
  1. package/dist/cdcchart.js +40313 -37543
  2. package/examples/cases-year.json +13379 -0
  3. package/examples/gallery/bar-chart-vertical/combo-line-chart.json +76 -15
  4. package/examples/gallery/bar-chart-vertical/vertical-bar-chart-stacked.json +5 -5
  5. package/index.html +17 -8
  6. package/package.json +2 -2
  7. package/src/CdcChart.tsx +383 -133
  8. package/src/_stories/Chart.Legend.Gradient.tsx +19 -0
  9. package/src/_stories/_mock/legend.gradient_mock.json +236 -0
  10. package/src/components/Annotations/components/AnnotationDraggable.tsx +64 -11
  11. package/src/components/Axis/Categorical.Axis.tsx +145 -0
  12. package/src/components/BarChart/components/BarChart.Horizontal.tsx +4 -3
  13. package/src/components/BarChart/components/BarChart.StackedHorizontal.tsx +1 -1
  14. package/src/components/BarChart/components/BarChart.StackedVertical.tsx +2 -5
  15. package/src/components/BarChart/components/BarChart.Vertical.tsx +17 -8
  16. package/src/components/BarChart/helpers/index.ts +5 -16
  17. package/src/components/BrushChart.tsx +205 -0
  18. package/src/components/EditorPanel/EditorPanel.tsx +1766 -509
  19. package/src/components/EditorPanel/components/Panels/Panel.Annotate.tsx +19 -5
  20. package/src/components/EditorPanel/components/Panels/Panel.General.tsx +190 -37
  21. package/src/components/EditorPanel/components/Panels/Panel.Sankey.tsx +43 -7
  22. package/src/components/EditorPanel/components/Panels/Panel.Series.tsx +4 -4
  23. package/src/components/EditorPanel/components/Panels/Panel.Visual.tsx +1 -11
  24. package/src/components/EditorPanel/editor-panel.scss +16 -3
  25. package/src/components/EditorPanel/{useEditorPermissions.js → useEditorPermissions.ts} +90 -19
  26. package/src/components/Legend/Legend.Component.tsx +185 -193
  27. package/src/components/Legend/Legend.Suppression.tsx +146 -0
  28. package/src/components/Legend/Legend.tsx +21 -5
  29. package/src/components/Legend/helpers/index.ts +33 -3
  30. package/src/components/LegendWrapper.tsx +26 -0
  31. package/src/components/LineChart/LineChartProps.ts +1 -18
  32. package/src/components/LineChart/components/LineChart.BumpCircle.tsx +103 -0
  33. package/src/components/LineChart/components/LineChart.Circle.tsx +47 -8
  34. package/src/components/LineChart/helpers.ts +55 -11
  35. package/src/components/LineChart/index.tsx +113 -38
  36. package/src/components/LinearChart.tsx +1366 -0
  37. package/src/components/PieChart/PieChart.tsx +74 -17
  38. package/src/components/Sankey/index.tsx +22 -16
  39. package/src/components/Sparkline/components/SparkLine.tsx +2 -2
  40. package/src/data/initial-state.js +13 -3
  41. package/src/hooks/useLegendClasses.ts +52 -15
  42. package/src/hooks/useMinMax.ts +4 -4
  43. package/src/hooks/useScales.ts +34 -24
  44. package/src/hooks/useTooltip.tsx +85 -22
  45. package/src/scss/DataTable.scss +2 -1
  46. package/src/scss/main.scss +107 -14
  47. package/src/types/ChartConfig.ts +34 -8
  48. package/src/types/ChartContext.ts +5 -4
  49. package/examples/feature/line/line-chart.json +0 -449
  50. package/src/components/BrushHandle.jsx +0 -17
  51. package/src/components/LineChart/index.scss +0 -1
@@ -0,0 +1,146 @@
1
+ import React from 'react'
2
+ import { ChartConfig } from '../../types/ChartConfig'
3
+ import Icon from '@cdc/core/components/ui/Icon'
4
+ import { Tooltip as ReactTooltip } from 'react-tooltip'
5
+ interface LegendProps {
6
+ config: ChartConfig
7
+ isBottomOrSmallViewport: boolean
8
+ }
9
+
10
+ const LegendSuppression: React.FC<LegendProps> = ({ config, isBottomOrSmallViewport }) => {
11
+ const { preliminaryData, visualizationType, visualizationSubType, legend } = config
12
+
13
+ const hasOpenCircleEffects = () =>
14
+ preliminaryData?.some(pd => pd.label && pd.type === 'effect' && pd.style === 'Open Circles') &&
15
+ ['Line', 'Combo'].includes(visualizationType)
16
+
17
+ const shouldShowSuppressedLabels = () =>
18
+ !legend.hideSuppressedLabels &&
19
+ preliminaryData?.some(
20
+ pd => pd.label && pd.displayLegend && pd.type === 'suppression' && pd.value && (pd?.style || pd.symbol)
21
+ ) &&
22
+ ((visualizationType === 'Bar' && visualizationSubType === 'regular') ||
23
+ visualizationType === 'Line' ||
24
+ visualizationType === 'Combo')
25
+
26
+ const renderEffectItems = () =>
27
+ preliminaryData?.map(
28
+ (pd, index) =>
29
+ pd.label &&
30
+ pd.type === 'effect' &&
31
+ pd.style && (
32
+ <div key={index} className='legend-preliminary'>
33
+ <span className={pd.symbol}>{pd.lineCode}</span>
34
+ <p>{pd.label}</p>
35
+ </div>
36
+ )
37
+ )
38
+ const handleLinkClick = event => {
39
+ // prevent defintion link to change URl
40
+ event.preventDefault()
41
+ }
42
+
43
+ const renderSuppressedItems = () => {
44
+ const getStyle = displayGray => {
45
+ if (displayGray) {
46
+ return {
47
+ color: '#777772'
48
+ }
49
+ }
50
+ return null
51
+ }
52
+ return preliminaryData?.map((pd, index) => {
53
+ if (!pd.displayLegend || pd.type !== 'suppression') return null
54
+
55
+ const baseClass = 'legend-preliminary'
56
+ const itemKey = index + visualizationType
57
+
58
+ if (visualizationType === 'Bar') {
59
+ return (
60
+ <div style={getStyle(pd.displayGray)} key={itemKey} className={`${baseClass} ${pd.symbol}`}>
61
+ <span className={pd.symbol}>{pd.iconCode}</span>
62
+ <p className={pd.type}>{pd.label}</p>
63
+ </div>
64
+ )
65
+ }
66
+
67
+ if (visualizationType === 'Line') {
68
+ return (
69
+ <div style={getStyle(pd.displayGray)} key={itemKey} className={baseClass}>
70
+ <span>{pd.lineCode}</span>
71
+ <p className={pd.type}>{pd.label}</p>
72
+ </div>
73
+ )
74
+ }
75
+
76
+ if (visualizationType === 'Combo') {
77
+ return (
78
+ <React.Fragment>
79
+ {pd.symbol && pd.iconCode && (
80
+ <div style={getStyle(pd.displayGray)} key={itemKey} className={`${baseClass} ${pd.symbol}`}>
81
+ <span className={pd.symbol}>{pd.iconCode}</span>
82
+ <p className={pd.type}>{pd.label}</p>
83
+ </div>
84
+ )}
85
+
86
+ {pd.style && pd.lineCode && (
87
+ <div style={getStyle(pd.displayGray)} key={itemKey} className={baseClass}>
88
+ <span>{pd.lineCode}</span>
89
+ <p>{pd.label}</p>
90
+ </div>
91
+ )}
92
+ </React.Fragment>
93
+ )
94
+ }
95
+
96
+ return null
97
+ })
98
+ }
99
+
100
+ const getLegendContainerClass = () =>
101
+ legend.singleRow && isBottomOrSmallViewport ? 'legend-container__inner bottom single-row' : ''
102
+
103
+ return (
104
+ <React.Fragment>
105
+ {hasOpenCircleEffects() && (
106
+ <React.Fragment>
107
+ <hr />
108
+ <div className={getLegendContainerClass()}>{renderEffectItems()}</div>
109
+ </React.Fragment>
110
+ )}
111
+
112
+ {shouldShowSuppressedLabels() && (
113
+ <React.Fragment>
114
+ <hr />
115
+ <div className={getLegendContainerClass()}>{renderSuppressedItems()}</div>
116
+ </React.Fragment>
117
+ )}
118
+ {!config.legend.hideSuppressionLink &&
119
+ config.visualizationSubType !== 'stacked' &&
120
+ preliminaryData?.some(pd => pd.label && pd.type === 'suppression' && pd.value && (pd?.style || pd.symbol)) && (
121
+ <div className='legend-container__outer definition-link'>
122
+ <Icon alt='info-icon' display='info' />
123
+ <p>
124
+ This chart contains
125
+ <a // prettier-ignore
126
+ onClick={handleLinkClick}
127
+ 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.'
128
+ data-tooltip-id='my-tooltip'
129
+ href='no-router-link'
130
+ >
131
+ suppressed data
132
+ </a>
133
+ </p>
134
+ </div>
135
+ )}
136
+
137
+ <ReactTooltip // prettier-ignore
138
+ id='my-tooltip'
139
+ variant='light'
140
+ style={{ background: `rgba(255,255,255, ${config.tooltips.opacity / 100})`, color: 'black', maxWidth: '100%' }}
141
+ />
142
+ </React.Fragment>
143
+ )
144
+ }
145
+
146
+ export default LegendSuppression
@@ -1,4 +1,4 @@
1
- import { useContext, forwardRef } from 'react'
1
+ import { useContext, forwardRef, Fragment } from 'react'
2
2
  import ConfigContext from '../../ConfigContext'
3
3
  import LegendComponent from './Legend.Component'
4
4
  import { createFormatLabels } from './helpers/createFormatLabels'
@@ -7,6 +7,7 @@ import { createFormatLabels } from './helpers/createFormatLabels'
7
7
  const Legend = forwardRef((props, ref) => {
8
8
  // prettier-ignore
9
9
  const {
10
+ // prettier-ignore
10
11
  config,
11
12
  colorScale,
12
13
  seriesHighlight,
@@ -14,17 +15,32 @@ const Legend = forwardRef((props, ref) => {
14
15
  tableData,
15
16
  highlightReset,
16
17
  transformedData: data,
17
- currentViewport
18
+ currentViewport,
19
+ dimensions,
20
+ getTextWidth,
18
21
  } = useContext(ConfigContext)
19
-
20
22
  if (!config.legend) return null
21
23
  // create fn to reverse labels while legend is Bottom. Legend-right , legend-left works by default.
22
24
 
23
25
  const createLegendLabels = createFormatLabels(config, tableData, data, colorScale)
24
26
 
25
27
  return (
26
- !['Box Plot', 'Pie'].includes(config.visualizationType) && (
27
- <LegendComponent ref={ref} skipId={props.skipId || 'legend'} config={config} colorScale={colorScale} seriesHighlight={seriesHighlight} highlight={highlight} highlightReset={highlightReset} currentViewport={currentViewport} formatLabels={createLegendLabels} />
28
+ !['Box Plot'].includes(config.visualizationType) && (
29
+ <Fragment>
30
+ <LegendComponent
31
+ getTextWidth={getTextWidth}
32
+ dimensions={dimensions}
33
+ ref={ref}
34
+ skipId={props.skipId || 'legend'}
35
+ config={config}
36
+ colorScale={colorScale}
37
+ seriesHighlight={seriesHighlight}
38
+ highlight={highlight}
39
+ highlightReset={highlightReset}
40
+ currentViewport={currentViewport}
41
+ formatLabels={createLegendLabels}
42
+ />
43
+ </Fragment>
28
44
  )
29
45
  )
30
46
  })
@@ -1,5 +1,35 @@
1
- export const getMarginTop = (isBottomOrSmallViewport, isBrushActive) => {
1
+ export const getMarginTop = (isBottomOrSmallViewport, isBrushActive, legend) => {
2
2
  if (!isBottomOrSmallViewport) return '0px'
3
- if (isBrushActive) return '35px'
4
- return '15px'
3
+ if (isBrushActive && legend.position === 'bottom') return '35px'
4
+ }
5
+
6
+ export const getGradientConfig = (config, formatLabels, colorScale) => {
7
+ const defaultValue = [{ datum: '', index: 0, text: '', value: '' }]
8
+
9
+ const formatted = formatLabels(defaultValue)
10
+ const colors = config.legend.colorCode ? formatted.map(label => label?.value) : colorScale?.range() ?? []
11
+ const labels = config.legend.colorCode
12
+ ? formatted.map(label => label?.text || label?.datum)
13
+ : colorScale?.domain() ?? []
14
+
15
+ return { colors, labels }
16
+ }
17
+
18
+ export const getMarginBottom = (isBottomOrSmallViewport, config) => {
19
+ const isSuppressedActive = config.preliminaryData.some(pd => pd.label) && !config.legend.hideSuppressionLink
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'
29
+ }
30
+ if (isBottomOrSmallViewport && isSuppressedActive) {
31
+ marginBottom = '45px'
32
+ }
33
+
34
+ return marginBottom
5
35
  }
@@ -0,0 +1,26 @@
1
+ import React, { useContext } from 'react'
2
+ import ConfigContext from '../ConfigContext'
3
+
4
+ type LegendWrapperProps = {
5
+ children: React.ReactNode
6
+ }
7
+
8
+ const LegendWrapper: React.FC<LegendWrapperProps> = props => {
9
+ const { children } = props
10
+
11
+ const { config, currentViewport } = useContext(ConfigContext)
12
+
13
+ const getLegendWrappingClasses = () => {
14
+ let classes = ['legend-wrapper', 'd-flex', 'flex-nowrap', 'w-100']
15
+ const { legend } = config
16
+ if (legend.position === 'bottom' || legend.position === 'top' || ['xxs', 'xs', 'sm'].includes(currentViewport)) {
17
+ classes = classes.filter(item => item !== 'flex-nowrap')
18
+ classes.push('flex-wrap')
19
+ }
20
+ return classes.join(' ')
21
+ }
22
+
23
+ return <div className={getLegendWrappingClasses()}>{...children}</div>
24
+ }
25
+
26
+ export default LegendWrapper
@@ -1,5 +1,6 @@
1
1
  // todo: review tooltipData type
2
2
  // todo: review svgRef type
3
+ import { type PreliminaryDataItem } from '../../types/ChartConfig'
3
4
  export type LineChartProps = {
4
5
  xScale: Function
5
6
  yScale: Function
@@ -16,24 +17,6 @@ export type LineChartProps = {
16
17
  tooltipData: any
17
18
  }
18
19
 
19
- export interface PreliminaryDataItem {
20
- column: string
21
- displayLegend: boolean
22
- displayTable: boolean
23
- displayTooltip: boolean
24
- iconCode: string
25
- label: string
26
- lineCode: string
27
- seriesKey: string
28
- style: string
29
- symbol: string
30
- type: 'effect' | 'suppression'
31
- value: string
32
- hideBarSymbol: boolean
33
- hideLineStyle: boolean
34
- circleSize: number
35
- }
36
-
37
20
  export interface DataItem {
38
21
  [key: string]: any
39
22
  }
@@ -0,0 +1,103 @@
1
+ import { useState } from 'react'
2
+ import { Group } from '@visx/group'
3
+ import { type Column } from '@cdc/core/types/Column'
4
+ import React from 'react'
5
+ import { type ChartConfig } from '../../../types/ChartConfig'
6
+
7
+ type LineChartBumpCircleProp = {
8
+ config: ChartConfig,
9
+ xScale: any,
10
+ yScale: any,
11
+ parseDate: any
12
+ }
13
+
14
+ const LineChartBumpCircle = (props: LineChartBumpCircleProp) => {
15
+ const { config, xScale, yScale, parseDate } = props
16
+
17
+ // get xScale and yScale...
18
+ if (!config?.runtime?.series) return
19
+
20
+ const handleX = xValue => {
21
+ if (config.xAxis.type === 'date') {
22
+ return parseDate(xValue).getTime()
23
+ }
24
+ if (config.xAxis.type === 'date-time') {
25
+ return new Date(xValue)
26
+ }
27
+ if (config.xAxis.type === 'categorical') {
28
+ return xValue
29
+ }
30
+ }
31
+
32
+ const checkBandScale = xValue => {
33
+ return xScale.bandwidth ? xScale.bandwidth() / 2 + Number(xValue) : Number(xValue)
34
+ }
35
+
36
+
37
+ const getListItems = dataRow => {
38
+ return Object.values(config.columns)
39
+ ?.filter(column => column.tooltips).map(column => {
40
+ const label = column.label || column.name;
41
+ return `
42
+ <li className='tooltip-body'>
43
+ <strong>${label}</strong>: ${dataRow[column.name]}
44
+ </li>`;
45
+ })
46
+ .join(' ');
47
+ }
48
+
49
+ const getTooltip = dataRow => `<ul> ${getListItems(dataRow)} </ul>`
50
+
51
+ const circles = config.runtime?.series.map((series) => {
52
+ return config.data.map((d, dataIndex) => {
53
+ let series_dataKey = d[series.dataKey]
54
+ let axis_dataKey = d[config.xAxis.dataKey]
55
+ return (
56
+ <React.Fragment key={`bump-circle-${series_dataKey}-${dataIndex}`}>
57
+ <Group left={Number(config.runtime.yAxis.size)}>
58
+ {series_dataKey && (
59
+ <>
60
+ <circle
61
+ key={`bump-circle-${series_dataKey}-${dataIndex}`}
62
+ data-tooltip-html={getTooltip(d)}
63
+ data-tooltip-id={`bump-chart`}
64
+ r={10}
65
+ cx={Number(checkBandScale(xScale(handleX(axis_dataKey))))}
66
+ cy={Number(yScale(series_dataKey))}
67
+ stroke='#CACACA'
68
+ strokeWidth={1}
69
+ fill='#E5E4E2'
70
+ />
71
+ {series_dataKey.toString().length === 2 ? (
72
+ // prettier-ignore
73
+ <text
74
+ x={Number(checkBandScale(xScale(handleX(axis_dataKey)))) - 7}
75
+ y={Number(yScale(series_dataKey)) + 4}
76
+ fill='#000000'
77
+ fontSize={11.5}
78
+ >
79
+ {series_dataKey}
80
+ </text>
81
+ ) : (
82
+ // prettier-ignore
83
+ <text
84
+ x={Number(checkBandScale(xScale(handleX(axis_dataKey)))) - 4}
85
+ y={Number(yScale(series_dataKey)) + 4}
86
+ fill='#000000'
87
+ fontSize={11.5}
88
+ >
89
+ {series_dataKey}
90
+ </text>
91
+ )}
92
+ </>
93
+ )}
94
+ </Group>
95
+ </React.Fragment>
96
+ )
97
+ })
98
+ })
99
+
100
+ return <>{circles}</>
101
+ }
102
+
103
+ export default LineChartBumpCircle
@@ -27,11 +27,33 @@ type LineChartCircleProps = {
27
27
  }
28
28
 
29
29
  const LineChartCircle = (props: LineChartCircleProps) => {
30
- const { config, d, tableData, displayArea, seriesKey, tooltipData, xScale, yScale, colorScale, parseDate, yScaleRight, data, circleData, dataIndex, mode } = props
30
+ const {
31
+ config,
32
+ d,
33
+ tableData,
34
+ displayArea,
35
+ seriesKey,
36
+ tooltipData,
37
+ xScale,
38
+ yScale,
39
+ colorScale,
40
+ parseDate,
41
+ yScaleRight,
42
+ data,
43
+ circleData,
44
+ dataIndex,
45
+ mode
46
+ } = props
31
47
  const { lineDatapointStyle } = config
32
- const filtered = config?.series.filter(s => s.dataKey === seriesKey)?.[0]
48
+ const filtered = config?.runtime?.series.filter(s => s.dataKey === seriesKey)?.[0]
33
49
  // If we're not showing the circle, simply return
34
- const getColor = (displayArea: boolean, colorScale: Function, config: ChartConfig, hoveredKey: string, seriesKey: string) => {
50
+ const getColor = (
51
+ displayArea: boolean,
52
+ colorScale: Function,
53
+ config: ChartConfig,
54
+ hoveredKey: string,
55
+ seriesKey: string
56
+ ) => {
35
57
  const seriesLabels = config.runtime.seriesLabels || []
36
58
  let color
37
59
 
@@ -47,14 +69,19 @@ const LineChartCircle = (props: LineChartCircleProps) => {
47
69
  return color
48
70
  }
49
71
  const getXPos = hoveredXValue => {
50
- return (config.xAxis.type === 'categorical' ? xScale(hoveredXValue) : xScale(parseDate(hoveredXValue))) + (xScale.bandwidth ? xScale.bandwidth() / 2 : 0)
72
+ return (
73
+ (config.xAxis.type === 'categorical' ? xScale(hoveredXValue) : xScale(parseDate(hoveredXValue))) +
74
+ (xScale.bandwidth ? xScale.bandwidth() / 2 : 0)
75
+ )
51
76
  }
52
77
  if (mode === 'ALWAYS_SHOW_POINTS') {
53
78
  if (lineDatapointStyle === 'hidden') return <></>
54
79
  const getIndex = seriesKey => config.runtime.seriesLabelsAll.indexOf(seriesKey)
55
80
 
56
81
  if (lineDatapointStyle === 'always show') {
57
- const isMatch = circleData?.some(cd => cd[config.xAxis.dataKey] === d[config.xAxis.dataKey] && cd[seriesKey] === d[seriesKey])
82
+ const isMatch = circleData?.some(
83
+ cd => cd[config.xAxis.dataKey] === d[config.xAxis.dataKey] && cd[seriesKey] === d[seriesKey]
84
+ )
58
85
  if (isMatch) {
59
86
  return <></>
60
87
  }
@@ -98,7 +125,7 @@ const LineChartCircle = (props: LineChartCircleProps) => {
98
125
  if (isNaN(hoveredSeriesValue)) return <></>
99
126
  const isMatch = circleData?.some(cd => cd[config.xAxis.dataKey] === hoveredXValue)
100
127
 
101
- if (isMatch) {
128
+ if (isMatch || !hoveredSeriesValue) {
102
129
  return <></>
103
130
  }
104
131
 
@@ -135,7 +162,12 @@ const LineChartCircle = (props: LineChartCircleProps) => {
135
162
  }
136
163
  // Handle points in the middle
137
164
  if (currentIndex > 0 && currentIndex < data.length - 1) {
138
- if (currentPoint && currentPoint[seriesKey] && (!previousPoint || !previousPoint[seriesKey]) && (!nextPoint || !nextPoint[seriesKey])) {
165
+ if (
166
+ currentPoint &&
167
+ currentPoint[seriesKey] &&
168
+ (!previousPoint || !previousPoint[seriesKey]) &&
169
+ (!nextPoint || !nextPoint[seriesKey])
170
+ ) {
139
171
  res = true
140
172
  }
141
173
  }
@@ -146,7 +178,14 @@ const LineChartCircle = (props: LineChartCircleProps) => {
146
178
  if (mode) {
147
179
  if (drawIsolatedPoints(dataIndex, seriesKey)) {
148
180
  return (
149
- <circle cx={getXPos(d[config.xAxis?.dataKey])} cy={filtered.axis === 'Right' ? yScaleRight(d[filtered.dataKey]) : yScale(d[filtered?.dataKey])} r={5.3} strokeWidth={2} stroke={colorScale(config.runtime.seriesLabels[seriesKey])} fill={colorScale(config.runtime?.seriesLabels[seriesKey])} />
181
+ <circle
182
+ cx={getXPos(d[config.xAxis?.dataKey])}
183
+ cy={filtered?.axis === 'Right' ? yScaleRight(d[filtered?.dataKey]) : yScale(d[filtered?.dataKey])}
184
+ r={5.3}
185
+ strokeWidth={2}
186
+ stroke={colorScale(config.runtime.seriesLabels[seriesKey])}
187
+ fill={colorScale(config.runtime?.seriesLabels[seriesKey])}
188
+ />
150
189
  )
151
190
  }
152
191
  }
@@ -1,10 +1,20 @@
1
- import { type PreliminaryDataItem, DataItem, StyleProps, Style } from './LineChartProps'
1
+ import { DataItem, StyleProps, Style } from './LineChartProps'
2
+ import { PreliminaryDataItem } from '../../types/ChartConfig'
2
3
  import _ from 'lodash'
3
4
  export const createStyles = (props: StyleProps): Style[] => {
4
5
  const { preliminaryData, data, stroke, strokeWidth, handleLineType, lineType, seriesKey } = props
5
6
 
6
- const validPreliminaryData: PreliminaryDataItem[] = preliminaryData.filter(pd => pd.seriesKey && pd.column && pd.value && pd.type && pd.style && pd.type === 'effect')
7
- const getMatchingPd = (point: DataItem): PreliminaryDataItem => validPreliminaryData.find(pd => pd.seriesKey === seriesKey && point[pd.column] === pd.value && pd.type === 'effect' && pd.style !== 'Open Circles')
7
+ const validPreliminaryData: PreliminaryDataItem[] = preliminaryData.filter(
8
+ pd => pd.seriesKey && pd.column && pd.value && pd.type && pd.style && pd.type === 'effect'
9
+ )
10
+ const getMatchingPd = (point: DataItem): PreliminaryDataItem =>
11
+ validPreliminaryData.find(
12
+ pd =>
13
+ pd.seriesKey === seriesKey &&
14
+ point[pd.column] === pd.value &&
15
+ pd.type === 'effect' &&
16
+ pd.style !== 'Open Circles'
17
+ )
8
18
 
9
19
  let styles: Style[] = []
10
20
  const createStyle = (lineStyle): Style => ({
@@ -15,7 +25,9 @@ export const createStyles = (props: StyleProps): Style[] => {
15
25
 
16
26
  data.forEach((d, index) => {
17
27
  let matchingPd: PreliminaryDataItem = getMatchingPd(d)
18
- let style: Style = matchingPd ? createStyle(handleLineType(matchingPd.style)) : createStyle(handleLineType(lineType))
28
+ let style: Style = matchingPd
29
+ ? createStyle(handleLineType(matchingPd.style))
30
+ : createStyle(handleLineType(lineType))
19
31
 
20
32
  styles.push(style)
21
33
 
@@ -27,14 +39,31 @@ export const createStyles = (props: StyleProps): Style[] => {
27
39
  return styles as Style[]
28
40
  }
29
41
 
30
- export const filterCircles = (preliminaryData: PreliminaryDataItem[], data: DataItem[], seriesKey: string): DataItem[] => {
42
+ export const filterCircles = (
43
+ preliminaryData: PreliminaryDataItem[],
44
+ data: DataItem[],
45
+ seriesKey: string
46
+ ): DataItem[] => {
31
47
  // Filter and map preliminaryData to get circlesFiltered
32
- const circlesFiltered = preliminaryData?.filter(item => item.style.includes('Circles') && item.type === 'effect').map(item => ({ column: item.column, value: item.value, seriesKey: item.seriesKey, circleSize: item.circleSize, style: item.style }))
48
+ const circlesFiltered = preliminaryData
49
+ ?.filter(item => item.style.includes('Circles') && item.type === 'effect')
50
+ .map(item => ({
51
+ column: item.column,
52
+ value: item.value,
53
+ seriesKey: item.seriesKey,
54
+ circleSize: item.circleSize,
55
+ style: item.style
56
+ }))
33
57
  const filteredData = []
34
58
  // Process data to find matching items
35
59
  data.forEach(item => {
36
60
  circlesFiltered.forEach(fc => {
37
- if (item[fc.column] === fc.value && fc.seriesKey === seriesKey && item[seriesKey] && fc.style === 'Open Circles') {
61
+ if (
62
+ item[fc.column] === fc.value &&
63
+ fc.seriesKey === seriesKey &&
64
+ item[seriesKey] &&
65
+ fc.style === 'Open Circles'
66
+ ) {
38
67
  const result = {
39
68
  data: item,
40
69
  size: fc.circleSize,
@@ -42,7 +71,12 @@ export const filterCircles = (preliminaryData: PreliminaryDataItem[], data: Data
42
71
  }
43
72
  filteredData.push(result)
44
73
  }
45
- if ((!fc.value || item[fc.column] === fc.value) && fc.seriesKey === seriesKey && item[seriesKey] && fc.style === 'Filled Circles') {
74
+ if (
75
+ (!fc.value || item[fc.column] === fc.value) &&
76
+ fc.seriesKey === seriesKey &&
77
+ item[seriesKey] &&
78
+ fc.style === 'Filled Circles'
79
+ ) {
46
80
  const result = {
47
81
  data: item,
48
82
  size: fc.circleSize,
@@ -71,7 +105,9 @@ const handleFirstIndex = (data, seriesKey, preliminaryData) => {
71
105
  // Function to check if a data item matches the suppression criteria
72
106
  const isSuppressed = pd => {
73
107
  if (pd.type === 'effect' || pd.hideLineStyle) return
74
- return pd.type == 'suppression' && pd.value === firstIndexDataItem[seriesKey] && (!pd.column || pd.column === seriesKey)
108
+ return (
109
+ pd.type == 'suppression' && pd.value === firstIndexDataItem[seriesKey] && (!pd.column || pd.column === seriesKey)
110
+ )
75
111
  }
76
112
 
77
113
  // Find applicable suppression data for the first item
@@ -107,7 +143,13 @@ const handleLastIndex = (data, seriesKey, preliminaryData) => {
107
143
  let lastAddedIndex = -1 // Tracks the last index added to the result
108
144
  preliminaryData?.forEach(pd => {
109
145
  if (pd.type === 'effect') return
110
- if (data[data.length - 1][seriesKey] === pd.value && pd.style && (!pd.column || pd.column === seriesKey) && pd.type == 'suppression' && !pd.hideLineStyle) {
146
+ if (
147
+ data[data.length - 1][seriesKey] === pd.value &&
148
+ pd.style &&
149
+ (!pd.column || pd.column === seriesKey) &&
150
+ pd.type == 'suppression' &&
151
+ !pd.hideLineStyle
152
+ ) {
111
153
  const lastIndex = data.length - 1
112
154
  const modifiedItem = { ...data[lastIndex], [seriesKey]: 0 }
113
155
  result.data.push(modifiedItem)
@@ -157,7 +199,9 @@ function handleMiddleIndices(data, seriesKey, dataKey, preliminaryData) {
157
199
  }
158
200
 
159
201
  // Find and add the next calculable object
160
- const nextIndex = data.slice(i + 1).findIndex(item => item[seriesKey] !== targetValue && isCalculable(item[seriesKey]))
202
+ const nextIndex = data
203
+ .slice(i + 1)
204
+ .findIndex(item => item[seriesKey] !== targetValue && isCalculable(item[seriesKey]))
161
205
  if (nextIndex !== -1) {
162
206
  result.data.push(data[i + 1 + nextIndex])
163
207
  }