@cdc/core 4.26.1 → 4.26.2
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/.claude/agents/qa-test-developer.md +126 -0
- package/CLAUDE.local.md +67 -0
- package/_stories/Gallery.Charts.stories.tsx +34 -41
- package/_stories/Gallery.DataBite.stories.tsx +14 -7
- package/_stories/Gallery.Maps.stories.tsx +36 -27
- package/_stories/Gallery.WaffleChart.stories.tsx +1 -1
- package/_stories/PageART.stories.tsx +4 -3
- package/_stories/PageBRFSS.stories.tsx +20 -15
- package/_stories/PageCancerRegistries.stories.tsx +14 -14
- package/_stories/PageEasternEquineEncephalitis.stories.tsx +30 -16
- package/_stories/PageExcessiveAlcoholUse.stories.tsx +148 -143
- package/_stories/PageMaternalMortality.stories.tsx +4 -3
- package/_stories/PageOralHealth.stories.tsx +14 -9
- package/_stories/PageSmokingTobacco.stories.tsx +14 -9
- package/_stories/PageStateDiabetesProfiles.stories.tsx +14 -9
- package/_stories/PageWastewater.stories.tsx +40 -26
- package/_stories/VegaImport.stories.tsx +401 -0
- package/_stories/vega-fixtures/bars-with-line.json +444 -0
- package/_stories/vega-fixtures/bars.json +58 -0
- package/_stories/vega-fixtures/combo-bar-rolling-mean.json +88 -0
- package/_stories/vega-fixtures/combo.json +68 -0
- package/_stories/vega-fixtures/grouped-horizontal-bars.json +83 -0
- package/_stories/vega-fixtures/grouped-horizontal-bars2.json +231 -0
- package/_stories/vega-fixtures/horizontal-bar.json +427 -0
- package/_stories/vega-fixtures/horizontal-bars-with-bad-colors.json +197 -0
- package/_stories/vega-fixtures/horizontal-bars2.json +58 -0
- package/_stories/vega-fixtures/lines.json +227 -0
- package/_stories/vega-fixtures/measles-bars.json +348 -0
- package/_stories/vega-fixtures/measles-map.json +11101 -0
- package/_stories/vega-fixtures/measles-stacked-bars.json +2147 -0
- package/_stories/vega-fixtures/multi-dataset.json +255 -0
- package/_stories/vega-fixtures/no-data.json +14 -0
- package/_stories/vega-fixtures/pie-chart.json +94 -0
- package/_stories/vega-fixtures/repeat-spec.json +47 -0
- package/_stories/vega-fixtures/stacked-area.json +222 -0
- package/_stories/vega-fixtures/stacked-bar-with-rect.json +3412 -0
- package/_stories/vega-fixtures/stacked-bars-with-line.json +364 -0
- package/_stories/vega-fixtures/stacked-bars.json +212 -0
- package/_stories/vega-fixtures/stacked-horizontal-bars.json +140 -0
- package/_stories/vega-fixtures/warning-combo.json +59 -0
- package/_stories/vega-fixtures/warning-scatter-and-line.json +1182 -0
- package/assets/icon-chart-area.svg +1 -0
- package/assets/icon-chart-radar.svg +23 -0
- package/assets/logo2.svg +31 -0
- package/components/AdvancedEditor/EmbedEditor.tsx +270 -38
- package/components/CustomColorsEditor/CustomColorsEditor.tsx +3 -10
- package/components/DataTable/helpers/getSeriesName.ts +6 -0
- package/components/EditorPanel/VizFilterEditor/NestedDropdownEditor.tsx +14 -6
- package/components/EditorPanel/VizFilterEditor/VizFilterEditor.tsx +4 -0
- package/components/EditorPanel/VizFilterEditor/components/FilterOrder.tsx +33 -29
- package/components/Layout/components/Sidebar/components/sidebar.styles.scss +2 -2
- package/components/Layout/components/Visualization/index.tsx +11 -0
- package/components/MediaControls.tsx +0 -1
- package/components/_stories/CustomColorsEditor.stories.tsx +37 -0
- package/components/_stories/DataTable.stories.tsx +1 -0
- package/data/colorPalettes.ts +18 -5
- package/data/mapColorPalettes.ts +10 -0
- package/devTemplate/dev.js +235 -0
- package/devTemplate/index.html +30 -0
- package/devTemplate/preview.html +1503 -0
- package/devTemplate/sidebar.css +151 -0
- package/dist/cove-main.css +2803 -4471
- package/dist/cove-main.css.map +1 -1
- package/generateViteConfig.js +111 -2
- package/helpers/DataTransform.ts +1 -5
- package/helpers/cove/date.ts +33 -1
- package/helpers/cove/string.ts +29 -0
- package/helpers/coveUpdateWorker.ts +3 -1
- package/helpers/embed/embedCodeGenerator.ts +80 -0
- package/helpers/embed/embedHelper.js +158 -0
- package/helpers/embed/filterUtils.ts +121 -0
- package/helpers/embed/index.ts +21 -0
- package/helpers/embed/urlValidation.ts +119 -0
- package/helpers/filterVizData.ts +6 -1
- package/helpers/getFileExtension.ts +0 -6
- package/helpers/metrics/types.ts +3 -0
- package/helpers/palettes/colorDistributions.ts +1 -1
- package/helpers/palettes/utils.ts +12 -12
- package/helpers/parseCsvWithQuotes.ts +15 -14
- package/helpers/prepareScreenshot.ts +27 -7
- package/helpers/testing.ts +44 -0
- package/helpers/tests/DataTransform.test.ts +125 -0
- package/helpers/tests/date.test.ts +64 -0
- package/helpers/vegaConfig.ts +1 -1
- package/helpers/vegaConfigImport.ts +160 -0
- package/helpers/ver/4.26.1.ts +1 -1
- package/helpers/ver/4.26.2.ts +84 -0
- package/helpers/ver/tests/4.26.1.test.ts +105 -0
- package/helpers/ver/tests/4.26.2.test.ts +298 -0
- package/helpers/viewports.ts +2 -0
- package/package.json +27 -32
- package/styles/v2/components/editor.scss +9 -9
- package/styles/v2/utils/_grid.scss +8 -3
- package/types/Annotation.ts +10 -11
- package/types/General.ts +2 -0
- package/types/Palette.ts +21 -0
- package/types/Visualization.ts +6 -0
- package/_stories/StoryRenderingTests.stories.tsx +0 -164
- package/helpers/embedCodeGenerator.ts +0 -109
|
@@ -0,0 +1,125 @@
|
|
|
1
|
+
import { describe, it, expect } from 'vitest'
|
|
2
|
+
import DataTransform from '../DataTransform'
|
|
3
|
+
|
|
4
|
+
describe('DataTransform', () => {
|
|
5
|
+
let transformer: DataTransform
|
|
6
|
+
|
|
7
|
+
beforeEach(() => {
|
|
8
|
+
transformer = new DataTransform()
|
|
9
|
+
})
|
|
10
|
+
|
|
11
|
+
describe('autoStandardize', () => {
|
|
12
|
+
it('returns undefined for empty data', () => {
|
|
13
|
+
expect(transformer.autoStandardize([])).toBeUndefined()
|
|
14
|
+
expect(transformer.autoStandardize(null)).toBeUndefined()
|
|
15
|
+
expect(transformer.autoStandardize(undefined)).toBeUndefined()
|
|
16
|
+
})
|
|
17
|
+
|
|
18
|
+
it('returns undefined for invalid data format', () => {
|
|
19
|
+
expect(transformer.autoStandardize(['string', 'array'])).toBeUndefined()
|
|
20
|
+
expect(transformer.autoStandardize([1, 2, 3])).toBeUndefined()
|
|
21
|
+
})
|
|
22
|
+
|
|
23
|
+
it('returns data unchanged when already in object format', () => {
|
|
24
|
+
const data = [
|
|
25
|
+
{ name: 'Alice', value: 10 },
|
|
26
|
+
{ name: 'Bob', value: 20 }
|
|
27
|
+
]
|
|
28
|
+
const result = transformer.autoStandardize(data)
|
|
29
|
+
expect(result).toEqual(data)
|
|
30
|
+
})
|
|
31
|
+
|
|
32
|
+
it('converts array of arrays to array of objects', () => {
|
|
33
|
+
const data = [
|
|
34
|
+
['name', 'value'],
|
|
35
|
+
['Alice', 10],
|
|
36
|
+
['Bob', 20]
|
|
37
|
+
]
|
|
38
|
+
const result = transformer.autoStandardize(data)
|
|
39
|
+
expect(result).toEqual([
|
|
40
|
+
{ name: 'Alice', value: 10 },
|
|
41
|
+
{ name: 'Bob', value: 20 }
|
|
42
|
+
])
|
|
43
|
+
})
|
|
44
|
+
|
|
45
|
+
it('handles array of arrays with multiple columns', () => {
|
|
46
|
+
const data = [
|
|
47
|
+
['state', 'year', 'population'],
|
|
48
|
+
['CA', 2020, 39538223],
|
|
49
|
+
['TX', 2020, 29145505]
|
|
50
|
+
]
|
|
51
|
+
const result = transformer.autoStandardize(data)
|
|
52
|
+
expect(result).toEqual([
|
|
53
|
+
{ state: 'CA', year: 2020, population: 39538223 },
|
|
54
|
+
{ state: 'TX', year: 2020, population: 29145505 }
|
|
55
|
+
])
|
|
56
|
+
})
|
|
57
|
+
})
|
|
58
|
+
|
|
59
|
+
describe('cleanDataPoint', () => {
|
|
60
|
+
it('removes commas from numbers', () => {
|
|
61
|
+
expect(transformer.cleanDataPoint('1,234')).toBe('1234')
|
|
62
|
+
expect(transformer.cleanDataPoint('1,234,567')).toBe('1234567')
|
|
63
|
+
})
|
|
64
|
+
|
|
65
|
+
it('removes dollar signs', () => {
|
|
66
|
+
expect(transformer.cleanDataPoint('$100')).toBe('100')
|
|
67
|
+
expect(transformer.cleanDataPoint('$1,234')).toBe('1234')
|
|
68
|
+
})
|
|
69
|
+
|
|
70
|
+
it('removes percent signs', () => {
|
|
71
|
+
expect(transformer.cleanDataPoint('50%')).toBe('50')
|
|
72
|
+
expect(transformer.cleanDataPoint('99.9%')).toBe('99.9')
|
|
73
|
+
})
|
|
74
|
+
|
|
75
|
+
it('handles combined formatting', () => {
|
|
76
|
+
expect(transformer.cleanDataPoint('$1,234,567')).toBe('1234567')
|
|
77
|
+
})
|
|
78
|
+
|
|
79
|
+
it('handles null and empty values', () => {
|
|
80
|
+
expect(transformer.cleanDataPoint(null)).toBe('')
|
|
81
|
+
expect(transformer.cleanDataPoint('')).toBe('')
|
|
82
|
+
})
|
|
83
|
+
|
|
84
|
+
it('handles non-string values', () => {
|
|
85
|
+
expect(transformer.cleanDataPoint(123)).toBe(123)
|
|
86
|
+
expect(transformer.cleanDataPoint(0)).toBe(0)
|
|
87
|
+
})
|
|
88
|
+
|
|
89
|
+
it('returns plain numbers unchanged', () => {
|
|
90
|
+
expect(transformer.cleanDataPoint('123')).toBe('123')
|
|
91
|
+
expect(transformer.cleanDataPoint('45.67')).toBe('45.67')
|
|
92
|
+
})
|
|
93
|
+
})
|
|
94
|
+
|
|
95
|
+
describe('developerStandardize', () => {
|
|
96
|
+
it('returns empty array when data is falsy', () => {
|
|
97
|
+
expect(transformer.developerStandardize(null, {})).toEqual([])
|
|
98
|
+
expect(transformer.developerStandardize(undefined, {})).toEqual([])
|
|
99
|
+
})
|
|
100
|
+
|
|
101
|
+
it('returns undefined when description is missing', () => {
|
|
102
|
+
const data = [{ a: 1 }]
|
|
103
|
+
expect(transformer.developerStandardize(data, null)).toBeUndefined()
|
|
104
|
+
expect(transformer.developerStandardize(data, undefined)).toBeUndefined()
|
|
105
|
+
})
|
|
106
|
+
|
|
107
|
+
it('returns undefined when description is incomplete', () => {
|
|
108
|
+
const data = [{ a: 1 }]
|
|
109
|
+
expect(transformer.developerStandardize(data, {})).toBeUndefined()
|
|
110
|
+
expect(transformer.developerStandardize(data, { horizontal: true })).toBeUndefined()
|
|
111
|
+
expect(transformer.developerStandardize(data, { series: true })).toBeUndefined()
|
|
112
|
+
})
|
|
113
|
+
|
|
114
|
+
it('handles horizontal data without series', () => {
|
|
115
|
+
const data = [
|
|
116
|
+
{ category: 'A', val1: 10, val2: 20 },
|
|
117
|
+
{ category: 'B', val1: 30, val2: 40 }
|
|
118
|
+
]
|
|
119
|
+
const description = { horizontal: true, series: false }
|
|
120
|
+
const result = transformer.developerStandardize(data, description)
|
|
121
|
+
expect(result).toBeDefined()
|
|
122
|
+
expect(Array.isArray(result)).toBe(true)
|
|
123
|
+
})
|
|
124
|
+
})
|
|
125
|
+
})
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
import { describe, it, expect } from 'vitest'
|
|
2
|
+
import { getDateRenderFormat, formatDate } from '../cove/date'
|
|
3
|
+
|
|
4
|
+
const NBSP = '\u00A0'
|
|
5
|
+
|
|
6
|
+
describe('getDateRenderFormat', () => {
|
|
7
|
+
it('replaces space between %b. and %-d with NBSP', () => {
|
|
8
|
+
expect(getDateRenderFormat('%b. %-d %Y')).toBe(`%b.${NBSP}%-d %Y`)
|
|
9
|
+
})
|
|
10
|
+
|
|
11
|
+
it('replaces space between %b and %-d with NBSP', () => {
|
|
12
|
+
expect(getDateRenderFormat('%b %-d %Y')).toBe(`%b${NBSP}%-d %Y`)
|
|
13
|
+
})
|
|
14
|
+
|
|
15
|
+
it('replaces space between %B and %-d (full month name)', () => {
|
|
16
|
+
expect(getDateRenderFormat('%B %-d, %Y')).toBe(`%B${NBSP}%-d, %Y`)
|
|
17
|
+
})
|
|
18
|
+
|
|
19
|
+
it('leaves format unchanged when no month-day space pattern', () => {
|
|
20
|
+
expect(getDateRenderFormat('%Y-%m-%d')).toBe('%Y-%m-%d')
|
|
21
|
+
})
|
|
22
|
+
|
|
23
|
+
it('does not replace existing NBSP (idempotent)', () => {
|
|
24
|
+
const alreadyNbsp = `%b.${NBSP}%-d %Y`
|
|
25
|
+
expect(getDateRenderFormat(alreadyNbsp)).toBe(alreadyNbsp)
|
|
26
|
+
})
|
|
27
|
+
|
|
28
|
+
it('returns undefined for undefined input', () => {
|
|
29
|
+
expect(getDateRenderFormat(undefined)).toBeUndefined()
|
|
30
|
+
})
|
|
31
|
+
|
|
32
|
+
it('returns empty string for empty string input', () => {
|
|
33
|
+
expect(getDateRenderFormat('')).toBe('')
|
|
34
|
+
})
|
|
35
|
+
})
|
|
36
|
+
|
|
37
|
+
describe('formatDate', () => {
|
|
38
|
+
it('renders date with NBSP when format has space between month and day', () => {
|
|
39
|
+
const date = new Date(2025, 0, 15) // Jan 15, 2025
|
|
40
|
+
const result = formatDate('%b. %-d %Y', date)
|
|
41
|
+
expect(result).toContain(NBSP)
|
|
42
|
+
expect(result).toBe(`Jan.${NBSP}15 2025`)
|
|
43
|
+
})
|
|
44
|
+
|
|
45
|
+
it('strips trailing period from "May." when using %b. format', () => {
|
|
46
|
+
const date = new Date(2025, 4, 15) // May 15, 2025
|
|
47
|
+
const result = formatDate('%b. %-d, %Y', date)
|
|
48
|
+
expect(result).not.toContain('May.')
|
|
49
|
+
expect(result).toContain('May')
|
|
50
|
+
})
|
|
51
|
+
|
|
52
|
+
it('leaves "May" alone when format does not use %b.', () => {
|
|
53
|
+
const date = new Date(2025, 4, 15) // May 15, 2025
|
|
54
|
+
const result = formatDate('%b %-d, %Y', date)
|
|
55
|
+
expect(result).toContain('May')
|
|
56
|
+
expect(result).not.toContain('May.')
|
|
57
|
+
})
|
|
58
|
+
|
|
59
|
+
it('does not strip period from other months when using %b. format', () => {
|
|
60
|
+
const date = new Date(2025, 0, 15) // Jan 15, 2025
|
|
61
|
+
const result = formatDate('%b. %-d, %Y', date)
|
|
62
|
+
expect(result).toContain('Jan.')
|
|
63
|
+
})
|
|
64
|
+
})
|
package/helpers/vegaConfig.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { DataTransform } from '@cdc/core/helpers/DataTransform'
|
|
2
2
|
import { formatDate } from '@cdc/core/helpers/cove/date.js'
|
|
3
|
-
import
|
|
3
|
+
import _ from 'lodash'
|
|
4
4
|
import { compile as vegaLiteCompile } from 'vega-lite'
|
|
5
5
|
import { parse as vegaParse, View as vegaView } from 'vega'
|
|
6
6
|
|
|
@@ -0,0 +1,160 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Shared Vega-to-COVE conversion helper for stories.
|
|
3
|
+
*/
|
|
4
|
+
import {
|
|
5
|
+
convertVegaConfig,
|
|
6
|
+
getVegaConfigType,
|
|
7
|
+
getVegaErrors,
|
|
8
|
+
getVegaWarnings,
|
|
9
|
+
isVegaConfig,
|
|
10
|
+
parseVegaConfig
|
|
11
|
+
} from './vegaConfig'
|
|
12
|
+
|
|
13
|
+
/** Chart-type "button" definitions used to seed new COVE configs. */
|
|
14
|
+
const buttons = [
|
|
15
|
+
{
|
|
16
|
+
id: 1,
|
|
17
|
+
category: 'Charts',
|
|
18
|
+
label: 'Bar',
|
|
19
|
+
type: 'chart',
|
|
20
|
+
subType: 'Bar',
|
|
21
|
+
orientation: 'vertical',
|
|
22
|
+
barThickness: '0.37',
|
|
23
|
+
visualizationSubType: 'regular',
|
|
24
|
+
xAxis: { type: 'categorical', size: 75, maxTickRotation: 45, labelOffset: 0 },
|
|
25
|
+
content: 'Use bars to show comparisons between data categories.'
|
|
26
|
+
},
|
|
27
|
+
{
|
|
28
|
+
id: 3,
|
|
29
|
+
category: 'Charts',
|
|
30
|
+
label: 'Combo Chart',
|
|
31
|
+
type: 'chart',
|
|
32
|
+
subType: 'Combo',
|
|
33
|
+
orientation: 'vertical',
|
|
34
|
+
content: 'Use bars to show comparisons between data categories.'
|
|
35
|
+
},
|
|
36
|
+
{
|
|
37
|
+
id: 4,
|
|
38
|
+
category: 'Charts',
|
|
39
|
+
label: 'Line',
|
|
40
|
+
type: 'chart',
|
|
41
|
+
subType: 'Line',
|
|
42
|
+
orientation: 'vertical',
|
|
43
|
+
content: 'Present one or more data trends over time.'
|
|
44
|
+
},
|
|
45
|
+
{
|
|
46
|
+
id: 6,
|
|
47
|
+
category: 'Charts',
|
|
48
|
+
label: 'Area Chart',
|
|
49
|
+
type: 'chart',
|
|
50
|
+
subType: 'Area Chart',
|
|
51
|
+
orientation: 'vertical',
|
|
52
|
+
content: 'Display an area chart to visualize quantities over time.'
|
|
53
|
+
},
|
|
54
|
+
{
|
|
55
|
+
id: 8,
|
|
56
|
+
category: 'Charts',
|
|
57
|
+
label: 'Scatter Plot',
|
|
58
|
+
type: 'chart',
|
|
59
|
+
subType: 'Scatter Plot',
|
|
60
|
+
orientation: 'vertical',
|
|
61
|
+
content: 'Display a scatter plot to explore relationships between numeric variables.'
|
|
62
|
+
},
|
|
63
|
+
{
|
|
64
|
+
id: 12,
|
|
65
|
+
category: 'Charts',
|
|
66
|
+
label: 'Horizontal Bar (Stacked)',
|
|
67
|
+
type: 'chart',
|
|
68
|
+
subType: 'Bar',
|
|
69
|
+
visualizationSubType: 'stacked',
|
|
70
|
+
orientation: 'horizontal',
|
|
71
|
+
content: 'Use bars to show comparisons between data categories.'
|
|
72
|
+
},
|
|
73
|
+
{
|
|
74
|
+
id: 19,
|
|
75
|
+
category: 'Maps',
|
|
76
|
+
label: 'United States (State- or County-Level)',
|
|
77
|
+
type: 'map',
|
|
78
|
+
subType: 'us',
|
|
79
|
+
content: 'Present a U.S. choropleth map at state or county level.',
|
|
80
|
+
position: 'right'
|
|
81
|
+
}
|
|
82
|
+
]
|
|
83
|
+
|
|
84
|
+
/** Build a seed COVE config from a button definition. */
|
|
85
|
+
const generateNewConfig = (props: any) => {
|
|
86
|
+
let newConfig: any = {}
|
|
87
|
+
switch (props.category) {
|
|
88
|
+
case 'Charts': {
|
|
89
|
+
const visualizationType = props.subType
|
|
90
|
+
const visualizationSubType = !props.visualizationSubType ? 'regular' : props.visualizationSubType
|
|
91
|
+
newConfig = {
|
|
92
|
+
...props,
|
|
93
|
+
visualizationType,
|
|
94
|
+
visualizationSubType,
|
|
95
|
+
newViz: true,
|
|
96
|
+
datasets: {}
|
|
97
|
+
}
|
|
98
|
+
break
|
|
99
|
+
}
|
|
100
|
+
case 'Maps': {
|
|
101
|
+
newConfig = { ...props, newViz: true, datasets: {}, type: 'map' }
|
|
102
|
+
newConfig['general'] = { geoType: props.subType, type: props?.generalType }
|
|
103
|
+
break
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
return newConfig
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
/**
|
|
110
|
+
* Convert a raw Vega/Vega-Lite config into a COVE config.
|
|
111
|
+
* Returns `null` if the config has errors.
|
|
112
|
+
*/
|
|
113
|
+
export const importVegaConfig = async (rawConfig: any): Promise<any | null> => {
|
|
114
|
+
const vegaConfig = await parseVegaConfig(rawConfig)
|
|
115
|
+
const vegaErrors = getVegaErrors(rawConfig, vegaConfig)
|
|
116
|
+
if (vegaErrors.length > 0) {
|
|
117
|
+
console.warn('Vega import errors:', vegaErrors)
|
|
118
|
+
return null
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
const configType = getVegaConfigType(vegaConfig)
|
|
122
|
+
const configSubType = configType === 'Map' ? 'United States (State- or County-Level)' : configType
|
|
123
|
+
const button = buttons.find(b => b.label === configSubType)
|
|
124
|
+
if (!button) {
|
|
125
|
+
console.warn(`No button found for config type "${configSubType}"`)
|
|
126
|
+
return null
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
const coveConfig = generateNewConfig(JSON.parse(JSON.stringify(button)))
|
|
130
|
+
try {
|
|
131
|
+
const warnings = getVegaWarnings(rawConfig, vegaConfig)
|
|
132
|
+
if (warnings.length) {
|
|
133
|
+
console.warn('Vega import warnings:', warnings)
|
|
134
|
+
}
|
|
135
|
+
const result = convertVegaConfig(configType, vegaConfig, coveConfig)
|
|
136
|
+
|
|
137
|
+
// Ensure imported Vega configs use the v1 qualitative palette so CdcChart
|
|
138
|
+
// doesn't apply v2 sequential-blue defaults (which would change the colors).
|
|
139
|
+
if (result && result.type !== 'map') {
|
|
140
|
+
result.general = result.general || {}
|
|
141
|
+
result.general.palette = {
|
|
142
|
+
name: 'qualitative-bold',
|
|
143
|
+
version: '1.0'
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
return result
|
|
148
|
+
} catch (err) {
|
|
149
|
+
console.error('Vega conversion error:', err)
|
|
150
|
+
return null
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
/** Check if a config is a Vega config and convert it; otherwise pass through. */
|
|
155
|
+
export const maybeConvertVega = async (config: any): Promise<any | null> => {
|
|
156
|
+
if (isVegaConfig(config)) {
|
|
157
|
+
return importVegaConfig(config)
|
|
158
|
+
}
|
|
159
|
+
return config
|
|
160
|
+
}
|
package/helpers/ver/4.26.1.ts
CHANGED
|
@@ -20,7 +20,7 @@ const removeOldBrushKeys = config => {
|
|
|
20
20
|
delete config.brush
|
|
21
21
|
}
|
|
22
22
|
|
|
23
|
-
if (config.type === 'dashboard') {
|
|
23
|
+
if (config.type === 'dashboard' && config.visualizations) {
|
|
24
24
|
Object.values((config as DashboardConfig).visualizations).forEach(visualization => {
|
|
25
25
|
removeOldBrushKeys(visualization)
|
|
26
26
|
})
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
import cloneConfig from '../cloneConfig'
|
|
2
|
+
import { DashboardConfig } from '@cdc/dashboard/src/types/DashboardConfig'
|
|
3
|
+
|
|
4
|
+
const migrateAnnotationDimensions = config => {
|
|
5
|
+
if (config.annotations && Array.isArray(config.annotations)) {
|
|
6
|
+
// Calculate chart area height for Y conversion (matches calcInitialHeight)
|
|
7
|
+
const isHorizontal = config.orientation === 'horizontal'
|
|
8
|
+
const chartAreaHeight = isHorizontal
|
|
9
|
+
? Number(config.heights?.horizontal) || 750 // default horizontal height
|
|
10
|
+
: Number(config.heights?.vertical) || 300 // default vertical height
|
|
11
|
+
|
|
12
|
+
config.annotations = config.annotations.map(annotation => {
|
|
13
|
+
if (!annotation) return annotation
|
|
14
|
+
if (annotation.y !== undefined && chartAreaHeight > 0) {
|
|
15
|
+
// Convert Y from pixels to percentage using the chart area height
|
|
16
|
+
annotation.y = (annotation.y / chartAreaHeight) * 100
|
|
17
|
+
} else {
|
|
18
|
+
annotation.y = 50
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
// Delete savedDimensions to preserve old dx/dy behavior (fixed pixel offsets).
|
|
22
|
+
// The scaling function falls back to raw dx/dy when savedDimensions is missing.
|
|
23
|
+
// Once user drags the annotation, savedDimensions will be set with current chart
|
|
24
|
+
// dimensions, enabling responsive scaling from that point forward.
|
|
25
|
+
delete annotation.savedDimensions
|
|
26
|
+
|
|
27
|
+
return annotation
|
|
28
|
+
})
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
if (config.type === 'dashboard' && config.visualizations) {
|
|
32
|
+
Object.values((config as DashboardConfig).visualizations).forEach(visualization => {
|
|
33
|
+
migrateAnnotationDimensions(visualization)
|
|
34
|
+
})
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
const migrateAnnotationDataModel = config => {
|
|
39
|
+
if (config.annotations && Array.isArray(config.annotations)) {
|
|
40
|
+
config.annotations = config.annotations.map(annotation => {
|
|
41
|
+
if (!annotation) return annotation
|
|
42
|
+
// Set all existing annotations to fixed mode
|
|
43
|
+
annotation.anchorMode = 'fixed'
|
|
44
|
+
|
|
45
|
+
// Delete xKey entirely - old format stored timestamps for dates,
|
|
46
|
+
// but new dataX expects raw data values. Format is incompatible.
|
|
47
|
+
// User can re-enable data mode which will set fresh dataX value.
|
|
48
|
+
delete annotation.xKey
|
|
49
|
+
|
|
50
|
+
// Delete yKey entirely (Y will be calculated dynamically in data mode)
|
|
51
|
+
delete annotation.yKey
|
|
52
|
+
|
|
53
|
+
// Delete empty seriesKey - it would cause yScale(undefined) errors in data mode
|
|
54
|
+
if (!annotation.seriesKey) {
|
|
55
|
+
delete annotation.seriesKey
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
// Delete deprecated properties
|
|
59
|
+
delete annotation.snapToNearestPoint
|
|
60
|
+
delete annotation.originalX
|
|
61
|
+
delete annotation.originalDX
|
|
62
|
+
delete annotation.originalY
|
|
63
|
+
|
|
64
|
+
return annotation
|
|
65
|
+
})
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
if (config.type === 'dashboard' && config.visualizations) {
|
|
69
|
+
Object.values((config as DashboardConfig).visualizations).forEach(visualization => {
|
|
70
|
+
migrateAnnotationDataModel(visualization)
|
|
71
|
+
})
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
const update_4_26_2 = config => {
|
|
76
|
+
const ver = '4.26.2'
|
|
77
|
+
const newConfig = cloneConfig(config)
|
|
78
|
+
migrateAnnotationDimensions(newConfig)
|
|
79
|
+
migrateAnnotationDataModel(newConfig)
|
|
80
|
+
newConfig.version = ver
|
|
81
|
+
return newConfig
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
export default update_4_26_2
|
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
import update_4_26_1 from '../4.26.1'
|
|
2
|
+
import { expect, describe, it } from 'vitest'
|
|
3
|
+
|
|
4
|
+
describe('update_4_26_1', () => {
|
|
5
|
+
describe('normalizeFilterParents', () => {
|
|
6
|
+
it('should convert string parents to array in shared filters', () => {
|
|
7
|
+
const config: any = {
|
|
8
|
+
type: 'dashboard',
|
|
9
|
+
version: '4.26.0',
|
|
10
|
+
dashboard: {
|
|
11
|
+
sharedFilters: [
|
|
12
|
+
{
|
|
13
|
+
type: 'datafilter',
|
|
14
|
+
parents: 'parent-filter-id'
|
|
15
|
+
}
|
|
16
|
+
]
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
const result = update_4_26_1(config)
|
|
21
|
+
|
|
22
|
+
expect(result.dashboard.sharedFilters[0].parents).toEqual(['parent-filter-id'])
|
|
23
|
+
expect(result.version).toBe('4.26.1')
|
|
24
|
+
})
|
|
25
|
+
|
|
26
|
+
it('should leave array parents unchanged', () => {
|
|
27
|
+
const config: any = {
|
|
28
|
+
type: 'dashboard',
|
|
29
|
+
version: '4.26.0',
|
|
30
|
+
dashboard: {
|
|
31
|
+
sharedFilters: [
|
|
32
|
+
{
|
|
33
|
+
type: 'datafilter',
|
|
34
|
+
parents: ['parent1', 'parent2']
|
|
35
|
+
}
|
|
36
|
+
]
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
const result = update_4_26_1(config)
|
|
41
|
+
|
|
42
|
+
expect(result.dashboard.sharedFilters[0].parents).toEqual(['parent1', 'parent2'])
|
|
43
|
+
})
|
|
44
|
+
})
|
|
45
|
+
|
|
46
|
+
describe('removeOldBrushKeys', () => {
|
|
47
|
+
it('should remove brush config from chart', () => {
|
|
48
|
+
const config: any = {
|
|
49
|
+
type: 'chart',
|
|
50
|
+
version: '4.26.0',
|
|
51
|
+
brush: { enabled: true }
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
const result = update_4_26_1(config)
|
|
55
|
+
|
|
56
|
+
expect(result.brush).toBeUndefined()
|
|
57
|
+
})
|
|
58
|
+
|
|
59
|
+
it('should remove brush config from dashboard visualizations', () => {
|
|
60
|
+
const config: any = {
|
|
61
|
+
type: 'dashboard',
|
|
62
|
+
version: '4.26.0',
|
|
63
|
+
visualizations: {
|
|
64
|
+
chart1: {
|
|
65
|
+
type: 'chart',
|
|
66
|
+
brush: { enabled: true }
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
const result = update_4_26_1(config)
|
|
72
|
+
|
|
73
|
+
expect(result.visualizations.chart1.brush).toBeUndefined()
|
|
74
|
+
})
|
|
75
|
+
})
|
|
76
|
+
|
|
77
|
+
describe('combined migrations', () => {
|
|
78
|
+
it('should run all migrations together', () => {
|
|
79
|
+
const config: any = {
|
|
80
|
+
type: 'dashboard',
|
|
81
|
+
version: '4.26.0',
|
|
82
|
+
dashboard: {
|
|
83
|
+
sharedFilters: [
|
|
84
|
+
{
|
|
85
|
+
type: 'datafilter',
|
|
86
|
+
parents: 'parent-id'
|
|
87
|
+
}
|
|
88
|
+
]
|
|
89
|
+
},
|
|
90
|
+
visualizations: {
|
|
91
|
+
chart1: {
|
|
92
|
+
type: 'chart',
|
|
93
|
+
brush: { enabled: true }
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
const result = update_4_26_1(config)
|
|
99
|
+
|
|
100
|
+
expect(result.dashboard.sharedFilters[0].parents).toEqual(['parent-id'])
|
|
101
|
+
expect(result.visualizations.chart1.brush).toBeUndefined()
|
|
102
|
+
expect(result.version).toBe('4.26.1')
|
|
103
|
+
})
|
|
104
|
+
})
|
|
105
|
+
})
|