You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@echarts.apache.org by ov...@apache.org on 2024/11/18 10:15:45 UTC

(echarts) branch feat-chord created (now c3dda28a9)

This is an automated email from the ASF dual-hosted git repository.

ovilia pushed a change to branch feat-chord
in repository https://gitbox.apache.org/repos/asf/echarts.git


      at c3dda28a9 WIP(chord): init chord

This branch includes the following new commits:

     new 302737901 refactor: remove duplicated logic
     new c3dda28a9 WIP(chord): init chord

The 2 revisions listed above as "new" are entirely new to this
repository and will be described in separate emails.  The revisions
listed as "add" were already present in the repository and have only
been added to this reference.



---------------------------------------------------------------------
To unsubscribe, e-mail: commits-unsubscribe@echarts.apache.org
For additional commands, e-mail: commits-help@echarts.apache.org


(echarts) 02/02: WIP(chord): init chord

Posted by ov...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

ovilia pushed a commit to branch feat-chord
in repository https://gitbox.apache.org/repos/asf/echarts.git

commit c3dda28a9ca3c1e4021aea683c894becb01b479a
Author: Ovilia <zw...@gmail.com>
AuthorDate: Mon Nov 18 18:15:14 2024 +0800

    WIP(chord): init chord
---
 src/chart/chord/ChordPiece.ts  |  43 +++++++++
 src/chart/chord/ChordSeries.ts | 205 +++++++++++++++++++++++++++++++++++++++++
 src/chart/chord/ChordView.ts   |  71 ++++++++++++++
 src/chart/chord/chordLayout.ts |  40 ++++++++
 src/chart/chord/install.ts     |  30 ++++++
 src/echarts.all.ts             |   2 +
 src/export/charts.ts           |   2 +
 src/export/option.ts           |   3 +
 src/model/Global.ts            |   1 +
 9 files changed, 397 insertions(+)

diff --git a/src/chart/chord/ChordPiece.ts b/src/chart/chord/ChordPiece.ts
new file mode 100644
index 000000000..853c86c24
--- /dev/null
+++ b/src/chart/chord/ChordPiece.ts
@@ -0,0 +1,43 @@
+import * as graphic from '../../util/graphic';
+import SeriesData from '../../data/SeriesData';
+import ChordSeriesModel, { ChordNodeItemOption } from './ChordSeries';
+import { extend } from 'zrender/src/core/util';
+import { getSectorCornerRadius } from '../helper/sectorHelper';
+
+export default class ChordPiece extends graphic.Sector {
+
+    constructor(data: SeriesData, idx: number, startAngle: number) {
+        super();
+
+        this.z2 = 2;
+
+        const text = new graphic.Text();
+
+        this.setTextContent(text);
+
+        this.updateData(data, idx, startAngle, true);
+    }
+
+    updateData(data: SeriesData, idx: number, startAngle?: number, firstCreate?: boolean): void {
+        const sector = this;
+        const node = data.graph.getNodeByIndex(idx);
+
+        const seriesModel = data.hostModel as ChordSeriesModel;
+        const itemModel = node.getModel<ChordNodeItemOption>();
+
+        // layout position is the center of the sector
+        const layout = data.getItemLayout(idx) as graphic.Sector['shape'];
+        // console.log(layout)
+
+        const sectorShape = extend(
+            getSectorCornerRadius(
+                itemModel.getModel('itemStyle'),
+                layout,
+                true
+            ),
+            layout
+        );
+        sector.setShape(sectorShape);
+        sector.useStyle(data.getItemVisual(idx, 'style'));
+    }
+}
diff --git a/src/chart/chord/ChordSeries.ts b/src/chart/chord/ChordSeries.ts
new file mode 100644
index 000000000..3cd203b91
--- /dev/null
+++ b/src/chart/chord/ChordSeries.ts
@@ -0,0 +1,205 @@
+/*
+* 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 * as zrUtil from 'zrender/src/core/util';
+import {
+    SeriesOption,
+    SeriesOnCartesianOptionMixin,
+    SeriesOnPolarOptionMixin,
+    SeriesOnCalendarOptionMixin,
+    SeriesOnGeoOptionMixin,
+    SeriesOnSingleOptionMixin,
+    OptionDataValue,
+    RoamOptionMixin,
+    SeriesLabelOption,
+    ItemStyleOption,
+    LineStyleOption,
+    SymbolOptionMixin,
+    BoxLayoutOptionMixin,
+    CircleLayoutOptionMixin,
+    SeriesLineLabelOption,
+    StatesOptionMixin,
+    GraphEdgeItemObject,
+    OptionDataValueNumeric,
+    CallbackDataParams,
+    DefaultEmphasisFocus
+} from '../../util/types';
+import Model from '../../model/Model';
+import SeriesModel from '../../model/Series';
+import GlobalModel from '../../model/Global';
+import SeriesData from '../../data/SeriesData';
+import createGraphFromNodeEdge from '../helper/createGraphFromNodeEdge';
+
+interface ChordStatesMixin {
+    emphasis?: DefaultEmphasisFocus
+}
+
+interface ChordEdgeStatesMixin {
+    emphasis?: DefaultEmphasisFocus
+}
+
+type ChordDataValue = OptionDataValue | OptionDataValue[];
+
+export interface ChordNodeStateOption<TCbParams = never> {
+    itemStyle?: ItemStyleOption<TCbParams>
+    label?: SeriesLabelOption
+}
+
+export interface ChordNodeItemOption extends ChordNodeStateOption,
+    StatesOptionMixin<ChordNodeStateOption, ChordStatesMixin> {
+
+    id?: string
+    name?: string
+    value?: ChordDataValue
+}
+
+interface ChordEdgeLineStyleOption extends LineStyleOption {
+    curveness?: number
+}
+
+export interface ChordEdgeStateOption {
+    lineStyle?: LineStyleOption
+    label?: SeriesLineLabelOption
+}
+
+export interface ChordEdgeItemOption extends ChordEdgeStateOption,
+    StatesOptionMixin<ChordEdgeStateOption, ChordEdgeStatesMixin>,
+    GraphEdgeItemObject<OptionDataValueNumeric> {
+
+    value?: number
+}
+
+export interface ChordSeriesOption
+    extends SeriesOption<ChordNodeStateOption<CallbackDataParams>, ChordStatesMixin>,
+    SeriesOnCartesianOptionMixin, SeriesOnPolarOptionMixin, SeriesOnCalendarOptionMixin,
+    SeriesOnGeoOptionMixin, SeriesOnSingleOptionMixin,
+    SymbolOptionMixin<CallbackDataParams>,
+    RoamOptionMixin,
+    BoxLayoutOptionMixin,
+    CircleLayoutOptionMixin
+{
+    type?: 'chord'
+
+    coordinateSystem?: 'none'
+
+    legendHoverLink?: boolean
+
+    clockwise?: boolean
+    startAngle?: number
+    endAngle?: number | 'auto'
+    padAngle?: number
+
+    data?: (ChordNodeItemOption | ChordDataValue)[]
+    nodes?: (ChordNodeItemOption | ChordDataValue)[]
+
+    edges?: ChordEdgeItemOption[]
+    links?: ChordEdgeItemOption[]
+
+    edgeLabel?: SeriesLineLabelOption
+    label?: SeriesLabelOption
+
+    itemStyle?: ItemStyleOption<CallbackDataParams>
+    lineStyle?: ChordEdgeLineStyleOption
+}
+
+class ChordSeriesModel extends SeriesModel<ChordSeriesOption> {
+
+    static type = 'series.chord';
+    readonly type = ChordSeriesModel.type;
+
+    init(option: ChordSeriesOption) {
+        super.init.apply(this, arguments as any);
+    }
+
+    getInitialData(option: ChordSeriesOption, ecModel: GlobalModel): SeriesData {
+        const edges = option.edges || option.links || [];
+        const nodes = option.data || option.nodes || [];
+        const self = this;
+
+        if (nodes && edges) {
+            // auto curveness
+            // initCurvenessList(this);
+            const graph = createGraphFromNodeEdge(nodes as ChordNodeItemOption[], edges, this, true, beforeLink);
+            zrUtil.each(graph.edges, function (edge) {
+                // createEdgeMapForCurveness(edge.node1, edge.node2, this, edge.dataIndex);
+            }, this);
+            return graph.data;
+        }
+
+        function beforeLink(nodeData: SeriesData, edgeData: SeriesData) {
+            // Overwrite nodeData.getItemModel to
+            // nodeData.wrapMethod('getItemModel', function (model) {
+
+            // });
+
+            // TODO Inherit resolveParentPath by default in Model#getModel?
+            const oldGetModel = Model.prototype.getModel;
+            function newGetModel(this: Model, path: any, parentModel?: Model) {
+                const model = oldGetModel.call(this, path, parentModel);
+                model.resolveParentPath = resolveParentPath;
+                return model;
+            }
+
+            edgeData.wrapMethod('getItemModel', function (model: Model) {
+                model.resolveParentPath = resolveParentPath;
+                model.getModel = newGetModel;
+                return model;
+            });
+
+            function resolveParentPath(this: Model, pathArr: readonly string[]): string[] {
+                if (pathArr && (pathArr[0] === 'label' || pathArr[1] === 'label')) {
+                    const newPathArr = pathArr.slice();
+                    if (pathArr[0] === 'label') {
+                        newPathArr[0] = 'edgeLabel';
+                    }
+                    else if (pathArr[1] === 'label') {
+                        newPathArr[1] = 'edgeLabel';
+                    }
+                    return newPathArr;
+                }
+                return pathArr as string[];
+            }
+        }
+    }
+
+    static defaultOption: ChordSeriesOption = {
+        // zlevel: 0,
+        z: 2,
+
+        coordinateSystem: 'none',
+
+        legendHoverLink: true,
+
+        left: 0,
+        top: 0,
+        right: 0,
+        bottom: 0,
+        width: null,
+        height: null,
+
+        center: ['50%', '50%'],
+        radius: ['65%', '75%'],
+
+        startAngle: 90,
+        endAngle: 'auto',
+        padAngle: 10
+    };
+}
+
+export default ChordSeriesModel;
diff --git a/src/chart/chord/ChordView.ts b/src/chart/chord/ChordView.ts
new file mode 100644
index 000000000..fb21221be
--- /dev/null
+++ b/src/chart/chord/ChordView.ts
@@ -0,0 +1,71 @@
+/*
+* 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 ChartView from '../../view/Chart';
+import GlobalModel from '../../model/Global';
+import ExtensionAPI from '../../core/ExtensionAPI';
+import SeriesData from '../../data/SeriesData';
+import ChordSeriesModel from './ChordSeries';
+import ChordPiece from './ChordPiece';
+
+class ChordView extends ChartView {
+
+    static readonly type = 'chord';
+    readonly type: string = ChordView.type;
+
+    private _data: SeriesData;
+
+    init(ecModel: GlobalModel, api: ExtensionAPI) {
+    }
+
+    render(seriesModel: ChordSeriesModel, ecModel: GlobalModel, api: ExtensionAPI) {
+        const data = seriesModel.getData();
+
+        const oldData = this._data;
+        const group = this.group;
+
+        data.diff(oldData)
+            .add(function (newIdx) {
+                const chordPiece = new ChordPiece(data, newIdx, 90);
+                data.setItemGraphicEl(newIdx, chordPiece);
+                group.add(chordPiece);
+            })
+
+            .update(function (newIdx, oldIdx) {
+                const chordPiece = oldData.getItemGraphicEl(oldIdx) as ChordPiece;
+                chordPiece.updateData(data, newIdx);
+                group.add(chordPiece);
+                data.setItemGraphicEl(newIdx, chordPiece);
+            })
+
+            .remove(function (oldIdx) {
+                const chordPiece = oldData.getItemGraphicEl(oldIdx) as ChordPiece;
+                group.remove(chordPiece);
+            })
+
+            .execute();
+
+    }
+
+    dispose() {
+
+    }
+}
+
+export default ChordView;
diff --git a/src/chart/chord/chordLayout.ts b/src/chart/chord/chordLayout.ts
new file mode 100644
index 000000000..d9ac7dd45
--- /dev/null
+++ b/src/chart/chord/chordLayout.ts
@@ -0,0 +1,40 @@
+/*
+* 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 GlobalModel from '../../model/Global';
+import ChordSeriesModel from './ChordSeries';
+import ExtensionAPI from '../../core/ExtensionAPI';
+import { getViewRect, getCircleLayout } from '../../util/layout';
+import SeriesModel from '../../model/Series';
+import { CircleLayoutOptionMixin, SeriesOption } from '../../util/types';
+
+export default function chordCircularLayout(ecModel: GlobalModel, api: ExtensionAPI) {
+    ecModel.eachSeriesByType('chord', function (seriesModel: ChordSeriesModel) {
+        chordLayout(seriesModel, api);
+    });
+}
+
+function chordLayout(seriesModel: ChordSeriesModel, api: ExtensionAPI) {
+    const viewRect = getViewRect(seriesModel, api);
+
+    const { cx, cy, r, r0 } = getCircleLayout(
+        seriesModel as unknown as SeriesModel<CircleLayoutOptionMixin & SeriesOption<unknown>>,
+        api
+    );
+}
diff --git a/src/chart/chord/install.ts b/src/chart/chord/install.ts
new file mode 100644
index 000000000..cb0412a04
--- /dev/null
+++ b/src/chart/chord/install.ts
@@ -0,0 +1,30 @@
+/*
+* 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 { EChartsExtensionInstallRegisters } from '../../extension';
+import ChordView from './ChordView';
+import ChordSeriesModel from './ChordSeries';
+import chordLayout from './chordLayout';
+
+export function install(registers: EChartsExtensionInstallRegisters) {
+    registers.registerChartView(ChordView);
+    registers.registerSeriesModel(ChordSeriesModel);
+
+    registers.registerLayout(registers.PRIORITY.VISUAL.POST_CHART_LAYOUT, chordLayout);
+}
\ No newline at end of file
diff --git a/src/echarts.all.ts b/src/echarts.all.ts
index 5eba7d34c..f53189747 100644
--- a/src/echarts.all.ts
+++ b/src/echarts.all.ts
@@ -43,6 +43,7 @@ import {
     TreeChart,
     TreemapChart,
     GraphChart,
+    ChordChart,
     GaugeChart,
     FunnelChart,
     ParallelChart,
@@ -127,6 +128,7 @@ use([
     TreeChart,
     TreemapChart,
     GraphChart,
+    ChordChart,
     GaugeChart,
     FunnelChart,
     ParallelChart,
diff --git a/src/export/charts.ts b/src/export/charts.ts
index 4356de914..3ccb85073 100644
--- a/src/export/charts.ts
+++ b/src/export/charts.ts
@@ -34,6 +34,7 @@ export {install as MapChart} from '../chart/map/install';
 export {install as TreeChart} from '../chart/tree/install';
 export {install as TreemapChart} from '../chart/treemap/install';
 export {install as GraphChart} from '../chart/graph/install';
+export {install as ChordChart} from '../chart/chord/install';
 export {install as GaugeChart} from '../chart/gauge/install';
 export {install as FunnelChart} from '../chart/funnel/install';
 export {install as ParallelChart} from '../chart/parallel/install';
@@ -83,6 +84,7 @@ export {
     TreeSeriesOption,
     TreemapSeriesOption,
     GraphSeriesOption,
+    ChordSeriesOption,
     GaugeSeriesOption,
     FunnelSeriesOption,
     ParallelSeriesOption,
diff --git a/src/export/option.ts b/src/export/option.ts
index d25e8197a..a39fe92e6 100644
--- a/src/export/option.ts
+++ b/src/export/option.ts
@@ -72,6 +72,7 @@ import type {MapSeriesOption as MapSeriesOptionInner} from '../chart/map/MapSeri
 import type {TreeSeriesOption as TreeSeriesOptionInner} from '../chart/tree/TreeSeries';
 import type {TreemapSeriesOption as TreemapSeriesOptionInner} from '../chart/treemap/TreemapSeries';
 import type {GraphSeriesOption as GraphSeriesOptionInner} from '../chart/graph/GraphSeries';
+import type {ChordSeriesOption as ChordSeriesOptionInner} from '../chart/chord/ChordSeries';
 import type {GaugeSeriesOption as GaugeSeriesOptionInner} from '../chart/gauge/GaugeSeries';
 import type {FunnelSeriesOption as FunnelSeriesOptionInner} from '../chart/funnel/FunnelSeries';
 import type {ParallelSeriesOption as ParallelSeriesOptionInner} from '../chart/parallel/ParallelSeries';
@@ -187,6 +188,7 @@ export type MapSeriesOption = MapSeriesOptionInner & SeriesInjectedOption;
 export type TreeSeriesOption = TreeSeriesOptionInner & SeriesInjectedOption;
 export type TreemapSeriesOption = TreemapSeriesOptionInner & SeriesInjectedOption;
 export type GraphSeriesOption = GraphSeriesOptionInner & SeriesInjectedOption;
+export type ChordSeriesOption = ChordSeriesOptionInner & SeriesInjectedOption;
 export type GaugeSeriesOption = GaugeSeriesOptionInner & SeriesInjectedOption;
 export type FunnelSeriesOption = FunnelSeriesOptionInner & SeriesInjectedOption;
 export type ParallelSeriesOption = ParallelSeriesOptionInner & SeriesInjectedOption;
@@ -225,6 +227,7 @@ export interface RegisteredSeriesOption {
     tree: TreeSeriesOption
     treemap: TreemapSeriesOption
     graph: GraphSeriesOption
+    chord: ChordSeriesOption
     gauge: GaugeSeriesOption
     funnel: FunnelSeriesOption
     parallel: ParallelSeriesOption
diff --git a/src/model/Global.ts b/src/model/Global.ts
index 7b2e8a339..8cef02375 100644
--- a/src/model/Global.ts
+++ b/src/model/Global.ts
@@ -120,6 +120,7 @@ const BUILTIN_CHARTS_MAP = {
     tree: 'TreeChart',
     treemap: 'TreemapChart',
     graph: 'GraphChart',
+    chord: 'ChordChart',
     gauge: 'GaugeChart',
     funnel: 'FunnelChart',
     parallel: 'ParallelChart',


---------------------------------------------------------------------
To unsubscribe, e-mail: commits-unsubscribe@echarts.apache.org
For additional commands, e-mail: commits-help@echarts.apache.org


(echarts) 01/02: refactor: remove duplicated logic

Posted by ov...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

ovilia pushed a commit to branch feat-chord
in repository https://gitbox.apache.org/repos/asf/echarts.git

commit 302737901f8701e791e952edb4e16dc2f0f4ada9
Author: Ovilia <zw...@gmail.com>
AuthorDate: Mon Nov 18 18:14:49 2024 +0800

    refactor: remove duplicated logic
---
 src/chart/funnel/funnelLayout.ts | 11 +-------
 src/chart/pie/PieView.ts         | 13 +++++++--
 src/chart/pie/pieLayout.ts       | 61 +++++-----------------------------------
 src/chart/sankey/sankeyLayout.ts | 14 +--------
 src/chart/tree/layoutHelper.ts   | 12 --------
 src/chart/tree/treeLayout.ts     |  2 +-
 src/util/layout.ts               | 59 +++++++++++++++++++++++++++++++++++++-
 7 files changed, 78 insertions(+), 94 deletions(-)

diff --git a/src/chart/funnel/funnelLayout.ts b/src/chart/funnel/funnelLayout.ts
index 4d433c1b1..d1f170872 100644
--- a/src/chart/funnel/funnelLayout.ts
+++ b/src/chart/funnel/funnelLayout.ts
@@ -25,15 +25,6 @@ import SeriesData from '../../data/SeriesData';
 import GlobalModel from '../../model/Global';
 import { isFunction } from 'zrender/src/core/util';
 
-function getViewRect(seriesModel: FunnelSeriesModel, api: ExtensionAPI) {
-    return layout.getLayoutRect(
-        seriesModel.getBoxLayoutParams(), {
-            width: api.getWidth(),
-            height: api.getHeight()
-        }
-    );
-}
-
 function getSortedIndices(data: SeriesData, sort: FunnelSeriesOption['sort']) {
     const valueDim = data.mapDimension('value');
     const valueArr = data.mapArray(valueDim, function (val: number) {
@@ -251,7 +242,7 @@ export default function funnelLayout(ecModel: GlobalModel, api: ExtensionAPI) {
         const data = seriesModel.getData();
         const valueDim = data.mapDimension('value');
         const sort = seriesModel.get('sort');
-        const viewRect = getViewRect(seriesModel, api);
+        const viewRect = layout.getViewRect(seriesModel, api);
         const orient = seriesModel.get('orient');
         const viewWidth = viewRect.width;
         const viewHeight = viewRect.height;
diff --git a/src/chart/pie/PieView.ts b/src/chart/pie/PieView.ts
index 04a81f1e6..ea87333ba 100644
--- a/src/chart/pie/PieView.ts
+++ b/src/chart/pie/PieView.ts
@@ -25,7 +25,8 @@ import { setStatesStylesFromModel, toggleHoverEmphasis } from '../../util/states
 import ChartView from '../../view/Chart';
 import GlobalModel from '../../model/Global';
 import ExtensionAPI from '../../core/ExtensionAPI';
-import { Payload, ColorString } from '../../util/types';
+import { Payload, ColorString, CircleLayoutOptionMixin, SeriesOption } from '../../util/types';
+import SeriesModel from '../../model/Series';
 import SeriesData from '../../data/SeriesData';
 import PieSeriesModel, {PieDataItemOption} from './PieSeries';
 import labelLayout from './labelLayout';
@@ -33,7 +34,8 @@ import { setLabelLineStyle, getLabelLineStatesModels } from '../../label/labelGu
 import { setLabelStyle, getLabelStatesModels } from '../../label/labelStyle';
 import { getSectorCornerRadius } from '../helper/sectorHelper';
 import { saveOldStyle } from '../../animation/basicTransition';
-import { getBasicPieLayout, getSeriesLayoutData } from './pieLayout';
+import { getSeriesLayoutData } from './pieLayout';
+import { getCircleLayout } from '../../util/layout';
 
 /**
  * Piece of pie including Sector, Label, LabelLine
@@ -262,7 +264,12 @@ class PieView extends ChartView {
         if (data.count() === 0 && seriesModel.get('showEmptyCircle')) {
             const layoutData = getSeriesLayoutData(seriesModel);
             const sector = new graphic.Sector({
-                shape: extend(getBasicPieLayout(seriesModel, api), layoutData)
+                shape: extend(
+                    getCircleLayout(
+                        seriesModel as unknown as SeriesModel<CircleLayoutOptionMixin & SeriesOption<unknown>>, api
+                    ),
+                    layoutData
+                )
             });
             sector.useStyle(seriesModel.getModel('emptyCircleStyle').getItemStyle());
             this._emptyCircleSector = sector;
diff --git a/src/chart/pie/pieLayout.ts b/src/chart/pie/pieLayout.ts
index 4ec992ec4..dfcbafbf4 100644
--- a/src/chart/pie/pieLayout.ts
+++ b/src/chart/pie/pieLayout.ts
@@ -19,67 +19,17 @@
 
 import { parsePercent, linearMap } from '../../util/number';
 import * as layout from '../../util/layout';
-import * as zrUtil from 'zrender/src/core/util';
 import GlobalModel from '../../model/Global';
 import ExtensionAPI from '../../core/ExtensionAPI';
 import PieSeriesModel from './PieSeries';
-import { SectorShape } from 'zrender/src/graphic/shape/Sector';
 import { normalizeArcAngles } from 'zrender/src/core/PathProxy';
 import { makeInner } from '../../util/model';
+import SeriesModel from '../../model/Series';
+import { CircleLayoutOptionMixin, SeriesOption } from '../../util/types';
 
 const PI2 = Math.PI * 2;
 const RADIAN = Math.PI / 180;
 
-function getViewRect(seriesModel: PieSeriesModel, api: ExtensionAPI) {
-    return layout.getLayoutRect(
-        seriesModel.getBoxLayoutParams(), {
-            width: api.getWidth(),
-            height: api.getHeight()
-        }
-    );
-}
-
-export function getBasicPieLayout(seriesModel: PieSeriesModel, api: ExtensionAPI):
-    Pick<SectorShape, 'cx' | 'cy' | 'r' | 'r0'> {
-    const viewRect = getViewRect(seriesModel, api);
-
-    // center can be string or number when coordinateSystem is specified
-    let center = seriesModel.get('center');
-    let radius = seriesModel.get('radius');
-
-    if (!zrUtil.isArray(radius)) {
-        radius = [0, radius];
-    }
-    const width = parsePercent(viewRect.width, api.getWidth());
-    const height = parsePercent(viewRect.height, api.getHeight());
-    const size = Math.min(width, height);
-    const r0 = parsePercent(radius[0], size / 2);
-    const r = parsePercent(radius[1], size / 2);
-
-    let cx: number;
-    let cy: number;
-    const coordSys = seriesModel.coordinateSystem;
-    if (coordSys) {
-        // percentage is not allowed when coordinate system is specified
-        const point = coordSys.dataToPoint(center);
-        cx = point[0] || 0;
-        cy = point[1] || 0;
-    }
-    else {
-        if (!zrUtil.isArray(center)) {
-            center = [center, center];
-        }
-        cx = parsePercent(center[0], width) + viewRect.x;
-        cy = parsePercent(center[1], height) + viewRect.y;
-    }
-
-    return {
-        cx,
-        cy,
-        r0,
-        r
-    };
-}
 
 export default function pieLayout(
     seriesType: 'pie',
@@ -89,9 +39,12 @@ export default function pieLayout(
     ecModel.eachSeriesByType(seriesType, function (seriesModel: PieSeriesModel) {
         const data = seriesModel.getData();
         const valueDim = data.mapDimension('value');
-        const viewRect = getViewRect(seriesModel, api);
+        const viewRect = layout.getViewRect(seriesModel, api);
 
-        const { cx, cy, r, r0 } = getBasicPieLayout(seriesModel, api);
+        const { cx, cy, r, r0 } = layout.getCircleLayout(
+            seriesModel as unknown as SeriesModel<CircleLayoutOptionMixin & SeriesOption<unknown>>,
+            api
+        );
 
         let startAngle = -seriesModel.get('startAngle') * RADIAN;
         let endAngle = seriesModel.get('endAngle');
diff --git a/src/chart/sankey/sankeyLayout.ts b/src/chart/sankey/sankeyLayout.ts
index 0ae24df1c..410ca7dfb 100644
--- a/src/chart/sankey/sankeyLayout.ts
+++ b/src/chart/sankey/sankeyLayout.ts
@@ -33,7 +33,7 @@ export default function sankeyLayout(ecModel: GlobalModel, api: ExtensionAPI) {
         const nodeWidth = seriesModel.get('nodeWidth');
         const nodeGap = seriesModel.get('nodeGap');
 
-        const layoutInfo = getViewRect(seriesModel, api);
+        const layoutInfo = layout.getViewRect(seriesModel, api);
 
         seriesModel.layoutInfo = layoutInfo;
 
@@ -61,18 +61,6 @@ export default function sankeyLayout(ecModel: GlobalModel, api: ExtensionAPI) {
     });
 }
 
-/**
- * Get the layout position of the whole view
- */
-function getViewRect(seriesModel: SankeySeriesModel, api: ExtensionAPI) {
-    return layout.getLayoutRect(
-        seriesModel.getBoxLayoutParams(), {
-            width: api.getWidth(),
-            height: api.getHeight()
-        }
-    );
-}
-
 function layoutSankey(
     nodes: GraphNode[],
     edges: GraphEdge[],
diff --git a/src/chart/tree/layoutHelper.ts b/src/chart/tree/layoutHelper.ts
index 2aada604e..cabc02de2 100644
--- a/src/chart/tree/layoutHelper.ts
+++ b/src/chart/tree/layoutHelper.ts
@@ -164,18 +164,6 @@ export function radialCoordinate(rad: number, r: number) {
     };
 }
 
-/**
- * Get the layout position of the whole view.
- */
-export function getViewRect(seriesModel: TreeSeriesModel, api: ExtensionAPI) {
-    return layout.getLayoutRect(
-        seriesModel.getBoxLayoutParams(), {
-            width: api.getWidth(),
-            height: api.getHeight()
-        }
-    );
-}
-
 /**
  * All other shifts, applied to the smaller subtrees between w- and w+, are
  * performed by this function.
diff --git a/src/chart/tree/treeLayout.ts b/src/chart/tree/treeLayout.ts
index d614347e2..0f1fe87df 100644
--- a/src/chart/tree/treeLayout.ts
+++ b/src/chart/tree/treeLayout.ts
@@ -27,12 +27,12 @@ import {
     secondWalk,
     separation as sep,
     radialCoordinate,
-    getViewRect,
     TreeLayoutNode
 } from './layoutHelper';
 import GlobalModel from '../../model/Global';
 import ExtensionAPI from '../../core/ExtensionAPI';
 import TreeSeriesModel from './TreeSeries';
+import { getViewRect } from '../../util/layout';
 
 export default function treeLayout(ecModel: GlobalModel, api: ExtensionAPI) {
     ecModel.eachSeriesByType('tree', function (seriesModel: TreeSeriesModel) {
diff --git a/src/util/layout.ts b/src/util/layout.ts
index 4138f74ee..dcf2f7762 100644
--- a/src/util/layout.ts
+++ b/src/util/layout.ts
@@ -23,10 +23,13 @@ import * as zrUtil from 'zrender/src/core/util';
 import BoundingRect, { RectLike } from 'zrender/src/core/BoundingRect';
 import {parsePercent} from './number';
 import * as formatUtil from './format';
-import { BoxLayoutOptionMixin, ComponentLayoutMode } from './types';
+import { BoxLayoutOptionMixin, CircleLayoutOptionMixin, ComponentLayoutMode, SeriesOption } from './types';
 import Group from 'zrender/src/graphic/Group';
+import { SectorShape } from 'zrender/src/graphic/shape/Sector';
 import Element from 'zrender/src/Element';
 import { Dictionary } from 'zrender/src/core/types';
+import SeriesModel from '../model/Series';
+import ExtensionAPI from '../core/ExtensionAPI';
 
 const each = zrUtil.each;
 
@@ -189,6 +192,60 @@ export function getAvailableSize(
     };
 }
 
+export function getViewRect(seriesModel: SeriesModel, api: ExtensionAPI) {
+    return getLayoutRect(
+        seriesModel.getBoxLayoutParams(), {
+            width: api.getWidth(),
+            height: api.getHeight()
+        }
+    );
+}
+
+export function getCircleLayout<T extends CircleLayoutOptionMixin & SeriesOption>(
+    seriesModel: SeriesModel<T>,
+    api: ExtensionAPI
+):
+    Pick<SectorShape, 'cx' | 'cy' | 'r' | 'r0'> {
+    const viewRect = getViewRect(seriesModel, api);
+
+    // center can be string or number when coordinateSystem is specified
+    let center = seriesModel.get('center');
+    let radius = seriesModel.get('radius');
+
+    if (!zrUtil.isArray(radius)) {
+        radius = [0, radius];
+    }
+    const width = parsePercent(viewRect.width, api.getWidth());
+    const height = parsePercent(viewRect.height, api.getHeight());
+    const size = Math.min(width, height);
+    const r0 = parsePercent(radius[0], size / 2);
+    const r = parsePercent(radius[1], size / 2);
+
+    let cx: number;
+    let cy: number;
+    const coordSys = seriesModel.coordinateSystem;
+    if (coordSys) {
+        // percentage is not allowed when coordinate system is specified
+        const point = coordSys.dataToPoint(center);
+        cx = point[0] || 0;
+        cy = point[1] || 0;
+    }
+    else {
+        if (!zrUtil.isArray(center)) {
+            center = [center, center];
+        }
+        cx = parsePercent(center[0], width) + viewRect.x;
+        cy = parsePercent(center[1], height) + viewRect.y;
+    }
+
+    return {
+        cx,
+        cy,
+        r0,
+        r
+    };
+}
+
 /**
  * Parse position info.
  */


---------------------------------------------------------------------
To unsubscribe, e-mail: commits-unsubscribe@echarts.apache.org
For additional commands, e-mail: commits-help@echarts.apache.org