@cdc/chart 4.25.8 → 4.25.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 (145) hide show
  1. package/.claude/settings.local.json +9 -0
  2. package/dist/{cdcchart-1a1724a1.es.js → cdcchart-dgT_1dIT.es.js} +136 -151
  3. package/dist/cdcchart.js +44236 -40355
  4. package/examples/feature/__data__/planet-example-data.json +0 -30
  5. package/examples/feature/boxplot/valid-boxplot.csv +38 -17
  6. package/examples/grouped-bar-test.json +400 -0
  7. package/examples/private/DEV-11825.json +573 -0
  8. package/examples/private/d.json +382 -0
  9. package/examples/private/example-2.json +49784 -0
  10. package/examples/private/f2.json +1 -0
  11. package/examples/private/f4.json +1577 -0
  12. package/examples/private/forecast.json +1180 -0
  13. package/examples/private/lollipop.json +468 -0
  14. package/examples/private/na.json +913 -0
  15. package/examples/private/new.json +48756 -0
  16. package/examples/private/pie-chart-legend.json +904 -0
  17. package/examples/private/test-data.csv +28 -0
  18. package/examples/suppressed_tooltip.json +480 -0
  19. package/index.html +2 -133
  20. package/package.json +25 -7
  21. package/src/CdcChart.tsx +9 -13
  22. package/src/CdcChartComponent.tsx +403 -92
  23. package/src/_stories/Chart.Anchors.stories.tsx +2 -2
  24. package/src/_stories/Chart.BoxPlot.stories.tsx +1 -1
  25. package/src/_stories/Chart.CI.stories.tsx +1 -1
  26. package/src/_stories/Chart.Combo.stories.tsx +18 -0
  27. package/src/_stories/Chart.CustomColors.stories.tsx +1 -1
  28. package/src/_stories/Chart.DynamicSeries.stories.tsx +2 -2
  29. package/src/_stories/Chart.Filters.stories.tsx +2 -2
  30. package/src/_stories/Chart.Forecast.stories.tsx +36 -0
  31. package/src/_stories/Chart.HTMLInDataTable.stories.tsx +520 -0
  32. package/src/_stories/Chart.Legend.Gradient.stories.tsx +2 -2
  33. package/src/_stories/Chart.Patterns.stories.tsx +20 -0
  34. package/src/_stories/Chart.PreserveDecimals.stories.tsx +220 -0
  35. package/src/_stories/Chart.ScatterPlot.stories.tsx +1 -1
  36. package/src/_stories/Chart.SmallMultiples.stories.tsx +47 -0
  37. package/src/_stories/Chart.stories.tsx +8 -5
  38. package/src/_stories/Chart.tooltip.stories.tsx +1 -1
  39. package/src/_stories/ChartAnnotation.stories.tsx +7 -4
  40. package/src/_stories/ChartAxisLabels.stories.tsx +2 -2
  41. package/src/_stories/ChartAxisTitles.stories.tsx +2 -2
  42. package/src/_stories/ChartBar.Editor.stories.tsx +3580 -0
  43. package/src/_stories/ChartEditor.Editor.stories.tsx +658 -0
  44. package/src/_stories/ChartEditor.stories.tsx +59 -60
  45. package/src/_stories/ChartLine.Suppression.stories.tsx +1 -1
  46. package/src/_stories/ChartLine.Symbols.stories.tsx +1 -1
  47. package/src/_stories/ChartPrefixSuffix.stories.tsx +2 -2
  48. package/src/_stories/_mock/combo.json +451 -0
  49. package/src/_stories/_mock/editor-test-configs.json +376 -0
  50. package/src/_stories/_mock/editor-test-datasets.json +477 -0
  51. package/src/_stories/_mock/editor-tests/bar-chart-editor-test.json +255 -0
  52. package/src/_stories/_mock/editor-tests/bar-chart-general-test.json +267 -0
  53. package/src/_stories/_mock/editor-tests/bar-chart-test.json +237 -0
  54. package/src/_stories/_mock/forecast_combo_with_gaps.json +913 -0
  55. package/src/_stories/_mock/pie_config.json +257 -62
  56. package/src/_stories/_mock/small_multiples/small_multiples_bars.json +1944 -0
  57. package/src/_stories/_mock/small_multiples/small_multiples_big_data_bars.json +1114 -0
  58. package/src/_stories/_mock/small_multiples/small_multiples_lines.json +2646 -0
  59. package/src/_stories/_mock/small_multiples/small_multiples_lines_colors.json +1305 -0
  60. package/src/_stories/_mock/small_multiples/small_multiples_stacked_bars.json +1936 -0
  61. package/src/_stories/_mock/stacked-pattern-test.json +520 -0
  62. package/src/components/Annotations/components/AnnotationDraggable.tsx +1 -0
  63. package/src/components/Annotations/components/AnnotationDropdown.tsx +1 -1
  64. package/src/components/Annotations/components/findNearestDatum.ts +6 -41
  65. package/src/components/AreaChart/components/AreaChart.Stacked.jsx +10 -6
  66. package/src/components/AreaChart/index.tsx +1 -2
  67. package/src/components/BarChart/components/BarChart.Horizontal.tsx +161 -22
  68. package/src/components/BarChart/components/BarChart.StackedHorizontal.tsx +138 -5
  69. package/src/components/BarChart/components/BarChart.StackedVertical.tsx +215 -73
  70. package/src/components/BarChart/components/BarChart.Vertical.tsx +155 -22
  71. package/src/components/BarChart/helpers/index.ts +43 -4
  72. package/src/components/BarChart/helpers/lollipopColors.ts +27 -0
  73. package/src/components/BarChart/helpers/useBarChart.ts +25 -3
  74. package/src/components/BoxPlot/BoxPlot.Vertical.tsx +2 -1
  75. package/src/components/BoxPlot/helpers/index.ts +3 -3
  76. package/src/components/Brush/BrushChart.tsx +1 -1
  77. package/src/components/DeviationBar.jsx +9 -6
  78. package/src/components/EditorPanel/EditorPanel.tsx +563 -229
  79. package/src/components/EditorPanel/EditorPanelContext.ts +3 -0
  80. package/src/components/EditorPanel/components/Panels/Panel.Annotate.tsx +96 -111
  81. package/src/components/EditorPanel/components/Panels/Panel.General.tsx +19 -1
  82. package/src/components/EditorPanel/components/Panels/Panel.PatternSettings.tsx +461 -0
  83. package/src/components/EditorPanel/components/Panels/Panel.Series.tsx +80 -67
  84. package/src/components/EditorPanel/components/Panels/Panel.SmallMultiples.tsx +422 -0
  85. package/src/components/EditorPanel/components/Panels/Panel.Visual.tsx +188 -139
  86. package/src/components/EditorPanel/components/Panels/index.tsx +5 -1
  87. package/src/components/EditorPanel/components/Panels/panelVisual.styles.css +0 -8
  88. package/src/components/EditorPanel/editor-panel.scss +0 -20
  89. package/src/components/EditorPanel/helpers/updateFieldRankByValue.ts +49 -48
  90. package/src/components/EditorPanel/useEditorPermissions.ts +7 -15
  91. package/src/components/Forecasting/Forecasting.tsx +175 -27
  92. package/src/components/ForestPlot/ForestPlot.tsx +11 -7
  93. package/src/components/ForestPlot/ForestPlotProps.ts +1 -1
  94. package/src/components/Legend/Legend.Component.tsx +114 -14
  95. package/src/components/Legend/helpers/createFormatLabels.tsx +230 -171
  96. package/src/components/Legend/helpers/getLegendClasses.ts +0 -1
  97. package/src/components/LegendWrapper.tsx +1 -1
  98. package/src/components/LineChart/LineChartProps.ts +0 -3
  99. package/src/components/LineChart/components/LineChart.Circle.tsx +2 -2
  100. package/src/components/LineChart/helpers.ts +1 -1
  101. package/src/components/LineChart/index.tsx +38 -15
  102. package/src/components/LinearChart.tsx +96 -84
  103. package/src/components/PairedBarChart.jsx +6 -4
  104. package/src/components/PieChart/PieChart.tsx +170 -54
  105. package/src/components/Regions/components/Regions.tsx +3 -24
  106. package/src/components/Sankey/components/Sankey.tsx +7 -1
  107. package/src/components/Sankey/types/index.ts +1 -1
  108. package/src/components/ScatterPlot/ScatterPlot.jsx +32 -4
  109. package/src/components/SmallMultiples/SmallMultipleTile.tsx +198 -0
  110. package/src/components/SmallMultiples/SmallMultiples.css +32 -0
  111. package/src/components/SmallMultiples/SmallMultiples.tsx +271 -0
  112. package/src/components/SmallMultiples/index.ts +2 -0
  113. package/src/data/initial-state.js +327 -293
  114. package/src/helpers/buildForecastPaletteMappings.ts +112 -0
  115. package/src/helpers/buildForecastPaletteOptions.ts +71 -0
  116. package/src/helpers/getColorScale.ts +82 -8
  117. package/src/{hooks/useMinMax.ts → helpers/getMinMax.ts} +14 -7
  118. package/src/helpers/getNewRuntime.ts +1 -1
  119. package/src/helpers/getTransformedData.ts +1 -1
  120. package/src/helpers/getYAxisAutoPadding.ts +53 -0
  121. package/src/helpers/smallMultiplesHelpers.ts +529 -0
  122. package/src/hooks/useChartHoverAnalytics.tsx +44 -0
  123. package/src/hooks/useProgrammaticTooltip.ts +96 -0
  124. package/src/hooks/useReduceData.ts +105 -70
  125. package/src/hooks/useScales.ts +88 -34
  126. package/src/hooks/useSmallMultipleSynchronization.ts +59 -0
  127. package/src/hooks/useTooltip.tsx +116 -29
  128. package/src/index.jsx +0 -2
  129. package/src/scss/main.scss +13 -80
  130. package/src/store/chart.actions.ts +2 -0
  131. package/src/store/chart.reducer.ts +5 -1
  132. package/src/test/CdcChart.test.jsx +8 -3
  133. package/src/types/ChartConfig.ts +53 -11
  134. package/src/types/ChartContext.ts +4 -0
  135. package/vite.config.js +1 -1
  136. package/vitest.config.ts +16 -0
  137. package/src/_stories/_mock/pie_data.json +0 -218
  138. package/src/components/AreaChart/components/AreaChart.jsx +0 -109
  139. package/src/coreStyles_chart.scss +0 -3
  140. package/src/helpers/configHelpers.ts +0 -28
  141. package/src/helpers/generateColorsArray.ts +0 -8
  142. package/src/helpers/sort.ts +0 -7
  143. package/src/hooks/useActiveElement.js +0 -19
  144. package/src/hooks/useChartClasses.js +0 -41
  145. package/src/hooks/useColorPalette.js +0 -76
@@ -1,4 +1,4 @@
1
- import { useContext } from 'react'
1
+ import { useContext, useRef, useLayoutEffect } from 'react'
2
2
  // Local imports
3
3
  import parse from 'html-react-parser'
4
4
  import ConfigContext from '../ConfigContext'
@@ -8,10 +8,12 @@ import { isDateScale } from '@cdc/core/helpers/cove/date'
8
8
  // Third-party library imports
9
9
  import { localPoint } from '@visx/event'
10
10
  import { bisector } from 'd3-array'
11
- import _ from 'lodash'
11
+ import _, { get } from 'lodash'
12
12
  import { getHorizontalBarHeights } from '../components/BarChart/helpers/getBarHeights'
13
13
 
14
14
  export const useTooltip = props => {
15
+ // Track the last X-axis value to prevent duplicate analytics events
16
+ const lastAnalyticsXValue = useRef<string | number | null>(null)
15
17
  const {
16
18
  tableData: data,
17
19
  config,
@@ -23,9 +25,17 @@ export const useTooltip = props => {
23
25
  setSharedFilter,
24
26
  isDraggingAnnotation
25
27
  } = useContext<ChartContext>(ConfigContext)
26
- const { xScale, yScale, seriesScale, showTooltip, hideTooltip } = props
28
+ const { xScale, yScale, seriesScale, showTooltip, hideTooltip, interactionLabel = '' } = props
27
29
  const { xAxis, visualizationType, orientation, yAxis, runtime } = config
28
30
 
31
+ // Track the latest xScale in a ref to prevent stale closures
32
+ const xScaleRef = useRef(xScale)
33
+
34
+ // Update ref whenever xScale prop changes
35
+ useLayoutEffect(() => {
36
+ xScaleRef.current = xScale
37
+ }, [xScale])
38
+
29
39
  const Y_AXIS_SIZE = Number(config.yAxis.size || 0)
30
40
 
31
41
  // function handles only Single series hovered data tooltips
@@ -84,6 +94,7 @@ export const useTooltip = props => {
84
94
 
85
95
  const resolvedScaleValues = getResolvedScaleValues([x, y])
86
96
  const singleSeriesValue = getYValueFromCoordinate(y, resolvedScaleValues)
97
+
87
98
  const columnsWithTooltips = []
88
99
  const tooltipItems = [] as any[][]
89
100
  for (const [colKey, column] of Object.entries(config.columns)) {
@@ -154,6 +165,10 @@ export const useTooltip = props => {
154
165
  return position
155
166
  }
156
167
  if (!config.tooltips.singleSeries || visualizationType === 'Line') {
168
+ // Collect analytics data for all series
169
+ const analyticsSeriesData: string[] = []
170
+ let xAxisValue: string | number | null = null
171
+
157
172
  tooltipItems.push(
158
173
  ...getIncludedTooltipSeries()
159
174
  ?.filter(seriesKey => {
@@ -168,6 +183,7 @@ export const useTooltip = props => {
168
183
  const seriesObjWithName = config.runtime.series.find(
169
184
  series => series.dataKey === seriesKey && series.name !== undefined
170
185
  )
186
+
171
187
  if (
172
188
  (value === null || value === undefined || value === '' || formattedValue === 'N/A') &&
173
189
  config.general.hideNullValue
@@ -181,6 +197,29 @@ export const useTooltip = props => {
181
197
  })
182
198
  )
183
199
 
200
+ // Publish a single analytics event with all tooltip data
201
+ // Only publish if the X-axis value has changed (different from last hover)
202
+ if (analyticsSeriesData.length > 0 && xAxisValue !== lastAnalyticsXValue.current) {
203
+ lastAnalyticsXValue.current = xAxisValue
204
+
205
+ // Extract series names for the series field
206
+ const seriesNames = analyticsSeriesData.map(item => item.split(':')[0].trim()).join(', ')
207
+
208
+ const specifics = xAxisValue
209
+ ? `series: ${seriesNames}, x: ${xAxisValue}, ${analyticsSeriesData.join(', ')}`
210
+ : `series: ${seriesNames}, ${analyticsSeriesData.join(', ')}`
211
+
212
+ publishAnalyticsEvent({
213
+ vizType: config?.type,
214
+ vizSubType: getVizSubType(config),
215
+ eventType: `chart_hover`,
216
+ eventAction: 'hover',
217
+ eventLabel: interactionLabel || 'unknown',
218
+ vizTitle: getVizTitle(config),
219
+ specifics
220
+ })
221
+ }
222
+
184
223
  const runtimeSeries =
185
224
  config.tooltips.singleSeries && visualizationType === 'Line'
186
225
  ? [_.find(config.runtime.series, d => d.dataKey === singleSeriesValue)]
@@ -239,6 +278,9 @@ export const useTooltip = props => {
239
278
  * @returns {void} - The tooltip information is hidden
240
279
  */
241
280
  const handleTooltipMouseOff = () => {
281
+ // Reset the analytics tracking when mouse leaves
282
+ lastAnalyticsXValue.current = null
283
+
242
284
  if (config.visualizationType === 'Area Chart') {
243
285
  setTimeout(() => {
244
286
  hideTooltip()
@@ -255,15 +297,15 @@ export const useTooltip = props => {
255
297
  */
256
298
  const getXValueFromCoordinateDate = x => {
257
299
  if (config.xAxis.type === 'categorical' || config.visualizationType === 'Combo') {
258
- let eachBand = xScale.step()
300
+ let eachBand = xScaleRef.current.step()
259
301
  let numerator = x
260
302
  const index = Math.floor(Number(numerator) / eachBand)
261
- return xScale.domain()[index - 1] // fixes off by 1 error
303
+ return xScaleRef.current.domain()[index - 1] // fixes off by 1 error
262
304
  }
263
305
 
264
306
  if (isDateScale(config.xAxis) && config.visualizationType !== 'Combo') {
265
307
  const bisectDate = bisector(d => parseDate(d[config.xAxis.dataKey])).left
266
- const x0 = xScale.invert(xScale(x))
308
+ const x0 = xScaleRef.current.invert(xScaleRef.current(x))
267
309
  const index = bisectDate(config.data, x0, 1)
268
310
  const val = parseDate(config.data[index - 1][config.xAxis.dataKey])
269
311
  return val
@@ -275,12 +317,12 @@ export const useTooltip = props => {
275
317
  * @function getXValueFromCoordinate
276
318
  * @returns {String} - the closest x value to the cursor position
277
319
  */
278
- const getXValueFromCoordinate = (x, isClick = false) => {
320
+ const getXValueFromCoordinate = x => {
279
321
  if (visualizationType === 'Pie') return
280
322
  if (orientation === 'horizontal') return
281
323
 
282
324
  // Check the type of x equal to point or if the type of xAxis is equal to continuous or date
283
- if (xScale.type === 'point' || xAxis.type === 'continuous' || isDateScale(xAxis)) {
325
+ if (xScaleRef.current.type === 'point' || xAxis.type === 'continuous' || isDateScale(xAxis)) {
284
326
  // Find the closest x value by calculating the minimum distance
285
327
  let closestX = null
286
328
  let minDistance = Number.MAX_VALUE
@@ -288,9 +330,11 @@ export const useTooltip = props => {
288
330
 
289
331
  const barThicknessOffset = config.xAxis.type === 'date' ? xScale.bandwidth() / 2 : 0
290
332
  data.forEach(d => {
291
- const xPosition = isDateScale(xAxis) ? xScale(parseDate(d[xAxis.dataKey])) : xScale(d[xAxis.dataKey])
333
+ const xPosition = isDateScale(xAxis)
334
+ ? xScaleRef.current(parseDate(d[xAxis.dataKey]))
335
+ : xScaleRef.current(d[xAxis.dataKey])
292
336
  let bwOffset = config.barHeight
293
- const distance = Math.abs(Number(xPosition + barThicknessOffset - offset + (isClick ? bwOffset * 2 : 0)))
337
+ const distance = Math.abs(Number(xPosition + barThicknessOffset - offset))
294
338
 
295
339
  if (distance <= minDistance) {
296
340
  minDistance = distance
@@ -300,16 +344,50 @@ export const useTooltip = props => {
300
344
  return closestX
301
345
  }
302
346
 
347
+ // For band scales, find which band the mouse x-coordinate falls within
303
348
  if (config.xAxis.type === 'categorical' || visualizationType === 'Combo') {
304
- let range = xScale.range()[1] - xScale.range()[0]
305
- let eachBand = range / (xScale.domain().length + 1)
349
+ const domain = xScaleRef.current.domain()
350
+ const bandwidth = xScaleRef.current.bandwidth()
306
351
 
307
- let numerator = x
308
- const index = Math.floor((Number(numerator) - eachBand / 2) / eachBand)
309
- return xScale.domain()[index] // fixes off by 1 error
352
+ let closestValue = null
353
+ let minDistance = Number.MAX_VALUE
354
+
355
+ domain.forEach(value => {
356
+ const bandStart = xScaleRef.current(value)
357
+ const bandCenter = bandStart + bandwidth / 2
358
+ const distance = Math.abs(x - bandCenter)
359
+
360
+ if (distance < minDistance) {
361
+ minDistance = distance
362
+ closestValue = value
363
+ }
364
+ })
365
+
366
+ return closestValue
310
367
  }
311
368
  }
312
369
 
370
+ /**
371
+ * Helper for converting data value to pixel coordinate (inverse of getXValueFromCoordinate)
372
+ * @function getCoordinateFromXValue
373
+ * @param {any} xAxisValue - X-axis data value (date, number, or category)
374
+ * @returns {number} - pixel coordinate for the data value
375
+ */
376
+ const getCoordinateFromXValue = xAxisValue => {
377
+ if (visualizationType === 'Pie') return 0
378
+ if (orientation === 'horizontal') return 0
379
+
380
+ // Convert data value to pixel coordinate using current xScale
381
+ let pixelX = isDateScale(xAxis) ? xScaleRef.current(parseDate(xAxisValue)) : xScaleRef.current(xAxisValue)
382
+
383
+ // For band scales (bar charts, categorical axes), add bandwidth offset to point to center of bar
384
+ if (xScaleRef.current.bandwidth) {
385
+ pixelX += xScaleRef.current.bandwidth() / 2
386
+ }
387
+
388
+ return pixelX
389
+ }
390
+
313
391
  const findClosest = (dataArray: [any, number][], mouseXorY) => {
314
392
  let dataColumn: Object
315
393
  dataArray.find(([d, xOrY]) => {
@@ -397,7 +475,7 @@ export const useTooltip = props => {
397
475
  const eventSvgCoords = localPoint(e)
398
476
  const { x } = eventSvgCoords
399
477
  if (!x) throw new Error('COVE: no x value in handleTooltipClick.')
400
- let closestXScaleValue = getXValueFromCoordinate(x, true)
478
+ let closestXScaleValue = getXValueFromCoordinate(x)
401
479
  let datum = config.data?.filter(item => item[config.xAxis.dataKey] === closestXScaleValue)
402
480
  if (!closestXScaleValue) throw new Error('COVE: no closest x scale value in handleTooltipClick')
403
481
  if (isDateScale(xAxis) && closestXScaleValue) {
@@ -468,7 +546,7 @@ export const useTooltip = props => {
468
546
  const dataWithXScale = dataToSearch.map(
469
547
  d => [d, seriesScale(d[dynamicSeries.dynamicCategory])] as [Object, number]
470
548
  )
471
- const xOffset = x - Y_AXIS_SIZE - xScale(closestXScaleValue)
549
+ const xOffset = x - Y_AXIS_SIZE - xScaleRef.current(closestXScaleValue)
472
550
  dataToSearch = [findClosest(dataWithXScale, xOffset)]
473
551
  }
474
552
  }
@@ -571,18 +649,26 @@ export const useTooltip = props => {
571
649
 
572
650
  // TOOLTIP BODY
573
651
  // handle suppressed tooltip items
574
- const { label, displayGray } =
575
- (config.visualizationSubType !== 'stacked' &&
576
- config.general.showSuppressedSymbol &&
577
- config.preliminaryData?.find(
578
- pd =>
579
- pd.label &&
580
- pd.type === 'suppression' &&
581
- pd.displayTooltip &&
582
- value === pd.value &&
583
- (!pd.column || key === pd.column)
584
- )) ||
585
- {}
652
+ const shouldCheckSuppression = config.visualizationSubType !== 'stacked'
653
+ let suppressionEntry
654
+ if (shouldCheckSuppression && config.preliminaryData) {
655
+ suppressionEntry = config.preliminaryData.find(
656
+ pd =>
657
+ pd.label &&
658
+ pd.type === 'suppression' &&
659
+ pd.displayTooltip &&
660
+ value === pd.value &&
661
+ (!pd.column || key === pd.column)
662
+ )
663
+ }
664
+
665
+ // Remove suppressed items entirely if not showing symbols
666
+ if (suppressionEntry && !config.general.showSuppressedSymbol) {
667
+ return null
668
+ }
669
+
670
+ const { label, displayGray } = suppressionEntry || {}
671
+
586
672
  let newValue = label || value
587
673
  const style = displayGray ? { color: '#8b8b8a' } : {}
588
674
 
@@ -603,6 +689,7 @@ export const useTooltip = props => {
603
689
  getIncludedTooltipSeries,
604
690
  getXValueFromCoordinate,
605
691
  getXValueFromCoordinateDate,
692
+ getCoordinateFromXValue,
606
693
  handleTooltipClick,
607
694
  handleTooltipMouseOff,
608
695
  handleTooltipMouseOver,
package/src/index.jsx CHANGED
@@ -1,9 +1,7 @@
1
1
  import React from 'react'
2
2
  import ReactDOM from 'react-dom/client'
3
3
 
4
- import './coreStyles_chart.scss'
5
4
  import '@cdc/core/styles/cove-main.scss'
6
- import 'react-tooltip/dist/react-tooltip.css'
7
5
 
8
6
  import CdcChart from './CdcChart'
9
7
 
@@ -1,52 +1,4 @@
1
- @import '@cdc/core/styles/accessibility';
2
-
3
- @mixin breakpoint($class) {
4
- @if $class == xs {
5
- @media (max-width: 767px) {
6
- @content;
7
- }
8
- } @else if $class == sm {
9
- @media (min-width: 768px) {
10
- @content;
11
- }
12
- } @else if $class == md {
13
- @media (min-width: 960px) {
14
- @content;
15
- }
16
- } @else if $class == lg {
17
- @media (min-width: 1300px) {
18
- @content;
19
- }
20
- } @else {
21
- @warn "Breakpoint mixin supports: xs, sm, md, lg";
22
- }
23
- }
24
-
25
- @mixin breakpointClass($class) {
26
- @if $class == xs {
27
- &.xs,
28
- &.xxs {
29
- @content;
30
- }
31
- } @else if $class == sm {
32
- &.sm,
33
- &.md,
34
- &.lg {
35
- @content;
36
- }
37
- } @else if $class == md {
38
- &.md,
39
- &.lg {
40
- @content;
41
- }
42
- } @else if $class == lg {
43
- &.lg {
44
- @content;
45
- }
46
- } @else {
47
- @warn "Breakpoint Class mixin supports: xs, sm, md, lg";
48
- }
49
- }
1
+ @import '@cdc/core/styles/v2/utils/breakpoints';
50
2
 
51
3
  .form-container {
52
4
  overflow-y: auto;
@@ -91,37 +43,6 @@
91
43
 
92
44
  border-radius: 3px;
93
45
 
94
- .checkbox-group {
95
- padding: 16px;
96
- border: 1px solid #c4c4c4;
97
- border-radius: 8px;
98
- margin-top: 8px;
99
- margin-bottom: 24px;
100
- }
101
-
102
- .loader {
103
- width: 100%;
104
- text-align: center;
105
- display: inline-block;
106
- animation: spin 1s linear infinite;
107
-
108
- &::before {
109
- content: '\21BB';
110
- }
111
- }
112
-
113
- .warning-icon {
114
- position: relative;
115
- top: 2px;
116
- width: 15px;
117
- height: 15px;
118
- margin-left: 5px;
119
-
120
- path {
121
- fill: #d8000c;
122
- }
123
- }
124
-
125
46
  .chart-description {
126
47
  margin-bottom: 20px;
127
48
  }
@@ -741,3 +662,15 @@
741
662
  .cdc-open-viz-module .debug {
742
663
  border: 2px solid red;
743
664
  }
665
+
666
+ // Pattern Legend Styles - using Bootstrap classes for layout, CSS for gaps only
667
+ .legend-patterns {
668
+ gap: var(--space-between-legend-item-rows);
669
+ margin-top: var(--space-between-legend-item-rows);
670
+
671
+ // When in row layout (top/bottom), override gap for proper row/column spacing
672
+ &.flex-row {
673
+ row-gap: var(--space-between-legend-item-rows);
674
+ column-gap: var(--space-between-legend-item-columns);
675
+ }
676
+ }
@@ -12,6 +12,7 @@ type SET_EXCLUDED_DATA = Action<'SET_EXCLUDED_DATA', object[]>
12
12
  type SET_FILTERED_DATA = Action<'SET_FILTERED_DATA', object[]>
13
13
  type SET_SERIES_HIGHLIGHT = Action<'SET_SERIES_HIGHLIGHT', string[]>
14
14
  type SET_VIEWPORT = Action<'SET_VIEWPORT', string>
15
+ type SET_VIZ_VIEWPORT = Action<'SET_VIZ_VIEWPORT', string>
15
16
  type SET_DIMENSIONS = Action<'SET_DIMENSIONS', DimensionsType>
16
17
  type SET_CONTAINER = Action<'SET_CONTAINER', object>
17
18
  type SET_LOADED_EVENT = Action<'SET_LOADED_EVENT', boolean>
@@ -26,6 +27,7 @@ type ChartActions =
26
27
  | SET_FILTERED_DATA
27
28
  | SET_SERIES_HIGHLIGHT
28
29
  | SET_VIEWPORT
30
+ | SET_VIZ_VIEWPORT
29
31
  | SET_DIMENSIONS
30
32
  | SET_CONTAINER
31
33
  | SET_LOADED_EVENT
@@ -13,6 +13,7 @@ type ChartState = {
13
13
  filteredData: object[]
14
14
  seriesHighlight: string[]
15
15
  currentViewport: ViewportSize
16
+ vizViewport: ViewportSize
16
17
  dimensions: DimensionsType
17
18
  container: HTMLElement | null
18
19
  coveLoadedEventRan: boolean
@@ -25,13 +26,14 @@ export const getInitialState = (configObj: ChartConfig): ChartState => {
25
26
  return {
26
27
  isLoading: true,
27
28
  config: defaults,
28
- stateData: _.cloneDeep(configObj?.data) || [],
29
+ stateData: configObj?.data || [],
29
30
  colorScale: null,
30
31
  excludedData: undefined,
31
32
  filteredData: undefined,
32
33
  seriesHighlight:
33
34
  configObj && configObj?.legend?.seriesHighlight?.length ? [...configObj?.legend?.seriesHighlight] : [],
34
35
  currentViewport: 'lg',
36
+ vizViewport: 'lg',
35
37
  dimensions: [0, 0],
36
38
  container: null,
37
39
  coveLoadedEventRan: false,
@@ -61,6 +63,8 @@ export const reducer = (state: ChartState, action: ChartActions): ChartState =>
61
63
  return { ...state, seriesHighlight: action.payload }
62
64
  case 'SET_VIEWPORT':
63
65
  return { ...state, currentViewport: action.payload }
66
+ case 'SET_VIZ_VIEWPORT':
67
+ return { ...state, vizViewport: action.payload }
64
68
  case 'SET_DIMENSIONS':
65
69
  return { ...state, dimensions: action.payload }
66
70
  case 'SET_CONTAINER':
@@ -1,6 +1,11 @@
1
- // Placeholder test until we add them in.
1
+ import path from 'path'
2
+ import { testStandaloneBuild } from '@cdc/core/helpers/tests/testStandaloneBuild.ts'
3
+ import { describe, it, expect } from 'vitest'
4
+
2
5
  describe('Chart', () => {
3
- it('has a test.', async () => {
4
- return true
6
+ it('Can be built in isolation', async () => {
7
+ const pkgDir = path.join(__dirname, '..')
8
+ const result = testStandaloneBuild(pkgDir)
9
+ expect(result).toBe(true)
5
10
  })
6
11
  })
@@ -1,4 +1,5 @@
1
1
  import { Axis } from '@cdc/core/types/Axis'
2
+ import { MarkupConfig } from '@cdc/core/types/MarkupVariable'
2
3
  import { type ForestPlotConfigSettings } from './ForestPlot'
3
4
  import { type Column } from '@cdc/core/types/Column'
4
5
  import { type Series } from '@cdc/core/types/Series'
@@ -6,7 +7,18 @@ import { Runtime } from '@cdc/core/types/Runtime'
6
7
  import { FilterBehavior } from '@cdc/core/types/FilterBehavior'
7
8
  import { Table } from '@cdc/core/types/Table'
8
9
  import { BoxPlot } from '@cdc/core/types/BoxPlot'
9
- import { General } from '@cdc/core/types/General'
10
+ import { General as CoreGeneral } from '@cdc/core/types/General'
11
+
12
+ // Extend the core General type to include palette information for charts
13
+ type General = CoreGeneral & {
14
+ palette?: {
15
+ name?: string
16
+ version?: string
17
+ isReversed?: boolean
18
+ customColors?: string[]
19
+ customColorsOrdered?: string[]
20
+ }
21
+ }
10
22
  import { type Link } from './../components/Sankey/types'
11
23
  import { type DataDescription } from '@cdc/core/types/DataDescription'
12
24
  import { type Legend as CoreLegend } from '@cdc/core/types/Legend'
@@ -19,7 +31,7 @@ import { Version } from '@cdc/core/types/Version'
19
31
  import Footnotes from '@cdc/core/types/Footnotes'
20
32
 
21
33
  export type ViewportSize = 'xxs' | 'xs' | 'sm' | 'md' | 'lg'
22
- export type ChartColumns = Record<string, Column>
34
+ type ChartColumns = Record<string, Column>
23
35
  export type ChartOrientation = 'vertical' | 'horizontal'
24
36
  export type VisualizationType =
25
37
  | 'Area Chart'
@@ -64,6 +76,7 @@ type DataFormat = {
64
76
  bottomSuffix: string
65
77
  commas: boolean
66
78
  prefix: string
79
+ preserveOriginalDecimals?: boolean
67
80
  rightCommas: boolean
68
81
  rightPrefix: string
69
82
  rightRoundTo: number
@@ -80,7 +93,7 @@ type Exclusions = {
80
93
  dateEnd: string
81
94
  }
82
95
 
83
- export type Legend = CoreLegend & {
96
+ type Legend = CoreLegend & {
84
97
  seriesHighlight: string[]
85
98
  unified: boolean
86
99
  hideSuppressionLink: boolean
@@ -96,6 +109,17 @@ export type Legend = CoreLegend & {
96
109
  }
97
110
  groupBy: string
98
111
  separators?: string
112
+ patterns?: {
113
+ [key: string]: {
114
+ label?: string
115
+ color?: string
116
+ shape?: string
117
+ dataKey?: string
118
+ dataValue?: string
119
+ contrastCheck?: boolean
120
+ patternSize?: number
121
+ }
122
+ }
99
123
  }
100
124
 
101
125
  type Visual = {
@@ -125,7 +149,6 @@ export type AllChartsConfig = {
125
149
  colorMatchLineSeriesLabels: boolean
126
150
  columns: ChartColumns
127
151
  confidenceKeys: ConfidenceInterval
128
- customColors: string[]
129
152
  data: Object[]
130
153
  dataUrl: string
131
154
  dataCutoff: number
@@ -171,8 +194,22 @@ export type AllChartsConfig = {
171
194
  runtimeDataUrl: string
172
195
  series: Series
173
196
  showLineSeriesLabels: boolean
197
+ showAreaUnderLine?: boolean
174
198
  showSidebar: boolean
175
199
  showTitle: boolean
200
+ smallMultiples?: {
201
+ mode?: 'by-column' | 'by-series'
202
+ tileColumn?: string
203
+ tilesPerRowDesktop?: number
204
+ tilesPerRowMobile?: number
205
+ tileOrderType?: 'asc' | 'desc' | 'custom'
206
+ tileOrder?: string[]
207
+ tileTitles?: { [key: string]: string }
208
+ independentYAxis?: boolean
209
+ colorMode?: 'same' | 'different'
210
+ synchronizedTooltips?: boolean
211
+ showAreaUnderLine?: boolean
212
+ }
176
213
  sortData: 'ascending' | 'descending'
177
214
  stackedAreaChartLineType: string
178
215
  suppressedData?: { label: string; icon: string; value: string }[]
@@ -196,6 +233,8 @@ export type AllChartsConfig = {
196
233
  visualizationSubType: string
197
234
  xAxis: Axis
198
235
  yAxis: Axis
236
+ hideXAxisLabel?: boolean
237
+ hideYAxisLabel?: boolean
199
238
  xScale: Function
200
239
  yScale: Function
201
240
  regions: Region[]
@@ -225,12 +264,13 @@ export type AllChartsConfig = {
225
264
  default: string
226
265
  }
227
266
  }
228
- }
267
+ } & MarkupConfig
229
268
 
230
- export type ForestPlotConfig = {
269
+ type ForestPlotConfig = {
231
270
  visualizationType: 'Forest Plot'
232
271
  forestPlot: ForestPlotConfigSettings
233
- } & AllChartsConfig
272
+ } & AllChartsConfig &
273
+ MarkupConfig
234
274
 
235
275
  export type LineChartConfig = {
236
276
  allowLineToBarGraph: boolean
@@ -238,9 +278,10 @@ export type LineChartConfig = {
238
278
  isolatedDotsSameSize: boolean
239
279
  lineDatapointStyle: 'hidden' | 'always show' | 'hover'
240
280
  visualizationType: 'Line'
241
- } & AllChartsConfig
281
+ } & AllChartsConfig &
282
+ MarkupConfig
242
283
 
243
- export type SankeyLink = {
284
+ type SankeyLink = {
244
285
  depth: number
245
286
  height: number
246
287
  id: string
@@ -261,7 +302,7 @@ type StoryNode = {
261
302
  segmentTextBefore: string
262
303
  }
263
304
 
264
- export type SankeyChartConfig = {
305
+ type SankeyChartConfig = {
265
306
  enableTooltips: boolean
266
307
  data: [
267
308
  {
@@ -279,6 +320,7 @@ export type SankeyChartConfig = {
279
320
  }
280
321
  ]
281
322
  visualizationType: 'Sankey'
282
- } & AllChartsConfig
323
+ } & AllChartsConfig &
324
+ MarkupConfig
283
325
 
284
326
  export type ChartConfig = SankeyChartConfig | LineChartConfig | ForestPlotConfig | AllChartsConfig
@@ -16,13 +16,16 @@ type SharedChartContext = {
16
16
  clean: Function
17
17
  colorScale?: ColorScale
18
18
  config: ChartConfig
19
+ convertLineToBarGraph?: boolean
19
20
  currentViewport?: 'lg' | 'md' | 'sm' | 'xs' | 'xxs'
21
+ vizViewport?: 'lg' | 'md' | 'sm' | 'xs' | 'xxs'
20
22
  dashboardConfig?: DashboardConfig
21
23
  // process top level chart aria label for each chart type
22
24
  handleChartAriaLabels: (config: any) => string
23
25
  handleDragStateChange: (isDragging: any) => void
24
26
  highlight?: Function
25
27
  handleShowAll?: Function
28
+ interactionLabel?: string
26
29
  // whether or not the chart is viewed within the editor screen
27
30
  isEditor?: boolean
28
31
  // whether or not the user is dragging an annotation
@@ -32,6 +35,7 @@ type SharedChartContext = {
32
35
  parentRef?: React.RefObject<HTMLDivElement>
33
36
  setLegendIsolateValues?: Function
34
37
  svgRef?: React.RefObject<SVGSVGElement>
38
+ handleSmallMultipleHover?: (xAxisValue: any, yCoordinate: number) => void
35
39
  }
36
40
 
37
41
  // Line Chart Specific Context
package/vite.config.js CHANGED
@@ -1,4 +1,4 @@
1
- import GenerateViteConfig from '../../generateViteConfig.js'
1
+ import GenerateViteConfig from '@cdc/core/generateViteConfig.js'
2
2
  import { moduleName } from './package.json'
3
3
 
4
4
  export default GenerateViteConfig(moduleName)
@@ -0,0 +1,16 @@
1
+ import { defineConfig } from 'vitest/config'
2
+
3
+ export default defineConfig({
4
+ test: {
5
+ environment: 'jsdom',
6
+ globals: true,
7
+ setupFiles: ['../../vitest.setup.ts'],
8
+ exclude: [
9
+ '**/node_modules/**',
10
+ '**/dist/**',
11
+ '**/.storybook/**',
12
+ '**/*.stories.*',
13
+ '**/storybook-static/**'
14
+ ]
15
+ }
16
+ })