@cdc/chart 4.24.7 → 4.24.9-1

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/LICENSE +201 -0
  2. package/dist/cdcchart.js +47567 -42391
  3. package/examples/cases-year.json +13379 -0
  4. package/examples/gallery/bar-chart-vertical/combo-line-chart.json +76 -15
  5. package/examples/gallery/bar-chart-vertical/vertical-bar-chart-stacked.json +5 -5
  6. package/index.html +17 -8
  7. package/package.json +2 -2
  8. package/src/CdcChart.tsx +382 -133
  9. package/src/_stories/Chart.Legend.Gradient.tsx +19 -0
  10. package/src/_stories/_mock/legend.gradient_mock.json +236 -0
  11. package/src/components/Annotations/components/AnnotationDraggable.tsx +64 -11
  12. package/src/components/Axis/Categorical.Axis.tsx +145 -0
  13. package/src/components/BarChart/components/BarChart.Horizontal.tsx +4 -3
  14. package/src/components/BarChart/components/BarChart.StackedHorizontal.tsx +1 -1
  15. package/src/components/BarChart/components/BarChart.StackedVertical.tsx +2 -5
  16. package/src/components/BarChart/components/BarChart.Vertical.tsx +17 -8
  17. package/src/components/BarChart/helpers/index.ts +5 -16
  18. package/src/components/BrushChart.tsx +205 -0
  19. package/src/components/EditorPanel/EditorPanel.tsx +1767 -510
  20. package/src/components/EditorPanel/components/Panels/Panel.Annotate.tsx +22 -8
  21. package/src/components/EditorPanel/components/Panels/Panel.General.tsx +190 -37
  22. package/src/components/EditorPanel/components/Panels/Panel.Sankey.tsx +43 -7
  23. package/src/components/EditorPanel/components/Panels/Panel.Series.tsx +4 -4
  24. package/src/components/EditorPanel/components/Panels/Panel.Visual.tsx +1 -11
  25. package/src/components/EditorPanel/editor-panel.scss +16 -3
  26. package/src/components/EditorPanel/{useEditorPermissions.js → useEditorPermissions.ts} +90 -19
  27. package/src/components/Legend/Legend.Component.tsx +185 -193
  28. package/src/components/Legend/Legend.Suppression.tsx +146 -0
  29. package/src/components/Legend/Legend.tsx +21 -5
  30. package/src/components/Legend/helpers/index.ts +33 -3
  31. package/src/components/LegendWrapper.tsx +26 -0
  32. package/src/components/LineChart/LineChartProps.ts +1 -18
  33. package/src/components/LineChart/components/LineChart.BumpCircle.tsx +103 -0
  34. package/src/components/LineChart/components/LineChart.Circle.tsx +57 -8
  35. package/src/components/LineChart/helpers.ts +55 -11
  36. package/src/components/LineChart/index.tsx +113 -38
  37. package/src/components/LinearChart.tsx +1366 -0
  38. package/src/components/PieChart/PieChart.tsx +74 -17
  39. package/src/components/Sankey/index.tsx +22 -16
  40. package/src/components/Sparkline/components/SparkLine.tsx +2 -2
  41. package/src/data/initial-state.js +13 -3
  42. package/src/hooks/useLegendClasses.ts +52 -15
  43. package/src/hooks/useMinMax.ts +4 -4
  44. package/src/hooks/useScales.ts +34 -24
  45. package/src/hooks/useTooltip.tsx +85 -22
  46. package/src/scss/DataTable.scss +2 -1
  47. package/src/scss/main.scss +107 -14
  48. package/src/types/ChartConfig.ts +34 -8
  49. package/src/types/ChartContext.ts +5 -4
  50. package/examples/feature/line/line-chart.json +0 -449
  51. package/src/components/BrushHandle.jsx +0 -17
  52. package/src/components/LineChart/index.scss +0 -1
  53. package/src/components/LinearChart.jsx +0 -817
@@ -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
 
@@ -120,10 +147,17 @@ const LineChartCircle = (props: LineChartCircleProps) => {
120
147
 
121
148
  if (mode === 'ISOLATED_POINTS') {
122
149
  const drawIsolatedPoints = (currentIndex, seriesKey) => {
150
+ let isMatch = false
123
151
  const currentPoint = data[currentIndex]
124
152
  const previousPoint = currentIndex > 0 ? data[currentIndex - 1] : null
125
153
  const nextPoint = currentIndex < data.length - 1 ? data[currentIndex + 1] : null
126
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
+ })
127
161
 
128
162
  // Handle the first point in the array
129
163
  if (currentIndex === 0 && nextPoint && !nextPoint[seriesKey]) {
@@ -135,10 +169,18 @@ const LineChartCircle = (props: LineChartCircleProps) => {
135
169
  }
136
170
  // Handle points in the middle
137
171
  if (currentIndex > 0 && currentIndex < data.length - 1) {
138
- if (currentPoint && currentPoint[seriesKey] && (!previousPoint || !previousPoint[seriesKey]) && (!nextPoint || !nextPoint[seriesKey])) {
172
+ if (
173
+ currentPoint &&
174
+ currentPoint[seriesKey] &&
175
+ (!previousPoint || !previousPoint[seriesKey]) &&
176
+ (!nextPoint || !nextPoint[seriesKey])
177
+ ) {
139
178
  res = true
140
179
  }
141
180
  }
181
+ if (isMatch) {
182
+ res = false
183
+ }
142
184
 
143
185
  return res
144
186
  }
@@ -146,7 +188,14 @@ const LineChartCircle = (props: LineChartCircleProps) => {
146
188
  if (mode) {
147
189
  if (drawIsolatedPoints(dataIndex, seriesKey)) {
148
190
  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])} />
191
+ <circle
192
+ cx={getXPos(d[config.xAxis?.dataKey])}
193
+ cy={filtered?.axis === 'Right' ? yScaleRight(d[filtered?.dataKey]) : yScale(d[filtered?.dataKey])}
194
+ r={5.3}
195
+ strokeWidth={2}
196
+ stroke={colorScale(config.runtime.seriesLabels[seriesKey])}
197
+ fill={colorScale(config.runtime?.seriesLabels[seriesKey])}
198
+ />
150
199
  )
151
200
  }
152
201
  }
@@ -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
  }