From ad05b4f43edd4edacb31e7ee63f70750589a5b04 Mon Sep 17 00:00:00 2001 From: Sahinur Date: Thu, 15 Oct 2026 10:15:34 +0600 Subject: [PATCH] fix(radar): animate symbols from the center on enter. close #21774 --- src/chart/radar/RadarView.ts | 4 +- test/ut/spec/series/radar.test.ts | 115 ++++++++++++++++++++++++++++++ 2 files changed, 118 insertions(+), 1 deletion(-) create mode 100644 test/ut/spec/series/radar.test.ts diff --git a/src/chart/radar/RadarView.ts b/src/chart/radar/RadarView.ts index a6d19eee33..7f1e83d22c 100644 --- a/src/chart/radar/RadarView.ts +++ b/src/chart/radar/RadarView.ts @@ -134,8 +134,10 @@ class RadarView extends ChartView { itemGroup.add(polygon); itemGroup.add(symbolGroup); + // `initProps` has already set `polyline.shape.points` to the target points, + // so animate the symbols from the initial points to keep them in sync with the lines. updateSymbols( - polyline.shape.points, points, symbolGroup, data, idx, true + getInitialPoints(points), points, symbolGroup, data, idx, true ); data.setItemGraphicEl(idx, itemGroup); diff --git a/test/ut/spec/series/radar.test.ts b/test/ut/spec/series/radar.test.ts new file mode 100644 index 0000000000..3b5cc8a60b --- /dev/null +++ b/test/ut/spec/series/radar.test.ts @@ -0,0 +1,115 @@ +/* +* Licensed to the Apache Software Foundation (ASF) under one +* or more contributor license agreements. See the NOTICE file +* distributed with this work for additional information +* regarding copyright ownership. The ASF licenses this file +* to you under the Apache License, Version 2.0 (the +* "License"); you may not use this file except in compliance +* with the License. You may obtain a copy of the License at +* +* http://www.apache.org/licenses/LICENSE-2.0 +* +* Unless required by applicable law or agreed to in writing, +* software distributed under the License is distributed on an +* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +* KIND, either express or implied. See the License for the +* specific language governing permissions and limitations +* under the License. +*/ + + +import { createChart } from '../../core/utHelper'; +import type { EChartsType } from '@/src/echarts'; + +function getSymbols(chart: EChartsType) { + const view = (chart as any)._chartsViews[0]; + const symbolGroup = view.group.childAt(0).childAt(2); + const symbols: any[] = []; + symbolGroup.eachChild((symbol: any) => symbols.push(symbol)); + return symbols; +} + +function getLayoutPoints(chart: EChartsType): number[][] { + return (chart as any).getModel().getSeriesByIndex(0).getData().getItemLayout(0); +} + +function getRadarCenter(chart: EChartsType): number[] { + const polar = (chart as any).getModel().getComponent('radar').coordinateSystem; + return [polar.cx, polar.cy]; +} + +const option = { + radar: { + indicator: [ + { name: 'a', max: 10 }, + { name: 'b', max: 10 }, + { name: 'c', max: 10 } + ] + }, + series: [{ + type: 'radar', + data: [{ value: [8, 6, 9] }] + }] +}; + +describe('radar', function () { + + it('should animate symbols from the center along with the lines on enter', function () { + const chart = createChart({width: 400, height: 400}); + + try { + chart.setOption({ + ...option, + animationDuration: 100000 + }); + + const center = getRadarCenter(chart); + const points = getLayoutPoints(chart); + const symbols = getSymbols(chart); + + expect(symbols.length).toBe(3); + symbols.forEach((symbol, i) => { + // Starts from the center, like the polyline and polygon. + expect(symbol.x).toBeCloseTo(center[0]); + expect(symbol.y).toBeCloseTo(center[1]); + + // And animates towards its final position. + expect(symbol.animators.length).toBe(1); + symbol.animators[0].getTracks().forEach((track: any) => { + const keyframes = track.keyframes; + const dim = track.propName === 'x' ? 0 : 1; + expect(keyframes[0].value).toBeCloseTo(center[dim]); + expect(keyframes[keyframes.length - 1].value).toBeCloseTo(points[i][dim]); + }); + }); + } + finally { + chart.dispose(); + } + }); + + it('should place symbols at their final positions when animation is disabled', function () { + const chart = createChart({width: 400, height: 400}); + + try { + chart.setOption({ + ...option, + animation: false + }); + + const points = getLayoutPoints(chart); + const symbols = getSymbols(chart); + + expect(symbols.length).toBe(3); + symbols.forEach((symbol, i) => { + expect(symbol.x).toBeCloseTo(points[i][0]); + expect(symbol.y).toBeCloseTo(points[i][1]); + expect(symbol.animators.length).toBe(0); + }); + } + finally { + chart.dispose(); + } + }); + +});