@cdc/chart 4.25.11 → 4.26.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (77) hide show
  1. package/dist/cdcchart.js +38898 -40013
  2. package/examples/feature/pie/planet-pie-example-config.json +48 -2
  3. package/examples/private/DEV-12100.json +1303 -0
  4. package/examples/private/cat-y.json +1235 -0
  5. package/examples/private/data-points.json +228 -0
  6. package/examples/private/height.json +3915 -0
  7. package/examples/private/links.json +569 -0
  8. package/examples/private/quadrant.txt +30 -0
  9. package/examples/private/test-forecast.json +5510 -0
  10. package/examples/private/warming-stripe-test.json +2578 -0
  11. package/examples/private/warming-stripes.json +4763 -0
  12. package/examples/tech-adoption-with-links.json +560 -0
  13. package/index.html +15 -20
  14. package/package.json +5 -4
  15. package/preview.html +1616 -0
  16. package/src/CdcChartComponent.tsx +111 -75
  17. package/src/_stories/Chart.Regions.Categorical.stories.tsx +148 -0
  18. package/src/_stories/Chart.Regions.DateScale.stories.tsx +197 -0
  19. package/src/_stories/Chart.Regions.DateTimeScale.stories.tsx +297 -0
  20. package/src/_stories/Chart.stories.tsx +8 -0
  21. package/src/_stories/ChartBar.Editor.stories.tsx +11 -6
  22. package/src/_stories/ChartBrush.Editor.stories.tsx +295 -0
  23. package/src/_stories/ChartBrush.stories.tsx +50 -0
  24. package/src/_stories/ChartEditor.Editor.stories.tsx +3 -5
  25. package/src/_stories/TechAdoptionWithLinks.stories.tsx +27 -0
  26. package/src/_stories/_mock/brush_enabled.json +326 -0
  27. package/src/_stories/_mock/brush_mock.json +2 -69
  28. package/src/_stories/_mock/horizontal-bars-dynamic-y-axis.json +413 -0
  29. package/src/components/AreaChart/components/AreaChart.Stacked.jsx +1 -2
  30. package/src/components/Axis/Categorical.Axis.tsx +6 -7
  31. package/src/components/BarChart/components/BarChart.Horizontal.tsx +178 -24
  32. package/src/components/BarChart/components/BarChart.StackedHorizontal.tsx +3 -1
  33. package/src/components/BarChart/components/BarChart.StackedVertical.tsx +1 -0
  34. package/src/components/BarChart/components/BarChart.Vertical.tsx +6 -8
  35. package/src/components/BarChart/components/context.tsx +1 -0
  36. package/src/components/BarChart/helpers/useBarChart.ts +14 -2
  37. package/src/components/Brush/BrushSelector.tsx +1258 -0
  38. package/src/components/Brush/MiniChartPreview.tsx +283 -0
  39. package/src/components/DeviationBar.jsx +9 -7
  40. package/src/components/EditorPanel/EditorPanel.tsx +2711 -2586
  41. package/src/components/EditorPanel/components/Panels/Panel.ForestPlotSettings.tsx +56 -34
  42. package/src/components/EditorPanel/components/Panels/Panel.General.tsx +57 -30
  43. package/src/components/EditorPanel/components/Panels/Panel.PatternSettings.tsx +2 -0
  44. package/src/components/EditorPanel/components/Panels/Panel.SmallMultiples.tsx +30 -25
  45. package/src/components/EditorPanel/components/Panels/Panel.Visual.tsx +21 -27
  46. package/src/components/EditorPanel/useEditorPermissions.ts +31 -18
  47. package/src/components/Legend/Legend.tsx +3 -2
  48. package/src/components/Legend/helpers/createFormatLabels.tsx +151 -2
  49. package/src/components/Legend/helpers/index.ts +10 -6
  50. package/src/components/LinearChart.tsx +495 -430
  51. package/src/components/PairedBarChart.jsx +20 -3
  52. package/src/components/Regions/components/Regions.tsx +365 -122
  53. package/src/components/ScatterPlot/ScatterPlot.jsx +2 -2
  54. package/src/components/SmallMultiples/SmallMultipleTile.tsx +5 -1
  55. package/src/components/WarmingStripes/WarmingStripes.tsx +160 -0
  56. package/src/components/WarmingStripes/WarmingStripesGradientLegend.css +35 -0
  57. package/src/components/WarmingStripes/WarmingStripesGradientLegend.tsx +104 -0
  58. package/src/components/WarmingStripes/index.tsx +3 -0
  59. package/src/data/initial-state.js +3 -1
  60. package/src/helpers/calculateHorizontalBarCategoryLabelWidth.ts +57 -0
  61. package/src/helpers/getMinMax.ts +12 -7
  62. package/src/helpers/sizeHelpers.ts +0 -20
  63. package/src/helpers/smallMultiplesHelpers.ts +1 -1
  64. package/src/hooks/useChartHoverAnalytics.tsx +10 -9
  65. package/src/hooks/useScales.ts +11 -1
  66. package/src/hooks/useTooltip.tsx +31 -10
  67. package/src/scss/DataTable.scss +0 -4
  68. package/src/scss/main.scss +17 -3
  69. package/src/test/CdcChart.test.jsx +1 -1
  70. package/src/types/ChartConfig.ts +3 -0
  71. package/src/types/Label.ts +1 -0
  72. package/src/utils/analyticsTracking.ts +19 -0
  73. package/LICENSE +0 -201
  74. package/src/components/Brush/BrushChart.tsx +0 -128
  75. package/src/components/Brush/BrushController.tsx +0 -71
  76. package/src/components/Brush/types.tsx +0 -8
  77. package/src/components/BrushChart.tsx +0 -223
@@ -0,0 +1,283 @@
1
+ import React, { memo } from 'react'
2
+ import { LinePath, AreaClosed, AreaStack } from '@visx/shape'
3
+ import * as allCurves from '@visx/curve'
4
+ import { handleLineType } from '../../helpers/handleLineType'
5
+ import { approvedCurveTypes } from '@cdc/core/helpers/lineChartHelpers'
6
+
7
+ interface MiniChartPreviewProps {
8
+ series: any[]
9
+ tableData: any[]
10
+ dataKey: string
11
+ xScale: any
12
+ miniYScale: any
13
+ colorScale: any
14
+ config: any
15
+ xMax: number
16
+ }
17
+
18
+ const MiniChartPreview = memo<MiniChartPreviewProps>(
19
+ ({ series, tableData, dataKey, xScale, miniYScale, colorScale, config }) => {
20
+ if (!series || !series.length || !tableData || !tableData.length || !xScale || !miniYScale) {
21
+ return null
22
+ }
23
+
24
+ const bandwidth = xScale.bandwidth?.() || 0
25
+ const isBarChart = config?.visualizationType === 'Bar'
26
+ const isStacked = config?.visualizationSubType === 'stacked'
27
+ const isAreaChart = config?.visualizationType === 'Area Chart'
28
+
29
+ // For bar charts, render rectangles
30
+ if (isBarChart) {
31
+ const bars: React.ReactElement[] = []
32
+
33
+ tableData.forEach((d, i) => {
34
+ const xVal = xScale(d[dataKey])
35
+ if (xVal === undefined || isNaN(xVal)) {
36
+ return
37
+ }
38
+
39
+ // Calculate bar position and width
40
+ // For band scales, center the bar in the band
41
+ const x = bandwidth > 0 ? xVal + bandwidth / 2 : xVal
42
+ const barWidth = bandwidth > 0 ? Math.max(bandwidth * 0.85, 2) : 4 // 85% of bandwidth for wider bars
43
+
44
+ if (isStacked) {
45
+ // For stacked bars, render each series as a segment
46
+ // Calculate cumulative values starting from bottom (0)
47
+ let cumulativeValue = 0
48
+ const zeroY = miniYScale(0)
49
+
50
+ series.forEach((s, seriesIndex) => {
51
+ const value = parseFloat(d[s.dataKey])
52
+ if (isNaN(value) || value === 0) return
53
+
54
+ const seriesColor = colorScale?.(config.runtime.seriesLabels?.[s.dataKey] || s.dataKey) || '#666'
55
+
56
+ // Calculate the bottom and top of this segment
57
+ // For stacked bars, each segment sits on top of the previous one
58
+ const bottomY = miniYScale(cumulativeValue)
59
+ cumulativeValue += value
60
+ const topY = miniYScale(cumulativeValue)
61
+ const barHeight = Math.abs(topY - bottomY)
62
+ const y = Math.min(bottomY, topY)
63
+
64
+ // Ensure minimum visible height
65
+ if (barHeight < 0.5) return
66
+
67
+ bars.push(
68
+ <rect
69
+ key={`mini-bar-stacked-${i}-${seriesIndex}`}
70
+ x={x - barWidth / 2}
71
+ y={y}
72
+ width={barWidth}
73
+ height={barHeight}
74
+ fill={seriesColor}
75
+ fillOpacity={0.7}
76
+ pointerEvents='none'
77
+ />
78
+ )
79
+ })
80
+ } else {
81
+ // For grouped bars, render bars side by side
82
+ const seriesCount = series.length
83
+ const groupBarWidth = barWidth / seriesCount
84
+ const zeroY = miniYScale(0)
85
+
86
+ series.forEach((s, seriesIndex) => {
87
+ const value = parseFloat(d[s.dataKey])
88
+ if (isNaN(value)) return
89
+
90
+ const seriesColor = colorScale?.(config.runtime.seriesLabels?.[s.dataKey] || s.dataKey) || '#666'
91
+
92
+ // Calculate bar position and height
93
+ const valueY = miniYScale(value)
94
+ const barHeight = Math.abs(valueY - zeroY)
95
+ const barX = x - barWidth / 2 + seriesIndex * groupBarWidth
96
+ const y = Math.min(valueY, zeroY) // Top of bar (smaller Y value)
97
+
98
+ bars.push(
99
+ <rect
100
+ key={`mini-bar-grouped-${i}-${seriesIndex}`}
101
+ x={barX}
102
+ y={y}
103
+ width={groupBarWidth * 0.9} // Wider bars with small gap between them
104
+ height={barHeight}
105
+ fill={seriesColor}
106
+ fillOpacity={0.7}
107
+ pointerEvents='none'
108
+ />
109
+ )
110
+ })
111
+ }
112
+ })
113
+
114
+ return <>{bars}</>
115
+ }
116
+
117
+ // For stacked area charts, use AreaStack
118
+ // Note: If only one series, treat as non-stacked area chart
119
+ if (isAreaChart && isStacked) {
120
+ // Use the same keys order as the main chart
121
+ const seriesKeys =
122
+ config.runtime?.areaSeriesKeys?.map(s => s.dataKey) || series.map(s => s.dataKey).filter(Boolean)
123
+
124
+ if (seriesKeys.length === 0) {
125
+ return null
126
+ }
127
+
128
+ // If only one series, render as regular area chart instead of stacked
129
+ if (seriesKeys.length === 1) {
130
+ const s = series[0]
131
+ const seriesKey = s.dataKey
132
+ const seriesColor = colorScale?.(config.runtime.seriesLabels?.[seriesKey] || seriesKey) || '#666'
133
+ const curveType = config.stackedAreaChartLineType
134
+ ? allCurves[approvedCurveTypes[config.stackedAreaChartLineType]]
135
+ : allCurves.curveLinear
136
+
137
+ const validData = tableData.filter(d => {
138
+ const xVal = xScale(d[dataKey])
139
+ const yVal = parseFloat(d[s.dataKey])
140
+ return xVal !== undefined && !isNaN(yVal)
141
+ })
142
+
143
+ if (validData.length === 0) return null
144
+
145
+ const getX = d => xScale(d[dataKey]) + bandwidth / 2
146
+ const getY = d => miniYScale(parseFloat(d[s.dataKey]))
147
+
148
+ return (
149
+ <AreaClosed
150
+ key={`mini-area-single-${seriesKey}`}
151
+ data={validData}
152
+ x={getX}
153
+ y={getY}
154
+ yScale={miniYScale}
155
+ fill={seriesColor}
156
+ fillOpacity={1}
157
+ stroke={seriesColor}
158
+ strokeWidth={2}
159
+ curve={curveType}
160
+ pointerEvents='none'
161
+ />
162
+ )
163
+ }
164
+
165
+ const curveType = config.stackedAreaChartLineType
166
+ ? allCurves[approvedCurveTypes[config.stackedAreaChartLineType]]
167
+ : allCurves.curveLinear
168
+
169
+ // Filter data to only include valid entries
170
+ const validData = tableData.filter(d => {
171
+ const xVal = xScale(d[dataKey])
172
+ return xVal !== undefined
173
+ })
174
+
175
+ if (validData.length === 0) {
176
+ return null
177
+ }
178
+
179
+ return (
180
+ <AreaStack
181
+ data={validData}
182
+ keys={seriesKeys}
183
+ x0={d => {
184
+ // AreaStack transforms data, so d has a 'data' property with the original data point
185
+ const xVal = xScale(d.data[dataKey])
186
+ return xVal !== undefined ? xVal + bandwidth / 2 : 0
187
+ }}
188
+ y0={d => Number(miniYScale(d[0]))}
189
+ y1={d => Number(miniYScale(d[1]))}
190
+ curve={curveType}
191
+ >
192
+ {({ stacks, path }) => {
193
+ if (!stacks || stacks.length === 0) {
194
+ return null
195
+ }
196
+ // Don't reverse - render in the same order as main chart
197
+ // The main chart doesn't reverse, so we should match that
198
+ return stacks.map(stack => {
199
+ const seriesKey = stack.key
200
+ const seriesColor = colorScale?.(config.runtime.seriesLabels?.[seriesKey] || seriesKey) || '#666'
201
+ const pathData = path(stack)
202
+ if (!pathData) return null
203
+ return (
204
+ <path
205
+ key={`mini-area-stacked-${seriesKey}`}
206
+ d={pathData}
207
+ fill={seriesColor}
208
+ fillOpacity={1}
209
+ stroke={seriesColor}
210
+ strokeWidth={2}
211
+ pointerEvents='none'
212
+ />
213
+ )
214
+ })
215
+ }}
216
+ </AreaStack>
217
+ )
218
+ }
219
+
220
+ // For line/area charts, render lines or areas
221
+ return (
222
+ <>
223
+ {series.map((s, i) => {
224
+ const seriesKey = s.dataKey
225
+ const seriesColor = colorScale?.(config.runtime.seriesLabels?.[seriesKey] || seriesKey) || '#666'
226
+
227
+ // Get series-specific styling
228
+ const seriesWeight = s.weight || 2
229
+ const seriesLineType = s.lineType || 'curveLinear'
230
+ const seriesStyle = s.type || 'solid'
231
+ const curve = allCurves[seriesLineType] || allCurves.curveLinear
232
+ const strokeDasharray = handleLineType(seriesStyle)
233
+
234
+ // Filter to only valid data points
235
+ const validData = tableData.filter(d => {
236
+ const xVal = xScale(d[dataKey])
237
+ const yVal = parseFloat(d[s.dataKey])
238
+ return xVal !== undefined && !isNaN(yVal)
239
+ })
240
+
241
+ if (validData.length === 0) return null
242
+
243
+ const getX = d => xScale(d[dataKey]) + bandwidth / 2
244
+ const getY = d => miniYScale(parseFloat(d[s.dataKey]))
245
+
246
+ return isAreaChart ? (
247
+ <AreaClosed
248
+ key={`mini-area-${seriesKey}-${i}`}
249
+ data={validData}
250
+ x={getX}
251
+ y={getY}
252
+ yScale={miniYScale}
253
+ fill={seriesColor}
254
+ fillOpacity={1}
255
+ stroke={seriesColor}
256
+ strokeWidth={seriesWeight}
257
+ strokeDasharray={strokeDasharray}
258
+ curve={curve}
259
+ pointerEvents='none'
260
+ />
261
+ ) : (
262
+ <LinePath
263
+ key={`mini-line-${seriesKey}-${i}`}
264
+ data={validData}
265
+ x={getX}
266
+ y={getY}
267
+ stroke={seriesColor}
268
+ strokeWidth={seriesWeight}
269
+ strokeDasharray={strokeDasharray}
270
+ strokeOpacity={0.8}
271
+ curve={curve}
272
+ pointerEvents='none'
273
+ />
274
+ )
275
+ })}
276
+ </>
277
+ )
278
+ }
279
+ )
280
+
281
+ MiniChartPreview.displayName = 'MiniChartPreview'
282
+
283
+ export default MiniChartPreview
@@ -9,6 +9,7 @@ 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
11
  import { isV1Palette } from '@cdc/core/helpers/palettes/utils'
12
+ import { isMobileFontViewport } from '@cdc/core/helpers/viewports'
12
13
 
13
14
  export default function DeviationBar({ height, xScale }) {
14
15
  const {
@@ -18,19 +19,20 @@ export default function DeviationBar({ height, xScale }) {
18
19
  twoColorPalette,
19
20
  parseDate,
20
21
  formatDate,
21
- currentViewport
22
+ vizViewport
22
23
  } = useContext(ConfigContext)
23
24
  const { barStyle, tipRounding, roundingStyle, twoColor } = config
25
+ const labelFontSize = isMobileFontViewport(vizViewport) ? 13 : 16
24
26
  const barRefs = useRef([])
25
27
  const [windowWidth, setWindowWidth] = useState(window.innerWidth)
26
28
  const radius =
27
29
  roundingStyle === 'standard'
28
30
  ? '8px'
29
31
  : roundingStyle === 'shallow'
30
- ? '5px'
31
- : roundingStyle === 'finger'
32
- ? '15px'
33
- : '0px'
32
+ ? '5px'
33
+ : roundingStyle === 'finger'
34
+ ? '15px'
35
+ : '0px'
34
36
  const isRounded = config.barStyle === 'rounded'
35
37
  const target = Number(config.xAxis.target)
36
38
  const seriesKey = config.series[0].dataKey
@@ -165,7 +167,7 @@ export default function DeviationBar({ height, xScale }) {
165
167
  config.heights.horizontal = totalheight
166
168
 
167
169
  // text,labels postiions
168
- const textWidth = getTextWidth(formatNumber(barValue, 'left'), `normal ${16}px sans-serif`)
170
+ const textWidth = getTextWidth(formatNumber(barValue, 'left'), `normal ${labelFontSize}px sans-serif`)
169
171
  const textFits = textWidth < barWidth - 6
170
172
  const textX = barBaseX
171
173
  const textY = barY + barHeight / 2
@@ -223,7 +225,7 @@ export default function DeviationBar({ height, xScale }) {
223
225
  ></div>
224
226
  </foreignObject>
225
227
  {config.yAxis.displayNumbersOnBar && (
226
- <Text verticalAnchor='middle' x={textX} y={textY} {...textProps[barPosition]}>
228
+ <Text verticalAnchor='middle' x={textX} y={textY} {...textProps[barPosition]} fontSize={labelFontSize}>
227
229
  {formatNumber(d[seriesKey], 'left')}
228
230
  </Text>
229
231
  )}