@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
@@ -8,6 +8,7 @@ import { isDateScale } from '@cdc/core/helpers/cove/date'
8
8
  import { localPoint } from '@visx/event'
9
9
  import { bisector } from 'd3-array'
10
10
  import _ from 'lodash'
11
+ import { getHorizontalBarHeights } from '../components/BarChart/helpers/getBarHeights'
11
12
 
12
13
  export const useTooltip = props => {
13
14
  const {
@@ -21,31 +22,35 @@ export const useTooltip = props => {
21
22
  setSharedFilter,
22
23
  isDraggingAnnotation
23
24
  } = useContext<ChartContext>(ConfigContext)
24
- const { xScale, yScale, showTooltip, hideTooltip } = props
25
+ const { xScale, yScale, seriesScale, showTooltip, hideTooltip } = props
25
26
  const { xAxis, visualizationType, orientation, yAxis, runtime } = config
26
27
 
27
- /**
28
- * Provides the tooltip information based on the tooltip data array and svg cursor coordinates
29
- * @function getTooltipInformation
30
- * @param {Array} tooltipDataArray - The array containing the tooltip data.
31
- * @param {Object} eventSvgCoords - The object containing the SVG coordinates of the event.
32
- * @return {Object} - The tooltip information with tooltip data.
33
- */
28
+ const Y_AXIS_SIZE = Number(config.yAxis.size || 0)
34
29
 
35
- // function handles only Single series hovred data tooltips
30
+ // function handles only Single series hovered data tooltips
36
31
  const findDataKeyByThreshold = (mouseY, datapoint) => {
37
- let sum = 0
38
- let threshold = Number(yScale.invert(mouseY))
39
32
  let hoveredKey = null
40
33
  let hoveredValue = null
41
-
42
- for (let key of config.runtime?.seriesKeys) {
43
- if (datapoint.hasOwnProperty(key)) {
44
- sum += Number(datapoint[key])
45
- if (sum >= threshold) {
46
- hoveredValue = datapoint[key]
47
- hoveredKey = key
48
- break
34
+ const dynamicSeries = config.series.find(s => s.dynamicCategory)
35
+ if (dynamicSeries) {
36
+ hoveredKey = datapoint[dynamicSeries.dynamicCategory]
37
+ hoveredValue = datapoint[dynamicSeries.dataKey]
38
+ } else {
39
+ let sum = 0
40
+ let threshold
41
+ try {
42
+ threshold = Number(yScale.invert(mouseY))
43
+ } catch (e) {
44
+ return []
45
+ }
46
+ for (let key of config.runtime?.seriesKeys) {
47
+ if (datapoint.hasOwnProperty(key)) {
48
+ sum += Number(datapoint[key])
49
+ if (sum >= threshold) {
50
+ hoveredValue = datapoint[key]
51
+ hoveredKey = key
52
+ break
53
+ }
49
54
  }
50
55
  }
51
56
  }
@@ -62,25 +67,6 @@ export const useTooltip = props => {
62
67
  return showMissingDataValue ? 'N/A' : formattedValue
63
68
  }
64
69
 
65
- const getTooltipInformation = (tooltipDataArray, eventSvgCoords) => {
66
- const { x, y } = eventSvgCoords
67
- let initialTooltipData = tooltipDataArray || {}
68
-
69
- const tooltipData = {
70
- data: initialTooltipData,
71
- dataXPosition: x + 10,
72
- dataYPosition: y
73
- }
74
-
75
- const tooltipInformation = {
76
- tooltipLeft: tooltipData.dataXPosition,
77
- tooltipTop: tooltipData.dataYPosition,
78
- tooltipData: tooltipData
79
- }
80
-
81
- return tooltipInformation
82
- }
83
-
84
70
  /**
85
71
  * Handles the mouse over event for the tooltip.
86
72
  * @function handleTooltipMouseOver
@@ -89,81 +75,75 @@ export const useTooltip = props => {
89
75
  */
90
76
  const handleTooltipMouseOver = (e, additionalChartData) => {
91
77
  if (visualizationType === 'Bump Chart') return
92
- e.stopPropagation()
78
+ //e.stopPropagation()
93
79
  if (isDraggingAnnotation) return
94
80
 
95
81
  const eventSvgCoords = localPoint(e)
96
82
  const { x, y } = eventSvgCoords
97
83
 
98
- // Additional data for pie charts
99
- const { data: pieChartData, arc } = additionalChartData ?? {}
100
-
101
- const closestXScaleValue = getXValueFromCoordinate(x - Number(config.yAxis.size || 0))
84
+ const resolvedScaleValues = getResolvedScaleValues([x, y])
85
+ const singleSeriesValue = getYValueFromCoordinate(y, resolvedScaleValues)
86
+ const columnsWithTooltips = []
87
+ const tooltipItems = [] as any[][]
88
+ for (const [colKey, column] of Object.entries(config.columns)) {
89
+ const formattingParams = {
90
+ addColPrefix: column.prefix,
91
+ addColSuffix: column.suffix,
92
+ addColRoundTo: column.roundToPlace || '',
93
+ addColCommas: column.commas
94
+ }
102
95
 
103
- const xScaleValues = data.filter(d => d[xAxis.dataKey] === getClosestYValue(y))
96
+ const pieColumnData = additionalChartData?.arc?.data[column.name]
97
+ const columnData =
98
+ config.tooltips.singleSeries && visualizationType === 'Line'
99
+ ? resolvedScaleValues.filter(
100
+ value => value[config.runtime.series[0].dynamicCategory] === singleSeriesValue
101
+ )[0][colKey]
102
+ : resolvedScaleValues[0]?.[colKey]
103
+ const closestValue = config.visualizationType === 'Pie' ? pieColumnData : columnData
104
104
 
105
- const resolvedScaleValues = orientation === 'vertical' ? getYScaleValues(closestXScaleValue) : xScaleValues
105
+ const formattedValue = formatColNumber(closestValue, 'left', true, config, formattingParams)
106
106
 
107
- const getAxisPosition = seriesKey => {
108
- const seriesObj = config.runtime.series.filter(s => s.dataKey === seriesKey)[0]
109
- const position = seriesObj?.axis ? String(seriesObj.axis).toLowerCase() : 'left'
110
- return position
107
+ if (column.tooltips) {
108
+ columnsWithTooltips.push([column.label, formattedValue])
109
+ }
111
110
  }
111
+ const additionalTooltipItems = [] as [string, string | number][]
112
112
 
113
- const getTooltipDataArray = () => {
114
- const columns = config.columns
115
- const columnsWithTooltips = []
116
- const tooltipItems = []
117
-
118
- for (const [colKeys, colVals] of Object.entries(columns)) {
119
- const formattingParams = {
120
- addColPrefix: config.columns[colKeys].prefix,
121
- addColSuffix: config.columns[colKeys].suffix,
122
- addColRoundTo: config.columns[colKeys].roundToPlace ? config.columns[colKeys].roundToPlace : '',
123
- addColCommas: config.columns[colKeys].commas
124
- }
125
- let closestValue = null
126
-
127
- if (config.visualizationType === 'Pie') {
128
- closestValue = arc?.data[colVals.name]
129
- } else {
130
- closestValue = resolvedScaleValues[0]?.[colVals.name]
131
- }
132
-
133
- const formattedValue = formatColNumber(closestValue, 'left', true, config, formattingParams)
113
+ columnsWithTooltips.forEach(columnData => {
114
+ additionalTooltipItems.push([columnData[0], columnData[1]])
115
+ })
134
116
 
135
- if (colVals.tooltips) {
136
- columnsWithTooltips.push([colVals.label, formattedValue])
137
- }
138
- }
139
- const additionalTooltipItems = []
117
+ if (visualizationType === 'Pie') {
118
+ const roundTo = Number(config.dataFormat.roundTo) || 0
140
119
 
141
- columnsWithTooltips.forEach(columnData => {
142
- additionalTooltipItems.push([columnData[0], columnData[1]])
143
- })
120
+ const pieData = additionalChartData?.data ?? {}
121
+ const startAngle = additionalChartData?.startAngle ?? 0
122
+ const endAngle = additionalChartData?.endAngle ?? 0
144
123
 
145
- if (visualizationType === 'Pie') {
146
- const roundTo = Number(config.dataFormat.roundTo) || 0
124
+ const degrees = ((endAngle - startAngle) * 180) / Math.PI
125
+ const pctOf360 = (degrees / 360) * 100
126
+ const pctString = pctOf360.toFixed(roundTo) + '%'
147
127
 
148
- const degrees = ((arc.endAngle - arc.startAngle) * 180) / Math.PI
149
-
150
- // Calculate the percentage of the full circle (360 degrees)
151
- const percentageOfCircle = (degrees / 360) * 100
152
- const roundedPercentage = percentageOfCircle.toFixed(roundTo)
128
+ tooltipItems.push(
129
+ [config.xAxis.dataKey, pieData[config.xAxis.dataKey]],
130
+ [config.runtime.yAxis.dataKey, formatNumber(pieData[config.runtime.yAxis.dataKey])],
131
+ ['Percent', pctString]
132
+ )
133
+ }
153
134
 
154
- tooltipItems.push(
155
- // ignore
156
- [config.xAxis.dataKey, pieChartData],
157
- [config.runtime.yAxis.dataKey, formatNumber(arc?.data[config.runtime.yAxis.dataKey])],
158
- ['Percent', `${roundedPercentage + '%'}`]
159
- )
160
- }
135
+ if (visualizationType === 'Forest Plot') {
136
+ tooltipItems.push([config.xAxis.dataKey, getClosestYValue(y)])
137
+ }
161
138
 
162
- if (visualizationType === 'Forest Plot') {
163
- tooltipItems.push([config.xAxis.dataKey, getClosestYValue(y)])
139
+ const isLinearChart = !['Pie', 'Forest Plot'].includes(visualizationType)
140
+ if (isLinearChart) {
141
+ const getAxisPosition = seriesKey => {
142
+ const seriesObj = config.runtime.series.filter(s => s.dataKey === seriesKey)[0]
143
+ const position = seriesObj?.axis ? String(seriesObj.axis).toLowerCase() : 'left'
144
+ return position
164
145
  }
165
- // handle tooltip for all hovered series
166
- if (visualizationType !== 'Pie' && visualizationType !== 'Forest Plot' && !config.tooltips.singleSeries) {
146
+ if (!config.tooltips.singleSeries || visualizationType === 'Line') {
167
147
  tooltipItems.push(
168
148
  ...getIncludedTooltipSeries()
169
149
  ?.filter(seriesKey => {
@@ -191,37 +171,53 @@ export const useTooltip = props => {
191
171
  })
192
172
  )
193
173
 
194
- config.runtime.series?.forEach(series => {
174
+ const runtimeSeries =
175
+ config.tooltips.singleSeries && visualizationType === 'Line'
176
+ ? [_.find(config.runtime.series, d => d.dataKey === singleSeriesValue)]
177
+ : config.runtime.series
178
+
179
+ runtimeSeries?.forEach(series => {
195
180
  if (series?.dynamicCategory) {
196
181
  const seriesKey = series.dataKey
197
182
  const resolvedScaleValue = resolvedScaleValues.find(v => v[series.dynamicCategory] === seriesKey)
198
- if (!resolvedScaleValue) return
199
- const value = resolvedScaleValue[series.originalDataKey]
200
- const formattedValue = getFormattedValue(seriesKey, value, config, getAxisPosition)
201
- tooltipItems.push([seriesKey, formattedValue, getAxisPosition(seriesKey)])
183
+ if (resolvedScaleValue) {
184
+ const value = resolvedScaleValue[series.originalDataKey]
185
+ const formattedValue = getFormattedValue(seriesKey, value, config, getAxisPosition)
186
+ tooltipItems.push([seriesKey, formattedValue, getAxisPosition(seriesKey)])
187
+ }
202
188
  }
203
189
  })
204
- }
205
-
206
- // handle tooltip for single hovered series
207
- if (visualizationType !== 'Pie' && visualizationType !== 'Forest Plot' && config.tooltips.singleSeries) {
208
- const [seriesKey, value] = findDataKeyByThreshold(y, resolvedScaleValues[0])
190
+ } else {
191
+ // Show Only the Hovered Series in Tooltip
192
+ const dataColumn = resolvedScaleValues[0]
193
+ const [seriesKey, value] = findDataKeyByThreshold(y, dataColumn)
209
194
  if (seriesKey && value) {
210
- tooltipItems.push([config.xAxis.dataKey, closestXScaleValue])
195
+ const xVal = dataColumn[config.xAxis.dataKey]
196
+ const closestXScaleValue = getXValueFromCoordinate(x - Y_AXIS_SIZE)
197
+
198
+ tooltipItems.push([config.xAxis.dataKey, closestXScaleValue || xVal])
211
199
  const formattedValue = getFormattedValue(seriesKey, value, config, getAxisPosition)
212
200
  tooltipItems.push([seriesKey, formattedValue])
201
+ } else {
202
+ Object.keys(dataColumn).forEach(key => {
203
+ tooltipItems.push([key, dataColumn[key]])
204
+ })
213
205
  }
214
206
  }
215
-
216
- return [...tooltipItems, ...additionalTooltipItems]
217
207
  }
218
208
 
219
- // Returns an array of arrays.
220
- // ie. [ ['Date', '01/01/2023'], ['close', 300] ]
221
- const tooltipDataArray = getTooltipDataArray()
209
+ const dataXPosition = eventSvgCoords.x + 10
210
+ const dataYPosition = eventSvgCoords.y
222
211
 
223
- if (!tooltipDataArray) return
224
- const tooltipInformation = getTooltipInformation(tooltipDataArray, eventSvgCoords)
212
+ const tooltipInformation = {
213
+ tooltipLeft: dataXPosition,
214
+ tooltipTop: dataYPosition,
215
+ tooltipData: {
216
+ data: [...tooltipItems, ...additionalTooltipItems],
217
+ dataXPosition,
218
+ dataYPosition
219
+ }
220
+ }
225
221
  showTooltip(tooltipInformation)
226
222
  }
227
223
 
@@ -291,10 +287,7 @@ export const useTooltip = props => {
291
287
  return closestX
292
288
  }
293
289
 
294
- if (
295
- config.xAxis.type === 'categorical' ||
296
- (visualizationType === 'Combo' && orientation !== 'horizontal' && visualizationType !== 'Forest Plot')
297
- ) {
290
+ if (config.xAxis.type === 'categorical' || visualizationType === 'Combo') {
298
291
  let range = xScale.range()[1] - xScale.range()[0]
299
292
  let eachBand = range / (xScale.domain().length + 1)
300
293
 
@@ -302,17 +295,62 @@ export const useTooltip = props => {
302
295
  const index = Math.floor((Number(numerator) - eachBand / 2) / eachBand)
303
296
  return xScale.domain()[index] // fixes off by 1 error
304
297
  }
298
+ }
305
299
 
306
- if (isDateScale(xAxis) && visualizationType !== 'Combo' && orientation !== 'horizontal') {
307
- const bisectDate = bisector(d => parseDate(d[config.xAxis.dataKey])).left
308
- const x0 = xScale.invert(x)
309
- const index = bisectDate(config.data, x0, 1)
310
- const val = parseDate(config.data[index - 1][config.xAxis.dataKey])
311
- return val
300
+ const findClosest = (dataArray: [any, number][], mouseXorY) => {
301
+ let dataColumn: Object
302
+ dataArray.find(([d, xOrY]) => {
303
+ if (xOrY > mouseXorY) {
304
+ return true
305
+ }
306
+ dataColumn = d
307
+ })
308
+ return dataColumn
309
+ }
310
+
311
+ const getYValueFromCoordinate = (y, xData) => {
312
+ let closestYSeriesValue = null
313
+ let minDistance = Number.MAX_VALUE
314
+ let offset = y
315
+
316
+ xData.forEach(d => {
317
+ const yPosition = yScale(d[config.runtime.series[0].originalDataKey])
318
+ const distance = Math.abs(Number(yPosition - offset))
319
+
320
+ if (distance <= minDistance) {
321
+ minDistance = distance
322
+ closestYSeriesValue = d[config.runtime.series[0].dynamicCategory]
323
+ }
324
+ })
325
+ return closestYSeriesValue
326
+ }
327
+
328
+ const getClosestYValueHorizontalChart = mouseY => {
329
+ const barGroups = yScale.domain().map((group, index) => ({ group, index }))
330
+ const barsWithHeights = getHorizontalBarHeights<{ group }>(config, barGroups)
331
+
332
+ const barGroup = findClosest(
333
+ barsWithHeights.map(d => [d, _.round(d.y)]),
334
+ mouseY
335
+ )
336
+
337
+ const subGroupMouseY = mouseY - barGroup.y
338
+ const columns = data.filter(d => d[config.xAxis.dataKey] === barGroup.group)
339
+
340
+ if (config.series.length > 1 && !config.series.find(s => s.dynamicCategory)) {
341
+ const seriesWithY = config.series.map((c, i) => [c, config.barHeight * i]) as [Object, number][]
342
+ const hoveredSeries = findClosest(seriesWithY, subGroupMouseY)
343
+ const exludeColumns = config.series.filter(s => s.dataKey !== hoveredSeries.dataKey).map(s => s.dataKey)
344
+ const dataColumn = _.omit(columns[0], exludeColumns)
345
+ return dataColumn
346
+ } else {
347
+ const columnsWithY = columns.map((c, i) => [c, config.barHeight * i]) as [Object, number][]
348
+ const dataColumn = findClosest(columnsWithY, subGroupMouseY)
349
+ return dataColumn
312
350
  }
313
351
  }
314
352
 
315
- const getClosestYValue = (yPosition, key) => {
353
+ const getClosestYValue = (yPosition, key = '') => {
316
354
  if (visualizationType === 'Pie') return
317
355
  let minDistance = Number.MAX_VALUE
318
356
  let closestYValue = null
@@ -369,11 +407,15 @@ export const useTooltip = props => {
369
407
  }
370
408
 
371
409
  /**
372
- * Provides an array of objects with the closest y series data items
373
- * @param {String} closestXScaleValue
374
- * @returns an array of objects with the closest y series data items
410
+ * Provides an array of objects with the closest series data items
375
411
  */
376
- const getYScaleValues = closestXScaleValue => {
412
+ const getResolvedScaleValues = ([x, y]) => {
413
+ if (orientation !== 'vertical') {
414
+ if (config.visualizationType === 'Bar' && config.tooltips.singleSeries) {
415
+ return [getClosestYValueHorizontalChart(y)]
416
+ }
417
+ return data.filter(d => d[xAxis.dataKey] === getClosestYValue(y))
418
+ }
377
419
  const runtimeSeries = config.runtime.series.filter(
378
420
  series => visualizationType === 'Pie' || (series.tooltip === true && !series.dynamicCategory)
379
421
  )
@@ -403,21 +445,22 @@ export const useTooltip = props => {
403
445
  const colNames = Object.values(config.columns).map(column => column.name)
404
446
  // @ Murad why are we adding them twice?
405
447
  includedSeries.push(...colNames, ...colNames)
448
+ const closestXScaleValue = getXValueFromCoordinate(x - Y_AXIS_SIZE)
406
449
 
407
- try {
408
- const dataToSearch = data.filter(d => d[xAxis.dataKey] === closestXScaleValue)
409
- // Return an empty array if no matching data is found.
410
- if (!dataToSearch || dataToSearch.length === 0) {
411
- return []
412
- }
450
+ let dataToSearch = (data || []).filter(d => d[xAxis.dataKey] === closestXScaleValue)
413
451
 
414
- const yScaleValues = dataToSearch.map(object => {
415
- return _.pick(object, includedSeries)
416
- })
417
- return yScaleValues
418
- } catch (error) {
419
- console.error('COVE', error)
452
+ if (config.tooltips.singleSeries && config.visualizationType !== 'Line') {
453
+ const dynamicSeries = config.series.find(s => s.dynamicCategory)
454
+ if (dynamicSeries) {
455
+ const dataWithXScale = dataToSearch.map(
456
+ d => [d, seriesScale(d[dynamicSeries.dynamicCategory])] as [Object, number]
457
+ )
458
+ const xOffset = x - Y_AXIS_SIZE - xScale(closestXScaleValue)
459
+ dataToSearch = [findClosest(dataWithXScale, xOffset)]
460
+ }
420
461
  }
462
+
463
+ return dataToSearch.map(d => _.pick(d, includedSeries))
421
464
  }
422
465
 
423
466
  /**
@@ -427,67 +470,26 @@ export const useTooltip = props => {
427
470
  * @returns {Array} Array of items to be included in the tooltip.
428
471
  */
429
472
  const getIncludedTooltipSeries = () => {
430
- try {
431
- let standardLoopItems
432
-
433
- let stageColumns = []
434
- let ciItems = []
435
-
436
- // loop through series for items to add to tooltip.
437
- // there is probably a better way of doing this.
438
- config.runtime.series?.forEach(s => {
439
- if (s.type === 'Forecasting') {
440
- stageColumns.push(s.stageColumn)
441
-
442
- s?.confidenceIntervals.forEach(ci => {
443
- if (ci.showInTooltip === true) {
444
- ciItems.push(ci.low)
445
- ciItems.push(ci.high)
446
- }
447
- })
448
- }
449
- })
450
-
451
- if (!config.dashboard) {
452
- switch (visualizationType) {
453
- case 'Combo':
454
- standardLoopItems = [runtime.xAxis.dataKey, ...runtime?.seriesKeys, ...ciItems]
455
- break
456
- case 'Forecasting':
457
- standardLoopItems = [runtime.xAxis.dataKey, ...stageColumns, ...ciItems]
458
- break
459
- case 'Line':
460
- standardLoopItems = [runtime.xAxis.dataKey, ...runtime?.seriesKeys]
461
- break
462
- case 'Area Chart':
463
- standardLoopItems = [runtime.xAxis.dataKey, ...runtime?.seriesKeys]
464
- break
465
- case 'Bar':
466
- standardLoopItems =
467
- orientation === 'vertical'
468
- ? [runtime.xAxis.dataKey, ...runtime?.seriesKeys]
469
- : [runtime.yAxis.dataKey, ...runtime?.seriesKeys]
470
- break
471
- case 'Pie':
472
- standardLoopItems = [runtime.xAxis.dataKey, ...runtime?.seriesKeys]
473
- default:
474
- throw new Error('No visualization type found in handleTooltipMouseOver')
475
- }
476
- }
477
-
478
- if (config.dashboard) {
479
- standardLoopItems = [
480
- runtime.xAxis.dataKey,
481
- ...runtime?.barSeriesKeys,
482
- ...runtime?.lineSeriesKeys,
483
- ...stageColumns,
484
- ...ciItems
485
- ]
486
- }
487
-
488
- return standardLoopItems
489
- } catch (error) {
490
- console.error('COVE', error)
473
+ const forcastingSeries = config.runtime.series.filter(series => series.type === 'Forecasting')
474
+ const stageColumns = forcastingSeries.map(series => series.stageColumn)
475
+ const ciItems = forcastingSeries.flatMap(series =>
476
+ series.confidenceIntervals?.filter(ci => ci.showInTooltip).map(ci => [ci.low, ci.high])
477
+ )
478
+ const common = [runtime.xAxis.dataKey, ...runtime?.seriesKeys]
479
+ switch (visualizationType) {
480
+ case 'Line':
481
+ case 'Area Chart':
482
+ case 'Pie':
483
+ return common
484
+ case 'Combo':
485
+ return [...common, ...ciItems]
486
+ case 'Forecasting':
487
+ return [runtime.xAxis.dataKey, ...stageColumns, ...ciItems]
488
+
489
+ case 'Bar':
490
+ return orientation === 'vertical' ? common : [runtime.yAxis.dataKey, ...runtime?.seriesKeys]
491
+ default:
492
+ throw new Error('No visualization type found in handleTooltipMouseOver')
491
493
  }
492
494
  }
493
495
 
@@ -586,7 +588,6 @@ export const useTooltip = props => {
586
588
  getIncludedTooltipSeries,
587
589
  getXValueFromCoordinate,
588
590
  getXValueFromCoordinateDate,
589
- getYScaleValues,
590
591
  handleTooltipClick,
591
592
  handleTooltipMouseOff,
592
593
  handleTooltipMouseOver,
package/src/index.jsx CHANGED
@@ -1,12 +1,12 @@
1
1
  import React from 'react'
2
2
  import ReactDOM from 'react-dom/client'
3
3
 
4
- import CdcChart from './CdcChart'
5
4
  import './coreStyles_chart.scss'
6
-
7
5
  import '@cdc/core/styles/cove-main.scss'
8
6
  import 'react-tooltip/dist/react-tooltip.css'
9
7
 
8
+ import CdcChart from './CdcChart'
9
+
10
10
  let isEditor = window.location.href.includes('editor=true')
11
11
  let isDebug = window.location.href.includes('debug=true')
12
12
 
@@ -97,11 +97,6 @@
97
97
  margin-bottom: 24px;
98
98
  }
99
99
 
100
- .visually-hidden {
101
- position: fixed;
102
- left: -10000px;
103
- }
104
-
105
100
  .loader {
106
101
  width: 100%;
107
102
  text-align: center;
@@ -144,6 +139,17 @@
144
139
  }
145
140
 
146
141
  .legend-container {
142
+ #supression-tooltip {
143
+ font-family: 'Nunito', sans-serif;
144
+ font-size: 0.8337rem;
145
+ font-weight: 400;
146
+ max-width: 16.7rem;
147
+ padding: 0.5rem 1rem;
148
+ border-radius: 4px;
149
+ box-shadow: 0px 2px 2px rgba(28, 29, 31, 0.45);
150
+ white-space: normal;
151
+ line-height: 1.4;
152
+ }
147
153
  background: #fff;
148
154
  width: 100%;
149
155
  vertical-align: top;
@@ -232,12 +238,13 @@
232
238
  }
233
239
 
234
240
  &__outer {
235
- &.definition-link {
241
+ &.link-container {
236
242
  display: flex;
237
243
  flex-direction: row;
238
244
  position: absolute;
239
245
  left: 0%;
240
246
  top: 108%;
247
+
241
248
  & > * {
242
249
  margin: 0;
243
250
  }
@@ -14,7 +14,7 @@ type SET_COLOR_SCALE = Action<'SET_COLOR_SCALE', Function>
14
14
  type SET_STATE_DATA = Action<'SET_STATE_DATA', object[]>
15
15
  type SET_EXCLUDED_DATA = Action<'SET_EXCLUDED_DATA', object[]>
16
16
  type SET_FILTERED_DATA = Action<'SET_FILTERED_DATA', object[]>
17
- type SET_SERIES_HIGHLIGHT = Action<'SET_SERIES_HIGHLIGHT', object>
17
+ type SET_SERIES_HIGHLIGHT = Action<'SET_SERIES_HIGHLIGHT', string[]>
18
18
  type SET_VIEWPORT = Action<'SET_VIEWPORT', string>
19
19
  type SET_DIMENSIONS = Action<'SET_DIMENSIONS', DimensionsType>
20
20
  type SET_CONTAINER = Action<'SET_CONTAINER', object>
@@ -10,6 +10,7 @@ import { General } from '@cdc/core/types/General'
10
10
  import { type Link } from './../components/Sankey/types'
11
11
  import { type DataDescription } from '@cdc/core/types/DataDescription'
12
12
  import { type Legend as CoreLegend } from '@cdc/core/types/Legend'
13
+ import { Label } from './Label'
13
14
  import { ConfidenceInterval } from '@cdc/core/types/ConfidenceInterval'
14
15
  import { Region } from '@cdc/core/types/Region'
15
16
  import { VizFilter } from '@cdc/core/types/VizFilter'
@@ -80,16 +81,20 @@ type Exclusions = {
80
81
 
81
82
  export type Legend = CoreLegend & {
82
83
  seriesHighlight: string[]
83
-
84
+ unified: boolean
84
85
  hideSuppressionLink: boolean
85
86
  style: 'circles' | 'boxes' | 'gradient' | 'lines'
86
87
  subStyle: 'linear blocks' | 'smooth'
87
88
  hasShape: boolean
89
+ order: 'dataColumn' | 'asc' | 'desc'
90
+ orderedValues: Label[]
88
91
  tickRotation: string
89
92
  hideBorder: {
90
93
  side: boolean
91
94
  topBottom: boolean
92
95
  }
96
+ groupBy: string
97
+ separators?: string
93
98
  }
94
99
 
95
100
  type Visual = {
@@ -232,6 +237,7 @@ export type ForestPlotConfig = {
232
237
  export type LineChartConfig = {
233
238
  allowLineToBarGraph: boolean
234
239
  convertLineToBarGraph: boolean
240
+ isolatedDotsSameSize: boolean
235
241
  lineDatapointStyle: 'hidden' | 'always show' | 'hover'
236
242
  visualizationType: 'Line'
237
243
  } & AllChartsConfig