@cdc/chart 4.25.3 → 4.25.6

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 (86) hide show
  1. package/dist/cdcchart.js +46641 -42561
  2. package/index.html +130 -129
  3. package/package.json +22 -27
  4. package/src/CdcChartComponent.tsx +75 -35
  5. package/src/_stories/Chart.CI.stories.tsx +10 -0
  6. package/src/_stories/Chart.DynamicSeries.stories.tsx +68 -49
  7. package/src/_stories/Chart.stories.tsx +99 -86
  8. package/src/_stories/ChartPrefixSuffix.stories.tsx +29 -32
  9. package/{examples/private/line-issue.json → src/_stories/_mock/barchart_labels.mock.json} +150 -35
  10. package/src/_stories/_mock/dynamic_series_bar_config.json +1 -1
  11. package/src/_stories/_mock/dynamic_series_suppression_mock.json +610 -0
  12. package/{examples/private/not-loading.json → src/_stories/_mock/pie_calculated_area.json} +152 -95
  13. package/src/components/Annotations/components/AnnotationDropdown.tsx +2 -2
  14. package/src/components/AreaChart/components/AreaChart.jsx +33 -5
  15. package/src/components/Axis/Categorical.Axis.tsx +2 -2
  16. package/src/components/BarChart/components/BarChart.Horizontal.tsx +38 -37
  17. package/src/components/BarChart/components/BarChart.StackedHorizontal.tsx +18 -8
  18. package/src/components/BarChart/components/BarChart.StackedVertical.tsx +8 -8
  19. package/src/components/BarChart/components/BarChart.Vertical.tsx +47 -36
  20. package/src/components/BarChart/components/{BarChart.jsx → BarChart.tsx} +23 -5
  21. package/src/components/BarChart/components/context.tsx +20 -2
  22. package/src/components/BarChart/helpers/getBarHeights.ts +47 -0
  23. package/src/components/BarChart/helpers/index.ts +5 -2
  24. package/src/components/BarChart/helpers/tests/getBarHeights.test.ts +83 -0
  25. package/src/{hooks → components/BarChart/helpers}/useBarChart.ts +9 -46
  26. package/src/components/BoxPlot/BoxPlot.tsx +2 -1
  27. package/src/components/Brush/BrushChart.tsx +73 -0
  28. package/src/components/Brush/BrushController..tsx +39 -0
  29. package/src/components/DeviationBar.jsx +2 -2
  30. package/src/components/EditorPanel/EditorPanel.tsx +232 -147
  31. package/src/components/EditorPanel/components/Panels/Panel.General.tsx +36 -36
  32. package/src/components/EditorPanel/components/Panels/Panel.Series.tsx +52 -25
  33. package/src/components/EditorPanel/components/Panels/Panel.Visual.tsx +12 -4
  34. package/src/components/EditorPanel/components/Panels/panelVisual.styles.css +8 -0
  35. package/src/components/EditorPanel/useEditorPermissions.ts +5 -5
  36. package/src/components/ForestPlot/ForestPlot.tsx +2 -2
  37. package/src/components/HoverLine/HoverLine.tsx +74 -0
  38. package/src/components/Legend/Legend.Component.tsx +1 -1
  39. package/src/components/Legend/Legend.Suppression.tsx +59 -25
  40. package/src/components/Legend/helpers/createFormatLabels.tsx +28 -0
  41. package/src/components/Legend/helpers/index.ts +1 -1
  42. package/src/components/LineChart/LineChartProps.ts +3 -1
  43. package/src/components/LineChart/components/LineChart.Circle.tsx +72 -119
  44. package/src/components/LineChart/helpers.ts +133 -56
  45. package/src/components/LineChart/index.tsx +106 -55
  46. package/src/components/LinearChart.tsx +178 -198
  47. package/src/components/PairedBarChart.jsx +3 -2
  48. package/src/components/PieChart/PieChart.tsx +127 -102
  49. package/src/components/ScatterPlot/ScatterPlot.jsx +5 -0
  50. package/src/components/Sparkline/components/SparkLine.tsx +80 -18
  51. package/src/data/initial-state.js +11 -6
  52. package/src/helpers/countNumOfTicks.ts +1 -1
  53. package/src/helpers/dataHelpers.ts +23 -2
  54. package/src/helpers/getNewRuntime.ts +35 -0
  55. package/src/helpers/getPiePercent.ts +22 -0
  56. package/src/helpers/getTransformedData.ts +22 -0
  57. package/src/helpers/sizeHelpers.ts +1 -1
  58. package/src/helpers/tests/getNewRuntime.test.ts +82 -0
  59. package/src/helpers/tests/getPiePercent.test.ts +38 -0
  60. package/src/hooks/useMinMax.ts +21 -28
  61. package/src/hooks/useRightAxis.ts +5 -3
  62. package/src/hooks/useScales.ts +15 -6
  63. package/src/hooks/useTooltip.tsx +218 -203
  64. package/src/index.jsx +2 -2
  65. package/src/scss/main.scss +13 -6
  66. package/src/store/chart.actions.ts +2 -6
  67. package/src/store/chart.reducer.ts +23 -23
  68. package/src/types/ChartConfig.ts +11 -3
  69. package/src/types/ChartContext.ts +0 -2
  70. package/examples/private/DEV-8850-2.json +0 -493
  71. package/examples/private/DEV-9822.json +0 -574
  72. package/examples/private/DEV-9840.json +0 -553
  73. package/examples/private/DEV-9850-3.json +0 -461
  74. package/examples/private/chart.json +0 -1084
  75. package/examples/private/ci_formatted.json +0 -202
  76. package/examples/private/ci_issue.json +0 -3016
  77. package/examples/private/completed.json +0 -634
  78. package/examples/private/dem-data-long.csv +0 -20
  79. package/examples/private/dem-data-long.json +0 -36
  80. package/examples/private/demographic_data.csv +0 -157
  81. package/examples/private/demographic_data.json +0 -2654
  82. package/examples/private/demographic_dynamic.json +0 -443
  83. package/examples/private/demographic_standard.json +0 -560
  84. package/examples/private/ehdi.json +0 -29939
  85. package/examples/private/test.json +0 -493
  86. package/src/components/ZoomBrush.tsx +0 -251
@@ -1,5 +1,6 @@
1
1
  import { useContext } from 'react'
2
2
  // Local imports
3
+ import parse from 'html-react-parser'
3
4
  import ConfigContext from '../ConfigContext'
4
5
  import { type ChartContext } from '../types/ChartContext'
5
6
  import { formatNumber as formatColNumber } from '@cdc/core/helpers/cove/number'
@@ -8,6 +9,7 @@ import { isDateScale } from '@cdc/core/helpers/cove/date'
8
9
  import { localPoint } from '@visx/event'
9
10
  import { bisector } from 'd3-array'
10
11
  import _ from 'lodash'
12
+ import { getHorizontalBarHeights } from '../components/BarChart/helpers/getBarHeights'
11
13
 
12
14
  export const useTooltip = props => {
13
15
  const {
@@ -21,31 +23,35 @@ export const useTooltip = props => {
21
23
  setSharedFilter,
22
24
  isDraggingAnnotation
23
25
  } = useContext<ChartContext>(ConfigContext)
24
- const { xScale, yScale, showTooltip, hideTooltip } = props
26
+ const { xScale, yScale, seriesScale, showTooltip, hideTooltip } = props
25
27
  const { xAxis, visualizationType, orientation, yAxis, runtime } = config
26
28
 
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
- */
29
+ const Y_AXIS_SIZE = Number(config.yAxis.size || 0)
34
30
 
35
- // function handles only Single series hovred data tooltips
31
+ // function handles only Single series hovered data tooltips
36
32
  const findDataKeyByThreshold = (mouseY, datapoint) => {
37
- let sum = 0
38
- let threshold = Number(yScale.invert(mouseY))
39
33
  let hoveredKey = null
40
34
  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
35
+ const dynamicSeries = config.series.find(s => s.dynamicCategory)
36
+ if (dynamicSeries) {
37
+ hoveredKey = datapoint[dynamicSeries.dynamicCategory]
38
+ hoveredValue = datapoint[dynamicSeries.dataKey]
39
+ } else {
40
+ let sum = 0
41
+ let threshold
42
+ try {
43
+ threshold = Number(yScale.invert(mouseY))
44
+ } catch (e) {
45
+ return []
46
+ }
47
+ for (let key of config.runtime?.seriesKeys) {
48
+ if (datapoint.hasOwnProperty(key)) {
49
+ sum += Number(datapoint[key])
50
+ if (sum >= threshold) {
51
+ hoveredValue = datapoint[key]
52
+ hoveredKey = key
53
+ break
54
+ }
49
55
  }
50
56
  }
51
57
  }
@@ -62,25 +68,6 @@ export const useTooltip = props => {
62
68
  return showMissingDataValue ? 'N/A' : formattedValue
63
69
  }
64
70
 
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
71
  /**
85
72
  * Handles the mouse over event for the tooltip.
86
73
  * @function handleTooltipMouseOver
@@ -89,81 +76,84 @@ export const useTooltip = props => {
89
76
  */
90
77
  const handleTooltipMouseOver = (e, additionalChartData) => {
91
78
  if (visualizationType === 'Bump Chart') return
92
- e.stopPropagation()
79
+ //e.stopPropagation()
93
80
  if (isDraggingAnnotation) return
94
81
 
95
82
  const eventSvgCoords = localPoint(e)
96
83
  const { x, y } = eventSvgCoords
97
84
 
98
- // Additional data for pie charts
99
- const { data: pieChartData, arc } = additionalChartData ?? {}
100
-
101
- const closestXScaleValue = getXValueFromCoordinate(x - Number(config.yAxis.size || 0))
102
-
103
- const xScaleValues = data.filter(d => d[xAxis.dataKey] === getClosestYValue(y))
104
-
105
- const resolvedScaleValues = orientation === 'vertical' ? getYScaleValues(closestXScaleValue) : xScaleValues
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
111
- }
85
+ const resolvedScaleValues = getResolvedScaleValues([x, y])
86
+ const singleSeriesValue = getYValueFromCoordinate(y, resolvedScaleValues)
87
+ const columnsWithTooltips = []
88
+ const tooltipItems = [] as any[][]
89
+ for (const [colKey, column] of Object.entries(config.columns)) {
90
+ const formattingParams = {
91
+ addColPrefix: column.prefix,
92
+ addColSuffix: column.suffix,
93
+ addColRoundTo: column.roundToPlace || '',
94
+ addColCommas: column.commas
95
+ }
112
96
 
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
97
+ const pieColumnData = additionalChartData?.arc?.data[column.name]
98
+ const columnData =
99
+ config.tooltips.singleSeries && visualizationType === 'Line'
100
+ ? resolvedScaleValues.filter(
101
+ value => value[config.runtime.series[0].dynamicCategory] === singleSeriesValue
102
+ )[0][colKey]
103
+ : resolvedScaleValues[0]?.[colKey]
104
+ const closestValue = config.visualizationType === 'Pie' ? pieColumnData : columnData
126
105
 
127
- if (config.visualizationType === 'Pie') {
128
- closestValue = arc?.data[colVals.name]
129
- } else {
130
- closestValue = resolvedScaleValues[0]?.[colVals.name]
131
- }
106
+ const formattedValue = formatColNumber(closestValue, 'left', true, config, formattingParams)
132
107
 
133
- const formattedValue = formatColNumber(closestValue, 'left', true, config, formattingParams)
134
-
135
- if (colVals.tooltips) {
136
- columnsWithTooltips.push([colVals.label, formattedValue])
137
- }
108
+ if (column.tooltips) {
109
+ columnsWithTooltips.push([column.label, formattedValue])
138
110
  }
139
- const additionalTooltipItems = []
111
+ }
112
+ const additionalTooltipItems = [] as [string, string | number][]
140
113
 
141
- columnsWithTooltips.forEach(columnData => {
142
- additionalTooltipItems.push([columnData[0], columnData[1]])
143
- })
114
+ columnsWithTooltips.forEach(columnData => {
115
+ additionalTooltipItems.push([columnData[0], columnData[1]])
116
+ })
144
117
 
145
- if (visualizationType === 'Pie') {
146
- const roundTo = Number(config.dataFormat.roundTo) || 0
118
+ if (visualizationType === 'Pie') {
119
+ const roundTo = Number(config.dataFormat.roundTo) || 0
147
120
 
148
- const degrees = ((arc.endAngle - arc.startAngle) * 180) / Math.PI
121
+ const pieData = additionalChartData?.data ?? {}
122
+ const startAngle = additionalChartData?.startAngle ?? 0
123
+ const endAngle = additionalChartData?.endAngle ?? 0
124
+ const actualPieValue = Number(additionalChartData.data[config?.yAxis?.dataKey])
149
125
 
150
- // Calculate the percentage of the full circle (360 degrees)
151
- const percentageOfCircle = (degrees / 360) * 100
152
- const roundedPercentage = percentageOfCircle.toFixed(roundTo)
126
+ const degrees = ((endAngle - startAngle) * 180) / Math.PI
127
+ const pctOf360 = (degrees / 360) * 100
128
+ const pctString = value => value.toFixed(roundTo) + '%'
129
+ const showPiePercent = config.dataFormat.showPiePercent || false
153
130
 
131
+ if (showPiePercent && pieData[config.xAxis.dataKey] === 'Calculated Area') {
132
+ tooltipItems.push(['', 'Calculated Area'])
133
+ } else {
154
134
  tooltipItems.push(
155
- // ignore
156
- [config.xAxis.dataKey, pieChartData],
157
- [config.runtime.yAxis.dataKey, formatNumber(arc?.data[config.runtime.yAxis.dataKey])],
158
- ['Percent', `${roundedPercentage + '%'}`]
135
+ [config.xAxis.dataKey, pieData[config.xAxis.dataKey]],
136
+ [
137
+ config.runtime.yAxis.dataKey,
138
+ showPiePercent ? pctString(actualPieValue) : formatNumber(pieData[config.runtime.yAxis.dataKey])
139
+ ],
140
+ showPiePercent ? [] : ['Percent', pctString(pctOf360)]
159
141
  )
160
142
  }
143
+ }
161
144
 
162
- if (visualizationType === 'Forest Plot') {
163
- tooltipItems.push([config.xAxis.dataKey, getClosestYValue(y)])
145
+ if (visualizationType === 'Forest Plot') {
146
+ tooltipItems.push([config.xAxis.dataKey, getClosestYValue(y)])
147
+ }
148
+
149
+ const isLinearChart = !['Pie', 'Forest Plot'].includes(visualizationType)
150
+ if (isLinearChart) {
151
+ const getAxisPosition = seriesKey => {
152
+ const seriesObj = config.runtime.series.filter(s => s.dataKey === seriesKey)[0]
153
+ const position = seriesObj?.axis ? String(seriesObj.axis).toLowerCase() : 'left'
154
+ return position
164
155
  }
165
- // handle tooltip for all hovered series
166
- if (visualizationType !== 'Pie' && visualizationType !== 'Forest Plot' && !config.tooltips.singleSeries) {
156
+ if (!config.tooltips.singleSeries || visualizationType === 'Line') {
167
157
  tooltipItems.push(
168
158
  ...getIncludedTooltipSeries()
169
159
  ?.filter(seriesKey => {
@@ -191,37 +181,55 @@ export const useTooltip = props => {
191
181
  })
192
182
  )
193
183
 
194
- config.runtime.series?.forEach(series => {
184
+ const runtimeSeries =
185
+ config.tooltips.singleSeries && visualizationType === 'Line'
186
+ ? [_.find(config.runtime.series, d => d.dataKey === singleSeriesValue)]
187
+ : config.runtime.series
188
+
189
+ runtimeSeries?.forEach(series => {
195
190
  if (series?.dynamicCategory) {
196
191
  const seriesKey = series.dataKey
197
192
  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)])
193
+ if (resolvedScaleValue) {
194
+ const value = resolvedScaleValue[series.originalDataKey]
195
+ const formattedValue = getFormattedValue(seriesKey, value, config, getAxisPosition)
196
+ tooltipItems.push([seriesKey, formattedValue, getAxisPosition(seriesKey)])
197
+ }
202
198
  }
203
199
  })
204
- }
200
+ } else {
201
+ const dynamicSeries = config.series.find(s => s.dynamicCategory)
205
202
 
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])
203
+ // Show Only the Hovered Series in Tooltip
204
+ const dataColumn = resolvedScaleValues[0]
205
+ const [seriesKey, value] = findDataKeyByThreshold(y, dataColumn)
209
206
  if (seriesKey && value) {
210
- tooltipItems.push([config.xAxis.dataKey, closestXScaleValue])
207
+ const xVal = dataColumn[config.xAxis.dataKey]
208
+ const closestXScaleValue = getXValueFromCoordinate(x - Y_AXIS_SIZE)
209
+
210
+ tooltipItems.push([config.xAxis.dataKey, closestXScaleValue || xVal])
211
211
  const formattedValue = getFormattedValue(seriesKey, value, config, getAxisPosition)
212
212
  tooltipItems.push([seriesKey, formattedValue])
213
+ } else if (dynamicSeries) {
214
+ Object.keys(dataColumn).forEach(key => {
215
+ tooltipItems.push([key, dataColumn[key]])
216
+ })
213
217
  }
214
218
  }
215
-
216
- return [...tooltipItems, ...additionalTooltipItems]
217
219
  }
218
220
 
219
- // Returns an array of arrays.
220
- // ie. [ ['Date', '01/01/2023'], ['close', 300] ]
221
- const tooltipDataArray = getTooltipDataArray()
221
+ const dataXPosition = eventSvgCoords.x + 10
222
+ const dataYPosition = eventSvgCoords.y
222
223
 
223
- if (!tooltipDataArray) return
224
- const tooltipInformation = getTooltipInformation(tooltipDataArray, eventSvgCoords)
224
+ const tooltipInformation = {
225
+ tooltipLeft: dataXPosition,
226
+ tooltipTop: dataYPosition,
227
+ tooltipData: {
228
+ data: [...tooltipItems, ...additionalTooltipItems],
229
+ dataXPosition,
230
+ dataYPosition
231
+ }
232
+ }
225
233
  showTooltip(tooltipInformation)
226
234
  }
227
235
 
@@ -291,10 +299,7 @@ export const useTooltip = props => {
291
299
  return closestX
292
300
  }
293
301
 
294
- if (
295
- config.xAxis.type === 'categorical' ||
296
- (visualizationType === 'Combo' && orientation !== 'horizontal' && visualizationType !== 'Forest Plot')
297
- ) {
302
+ if (config.xAxis.type === 'categorical' || visualizationType === 'Combo') {
298
303
  let range = xScale.range()[1] - xScale.range()[0]
299
304
  let eachBand = range / (xScale.domain().length + 1)
300
305
 
@@ -302,17 +307,62 @@ export const useTooltip = props => {
302
307
  const index = Math.floor((Number(numerator) - eachBand / 2) / eachBand)
303
308
  return xScale.domain()[index] // fixes off by 1 error
304
309
  }
310
+ }
305
311
 
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
312
+ const findClosest = (dataArray: [any, number][], mouseXorY) => {
313
+ let dataColumn: Object
314
+ dataArray.find(([d, xOrY]) => {
315
+ if (xOrY > mouseXorY) {
316
+ return true
317
+ }
318
+ dataColumn = d
319
+ })
320
+ return dataColumn
321
+ }
322
+
323
+ const getYValueFromCoordinate = (y, xData) => {
324
+ let closestYSeriesValue = null
325
+ let minDistance = Number.MAX_VALUE
326
+ let offset = y
327
+
328
+ xData.forEach(d => {
329
+ const yPosition = yScale(d[config.runtime.series[0].originalDataKey])
330
+ const distance = Math.abs(Number(yPosition - offset))
331
+
332
+ if (distance <= minDistance) {
333
+ minDistance = distance
334
+ closestYSeriesValue = d[config.runtime.series[0].dynamicCategory]
335
+ }
336
+ })
337
+ return closestYSeriesValue
338
+ }
339
+
340
+ const getClosestYValueHorizontalChart = mouseY => {
341
+ const barGroups = yScale.domain().map((group, index) => ({ group, index }))
342
+ const barsWithHeights = getHorizontalBarHeights<{ group }>(config, barGroups)
343
+
344
+ const barGroup = findClosest(
345
+ barsWithHeights.map(d => [d, _.round(d.y)]),
346
+ mouseY
347
+ )
348
+
349
+ const subGroupMouseY = mouseY - barGroup.y
350
+ const columns = data.filter(d => d[config.xAxis.dataKey] === barGroup.group)
351
+
352
+ if (config.series.length > 1 && !config.series.find(s => s.dynamicCategory)) {
353
+ const seriesWithY = config.series.map((c, i) => [c, config.barHeight * i]) as [Object, number][]
354
+ const hoveredSeries = findClosest(seriesWithY, subGroupMouseY)
355
+ const exludeColumns = config.series.filter(s => s.dataKey !== hoveredSeries.dataKey).map(s => s.dataKey)
356
+ const dataColumn = _.omit(columns[0], exludeColumns)
357
+ return dataColumn
358
+ } else {
359
+ const columnsWithY = columns.map((c, i) => [c, config.barHeight * i]) as [Object, number][]
360
+ const dataColumn = findClosest(columnsWithY, subGroupMouseY)
361
+ return dataColumn
312
362
  }
313
363
  }
314
364
 
315
- const getClosestYValue = (yPosition, key) => {
365
+ const getClosestYValue = (yPosition, key = '') => {
316
366
  if (visualizationType === 'Pie') return
317
367
  let minDistance = Number.MAX_VALUE
318
368
  let closestYValue = null
@@ -369,11 +419,15 @@ export const useTooltip = props => {
369
419
  }
370
420
 
371
421
  /**
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
422
+ * Provides an array of objects with the closest series data items
375
423
  */
376
- const getYScaleValues = closestXScaleValue => {
424
+ const getResolvedScaleValues = ([x, y]) => {
425
+ if (orientation !== 'vertical') {
426
+ if (config.visualizationType === 'Bar' && config.tooltips.singleSeries) {
427
+ return [getClosestYValueHorizontalChart(y)]
428
+ }
429
+ return data.filter(d => d[xAxis.dataKey] === getClosestYValue(y))
430
+ }
377
431
  const runtimeSeries = config.runtime.series.filter(
378
432
  series => visualizationType === 'Pie' || (series.tooltip === true && !series.dynamicCategory)
379
433
  )
@@ -403,21 +457,22 @@ export const useTooltip = props => {
403
457
  const colNames = Object.values(config.columns).map(column => column.name)
404
458
  // @ Murad why are we adding them twice?
405
459
  includedSeries.push(...colNames, ...colNames)
460
+ const closestXScaleValue = getXValueFromCoordinate(x - Y_AXIS_SIZE)
406
461
 
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
- }
462
+ let dataToSearch = (data || []).filter(d => d[xAxis.dataKey] === closestXScaleValue)
413
463
 
414
- const yScaleValues = dataToSearch.map(object => {
415
- return _.pick(object, includedSeries)
416
- })
417
- return yScaleValues
418
- } catch (error) {
419
- console.error('COVE', error)
464
+ if (config.tooltips.singleSeries && config.visualizationType !== 'Line') {
465
+ const dynamicSeries = config.series.find(s => s.dynamicCategory)
466
+ if (dynamicSeries) {
467
+ const dataWithXScale = dataToSearch.map(
468
+ d => [d, seriesScale(d[dynamicSeries.dynamicCategory])] as [Object, number]
469
+ )
470
+ const xOffset = x - Y_AXIS_SIZE - xScale(closestXScaleValue)
471
+ dataToSearch = [findClosest(dataWithXScale, xOffset)]
472
+ }
420
473
  }
474
+
475
+ return dataToSearch.map(d => _.pick(d, includedSeries))
421
476
  }
422
477
 
423
478
  /**
@@ -427,67 +482,26 @@ export const useTooltip = props => {
427
482
  * @returns {Array} Array of items to be included in the tooltip.
428
483
  */
429
484
  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)
485
+ const forcastingSeries = config.runtime.series.filter(series => series.type === 'Forecasting')
486
+ const stageColumns = forcastingSeries.map(series => series.stageColumn)
487
+ const ciItems = forcastingSeries.flatMap(series =>
488
+ series.confidenceIntervals?.filter(ci => ci.showInTooltip).map(ci => [ci.low, ci.high])
489
+ )
490
+ const common = [runtime.xAxis.dataKey, ...runtime?.seriesKeys]
491
+ switch (visualizationType) {
492
+ case 'Line':
493
+ case 'Area Chart':
494
+ case 'Pie':
495
+ return common
496
+ case 'Combo':
497
+ return [...common, ...ciItems]
498
+ case 'Forecasting':
499
+ return [runtime.xAxis.dataKey, ...stageColumns, ...ciItems]
500
+
501
+ case 'Bar':
502
+ return orientation === 'vertical' ? common : [runtime.yAxis.dataKey, ...runtime?.seriesKeys]
503
+ default:
504
+ throw new Error('No visualization type found in handleTooltipMouseOver')
491
505
  }
492
506
  }
493
507
 
@@ -544,7 +558,9 @@ export const useTooltip = props => {
544
558
  config.runtime.yAxis.label ? `${config.runtime.yAxis.label}: ` : ''
545
559
  )} ${config.xAxis.type === 'date' ? formattedDate : value}`}</li>
546
560
  )
547
-
561
+ if (visualizationType === 'Pie' && config.dataFormat.showPiePercent && value === 'Calculated Area') {
562
+ return <li className='tooltip-heading'>{`${capitalize('Calculated Area')} `}</li>
563
+ }
548
564
  if (key === config.xAxis.dataKey)
549
565
  return (
550
566
  <li className='tooltip-heading'>{`${capitalize(
@@ -569,14 +585,14 @@ export const useTooltip = props => {
569
585
  let newValue = label || value
570
586
  const style = displayGray ? { color: '#8b8b8a' } : {}
571
587
 
572
- if (index == 1 && config.dataFormat.onlyShowTopPrefixSuffix) {
588
+ if (index == 1 && config.yAxis?.inlineLabel) {
573
589
  newValue = `${config.dataFormat.prefix}${newValue}${config.dataFormat.suffix}`
574
590
  }
575
591
  const activeLabel = getSeriesNameFromLabel(key)
576
592
  const displayText = activeLabel ? `${activeLabel}: ${newValue}` : newValue
577
593
 
578
594
  return (
579
- <li style={style} className='tooltip-body'>
595
+ <li style={style} className='tooltip-body mb-1'>
580
596
  {displayText}
581
597
  </li>
582
598
  )
@@ -586,7 +602,6 @@ export const useTooltip = props => {
586
602
  getIncludedTooltipSeries,
587
603
  getXValueFromCoordinate,
588
604
  getXValueFromCoordinateDate,
589
- getYScaleValues,
590
605
  handleTooltipClick,
591
606
  handleTooltipMouseOff,
592
607
  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
  }
@@ -1,10 +1,6 @@
1
1
  import { DimensionsType } from '@cdc/core/types/Dimensions'
2
2
  import { ChartConfig } from '../types/ChartConfig'
3
-
4
- type Action<T, P = undefined, R = undefined> = {
5
- type: T
6
- payload?: P
7
- }
3
+ import { Action } from '@cdc/core/types/Action'
8
4
 
9
5
  // Action Types
10
6
  type SET_CONFIG = Action<'SET_CONFIG', ChartConfig>
@@ -34,7 +30,7 @@ type ChartActions =
34
30
  | SET_CONTAINER
35
31
  | SET_LOADED_EVENT
36
32
  | SET_DRAG_ANNOTATIONS
37
- | SET_BRUSH_CONFIG
38
33
  | SET_LOADING
34
+ | SET_BRUSH_CONFIG
39
35
 
40
36
  export default ChartActions