@cdc/chart 4.26.4 → 4.26.6

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 (141) hide show
  1. package/CONFIG.md +353 -29
  2. package/LICENSE +201 -0
  3. package/dist/{cdcchart-DQ00cQCm.es.js → cdcchart-BWVIUcAF.es.js} +5 -55
  4. package/dist/cdcchart.js +37885 -35117
  5. package/examples/feature/heatmap/average-age-categorical.json +313 -0
  6. package/examples/feature/heatmap/calendar-heatmap.json +134 -0
  7. package/examples/feature/heatmap/cell-values-bottom-axis.json +181 -0
  8. package/examples/feature/heatmap/sparse-aggregation.json +158 -0
  9. package/examples/private/bad-y-axis.json +543 -0
  10. package/examples/private/bar-chart-negative-value.json +455 -0
  11. package/examples/private/bump-chart.json +1116 -0
  12. package/examples/private/test-123.json +624 -0
  13. package/package.json +4 -3
  14. package/src/CdcChart.tsx +5 -4
  15. package/src/CdcChartComponent.tsx +166 -62
  16. package/src/_stories/Chart.AltText.stories.tsx +136 -0
  17. package/src/_stories/Chart.HeatMap.stories.tsx +187 -0
  18. package/src/_stories/Chart.Sankey.smoke.stories.tsx +62 -0
  19. package/src/_stories/Chart.SeriesColorAssignments.stories.tsx +183 -0
  20. package/src/_stories/Chart.SmallMultiples.smoke.stories.tsx +29 -0
  21. package/src/_stories/Chart.YAxisAutoMaxStrategy.stories.tsx +52 -0
  22. package/src/_stories/Chart.YAxisFilterDomain.stories.tsx +90 -0
  23. package/src/_stories/ChartAnnotation.smoke.stories.tsx +10 -0
  24. package/src/_stories/ChartAxisTitles.smoke.stories.tsx +13 -0
  25. package/src/_stories/ChartBar.Editor.stories.tsx +63 -0
  26. package/src/_stories/ChartBrush.Interaction.stories.tsx +112 -0
  27. package/src/_stories/ChartBrush.smoke.stories.tsx +13 -4
  28. package/src/_stories/_mock/alt_text_metadata.json +38 -0
  29. package/src/_stories/_mock/annotation_event-line_mock.json +1064 -0
  30. package/src/_stories/_mock/heatmap-average-age-categorical.json +315 -0
  31. package/src/_stories/_mock/heatmap.json +156 -0
  32. package/src/_stories/_mock/reported_pertussis_cases.json +1643 -0
  33. package/src/_stories/_mock/sankey_mock.json +78 -0
  34. package/src/_stories/_mock/small_multiples/small_multiples_lines.json +1 -1
  35. package/src/components/Annotations/components/AnnotationDraggable.styles.css +22 -1
  36. package/src/components/Annotations/components/AnnotationDraggable.tsx +220 -120
  37. package/src/components/Annotations/components/AnnotationDropdown.tsx +5 -2
  38. package/src/components/Annotations/tests/AnnotationDraggable.test.tsx +198 -0
  39. package/src/components/Axis/LeftAxis.tsx +17 -11
  40. package/src/components/Axis/LeftAxisGridlines.tsx +22 -12
  41. package/src/components/Axis/RightAxis.tsx +3 -0
  42. package/src/components/Axis/tests/AxisTickValues.test.tsx +86 -0
  43. package/src/components/Brush/BrushSelector.tsx +88 -187
  44. package/src/components/Brush/brushInteraction.ts +22 -0
  45. package/src/components/Brush/tests/brushInteraction.test.ts +38 -0
  46. package/src/components/EditorPanel/EditorPanel.tsx +677 -185
  47. package/src/components/EditorPanel/components/Panels/Panel.Annotate.tsx +114 -52
  48. package/src/components/EditorPanel/components/Panels/Panel.General.tsx +105 -13
  49. package/src/components/EditorPanel/components/Panels/Panel.PatternSettings.tsx +1 -0
  50. package/src/components/EditorPanel/components/Panels/Panel.Regions.tsx +5 -0
  51. package/src/components/EditorPanel/components/Panels/Panel.Series.tsx +2 -2
  52. package/src/components/EditorPanel/components/Panels/Panel.SmallMultiples.test.tsx +98 -0
  53. package/src/components/EditorPanel/components/Panels/Panel.SmallMultiples.tsx +10 -0
  54. package/src/components/EditorPanel/components/Panels/Panel.Visual.test.tsx +484 -0
  55. package/src/components/EditorPanel/components/Panels/Panel.Visual.tsx +4 -1
  56. package/src/components/EditorPanel/components/Panels/SeriesColorAssignments.tsx +262 -0
  57. package/src/components/EditorPanel/components/Panels/panelVisual.styles.css +32 -0
  58. package/src/components/EditorPanel/components/panels.scss +10 -0
  59. package/src/components/EditorPanel/editor-panel.scss +47 -0
  60. package/src/components/EditorPanel/helpers/getVisualizationTypeConfigUpdate.ts +61 -0
  61. package/src/components/EditorPanel/helpers/tests/getVisualizationTypeConfigUpdate.test.ts +74 -0
  62. package/src/components/EditorPanel/useEditorPermissions.test.tsx +315 -0
  63. package/src/components/EditorPanel/useEditorPermissions.ts +77 -21
  64. package/src/components/HeatMap/components/HeatMap.tsx +767 -0
  65. package/src/components/HeatMap/components/HeatMapGradientLegend.tsx +128 -0
  66. package/src/components/HeatMap/heatmap.constants.ts +13 -0
  67. package/src/components/HeatMap/heatmap.css +85 -0
  68. package/src/components/HeatMap/helpers/index.ts +261 -0
  69. package/src/components/HeatMap/index.tsx +5 -0
  70. package/src/components/HeatMap/tests/HeatMap.test.tsx +1072 -0
  71. package/src/components/HeatMap/tests/helpers.test.ts +167 -0
  72. package/src/components/Legend/Legend.Component.tsx +2 -2
  73. package/src/components/Legend/helpers/createFormatLabels.test.ts +88 -0
  74. package/src/components/Legend/helpers/createFormatLabels.tsx +6 -1
  75. package/src/components/LineChart/components/LineChart.BumpCircle.tsx +19 -22
  76. package/src/components/LineChart/helpers.test.ts +574 -0
  77. package/src/components/LineChart/helpers.ts +39 -16
  78. package/src/components/LineChart/index.tsx +28 -5
  79. package/src/components/LinearChart/tests/LinearChart.test.tsx +137 -33
  80. package/src/components/LinearChart/tests/mockConfigContext.ts +1 -0
  81. package/src/components/LinearChart.tsx +103 -47
  82. package/src/components/PieChart/PieChart.tsx +4 -1
  83. package/src/components/RadarChart/RadarChart.tsx +3 -1
  84. package/src/components/Regions/components/Regions.tsx +195 -41
  85. package/src/components/Sankey/components/Sankey.tsx +7 -3
  86. package/src/components/Sankey/helpers/getSankeyTooltip.tsx +7 -6
  87. package/src/components/ScatterPlot/ScatterPlot.jsx +3 -3
  88. package/src/components/SmallMultiples/SmallMultipleTile.tsx +33 -3
  89. package/src/components/SmallMultiples/SmallMultiples.css +12 -0
  90. package/src/components/SmallMultiples/SmallMultiples.tsx +50 -15
  91. package/src/components/SmallMultiples/tests/SmallMultipleTile.test.tsx +152 -0
  92. package/src/components/SmallMultiples/tests/SmallMultiples.test.tsx +170 -0
  93. package/src/components/Sparkline/components/SparkLine.tsx +4 -9
  94. package/src/components/WarmingStripes/WarmingStripesGradientLegend.css +1 -1
  95. package/src/components/WarmingStripes/WarmingStripesGradientLegend.tsx +2 -2
  96. package/src/data/initial-state.js +12 -0
  97. package/src/helpers/axisLabelFontSize.ts +8 -0
  98. package/src/helpers/bumpChartTooltip.ts +54 -0
  99. package/src/helpers/calculateLeftYAxisWidth.ts +13 -3
  100. package/src/helpers/colorAssignmentHelpers.ts +102 -0
  101. package/src/helpers/dataHelpers.ts +11 -1
  102. package/src/helpers/ensureSpecialChartAxisTypes.ts +11 -0
  103. package/src/helpers/getAxisMaxOverride.ts +16 -0
  104. package/src/helpers/getCleanTopTickMax.ts +64 -0
  105. package/src/helpers/getColorScale.ts +18 -9
  106. package/src/helpers/getHasBoundarySuppression.ts +26 -0
  107. package/src/helpers/getMinMax.ts +26 -25
  108. package/src/helpers/getYAxisDomainData.ts +38 -0
  109. package/src/helpers/getYAxisFinalizationEligibility.ts +40 -0
  110. package/src/helpers/handleChartAriaLabels.ts +23 -10
  111. package/src/helpers/hasSpacedInlineLabel.ts +10 -0
  112. package/src/helpers/seriesColumnSettings.ts +5 -4
  113. package/src/helpers/seriesHighlight.ts +26 -0
  114. package/src/helpers/smallMultiplesHelpers.ts +5 -0
  115. package/src/helpers/tests/axisLabelFontSize.test.ts +15 -0
  116. package/src/helpers/tests/bumpChartTooltip.test.ts +75 -0
  117. package/src/helpers/tests/calculateLeftYAxisWidth.test.ts +38 -0
  118. package/src/helpers/tests/dataHelpers.test.ts +135 -0
  119. package/src/helpers/tests/ensureSpecialChartAxisTypes.test.ts +30 -0
  120. package/src/helpers/tests/getAxisMaxOverride.test.ts +36 -0
  121. package/src/helpers/tests/getCleanTopTickMax.test.ts +165 -0
  122. package/src/helpers/tests/getColorScale.test.ts +197 -0
  123. package/src/helpers/tests/getMinMax.test.ts +211 -0
  124. package/src/helpers/tests/getYAxisDomainData.test.ts +119 -0
  125. package/src/helpers/tests/getYAxisFinalizationEligibility.test.ts +185 -0
  126. package/src/helpers/tests/handleChartAriaLabels.test.ts +71 -0
  127. package/src/helpers/tests/hasSpacedInlineLabel.test.ts +75 -0
  128. package/src/helpers/tests/seriesColumnSettings.test.ts +16 -2
  129. package/src/helpers/tests/seriesHighlight.test.ts +61 -0
  130. package/src/helpers/tests/smallMultiplesHelpers.test.ts +99 -0
  131. package/src/hooks/tests/useRightAxis.test.tsx +127 -0
  132. package/src/hooks/tests/useScales.test.tsx +383 -0
  133. package/src/hooks/useRightAxis.ts +30 -13
  134. package/src/hooks/useScales.ts +48 -21
  135. package/src/hooks/useTooltip.tsx +3 -1
  136. package/src/index.jsx +5 -2
  137. package/src/scss/main.scss +32 -49
  138. package/src/test/CdcChart.dataTable.test.tsx +122 -0
  139. package/src/types/ChartConfig.ts +24 -1
  140. package/src/types/ChartContext.ts +5 -0
  141. package/src/helpers/getYAxisAutoPadding.ts +0 -53
@@ -2,6 +2,7 @@ import React, { useContext } from 'react'
2
2
  import { Draggable } from '@hello-pangea/dnd'
3
3
  import ConfigContext from '../../../../ConfigContext.js'
4
4
  import { useEditorPermissions } from '../../useEditorPermissions'
5
+ import { EVENT_LINE_LABEL_OFFSET } from '../../../Annotations/components/AnnotationDraggable'
5
6
 
6
7
  // CDC Core
7
8
  import Accordion from '@cdc/core/components/ui/Accordion'
@@ -63,6 +64,7 @@ const PanelAnnotate: React.FC<PanelProps> = props => {
63
64
  const handleAddAnnotation = () => {
64
65
  const newAnnotation = {
65
66
  text: 'New annotation',
67
+ style: 'callout',
66
68
  anchorMode: 'fixed',
67
69
  fontSize: 16,
68
70
  bezier: 10,
@@ -134,6 +136,19 @@ const PanelAnnotate: React.FC<PanelProps> = props => {
134
136
  updateField={updateField}
135
137
  />
136
138
 
139
+ <Select
140
+ label='Mobile Annotations'
141
+ value={config?.general?.mobileAnnotationDisplay || 'symbol'}
142
+ options={[
143
+ { value: 'symbol', label: 'Show Symbol' },
144
+ { value: 'text', label: 'Show Text' }
145
+ ]}
146
+ section='general'
147
+ subsection={null}
148
+ fieldName='mobileAnnotationDisplay'
149
+ updateField={updateField}
150
+ />
151
+
137
152
  {config.general.showAnnotationDropdown && (
138
153
  <label key={`key-2`}>
139
154
  Annotation Dropdown Title:
@@ -200,7 +215,48 @@ const PanelAnnotate: React.FC<PanelProps> = props => {
200
215
  />
201
216
  </label>
202
217
 
203
- {visSupportsDataAnnotations() && (
218
+ <Select
219
+ label='Annotation Style:'
220
+ value={annotation.style || 'callout'}
221
+ options={[
222
+ { value: 'callout', label: 'Callout' },
223
+ {
224
+ value: 'event-line',
225
+ label: visSupportsDataAnnotations()
226
+ ? 'Event Line'
227
+ : 'Event Line (requires Line/Bar/Combo/Area/Forecasting, vertical)'
228
+ }
229
+ ]}
230
+ section='annotations'
231
+ subsection={null}
232
+ fieldName='style'
233
+ updateField={(section, subsection, fieldName, value) => {
234
+ const updatedAnnotations = cloneDeep(config?.annotations)
235
+ const target = updatedAnnotations[index]
236
+ target.style = value
237
+
238
+ if (value === 'event-line') {
239
+ target.anchorMode = 'data'
240
+ if (!target.dataX) {
241
+ target.dataX = transformedData?.[0]?.[config.xAxis.dataKey] || ''
242
+ }
243
+ target.seriesKey = ''
244
+ target.edit = { subject: false, label: true }
245
+ target.dx =
246
+ typeof target.dx === 'number' && target.dx < 0
247
+ ? -EVENT_LINE_LABEL_OFFSET
248
+ : EVENT_LINE_LABEL_OFFSET
249
+ if (target.dy === undefined || target.dy === null) target.dy = -120
250
+ }
251
+
252
+ updateConfig({
253
+ ...config,
254
+ annotations: updatedAnnotations
255
+ })
256
+ }}
257
+ />
258
+
259
+ {visSupportsDataAnnotations() && annotation.style !== 'event-line' && (
204
260
  <Select
205
261
  label='Position Mode:'
206
262
  value={annotation.anchorMode || 'fixed'}
@@ -233,24 +289,26 @@ const PanelAnnotate: React.FC<PanelProps> = props => {
233
289
  />
234
290
  )}
235
291
 
236
- {visSupportsDataAnnotations() && annotation.anchorMode === 'data' && (
237
- <Select
238
- label='Series:'
239
- value={annotation.seriesKey || config.series?.[0]?.dataKey || ''}
240
- options={config.series.map(s => s.dataKey)}
241
- section='annotations'
242
- subsection={null}
243
- fieldName='seriesKey'
244
- updateField={(section, subsection, fieldName, value) => {
245
- const updatedAnnotations = cloneDeep(config?.annotations)
246
- updatedAnnotations[index].seriesKey = value || config.series?.[0]?.dataKey
247
- updateConfig({
248
- ...config,
249
- annotations: updatedAnnotations
250
- })
251
- }}
252
- />
253
- )}
292
+ {visSupportsDataAnnotations() &&
293
+ annotation.style !== 'event-line' &&
294
+ annotation.anchorMode === 'data' && (
295
+ <Select
296
+ label='Series:'
297
+ value={annotation.seriesKey || config.series?.[0]?.dataKey || ''}
298
+ options={config.series.map(s => s.dataKey)}
299
+ section='annotations'
300
+ subsection={null}
301
+ fieldName='seriesKey'
302
+ updateField={(section, subsection, fieldName, value) => {
303
+ const updatedAnnotations = cloneDeep(config?.annotations)
304
+ updatedAnnotations[index].seriesKey = value || config.series?.[0]?.dataKey
305
+ updateConfig({
306
+ ...config,
307
+ annotations: updatedAnnotations
308
+ })
309
+ }}
310
+ />
311
+ )}
254
312
 
255
313
  <label>
256
314
  Opacity
@@ -300,24 +358,26 @@ const PanelAnnotate: React.FC<PanelProps> = props => {
300
358
  }}
301
359
  />
302
360
 
303
- <Select
304
- label='Connection Type:'
305
- value={config?.annotations[index]?.connectionType}
306
- options={['Select', 'curve', 'line', 'elbow', 'none']}
307
- section='annotations'
308
- subsection={null}
309
- fieldName='connectionType'
310
- updateField={(section, subsection, fieldName, value) => {
311
- const updatedAnnotations = cloneDeep(config?.annotations)
312
- updatedAnnotations[index].connectionType = value
313
- updateConfig({
314
- ...config,
315
- annotations: updatedAnnotations
316
- })
317
- }}
318
- />
361
+ {annotation.style !== 'event-line' && (
362
+ <Select
363
+ label='Connection Type:'
364
+ value={config?.annotations[index]?.connectionType}
365
+ options={['Select', 'curve', 'line', 'elbow', 'none']}
366
+ section='annotations'
367
+ subsection={null}
368
+ fieldName='connectionType'
369
+ updateField={(section, subsection, fieldName, value) => {
370
+ const updatedAnnotations = cloneDeep(config?.annotations)
371
+ updatedAnnotations[index].connectionType = value
372
+ updateConfig({
373
+ ...config,
374
+ annotations: updatedAnnotations
375
+ })
376
+ }}
377
+ />
378
+ )}
319
379
 
320
- {annotation.connectionType === 'curve' && (
380
+ {annotation.style !== 'event-line' && annotation.connectionType === 'curve' && (
321
381
  <>
322
382
  <label>
323
383
  Curve Control
@@ -340,22 +400,24 @@ const PanelAnnotate: React.FC<PanelProps> = props => {
340
400
  </>
341
401
  )}
342
402
 
343
- <Select
344
- label='Marker'
345
- value={annotation.marker}
346
- options={['arrow', 'circle']}
347
- section='annotations'
348
- subsection={null}
349
- fieldName='marker'
350
- updateField={(section, subsection, fieldName, value) => {
351
- const updatedAnnotations = cloneDeep(config?.annotations)
352
- updatedAnnotations[index].marker = value
353
- updateConfig({
354
- ...config,
355
- annotations: updatedAnnotations
356
- })
357
- }}
358
- />
403
+ {annotation.style !== 'event-line' && (
404
+ <Select
405
+ label='Marker'
406
+ value={annotation.marker}
407
+ options={['arrow', 'circle']}
408
+ section='annotations'
409
+ subsection={null}
410
+ fieldName='marker'
411
+ updateField={(section, subsection, fieldName, value) => {
412
+ const updatedAnnotations = cloneDeep(config?.annotations)
413
+ updatedAnnotations[index].marker = value
414
+ updateConfig({
415
+ ...config,
416
+ annotations: updatedAnnotations
417
+ })
418
+ }}
419
+ />
420
+ )}
359
421
  </AccordionItemPanel>
360
422
  </AccordionItem>
361
423
  </AccessibleAccordion>
@@ -13,12 +13,15 @@ import { approvedCurveTypes } from '@cdc/core/helpers/lineChartHelpers'
13
13
  import { TextField, Select, CheckBox } from '@cdc/core/components/EditorPanel/Inputs'
14
14
  import Tooltip from '@cdc/core/components/ui/Tooltip'
15
15
  import Icon from '@cdc/core/components/ui/Icon'
16
+ import { resolveAltTextDescription } from '@cdc/core/helpers/resolveAltTextDescription'
16
17
 
17
18
  // contexts
18
19
  import { useEditorPermissions } from '../../useEditorPermissions.js'
19
20
  import { useEditorPanelContext } from '../../EditorPanelContext.js'
20
21
  import ConfigContext from '../../../../ConfigContext.js'
21
22
  import { PanelProps } from '../PanelProps'
23
+ import { getVisualizationTypeConfigUpdate } from '../../helpers/getVisualizationTypeConfigUpdate'
24
+ import { type VisualizationType } from '../../../../types/ChartConfig'
22
25
 
23
26
  const PanelGeneral: FC<PanelProps> = props => {
24
27
  const { config, updateConfig } = useContext(ConfigContext)
@@ -79,22 +82,12 @@ const PanelGeneral: FC<PanelProps> = props => {
79
82
  updateField={updateField}
80
83
  options={enabledChartTypes}
81
84
  onChange={event => {
82
- const newVisType = event.target.value
85
+ const newVisType = event.target.value as VisualizationType
83
86
 
84
87
  updateField(null, null, 'visualizationType', newVisType)
85
88
 
86
- if (newVisType === 'Forecasting' && config.xAxis.type === 'categorical') {
87
- updateConfig({
88
- ...config,
89
- visualizationType: newVisType,
90
- xAxis: {
91
- ...config.xAxis,
92
- type: 'date',
93
- dateParseFormat: config.xAxis.dateParseFormat || '%Y-%m-%d',
94
- dateDisplayFormat: config.xAxis.dateDisplayFormat || '%Y-%m-%d'
95
- }
96
- })
97
- }
89
+ const updatedConfig = getVisualizationTypeConfigUpdate(config, newVisType)
90
+ if (updatedConfig) updateConfig(updatedConfig)
98
91
  }}
99
92
  />
100
93
  )}
@@ -617,6 +610,105 @@ const PanelGeneral: FC<PanelProps> = props => {
617
610
  </Tooltip>
618
611
  }
619
612
  />
613
+
614
+ {/* Accessible Alt Text Description */}
615
+ {(() => {
616
+ const metadataKeys = Object.keys(config.dataMetadata || {})
617
+ const hasMetadata = metadataKeys.length > 0
618
+ const descType = config.altText?.type || ''
619
+ const resolvedDescription = resolveAltTextDescription(config.altText, config.dataMetadata)
620
+ return (
621
+ <>
622
+ <Select
623
+ value={descType}
624
+ fieldName='altTextType'
625
+ label='Alt Text Description'
626
+ options={[
627
+ { value: '', label: 'None' },
628
+ { value: 'static', label: 'Static (manual text)' },
629
+ { value: 'metadata', label: 'Data File Metadata' }
630
+ ]}
631
+ updateField={(_section, _subsection, _fieldName, value) => {
632
+ if (value === '') {
633
+ updateField(null, null, 'altText', undefined)
634
+ } else {
635
+ updateField(null, null, 'altText', { type: value as 'static' | 'metadata' })
636
+ }
637
+ }}
638
+ tooltip={
639
+ <Tooltip style={{ textTransform: 'none' }}>
640
+ <Tooltip.Target>
641
+ <Icon display='question' style={{ marginLeft: '0.5rem' }} />
642
+ </Tooltip.Target>
643
+ <Tooltip.Content>
644
+ <p>
645
+ Add a longer description for screen readers. The chart title is always auto-generated. Use
646
+ "Static" for manually written text, or "Data File Metadata" to pull it from a key in your data
647
+ file.
648
+ </p>
649
+ </Tooltip.Content>
650
+ </Tooltip>
651
+ }
652
+ />
653
+ {descType === 'static' && (
654
+ <TextField
655
+ value={config.altText?.value || ''}
656
+ fieldName='altTextValue'
657
+ type='textarea'
658
+ label='Description Text'
659
+ placeholder='Longer interpretive description of chart insights...'
660
+ updateField={(_section, _subsection, _fieldName, value) => {
661
+ updateField(null, null, 'altText', { ...config.altText, value })
662
+ }}
663
+ />
664
+ )}
665
+ {descType === 'metadata' && (
666
+ <>
667
+ {hasMetadata ? (
668
+ <Select
669
+ value={config.altText?.metadataKey || ''}
670
+ fieldName='altTextMetadataKey'
671
+ label='Description Metadata Field'
672
+ options={[
673
+ { value: '', label: 'Select Metadata Field...' },
674
+ ...metadataKeys.map(key => ({
675
+ value: key,
676
+ label: `${key}: ${config.dataMetadata[key]}`
677
+ }))
678
+ ]}
679
+ updateField={(_section, _subsection, _fieldName, value) => {
680
+ updateField(null, null, 'altText', { ...config.altText, metadataKey: value })
681
+ }}
682
+ />
683
+ ) : (
684
+ <span className='subtext'>
685
+ No metadata fields are available. Your data file must be a JSON object with a <code>data</code>{' '}
686
+ array and sibling key-value pairs, for example:{' '}
687
+ <code>{`{ "altDescription": "...", "data": [...] }`}</code>
688
+ </span>
689
+ )}
690
+ </>
691
+ )}
692
+ {resolvedDescription && (
693
+ <div
694
+ style={{
695
+ marginTop: '1em',
696
+ padding: '0.75em',
697
+ background: '#f5f5f5',
698
+ borderRadius: '4px',
699
+ fontSize: '0.8em',
700
+ textTransform: 'none'
701
+ }}
702
+ >
703
+ <strong style={{ display: 'block', marginBottom: '0.25em' }}>Preview:</strong>
704
+ <p data-testid='alt-text-desc-preview' style={{ margin: 0, fontStyle: 'italic' }}>
705
+ {resolvedDescription}
706
+ </p>
707
+ </div>
708
+ )}
709
+ </>
710
+ )
711
+ })()}
620
712
  </AccordionItemPanel>
621
713
  </AccordionItem>
622
714
  )
@@ -22,6 +22,7 @@ import { sanitizeToSvgId } from '@cdc/core/helpers/cove/string'
22
22
 
23
23
  const PanelPatternSettings: FC<PanelProps> = props => {
24
24
  const { config, updateConfig, transformedData } = useContext<ChartContext>(ConfigContext)
25
+ if (config.visualizationType === 'HeatMap') return null
25
26
 
26
27
  type LegendPattern = {
27
28
  label?: string
@@ -14,10 +14,13 @@ import { TextField, Select } from '@cdc/core/components/EditorPanel/Inputs'
14
14
  import Tooltip from '@cdc/core/components/ui/Tooltip'
15
15
  import Icon from '@cdc/core/components/ui/Icon'
16
16
  import Button from '@cdc/core/components/elements/Button'
17
+ import { APP_FONT_COLOR } from '@cdc/core/helpers/constants'
17
18
  import { type ChartContext } from '../../../../types/ChartContext.js'
18
19
  import { type PanelProps } from '../PanelProps.js'
19
20
  import ConfigContext from '../../../../ConfigContext.js'
20
21
 
22
+ const DEFAULT_REGION_BACKGROUND = getComputedStyle(document.documentElement).getPropertyValue('--cool-gray-50').trim()
23
+
21
24
  const RegionSettings = memo(({ config, updateConfig }: { config: ChartConfig; updateConfig: Function }) => {
22
25
  let regionUpdate = (fieldName, value, i) => {
23
26
  let regions = []
@@ -116,12 +119,14 @@ const RegionSettings = memo(({ config, updateConfig }: { config: ChartConfig; up
116
119
  value={color}
117
120
  label='Text Color'
118
121
  fieldName='color'
122
+ placeholder={APP_FONT_COLOR}
119
123
  updateField={(section, subsection, fieldName, value) => regionUpdate(fieldName, value, i)}
120
124
  />
121
125
  <TextField
122
126
  value={background}
123
127
  label='Background'
124
128
  fieldName='background'
129
+ placeholder={DEFAULT_REGION_BACKGROUND}
125
130
  updateField={(section, subsection, fieldName, value) => regionUpdate(fieldName, value, i)}
126
131
  />
127
132
  </div>
@@ -660,7 +660,7 @@ const SeriesButtonRemove = props => {
660
660
 
661
661
  return (
662
662
  config.series &&
663
- config.series.length > 1 && (
663
+ (config.series.length > 1 || config.visualizationType === 'HeatMap') && (
664
664
  <Button variant='danger' size='sm' className='grouped-list__remove' onClick={e => handleRemoveSeries(e, series)}>
665
665
  Remove
666
666
  </Button>
@@ -710,7 +710,7 @@ const SeriesItem = props => {
710
710
  }
711
711
  >
712
712
  <Icon display='move' size={15} style={{ cursor: 'default' }} />
713
- {series.dataKey}
713
+ {config.visualizationType === 'HeatMap' ? series.name || series.dataKey : series.dataKey}
714
714
  </AccordionItemButton>
715
715
  </AccordionItemHeading>
716
716
  {chartsWithOptions.includes(config.visualizationType) && (
@@ -0,0 +1,98 @@
1
+ import React from 'react'
2
+ import { Accordion } from 'react-accessible-accordion'
3
+ import { render, screen, fireEvent } from '@testing-library/react'
4
+ import { describe, expect, it, vi } from 'vitest'
5
+ import ConfigContext from '../../../../ConfigContext'
6
+ import EditorPanelContext from '../../EditorPanelContext'
7
+ import PanelSmallMultiples from './Panel.SmallMultiples'
8
+ import { createMockChartContext, createMockConfig } from '../../../LinearChart/tests/mockConfigContext'
9
+
10
+ vi.mock('@cdc/core/components/ui/Icon', () => ({
11
+ default: () => <span data-testid='icon' />
12
+ }))
13
+
14
+ const renderPanel = config => {
15
+ const context = createMockChartContext(config, {
16
+ rawData: [{ Date: '2024-01-01', series_a: 1, series_b: 2 }]
17
+ } as any)
18
+
19
+ render(
20
+ <ConfigContext.Provider value={context}>
21
+ <EditorPanelContext.Provider value={{ updateField: vi.fn() }}>
22
+ <Accordion allowZeroExpanded>
23
+ <PanelSmallMultiples name='Small Multiples' />
24
+ </Accordion>
25
+ </EditorPanelContext.Provider>
26
+ </ConfigContext.Provider>
27
+ )
28
+
29
+ fireEvent.click(screen.getByRole('button', { name: 'Small Multiples' }))
30
+ }
31
+
32
+ describe('PanelSmallMultiples color assignments warning', () => {
33
+ it('shows the by-series same-color override warning only when assignments are active', () => {
34
+ const config = createMockConfig({
35
+ visualizationType: 'Line',
36
+ general: {
37
+ palette: {
38
+ colorAssignmentMode: 'by-value',
39
+ colorAssignments: [{ key: 'series_a', color: '#123456' }]
40
+ }
41
+ } as any,
42
+ smallMultiples: {
43
+ mode: 'by-series',
44
+ colorMode: 'same'
45
+ },
46
+ series: [
47
+ { dataKey: 'series_a', name: 'Alpha', type: 'Line' },
48
+ { dataKey: 'series_b', name: 'Beta', type: 'Line' }
49
+ ] as any
50
+ })
51
+
52
+ renderPanel(config)
53
+
54
+ expect(screen.queryByText(/Color assignments are enabled/)).toBeInTheDocument()
55
+ })
56
+
57
+ it('does not show the override warning when assignment mode is ordered', () => {
58
+ const config = createMockConfig({
59
+ visualizationType: 'Line',
60
+ general: {
61
+ palette: {
62
+ colorAssignmentMode: 'ordered',
63
+ colorAssignments: [{ key: 'series_a', color: '#123456' }]
64
+ }
65
+ } as any,
66
+ smallMultiples: {
67
+ mode: 'by-series',
68
+ colorMode: 'same'
69
+ },
70
+ series: [{ dataKey: 'series_a', name: 'Alpha', type: 'Line' }] as any
71
+ })
72
+
73
+ renderPanel(config)
74
+
75
+ expect(screen.queryByText(/Color assignments are enabled/)).not.toBeInTheDocument()
76
+ })
77
+
78
+ it('does not show the override warning when assignments only contain stale keys', () => {
79
+ const config = createMockConfig({
80
+ visualizationType: 'Line',
81
+ general: {
82
+ palette: {
83
+ colorAssignmentMode: 'by-value',
84
+ colorAssignments: [{ key: 'removed_series', color: '#123456' }]
85
+ }
86
+ } as any,
87
+ smallMultiples: {
88
+ mode: 'by-series',
89
+ colorMode: 'same'
90
+ },
91
+ series: [{ dataKey: 'series_a', name: 'Alpha', type: 'Line' }] as any
92
+ })
93
+
94
+ renderPanel(config)
95
+
96
+ expect(screen.queryByText(/Color assignments are enabled/)).not.toBeInTheDocument()
97
+ })
98
+ })
@@ -20,6 +20,7 @@ import { useEditorPanelContext } from '../../EditorPanelContext.js'
20
20
  import ConfigContext from '../../../../ConfigContext.js'
21
21
  import { PanelProps } from '../PanelProps'
22
22
  import { getTileKeys } from '../../../../helpers/smallMultiplesHelpers'
23
+ import { hasSeriesColorAssignmentOverrides } from '../../../../helpers/colorAssignmentHelpers'
23
24
 
24
25
  const PanelSmallMultiples: FC<PanelProps> = props => {
25
26
  const { config, rawData, updateConfig } = useContext<ChartContext>(ConfigContext)
@@ -273,6 +274,15 @@ const PanelSmallMultiples: FC<PanelProps> = props => {
273
274
  }
274
275
  />
275
276
 
277
+ {config.smallMultiples?.mode === 'by-series' &&
278
+ config.smallMultiples?.colorMode === 'same' &&
279
+ hasSeriesColorAssignmentOverrides(config) && (
280
+ <p className='small-multiples-color-assignment-warning mt-1'>
281
+ Color assignments are enabled in the Visual section, so tiles will use their assigned series
282
+ colors instead of the &lsquo;Same Color&rsquo; setting.
283
+ </p>
284
+ )}
285
+
276
286
  {/* Custom Tile Titles - only show for by-column mode */}
277
287
  {config.smallMultiples?.mode === 'by-column' && (
278
288
  <div>