@cdc/core 4.25.8 → 4.25.10
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/components/AdvancedEditor/AdvancedEditor.tsx +29 -8
- package/components/DataTable/DataTable.tsx +56 -38
- package/components/DataTable/components/ChartHeader.tsx +44 -14
- package/components/DataTable/components/ExpandCollapse.tsx +10 -1
- package/components/DataTable/components/MapHeader.tsx +24 -13
- package/components/DataTable/data-table.css +6 -0
- package/components/DataTable/helpers/chartCellMatrix.tsx +11 -8
- package/components/DataTable/helpers/mapCellMatrix.tsx +19 -1
- package/components/DownloadButton.tsx +40 -14
- package/components/EditorPanel/components/MarkupHighlightedTextField.tsx +227 -0
- package/components/EditorPanel/components/MarkupVariablesEditor.tsx +411 -0
- package/components/EditorPanel/components/PanelMarkup.tsx +59 -0
- package/components/ErrorBoundary.jsx +3 -1
- package/components/Filters/Filters.tsx +27 -20
- package/components/Filters/components/Tabs.tsx +1 -0
- package/components/Legend/Legend.Gradient.tsx +3 -6
- package/components/LegendShape.tsx +121 -3
- package/components/MediaControls.tsx +51 -3
- package/components/PaletteConversionModal.tsx +87 -0
- package/components/PaletteSelector/DeveloperPaletteRollback.tsx +114 -0
- package/components/PaletteSelector/PaletteSelector.css +51 -0
- package/components/PaletteSelector/PaletteSelector.tsx +112 -0
- package/components/PaletteSelector/index.ts +2 -0
- package/components/RichTooltip/RichTooltip.tsx +1 -0
- package/components/Table/Table.tsx +3 -1
- package/components/_stories/BlurStrokeTest.stories.tsx +1 -1
- package/components/_stories/DataTable.stories.tsx +1 -1
- package/components/_stories/Filters.stories.tsx +1 -1
- package/components/_stories/Footnotes.stories.tsx +1 -1
- package/components/_stories/Inputs.stories.tsx +1 -1
- package/components/_stories/MultiSelect.stories.tsx +3 -3
- package/components/_stories/NestedDropdown.stories.tsx +1 -1
- package/components/_stories/Table.stories.tsx +1 -1
- package/components/elements/_stories/Button.stories.tsx +1 -1
- package/components/elements/_stories/Card.stories.tsx +1 -1
- package/components/inputs/InputToggle.tsx +2 -0
- package/components/managers/DataDesigner.tsx +10 -9
- package/components/managers/_stories/DataDesigner.stories.tsx +1 -1
- package/components/ui/Tooltip.tsx +2 -1
- package/components/ui/_stories/Accordion.stories.tsx +1 -1
- package/components/ui/_stories/ColorPaletteMigration.stories.mdx +275 -0
- package/components/ui/_stories/Colors.stories.tsx +330 -0
- package/components/ui/_stories/IconGallery.stories.tsx +316 -0
- package/components/ui/_stories/Title.stories.tsx +1 -1
- package/contexts/EditorContext.ts +18 -0
- package/contexts/editor.actions.ts +28 -0
- package/contexts/editor.reducer.ts +94 -0
- package/data/chartColorPalettes.ts +118 -0
- package/data/colorPalettes.ts +9 -0
- package/data/mapColorPalettes.ts +45 -0
- package/data/sharedPalettes.ts +50 -0
- package/dist/cove-main.css +14 -11
- package/dist/cove-main.css.map +1 -1
- package/generateViteConfig.js +80 -0
- package/helpers/addValuesToFilters.ts +2 -3
- package/helpers/cloneConfig.ts +31 -0
- package/helpers/configDataHelpers.ts +128 -0
- package/helpers/configHelpers.ts +27 -0
- package/helpers/constants.ts +5 -2
- package/helpers/coveUpdateWorker.ts +13 -3
- package/helpers/filterColorPalettes.ts +152 -0
- package/helpers/generateColorsArray.ts +13 -0
- package/helpers/getColorPaletteVersion.ts +33 -0
- package/helpers/getPaletteAccessor.ts +18 -0
- package/helpers/markupProcessor.ts +205 -0
- package/helpers/metrics/helpers.ts +42 -19
- package/helpers/metrics/types.ts +48 -9
- package/helpers/metrics/utils.ts +34 -0
- package/helpers/palettes/colorDistributions.ts +56 -0
- package/helpers/palettes/migratePaletteName.ts +150 -0
- package/helpers/palettes/standardizePaletteNames.ts +77 -0
- package/helpers/palettes/utils.ts +267 -0
- package/helpers/queryStringUtils.ts +13 -0
- package/helpers/testing.ts +345 -0
- package/helpers/tests/addValuesToFilters.test.ts +1 -2
- package/helpers/tests/generateColorsArray.test.ts +24 -0
- package/helpers/tests/markupProcessor.test.ts +538 -0
- package/helpers/tests/testStandaloneBuild.ts +44 -0
- package/helpers/useMarkupVariables.ts +31 -0
- package/helpers/vegaConfig.ts +0 -1
- package/helpers/ver/4.24.10.ts +2 -1
- package/helpers/ver/4.24.11.ts +2 -1
- package/helpers/ver/4.24.3.ts +2 -1
- package/helpers/ver/4.24.4.ts +2 -1
- package/helpers/ver/4.24.5.ts +2 -1
- package/helpers/ver/4.24.7.ts +2 -1
- package/helpers/ver/4.24.9.ts +2 -1
- package/helpers/ver/4.25.1.ts +2 -1
- package/helpers/ver/4.25.10.ts +36 -0
- package/helpers/ver/4.25.3.ts +2 -1
- package/helpers/ver/4.25.4.ts +2 -1
- package/helpers/ver/4.25.6.ts +2 -1
- package/helpers/ver/4.25.7.ts +2 -1
- package/helpers/ver/4.25.8.ts +2 -1
- package/helpers/ver/4.25.9.ts +293 -0
- package/helpers/ver/tests/4.25.10.test.ts +204 -0
- package/helpers/ver/tests/4.25.8.test.ts +1 -1
- package/helpers/ver/tests/4.25.9.test.ts +51 -0
- package/hooks/useColorPalette.ts +79 -0
- package/package.json +12 -4
- package/styles/_global.scss +7 -5
- package/styles/base.scss +8 -5
- package/styles/v2/components/button.scss +4 -3
- package/styles/v2/components/editor.scss +2 -1
- package/styles/v2/layout/_data-table.scss +3 -2
- package/styles/v2/themes/_color-definitions.scss +18 -17
- package/testBuild.js +0 -0
- package/testing-setup.js +32 -0
- package/types/MarkupInclude.ts +6 -1
- package/types/MarkupVariable.ts +19 -0
- package/types/VizFilter.ts +1 -0
- package/vitest.config.ts +16 -0
- package/components/ui/_stories/Colors.stories.mdx +0 -220
- package/components/ui/_stories/IconGallery.stories.mdx +0 -14
- package/data/colorPalettes.js +0 -171
- package/helpers/formatConfigBeforeSave.ts +0 -135
- package/helpers/tests/formatConfigBeforeSave.test.ts +0 -68
|
@@ -0,0 +1,128 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Helper functions for stripping and restoring data arrays from config objects
|
|
3
|
+
* to improve performance during cloning operations.
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Strips data arrays from config to improve cloning performance
|
|
8
|
+
*/
|
|
9
|
+
export const stripDataFromConfig = (config: any): { strippedConfig: any; extractedData: any } => {
|
|
10
|
+
const extractedData: any = {}
|
|
11
|
+
const strippedConfig = { ...config }
|
|
12
|
+
|
|
13
|
+
// Extract root-level data arrays
|
|
14
|
+
if (strippedConfig.data) {
|
|
15
|
+
extractedData.data = strippedConfig.data
|
|
16
|
+
delete strippedConfig.data
|
|
17
|
+
}
|
|
18
|
+
if (strippedConfig.formattedData) {
|
|
19
|
+
extractedData.formattedData = strippedConfig.formattedData
|
|
20
|
+
delete strippedConfig.formattedData
|
|
21
|
+
}
|
|
22
|
+
if (strippedConfig.originalFormattedData) {
|
|
23
|
+
extractedData.originalFormattedData = strippedConfig.originalFormattedData
|
|
24
|
+
delete strippedConfig.originalFormattedData
|
|
25
|
+
}
|
|
26
|
+
if (strippedConfig.datasets) {
|
|
27
|
+
extractedData.datasets = strippedConfig.datasets
|
|
28
|
+
delete strippedConfig.datasets
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
// Handle dashboard visualizations
|
|
32
|
+
if (strippedConfig.visualizations) {
|
|
33
|
+
extractedData.visualizations = {}
|
|
34
|
+
Object.keys(strippedConfig.visualizations).forEach(vizKey => {
|
|
35
|
+
const viz = strippedConfig.visualizations[vizKey]
|
|
36
|
+
const vizData: any = {}
|
|
37
|
+
|
|
38
|
+
if (!viz.type) return
|
|
39
|
+
|
|
40
|
+
if (viz.data) {
|
|
41
|
+
vizData.data = viz.data
|
|
42
|
+
delete strippedConfig.visualizations[vizKey].data
|
|
43
|
+
}
|
|
44
|
+
if (viz.formattedData) {
|
|
45
|
+
vizData.formattedData = viz.formattedData
|
|
46
|
+
delete strippedConfig.visualizations[vizKey].formattedData
|
|
47
|
+
}
|
|
48
|
+
if (viz.originalFormattedData) {
|
|
49
|
+
vizData.originalFormattedData = viz.originalFormattedData
|
|
50
|
+
delete strippedConfig.visualizations[vizKey].originalFormattedData
|
|
51
|
+
}
|
|
52
|
+
if (viz.datasets) {
|
|
53
|
+
vizData.datasets = viz.datasets
|
|
54
|
+
delete strippedConfig.visualizations[vizKey].datasets
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
if (Object.keys(vizData).length > 0) {
|
|
58
|
+
extractedData.visualizations[vizKey] = vizData
|
|
59
|
+
}
|
|
60
|
+
})
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
// Handle multiDashboards
|
|
64
|
+
if (strippedConfig.multiDashboards) {
|
|
65
|
+
extractedData.multiDashboards = []
|
|
66
|
+
strippedConfig.multiDashboards.forEach((dashboard, index) => {
|
|
67
|
+
const { strippedConfig: strippedDashboard, extractedData: dashboardData } = stripDataFromConfig(dashboard)
|
|
68
|
+
strippedConfig.multiDashboards[index] = strippedDashboard
|
|
69
|
+
extractedData.multiDashboards[index] = dashboardData
|
|
70
|
+
})
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
return { strippedConfig, extractedData }
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
/**
|
|
77
|
+
* Restores data arrays back to config after updates are complete
|
|
78
|
+
*/
|
|
79
|
+
export const restoreDataToConfig = (config: any, extractedData: any): any => {
|
|
80
|
+
const restoredConfig = { ...config }
|
|
81
|
+
|
|
82
|
+
// Restore root-level data arrays
|
|
83
|
+
if (extractedData.data) {
|
|
84
|
+
restoredConfig.data = extractedData.data
|
|
85
|
+
}
|
|
86
|
+
if (extractedData.formattedData) {
|
|
87
|
+
restoredConfig.formattedData = extractedData.formattedData
|
|
88
|
+
}
|
|
89
|
+
if (extractedData.originalFormattedData) {
|
|
90
|
+
restoredConfig.originalFormattedData = extractedData.originalFormattedData
|
|
91
|
+
}
|
|
92
|
+
if (extractedData.datasets) {
|
|
93
|
+
restoredConfig.datasets = extractedData.datasets
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
// Restore dashboard visualizations data
|
|
97
|
+
if (extractedData.visualizations && restoredConfig.visualizations) {
|
|
98
|
+
Object.keys(extractedData.visualizations).forEach(vizKey => {
|
|
99
|
+
const vizData = extractedData.visualizations[vizKey]
|
|
100
|
+
if (vizData.data) {
|
|
101
|
+
restoredConfig.visualizations[vizKey].data = vizData.data
|
|
102
|
+
}
|
|
103
|
+
if (vizData.formattedData) {
|
|
104
|
+
restoredConfig.visualizations[vizKey].formattedData = vizData.formattedData
|
|
105
|
+
}
|
|
106
|
+
if (vizData.originalFormattedData) {
|
|
107
|
+
restoredConfig.visualizations[vizKey].originalFormattedData = vizData.originalFormattedData
|
|
108
|
+
}
|
|
109
|
+
if (vizData.datasets) {
|
|
110
|
+
restoredConfig.visualizations[vizKey].datasets = vizData.datasets
|
|
111
|
+
}
|
|
112
|
+
})
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
// Restore multiDashboards data
|
|
116
|
+
if (extractedData.multiDashboards && restoredConfig.multiDashboards) {
|
|
117
|
+
extractedData.multiDashboards.forEach((dashboardData, index) => {
|
|
118
|
+
if (dashboardData && Object.keys(dashboardData).length > 0) {
|
|
119
|
+
restoredConfig.multiDashboards[index] = restoreDataToConfig(
|
|
120
|
+
restoredConfig.multiDashboards[index],
|
|
121
|
+
dashboardData
|
|
122
|
+
)
|
|
123
|
+
}
|
|
124
|
+
})
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
return restoredConfig
|
|
128
|
+
}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import cloneConfig from './cloneConfig'
|
|
2
|
+
|
|
3
|
+
/* editConfigKeys
|
|
4
|
+
* Add edit or update config keys
|
|
5
|
+
* keyUpdates: { path: string[], value: any }[]
|
|
6
|
+
* path is the array of keys needed to reach the value to be updated
|
|
7
|
+
* value is the new value to be set
|
|
8
|
+
* if the key does not exist, it will be created
|
|
9
|
+
*/
|
|
10
|
+
export function editConfigKeys<T = any>(config: T, keyUpdates: { path: string[]; value: any }[]): T {
|
|
11
|
+
const configDeepCopy = cloneConfig(config)
|
|
12
|
+
|
|
13
|
+
const newConfig = keyUpdates.reduce((acc, { path, value }) => {
|
|
14
|
+
const pathCopy = [...path]
|
|
15
|
+
const lastKey = pathCopy.pop()
|
|
16
|
+
const target = pathCopy.reduce((target, key) => {
|
|
17
|
+
if (!target[key]) {
|
|
18
|
+
target[key] = {}
|
|
19
|
+
}
|
|
20
|
+
return target[key]
|
|
21
|
+
}, acc)
|
|
22
|
+
target[lastKey] = value
|
|
23
|
+
return acc
|
|
24
|
+
}, configDeepCopy)
|
|
25
|
+
|
|
26
|
+
return newConfig
|
|
27
|
+
}
|
package/helpers/constants.ts
CHANGED
|
@@ -1,6 +1,9 @@
|
|
|
1
1
|
export const APP_FONT_SIZE = 18
|
|
2
|
-
|
|
3
2
|
export const COOL_GRAY_90 = getComputedStyle(document.documentElement).getPropertyValue('--cool-gray-90').trim()
|
|
4
3
|
export const APP_FONT_COLOR = COOL_GRAY_90
|
|
5
|
-
|
|
4
|
+
export const FALLBACK_COLOR_PALETTE_V1 = 'qualitative-bold'
|
|
5
|
+
export const FALLBACK_COLOR_PALETTE_V2 = 'sequential_blue'
|
|
6
6
|
export const EDITOR_WIDTH = 350
|
|
7
|
+
|
|
8
|
+
// Palette migration behavior flag
|
|
9
|
+
export const USE_V2_MIGRATION = true // Set to true to enable v2 migration and conversion modal
|
|
@@ -17,9 +17,15 @@ import update_4_25_4 from './ver/4.25.4'
|
|
|
17
17
|
import update_4_25_6 from './ver/4.25.6'
|
|
18
18
|
import update_4_25_7 from './ver/4.25.7'
|
|
19
19
|
import update_4_25_8 from './ver/4.25.8'
|
|
20
|
+
import update_4_25_9 from './ver/4.25.9'
|
|
21
|
+
import update_4_25_10 from './ver/4.25.10'
|
|
22
|
+
|
|
23
|
+
import { stripDataFromConfig, restoreDataToConfig } from './configDataHelpers'
|
|
20
24
|
|
|
21
25
|
export const coveUpdateWorker = (config, multiDashboardVersion?) => {
|
|
22
|
-
|
|
26
|
+
// Strip data from config for performance
|
|
27
|
+
const { strippedConfig, extractedData } = stripDataFromConfig(config)
|
|
28
|
+
let genConfig = strippedConfig
|
|
23
29
|
|
|
24
30
|
if (multiDashboardVersion) genConfig.version = multiDashboardVersion
|
|
25
31
|
|
|
@@ -36,7 +42,9 @@ export const coveUpdateWorker = (config, multiDashboardVersion?) => {
|
|
|
36
42
|
['4.25.4', update_4_25_4],
|
|
37
43
|
['4.25.6', update_4_25_6],
|
|
38
44
|
['4.25.7', update_4_25_7],
|
|
39
|
-
['4.25.8', update_4_25_8]
|
|
45
|
+
['4.25.8', update_4_25_8],
|
|
46
|
+
['4.25.9', update_4_25_9],
|
|
47
|
+
['4.25.10', update_4_25_10]
|
|
40
48
|
]
|
|
41
49
|
|
|
42
50
|
versions.forEach(([version, updateFunction, alwaysRun]: [string, UpdateFunction, boolean?]) => {
|
|
@@ -54,7 +62,9 @@ export const coveUpdateWorker = (config, multiDashboardVersion?) => {
|
|
|
54
62
|
|
|
55
63
|
// config version is stored at the root level of the config.
|
|
56
64
|
if (multiDashboardVersion) delete genConfig.version
|
|
57
|
-
|
|
65
|
+
|
|
66
|
+
// Restore data arrays after all updates are complete
|
|
67
|
+
return restoreDataToConfig(genConfig, extractedData)
|
|
58
68
|
}
|
|
59
69
|
|
|
60
70
|
export default coveUpdateWorker
|
|
@@ -0,0 +1,152 @@
|
|
|
1
|
+
import { getColorPaletteVersion } from './getColorPaletteVersion'
|
|
2
|
+
import { chartColorPalettes, twoColorPalette } from '../data/colorPalettes'
|
|
3
|
+
|
|
4
|
+
export interface FilterColorPalettesOptions {
|
|
5
|
+
config: any
|
|
6
|
+
isReversed?: boolean
|
|
7
|
+
colorPalettes?: any
|
|
8
|
+
visualizationType?: string
|
|
9
|
+
useV2Migration?: boolean
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
export interface FilteredPalettes {
|
|
13
|
+
sequential: string[]
|
|
14
|
+
nonSequential: string[]
|
|
15
|
+
accessibleColors: string[]
|
|
16
|
+
twoColorPalettes?: string[]
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* Universal color palette filtering function that works across all visualization types
|
|
21
|
+
* Combines chart and map palette filtering logic with backwards compatibility
|
|
22
|
+
*/
|
|
23
|
+
export const filterColorPalettes = ({
|
|
24
|
+
config,
|
|
25
|
+
isReversed,
|
|
26
|
+
colorPalettes,
|
|
27
|
+
visualizationType,
|
|
28
|
+
useV2Migration
|
|
29
|
+
}: FilterColorPalettesOptions): FilteredPalettes => {
|
|
30
|
+
// Use provided colorPalettes or fall back to chart palettes
|
|
31
|
+
const palettes = colorPalettes || chartColorPalettes
|
|
32
|
+
const version = getColorPaletteVersion(config, useV2Migration)
|
|
33
|
+
const versionKey = `v${version}`
|
|
34
|
+
const currentPalettes = palettes[versionKey] || palettes.v2
|
|
35
|
+
|
|
36
|
+
// Handle two-color palettes for specific chart types
|
|
37
|
+
if (visualizationType === 'Paired Bar' || visualizationType === 'Deviation Bar') {
|
|
38
|
+
const twoColorPalettes = filterTwoColorPalettes(version, isReversed)
|
|
39
|
+
return {
|
|
40
|
+
sequential: [],
|
|
41
|
+
nonSequential: [],
|
|
42
|
+
accessibleColors: [],
|
|
43
|
+
twoColorPalettes
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
// Handle regular palette filtering
|
|
48
|
+
const isReversedFromConfig = isReversed !== undefined
|
|
49
|
+
? isReversed
|
|
50
|
+
: config.general?.palette?.isReversed
|
|
51
|
+
|
|
52
|
+
return filterRegularPalettes(currentPalettes, version, isReversedFromConfig)
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
/**
|
|
56
|
+
* Filter two-color palettes (for Paired Bar and Deviation Bar charts)
|
|
57
|
+
*/
|
|
58
|
+
function filterTwoColorPalettes(version: number, isReversed?: boolean): string[] {
|
|
59
|
+
// Use the version to get the correct two-color palettes
|
|
60
|
+
const versionKey = `v${version}`
|
|
61
|
+
const versionedTwoColorPalettes = twoColorPalette[versionKey] || twoColorPalette.v2
|
|
62
|
+
|
|
63
|
+
return Object.keys(versionedTwoColorPalettes).filter(name =>
|
|
64
|
+
isReversed ? name.endsWith('reverse') : !name.endsWith('reverse')
|
|
65
|
+
)
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
/**
|
|
69
|
+
* Filter regular palettes (sequential, non-sequential, accessible)
|
|
70
|
+
*/
|
|
71
|
+
function filterRegularPalettes(palettes: any, version: number, isReversed?: boolean): FilteredPalettes {
|
|
72
|
+
const sequential: string[] = []
|
|
73
|
+
const nonSequential: string[] = []
|
|
74
|
+
const accessibleColors: string[] = []
|
|
75
|
+
|
|
76
|
+
for (const paletteName in palettes) {
|
|
77
|
+
const isPaletteReversed = paletteName.endsWith('reverse')
|
|
78
|
+
const matchesReversed = (!isReversed && !isPaletteReversed) || (isReversed && isPaletteReversed)
|
|
79
|
+
|
|
80
|
+
if (version === 1) {
|
|
81
|
+
filterV1Palette(paletteName, matchesReversed, sequential, nonSequential, accessibleColors)
|
|
82
|
+
} else {
|
|
83
|
+
filterV2Palette(paletteName, matchesReversed, sequential, nonSequential, accessibleColors)
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
return { sequential, nonSequential, accessibleColors }
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
/**
|
|
91
|
+
* Filter V1 palettes using original chart logic
|
|
92
|
+
*/
|
|
93
|
+
function filterV1Palette(
|
|
94
|
+
paletteName: string,
|
|
95
|
+
matchesReversed: boolean,
|
|
96
|
+
sequential: string[],
|
|
97
|
+
nonSequential: string[],
|
|
98
|
+
accessibleColors: string[]
|
|
99
|
+
): void {
|
|
100
|
+
if (!matchesReversed) return
|
|
101
|
+
|
|
102
|
+
if (paletteName.startsWith('sequential')) {
|
|
103
|
+
sequential.push(paletteName)
|
|
104
|
+
} else if (paletteName.startsWith('qualitative') && !paletteName.startsWith('colorblindsafe')) {
|
|
105
|
+
nonSequential.push(paletteName)
|
|
106
|
+
} else if (paletteName.startsWith('colorblindsafe') || paletteName.includes('colorblindsafe')) {
|
|
107
|
+
accessibleColors.push(paletteName)
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
/**
|
|
112
|
+
* Filter V2 palettes using updated logic for new palette structure
|
|
113
|
+
*/
|
|
114
|
+
function filterV2Palette(
|
|
115
|
+
paletteName: string,
|
|
116
|
+
matchesReversed: boolean,
|
|
117
|
+
sequential: string[],
|
|
118
|
+
nonSequential: string[],
|
|
119
|
+
accessibleColors: string[]
|
|
120
|
+
): void {
|
|
121
|
+
if (!matchesReversed) return
|
|
122
|
+
|
|
123
|
+
if (paletteName.startsWith('sequential')) {
|
|
124
|
+
sequential.push(paletteName)
|
|
125
|
+
} else if (paletteName.startsWith('divergent')) {
|
|
126
|
+
nonSequential.push(paletteName)
|
|
127
|
+
} else if (paletteName.includes('colorblindsafe') || paletteName.startsWith('qualitative-standard')) {
|
|
128
|
+
accessibleColors.push(paletteName)
|
|
129
|
+
} else if (paletteName.startsWith('qualitative') && !paletteName.includes('colorblindsafe')) {
|
|
130
|
+
// V2 qualitative palettes go to accessible colors if they're standard
|
|
131
|
+
if (paletteName.includes('standard')) {
|
|
132
|
+
accessibleColors.push(paletteName)
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
/**
|
|
138
|
+
* Legacy function for backwards compatibility with chart package
|
|
139
|
+
*/
|
|
140
|
+
export const filterChartColorPalettes = (config: any) => {
|
|
141
|
+
const version = getColorPaletteVersion(config)
|
|
142
|
+
|
|
143
|
+
if (version === 1) {
|
|
144
|
+
return chartColorPalettes.v1
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
if (version === 2) {
|
|
148
|
+
return chartColorPalettes.v2
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
return chartColorPalettes.v2
|
|
152
|
+
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import chroma from 'chroma-js'
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Generate an array of colors based on a given color [color, hoverColor, darkColor]
|
|
5
|
+
* @param {string} color - The base color to generate the array from (defaults to black)
|
|
6
|
+
* @param {boolean} special - A flag to determine if the hover color should be brighter or saturated
|
|
7
|
+
* @returns {string[]} - An array of colors [baseColor, hoverColor, darkerColor]
|
|
8
|
+
*/
|
|
9
|
+
export const generateColorsArray = (color: string = '#000000', special: boolean = false): string[] => {
|
|
10
|
+
const colorObj = chroma(color)
|
|
11
|
+
const hoverColor = special ? colorObj.brighten(0.5).hex() : colorObj.saturate(1.3).hex()
|
|
12
|
+
return [color, hoverColor, colorObj.darken(0.3).hex()]
|
|
13
|
+
}
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import { USE_V2_MIGRATION } from './constants'
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Gets the color palette version from a visualization config
|
|
5
|
+
* @param config - The visualization config object
|
|
6
|
+
* @param useV2Migration - If provided, overrides the global flag
|
|
7
|
+
* @returns The major version number
|
|
8
|
+
*/
|
|
9
|
+
export const getColorPaletteVersion = (config: any, useV2Migration?: boolean): number => {
|
|
10
|
+
// Use passed parameter or fall back to global flag
|
|
11
|
+
const shouldUseV2 = useV2Migration !== undefined ? useV2Migration : USE_V2_MIGRATION
|
|
12
|
+
|
|
13
|
+
// If not using v2 migration, force v1 unless explicitly set to v2
|
|
14
|
+
if (!shouldUseV2) {
|
|
15
|
+
if (config.general?.palette?.version) {
|
|
16
|
+
return parseInt(config.general.palette.version.split('.')[0])
|
|
17
|
+
}
|
|
18
|
+
return 1
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
// V2 migration logic - If general.palette exists, it's either migrated or new
|
|
22
|
+
if (config.general?.palette) {
|
|
23
|
+
// If version is explicitly set, use it
|
|
24
|
+
if (config.general.palette.version) {
|
|
25
|
+
return parseInt(config.general.palette.version.split('.')[0])
|
|
26
|
+
}
|
|
27
|
+
// If no version but palette exists, it's likely migrated → use v2
|
|
28
|
+
return 2
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
// If no general.palette at all, it's legacy → use v1
|
|
32
|
+
return 1
|
|
33
|
+
}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import { getColorPaletteVersion } from './getColorPaletteVersion'
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Gets the appropriate palette accessor based on config version
|
|
5
|
+
* @param colorPalettes - The color palettes object (e.g., mapColorPalettes, chartColorPalettes)
|
|
6
|
+
* @param config - The visualization config object
|
|
7
|
+
* @param paletteName - Optional palette name to get specific palette
|
|
8
|
+
* @returns The versioned palette accessor or fallback to main palettes, optionally filtered to specific palette
|
|
9
|
+
*/
|
|
10
|
+
export const getPaletteAccessor = (colorPalettes: any, config: any, paletteName?: string) => {
|
|
11
|
+
const paletteAccessor = colorPalettes?.[`v${getColorPaletteVersion(config)}`] || colorPalettes
|
|
12
|
+
|
|
13
|
+
if (paletteName && paletteAccessor) {
|
|
14
|
+
return paletteAccessor[paletteName]
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
return paletteAccessor
|
|
18
|
+
}
|
|
@@ -0,0 +1,205 @@
|
|
|
1
|
+
import _ from 'lodash'
|
|
2
|
+
import { MarkupVariable, MarkupCondition } from '../types/MarkupVariable'
|
|
3
|
+
import { VizFilter } from '../types/VizFilter'
|
|
4
|
+
import { filterVizData } from './filterVizData'
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Replaces {{variable}} tags in content with actual data values.
|
|
8
|
+
*
|
|
9
|
+
* @param content - Content string with markup variables
|
|
10
|
+
* @param data - Dataset to extract values from
|
|
11
|
+
* @param markupVariables - Variable configurations
|
|
12
|
+
* @param options - isEditor, showNoDataMessage, allowHideSection, filters
|
|
13
|
+
* @returns Processed content and state flags
|
|
14
|
+
*
|
|
15
|
+
* @security Returns plain text - must be parsed with html-react-parser before rendering
|
|
16
|
+
*/
|
|
17
|
+
export const processMarkupVariables = (
|
|
18
|
+
content: string,
|
|
19
|
+
data: any[] = [],
|
|
20
|
+
markupVariables: MarkupVariable[] = [],
|
|
21
|
+
options: {
|
|
22
|
+
isEditor?: boolean
|
|
23
|
+
showNoDataMessage?: boolean
|
|
24
|
+
allowHideSection?: boolean
|
|
25
|
+
filters?: VizFilter[]
|
|
26
|
+
} = {}
|
|
27
|
+
): {
|
|
28
|
+
processedContent: string
|
|
29
|
+
shouldHideSection: boolean
|
|
30
|
+
shouldShowNoDataMessage: boolean
|
|
31
|
+
} => {
|
|
32
|
+
const { isEditor = false, showNoDataMessage = false, allowHideSection = false, filters = [] } = options
|
|
33
|
+
|
|
34
|
+
// Early return for invalid inputs
|
|
35
|
+
if (_.isEmpty(markupVariables) || !content) {
|
|
36
|
+
return {
|
|
37
|
+
processedContent: content || '',
|
|
38
|
+
shouldHideSection: false,
|
|
39
|
+
shouldShowNoDataMessage: false
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
// Apply filters to data if filters are present
|
|
44
|
+
let workingData = data
|
|
45
|
+
if (filters && filters.length > 0) {
|
|
46
|
+
workingData = filterVizData(filters, data)
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
try {
|
|
50
|
+
const emptyVariableChecker: boolean[] = []
|
|
51
|
+
const noDataMessageChecker: boolean[] = []
|
|
52
|
+
|
|
53
|
+
const variableRegexPattern = /{{(.*?)}}/g
|
|
54
|
+
const processedContent = content.replace(variableRegexPattern, variableTag => {
|
|
55
|
+
try {
|
|
56
|
+
if (emptyVariableChecker.length > 0) return variableTag
|
|
57
|
+
|
|
58
|
+
const workingVariable = markupVariables.find(variable => variable.tag === variableTag)
|
|
59
|
+
if (!workingVariable) return variableTag
|
|
60
|
+
|
|
61
|
+
// Validate that columnName exists
|
|
62
|
+
if (!workingVariable.columnName) {
|
|
63
|
+
console.warn(`Markup variable ${variableTag} has no columnName specified`)
|
|
64
|
+
return variableTag
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
// Filter data with error handling (apply conditions on top of already filtered data)
|
|
68
|
+
const conditionFilteredData =
|
|
69
|
+
workingVariable.conditions.length === 0
|
|
70
|
+
? workingData
|
|
71
|
+
: filterDataByConditions(workingData, [...workingVariable.conditions])
|
|
72
|
+
|
|
73
|
+
// Extract values with error handling
|
|
74
|
+
const variableValues: string[] = _.uniq(
|
|
75
|
+
(conditionFilteredData || []).map(dataObject => {
|
|
76
|
+
try {
|
|
77
|
+
const dataObjectValue = dataObject[workingVariable.columnName]
|
|
78
|
+
|
|
79
|
+
// Handle undefined column
|
|
80
|
+
if (dataObjectValue === undefined && isEditor) {
|
|
81
|
+
console.warn(
|
|
82
|
+
`Column "${workingVariable.columnName}" not found in data for variable ${variableTag}`
|
|
83
|
+
)
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
return workingVariable.addCommas && !isNaN(parseFloat(dataObjectValue))
|
|
87
|
+
? parseFloat(dataObjectValue).toLocaleString('en-US', { useGrouping: true })
|
|
88
|
+
: String(dataObjectValue || '')
|
|
89
|
+
} catch (error) {
|
|
90
|
+
console.error(`Error processing data value for ${variableTag}:`, error)
|
|
91
|
+
return ''
|
|
92
|
+
}
|
|
93
|
+
})
|
|
94
|
+
).filter(value => value !== '') // Filter out empty values
|
|
95
|
+
|
|
96
|
+
const listConjunction = !isEditor ? 'and' : 'or'
|
|
97
|
+
const formattedValues = formatValuesList(variableValues, listConjunction)
|
|
98
|
+
|
|
99
|
+
const finalDisplay = formattedValues.join(', ')
|
|
100
|
+
|
|
101
|
+
if (showNoDataMessage && finalDisplay === '') {
|
|
102
|
+
noDataMessageChecker.push(true)
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
if (finalDisplay === '' && allowHideSection) {
|
|
106
|
+
emptyVariableChecker.push(true)
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
return finalDisplay
|
|
110
|
+
} catch (error) {
|
|
111
|
+
console.error(`Error processing markup variable ${variableTag}:`, error)
|
|
112
|
+
return variableTag // Return original tag on error
|
|
113
|
+
}
|
|
114
|
+
})
|
|
115
|
+
|
|
116
|
+
return {
|
|
117
|
+
processedContent,
|
|
118
|
+
shouldHideSection: allowHideSection && emptyVariableChecker.length > 0 && !isEditor,
|
|
119
|
+
shouldShowNoDataMessage: showNoDataMessage && noDataMessageChecker.length > 0 && !isEditor
|
|
120
|
+
}
|
|
121
|
+
} catch (error) {
|
|
122
|
+
console.error('Error in processMarkupVariables:', error)
|
|
123
|
+
// Return original content on error
|
|
124
|
+
return {
|
|
125
|
+
processedContent: content,
|
|
126
|
+
shouldHideSection: false,
|
|
127
|
+
shouldShowNoDataMessage: false
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
/**
|
|
133
|
+
* Filters data based on multiple conditions
|
|
134
|
+
*/
|
|
135
|
+
const filterDataByConditions = (data: any[], conditions: MarkupCondition[]): any[] => {
|
|
136
|
+
if (!conditions.length) return data
|
|
137
|
+
|
|
138
|
+
const [currentCondition, ...remainingConditions] = conditions
|
|
139
|
+
const { columnName, isOrIsNotEqualTo, value } = currentCondition
|
|
140
|
+
|
|
141
|
+
const filteredData = isOrIsNotEqualTo === 'is'
|
|
142
|
+
? data.filter(dataObject => String(dataObject[columnName]) === value)
|
|
143
|
+
: data.filter(dataObject => String(dataObject[columnName]) !== value)
|
|
144
|
+
|
|
145
|
+
return remainingConditions.length === 0
|
|
146
|
+
? filteredData
|
|
147
|
+
: filterDataByConditions(filteredData, remainingConditions)
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
/**
|
|
151
|
+
* Formats a list of values with proper conjunction
|
|
152
|
+
*/
|
|
153
|
+
const formatValuesList = (values: string[], conjunction: string): string[] => {
|
|
154
|
+
if (values.length === 0) return values
|
|
155
|
+
if (values.length === 1) return values
|
|
156
|
+
if (values.length === 2) {
|
|
157
|
+
return [`${values[0]} ${conjunction} ${values[1]}`]
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
const formatted = [...values]
|
|
161
|
+
formatted[formatted.length - 1] = `${conjunction} ${formatted[formatted.length - 1]}`
|
|
162
|
+
return [formatted.join(', ')]
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
/**
|
|
166
|
+
* Validates markup variables configuration
|
|
167
|
+
*/
|
|
168
|
+
export const validateMarkupVariables = (
|
|
169
|
+
markupVariables: MarkupVariable[],
|
|
170
|
+
data: any[]
|
|
171
|
+
): string[] => {
|
|
172
|
+
const errors: string[] = []
|
|
173
|
+
|
|
174
|
+
if (!markupVariables || !Array.isArray(markupVariables)) {
|
|
175
|
+
return errors
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
const availableColumns = data.length > 0 ? Object.keys(data[0]) : []
|
|
179
|
+
|
|
180
|
+
markupVariables.forEach((variable, index) => {
|
|
181
|
+
if (!variable.tag || !variable.tag.match(/^{{.+}}$/)) {
|
|
182
|
+
errors.push(`Variable ${index + 1}: Tag must be in format {{tagName}}`)
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
if (!variable.columnName) {
|
|
186
|
+
errors.push(`Variable ${index + 1}: Column name is required`)
|
|
187
|
+
} else if (availableColumns.length > 0 && !availableColumns.includes(variable.columnName)) {
|
|
188
|
+
errors.push(`Variable ${index + 1}: Column "${variable.columnName}" not found in data`)
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
variable.conditions.forEach((condition, condIndex) => {
|
|
192
|
+
if (!condition.columnName) {
|
|
193
|
+
errors.push(`Variable ${index + 1}, Condition ${condIndex + 1}: Column name is required`)
|
|
194
|
+
} else if (availableColumns.length > 0 && !availableColumns.includes(condition.columnName)) {
|
|
195
|
+
errors.push(`Variable ${index + 1}, Condition ${condIndex + 1}: Column "${condition.columnName}" not found in data`)
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
if (!condition.value) {
|
|
199
|
+
errors.push(`Variable ${index + 1}, Condition ${condIndex + 1}: Value is required`)
|
|
200
|
+
}
|
|
201
|
+
})
|
|
202
|
+
})
|
|
203
|
+
|
|
204
|
+
return errors
|
|
205
|
+
}
|