247 lines
9.3 KiB
JavaScript
247 lines
9.3 KiB
JavaScript
|
|
|
||
|
|
/*
|
||
|
|
* 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.
|
||
|
|
*/
|
||
|
|
|
||
|
|
|
||
|
|
/**
|
||
|
|
* AUTO-GENERATED FILE. DO NOT MODIFY.
|
||
|
|
*/
|
||
|
|
|
||
|
|
/*
|
||
|
|
* 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/lib/core/PathProxy.js';
|
||
|
|
import { getCircleLayout } from '../../util/layout.js';
|
||
|
|
var RADIAN = Math.PI / 180;
|
||
|
|
export default function chordCircularLayout(ecModel, api) {
|
||
|
|
ecModel.eachSeriesByType('chord', function (seriesModel) {
|
||
|
|
chordLayout(seriesModel, api);
|
||
|
|
});
|
||
|
|
}
|
||
|
|
function chordLayout(seriesModel, api) {
|
||
|
|
var nodeData = seriesModel.getData();
|
||
|
|
var nodeGraph = nodeData.graph;
|
||
|
|
var edgeData = seriesModel.getEdgeData();
|
||
|
|
var edgeCount = edgeData.count();
|
||
|
|
if (!edgeCount) {
|
||
|
|
return;
|
||
|
|
}
|
||
|
|
var _a = getCircleLayout(seriesModel, api),
|
||
|
|
cx = _a.cx,
|
||
|
|
cy = _a.cy,
|
||
|
|
r = _a.r,
|
||
|
|
r0 = _a.r0;
|
||
|
|
var padAngle = Math.max((seriesModel.get('padAngle') || 0) * RADIAN, 0);
|
||
|
|
var minAngle = Math.max((seriesModel.get('minAngle') || 0) * RADIAN, 0);
|
||
|
|
var startAngle = -seriesModel.get('startAngle') * RADIAN;
|
||
|
|
var endAngle = startAngle + Math.PI * 2;
|
||
|
|
var clockwise = seriesModel.get('clockwise');
|
||
|
|
var dir = clockwise ? 1 : -1;
|
||
|
|
// Normalize angles
|
||
|
|
var angles = [startAngle, endAngle];
|
||
|
|
normalizeArcAngles(angles, !clockwise);
|
||
|
|
var normalizedStartAngle = angles[0],
|
||
|
|
normalizedEndAngle = angles[1];
|
||
|
|
var totalAngle = normalizedEndAngle - normalizedStartAngle;
|
||
|
|
var allZero = nodeData.getSum('value') === 0 && edgeData.getSum('value') === 0;
|
||
|
|
// Sum of each node's edge values
|
||
|
|
var nodeValues = [];
|
||
|
|
var renderedNodeCount = 0;
|
||
|
|
nodeGraph.eachEdge(function (edge) {
|
||
|
|
// All links use the same value 1 when allZero is true
|
||
|
|
var value = allZero ? 1 : edge.getValue('value');
|
||
|
|
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;
|
||
|
|
}
|
||
|
|
var node1Index = edge.node1.dataIndex;
|
||
|
|
var node2Index = edge.node2.dataIndex;
|
||
|
|
nodeValues[node1Index] = (nodeValues[node1Index] || 0) + value;
|
||
|
|
nodeValues[node2Index] = (nodeValues[node2Index] || 0) + value;
|
||
|
|
});
|
||
|
|
// Update nodeValues with data.value if exists
|
||
|
|
var nodeValueSum = 0;
|
||
|
|
nodeGraph.eachNode(function (node) {
|
||
|
|
var dataValue = node.getValue('value');
|
||
|
|
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;
|
||
|
|
}
|
||
|
|
var unitAngle = (totalAngle - padAngle * renderedNodeCount * dir) / nodeValueSum;
|
||
|
|
var totalDeficit = 0; // sum of deficits of nodes with span < minAngle
|
||
|
|
var totalSurplus = 0; // sum of (spans - minAngle) of nodes with span > minAngle
|
||
|
|
var totalSurplusSpan = 0; // sum of spans of nodes with span > minAngle
|
||
|
|
var minSurplus = Infinity; // min of (spans - minAngle) of nodes with span > minAngle
|
||
|
|
nodeGraph.eachNode(function (node) {
|
||
|
|
var value = nodeValues[node.dataIndex] || 0;
|
||
|
|
var 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
|
||
|
|
});
|
||
|
|
});
|
||
|
|
var surplusAsMuchAsPossible = false;
|
||
|
|
if (totalDeficit > totalSurplus) {
|
||
|
|
// Not enough angle to spread the nodes, scale all
|
||
|
|
var scale_1 = totalDeficit / totalSurplus;
|
||
|
|
nodeGraph.eachNode(function (node) {
|
||
|
|
var spanAngle = node.getLayout().angle;
|
||
|
|
if (Math.abs(spanAngle) >= minAngle) {
|
||
|
|
node.setLayout({
|
||
|
|
angle: spanAngle * scale_1,
|
||
|
|
ratio: scale_1
|
||
|
|
}, 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(function (node) {
|
||
|
|
if (surplusAsMuchAsPossible) {
|
||
|
|
return;
|
||
|
|
}
|
||
|
|
var spanAngle = node.getLayout().angle;
|
||
|
|
var borrowRatio = Math.min(spanAngle / totalSurplusSpan, 1);
|
||
|
|
var borrowAngle = borrowRatio * totalDeficit;
|
||
|
|
if (spanAngle - borrowAngle < minAngle) {
|
||
|
|
// It will have less than minAngle after borrowing
|
||
|
|
surplusAsMuchAsPossible = true;
|
||
|
|
}
|
||
|
|
});
|
||
|
|
}
|
||
|
|
var restDeficit = totalDeficit;
|
||
|
|
nodeGraph.eachNode(function (node) {
|
||
|
|
if (restDeficit <= 0) {
|
||
|
|
return;
|
||
|
|
}
|
||
|
|
var spanAngle = node.getLayout().angle;
|
||
|
|
if (spanAngle > minAngle && minAngle > 0) {
|
||
|
|
var borrowRatio = surplusAsMuchAsPossible ? 1 : Math.min(spanAngle / totalSurplusSpan, 1);
|
||
|
|
var maxBorrowAngle = spanAngle - minAngle;
|
||
|
|
var 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);
|
||
|
|
}
|
||
|
|
});
|
||
|
|
var angle = normalizedStartAngle;
|
||
|
|
var edgeAccAngle = [];
|
||
|
|
nodeGraph.eachNode(function (node) {
|
||
|
|
var spanAngle = Math.max(node.getLayout().angle, minAngle);
|
||
|
|
node.setLayout({
|
||
|
|
cx: cx,
|
||
|
|
cy: cy,
|
||
|
|
r0: r0,
|
||
|
|
r: r,
|
||
|
|
startAngle: angle,
|
||
|
|
endAngle: angle + spanAngle * dir,
|
||
|
|
clockwise: clockwise
|
||
|
|
}, true);
|
||
|
|
edgeAccAngle[node.dataIndex] = angle;
|
||
|
|
angle += (spanAngle + padAngle) * dir;
|
||
|
|
});
|
||
|
|
nodeGraph.eachEdge(function (edge) {
|
||
|
|
var value = allZero ? 1 : edge.getValue('value');
|
||
|
|
var spanAngle = unitAngle * (nodeValueSum ? value : 1) * dir;
|
||
|
|
var node1Index = edge.node1.dataIndex;
|
||
|
|
var sStartAngle = edgeAccAngle[node1Index] || 0;
|
||
|
|
var sSpan = Math.abs((edge.node1.getLayout().ratio || 1) * spanAngle);
|
||
|
|
var sEndAngle = sStartAngle + sSpan * dir;
|
||
|
|
var s1 = [cx + r0 * Math.cos(sStartAngle), cy + r0 * Math.sin(sStartAngle)];
|
||
|
|
var s2 = [cx + r0 * Math.cos(sEndAngle), cy + r0 * Math.sin(sEndAngle)];
|
||
|
|
var node2Index = edge.node2.dataIndex;
|
||
|
|
var tStartAngle = edgeAccAngle[node2Index] || 0;
|
||
|
|
var tSpan = Math.abs((edge.node2.getLayout().ratio || 1) * spanAngle);
|
||
|
|
var tEndAngle = tStartAngle + tSpan * dir;
|
||
|
|
var t1 = [cx + r0 * Math.cos(tStartAngle), cy + r0 * Math.sin(tStartAngle)];
|
||
|
|
var t2 = [cx + r0 * Math.cos(tEndAngle), cy + r0 * Math.sin(tEndAngle)];
|
||
|
|
edge.setLayout({
|
||
|
|
s1: s1,
|
||
|
|
s2: s2,
|
||
|
|
sStartAngle: sStartAngle,
|
||
|
|
sEndAngle: sEndAngle,
|
||
|
|
t1: t1,
|
||
|
|
t2: t2,
|
||
|
|
tStartAngle: tStartAngle,
|
||
|
|
tEndAngle: tEndAngle,
|
||
|
|
cx: cx,
|
||
|
|
cy: cy,
|
||
|
|
r: r0,
|
||
|
|
value: value,
|
||
|
|
clockwise: clockwise
|
||
|
|
});
|
||
|
|
edgeAccAngle[node1Index] = sEndAngle;
|
||
|
|
edgeAccAngle[node2Index] = tEndAngle;
|
||
|
|
});
|
||
|
|
}
|