@cdc/chart 4.25.10 → 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 (135) hide show
  1. package/dist/{cdcchart-1a1724a1.es.js → cdcchart-dgT_1dIT.es.js} +136 -151
  2. package/dist/cdcchart.js +44003 -43518
  3. package/examples/feature/__data__/planet-example-data.json +1 -1
  4. package/examples/feature/boxplot/valid-boxplot.csv +38 -17
  5. package/examples/feature/pie/planet-pie-example-config.json +48 -2
  6. package/examples/private/DEV-11825.json +573 -0
  7. package/examples/private/DEV-12100.json +1303 -0
  8. package/examples/private/cat-y.json +1235 -0
  9. package/examples/private/data-points.json +228 -0
  10. package/examples/private/height.json +3915 -0
  11. package/examples/private/links.json +569 -0
  12. package/examples/private/na.json +913 -0
  13. package/examples/private/quadrant.txt +30 -0
  14. package/examples/private/test-data.csv +28 -0
  15. package/examples/private/test-forecast.json +5510 -0
  16. package/examples/private/warming-stripe-test.json +2578 -0
  17. package/examples/private/warming-stripes.json +4763 -0
  18. package/examples/tech-adoption-with-links.json +560 -0
  19. package/index.html +16 -140
  20. package/package.json +6 -5
  21. package/preview.html +1616 -0
  22. package/src/CdcChart.tsx +8 -11
  23. package/src/CdcChartComponent.tsx +329 -124
  24. package/src/_stories/Chart.Combo.stories.tsx +18 -0
  25. package/src/_stories/Chart.Forecast.stories.tsx +36 -0
  26. package/src/_stories/Chart.HTMLInDataTable.stories.tsx +520 -0
  27. package/src/_stories/Chart.Patterns.stories.tsx +2 -1
  28. package/src/_stories/Chart.PreserveDecimals.stories.tsx +220 -0
  29. package/src/_stories/Chart.Regions.Categorical.stories.tsx +148 -0
  30. package/src/_stories/Chart.Regions.DateScale.stories.tsx +197 -0
  31. package/src/_stories/Chart.Regions.DateTimeScale.stories.tsx +297 -0
  32. package/src/_stories/Chart.SmallMultiples.stories.tsx +47 -0
  33. package/src/_stories/Chart.stories.tsx +8 -0
  34. package/src/_stories/ChartAnnotation.stories.tsx +6 -3
  35. package/src/_stories/ChartBar.Editor.stories.tsx +3585 -0
  36. package/src/_stories/ChartBrush.Editor.stories.tsx +295 -0
  37. package/src/_stories/ChartBrush.stories.tsx +50 -0
  38. package/src/_stories/ChartEditor.Editor.stories.tsx +656 -0
  39. package/src/_stories/ChartEditor.stories.tsx +1 -2
  40. package/src/_stories/TechAdoptionWithLinks.stories.tsx +27 -0
  41. package/src/_stories/_mock/brush_enabled.json +326 -0
  42. package/src/_stories/_mock/brush_mock.json +2 -69
  43. package/src/_stories/_mock/combo.json +451 -0
  44. package/src/_stories/_mock/editor-test-configs.json +376 -0
  45. package/src/_stories/_mock/editor-test-datasets.json +477 -0
  46. package/src/_stories/_mock/editor-tests/bar-chart-editor-test.json +255 -0
  47. package/src/_stories/_mock/editor-tests/bar-chart-general-test.json +267 -0
  48. package/src/_stories/_mock/editor-tests/bar-chart-test.json +237 -0
  49. package/src/_stories/_mock/forecast_combo_with_gaps.json +913 -0
  50. package/src/_stories/_mock/horizontal-bars-dynamic-y-axis.json +413 -0
  51. package/src/_stories/_mock/pie_config.json +257 -62
  52. package/src/_stories/_mock/small_multiples/small_multiples_bars.json +1944 -0
  53. package/src/_stories/_mock/small_multiples/small_multiples_big_data_bars.json +1114 -0
  54. package/src/_stories/_mock/small_multiples/small_multiples_lines.json +2646 -0
  55. package/src/_stories/_mock/small_multiples/small_multiples_lines_colors.json +1305 -0
  56. package/src/_stories/_mock/small_multiples/small_multiples_stacked_bars.json +1936 -0
  57. package/src/components/Annotations/components/findNearestDatum.ts +6 -41
  58. package/src/components/AreaChart/components/AreaChart.Stacked.jsx +10 -7
  59. package/src/components/AreaChart/index.tsx +1 -2
  60. package/src/components/Axis/Categorical.Axis.tsx +6 -7
  61. package/src/components/BarChart/components/BarChart.Horizontal.tsx +181 -27
  62. package/src/components/BarChart/components/BarChart.StackedHorizontal.tsx +3 -1
  63. package/src/components/BarChart/components/BarChart.StackedVertical.tsx +1 -0
  64. package/src/components/BarChart/components/BarChart.Vertical.tsx +8 -9
  65. package/src/components/BarChart/components/context.tsx +1 -0
  66. package/src/components/BarChart/helpers/useBarChart.ts +14 -2
  67. package/src/components/BoxPlot/helpers/index.ts +3 -3
  68. package/src/components/Brush/BrushSelector.tsx +1258 -0
  69. package/src/components/Brush/MiniChartPreview.tsx +283 -0
  70. package/src/components/DeviationBar.jsx +9 -7
  71. package/src/components/EditorPanel/EditorPanel.tsx +2720 -2586
  72. package/src/components/EditorPanel/components/Panels/Panel.Annotate.tsx +96 -111
  73. package/src/components/EditorPanel/components/Panels/Panel.ForestPlotSettings.tsx +56 -34
  74. package/src/components/EditorPanel/components/Panels/Panel.General.tsx +76 -31
  75. package/src/components/EditorPanel/components/Panels/Panel.PatternSettings.tsx +104 -55
  76. package/src/components/EditorPanel/components/Panels/Panel.Series.tsx +54 -49
  77. package/src/components/EditorPanel/components/Panels/Panel.SmallMultiples.tsx +427 -0
  78. package/src/components/EditorPanel/components/Panels/Panel.Visual.tsx +96 -48
  79. package/src/components/EditorPanel/components/Panels/index.tsx +3 -1
  80. package/src/components/EditorPanel/editor-panel.scss +0 -20
  81. package/src/components/EditorPanel/useEditorPermissions.ts +36 -31
  82. package/src/components/Forecasting/Forecasting.tsx +139 -21
  83. package/src/components/Legend/Legend.Component.tsx +16 -9
  84. package/src/components/Legend/Legend.tsx +3 -2
  85. package/src/components/Legend/helpers/createFormatLabels.tsx +325 -176
  86. package/src/components/Legend/helpers/getLegendClasses.ts +0 -1
  87. package/src/components/Legend/helpers/index.ts +10 -6
  88. package/src/components/LineChart/LineChartProps.ts +0 -3
  89. package/src/components/LineChart/helpers.ts +1 -1
  90. package/src/components/LineChart/index.tsx +36 -13
  91. package/src/components/LinearChart.tsx +559 -499
  92. package/src/components/PairedBarChart.jsx +20 -3
  93. package/src/components/Regions/components/Regions.tsx +366 -144
  94. package/src/components/Sankey/types/index.ts +1 -1
  95. package/src/components/ScatterPlot/ScatterPlot.jsx +2 -2
  96. package/src/components/SmallMultiples/SmallMultipleTile.tsx +202 -0
  97. package/src/components/SmallMultiples/SmallMultiples.css +32 -0
  98. package/src/components/SmallMultiples/SmallMultiples.tsx +271 -0
  99. package/src/components/SmallMultiples/index.ts +2 -0
  100. package/src/components/WarmingStripes/WarmingStripes.tsx +160 -0
  101. package/src/components/WarmingStripes/WarmingStripesGradientLegend.css +35 -0
  102. package/src/components/WarmingStripes/WarmingStripesGradientLegend.tsx +104 -0
  103. package/src/components/WarmingStripes/index.tsx +3 -0
  104. package/src/data/initial-state.js +16 -2
  105. package/src/helpers/buildForecastPaletteOptions.ts +0 -38
  106. package/src/helpers/calculateHorizontalBarCategoryLabelWidth.ts +57 -0
  107. package/src/helpers/getColorScale.ts +10 -0
  108. package/src/{hooks/useMinMax.ts → helpers/getMinMax.ts} +26 -14
  109. package/src/helpers/getYAxisAutoPadding.ts +53 -0
  110. package/src/helpers/sizeHelpers.ts +0 -20
  111. package/src/helpers/smallMultiplesHelpers.ts +529 -0
  112. package/src/hooks/useChartHoverAnalytics.tsx +10 -9
  113. package/src/hooks/useProgrammaticTooltip.ts +96 -0
  114. package/src/hooks/useScales.ts +98 -34
  115. package/src/hooks/useSmallMultipleSynchronization.ts +59 -0
  116. package/src/hooks/useTooltip.tsx +91 -25
  117. package/src/scss/DataTable.scss +0 -4
  118. package/src/scss/main.scss +18 -83
  119. package/src/store/chart.actions.ts +2 -0
  120. package/src/store/chart.reducer.ts +4 -0
  121. package/src/test/CdcChart.test.jsx +1 -1
  122. package/src/types/ChartConfig.ts +27 -6
  123. package/src/types/ChartContext.ts +3 -0
  124. package/src/types/Label.ts +1 -0
  125. package/src/utils/analyticsTracking.ts +19 -0
  126. package/LICENSE +0 -201
  127. package/src/_stories/_mock/pie_data.json +0 -218
  128. package/src/components/AreaChart/components/AreaChart.jsx +0 -109
  129. package/src/components/Brush/BrushChart.tsx +0 -128
  130. package/src/components/Brush/BrushController.tsx +0 -71
  131. package/src/components/Brush/types.tsx +0 -8
  132. package/src/components/BrushChart.tsx +0 -223
  133. package/src/helpers/sort.ts +0 -7
  134. package/src/hooks/useActiveElement.js +0 -19
  135. package/src/hooks/useChartClasses.js +0 -41
@@ -0,0 +1,427 @@
1
+ import { useContext, FC, useMemo } from 'react'
2
+ import { DragDropContext, Droppable, Draggable } from '@hello-pangea/dnd'
3
+ import {
4
+ AccordionItem,
5
+ AccordionItemHeading,
6
+ AccordionItemPanel,
7
+ AccordionItemButton
8
+ } from 'react-accessible-accordion'
9
+
10
+ // core
11
+ import { TextField, Select, CheckBox } from '@cdc/core/components/EditorPanel/Inputs'
12
+ import Tooltip from '@cdc/core/components/ui/Tooltip'
13
+ import Icon from '@cdc/core/components/ui/Icon'
14
+ import { useDataColumns } from '@cdc/core/hooks/useDataColumns'
15
+
16
+ // contexts
17
+ import { ChartContext } from './../../../../types/ChartContext.js'
18
+ import { useEditorPermissions } from '../../useEditorPermissions.js'
19
+ import { useEditorPanelContext } from '../../EditorPanelContext.js'
20
+ import ConfigContext from '../../../../ConfigContext.js'
21
+ import { PanelProps } from '../PanelProps'
22
+ import { getTileKeys } from '../../../../helpers/smallMultiplesHelpers'
23
+
24
+ const PanelSmallMultiples: FC<PanelProps> = props => {
25
+ const { config, rawData, updateConfig } = useContext<ChartContext>(ConfigContext)
26
+ const { updateField } = useEditorPanelContext()
27
+ const { visSupportsSmallMultiples } = useEditorPermissions()
28
+
29
+ // Extract column names from data with memoization (replaces getColumns)
30
+ const allColumns = useDataColumns(rawData)
31
+
32
+ // Filter out series columns and confidence key columns (except lower and upper)
33
+ const filteredColumns = useMemo(() => {
34
+ const { lower, upper } = config.confidenceKeys || {}
35
+ return allColumns.filter(key => {
36
+ // Filter out series columns
37
+ if (config.series && config.series.some(series => series.dataKey === key)) {
38
+ return false
39
+ }
40
+ // Filter out confidence key columns (except lower and upper)
41
+ if (
42
+ config.confidenceKeys &&
43
+ Object.keys(config.confidenceKeys).includes(key) &&
44
+ ((lower && upper) || lower || upper) &&
45
+ key !== lower &&
46
+ key !== upper
47
+ ) {
48
+ return false
49
+ }
50
+ return true
51
+ })
52
+ }, [allColumns, config.series, config.confidenceKeys])
53
+
54
+ return (
55
+ <>
56
+ {visSupportsSmallMultiples() && (
57
+ <AccordionItem>
58
+ <AccordionItemHeading>
59
+ <AccordionItemButton>Small Multiples</AccordionItemButton>
60
+ </AccordionItemHeading>
61
+ <AccordionItemPanel>
62
+ <Select
63
+ value={config.smallMultiples?.mode || ''}
64
+ fieldName='mode'
65
+ section='smallMultiples'
66
+ label='Tile Mode'
67
+ initial='Select Mode'
68
+ updateField={updateField}
69
+ options={[
70
+ { label: 'By data series', value: 'by-series' },
71
+ { label: 'By column values', value: 'by-column' }
72
+ ]}
73
+ tooltip={
74
+ <Tooltip style={{ textTransform: 'none' }}>
75
+ <Tooltip.Target>
76
+ <Icon display='question' style={{ marginLeft: '0.5rem' }} />
77
+ </Tooltip.Target>
78
+ <Tooltip.Content>
79
+ <p>
80
+ Choose how to create multiple charts. "By Data Series" creates a tile for each configured data
81
+ series. "By Column Values" creates a tile for each unique value in the selected column.
82
+ </p>
83
+ </Tooltip.Content>
84
+ </Tooltip>
85
+ }
86
+ />
87
+ {config.smallMultiples?.mode === 'by-column' && (
88
+ <Select
89
+ value={config.smallMultiples?.tileColumn || ''}
90
+ fieldName='tileColumn'
91
+ section='smallMultiples'
92
+ label='Tile By Column'
93
+ initial='Select Column'
94
+ updateField={updateField}
95
+ options={filteredColumns}
96
+ />
97
+ )}
98
+
99
+ {config.smallMultiples?.mode && (
100
+ <>
101
+ <TextField
102
+ type='number'
103
+ value={config.smallMultiples?.tilesPerRowDesktop}
104
+ section='smallMultiples'
105
+ fieldName='tilesPerRowDesktop'
106
+ label='Tiles Per Row (Desktop)'
107
+ updateField={updateField}
108
+ min={1}
109
+ max={3}
110
+ tooltip={
111
+ <Tooltip style={{ textTransform: 'none' }}>
112
+ <Tooltip.Target>
113
+ <Icon display='question' style={{ marginLeft: '0.5rem' }} />
114
+ </Tooltip.Target>
115
+ <Tooltip.Content>
116
+ <p>
117
+ Number of chart tiles to display per row on desktop screens. Mobile will always show 1 tile
118
+ per row.
119
+ </p>
120
+ </Tooltip.Content>
121
+ </Tooltip>
122
+ }
123
+ />
124
+
125
+ {/* Tile Ordering */}
126
+ {(() => {
127
+ const availableTiles = getTileKeys(config, rawData)
128
+ if (availableTiles.length === 0) return null
129
+
130
+ const tileOrderOptions = [
131
+ {
132
+ label: 'Ascending By Title',
133
+ value: 'asc'
134
+ },
135
+ {
136
+ label: 'Descending By Title',
137
+ value: 'desc'
138
+ },
139
+ {
140
+ label: 'Custom',
141
+ value: 'custom'
142
+ }
143
+ ]
144
+
145
+ const currentOrderType = config.smallMultiples?.tileOrderType || 'asc'
146
+
147
+ const handleOrderTypeChange = orderType => {
148
+ const newConfig = {
149
+ ...config,
150
+ smallMultiples: {
151
+ ...config.smallMultiples,
152
+ tileOrderType: orderType
153
+ }
154
+ }
155
+
156
+ // If switching to custom, initialize with current tile order
157
+ if (orderType === 'custom' && !config.smallMultiples?.tileOrder?.length) {
158
+ newConfig.smallMultiples.tileOrder = [...availableTiles]
159
+ }
160
+
161
+ updateConfig(newConfig)
162
+ }
163
+
164
+ const handleCustomTileOrderChange = (sourceIndex, destinationIndex) => {
165
+ if (destinationIndex === null) return
166
+
167
+ const currentOrder = config.smallMultiples?.tileOrder || [...availableTiles]
168
+ const newOrder = [...currentOrder]
169
+ const [removed] = newOrder.splice(sourceIndex, 1)
170
+ newOrder.splice(destinationIndex, 0, removed)
171
+
172
+ updateConfig({
173
+ ...config,
174
+ smallMultiples: {
175
+ ...config.smallMultiples,
176
+ tileOrder: newOrder,
177
+ tileOrderType: 'custom'
178
+ }
179
+ })
180
+ }
181
+
182
+ return (
183
+ <>
184
+ <Select
185
+ value={currentOrderType}
186
+ options={tileOrderOptions}
187
+ label='Tile Order'
188
+ fieldName='tileOrderType'
189
+ section='smallMultiples'
190
+ updateField={(_section, _subsection, _fieldName, value) => {
191
+ handleOrderTypeChange(value)
192
+ }}
193
+ />
194
+
195
+ {currentOrderType === 'custom' && (
196
+ <DragDropContext
197
+ onDragEnd={({ source, destination }) =>
198
+ handleCustomTileOrderChange(source.index, destination?.index)
199
+ }
200
+ >
201
+ <Droppable droppableId='tile_order'>
202
+ {provided => (
203
+ <ul
204
+ {...provided.droppableProps}
205
+ className='sort-list'
206
+ ref={provided.innerRef}
207
+ style={{ marginTop: '1em' }}
208
+ >
209
+ {(config.smallMultiples?.tileOrder || availableTiles).map((tileKey, index) => (
210
+ <Draggable key={tileKey} draggableId={`tile-${tileKey}`} index={index}>
211
+ {(provided, snapshot) => (
212
+ <li>
213
+ <div
214
+ className={snapshot.isDragging ? 'currently-dragging' : ''}
215
+ style={provided.draggableProps.style}
216
+ ref={provided.innerRef}
217
+ {...provided.draggableProps}
218
+ {...provided.dragHandleProps}
219
+ >
220
+ {tileKey}
221
+ </div>
222
+ </li>
223
+ )}
224
+ </Draggable>
225
+ ))}
226
+ {provided.placeholder}
227
+ </ul>
228
+ )}
229
+ </Droppable>
230
+ </DragDropContext>
231
+ )}
232
+ </>
233
+ )
234
+ })()}
235
+
236
+ {/* Color Mode */}
237
+ <Select
238
+ value={config.smallMultiples?.colorMode || 'different'}
239
+ options={[
240
+ {
241
+ label: 'Same Color',
242
+ value: 'same'
243
+ },
244
+ {
245
+ label: 'Different Colors',
246
+ value: 'different'
247
+ }
248
+ ]}
249
+ label='Color Mode'
250
+ fieldName='colorMode'
251
+ section='smallMultiples'
252
+ updateField={(_section, _subsection, _fieldName, value) => {
253
+ updateConfig({
254
+ ...config,
255
+ smallMultiples: {
256
+ ...config.smallMultiples,
257
+ colorMode: value
258
+ }
259
+ })
260
+ }}
261
+ tooltip={
262
+ <Tooltip style={{ textTransform: 'none' }}>
263
+ <Tooltip.Target>
264
+ <Icon display='question' style={{ marginLeft: '0.5rem' }} />
265
+ </Tooltip.Target>
266
+ <Tooltip.Content>
267
+ <p>
268
+ When "Different Colors" is selected, each tile will use the next color in the configured color
269
+ palette.
270
+ </p>
271
+ </Tooltip.Content>
272
+ </Tooltip>
273
+ }
274
+ />
275
+
276
+ {/* Custom Tile Titles - only show for by-column mode */}
277
+ {config.smallMultiples?.mode === 'by-column' && (
278
+ <div>
279
+ <label style={{ marginTop: '1.5rem', marginBottom: '0.5rem' }}>Custom Tile Titles</label>
280
+
281
+ {(() => {
282
+ const availableTiles = getTileKeys(config, rawData)
283
+ if (availableTiles.length === 0) return null
284
+
285
+ const handleTitleChange = (tileKey, customTitle) => {
286
+ const newTitles = { ...config.smallMultiples?.tileTitles }
287
+ if (customTitle.trim() === '' || customTitle === tileKey) {
288
+ delete newTitles[tileKey] // Remove entry if empty or same as key
289
+ } else {
290
+ newTitles[tileKey] = customTitle
291
+ }
292
+
293
+ updateConfig({
294
+ ...config,
295
+ smallMultiples: {
296
+ ...config.smallMultiples,
297
+ tileTitles: newTitles
298
+ }
299
+ })
300
+ }
301
+
302
+ return (
303
+ <div className='tile-titles-editor' style={{ maxWidth: '100%', overflow: 'hidden' }}>
304
+ {availableTiles.map(tileKey => {
305
+ const customTitle = config.smallMultiples?.tileTitles?.[tileKey] || ''
306
+ return (
307
+ <div
308
+ key={tileKey}
309
+ className='tile-title-row'
310
+ style={{
311
+ display: 'flex',
312
+ alignItems: 'center',
313
+ marginBottom: '0.75rem',
314
+ maxWidth: '100%'
315
+ }}
316
+ >
317
+ <label
318
+ style={{
319
+ minWidth: '80px',
320
+ maxWidth: '120px',
321
+ marginRight: '0.75rem',
322
+ fontWeight: 'normal',
323
+ fontSize: '13px',
324
+ overflow: 'hidden',
325
+ textOverflow: 'ellipsis',
326
+ whiteSpace: 'nowrap',
327
+ flexShrink: 0
328
+ }}
329
+ >
330
+ {tileKey}:
331
+ </label>
332
+ <input
333
+ type='text'
334
+ value={customTitle}
335
+ placeholder={tileKey}
336
+ onChange={event => handleTitleChange(tileKey, event.target.value)}
337
+ style={{
338
+ flex: 1,
339
+ minWidth: 0,
340
+ maxWidth: '200px',
341
+ fontSize: '13px',
342
+ padding: '4px 8px',
343
+ height: '30px',
344
+ border: '1px solid #ccc',
345
+ borderRadius: '3px'
346
+ }}
347
+ />
348
+ </div>
349
+ )
350
+ })}
351
+ </div>
352
+ )
353
+ })()}
354
+ </div>
355
+ )}
356
+
357
+ <CheckBox
358
+ value={config.smallMultiples?.independentYAxis}
359
+ section='smallMultiples'
360
+ fieldName='independentYAxis'
361
+ label='Independent Y-Axis Scales'
362
+ updateField={updateField}
363
+ tooltip={
364
+ <Tooltip style={{ textTransform: 'none' }}>
365
+ <Tooltip.Target>
366
+ <Icon display='question' style={{ marginLeft: '0.5rem' }} />
367
+ </Tooltip.Target>
368
+ <Tooltip.Content>
369
+ <p>
370
+ When checked, the y-axis scale for each tile will be calculated separately. The chart's y-axis
371
+ min/max will override this setting if they are configured.
372
+ </p>
373
+ </Tooltip.Content>
374
+ </Tooltip>
375
+ }
376
+ />
377
+
378
+ <CheckBox
379
+ value={config.smallMultiples?.synchronizedTooltips}
380
+ fieldName='synchronizedTooltips'
381
+ section='smallMultiples'
382
+ label='Synchronized Tooltips'
383
+ updateField={updateField}
384
+ tooltip={
385
+ <Tooltip style={{ textTransform: 'none' }}>
386
+ <Tooltip.Target>
387
+ <Icon display='question' style={{ marginLeft: '0.5rem' }} />
388
+ </Tooltip.Target>
389
+ <Tooltip.Content>
390
+ <p>
391
+ When checked, hovering over one chart will show synchronized tooltips on all other charts at
392
+ the same data point.
393
+ </p>
394
+ </Tooltip.Content>
395
+ </Tooltip>
396
+ }
397
+ />
398
+
399
+ {config.visualizationType === 'Line' && (
400
+ <CheckBox
401
+ value={config.smallMultiples?.showAreaUnderLine}
402
+ fieldName='showAreaUnderLine'
403
+ section='smallMultiples'
404
+ label='Shade Area Under Lines'
405
+ updateField={updateField}
406
+ tooltip={
407
+ <Tooltip style={{ textTransform: 'none' }}>
408
+ <Tooltip.Target>
409
+ <Icon display='question' style={{ marginLeft: '0.5rem' }} />
410
+ </Tooltip.Target>
411
+ <Tooltip.Content>
412
+ <p>When checked, each tile chart will display a shaded area underneath the line.</p>
413
+ </Tooltip.Content>
414
+ </Tooltip>
415
+ }
416
+ />
417
+ )}
418
+ </>
419
+ )}
420
+ </AccordionItemPanel>
421
+ </AccordionItem>
422
+ )}
423
+ </>
424
+ )
425
+ }
426
+
427
+ export default PanelSmallMultiples
@@ -20,6 +20,7 @@ import InputToggle from '@cdc/core/components/inputs/InputToggle'
20
20
  import { useColorPalette } from '@cdc/core/hooks/useColorPalette'
21
21
  import { getCurrentPaletteName } from '@cdc/core/helpers/palettes/utils'
22
22
  import { getColorPaletteVersion } from '@cdc/core/helpers/getColorPaletteVersion'
23
+ import { isCoveDeveloperMode } from '@cdc/core/helpers/queryStringUtils'
23
24
  import { ChartContext } from './../../../../types/ChartContext.js'
24
25
 
25
26
  import { useEditorPermissions } from '../../useEditorPermissions.js'
@@ -28,6 +29,10 @@ import ConfigContext from '../../../../ConfigContext.js'
28
29
  import { PanelProps } from '../PanelProps'
29
30
  import { LineChartConfig } from '../../../../types/ChartConfig'
30
31
  import { PaletteSelector, DeveloperPaletteRollback } from '@cdc/core/components/PaletteSelector'
32
+ import { HeaderThemeSelector } from '@cdc/core/components/HeaderThemeSelector'
33
+ import { CustomColorsEditor } from '@cdc/core/components/CustomColorsEditor'
34
+ import { getColorScale } from '../../../../helpers/getColorScale'
35
+ import '@cdc/core/styles/v2/components/editor.scss'
31
36
  import './panelVisual.styles.css'
32
37
 
33
38
  const PanelVisual: FC<PanelProps> = props => {
@@ -40,7 +45,6 @@ const PanelVisual: FC<PanelProps> = props => {
40
45
  visHasBarBorders,
41
46
  visCanAnimate,
42
47
  visSupportsNonSequentialPallete,
43
- headerColors,
44
48
  visSupportsTooltipOpacity,
45
49
  visSupportsTooltipLines,
46
50
  visSupportsBarSpace,
@@ -86,7 +90,7 @@ const PanelVisual: FC<PanelProps> = props => {
86
90
  }
87
91
 
88
92
  return (
89
- <AccordionItem>
93
+ <AccordionItem className='panel-visual'>
90
94
  <AccordionItemHeading>
91
95
  <AccordionItemButton>Visual</AccordionItemButton>
92
96
  </AccordionItemHeading>
@@ -247,24 +251,7 @@ const PanelVisual: FC<PanelProps> = props => {
247
251
  />
248
252
  </>
249
253
  )}
250
- {/* eslint-disable */}
251
- <label className='header'>
252
- <span className='edit-label'>Header Theme</span>
253
- <ul className='color-palette'>
254
- {headerColors.map(palette => (
255
- <button
256
- title={palette}
257
- key={palette}
258
- onClick={e => {
259
- e.preventDefault()
260
- updateConfig({ ...config, theme: palette })
261
- }}
262
- className={config.theme === palette ? 'selected ' + palette : palette}
263
- ></button>
264
- ))}
265
- </ul>
266
- </label>
267
- {/* eslint-enable */}
254
+ <HeaderThemeSelector selectedTheme={config.theme} onThemeSelect={theme => updateConfig({ ...config, theme })} />
268
255
  {(visSupportsNonSequentialPallete() || visSupportsNonSequentialPallete()) && (
269
256
  <>
270
257
  <label>
@@ -332,16 +319,89 @@ const PanelVisual: FC<PanelProps> = props => {
332
319
  colorIndices={[2, 4, 6]}
333
320
  className='color-palette'
334
321
  />
335
- <span>Colorblind Safe</span>
336
- <PaletteSelector
337
- palettes={accessibleColors}
338
- colorPalettes={colorPalettes}
339
- config={config}
340
- onPaletteSelect={handlePaletteSelection}
341
- selectedPalette={getCurrentPaletteName(config)}
342
- colorIndices={[2, 3, 5]}
343
- className='color-palette'
344
- />
322
+
323
+ {config.visualizationType !== 'Warming Stripes' && (
324
+ <>
325
+ <span>Colorblind Safe</span>
326
+ <PaletteSelector
327
+ palettes={accessibleColors}
328
+ colorPalettes={colorPalettes}
329
+ config={config}
330
+ onPaletteSelect={handlePaletteSelection}
331
+ selectedPalette={getCurrentPaletteName(config)}
332
+ colorIndices={[2, 3, 5]}
333
+ className='color-palette'
334
+ />
335
+ </>
336
+ )}
337
+ </>
338
+ )}
339
+
340
+ {isCoveDeveloperMode() && (visSupportsSequentialPallete() || visSupportsNonSequentialPallete()) && (
341
+ <>
342
+ <div className='mt-3'>
343
+ <label className='checkbox'>
344
+ <input
345
+ type='checkbox'
346
+ checked={
347
+ !!(config.general?.palette?.customColorsOrdered || config.general?.palette?.customColors)
348
+ }
349
+ onChange={e => {
350
+ const _state = cloneConfig(config)
351
+ // Ensure palette object exists
352
+ if (!_state.general.palette) {
353
+ _state.general.palette = {}
354
+ }
355
+ if (e.target.checked) {
356
+ // Extract colors from current color scale if runtime data available
357
+ if (config.runtime?.seriesLabelsAll && config.runtime.seriesLabelsAll.length > 0) {
358
+ const colorScale = getColorScale(config)
359
+ const extractedColors = config.runtime.seriesLabelsAll.map((label: string) =>
360
+ colorScale(label)
361
+ )
362
+ _state.general.palette.customColorsOrdered =
363
+ extractedColors.length > 0
364
+ ? extractedColors
365
+ : ['#3366cc', '#5588dd', '#77aaee', '#99ccff']
366
+ } else {
367
+ // Fallback to default colors if runtime not available
368
+ _state.general.palette.customColorsOrdered = ['#3366cc', '#5588dd', '#77aaee', '#99ccff']
369
+ }
370
+ } else {
371
+ // Remove custom colors and revert to default palette
372
+ delete _state.general.palette.customColorsOrdered
373
+ delete _state.general.palette.customColors
374
+ // Set default palette if none exists
375
+ if (!_state.general.palette.name) {
376
+ _state.general.palette.name = 'qualitative_standard'
377
+ _state.general.palette.version = '2.0'
378
+ }
379
+ }
380
+ updateConfig(_state)
381
+ }}
382
+ />
383
+ Use Custom Colors
384
+ </label>
385
+ </div>
386
+
387
+ {(config.general?.palette?.customColorsOrdered || config.general?.palette?.customColors) && (
388
+ <div className='mt-2'>
389
+ <CustomColorsEditor
390
+ colors={config.general.palette.customColorsOrdered || config.general.palette.customColors}
391
+ onChange={newColors => {
392
+ const _state = cloneConfig(config)
393
+ if (!_state.general.palette) {
394
+ _state.general.palette = {}
395
+ }
396
+ _state.general.palette.customColorsOrdered = newColors
397
+ updateConfig(_state)
398
+ }}
399
+ label='Custom Color Order'
400
+ minColors={1}
401
+ maxColors={20}
402
+ />
403
+ </div>
404
+ )}
345
405
  </>
346
406
  )}
347
407
  </>
@@ -443,7 +503,6 @@ const PanelVisual: FC<PanelProps> = props => {
443
503
  value={config.dataCutoff}
444
504
  type='number'
445
505
  fieldName='dataCutoff'
446
- className='number-narrow'
447
506
  label='Data Cutoff'
448
507
  updateField={updateField}
449
508
  tooltip={
@@ -462,28 +521,17 @@ const PanelVisual: FC<PanelProps> = props => {
462
521
  />
463
522
  </>
464
523
  )}
465
- {visSupportsBarThickness() &&
466
- config.orientation === 'horizontal' &&
467
- !config.isLollipopChart &&
468
- config.yAxis.labelPlacement !== 'On Bar' && (
524
+
525
+ {(config.orientation !== 'horizontal' || config.visualizationType === 'Combo') &&
526
+ config.visualizationType !== 'Warming Stripes' && (
469
527
  <TextField
528
+ value={config.barThickness}
470
529
  type='number'
471
- value={config.barHeight || '25'}
472
- fieldName='barHeight'
473
- label=' Bar Thickness'
530
+ fieldName='barThickness'
531
+ label='Bar Thickness'
474
532
  updateField={updateField}
475
- min={15}
476
533
  />
477
534
  )}
478
- {(config.orientation !== 'horizontal' || config.visualizationType === 'Combo') && (
479
- <TextField
480
- value={config.barThickness}
481
- type='number'
482
- fieldName='barThickness'
483
- label='Bar Thickness'
484
- updateField={updateField}
485
- />
486
- )}
487
535
  {visSupportsBarSpace() && (
488
536
  <TextField
489
537
  type='number'
@@ -7,6 +7,7 @@ import Visual from './Panel.Visual'
7
7
  import Sankey from './Panel.Sankey'
8
8
  import Annotate from './Panel.Annotate'
9
9
  import PatternSettings from './Panel.PatternSettings'
10
+ import SmallMultiples from './Panel.SmallMultiples'
10
11
 
11
12
  const Panels = {
12
13
  ForestPlot: ForestPlotSettings,
@@ -17,7 +18,8 @@ const Panels = {
17
18
  Visual,
18
19
  Sankey,
19
20
  Annotate,
20
- PatternSettings
21
+ PatternSettings,
22
+ SmallMultiples
21
23
  }
22
24
 
23
25
  export default Panels
@@ -7,26 +7,6 @@
7
7
  text-align: left;
8
8
 
9
9
  span {
10
- display: inline-block;
11
- float: right;
12
- }
13
- }
14
-
15
- .edit-block {
16
- margin-top: 0;
17
- padding: 1em;
18
- }
19
- }
20
-
21
- .viewport-overrides {
22
- button {
23
- width: 100%;
24
- padding: 1em;
25
- margin-top: 1em;
26
- text-align: left;
27
-
28
- span {
29
- display: inline-block;
30
10
  float: right;
31
11
  }
32
12
  }