Skip to content

Commit 2aeb1cf

Browse files
authored
Merge pull request #4606 from VisActor/fix/pie-legend-angle-animation
Fix/pie legend angle animation
2 parents 7773a28 + 3193dee commit 2aeb1cf

3 files changed

Lines changed: 227 additions & 2 deletions

File tree

packages/vchart/__tests__/unit/animation/manual-ticker.test.ts

Lines changed: 200 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import VChart, {
66
type ILineChartSpec,
77
type IMark,
88
type IMarkGraphic,
9+
type IPieChartSpec,
910
type ISeries,
1011
type IWordCloudChartSpec
1112
} from '../../../src';
@@ -218,6 +219,52 @@ const getGraphicDatum = (graphic: AnimatedGraphic) => {
218219

219220
const getStateDatum = (datum: any) => (Array.isArray(datum) ? datum[0] : datum);
220221

222+
const createCustomGroupSpec = (x: number): IBarChartSpec =>
223+
({
224+
type: 'bar',
225+
data: [
226+
{
227+
id: 'data',
228+
values: [{ x: 'A', y: 1 }]
229+
}
230+
],
231+
xField: 'x',
232+
yField: 'y',
233+
animation: true,
234+
customMark: [
235+
{
236+
type: 'group',
237+
name: 'customGroup',
238+
animation: true,
239+
animationUpdate: false,
240+
style: {
241+
x,
242+
y: 20,
243+
width: 40,
244+
height: 30
245+
}
246+
}
247+
]
248+
} as unknown as IBarChartSpec);
249+
250+
const getCustomGroupGraphic = (chart: VChart) => {
251+
const mark = (chart.getChart() as any).getAllMarks().find((m: IMark) => m.name === 'customGroup');
252+
253+
expect(mark).toBeDefined();
254+
if (!mark) {
255+
throw new Error('Expected custom group mark to exist');
256+
}
257+
258+
const group = mark.getGraphics?.()[0] as AnimatedGraphic;
259+
260+
expect(group).toBeDefined();
261+
if (!group) {
262+
throw new Error('Expected custom group graphic to exist');
263+
}
264+
265+
return group;
266+
};
267+
221268
const getBarGraphicByDatum = (chart: VChart, predicate: (datum: any) => boolean) => {
222269
const bar = getBarGraphics(chart).find(graphic => predicate(getGraphicDatum(graphic)));
223270

@@ -308,6 +355,25 @@ const getBarClipPathRects = (chart: VChart) =>
308355

309356
const getBarClipPathGraphics = (chart: VChart) => (getBarMarkProduct(chart).attribute.path ?? []) as AnimatedGraphic[];
310357

358+
const getPieGraphics = (chart: VChart) => {
359+
const model = chart.getChart() as IChart;
360+
const pieSeries = model.getAllSeries().find((series: ISeries) => series.type === 'pie');
361+
362+
expect(pieSeries).toBeDefined();
363+
if (!pieSeries) {
364+
throw new Error('Expected pie series to exist');
365+
}
366+
367+
const pieMark = pieSeries.getMarks().find((mark: IMark) => mark.name === 'pie');
368+
369+
expect(pieMark).toBeDefined();
370+
if (!pieMark) {
371+
throw new Error('Expected pie mark to exist');
372+
}
373+
374+
return pieMark.getGraphics() as AnimatedGraphic[];
375+
};
376+
311377
const clickLegendItem = (chart: VChart, index: number) => {
312378
const legendModel = chart.getComponents().find((component: any) => component.type === 'discreteLegend') as any;
313379
const legendComponent = legendModel?._legendComponent;
@@ -469,6 +535,57 @@ const createSeriesChangeDocBarSpec = (mode: 'grouped' | 'single') => {
469535
} as unknown as IBarChartSpec;
470536
};
471537

538+
const createPieLegendFilterSpec = (): IPieChartSpec =>
539+
({
540+
type: 'pie',
541+
width: 500,
542+
height: 500,
543+
padding: 0,
544+
data: [
545+
{
546+
id: 'id0',
547+
values: [
548+
{ group: '0', value: 455, labelValue: '455', dataIndex: 0, seriesIndex: 0, name: '满意' },
549+
{ group: '0', value: 655, labelValue: '655', dataIndex: 1, seriesIndex: 0, name: '一般' },
550+
{ group: '0', value: 160, labelValue: '160', dataIndex: 2, seriesIndex: 0, name: '差评' }
551+
]
552+
}
553+
],
554+
categoryField: 'dataIndex',
555+
valueField: 'value',
556+
startAngle: -90,
557+
endAngle: -90,
558+
animation: true,
559+
animationAppear: {
560+
duration: APPEAR_DURATION,
561+
easing: 'linear'
562+
},
563+
animationUpdate: false,
564+
animationEnter: false,
565+
animationExit: false,
566+
animationDisappear: false,
567+
legends: {
568+
orient: 'bottom',
569+
position: 'middle',
570+
visible: true,
571+
allowAllCanceled: false,
572+
item: {
573+
visible: true
574+
}
575+
},
576+
pie: {
577+
state: {
578+
hover: {
579+
outerRadius: 1.06
580+
}
581+
},
582+
style: {
583+
cursor: 'pointer',
584+
lineWidth: 2
585+
}
586+
}
587+
} as unknown as IPieChartSpec);
588+
472589
const createLineGrowthSpec = (values: Array<{ time: string; value: number }>) =>
473590
({
474591
type: 'line',
@@ -1282,6 +1399,89 @@ const hasRenderableBarGeometry = (graphic: AnimatedGraphic) => {
12821399
};
12831400

12841401
describe('manual ticker animation regressions', () => {
1402+
it('keeps custom group final attributes after a prevented update animation', () => {
1403+
const { container, dom } = createChartContainer();
1404+
const ticker = createManualTicker();
1405+
const chart = new VChart(createCustomGroupSpec(20), {
1406+
dom,
1407+
ticker,
1408+
animation: true
1409+
});
1410+
1411+
chart.renderSync();
1412+
1413+
try {
1414+
ticker.tickAt(APPEAR_DURATION + 50);
1415+
1416+
chart.updateSpecSync(createCustomGroupSpec(80));
1417+
1418+
const group = getCustomGroupGraphic(chart);
1419+
1420+
expectClose(group.attribute.x, 80);
1421+
expectClose(group.baseAttributes?.x, 80);
1422+
expectClose(getGraphicFinalAttribute(group).x, 80);
1423+
} finally {
1424+
chart.release();
1425+
ticker.release();
1426+
removeDom(container);
1427+
}
1428+
});
1429+
1430+
it('keeps pie arc angles stable after legend filtering and hover state changes', () => {
1431+
const { container, dom } = createChartContainer();
1432+
const ticker = createManualTicker();
1433+
const chart = new VChart(createPieLegendFilterSpec(), {
1434+
dom,
1435+
ticker,
1436+
animation: true
1437+
});
1438+
1439+
chart.renderSync();
1440+
1441+
try {
1442+
ticker.tickAt(APPEAR_DURATION + 50);
1443+
1444+
chart.setLegendSelectedDataByIndex(0, [0, 1]);
1445+
chart.renderSync();
1446+
1447+
const updateStart = ticker.getTime();
1448+
ticker.tickAt(updateStart + UPDATE_DURATION + 50);
1449+
1450+
const secondPie = getPieGraphics(chart).find(graphic => graphic.context.data[0]?.dataIndex === 1);
1451+
const expectedStartAngle = -Math.PI / 2 + (Math.PI * 2 * 455) / (455 + 655);
1452+
const expectedEndAngle = -Math.PI / 2 + Math.PI * 2;
1453+
1454+
expect(secondPie).toBeDefined();
1455+
if (!secondPie) {
1456+
throw new Error('Expected second pie graphic to exist');
1457+
}
1458+
1459+
expectClose(secondPie.attribute.startAngle, expectedStartAngle);
1460+
expectClose(secondPie.attribute.endAngle, expectedEndAngle);
1461+
expectClose(secondPie.baseAttributes?.startAngle, expectedStartAngle);
1462+
expectClose(secondPie.baseAttributes?.endAngle, expectedEndAngle);
1463+
expectClose(getGraphicFinalAttribute(secondPie).startAngle, expectedStartAngle);
1464+
expectClose(getGraphicFinalAttribute(secondPie).endAngle, expectedEndAngle);
1465+
1466+
secondPie.useStates(['hover']);
1467+
expectClose(secondPie.attribute.startAngle, expectedStartAngle);
1468+
expectClose(secondPie.attribute.endAngle, expectedEndAngle);
1469+
1470+
ticker.tickAt(ticker.getTime() + UPDATE_DURATION + 50);
1471+
1472+
expectClose(secondPie.attribute.startAngle, expectedStartAngle);
1473+
expectClose(secondPie.attribute.endAngle, expectedEndAngle);
1474+
expectClose(secondPie.baseAttributes?.startAngle, expectedStartAngle);
1475+
expectClose(secondPie.baseAttributes?.endAngle, expectedEndAngle);
1476+
expectClose(getGraphicFinalAttribute(secondPie).startAngle, expectedStartAngle);
1477+
expectClose(getGraphicFinalAttribute(secondPie).endAngle, expectedEndAngle);
1478+
} finally {
1479+
chart.release();
1480+
ticker.release();
1481+
removeDom(container);
1482+
}
1483+
});
1484+
12851485
it('runs word cloud scaleIn appear from zero scale', () => {
12861486
const { container, dom } = createChartContainer();
12871487
const ticker = createManualTicker();

packages/vchart/src/mark/base/base-mark.ts

Lines changed: 21 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1774,6 +1774,21 @@ export class BaseMark<T extends ICommonSpec> extends GrammarItem implements IMar
17741774
return !!g.context.diffAttrs && Object.keys(g.context.diffAttrs).length > 0;
17751775
}
17761776

1777+
protected _commitPreventedAnimationStaticAttrs(g: IMarkGraphic, attrs: Record<string, any>) {
1778+
if (!attrs || !Object.keys(attrs).length) {
1779+
return;
1780+
}
1781+
1782+
const graphic = g as any;
1783+
graphic.setFinalAttributes?.(attrs);
1784+
1785+
if (graphic._commitAnimationStaticAttributes) {
1786+
graphic._commitAnimationStaticAttributes(attrs);
1787+
} else {
1788+
graphic.commitInternalBaseAttributes?.(attrs);
1789+
}
1790+
}
1791+
17771792
protected _runApplyGraphic(graphics: IMarkGraphic[]) {
17781793
const hasAnimation = this.hasAnimation();
17791794

@@ -1821,7 +1836,12 @@ export class BaseMark<T extends ICommonSpec> extends GrammarItem implements IMar
18211836
g.context.reusing = false;
18221837
} else if (!hasStateAnimation) {
18231838
// 不是正在被复用的属性,也不需要走动画,那就设置属性
1824-
hasAnimation ? g.setAttributesAndPreventAnimate(diffAttrs) : g.setAttributes(diffAttrs);
1839+
if (hasAnimation) {
1840+
g.setAttributesAndPreventAnimate(diffAttrs);
1841+
this._commitPreventedAnimationStaticAttrs(g, diffAttrs);
1842+
} else {
1843+
g.setAttributes(diffAttrs);
1844+
}
18251845
}
18261846

18271847
// 恢复visible: true时,需要将graphic重新添加到product中

packages/vchart/src/mark/group.ts

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -136,7 +136,12 @@ export class GroupMark extends BaseMark<IGroupMarkSpec> implements IGroupMark {
136136
this._product.context.diffAttrs = diffAttrs;
137137

138138
if (!this.hasAnimationByState(this._product.context.animationState)) {
139-
hasAnimation ? this._product.setAttributesAndPreventAnimate(diffAttrs) : this._product.setAttributes(diffAttrs);
139+
if (hasAnimation) {
140+
this._product.setAttributesAndPreventAnimate(diffAttrs);
141+
this._commitPreventedAnimationStaticAttrs(this._product as unknown as IMarkGraphic, diffAttrs);
142+
} else {
143+
this._product.setAttributes(diffAttrs);
144+
}
140145
}
141146
} else {
142147
this._product.setAttributes(newAttrs);

0 commit comments

Comments
 (0)