@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,7 +1,7 @@
1
1
  import { useState, useEffect, useCallback, memo, useContext, useMemo } from 'react'
2
- import { DragDropContext, Droppable, Draggable } from '@hello-pangea/dnd'
2
+ import { Draggable } from '@hello-pangea/dnd'
3
3
  import chroma from 'chroma-js'
4
- import { isDateScale } from '@cdc/core/helpers/cove/date'
4
+ import { getAutoDetectedDateParseFormat, isDateScale } from '@cdc/core/helpers/cove/date'
5
5
  import {
6
6
  Accordion,
7
7
  AccordionItem,
@@ -16,10 +16,12 @@ import AdvancedEditor from '@cdc/core/components/AdvancedEditor'
16
16
  import Icon from '@cdc/core/components/ui/Icon'
17
17
  import ColumnsEditor from '@cdc/core/components/EditorPanel/ColumnsEditor'
18
18
  import DataTableEditor from '@cdc/core/components/EditorPanel/DataTableEditor'
19
+ import GroupedList from '@cdc/core/components/EditorPanel/GroupedList'
19
20
  import VizFilterEditor from '@cdc/core/components/EditorPanel/VizFilterEditor'
20
21
  import Tooltip from '@cdc/core/components/ui/Tooltip'
21
22
  import { Select, TextField, CheckBox } from '@cdc/core/components/EditorPanel/Inputs'
22
23
  import MultiSelect from '@cdc/core/components/MultiSelect'
24
+ import Button from '@cdc/core/components/elements/Button'
23
25
  import { viewports } from '@cdc/core/helpers/getViewport'
24
26
  import { approvedCurveTypes } from '@cdc/core/helpers/lineChartHelpers'
25
27
  import PanelMarkup from '@cdc/core/components/EditorPanel/components/PanelMarkup'
@@ -59,6 +61,11 @@ import { paletteMigrationMap, twoColorPaletteMigrationMap } from '@cdc/core/help
59
61
  import { isV1Palette, migratePaletteWithMap } from '@cdc/core/helpers/palettes/utils'
60
62
  import { USE_V2_MIGRATION } from '@cdc/core/helpers/constants'
61
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'
62
69
 
63
70
  interface PreliminaryProps {
64
71
  config: ChartConfig
@@ -158,324 +165,363 @@ const PreliminaryData: React.FC<PreliminaryProps> = ({ config, updateConfig, dat
158
165
  updateConfig({ ...config, preliminaryData })
159
166
  }
160
167
 
168
+ const moveColumn = (sourceIndex, destinationIndex) => {
169
+ const preliminaryData = config.preliminaryData ? [...config.preliminaryData] : []
170
+ const [movedItem] = preliminaryData.splice(sourceIndex, 1)
171
+ preliminaryData.splice(destinationIndex, 0, movedItem)
172
+ updateConfig({ ...config, preliminaryData })
173
+ }
174
+
161
175
  return (
162
176
  <>
163
- {config.preliminaryData &&
164
- config.preliminaryData?.map(
165
- (
166
- {
167
- displayGray,
168
- circleSize,
169
- column,
170
- displayLegend,
171
- displayTable,
172
- displayTooltip,
173
- label,
174
- seriesKeys,
175
- style,
176
- symbol,
177
- type,
178
- value,
179
- hideBarSymbol,
180
- hideLineStyle
181
- },
182
- i
183
- ) => {
184
- return (
185
- <div key={`preliminaryData-${i}`} className='edit-block'>
186
- <p> {type === 'suppression' ? 'Suppressed' : 'Effect'} Data</p>
187
- <button
188
- type='button'
189
- className='btn btn-danger'
190
- onClick={event => {
191
- event.preventDefault()
192
- removeColumn(i)
193
- }}
194
- >
195
- Remove
196
- </button>
197
-
198
- <Select
199
- value={type}
200
- initial={config.visualizationType == 'Bar' ? '' : 'Select'}
201
- fieldName='type'
202
- label='Type'
203
- updateField={(_, __, fieldName, value) => update(fieldName, value, i)}
204
- options={getTypeOptions()}
205
- />
206
- {type === 'suppression' ? (
207
- <>
208
- <Select
209
- tooltip={
210
- <Tooltip style={{ textTransform: 'none' }}>
211
- <Tooltip.Target>
212
- <Icon display='question' style={{ marginLeft: '0.5rem' }} />
213
- </Tooltip.Target>
214
- <Tooltip.Content>
215
- <p>
216
- {' '}
217
- Without a selected "Data Series", the suppression symbol will be applied for all series in
218
- the current dataset visualization. However, choosing a specific "data series" will isolate
219
- the suppression to that series.
220
- </p>
221
- </Tooltip.Content>
222
- </Tooltip>
223
- }
224
- value={column}
225
- initial='Select'
226
- fieldName='column'
227
- label='Add Data Series'
228
- updateField={(_, __, fieldName, value) => update(fieldName, value, i)}
229
- options={config.runtime?.seriesKeys}
230
- />
231
- <TextField
232
- value={value}
233
- fieldName='value'
234
- label='Suppressed Data Value'
235
- updateField={(_, __, fieldName, value) => update(fieldName, value, i)}
236
- />
237
- {(hasComboLineSeries || config.visualizationType === 'Line') && (
238
- <>
239
- <Select
240
- tooltip={
241
- <Tooltip style={{ textTransform: 'none' }}>
242
- <Tooltip.Target>
243
- <Icon display='question' style={{ marginLeft: '0.5rem' }} />
244
- </Tooltip.Target>
245
- <Tooltip.Content>
246
- <p>
247
- The recommended approach for presenting data is to include a footnote indicating any
248
- data suppression.
249
- </p>
250
- </Tooltip.Content>
251
- </Tooltip>
252
- }
253
- value={style}
254
- initial='Select'
255
- fieldName='style'
256
- label={'suppression line style'}
257
- updateField={(_, __, fieldName, value) => update(fieldName, value, i)}
258
- options={getStyleOptions(type)}
259
- />
260
- <CheckBox
261
- value={hideLineStyle}
262
- fieldName='hideLineStyle'
263
- label='Hide Suppressed line Style'
264
- updateField={(_, __, fieldName, value) => update(fieldName, value, i)}
265
- />
266
- </>
267
- )}
268
-
269
- {(hasComboBarSeries || config.visualizationType === 'Bar') && (
270
- <>
271
- <Select
272
- tooltip={
273
- <Tooltip style={{ textTransform: 'none' }}>
274
- <Tooltip.Target>
275
- <Icon display='question' style={{ marginLeft: '0.5rem' }} />
276
- </Tooltip.Target>
277
- <Tooltip.Content>
278
- <p>
279
- A symbol is <i>required</i> to indicate suppressed data. We suggest "double
280
- asterisks." If "double asterisks" are already used elsewhere (e.g., footnotes), please
281
- select an alternative symbol from the menu to denote data suppression.
282
- </p>
283
- </Tooltip.Content>
284
- </Tooltip>
285
- }
286
- value={symbol}
287
- initial='Select'
288
- fieldName='symbol'
289
- label={config.visualizationType === 'Combo' ? 'suppression bar symbol' : 'suppression symbol'}
290
- updateField={(_, __, fieldName, value) => update(fieldName, value, i)}
291
- options={getSymbolOptions()}
292
- />
293
- <CheckBox
294
- value={hideBarSymbol}
295
- fieldName='hideBarSymbol'
296
- label='Hide Suppressed Bar Symbol '
297
- updateField={(_, __, fieldName, value) => update(fieldName, value, i)}
298
- />
299
- </>
300
- )}
177
+ <GroupedList
178
+ items={config.preliminaryData}
179
+ label='Suppressed Data'
180
+ droppableId='chart-preliminary-data-order'
181
+ onDragEnd={({ source, destination }) => {
182
+ if (!destination || source.index === destination.index) return
183
+ moveColumn(source.index, destination.index)
184
+ }}
185
+ renderItem={(
186
+ {
187
+ displayGray,
188
+ circleSize,
189
+ column,
190
+ displayLegend,
191
+ displayTable,
192
+ displayTooltip,
193
+ label,
194
+ seriesKeys,
195
+ style,
196
+ symbol,
197
+ type,
198
+ value,
199
+ hideBarSymbol,
200
+ hideLineStyle
201
+ },
202
+ i
203
+ ) => (
204
+ <Draggable key={`preliminaryData-${i}`} draggableId={`preliminaryData-${i}`} index={i}>
205
+ {(provided, snapshot) => (
206
+ <div
207
+ className={snapshot.isDragging ? 'currently-dragging' : ''}
208
+ style={provided.draggableProps.style}
209
+ ref={provided.innerRef}
210
+ {...provided.draggableProps}
211
+ {...provided.dragHandleProps}
212
+ >
213
+ <Accordion allowZeroExpanded>
214
+ <AccordionItem className='series-item series-item--chart'>
215
+ <AccordionItemHeading className='series-item__title'>
216
+ <AccordionItemButton className='accordion__button'>
217
+ <Icon display='move' size={15} style={{ cursor: 'default' }} />
218
+ {type === 'suppression' ? 'Suppressed Data' : `Effect Data ${i + 1}`}
219
+ </AccordionItemButton>
220
+ </AccordionItemHeading>
221
+ <AccordionItemPanel>
222
+ <div className='series-item__panel-actions'>
223
+ <Button
224
+ type='button'
225
+ variant='danger'
226
+ size='sm'
227
+ className='grouped-list__remove'
228
+ onClick={event => {
229
+ event.preventDefault()
230
+ removeColumn(i)
231
+ }}
232
+ >
233
+ Remove
234
+ </Button>
235
+ </div>
301
236
 
302
- <TextField
303
- tooltip={
304
- <Tooltip style={{ textTransform: 'none' }}>
305
- <Tooltip.Target>
306
- <Icon display='question' style={{ marginLeft: '0.5rem' }} />
307
- </Tooltip.Target>
308
- <Tooltip.Content>
309
- <p>This label will display in the tooltip and legend.</p>
310
- </Tooltip.Content>
311
- </Tooltip>
312
- }
313
- value={label ? label : 'Suppressed'}
314
- fieldName='label'
315
- label='Suppressed Data Label'
316
- placeholder=''
317
- updateField={(_, __, fieldName, value) => update(fieldName, value, i)}
318
- />
319
- <CheckBox
320
- display={config.visualizationSubType === 'regular'}
321
- tooltip={
322
- <Tooltip style={{ textTransform: 'none' }}>
323
- <Tooltip.Target>
324
- <Icon display='question' style={{ marginLeft: '0.5rem' }} />
325
- </Tooltip.Target>
326
- <Tooltip.Content>
327
- <p>
328
- Deselecting the "Display In Tooltips" option prevents suppressed values from appearing in
329
- tooltips.
330
- </p>
331
- </Tooltip.Content>
332
- </Tooltip>
333
- }
334
- value={displayTooltip}
335
- fieldName='displayTooltip'
336
- label='Display in tooltips'
337
- updateField={(_, __, fieldName, value) => update(fieldName, value, i)}
338
- />
339
- <CheckBox
340
- display={config.visualizationSubType === 'regular'}
341
- tooltip={
342
- <Tooltip style={{ textTransform: 'none' }}>
343
- <Tooltip.Target>
344
- <Icon display='question' style={{ marginLeft: '0.5rem' }} />
345
- </Tooltip.Target>
346
- <Tooltip.Content>
347
- <p>
348
- Deselecting "Display in Legend" indicates that you do not want to display suppressed data
349
- in the legend.
350
- </p>
351
- </Tooltip.Content>
352
- </Tooltip>
353
- }
354
- value={displayLegend}
355
- fieldName='displayLegend'
356
- label='Display in legend'
357
- updateField={(_, __, fieldName, value) => update(fieldName, value, i)}
358
- />
359
- <CheckBox
360
- display={config.visualizationSubType === 'regular'}
361
- tooltip={
362
- <Tooltip style={{ textTransform: 'none' }}>
363
- <Tooltip.Target>
364
- <Icon display='question' style={{ marginLeft: '0.5rem' }} />
365
- </Tooltip.Target>
366
- <Tooltip.Content>
367
- <p>
368
- Deselecting "Display In Data Table" indicates that you do not want to display suppressed
369
- data in the data table.
370
- </p>
371
- </Tooltip.Content>
372
- </Tooltip>
373
- }
374
- value={displayTable}
375
- fieldName='displayTable'
376
- label='Display in table'
377
- updateField={(_, __, fieldName, value) => update(fieldName, value, i)}
378
- />
379
- <CheckBox
380
- display={config.visualizationSubType === 'regular'}
381
- tooltip={
382
- <Tooltip style={{ textTransform: 'none' }}>
383
- <Tooltip.Target>
384
- <Icon display='question' style={{ marginLeft: '0.5rem' }} />
385
- </Tooltip.Target>
386
- <Tooltip.Content>
387
- <p>Selecting this option will apply to chart, tooltip hover, legend, and data table.</p>
388
- </Tooltip.Content>
389
- </Tooltip>
390
- }
391
- value={displayGray}
392
- fieldName='displayGray'
393
- label='Highlight Suppressed Data In Gray'
394
- updateField={(_, __, fieldName, value) => update(fieldName, value, i)}
395
- />
396
- </>
397
- ) : (
398
- <>
399
- <label>
400
- <span className='edit-label'>ASSOCIATE TO THESE SERIES</span>
401
- <MultiSelect
402
- fieldName='seriesKeys'
403
- updateField={(_, __, fieldName, value) => update(fieldName, value, i)}
404
- options={(config.runtime.lineSeriesKeys ?? config.runtime?.seriesKeys).map(c => ({
405
- label: c,
406
- value: c
407
- }))}
408
- selected={seriesKeys}
409
- />
410
- </label>
411
- <Select
412
- value={column}
413
- initial='Select'
414
- fieldName='column'
415
- label='COLUMN WITH CONFIGURATION VALUE'
416
- updateField={(_, __, fieldName, value) => update(fieldName, value, i)}
417
- options={getColumnOptions()}
418
- />
419
- <TextField
420
- tooltip={
421
- <Tooltip style={{ textTransform: 'none' }}>
422
- <Tooltip.Target>
423
- <Icon display='question' style={{ marginLeft: '0.5rem' }} />
424
- </Tooltip.Target>
425
- <Tooltip.Content>
426
- <p>
427
- If 'Filled Circles' is selected as the style, this field is optional, and the style
428
- 'Filled Circles' will apply to all points within the associated series data.
429
- </p>
430
- </Tooltip.Content>
431
- </Tooltip>
432
- }
433
- value={value}
434
- fieldName='value'
435
- label='VALUE TO TRIGGER'
436
- updateField={(_, __, fieldName, value) => update(fieldName, value, i)}
437
- />
438
- <Select
439
- value={style}
440
- initial='Select'
441
- fieldName='style'
442
- label='Style'
443
- updateField={(_, __, fieldName, value) => update(fieldName, value, i)}
444
- options={getStyleOptions(type)}
445
- />
446
- {style.includes('Circles') && (
447
- <TextField
448
- className='number-narrow'
449
- type='number'
450
- value={circleSize}
451
- fieldName='circleSize'
452
- label='circle size'
453
- updateField={(_, __, fieldName, value) => update(fieldName, value, i)}
454
- />
455
- )}
456
- {style !== 'Filled Circles' && (
457
- <TextField
458
- value={label}
459
- fieldName='label'
460
- label='Label'
461
- placeholder=''
237
+ <Select
238
+ value={type}
239
+ initial={config.visualizationType == 'Bar' ? '' : 'Select'}
240
+ fieldName='type'
241
+ label='Type'
462
242
  updateField={(_, __, fieldName, value) => update(fieldName, value, i)}
243
+ options={getTypeOptions()}
463
244
  />
464
- )}
465
- </>
466
- )}
245
+ {type === 'suppression' ? (
246
+ <>
247
+ <Select
248
+ tooltip={
249
+ <Tooltip style={{ textTransform: 'none' }}>
250
+ <Tooltip.Target>
251
+ <Icon display='question' style={{ marginLeft: '0.5rem' }} />
252
+ </Tooltip.Target>
253
+ <Tooltip.Content>
254
+ <p>
255
+ {' '}
256
+ Without a selected "Data Series", the suppression symbol will be applied for all
257
+ series in the current dataset visualization. However, choosing a specific "data
258
+ series" will isolate the suppression to that series.
259
+ </p>
260
+ </Tooltip.Content>
261
+ </Tooltip>
262
+ }
263
+ value={column}
264
+ initial='Select'
265
+ fieldName='column'
266
+ label='Add Data Series'
267
+ updateField={(_, __, fieldName, value) => update(fieldName, value, i)}
268
+ options={config.runtime?.seriesKeys}
269
+ />
270
+ <TextField
271
+ value={value}
272
+ fieldName='value'
273
+ label='Suppressed Data Value'
274
+ updateField={(_, __, fieldName, value) => update(fieldName, value, i)}
275
+ />
276
+ {(hasComboLineSeries || config.visualizationType === 'Line') && (
277
+ <>
278
+ <Select
279
+ tooltip={
280
+ <Tooltip style={{ textTransform: 'none' }}>
281
+ <Tooltip.Target>
282
+ <Icon display='question' style={{ marginLeft: '0.5rem' }} />
283
+ </Tooltip.Target>
284
+ <Tooltip.Content>
285
+ <p>
286
+ The recommended approach for presenting data is to include a footnote indicating
287
+ any data suppression.
288
+ </p>
289
+ </Tooltip.Content>
290
+ </Tooltip>
291
+ }
292
+ value={style}
293
+ initial='Select'
294
+ fieldName='style'
295
+ label={'suppression line style'}
296
+ updateField={(_, __, fieldName, value) => update(fieldName, value, i)}
297
+ options={getStyleOptions(type)}
298
+ />
299
+ <CheckBox
300
+ value={hideLineStyle}
301
+ fieldName='hideLineStyle'
302
+ label='Hide Suppressed line Style'
303
+ updateField={(_, __, fieldName, value) => update(fieldName, value, i)}
304
+ />
305
+ </>
306
+ )}
307
+
308
+ {(hasComboBarSeries || config.visualizationType === 'Bar') && (
309
+ <>
310
+ <Select
311
+ tooltip={
312
+ <Tooltip style={{ textTransform: 'none' }}>
313
+ <Tooltip.Target>
314
+ <Icon display='question' style={{ marginLeft: '0.5rem' }} />
315
+ </Tooltip.Target>
316
+ <Tooltip.Content>
317
+ <p>
318
+ A symbol is <i>required</i> to indicate suppressed data. We suggest "double
319
+ asterisks." If "double asterisks" are already used elsewhere (e.g., footnotes),
320
+ please select an alternative symbol from the menu to denote data suppression.
321
+ </p>
322
+ </Tooltip.Content>
323
+ </Tooltip>
324
+ }
325
+ value={symbol}
326
+ initial='Select'
327
+ fieldName='symbol'
328
+ label={
329
+ config.visualizationType === 'Combo' ? 'suppression bar symbol' : 'suppression symbol'
330
+ }
331
+ updateField={(_, __, fieldName, value) => update(fieldName, value, i)}
332
+ options={getSymbolOptions()}
333
+ />
334
+ <CheckBox
335
+ value={hideBarSymbol}
336
+ fieldName='hideBarSymbol'
337
+ label='Hide Suppressed Bar Symbol '
338
+ updateField={(_, __, fieldName, value) => update(fieldName, value, i)}
339
+ />
340
+ </>
341
+ )}
342
+
343
+ <TextField
344
+ tooltip={
345
+ <Tooltip style={{ textTransform: 'none' }}>
346
+ <Tooltip.Target>
347
+ <Icon display='question' style={{ marginLeft: '0.5rem' }} />
348
+ </Tooltip.Target>
349
+ <Tooltip.Content>
350
+ <p>This label will display in the tooltip and legend.</p>
351
+ </Tooltip.Content>
352
+ </Tooltip>
353
+ }
354
+ value={label ? label : 'Suppressed'}
355
+ fieldName='label'
356
+ label='Suppressed Data Label'
357
+ placeholder=''
358
+ updateField={(_, __, fieldName, value) => update(fieldName, value, i)}
359
+ />
360
+ <CheckBox
361
+ display={config.visualizationSubType === 'regular'}
362
+ tooltip={
363
+ <Tooltip style={{ textTransform: 'none' }}>
364
+ <Tooltip.Target>
365
+ <Icon display='question' style={{ marginLeft: '0.5rem' }} />
366
+ </Tooltip.Target>
367
+ <Tooltip.Content>
368
+ <p>
369
+ Deselecting the "Display In Tooltips" option prevents suppressed values from
370
+ appearing in tooltips.
371
+ </p>
372
+ </Tooltip.Content>
373
+ </Tooltip>
374
+ }
375
+ value={displayTooltip}
376
+ fieldName='displayTooltip'
377
+ label='Display in tooltips'
378
+ updateField={(_, __, fieldName, value) => update(fieldName, value, i)}
379
+ />
380
+ <CheckBox
381
+ display={config.visualizationSubType === 'regular'}
382
+ tooltip={
383
+ <Tooltip style={{ textTransform: 'none' }}>
384
+ <Tooltip.Target>
385
+ <Icon display='question' style={{ marginLeft: '0.5rem' }} />
386
+ </Tooltip.Target>
387
+ <Tooltip.Content>
388
+ <p>
389
+ Deselecting "Display in Legend" indicates that you do not want to display suppressed
390
+ data in the legend.
391
+ </p>
392
+ </Tooltip.Content>
393
+ </Tooltip>
394
+ }
395
+ value={displayLegend}
396
+ fieldName='displayLegend'
397
+ label='Display in legend'
398
+ updateField={(_, __, fieldName, value) => update(fieldName, value, i)}
399
+ />
400
+ <CheckBox
401
+ display={config.visualizationSubType === 'regular'}
402
+ tooltip={
403
+ <Tooltip style={{ textTransform: 'none' }}>
404
+ <Tooltip.Target>
405
+ <Icon display='question' style={{ marginLeft: '0.5rem' }} />
406
+ </Tooltip.Target>
407
+ <Tooltip.Content>
408
+ <p>
409
+ Deselecting "Display In Data Table" indicates that you do not want to display
410
+ suppressed data in the data table.
411
+ </p>
412
+ </Tooltip.Content>
413
+ </Tooltip>
414
+ }
415
+ value={displayTable}
416
+ fieldName='displayTable'
417
+ label='Display in table'
418
+ updateField={(_, __, fieldName, value) => update(fieldName, value, i)}
419
+ />
420
+ <CheckBox
421
+ display={config.visualizationSubType === 'regular'}
422
+ tooltip={
423
+ <Tooltip style={{ textTransform: 'none' }}>
424
+ <Tooltip.Target>
425
+ <Icon display='question' style={{ marginLeft: '0.5rem' }} />
426
+ </Tooltip.Target>
427
+ <Tooltip.Content>
428
+ <p>
429
+ Selecting this option will apply to chart, tooltip hover, legend, and data table.
430
+ </p>
431
+ </Tooltip.Content>
432
+ </Tooltip>
433
+ }
434
+ value={displayGray}
435
+ fieldName='displayGray'
436
+ label='Highlight Suppressed Data In Gray'
437
+ updateField={(_, __, fieldName, value) => update(fieldName, value, i)}
438
+ />
439
+ </>
440
+ ) : (
441
+ <>
442
+ <label>
443
+ <span className='edit-label'>ASSOCIATE TO THESE SERIES</span>
444
+ <MultiSelect
445
+ fieldName='seriesKeys'
446
+ updateField={(_, __, fieldName, value) => update(fieldName, value, i)}
447
+ options={(config.runtime.lineSeriesKeys ?? config.runtime?.seriesKeys).map(c => ({
448
+ label: c,
449
+ value: c
450
+ }))}
451
+ selected={seriesKeys}
452
+ />
453
+ </label>
454
+ <Select
455
+ value={column}
456
+ initial='Select'
457
+ fieldName='column'
458
+ label='COLUMN WITH CONFIGURATION VALUE'
459
+ updateField={(_, __, fieldName, value) => update(fieldName, value, i)}
460
+ options={getColumnOptions()}
461
+ />
462
+ <TextField
463
+ tooltip={
464
+ <Tooltip style={{ textTransform: 'none' }}>
465
+ <Tooltip.Target>
466
+ <Icon display='question' style={{ marginLeft: '0.5rem' }} />
467
+ </Tooltip.Target>
468
+ <Tooltip.Content>
469
+ <p>
470
+ If 'Filled Circles' is selected as the style, this field is optional, and the style
471
+ 'Filled Circles' will apply to all points within the associated series data.
472
+ </p>
473
+ </Tooltip.Content>
474
+ </Tooltip>
475
+ }
476
+ value={value}
477
+ fieldName='value'
478
+ label='VALUE TO TRIGGER'
479
+ updateField={(_, __, fieldName, value) => update(fieldName, value, i)}
480
+ />
481
+ <Select
482
+ value={style}
483
+ initial='Select'
484
+ fieldName='style'
485
+ label='Style'
486
+ updateField={(_, __, fieldName, value) => update(fieldName, value, i)}
487
+ options={getStyleOptions(type)}
488
+ />
489
+ {style?.includes('Circles') && (
490
+ <TextField
491
+ className='number-narrow'
492
+ type='number'
493
+ value={circleSize}
494
+ fieldName='circleSize'
495
+ label='circle size'
496
+ updateField={(_, __, fieldName, value) => update(fieldName, value, i)}
497
+ />
498
+ )}
499
+ {style !== 'Filled Circles' && (
500
+ <TextField
501
+ value={label}
502
+ fieldName='label'
503
+ label='Label'
504
+ placeholder=''
505
+ updateField={(_, __, fieldName, value) => update(fieldName, value, i)}
506
+ />
507
+ )}
508
+ </>
509
+ )}
510
+ </AccordionItemPanel>
511
+ </AccordionItem>
512
+ </Accordion>
467
513
  </div>
468
- )
469
- }
514
+ )}
515
+ </Draggable>
470
516
  )}
471
-
472
- <button type='button' onClick={addColumn} className='btn btn-primary full-width'>
517
+ />
518
+ <Button type='button' variant='editor-primary' onClick={addColumn}>
473
519
  {config.visualizationType === 'Line'
474
520
  ? 'Add Special Line'
475
521
  : config.visualizationType === 'Bar'
476
522
  ? ' Add Special Bar'
477
523
  : 'Add Special Bar/Line'}
478
- </button>
524
+ </Button>
479
525
  </>
480
526
  )
481
527
  }
@@ -486,6 +532,158 @@ interface CategoricalAxisProps {
486
532
  display: boolean
487
533
  }
488
534
 
535
+ type AxisAnchorEditorProps = {
536
+ axisKey: 'xAxis' | 'yAxis'
537
+ config: ChartConfig
538
+ lineOptions: { value: string; label?: string }[]
539
+ updateConfig: Function
540
+ }
541
+
542
+ const AxisAnchorEditor: React.FC<AxisAnchorEditorProps> = ({ axisKey, config, lineOptions, updateConfig }) => {
543
+ const anchors = config[axisKey]?.anchors || []
544
+
545
+ const updateAnchors = (nextAnchors: Anchor[]) => {
546
+ updateConfig({
547
+ ...config,
548
+ [axisKey]: {
549
+ ...config[axisKey],
550
+ anchors: nextAnchors
551
+ }
552
+ })
553
+ }
554
+
555
+ const updateAnchorField = (index: number, field: keyof Anchor, value: string) => {
556
+ const copiedAnchors = [...anchors]
557
+ copiedAnchors[index] = {
558
+ ...copiedAnchors[index],
559
+ [field]: value
560
+ }
561
+ updateAnchors(copiedAnchors)
562
+ }
563
+
564
+ const removeAnchor = (index: number) => {
565
+ const copiedAnchors = [...anchors]
566
+ copiedAnchors.splice(index, 1)
567
+ updateAnchors(copiedAnchors)
568
+ }
569
+
570
+ const addAnchor = () => {
571
+ updateAnchors([...anchors, {} as Anchor])
572
+ }
573
+
574
+ const reorderAnchors = (sourceIndex: number, destinationIndex: number) => {
575
+ const copiedAnchors = [...anchors]
576
+ const [movedAnchor] = copiedAnchors.splice(sourceIndex, 1)
577
+ copiedAnchors.splice(destinationIndex, 0, movedAnchor)
578
+ updateAnchors(copiedAnchors)
579
+ }
580
+
581
+ return (
582
+ <>
583
+ <GroupedList
584
+ items={anchors}
585
+ label='Anchors'
586
+ droppableId={`${axisKey}-anchors-order`}
587
+ onDragEnd={({ source, destination }) => {
588
+ if (!destination || source.index === destination.index) return
589
+ reorderAnchors(source.index, destination.index)
590
+ }}
591
+ renderItem={(anchor, index) => (
592
+ <Draggable key={`${axisKey}-anchor-${index}`} draggableId={`${axisKey}-anchor-${index}`} index={index}>
593
+ {(provided, snapshot) => (
594
+ <div
595
+ ref={provided.innerRef}
596
+ {...provided.draggableProps}
597
+ {...provided.dragHandleProps}
598
+ className={snapshot.isDragging ? 'currently-dragging' : ''}
599
+ style={provided.draggableProps.style}
600
+ >
601
+ <Accordion allowZeroExpanded>
602
+ <AccordionItem className='series-item series-item--chart'>
603
+ <AccordionItemHeading className='series-item__title'>
604
+ <AccordionItemButton className='accordion__button'>
605
+ <Icon display='move' size={15} style={{ cursor: 'default' }} />
606
+ {`Anchor ${index + 1}`}
607
+ </AccordionItemButton>
608
+ </AccordionItemHeading>
609
+ <AccordionItemPanel>
610
+ <div className='series-item__panel-actions'>
611
+ <Button
612
+ type='button'
613
+ variant='danger'
614
+ size='sm'
615
+ className='grouped-list__remove'
616
+ onClick={e => {
617
+ e.preventDefault()
618
+ removeAnchor(index)
619
+ }}
620
+ >
621
+ Remove
622
+ </Button>
623
+ </div>
624
+ <label>
625
+ <span>Anchor Value</span>
626
+ <Tooltip style={{ textTransform: 'none' }}>
627
+ <Tooltip.Target>
628
+ <Icon display='question' style={{ marginLeft: '0.5rem' }} />
629
+ </Tooltip.Target>
630
+ <Tooltip.Content>
631
+ <p>Enter the value as its shown in the data column</p>
632
+ </Tooltip.Content>
633
+ </Tooltip>
634
+ <input
635
+ type='text'
636
+ value={anchor?.value || ''}
637
+ onChange={e => {
638
+ e.preventDefault()
639
+ updateAnchorField(index, 'value', e.target.value)
640
+ }}
641
+ />
642
+ </label>
643
+
644
+ <label>
645
+ <span>Anchor Color</span>
646
+ <input
647
+ type='text'
648
+ value={anchor?.color || ''}
649
+ onChange={e => {
650
+ e.preventDefault()
651
+ updateAnchorField(index, 'color', e.target.value)
652
+ }}
653
+ />
654
+ </label>
655
+
656
+ <Select
657
+ value={anchor?.lineStyle || ''}
658
+ label='Anchor Line Style'
659
+ onChange={e => updateAnchorField(index, 'lineStyle', e.target.value)}
660
+ options={[
661
+ { value: '', label: 'Select' },
662
+ ...lineOptions.map(line => ({ value: line.value, label: line.value }))
663
+ ]}
664
+ />
665
+ </AccordionItemPanel>
666
+ </AccordionItem>
667
+ </Accordion>
668
+ </div>
669
+ )}
670
+ </Draggable>
671
+ )}
672
+ />
673
+
674
+ <Button
675
+ variant='editor-primary'
676
+ onClick={e => {
677
+ e.preventDefault()
678
+ addAnchor()
679
+ }}
680
+ >
681
+ Add Anchor
682
+ </Button>
683
+ </>
684
+ )
685
+ }
686
+
489
687
  const CategoricalAxis: React.FC<CategoricalAxisProps> = ({ config, updateConfig, display }) => {
490
688
  const maxHeight = config?.yAxis?.maxValue
491
689
 
@@ -547,7 +745,7 @@ const CategoricalAxis: React.FC<CategoricalAxisProps> = ({ config, updateConfig,
547
745
  return (
548
746
  <div key={`preliminaryData-${i}`} className='edit-block'>
549
747
  <p>Axis Category {i + 1}</p>
550
- <button
748
+ <Button
551
749
  type='button'
552
750
  className='btn btn-danger'
553
751
  onClick={event => {
@@ -556,7 +754,7 @@ const CategoricalAxis: React.FC<CategoricalAxisProps> = ({ config, updateConfig,
556
754
  }}
557
755
  >
558
756
  Remove
559
- </button>
757
+ </Button>
560
758
  <TextField
561
759
  tooltip={
562
760
  <Tooltip style={{ textTransform: 'none' }}>
@@ -602,9 +800,9 @@ const CategoricalAxis: React.FC<CategoricalAxisProps> = ({ config, updateConfig,
602
800
  )
603
801
  })}
604
802
 
605
- <button type='button' onClick={addColumn} className='btn btn-primary full-width'>
803
+ <Button type='button' variant='editor-primary' onClick={addColumn}>
606
804
  Add Axis Category
607
- </button>
805
+ </Button>
608
806
  </>
609
807
  )
610
808
  }
@@ -725,7 +923,7 @@ const EditorPanel: React.FC<ChartEditorPanelProps> = ({ datasets }) => {
725
923
  }
726
924
  }, [config.visualizationType])
727
925
 
728
- const { hasRightAxis } = useRightAxis({ config: config, yMax: config.yAxis.size, data: config.data, updateConfig })
926
+ const { hasRightAxis } = useRightAxis({ config: config, yMax: 0, data: config.data, updateConfig })
729
927
 
730
928
  const getItemStyle = (isDragging, draggableStyle) => ({
731
929
  ...draggableStyle
@@ -774,6 +972,17 @@ const EditorPanel: React.FC<ChartEditorPanelProps> = ({ datasets }) => {
774
972
  }
775
973
 
776
974
  const updateField = updateFieldFactory(config, updateConfig)
975
+ const getAutoDetectedXAxisDateParseFormat = (dataKey = config.xAxis.dataKey, axisType = config.xAxis.type) => {
976
+ const hasExistingDateParseFormat =
977
+ typeof config.xAxis.dateParseFormat === 'string' && config.xAxis.dateParseFormat.trim() !== ''
978
+
979
+ if (hasExistingDateParseFormat || !['date', 'date-time'].includes(axisType)) {
980
+ return undefined
981
+ }
982
+
983
+ return getAutoDetectedDateParseFormat(config.data, dataKey)
984
+ }
985
+
777
986
  const updateFieldDeprecated = (section, subsection, fieldName, newValue) => {
778
987
  if (isDebug)
779
988
  console.error(
@@ -816,6 +1025,23 @@ const EditorPanel: React.FC<ChartEditorPanelProps> = ({ datasets }) => {
816
1025
  })
817
1026
  return
818
1027
  }
1028
+
1029
+ if (section === 'xAxis' && !truthy(subsection) && fieldName === 'dataKey') {
1030
+ const autoDetectedDateParseFormat = getAutoDetectedXAxisDateParseFormat(newValue)
1031
+ const updatedConfig = {
1032
+ ...config,
1033
+ xAxis: {
1034
+ ...config.xAxis,
1035
+ dataKey: newValue,
1036
+ ...(autoDetectedDateParseFormat ? { dateParseFormat: autoDetectedDateParseFormat } : {})
1037
+ }
1038
+ }
1039
+
1040
+ enforceRestrictions(updatedConfig)
1041
+ updateConfig(updatedConfig)
1042
+ return
1043
+ }
1044
+
819
1045
  if (null === section && null === subsection) {
820
1046
  // special case that allows for updating the config object directly
821
1047
  if (!truthy(fieldName)) console.error('fieldName is required')
@@ -968,12 +1194,23 @@ const EditorPanel: React.FC<ChartEditorPanelProps> = ({ datasets }) => {
968
1194
  return filter ? filteredColumns : allColumns
969
1195
  }
970
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
+
971
1203
  const getLegendStyleOptions = (option: 'style' | 'subStyle' | 'shapes' | 'groupBy'): string[] => {
972
1204
  const options: string[] = []
973
1205
 
974
1206
  switch (option) {
975
1207
  case 'style':
976
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
+ }
977
1214
  if (
978
1215
  config.visualizationType === 'Bar' &&
979
1216
  (!['right', 'left'].includes(config.legend.position) || !config.legend.position)
@@ -1024,7 +1261,7 @@ const EditorPanel: React.FC<ChartEditorPanelProps> = ({ datasets }) => {
1024
1261
  // prettier-ignore
1025
1262
  const {
1026
1263
  highlightedBarValues,
1027
- highlightedSeriesValues,
1264
+ getHighlightedSeriesValues,
1028
1265
  handleUpdateHighlightedBar,
1029
1266
  handleAddNewHighlightedBar,
1030
1267
  handleRemoveHighlightedBar,
@@ -1117,6 +1354,15 @@ const EditorPanel: React.FC<ChartEditorPanelProps> = ({ datasets }) => {
1117
1354
  if (config.isLollipopChart && config?.series?.length === 0) {
1118
1355
  config.runtime.editorErrorMessage = 'Add a data series'
1119
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
+ }
1120
1366
 
1121
1367
  const section = config.orientation === 'horizontal' ? 'xAxis' : 'yAxis'
1122
1368
  const [warningMsg, setWarningMsg] = useState({ maxMsg: '', minMsg: '', rightMaxMessage: '', minMsgRight: '' })
@@ -1516,6 +1762,7 @@ const EditorPanel: React.FC<ChartEditorPanelProps> = ({ datasets }) => {
1516
1762
  'Bump Chart',
1517
1763
  'Area Chart',
1518
1764
  'Combo',
1765
+ 'HeatMap',
1519
1766
  'Line',
1520
1767
  'Bar',
1521
1768
  'Forecasting',
@@ -1634,6 +1881,27 @@ const EditorPanel: React.FC<ChartEditorPanelProps> = ({ datasets }) => {
1634
1881
  const hasDynamicCategory = ![undefined, '- Select - '].includes(config.series?.[0]?.dynamicCategory)
1635
1882
  const hasMultipleSeries = config.series?.length > 1
1636
1883
 
1884
+ const highlightedSeriesOptions = useMemo(() => {
1885
+ if (
1886
+ !(
1887
+ config.visualizationType === 'Bar' &&
1888
+ config.series?.length === 1 &&
1889
+ config.highlightedBarValues?.length > 0 &&
1890
+ getHighlightedSeriesValues
1891
+ )
1892
+ ) {
1893
+ return []
1894
+ }
1895
+
1896
+ return [...new Set(getHighlightedSeriesValues())].sort().map(option => ({ value: option, label: option }))
1897
+ }, [
1898
+ config.visualizationType,
1899
+ config.series?.length,
1900
+ config.highlightedBarValues?.length,
1901
+ config.data,
1902
+ config.xAxis.dataKey
1903
+ ])
1904
+
1637
1905
  const editorContextValues = {
1638
1906
  addNewExclusion,
1639
1907
  data,
@@ -1652,7 +1920,7 @@ const EditorPanel: React.FC<ChartEditorPanelProps> = ({ datasets }) => {
1652
1920
  handleUpdateHighlightedBar,
1653
1921
  handleRemoveHighlightedBar,
1654
1922
  isPaletteReversed: config.general?.palette?.isReversed,
1655
- highlightedSeriesValues,
1923
+ getHighlightedSeriesValues,
1656
1924
  handleUpdateHighlightedBorderWidth,
1657
1925
  handleUpdateHighlightedBarColor,
1658
1926
  setLollipopShape,
@@ -1678,6 +1946,76 @@ const EditorPanel: React.FC<ChartEditorPanelProps> = ({ datasets }) => {
1678
1946
  return strippedState
1679
1947
  }
1680
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
+
1681
2019
  return (
1682
2020
  <EditorPanelContext.Provider value={editorContextValues}>
1683
2021
  <BaseEditorPanel
@@ -1708,7 +2046,10 @@ const EditorPanel: React.FC<ChartEditorPanelProps> = ({ datasets }) => {
1708
2046
  {(!config.series ||
1709
2047
  config.series.length === 0 ||
1710
2048
  (config.visualizationType === 'Paired Bar' && config.series.length < 2)) &&
1711
- !config.dynamicSeries && <WarningImage width='25' className='warning-icon' />}
2049
+ !config.dynamicSeries &&
2050
+ config.visualizationType !== 'HeatMap' && (
2051
+ <WarningImage width='25' className='warning-icon' />
2052
+ )}
1712
2053
  </AccordionItemButton>
1713
2054
  </AccordionItemHeading>
1714
2055
  <AccordionItemPanel>
@@ -1745,9 +2086,21 @@ const EditorPanel: React.FC<ChartEditorPanelProps> = ({ datasets }) => {
1745
2086
  )}
1746
2087
  {(!visSupportsDynamicSeries() || !config.dynamicSeries) && (
1747
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
+ )}
2095
+ {(!config.series || config.series.length === 0) &&
2096
+ !config.dynamicSeries &&
2097
+ config.visualizationType !== 'Paired Bar' &&
2098
+ config.visualizationType !== 'HeatMap' && (
2099
+ <p className='warning'>At least one series is required</p>
2100
+ )}
1748
2101
  {(!config.series || config.series.length === 0) &&
1749
2102
  !config.dynamicSeries &&
1750
- config.visualizationType !== 'Paired Bar' && (
2103
+ config.visualizationType === 'HeatMap' && (
1751
2104
  <p className='warning'>At least one series is required</p>
1752
2105
  )}
1753
2106
  {(!config.series || config.series.length === 0 || config.series.length < 2) &&
@@ -1768,56 +2121,54 @@ const EditorPanel: React.FC<ChartEditorPanelProps> = ({ datasets }) => {
1768
2121
  }
1769
2122
  e.target.value = ''
1770
2123
  }}
1771
- options={getColumns()}
2124
+ options={getColumns().filter(column => column !== config.xAxis?.dataKey)}
1772
2125
  />
1773
2126
  {config.series && config.series.length !== 0 && (
1774
2127
  <Panels.Series.Wrapper
1775
2128
  getColumns={getColumns}
1776
2129
  handleForecastPaletteSelection={handleForecastPaletteSelection}
1777
2130
  >
1778
- <fieldset>
1779
- <legend className='edit-label d-flex align-items-center'>
1780
- Displaying
1781
- <Tooltip style={{ textTransform: 'none' }}>
1782
- <Tooltip.Target>
1783
- <Icon display='question' style={{ marginLeft: '0.5rem' }} />
1784
- </Tooltip.Target>
1785
- <Tooltip.Content>
1786
- <p>
1787
- A data series is a set of related data points plotted in a chart and
1788
- typically represented in the chart legend.
1789
- </p>
1790
- </Tooltip.Content>
1791
- </Tooltip>
1792
- </legend>
1793
- </fieldset>
1794
-
1795
- <DragDropContext
1796
- onDragEnd={({ source, destination }) =>
1797
- handleSeriesChange(source.index, destination.index)
2131
+ <GroupedList
2132
+ items={config.series}
2133
+ droppableId='chart-series-order'
2134
+ label={
2135
+ <>
2136
+ {config.visualizationType === 'HeatMap' ? 'Displaying Rows' : 'Displaying'}
2137
+ <Tooltip style={{ textTransform: 'none' }}>
2138
+ <Tooltip.Target>
2139
+ <Icon display='question' style={{ marginLeft: '0.5rem' }} />
2140
+ </Tooltip.Target>
2141
+ <Tooltip.Content>
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
+ )}
2153
+ </Tooltip.Content>
2154
+ </Tooltip>
2155
+ </>
1798
2156
  }
1799
- >
1800
- <Droppable droppableId='filter_order'>
1801
- {/* prettier-ignore */}
1802
- {provided => {
1803
- return (
1804
- <ul
1805
- {...provided.droppableProps}
1806
- className='series-list'
1807
- ref={provided.innerRef}
1808
- >
1809
- <Panels.Series.List
1810
- series={config.series}
1811
- getItemStyle={getItemStyle}
1812
- sortableItemStyles={sortableItemStyles}
1813
- chartsWithOptions={chartsWithOptions}
1814
- />
1815
- {provided.placeholder}
1816
- </ul>
1817
- )
1818
- }}
1819
- </Droppable>
1820
- </DragDropContext>
2157
+ onDragEnd={({ source, destination }) => {
2158
+ if (!destination || source.index === destination.index) return
2159
+ handleSeriesChange(source.index, destination.index)
2160
+ }}
2161
+ renderItem={(series, index) => (
2162
+ <Panels.Series.Item
2163
+ getItemStyle={getItemStyle}
2164
+ sortableItemStyles={sortableItemStyles}
2165
+ chartsWithOptions={chartsWithOptions}
2166
+ series={series}
2167
+ index={index}
2168
+ key={`series-list-${index}`}
2169
+ />
2170
+ )}
2171
+ />
1821
2172
  </Panels.Series.Wrapper>
1822
2173
  )}
1823
2174
  </>
@@ -1875,9 +2226,9 @@ const EditorPanel: React.FC<ChartEditorPanelProps> = ({ datasets }) => {
1875
2226
  : config.orientation === 'vertical'
1876
2227
  ? 'Left Value Axis'
1877
2228
  : 'Value Axis'}
1878
- {config.visualizationType === 'Pie' && !config.yAxis.dataKey && (
2229
+ {config.visualizationType === 'Pie' && !config.yAxis.dataKey ? (
1879
2230
  <WarningImage width='25' className='warning-icon' />
1880
- )}
2231
+ ) : null}
1881
2232
  </AccordionItemButton>
1882
2233
  </AccordionItemHeading>
1883
2234
  <AccordionItemPanel>
@@ -1904,28 +2255,107 @@ const EditorPanel: React.FC<ChartEditorPanelProps> = ({ datasets }) => {
1904
2255
  }
1905
2256
  />
1906
2257
  <TextField
1907
- value={config.yAxis.label}
2258
+ value={config.yAxis.label}
2259
+ section='yAxis'
2260
+ fieldName='label'
2261
+ label='Data Label'
2262
+ updateField={updateFieldDeprecated}
2263
+ maxLength={35}
2264
+ tooltip={
2265
+ <Tooltip style={{ textTransform: 'none' }}>
2266
+ <Tooltip.Target>
2267
+ <Icon display='question' style={{ marginLeft: '0.5rem' }} />
2268
+ </Tooltip.Target>
2269
+ <Tooltip.Content>
2270
+ <p>
2271
+ Override the data column name shown in tooltips and data table (35 character limit)
2272
+ </p>
2273
+ </Tooltip.Content>
2274
+ </Tooltip>
2275
+ }
2276
+ />
2277
+ </>
2278
+ )}
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'
1908
2313
  section='yAxis'
1909
- fieldName='label'
1910
- label='Data Label'
2314
+ fieldName='size'
2315
+ label='Size (Width)'
2316
+ className='number-narrow'
1911
2317
  updateField={updateFieldDeprecated}
1912
- maxLength={35}
1913
2318
  tooltip={
1914
2319
  <Tooltip style={{ textTransform: 'none' }}>
1915
2320
  <Tooltip.Target>
1916
- <Icon display='question' style={{ marginLeft: '0.5rem' }} />
2321
+ <Icon
2322
+ display='question'
2323
+ style={{ marginLeft: '0.5rem', display: 'inline-block', whiteSpace: 'nowrap' }}
2324
+ />
1917
2325
  </Tooltip.Target>
1918
2326
  <Tooltip.Content>
1919
- <p>
1920
- Override the data column name shown in tooltips and data table (35 character limit)
1921
- </p>
2327
+ <p>Increase the size if month labels are crowded or overlap the chart area.</p>
1922
2328
  </Tooltip.Content>
1923
2329
  </Tooltip>
1924
2330
  }
1925
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
+ />
1926
2356
  </>
1927
2357
  )}
1928
- {config.visualizationType !== 'Pie' && (
2358
+ {config.visualizationType !== 'Pie' && config.visualizationType !== 'HeatMap' && (
1929
2359
  <>
1930
2360
  <Select
1931
2361
  label='Axis Type'
@@ -1976,18 +2406,30 @@ const EditorPanel: React.FC<ChartEditorPanelProps> = ({ datasets }) => {
1976
2406
  fieldName='label'
1977
2407
  label='Label'
1978
2408
  updateField={updateFieldDeprecated}
1979
- maxLength={35}
2409
+ maxLength={config.yAxis.titlePlacement === 'side' ? 35 : undefined}
1980
2410
  tooltip={
1981
2411
  <Tooltip style={{ textTransform: 'none' }}>
1982
2412
  <Tooltip.Target>
1983
2413
  <Icon display='question' style={{ marginLeft: '0.5rem' }} />
1984
2414
  </Tooltip.Target>
1985
2415
  <Tooltip.Content>
1986
- <p>35 character limit</p>
2416
+ <p>35 character limit when Label Placement is Side</p>
1987
2417
  </Tooltip.Content>
1988
2418
  </Tooltip>
1989
2419
  }
1990
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
+ />
1991
2433
  <TextField
1992
2434
  display={!visHasCategoricalAxis()}
1993
2435
  value={config.yAxis.inlineLabel}
@@ -2045,38 +2487,6 @@ const EditorPanel: React.FC<ChartEditorPanelProps> = ({ datasets }) => {
2045
2487
  }
2046
2488
  updateField={updateFieldDeprecated}
2047
2489
  />
2048
- <TextField
2049
- value={config.yAxis.size}
2050
- type='number'
2051
- section='yAxis'
2052
- fieldName='size'
2053
- label={config.orientation === 'horizontal' ? 'Size (Height)' : 'Size (Width)'}
2054
- className='number-narrow'
2055
- updateField={updateFieldDeprecated}
2056
- tooltip={
2057
- <Tooltip style={{ textTransform: 'none' }}>
2058
- <Tooltip.Target>
2059
- <Icon
2060
- display='question'
2061
- style={{ marginLeft: '0.5rem', display: 'inline-block', whiteSpace: 'nowrap' }}
2062
- />
2063
- </Tooltip.Target>
2064
- <Tooltip.Content>
2065
- <p>{`Increase the size if elements in the ${config.orientation} axis are being crowded or hidden behind other elements. Decrease if less space is required for the value axis.`}</p>
2066
- </Tooltip.Content>
2067
- </Tooltip>
2068
- }
2069
- />
2070
- <TextField
2071
- display={!visHasCategoricalAxis()}
2072
- value={config.yAxis.labelOffset}
2073
- section='yAxis'
2074
- fieldName='labelOffset'
2075
- label='Label offset'
2076
- type='number'
2077
- className='number-narrow'
2078
- updateField={updateFieldDeprecated}
2079
- />
2080
2490
  {config.orientation === 'horizontal' && (
2081
2491
  <CheckBox
2082
2492
  value={config.isResponsiveTicks}
@@ -2403,7 +2813,8 @@ const EditorPanel: React.FC<ChartEditorPanelProps> = ({ datasets }) => {
2403
2813
  )}
2404
2814
  </>
2405
2815
  ) : (
2406
- config.visualizationType !== 'Pie' && (
2816
+ config.visualizationType !== 'Pie' &&
2817
+ config.visualizationType !== 'HeatMap' && (
2407
2818
  <>
2408
2819
  <CheckBox
2409
2820
  display={!visHasCategoricalAxis()}
@@ -2481,251 +2892,20 @@ const EditorPanel: React.FC<ChartEditorPanelProps> = ({ datasets }) => {
2481
2892
  )}
2482
2893
  {/* start: anchors */}
2483
2894
  {visHasAnchors() && config.orientation !== 'horizontal' && (
2484
- <div className='edit-block'>
2485
- <span className='edit-label column-heading'>Anchors</span>
2486
- <Accordion allowZeroExpanded>
2487
- {config.yAxis?.anchors?.map((anchor, index) => (
2488
- <AccordionItem
2489
- className='series-item series-item--chart'
2490
- key={`yaxis-anchors-2-${index}`}
2491
- >
2492
- <AccordionItemHeading className='series-item__title'>
2493
- <>
2494
- <AccordionItemButton className={'accordion__button accordion__button'}>
2495
- Anchor {index + 1}
2496
- <button
2497
- className='series-list__remove'
2498
- onClick={e => {
2499
- e.preventDefault()
2500
- const copiedAnchorGroups = [...config.yAxis.anchors]
2501
- copiedAnchorGroups.splice(index, 1)
2502
- updateConfig({
2503
- ...config,
2504
- yAxis: {
2505
- ...config.yAxis,
2506
- anchors: copiedAnchorGroups
2507
- }
2508
- })
2509
- }}
2510
- >
2511
- Remove
2512
- </button>
2513
- </AccordionItemButton>
2514
- </>
2515
- </AccordionItemHeading>
2516
- <AccordionItemPanel>
2517
- <label>
2518
- <span>Anchor Value</span>
2519
- <Tooltip style={{ textTransform: 'none' }}>
2520
- <Tooltip.Target>
2521
- <Icon display='question' style={{ marginLeft: '0.5rem' }} />
2522
- </Tooltip.Target>
2523
- <Tooltip.Content>
2524
- <p>Enter the value as its shown in the data column</p>
2525
- </Tooltip.Content>
2526
- </Tooltip>
2527
- <input
2528
- type='text'
2529
- value={config.yAxis.anchors[index].value ? config.yAxis.anchors[index].value : ''}
2530
- onChange={e => {
2531
- e.preventDefault()
2532
- const copiedAnchors = [...config.yAxis.anchors]
2533
- copiedAnchors[index].value = e.target.value
2534
- updateConfig({
2535
- ...config,
2536
- yAxis: {
2537
- ...config.yAxis,
2538
- anchors: copiedAnchors
2539
- }
2540
- })
2541
- }}
2542
- />
2543
- </label>
2544
-
2545
- <label>
2546
- <span>Anchor Color</span>
2547
- <input
2548
- type='text'
2549
- value={config.yAxis.anchors[index].color ? config.yAxis.anchors[index].color : ''}
2550
- onChange={e => {
2551
- e.preventDefault()
2552
- const copiedAnchors = [...config.yAxis.anchors]
2553
- copiedAnchors[index].color = e.target.value
2554
- updateConfig({
2555
- ...config,
2556
- yAxis: {
2557
- ...config.yAxis,
2558
- anchors: copiedAnchors
2559
- }
2560
- })
2561
- }}
2562
- />
2563
- </label>
2564
-
2565
- <Select
2566
- value={config.yAxis.anchors[index].lineStyle || ''}
2567
- label='Anchor Line Style'
2568
- onChange={e => {
2569
- const copiedAnchors = [...config.yAxis.anchors]
2570
- copiedAnchors[index].lineStyle = e.target.value
2571
- updateConfig({
2572
- ...config,
2573
- yAxis: {
2574
- ...config.yAxis,
2575
- anchors: copiedAnchors
2576
- }
2577
- })
2578
- }}
2579
- options={[
2580
- { value: '', label: 'Select' },
2581
- ...lineOptions.map(line => ({ value: line.value, label: line.value }))
2582
- ]}
2583
- />
2584
- </AccordionItemPanel>
2585
- </AccordionItem>
2586
- ))}
2587
- </Accordion>
2588
-
2589
- <button
2590
- className='btn btn-primary full-width'
2591
- onClick={e => {
2592
- e.preventDefault()
2593
- const anchors = [...config.yAxis.anchors]
2594
- anchors.push({} as Anchor)
2595
- updateConfig({
2596
- ...config,
2597
- yAxis: {
2598
- ...config.yAxis,
2599
- anchors
2600
- }
2601
- })
2602
- }}
2603
- >
2604
- Add Anchor
2605
- </button>
2606
- </div>
2895
+ <AxisAnchorEditor
2896
+ axisKey='yAxis'
2897
+ config={config}
2898
+ lineOptions={lineOptions}
2899
+ updateConfig={updateConfig}
2900
+ />
2607
2901
  )}
2608
2902
  {visHasAnchors() && config.orientation === 'horizontal' && (
2609
- <div className='edit-block'>
2610
- <span className='edit-label column-heading'>Anchors</span>
2611
- <Accordion allowZeroExpanded>
2612
- {config.xAxis?.anchors?.map((anchor, index) => (
2613
- <AccordionItem className='series-item series-item--chart' key={`xaxis-anchors-${index}`}>
2614
- <AccordionItemHeading className='series-item__title'>
2615
- <>
2616
- <AccordionItemButton className={'accordion__button accordion__button'}>
2617
- Anchor {index + 1}
2618
- <button
2619
- className='series-list__remove'
2620
- onClick={e => {
2621
- e.preventDefault()
2622
- const copiedAnchorGroups = [...config.xAxis.anchors]
2623
- copiedAnchorGroups.splice(index, 1)
2624
- updateConfig({
2625
- ...config,
2626
- xAxis: {
2627
- ...config.xAxis,
2628
- anchors: copiedAnchorGroups
2629
- }
2630
- })
2631
- }}
2632
- >
2633
- Remove
2634
- </button>
2635
- </AccordionItemButton>
2636
- </>
2637
- </AccordionItemHeading>
2638
- <AccordionItemPanel>
2639
- <label>
2640
- <span>Anchor Value</span>
2641
- <Tooltip style={{ textTransform: 'none' }}>
2642
- <Tooltip.Target>
2643
- <Icon display='question' style={{ marginLeft: '0.5rem' }} />
2644
- </Tooltip.Target>
2645
- <Tooltip.Content>
2646
- <p>Enter the value as its shown in the data column</p>
2647
- </Tooltip.Content>
2648
- </Tooltip>
2649
- <input
2650
- type='text'
2651
- value={config.xAxis.anchors[index].value ? config.xAxis.anchors[index].value : ''}
2652
- onChange={e => {
2653
- e.preventDefault()
2654
- const copiedAnchors = [...config.xAxis.anchors]
2655
- copiedAnchors[index].value = e.target.value
2656
- updateConfig({
2657
- ...config,
2658
- xAxis: {
2659
- ...config.xAxis,
2660
- anchors: copiedAnchors
2661
- }
2662
- })
2663
- }}
2664
- />
2665
- </label>
2666
-
2667
- <label>
2668
- <span>Anchor Color</span>
2669
- <input
2670
- type='text'
2671
- value={config.xAxis.anchors[index].color ? config.xAxis.anchors[index].color : ''}
2672
- onChange={e => {
2673
- e.preventDefault()
2674
- const copiedAnchors = [...config.xAxis.anchors]
2675
- copiedAnchors[index].color = e.target.value
2676
- updateConfig({
2677
- ...config,
2678
- xAxis: {
2679
- ...config.xAxis,
2680
- anchors: copiedAnchors
2681
- }
2682
- })
2683
- }}
2684
- />
2685
- </label>
2686
-
2687
- <Select
2688
- value={config.xAxis.anchors[index].lineStyle || ''}
2689
- label='Anchor Line Style'
2690
- onChange={e => {
2691
- const copiedAnchors = [...config.xAxis.anchors]
2692
- copiedAnchors[index].lineStyle = e.target.value
2693
- updateConfig({
2694
- ...config,
2695
- xAxis: {
2696
- ...config.xAxis,
2697
- anchors: copiedAnchors
2698
- }
2699
- })
2700
- }}
2701
- options={[
2702
- { value: '', label: 'Select' },
2703
- ...lineOptions.map(line => ({ value: line.value, label: line.value }))
2704
- ]}
2705
- />
2706
- </AccordionItemPanel>
2707
- </AccordionItem>
2708
- ))}
2709
- </Accordion>
2710
-
2711
- <button
2712
- className='btn btn-primary full-width'
2713
- onClick={e => {
2714
- e.preventDefault()
2715
- const anchors = [...config.xAxis.anchors]
2716
- anchors.push({} as Anchor)
2717
- updateConfig({
2718
- ...config,
2719
- xAxis: {
2720
- ...config.xAxis,
2721
- anchors
2722
- }
2723
- })
2724
- }}
2725
- >
2726
- Add Anchor
2727
- </button>
2728
- </div>
2903
+ <AxisAnchorEditor
2904
+ axisKey='xAxis'
2905
+ config={config}
2906
+ lineOptions={lineOptions}
2907
+ updateConfig={updateConfig}
2908
+ />
2729
2909
  )}
2730
2910
  {/* end: anchors */}
2731
2911
  </AccordionItemPanel>
@@ -2961,11 +3141,19 @@ const EditorPanel: React.FC<ChartEditorPanelProps> = ({ datasets }) => {
2961
3141
  section='xAxis'
2962
3142
  fieldName='type'
2963
3143
  updateField={(_section, _subsection, _fieldName, value) => {
3144
+ const autoDetectedDateParseFormat = getAutoDetectedXAxisDateParseFormat(
3145
+ config.xAxis.dataKey,
3146
+ value
3147
+ )
3148
+
2964
3149
  updateConfig({
2965
3150
  ...config,
2966
3151
  xAxis: {
2967
3152
  ...config.xAxis,
2968
- type: value
3153
+ type: value,
3154
+ ...(autoDetectedDateParseFormat
3155
+ ? { dateParseFormat: autoDetectedDateParseFormat }
3156
+ : {})
2969
3157
  }
2970
3158
  })
2971
3159
  }}
@@ -3082,6 +3270,16 @@ const EditorPanel: React.FC<ChartEditorPanelProps> = ({ datasets }) => {
3082
3270
  </Tooltip>
3083
3271
  }
3084
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
+ )}
3085
3283
 
3086
3284
  {config.xAxis.type === 'continuous' && (
3087
3285
  <>
@@ -3341,38 +3539,71 @@ const EditorPanel: React.FC<ChartEditorPanelProps> = ({ datasets }) => {
3341
3539
  }
3342
3540
  />
3343
3541
  {config.xAxis.brushActive && (
3344
- <TextField
3345
- value={config.xAxis.brushDefaultRecentDateCount ?? ''}
3346
- placeholder='Default (35%)'
3347
- type='number'
3348
- min={1}
3349
- section='xAxis'
3350
- fieldName='brushDefaultRecentDateCount'
3351
- label='Show Last X Dates by Default'
3352
- className='number-narrow'
3353
- updateField={updateFieldDeprecated}
3354
- tooltip={
3355
- <Tooltip style={{ textTransform: 'none' }}>
3356
- <Tooltip.Target>
3357
- <Icon
3358
- display='question'
3359
- style={{
3360
- marginLeft: '0.5rem',
3361
- display: 'inline-block',
3362
- whiteSpace: 'nowrap'
3363
- }}
3364
- />
3365
- </Tooltip.Target>
3366
- <Tooltip.Content>
3367
- <p>
3368
- When set, the brush slider will initially select this many recent data points
3369
- instead of the default 35%. Leave empty to use the default percentage-based
3370
- selection.
3371
- </p>
3372
- </Tooltip.Content>
3373
- </Tooltip>
3374
- }
3375
- />
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
+ </>
3376
3607
  )}
3377
3608
  </>
3378
3609
  )}
@@ -3587,18 +3818,6 @@ const EditorPanel: React.FC<ChartEditorPanelProps> = ({ datasets }) => {
3587
3818
  updateField={updateFieldDeprecated}
3588
3819
  />
3589
3820
  )}
3590
- {config.orientation === 'horizontal' && (
3591
- <TextField
3592
- value={config.xAxis.labelOffset}
3593
- section='xAxis'
3594
- fieldName='labelOffset'
3595
- label='Label offset'
3596
- type='number'
3597
- className='number-narrow'
3598
- updateField={updateFieldDeprecated}
3599
- />
3600
- )}
3601
-
3602
3821
  {/* Hiding this for now, not interested in moving the axis lines away from chart comp. right now. */}
3603
3822
  {/* <TextField value={config.xAxis.axisPadding} type='number' max={10} min={0} section='xAxis' fieldName='axisPadding' label={'Axis Padding'} className='number-narrow' updateField={updateFieldDeprecated} /> */}
3604
3823
  {(config.xAxis.type === 'continuous' || config.forestPlot.type === 'Logarithmic') && (
@@ -3737,12 +3956,12 @@ const EditorPanel: React.FC<ChartEditorPanelProps> = ({ datasets }) => {
3737
3956
  highlightedBarValues.map((highlightedBarValue, i) => (
3738
3957
  <fieldset>
3739
3958
  <div className='edit-block' key={`highlighted-bar-${i}`}>
3740
- <button
3959
+ <Button
3741
3960
  className='btn btn-danger'
3742
3961
  onClick={e => handleRemoveHighlightedBar(e, i)}
3743
3962
  >
3744
3963
  Remove
3745
- </button>
3964
+ </Button>
3746
3965
  <p>Highlighted Bar {i + 1}</p>
3747
3966
  <Select
3748
3967
  value={config.highlightedBarValues[i].value}
@@ -3750,11 +3969,7 @@ const EditorPanel: React.FC<ChartEditorPanelProps> = ({ datasets }) => {
3750
3969
  onChange={e => handleUpdateHighlightedBar(e, i)}
3751
3970
  options={[
3752
3971
  { value: '', label: '- Select Value -' },
3753
- ...(highlightedSeriesValues
3754
- ? [...new Set(highlightedSeriesValues)]
3755
- .sort()
3756
- .map(option => ({ value: option, label: option }))
3757
- : [])
3972
+ ...highlightedSeriesOptions
3758
3973
  ]}
3759
3974
  />
3760
3975
  <label>
@@ -3798,12 +4013,9 @@ const EditorPanel: React.FC<ChartEditorPanelProps> = ({ datasets }) => {
3798
4013
  </div>
3799
4014
  </fieldset>
3800
4015
  ))}
3801
- <button
3802
- className='btn btn-primary full-width'
3803
- onClick={e => handleAddNewHighlightedBar(e)}
3804
- >
4016
+ <Button variant='editor-primary' onClick={e => handleAddNewHighlightedBar(e)}>
3805
4017
  Add Highlighted Bar
3806
- </button>
4018
+ </Button>
3807
4019
  </>
3808
4020
  )}
3809
4021
  </>
@@ -3888,259 +4100,26 @@ const EditorPanel: React.FC<ChartEditorPanelProps> = ({ datasets }) => {
3888
4100
 
3889
4101
  {/* anchors */}
3890
4102
  {visHasAnchors() && config.orientation !== 'horizontal' && (
3891
- <div className='edit-block'>
3892
- <span className='edit-label column-heading'>Anchors</span>
3893
- <Accordion allowZeroExpanded>
3894
- {config.xAxis?.anchors?.map((anchor, index) => (
3895
- <AccordionItem
3896
- className='series-item series-item--chart'
3897
- key={`xaxis-anchors-2-${index}`}
3898
- >
3899
- <AccordionItemHeading className='series-item__title'>
3900
- <>
3901
- <AccordionItemButton className={'accordion__button accordion__button'}>
3902
- Anchor {index + 1}
3903
- <button
3904
- className='series-list__remove'
3905
- onClick={e => {
3906
- e.preventDefault()
3907
- const copiedAnchorGroups = [...config.xAxis.anchors]
3908
- copiedAnchorGroups.splice(index, 1)
3909
- updateConfig({
3910
- ...config,
3911
- xAxis: {
3912
- ...config.xAxis,
3913
- anchors: copiedAnchorGroups
3914
- }
3915
- })
3916
- }}
3917
- >
3918
- Remove
3919
- </button>
3920
- </AccordionItemButton>
3921
- </>
3922
- </AccordionItemHeading>
3923
- <AccordionItemPanel>
3924
- <label>
3925
- <span>Anchor Value</span>
3926
- <Tooltip style={{ textTransform: 'none' }}>
3927
- <Tooltip.Target>
3928
- <Icon display='question' style={{ marginLeft: '0.5rem' }} />
3929
- </Tooltip.Target>
3930
- <Tooltip.Content>
3931
- <p>Enter the value as its shown in the data column</p>
3932
- </Tooltip.Content>
3933
- </Tooltip>
3934
- <input
3935
- type='text'
3936
- value={config.xAxis.anchors[index].value ? config.xAxis.anchors[index].value : ''}
3937
- onChange={e => {
3938
- e.preventDefault()
3939
- const copiedAnchors = [...config.xAxis.anchors]
3940
- copiedAnchors[index].value = e.target.value
3941
- updateConfig({
3942
- ...config,
3943
- xAxis: {
3944
- ...config.xAxis,
3945
- anchors: copiedAnchors
3946
- }
3947
- })
3948
- }}
3949
- />
3950
- </label>
3951
-
3952
- <label>
3953
- <span>Anchor Color</span>
3954
- <input
3955
- type='text'
3956
- value={config.xAxis.anchors[index].color ? config.xAxis.anchors[index].color : ''}
3957
- onChange={e => {
3958
- e.preventDefault()
3959
- const copiedAnchors = [...config.xAxis.anchors]
3960
- copiedAnchors[index].color = e.target.value
3961
- updateConfig({
3962
- ...config,
3963
- xAxis: {
3964
- ...config.xAxis,
3965
- anchors: copiedAnchors
3966
- }
3967
- })
3968
- }}
3969
- />
3970
- </label>
3971
-
3972
- <Select
3973
- value={config.xAxis.anchors[index].lineStyle || ''}
3974
- label='Anchor Line Style'
3975
- onChange={e => {
3976
- const copiedAnchors = [...config.xAxis.anchors]
3977
- copiedAnchors[index].lineStyle = e.target.value
3978
- updateConfig({
3979
- ...config,
3980
- xAxis: {
3981
- ...config.xAxis,
3982
- anchors: copiedAnchors
3983
- }
3984
- })
3985
- }}
3986
- options={[
3987
- { value: '', label: 'Select' },
3988
- ...lineOptions.map(line => ({ value: line.value, label: line.value }))
3989
- ]}
3990
- />
3991
- </AccordionItemPanel>
3992
- </AccordionItem>
3993
- ))}
3994
- </Accordion>
3995
-
3996
- <button
3997
- className='btn btn-primary full-width'
3998
- onClick={e => {
3999
- e.preventDefault()
4000
- const anchors = [...config.xAxis.anchors]
4001
- anchors.push({} as Anchor)
4002
- updateConfig({
4003
- ...config,
4004
- xAxis: {
4005
- ...config.xAxis,
4006
- anchors
4007
- }
4008
- })
4009
- }}
4010
- >
4011
- Add Anchor
4012
- </button>
4013
- </div>
4103
+ <AxisAnchorEditor
4104
+ axisKey='xAxis'
4105
+ config={config}
4106
+ lineOptions={lineOptions}
4107
+ updateConfig={updateConfig}
4108
+ />
4014
4109
  )}
4015
4110
 
4016
4111
  {visHasAnchors() && config.orientation === 'horizontal' && (
4017
- <div className='edit-block'>
4018
- <span className='edit-label column-heading'>Anchors</span>
4019
- <Accordion allowZeroExpanded>
4020
- {config.yAxis?.anchors?.map((anchor, index) => (
4021
- <AccordionItem
4022
- className='series-item series-item--chart'
4023
- key={`accordion-yaxis-anchors-${index}`}
4024
- >
4025
- <AccordionItemHeading className='series-item__title'>
4026
- <>
4027
- <AccordionItemButton className={'accordion__button accordion__button'}>
4028
- Anchor {index + 1}
4029
- <button
4030
- className='series-list__remove'
4031
- onClick={e => {
4032
- e.preventDefault()
4033
- const copiedAnchorGroups = [...config.yAxis.anchors]
4034
- copiedAnchorGroups.splice(index, 1)
4035
- updateConfig({
4036
- ...config,
4037
- yAxis: {
4038
- ...config.yAxis,
4039
- anchors: copiedAnchorGroups
4040
- }
4041
- })
4042
- }}
4043
- >
4044
- Remove
4045
- </button>
4046
- </AccordionItemButton>
4047
- </>
4048
- </AccordionItemHeading>
4049
- <AccordionItemPanel>
4050
- <label>
4051
- <span>Anchor Value</span>
4052
- <Tooltip style={{ textTransform: 'none' }}>
4053
- <Tooltip.Target>
4054
- <Icon display='question' style={{ marginLeft: '0.5rem' }} />
4055
- </Tooltip.Target>
4056
- <Tooltip.Content>
4057
- <p>Enter the value as its shown in the data column</p>
4058
- </Tooltip.Content>
4059
- </Tooltip>
4060
- <input
4061
- type='text'
4062
- value={config.yAxis.anchors[index].value ? config.yAxis.anchors[index].value : ''}
4063
- onChange={e => {
4064
- e.preventDefault()
4065
- const copiedAnchors = [...config.yAxis.anchors]
4066
- copiedAnchors[index].value = e.target.value
4067
- updateConfig({
4068
- ...config,
4069
- yAxis: {
4070
- ...config.yAxis,
4071
- anchors: copiedAnchors
4072
- }
4073
- })
4074
- }}
4075
- />
4076
- </label>
4077
-
4078
- <label>
4079
- <span>Anchor Color</span>
4080
- <input
4081
- type='text'
4082
- value={config.yAxis.anchors[index].color ? config.yAxis.anchors[index].color : ''}
4083
- onChange={e => {
4084
- e.preventDefault()
4085
- const copiedAnchors = [...config.yAxis.anchors]
4086
- copiedAnchors[index].color = e.target.value
4087
- updateConfig({
4088
- ...config,
4089
- yAxis: {
4090
- ...config.yAxis,
4091
- anchors: copiedAnchors
4092
- }
4093
- })
4094
- }}
4095
- />
4096
- </label>
4097
-
4098
- <Select
4099
- value={config.yAxis.anchors[index].lineStyle || ''}
4100
- label='Anchor Line Style'
4101
- onChange={e => {
4102
- const copiedAnchors = [...config.yAxis.anchors]
4103
- copiedAnchors[index].lineStyle = e.target.value
4104
- updateConfig({
4105
- ...config,
4106
- yAxis: {
4107
- ...config.yAxis,
4108
- anchors: copiedAnchors
4109
- }
4110
- })
4111
- }}
4112
- options={[
4113
- { value: '', label: 'Select' },
4114
- ...lineOptions.map(line => ({ value: line.value, label: line.value }))
4115
- ]}
4116
- />
4117
- </AccordionItemPanel>
4118
- </AccordionItem>
4119
- ))}
4120
- </Accordion>
4121
-
4122
- <button
4123
- className='btn btn-primary full-width'
4124
- onClick={e => {
4125
- e.preventDefault()
4126
- const anchors = [...config.yAxis.anchors]
4127
- anchors.push({} as Anchor)
4128
- updateConfig({
4129
- ...config,
4130
- yAxis: {
4131
- ...config.yAxis,
4132
- anchors
4133
- }
4134
- })
4135
- }}
4136
- >
4137
- Add Anchor
4138
- </button>
4139
- </div>
4112
+ <AxisAnchorEditor
4113
+ axisKey='yAxis'
4114
+ config={config}
4115
+ lineOptions={lineOptions}
4116
+ updateConfig={updateConfig}
4117
+ />
4140
4118
  )}
4141
4119
  </AccordionItemPanel>
4142
4120
  </AccordionItem>
4143
4121
  )}
4122
+ {renderHeatMapSettingsAccordion()}
4144
4123
  <Panels.Regions name='Regions' />
4145
4124
 
4146
4125
  {/* Columns */}
@@ -4154,7 +4133,9 @@ const EditorPanel: React.FC<ChartEditorPanelProps> = ({ datasets }) => {
4154
4133
  config={config}
4155
4134
  updateField={updateFieldDeprecated}
4156
4135
  deleteColumn={removeAdditionalColumn}
4157
- hiddenColumnNames={seriesOwnedColumnNames}
4136
+ hiddenColumnNames={
4137
+ isHeatMap ? [...seriesOwnedColumnNames, ...heatMapOwnedColumnNames] : seriesOwnedColumnNames
4138
+ }
4158
4139
  />{' '}
4159
4140
  </AccordionItemPanel>
4160
4141
  </AccordionItem>
@@ -4173,7 +4154,11 @@ const EditorPanel: React.FC<ChartEditorPanelProps> = ({ datasets }) => {
4173
4154
  label='Position'
4174
4155
  updateField={updateFieldDeprecated}
4175
4156
  options={
4176
- 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'
4177
4162
  ? ['bottom']
4178
4163
  : ['right', 'left', 'bottom', 'top']
4179
4164
  }
@@ -4213,7 +4198,7 @@ const EditorPanel: React.FC<ChartEditorPanelProps> = ({ datasets }) => {
4213
4198
  options={getLegendStyleOptions('style')}
4214
4199
  />
4215
4200
 
4216
- {config.visualizationType !== 'Warming Stripes' && (
4201
+ {config.visualizationType !== 'Warming Stripes' && config.visualizationType !== 'HeatMap' && (
4217
4202
  <Select
4218
4203
  value={config.legend.groupBy}
4219
4204
  section='legend'
@@ -4379,7 +4364,7 @@ const EditorPanel: React.FC<ChartEditorPanelProps> = ({ datasets }) => {
4379
4364
  {config.legend.seriesHighlight &&
4380
4365
  config.legend.seriesHighlight.map((val, i) => (
4381
4366
  <fieldset className='edit-block' key={`${val}-${i}`}>
4382
- <button
4367
+ <Button
4383
4368
  className='btn btn-danger'
4384
4369
  onClick={event => {
4385
4370
  event.preventDefault()
@@ -4392,7 +4377,7 @@ const EditorPanel: React.FC<ChartEditorPanelProps> = ({ datasets }) => {
4392
4377
  }}
4393
4378
  >
4394
4379
  Remove
4395
- </button>
4380
+ </Button>
4396
4381
  <Select
4397
4382
  value={config.legend.seriesHighlight[i]}
4398
4383
  fieldName='seriesHighlight'
@@ -4408,8 +4393,8 @@ const EditorPanel: React.FC<ChartEditorPanelProps> = ({ datasets }) => {
4408
4393
  />
4409
4394
  </fieldset>
4410
4395
  ))}
4411
- <button
4412
- className={'btn btn-primary full-width'}
4396
+ <Button
4397
+ variant='editor-primary'
4413
4398
  onClick={event => {
4414
4399
  event.preventDefault()
4415
4400
  const legendColumns = getLegendColumns()
@@ -4425,7 +4410,7 @@ const EditorPanel: React.FC<ChartEditorPanelProps> = ({ datasets }) => {
4425
4410
  }}
4426
4411
  >
4427
4412
  Add Isolate Value
4428
- </button>
4413
+ </Button>
4429
4414
  </fieldset>
4430
4415
  )}
4431
4416
  {/* end: isolated values */}
@@ -4622,6 +4607,7 @@ const EditorPanel: React.FC<ChartEditorPanelProps> = ({ datasets }) => {
4622
4607
  loadConfig={updateConfig}
4623
4608
  config={config}
4624
4609
  convertStateToConfig={chartConvertStateToConfig}
4610
+ defaultExpanded={config.visualizationType === 'HeatMap'}
4625
4611
  />
4626
4612
  )}
4627
4613
  </>