@cdc/chart 4.25.3-6 → 4.25.5-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 (84) hide show
  1. package/dist/cdcchart-1a1724a1.es.js +4886 -0
  2. package/dist/cdcchart.js +50347 -75521
  3. package/index.html +1 -0
  4. package/package.json +22 -27
  5. package/src/CdcChart.tsx +1 -22
  6. package/src/CdcChartComponent.tsx +35 -21
  7. package/src/_stories/Chart.CI.stories.tsx +43 -0
  8. package/src/_stories/Chart.DynamicSeries.stories.tsx +68 -49
  9. package/src/_stories/Chart.Legend.Gradient.stories.tsx +6 -0
  10. package/src/_stories/Chart.stories.tsx +7 -16
  11. package/src/_stories/_mock/bar_chart_ci_labels.json +620 -0
  12. package/src/_stories/_mock/barchart_labels.mock.json +612 -0
  13. package/src/_stories/_mock/dynamic_series_bar_config.json +1 -1
  14. package/src/_stories/_mock/dynamic_series_suppression_mock.json +610 -0
  15. package/{examples/private/line-issue.json → src/_stories/_mock/legend_groupBy_mock.json} +46 -69
  16. package/src/components/Annotations/components/AnnotationDropdown.tsx +2 -2
  17. package/src/components/AreaChart/components/AreaChart.jsx +33 -5
  18. package/src/components/Axis/Categorical.Axis.tsx +2 -2
  19. package/src/components/BarChart/components/BarChart.Horizontal.tsx +51 -41
  20. package/src/components/BarChart/components/BarChart.StackedHorizontal.tsx +19 -9
  21. package/src/components/BarChart/components/BarChart.StackedVertical.tsx +20 -9
  22. package/src/components/BarChart/components/BarChart.Vertical.tsx +48 -31
  23. package/src/components/BarChart/components/{BarChart.jsx → BarChart.tsx} +23 -5
  24. package/src/components/BarChart/components/context.tsx +20 -2
  25. package/src/components/BarChart/helpers/getBarHeights.ts +47 -0
  26. package/src/components/BarChart/helpers/index.ts +5 -2
  27. package/src/components/BarChart/helpers/tests/getBarHeights.test.ts +83 -0
  28. package/src/{hooks → components/BarChart/helpers}/useBarChart.ts +11 -47
  29. package/src/components/BoxPlot/BoxPlot.tsx +2 -1
  30. package/src/components/DeviationBar.jsx +2 -1
  31. package/src/components/EditorPanel/EditorPanel.tsx +60 -24
  32. package/src/components/EditorPanel/components/Panels/Panel.General.tsx +34 -34
  33. package/src/components/EditorPanel/components/Panels/Panel.Series.tsx +51 -25
  34. package/src/components/EditorPanel/components/Panels/Panel.Visual.tsx +10 -3
  35. package/src/components/EditorPanel/helpers/updateFieldRankByValue.ts +4 -3
  36. package/src/components/EditorPanel/useEditorPermissions.ts +1 -4
  37. package/src/components/ForestPlot/ForestPlot.tsx +2 -2
  38. package/src/components/Legend/Legend.Component.tsx +69 -58
  39. package/src/components/Legend/Legend.Suppression.tsx +12 -22
  40. package/src/components/Legend/Legend.tsx +3 -1
  41. package/src/components/Legend/LegendGroup/LegendGroup.styles.css +40 -0
  42. package/src/components/Legend/LegendGroup/LegendGroup.tsx +103 -0
  43. package/src/components/Legend/LegendGroup/index.tsx +3 -0
  44. package/src/components/Legend/helpers/createFormatLabels.tsx +28 -0
  45. package/src/components/LineChart/LineChartProps.ts +3 -1
  46. package/src/components/LineChart/components/LineChart.Circle.tsx +77 -119
  47. package/src/components/LineChart/helpers.ts +133 -56
  48. package/src/components/LineChart/index.tsx +125 -60
  49. package/src/components/LinearChart.tsx +74 -115
  50. package/src/components/PairedBarChart.jsx +3 -2
  51. package/src/components/PieChart/PieChart.tsx +71 -91
  52. package/src/components/ScatterPlot/ScatterPlot.jsx +5 -0
  53. package/src/components/Sparkline/components/SparkLine.tsx +80 -18
  54. package/src/components/ZoomBrush.tsx +4 -4
  55. package/src/data/initial-state.js +4 -2
  56. package/src/helpers/countNumOfTicks.ts +1 -1
  57. package/src/helpers/dataHelpers.ts +31 -0
  58. package/src/helpers/sizeHelpers.ts +23 -0
  59. package/src/hooks/useMinMax.ts +21 -28
  60. package/src/hooks/useRightAxis.ts +4 -2
  61. package/src/hooks/useScales.ts +12 -14
  62. package/src/hooks/useTooltip.tsx +204 -203
  63. package/src/index.jsx +2 -2
  64. package/src/scss/main.scss +13 -6
  65. package/src/store/chart.actions.ts +1 -1
  66. package/src/types/ChartConfig.ts +7 -1
  67. package/LICENSE +0 -201
  68. package/examples/private/DEV-8850-2.json +0 -493
  69. package/examples/private/DEV-9822.json +0 -574
  70. package/examples/private/DEV-9840.json +0 -553
  71. package/examples/private/DEV-9850-3.json +0 -461
  72. package/examples/private/chart.json +0 -1084
  73. package/examples/private/ci_formatted.json +0 -202
  74. package/examples/private/ci_issue.json +0 -3016
  75. package/examples/private/completed.json +0 -634
  76. package/examples/private/dem-data-long.csv +0 -20
  77. package/examples/private/dem-data-long.json +0 -36
  78. package/examples/private/demographic_data.csv +0 -157
  79. package/examples/private/demographic_data.json +0 -2654
  80. package/examples/private/demographic_dynamic.json +0 -443
  81. package/examples/private/demographic_standard.json +0 -560
  82. package/examples/private/ehdi.json +0 -29939
  83. package/examples/private/not-loading.json +0 -360
  84. package/examples/private/test.json +0 -493
@@ -1,23 +1,46 @@
1
1
  import { DataItem, StyleProps, Style } from './LineChartProps'
2
2
  import { PreliminaryDataItem } from '../../types/ChartConfig'
3
- import { getTextWidth } from '@cdc/core/helpers/getTextWidth'
3
+
4
4
  import _ from 'lodash'
5
5
  export const createStyles = (props: StyleProps): Style[] => {
6
- const { preliminaryData, data, stroke, strokeWidth, handleLineType, lineType, seriesKey } = props
6
+ const {
7
+ preliminaryData,
8
+ data,
9
+ stroke,
10
+ strokeWidth,
11
+ handleLineType,
12
+ lineType,
13
+ seriesKey,
14
+ dynamicCategory,
15
+ originalSeriesKey
16
+ } = props
7
17
 
18
+ const dynamicSeriesKey = dynamicCategory ? originalSeriesKey : seriesKey
8
19
  const validPreliminaryData: PreliminaryDataItem[] = preliminaryData.filter(
9
20
  pd => pd.seriesKey && pd.column && pd.value && pd.type && pd.style && pd.type === 'effect'
10
21
  )
11
- const getMatchingPd = (point: DataItem): PreliminaryDataItem =>
12
- validPreliminaryData.find(
13
- pd =>
22
+ const isEffectLine = (pd, dataPoint) => {
23
+ if (dynamicCategory) {
24
+ return (
25
+ pd.type === 'effect' &&
26
+ pd.style !== 'Open Circles' &&
14
27
  pd.seriesKey === seriesKey &&
15
- point[pd.column] === pd.value &&
28
+ String(dataPoint[dynamicSeriesKey]) === String(pd.value)
29
+ )
30
+ } else {
31
+ return (
32
+ pd.seriesKey === seriesKey &&
33
+ dataPoint[pd.column] === pd.value &&
16
34
  pd.type === 'effect' &&
17
35
  pd.style !== 'Open Circles'
18
- )
36
+ )
37
+ }
38
+ }
39
+
40
+ const getMatchingPd = (point: DataItem): PreliminaryDataItem =>
41
+ validPreliminaryData.find(pd => isEffectLine(pd, point))
19
42
 
20
- let styles: Style[] = []
43
+ const styles: Style[] = []
21
44
  const createStyle = (lineStyle): Style => ({
22
45
  stroke: stroke,
23
46
  strokeWidth: strokeWidth,
@@ -25,7 +48,8 @@ export const createStyles = (props: StyleProps): Style[] => {
25
48
  })
26
49
 
27
50
  data.forEach((d, index) => {
28
- let matchingPd: PreliminaryDataItem = getMatchingPd(d)
51
+ const matchingPd: PreliminaryDataItem = getMatchingPd(d)
52
+
29
53
  let style: Style = matchingPd
30
54
  ? createStyle(handleLineType(matchingPd.style))
31
55
  : createStyle(handleLineType(lineType))
@@ -92,11 +116,20 @@ export const filterCircles = (
92
116
  }
93
117
 
94
118
  const isCalculable = value => !isNaN(parseFloat(value)) && isFinite(value)
95
- const handleFirstIndex = (data, seriesKey, preliminaryData) => {
119
+ const handleFirstIndex = ({
120
+ data,
121
+ seriesKey,
122
+ preliminaryData,
123
+ dynamicCategory,
124
+ originalSeriesKey,
125
+ colorScale,
126
+ isSuppressed
127
+ }) => {
96
128
  let pairCount = '0'
97
129
  const result = {
98
130
  data: { '0': [] },
99
- style: ''
131
+ style: '',
132
+ color: ''
100
133
  }
101
134
 
102
135
  // If data is empty, return the empty result
@@ -104,26 +137,24 @@ const handleFirstIndex = (data, seriesKey, preliminaryData) => {
104
137
 
105
138
  const firstIndexDataItem = data[0]
106
139
 
107
- // Function to check if a data item matches the suppression criteria
108
- const isSuppressed = pd => {
109
- if (pd.type === 'effect' || pd.hideLineStyle) return
110
- return (
111
- pd.type == 'suppression' && pd.value === firstIndexDataItem[seriesKey] && (!pd.column || pd.column === seriesKey)
112
- )
113
- }
114
-
115
140
  // Find applicable suppression data for the first item
116
- const suppressionData = preliminaryData.find(isSuppressed)
141
+ const suppressionData = (preliminaryData ?? []).find(
142
+ item => item && firstIndexDataItem && isSuppressed(item, firstIndexDataItem)
143
+ )
117
144
 
118
145
  if (suppressionData && suppressionData.style) {
119
146
  // Modify first item and add to result
120
- const modifiedItem = { ...firstIndexDataItem, [seriesKey]: 0 }
147
+ const dynamicSeriesKey = dynamicCategory ? originalSeriesKey : seriesKey
148
+
149
+ const modifiedItem = { ...firstIndexDataItem, [dynamicSeriesKey]: 0 }
150
+
121
151
  result.data[pairCount].push(modifiedItem)
122
152
  result.style = suppressionData.style
153
+ result.color = dynamicCategory && modifiedItem ? colorScale(modifiedItem[dynamicCategory]) : ''
123
154
 
124
155
  // Find the next calculable item index
125
156
  let nextIndex = 1
126
- while (nextIndex < data.length && !isCalculable(data[nextIndex][seriesKey])) {
157
+ while (nextIndex < data.length && !isCalculable(data[nextIndex][dynamicSeriesKey])) {
127
158
  nextIndex++
128
159
  }
129
160
  if (nextIndex < data.length) {
@@ -133,33 +164,38 @@ const handleFirstIndex = (data, seriesKey, preliminaryData) => {
133
164
  // If no suppression, just add the first item
134
165
  result.data[pairCount].push(firstIndexDataItem)
135
166
  }
136
-
137
167
  return result
138
168
  }
139
169
 
140
- const handleLastIndex = (data, seriesKey, preliminaryData) => {
170
+ const handleLastIndex = ({
171
+ data,
172
+ seriesKey,
173
+ preliminaryData,
174
+ dynamicCategory,
175
+ originalSeriesKey,
176
+ colorScale,
177
+ isSuppressed
178
+ }) => {
141
179
  let pairCount = '0'
142
180
  const result = {
143
181
  data: { '0': [] },
144
- style: ''
182
+ style: '',
183
+ color: ''
145
184
  }
146
- let lastAddedIndex = -1 // Tracks the last index added to the result
185
+ const lastIndexDataItem = data[data.length - 1]
186
+
187
+ const dynamicSeriesKey = dynamicCategory ? originalSeriesKey : seriesKey
188
+ let lastAddedIndex = -1
147
189
  preliminaryData?.forEach(pd => {
148
- if (pd.type === 'effect') return
149
- if (
150
- data[data.length - 1][seriesKey] === pd.value &&
151
- pd.style &&
152
- (!pd.column || pd.column === seriesKey) &&
153
- pd.type == 'suppression' &&
154
- !pd.hideLineStyle
155
- ) {
190
+ if (pd.type === 'effect') return []
191
+ if (isSuppressed(pd, lastIndexDataItem)) {
156
192
  const lastIndex = data.length - 1
157
- const modifiedItem = { ...data[lastIndex], [seriesKey]: 0 }
193
+ const modifiedItem = { ...data[lastIndex], [dynamicSeriesKey]: 0 }
158
194
  result.data[pairCount].push(modifiedItem)
159
195
 
160
196
  // Find previous calculable item
161
197
  let prevIndex = lastIndex - 1
162
- while (prevIndex >= 0 && !isCalculable(data[prevIndex][seriesKey])) {
198
+ while (prevIndex >= 0 && !isCalculable(data[prevIndex][dynamicSeriesKey])) {
163
199
  prevIndex--
164
200
  }
165
201
  if (prevIndex >= 0 && lastAddedIndex !== prevIndex) {
@@ -167,33 +203,47 @@ const handleLastIndex = (data, seriesKey, preliminaryData) => {
167
203
  lastAddedIndex = prevIndex
168
204
  }
169
205
  result.style = pd.style
206
+ result.color = colorScale(modifiedItem[dynamicCategory])
170
207
  }
171
208
  })
172
209
 
173
210
  return result
174
211
  }
175
212
 
176
- function handleMiddleIndices(data, seriesKey, preliminaryData) {
177
- // slice data to remove first and last object these no need for handleMiddleIndices
178
-
213
+ export const handleMiddleIndices = ({
214
+ data,
215
+ seriesKey,
216
+ preliminaryData,
217
+ dynamicCategory,
218
+ originalSeriesKey,
219
+ colorScale,
220
+ isSuppressed
221
+ }) => {
179
222
  let result = {
180
223
  data: {},
181
- style: ''
224
+ style: '',
225
+ color: 'red'
182
226
  }
227
+
228
+ //skip processing if data or preliminaryData is not an array
229
+ if (!Array.isArray(data) || !Array.isArray(preliminaryData)) {
230
+ return result
231
+ }
232
+
183
233
  // Variable to count the number of sibling pairs found
184
234
  let pairCount = 1
235
+ const dynamicSeriesKey = dynamicCategory ? originalSeriesKey : seriesKey
185
236
 
186
237
  // Loop through the data array to find each occurrence of the target value
187
238
  data.forEach((item, index) => {
188
239
  preliminaryData.forEach(pd => {
189
- const targetValue = pd.value
190
- if (item[seriesKey] === targetValue) {
240
+ if (isSuppressed(pd, item)) {
191
241
  let siblingBefore = null
192
242
  let siblingAfter = null
193
243
 
194
244
  // Find the nearest numeric sibling before the current index
195
245
  for (let i = index - 1; i >= 0; i--) {
196
- if (isCalculable(data[i][seriesKey])) {
246
+ if (isCalculable(data[i][dynamicSeriesKey])) {
197
247
  siblingBefore = data[i]
198
248
  break // Stop searching once a valid sibling is found
199
249
  }
@@ -201,7 +251,7 @@ function handleMiddleIndices(data, seriesKey, preliminaryData) {
201
251
 
202
252
  // Find the nearest numeric sibling after the current index
203
253
  for (let j = index + 1; j < data.length; j++) {
204
- if (isCalculable(data[j][seriesKey])) {
254
+ if (isCalculable(data[j][dynamicSeriesKey])) {
205
255
  siblingAfter = data[j]
206
256
  break // Stop searching once a valid sibling is found
207
257
  }
@@ -210,24 +260,51 @@ function handleMiddleIndices(data, seriesKey, preliminaryData) {
210
260
  // Only add siblings to results if both siblings are found
211
261
  if (siblingBefore && siblingAfter) {
212
262
  result.style = pd.style
263
+ result.color = colorScale(item[dynamicCategory])
213
264
  result.data[pairCount++] = [siblingBefore, siblingAfter]
214
265
  }
215
266
  }
216
267
  })
217
268
  })
269
+
218
270
  return result
219
271
  }
220
272
 
221
- // create segments (array of arrays) for building suppressed Lines
222
- export const createDataSegments = (data, seriesKey, preliminaryData, dataKey) => {
223
- // Process the first index if necessary
224
- const firstSegment = handleFirstIndex(data, seriesKey, preliminaryData)
225
- // Process the last index if necessary
226
- const lastSegment = handleLastIndex(data, seriesKey, preliminaryData)
227
- // Process the middle segment
228
- const middleSegments = handleMiddleIndices(data, seriesKey, preliminaryData)
229
-
230
- // Combine all segments into a single array
231
- return [firstSegment, middleSegments, lastSegment]
232
- // return [firstSegment, middleSegments, lastSegment].filter(segment => segment.data.length > 0 && segment.style !== '')
273
+ export const createDataSegments = props => {
274
+ const dynamicData = (props.data ?? []).filter(d => {
275
+ if (!props?.dynamicCategory) return true
276
+
277
+ return (props.preliminaryData ?? []).some(pd => d?.[props.dynamicCategory] === props?.seriesKey)
278
+ })
279
+ const isSuppressed = (pd, dataItem) => {
280
+ if (pd.type === 'effect' || pd.hideLineStyle) return false
281
+
282
+ if (props.dynamicCategory) {
283
+ return (
284
+ pd.type === 'suppression' &&
285
+ (!pd.column || pd.column === dataItem[props.dynamicCategory]) &&
286
+ pd.value === dataItem[props.originalSeriesKey]
287
+ )
288
+ }
289
+ return (
290
+ pd.type === 'suppression' &&
291
+ pd.value === dataItem[props.seriesKey] &&
292
+ (!pd.column || pd.column === props.seriesKey)
293
+ )
294
+ }
295
+ const firstSegment = handleFirstIndex({ ...props, data: dynamicData, isSuppressed })
296
+ const lastSegment = handleLastIndex({ ...props, data: dynamicData, isSuppressed })
297
+ const middleSegments = handleMiddleIndices({ ...props, data: dynamicData, isSuppressed })
298
+
299
+ const segments = [firstSegment, middleSegments, lastSegment]
300
+
301
+ // ✅ Filter: keep only segments with real data
302
+ return segments.filter(segment => {
303
+ if (!segment || !segment.data) return false
304
+
305
+ // Check if at least one non-empty array exists in `data`
306
+ const hasData = Object.values(segment.data).some(arr => Array.isArray(arr) && arr.length > 0)
307
+
308
+ return hasData
309
+ })
233
310
  }
@@ -28,7 +28,6 @@ const LineChart = (props: LineChartProps) => {
28
28
  const {
29
29
  getXAxisData,
30
30
  getYAxisData,
31
- handleTooltipClick,
32
31
  handleTooltipMouseOff,
33
32
  handleTooltipMouseOver,
34
33
  tooltipData,
@@ -36,53 +35,53 @@ const LineChart = (props: LineChartProps) => {
36
35
  xScale,
37
36
  yMax,
38
37
  yScale,
39
- } = props
38
+ } = props
40
39
 
41
40
  // prettier-ignore
42
41
  const { colorScale, config, formatNumber, handleLineType, parseDate, seriesHighlight, tableData, transformedData, updateConfig, brushConfig,clean } = useContext<ChartContext>(ConfigContext)
43
42
  const { yScaleRight } = useRightAxis({ config, yMax, data: transformedData, updateConfig })
44
- if (!handleTooltipMouseOver) return
43
+ const showSingleSeries = config.tooltips.singleSeries
45
44
 
46
45
  const DEBUG = false
47
46
  const { lineDatapointStyle, showLineSeriesLabels, legend } = config
48
- let data = transformedData
49
- let tableD = tableData
47
+ const isBrushOn = brushConfig.data.length > 0 && config.brush?.active
50
48
  // if brush on use brush data and clean
51
- if (brushConfig.data.length > 0 && config.brush?.active) {
52
- data = clean(brushConfig.data)
53
- tableD = clean(brushConfig.data)
54
- }
49
+ const data = isBrushOn ? clean(brushConfig.data) : transformedData
50
+ const _tableData = isBrushOn ? clean(brushConfig.data) : tableData
55
51
 
56
52
  const xPos = d => {
57
53
  return xScale(getXAxisData(d)) + (xScale.bandwidth ? xScale.bandwidth() / 2 : 0)
58
54
  }
59
55
 
56
+ const tooltipPoints = []
57
+
60
58
  return (
61
59
  <ErrorBoundary component='LineChart'>
62
- <Group left={Number(config.runtime.yAxis.size)}>
63
- {' '}
60
+ <Group left={Number(config.runtime.yAxis.size)} className='line-chart-group'>
64
61
  {/* left - expects a number not a string */}
65
62
  {(config.runtime.lineSeriesKeys || config.runtime.seriesKeys).map((seriesKey, index) => {
66
63
  const seriesData = config.runtime.series.find(item => item.dataKey === seriesKey)
64
+ const _seriesKey = seriesData.dynamicCategory ? seriesData.originalDataKey : seriesKey
67
65
  const lineType = seriesData.type
68
66
  const seriesAxis = seriesData.axis || 'left'
69
67
  const displayArea =
70
68
  legend.behavior === 'highlight' || seriesHighlight.length === 0 || seriesHighlight.indexOf(seriesKey) !== -1
71
69
 
72
- const suppressedSegments = createDataSegments(
73
- tableData,
70
+ const suppressedSegments = createDataSegments({
71
+ data: tableData,
74
72
  seriesKey,
75
- config.preliminaryData,
76
- config.xAxis.dataKey
77
- )
73
+ preliminaryData: config.preliminaryData,
74
+ dynamicCategory: seriesData.dynamicCategory,
75
+ originalSeriesKey: _seriesKey,
76
+ colorScale
77
+ })
78
78
  const isSplitLine =
79
79
  config?.preliminaryData?.filter(pd => pd.style && !pd.style.includes('Circles')).length > 0
80
80
 
81
81
  const _data = seriesData.dynamicCategory
82
82
  ? data.filter(d => d[seriesData.dynamicCategory] === seriesKey)
83
83
  : data
84
- const _seriesKey = seriesData.dynamicCategory ? seriesData.originalDataKey : seriesKey
85
- const circleData = filterCircles(config?.preliminaryData, tableD, _seriesKey)
84
+ const circleData = filterCircles(config?.preliminaryData, tableData, _seriesKey)
86
85
  return (
87
86
  <Group
88
87
  key={`series-${seriesKey}-${index}`}
@@ -108,11 +107,15 @@ const LineChart = (props: LineChartProps) => {
108
107
  height={Number(yMax)}
109
108
  fill={DEBUG ? 'red' : 'transparent'}
110
109
  fillOpacity={0.05}
111
- onMouseMove={e => handleTooltipMouseOver(e, tableData)}
112
- onMouseOut={handleTooltipMouseOff}
113
- onClick={e => handleTooltipClick(e, data)}
114
110
  />
115
111
  {_data.map((d, dataIndex) => {
112
+ tooltipPoints.push({
113
+ color: colorScale(config.runtime.seriesLabels[seriesKey]),
114
+ seriesKey: _seriesKey,
115
+ seriesIndex: index,
116
+ xValue: d[config.xAxis.dataKey],
117
+ yValue: d[_seriesKey]
118
+ })
116
119
  return (
117
120
  isNumber(d[_seriesKey]) && (
118
121
  <React.Fragment key={`series-${seriesKey}-point-${dataIndex}`}>
@@ -136,7 +139,6 @@ const LineChart = (props: LineChartProps) => {
136
139
  <LineChartCircle
137
140
  mode='ALWAYS_SHOW_POINTS'
138
141
  dataIndex={dataIndex}
139
- circleData={circleData}
140
142
  tableData={tableData}
141
143
  data={_data}
142
144
  d={d}
@@ -155,18 +157,37 @@ const LineChart = (props: LineChartProps) => {
155
157
  />
156
158
  )}
157
159
 
160
+ {(lineDatapointStyle === 'hover' || lineDatapointStyle === 'hidden') && (
161
+ <LineChartCircle
162
+ mode='HOVER_POINTS'
163
+ dataIndex={dataIndex}
164
+ tableData={tableData}
165
+ data={_data}
166
+ d={d}
167
+ config={config}
168
+ seriesKey={_seriesKey}
169
+ displayArea={displayArea}
170
+ xScale={xScale}
171
+ yScale={yScale}
172
+ colorScale={colorScale}
173
+ parseDate={parseDate}
174
+ yScaleRight={yScaleRight}
175
+ seriesAxis={seriesAxis}
176
+ seriesIndex={index}
177
+ key={`line-hover-circle--${dataIndex}`}
178
+ />
179
+ )}
180
+
158
181
  <LineChartCircle
159
182
  mode='ISOLATED_POINTS'
160
183
  seriesIndex={index}
161
184
  dataIndex={dataIndex}
162
185
  tableData={tableData}
163
- circleData={circleData}
164
186
  data={_data}
165
187
  d={d}
166
188
  config={config}
167
189
  seriesKey={_seriesKey}
168
190
  displayArea={displayArea}
169
- tooltipData={tooltipData}
170
191
  xScale={xScale}
171
192
  yScale={yScale}
172
193
  colorScale={colorScale}
@@ -179,28 +200,6 @@ const LineChart = (props: LineChartProps) => {
179
200
  )
180
201
  )
181
202
  })}
182
- <>
183
- {lineDatapointStyle === 'hover' && (
184
- <LineChartCircle
185
- seriesIndex={index}
186
- tableData={tableData}
187
- dataIndex={0}
188
- mode='HOVER_POINTS'
189
- circleData={circleData}
190
- data={_data}
191
- config={config}
192
- seriesKey={seriesKey}
193
- displayArea={displayArea}
194
- tooltipData={tooltipData}
195
- xScale={xScale}
196
- yScale={yScale}
197
- colorScale={colorScale}
198
- parseDate={parseDate}
199
- yScaleRight={yScaleRight}
200
- seriesAxis={seriesAxis}
201
- />
202
- )}
203
- </>
204
203
 
205
204
  {/* SPLIT LINE */}
206
205
  {isSplitLine ? (
@@ -217,15 +216,17 @@ const LineChart = (props: LineChartProps) => {
217
216
  }
218
217
  styles={createStyles({
219
218
  preliminaryData: config.preliminaryData,
220
- data: tableD,
219
+ data: tableData,
221
220
  stroke: colorScale(config.runtime.seriesLabels[seriesKey]),
222
221
  strokeWidth: seriesData.weight || 2,
223
222
  handleLineType,
224
223
  lineType,
225
- seriesKey
224
+ seriesKey,
225
+ dynamicCategory: seriesData.dynamicCategory,
226
+ originalSeriesKey: _seriesKey
226
227
  })}
227
228
  defined={(item, i) => {
228
- return item[seriesKey] !== '' && item[seriesKey] !== null && item[seriesKey] !== undefined
229
+ return item[_seriesKey] !== '' && item[_seriesKey] !== null && item[_seriesKey] !== undefined
229
230
  }}
230
231
  />
231
232
 
@@ -238,16 +239,22 @@ const LineChart = (props: LineChartProps) => {
238
239
  x={d => xPos(d)}
239
240
  y={d =>
240
241
  seriesAxis === 'Right'
241
- ? yScaleRight(getYAxisData(d, seriesKey))
242
- : yScale(Number(getYAxisData(d, seriesKey)))
242
+ ? yScaleRight(getYAxisData(d, _seriesKey))
243
+ : yScale(Number(getYAxisData(d, _seriesKey)))
244
+ }
245
+ stroke={
246
+ seriesData.dynamicCategory
247
+ ? segment.color
248
+ : colorScale(config.runtime.seriesLabels[seriesKey])
243
249
  }
244
- stroke={colorScale(config.runtime.seriesLabels[seriesKey])}
245
250
  strokeWidth={seriesData[0]?.weight || 2}
246
251
  strokeOpacity={1}
247
252
  shapeRendering='geometricPrecision'
248
253
  strokeDasharray={handleLineType(segment.style)}
249
254
  defined={(item, i) => {
250
- return item[seriesKey] !== '' && item[seriesKey] !== null && item[seriesKey] !== undefined
255
+ return (
256
+ item[_seriesKey] !== '' && item[_seriesKey] !== null && item[_seriesKey] !== undefined
257
+ )
251
258
  }}
252
259
  />
253
260
  )
@@ -269,6 +276,7 @@ const LineChart = (props: LineChartProps) => {
269
276
  return (
270
277
  <AreaClosed
271
278
  key={`area-closed-${seriesKey}-${categoryIndex}`}
279
+ className='confidence-interval'
272
280
  data={categoryData}
273
281
  x={d => xPos(d)}
274
282
  y0={d => yScale(d[config.confidenceKeys.lower])} // Lower bound of the confidence interval
@@ -393,21 +401,32 @@ const LineChart = (props: LineChartProps) => {
393
401
  break
394
402
  }
395
403
  }
396
- if (!lastDatum) {
404
+ if (!lastDatum || legend.position === 'right') {
397
405
  return <></>
398
406
  }
407
+
408
+ let labelText = config.runtime.seriesLabels[seriesKey] || seriesKey
409
+ // truncate labels longer that 10 chars
410
+ const ellipsis = '...'
411
+ if (labelText.length > 10) {
412
+ labelText = labelText.substring(0, 10) + ellipsis
413
+ }
414
+
399
415
  return (
400
416
  <Text
417
+ display={
418
+ legend.behavior === 'highlight' ||
419
+ (seriesHighlight.length === 0 && !legend.dynamicLegend) ||
420
+ seriesHighlight.indexOf(seriesKey) !== -1
421
+ ? 'block'
422
+ : 'none'
423
+ }
401
424
  x={xPos(lastDatum) + 5}
402
425
  y={yScale(getYAxisData(lastDatum, seriesKey))}
403
426
  alignmentBaseline='middle'
404
- fill={
405
- config.colorMatchLineSeriesLabels && colorScale
406
- ? colorScale(config.runtime.seriesLabels[seriesKey] || seriesKey)
407
- : 'black'
408
- }
427
+ fill={colorScale(config.runtime.seriesLabels[seriesKey] || seriesKey)}
409
428
  >
410
- {config.runtime.seriesLabels[seriesKey] || seriesKey}
429
+ {labelText}
411
430
  </Text>
412
431
  )
413
432
  })}
@@ -421,6 +440,52 @@ const LineChart = (props: LineChartProps) => {
421
440
  </Text>
422
441
  )}
423
442
  </Group>
443
+ <Group left={Number(config.runtime.yAxis.size)} className='glyph-tooltip-group'>
444
+ <Group key={`tooltip-group`} display={'block'}>
445
+ {/* tooltips */}
446
+ <Bar
447
+ key={'tooltip bars'}
448
+ width={Number(xMax)}
449
+ height={Number(yMax)}
450
+ fill={DEBUG ? 'red' : 'transparent'}
451
+ fillOpacity={0.05}
452
+ onMouseMove={e => {
453
+ if (showSingleSeries) return
454
+ handleTooltipMouseOver(e, tableData)
455
+ }}
456
+ onMouseOut={handleTooltipMouseOff}
457
+ />
458
+ {tooltipPoints.map((d, dataIndex) => {
459
+ const { _data, seriesKey, seriesIndex, color } = d
460
+ return (
461
+ <React.Fragment key={`series-${seriesKey}-point-${dataIndex}`}>
462
+ <LineChartCircle
463
+ mode='TOOLTIP_POINTS'
464
+ dataIndex={dataIndex}
465
+ tooltipPoint={d}
466
+ tableData={tableData}
467
+ data={_data}
468
+ d={d}
469
+ config={config}
470
+ seriesKey={seriesKey}
471
+ displayArea={true}
472
+ tooltipData={tooltipData}
473
+ xScale={xScale}
474
+ yScale={yScale}
475
+ colorScale={colorScale}
476
+ parseDate={parseDate}
477
+ yScaleRight={yScaleRight}
478
+ seriesAxis={'[circle]'}
479
+ seriesIndex={seriesIndex}
480
+ key={`line-circle--${dataIndex}`}
481
+ handleTooltipMouseOver={handleTooltipMouseOver}
482
+ handleTooltipMouseOff={handleTooltipMouseOff}
483
+ />
484
+ </React.Fragment>
485
+ )
486
+ })}
487
+ </Group>
488
+ </Group>
424
489
  {config.visualizationType === 'Bump Chart' && (
425
490
  <LineChartBumpCircle config={config} xScale={xScale} yScale={yScale} />
426
491
  )}