@cdc/chart 4.25.8 → 4.25.10

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 (89) hide show
  1. package/.claude/settings.local.json +9 -0
  2. package/dist/cdcchart.js +37524 -35243
  3. package/examples/feature/__data__/planet-example-data.json +0 -30
  4. package/examples/grouped-bar-test.json +400 -0
  5. package/examples/private/d.json +382 -0
  6. package/examples/private/example-2.json +49784 -0
  7. package/examples/private/f2.json +1 -0
  8. package/examples/private/f4.json +1577 -0
  9. package/examples/private/forecast.json +1180 -0
  10. package/examples/private/lollipop.json +468 -0
  11. package/examples/private/new.json +48756 -0
  12. package/examples/private/pie-chart-legend.json +904 -0
  13. package/examples/suppressed_tooltip.json +480 -0
  14. package/index.html +10 -22
  15. package/package.json +25 -7
  16. package/src/CdcChart.tsx +1 -2
  17. package/src/CdcChartComponent.tsx +174 -32
  18. package/src/_stories/Chart.Anchors.stories.tsx +2 -2
  19. package/src/_stories/Chart.BoxPlot.stories.tsx +1 -1
  20. package/src/_stories/Chart.CI.stories.tsx +1 -1
  21. package/src/_stories/Chart.CustomColors.stories.tsx +1 -1
  22. package/src/_stories/Chart.DynamicSeries.stories.tsx +2 -2
  23. package/src/_stories/Chart.Filters.stories.tsx +2 -2
  24. package/src/_stories/Chart.Legend.Gradient.stories.tsx +2 -2
  25. package/src/_stories/Chart.Patterns.stories.tsx +19 -0
  26. package/src/_stories/Chart.ScatterPlot.stories.tsx +1 -1
  27. package/src/_stories/Chart.stories.tsx +8 -5
  28. package/src/_stories/Chart.tooltip.stories.tsx +1 -1
  29. package/src/_stories/ChartAnnotation.stories.tsx +1 -1
  30. package/src/_stories/ChartAxisLabels.stories.tsx +2 -2
  31. package/src/_stories/ChartAxisTitles.stories.tsx +2 -2
  32. package/src/_stories/ChartEditor.stories.tsx +60 -60
  33. package/src/_stories/ChartLine.Suppression.stories.tsx +1 -1
  34. package/src/_stories/ChartLine.Symbols.stories.tsx +1 -1
  35. package/src/_stories/ChartPrefixSuffix.stories.tsx +2 -2
  36. package/src/_stories/_mock/stacked-pattern-test.json +520 -0
  37. package/src/components/Annotations/components/AnnotationDraggable.tsx +1 -0
  38. package/src/components/Annotations/components/AnnotationDropdown.tsx +1 -1
  39. package/src/components/BarChart/components/BarChart.Horizontal.tsx +159 -20
  40. package/src/components/BarChart/components/BarChart.StackedHorizontal.tsx +138 -5
  41. package/src/components/BarChart/components/BarChart.StackedVertical.tsx +215 -73
  42. package/src/components/BarChart/components/BarChart.Vertical.tsx +153 -21
  43. package/src/components/BarChart/helpers/index.ts +43 -4
  44. package/src/components/BarChart/helpers/lollipopColors.ts +27 -0
  45. package/src/components/BarChart/helpers/useBarChart.ts +25 -3
  46. package/src/components/BoxPlot/BoxPlot.Vertical.tsx +2 -1
  47. package/src/components/DeviationBar.jsx +9 -6
  48. package/src/components/EditorPanel/EditorPanel.tsx +364 -39
  49. package/src/components/EditorPanel/EditorPanelContext.ts +3 -0
  50. package/src/components/EditorPanel/components/Panels/Panel.PatternSettings.tsx +414 -0
  51. package/src/components/EditorPanel/components/Panels/Panel.Series.tsx +28 -20
  52. package/src/components/EditorPanel/components/Panels/Panel.Visual.tsx +115 -120
  53. package/src/components/EditorPanel/components/Panels/index.tsx +3 -1
  54. package/src/components/EditorPanel/components/Panels/panelVisual.styles.css +0 -8
  55. package/src/components/EditorPanel/helpers/updateFieldRankByValue.ts +49 -48
  56. package/src/components/Forecasting/Forecasting.tsx +36 -6
  57. package/src/components/ForestPlot/ForestPlot.tsx +11 -7
  58. package/src/components/ForestPlot/ForestPlotProps.ts +1 -1
  59. package/src/components/Legend/Legend.Component.tsx +106 -13
  60. package/src/components/Legend/helpers/createFormatLabels.tsx +230 -171
  61. package/src/components/LegendWrapper.tsx +1 -1
  62. package/src/components/LineChart/components/LineChart.Circle.tsx +2 -2
  63. package/src/components/LineChart/index.tsx +2 -2
  64. package/src/components/LinearChart.tsx +22 -5
  65. package/src/components/PairedBarChart.jsx +6 -4
  66. package/src/components/PieChart/PieChart.tsx +170 -54
  67. package/src/components/Sankey/components/Sankey.tsx +7 -1
  68. package/src/components/ScatterPlot/ScatterPlot.jsx +32 -4
  69. package/src/data/initial-state.js +315 -293
  70. package/src/helpers/buildForecastPaletteMappings.ts +112 -0
  71. package/src/helpers/buildForecastPaletteOptions.ts +109 -0
  72. package/src/helpers/getColorScale.ts +72 -8
  73. package/src/helpers/getNewRuntime.ts +1 -1
  74. package/src/helpers/getTransformedData.ts +1 -1
  75. package/src/hooks/useChartHoverAnalytics.tsx +44 -0
  76. package/src/hooks/useReduceData.ts +105 -70
  77. package/src/hooks/useTooltip.tsx +57 -15
  78. package/src/index.jsx +0 -2
  79. package/src/scss/main.scss +12 -0
  80. package/src/store/chart.reducer.ts +1 -1
  81. package/src/test/CdcChart.test.jsx +8 -3
  82. package/src/types/ChartConfig.ts +30 -6
  83. package/src/types/ChartContext.ts +1 -0
  84. package/vite.config.js +1 -1
  85. package/vitest.config.ts +16 -0
  86. package/src/coreStyles_chart.scss +0 -3
  87. package/src/helpers/configHelpers.ts +0 -28
  88. package/src/helpers/generateColorsArray.ts +0 -8
  89. package/src/hooks/useColorPalette.js +0 -76
@@ -10,6 +10,7 @@ interface BarConfigProps {
10
10
  barWidth: number
11
11
  isVertical: boolean
12
12
  yAxisValue: number
13
+ labelFontSize: number
13
14
  }
14
15
 
15
16
  // Function to create bar width based on suppression status and missing data label
@@ -20,7 +21,8 @@ export const getBarConfig = ({
20
21
  config,
21
22
  barWidth,
22
23
  isVertical,
23
- yAxisValue
24
+ yAxisValue,
25
+ labelFontSize
24
26
  }: BarConfigProps) => {
25
27
  const heightMini = 3 /// height of small bars aka suppressed/NA/Zero valued
26
28
  let barHeight = defaultBarHeight
@@ -50,7 +52,7 @@ export const getBarConfig = ({
50
52
 
51
53
  // Handle undefined, null, or non-calculable bar.value
52
54
  if (!isSuppressed && !isNumber(bar.value) && config.general.showMissingDataLabel) {
53
- const labelWidth = getTextWidth(barLabel, `normal ${barWidth / 2}px sans-serif`)
55
+ const labelWidth = getTextWidth(barLabel, `normal ${labelFontSize}px sans-serif`)
54
56
  const labelFits = Number(labelWidth) < barWidth && barWidth > 10
55
57
  showMissingDataLabel = true
56
58
  barHeight = labelFits ? heightMini : 0
@@ -59,7 +61,7 @@ export const getBarConfig = ({
59
61
  // Handle undefined, null, or non-calculable bar.value
60
62
 
61
63
  if (!isSuppressed && bar.value === '0' && config.general.showZeroValueData) {
62
- const labelWidth = getTextWidth('0', `normal ${barWidth / 2}px sans-serif`)
64
+ const labelWidth = getTextWidth('0', `normal ${labelFontSize}px sans-serif`)
63
65
  const labelFits = Number(labelWidth) < barWidth && barWidth > 10
64
66
  showZeroValueData = true
65
67
  barHeight = labelFits ? heightMini : 0
@@ -91,7 +93,7 @@ export const getBarConfig = ({
91
93
  if (showZeroValueData) label = '0'
92
94
 
93
95
  // determine label width in pixels & check if it fits to the bar width
94
- const labelWidth = getTextWidth(barLabel, `normal ${barWidth / 2}px sans-serif`)
96
+ const labelWidth = getTextWidth(barLabel, `normal ${labelFontSize}px sans-serif`)
95
97
  const labelFits = Number(labelWidth) < barWidth && barWidth > 10
96
98
  if (config.isLollipopChart) {
97
99
  return label
@@ -110,3 +112,40 @@ export const testZeroValue = value => {
110
112
  const regex = /^0(\.0)?$/
111
113
  return regex.test(value.toString())
112
114
  }
115
+
116
+ export const addMinimumBarHeights = barStacks => {
117
+ const MIN_BAR_HEIGHT = 3
118
+
119
+ barStacks[0].bars.forEach((_, i) => {
120
+ let segments = barStacks
121
+ .map(bs => bs.bars[i])
122
+ .filter(s => s.bar.data[s.key])
123
+ .reverse()
124
+
125
+ const segmentsNeedingAdjustment = segments.filter(segment => segment.height > 0 && segment.height < MIN_BAR_HEIGHT)
126
+ const segmentsToShrink = segments.filter(segment => segment.height > MIN_BAR_HEIGHT)
127
+
128
+ if (segmentsNeedingAdjustment.length > 0 && segmentsToShrink.length > 0) {
129
+ segmentsNeedingAdjustment.forEach(smallSegment => {
130
+ const heightToAdd = MIN_BAR_HEIGHT - smallSegment.height
131
+ const heightToTakePerSegment = heightToAdd / segmentsToShrink.length
132
+
133
+ segmentsToShrink.forEach(largeSegment => {
134
+ largeSegment.height -= heightToTakePerSegment
135
+ })
136
+
137
+ smallSegment.height = MIN_BAR_HEIGHT
138
+ })
139
+
140
+ let cumulativeY = segments[0].y
141
+ segments.forEach(segment => {
142
+ segment.y = cumulativeY
143
+ cumulativeY += segment.height
144
+ })
145
+ }
146
+ })
147
+
148
+ return barStacks
149
+ }
150
+
151
+ export { getLollipopStemColor, getLollipopHeadColor } from './lollipopColors'
@@ -0,0 +1,27 @@
1
+ import chroma from 'chroma-js'
2
+
3
+ /**
4
+ * Adjusts color for lollipop stem (lighter than original for contrast)
5
+ */
6
+ export const getLollipopStemColor = (baseColor: string): string => {
7
+ const lightness = chroma(baseColor).get('hsl.l')
8
+
9
+ if (lightness > 0.7) {
10
+ // Very light colors: darken slightly to ensure visibility
11
+ return chroma(baseColor).darken(0.1).hex()
12
+ } else if (lightness > 0.4) {
13
+ // Medium colors: lighten moderately
14
+ return chroma(baseColor).brighten(0.5).hex()
15
+ } else {
16
+ // Dark colors: lighten significantly but keep visible
17
+ return chroma(baseColor).brighten(1.0).hex()
18
+ }
19
+ }
20
+
21
+ /**
22
+ * Adjusts color for lollipop head (darker than original for contrast)
23
+ */
24
+ export const getLollipopHeadColor = (baseColor: string): string => {
25
+ const lightness = chroma(baseColor).get('hsl.l')
26
+ return lightness > 0.6 ? chroma(baseColor).darken(1.5).hex() : baseColor
27
+ }
@@ -2,9 +2,13 @@ import React, { useContext, useEffect, useState } from 'react'
2
2
  import { ChartDispatchContext } from '../../../ConfigContext'
3
3
  import { formatNumber as formatColNumber } from '@cdc/core/helpers/cove/number'
4
4
  import { APP_FONT_SIZE } from '@cdc/core/helpers/constants'
5
+ import { getPaletteColors } from '@cdc/core/helpers/palettes/utils'
6
+ import { publishAnalyticsEvent } from '@cdc/core/helpers/metrics/helpers'
7
+ import { getVizSubType, getVizTitle } from '@cdc/core/helpers/metrics/utils'
5
8
 
6
9
  export const useBarChart = (handleTooltipMouseOver, handleTooltipMouseOff, configContext) => {
7
- const { config, colorPalettes, tableData, updateConfig, parseDate, formatDate, seriesHighlight } = configContext
10
+ const { config, colorPalettes, tableData, updateConfig, parseDate, formatDate, seriesHighlight, interactionLabel } =
11
+ configContext
8
12
  const { orientation } = config
9
13
  const dispatch = useContext(ChartDispatchContext)
10
14
  const [hoveredBar, setHoveredBar] = useState(null)
@@ -98,7 +102,7 @@ export const useBarChart = (handleTooltipMouseOver, handleTooltipMouseOff, confi
98
102
  if (!config.legend.colorCode && config.series.length > 1) {
99
103
  return currentBarColor
100
104
  }
101
- const palettesArr = config.customColors ?? colorPalettes[config.palette]
105
+ const palettesArr = getPaletteColors(config, colorPalettes)
102
106
  const values = tableData.map(d => {
103
107
  return d[config.legend.colorCode]
104
108
  })
@@ -189,12 +193,30 @@ export const useBarChart = (handleTooltipMouseOver, handleTooltipMouseOff, confi
189
193
  return additionalTooltipItems
190
194
  }
191
195
 
192
- const onMouseOverBar = (categoryValue, barKey, event, data) => {
196
+ const onMouseOverBar = (categoryValue, barKey, event, data, barValue) => {
193
197
  if (config.legend.highlightOnHover && config.legend.behavior === 'highlight' && barKey) {
194
198
  dispatch({ type: 'SET_SERIES_HIGHLIGHT', payload: [barKey] })
195
199
  }
196
200
  handleTooltipMouseOver(event, data)
197
201
  setHoveredBar(categoryValue)
202
+
203
+ if (config.tooltips.singleSeries) {
204
+ const numericValue = barValue || 'none'
205
+ publishAnalyticsEvent({
206
+ vizType: config.type,
207
+ vizSubType: getVizSubType(config),
208
+ eventType: `chart_hover`,
209
+ eventAction: 'hover',
210
+ eventLabel: interactionLabel,
211
+ vizTitle: getVizTitle(config),
212
+ series: barKey || 'none',
213
+ specifics: `series: ${barKey || 'none'}, yValue: ${
214
+ orientation === 'horizontal' ? categoryValue : numericValue
215
+ }, xValue: ${orientation === 'horizontal' ? numericValue : categoryValue}, orientation: ${
216
+ orientation || 'none'
217
+ }`
218
+ })
219
+ }
198
220
  }
199
221
  const onMouseLeaveBar = () => {
200
222
  if (config.legend.highlightOnHover && config.legend.behavior === 'highlight') {
@@ -4,6 +4,7 @@ import { Group } from '@visx/group'
4
4
  import ConfigContext from '../../ConfigContext'
5
5
  import ErrorBoundary from '@cdc/core/components/ErrorBoundary'
6
6
  import { colorPalettesChart } from '@cdc/core/data/colorPalettes'
7
+ import { getCurrentPaletteName } from '@cdc/core/helpers/palettes/utils'
7
8
  import { handleTooltip, createPlots } from './helpers/index'
8
9
  import { APP_FONT_COLOR } from '@cdc/core/helpers/constants'
9
10
  import _ from 'lodash'
@@ -18,7 +19,7 @@ const BoxPlotVertical = ({ xScale, yScale, seriesScale }) => {
18
19
  const bodyStyles = getComputedStyle(document.body)
19
20
  const defaultColor = APP_FONT_COLOR
20
21
  const constrainedWidth = Math.min(40, boxWidth)
21
- const color_0 = _.get(colorPalettesChart, [config.palette, 0], '#000')
22
+ const color_0 = _.get(colorPalettesChart, [getCurrentPaletteName(config), 0], '#000')
22
23
  const plots = createPlots(data, config)
23
24
  return (
24
25
  <ErrorBoundary component='BoxPlot Vertical'>
@@ -1,6 +1,6 @@
1
1
  import { Line } from '@visx/shape'
2
2
  import { Group } from '@visx/group'
3
- import { useContext, useEffect, useRef, useState } from 'react'
3
+ import React, { useContext, useEffect, useRef, useState } from 'react'
4
4
  import ConfigContext from '../ConfigContext'
5
5
  import { Text } from '@visx/text'
6
6
  import ErrorBoundary from '@cdc/core/components/ErrorBoundary'
@@ -8,6 +8,7 @@ import useIntersectionObserver from '../hooks/useIntersectionObserver'
8
8
  import { getContrastColor } from '@cdc/core/helpers/cove/accessibility'
9
9
  import { APP_FONT_COLOR } from '@cdc/core/helpers/constants'
10
10
  import { getTextWidth } from '@cdc/core/helpers/getTextWidth'
11
+ import { isV1Palette } from '@cdc/core/helpers/palettes/utils'
11
12
 
12
13
  export default function DeviationBar({ height, xScale }) {
13
14
  const {
@@ -26,10 +27,10 @@ export default function DeviationBar({ height, xScale }) {
26
27
  roundingStyle === 'standard'
27
28
  ? '8px'
28
29
  : roundingStyle === 'shallow'
29
- ? '5px'
30
- : roundingStyle === 'finger'
31
- ? '15px'
32
- : '0px'
30
+ ? '5px'
31
+ : roundingStyle === 'finger'
32
+ ? '15px'
33
+ : '0px'
33
34
  const isRounded = config.barStyle === 'rounded'
34
35
  const target = Number(config.xAxis.target)
35
36
  const seriesKey = config.series[0].dataKey
@@ -176,7 +177,8 @@ export default function DeviationBar({ height, xScale }) {
176
177
  const squareY = barY - barHeight / 2
177
178
  const borderRadius = applyRadius(barPosition)
178
179
  // colors
179
- const [leftColor, rightColor] = twoColorPalette[twoColor.palette]
180
+ let versionName = isV1Palette(config) ? 'v1' : 'v2'
181
+ const [leftColor, rightColor] = twoColorPalette?.[versionName]?.[twoColor.palette] || ['#1D6ABF', '#935586']
180
182
  const barColor = { left: leftColor, right: rightColor }
181
183
  const fill = getContrastColor(APP_FONT_COLOR, barColor[barPosition])
182
184
 
@@ -205,6 +207,7 @@ export default function DeviationBar({ height, xScale }) {
205
207
  y={barY}
206
208
  width={barWidth}
207
209
  height={barHeight}
210
+ onMouseEnter={() => {}}
208
211
  data-tooltip-html={tooltip}
209
212
  data-tooltip-id={`cdc-open-viz-tooltip-${config.runtime.uniqueId}`}
210
213
  tabIndex={-1}