@cdc/chart 4.26.5 → 4.26.6
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CONFIG.md +12 -8
- package/dist/{cdcchart-DQ00cQCm.es.js → cdcchart-BWVIUcAF.es.js} +5 -55
- package/dist/cdcchart.js +34093 -32767
- package/examples/feature/heatmap/average-age-categorical.json +1 -1
- package/examples/feature/heatmap/calendar-heatmap.json +1 -1
- package/examples/feature/heatmap/sparse-aggregation.json +1 -1
- package/examples/private/bad-y-axis.json +543 -0
- package/examples/private/bar-chart-negative-value.json +455 -0
- package/package.json +3 -3
- package/src/CdcChart.tsx +5 -4
- package/src/CdcChartComponent.tsx +32 -9
- package/src/_stories/Chart.SeriesColorAssignments.stories.tsx +183 -0
- package/src/_stories/Chart.YAxisAutoMaxStrategy.stories.tsx +52 -0
- package/src/_stories/Chart.YAxisFilterDomain.stories.tsx +90 -0
- package/src/_stories/ChartAnnotation.smoke.stories.tsx +10 -0
- package/src/_stories/_mock/annotation_event-line_mock.json +1064 -0
- package/src/_stories/_mock/heatmap-average-age-categorical.json +1 -1
- package/src/_stories/_mock/heatmap.json +1 -1
- package/src/components/Annotations/components/AnnotationDraggable.styles.css +22 -1
- package/src/components/Annotations/components/AnnotationDraggable.tsx +219 -119
- package/src/components/Annotations/tests/AnnotationDraggable.test.tsx +198 -0
- package/src/components/Axis/LeftAxis.tsx +3 -0
- package/src/components/Axis/LeftAxisGridlines.tsx +8 -1
- package/src/components/Axis/RightAxis.tsx +3 -0
- package/src/components/Axis/tests/AxisTickValues.test.tsx +86 -0
- package/src/components/EditorPanel/EditorPanel.tsx +367 -136
- package/src/components/EditorPanel/components/Panels/Panel.Annotate.tsx +101 -52
- package/src/components/EditorPanel/components/Panels/Panel.SmallMultiples.test.tsx +98 -0
- package/src/components/EditorPanel/components/Panels/Panel.SmallMultiples.tsx +10 -0
- package/src/components/EditorPanel/components/Panels/Panel.Visual.test.tsx +484 -0
- package/src/components/EditorPanel/components/Panels/Panel.Visual.tsx +3 -0
- package/src/components/EditorPanel/components/Panels/SeriesColorAssignments.tsx +262 -0
- package/src/components/EditorPanel/components/Panels/panelVisual.styles.css +32 -0
- package/src/components/EditorPanel/components/panels.scss +10 -0
- package/src/components/EditorPanel/editor-panel.scss +47 -0
- package/src/components/EditorPanel/helpers/getVisualizationTypeConfigUpdate.ts +6 -2
- package/src/components/EditorPanel/helpers/tests/getVisualizationTypeConfigUpdate.test.ts +20 -2
- package/src/components/EditorPanel/useEditorPermissions.test.tsx +256 -11
- package/src/components/EditorPanel/useEditorPermissions.ts +41 -5
- package/src/components/HeatMap/components/HeatMap.tsx +22 -17
- package/src/components/HeatMap/components/HeatMapGradientLegend.tsx +29 -15
- package/src/components/HeatMap/heatmap.css +15 -1
- package/src/components/HeatMap/tests/HeatMap.test.tsx +39 -7
- package/src/components/Legend/Legend.Component.tsx +2 -2
- package/src/components/Legend/helpers/createFormatLabels.test.ts +88 -0
- package/src/components/Legend/helpers/createFormatLabels.tsx +6 -1
- package/src/components/LineChart/helpers.test.ts +230 -1
- package/src/components/LineChart/helpers.ts +26 -13
- package/src/components/LineChart/index.tsx +21 -4
- package/src/components/LinearChart/tests/LinearChart.test.tsx +30 -0
- package/src/components/LinearChart.tsx +50 -15
- package/src/components/ScatterPlot/ScatterPlot.jsx +3 -3
- package/src/components/SmallMultiples/SmallMultipleTile.tsx +3 -0
- package/src/components/SmallMultiples/SmallMultiples.tsx +38 -9
- package/src/components/SmallMultiples/tests/SmallMultiples.test.tsx +170 -0
- package/src/data/initial-state.js +1 -0
- package/src/helpers/calculateLeftYAxisWidth.ts +9 -3
- package/src/helpers/colorAssignmentHelpers.ts +102 -0
- package/src/helpers/dataHelpers.ts +11 -1
- package/src/helpers/getAxisMaxOverride.ts +16 -0
- package/src/helpers/getCleanTopTickMax.ts +64 -0
- package/src/helpers/getColorScale.ts +18 -9
- package/src/helpers/getHasBoundarySuppression.ts +26 -0
- package/src/helpers/getMinMax.ts +24 -22
- package/src/helpers/getYAxisDomainData.ts +38 -0
- package/src/helpers/getYAxisFinalizationEligibility.ts +40 -0
- package/src/helpers/hasSpacedInlineLabel.ts +10 -0
- package/src/helpers/seriesColumnSettings.ts +5 -4
- package/src/helpers/smallMultiplesHelpers.ts +5 -0
- package/src/helpers/tests/calculateLeftYAxisWidth.test.ts +38 -0
- package/src/helpers/tests/dataHelpers.test.ts +135 -0
- package/src/helpers/tests/getAxisMaxOverride.test.ts +36 -0
- package/src/helpers/tests/getCleanTopTickMax.test.ts +165 -0
- package/src/helpers/tests/getColorScale.test.ts +197 -0
- package/src/helpers/tests/getMinMax.test.ts +211 -0
- package/src/helpers/tests/getYAxisDomainData.test.ts +119 -0
- package/src/helpers/tests/getYAxisFinalizationEligibility.test.ts +185 -0
- package/src/helpers/tests/{getYAxisAutoPaddingMode.test.ts → hasSpacedInlineLabel.test.ts} +17 -32
- package/src/helpers/tests/seriesColumnSettings.test.ts +16 -2
- package/src/helpers/tests/smallMultiplesHelpers.test.ts +99 -0
- package/src/hooks/tests/useRightAxis.test.tsx +127 -0
- package/src/hooks/tests/useScales.test.tsx +307 -29
- package/src/hooks/useRightAxis.ts +30 -13
- package/src/hooks/useScales.ts +45 -28
- package/src/hooks/useTooltip.tsx +3 -1
- package/src/test/CdcChart.dataTable.test.tsx +122 -0
- package/src/types/ChartConfig.ts +6 -1
- package/src/types/ChartContext.ts +3 -0
- package/src/helpers/getYAxisAutoPadding.ts +0 -55
- package/src/helpers/getYAxisAutoPaddingMode.ts +0 -17
- package/src/helpers/tests/getYAxisAutoPadding.test.ts +0 -37
|
@@ -6,9 +6,12 @@ import { useEditorPermissions } from './useEditorPermissions'
|
|
|
6
6
|
import { createMockChartContext, createMockConfig } from '../LinearChart/tests/mockConfigContext'
|
|
7
7
|
import { ChartConfig } from '../../types/ChartConfig'
|
|
8
8
|
|
|
9
|
-
const renderUseEditorPermissions = (
|
|
9
|
+
const renderUseEditorPermissions = (
|
|
10
|
+
configOverrides: Partial<ChartConfig>,
|
|
11
|
+
contextOverrides: Partial<ReturnType<typeof createMockChartContext>> = {}
|
|
12
|
+
) => {
|
|
10
13
|
const config = createMockConfig(configOverrides)
|
|
11
|
-
const context = createMockChartContext(config)
|
|
14
|
+
const context = createMockChartContext(config, contextOverrides)
|
|
12
15
|
const wrapper = ({ children }: React.PropsWithChildren) => (
|
|
13
16
|
<ConfigContext.Provider value={context}>{children}</ConfigContext.Provider>
|
|
14
17
|
)
|
|
@@ -17,7 +20,7 @@ const renderUseEditorPermissions = (configOverrides: Partial<ChartConfig>) => {
|
|
|
17
20
|
}
|
|
18
21
|
|
|
19
22
|
describe('useEditorPermissions', () => {
|
|
20
|
-
it('shows manual y-axis padding controls when
|
|
23
|
+
it('shows manual y-axis padding controls when there is no spaced inline label', () => {
|
|
21
24
|
const { result } = renderUseEditorPermissions({
|
|
22
25
|
yAxis: {
|
|
23
26
|
...createMockConfig().yAxis,
|
|
@@ -29,7 +32,7 @@ describe('useEditorPermissions', () => {
|
|
|
29
32
|
expect(result.current.visSupportsYPadding()).toBe(true)
|
|
30
33
|
})
|
|
31
34
|
|
|
32
|
-
it('hides manual y-axis padding controls for inline
|
|
35
|
+
it('hides manual y-axis padding controls for spaced inline labels when renderer headroom applies', () => {
|
|
33
36
|
const { result } = renderUseEditorPermissions({
|
|
34
37
|
yAxis: {
|
|
35
38
|
...createMockConfig().yAxis,
|
|
@@ -41,22 +44,34 @@ describe('useEditorPermissions', () => {
|
|
|
41
44
|
expect(result.current.visSupportsYPadding()).toBe(false)
|
|
42
45
|
})
|
|
43
46
|
|
|
44
|
-
it('
|
|
47
|
+
it('shows manual y-axis padding controls for horizontal charts with spaced inline labels', () => {
|
|
45
48
|
const { result } = renderUseEditorPermissions({
|
|
49
|
+
orientation: 'horizontal',
|
|
46
50
|
yAxis: {
|
|
47
51
|
...createMockConfig().yAxis,
|
|
48
|
-
titlePlacement: '
|
|
49
|
-
|
|
50
|
-
inlineLabel: ''
|
|
52
|
+
titlePlacement: 'side',
|
|
53
|
+
inlineLabel: 'per 100k'
|
|
51
54
|
}
|
|
52
55
|
})
|
|
53
56
|
|
|
54
|
-
expect(result.current.visSupportsYPadding()).toBe(
|
|
57
|
+
expect(result.current.visSupportsYPadding()).toBe(true)
|
|
58
|
+
})
|
|
59
|
+
|
|
60
|
+
it('shows manual y-axis padding controls for Combo charts with spaced inline labels', () => {
|
|
61
|
+
const { result } = renderUseEditorPermissions({
|
|
62
|
+
visualizationType: 'Combo',
|
|
63
|
+
yAxis: {
|
|
64
|
+
...createMockConfig().yAxis,
|
|
65
|
+
titlePlacement: 'side',
|
|
66
|
+
inlineLabel: 'per 100k'
|
|
67
|
+
}
|
|
68
|
+
})
|
|
69
|
+
|
|
70
|
+
expect(result.current.visSupportsYPadding()).toBe(true)
|
|
55
71
|
})
|
|
56
72
|
|
|
57
|
-
it('shows manual y-axis padding controls
|
|
73
|
+
it('shows manual y-axis padding controls for top y-axis titles', () => {
|
|
58
74
|
const { result } = renderUseEditorPermissions({
|
|
59
|
-
hideYAxisLabel: true,
|
|
60
75
|
yAxis: {
|
|
61
76
|
...createMockConfig().yAxis,
|
|
62
77
|
titlePlacement: 'top',
|
|
@@ -67,4 +82,234 @@ describe('useEditorPermissions', () => {
|
|
|
67
82
|
|
|
68
83
|
expect(result.current.visSupportsYPadding()).toBe(true)
|
|
69
84
|
})
|
|
85
|
+
|
|
86
|
+
it('supports series color assignments only for eligible authored-series charts', () => {
|
|
87
|
+
const supported = renderUseEditorPermissions({
|
|
88
|
+
visualizationType: 'Line',
|
|
89
|
+
series: [{ dataKey: 'cases', type: 'Line' }] as any
|
|
90
|
+
})
|
|
91
|
+
|
|
92
|
+
expect(supported.result.current.visSupportsSeriesColorAssignments()).toBe(true)
|
|
93
|
+
|
|
94
|
+
const unsupported = renderUseEditorPermissions({
|
|
95
|
+
visualizationType: 'Pie',
|
|
96
|
+
series: [{ dataKey: 'cases', type: 'Pie' }] as any
|
|
97
|
+
})
|
|
98
|
+
|
|
99
|
+
expect(unsupported.result.current.visSupportsSeriesColorAssignments()).toBe(false)
|
|
100
|
+
})
|
|
101
|
+
|
|
102
|
+
it('shows filter-domain behavior when a standalone chart has visible filters and automatic max', () => {
|
|
103
|
+
const { result } = renderUseEditorPermissions({
|
|
104
|
+
filters: [{ columnName: 'Region', showDropdown: true } as any],
|
|
105
|
+
yAxis: {
|
|
106
|
+
...createMockConfig().yAxis,
|
|
107
|
+
max: '',
|
|
108
|
+
type: 'linear'
|
|
109
|
+
}
|
|
110
|
+
})
|
|
111
|
+
|
|
112
|
+
expect(result.current.visSupportsFilterDomainBehavior()).toBe(true)
|
|
113
|
+
})
|
|
114
|
+
|
|
115
|
+
it('shows filter-domain behavior for horizontal bar charts when automatic max can respond to filters', () => {
|
|
116
|
+
const { result } = renderUseEditorPermissions({
|
|
117
|
+
visualizationType: 'Bar',
|
|
118
|
+
orientation: 'horizontal',
|
|
119
|
+
filters: [{ columnName: 'Region', showDropdown: true } as any],
|
|
120
|
+
yAxis: {
|
|
121
|
+
...createMockConfig().yAxis,
|
|
122
|
+
max: '',
|
|
123
|
+
type: 'linear'
|
|
124
|
+
}
|
|
125
|
+
})
|
|
126
|
+
|
|
127
|
+
expect(result.current.visSupportsFilterDomainBehavior()).toBe(true)
|
|
128
|
+
})
|
|
129
|
+
|
|
130
|
+
it('shows filter-domain behavior for dashboard charts without local filters', () => {
|
|
131
|
+
const { result } = renderUseEditorPermissions(
|
|
132
|
+
{
|
|
133
|
+
filters: [],
|
|
134
|
+
yAxis: {
|
|
135
|
+
...createMockConfig().yAxis,
|
|
136
|
+
max: '',
|
|
137
|
+
type: 'linear'
|
|
138
|
+
}
|
|
139
|
+
},
|
|
140
|
+
{ isDashboard: true }
|
|
141
|
+
)
|
|
142
|
+
|
|
143
|
+
expect(result.current.visSupportsFilterDomainBehavior()).toBe(true)
|
|
144
|
+
})
|
|
145
|
+
|
|
146
|
+
it('hides filter-domain behavior when vertical value-axis max is explicit', () => {
|
|
147
|
+
const { result } = renderUseEditorPermissions(
|
|
148
|
+
{
|
|
149
|
+
filters: [{ columnName: 'Region', showDropdown: true } as any],
|
|
150
|
+
yAxis: {
|
|
151
|
+
...createMockConfig().yAxis,
|
|
152
|
+
max: '100',
|
|
153
|
+
type: 'linear'
|
|
154
|
+
}
|
|
155
|
+
},
|
|
156
|
+
{ isDashboard: true }
|
|
157
|
+
)
|
|
158
|
+
|
|
159
|
+
expect(result.current.visSupportsFilterDomainBehavior()).toBe(false)
|
|
160
|
+
})
|
|
161
|
+
|
|
162
|
+
it('hides filter-domain behavior for horizontal charts when value-axis max is explicit', () => {
|
|
163
|
+
const { result } = renderUseEditorPermissions(
|
|
164
|
+
{
|
|
165
|
+
visualizationType: 'Bar',
|
|
166
|
+
orientation: 'horizontal',
|
|
167
|
+
filters: [{ columnName: 'Region', showDropdown: true } as any],
|
|
168
|
+
xAxis: {
|
|
169
|
+
...createMockConfig().xAxis,
|
|
170
|
+
max: '100'
|
|
171
|
+
},
|
|
172
|
+
yAxis: {
|
|
173
|
+
...createMockConfig().yAxis,
|
|
174
|
+
max: '',
|
|
175
|
+
type: 'linear'
|
|
176
|
+
}
|
|
177
|
+
},
|
|
178
|
+
{ isDashboard: true }
|
|
179
|
+
)
|
|
180
|
+
|
|
181
|
+
expect(result.current.visSupportsFilterDomainBehavior()).toBe(false)
|
|
182
|
+
})
|
|
183
|
+
|
|
184
|
+
it('hides filter-domain behavior for unfiltered standalone charts', () => {
|
|
185
|
+
const { result } = renderUseEditorPermissions({
|
|
186
|
+
filters: [],
|
|
187
|
+
yAxis: {
|
|
188
|
+
...createMockConfig().yAxis,
|
|
189
|
+
max: '',
|
|
190
|
+
type: 'linear'
|
|
191
|
+
}
|
|
192
|
+
})
|
|
193
|
+
|
|
194
|
+
expect(result.current.visSupportsFilterDomainBehavior()).toBe(false)
|
|
195
|
+
})
|
|
196
|
+
|
|
197
|
+
it('hides filter-domain behavior for charts outside the value-axis allowlist', () => {
|
|
198
|
+
const { result } = renderUseEditorPermissions(
|
|
199
|
+
{
|
|
200
|
+
visualizationType: 'Bump Chart',
|
|
201
|
+
filters: [{ columnName: 'Region', showDropdown: true } as any],
|
|
202
|
+
yAxis: {
|
|
203
|
+
...createMockConfig().yAxis,
|
|
204
|
+
max: '',
|
|
205
|
+
type: 'linear'
|
|
206
|
+
}
|
|
207
|
+
},
|
|
208
|
+
{ isDashboard: true }
|
|
209
|
+
)
|
|
210
|
+
|
|
211
|
+
expect(result.current.visSupportsFilterDomainBehavior()).toBe(false)
|
|
212
|
+
})
|
|
213
|
+
|
|
214
|
+
it('hides filter-domain behavior for chart types with specialized value scales', () => {
|
|
215
|
+
const { result } = renderUseEditorPermissions(
|
|
216
|
+
{
|
|
217
|
+
visualizationType: 'Paired Bar',
|
|
218
|
+
filters: [{ columnName: 'Region', showDropdown: true } as any],
|
|
219
|
+
yAxis: {
|
|
220
|
+
...createMockConfig().yAxis,
|
|
221
|
+
max: '',
|
|
222
|
+
type: 'linear'
|
|
223
|
+
}
|
|
224
|
+
},
|
|
225
|
+
{ isDashboard: true }
|
|
226
|
+
)
|
|
227
|
+
|
|
228
|
+
expect(result.current.visSupportsFilterDomainBehavior()).toBe(false)
|
|
229
|
+
})
|
|
230
|
+
|
|
231
|
+
it('shows automatic max strategy for automatic numeric value axes', () => {
|
|
232
|
+
const { result } = renderUseEditorPermissions({
|
|
233
|
+
yAxis: {
|
|
234
|
+
...createMockConfig().yAxis,
|
|
235
|
+
max: '',
|
|
236
|
+
type: 'linear'
|
|
237
|
+
}
|
|
238
|
+
})
|
|
239
|
+
|
|
240
|
+
expect(result.current.visSupportsAutoMaxStrategy()).toBe(true)
|
|
241
|
+
})
|
|
242
|
+
|
|
243
|
+
it('shows automatic max strategy for horizontal charts with automatic value axes', () => {
|
|
244
|
+
const { result } = renderUseEditorPermissions({
|
|
245
|
+
visualizationType: 'Bar',
|
|
246
|
+
orientation: 'horizontal',
|
|
247
|
+
xAxis: {
|
|
248
|
+
...createMockConfig().xAxis,
|
|
249
|
+
max: ''
|
|
250
|
+
},
|
|
251
|
+
yAxis: {
|
|
252
|
+
...createMockConfig().yAxis,
|
|
253
|
+
max: '',
|
|
254
|
+
type: 'linear'
|
|
255
|
+
}
|
|
256
|
+
})
|
|
257
|
+
|
|
258
|
+
expect(result.current.visSupportsAutoMaxStrategy()).toBe(true)
|
|
259
|
+
})
|
|
260
|
+
|
|
261
|
+
it('hides automatic max strategy when vertical value-axis max is explicit', () => {
|
|
262
|
+
const { result } = renderUseEditorPermissions({
|
|
263
|
+
yAxis: {
|
|
264
|
+
...createMockConfig().yAxis,
|
|
265
|
+
max: '100',
|
|
266
|
+
type: 'linear'
|
|
267
|
+
}
|
|
268
|
+
})
|
|
269
|
+
|
|
270
|
+
expect(result.current.visSupportsAutoMaxStrategy()).toBe(false)
|
|
271
|
+
})
|
|
272
|
+
|
|
273
|
+
it('hides automatic max strategy for horizontal charts when value-axis max is explicit', () => {
|
|
274
|
+
const { result } = renderUseEditorPermissions({
|
|
275
|
+
visualizationType: 'Bar',
|
|
276
|
+
orientation: 'horizontal',
|
|
277
|
+
xAxis: {
|
|
278
|
+
...createMockConfig().xAxis,
|
|
279
|
+
max: '100'
|
|
280
|
+
},
|
|
281
|
+
yAxis: {
|
|
282
|
+
...createMockConfig().yAxis,
|
|
283
|
+
max: '',
|
|
284
|
+
type: 'linear'
|
|
285
|
+
}
|
|
286
|
+
})
|
|
287
|
+
|
|
288
|
+
expect(result.current.visSupportsAutoMaxStrategy()).toBe(false)
|
|
289
|
+
})
|
|
290
|
+
|
|
291
|
+
it('hides automatic max strategy for categorical value axes', () => {
|
|
292
|
+
const { result } = renderUseEditorPermissions({
|
|
293
|
+
yAxis: {
|
|
294
|
+
...createMockConfig().yAxis,
|
|
295
|
+
max: '',
|
|
296
|
+
type: 'categorical'
|
|
297
|
+
}
|
|
298
|
+
})
|
|
299
|
+
|
|
300
|
+
expect(result.current.visSupportsAutoMaxStrategy()).toBe(false)
|
|
301
|
+
})
|
|
302
|
+
|
|
303
|
+
it('hides automatic max strategy for chart types with specialized value scales', () => {
|
|
304
|
+
const { result } = renderUseEditorPermissions({
|
|
305
|
+
visualizationType: 'Paired Bar',
|
|
306
|
+
yAxis: {
|
|
307
|
+
...createMockConfig().yAxis,
|
|
308
|
+
max: '',
|
|
309
|
+
type: 'linear'
|
|
310
|
+
}
|
|
311
|
+
})
|
|
312
|
+
|
|
313
|
+
expect(result.current.visSupportsAutoMaxStrategy()).toBe(false)
|
|
314
|
+
})
|
|
70
315
|
})
|
|
@@ -1,9 +1,12 @@
|
|
|
1
1
|
import React, { useContext } from 'react'
|
|
2
2
|
import ConfigContext from '../../ConfigContext'
|
|
3
|
-
import {
|
|
3
|
+
import { getYAxisFinalizationEligibility } from '../../helpers/getYAxisFinalizationEligibility'
|
|
4
|
+
import { hasSpacedInlineLabel } from '../../helpers/hasSpacedInlineLabel'
|
|
5
|
+
import { supportsSeriesColorAssignments } from '../../helpers/colorAssignmentHelpers'
|
|
6
|
+
import { hasVisibleVizFilters } from '@cdc/core/helpers/filterVisibility'
|
|
4
7
|
|
|
5
8
|
export const useEditorPermissions = () => {
|
|
6
|
-
const { config } = useContext(ConfigContext)
|
|
9
|
+
const { config, isDashboard } = useContext(ConfigContext)
|
|
7
10
|
const { visualizationType, series, orientation, visualizationSubType } = config
|
|
8
11
|
|
|
9
12
|
// Overall support for the chart types
|
|
@@ -254,13 +257,13 @@ export const useEditorPermissions = () => {
|
|
|
254
257
|
}
|
|
255
258
|
|
|
256
259
|
const visSupportsDateCategoryAxisLine = () => {
|
|
257
|
-
const disabledCharts = ['Forest Plot', '
|
|
260
|
+
const disabledCharts = ['Forest Plot', 'Spark Line', 'Warming Stripes']
|
|
258
261
|
if (disabledCharts.includes(visualizationType)) return false
|
|
259
262
|
return true
|
|
260
263
|
}
|
|
261
264
|
|
|
262
265
|
const visSupportsDateCategoryAxisTicks = () => {
|
|
263
|
-
const disabledCharts = ['Forest Plot', '
|
|
266
|
+
const disabledCharts = ['Forest Plot', 'Spark Line', 'Warming Stripes']
|
|
264
267
|
if (disabledCharts.includes(visualizationType)) return false
|
|
265
268
|
return true
|
|
266
269
|
}
|
|
@@ -317,6 +320,27 @@ export const useEditorPermissions = () => {
|
|
|
317
320
|
return true
|
|
318
321
|
}
|
|
319
322
|
|
|
323
|
+
const hasExplicitValueAxisMax = () => {
|
|
324
|
+
const valueAxisMax = orientation === 'horizontal' ? config.xAxis?.max : config.yAxis?.max
|
|
325
|
+
return valueAxisMax !== undefined && valueAxisMax !== null && valueAxisMax !== ''
|
|
326
|
+
}
|
|
327
|
+
|
|
328
|
+
const visSupportsAutomaticValueDomain = () => {
|
|
329
|
+
const enabledCharts = ['Area Chart', 'Bar', 'Combo', 'Deviation Bar', 'Forecasting', 'Line', 'Scatter Plot']
|
|
330
|
+
if (!enabledCharts.includes(visualizationType)) return false
|
|
331
|
+
if (config.yAxis?.type === 'categorical') return false
|
|
332
|
+
if (hasExplicitValueAxisMax()) return false
|
|
333
|
+
return true
|
|
334
|
+
}
|
|
335
|
+
|
|
336
|
+
const visSupportsAutoMaxStrategy = () => {
|
|
337
|
+
return visSupportsAutomaticValueDomain()
|
|
338
|
+
}
|
|
339
|
+
|
|
340
|
+
const visSupportsFilterDomainBehavior = () => {
|
|
341
|
+
return visSupportsAutomaticValueDomain() && Boolean(isDashboard || hasVisibleVizFilters(config.filters))
|
|
342
|
+
}
|
|
343
|
+
|
|
320
344
|
const visSupportsValueAxisGridLines = () => {
|
|
321
345
|
const disabledCharts = ['Forest Plot', 'HeatMap']
|
|
322
346
|
if (disabledCharts.includes(visualizationType)) return false
|
|
@@ -426,8 +450,17 @@ export const useEditorPermissions = () => {
|
|
|
426
450
|
return false
|
|
427
451
|
}
|
|
428
452
|
|
|
453
|
+
const visSupportsSeriesColorAssignments = () => supportsSeriesColorAssignments(config)
|
|
454
|
+
|
|
429
455
|
const visSupportsYPadding = () => {
|
|
430
|
-
|
|
456
|
+
const { shouldUseInlineLabelHeadroom } = getYAxisFinalizationEligibility({
|
|
457
|
+
config,
|
|
458
|
+
hasSpacedInlineLabel: hasSpacedInlineLabel(config),
|
|
459
|
+
hasValidExplicitLeftMax: false,
|
|
460
|
+
isHorizontal: orientation === 'horizontal'
|
|
461
|
+
})
|
|
462
|
+
|
|
463
|
+
return !shouldUseInlineLabelHeadroom
|
|
431
464
|
}
|
|
432
465
|
|
|
433
466
|
const visHasSingleSeriesTooltip = () => {
|
|
@@ -486,6 +519,8 @@ export const useEditorPermissions = () => {
|
|
|
486
519
|
visSupportsDateCategoryTickRotation,
|
|
487
520
|
visSupportsDynamicSeries,
|
|
488
521
|
visSupportsFilters,
|
|
522
|
+
visSupportsAutoMaxStrategy,
|
|
523
|
+
visSupportsFilterDomainBehavior,
|
|
489
524
|
visSupportsFootnotes,
|
|
490
525
|
visSupportsLeftValueAxis,
|
|
491
526
|
visSupportsMobileChartHeight,
|
|
@@ -497,6 +532,7 @@ export const useEditorPermissions = () => {
|
|
|
497
532
|
visSupportsResponsiveTicks,
|
|
498
533
|
visSupportsReverseColorPalette,
|
|
499
534
|
visSupportsSequentialPallete,
|
|
535
|
+
visSupportsSeriesColorAssignments,
|
|
500
536
|
visSupportsSmallMultiples,
|
|
501
537
|
visSupportsSuperTitle,
|
|
502
538
|
visSupportsTooltipLines,
|
|
@@ -64,7 +64,7 @@ type HeatMapLayout = {
|
|
|
64
64
|
const AXIS_TOP_WITH_TICKS = 36
|
|
65
65
|
const AXIS_TOP_WITHOUT_TICKS = 24
|
|
66
66
|
const AXIS_TOP_LABEL_SPACE = 28
|
|
67
|
-
const AXIS_TICK_FONT_SIZE =
|
|
67
|
+
const AXIS_TICK_FONT_SIZE = 16
|
|
68
68
|
const AXIS_TITLE_SPACE = 30
|
|
69
69
|
const AXIS_TOP_TITLE_BASELINE = 18
|
|
70
70
|
const X_AXIS_TITLE_LABEL_SPACE = 32
|
|
@@ -82,6 +82,7 @@ const EMPTY_VALUE_LABEL = 'No data'
|
|
|
82
82
|
const MIN_CELL_VALUE_WIDTH = 18
|
|
83
83
|
const MIN_CELL_VALUE_HEIGHT = 14
|
|
84
84
|
const SIDE_Y_AXIS_TITLE_GAP = 20
|
|
85
|
+
const AXIS_TITLE_FONT_WEIGHT = 'bold'
|
|
85
86
|
|
|
86
87
|
const clamp = (value: number, min: number, max: number) => Math.min(Math.max(value, min), max)
|
|
87
88
|
|
|
@@ -129,7 +130,7 @@ const getXAxisTickLabelProps = (xAxisPosition: HeatMapXAxisPosition, xTickRotati
|
|
|
129
130
|
|
|
130
131
|
if (!xTickRotation) {
|
|
131
132
|
return {
|
|
132
|
-
fontSize:
|
|
133
|
+
fontSize: AXIS_TICK_FONT_SIZE,
|
|
133
134
|
textAnchor: 'middle',
|
|
134
135
|
angle: 0,
|
|
135
136
|
dx: 0,
|
|
@@ -138,7 +139,7 @@ const getXAxisTickLabelProps = (xAxisPosition: HeatMapXAxisPosition, xTickRotati
|
|
|
138
139
|
}
|
|
139
140
|
|
|
140
141
|
return {
|
|
141
|
-
fontSize:
|
|
142
|
+
fontSize: AXIS_TICK_FONT_SIZE,
|
|
142
143
|
textAnchor: xAxisPosition === 'top' ? 'start' : 'end',
|
|
143
144
|
angle: xTickRotation,
|
|
144
145
|
dx: isSteepRotation || xAxisPosition === 'top' ? 0 : '-0.5em',
|
|
@@ -313,20 +314,16 @@ const getTooltipColumns = (
|
|
|
313
314
|
xDataKey: string | undefined,
|
|
314
315
|
heatMapSeries: { dataKey: string }[]
|
|
315
316
|
): TooltipColumn[] =>
|
|
316
|
-
Object.entries(config.columns || {}).reduce<TooltipColumn[]>((tooltipColumns, [
|
|
317
|
-
|
|
318
|
-
if (value.
|
|
319
|
-
if (
|
|
317
|
+
Object.entries(config.columns || {}).reduce<TooltipColumn[]>((tooltipColumns, [columnKey, value]) => {
|
|
318
|
+
const columnName = value.name || columnKey
|
|
319
|
+
if (!value.tooltips || !columnName) return tooltipColumns
|
|
320
|
+
if (columnName === xDataKey) return tooltipColumns
|
|
321
|
+
if (heatMapSeries.some(series => series.dataKey === columnName)) return tooltipColumns
|
|
320
322
|
|
|
321
323
|
tooltipColumns.push({
|
|
322
|
-
label: value.label ||
|
|
323
|
-
name:
|
|
324
|
-
options: {
|
|
325
|
-
addColPrefix: value.prefix,
|
|
326
|
-
addColSuffix: value.suffix,
|
|
327
|
-
addColRoundTo: value.roundToPlace,
|
|
328
|
-
addColCommas: value.commas
|
|
329
|
-
}
|
|
324
|
+
label: value.label || columnName,
|
|
325
|
+
name: columnName,
|
|
326
|
+
options: getSeriesColumnFormattingParams(value) || {}
|
|
330
327
|
})
|
|
331
328
|
|
|
332
329
|
return tooltipColumns
|
|
@@ -601,6 +598,7 @@ const HeatMap: React.FC<HeatMapProps> = ({ parentWidth, parentHeight }) => {
|
|
|
601
598
|
x={xOffset + gridWidth / 2}
|
|
602
599
|
y={xAxisPosition === 'top' ? -xAxisTitleDistance : gridHeight + xAxisTitleDistance}
|
|
603
600
|
textAnchor='middle'
|
|
601
|
+
fontWeight={AXIS_TITLE_FONT_WEIGHT}
|
|
604
602
|
>
|
|
605
603
|
{xAxisLabel}
|
|
606
604
|
</text>
|
|
@@ -611,7 +609,13 @@ const HeatMap: React.FC<HeatMapProps> = ({ parentWidth, parentHeight }) => {
|
|
|
611
609
|
|
|
612
610
|
if (showTopYAxisTitle) {
|
|
613
611
|
return (
|
|
614
|
-
<text
|
|
612
|
+
<text
|
|
613
|
+
className='cdc-heatmap__axis-title'
|
|
614
|
+
x={rowLabelTitleX}
|
|
615
|
+
y={topYAxisTitleY}
|
|
616
|
+
textAnchor='start'
|
|
617
|
+
fontWeight={AXIS_TITLE_FONT_WEIGHT}
|
|
618
|
+
>
|
|
615
619
|
{yAxisLabel}
|
|
616
620
|
</text>
|
|
617
621
|
)
|
|
@@ -622,6 +626,7 @@ const HeatMap: React.FC<HeatMapProps> = ({ parentWidth, parentHeight }) => {
|
|
|
622
626
|
className='cdc-heatmap__axis-title'
|
|
623
627
|
transform={`translate(${sideYAxisTitleX}, ${yOffset + gridHeight / 2}) rotate(-90)`}
|
|
624
628
|
textAnchor='middle'
|
|
629
|
+
fontWeight={AXIS_TITLE_FONT_WEIGHT}
|
|
625
630
|
>
|
|
626
631
|
{yAxisLabel}
|
|
627
632
|
</text>
|
|
@@ -737,7 +742,7 @@ const HeatMap: React.FC<HeatMapProps> = ({ parentWidth, parentHeight }) => {
|
|
|
737
742
|
hideAxisLine={Boolean(config.yAxis?.hideAxis)}
|
|
738
743
|
hideTicks={Boolean(config.yAxis?.hideTicks)}
|
|
739
744
|
tickLabelProps={() => ({
|
|
740
|
-
fontSize:
|
|
745
|
+
fontSize: AXIS_TICK_FONT_SIZE,
|
|
741
746
|
textAnchor: 'end',
|
|
742
747
|
angle: yTickRotation,
|
|
743
748
|
dx: yTickRotation ? '-0.25em' : 0,
|
|
@@ -62,6 +62,10 @@ const HeatMapGradientLegend = () => {
|
|
|
62
62
|
const ranges = getHeatMapDataGroupRanges(config, minValue, maxValue, formatNumber)
|
|
63
63
|
const displayRanges = config.legend?.reverseLabelOrder ? [...ranges].reverse() : ranges
|
|
64
64
|
const displayPalette = config.legend?.reverseLabelOrder ? [...palette].reverse() : palette
|
|
65
|
+
const displayLegendItems = displayRanges.map((range, index) => ({
|
|
66
|
+
color: displayPalette[index % displayPalette.length],
|
|
67
|
+
range
|
|
68
|
+
}))
|
|
65
69
|
|
|
66
70
|
if (config.legend?.hide) return null
|
|
67
71
|
|
|
@@ -72,34 +76,44 @@ const HeatMapGradientLegend = () => {
|
|
|
72
76
|
|
|
73
77
|
{isGradientLegend ? (
|
|
74
78
|
<div className='cdc-heatmap__legend-scale'>
|
|
75
|
-
<svg className='cdc-heatmap__legend-svg' height='
|
|
79
|
+
<svg className='cdc-heatmap__legend-svg' height='25' width='100%'>
|
|
76
80
|
<rect x='0' y='0' width='100%' height='25' fill='#d3d3d3' />
|
|
77
|
-
{
|
|
81
|
+
{displayLegendItems.map(({ color, range }, index) => (
|
|
78
82
|
<rect
|
|
79
|
-
key={
|
|
80
|
-
x={`${(index /
|
|
83
|
+
key={`heatmap-gradient-block-${range.min}-${range.max}-${color}`}
|
|
84
|
+
x={`${(index / displayLegendItems.length) * 100}%`}
|
|
81
85
|
y='1'
|
|
82
|
-
width={`${100 /
|
|
86
|
+
width={`${100 / displayLegendItems.length}%`}
|
|
83
87
|
height='23'
|
|
84
88
|
fill={color}
|
|
85
89
|
/>
|
|
86
90
|
))}
|
|
87
|
-
|
|
88
|
-
<text x='0' y='40' fontSize='14' textAnchor='start' fill='#333'>
|
|
89
|
-
{displayRanges[0]?.label.split('\u2013')[0]}
|
|
90
|
-
</text>
|
|
91
|
-
<text x='100%' y='40' fontSize='14' textAnchor='end' fill='#333'>
|
|
92
|
-
{displayRanges[displayRanges.length - 1]?.label.split('\u2013').pop()}
|
|
93
|
-
</text>
|
|
94
91
|
</svg>
|
|
92
|
+
<div
|
|
93
|
+
className='cdc-heatmap__legend-range-labels'
|
|
94
|
+
style={{ gridTemplateColumns: `repeat(${Math.max(displayLegendItems.length, 1)}, minmax(0, 1fr))` }}
|
|
95
|
+
>
|
|
96
|
+
{displayLegendItems.map(({ range }) => (
|
|
97
|
+
<span
|
|
98
|
+
className='cdc-heatmap__legend-range-label'
|
|
99
|
+
key={`heatmap-gradient-label-${range.min}-${range.max}`}
|
|
100
|
+
>
|
|
101
|
+
{range.label}
|
|
102
|
+
</span>
|
|
103
|
+
))}
|
|
104
|
+
</div>
|
|
95
105
|
|
|
96
106
|
{valueLabel && <div className='cdc-heatmap__legend-series-label'>{valueLabel}</div>}
|
|
97
107
|
</div>
|
|
98
108
|
) : (
|
|
99
109
|
<div className={innerClasses.join(' ')}>
|
|
100
|
-
{
|
|
101
|
-
<LegendItem
|
|
102
|
-
|
|
110
|
+
{displayLegendItems.map(({ color, range }) => (
|
|
111
|
+
<LegendItem
|
|
112
|
+
className='legend-item not-clickable'
|
|
113
|
+
key={`heatmap-legend-range-${range.min}-${range.max}`}
|
|
114
|
+
tabIndex={-1}
|
|
115
|
+
>
|
|
116
|
+
<LegendShape shape={shape} fill={color} />
|
|
103
117
|
<LegendLabel align='left' className='m-0'>
|
|
104
118
|
{range.label}
|
|
105
119
|
</LegendLabel>
|
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
--cove-heatmap-cell-stroke-color: rgba(255, 255, 255, 0.7);
|
|
4
4
|
--cove-heatmap-cell-stroke-width: 0.1px;
|
|
5
5
|
--cove-heatmap-axis-title-color: var(--darkGray, #333);
|
|
6
|
-
--cove-heatmap-axis-title-font-size:
|
|
6
|
+
--cove-heatmap-axis-title-font-size: 18px;
|
|
7
7
|
|
|
8
8
|
width: 100%;
|
|
9
9
|
}
|
|
@@ -63,6 +63,20 @@
|
|
|
63
63
|
overflow: visible;
|
|
64
64
|
}
|
|
65
65
|
|
|
66
|
+
.cove-visualization .cdc-heatmap__legend .cdc-heatmap__legend-range-labels {
|
|
67
|
+
color: #333;
|
|
68
|
+
display: grid;
|
|
69
|
+
font-size: 0.875rem;
|
|
70
|
+
line-height: 1.2;
|
|
71
|
+
margin-top: 0.35rem;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
.cove-visualization .cdc-heatmap__legend .cdc-heatmap__legend-range-label {
|
|
75
|
+
min-width: 0;
|
|
76
|
+
overflow-wrap: anywhere;
|
|
77
|
+
text-align: center;
|
|
78
|
+
}
|
|
79
|
+
|
|
66
80
|
.cove-visualization .cdc-heatmap__legend .cdc-heatmap__legend-series-label {
|
|
67
81
|
color: #333;
|
|
68
82
|
font-weight: 600;
|