@cdc/chart 4.24.9 → 4.24.11

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 (95) hide show
  1. package/LICENSE +201 -0
  2. package/dist/cdcchart.js +45911 -41739
  3. package/examples/feature/boxplot/boxplot-data.json +88 -22
  4. package/examples/feature/boxplot/boxplot.json +540 -16
  5. package/examples/feature/boxplot/testing.csv +7 -7
  6. package/examples/feature/sankey/sankey-example-data.json +0 -1
  7. package/examples/private/test.json +20092 -0
  8. package/index.html +4 -4
  9. package/package.json +2 -2
  10. package/src/CdcChart.tsx +209 -188
  11. package/src/_stories/Chart.CustomColors.stories.tsx +19 -0
  12. package/src/_stories/Chart.DynamicSeries.stories.tsx +27 -0
  13. package/src/_stories/Chart.Legend.Gradient.stories.tsx +74 -0
  14. package/src/_stories/Chart.stories.tsx +30 -3
  15. package/src/_stories/ChartAxisLabels.stories.tsx +20 -0
  16. package/src/_stories/ChartAxisTitles.stories.tsx +53 -0
  17. package/src/_stories/ChartEditor.stories.tsx +27 -0
  18. package/src/_stories/ChartLine.Suppression.stories.tsx +25 -0
  19. package/src/_stories/ChartPrefixSuffix.stories.tsx +159 -0
  20. package/src/_stories/_mock/boxplot_multiseries.json +647 -0
  21. package/src/_stories/_mock/dynamic_series_bar_config.json +723 -0
  22. package/src/_stories/_mock/dynamic_series_config.json +979 -0
  23. package/src/_stories/_mock/horizontal_bar.json +257 -0
  24. package/src/_stories/_mock/large_x_axis_labels.json +261 -0
  25. package/src/_stories/_mock/paired-bar.json +262 -0
  26. package/src/_stories/_mock/pie_with_data.json +255 -0
  27. package/{examples/feature/scatterplot/scatterplot.json → src/_stories/_mock/scatterplot_mock.json} +62 -92
  28. package/src/_stories/_mock/simplified_line.json +1510 -0
  29. package/src/_stories/_mock/suppression_mock.json +1549 -0
  30. package/src/components/Annotations/components/AnnotationDraggable.tsx +0 -3
  31. package/src/components/Annotations/components/AnnotationDropdown.tsx +1 -1
  32. package/src/components/Axis/Categorical.Axis.tsx +22 -4
  33. package/src/components/BarChart/components/BarChart.Horizontal.tsx +95 -16
  34. package/src/components/BarChart/components/BarChart.StackedHorizontal.tsx +41 -17
  35. package/src/components/BarChart/components/BarChart.StackedVertical.tsx +43 -9
  36. package/src/components/BarChart/components/BarChart.Vertical.tsx +123 -47
  37. package/src/components/BarChart/helpers/index.ts +23 -5
  38. package/src/components/BoxPlot/BoxPlot.tsx +189 -0
  39. package/src/components/BrushChart.tsx +3 -2
  40. package/src/components/DeviationBar.jsx +58 -8
  41. package/src/components/EditorPanel/EditorPanel.tsx +127 -102
  42. package/src/components/EditorPanel/components/Panels/Panel.Annotate.tsx +11 -28
  43. package/src/components/EditorPanel/components/Panels/Panel.BoxPlot.tsx +51 -6
  44. package/src/components/EditorPanel/components/Panels/Panel.General.tsx +21 -4
  45. package/src/components/EditorPanel/components/Panels/Panel.Regions.tsx +40 -9
  46. package/src/components/EditorPanel/components/Panels/Panel.Sankey.tsx +3 -3
  47. package/src/components/EditorPanel/components/Panels/Panel.Series.tsx +121 -56
  48. package/src/components/EditorPanel/components/Panels/Panel.Visual.tsx +296 -35
  49. package/src/components/EditorPanel/components/panels.scss +4 -6
  50. package/src/components/EditorPanel/editor-panel.scss +0 -8
  51. package/src/components/EditorPanel/helpers/tests/updateFieldRankByValue.test.ts +38 -0
  52. package/src/components/EditorPanel/helpers/updateFieldRankByValue.ts +42 -0
  53. package/src/components/EditorPanel/useEditorPermissions.ts +16 -1
  54. package/src/components/ForestPlot/ForestPlot.tsx +2 -3
  55. package/src/components/ForestPlot/ForestPlotProps.ts +2 -0
  56. package/src/components/Legend/Legend.Component.tsx +23 -24
  57. package/src/components/Legend/Legend.Suppression.tsx +25 -20
  58. package/src/components/Legend/Legend.tsx +16 -18
  59. package/src/components/Legend/helpers/index.ts +16 -19
  60. package/src/components/LegendWrapper.tsx +3 -1
  61. package/src/components/LineChart/components/LineChart.Circle.tsx +10 -0
  62. package/src/components/LineChart/helpers.ts +48 -43
  63. package/src/components/LineChart/index.tsx +88 -82
  64. package/src/components/LinearChart.tsx +747 -562
  65. package/src/components/PairedBarChart.jsx +50 -10
  66. package/src/components/PieChart/PieChart.tsx +1 -6
  67. package/src/components/Regions/components/Regions.tsx +33 -19
  68. package/src/components/Sankey/index.tsx +50 -32
  69. package/src/components/Sankey/sankey.scss +6 -5
  70. package/src/components/Sankey/useSankeyAlert.tsx +60 -0
  71. package/src/components/ScatterPlot/ScatterPlot.jsx +20 -4
  72. package/src/components/ZoomBrush.tsx +25 -6
  73. package/src/coreStyles_chart.scss +3 -0
  74. package/src/data/initial-state.js +8 -10
  75. package/src/helpers/configHelpers.ts +28 -0
  76. package/src/helpers/handleRankByValue.ts +15 -0
  77. package/src/helpers/sizeHelpers.ts +25 -0
  78. package/src/helpers/tests/handleRankByValue.test.ts +37 -0
  79. package/src/helpers/tests/sizeHelpers.test.ts +80 -0
  80. package/src/hooks/useColorPalette.js +10 -2
  81. package/src/hooks/useLegendClasses.ts +13 -22
  82. package/src/hooks/useMinMax.ts +27 -13
  83. package/src/hooks/useReduceData.ts +43 -10
  84. package/src/hooks/useScales.ts +87 -38
  85. package/src/hooks/useTooltip.tsx +62 -53
  86. package/src/index.jsx +1 -0
  87. package/src/scss/DataTable.scss +5 -4
  88. package/src/scss/main.scss +57 -70
  89. package/src/types/ChartConfig.ts +43 -34
  90. package/src/types/ChartContext.ts +22 -15
  91. package/src/types/ForestPlot.ts +8 -0
  92. package/src/_stories/Chart.Legend.Gradient.tsx +0 -19
  93. package/src/_stories/ChartBrush.stories.tsx +0 -19
  94. package/src/components/BoxPlot/BoxPlot.jsx +0 -111
  95. package/src/components/LinearChart.jsx +0 -817
@@ -1,9 +1,9 @@
1
- import { LogScaleConfig, scaleBand, scaleLinear, scaleLog, scalePoint, scaleTime } from '@visx/scale'
1
+ import { LinearScaleConfig, LogScaleConfig, scaleBand, scaleLinear, scaleLog, scalePoint, scaleTime } from '@visx/scale'
2
2
  import { useContext } from 'react'
3
3
  import ConfigContext from '../ConfigContext'
4
4
  import { ChartConfig } from '../types/ChartConfig'
5
5
  import { ChartContext } from '../types/ChartContext'
6
- import * as d3 from 'd3'
6
+
7
7
  const scaleTypes = {
8
8
  TIME: 'time',
9
9
  LOG: 'log',
@@ -31,8 +31,7 @@ const useScales = (properties: useScaleProps) => {
31
31
  const seriesDomain = config.runtime.barSeriesKeys || config.runtime.seriesKeys
32
32
  const xAxisType = config.runtime.xAxis.type
33
33
  const isHorizontal = config.orientation === 'horizontal'
34
-
35
- const { visualizationType } = config
34
+ const { visualizationType, xAxis, forestPlot } = config
36
35
 
37
36
  // define scales
38
37
  let xScale = null
@@ -64,24 +63,29 @@ const useScales = (properties: useScaleProps) => {
64
63
 
65
64
  // handle Linear scaled viz
66
65
  if (config.xAxis.type === 'date' && !isHorizontal) {
67
- const xAxisDataMappedSorted = xAxisDataMapped ? xAxisDataMapped.sort() : []
66
+ const xAxisDataMappedSorted = sortXAxisData(xAxisDataMapped, config.xAxis.sortByRecentDate)
68
67
  xScale = composeScaleBand(xAxisDataMappedSorted, [0, xMax], 1 - config.barThickness)
69
68
  }
70
69
 
71
- if (config.xAxis.type === 'date-time') {
70
+ if (xAxis.type === 'date-time' || xAxis.type === 'continuous') {
72
71
  let xAxisMin = Math.min(...xAxisDataMapped.map(Number))
73
72
  let xAxisMax = Math.max(...xAxisDataMapped.map(Number))
74
73
  xAxisMin -= (config.xAxis.padding ? config.xAxis.padding * 0.01 : 0) * (xAxisMax - xAxisMin)
75
- xAxisMax += (config.xAxis.padding ? config.xAxis.padding * 0.01 : 0) * (xAxisMax - xAxisMin)
74
+ xAxisMax +=
75
+ visualizationType === 'Line'
76
+ ? 0
77
+ : (config.xAxis.padding ? config.xAxis.padding * 0.01 : 0) * (xAxisMax - xAxisMin)
78
+ const range = config.xAxis.sortByRecentDate ? [xMax, 0] : [0, xMax]
76
79
  xScale = scaleTime({
77
80
  domain: [xAxisMin, xAxisMax],
78
- range: [0, xMax]
81
+ range: range
79
82
  })
80
83
 
81
84
  xScale.type = scaleTypes.TIME
82
85
 
83
86
  let minDistance = Number.MAX_VALUE
84
- let xAxisDataMappedSorted = xAxisDataMapped ? xAxisDataMapped.sort() : []
87
+ let xAxisDataMappedSorted = sortXAxisData(xAxisDataMapped, config.xAxis.sortByRecentDate)
88
+
85
89
  for (let i = 0; i < xAxisDataMappedSorted.length - 1; i++) {
86
90
  let distance = xScale(xAxisDataMappedSorted[i + 1]) - xScale(xAxisDataMappedSorted[i])
87
91
 
@@ -103,7 +107,7 @@ const useScales = (properties: useScaleProps) => {
103
107
  range: [0, yMax]
104
108
  })
105
109
  xScale = scaleLinear({
106
- domain: [min * leftOffset, Math.max(Number(config.xAxis.target), max)],
110
+ domain: [min * leftOffset, Math.max(Number(xAxis.target), max)],
107
111
  range: [0, xMax],
108
112
  round: true,
109
113
  nice: true
@@ -113,9 +117,11 @@ const useScales = (properties: useScaleProps) => {
113
117
 
114
118
  // handle Scatter plot
115
119
  if (config.visualizationType === 'Scatter Plot') {
116
- if (config.xAxis.type === 'continuous') {
120
+ if (xAxis.type === 'continuous') {
121
+ let min = xAxis.min ? xAxis.min : Math.min.apply(null, xScale.domain())
122
+ let max = xAxis.max ? xAxis.max : Math.max.apply(null, xScale.domain())
117
123
  xScale = scaleLinear({
118
- domain: [0, Math.max.apply(null, xScale.domain())],
124
+ domain: [min, max],
119
125
  range: [0, xMax]
120
126
  })
121
127
  xScale.type = scaleTypes.LINEAR
@@ -147,19 +153,19 @@ const useScales = (properties: useScaleProps) => {
147
153
  if (highestFence > max) max = highestFence
148
154
 
149
155
  // Set Scales
156
+
150
157
  yScale = scaleLinear({
151
158
  range: [yMax, 0],
152
159
  round: true,
153
160
  domain: [min, max]
154
161
  })
155
-
156
162
  xScale = scaleBand({
157
163
  range: [0, xMax],
158
- round: true,
159
- domain: config.boxplot.categories,
160
- padding: 0.4
164
+ domain: config.boxplot.categories
161
165
  })
162
166
  xScale.type = scaleTypes.BAND
167
+
168
+ seriesScale = composeScalePoint(seriesDomain, [0, yMax])
163
169
  }
164
170
 
165
171
  // handle Paired bar
@@ -190,10 +196,10 @@ const useScales = (properties: useScaleProps) => {
190
196
 
191
197
  if (visualizationType === 'Forest Plot') {
192
198
  const resolvedYRange = () => {
193
- if (config.forestPlot.regression.showDiamond || config.forestPlot.regression.description) {
194
- return [0 + config.forestPlot.rowHeight * 2, yMax - config.forestPlot.rowHeight]
199
+ if (forestPlot.regression.showDiamond || forestPlot.regression.description) {
200
+ return [0 + forestPlot.rowHeight * 2, yMax - forestPlot.rowHeight]
195
201
  } else {
196
- return [0 + config.forestPlot.rowHeight * 2, yMax]
202
+ return [0 + forestPlot.rowHeight * 2, yMax]
197
203
  }
198
204
  }
199
205
 
@@ -204,26 +210,26 @@ const useScales = (properties: useScaleProps) => {
204
210
 
205
211
  const xAxisPadding = 5
206
212
 
207
- const leftWidthOffset = (Number(config.forestPlot.leftWidthOffset) / 100) * xMax
208
- const rightWidthOffset = (Number(config.forestPlot.rightWidthOffset) / 100) * xMax
213
+ const leftWidthOffset = (Number(forestPlot.leftWidthOffset) / 100) * xMax
214
+ const rightWidthOffset = (Number(forestPlot.rightWidthOffset) / 100) * xMax
209
215
 
210
- const rightWidthOffsetMobile = (Number(config.forestPlot.rightWidthOffsetMobile) / 100) * xMax
211
- const leftWidthOffsetMobile = (Number(config.forestPlot.leftWidthOffsetMobile) / 100) * xMax
216
+ const rightWidthOffsetMobile = (Number(forestPlot.rightWidthOffsetMobile) / 100) * xMax
217
+ const leftWidthOffsetMobile = (Number(forestPlot.leftWidthOffsetMobile) / 100) * xMax
212
218
 
213
219
  if (screenWidth > 480) {
214
- if (config.forestPlot.type === 'Linear') {
220
+ if (forestPlot.type === 'Linear') {
215
221
  xScale = scaleLinear({
216
222
  domain: [
217
- Math.min(...data.map(d => parseFloat(d[config.forestPlot.lower]))) - xAxisPadding,
218
- Math.max(...data.map(d => parseFloat(d[config.forestPlot.upper]))) + xAxisPadding
223
+ Math.min(...data.map(d => parseFloat(d[forestPlot.lower]))) - xAxisPadding,
224
+ Math.max(...data.map(d => parseFloat(d[forestPlot.upper]))) + xAxisPadding
219
225
  ],
220
226
  range: [leftWidthOffset, Number(screenWidth) - rightWidthOffset]
221
227
  })
222
228
  xScale.type = scaleTypes.LINEAR
223
229
  }
224
- if (config.forestPlot.type === 'Logarithmic') {
225
- let max = Math.max(...data.map(d => parseFloat(d[config.forestPlot.upper])))
226
- let fp_min = Math.min(...data.map(d => parseFloat(d[config.forestPlot.lower])))
230
+ if (forestPlot.type === 'Logarithmic') {
231
+ let max = Math.max(...data.map(d => parseFloat(d[forestPlot.upper])))
232
+ let fp_min = Math.min(...data.map(d => parseFloat(d[forestPlot.lower])))
227
233
 
228
234
  xScale = scaleLog<LogScaleConfig>({
229
235
  domain: [fp_min, max],
@@ -233,20 +239,20 @@ const useScales = (properties: useScaleProps) => {
233
239
  xScale.type = scaleTypes.LOG
234
240
  }
235
241
  } else {
236
- if (config.forestPlot.type === 'Linear') {
237
- xScale = scaleLinear({
242
+ if (forestPlot.type === 'Linear') {
243
+ xScale = scaleLinear<LinearScaleConfig>({
238
244
  domain: [
239
- Math.min(...data.map(d => parseFloat(d[config.forestPlot.lower]))) - xAxisPadding,
240
- Math.max(...data.map(d => parseFloat(d[config.forestPlot.upper]))) + xAxisPadding
245
+ Math.min(...data.map(d => parseFloat(d[forestPlot.lower]))) - xAxisPadding,
246
+ Math.max(...data.map(d => parseFloat(d[forestPlot.upper]))) + xAxisPadding
241
247
  ],
242
248
  range: [leftWidthOffsetMobile, xMax - rightWidthOffsetMobile],
243
249
  type: scaleTypes.LINEAR
244
250
  })
245
251
  }
246
252
 
247
- if (config.forestPlot.type === 'Logarithmic') {
248
- let max = Math.max(...data.map(d => parseFloat(d[config.forestPlot.upper])))
249
- let fp_min = Math.min(...data.map(d => parseFloat(d[config.forestPlot.lower])))
253
+ if (forestPlot.type === 'Logarithmic') {
254
+ let max = Math.max(...data.map(d => parseFloat(d[forestPlot.upper])))
255
+ let fp_min = Math.min(...data.map(d => parseFloat(d[forestPlot.lower])))
250
256
 
251
257
  xScale = scaleLog<LogScaleConfig>({
252
258
  domain: [fp_min, max],
@@ -264,14 +270,34 @@ const useScales = (properties: useScaleProps) => {
264
270
 
265
271
  export default useScales
266
272
 
267
- export const getTickValues = (xAxisDataMapped, xScale, num) => {
273
+ export const getFirstDayOfMonth = ms => {
274
+ const date = new Date(ms)
275
+ return new Date(date.getFullYear(), date.getMonth(), 1).getTime()
276
+ }
277
+
278
+ export const dateFormatHasMonthButNoDays = dateFormat => {
279
+ return (
280
+ (dateFormat.includes('%b') ||
281
+ dateFormat.includes('%B') ||
282
+ dateFormat.includes('%m') ||
283
+ dateFormat.includes('%-m') ||
284
+ dateFormat.includes('%_m')) &&
285
+ !dateFormat.includes('%d') &&
286
+ !dateFormat.includes('%-d') &&
287
+ !dateFormat.includes('%_d') &&
288
+ !dateFormat.includes('%e')
289
+ )
290
+ }
291
+
292
+ export const getTickValues = (xAxisDataMapped, xScale, num, config) => {
268
293
  const xDomain = xScale.domain()
269
294
 
270
295
  if (xScale.type === 'time') {
271
296
  const xDomainMax = xAxisDataMapped[xAxisDataMapped.length - 1]
272
297
  const xDomainMin = xAxisDataMapped[0]
298
+
273
299
  const step = (xDomainMax - xDomainMin) / (num - 1)
274
- const tickValues = []
300
+ let tickValues = []
275
301
  for (let i = xDomainMax; i >= xDomainMin; i -= step) {
276
302
  tickValues.push(i)
277
303
  }
@@ -280,6 +306,11 @@ export const getTickValues = (xAxisDataMapped, xScale, num) => {
280
306
  }
281
307
  tickValues.reverse()
282
308
 
309
+ // Use first days of months when showing months without days
310
+ if (dateFormatHasMonthButNoDays(config.xAxis.dateDisplayFormat)) {
311
+ tickValues = tickValues.map(tv => getFirstDayOfMonth(tv))
312
+ }
313
+
283
314
  return tickValues
284
315
  }
285
316
 
@@ -358,3 +389,21 @@ const composeScaleBand = (domain, range, padding = 0) => {
358
389
  padding: padding
359
390
  })
360
391
  }
392
+
393
+ const sortXAxisData = (xAxisData, sortByRecentDate) => {
394
+ if (!xAxisData || xAxisData.length === 0) {
395
+ return []
396
+ }
397
+
398
+ // Check if the array has only one item
399
+ if (xAxisData.length === 1) {
400
+ return xAxisData
401
+ }
402
+ if (sortByRecentDate) {
403
+ // Sort from newest to oldes (recent dates first)
404
+ return xAxisData.sort((a, b) => Number(b) - Number(a))
405
+ } else {
406
+ // Sort from oldest to newest
407
+ return xAxisData.sort((a, b) => Number(a) - Number(b))
408
+ }
409
+ }
@@ -7,6 +7,7 @@ import { isDateScale } from '@cdc/core/helpers/cove/date'
7
7
  // Third-party library imports
8
8
  import { localPoint } from '@visx/event'
9
9
  import { bisector } from 'd3-array'
10
+ import _ from 'lodash'
10
11
 
11
12
  export const useTooltip = props => {
12
13
  const {
@@ -56,14 +57,9 @@ export const useTooltip = props => {
56
57
  const getFormattedValue = (seriesKey, value, config, getAxisPosition) => {
57
58
  // handle case where data is missing
58
59
  const showMissingDataValue = config.general.showMissingDataLabel && (!value || value === 'null')
59
- let formattedValue = seriesKey === config.xAxis.dataKey ? value : formatNumber(value, getAxisPosition(seriesKey))
60
+ const formattedValue = seriesKey === config.xAxis.dataKey ? value : formatNumber(value, getAxisPosition(seriesKey))
60
61
 
61
- formattedValue =
62
- showMissingDataValue && (config.visualizationSubType === 'stacked' ? !config.general.hideNullValue : true)
63
- ? 'N/A'
64
- : formattedValue
65
-
66
- return formattedValue
62
+ return showMissingDataValue ? 'N/A' : formattedValue
67
63
  }
68
64
 
69
65
  const getTooltipInformation = (tooltipDataArray, eventSvgCoords) => {
@@ -104,37 +100,9 @@ export const useTooltip = props => {
104
100
 
105
101
  const closestXScaleValue = getXValueFromCoordinate(x - Number(config.yAxis.size || 0))
106
102
 
107
- const includedSeries =
108
- visualizationType !== 'Pie'
109
- ? config.runtime.series.filter(series => series.tooltip === true).map(item => item.dataKey)
110
- : config.runtime.series.map(item => item.dataKey)
111
- includedSeries.push(config.xAxis.dataKey)
112
- if (config.visualizationType === 'Forecasting') {
113
- config.runtime.series.map(s => {
114
- s.confidenceIntervals.map(c => {
115
- if (c.showInTooltip) {
116
- includedSeries.push(c.high)
117
- includedSeries.push(c.low)
118
- }
119
- })
120
- })
121
- }
122
- function getColumnNames(columns) {
123
- let names = []
124
- for (let key in columns) {
125
- if (columns.hasOwnProperty(key)) {
126
- names.push(columns[key].name)
127
- }
128
- }
129
- return names
130
- }
131
- includedSeries.push(...getColumnNames(config.columns))
132
- includedSeries.push(...getColumnNames(config.columns))
133
-
134
- const yScaleValues = getYScaleValues(closestXScaleValue, includedSeries)
135
103
  const xScaleValues = data.filter(d => d[xAxis.dataKey] === getClosestYValue(y))
136
104
 
137
- const resolvedScaleValues = orientation === 'vertical' ? yScaleValues : xScaleValues
105
+ const resolvedScaleValues = orientation === 'vertical' ? getYScaleValues(closestXScaleValue) : xScaleValues
138
106
 
139
107
  const getAxisPosition = seriesKey => {
140
108
  const seriesObj = config.runtime.series.filter(s => s.dataKey === seriesKey)[0]
@@ -190,18 +158,36 @@ export const useTooltip = props => {
190
158
  if (visualizationType !== 'Pie' && visualizationType !== 'Forest Plot' && !config.tooltips.singleSeries) {
191
159
  tooltipItems.push(
192
160
  ...getIncludedTooltipSeries()
193
- ?.filter(
194
- seriesKey =>
195
- config.runtime.series?.find(item => item.dataKey === seriesKey && item?.tooltip) ||
196
- config.xAxis?.dataKey == seriesKey ||
197
- visualizationType === 'Forecasting'
198
- )
161
+ ?.filter(seriesKey => {
162
+ const series = config.runtime.series?.find(
163
+ s => s.dataKey === seriesKey && s?.tooltip && !s.dynamicCategory
164
+ )
165
+ return series || config.xAxis?.dataKey == seriesKey || visualizationType === 'Forecasting'
166
+ })
199
167
  ?.flatMap(seriesKey => {
200
168
  const value = resolvedScaleValues[0]?.[seriesKey]
201
169
  const formattedValue = getFormattedValue(seriesKey, value, config, getAxisPosition)
202
- return [[seriesKey, formattedValue, getAxisPosition(seriesKey)]]
170
+ if (
171
+ (value === null || value === undefined || value === '' || formattedValue === 'N/A') &&
172
+ config.general.hideNullValue
173
+ ) {
174
+ return []
175
+ } else {
176
+ return [[seriesKey, formattedValue, getAxisPosition(seriesKey)]]
177
+ }
203
178
  })
204
179
  )
180
+
181
+ config.runtime.series?.forEach(series => {
182
+ if (series?.dynamicCategory) {
183
+ const seriesKey = series.dataKey
184
+ const resolvedScaleValue = resolvedScaleValues.find(v => v[series.dynamicCategory] === seriesKey)
185
+ if (!resolvedScaleValue) return
186
+ const value = resolvedScaleValue[series.originalDataKey]
187
+ const formattedValue = getFormattedValue(seriesKey, value, config, getAxisPosition)
188
+ tooltipItems.push([seriesKey, formattedValue, getAxisPosition(seriesKey)])
189
+ }
190
+ })
205
191
  }
206
192
 
207
193
  // handle tooltip for single hovered series
@@ -372,25 +358,48 @@ export const useTooltip = props => {
372
358
  /**
373
359
  * Provides an array of objects with the closest y series data items
374
360
  * @param {String} closestXScaleValue
375
- * @param {Array} includedSeries
376
361
  * @returns an array of objects with the closest y series data items
377
362
  */
378
- const getYScaleValues = (closestXScaleValue, includedSeries) => {
379
- try {
380
- let dataToSearch
363
+ const getYScaleValues = closestXScaleValue => {
364
+ const runtimeSeries = config.runtime.series.filter(
365
+ series => visualizationType === 'Pie' || (series.tooltip === true && !series.dynamicCategory)
366
+ )
367
+ const includedSeries = runtimeSeries.map(item => item.dataKey)
368
+ includedSeries.push(config.xAxis.dataKey)
369
+ // get dynamic category series
370
+ const dynamicDataCategories = _.uniq(
371
+ config.runtime.series.flatMap(series => {
372
+ if (series.dynamicCategory) {
373
+ return [series.dynamicCategory, series.originalDataKey]
374
+ }
375
+ })
376
+ )
377
+ includedSeries.push(...dynamicDataCategories)
381
378
 
382
- if (xAxis.type === 'categorical') {
383
- dataToSearch = data.filter(d => d[xAxis.dataKey] === closestXScaleValue)
384
- } else {
385
- dataToSearch = data.filter(d => d[xAxis.dataKey] === closestXScaleValue)
386
- }
379
+ if (config.visualizationType === 'Forecasting') {
380
+ config.runtime.series.map(s => {
381
+ s.confidenceIntervals.map(c => {
382
+ if (c.showInTooltip) {
383
+ includedSeries.push(c.high)
384
+ includedSeries.push(c.low)
385
+ }
386
+ })
387
+ })
388
+ }
389
+
390
+ const colNames = Object.values(config.columns).map(column => column.name)
391
+ // @ Murad why are we adding them twice?
392
+ includedSeries.push(...colNames, ...colNames)
393
+
394
+ try {
395
+ const dataToSearch = data.filter(d => d[xAxis.dataKey] === closestXScaleValue)
387
396
  // Return an empty array if no matching data is found.
388
397
  if (!dataToSearch || dataToSearch.length === 0) {
389
398
  return []
390
399
  }
391
400
 
392
401
  const yScaleValues = dataToSearch.map(object => {
393
- return Object.fromEntries(Object.entries(object).filter(([key, value]) => includedSeries.includes(key)))
402
+ return _.pick(object, includedSeries)
394
403
  })
395
404
  return yScaleValues
396
405
  } catch (error) {
package/src/index.jsx CHANGED
@@ -2,6 +2,7 @@ import React from 'react'
2
2
  import ReactDOM from 'react-dom/client'
3
3
 
4
4
  import CdcChart from './CdcChart'
5
+ import './coreStyles_chart.scss'
5
6
 
6
7
  import 'react-tooltip/dist/react-tooltip.css'
7
8
 
@@ -1,7 +1,8 @@
1
- @include breakpointClass(md) {
2
- .data-table-container {
3
- margin: 5px 0em;
4
- width: 100%;
1
+ .data-table-container {
2
+ margin: 20px 0 0;
3
+ width: 100%;
4
+ &.download-link-above {
5
+ margin: 0;
5
6
  }
6
7
  }
7
8
 
@@ -1,6 +1,50 @@
1
- @import '@cdc/core/styles/base';
2
- @import '@cdc/core/styles/heading-colors';
3
- @import '@cdc/core/styles/v2/themes/color-definitions';
1
+ @mixin breakpoint($class) {
2
+ @if $class == xs {
3
+ @media (max-width: 767px) {
4
+ @content;
5
+ }
6
+ } @else if $class == sm {
7
+ @media (min-width: 768px) {
8
+ @content;
9
+ }
10
+ } @else if $class == md {
11
+ @media (min-width: 960px) {
12
+ @content;
13
+ }
14
+ } @else if $class == lg {
15
+ @media (min-width: 1300px) {
16
+ @content;
17
+ }
18
+ } @else {
19
+ @warn "Breakpoint mixin supports: xs, sm, md, lg";
20
+ }
21
+ }
22
+
23
+ @mixin breakpointClass($class) {
24
+ @if $class == xs {
25
+ &.xs,
26
+ &.xxs {
27
+ @content;
28
+ }
29
+ } @else if $class == sm {
30
+ &.sm,
31
+ &.md,
32
+ &.lg {
33
+ @content;
34
+ }
35
+ } @else if $class == md {
36
+ &.md,
37
+ &.lg {
38
+ @content;
39
+ }
40
+ } @else if $class == lg {
41
+ &.lg {
42
+ @content;
43
+ }
44
+ } @else {
45
+ @warn "Breakpoint Class mixin supports: xs, sm, md, lg";
46
+ }
47
+ }
4
48
 
5
49
  .form-container {
6
50
  overflow-y: auto;
@@ -76,23 +120,6 @@
76
120
  }
77
121
  }
78
122
 
79
- .btn {
80
- background: #005eaa;
81
- color: #fff !important;
82
- border: 0;
83
- padding: 0.4em 0.8em;
84
- display: block;
85
- border-radius: 5px;
86
- transition: 0.1s all;
87
- cursor: pointer;
88
-
89
- &[disabled] {
90
- opacity: 0.5;
91
- z-index: -1;
92
- position: relative;
93
- }
94
- }
95
-
96
123
  .warning-icon {
97
124
  position: relative;
98
125
  top: 2px;
@@ -129,11 +156,12 @@
129
156
  padding: 15px;
130
157
  vertical-align: top;
131
158
  text-align: left;
132
- border: 1px solid $lightGray;
159
+ border: 1px solid var(--lightGray);
133
160
  position: relative;
134
161
 
135
162
  &.no-border {
136
163
  border: 1px solid transparent;
164
+ padding: 0;
137
165
  }
138
166
 
139
167
  &__inner {
@@ -147,7 +175,7 @@
147
175
  &.vertical-sorted {
148
176
  display: block;
149
177
 
150
- @include breakpoint(md) {
178
+ @include breakpoint(sm) {
151
179
  column-count: 2;
152
180
  column-width: 100%;
153
181
  }
@@ -168,7 +196,6 @@
168
196
 
169
197
  .legend-item {
170
198
  text-align: left;
171
- align-items: flex-start !important;
172
199
  user-select: none;
173
200
  white-space: nowrap;
174
201
 
@@ -177,10 +204,6 @@
177
204
  white-space: pre-wrap;
178
205
  word-break: break-word;
179
206
  }
180
-
181
- & > :first-child {
182
- margin-top: 3px;
183
- }
184
207
  }
185
208
 
186
209
  .vertical-sorted:not(.single-row) .legend-item {
@@ -199,11 +222,11 @@
199
222
 
200
223
  h3,
201
224
  p {
202
- margin-bottom: 0.8em;
225
+ margin-bottom: 0.4em;
203
226
  }
204
227
 
205
228
  & div.legend-item {
206
- margin-bottom: 0.5em !important;
229
+ margin-bottom: 0.2em !important;
207
230
 
208
231
  &:last-child {
209
232
  margin: 0 !important;
@@ -456,46 +479,6 @@
456
479
  }
457
480
  }
458
481
 
459
- .filters-section {
460
- margin-left: 1rem;
461
- margin-right: 1rem;
462
-
463
- &__title {
464
- margin: 15px 0;
465
- }
466
-
467
- &__wrapper {
468
- hr {
469
- margin-bottom: 20px;
470
- }
471
-
472
- label {
473
- display: inherit;
474
- margin-bottom: 5px;
475
- font-weight: 600;
476
- font-size: 16px;
477
- }
478
- }
479
-
480
- @include breakpoint(md) {
481
- display: flex;
482
- gap: 30px;
483
- }
484
-
485
- label:not(:empty) {
486
- margin-right: 0.4em;
487
- }
488
-
489
- select {
490
- font-size: 1em;
491
- padding-right: 5px;
492
- }
493
-
494
- .single-filter {
495
- margin-bottom: 0.5em;
496
- }
497
- }
498
-
499
482
  @include breakpointClass(xs) {
500
483
  &.font-small {
501
484
  font-size: 0.8em;
@@ -522,7 +505,7 @@
522
505
  }
523
506
  }
524
507
 
525
- @include breakpointClass(md) {
508
+ @include breakpointClass(sm) {
526
509
  .chart-container {
527
510
  .no-wrap {
528
511
  flex-wrap: nowrap;
@@ -544,7 +527,11 @@
544
527
  margin-left: 0;
545
528
  }
546
529
  }
530
+ }
531
+ }
547
532
 
533
+ @include breakpointClass(md) {
534
+ .chart-container {
548
535
  > svg {
549
536
  font-size: 16px;
550
537
  width: 75%;