/
githubmirror
/
echarts
Обзор
Документация
Войти
/
githubmirror
/
echarts
Код
Запросы
0
Пакеты
0
Релизы
0
Аналитика
Безопасность
master
src/chart/chord/chordLayout.ts
272 строки
10 KB
100pah
chore: (1) Shift all pure function handlers (legacy pattern) to stage handler to remove unnecessary throw and catch - better for debug; (2) sunburst did not use dataFilter in correct way and it did not work, therefore remove it.
08 апр 2026, 17:33
08 апр 2026, 17:33
4b99289
Код
Авторство
О чём код?
/* * 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 { normalizeArcAngles } from 'zrender/src/core/PathProxy'; import GlobalModel from '../../model/Global'; import ChordSeriesModel, { SERIES_TYPE_CHORD } from './ChordSeries'; import ExtensionAPI from '../../core/ExtensionAPI'; import { getCircleLayout } from '../../util/layout'; import SeriesModel from '../../model/Series'; import { CircleLayoutOptionMixin, SeriesOption } from '../../util/types'; import { createSimpleOverallStageHandler } from '../../util/model'; const RADIAN = Math.PI / 180; export const chordCircularLayoutStageHandler = createSimpleOverallStageHandler(SERIES_TYPE_CHORD, chordCircularLayout); function chordCircularLayout(ecModel: GlobalModel, api: ExtensionAPI) { ecModel.eachSeriesByType(SERIES_TYPE_CHORD, function (seriesModel: ChordSeriesModel) { chordLayout(seriesModel, api); }); } function chordLayout(seriesModel: ChordSeriesModel, api: ExtensionAPI) { const nodeData = seriesModel.getData(); const nodeGraph = nodeData.graph; const edgeData = seriesModel.getEdgeData(); const edgeCount = edgeData.count(); if (!edgeCount) { return; } const { cx, cy, r, r0 } = getCircleLayout( seriesModel as unknown as SeriesModel<CircleLayoutOptionMixin & SeriesOption<unknown>>, api ); let padAngle = Math.max((seriesModel.get('padAngle') || 0) * RADIAN, 0); let minAngle = Math.max((seriesModel.get('minAngle') || 0) * RADIAN, 0); const startAngle = -seriesModel.get('startAngle') * RADIAN; const endAngle = startAngle + Math.PI * 2; const clockwise = seriesModel.get('clockwise'); const dir = clockwise ? 1 : -1; // Normalize angles const angles = [startAngle, endAngle]; normalizeArcAngles(angles, !clockwise); const [normalizedStartAngle, normalizedEndAngle] = angles; const totalAngle = normalizedEndAngle - normalizedStartAngle; const allZero = nodeData.getSum('value') === 0 && edgeData.getSum('value') === 0; // Sum of each node's edge values const nodeValues: number[] = []; let renderedNodeCount = 0; nodeGraph.eachEdge(function (edge) { // All links use the same value 1 when allZero is true const value = allZero ? 1 : edge.getValue('value') as number; if (allZero && (value > 0 || minAngle)) { // When allZero is true, angle is in direct proportion to number // of links both in and out of the node. renderedNodeCount += 2; } const node1Index = edge.node1.dataIndex; const node2Index = edge.node2.dataIndex; nodeValues[node1Index] = (nodeValues[node1Index] || 0) + value; nodeValues[node2Index] = (nodeValues[node2Index] || 0) + value; }); // Update nodeValues with data.value if exists let nodeValueSum = 0; nodeGraph.eachNode(node => { const dataValue = node.getValue('value') as number; if (!isNaN(dataValue)) { nodeValues[node.dataIndex] = Math.max(dataValue, nodeValues[node.dataIndex] || 0); } if (!allZero && (nodeValues[node.dataIndex] > 0 || minAngle)) { // When allZero is false, angle is in direct proportion to node's // value renderedNodeCount++; } nodeValueSum += nodeValues[node.dataIndex] || 0; }); if (renderedNodeCount === 0 || nodeValueSum === 0) { return; } if (padAngle * renderedNodeCount >= Math.abs(totalAngle)) { // Not enough angle to render the pad, minAngle has higher priority, and padAngle takes the rest padAngle = Math.max(0, (Math.abs(totalAngle) - minAngle * renderedNodeCount) / renderedNodeCount); } if ((padAngle + minAngle) * renderedNodeCount >= Math.abs(totalAngle)) { // Not enough angle to render the minAngle, so ignore the minAngle minAngle = (Math.abs(totalAngle) - padAngle * renderedNodeCount) / renderedNodeCount; } const unitAngle = (totalAngle - padAngle * renderedNodeCount * dir) / nodeValueSum; let totalDeficit = 0; // sum of deficits of nodes with span < minAngle let totalSurplus = 0; // sum of (spans - minAngle) of nodes with span > minAngle let totalSurplusSpan = 0; // sum of spans of nodes with span > minAngle let minSurplus = Infinity; // min of (spans - minAngle) of nodes with span > minAngle nodeGraph.eachNode(node => { const value = nodeValues[node.dataIndex] || 0; const spanAngle = unitAngle * (nodeValueSum ? value : 1) * dir; if (Math.abs(spanAngle) < minAngle) { totalDeficit += minAngle - Math.abs(spanAngle); } else { minSurplus = Math.min(minSurplus, Math.abs(spanAngle) - minAngle); totalSurplus += Math.abs(spanAngle) - minAngle; totalSurplusSpan += Math.abs(spanAngle); } node.setLayout({ angle: spanAngle, value: value }); }); let surplusAsMuchAsPossible = false; if (totalDeficit > totalSurplus) { // Not enough angle to spread the nodes, scale all const scale = totalDeficit / totalSurplus; nodeGraph.eachNode(node => { const spanAngle = node.getLayout().angle; if (Math.abs(spanAngle) >= minAngle) { node.setLayout({ angle: spanAngle * scale, ratio: scale }, true); } else { node.setLayout({ angle: minAngle, ratio: minAngle === 0 ? 1 : spanAngle / minAngle }, true); } }); } else { // For example, if totalDeficit is 60 degrees and totalSurplus is 70 // degrees but one of the sector can only reduced by 1 degree, // if we decrease it with the ratio of value to other surplused nodes, // it will have smaller angle than minAngle itself. // So we need to borrow some angle from other nodes. nodeGraph.eachNode(node => { if (surplusAsMuchAsPossible) { return; } const spanAngle = node.getLayout().angle; const borrowRatio = Math.min(spanAngle / totalSurplusSpan, 1); const borrowAngle = borrowRatio * totalDeficit; if (spanAngle - borrowAngle < minAngle) { // It will have less than minAngle after borrowing surplusAsMuchAsPossible = true; } }); } let restDeficit = totalDeficit; nodeGraph.eachNode(node => { if (restDeficit <= 0) { return; } const spanAngle = node.getLayout().angle; if (spanAngle > minAngle && minAngle > 0) { const borrowRatio = surplusAsMuchAsPossible ? 1 : Math.min(spanAngle / totalSurplusSpan, 1); const maxBorrowAngle = spanAngle - minAngle; const borrowAngle = Math.min(maxBorrowAngle, Math.min(restDeficit, totalDeficit * borrowRatio) ); restDeficit -= borrowAngle; node.setLayout({ angle: spanAngle - borrowAngle, ratio: (spanAngle - borrowAngle) / spanAngle }, true); } else if (minAngle > 0) { node.setLayout({ angle: minAngle, ratio: spanAngle === 0 ? 1 : minAngle / spanAngle }, true); } }); let angle = normalizedStartAngle; const edgeAccAngle: number[] = []; nodeGraph.eachNode(node => { const spanAngle = Math.max(node.getLayout().angle, minAngle); node.setLayout({ cx, cy, r0, r, startAngle: angle, endAngle: angle + spanAngle * dir, clockwise }, true); edgeAccAngle[node.dataIndex] = angle; angle += (spanAngle + padAngle) * dir; }); nodeGraph.eachEdge(edge => { const value = allZero ? 1 : edge.getValue('value') as number; const spanAngle = unitAngle * (nodeValueSum ? value : 1) * dir; const node1Index = edge.node1.dataIndex; const sStartAngle = edgeAccAngle[node1Index] || 0; const sSpan = Math.abs((edge.node1.getLayout().ratio || 1) * spanAngle); const sEndAngle = sStartAngle + sSpan * dir; const s1 = [ cx + r0 * Math.cos(sStartAngle), cy + r0 * Math.sin(sStartAngle) ]; const s2 = [ cx + r0 * Math.cos(sEndAngle), cy + r0 * Math.sin(sEndAngle) ]; const node2Index = edge.node2.dataIndex; const tStartAngle = edgeAccAngle[node2Index] || 0; const tSpan = Math.abs((edge.node2.getLayout().ratio || 1) * spanAngle); const tEndAngle = tStartAngle + tSpan * dir; const t1 = [ cx + r0 * Math.cos(tStartAngle), cy + r0 * Math.sin(tStartAngle) ]; const t2 = [ cx + r0 * Math.cos(tEndAngle), cy + r0 * Math.sin(tEndAngle) ]; edge.setLayout({ s1, s2, sStartAngle, sEndAngle, t1, t2, tStartAngle, tEndAngle, cx, cy, r: r0, value, clockwise }); edgeAccAngle[node1Index] = sEndAngle; edgeAccAngle[node2Index] = tEndAngle; }); }