Skip to content

Commit d027bbc

Browse files
authored
Merge pull request #4619 from VisActor/feat/4615-linear-x-auto-bar-width
feat: support linear x bar auto width
2 parents 79bcd08 + 6b715f0 commit d027bbc

5 files changed

Lines changed: 334 additions & 50 deletions

File tree

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
{
2+
"changes": [
3+
{
4+
"comment": "feat: support automatic bar width for linear x axis",
5+
"type": "minor",
6+
"packageName": "@visactor/vchart"
7+
}
8+
],
9+
"packageName": "@visactor/vchart",
10+
"email": "lixuef1313@163.com"
11+
}

packages/vchart/__tests__/unit/chart/bar.test.ts

Lines changed: 164 additions & 46 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ import { GlobalScale } from '../../../src/scale/global-scale';
44
import { EventDispatcher } from '../../../src/event/event-dispatcher';
55
import type { BarSeries } from '../../../src';
66
// eslint-disable-next-line no-duplicate-imports
7-
import { BarChart } from '../../../src';
7+
import { BarChart, CommonChart } from '../../../src';
88
import { DataSet } from '@visactor/vdataset';
99
import { createCanvas, removeDom } from '../../util/dom';
1010
import { getTheme, initChartDataSet } from '../../util/context';
@@ -74,7 +74,7 @@ const spec = {
7474

7575
describe('Bar chart test', () => {
7676
let canvasDom: HTMLCanvasElement;
77-
let chart: BarChart;
77+
let chart: BarChart | CommonChart;
7878
beforeEach(() => {
7979
canvasDom = createCanvas();
8080
canvasDom.style.position = 'relative';
@@ -85,41 +85,116 @@ describe('Bar chart test', () => {
8585
});
8686

8787
afterEach(() => {
88+
chart?.release?.();
89+
chart = null;
8890
removeDom(canvasDom);
8991
});
9092

91-
test('Bar chart init', () => {
93+
const createBarChart = (chartSpec: Record<string, unknown>) => {
9294
const transformer = new BarChart.transformerConstructor({
9395
type: 'bar',
9496
seriesType: 'bar',
9597
getTheme: getTheme,
9698
mode: 'desktop-browser'
9799
});
98-
const info = transformer.initChartSpec(spec as any);
99-
chart = new BarChart(
100-
spec as any,
100+
const info = transformer.initChartSpec(chartSpec as never);
101+
const barChart = new BarChart(
102+
chartSpec as never,
101103
{
102104
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
103105
// @ts-ignore
104-
eventDispatcher: new EventDispatcher({} as any, { addEventListener: () => {} } as any),
106+
eventDispatcher: new EventDispatcher({} as never, { addEventListener: () => {} } as never),
105107
globalInstance: {
106108
isAnimationEnable: () => true,
107109
getContainer: () => ({}),
108110
getTooltipHandlerByUser: (() => undefined) as () => undefined
109111
},
110-
render: {} as any,
112+
render: {} as never,
111113
dataSet,
112114
map: new Map(),
113115
container: null,
114116
mode: 'desktop-browser',
115117
getCompiler: getTestCompiler,
116-
globalScale: new GlobalScale([], { getAllSeries: () => [] as any[] } as any),
118+
globalScale: new GlobalScale([], { getAllSeries: (): never[] => [] } as never),
117119
getTheme: getTheme,
118120
getSpecInfo: () => info
119-
} as any
121+
} as never
120122
);
121-
chart.created(transformer);
122-
chart.init();
123+
barChart.created(transformer);
124+
barChart.init();
125+
return barChart;
126+
};
127+
128+
const createCommonChart = (chartSpec: Record<string, unknown>) => {
129+
const transformer = new CommonChart.transformerConstructor({
130+
type: 'common',
131+
getTheme: getTheme,
132+
mode: 'desktop-browser'
133+
});
134+
const info = transformer.initChartSpec(chartSpec as never);
135+
const commonChart = new CommonChart(
136+
chartSpec as never,
137+
{
138+
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
139+
// @ts-ignore
140+
eventDispatcher: new EventDispatcher({} as never, { addEventListener: () => {} } as never),
141+
globalInstance: {
142+
isAnimationEnable: () => true,
143+
getContainer: () => ({}),
144+
getTooltipHandlerByUser: (() => undefined) as () => undefined
145+
},
146+
render: {} as never,
147+
dataSet,
148+
map: new Map(),
149+
container: null,
150+
mode: 'desktop-browser',
151+
getCompiler: getTestCompiler,
152+
globalScale: new GlobalScale([], { getAllSeries: (): never[] => [] } as never),
153+
getTheme: getTheme,
154+
getSpecInfo: () => info
155+
} as never
156+
);
157+
commonChart.created(transformer);
158+
commonChart.init();
159+
return commonChart;
160+
};
161+
162+
const getLinearBarWidth = (extraSpec: Record<string, unknown> = {}) => {
163+
const linearSpec = {
164+
type: 'bar',
165+
data: {
166+
values: [
167+
{ x: 0, y: 10 },
168+
{ x: 10, y: 20 },
169+
{ x: 20, y: 12 }
170+
]
171+
},
172+
xField: 'x',
173+
yField: 'y',
174+
axes: [
175+
{ orient: 'bottom', type: 'linear' },
176+
{ orient: 'left', type: 'linear' }
177+
],
178+
...extraSpec
179+
};
180+
chart = createBarChart(linearSpec);
181+
const series = chart.getAllSeries()[0] as BarSeries;
182+
const xScale = series.getXAxisHelper().getScale(0) as {
183+
domain: (domain: number[]) => void;
184+
range: (range: number[]) => void;
185+
};
186+
xScale.domain([0, 20]);
187+
xScale.range([0, 200]);
188+
189+
return (
190+
series.getMarkInName('bar') as {
191+
getAttribute: (key: string, datum: unknown) => unknown;
192+
}
193+
).getAttribute('width', series.getViewData().latestData[0]) as number;
194+
};
195+
196+
test('Bar chart init', () => {
197+
chart = createBarChart(spec);
123198

124199
// spec
125200
const transformSpec = chart.getSpec();
@@ -138,7 +213,8 @@ describe('Bar chart test', () => {
138213
});
139214

140215
test('Bar chart updateSpec', () => {
141-
chart.updateSpec(spec as any);
216+
chart = createBarChart(spec);
217+
chart.updateSpec(spec as never);
142218

143219
expect(chart.getAllSeries().length).toEqual(1);
144220
const series: BarSeries = chart.getAllSeries()[0] as BarSeries;
@@ -171,48 +247,90 @@ describe('Bar chart test', () => {
171247
yField: 'value',
172248
seriesField: 'type'
173249
};
174-
const transformer = new BarChart.transformerConstructor({
175-
type: 'bar',
176-
seriesType: 'bar',
177-
getTheme: getTheme,
178-
mode: 'desktop-browser'
179-
});
180-
const info = transformer.initChartSpec(stackSpec as any);
181-
chart = new BarChart(
182-
stackSpec as any,
183-
{
184-
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
185-
// @ts-ignore
186-
eventDispatcher: new EventDispatcher({} as any, { addEventListener: () => {} } as any),
187-
globalInstance: {
188-
isAnimationEnable: () => true,
189-
getContainer: () => ({}),
190-
getTooltipHandlerByUser: (() => undefined) as () => undefined
191-
},
192-
render: {} as any,
193-
dataSet,
194-
map: new Map(),
195-
container: null,
196-
mode: 'desktop-browser',
197-
getCompiler: getTestCompiler,
198-
globalScale: new GlobalScale([], { getAllSeries: () => [] as any[] } as any),
199-
getTheme: getTheme,
200-
getSpecInfo: () => info
201-
} as any
202-
);
203-
chart.created(transformer);
204-
chart.init();
250+
chart = createBarChart(stackSpec);
205251

206252
const series: BarSeries = chart.getAllSeries()[0] as BarSeries;
207-
const barMark = series.getMarkInName('bar') as any;
253+
const barMark = series.getMarkInName('bar') as unknown as {
254+
_markConfig: {
255+
clipPath: () => Array<{ attribute: { x: number; y: number; y1: number; width: number } }>;
256+
};
257+
};
208258
const clipPaths = barMark._markConfig.clipPath();
209259

210260
expect(clipPaths.length).toBeGreaterThan(0);
211-
clipPaths.forEach((path: any) => {
261+
clipPaths.forEach(path => {
212262
expect(Number.isFinite(path.attribute.x)).toBe(true);
213263
expect(Number.isFinite(path.attribute.y)).toBe(true);
214264
expect(Number.isFinite(path.attribute.y1)).toBe(true);
215265
expect(Number.isFinite(path.attribute.width)).toBe(true);
216266
});
217267
});
268+
269+
test('linear x axis bar uses adjacent data spacing as automatic width base', () => {
270+
expect(getLinearBarWidth()).toBe(50);
271+
});
272+
273+
test('linear x axis bar respects numeric barWidth', () => {
274+
expect(getLinearBarWidth({ barWidth: 18 })).toBe(18);
275+
});
276+
277+
test('linear x axis bar resolves percent barWidth from automatic width base', () => {
278+
expect(getLinearBarWidth({ barWidth: '25%' })).toBe(25);
279+
});
280+
281+
test('linear x axis common chart bar series share automatic width base', () => {
282+
chart = createCommonChart({
283+
type: 'common',
284+
data: [
285+
{
286+
id: 'barA',
287+
values: [
288+
{ x: 0, y: 10 },
289+
{ x: 10, y: 20 }
290+
]
291+
},
292+
{
293+
id: 'barB',
294+
values: [
295+
{ x: 11, y: 12 },
296+
{ x: 20, y: 18 }
297+
]
298+
},
299+
{
300+
id: 'line',
301+
values: [
302+
{ x: 10.2, y: 30 },
303+
{ x: 10.3, y: 36 }
304+
]
305+
}
306+
],
307+
series: [
308+
{ type: 'bar', dataId: 'barA', xField: 'x', yField: 'y' },
309+
{ type: 'bar', dataId: 'barB', xField: 'x', yField: 'y' },
310+
{ type: 'line', dataId: 'line', xField: 'x', yField: 'y' }
311+
],
312+
axes: [
313+
{ orient: 'bottom', type: 'linear' },
314+
{ orient: 'left', type: 'linear' }
315+
]
316+
});
317+
318+
const barSeriesList = chart.getAllSeries().filter(series => series.type === 'bar') as BarSeries[];
319+
const xScale = barSeriesList[0].getXAxisHelper().getScale(0) as {
320+
domain: (domain: number[]) => void;
321+
range: (range: number[]) => void;
322+
};
323+
xScale.domain([0, 20]);
324+
xScale.range([0, 200]);
325+
326+
const getWidth = (series: BarSeries) =>
327+
(
328+
series.getMarkInName('bar') as {
329+
getAttribute: (key: string, datum: unknown) => unknown;
330+
}
331+
).getAttribute('width', series.getViewData().latestData[0]) as number;
332+
333+
expect(getWidth(barSeriesList[0])).toBe(5);
334+
expect(getWidth(barSeriesList[1])).toBe(5);
335+
});
218336
});

0 commit comments

Comments
 (0)