@cdc/chart 4.26.3 → 4.26.5

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 (192) hide show
  1. package/CONFIG.md +489 -0
  2. package/README.md +42 -17
  3. package/dist/cdcchart.js +39024 -36587
  4. package/examples/feature/bar/additional-column-tooltip.json +3 -3
  5. package/examples/feature/deviation/planet-deviation-config.json +2 -2
  6. package/examples/feature/heatmap/average-age-categorical.json +313 -0
  7. package/examples/feature/heatmap/calendar-heatmap.json +134 -0
  8. package/examples/feature/heatmap/cell-values-bottom-axis.json +181 -0
  9. package/examples/feature/heatmap/sparse-aggregation.json +158 -0
  10. package/examples/feature/paired-bar/paired-bar-example.json +2 -2
  11. package/examples/feature/tests-case-rate/case-rate-example-config.json +2 -2
  12. package/examples/feature/tests-covid/covid-confidence-example-config.json +2 -2
  13. package/examples/feature/tests-covid/covid-example-config.json +2 -2
  14. package/examples/feature/tests-cutoff/cutoff-example-config.json +2 -2
  15. package/examples/feature/tests-non-numerics/planet-pie-example-config-nonnumeric.json +2 -2
  16. package/examples/metadata-variables.json +1 -1
  17. package/examples/minimal-example.json +24 -0
  18. package/examples/private/bump-chart.json +1116 -0
  19. package/examples/private/test-123.json +624 -0
  20. package/examples/private/test2.json +35012 -0
  21. package/package.json +5 -4
  22. package/src/CdcChartComponent.tsx +144 -60
  23. package/src/_stories/Chart.AltText.stories.tsx +136 -0
  24. package/src/_stories/{Chart.HTMLInDataTable.stories.tsx → Chart.HTMLInDataTable.smoke.stories.tsx} +10 -0
  25. package/src/_stories/Chart.HeatMap.stories.tsx +187 -0
  26. package/src/_stories/Chart.Minimal.smoke.stories.tsx +25 -0
  27. package/src/_stories/Chart.Sankey.smoke.stories.tsx +62 -0
  28. package/src/_stories/Chart.SmallMultiples.smoke.stories.tsx +108 -0
  29. package/src/_stories/{Chart.stories.tsx → Chart.smoke.stories.tsx} +12 -0
  30. package/src/_stories/{Chart.tooltip.stories.tsx → Chart.tooltip.smoke.stories.tsx} +68 -0
  31. package/src/_stories/{ChartAxisTitles.stories.tsx → ChartAxisTitles.smoke.stories.tsx} +13 -0
  32. package/src/_stories/ChartBar.Editor.stories.tsx +151 -85
  33. package/src/_stories/ChartBrush.Interaction.stories.tsx +112 -0
  34. package/src/_stories/{ChartBrush.stories.tsx → ChartBrush.smoke.stories.tsx} +13 -4
  35. package/src/_stories/ChartEditor.Editor.stories.tsx +23 -0
  36. package/src/_stories/ChartLine.DashedDates.smoke.stories.tsx +23 -0
  37. package/src/_stories/_mock/alt_text_metadata.json +38 -0
  38. package/src/_stories/_mock/heatmap-average-age-categorical.json +315 -0
  39. package/src/_stories/_mock/heatmap.json +156 -0
  40. package/src/_stories/_mock/line_chart_dashed_dates.json +276 -0
  41. package/src/_stories/_mock/paired-bar-abbr.json +1 -1
  42. package/src/_stories/_mock/paired-bar.json +1 -1
  43. package/src/_stories/_mock/reported_pertussis_cases.json +1643 -0
  44. package/src/_stories/_mock/sankey_mock.json +78 -0
  45. package/src/_stories/_mock/small_multiples/small_multiples_lines.json +1 -1
  46. package/src/components/Annotations/components/AnnotationDraggable.tsx +2 -2
  47. package/src/components/Annotations/components/AnnotationDropdown.tsx +5 -2
  48. package/src/components/AreaChart/components/AreaChart.Stacked.jsx +10 -2
  49. package/src/components/Axis/BottomAxis.tsx +1 -1
  50. package/src/components/Axis/LeftAxis.tsx +15 -14
  51. package/src/components/Axis/LeftAxisGridlines.tsx +14 -13
  52. package/src/components/BarChart/components/BarChart.Horizontal.tsx +9 -5
  53. package/src/components/BarChart/components/BarChart.StackedHorizontal.tsx +9 -5
  54. package/src/components/BarChart/components/BarChart.StackedVertical.tsx +9 -5
  55. package/src/components/BarChart/components/BarChart.Vertical.tsx +9 -6
  56. package/src/components/BarChart/components/BarChart.tsx +2 -6
  57. package/src/components/BarChart/helpers/useBarChart.ts +1 -1
  58. package/src/components/BoxPlot/BoxPlot.Vertical.tsx +3 -3
  59. package/src/components/Brush/BrushSelector.tsx +88 -187
  60. package/src/components/Brush/brushInteraction.ts +22 -0
  61. package/src/components/Brush/tests/brushInteraction.test.ts +38 -0
  62. package/src/components/EditorPanel/EditorPanel.tsx +946 -960
  63. package/src/components/EditorPanel/EditorPanelContext.ts +1 -1
  64. package/src/components/EditorPanel/components/Panels/Panel.Annotate.tsx +229 -165
  65. package/src/components/EditorPanel/components/Panels/Panel.General.tsx +105 -13
  66. package/src/components/EditorPanel/components/Panels/Panel.PatternSettings.tsx +169 -116
  67. package/src/components/EditorPanel/components/Panels/Panel.Regions.tsx +159 -109
  68. package/src/components/EditorPanel/components/Panels/Panel.Sankey.tsx +3 -3
  69. package/src/components/EditorPanel/components/Panels/Panel.Series.tsx +14 -28
  70. package/src/components/EditorPanel/components/Panels/Panel.Visual.tsx +1 -1
  71. package/src/components/EditorPanel/helpers/getVisualizationTypeConfigUpdate.ts +57 -0
  72. package/src/components/EditorPanel/helpers/tests/getVisualizationTypeConfigUpdate.test.ts +56 -0
  73. package/src/components/EditorPanel/useEditorPermissions.test.tsx +70 -0
  74. package/src/components/EditorPanel/useEditorPermissions.ts +32 -21
  75. package/src/components/Forecasting/Forecasting.tsx +2 -2
  76. package/src/components/HeatMap/components/HeatMap.tsx +762 -0
  77. package/src/components/HeatMap/components/HeatMapGradientLegend.tsx +114 -0
  78. package/src/components/HeatMap/heatmap.constants.ts +13 -0
  79. package/src/components/HeatMap/heatmap.css +71 -0
  80. package/src/components/HeatMap/helpers/index.ts +261 -0
  81. package/src/components/HeatMap/index.tsx +5 -0
  82. package/src/components/HeatMap/tests/HeatMap.test.tsx +1040 -0
  83. package/src/components/HeatMap/tests/helpers.test.ts +167 -0
  84. package/src/components/HorizonChart/HorizonChart.tsx +10 -2
  85. package/src/components/HoverLine/HoverLine.tsx +6 -8
  86. package/src/components/Legend/Legend.Component.tsx +4 -4
  87. package/src/components/Legend/LegendGroup/LegendGroup.styles.css +5 -0
  88. package/src/components/Legend/LegendGroup/LegendGroup.tsx +3 -1
  89. package/src/components/Legend/helpers/index.ts +0 -2
  90. package/src/components/LineChart/LineChartProps.ts +1 -0
  91. package/src/components/LineChart/components/LineChart.BumpCircle.tsx +22 -24
  92. package/src/components/LineChart/helpers.test.ts +345 -0
  93. package/src/components/LineChart/helpers.ts +15 -5
  94. package/src/components/LineChart/index.tsx +18 -5
  95. package/src/components/LinearChart/VisualizationRenderer.tsx +11 -1
  96. package/src/components/LinearChart/tests/LinearChart.test.tsx +228 -7
  97. package/src/components/LinearChart/tests/mockConfigContext.ts +1 -0
  98. package/src/components/LinearChart.tsx +155 -77
  99. package/src/components/PairedBarChart.jsx +2 -2
  100. package/src/components/PieChart/PieChart.tsx +15 -10
  101. package/src/components/RadarChart/RadarChart.tsx +3 -1
  102. package/src/components/Regions/components/Regions.tsx +199 -35
  103. package/src/components/Sankey/components/Sankey.tsx +7 -3
  104. package/src/components/Sankey/helpers/getSankeyTooltip.tsx +7 -6
  105. package/src/components/ScatterPlot/ScatterPlot.jsx +19 -17
  106. package/src/components/SmallMultiples/SmallMultipleTile.tsx +31 -3
  107. package/src/components/SmallMultiples/SmallMultiples.css +12 -0
  108. package/src/components/SmallMultiples/SmallMultiples.tsx +15 -9
  109. package/src/components/SmallMultiples/tests/SmallMultipleTile.test.tsx +152 -0
  110. package/src/components/Sparkline/components/SparkLine.tsx +5 -10
  111. package/src/components/WarmingStripes/WarmingStripes.tsx +5 -3
  112. package/src/components/WarmingStripes/WarmingStripesGradientLegend.css +1 -1
  113. package/src/components/WarmingStripes/WarmingStripesGradientLegend.tsx +2 -2
  114. package/src/data/initial-state.js +12 -0
  115. package/src/helpers/axisLabelFontSize.ts +8 -0
  116. package/src/helpers/bumpChartTooltip.ts +54 -0
  117. package/src/helpers/calculateLeftYAxisWidth.ts +197 -0
  118. package/src/helpers/ensureSpecialChartAxisTypes.ts +11 -0
  119. package/src/helpers/getMinMax.ts +2 -3
  120. package/src/helpers/getYAxisAutoPadding.ts +8 -6
  121. package/src/helpers/getYAxisAutoPaddingMode.ts +17 -0
  122. package/src/helpers/handleChartAriaLabels.ts +23 -10
  123. package/src/helpers/seriesHighlight.ts +26 -0
  124. package/src/helpers/tests/axisLabelFontSize.test.ts +15 -0
  125. package/src/helpers/tests/bumpChartTooltip.test.ts +75 -0
  126. package/src/helpers/tests/ensureSpecialChartAxisTypes.test.ts +30 -0
  127. package/src/helpers/tests/getYAxisAutoPadding.test.ts +37 -0
  128. package/src/helpers/tests/getYAxisAutoPaddingMode.test.ts +90 -0
  129. package/src/helpers/tests/handleChartAriaLabels.test.ts +71 -0
  130. package/src/helpers/tests/seriesHighlight.test.ts +61 -0
  131. package/src/helpers/tests/tooltipHelpers.test.ts +151 -0
  132. package/src/helpers/tooltipHelpers.ts +176 -0
  133. package/src/hooks/tests/useScales.test.tsx +105 -0
  134. package/src/hooks/useHighlightedBars.ts +9 -16
  135. package/src/hooks/useProgrammaticTooltip.ts +16 -2
  136. package/src/hooks/useScales.ts +19 -9
  137. package/src/hooks/useSmallMultipleSynchronization.ts +3 -4
  138. package/src/hooks/useTooltip.tsx +167 -38
  139. package/src/index.jsx +5 -2
  140. package/src/scss/main.scss +32 -49
  141. package/src/test/CdcChart.test.jsx +24 -0
  142. package/src/types/ChartConfig.ts +19 -0
  143. package/src/types/ChartContext.ts +2 -0
  144. package/examples/chart-regression-2.json +0 -2360
  145. package/examples/feature/__data__/age-adjusted-rates.json +0 -1486
  146. package/examples/feature/__data__/city-temperature.json +0 -2198
  147. package/examples/feature/bar/Barchart_with_negative.json +0 -34
  148. package/examples/feature/boxplot/box-plot-data.json +0 -71
  149. package/examples/feature/boxplot/boxplot-data.json +0 -156
  150. package/examples/feature/combo/combochart-categories_are_numbers .json +0 -18
  151. package/examples/feature/forest-plot/log.json +0 -26
  152. package/examples/feature/tests-date-exclusions/date-exclusions-data.json +0 -162
  153. package/examples/region-issue.json +0 -2065
  154. package/examples/xaxis.json +0 -493
  155. package/src/_stories/Chart.SmallMultiples.stories.tsx +0 -63
  156. /package/examples/{data → __data__}/data-with-metadata.json +0 -0
  157. /package/examples/feature/bar/{tall-data.json → __data__/tall-data.json} +0 -0
  158. /package/examples/feature/deviation/{planet-deviation-data.json → __data__/planet-deviation-data.json} +0 -0
  159. /package/examples/feature/paired-bar/{paired-bar-data.json → __data__/paired-bar-data.json} +0 -0
  160. /package/examples/feature/paired-bar/{paired-bar-formatted.json → __data__/paired-bar-formatted.json} +0 -0
  161. /package/examples/feature/tests-case-rate/{case-rate-example-data.json → __data__/case-rate-example-data.json} +0 -0
  162. /package/examples/feature/tests-covid/{covid-example-data-confidence.json → __data__/covid-example-data-confidence.json} +0 -0
  163. /package/examples/feature/tests-covid/{covid-example-data.json → __data__/covid-example-data.json} +0 -0
  164. /package/examples/feature/tests-cutoff/{cutoff-example-data.json → __data__/cutoff-example-data.json} +0 -0
  165. /package/examples/feature/tests-non-numerics/{planet-example-data-nonnumeric.json → __data__/planet-example-data-nonnumeric.json} +0 -0
  166. /package/src/_stories/{Chart.Anchors.stories.tsx → Chart.Anchors.smoke.stories.tsx} +0 -0
  167. /package/src/_stories/{Chart.BoxPlot.stories.tsx → Chart.BoxPlot.smoke.stories.tsx} +0 -0
  168. /package/src/_stories/{Chart.CI.stories.tsx → Chart.CI.smoke.stories.tsx} +0 -0
  169. /package/src/_stories/{Chart.Combo.stories.tsx → Chart.Combo.smoke.stories.tsx} +0 -0
  170. /package/src/_stories/{Chart.CustomColors.stories.tsx → Chart.CustomColors.smoke.stories.tsx} +0 -0
  171. /package/src/_stories/{Chart.Defaults.stories.tsx → Chart.Defaults.smoke.stories.tsx} +0 -0
  172. /package/src/_stories/{Chart.DynamicSeries.stories.tsx → Chart.DynamicSeries.smoke.stories.tsx} +0 -0
  173. /package/src/_stories/{Chart.Filters.stories.tsx → Chart.Filters.smoke.stories.tsx} +0 -0
  174. /package/src/_stories/{Chart.Forecast.stories.tsx → Chart.Forecast.smoke.stories.tsx} +0 -0
  175. /package/src/_stories/{Chart.Legend.Gradient.stories.tsx → Chart.Legend.Gradient.smoke.stories.tsx} +0 -0
  176. /package/src/_stories/{Chart.Patterns.stories.tsx → Chart.Patterns.smoke.stories.tsx} +0 -0
  177. /package/src/_stories/{Chart.PreserveDecimals.stories.tsx → Chart.PreserveDecimals.smoke.stories.tsx} +0 -0
  178. /package/src/_stories/{Chart.Regions.Categorical.stories.tsx → Chart.Regions.Categorical.smoke.stories.tsx} +0 -0
  179. /package/src/_stories/{Chart.Regions.DateScale.stories.tsx → Chart.Regions.DateScale.smoke.stories.tsx} +0 -0
  180. /package/src/_stories/{Chart.Regions.DateTimeScale.stories.tsx → Chart.Regions.DateTimeScale.smoke.stories.tsx} +0 -0
  181. /package/src/_stories/{Chart.ScatterPlot.stories.tsx → Chart.ScatterPlot.smoke.stories.tsx} +0 -0
  182. /package/src/_stories/{Chart.SmallestLeftAxisMax.stories.tsx → Chart.SmallestLeftAxisMax.smoke.stories.tsx} +0 -0
  183. /package/src/_stories/{ChartAnnotation.stories.tsx → ChartAnnotation.smoke.stories.tsx} +0 -0
  184. /package/src/_stories/{ChartAxisLabels.stories.tsx → ChartAxisLabels.smoke.stories.tsx} +0 -0
  185. /package/src/_stories/{ChartBrush.Matrix.Continuous.stories.tsx → ChartBrush.Matrix.Continuous.smoke.stories.tsx} +0 -0
  186. /package/src/_stories/{ChartBrush.Matrix.Date.stories.tsx → ChartBrush.Matrix.Date.smoke.stories.tsx} +0 -0
  187. /package/src/_stories/{ChartBrush.Matrix.DateTime.stories.tsx → ChartBrush.Matrix.DateTime.smoke.stories.tsx} +0 -0
  188. /package/src/_stories/{ChartLine.QuadrantAngles.stories.tsx → ChartLine.QuadrantAngles.smoke.stories.tsx} +0 -0
  189. /package/src/_stories/{ChartLine.Suppression.stories.tsx → ChartLine.Suppression.smoke.stories.tsx} +0 -0
  190. /package/src/_stories/{ChartLine.Symbols.stories.tsx → ChartLine.Symbols.smoke.stories.tsx} +0 -0
  191. /package/src/_stories/{ChartPrefixSuffix.stories.tsx → ChartPrefixSuffix.smoke.stories.tsx} +0 -0
  192. /package/src/_stories/{TechAdoptionWithLinks.stories.tsx → TechAdoptionWithLinks.smoke.stories.tsx} +0 -0
@@ -1,4 +1,5 @@
1
1
  import { useContext, FC } from 'react'
2
+ import { Draggable } from '@hello-pangea/dnd'
2
3
  import {
3
4
  Accordion,
4
5
  AccordionItem,
@@ -6,6 +7,7 @@ import {
6
7
  AccordionItemPanel,
7
8
  AccordionItemButton
8
9
  } from 'react-accessible-accordion'
10
+ import GroupedList from '@cdc/core/components/EditorPanel/GroupedList'
9
11
  import Tooltip from '@cdc/core/components/ui/Tooltip'
10
12
  import Icon from '@cdc/core/components/ui/Icon'
11
13
  import Button from '@cdc/core/components/elements/Button'
@@ -20,6 +22,7 @@ import { sanitizeToSvgId } from '@cdc/core/helpers/cove/string'
20
22
 
21
23
  const PanelPatternSettings: FC<PanelProps> = props => {
22
24
  const { config, updateConfig, transformedData } = useContext<ChartContext>(ConfigContext)
25
+ if (config.visualizationType === 'HeatMap') return null
23
26
 
24
27
  type LegendPattern = {
25
28
  label?: string
@@ -153,6 +156,21 @@ const PanelPatternSettings: FC<PanelProps> = props => {
153
156
  const fieldOptions = getFieldOptions()
154
157
  const currentPatterns: Record<string, LegendPattern> = legendCfg.patterns || {}
155
158
 
159
+ const handlePatternReorder = (sourceIndex: number, destinationIndex: number) => {
160
+ const patternEntries = Object.entries(currentPatterns)
161
+ const [movedPattern] = patternEntries.splice(sourceIndex, 1)
162
+ patternEntries.splice(destinationIndex, 0, movedPattern)
163
+
164
+ const newPatterns = Object.fromEntries(patternEntries)
165
+ updateConfig({
166
+ ...config,
167
+ legend: {
168
+ ...config.legend,
169
+ patterns: newPatterns
170
+ }
171
+ })
172
+ }
173
+
156
174
  // Check if all patterns pass contrast requirements
157
175
  const checkPatternContrasts = () => {
158
176
  return Object.values(currentPatterns).every(pattern => pattern.contrastCheck !== false)
@@ -308,130 +326,165 @@ const PanelPatternSettings: FC<PanelProps> = props => {
308
326
  message='Pattern colors must comply with <a href="https://www.w3.org/TR/WCAG21/">WCAG 2.1</a> 3:1 contrast ratio.'
309
327
  showCloseButton={false}
310
328
  />
311
- <br />
312
329
  </>
313
330
  )}
314
331
 
315
- {/* Individual Pattern Configurations */}
316
- {Object.entries(currentPatterns).map(([patternKey, pattern], index) => {
317
- const p: LegendPattern = pattern || {}
318
- const domPatternKey = `${sanitizeToSvgId(patternKey)}-${index}`
319
-
320
- return (
321
- <Accordion allowZeroExpanded key={`pattern-accordion-${index}`}>
322
- <AccordionItem>
323
- <AccordionItemHeading>
324
- <AccordionItemButton>
325
- {p.dataKey && p.dataValue
326
- ? `${p.dataKey}: ${p.dataValue}`
327
- : p.dataValue
328
- ? `All Series: ${p.dataValue}`
329
- : `Pattern ${index + 1}`}
330
- </AccordionItemButton>
331
- </AccordionItemHeading>
332
- <AccordionItemPanel>
333
- {p.contrastCheck ?? true ? (
334
- <Alert type='success' message='This pattern passes contrast checks' showCloseButton={false} />
335
- ) : (
336
- <Alert
337
- type='danger'
338
- message='Error: <a href="https://webaim.org/resources/contrastchecker/" target="_blank"> Review Color Contrast</a>'
339
- showCloseButton={false}
340
- />
341
- )}
342
-
343
- <Select
344
- label='Data Key:'
345
- value={p.dataKey || ''}
346
- options={fieldOptions}
347
- initial='Select Data Key'
348
- fieldName={`pattern-datakey-${domPatternKey}`}
349
- updateField={(section, subsection, fieldName, value) =>
350
- handlePatternUpdate(patternKey, 'dataKey', value)
351
- }
352
- />
353
-
354
- <label htmlFor={`pattern-datavalue-${domPatternKey}`}>
355
- Data Value:
356
- <input
357
- type='text'
358
- id={`pattern-datavalue-${domPatternKey}`}
359
- value={p.dataValue || ''}
360
- onChange={e => handlePatternUpdate(patternKey, 'dataValue', e.target.value)}
361
- placeholder='Enter data value'
362
- />
363
- </label>
364
-
365
- <label htmlFor={`pattern-label-${domPatternKey}`}>
366
- Label (optional):
367
- <input
368
- type='text'
369
- id={`pattern-label-${domPatternKey}`}
370
- value={p.label || ''}
371
- onChange={e => handlePatternUpdate(patternKey, 'label', e.target.value)}
372
- />
373
- </label>
374
-
375
- <Select
376
- label='Pattern Type:'
377
- value={p.shape || 'circles'}
378
- options={patternTypes}
379
- fieldName={`pattern-type-${domPatternKey}`}
380
- updateField={(section, subsection, fieldName, value) =>
381
- handlePatternUpdate(patternKey, 'shape', value)
382
- }
383
- />
384
-
385
- <Select
386
- label='Pattern Size:'
387
- value={getPatternSizeText(p.patternSize || 8)}
388
- options={patternSizes}
389
- fieldName={`pattern-size-${domPatternKey}`}
390
- updateField={(section, subsection, fieldName, value) =>
391
- handlePatternUpdate(patternKey, 'patternSize', getPatternSizeNumeric(value))
392
- }
393
- />
394
-
395
- <div className='mt-3'>
396
- <label htmlFor={`pattern-color-${domPatternKey}`}>
397
- Pattern Color
398
- <Tooltip style={{ textTransform: 'none' }}>
399
- <Tooltip.Target>
400
- <Icon
401
- display='question'
402
- style={{ marginLeft: '0.5rem', display: 'inline-block', whiteSpace: 'nowrap' }}
332
+ <GroupedList
333
+ items={Object.entries(currentPatterns)}
334
+ label='Pattern'
335
+ droppableId='chart-patterns-order'
336
+ onDragEnd={({ source, destination }) => {
337
+ if (!destination || source.index === destination.index) return
338
+ handlePatternReorder(source.index, destination.index)
339
+ }}
340
+ renderItem={([patternKey, pattern], index) => {
341
+ const p: LegendPattern = pattern || {}
342
+ const domPatternKey = `${sanitizeToSvgId(patternKey)}-${index}`
343
+
344
+ return (
345
+ <Draggable
346
+ key={`pattern-${patternKey}-${index}`}
347
+ draggableId={`pattern-${patternKey}-${index}`}
348
+ index={index}
349
+ >
350
+ {(provided, snapshot) => (
351
+ <div
352
+ ref={provided.innerRef}
353
+ {...provided.draggableProps}
354
+ {...provided.dragHandleProps}
355
+ className={snapshot.isDragging ? 'currently-dragging' : ''}
356
+ style={provided.draggableProps.style}
357
+ >
358
+ <Accordion allowZeroExpanded preExpanded={['panel']}>
359
+ <AccordionItem uuid='panel' className='series-item series-item--chart'>
360
+ <AccordionItemHeading className='series-item__title'>
361
+ <AccordionItemButton className='accordion__button'>
362
+ <Icon display='move' size={15} style={{ cursor: 'default' }} />
363
+ {p.dataKey && p.dataValue
364
+ ? `${p.dataKey}: ${p.dataValue}`
365
+ : p.dataValue
366
+ ? `All Series: ${p.dataValue}`
367
+ : `Pattern ${index + 1}`}
368
+ </AccordionItemButton>
369
+ </AccordionItemHeading>
370
+ <AccordionItemPanel>
371
+ <div className='series-item__panel-actions'>
372
+ <Button
373
+ type='button'
374
+ variant='danger'
375
+ size='sm'
376
+ className='grouped-list__remove'
377
+ onClick={() => handleRemovePattern(patternKey)}
378
+ >
379
+ Remove Pattern
380
+ </Button>
381
+ </div>
382
+ {p.contrastCheck ?? true ? (
383
+ <Alert
384
+ type='success'
385
+ message='This pattern passes contrast checks'
386
+ showCloseButton={false}
387
+ />
388
+ ) : (
389
+ <Alert
390
+ type='danger'
391
+ message='Error: <a href="https://webaim.org/resources/contrastchecker/" target="_blank"> Review Color Contrast</a>'
392
+ showCloseButton={false}
393
+ />
394
+ )}
395
+
396
+ <Select
397
+ label='Data Key:'
398
+ value={p.dataKey || ''}
399
+ options={fieldOptions}
400
+ initial='Select Data Key'
401
+ fieldName={`pattern-datakey-${domPatternKey}`}
402
+ updateField={(section, subsection, fieldName, value) =>
403
+ handlePatternUpdate(patternKey, 'dataKey', value)
404
+ }
405
+ />
406
+
407
+ <label htmlFor={`pattern-datavalue-${domPatternKey}`}>
408
+ Data Value:
409
+ <input
410
+ type='text'
411
+ id={`pattern-datavalue-${domPatternKey}`}
412
+ value={p.dataValue || ''}
413
+ onChange={e => handlePatternUpdate(patternKey, 'dataValue', e.target.value)}
414
+ placeholder='Enter data value'
415
+ />
416
+ </label>
417
+
418
+ <label htmlFor={`pattern-label-${domPatternKey}`}>
419
+ Label (optional):
420
+ <input
421
+ type='text'
422
+ id={`pattern-label-${domPatternKey}`}
423
+ value={p.label || ''}
424
+ onChange={e => handlePatternUpdate(patternKey, 'label', e.target.value)}
425
+ />
426
+ </label>
427
+
428
+ <Select
429
+ label='Pattern Type:'
430
+ value={p.shape || 'circles'}
431
+ options={patternTypes}
432
+ fieldName={`pattern-type-${domPatternKey}`}
433
+ updateField={(section, subsection, fieldName, value) =>
434
+ handlePatternUpdate(patternKey, 'shape', value)
435
+ }
403
436
  />
404
- </Tooltip.Target>
405
- <Tooltip.Content>
406
- <p>
407
- If this setting is used, it is the responsibility of the visualization author to verify the
408
- visualization colors meet WCAG 3:1 contrast ratios.
409
- </p>
410
- </Tooltip.Content>
411
- </Tooltip>
412
- <input
413
- type='text'
414
- value={p.color || ''}
415
- id={`pattern-color-${domPatternKey}`}
416
- onChange={e => handlePatternUpdate(patternKey, 'color', e.target.value)}
417
- placeholder='#666666'
418
- />
419
- </label>
420
- </div>
421
437
 
422
- <Button onClick={() => handleRemovePattern(patternKey)} className='btn btn-danger'>
423
- Remove Pattern
424
- </Button>
425
- </AccordionItemPanel>
426
- </AccordionItem>
427
- </Accordion>
428
- )
429
- })}
438
+ <Select
439
+ label='Pattern Size:'
440
+ value={getPatternSizeText(p.patternSize || 8)}
441
+ options={patternSizes}
442
+ fieldName={`pattern-size-${domPatternKey}`}
443
+ updateField={(section, subsection, fieldName, value) =>
444
+ handlePatternUpdate(patternKey, 'patternSize', getPatternSizeNumeric(value))
445
+ }
446
+ />
447
+
448
+ <div className='mt-3'>
449
+ <label htmlFor={`pattern-color-${domPatternKey}`}>
450
+ Pattern Color
451
+ <Tooltip style={{ textTransform: 'none' }}>
452
+ <Tooltip.Target>
453
+ <Icon
454
+ display='question'
455
+ style={{ marginLeft: '0.5rem', display: 'inline-block', whiteSpace: 'nowrap' }}
456
+ />
457
+ </Tooltip.Target>
458
+ <Tooltip.Content>
459
+ <p>
460
+ If this setting is used, it is the responsibility of the visualization author to
461
+ verify the visualization colors meet WCAG 3:1 contrast ratios.
462
+ </p>
463
+ </Tooltip.Content>
464
+ </Tooltip>
465
+ <input
466
+ type='text'
467
+ value={p.color || ''}
468
+ id={`pattern-color-${domPatternKey}`}
469
+ onChange={e => handlePatternUpdate(patternKey, 'color', e.target.value)}
470
+ placeholder='#666666'
471
+ />
472
+ </label>
473
+ </div>
474
+ </AccordionItemPanel>
475
+ </AccordionItem>
476
+ </Accordion>
477
+ </div>
478
+ )}
479
+ </Draggable>
480
+ )
481
+ }}
482
+ />
430
483
 
431
484
  {/* Add Pattern Button */}
432
- <button className='btn btn-primary full-width mt-2' onClick={handleAddPattern}>
485
+ <Button variant='editor-primary' onClick={handleAddPattern}>
433
486
  Add Pattern
434
- </button>
487
+ </Button>
435
488
 
436
489
  {Object.keys(currentPatterns).length === 0 && (
437
490
  <p style={{ color: '#666', fontStyle: 'italic' }}>
@@ -1,19 +1,26 @@
1
1
  import { memo, useContext } from 'react'
2
+ import { Draggable } from '@hello-pangea/dnd'
2
3
  import { useEditorPermissions } from '../../useEditorPermissions.js'
3
4
  import {
5
+ Accordion,
4
6
  AccordionItem,
5
7
  AccordionItemHeading,
6
8
  AccordionItemPanel,
7
9
  AccordionItemButton
8
10
  } from 'react-accessible-accordion'
9
11
  import { type ChartConfig } from '../../../../types/ChartConfig.js'
12
+ import GroupedList from '@cdc/core/components/EditorPanel/GroupedList'
10
13
  import { TextField, Select } from '@cdc/core/components/EditorPanel/Inputs'
11
14
  import Tooltip from '@cdc/core/components/ui/Tooltip'
12
15
  import Icon from '@cdc/core/components/ui/Icon'
16
+ import Button from '@cdc/core/components/elements/Button'
17
+ import { APP_FONT_COLOR } from '@cdc/core/helpers/constants'
13
18
  import { type ChartContext } from '../../../../types/ChartContext.js'
14
19
  import { type PanelProps } from '../PanelProps.js'
15
20
  import ConfigContext from '../../../../ConfigContext.js'
16
21
 
22
+ const DEFAULT_REGION_BACKGROUND = getComputedStyle(document.documentElement).getPropertyValue('--cool-gray-50').trim()
23
+
17
24
  const RegionSettings = memo(({ config, updateConfig }: { config: ChartConfig; updateConfig: Function }) => {
18
25
  let regionUpdate = (fieldName, value, i) => {
19
26
  let regions = []
@@ -53,129 +60,172 @@ const RegionSettings = memo(({ config, updateConfig }: { config: ChartConfig; up
53
60
  updateConfig({ ...config, regions })
54
61
  }
55
62
 
63
+ const moveRegion = (sourceIndex, destinationIndex) => {
64
+ const regions = config.regions ? [...config.regions] : []
65
+ const [movedRegion] = regions.splice(sourceIndex, 1)
66
+ regions.splice(destinationIndex, 0, movedRegion)
67
+ updateConfig({ ...config, regions })
68
+ }
69
+
56
70
  const fromOptions = ['Fixed', 'Previous Days']
57
71
  const toOptions = ['Last Date', 'Fixed']
58
72
 
59
73
  return (
60
74
  <>
61
- {config.regions &&
62
- config.regions.map(({ label, color, from, to, background, range = 'Custom' }, i) => (
63
- <div className='edit-block' key={`region-${i}`}>
64
- <button
65
- type='button'
66
- className='btn btn-danger remove-column'
67
- onClick={event => {
68
- event.preventDefault()
69
- removeColumn(i)
70
- }}
71
- >
72
- Remove
73
- </button>
74
- <TextField value={label} label='Region Label' fieldName='label' i={i} updateField={updateField} />
75
- <div className='two-col-inputs'>
76
- <TextField
77
- value={color}
78
- label='Text Color'
79
- fieldName='color'
80
- updateField={(section, subsection, fieldName, value) => regionUpdate(fieldName, value, i)}
81
- />
82
- <TextField
83
- value={background}
84
- label='Background'
85
- fieldName='background'
86
- updateField={(section, subsection, fieldName, value) => regionUpdate(fieldName, value, i)}
87
- />
88
- </div>
89
-
90
- <Select
91
- value={config.regions[i].fromType ?? 'Fixed'}
92
- label='Minimum Region Type'
93
- initial={'Select'}
94
- required={true}
95
- onChange={e => {
96
- if (e.target.value !== '' && e.target.value !== 'Select') {
97
- const newRegions = [...config.regions]
98
- newRegions[i].fromType = e.target.value
99
- updateConfig({
100
- ...config,
101
- regions: newRegions
102
- })
103
- }
104
- e.target.value = ''
105
- }}
106
- options={fromOptions}
107
- />
108
-
109
- {(config.regions[i].fromType === 'Fixed' ||
110
- config.regions[i].fromType === 'Previous Days' ||
111
- !config.regions[i].fromType) && (
112
- <>
113
- <TextField
114
- value={from}
115
- label={
116
- config.regions[i].fromType === 'Fixed' || !config.regions[i]?.fromType
117
- ? 'From Value'
118
- : 'Previous Number of Days'
119
- }
120
- fieldName='from'
121
- updateField={(section, subsection, fieldName, value) => regionUpdate(fieldName, value, i)}
122
- tooltip={
123
- <Tooltip style={{ textTransform: 'none' }}>
124
- <Tooltip.Target>
125
- <Icon display='question' style={{ marginLeft: '0.5rem' }} />
126
- </Tooltip.Target>
127
- <Tooltip.Content>
128
- <p>
129
- When using categorical (linear scale) match the data set value. When using date (linear / date
130
- time scale) match the x-axis value.
131
- </p>
132
- </Tooltip.Content>
133
- </Tooltip>
134
- }
135
- />
136
- </>
137
- )}
138
-
139
- <Select
140
- value={config.regions[i].toType ?? 'Fixed'}
141
- label='Maximum Region Type'
142
- initial={'Select'}
143
- required={true}
144
- onChange={e => {
145
- if (e.target.value !== '' && e.target.value !== 'Select') {
146
- const newRegions = [...config.regions]
147
- newRegions[i].toType = e.target.value
148
- updateConfig({
149
- ...config,
150
- regions: newRegions
151
- })
152
- }
153
- e.target.value = ''
154
- }}
155
- options={toOptions}
156
- />
157
-
158
- {(config.regions[i].toType === 'Fixed' || !config.regions[i].toType) && (
159
- <TextField
160
- value={to}
161
- label='To Value'
162
- fieldName='to'
163
- updateField={(section, subsection, fieldName, value) => regionUpdate(fieldName, value, i)}
164
- />
75
+ <GroupedList
76
+ items={config.regions}
77
+ label='Regions'
78
+ droppableId='chart-regions-order'
79
+ onDragEnd={({ source, destination }) => {
80
+ if (!destination || source.index === destination.index) return
81
+ moveRegion(source.index, destination.index)
82
+ }}
83
+ renderItem={({ label, color, from, to, background, range = 'Custom' }, i) => (
84
+ <Draggable key={`region-${i}`} draggableId={`region-${i}`} index={i}>
85
+ {(provided, snapshot) => (
86
+ <div
87
+ className={snapshot.isDragging ? 'currently-dragging' : ''}
88
+ style={provided.draggableProps.style}
89
+ ref={provided.innerRef}
90
+ {...provided.draggableProps}
91
+ {...provided.dragHandleProps}
92
+ >
93
+ <Accordion allowZeroExpanded preExpanded={['panel']}>
94
+ <AccordionItem uuid='panel' className='series-item series-item--chart'>
95
+ <AccordionItemHeading className='series-item__title'>
96
+ <AccordionItemButton className='accordion__button'>
97
+ <Icon display='move' size={15} style={{ cursor: 'default' }} />
98
+ {label || `Region ${i + 1}`}
99
+ </AccordionItemButton>
100
+ </AccordionItemHeading>
101
+ <AccordionItemPanel>
102
+ <div className='series-item__panel-actions'>
103
+ <Button
104
+ type='button'
105
+ variant='danger'
106
+ size='sm'
107
+ className='grouped-list__remove'
108
+ onClick={event => {
109
+ event.preventDefault()
110
+ removeColumn(i)
111
+ }}
112
+ >
113
+ Remove
114
+ </Button>
115
+ </div>
116
+ <TextField value={label} label='Region Label' fieldName='label' i={i} updateField={updateField} />
117
+ <div className='two-col-inputs'>
118
+ <TextField
119
+ value={color}
120
+ label='Text Color'
121
+ fieldName='color'
122
+ placeholder={APP_FONT_COLOR}
123
+ updateField={(section, subsection, fieldName, value) => regionUpdate(fieldName, value, i)}
124
+ />
125
+ <TextField
126
+ value={background}
127
+ label='Background'
128
+ fieldName='background'
129
+ placeholder={DEFAULT_REGION_BACKGROUND}
130
+ updateField={(section, subsection, fieldName, value) => regionUpdate(fieldName, value, i)}
131
+ />
132
+ </div>
133
+
134
+ <Select
135
+ value={config.regions[i].fromType ?? 'Fixed'}
136
+ label='Minimum Region Type'
137
+ initial={'Select'}
138
+ required={true}
139
+ onChange={e => {
140
+ if (e.target.value !== '' && e.target.value !== 'Select') {
141
+ const newRegions = [...config.regions]
142
+ newRegions[i].fromType = e.target.value
143
+ updateConfig({
144
+ ...config,
145
+ regions: newRegions
146
+ })
147
+ }
148
+ e.target.value = ''
149
+ }}
150
+ options={fromOptions}
151
+ />
152
+
153
+ {(config.regions[i].fromType === 'Fixed' ||
154
+ config.regions[i].fromType === 'Previous Days' ||
155
+ !config.regions[i].fromType) && (
156
+ <>
157
+ <TextField
158
+ value={from}
159
+ label={
160
+ config.regions[i].fromType === 'Fixed' || !config.regions[i]?.fromType
161
+ ? 'From Value'
162
+ : 'Previous Number of Days'
163
+ }
164
+ fieldName='from'
165
+ updateField={(section, subsection, fieldName, value) => regionUpdate(fieldName, value, i)}
166
+ tooltip={
167
+ <Tooltip style={{ textTransform: 'none' }}>
168
+ <Tooltip.Target>
169
+ <Icon display='question' style={{ marginLeft: '0.5rem' }} />
170
+ </Tooltip.Target>
171
+ <Tooltip.Content>
172
+ <p>
173
+ When using categorical (linear scale) match the data set value. When using date
174
+ (linear / date time scale) match the x-axis value.
175
+ </p>
176
+ </Tooltip.Content>
177
+ </Tooltip>
178
+ }
179
+ />
180
+ </>
181
+ )}
182
+
183
+ <Select
184
+ value={config.regions[i].toType ?? 'Fixed'}
185
+ label='Maximum Region Type'
186
+ initial={'Select'}
187
+ required={true}
188
+ onChange={e => {
189
+ if (e.target.value !== '' && e.target.value !== 'Select') {
190
+ const newRegions = [...config.regions]
191
+ newRegions[i].toType = e.target.value
192
+ updateConfig({
193
+ ...config,
194
+ regions: newRegions
195
+ })
196
+ }
197
+ e.target.value = ''
198
+ }}
199
+ options={toOptions}
200
+ />
201
+
202
+ {(config.regions[i].toType === 'Fixed' || !config.regions[i].toType) && (
203
+ <TextField
204
+ value={to}
205
+ label='To Value'
206
+ fieldName='to'
207
+ updateField={(section, subsection, fieldName, value) => regionUpdate(fieldName, value, i)}
208
+ />
209
+ )}
210
+ </AccordionItemPanel>
211
+ </AccordionItem>
212
+ </Accordion>
213
+ </div>
165
214
  )}
166
- </div>
167
- ))}
215
+ </Draggable>
216
+ )}
217
+ />
168
218
  {!config.regions && <p style={{ textAlign: 'center' }}>There are currently no regions.</p>}
169
- <button
219
+ <Button
170
220
  type='button'
171
- className='btn btn-primary full-width'
221
+ variant='editor-primary'
172
222
  onClick={e => {
173
223
  e.preventDefault()
174
224
  addColumn()
175
225
  }}
176
226
  >
177
227
  Add Region
178
- </button>
228
+ </Button>
179
229
  </>
180
230
  )
181
231
  })