@cdc/chart 4.26.2 → 4.26.3
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/LICENSE +201 -0
- package/dist/cdcchart.js +35674 -32430
- package/examples/data/data-with-metadata.json +10 -0
- package/examples/feature/pie/planet-pie-example-config.json +2 -1
- package/examples/metadata-variables.json +58 -0
- package/package.json +3 -3
- package/src/CdcChart.tsx +8 -4
- package/src/CdcChartComponent.tsx +321 -288
- package/src/_stories/Chart.CustomColors.stories.tsx +74 -0
- package/src/_stories/Chart.Defaults.stories.tsx +95 -0
- package/src/_stories/Chart.SmallestLeftAxisMax.stories.tsx +64 -0
- package/src/_stories/Chart.stories.tsx +36 -2
- package/src/_stories/ChartBar.Editor.stories.tsx +97 -38
- package/src/_stories/ChartBrush.Editor.stories.tsx +11 -25
- package/src/_stories/ChartEditor.Editor.stories.tsx +1 -1
- package/src/_stories/_mock/paired-bar-abbr.json +421 -0
- package/src/_stories/_mock/pie_custom_colors.json +268 -0
- package/src/_stories/_mock/smallest_left_axis_max.json +104 -0
- package/src/components/Annotations/components/AnnotationDraggable.styles.css +10 -10
- package/src/components/Annotations/components/AnnotationDropdown.styles.css +1 -1
- package/src/components/Annotations/components/AnnotationList.styles.css +11 -11
- package/src/components/Axis/BottomAxis.tsx +10 -3
- package/src/components/Axis/PairedBarAxis.tsx +10 -4
- package/src/components/BarChart/components/BarChart.Horizontal.tsx +12 -28
- package/src/components/BarChart/components/BarChart.StackedHorizontal.tsx +12 -30
- package/src/components/BarChart/components/BarChart.StackedVertical.tsx +12 -31
- package/src/components/BarChart/components/BarChart.Vertical.tsx +12 -28
- package/src/components/BarChart/helpers/getPatternUrl.ts +94 -0
- package/src/components/BarChart/helpers/tests/getPatternUrl.test.ts +134 -0
- package/src/components/BarChart/helpers/useBarChart.ts +3 -0
- package/src/components/Brush/BrushSelector.tsx +2 -1
- package/src/components/Brush/MiniChartPreview.tsx +21 -26
- package/src/components/EditorPanel/EditorPanel.tsx +56 -43
- package/src/components/EditorPanel/components/Panels/Panel.Annotate.tsx +9 -9
- package/src/components/EditorPanel/components/Panels/Panel.ForestPlotSettings.tsx +0 -78
- package/src/components/EditorPanel/components/Panels/Panel.General.tsx +39 -1
- package/src/components/EditorPanel/components/Panels/Panel.PatternSettings.tsx +24 -42
- package/src/components/EditorPanel/components/Panels/Panel.Series.tsx +83 -2
- package/src/components/EditorPanel/components/Panels/Panel.Visual.tsx +45 -42
- package/src/components/EditorPanel/editor-panel.scss +1 -1
- package/src/components/ForestPlot/ForestPlot.tsx +26 -22
- package/src/components/Legend/LegendGroup/LegendGroup.styles.css +4 -4
- package/src/components/Legend/helpers/createFormatLabels.tsx +3 -2
- package/src/components/LinearChart/tests/LinearChart.test.tsx +77 -0
- package/src/components/LinearChart/tests/mockConfigContext.ts +2 -0
- package/src/components/LinearChart.tsx +26 -6
- package/src/components/PieChart/PieChart.tsx +19 -4
- package/src/components/RadarChart/RadarChart.tsx +1 -1
- package/src/components/Regions/components/Regions.tsx +6 -6
- package/src/components/Sankey/components/Sankey.tsx +3 -3
- package/src/components/Sankey/sankey.scss +1 -1
- package/src/components/SmallMultiples/SmallMultiples.css +5 -5
- package/src/components/Sparkline/index.scss +4 -2
- package/src/components/WarmingStripes/WarmingStripesGradientLegend.css +8 -8
- package/src/data/initial-state.js +23 -14
- package/src/data/legacy-defaults.ts +18 -0
- package/src/helpers/abbreviateNumber.ts +24 -17
- package/src/helpers/getChartPatternId.ts +17 -0
- package/src/helpers/getMinMax.ts +16 -2
- package/src/helpers/seriesColumnSettings.ts +114 -0
- package/src/helpers/tests/countNumOfTicks.test.ts +77 -0
- package/src/helpers/tests/seriesColumnSettings.test.ts +84 -0
- package/src/hooks/useRightAxis.ts +14 -0
- package/src/hooks/useScales.ts +92 -56
- package/src/hooks/useTooltip.tsx +20 -3
- package/src/scss/main.scss +152 -79
- package/src/test/CdcChart.test.jsx +2 -2
- package/src/types/ChartConfig.ts +4 -0
- package/tests/fixtures/chart-config-with-metadata.json +29 -0
- package/tests/fixtures/data-with-metadata.json +10 -0
|
@@ -1,7 +1,9 @@
|
|
|
1
1
|
import type { Meta, StoryObj } from '@storybook/react-vite'
|
|
2
2
|
import Chart from '../CdcChartComponent'
|
|
3
3
|
import scatterPlotCustomColorConfig from './_mock/scatterplot_mock.json'
|
|
4
|
+
import pieCustomColorConfig from './_mock/pie_custom_colors.json'
|
|
4
5
|
import { assertVisualizationRendered } from '@cdc/core/helpers/testing'
|
|
6
|
+
import { expect, userEvent, waitFor } from 'storybook/test'
|
|
5
7
|
|
|
6
8
|
const meta: Meta<typeof Chart> = {
|
|
7
9
|
title: 'Components/Templates/Chart/Custom Colors',
|
|
@@ -20,4 +22,76 @@ export const ScatterPlot: Story = {
|
|
|
20
22
|
}
|
|
21
23
|
}
|
|
22
24
|
|
|
25
|
+
const pieCustomColors = ['#0B6E4F', '#C75000', '#7C4DFF', '#0077B6', '#D7263D']
|
|
26
|
+
const pieFallbackCustomColors = ['#004E89', '#1A659E', '#47A8BD', '#D1495B', '#EDAE49']
|
|
27
|
+
|
|
28
|
+
const normalizeColor = (color: string) => {
|
|
29
|
+
const swatch = document.createElement('div')
|
|
30
|
+
swatch.style.color = color
|
|
31
|
+
document.body.appendChild(swatch)
|
|
32
|
+
const normalized = getComputedStyle(swatch).color
|
|
33
|
+
swatch.remove()
|
|
34
|
+
return normalized
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
const assertPieCustomColorStory = async (canvasElement: HTMLElement, expectedColors: string[]) => {
|
|
38
|
+
await assertVisualizationRendered(canvasElement)
|
|
39
|
+
|
|
40
|
+
const chart = canvasElement.querySelector('svg.animated-pie')
|
|
41
|
+
const legend = canvasElement.querySelector('.legend-container')
|
|
42
|
+
expect(chart).toBeTruthy()
|
|
43
|
+
expect(legend).toBeTruthy()
|
|
44
|
+
|
|
45
|
+
await waitFor(() => {
|
|
46
|
+
expect(chart?.querySelectorAll('path')).toHaveLength(expectedColors.length)
|
|
47
|
+
})
|
|
48
|
+
|
|
49
|
+
const legendItems = Array.from(legend!.querySelectorAll('[role="button"]')) as HTMLElement[]
|
|
50
|
+
expect(legendItems).toHaveLength(expectedColors.length)
|
|
51
|
+
|
|
52
|
+
const sliceColors = Array.from(chart!.querySelectorAll('path')).map(path =>
|
|
53
|
+
normalizeColor(path.getAttribute('fill') || '')
|
|
54
|
+
)
|
|
55
|
+
expect(sliceColors).toEqual(expectedColors.map(normalizeColor))
|
|
56
|
+
|
|
57
|
+
const legendColors = legendItems.map(item => {
|
|
58
|
+
const swatch = item.querySelector('span.legend-item') as HTMLElement | null
|
|
59
|
+
return normalizeColor(swatch ? getComputedStyle(swatch).backgroundColor : '')
|
|
60
|
+
})
|
|
61
|
+
expect(legendColors).toEqual(expectedColors.map(normalizeColor))
|
|
62
|
+
|
|
63
|
+
await userEvent.click(legendItems[0])
|
|
64
|
+
|
|
65
|
+
await waitFor(() => {
|
|
66
|
+
expect(chart?.querySelectorAll('path')).toHaveLength(1)
|
|
67
|
+
expect(legendItems[0].className).toContain('highlighted')
|
|
68
|
+
expect(legendItems[1].className).toContain('inactive')
|
|
69
|
+
})
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
export const PieChart: Story = {
|
|
73
|
+
args: {
|
|
74
|
+
config: pieCustomColorConfig,
|
|
75
|
+
isEditor: false
|
|
76
|
+
},
|
|
77
|
+
play: async ({ canvasElement }) => {
|
|
78
|
+
await assertPieCustomColorStory(canvasElement, pieCustomColors)
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
const pieChartCustomColorsFallbackConfig = JSON.parse(JSON.stringify(pieCustomColorConfig))
|
|
83
|
+
pieChartCustomColorsFallbackConfig.title = 'Pie Chart Custom Colors Fallback'
|
|
84
|
+
pieChartCustomColorsFallbackConfig.general.palette.customColors = pieFallbackCustomColors
|
|
85
|
+
delete pieChartCustomColorsFallbackConfig.general.palette.customColorsOrdered
|
|
86
|
+
|
|
87
|
+
export const PieChartCustomColorsFallback: Story = {
|
|
88
|
+
args: {
|
|
89
|
+
config: pieChartCustomColorsFallbackConfig,
|
|
90
|
+
isEditor: false
|
|
91
|
+
},
|
|
92
|
+
play: async ({ canvasElement }) => {
|
|
93
|
+
await assertPieCustomColorStory(canvasElement, pieFallbackCustomColors)
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
|
|
23
97
|
export default meta
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
import type { Meta, StoryObj } from '@storybook/react-vite'
|
|
2
|
+
import { expect } from 'storybook/test'
|
|
3
|
+
import Chart from '../CdcChart'
|
|
4
|
+
import simplifiedLine from './_mock/simplified_line.json'
|
|
5
|
+
import { editConfigKeys } from '@cdc/core/helpers/configHelpers'
|
|
6
|
+
import { assertVisualizationRendered, waitForPresence } from '@cdc/core/helpers/testing'
|
|
7
|
+
|
|
8
|
+
const meta: Meta<typeof Chart> = {
|
|
9
|
+
title: 'Components/Templates/Chart/Defaults',
|
|
10
|
+
component: Chart
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
type Story = StoryObj<typeof Chart>
|
|
14
|
+
|
|
15
|
+
const oldConfig = editConfigKeys(simplifiedLine, [
|
|
16
|
+
{ path: ['yAxis', 'hideAxis'], value: false },
|
|
17
|
+
{ path: ['yAxis', 'hideTicks'], value: false },
|
|
18
|
+
{ path: ['yAxis', 'gridLines'], value: false },
|
|
19
|
+
{ path: ['yAxis', 'numTicks'], value: '' },
|
|
20
|
+
{ path: ['xAxis', 'numTicks'], value: '' },
|
|
21
|
+
{ path: ['table', 'expanded'], value: true },
|
|
22
|
+
{ path: ['legend', 'position'], value: 'right' },
|
|
23
|
+
{ path: ['dataFormat', 'commas'], value: false },
|
|
24
|
+
{ path: ['tooltips', 'dateDisplayFormat'], value: '' }
|
|
25
|
+
])
|
|
26
|
+
|
|
27
|
+
const newConfig = editConfigKeys(simplifiedLine, [
|
|
28
|
+
{ path: ['yAxis', 'hideAxis'], value: true },
|
|
29
|
+
{ path: ['yAxis', 'hideTicks'], value: true },
|
|
30
|
+
{ path: ['yAxis', 'gridLines'], value: true },
|
|
31
|
+
{ path: ['yAxis', 'numTicks'], value: 4 },
|
|
32
|
+
{ path: ['xAxis', 'numTicks'], value: 6 },
|
|
33
|
+
{ path: ['table', 'expanded'], value: false },
|
|
34
|
+
{ path: ['legend', 'position'], value: 'top' },
|
|
35
|
+
{ path: ['dataFormat', 'commas'], value: true },
|
|
36
|
+
{ path: ['tooltips', 'dateDisplayFormat'], value: '%B %-d, %Y' }
|
|
37
|
+
])
|
|
38
|
+
|
|
39
|
+
export const OldConfig_Preserves_Legacy_Defaults: Story = {
|
|
40
|
+
args: {
|
|
41
|
+
config: oldConfig,
|
|
42
|
+
isEditor: false
|
|
43
|
+
},
|
|
44
|
+
play: async ({ canvasElement }) => {
|
|
45
|
+
await assertVisualizationRendered(canvasElement)
|
|
46
|
+
|
|
47
|
+
await waitForPresence('.legend-container', canvasElement)
|
|
48
|
+
const legend = canvasElement.querySelector('.legend-container')
|
|
49
|
+
expect(legend).toBeInTheDocument()
|
|
50
|
+
expect(legend?.classList.contains('right')).toBe(true)
|
|
51
|
+
|
|
52
|
+
const axisLine = canvasElement.querySelector('.left-axis line[stroke="#000"]')
|
|
53
|
+
expect(axisLine).toBeInTheDocument()
|
|
54
|
+
|
|
55
|
+
const gridlines = canvasElement.querySelectorAll('.left-axis line[stroke="#d6d6d6"]')
|
|
56
|
+
expect(gridlines.length).toBe(0)
|
|
57
|
+
|
|
58
|
+
const dataTable = canvasElement.querySelector('.data-table')
|
|
59
|
+
expect(dataTable).toBeInTheDocument()
|
|
60
|
+
expect(dataTable?.getAttribute('hidden')).toBeNull()
|
|
61
|
+
|
|
62
|
+
const tickTexts = canvasElement.querySelectorAll('.left-axis .vx-axis-tick text')
|
|
63
|
+
for (const tick of tickTexts) {
|
|
64
|
+
expect(tick.textContent).not.toMatch(/,/)
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
export const NewConfig_Gets_New_Defaults: Story = {
|
|
70
|
+
args: {
|
|
71
|
+
config: newConfig,
|
|
72
|
+
isEditor: false
|
|
73
|
+
},
|
|
74
|
+
play: async ({ canvasElement }) => {
|
|
75
|
+
await assertVisualizationRendered(canvasElement)
|
|
76
|
+
|
|
77
|
+
await waitForPresence('.legend-container', canvasElement)
|
|
78
|
+
const legend = canvasElement.querySelector('.legend-container')
|
|
79
|
+
expect(legend).toBeInTheDocument()
|
|
80
|
+
expect(legend?.classList.contains('top')).toBe(true)
|
|
81
|
+
|
|
82
|
+
const axisLine = canvasElement.querySelector('.left-axis line[stroke="#000"]')
|
|
83
|
+
expect(axisLine).toBeNull()
|
|
84
|
+
|
|
85
|
+
await waitForPresence('.left-axis line[stroke="#d6d6d6"]', canvasElement)
|
|
86
|
+
const gridlines = canvasElement.querySelectorAll('.left-axis line[stroke="#d6d6d6"]')
|
|
87
|
+
expect(gridlines.length).toBeGreaterThan(0)
|
|
88
|
+
|
|
89
|
+
const dataTable = canvasElement.querySelector('.data-table')
|
|
90
|
+
expect(dataTable).toBeInTheDocument()
|
|
91
|
+
expect(dataTable?.getAttribute('hidden')).not.toBeNull()
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
export default meta
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
import type { Meta, StoryObj } from '@storybook/react-vite'
|
|
2
|
+
import Chart from '../CdcChartComponent'
|
|
3
|
+
import { editConfigKeys } from '@cdc/core/helpers/configHelpers'
|
|
4
|
+
import smallestLeftAxisMaxConfig from './_mock/smallest_left_axis_max.json'
|
|
5
|
+
import { assertVisualizationRendered } from '@cdc/core/helpers/testing'
|
|
6
|
+
|
|
7
|
+
const meta: Meta<typeof Chart> = {
|
|
8
|
+
title: 'Components/Templates/Chart/Smallest Left Axis Maximum',
|
|
9
|
+
component: Chart
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
type Story = StoryObj<typeof Chart>
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* Region B is the default filter — data only goes up to 1.
|
|
16
|
+
* Without smallestLeftAxisMax, the Y axis would show decimal ticks (0, 0.2, 0.4…).
|
|
17
|
+
* With smallestLeftAxisMax: 5, the axis extends to at least 5, producing clean integer ticks.
|
|
18
|
+
*/
|
|
19
|
+
export const WithSmallestLeftAxisMax: Story = {
|
|
20
|
+
args: {
|
|
21
|
+
config: smallestLeftAxisMaxConfig
|
|
22
|
+
},
|
|
23
|
+
play: async ({ canvasElement }) => {
|
|
24
|
+
await assertVisualizationRendered(canvasElement)
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* Same chart without the smallestLeftAxisMax setting.
|
|
30
|
+
* Region B data (max 1) causes decimal ticks on the Y axis — the problem this feature solves.
|
|
31
|
+
*/
|
|
32
|
+
export const WithoutSmallestLeftAxisMax: Story = {
|
|
33
|
+
args: {
|
|
34
|
+
config: editConfigKeys(smallestLeftAxisMaxConfig, [{ path: ['yAxis', 'smallestLeftAxisMax'], value: undefined }])
|
|
35
|
+
},
|
|
36
|
+
play: async ({ canvasElement }) => {
|
|
37
|
+
await assertVisualizationRendered(canvasElement)
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* Region A has data up to 28, well above the smallestLeftAxisMax of 5.
|
|
43
|
+
* The axis naturally scales to the data — the setting has no effect here.
|
|
44
|
+
*/
|
|
45
|
+
export const LargerDataUnaffected: Story = {
|
|
46
|
+
args: {
|
|
47
|
+
config: editConfigKeys(smallestLeftAxisMaxConfig, [{ path: ['filters', '0', 'active'], value: 'Region A' }])
|
|
48
|
+
},
|
|
49
|
+
play: async ({ canvasElement }) => {
|
|
50
|
+
await assertVisualizationRendered(canvasElement)
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
/**
|
|
55
|
+
* Editor view so you can see the Smallest Left Axis Maximum field in the Left Value Axis section.
|
|
56
|
+
*/
|
|
57
|
+
export const Editor: Story = {
|
|
58
|
+
args: {
|
|
59
|
+
config: smallestLeftAxisMaxConfig,
|
|
60
|
+
isEditor: true
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
export default meta
|
|
@@ -6,6 +6,7 @@ import lineChartTwoPointsNewChart from './_mock/line_chart_two_points_new_chart.
|
|
|
6
6
|
import lollipop from './_mock/lollipop.json'
|
|
7
7
|
import forestPlot from '../../examples/feature/forest-plot/forest-plot.json'
|
|
8
8
|
import pairedBar from './_mock/paired-bar.json'
|
|
9
|
+
import pairedBarAbbreviated from './_mock/paired-bar-abbr.json'
|
|
9
10
|
import horizontalBarConfig from './_mock/horizontal_bar.json'
|
|
10
11
|
import horizontalBarsDynamicYAxis from './_mock/horizontal-bars-dynamic-y-axis.json'
|
|
11
12
|
import barChartLabels from './_mock/barchart_labels.mock.json'
|
|
@@ -14,7 +15,8 @@ import pieCalculatedArea from './_mock/pie_calculated_area.json'
|
|
|
14
15
|
import areaChartStacked from './_mock/area_chart_stacked.json'
|
|
15
16
|
import multipleLines from './_mock/short_dates.json'
|
|
16
17
|
import { editConfigKeys } from '@cdc/core/helpers/configHelpers'
|
|
17
|
-
import { assertVisualizationRendered } from '@cdc/core/helpers/testing'
|
|
18
|
+
import { assertVisualizationRendered, waitForPresence } from '@cdc/core/helpers/testing'
|
|
19
|
+
import { expect } from 'storybook/test'
|
|
18
20
|
|
|
19
21
|
const meta: Meta<typeof Chart> = {
|
|
20
22
|
title: 'Components/Templates/Chart',
|
|
@@ -63,7 +65,8 @@ export const Lollipop: Story = {
|
|
|
63
65
|
|
|
64
66
|
export const Forest_Plot: Story = {
|
|
65
67
|
args: {
|
|
66
|
-
config: forestPlot
|
|
68
|
+
config: forestPlot,
|
|
69
|
+
isEditor: true
|
|
67
70
|
},
|
|
68
71
|
play: async ({ canvasElement }) => {
|
|
69
72
|
await assertVisualizationRendered(canvasElement)
|
|
@@ -127,6 +130,16 @@ export const Paired_Bar: Story = {
|
|
|
127
130
|
}
|
|
128
131
|
}
|
|
129
132
|
|
|
133
|
+
export const Paired_Bar_Year_Tick_Format_Regression: Story = {
|
|
134
|
+
args: {
|
|
135
|
+
config: pairedBarAbbreviated,
|
|
136
|
+
isEditor: true
|
|
137
|
+
},
|
|
138
|
+
play: async ({ canvasElement }) => {
|
|
139
|
+
await assertVisualizationRendered(canvasElement)
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
|
|
130
143
|
export const Area_Chart_stacked: Story = {
|
|
131
144
|
args: {
|
|
132
145
|
config: areaChartStacked,
|
|
@@ -137,4 +150,25 @@ export const Area_Chart_stacked: Story = {
|
|
|
137
150
|
}
|
|
138
151
|
}
|
|
139
152
|
|
|
153
|
+
export const Metadata_In_Description: Story = {
|
|
154
|
+
args: {
|
|
155
|
+
configUrl: '/packages/chart/tests/fixtures/chart-config-with-metadata.json'
|
|
156
|
+
},
|
|
157
|
+
play: async ({ canvasElement }) => {
|
|
158
|
+
await assertVisualizationRendered(canvasElement)
|
|
159
|
+
const subtext = await waitForPresence('.subtext', canvasElement)
|
|
160
|
+
expect(subtext?.textContent).toContain('January 15, 2026')
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
export const Metadata_Backward_Compat_Plain_Array: Story = {
|
|
165
|
+
args: {
|
|
166
|
+
config: lineChartTwoPointsRegressionTest,
|
|
167
|
+
isEditor: false
|
|
168
|
+
},
|
|
169
|
+
play: async ({ canvasElement }) => {
|
|
170
|
+
await assertVisualizationRendered(canvasElement)
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
|
|
140
174
|
export default meta
|
|
@@ -92,7 +92,7 @@ export const BarGeneralTests: Story = {
|
|
|
92
92
|
|
|
93
93
|
const getChartSubtypeVisualization = () => {
|
|
94
94
|
// Target the chart visualization SVG specifically, not editor UI icons
|
|
95
|
-
const chartContainer = canvasElement.querySelector('.cove-
|
|
95
|
+
const chartContainer = canvasElement.querySelector('.cove-visualization__body, .chart-container, .visualization')
|
|
96
96
|
const svg = chartContainer?.querySelector('svg') || canvasElement.querySelector('svg:not(.icon)')
|
|
97
97
|
const legendContainer = canvasElement.querySelector('.legend, [class*="legend"]')
|
|
98
98
|
|
|
@@ -160,7 +160,7 @@ export const BarGeneralTests: Story = {
|
|
|
160
160
|
|
|
161
161
|
const getOrientationVisualization = () => {
|
|
162
162
|
// Target the chart visualization SVG specifically, not editor UI icons
|
|
163
|
-
const chartContainer = canvasElement.querySelector('.cove-
|
|
163
|
+
const chartContainer = canvasElement.querySelector('.cove-visualization__body, .chart-container, .visualization')
|
|
164
164
|
const svg = chartContainer?.querySelector('svg') || canvasElement.querySelector('svg:not(.icon)')
|
|
165
165
|
|
|
166
166
|
// Look for bar elements - different structures for horizontal vs vertical
|
|
@@ -273,7 +273,7 @@ export const BarGeneralTests: Story = {
|
|
|
273
273
|
|
|
274
274
|
const getBarStyleVisualization = () => {
|
|
275
275
|
// Target the chart visualization SVG specifically, not editor UI icons
|
|
276
|
-
const chartContainer = canvasElement.querySelector('.cove-
|
|
276
|
+
const chartContainer = canvasElement.querySelector('.cove-visualization__body, .chart-container, .visualization')
|
|
277
277
|
const svg = chartContainer?.querySelector('svg') || canvasElement.querySelector('svg:not(.icon)')
|
|
278
278
|
|
|
279
279
|
return {
|
|
@@ -528,7 +528,7 @@ export const BarLeftValueAxisTests: Story = {
|
|
|
528
528
|
|
|
529
529
|
const getAxisTypeVisualization = () => {
|
|
530
530
|
// Target the chart visualization SVG specifically, not editor UI icons
|
|
531
|
-
const chartContainer = canvasElement.querySelector('.cove-
|
|
531
|
+
const chartContainer = canvasElement.querySelector('.cove-visualization__body, .chart-container, .visualization')
|
|
532
532
|
const svg = chartContainer?.querySelector('svg') || canvasElement.querySelector('svg:not(.icon)')
|
|
533
533
|
|
|
534
534
|
// Find the left axis specifically
|
|
@@ -693,7 +693,7 @@ export const BarLeftValueAxisTests: Story = {
|
|
|
693
693
|
|
|
694
694
|
const getAxisLabelVisualization = () => {
|
|
695
695
|
// Target the chart visualization SVG specifically, not editor UI icons
|
|
696
|
-
const chartContainer = canvasElement.querySelector('.cove-
|
|
696
|
+
const chartContainer = canvasElement.querySelector('.cove-visualization__body, .chart-container, .visualization')
|
|
697
697
|
const svg = chartContainer?.querySelector('svg') || canvasElement.querySelector('svg:not(.icon)')
|
|
698
698
|
|
|
699
699
|
// Find Y-axis label elements with multiple possible selectors
|
|
@@ -795,7 +795,9 @@ export const BarLeftValueAxisTests: Story = {
|
|
|
795
795
|
expect(yAxisLabelInput.value).toBe('Custom Y-Axis Label')
|
|
796
796
|
|
|
797
797
|
// Debug: Log the label element to confirm selection
|
|
798
|
-
const chartContainer = canvasElement.querySelector(
|
|
798
|
+
const chartContainer = canvasElement.querySelector(
|
|
799
|
+
'.cove-visualization__body, .chart-container, .visualization'
|
|
800
|
+
)
|
|
799
801
|
const svg = chartContainer?.querySelector('svg') || canvasElement.querySelector('svg:not(.icon)')
|
|
800
802
|
const labelElement = svg?.querySelector('text.y-label')
|
|
801
803
|
|
|
@@ -853,7 +855,7 @@ export const BarLeftValueAxisTests: Story = {
|
|
|
853
855
|
|
|
854
856
|
const getInlineLabelVisualization = () => {
|
|
855
857
|
// Target the chart visualization SVG specifically, not editor UI icons
|
|
856
|
-
const chartContainer = canvasElement.querySelector('.cove-
|
|
858
|
+
const chartContainer = canvasElement.querySelector('.cove-visualization__body, .chart-container, .visualization')
|
|
857
859
|
const svg = chartContainer?.querySelector('svg') || canvasElement.querySelector('svg:not(.icon)')
|
|
858
860
|
|
|
859
861
|
// Find the Y-axis area to locate the top tick area
|
|
@@ -989,7 +991,7 @@ export const BarLeftValueAxisTests: Story = {
|
|
|
989
991
|
|
|
990
992
|
const getNumTicksVisualization = () => {
|
|
991
993
|
// Target the chart visualization SVG specifically, not editor UI icons
|
|
992
|
-
const chartContainer = canvasElement.querySelector('.cove-
|
|
994
|
+
const chartContainer = canvasElement.querySelector('.cove-visualization__body, .chart-container, .visualization')
|
|
993
995
|
const svg = chartContainer?.querySelector('svg') || canvasElement.querySelector('svg:not(.icon)')
|
|
994
996
|
|
|
995
997
|
// Find the left axis specifically
|
|
@@ -1190,7 +1192,7 @@ export const DateCategoryAxisSectionTests: StoryObj<typeof Chart> = {
|
|
|
1190
1192
|
// Method 2: Look for SVG in chart container areas
|
|
1191
1193
|
if (!svgElement) {
|
|
1192
1194
|
const chartContainer = canvasElement.querySelector(
|
|
1193
|
-
'.cove-
|
|
1195
|
+
'.cove-visualization__body, .chart-container, .visualization, .linear'
|
|
1194
1196
|
)
|
|
1195
1197
|
if (chartContainer) {
|
|
1196
1198
|
svgElement = chartContainer.querySelector('svg')
|
|
@@ -1541,7 +1543,7 @@ export const BarRegionsSectionTests: Story = {
|
|
|
1541
1543
|
await performAndAssert(
|
|
1542
1544
|
'Configure Fixed-to-Fixed region with label and colors',
|
|
1543
1545
|
() => {
|
|
1544
|
-
const chartContainer = canvasElement.querySelector('.cove-
|
|
1546
|
+
const chartContainer = canvasElement.querySelector('.cove-visualization__body')
|
|
1545
1547
|
const chartSvg = chartContainer?.querySelector('svg')
|
|
1546
1548
|
const regionElements = chartSvg?.querySelectorAll('rect[fill*="rgba"], rect[style*="rgba"]') || []
|
|
1547
1549
|
|
|
@@ -1680,6 +1682,17 @@ export const BarColumnsSectionTests: Story = {
|
|
|
1680
1682
|
// Open Columns accordion
|
|
1681
1683
|
await openAccordion(canvas, 'Columns')
|
|
1682
1684
|
|
|
1685
|
+
const getFirstColumnConfig = async () => {
|
|
1686
|
+
const columnSelect = (await canvas.findAllByLabelText(/^column$/i))[0]
|
|
1687
|
+
const fieldset = columnSelect.closest('fieldset')
|
|
1688
|
+
|
|
1689
|
+
if (!fieldset) {
|
|
1690
|
+
throw new Error('Unable to find the first column configuration fieldset')
|
|
1691
|
+
}
|
|
1692
|
+
|
|
1693
|
+
return within(fieldset as HTMLElement)
|
|
1694
|
+
}
|
|
1695
|
+
|
|
1683
1696
|
// Test 1: Add first column configuration and verify fields appear
|
|
1684
1697
|
await performAndAssert(
|
|
1685
1698
|
'Add first column configuration and verify fields appear',
|
|
@@ -1712,9 +1725,10 @@ export const BarColumnsSectionTests: Story = {
|
|
|
1712
1725
|
await performAndAssert(
|
|
1713
1726
|
'Configure column with data column and custom label',
|
|
1714
1727
|
() => {
|
|
1715
|
-
const
|
|
1728
|
+
const fieldsets = canvasElement.querySelectorAll('fieldset.edit-block')
|
|
1729
|
+
const labelInput = fieldsets[0]?.querySelector('input[name*="label"]') as HTMLInputElement | null
|
|
1716
1730
|
return {
|
|
1717
|
-
labelValue:
|
|
1731
|
+
labelValue: labelInput?.value || ''
|
|
1718
1732
|
}
|
|
1719
1733
|
},
|
|
1720
1734
|
async () => {
|
|
@@ -1724,8 +1738,8 @@ export const BarColumnsSectionTests: Story = {
|
|
|
1724
1738
|
await userEvent.selectOptions(columnSelect, 'Year')
|
|
1725
1739
|
|
|
1726
1740
|
// Set custom label
|
|
1727
|
-
const
|
|
1728
|
-
const labelInput =
|
|
1741
|
+
const firstColumnConfig = await getFirstColumnConfig()
|
|
1742
|
+
const labelInput = await firstColumnConfig.findByLabelText(/^label$/i)
|
|
1729
1743
|
await userEvent.clear(labelInput)
|
|
1730
1744
|
await userEvent.type(labelInput, 'Report Year')
|
|
1731
1745
|
},
|
|
@@ -1741,39 +1755,42 @@ export const BarColumnsSectionTests: Story = {
|
|
|
1741
1755
|
await performAndAssert(
|
|
1742
1756
|
'Enable tooltip display and configure number formatting',
|
|
1743
1757
|
() => {
|
|
1744
|
-
const
|
|
1745
|
-
const
|
|
1758
|
+
const firstColumnSelect = canvas.queryAllByLabelText(/^column$/i)[0]
|
|
1759
|
+
const fieldset = firstColumnSelect?.closest('fieldset')
|
|
1760
|
+
const scopedFieldset = fieldset ? within(fieldset as HTMLElement) : null
|
|
1761
|
+
const tooltipCheckbox = scopedFieldset?.queryByLabelText(/show in tooltip/i) as HTMLInputElement | null
|
|
1762
|
+
const commasCheckbox = scopedFieldset?.queryByLabelText(/add commas to numbers/i) as HTMLInputElement | null
|
|
1763
|
+
const prefixInput = scopedFieldset?.queryByLabelText(/^prefix$/i) as HTMLInputElement | null
|
|
1764
|
+
const suffixInput = scopedFieldset?.queryByLabelText(/^suffix$/i) as HTMLInputElement | null
|
|
1746
1765
|
|
|
1747
1766
|
return {
|
|
1748
|
-
tooltipChecked:
|
|
1749
|
-
commasChecked:
|
|
1767
|
+
tooltipChecked: tooltipCheckbox?.checked || false,
|
|
1768
|
+
commasChecked: commasCheckbox?.checked || false,
|
|
1769
|
+
prefixValue: prefixInput?.value || '',
|
|
1770
|
+
suffixValue: suffixInput?.value || ''
|
|
1750
1771
|
}
|
|
1751
1772
|
},
|
|
1752
1773
|
async () => {
|
|
1774
|
+
const firstColumnConfig = await getFirstColumnConfig()
|
|
1775
|
+
|
|
1753
1776
|
// Enable tooltip display
|
|
1754
|
-
const
|
|
1755
|
-
const tooltipCheckbox = tooltipCheckboxes[0] as HTMLInputElement
|
|
1777
|
+
const tooltipCheckbox = (await firstColumnConfig.findByLabelText(/show in tooltip/i)) as HTMLInputElement
|
|
1756
1778
|
if (!tooltipCheckbox.checked) {
|
|
1757
1779
|
await userEvent.click(tooltipCheckbox)
|
|
1758
1780
|
}
|
|
1759
1781
|
|
|
1760
1782
|
// Enable commas for numbers
|
|
1761
|
-
const
|
|
1762
|
-
const commasCheckbox = commasCheckboxes[0] as HTMLInputElement
|
|
1783
|
+
const commasCheckbox = (await firstColumnConfig.findByLabelText(/add commas to numbers/i)) as HTMLInputElement
|
|
1763
1784
|
if (!commasCheckbox.checked) {
|
|
1764
1785
|
await userEvent.click(commasCheckbox)
|
|
1765
1786
|
}
|
|
1766
1787
|
|
|
1767
1788
|
// Add prefix and suffix
|
|
1768
|
-
const
|
|
1769
|
-
|
|
1770
|
-
const prefixInput = prefixInputs[1]
|
|
1771
|
-
|
|
1789
|
+
const prefixInput = await firstColumnConfig.findByLabelText(/^prefix$/i)
|
|
1772
1790
|
await userEvent.clear(prefixInput)
|
|
1773
1791
|
await userEvent.type(prefixInput, 'Year: ')
|
|
1774
1792
|
|
|
1775
|
-
const
|
|
1776
|
-
const suffixInput = suffixInputs[1]
|
|
1793
|
+
const suffixInput = await firstColumnConfig.findByLabelText(/^suffix$/i)
|
|
1777
1794
|
await userEvent.clear(suffixInput)
|
|
1778
1795
|
await userEvent.type(suffixInput, ' AD')
|
|
1779
1796
|
},
|
|
@@ -1781,6 +1798,8 @@ export const BarColumnsSectionTests: Story = {
|
|
|
1781
1798
|
// Checkboxes should be enabled
|
|
1782
1799
|
expect(after.tooltipChecked).toBe(true)
|
|
1783
1800
|
expect(after.commasChecked).toBe(true)
|
|
1801
|
+
expect(after.prefixValue).toBe('Year: ')
|
|
1802
|
+
expect(after.suffixValue).toBe(' AD')
|
|
1784
1803
|
|
|
1785
1804
|
return true
|
|
1786
1805
|
}
|
|
@@ -1905,7 +1924,7 @@ export const BarLegendTests: Story = {
|
|
|
1905
1924
|
const rightLegend = canvasElement.querySelector('.legend-container.right')
|
|
1906
1925
|
const bottomLegend = canvasElement.querySelector('.legend-container.bottom')
|
|
1907
1926
|
const topLegend = canvasElement.querySelector('.legend-container.top')
|
|
1908
|
-
const chartContainer = canvasElement.querySelector('.cove-
|
|
1927
|
+
const chartContainer = canvasElement.querySelector('.cove-visualization__body, .chart-container, .visualization')
|
|
1909
1928
|
|
|
1910
1929
|
return {
|
|
1911
1930
|
hasLeftLegend: !!leftLegend,
|
|
@@ -2385,7 +2404,7 @@ export const BarFiltersTests: Story = {
|
|
|
2385
2404
|
// Helper function to get chart data visualization state
|
|
2386
2405
|
// Tests VISUALIZATION OUTPUT (filtered data in chart) not control state
|
|
2387
2406
|
const getChartDataState = () => {
|
|
2388
|
-
const chartContainer = canvasElement.querySelector('.cove-
|
|
2407
|
+
const chartContainer = canvasElement.querySelector('.cove-visualization__body, .chart-container, .visualization')
|
|
2389
2408
|
const svg = chartContainer?.querySelector('svg') || canvasElement.querySelector('svg:not(.icon)')
|
|
2390
2409
|
const bars = svg?.querySelectorAll('rect[class*="bar"], rect[data-testid*="bar"], g[class*="bar"] rect') || []
|
|
2391
2410
|
const filtersList = canvasElement.querySelector('.draggable-field-list')
|
|
@@ -2687,7 +2706,7 @@ export const BarVisualTests: Story = {
|
|
|
2687
2706
|
// Helper function to capture animation visualization state
|
|
2688
2707
|
const getAnimationVisualizationState = () => {
|
|
2689
2708
|
// Find the actual chart SVG, not UI icons or other SVGs
|
|
2690
|
-
const chartContainer = canvasElement.querySelector('.cove-
|
|
2709
|
+
const chartContainer = canvasElement.querySelector('.cove-visualization__body, .chart-container, .visualization')
|
|
2691
2710
|
const chartSvg = chartContainer?.querySelector('svg') || canvasElement.querySelector('svg:not(.icon)')
|
|
2692
2711
|
|
|
2693
2712
|
// Animation affects SVG classes and chart elements
|
|
@@ -2802,12 +2821,12 @@ export const BarVisualTests: Story = {
|
|
|
2802
2821
|
|
|
2803
2822
|
// Helper function to capture bar border visualization state
|
|
2804
2823
|
const getBarBorderVisualizationState = () => {
|
|
2805
|
-
const chartContainer = canvasElement.querySelector('.cove-
|
|
2824
|
+
const chartContainer = canvasElement.querySelector('.cove-visualization__body, .chart-container, .visualization')
|
|
2806
2825
|
const chartSvg = chartContainer?.querySelector('svg') || canvasElement.querySelector('svg:not(.icon)')
|
|
2807
2826
|
|
|
2808
2827
|
// Find bar elements in the chart
|
|
2809
2828
|
const barElements =
|
|
2810
|
-
chartSvg?.querySelectorAll('rect[class*="bar"], path[class*="bar"], g[class*="bar"]
|
|
2829
|
+
chartSvg?.querySelectorAll('rect[class*="bar"], path[class*="bar"], g[class*="bar"] path') || []
|
|
2811
2830
|
|
|
2812
2831
|
// Check for border-related styles and attributes
|
|
2813
2832
|
const barsWithStroke = Array.from(barElements).filter(bar => {
|
|
@@ -2910,10 +2929,11 @@ export const BarPatternSettingsTests: Story = {
|
|
|
2910
2929
|
type: 'continuous',
|
|
2911
2930
|
dataKey: 'y1'
|
|
2912
2931
|
},
|
|
2932
|
+
series: [{ dataKey: 'y1' }, { dataKey: 'y2' }, { dataKey: 'y3' }, { dataKey: 'y4' }],
|
|
2913
2933
|
// Override with data suitable for pattern testing
|
|
2914
2934
|
data: [
|
|
2915
2935
|
{ category: 'Q1', y1: 19000, y2: 47000, y3: 59000, y4: 91000 },
|
|
2916
|
-
{ category: 'Q2', y1: 18000, y2: 32000, y3:
|
|
2936
|
+
{ category: 'Q2', y1: 18000, y2: 32000, y3: 19000, y4: 89000 },
|
|
2917
2937
|
{ category: 'Q3', y1: 7000, y2: 38000, y3: 74000, y4: 89000 },
|
|
2918
2938
|
{ category: 'Q4', y1: 15000, y2: 41000, y3: 67000, y4: 95000 }
|
|
2919
2939
|
],
|
|
@@ -2938,7 +2958,7 @@ export const BarPatternSettingsTests: Story = {
|
|
|
2938
2958
|
|
|
2939
2959
|
// Helper function to capture SVG pattern visualization state
|
|
2940
2960
|
const getPatternVisualizationState = () => {
|
|
2941
|
-
const chartContainer = canvasElement.querySelector('.cove-
|
|
2961
|
+
const chartContainer = canvasElement.querySelector('.cove-visualization__body, .chart-container, .visualization')
|
|
2942
2962
|
const chartSvg = chartContainer?.querySelector('svg') || canvasElement.querySelector('svg:not(.icon)')
|
|
2943
2963
|
|
|
2944
2964
|
// Find SVG <defs> section with pattern definitions
|
|
@@ -2948,8 +2968,10 @@ export const BarPatternSettingsTests: Story = {
|
|
|
2948
2968
|
// Find pattern overlays (visual application of patterns)
|
|
2949
2969
|
const patternOverlays = chartSvg?.querySelectorAll('.pattern-overlay') || []
|
|
2950
2970
|
|
|
2951
|
-
// Find bars with pattern fills
|
|
2952
|
-
const barsWithPatterns = chartSvg?.querySelectorAll('rect
|
|
2971
|
+
// Find bars with pattern fills (works for both fragment refs and absolute URL refs)
|
|
2972
|
+
const barsWithPatterns = Array.from(chartSvg?.querySelectorAll('path, rect') || []).filter(shape =>
|
|
2973
|
+
(shape.getAttribute('fill') || '').includes('chart-pattern-')
|
|
2974
|
+
)
|
|
2953
2975
|
|
|
2954
2976
|
// Get pattern configuration UI state
|
|
2955
2977
|
const patternConfigSections = canvasElement.querySelectorAll('.accordion__panel .accordion .accordion__item')
|
|
@@ -3103,6 +3125,43 @@ export const BarPatternSettingsTests: Story = {
|
|
|
3103
3125
|
}
|
|
3104
3126
|
)
|
|
3105
3127
|
|
|
3128
|
+
await performAndAssert(
|
|
3129
|
+
'Clear Pattern Data Key - Existing data value applies across all series',
|
|
3130
|
+
getPatternVisualizationState,
|
|
3131
|
+
async () => {
|
|
3132
|
+
const dataKeyDropdown = canvasElement.querySelector('select[id*="pattern-datakey-"]') as HTMLSelectElement
|
|
3133
|
+
|
|
3134
|
+
if (dataKeyDropdown) {
|
|
3135
|
+
await userEvent.selectOptions(dataKeyDropdown, '')
|
|
3136
|
+
}
|
|
3137
|
+
},
|
|
3138
|
+
(before, after) => {
|
|
3139
|
+
// Clearing data key should keep value matching active and broaden the match across series.
|
|
3140
|
+
expect(after.hasBarsWithPatterns).toBe(true)
|
|
3141
|
+
expect(after.barsWithPatternsCount).toBeGreaterThan(before.barsWithPatternsCount)
|
|
3142
|
+
|
|
3143
|
+
return true
|
|
3144
|
+
}
|
|
3145
|
+
)
|
|
3146
|
+
|
|
3147
|
+
await performAndAssert(
|
|
3148
|
+
'Clear Pattern Data Value - Pattern stops rendering when value is empty',
|
|
3149
|
+
getPatternVisualizationState,
|
|
3150
|
+
async () => {
|
|
3151
|
+
const dataValueInput = canvasElement.querySelector('input[id*="pattern-datavalue-"]') as HTMLInputElement
|
|
3152
|
+
|
|
3153
|
+
if (dataValueInput) {
|
|
3154
|
+
await userEvent.clear(dataValueInput)
|
|
3155
|
+
}
|
|
3156
|
+
},
|
|
3157
|
+
(before, after) => {
|
|
3158
|
+
expect(before.barsWithPatternsCount).toBeGreaterThan(0)
|
|
3159
|
+
expect(after.barsWithPatternsCount).toBe(0)
|
|
3160
|
+
|
|
3161
|
+
return true
|
|
3162
|
+
}
|
|
3163
|
+
)
|
|
3164
|
+
|
|
3106
3165
|
// ========================================================================
|
|
3107
3166
|
// Test Pattern Size Configuration - Pattern Density Changes
|
|
3108
3167
|
// Tests how pattern size affects visual pattern rendering
|
|
@@ -3402,7 +3461,7 @@ export const BarTextAnnotationsTests: Story = {
|
|
|
3402
3461
|
|
|
3403
3462
|
// Helper function to capture SVG annotation visualization state
|
|
3404
3463
|
const getAnnotationVisualizationState = () => {
|
|
3405
|
-
const chartContainer = canvasElement.querySelector('.cove-
|
|
3464
|
+
const chartContainer = canvasElement.querySelector('.cove-visualization__body, .chart-container, .visualization')
|
|
3406
3465
|
const chartSvg = chartContainer?.querySelector('svg') || canvasElement.querySelector('svg:not(.icon)')
|
|
3407
3466
|
|
|
3408
3467
|
// Find annotation accordion sections (nested accordions for each annotation)
|