@cdc/chart 4.24.1 → 4.24.2

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 (58) hide show
  1. package/dist/cdcchart.js +30014 -29757
  2. package/examples/feature/line/line-chart-preliminary.json +84 -37
  3. package/examples/feature/regions/index.json +9 -5
  4. package/index.html +4 -2
  5. package/package.json +2 -2
  6. package/src/CdcChart.tsx +41 -24
  7. package/src/_stories/ChartEditor.stories.tsx +1 -1
  8. package/src/_stories/_mock/pie_config.json +4 -3
  9. package/src/components/AreaChart/components/AreaChart.jsx +1 -25
  10. package/src/components/BarChart/components/BarChart.StackedVertical.tsx +7 -5
  11. package/src/components/BarChart/components/BarChart.Vertical.tsx +12 -13
  12. package/src/components/BoxPlot/BoxPlot.jsx +9 -8
  13. package/src/components/EditorPanel/EditorPanel.tsx +1563 -1959
  14. package/src/components/EditorPanel/EditorPanelContext.ts +40 -0
  15. package/src/components/EditorPanel/components/Panels/Panel.BoxPlot.tsx +148 -0
  16. package/src/components/EditorPanel/components/{Panel.ForestPlotSettings.tsx → Panels/Panel.ForestPlotSettings.tsx} +16 -7
  17. package/src/components/EditorPanel/components/Panels/Panel.General.tsx +160 -0
  18. package/src/components/EditorPanel/components/{Panel.Regions.tsx → Panels/Panel.Regions.tsx} +5 -5
  19. package/src/components/EditorPanel/components/{Panel.Series.tsx → Panels/Panel.Series.tsx} +4 -4
  20. package/src/components/EditorPanel/components/Panels/Panel.Visual.tsx +297 -0
  21. package/src/components/EditorPanel/components/Panels/index.tsx +17 -0
  22. package/src/components/EditorPanel/editor-panel.scss +1 -13
  23. package/src/components/EditorPanel/useEditorPermissions.js +5 -0
  24. package/src/components/Legend/Legend.Component.tsx +199 -0
  25. package/src/components/Legend/Legend.tsx +5 -324
  26. package/src/components/Legend/helpers/createFormatLabels.tsx +140 -0
  27. package/src/components/LineChart/LineChartProps.ts +1 -1
  28. package/src/components/LineChart/components/LineChart.Circle.tsx +85 -52
  29. package/src/components/LineChart/helpers.ts +2 -2
  30. package/src/components/LineChart/index.tsx +97 -21
  31. package/src/components/LinearChart.jsx +3 -3
  32. package/src/components/PairedBarChart.jsx +4 -2
  33. package/src/components/PieChart/PieChart.tsx +78 -25
  34. package/src/components/Regions/components/Regions.tsx +14 -5
  35. package/src/data/initial-state.js +5 -2
  36. package/src/helpers/computeMarginBottom.ts +2 -2
  37. package/src/hooks/useHighlightedBars.js +1 -1
  38. package/src/hooks/useMinMax.ts +3 -3
  39. package/src/hooks/useScales.ts +18 -5
  40. package/src/hooks/useTooltip.tsx +11 -7
  41. package/src/scss/main.scss +0 -67
  42. package/src/types/ChartConfig.ts +17 -8
  43. package/src/types/ChartContext.ts +10 -4
  44. package/src/types/Label.ts +7 -0
  45. package/examples/private/chart-t.json +0 -3740
  46. package/examples/private/combo.json +0 -369
  47. package/examples/private/epi-data.csv +0 -13
  48. package/examples/private/epi-data.json +0 -62
  49. package/examples/private/epi.json +0 -403
  50. package/examples/private/occupancy.json +0 -109283
  51. package/examples/private/prod-line-config.json +0 -401
  52. package/examples/private/region-data.json +0 -822
  53. package/examples/private/region-testing.json +0 -312
  54. package/examples/private/scaling.json +0 -45325
  55. package/examples/private/testing-data.json +0 -1739
  56. package/examples/private/testing.json +0 -816
  57. package/src/components/EditorPanel/components/Panel.DateHighlighting.tsx +0 -109
  58. package/src/components/EditorPanel/components/Panels.tsx +0 -13
@@ -1,26 +1,7 @@
1
1
  import { useContext } from 'react'
2
2
  import ConfigContext from '../../ConfigContext'
3
- import parse from 'html-react-parser'
4
- import { LegendOrdinal, LegendItem, LegendLabel } from '@visx/legend'
5
- import LegendCircle from '@cdc/core/components/LegendCircle'
6
-
7
- import useLegendClasses from '../../hooks/useLegendClasses'
8
- import { useHighlightedBars } from '../../hooks/useHighlightedBars'
9
- import { handleLineType } from '../../helpers/handleLineType'
10
- import { Line } from '@visx/shape'
11
- import { colorPalettesChart as colorPalettes, sequentialPalettes, twoColorPalette } from '@cdc/core/data/colorPalettes'
12
- import { scaleOrdinal } from '@visx/scale'
13
- import { FaStar } from 'react-icons/fa'
14
- import { Text } from '@visx/text'
15
- import { Group } from '@visx/group'
16
-
17
- type Label = {
18
- datum: string
19
- index: number
20
- text: string
21
- value: string
22
- icon?: any
23
- }
3
+ import LegendComponent from './Legend.Component'
4
+ import { createFormatLabels } from './helpers/createFormatLabels'
24
5
 
25
6
  /* eslint-disable jsx-a11y/no-noninteractive-tabindex, jsx-a11y/no-static-element-interactions */
26
7
  const Legend = () => {
@@ -36,312 +17,12 @@ const Legend = () => {
36
17
  currentViewport
37
18
  } = useContext(ConfigContext)
38
19
 
39
- const { innerClasses, containerClasses } = useLegendClasses(config)
40
- const { visualizationType, visualizationSubType, series, runtime, orientation, legend } = config
20
+ if (!config.legend) return null
41
21
  // create fn to reverse labels while legend is Bottom. Legend-right , legend-left works by default.
42
- const reverseLabels = labels => (config.legend.reverseLabelOrder && config.legend.position === 'bottom' ? labels.reverse() : labels)
43
- const displayScale = scaleOrdinal({
44
- domain: config.suppressedData?.map(d => d.label),
45
- range: ['none'],
46
- unknown: 'block'
47
- })
48
-
49
- const renderDashes = style => {
50
- const dashCount = style === 'Dashed Small' ? 3 : 2
51
- const dashClass = `dashes ${style.toLowerCase().replace(' ', '-')}`
52
-
53
- return (
54
- <div className={dashClass}>
55
- {Array.from({ length: dashCount }, (_, i) => (
56
- <span key={i}>-</span>
57
- ))}
58
- </div>
59
- )
60
- }
61
- const renderDashesOrCircle = style => {
62
- if (['Dashed Small', 'Dashed Medium', 'Dashed Large'].includes(style)) {
63
- return renderDashes(style)
64
- } else if (style === 'Open Circles') {
65
- return <div className='dashes open-circles'></div>
66
- }
67
- }
68
- const createLegendLabels = (defaultLabels): Label[] => {
69
- const colorCode = config.legend?.colorCode
70
- if (visualizationType === 'Deviation Bar') {
71
- const [belowColor, aboveColor] = twoColorPalette[config.twoColor.palette]
72
- const labelBelow = {
73
- datum: 'X',
74
- index: 0,
75
- text: `Below ${config.xAxis.targetLabel}`,
76
- value: belowColor
77
- }
78
- const labelAbove = {
79
- datum: 'X',
80
- index: 1,
81
- text: `Above ${config.xAxis.targetLabel}`,
82
- value: aboveColor
83
- }
84
-
85
- return reverseLabels([labelBelow, labelAbove])
86
- }
87
- if (visualizationType === 'Bar' && visualizationSubType === 'regular' && colorCode && series?.length === 1) {
88
- let palette = colorPalettes[config.palette]
89
-
90
- while (tableData.length > palette.length) {
91
- palette = palette.concat(palette)
92
- }
93
- palette = palette.slice(0, data.length)
94
- //store unique values to Set by colorCode
95
- const set = new Set()
96
-
97
- tableData.forEach(d => set.add(d[colorCode]))
98
-
99
- // create labels with unique values
100
- const uniqueLabels = Array.from(set).map((val, i) => {
101
- const newLabel = {
102
- datum: val,
103
- index: i,
104
- text: val,
105
- value: palette[i]
106
- }
107
- return newLabel
108
- })
109
-
110
- return reverseLabels(uniqueLabels)
111
- }
112
-
113
- // get forecasting items inside of combo
114
- if (runtime?.forecastingSeriesKeys?.length > 0) {
115
- let seriesLabels = []
116
-
117
- //store unique values to Set by colorCode
118
- // loop through each stage/group/area on the chart and create a label
119
- config.runtime?.forecastingSeriesKeys?.map((outerGroup, index) => {
120
- return outerGroup?.stages?.map((stage, index) => {
121
- let colorValue = sequentialPalettes[stage.color]?.[2] ? sequentialPalettes[stage.color]?.[2] : colorPalettes[stage.color]?.[2] ? colorPalettes[stage.color]?.[2] : '#ccc'
122
-
123
- const newLabel = {
124
- datum: stage.key,
125
- index: index,
126
- text: stage.key,
127
- value: colorValue
128
- }
129
-
130
- seriesLabels.push(newLabel)
131
- })
132
- })
133
-
134
- // loop through bars for now to meet requirements.
135
- config.runtime.barSeriesKeys &&
136
- config.runtime.barSeriesKeys.forEach((bar, index) => {
137
- let colorValue = colorPalettes[config.palette][index] ? colorPalettes[config.palette][index] : '#ccc'
138
-
139
- const newLabel = {
140
- datum: bar,
141
- index: index,
142
- text: bar,
143
- value: colorValue
144
- }
145
-
146
- seriesLabels.push(newLabel)
147
- })
148
-
149
- return reverseLabels(seriesLabels)
150
- }
151
-
152
- // DEV-4161: replaceable series name in the legend
153
- const hasNewSeriesName = config.series.map(s => s.name).filter(item => item).length > 0
154
- if (hasNewSeriesName) {
155
- let palette = colorPalettes[config.palette]
156
-
157
- while (tableData.length > palette.length) {
158
- palette = palette.concat(palette)
159
- }
160
-
161
- palette = palette.slice(0, data.length)
162
- //store unique values to Set by colorCode
163
- const set = new Set()
164
-
165
- config.series.forEach(d => {
166
- set.add(d['name'] ? d['name'] : d['dataKey'])
167
- })
168
-
169
- // create labels with unique values
170
- const uniqueLabels = Array.from(set).map((val, i) => {
171
- const newLabel = {
172
- datum: val,
173
- index: i,
174
- text: val,
175
- value: colorScale(val)
176
- }
177
- return newLabel
178
- })
179
-
180
- return reverseLabels(uniqueLabels)
181
- }
182
-
183
- if ((config.visualizationType === 'Bar' || config.visualizationType === 'Combo') && config.visualizationSubType === 'regular' && config.suppressedData) {
184
- const lastIndex = defaultLabels.length - 1
185
- let newLabels = []
186
-
187
- config.suppressedData?.forEach(({ label, icon }, index) => {
188
- if (label && icon) {
189
- const newLabel = {
190
- datum: label,
191
- index: lastIndex + index,
192
- text: label,
193
- icon: <FaStar color='#000' size={15} />
194
- }
195
- newLabels.push(newLabel)
196
- }
197
- })
198
-
199
- return [...defaultLabels, ...newLabels]
200
- }
201
-
202
- return reverseLabels(defaultLabels)
203
- }
204
-
205
- const isBottomOrSmallViewport = legend.position === 'bottom' || ['sm', 'xs', 'xxs'].includes(currentViewport)
206
-
207
- const legendClasses = {
208
- marginBottom: isBottomOrSmallViewport ? '15px' : '0px',
209
- marginTop: isBottomOrSmallViewport && orientation === 'horizontal' ? `${config.yAxis.label && config.isResponsiveTicks ? config.dynamicMarginTop : config.runtime.xAxis.size}px` : `${isBottomOrSmallViewport ? config.dynamicMarginTop + 15 : 0}px`
210
- }
211
-
212
- const { HighLightedBarUtils } = useHighlightedBars(config)
213
-
214
- let highLightedLegendItems = HighLightedBarUtils.findDuplicates(config.highlightedBarValues)
215
- if (!legend) return null
216
-
217
- return (
218
- config.visualizationType !== 'Box Plot' && (
219
- <aside style={legendClasses} id='legend' className={containerClasses.join(' ')} role='region' aria-label='legend' tabIndex={0}>
220
- {legend.label && <h2>{parse(legend.label)}</h2>}
221
- {legend.description && <p>{parse(legend.description)}</p>}
222
- <LegendOrdinal scale={colorScale} itemDirection='row' labelMargin='0 20px 0 0' shapeMargin='0 10px 0'>
223
- {labels => {
224
- return (
225
- <>
226
- <div className={innerClasses.join(' ')}>
227
- {createLegendLabels(labels).map((label, i) => {
228
- let className = ['legend-item', `legend-text--${label.text.replace(' ', '').toLowerCase()}`]
229
- let itemName = label.datum
230
-
231
- // Filter excluded data keys from legend
232
- if (config.exclusions.active && config.exclusions.keys?.includes(itemName)) {
233
- return null
234
- }
235
-
236
- if (runtime.seriesLabels) {
237
- let index = config.runtime.seriesLabelsAll.indexOf(itemName)
238
- itemName = config.runtime.seriesKeys[index]
239
-
240
- if (runtime?.forecastingSeriesKeys?.length > 0) {
241
- itemName = label.text
242
- }
243
- }
244
-
245
- if (seriesHighlight.length > 0 && false === seriesHighlight.includes(itemName)) {
246
- className.push('inactive')
247
- }
248
-
249
- return (
250
- <LegendItem
251
- className={className.join(' ')}
252
- tabIndex={0}
253
- key={`legend-quantile-${i}`}
254
- onKeyPress={e => {
255
- if (e.key === 'Enter') {
256
- highlight(label)
257
- }
258
- }}
259
- onClick={() => {
260
- highlight(label)
261
- }}
262
- >
263
- {config.visualizationType === 'Line' && config.legend.lineMode ? (
264
- <svg width={40} height={20}>
265
- <Line from={{ x: 10, y: 10 }} to={{ x: 40, y: 10 }} stroke={label.value} strokeWidth={2} strokeDasharray={handleLineType(config.series[i]?.type ? config.series[i]?.type : '')} />
266
- </svg>
267
- ) : (
268
- <div style={{ display: 'flex', flexDirection: 'column' }}>
269
- <LegendCircle margin='0' fill={label.value} display={displayScale(label.datum)} />
270
- <div style={{ marginTop: '2px', marginRight: '6px' }}>{label.icon}</div>
271
- </div>
272
- )}
273
-
274
- <LegendLabel align='left' margin='0 0 0 4px'>
275
- {label.text}
276
- </LegendLabel>
277
- </LegendItem>
278
- )
279
- })}
280
22
 
281
- {highLightedLegendItems.map((bar, i) => {
282
- // if duplicates only return first item
283
- let className = 'legend-item'
284
- let itemName = bar.legendLabel
23
+ const createLegendLabels = createFormatLabels(config, tableData, data, colorScale)
285
24
 
286
- if (!itemName) return false
287
- if (seriesHighlight.length > 0 && false === seriesHighlight.includes(itemName)) {
288
- className += ' inactive'
289
- }
290
- return (
291
- <LegendItem
292
- className={className}
293
- tabIndex={0}
294
- key={`legend-quantile-${i}`}
295
- onKeyPress={e => {
296
- if (e.key === 'Enter') {
297
- highlight(bar.legendLabel)
298
- }
299
- }}
300
- onClick={() => {
301
- highlight(bar.legendLabel)
302
- }}
303
- >
304
- <LegendCircle fill='transparent' borderColor={bar.color ? bar.color : `rgba(255, 102, 1)`} />{' '}
305
- <LegendLabel align='left' margin='0 0 0 4px'>
306
- {bar.legendLabel ? bar.legendLabel : bar.value}
307
- </LegendLabel>
308
- </LegendItem>
309
- )
310
- })}
311
- {seriesHighlight.length > 0 && (
312
- <button className={`legend-reset ${config.theme}`} onClick={labels => highlightReset(labels)} tabIndex={0}>
313
- Reset
314
- </button>
315
- )}
316
- </div>
317
- <>
318
- {config.preliminaryData.some(pd => pd.label) && (
319
- <>
320
- <hr></hr>
321
- <div className={config.legend.singleRow && isBottomOrSmallViewport ? 'legend-container__inner bottom single-row' : 'dash-left'}>
322
- {config.preliminaryData.map((pd, index) => {
323
- return (
324
- <div className='dash-container' key={index}>
325
- {pd.label && (
326
- <>
327
- <div className='dash-inner'>{renderDashesOrCircle(pd.style)}</div>
328
- <div style={{ marginLeft: '7px' }}>{pd.label}</div>
329
- </>
330
- )}
331
- </div>
332
- )
333
- })}
334
- </div>
335
- </>
336
- )}
337
- </>
338
- </>
339
- )
340
- }}
341
- </LegendOrdinal>
342
- </aside>
343
- )
344
- )
25
+ return !['Box Plot', 'Pie'].includes(config.visualizationType) && <LegendComponent config={config} colorScale={colorScale} seriesHighlight={seriesHighlight} highlight={highlight} highlightReset={highlightReset} currentViewport={currentViewport} formatLabels={createLegendLabels} />
345
26
  }
346
27
 
347
28
  export default Legend
@@ -0,0 +1,140 @@
1
+ import { colorPalettesChart as colorPalettes, sequentialPalettes, twoColorPalette } from '@cdc/core/data/colorPalettes'
2
+ import { FaStar } from 'react-icons/fa'
3
+ import { Label } from '../../../types/Label'
4
+ import { ColorScale, TransformedData } from '../../../types/ChartContext'
5
+ import { ChartConfig } from '../../../types/ChartConfig'
6
+
7
+ export const createFormatLabels =
8
+ (config: ChartConfig, tableData: Object[], data: TransformedData[], colorScale: ColorScale) =>
9
+ (defaultLabels: Label[]): Label[] => {
10
+ const { visualizationType, visualizationSubType, series, runtime } = config
11
+
12
+ const reverseLabels = labels => (config.legend.reverseLabelOrder && config.legend.position === 'bottom' ? labels.reverse() : labels)
13
+ const colorCode = config.legend?.colorCode
14
+ if (visualizationType === 'Deviation Bar') {
15
+ const [belowColor, aboveColor] = twoColorPalette[config.twoColor.palette]
16
+ const labelBelow = {
17
+ datum: 'X',
18
+ index: 0,
19
+ text: `Below ${config.xAxis.targetLabel}`,
20
+ value: belowColor
21
+ }
22
+ const labelAbove = {
23
+ datum: 'X',
24
+ index: 1,
25
+ text: `Above ${config.xAxis.targetLabel}`,
26
+ value: aboveColor
27
+ }
28
+
29
+ return reverseLabels([labelBelow, labelAbove])
30
+ }
31
+ if (visualizationType === 'Bar' && visualizationSubType === 'regular' && colorCode && series?.length === 1) {
32
+ let palette = colorPalettes[config.palette]
33
+
34
+ while (tableData.length > palette.length) {
35
+ palette = palette.concat(palette)
36
+ }
37
+ palette = palette.slice(0, data.length)
38
+ //store unique values to Set by colorCode
39
+ const set = new Set()
40
+
41
+ tableData.forEach(d => set.add(d[colorCode]))
42
+
43
+ // create labels with unique values
44
+ const uniqueLabels = Array.from(set).map((val, i) => {
45
+ const newLabel = {
46
+ datum: val,
47
+ index: i,
48
+ text: val,
49
+ value: palette[i]
50
+ }
51
+ return newLabel
52
+ })
53
+
54
+ return reverseLabels(uniqueLabels)
55
+ }
56
+
57
+ // get forecasting items inside of combo
58
+ if (runtime?.forecastingSeriesKeys?.length > 0) {
59
+ let seriesLabels = []
60
+
61
+ //store unique values to Set by colorCode
62
+ // loop through each stage/group/area on the chart and create a label
63
+ config.runtime?.forecastingSeriesKeys?.map((outerGroup, index) => {
64
+ return outerGroup?.stages?.map((stage, index) => {
65
+ let colorValue = sequentialPalettes[stage.color]?.[2] ? sequentialPalettes[stage.color]?.[2] : colorPalettes[stage.color]?.[2] ? colorPalettes[stage.color]?.[2] : '#ccc'
66
+
67
+ const newLabel = {
68
+ datum: stage.key,
69
+ index: index,
70
+ text: stage.key,
71
+ value: colorValue
72
+ }
73
+
74
+ seriesLabels.push(newLabel)
75
+ })
76
+ })
77
+
78
+ // loop through bars for now to meet requirements.
79
+ config.runtime.barSeriesKeys &&
80
+ config.runtime.barSeriesKeys.forEach((bar, index) => {
81
+ let colorValue = colorPalettes[config.palette][index] ? colorPalettes[config.palette][index] : '#ccc'
82
+
83
+ const newLabel = {
84
+ datum: bar,
85
+ index: index,
86
+ text: bar,
87
+ value: colorValue
88
+ }
89
+
90
+ seriesLabels.push(newLabel)
91
+ })
92
+
93
+ return reverseLabels(seriesLabels)
94
+ }
95
+
96
+ // DEV-4161: replaceable series name in the legend
97
+ const hasNewSeriesName = config.series.filter(item => !!item.name).length > 0
98
+ if (hasNewSeriesName) {
99
+ //store unique values to Set by colorCode
100
+ const set = new Set()
101
+
102
+ config.series.forEach(d => {
103
+ set.add(d.name || d.dataKey)
104
+ })
105
+
106
+ // create labels with unique values
107
+ const uniqueLabels = Array.from(set).map((val, i) => {
108
+ const newLabel = {
109
+ datum: val,
110
+ index: i,
111
+ text: val,
112
+ value: colorScale(val)
113
+ }
114
+ return newLabel
115
+ })
116
+
117
+ return reverseLabels(uniqueLabels)
118
+ }
119
+
120
+ if ((config.visualizationType === 'Bar' || config.visualizationType === 'Combo') && config.visualizationSubType === 'regular' && config.suppressedData) {
121
+ const lastIndex = defaultLabels.length - 1
122
+ let newLabels = []
123
+
124
+ config.suppressedData?.forEach(({ label, icon }, index) => {
125
+ if (label && icon) {
126
+ const newLabel = {
127
+ datum: label,
128
+ index: lastIndex + index,
129
+ text: label,
130
+ icon: <FaStar color='#000' size={15} />
131
+ }
132
+ newLabels.push(newLabel)
133
+ }
134
+ })
135
+
136
+ return [...defaultLabels, ...newLabels]
137
+ }
138
+
139
+ return reverseLabels(defaultLabels)
140
+ }
@@ -33,7 +33,7 @@ export interface Config {
33
33
  }
34
34
  export interface StyleProps {
35
35
  preliminaryData: PreliminaryDataItem[]
36
- rawData: DataItem[]
36
+ data: DataItem[]
37
37
  stroke: string
38
38
  handleLineType: Function
39
39
  lineType: string
@@ -21,17 +21,15 @@ type LineChartCircleProps = {
21
21
  colorScale: any
22
22
  parseDate: any
23
23
  seriesAxis: string
24
+ dataIndex: number
25
+ mode: 'ISOLATED_POINTS' | 'HOVER_POINTS' | 'ALWAYS_SHOW_POINTS'
24
26
  }
25
27
 
26
28
  const LineChartCircle = (props: LineChartCircleProps) => {
27
- const { config, d, displayArea, seriesKey, tooltipData, xScale, yScale, colorScale, parseDate, yScaleRight, data, circleData } = props
29
+ const { config, d, displayArea, seriesKey, tooltipData, xScale, yScale, colorScale, parseDate, yScaleRight, data, circleData, dataIndex, mode } = props
28
30
  const { lineDatapointStyle } = config
29
31
  const filtered = config?.series.filter(s => s.dataKey === seriesKey)?.[0]
30
32
  // If we're not showing the circle, simply return
31
- if (lineDatapointStyle === 'hidden') return <></>
32
-
33
- const getIndex = seriesKey => config.runtime.seriesLabelsAll.indexOf(seriesKey)
34
-
35
33
  const getColor = (displayArea: boolean, colorScale: Function, config: ChartConfig, hoveredKey: string, seriesKey: string) => {
36
34
  const seriesLabels = config.runtime.seriesLabels || []
37
35
  let color
@@ -47,65 +45,100 @@ const LineChartCircle = (props: LineChartCircleProps) => {
47
45
  }
48
46
  return color
49
47
  }
50
- if (lineDatapointStyle === 'always show') {
51
- const isMatch = circleData?.some(cd => cd[config.xAxis.dataKey] === d[config.xAxis.dataKey] && cd[seriesKey] === d[seriesKey])
52
- if (isMatch) {
53
- return <></>
54
- }
55
- return (
56
- <circle
57
- cx={config.xAxis.type === 'categorical' ? xScale(d[config.xAxis.dataKey]) : xScale(parseDate(d[config.xAxis.dataKey]))}
58
- cy={filtered.axis === 'Right' ? yScaleRight(d[filtered.dataKey]) : yScale(d[filtered.dataKey])}
59
- r={4.5}
60
- opacity={d[seriesKey] ? 1 : 0}
61
- fillOpacity={1}
62
- fill={getColor(displayArea, colorScale, config, seriesKey, seriesKey)}
63
- style={{ filter: 'unset', opacity: 1 }}
64
- />
65
- )
48
+ const getXPos = hoveredXValue => {
49
+ return (config.xAxis.type === 'categorical' ? xScale(hoveredXValue) : xScale(parseDate(hoveredXValue))) + (xScale.bandwidth ? xScale.bandwidth() / 2 : 0)
66
50
  }
51
+ if (mode === 'ALWAYS_SHOW_POINTS') {
52
+ if (lineDatapointStyle === 'hidden') return <></>
53
+ const getIndex = seriesKey => config.runtime.seriesLabelsAll.indexOf(seriesKey)
67
54
 
68
- if (lineDatapointStyle === 'hover') {
69
- if (!tooltipData) return
70
- if (!seriesKey) return
71
- if (!tooltipData.data) return
72
- let hoveredXValue = tooltipData?.data?.[0]?.[1]
73
- if (!hoveredXValue) return
74
-
75
- let hoveredSeriesValue
76
- let hoveredSeriesIndex
77
- let hoveredSeriesData = tooltipData.data.filter(d => d[0] === seriesKey)
78
- let hoveredSeriesKey = hoveredSeriesData?.[0]?.[0]
79
- let hoveredSeriesAxis = hoveredSeriesData?.[0]?.[2]
80
- if (!hoveredSeriesKey) return
81
- hoveredSeriesIndex = tooltipData?.data.indexOf(hoveredSeriesKey)
82
- hoveredSeriesValue = data?.find(d => {
83
- return d[config?.xAxis.dataKey] === hoveredXValue
84
- })?.[seriesKey]
85
-
86
- // hoveredSeriesValue = extractNumber(hoveredSeriesValue)
87
- return tooltipData?.data.map((tooltipItem, index) => {
88
- let seriesIndex = config.runtime.seriesLabelsAll.indexOf(hoveredXValue)
89
-
90
- if (isNaN(hoveredSeriesValue)) return <></>
91
- const isMatch = circleData?.some(cd => cd[config.xAxis.dataKey] === hoveredXValue)
92
-
55
+ if (lineDatapointStyle === 'always show') {
56
+ const isMatch = circleData?.some(cd => cd[config.xAxis.dataKey] === d[config.xAxis.dataKey] && cd[seriesKey] === d[seriesKey])
93
57
  if (isMatch) {
94
58
  return <></>
95
59
  }
96
60
  return (
97
61
  <circle
98
- cx={config.xAxis.type === 'categorical' ? xScale(hoveredXValue) : xScale(parseDate(hoveredXValue))}
99
- cy={hoveredSeriesAxis === 'right' ? yScaleRight(hoveredSeriesValue) : yScale(hoveredSeriesValue)}
62
+ cx={getXPos(d[config.xAxis.dataKey])}
63
+ cy={filtered.axis === 'Right' ? yScaleRight(d[filtered.dataKey]) : yScale(d[filtered.dataKey])}
100
64
  r={4.5}
101
- opacity={1}
65
+ opacity={d[seriesKey] ? 1 : 0}
102
66
  fillOpacity={1}
103
- fill={getColor(displayArea, colorScale, config, hoveredSeriesKey, seriesKey)}
67
+ fill={getColor(displayArea, colorScale, config, seriesKey, seriesKey)}
104
68
  style={{ filter: 'unset', opacity: 1 }}
105
- key={`line-chart-circle--${JSON.stringify(tooltipItem)}--${index}`}
106
69
  />
107
70
  )
108
- })
71
+ }
72
+ }
73
+
74
+ if (mode === 'HOVER_POINTS') {
75
+ if (lineDatapointStyle === 'hover') {
76
+ if (!tooltipData) return
77
+ if (!seriesKey) return
78
+ if (!tooltipData.data) return
79
+ let hoveredXValue = tooltipData?.data?.[0]?.[1]
80
+ if (!hoveredXValue) return
81
+
82
+ let hoveredSeriesValue
83
+ let hoveredSeriesIndex
84
+ let hoveredSeriesData = tooltipData.data.filter(d => d[0] === seriesKey)
85
+ let hoveredSeriesKey = hoveredSeriesData?.[0]?.[0]
86
+ let hoveredSeriesAxis = hoveredSeriesData?.[0]?.[2]
87
+ if (!hoveredSeriesKey) return
88
+ hoveredSeriesIndex = tooltipData?.data.indexOf(hoveredSeriesKey)
89
+ hoveredSeriesValue = data?.find(d => {
90
+ return d[config?.xAxis.dataKey] === hoveredXValue
91
+ })?.[seriesKey]
92
+
93
+ // hoveredSeriesValue = extractNumber(hoveredSeriesValue)
94
+ return tooltipData?.data.map((tooltipItem, index) => {
95
+ let seriesIndex = config.runtime.seriesLabelsAll.indexOf(hoveredXValue)
96
+
97
+ if (isNaN(hoveredSeriesValue)) return <></>
98
+ const isMatch = circleData?.some(cd => cd[config.xAxis.dataKey] === hoveredXValue)
99
+
100
+ if (isMatch) {
101
+ return <></>
102
+ }
103
+ return (
104
+ <circle
105
+ cx={getXPos(hoveredXValue)}
106
+ cy={hoveredSeriesAxis === 'right' ? yScaleRight(hoveredSeriesValue) : yScale(hoveredSeriesValue)}
107
+ r={4.5}
108
+ opacity={1}
109
+ fillOpacity={1}
110
+ fill={getColor(displayArea, colorScale, config, hoveredSeriesKey, seriesKey)}
111
+ style={{ filter: 'unset', opacity: 1 }}
112
+ key={`line-chart-circle--${JSON.stringify(tooltipItem)}--${index}`}
113
+ />
114
+ )
115
+ })
116
+ }
117
+ }
118
+
119
+ if (mode === 'ISOLATED_POINTS') {
120
+ const drawIsolatedPoints = (currentIndex, seriesKey) => {
121
+ const currentPoint = data[currentIndex]
122
+ const previousPoint = data[currentIndex - 1]
123
+ const nextPoint = data[currentIndex + 1]
124
+ if (currentIndex === 0 && !nextPoint[seriesKey]) {
125
+ return true
126
+ }
127
+ if (currentIndex === data.length - 1 && !previousPoint[seriesKey]) {
128
+ return true
129
+ }
130
+ if (currentIndex !== 0 && currentPoint[seriesKey] && !previousPoint[seriesKey] && !nextPoint[seriesKey]) {
131
+ return true
132
+ }
133
+ }
134
+
135
+ if (mode) {
136
+ if (drawIsolatedPoints(dataIndex, seriesKey)) {
137
+ return (
138
+ <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])} />
139
+ )
140
+ }
141
+ }
109
142
  }
110
143
 
111
144
  return null
@@ -1,7 +1,7 @@
1
1
  import { type PreliminaryDataItem, DataItem, StyleProps, Style } from './LineChartProps'
2
2
 
3
3
  export const createStyles = (props: StyleProps): Style[] => {
4
- const { preliminaryData, rawData, stroke, handleLineType, lineType, seriesKey } = props
4
+ const { preliminaryData, data, stroke, handleLineType, lineType, seriesKey } = props
5
5
 
6
6
  const validPreliminaryData: PreliminaryDataItem[] = preliminaryData.filter(pd => pd.seriesKey && pd.column && pd.value && pd.type && pd.style)
7
7
  const getMatchingPd = (point: DataItem): PreliminaryDataItem => validPreliminaryData.find(pd => pd.seriesKey === seriesKey && point[pd.column] === pd.value && pd.type === 'effect' && pd.style !== 'Open Circles')
@@ -13,7 +13,7 @@ export const createStyles = (props: StyleProps): Style[] => {
13
13
  strokeDasharray: lineStyle
14
14
  })
15
15
 
16
- rawData.forEach((d, index) => {
16
+ data.forEach((d, index) => {
17
17
  let matchingPd: PreliminaryDataItem = getMatchingPd(d)
18
18
  let style: Style = matchingPd ? createStyle(handleLineType(matchingPd.style)) : createStyle(handleLineType(lineType))
19
19