@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.
- package/CONFIG.md +349 -29
- package/LICENSE +201 -0
- package/dist/cdcchart.js +34247 -32805
- package/examples/feature/heatmap/average-age-categorical.json +313 -0
- package/examples/feature/heatmap/calendar-heatmap.json +134 -0
- package/examples/feature/heatmap/cell-values-bottom-axis.json +181 -0
- package/examples/feature/heatmap/sparse-aggregation.json +158 -0
- package/examples/private/bump-chart.json +1116 -0
- package/examples/private/test-123.json +624 -0
- package/package.json +4 -3
- package/src/CdcChartComponent.tsx +135 -54
- package/src/_stories/Chart.AltText.stories.tsx +136 -0
- package/src/_stories/Chart.HeatMap.stories.tsx +187 -0
- package/src/_stories/Chart.Sankey.smoke.stories.tsx +62 -0
- package/src/_stories/Chart.SmallMultiples.smoke.stories.tsx +29 -0
- package/src/_stories/ChartAxisTitles.smoke.stories.tsx +13 -0
- package/src/_stories/ChartBar.Editor.stories.tsx +63 -0
- package/src/_stories/ChartBrush.Interaction.stories.tsx +112 -0
- package/src/_stories/ChartBrush.smoke.stories.tsx +13 -4
- package/src/_stories/_mock/alt_text_metadata.json +38 -0
- package/src/_stories/_mock/heatmap-average-age-categorical.json +315 -0
- package/src/_stories/_mock/heatmap.json +156 -0
- package/src/_stories/_mock/reported_pertussis_cases.json +1643 -0
- package/src/_stories/_mock/sankey_mock.json +78 -0
- package/src/_stories/_mock/small_multiples/small_multiples_lines.json +1 -1
- package/src/components/Annotations/components/AnnotationDraggable.tsx +1 -1
- package/src/components/Annotations/components/AnnotationDropdown.tsx +5 -2
- package/src/components/Axis/LeftAxis.tsx +14 -11
- package/src/components/Axis/LeftAxisGridlines.tsx +14 -11
- package/src/components/Brush/BrushSelector.tsx +88 -187
- package/src/components/Brush/brushInteraction.ts +22 -0
- package/src/components/Brush/tests/brushInteraction.test.ts +38 -0
- package/src/components/EditorPanel/EditorPanel.tsx +310 -49
- package/src/components/EditorPanel/components/Panels/Panel.Annotate.tsx +13 -0
- package/src/components/EditorPanel/components/Panels/Panel.General.tsx +105 -13
- package/src/components/EditorPanel/components/Panels/Panel.PatternSettings.tsx +1 -0
- package/src/components/EditorPanel/components/Panels/Panel.Regions.tsx +5 -0
- package/src/components/EditorPanel/components/Panels/Panel.Series.tsx +2 -2
- package/src/components/EditorPanel/components/Panels/Panel.Visual.tsx +1 -1
- package/src/components/EditorPanel/helpers/getVisualizationTypeConfigUpdate.ts +57 -0
- package/src/components/EditorPanel/helpers/tests/getVisualizationTypeConfigUpdate.test.ts +56 -0
- package/src/components/EditorPanel/useEditorPermissions.test.tsx +70 -0
- package/src/components/EditorPanel/useEditorPermissions.ts +42 -22
- package/src/components/HeatMap/components/HeatMap.tsx +762 -0
- package/src/components/HeatMap/components/HeatMapGradientLegend.tsx +114 -0
- package/src/components/HeatMap/heatmap.constants.ts +13 -0
- package/src/components/HeatMap/heatmap.css +71 -0
- package/src/components/HeatMap/helpers/index.ts +261 -0
- package/src/components/HeatMap/index.tsx +5 -0
- package/src/components/HeatMap/tests/HeatMap.test.tsx +1040 -0
- package/src/components/HeatMap/tests/helpers.test.ts +167 -0
- package/src/components/LineChart/components/LineChart.BumpCircle.tsx +19 -22
- package/src/components/LineChart/helpers.test.ts +345 -0
- package/src/components/LineChart/helpers.ts +15 -5
- package/src/components/LineChart/index.tsx +7 -1
- package/src/components/LinearChart/tests/LinearChart.test.tsx +107 -33
- package/src/components/LinearChart/tests/mockConfigContext.ts +1 -0
- package/src/components/LinearChart.tsx +58 -37
- package/src/components/PieChart/PieChart.tsx +4 -1
- package/src/components/RadarChart/RadarChart.tsx +3 -1
- package/src/components/Regions/components/Regions.tsx +195 -41
- package/src/components/Sankey/components/Sankey.tsx +7 -3
- package/src/components/Sankey/helpers/getSankeyTooltip.tsx +7 -6
- package/src/components/SmallMultiples/SmallMultipleTile.tsx +30 -3
- package/src/components/SmallMultiples/SmallMultiples.css +12 -0
- package/src/components/SmallMultiples/SmallMultiples.tsx +15 -9
- package/src/components/SmallMultiples/tests/SmallMultipleTile.test.tsx +152 -0
- package/src/components/Sparkline/components/SparkLine.tsx +4 -9
- package/src/components/WarmingStripes/WarmingStripesGradientLegend.css +1 -1
- package/src/components/WarmingStripes/WarmingStripesGradientLegend.tsx +2 -2
- package/src/data/initial-state.js +11 -0
- package/src/helpers/axisLabelFontSize.ts +8 -0
- package/src/helpers/bumpChartTooltip.ts +54 -0
- package/src/helpers/calculateLeftYAxisWidth.ts +4 -0
- package/src/helpers/ensureSpecialChartAxisTypes.ts +11 -0
- package/src/helpers/getMinMax.ts +2 -3
- package/src/helpers/getYAxisAutoPadding.ts +8 -6
- package/src/helpers/getYAxisAutoPaddingMode.ts +17 -0
- package/src/helpers/handleChartAriaLabels.ts +23 -10
- package/src/helpers/seriesHighlight.ts +26 -0
- package/src/helpers/tests/axisLabelFontSize.test.ts +15 -0
- package/src/helpers/tests/bumpChartTooltip.test.ts +75 -0
- package/src/helpers/tests/ensureSpecialChartAxisTypes.test.ts +30 -0
- package/src/helpers/tests/getYAxisAutoPadding.test.ts +37 -0
- package/src/helpers/tests/getYAxisAutoPaddingMode.test.ts +90 -0
- package/src/helpers/tests/handleChartAriaLabels.test.ts +71 -0
- package/src/helpers/tests/seriesHighlight.test.ts +61 -0
- package/src/hooks/tests/useScales.test.tsx +105 -0
- package/src/hooks/useScales.ts +19 -9
- package/src/index.jsx +5 -2
- package/src/scss/main.scss +32 -49
- package/src/types/ChartConfig.ts +18 -0
- package/src/types/ChartContext.ts +2 -0
|
@@ -13,12 +13,15 @@ import { approvedCurveTypes } from '@cdc/core/helpers/lineChartHelpers'
|
|
|
13
13
|
import { TextField, Select, CheckBox } from '@cdc/core/components/EditorPanel/Inputs'
|
|
14
14
|
import Tooltip from '@cdc/core/components/ui/Tooltip'
|
|
15
15
|
import Icon from '@cdc/core/components/ui/Icon'
|
|
16
|
+
import { resolveAltTextDescription } from '@cdc/core/helpers/resolveAltTextDescription'
|
|
16
17
|
|
|
17
18
|
// contexts
|
|
18
19
|
import { useEditorPermissions } from '../../useEditorPermissions.js'
|
|
19
20
|
import { useEditorPanelContext } from '../../EditorPanelContext.js'
|
|
20
21
|
import ConfigContext from '../../../../ConfigContext.js'
|
|
21
22
|
import { PanelProps } from '../PanelProps'
|
|
23
|
+
import { getVisualizationTypeConfigUpdate } from '../../helpers/getVisualizationTypeConfigUpdate'
|
|
24
|
+
import { type VisualizationType } from '../../../../types/ChartConfig'
|
|
22
25
|
|
|
23
26
|
const PanelGeneral: FC<PanelProps> = props => {
|
|
24
27
|
const { config, updateConfig } = useContext(ConfigContext)
|
|
@@ -79,22 +82,12 @@ const PanelGeneral: FC<PanelProps> = props => {
|
|
|
79
82
|
updateField={updateField}
|
|
80
83
|
options={enabledChartTypes}
|
|
81
84
|
onChange={event => {
|
|
82
|
-
const newVisType = event.target.value
|
|
85
|
+
const newVisType = event.target.value as VisualizationType
|
|
83
86
|
|
|
84
87
|
updateField(null, null, 'visualizationType', newVisType)
|
|
85
88
|
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
...config,
|
|
89
|
-
visualizationType: newVisType,
|
|
90
|
-
xAxis: {
|
|
91
|
-
...config.xAxis,
|
|
92
|
-
type: 'date',
|
|
93
|
-
dateParseFormat: config.xAxis.dateParseFormat || '%Y-%m-%d',
|
|
94
|
-
dateDisplayFormat: config.xAxis.dateDisplayFormat || '%Y-%m-%d'
|
|
95
|
-
}
|
|
96
|
-
})
|
|
97
|
-
}
|
|
89
|
+
const updatedConfig = getVisualizationTypeConfigUpdate(config, newVisType)
|
|
90
|
+
if (updatedConfig) updateConfig(updatedConfig)
|
|
98
91
|
}}
|
|
99
92
|
/>
|
|
100
93
|
)}
|
|
@@ -617,6 +610,105 @@ const PanelGeneral: FC<PanelProps> = props => {
|
|
|
617
610
|
</Tooltip>
|
|
618
611
|
}
|
|
619
612
|
/>
|
|
613
|
+
|
|
614
|
+
{/* Accessible Alt Text Description */}
|
|
615
|
+
{(() => {
|
|
616
|
+
const metadataKeys = Object.keys(config.dataMetadata || {})
|
|
617
|
+
const hasMetadata = metadataKeys.length > 0
|
|
618
|
+
const descType = config.altText?.type || ''
|
|
619
|
+
const resolvedDescription = resolveAltTextDescription(config.altText, config.dataMetadata)
|
|
620
|
+
return (
|
|
621
|
+
<>
|
|
622
|
+
<Select
|
|
623
|
+
value={descType}
|
|
624
|
+
fieldName='altTextType'
|
|
625
|
+
label='Alt Text Description'
|
|
626
|
+
options={[
|
|
627
|
+
{ value: '', label: 'None' },
|
|
628
|
+
{ value: 'static', label: 'Static (manual text)' },
|
|
629
|
+
{ value: 'metadata', label: 'Data File Metadata' }
|
|
630
|
+
]}
|
|
631
|
+
updateField={(_section, _subsection, _fieldName, value) => {
|
|
632
|
+
if (value === '') {
|
|
633
|
+
updateField(null, null, 'altText', undefined)
|
|
634
|
+
} else {
|
|
635
|
+
updateField(null, null, 'altText', { type: value as 'static' | 'metadata' })
|
|
636
|
+
}
|
|
637
|
+
}}
|
|
638
|
+
tooltip={
|
|
639
|
+
<Tooltip style={{ textTransform: 'none' }}>
|
|
640
|
+
<Tooltip.Target>
|
|
641
|
+
<Icon display='question' style={{ marginLeft: '0.5rem' }} />
|
|
642
|
+
</Tooltip.Target>
|
|
643
|
+
<Tooltip.Content>
|
|
644
|
+
<p>
|
|
645
|
+
Add a longer description for screen readers. The chart title is always auto-generated. Use
|
|
646
|
+
"Static" for manually written text, or "Data File Metadata" to pull it from a key in your data
|
|
647
|
+
file.
|
|
648
|
+
</p>
|
|
649
|
+
</Tooltip.Content>
|
|
650
|
+
</Tooltip>
|
|
651
|
+
}
|
|
652
|
+
/>
|
|
653
|
+
{descType === 'static' && (
|
|
654
|
+
<TextField
|
|
655
|
+
value={config.altText?.value || ''}
|
|
656
|
+
fieldName='altTextValue'
|
|
657
|
+
type='textarea'
|
|
658
|
+
label='Description Text'
|
|
659
|
+
placeholder='Longer interpretive description of chart insights...'
|
|
660
|
+
updateField={(_section, _subsection, _fieldName, value) => {
|
|
661
|
+
updateField(null, null, 'altText', { ...config.altText, value })
|
|
662
|
+
}}
|
|
663
|
+
/>
|
|
664
|
+
)}
|
|
665
|
+
{descType === 'metadata' && (
|
|
666
|
+
<>
|
|
667
|
+
{hasMetadata ? (
|
|
668
|
+
<Select
|
|
669
|
+
value={config.altText?.metadataKey || ''}
|
|
670
|
+
fieldName='altTextMetadataKey'
|
|
671
|
+
label='Description Metadata Field'
|
|
672
|
+
options={[
|
|
673
|
+
{ value: '', label: 'Select Metadata Field...' },
|
|
674
|
+
...metadataKeys.map(key => ({
|
|
675
|
+
value: key,
|
|
676
|
+
label: `${key}: ${config.dataMetadata[key]}`
|
|
677
|
+
}))
|
|
678
|
+
]}
|
|
679
|
+
updateField={(_section, _subsection, _fieldName, value) => {
|
|
680
|
+
updateField(null, null, 'altText', { ...config.altText, metadataKey: value })
|
|
681
|
+
}}
|
|
682
|
+
/>
|
|
683
|
+
) : (
|
|
684
|
+
<span className='subtext'>
|
|
685
|
+
No metadata fields are available. Your data file must be a JSON object with a <code>data</code>{' '}
|
|
686
|
+
array and sibling key-value pairs, for example:{' '}
|
|
687
|
+
<code>{`{ "altDescription": "...", "data": [...] }`}</code>
|
|
688
|
+
</span>
|
|
689
|
+
)}
|
|
690
|
+
</>
|
|
691
|
+
)}
|
|
692
|
+
{resolvedDescription && (
|
|
693
|
+
<div
|
|
694
|
+
style={{
|
|
695
|
+
marginTop: '1em',
|
|
696
|
+
padding: '0.75em',
|
|
697
|
+
background: '#f5f5f5',
|
|
698
|
+
borderRadius: '4px',
|
|
699
|
+
fontSize: '0.8em',
|
|
700
|
+
textTransform: 'none'
|
|
701
|
+
}}
|
|
702
|
+
>
|
|
703
|
+
<strong style={{ display: 'block', marginBottom: '0.25em' }}>Preview:</strong>
|
|
704
|
+
<p data-testid='alt-text-desc-preview' style={{ margin: 0, fontStyle: 'italic' }}>
|
|
705
|
+
{resolvedDescription}
|
|
706
|
+
</p>
|
|
707
|
+
</div>
|
|
708
|
+
)}
|
|
709
|
+
</>
|
|
710
|
+
)
|
|
711
|
+
})()}
|
|
620
712
|
</AccordionItemPanel>
|
|
621
713
|
</AccordionItem>
|
|
622
714
|
)
|
|
@@ -22,6 +22,7 @@ import { sanitizeToSvgId } from '@cdc/core/helpers/cove/string'
|
|
|
22
22
|
|
|
23
23
|
const PanelPatternSettings: FC<PanelProps> = props => {
|
|
24
24
|
const { config, updateConfig, transformedData } = useContext<ChartContext>(ConfigContext)
|
|
25
|
+
if (config.visualizationType === 'HeatMap') return null
|
|
25
26
|
|
|
26
27
|
type LegendPattern = {
|
|
27
28
|
label?: string
|
|
@@ -14,10 +14,13 @@ import { TextField, Select } from '@cdc/core/components/EditorPanel/Inputs'
|
|
|
14
14
|
import Tooltip from '@cdc/core/components/ui/Tooltip'
|
|
15
15
|
import Icon from '@cdc/core/components/ui/Icon'
|
|
16
16
|
import Button from '@cdc/core/components/elements/Button'
|
|
17
|
+
import { APP_FONT_COLOR } from '@cdc/core/helpers/constants'
|
|
17
18
|
import { type ChartContext } from '../../../../types/ChartContext.js'
|
|
18
19
|
import { type PanelProps } from '../PanelProps.js'
|
|
19
20
|
import ConfigContext from '../../../../ConfigContext.js'
|
|
20
21
|
|
|
22
|
+
const DEFAULT_REGION_BACKGROUND = getComputedStyle(document.documentElement).getPropertyValue('--cool-gray-50').trim()
|
|
23
|
+
|
|
21
24
|
const RegionSettings = memo(({ config, updateConfig }: { config: ChartConfig; updateConfig: Function }) => {
|
|
22
25
|
let regionUpdate = (fieldName, value, i) => {
|
|
23
26
|
let regions = []
|
|
@@ -116,12 +119,14 @@ const RegionSettings = memo(({ config, updateConfig }: { config: ChartConfig; up
|
|
|
116
119
|
value={color}
|
|
117
120
|
label='Text Color'
|
|
118
121
|
fieldName='color'
|
|
122
|
+
placeholder={APP_FONT_COLOR}
|
|
119
123
|
updateField={(section, subsection, fieldName, value) => regionUpdate(fieldName, value, i)}
|
|
120
124
|
/>
|
|
121
125
|
<TextField
|
|
122
126
|
value={background}
|
|
123
127
|
label='Background'
|
|
124
128
|
fieldName='background'
|
|
129
|
+
placeholder={DEFAULT_REGION_BACKGROUND}
|
|
125
130
|
updateField={(section, subsection, fieldName, value) => regionUpdate(fieldName, value, i)}
|
|
126
131
|
/>
|
|
127
132
|
</div>
|
|
@@ -660,7 +660,7 @@ const SeriesButtonRemove = props => {
|
|
|
660
660
|
|
|
661
661
|
return (
|
|
662
662
|
config.series &&
|
|
663
|
-
config.series.length > 1 && (
|
|
663
|
+
(config.series.length > 1 || config.visualizationType === 'HeatMap') && (
|
|
664
664
|
<Button variant='danger' size='sm' className='grouped-list__remove' onClick={e => handleRemoveSeries(e, series)}>
|
|
665
665
|
Remove
|
|
666
666
|
</Button>
|
|
@@ -710,7 +710,7 @@ const SeriesItem = props => {
|
|
|
710
710
|
}
|
|
711
711
|
>
|
|
712
712
|
<Icon display='move' size={15} style={{ cursor: 'default' }} />
|
|
713
|
-
{series.dataKey}
|
|
713
|
+
{config.visualizationType === 'HeatMap' ? series.name || series.dataKey : series.dataKey}
|
|
714
714
|
</AccordionItemButton>
|
|
715
715
|
</AccordionItemHeading>
|
|
716
716
|
{chartsWithOptions.includes(config.visualizationType) && (
|
|
@@ -306,7 +306,7 @@ const PanelVisual: FC<PanelProps> = props => {
|
|
|
306
306
|
/>
|
|
307
307
|
</>
|
|
308
308
|
)}
|
|
309
|
-
{(
|
|
309
|
+
{(visSupportsSequentialPallete() || visSupportsNonSequentialPallete()) && (
|
|
310
310
|
<>
|
|
311
311
|
<label>
|
|
312
312
|
<span className='edit-label'>Chart Color Palette</span>
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
import { type ChartConfig, type VisualizationType } from '../../../types/ChartConfig'
|
|
2
|
+
import { HEATMAP_CONFIG_DEFAULTS } from '../../HeatMap/heatmap.constants'
|
|
3
|
+
|
|
4
|
+
export const getVisualizationTypeConfigUpdate = (
|
|
5
|
+
config: ChartConfig,
|
|
6
|
+
visualizationType: VisualizationType
|
|
7
|
+
): ChartConfig | undefined => {
|
|
8
|
+
if (visualizationType === 'Forecasting' && config.xAxis.type === 'categorical') {
|
|
9
|
+
return {
|
|
10
|
+
...config,
|
|
11
|
+
visualizationType,
|
|
12
|
+
xAxis: {
|
|
13
|
+
...config.xAxis,
|
|
14
|
+
type: 'date',
|
|
15
|
+
dateParseFormat: config.xAxis.dateParseFormat || '%Y-%m-%d',
|
|
16
|
+
dateDisplayFormat: config.xAxis.dateDisplayFormat || '%Y-%m-%d'
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
if (visualizationType === 'HeatMap') {
|
|
22
|
+
return {
|
|
23
|
+
...config,
|
|
24
|
+
visualizationType,
|
|
25
|
+
series: [],
|
|
26
|
+
yAxis: {
|
|
27
|
+
...config.yAxis,
|
|
28
|
+
type: 'categorical',
|
|
29
|
+
titlePlacement: 'side'
|
|
30
|
+
},
|
|
31
|
+
heatmap: {
|
|
32
|
+
cellPadding: config.heatmap?.cellPadding ?? HEATMAP_CONFIG_DEFAULTS.cellPadding,
|
|
33
|
+
rowLabelGap: config.heatmap?.rowLabelGap ?? HEATMAP_CONFIG_DEFAULTS.rowLabelGap,
|
|
34
|
+
columnLabelGap: config.heatmap?.columnLabelGap ?? HEATMAP_CONFIG_DEFAULTS.columnLabelGap,
|
|
35
|
+
colorBucketCount: config.heatmap?.colorBucketCount ?? HEATMAP_CONFIG_DEFAULTS.colorBucketCount,
|
|
36
|
+
xAxisPosition: config.heatmap?.xAxisPosition ?? HEATMAP_CONFIG_DEFAULTS.xAxisPosition,
|
|
37
|
+
showCellValues: config.heatmap?.showCellValues ?? HEATMAP_CONFIG_DEFAULTS.showCellValues
|
|
38
|
+
},
|
|
39
|
+
legend: {
|
|
40
|
+
...config.legend,
|
|
41
|
+
position: 'top',
|
|
42
|
+
style: 'gradient',
|
|
43
|
+
subStyle: 'smooth'
|
|
44
|
+
},
|
|
45
|
+
general: {
|
|
46
|
+
...config.general,
|
|
47
|
+
palette: config.general?.palette || {
|
|
48
|
+
isReversed: false,
|
|
49
|
+
version: '2.0',
|
|
50
|
+
name: 'sequential_blue'
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
return undefined
|
|
57
|
+
}
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
import { describe, expect, it } from 'vitest'
|
|
2
|
+
import { type ChartConfig } from '../../../../types/ChartConfig'
|
|
3
|
+
import { HEATMAP_CONFIG_DEFAULTS } from '../../../HeatMap/heatmap.constants'
|
|
4
|
+
import { getVisualizationTypeConfigUpdate } from '../getVisualizationTypeConfigUpdate'
|
|
5
|
+
|
|
6
|
+
const buildConfig = (overrides: Partial<ChartConfig> = {}) =>
|
|
7
|
+
({
|
|
8
|
+
visualizationType: 'Bar',
|
|
9
|
+
xAxis: {
|
|
10
|
+
type: 'categorical'
|
|
11
|
+
},
|
|
12
|
+
yAxis: {
|
|
13
|
+
type: 'linear'
|
|
14
|
+
},
|
|
15
|
+
series: [{ dataKey: 'Value' }],
|
|
16
|
+
legend: {
|
|
17
|
+
position: 'bottom',
|
|
18
|
+
style: 'circles',
|
|
19
|
+
subStyle: 'linear blocks'
|
|
20
|
+
},
|
|
21
|
+
general: {},
|
|
22
|
+
...overrides
|
|
23
|
+
} as ChartConfig)
|
|
24
|
+
|
|
25
|
+
describe('getVisualizationTypeConfigUpdate', () => {
|
|
26
|
+
it('does not build a special config update for chart types without transition rules', () => {
|
|
27
|
+
expect(getVisualizationTypeConfigUpdate(buildConfig(), 'Line')).toBeUndefined()
|
|
28
|
+
})
|
|
29
|
+
|
|
30
|
+
it('normalizes categorical x-axis settings when switching to Forecasting', () => {
|
|
31
|
+
const updatedConfig = getVisualizationTypeConfigUpdate(buildConfig(), 'Forecasting')
|
|
32
|
+
|
|
33
|
+
expect(updatedConfig?.visualizationType).toBe('Forecasting')
|
|
34
|
+
expect(updatedConfig?.xAxis.type).toBe('date')
|
|
35
|
+
expect(updatedConfig?.xAxis.dateParseFormat).toBe('%Y-%m-%d')
|
|
36
|
+
expect(updatedConfig?.xAxis.dateDisplayFormat).toBe('%Y-%m-%d')
|
|
37
|
+
})
|
|
38
|
+
|
|
39
|
+
it('applies HeatMap defaults and required editor config when switching to HeatMap', () => {
|
|
40
|
+
const updatedConfig = getVisualizationTypeConfigUpdate(buildConfig(), 'HeatMap')
|
|
41
|
+
|
|
42
|
+
expect(updatedConfig?.visualizationType).toBe('HeatMap')
|
|
43
|
+
expect(updatedConfig?.series).toEqual([])
|
|
44
|
+
expect(updatedConfig?.yAxis.type).toBe('categorical')
|
|
45
|
+
expect(updatedConfig?.yAxis.titlePlacement).toBe('side')
|
|
46
|
+
expect(updatedConfig?.heatmap).toEqual(HEATMAP_CONFIG_DEFAULTS)
|
|
47
|
+
expect(updatedConfig?.legend.position).toBe('top')
|
|
48
|
+
expect(updatedConfig?.legend.style).toBe('gradient')
|
|
49
|
+
expect(updatedConfig?.legend.subStyle).toBe('smooth')
|
|
50
|
+
expect(updatedConfig?.general.palette).toEqual({
|
|
51
|
+
isReversed: false,
|
|
52
|
+
version: '2.0',
|
|
53
|
+
name: 'sequential_blue'
|
|
54
|
+
})
|
|
55
|
+
})
|
|
56
|
+
})
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
import React from 'react'
|
|
2
|
+
import { renderHook } from '@testing-library/react'
|
|
3
|
+
import { describe, expect, it } from 'vitest'
|
|
4
|
+
import ConfigContext from '../../ConfigContext'
|
|
5
|
+
import { useEditorPermissions } from './useEditorPermissions'
|
|
6
|
+
import { createMockChartContext, createMockConfig } from '../LinearChart/tests/mockConfigContext'
|
|
7
|
+
import { ChartConfig } from '../../types/ChartConfig'
|
|
8
|
+
|
|
9
|
+
const renderUseEditorPermissions = (configOverrides: Partial<ChartConfig>) => {
|
|
10
|
+
const config = createMockConfig(configOverrides)
|
|
11
|
+
const context = createMockChartContext(config)
|
|
12
|
+
const wrapper = ({ children }: React.PropsWithChildren) => (
|
|
13
|
+
<ConfigContext.Provider value={context}>{children}</ConfigContext.Provider>
|
|
14
|
+
)
|
|
15
|
+
|
|
16
|
+
return renderHook(() => useEditorPermissions(), { wrapper })
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
describe('useEditorPermissions', () => {
|
|
20
|
+
it('shows manual y-axis padding controls when auto-padding mode is none', () => {
|
|
21
|
+
const { result } = renderUseEditorPermissions({
|
|
22
|
+
yAxis: {
|
|
23
|
+
...createMockConfig().yAxis,
|
|
24
|
+
titlePlacement: 'side',
|
|
25
|
+
inlineLabel: 'Rate'
|
|
26
|
+
}
|
|
27
|
+
})
|
|
28
|
+
|
|
29
|
+
expect(result.current.visSupportsYPadding()).toBe(true)
|
|
30
|
+
})
|
|
31
|
+
|
|
32
|
+
it('hides manual y-axis padding controls for inline-label auto-padding', () => {
|
|
33
|
+
const { result } = renderUseEditorPermissions({
|
|
34
|
+
yAxis: {
|
|
35
|
+
...createMockConfig().yAxis,
|
|
36
|
+
titlePlacement: 'side',
|
|
37
|
+
inlineLabel: 'per 100k'
|
|
38
|
+
}
|
|
39
|
+
})
|
|
40
|
+
|
|
41
|
+
expect(result.current.visSupportsYPadding()).toBe(false)
|
|
42
|
+
})
|
|
43
|
+
|
|
44
|
+
it('hides manual y-axis padding controls for top-title auto-padding', () => {
|
|
45
|
+
const { result } = renderUseEditorPermissions({
|
|
46
|
+
yAxis: {
|
|
47
|
+
...createMockConfig().yAxis,
|
|
48
|
+
titlePlacement: 'top',
|
|
49
|
+
label: 'Y-Axis',
|
|
50
|
+
inlineLabel: ''
|
|
51
|
+
}
|
|
52
|
+
})
|
|
53
|
+
|
|
54
|
+
expect(result.current.visSupportsYPadding()).toBe(false)
|
|
55
|
+
})
|
|
56
|
+
|
|
57
|
+
it('shows manual y-axis padding controls when a top y-axis title is hidden', () => {
|
|
58
|
+
const { result } = renderUseEditorPermissions({
|
|
59
|
+
hideYAxisLabel: true,
|
|
60
|
+
yAxis: {
|
|
61
|
+
...createMockConfig().yAxis,
|
|
62
|
+
titlePlacement: 'top',
|
|
63
|
+
label: 'Y-Axis',
|
|
64
|
+
inlineLabel: ''
|
|
65
|
+
}
|
|
66
|
+
})
|
|
67
|
+
|
|
68
|
+
expect(result.current.visSupportsYPadding()).toBe(true)
|
|
69
|
+
})
|
|
70
|
+
})
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import React, { useContext } from 'react'
|
|
2
2
|
import ConfigContext from '../../ConfigContext'
|
|
3
|
+
import { getYAxisAutoPaddingMode } from '../../helpers/getYAxisAutoPaddingMode'
|
|
3
4
|
|
|
4
5
|
export const useEditorPermissions = () => {
|
|
5
6
|
const { config } = useContext(ConfigContext)
|
|
@@ -16,6 +17,7 @@ export const useEditorPermissions = () => {
|
|
|
16
17
|
'Deviation Bar',
|
|
17
18
|
'Forecasting',
|
|
18
19
|
// 'Forest Plot',
|
|
20
|
+
'HeatMap',
|
|
19
21
|
'Horizon Chart',
|
|
20
22
|
'Line',
|
|
21
23
|
'Paired Bar',
|
|
@@ -58,7 +60,7 @@ export const useEditorPermissions = () => {
|
|
|
58
60
|
}
|
|
59
61
|
|
|
60
62
|
const visSupportsClickingLegend = () => {
|
|
61
|
-
const disabledCharts = ['Horizon Chart']
|
|
63
|
+
const disabledCharts = ['HeatMap', 'Horizon Chart']
|
|
62
64
|
if (disabledCharts.includes(visualizationType)) return false
|
|
63
65
|
return true
|
|
64
66
|
}
|
|
@@ -75,6 +77,7 @@ export const useEditorPermissions = () => {
|
|
|
75
77
|
'Box Plot',
|
|
76
78
|
'Bump Chart',
|
|
77
79
|
'Forest Plot',
|
|
80
|
+
'HeatMap',
|
|
78
81
|
'Horizon Chart',
|
|
79
82
|
'Pie',
|
|
80
83
|
'Radar',
|
|
@@ -93,6 +96,7 @@ export const useEditorPermissions = () => {
|
|
|
93
96
|
'Box Plot',
|
|
94
97
|
'Bump Chart',
|
|
95
98
|
'Forest Plot',
|
|
99
|
+
'HeatMap',
|
|
96
100
|
'Radar',
|
|
97
101
|
'Sankey',
|
|
98
102
|
'Scatter Plot',
|
|
@@ -109,6 +113,8 @@ export const useEditorPermissions = () => {
|
|
|
109
113
|
return true
|
|
110
114
|
case 'Forest Plot':
|
|
111
115
|
return false
|
|
116
|
+
case 'HeatMap':
|
|
117
|
+
return true
|
|
112
118
|
case 'Spark Line':
|
|
113
119
|
return false
|
|
114
120
|
case 'Sankey':
|
|
@@ -197,9 +203,13 @@ export const useEditorPermissions = () => {
|
|
|
197
203
|
}
|
|
198
204
|
}
|
|
199
205
|
|
|
200
|
-
const visHasSelectableLegendValues = ![
|
|
201
|
-
|
|
202
|
-
|
|
206
|
+
const visHasSelectableLegendValues = ![
|
|
207
|
+
'Box Plot',
|
|
208
|
+
'Forest Plot',
|
|
209
|
+
'HeatMap',
|
|
210
|
+
'Spark Line',
|
|
211
|
+
'Warming Stripes'
|
|
212
|
+
].includes(visualizationType)
|
|
203
213
|
const visHasLegendAxisAlign = () => {
|
|
204
214
|
return visualizationType === 'Bar' && visualizationSubType === 'stacked' && config.legend.behavior === 'isolate'
|
|
205
215
|
}
|
|
@@ -208,7 +218,7 @@ export const useEditorPermissions = () => {
|
|
|
208
218
|
}
|
|
209
219
|
|
|
210
220
|
const visSupportsTooltipOpacity = () => {
|
|
211
|
-
const disabledCharts = ['Spark Line', 'Sankey', 'Warming Stripes']
|
|
221
|
+
const disabledCharts = ['HeatMap', 'Spark Line', 'Sankey', 'Warming Stripes']
|
|
212
222
|
if (disabledCharts.includes(visualizationType)) return false
|
|
213
223
|
return true
|
|
214
224
|
}
|
|
@@ -226,7 +236,7 @@ export const useEditorPermissions = () => {
|
|
|
226
236
|
}
|
|
227
237
|
|
|
228
238
|
const visSupportsNonSequentialPallete = () => {
|
|
229
|
-
const disabledCharts = ['Paired Bar', 'Deviation Bar', 'Forest Plot', 'Forecasting', 'Sankey']
|
|
239
|
+
const disabledCharts = ['HeatMap', 'Paired Bar', 'Deviation Bar', 'Forest Plot', 'Forecasting', 'Sankey']
|
|
230
240
|
if (disabledCharts.includes(visualizationType)) return false
|
|
231
241
|
return true
|
|
232
242
|
}
|
|
@@ -238,19 +248,19 @@ export const useEditorPermissions = () => {
|
|
|
238
248
|
}
|
|
239
249
|
|
|
240
250
|
const visSupportsDateCategoryAxisLabel = () => {
|
|
241
|
-
const disabledCharts = ['Forest Plot', 'Spark Line', 'Bump Chart', 'Warming Stripes']
|
|
251
|
+
const disabledCharts = ['Forest Plot', 'HeatMap', 'Spark Line', 'Bump Chart', 'Warming Stripes']
|
|
242
252
|
if (disabledCharts.includes(visualizationType)) return false
|
|
243
253
|
return true
|
|
244
254
|
}
|
|
245
255
|
|
|
246
256
|
const visSupportsDateCategoryAxisLine = () => {
|
|
247
|
-
const disabledCharts = ['Forest Plot', 'Spark Line', 'Warming Stripes']
|
|
257
|
+
const disabledCharts = ['Forest Plot', 'HeatMap', 'Spark Line', 'Warming Stripes']
|
|
248
258
|
if (disabledCharts.includes(visualizationType)) return false
|
|
249
259
|
return true
|
|
250
260
|
}
|
|
251
261
|
|
|
252
262
|
const visSupportsDateCategoryAxisTicks = () => {
|
|
253
|
-
const disabledCharts = ['Forest Plot', 'Spark Line', 'Warming Stripes']
|
|
263
|
+
const disabledCharts = ['Forest Plot', 'HeatMap', 'Spark Line', 'Warming Stripes']
|
|
254
264
|
if (disabledCharts.includes(visualizationType)) return false
|
|
255
265
|
return true
|
|
256
266
|
}
|
|
@@ -262,31 +272,41 @@ export const useEditorPermissions = () => {
|
|
|
262
272
|
}
|
|
263
273
|
|
|
264
274
|
const visSupportsDateCategoryNumTicks = () => {
|
|
265
|
-
const disabledCharts = ['Spark Line']
|
|
275
|
+
const disabledCharts = ['HeatMap', 'Spark Line']
|
|
266
276
|
if (disabledCharts.includes(visualizationType)) return false
|
|
267
277
|
return true
|
|
268
278
|
}
|
|
269
279
|
|
|
270
280
|
const visSupportsResponsiveTicks = () => {
|
|
271
|
-
const disabledCharts = ['Spark Line']
|
|
281
|
+
const disabledCharts = ['HeatMap', 'Spark Line']
|
|
272
282
|
if (disabledCharts.includes(visualizationType)) return false
|
|
273
283
|
return true
|
|
274
284
|
}
|
|
275
285
|
|
|
276
286
|
const visSupportsRegions = () => {
|
|
277
|
-
const disabledCharts = [
|
|
287
|
+
const disabledCharts = [
|
|
288
|
+
'Forest Plot',
|
|
289
|
+
'Horizon Chart',
|
|
290
|
+
'HeatMap',
|
|
291
|
+
'Pie',
|
|
292
|
+
'Paired Bar',
|
|
293
|
+
'Radar',
|
|
294
|
+
'Spark Line',
|
|
295
|
+
'Sankey',
|
|
296
|
+
'Warming Stripes'
|
|
297
|
+
]
|
|
278
298
|
if (disabledCharts.includes(visualizationType)) return false
|
|
279
299
|
return true
|
|
280
300
|
}
|
|
281
301
|
|
|
282
302
|
const visSupportsValueAxisMax = () => {
|
|
283
|
-
const disabledCharts = ['Forest Plot']
|
|
303
|
+
const disabledCharts = ['Forest Plot', 'HeatMap']
|
|
284
304
|
if (disabledCharts.includes(visualizationType)) return false
|
|
285
305
|
return true
|
|
286
306
|
}
|
|
287
307
|
|
|
288
308
|
const visSupportsValueAxisMin = () => {
|
|
289
|
-
const disabledCharts = ['Forest Plot']
|
|
309
|
+
const disabledCharts = ['Forest Plot', 'HeatMap']
|
|
290
310
|
if (disabledCharts.includes(visualizationType)) return false
|
|
291
311
|
return true
|
|
292
312
|
}
|
|
@@ -298,14 +318,14 @@ export const useEditorPermissions = () => {
|
|
|
298
318
|
}
|
|
299
319
|
|
|
300
320
|
const visSupportsValueAxisGridLines = () => {
|
|
301
|
-
const disabledCharts = ['Forest Plot']
|
|
321
|
+
const disabledCharts = ['Forest Plot', 'HeatMap']
|
|
302
322
|
if (disabledCharts.includes(visualizationType)) return false
|
|
303
323
|
return true
|
|
304
324
|
}
|
|
305
325
|
|
|
306
326
|
// implement later
|
|
307
327
|
const visSupportsValueAxisTicks = () => {
|
|
308
|
-
const disabledCharts = ['Forest Plot']
|
|
328
|
+
const disabledCharts = ['Forest Plot', 'HeatMap']
|
|
309
329
|
if (disabledCharts.includes(visualizationType)) return false
|
|
310
330
|
return true
|
|
311
331
|
}
|
|
@@ -319,7 +339,7 @@ export const useEditorPermissions = () => {
|
|
|
319
339
|
|
|
320
340
|
// implement later
|
|
321
341
|
const visSupportsValueAxisLabels = () => {
|
|
322
|
-
const disabledCharts = ['Forest Plot', 'Bump Chart']
|
|
342
|
+
const disabledCharts = ['Forest Plot', 'HeatMap', 'Bump Chart']
|
|
323
343
|
if (disabledCharts.includes(visualizationType)) return false
|
|
324
344
|
return true
|
|
325
345
|
}
|
|
@@ -332,7 +352,7 @@ export const useEditorPermissions = () => {
|
|
|
332
352
|
}
|
|
333
353
|
|
|
334
354
|
const visSupportsBarThickness = () => {
|
|
335
|
-
const disabledCharts = ['Forest Plot', 'Warming Stripes']
|
|
355
|
+
const disabledCharts = ['Forest Plot', 'HeatMap', 'Warming Stripes']
|
|
336
356
|
if (disabledCharts.includes(visualizationType)) return false
|
|
337
357
|
return true
|
|
338
358
|
}
|
|
@@ -344,7 +364,7 @@ export const useEditorPermissions = () => {
|
|
|
344
364
|
}
|
|
345
365
|
const visSupportsMobileChartHeight = () => {
|
|
346
366
|
// TODO: this is a soft release. Support should eventually match visSupportsChartHeight
|
|
347
|
-
const enabledCharts = ['Bar', 'Line', 'Combo', 'Area Chart', 'Radar']
|
|
367
|
+
const enabledCharts = ['Bar', 'Line', 'Combo', 'Area Chart', 'HeatMap', 'Radar']
|
|
348
368
|
if (enabledCharts.includes(visualizationType)) return true
|
|
349
369
|
return false
|
|
350
370
|
}
|
|
@@ -356,13 +376,13 @@ export const useEditorPermissions = () => {
|
|
|
356
376
|
}
|
|
357
377
|
|
|
358
378
|
const visSupportsRankByValue = () => {
|
|
359
|
-
const disabledCharts = ['Spark Line']
|
|
379
|
+
const disabledCharts = ['HeatMap', 'Spark Line']
|
|
360
380
|
if (disabledCharts.includes(visualizationType)) return false
|
|
361
381
|
return true
|
|
362
382
|
}
|
|
363
383
|
|
|
364
384
|
const visSupportsDateCategoryHeight = () => {
|
|
365
|
-
const disabledCharts = ['Spark Line', 'Sankey', 'Bump Chart']
|
|
385
|
+
const disabledCharts = ['HeatMap', 'Spark Line', 'Sankey', 'Bump Chart']
|
|
366
386
|
if (disabledCharts.includes(visualizationType)) return false
|
|
367
387
|
if (config.orientation !== 'horizontal') return false
|
|
368
388
|
if (config.orientation === 'horizontal' && visualizationType === 'Bar' && !config.isLollipopChart) return false
|
|
@@ -407,7 +427,7 @@ export const useEditorPermissions = () => {
|
|
|
407
427
|
}
|
|
408
428
|
|
|
409
429
|
const visSupportsYPadding = () => {
|
|
410
|
-
return
|
|
430
|
+
return getYAxisAutoPaddingMode(config) === 'none'
|
|
411
431
|
}
|
|
412
432
|
|
|
413
433
|
const visHasSingleSeriesTooltip = () => {
|