@cdc/chart 4.26.4 → 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 (93) hide show
  1. package/CONFIG.md +349 -29
  2. package/LICENSE +201 -0
  3. package/dist/cdcchart.js +34247 -32805
  4. package/examples/feature/heatmap/average-age-categorical.json +313 -0
  5. package/examples/feature/heatmap/calendar-heatmap.json +134 -0
  6. package/examples/feature/heatmap/cell-values-bottom-axis.json +181 -0
  7. package/examples/feature/heatmap/sparse-aggregation.json +158 -0
  8. package/examples/private/bump-chart.json +1116 -0
  9. package/examples/private/test-123.json +624 -0
  10. package/package.json +4 -3
  11. package/src/CdcChartComponent.tsx +135 -54
  12. package/src/_stories/Chart.AltText.stories.tsx +136 -0
  13. package/src/_stories/Chart.HeatMap.stories.tsx +187 -0
  14. package/src/_stories/Chart.Sankey.smoke.stories.tsx +62 -0
  15. package/src/_stories/Chart.SmallMultiples.smoke.stories.tsx +29 -0
  16. package/src/_stories/ChartAxisTitles.smoke.stories.tsx +13 -0
  17. package/src/_stories/ChartBar.Editor.stories.tsx +63 -0
  18. package/src/_stories/ChartBrush.Interaction.stories.tsx +112 -0
  19. package/src/_stories/ChartBrush.smoke.stories.tsx +13 -4
  20. package/src/_stories/_mock/alt_text_metadata.json +38 -0
  21. package/src/_stories/_mock/heatmap-average-age-categorical.json +315 -0
  22. package/src/_stories/_mock/heatmap.json +156 -0
  23. package/src/_stories/_mock/reported_pertussis_cases.json +1643 -0
  24. package/src/_stories/_mock/sankey_mock.json +78 -0
  25. package/src/_stories/_mock/small_multiples/small_multiples_lines.json +1 -1
  26. package/src/components/Annotations/components/AnnotationDraggable.tsx +1 -1
  27. package/src/components/Annotations/components/AnnotationDropdown.tsx +5 -2
  28. package/src/components/Axis/LeftAxis.tsx +14 -11
  29. package/src/components/Axis/LeftAxisGridlines.tsx +14 -11
  30. package/src/components/Brush/BrushSelector.tsx +88 -187
  31. package/src/components/Brush/brushInteraction.ts +22 -0
  32. package/src/components/Brush/tests/brushInteraction.test.ts +38 -0
  33. package/src/components/EditorPanel/EditorPanel.tsx +310 -49
  34. package/src/components/EditorPanel/components/Panels/Panel.Annotate.tsx +13 -0
  35. package/src/components/EditorPanel/components/Panels/Panel.General.tsx +105 -13
  36. package/src/components/EditorPanel/components/Panels/Panel.PatternSettings.tsx +1 -0
  37. package/src/components/EditorPanel/components/Panels/Panel.Regions.tsx +5 -0
  38. package/src/components/EditorPanel/components/Panels/Panel.Series.tsx +2 -2
  39. package/src/components/EditorPanel/components/Panels/Panel.Visual.tsx +1 -1
  40. package/src/components/EditorPanel/helpers/getVisualizationTypeConfigUpdate.ts +57 -0
  41. package/src/components/EditorPanel/helpers/tests/getVisualizationTypeConfigUpdate.test.ts +56 -0
  42. package/src/components/EditorPanel/useEditorPermissions.test.tsx +70 -0
  43. package/src/components/EditorPanel/useEditorPermissions.ts +42 -22
  44. package/src/components/HeatMap/components/HeatMap.tsx +762 -0
  45. package/src/components/HeatMap/components/HeatMapGradientLegend.tsx +114 -0
  46. package/src/components/HeatMap/heatmap.constants.ts +13 -0
  47. package/src/components/HeatMap/heatmap.css +71 -0
  48. package/src/components/HeatMap/helpers/index.ts +261 -0
  49. package/src/components/HeatMap/index.tsx +5 -0
  50. package/src/components/HeatMap/tests/HeatMap.test.tsx +1040 -0
  51. package/src/components/HeatMap/tests/helpers.test.ts +167 -0
  52. package/src/components/LineChart/components/LineChart.BumpCircle.tsx +19 -22
  53. package/src/components/LineChart/helpers.test.ts +345 -0
  54. package/src/components/LineChart/helpers.ts +15 -5
  55. package/src/components/LineChart/index.tsx +7 -1
  56. package/src/components/LinearChart/tests/LinearChart.test.tsx +107 -33
  57. package/src/components/LinearChart/tests/mockConfigContext.ts +1 -0
  58. package/src/components/LinearChart.tsx +58 -37
  59. package/src/components/PieChart/PieChart.tsx +4 -1
  60. package/src/components/RadarChart/RadarChart.tsx +3 -1
  61. package/src/components/Regions/components/Regions.tsx +195 -41
  62. package/src/components/Sankey/components/Sankey.tsx +7 -3
  63. package/src/components/Sankey/helpers/getSankeyTooltip.tsx +7 -6
  64. package/src/components/SmallMultiples/SmallMultipleTile.tsx +30 -3
  65. package/src/components/SmallMultiples/SmallMultiples.css +12 -0
  66. package/src/components/SmallMultiples/SmallMultiples.tsx +15 -9
  67. package/src/components/SmallMultiples/tests/SmallMultipleTile.test.tsx +152 -0
  68. package/src/components/Sparkline/components/SparkLine.tsx +4 -9
  69. package/src/components/WarmingStripes/WarmingStripesGradientLegend.css +1 -1
  70. package/src/components/WarmingStripes/WarmingStripesGradientLegend.tsx +2 -2
  71. package/src/data/initial-state.js +11 -0
  72. package/src/helpers/axisLabelFontSize.ts +8 -0
  73. package/src/helpers/bumpChartTooltip.ts +54 -0
  74. package/src/helpers/calculateLeftYAxisWidth.ts +4 -0
  75. package/src/helpers/ensureSpecialChartAxisTypes.ts +11 -0
  76. package/src/helpers/getMinMax.ts +2 -3
  77. package/src/helpers/getYAxisAutoPadding.ts +8 -6
  78. package/src/helpers/getYAxisAutoPaddingMode.ts +17 -0
  79. package/src/helpers/handleChartAriaLabels.ts +23 -10
  80. package/src/helpers/seriesHighlight.ts +26 -0
  81. package/src/helpers/tests/axisLabelFontSize.test.ts +15 -0
  82. package/src/helpers/tests/bumpChartTooltip.test.ts +75 -0
  83. package/src/helpers/tests/ensureSpecialChartAxisTypes.test.ts +30 -0
  84. package/src/helpers/tests/getYAxisAutoPadding.test.ts +37 -0
  85. package/src/helpers/tests/getYAxisAutoPaddingMode.test.ts +90 -0
  86. package/src/helpers/tests/handleChartAriaLabels.test.ts +71 -0
  87. package/src/helpers/tests/seriesHighlight.test.ts +61 -0
  88. package/src/hooks/tests/useScales.test.tsx +105 -0
  89. package/src/hooks/useScales.ts +19 -9
  90. package/src/index.jsx +5 -2
  91. package/src/scss/main.scss +32 -49
  92. package/src/types/ChartConfig.ts +18 -0
  93. package/src/types/ChartContext.ts +2 -0
@@ -0,0 +1,38 @@
1
+ import { describe, expect, it } from 'vitest'
2
+ import { classifyBrushInteraction } from '../brushInteraction'
3
+
4
+ describe('classifyBrushInteraction', () => {
5
+ const extent = { x0: 100, x1: 300 }
6
+ const handleHitWidth = 24
7
+
8
+ it('allows interactions inside the selection', () => {
9
+ expect(classifyBrushInteraction(200, extent, handleHitWidth)).toBe('selection')
10
+ })
11
+
12
+ it('allows interactions within the left handle hit area', () => {
13
+ expect(classifyBrushInteraction(82, extent, handleHitWidth)).toBe('left-handle')
14
+ expect(classifyBrushInteraction(124, extent, handleHitWidth)).toBe('left-handle')
15
+ })
16
+
17
+ it('allows interactions within the right handle hit area', () => {
18
+ expect(classifyBrushInteraction(276, extent, handleHitWidth)).toBe('right-handle')
19
+ expect(classifyBrushInteraction(318, extent, handleHitWidth)).toBe('right-handle')
20
+ })
21
+
22
+ it('blocks interactions outside the selection and handle hit areas', () => {
23
+ expect(classifyBrushInteraction(50, extent, handleHitWidth)).toBe('outside')
24
+ expect(classifyBrushInteraction(350, extent, handleHitWidth)).toBe('outside')
25
+ })
26
+
27
+ it('handles reversed extents', () => {
28
+ expect(classifyBrushInteraction(200, { x0: 300, x1: 100 }, handleHitWidth)).toBe('selection')
29
+ expect(classifyBrushInteraction(82, { x0: 300, x1: 100 }, handleHitWidth)).toBe('left-handle')
30
+ expect(classifyBrushInteraction(318, { x0: 300, x1: 100 }, handleHitWidth)).toBe('right-handle')
31
+ })
32
+
33
+ it('blocks interactions for uninitialized or invalid extents', () => {
34
+ expect(classifyBrushInteraction(100, null, handleHitWidth)).toBe('outside')
35
+ expect(classifyBrushInteraction(100, { x0: -1, x1: -1 }, handleHitWidth)).toBe('outside')
36
+ expect(classifyBrushInteraction(100, { x0: Number.NaN, x1: 200 }, handleHitWidth)).toBe('outside')
37
+ })
38
+ })
@@ -61,6 +61,11 @@ import { paletteMigrationMap, twoColorPaletteMigrationMap } from '@cdc/core/help
61
61
  import { isV1Palette, migratePaletteWithMap } from '@cdc/core/helpers/palettes/utils'
62
62
  import { USE_V2_MIGRATION } from '@cdc/core/helpers/constants'
63
63
  import { getSeriesOwnedColumnNames } from '../../helpers/seriesColumnSettings'
64
+ import {
65
+ HEATMAP_CONFIG_DEFAULTS,
66
+ MAX_HEATMAP_COLOR_BUCKETS,
67
+ MIN_HEATMAP_COLOR_BUCKETS
68
+ } from '../HeatMap/heatmap.constants'
64
69
 
65
70
  interface PreliminaryProps {
66
71
  config: ChartConfig
@@ -1189,12 +1194,23 @@ const EditorPanel: React.FC<ChartEditorPanelProps> = ({ datasets }) => {
1189
1194
  return filter ? filteredColumns : allColumns
1190
1195
  }
1191
1196
 
1197
+ const isHeatMap = config.visualizationType === 'HeatMap'
1198
+ const heatMapOwnedColumnNames = useMemo(
1199
+ () => [config.xAxis?.dataKey, ...(config.series || []).map(series => series.dataKey)].filter(Boolean),
1200
+ [config.xAxis?.dataKey, config.series]
1201
+ )
1202
+
1192
1203
  const getLegendStyleOptions = (option: 'style' | 'subStyle' | 'shapes' | 'groupBy'): string[] => {
1193
1204
  const options: string[] = []
1194
1205
 
1195
1206
  switch (option) {
1196
1207
  case 'style':
1197
1208
  options.push('circles', 'boxes')
1209
+ if (config.visualizationType === 'HeatMap') {
1210
+ if (!['right', 'left'].includes(config.legend.position) && config.legend.position) {
1211
+ options.push('gradient')
1212
+ }
1213
+ }
1198
1214
  if (
1199
1215
  config.visualizationType === 'Bar' &&
1200
1216
  (!['right', 'left'].includes(config.legend.position) || !config.legend.position)
@@ -1338,6 +1354,15 @@ const EditorPanel: React.FC<ChartEditorPanelProps> = ({ datasets }) => {
1338
1354
  if (config.isLollipopChart && config?.series?.length === 0) {
1339
1355
  config.runtime.editorErrorMessage = 'Add a data series'
1340
1356
  }
1357
+ if (config.visualizationType === 'HeatMap') {
1358
+ const missingMappings = [
1359
+ !config.xAxis?.dataKey && 'Date/Category Axis',
1360
+ (!config.series || config.series.length === 0) && 'Data Series'
1361
+ ].filter(Boolean)
1362
+
1363
+ config.runtime.editorErrorMessage =
1364
+ missingMappings.length > 0 ? `${missingMappings.join(', ')} must be set for heatmaps.` : ''
1365
+ }
1341
1366
 
1342
1367
  const section = config.orientation === 'horizontal' ? 'xAxis' : 'yAxis'
1343
1368
  const [warningMsg, setWarningMsg] = useState({ maxMsg: '', minMsg: '', rightMaxMessage: '', minMsgRight: '' })
@@ -1737,6 +1762,7 @@ const EditorPanel: React.FC<ChartEditorPanelProps> = ({ datasets }) => {
1737
1762
  'Bump Chart',
1738
1763
  'Area Chart',
1739
1764
  'Combo',
1765
+ 'HeatMap',
1740
1766
  'Line',
1741
1767
  'Bar',
1742
1768
  'Forecasting',
@@ -1920,6 +1946,76 @@ const EditorPanel: React.FC<ChartEditorPanelProps> = ({ datasets }) => {
1920
1946
  return strippedState
1921
1947
  }
1922
1948
 
1949
+ const renderHeatMapSettingsAccordion = () => {
1950
+ if (config.visualizationType !== 'HeatMap') return null
1951
+
1952
+ return (
1953
+ <AccordionItem>
1954
+ <AccordionItemHeading>
1955
+ <AccordionItemButton>HeatMap Settings</AccordionItemButton>
1956
+ </AccordionItemHeading>
1957
+ <AccordionItemPanel>
1958
+ <p className='helper-text'>Selected data series columns provide the heatmap cell values.</p>
1959
+ <CheckBox
1960
+ value={Boolean(config.heatmap?.showCellValues ?? HEATMAP_CONFIG_DEFAULTS.showCellValues)}
1961
+ section='heatmap'
1962
+ fieldName='showCellValues'
1963
+ label='Show Cell Values'
1964
+ updateField={updateFieldDeprecated}
1965
+ />
1966
+ <TextField
1967
+ value={config.heatmap?.cellPadding ?? HEATMAP_CONFIG_DEFAULTS.cellPadding}
1968
+ type='number'
1969
+ min={0}
1970
+ section='heatmap'
1971
+ fieldName='cellPadding'
1972
+ label='Cell Padding'
1973
+ updateField={updateFieldDeprecated}
1974
+ />
1975
+ <TextField
1976
+ value={config.heatmap?.rowLabelGap ?? HEATMAP_CONFIG_DEFAULTS.rowLabelGap}
1977
+ type='number'
1978
+ min={0}
1979
+ section='heatmap'
1980
+ fieldName='rowLabelGap'
1981
+ label='Row Label Gap'
1982
+ updateField={updateFieldDeprecated}
1983
+ />
1984
+ <TextField
1985
+ value={config.heatmap?.columnLabelGap ?? HEATMAP_CONFIG_DEFAULTS.columnLabelGap}
1986
+ type='number'
1987
+ min={0}
1988
+ section='heatmap'
1989
+ fieldName='columnLabelGap'
1990
+ label='Column Label Gap'
1991
+ updateField={updateFieldDeprecated}
1992
+ />
1993
+ <TextField
1994
+ value={config.heatmap?.colorBucketCount ?? HEATMAP_CONFIG_DEFAULTS.colorBucketCount}
1995
+ type='number'
1996
+ min={MIN_HEATMAP_COLOR_BUCKETS}
1997
+ max={MAX_HEATMAP_COLOR_BUCKETS}
1998
+ step={1}
1999
+ section='heatmap'
2000
+ fieldName='colorBucketCount'
2001
+ label='Data Grouping'
2002
+ updateField={updateFieldDeprecated}
2003
+ tooltip={
2004
+ <Tooltip style={{ textTransform: 'none' }}>
2005
+ <Tooltip.Target>
2006
+ <Icon display='question' style={{ marginLeft: '0.5rem' }} />
2007
+ </Tooltip.Target>
2008
+ <Tooltip.Content>
2009
+ Choose the number of value groups used to color the heatmap. Values are limited to 1-9.
2010
+ </Tooltip.Content>
2011
+ </Tooltip>
2012
+ }
2013
+ />
2014
+ </AccordionItemPanel>
2015
+ </AccordionItem>
2016
+ )
2017
+ }
2018
+
1923
2019
  return (
1924
2020
  <EditorPanelContext.Provider value={editorContextValues}>
1925
2021
  <BaseEditorPanel
@@ -1950,7 +2046,10 @@ const EditorPanel: React.FC<ChartEditorPanelProps> = ({ datasets }) => {
1950
2046
  {(!config.series ||
1951
2047
  config.series.length === 0 ||
1952
2048
  (config.visualizationType === 'Paired Bar' && config.series.length < 2)) &&
1953
- !config.dynamicSeries && <WarningImage width='25' className='warning-icon' />}
2049
+ !config.dynamicSeries &&
2050
+ config.visualizationType !== 'HeatMap' && (
2051
+ <WarningImage width='25' className='warning-icon' />
2052
+ )}
1954
2053
  </AccordionItemButton>
1955
2054
  </AccordionItemHeading>
1956
2055
  <AccordionItemPanel>
@@ -1987,9 +2086,21 @@ const EditorPanel: React.FC<ChartEditorPanelProps> = ({ datasets }) => {
1987
2086
  )}
1988
2087
  {(!visSupportsDynamicSeries() || !config.dynamicSeries) && (
1989
2088
  <>
2089
+ {config.visualizationType === 'HeatMap' && (
2090
+ <p className='helper-text'>
2091
+ Add one or more data series to create heatmap rows. Each selected series becomes a row
2092
+ in the heatmap, while the Date/Category Axis provides the columns.
2093
+ </p>
2094
+ )}
1990
2095
  {(!config.series || config.series.length === 0) &&
1991
2096
  !config.dynamicSeries &&
1992
- config.visualizationType !== 'Paired Bar' && (
2097
+ config.visualizationType !== 'Paired Bar' &&
2098
+ config.visualizationType !== 'HeatMap' && (
2099
+ <p className='warning'>At least one series is required</p>
2100
+ )}
2101
+ {(!config.series || config.series.length === 0) &&
2102
+ !config.dynamicSeries &&
2103
+ config.visualizationType === 'HeatMap' && (
1993
2104
  <p className='warning'>At least one series is required</p>
1994
2105
  )}
1995
2106
  {(!config.series || config.series.length === 0 || config.series.length < 2) &&
@@ -2010,7 +2121,7 @@ const EditorPanel: React.FC<ChartEditorPanelProps> = ({ datasets }) => {
2010
2121
  }
2011
2122
  e.target.value = ''
2012
2123
  }}
2013
- options={getColumns()}
2124
+ options={getColumns().filter(column => column !== config.xAxis?.dataKey)}
2014
2125
  />
2015
2126
  {config.series && config.series.length !== 0 && (
2016
2127
  <Panels.Series.Wrapper
@@ -2022,16 +2133,23 @@ const EditorPanel: React.FC<ChartEditorPanelProps> = ({ datasets }) => {
2022
2133
  droppableId='chart-series-order'
2023
2134
  label={
2024
2135
  <>
2025
- Displaying
2136
+ {config.visualizationType === 'HeatMap' ? 'Displaying Rows' : 'Displaying'}
2026
2137
  <Tooltip style={{ textTransform: 'none' }}>
2027
2138
  <Tooltip.Target>
2028
2139
  <Icon display='question' style={{ marginLeft: '0.5rem' }} />
2029
2140
  </Tooltip.Target>
2030
2141
  <Tooltip.Content>
2031
- <p>
2032
- A data series is a set of related data points plotted in a chart and
2033
- typically represented in the chart legend.
2034
- </p>
2142
+ {config.visualizationType === 'HeatMap' ? (
2143
+ <p>
2144
+ Each selected data series becomes a row in the heatmap. Reorder these
2145
+ entries to change the row order shown in the visualization.
2146
+ </p>
2147
+ ) : (
2148
+ <p>
2149
+ A data series is a set of related data points plotted in a chart and
2150
+ typically represented in the chart legend.
2151
+ </p>
2152
+ )}
2035
2153
  </Tooltip.Content>
2036
2154
  </Tooltip>
2037
2155
  </>
@@ -2108,9 +2226,9 @@ const EditorPanel: React.FC<ChartEditorPanelProps> = ({ datasets }) => {
2108
2226
  : config.orientation === 'vertical'
2109
2227
  ? 'Left Value Axis'
2110
2228
  : 'Value Axis'}
2111
- {config.visualizationType === 'Pie' && !config.yAxis.dataKey && (
2229
+ {config.visualizationType === 'Pie' && !config.yAxis.dataKey ? (
2112
2230
  <WarningImage width='25' className='warning-icon' />
2113
- )}
2231
+ ) : null}
2114
2232
  </AccordionItemButton>
2115
2233
  </AccordionItemHeading>
2116
2234
  <AccordionItemPanel>
@@ -2158,7 +2276,86 @@ const EditorPanel: React.FC<ChartEditorPanelProps> = ({ datasets }) => {
2158
2276
  />
2159
2277
  </>
2160
2278
  )}
2161
- {config.visualizationType !== 'Pie' && (
2279
+ {config.visualizationType === 'HeatMap' && (
2280
+ <>
2281
+ <TextField
2282
+ value={config.yAxis.label}
2283
+ section='yAxis'
2284
+ fieldName='label'
2285
+ label='Label'
2286
+ updateField={updateFieldDeprecated}
2287
+ maxLength={35}
2288
+ tooltip={
2289
+ <Tooltip style={{ textTransform: 'none' }}>
2290
+ <Tooltip.Target>
2291
+ <Icon display='question' style={{ marginLeft: '0.5rem' }} />
2292
+ </Tooltip.Target>
2293
+ <Tooltip.Content>
2294
+ <p>35 character limit</p>
2295
+ </Tooltip.Content>
2296
+ </Tooltip>
2297
+ }
2298
+ />
2299
+ <Select
2300
+ value={config.yAxis.titlePlacement || 'side'}
2301
+ section='yAxis'
2302
+ fieldName='titlePlacement'
2303
+ label='Label Placement'
2304
+ updateField={updateFieldDeprecated}
2305
+ options={[
2306
+ { value: 'side', label: 'Side' },
2307
+ { value: 'top', label: 'Top' }
2308
+ ]}
2309
+ />
2310
+ <TextField
2311
+ value={config.yAxis.size}
2312
+ type='number'
2313
+ section='yAxis'
2314
+ fieldName='size'
2315
+ label='Size (Width)'
2316
+ className='number-narrow'
2317
+ updateField={updateFieldDeprecated}
2318
+ tooltip={
2319
+ <Tooltip style={{ textTransform: 'none' }}>
2320
+ <Tooltip.Target>
2321
+ <Icon
2322
+ display='question'
2323
+ style={{ marginLeft: '0.5rem', display: 'inline-block', whiteSpace: 'nowrap' }}
2324
+ />
2325
+ </Tooltip.Target>
2326
+ <Tooltip.Content>
2327
+ <p>Increase the size if month labels are crowded or overlap the chart area.</p>
2328
+ </Tooltip.Content>
2329
+ </Tooltip>
2330
+ }
2331
+ />
2332
+ <CheckBox
2333
+ value={config.yAxis.hideAxis}
2334
+ section='yAxis'
2335
+ fieldName='hideAxis'
2336
+ label='Hide Axis'
2337
+ updateField={updateFieldDeprecated}
2338
+ />
2339
+ <CheckBox
2340
+ value={config.yAxis.hideTicks}
2341
+ section='yAxis'
2342
+ fieldName='hideTicks'
2343
+ label='Hide Ticks'
2344
+ updateField={updateFieldDeprecated}
2345
+ />
2346
+ <TextField
2347
+ value={config.yAxis.tickRotation || 0}
2348
+ type='number'
2349
+ min={0}
2350
+ section='yAxis'
2351
+ fieldName='tickRotation'
2352
+ label='Tick rotation (Degrees)'
2353
+ className='number-narrow'
2354
+ updateField={updateFieldDeprecated}
2355
+ />
2356
+ </>
2357
+ )}
2358
+ {config.visualizationType !== 'Pie' && config.visualizationType !== 'HeatMap' && (
2162
2359
  <>
2163
2360
  <Select
2164
2361
  label='Axis Type'
@@ -2209,18 +2406,30 @@ const EditorPanel: React.FC<ChartEditorPanelProps> = ({ datasets }) => {
2209
2406
  fieldName='label'
2210
2407
  label='Label'
2211
2408
  updateField={updateFieldDeprecated}
2212
- maxLength={35}
2409
+ maxLength={config.yAxis.titlePlacement === 'side' ? 35 : undefined}
2213
2410
  tooltip={
2214
2411
  <Tooltip style={{ textTransform: 'none' }}>
2215
2412
  <Tooltip.Target>
2216
2413
  <Icon display='question' style={{ marginLeft: '0.5rem' }} />
2217
2414
  </Tooltip.Target>
2218
2415
  <Tooltip.Content>
2219
- <p>35 character limit</p>
2416
+ <p>35 character limit when Label Placement is Side</p>
2220
2417
  </Tooltip.Content>
2221
2418
  </Tooltip>
2222
2419
  }
2223
2420
  />
2421
+ <Select
2422
+ display={!visHasCategoricalAxis()}
2423
+ value={config.yAxis.titlePlacement}
2424
+ section='yAxis'
2425
+ fieldName='titlePlacement'
2426
+ label='Label Placement'
2427
+ updateField={updateField}
2428
+ options={[
2429
+ { value: 'side', label: 'Side' },
2430
+ { value: 'top', label: 'Top' }
2431
+ ]}
2432
+ />
2224
2433
  <TextField
2225
2434
  display={!visHasCategoricalAxis()}
2226
2435
  value={config.yAxis.inlineLabel}
@@ -2604,7 +2813,8 @@ const EditorPanel: React.FC<ChartEditorPanelProps> = ({ datasets }) => {
2604
2813
  )}
2605
2814
  </>
2606
2815
  ) : (
2607
- config.visualizationType !== 'Pie' && (
2816
+ config.visualizationType !== 'Pie' &&
2817
+ config.visualizationType !== 'HeatMap' && (
2608
2818
  <>
2609
2819
  <CheckBox
2610
2820
  display={!visHasCategoricalAxis()}
@@ -3060,6 +3270,16 @@ const EditorPanel: React.FC<ChartEditorPanelProps> = ({ datasets }) => {
3060
3270
  </Tooltip>
3061
3271
  }
3062
3272
  />
3273
+ {config.visualizationType === 'HeatMap' && (
3274
+ <Select
3275
+ value={config.heatmap?.xAxisPosition ?? HEATMAP_CONFIG_DEFAULTS.xAxisPosition}
3276
+ section='heatmap'
3277
+ fieldName='xAxisPosition'
3278
+ label='X-Axis Position'
3279
+ updateField={updateFieldDeprecated}
3280
+ options={['top', 'bottom']}
3281
+ />
3282
+ )}
3063
3283
 
3064
3284
  {config.xAxis.type === 'continuous' && (
3065
3285
  <>
@@ -3319,38 +3539,71 @@ const EditorPanel: React.FC<ChartEditorPanelProps> = ({ datasets }) => {
3319
3539
  }
3320
3540
  />
3321
3541
  {config.xAxis.brushActive && (
3322
- <TextField
3323
- value={config.xAxis.brushDefaultRecentDateCount ?? ''}
3324
- placeholder='Default (35%)'
3325
- type='number'
3326
- min={1}
3327
- section='xAxis'
3328
- fieldName='brushDefaultRecentDateCount'
3329
- label='Show Last X Dates by Default'
3330
- className='number-narrow'
3331
- updateField={updateFieldDeprecated}
3332
- tooltip={
3333
- <Tooltip style={{ textTransform: 'none' }}>
3334
- <Tooltip.Target>
3335
- <Icon
3336
- display='question'
3337
- style={{
3338
- marginLeft: '0.5rem',
3339
- display: 'inline-block',
3340
- whiteSpace: 'nowrap'
3341
- }}
3342
- />
3343
- </Tooltip.Target>
3344
- <Tooltip.Content>
3345
- <p>
3346
- When set, the brush slider will initially select this many recent data points
3347
- instead of the default 35%. Leave empty to use the default percentage-based
3348
- selection.
3349
- </p>
3350
- </Tooltip.Content>
3351
- </Tooltip>
3352
- }
3353
- />
3542
+ <>
3543
+ <CheckBox
3544
+ value={!!config.xAxis.brushDynamicYAxis}
3545
+ section='xAxis'
3546
+ fieldName='brushDynamicYAxis'
3547
+ label='Dynamic Y-Axis'
3548
+ className='ms-4'
3549
+ updateField={updateFieldDeprecated}
3550
+ tooltip={
3551
+ <Tooltip style={{ textTransform: 'none' }}>
3552
+ <Tooltip.Target>
3553
+ <Icon
3554
+ display='question'
3555
+ style={{
3556
+ marginLeft: '0.5rem',
3557
+ display: 'inline-block',
3558
+ whiteSpace: 'nowrap'
3559
+ }}
3560
+ />
3561
+ </Tooltip.Target>
3562
+ <Tooltip.Content>
3563
+ <p>
3564
+ When enabled, the Y-axis rescales to fit only the data visible in the
3565
+ current brush selection. When disabled, the Y-axis shows the full data
3566
+ range.
3567
+ </p>
3568
+ </Tooltip.Content>
3569
+ </Tooltip>
3570
+ }
3571
+ />
3572
+ <div className='ms-4 mt-2'>
3573
+ <TextField
3574
+ value={config.xAxis.brushDefaultRecentDateCount ?? ''}
3575
+ placeholder='Default (35%)'
3576
+ type='number'
3577
+ min={1}
3578
+ section='xAxis'
3579
+ fieldName='brushDefaultRecentDateCount'
3580
+ label='Show Last X Dates by Default'
3581
+ className='number-narrow'
3582
+ updateField={updateFieldDeprecated}
3583
+ tooltip={
3584
+ <Tooltip style={{ textTransform: 'none' }}>
3585
+ <Tooltip.Target>
3586
+ <Icon
3587
+ display='question'
3588
+ style={{
3589
+ marginLeft: '0.5rem',
3590
+ display: 'inline-block',
3591
+ whiteSpace: 'nowrap'
3592
+ }}
3593
+ />
3594
+ </Tooltip.Target>
3595
+ <Tooltip.Content>
3596
+ <p>
3597
+ When set, the brush slider will initially select this many recent data
3598
+ points instead of the default 35%. Leave empty to use the default
3599
+ percentage-based selection.
3600
+ </p>
3601
+ </Tooltip.Content>
3602
+ </Tooltip>
3603
+ }
3604
+ />
3605
+ </div>
3606
+ </>
3354
3607
  )}
3355
3608
  </>
3356
3609
  )}
@@ -3866,6 +4119,7 @@ const EditorPanel: React.FC<ChartEditorPanelProps> = ({ datasets }) => {
3866
4119
  </AccordionItemPanel>
3867
4120
  </AccordionItem>
3868
4121
  )}
4122
+ {renderHeatMapSettingsAccordion()}
3869
4123
  <Panels.Regions name='Regions' />
3870
4124
 
3871
4125
  {/* Columns */}
@@ -3879,7 +4133,9 @@ const EditorPanel: React.FC<ChartEditorPanelProps> = ({ datasets }) => {
3879
4133
  config={config}
3880
4134
  updateField={updateFieldDeprecated}
3881
4135
  deleteColumn={removeAdditionalColumn}
3882
- hiddenColumnNames={seriesOwnedColumnNames}
4136
+ hiddenColumnNames={
4137
+ isHeatMap ? [...seriesOwnedColumnNames, ...heatMapOwnedColumnNames] : seriesOwnedColumnNames
4138
+ }
3883
4139
  />{' '}
3884
4140
  </AccordionItemPanel>
3885
4141
  </AccordionItem>
@@ -3898,7 +4154,11 @@ const EditorPanel: React.FC<ChartEditorPanelProps> = ({ datasets }) => {
3898
4154
  label='Position'
3899
4155
  updateField={updateFieldDeprecated}
3900
4156
  options={
3901
- config.visualizationType === 'Warming Stripes'
4157
+ config.visualizationType === 'HeatMap'
4158
+ ? config.legend.style === 'gradient'
4159
+ ? ['bottom', 'top']
4160
+ : ['right', 'left', 'bottom', 'top']
4161
+ : config.visualizationType === 'Warming Stripes'
3902
4162
  ? ['bottom']
3903
4163
  : ['right', 'left', 'bottom', 'top']
3904
4164
  }
@@ -3938,7 +4198,7 @@ const EditorPanel: React.FC<ChartEditorPanelProps> = ({ datasets }) => {
3938
4198
  options={getLegendStyleOptions('style')}
3939
4199
  />
3940
4200
 
3941
- {config.visualizationType !== 'Warming Stripes' && (
4201
+ {config.visualizationType !== 'Warming Stripes' && config.visualizationType !== 'HeatMap' && (
3942
4202
  <Select
3943
4203
  value={config.legend.groupBy}
3944
4204
  section='legend'
@@ -4347,6 +4607,7 @@ const EditorPanel: React.FC<ChartEditorPanelProps> = ({ datasets }) => {
4347
4607
  loadConfig={updateConfig}
4348
4608
  config={config}
4349
4609
  convertStateToConfig={chartConvertStateToConfig}
4610
+ defaultExpanded={config.visualizationType === 'HeatMap'}
4350
4611
  />
4351
4612
  )}
4352
4613
  </>
@@ -134,6 +134,19 @@ const PanelAnnotate: React.FC<PanelProps> = props => {
134
134
  updateField={updateField}
135
135
  />
136
136
 
137
+ <Select
138
+ label='Mobile Annotations'
139
+ value={config?.general?.mobileAnnotationDisplay || 'symbol'}
140
+ options={[
141
+ { value: 'symbol', label: 'Show Symbol' },
142
+ { value: 'text', label: 'Show Text' }
143
+ ]}
144
+ section='general'
145
+ subsection={null}
146
+ fieldName='mobileAnnotationDisplay'
147
+ updateField={updateField}
148
+ />
149
+
137
150
  {config.general.showAnnotationDropdown && (
138
151
  <label key={`key-2`}>
139
152
  Annotation Dropdown Title: