mirror of
https://gitee.com/antv/g6.git
synced 2024-12-05 05:09:07 +08:00
fix: conflict from polyline
This commit is contained in:
commit
036cf1acd8
152
demos/default-edges.html
Normal file
152
demos/default-edges.html
Normal file
@ -0,0 +1,152 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<title>内置的边</title>
|
||||
</head>
|
||||
<body>
|
||||
<div id="mountNode"></div>
|
||||
<script src="../build/g6.js"></script>
|
||||
<script>
|
||||
const graph = new G6.Graph({
|
||||
container: 'mountNode',
|
||||
width: 800,
|
||||
height: 600,
|
||||
defaultNode: {
|
||||
size: [40, 40]
|
||||
},
|
||||
modes: {
|
||||
default: [ 'brush-select', 'drag-node' ]
|
||||
}
|
||||
});
|
||||
|
||||
/*
|
||||
* data
|
||||
*/
|
||||
const data = {
|
||||
nodes: [
|
||||
{
|
||||
id: 'node1',
|
||||
label: 'node1',
|
||||
x: 100,
|
||||
y: 100,
|
||||
shape: 'circle'
|
||||
},
|
||||
{
|
||||
id: 'node4',
|
||||
label: 'node4',
|
||||
x: 150,
|
||||
y: 300,
|
||||
shape: 'rect'
|
||||
},
|
||||
{
|
||||
id: 'node5',
|
||||
label: 'node5',
|
||||
x: 200,
|
||||
y: 100,
|
||||
shape: 'rect'
|
||||
},
|
||||
{
|
||||
id: 'node6',
|
||||
label: 'node6',
|
||||
x: 350,
|
||||
y: 300,
|
||||
shape: 'rect'
|
||||
},
|
||||
{
|
||||
id: 'node7',
|
||||
label: 'node7',
|
||||
x: 350,
|
||||
y: 200,
|
||||
shape: 'rect'
|
||||
},
|
||||
{
|
||||
id: 'node8',
|
||||
label: 'node8',
|
||||
x: 350,
|
||||
y: 450,
|
||||
shape: 'rect'
|
||||
},
|
||||
],
|
||||
edges: [
|
||||
// {
|
||||
// source: 'node1',
|
||||
// target: 'node4',
|
||||
// shape: 'polyline',
|
||||
// label: 'edge1-4',
|
||||
// controlPoints: [
|
||||
// { x: 100, y: 200 },
|
||||
// ],
|
||||
// style: {
|
||||
// radius: 10,
|
||||
// offset: 20
|
||||
// }
|
||||
// },
|
||||
// {
|
||||
// source: 'node4',
|
||||
// target: 'node5',
|
||||
// shape: 'polyline',
|
||||
// label: 'edge5-7',
|
||||
// style: {
|
||||
// lineWidth: 1.5,
|
||||
// stroke: '#888',
|
||||
// }
|
||||
// },
|
||||
// {
|
||||
// source: 'node4',
|
||||
// target: 'node7',
|
||||
// shape: 'polyline'
|
||||
// },
|
||||
// {
|
||||
// source: 'node6',
|
||||
// target: 'node4',
|
||||
// shape: 'polyline'
|
||||
// },
|
||||
{
|
||||
source: 'node4',
|
||||
target: 'node8',
|
||||
shape: 'polyline',
|
||||
style: {
|
||||
radius: 10,
|
||||
offset: 20
|
||||
}
|
||||
},
|
||||
]
|
||||
}
|
||||
|
||||
graph.data(data)
|
||||
graph.render()
|
||||
|
||||
graph.on('edge:click', evt => {
|
||||
console.log(evt)
|
||||
const { item } = evt
|
||||
graph.updateItem(item, {
|
||||
style: {
|
||||
lineWidth: 3,
|
||||
stroke: 'red'
|
||||
}
|
||||
})
|
||||
// const hasState = item.hasState('select')
|
||||
// if(!hasState) {
|
||||
// graph.setItemState(item, 'select', true);
|
||||
// }
|
||||
})
|
||||
|
||||
graph.on('edge:mouseenter', evt => {
|
||||
const { item } = evt;
|
||||
graph.setItemState(item, 'hover', true);
|
||||
})
|
||||
|
||||
graph.on('edge:mouseleave', evt => {
|
||||
const { item } = evt;
|
||||
const hasState = item.hasState('select')
|
||||
if(!hasState) {
|
||||
graph.setItemState(item, 'hover', false);
|
||||
graph.setItemState(item, 'running', false);
|
||||
}
|
||||
})
|
||||
|
||||
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
@ -102,6 +102,7 @@
|
||||
"test": "torch --compile --renderer --opts test/mocha.opts --recursive ./test/unit",
|
||||
"test-live": "torch --compile --interactive --watch --opts test/mocha.opts --recursive ./test/unit/",
|
||||
"test-live-shape": "torch --compile --interactive --watch --opts test/mocha.opts --recursive ./test/unit/shape/nodes/modelRect-spec.js",
|
||||
"test-live-util": "torch --compile --interactive --watch --opts test/mocha.opts --recursive ./test/unit/shape/edge-spec.js",
|
||||
"test-bugs": "torch --compile --renderer --recursive ./test/bugs",
|
||||
"test-bugs-live": "torch --compile --interactive --watch --recursive ./test/bugs",
|
||||
"test-all": "npm run test && npm run test-bugs",
|
||||
|
@ -152,13 +152,23 @@ const singleEdgeDefinition = Util.mix({}, SingleShapeMixin, {
|
||||
attrs: shapeStyle
|
||||
});
|
||||
return shape;
|
||||
},
|
||||
drawLabel(cfg, group) {
|
||||
const customStyle = this.getCustomConfig(cfg) || {};
|
||||
const defaultConfig = customStyle.default || {};
|
||||
const labelCfg = Util.deepMix({}, this.options.default.labelCfg, defaultConfig.labelCfg, cfg.labelCfg);
|
||||
const labelStyle = this.getLabelStyle(cfg, labelCfg, group);
|
||||
const label = group.addShape('text', {
|
||||
attrs: labelStyle
|
||||
});
|
||||
return label;
|
||||
}
|
||||
});
|
||||
|
||||
// 直线
|
||||
// // 直线
|
||||
Shape.registerEdge('single-line', singleEdgeDefinition);
|
||||
|
||||
// 直线, 不支持控制点
|
||||
// // 直线, 不支持控制点
|
||||
Shape.registerEdge('line', {
|
||||
// 控制点不生效
|
||||
getControlPoints() {
|
||||
@ -166,8 +176,8 @@ Shape.registerEdge('line', {
|
||||
}
|
||||
}, 'single-line');
|
||||
|
||||
// 折线,支持多个控制点
|
||||
Shape.registerEdge('polyline', {}, 'single-line');
|
||||
// // 折线,支持多个控制点
|
||||
// Shape.registerEdge('polyline', {}, 'single-line');
|
||||
|
||||
// 直线
|
||||
Shape.registerEdge('spline', {
|
||||
|
1
src/shape/edges/index.js
Normal file
1
src/shape/edges/index.js
Normal file
@ -0,0 +1 @@
|
||||
require('./polyline');
|
483
src/shape/edges/polyline.js
Normal file
483
src/shape/edges/polyline.js
Normal file
@ -0,0 +1,483 @@
|
||||
const Shape = require('../shape');
|
||||
const Util = require('../../util/index');
|
||||
|
||||
const CLS_SHAPE_SUFFIX = '-shape';
|
||||
const CLS_LABEL_SUFFIX = '-label';
|
||||
|
||||
function getBBoxFromPoint(point) {
|
||||
const { x, y } = point;
|
||||
return {
|
||||
centerX: x, centerY: y,
|
||||
minX: x, minY: y, maxX: x, maxY: y,
|
||||
height: 0, width: 0
|
||||
};
|
||||
}
|
||||
function getBBoxFromPoints(points = []) {
|
||||
const xs = [];
|
||||
const ys = [];
|
||||
points.forEach(p => {
|
||||
xs.push(p.x);
|
||||
ys.push(p.y);
|
||||
});
|
||||
const minX = Math.min.apply(Math, xs);
|
||||
const maxX = Math.max.apply(Math, xs);
|
||||
const minY = Math.min.apply(Math, ys);
|
||||
const maxY = Math.max.apply(Math, ys);
|
||||
return {
|
||||
centerX: (minX + maxX) / 2, centerY: (minY + maxY) / 2,
|
||||
maxX, maxY, minX, minY,
|
||||
height: (maxY - minY), width: (maxX - minX)
|
||||
};
|
||||
}
|
||||
function isBBoxesOverlapping(b1, b2) {
|
||||
return Math.abs(b1.centerX - b2.centerX) * 2 < (b1.width + b2.width) &&
|
||||
Math.abs(b1.centerY - b2.centerY) * 2 < (b1.height + b2.height);
|
||||
}
|
||||
function simplifyPolyline(points) {
|
||||
points = filterConnectPoints(points);
|
||||
return points;
|
||||
}
|
||||
function filterConnectPoints(points) {
|
||||
// pre-process: remove duplicated points
|
||||
const result = [];
|
||||
const pointsMap = {};
|
||||
points.forEach(p => {
|
||||
const id = p.id = `${p.x}-${p.y}`;
|
||||
pointsMap[id] = p;
|
||||
});
|
||||
Util.each(pointsMap, p => {
|
||||
result.push(p);
|
||||
});
|
||||
return result;
|
||||
}
|
||||
function getSimplePolyline(sPoint, tPoint) {
|
||||
return [
|
||||
sPoint,
|
||||
{ x: sPoint.x, y: tPoint.y },
|
||||
tPoint
|
||||
];
|
||||
}
|
||||
function getExpandedBBox(bbox, offset) {
|
||||
if (bbox.width === 0 && bbox.height === 0) { // when it is a point
|
||||
return bbox;
|
||||
}
|
||||
return {
|
||||
centerX: bbox.centerX, centerY: bbox.centerY,
|
||||
minX: bbox.minX - offset, minY: bbox.minY - offset, maxX: bbox.maxX + offset, maxY: bbox.maxY + offset,
|
||||
height: bbox.height + 2 * offset, width: bbox.width + 2 * offset
|
||||
};
|
||||
}
|
||||
function getExpandedBBoxPoint(bbox, point) {
|
||||
const isHorizontal = isHorizontalPort(point, bbox);
|
||||
if (isHorizontal) {
|
||||
return { x: point.x > bbox.centerX ? bbox.maxX : bbox.minX, y: point.y };
|
||||
}
|
||||
return { x: point.x, y: point.y > bbox.centerY ? bbox.maxY : bbox.minY };
|
||||
}
|
||||
function isHorizontalPort(port, bbox) {
|
||||
const dx = Math.abs(port.x - bbox.centerX);
|
||||
const dy = Math.abs(port.y - bbox.centerY);
|
||||
return (dx / bbox.width) > (dy / bbox.height);
|
||||
}
|
||||
/**
|
||||
* @param {object} b1 bbox1
|
||||
* @param {object} b2 bbox2
|
||||
* @return {object} { centerX, centerY, height, maxX, maxY, minX, minY, width }
|
||||
**/
|
||||
function mergeBBox(b1, b2) {
|
||||
const minX = Math.min(b1.minX, b2.minX);
|
||||
const minY = Math.min(b1.minY, b2.minY);
|
||||
const maxX = Math.max(b1.maxX, b2.maxX);
|
||||
const maxY = Math.max(b1.maxY, b2.maxY);
|
||||
return {
|
||||
centerX: (minX + maxX) / 2, centerY: (minY + maxY) / 2,
|
||||
minX, minY, maxX, maxY,
|
||||
height: maxY - minY, width: maxX - minX
|
||||
};
|
||||
}
|
||||
function getPointsFromBBox(bbox) {
|
||||
// anticlockwise
|
||||
const { minX, minY, maxX, maxY } = bbox;
|
||||
return [
|
||||
{ x: minX, y: minY },
|
||||
{ x: maxX, y: minY },
|
||||
{ x: maxX, y: maxY },
|
||||
{ x: minX, y: maxY }
|
||||
];
|
||||
}
|
||||
function isPointOutsideBBox(point, bbox) {
|
||||
const { x, y } = point;
|
||||
return x < bbox.minX || x > bbox.maxX || y < bbox.minY || y > bbox.maxY;
|
||||
}
|
||||
function getBBoxCrossPointsByPoint(bbox, point) {
|
||||
return getBBoxXCrossPoints(bbox, point.x).concat(getBBoxYCrossPoints(bbox, point.y));
|
||||
}
|
||||
function getBBoxXCrossPoints(bbox, x) {
|
||||
if (x < bbox.minX || x > bbox.maxX) {
|
||||
return [];
|
||||
}
|
||||
return [
|
||||
{ x, y: bbox.minY },
|
||||
{ x, y: bbox.maxY }
|
||||
];
|
||||
}
|
||||
function getBBoxYCrossPoints(bbox, y) {
|
||||
if (y < bbox.minY || y > bbox.maxY) {
|
||||
return [];
|
||||
}
|
||||
return [
|
||||
{ x: bbox.minX, y },
|
||||
{ x: bbox.maxX, y }
|
||||
];
|
||||
}
|
||||
function distance(p1, p2) {
|
||||
return Math.abs(p1.x - p2.x) + Math.abs(p1.y - p2.y);
|
||||
}
|
||||
function _costByPoints(p, points) {
|
||||
const offset = -2;
|
||||
let result = 0;
|
||||
points.forEach(point => {
|
||||
if (point) {
|
||||
if (p.x === point.x) result += offset;
|
||||
if (p.y === point.y) result += offset;
|
||||
}
|
||||
});
|
||||
return result;
|
||||
}
|
||||
function heuristicCostEstimate(p, ps, pt, source, target) {
|
||||
return (distance(p, ps) + distance(p, pt)) + _costByPoints(p, [ ps, pt, source, target ]);
|
||||
}
|
||||
function reconstructPath(pathPoints, pointById, cameFrom, currentId, iterator = 0) {
|
||||
pathPoints.unshift(pointById[currentId]);
|
||||
if (cameFrom[currentId] && cameFrom[currentId] !== currentId && iterator <= 100) {
|
||||
reconstructPath(pathPoints, pointById, cameFrom, cameFrom[currentId], iterator + 1);
|
||||
}
|
||||
}
|
||||
function removeFrom(arr, item) {
|
||||
const index = arr.indexOf(item);
|
||||
if (index > -1) {
|
||||
arr.splice(index, 1);
|
||||
}
|
||||
}
|
||||
function isSegmentsIntersected(p0, p1, p2, p3) {
|
||||
const s1_x = p1.x - p0.x;
|
||||
const s1_y = p1.y - p0.y;
|
||||
const s2_x = p3.x - p2.x;
|
||||
const s2_y = p3.y - p2.y;
|
||||
|
||||
const s = (-s1_y * (p0.x - p2.x) + s1_x * (p0.y - p2.y)) / (-s2_x * s1_y + s1_x * s2_y);
|
||||
const t = (s2_x * (p0.y - p2.y) - s2_y * (p0.x - p2.x)) / (-s2_x * s1_y + s1_x * s2_y);
|
||||
|
||||
return (s >= 0 && s <= 1 && t >= 0 && t <= 1);
|
||||
}
|
||||
function isSegmentCrossingBBox(p1, p2, bbox) {
|
||||
if (bbox.width === bbox.height === 0) {
|
||||
return false;
|
||||
}
|
||||
const [ pa, pb, pc, pd ] = getPointsFromBBox(bbox);
|
||||
return isSegmentsIntersected(p1, p2, pa, pb) ||
|
||||
isSegmentsIntersected(p1, p2, pa, pd) ||
|
||||
isSegmentsIntersected(p1, p2, pb, pc) ||
|
||||
isSegmentsIntersected(p1, p2, pc, pd);
|
||||
}
|
||||
function getNeighborPoints(points, point, bbox1, bbox2) {
|
||||
const neighbors = [];
|
||||
points.forEach(p => {
|
||||
if (p !== point) {
|
||||
if (p.x === point.x || p.y === point.y) {
|
||||
if (!isSegmentCrossingBBox(p, point, bbox1) && !isSegmentCrossingBBox(p, point, bbox2)) {
|
||||
neighbors.push(p);
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
return filterConnectPoints(neighbors);
|
||||
}
|
||||
function pathFinder(points, start, goal, sBBox, tBBox, os, ot) { // A-Star Algorithm
|
||||
const closedSet = [];
|
||||
const openSet = [ start ];
|
||||
const cameFrom = {};
|
||||
const gScore = {}; // all default values are Infinity
|
||||
const fScore = {}; // all default values are Infinity
|
||||
|
||||
gScore[start.id] = 0;
|
||||
fScore[start.id] = heuristicCostEstimate(start, goal, start);
|
||||
|
||||
const pointById = {};
|
||||
points.forEach(p => {
|
||||
pointById[p.id] = p;
|
||||
});
|
||||
|
||||
while (openSet.length) {
|
||||
let current;
|
||||
let lowestFScore = Infinity;
|
||||
openSet.forEach(p => {
|
||||
if (fScore[p.id] < lowestFScore) {
|
||||
lowestFScore = fScore[p.id];
|
||||
current = p;
|
||||
}
|
||||
});
|
||||
|
||||
if (current === goal) { // ending condition
|
||||
const pathPoints = [];
|
||||
reconstructPath(pathPoints, pointById, cameFrom, goal.id);
|
||||
return pathPoints;
|
||||
}
|
||||
|
||||
removeFrom(openSet, current);
|
||||
closedSet.push(current);
|
||||
|
||||
getNeighborPoints(points, current, sBBox, tBBox).forEach(neighbor => {
|
||||
if (closedSet.indexOf(neighbor) !== -1) return;
|
||||
|
||||
if (openSet.indexOf(neighbor) === -1) {
|
||||
openSet.push(neighbor);
|
||||
}
|
||||
|
||||
const tentativeGScore = fScore[current.id] + distance(current, neighbor);// + distance(neighbor, goal);
|
||||
|
||||
if (gScore[neighbor.id] && (tentativeGScore >= gScore[neighbor.id])) return;
|
||||
|
||||
cameFrom[neighbor.id] = current.id;
|
||||
gScore[neighbor.id] = tentativeGScore;
|
||||
fScore[neighbor.id] = gScore[neighbor.id] + heuristicCostEstimate(neighbor, goal, start, os, ot);
|
||||
});
|
||||
}
|
||||
// throw new Error('Cannot find path');
|
||||
return [ start, goal ];
|
||||
}
|
||||
function getPathWithBorderRadiusByPolyline(points, borderRadius) {
|
||||
// TODO
|
||||
const pathSegments = [];
|
||||
const startPoint = points[0];
|
||||
pathSegments.push(`M${startPoint.x} ${startPoint.y}`);
|
||||
points.forEach((p, i) => {
|
||||
const p1 = points[i + 1];
|
||||
const p2 = points[i + 2];
|
||||
if (p1 && p2) {
|
||||
if (isBending(p, p1, p2)) {
|
||||
const [ ps, pt ] = getBorderRadiusPoints(p, p1, p2, borderRadius);
|
||||
pathSegments.push(`L${ps.x} ${ps.y}`);
|
||||
pathSegments.push(`Q${p1.x} ${p1.y} ${pt.x} ${pt.y}`);
|
||||
pathSegments.push(`L${pt.x} ${pt.y}`);
|
||||
} else {
|
||||
pathSegments.push(`L${p1.x} ${p1.y}`);
|
||||
}
|
||||
} else if (p1) {
|
||||
pathSegments.push(`L${p1.x} ${p1.y}`);
|
||||
}
|
||||
});
|
||||
return pathSegments.join('');
|
||||
}
|
||||
function isBending(p0, p1, p2) {
|
||||
return !((p0.x === p1.x === p2.x) || (p0.y === p1.y === p2.y));
|
||||
}
|
||||
function getBorderRadiusPoints(p0, p1, p2, r) {
|
||||
const d0 = distance(p0, p1);
|
||||
const d1 = distance(p2, p1);
|
||||
if (d0 < r) {
|
||||
r = d0;
|
||||
}
|
||||
if (d1 < r) {
|
||||
r = d1;
|
||||
}
|
||||
const ps = {
|
||||
x: p1.x - r / d0 * (p1.x - p0.x),
|
||||
y: p1.y - r / d0 * (p1.y - p0.y)
|
||||
};
|
||||
const pt = {
|
||||
x: p1.x - r / d1 * (p1.x - p2.x),
|
||||
y: p1.y - r / d1 * (p1.y - p2.y)
|
||||
};
|
||||
return [ ps, pt ];
|
||||
}
|
||||
function getPolylinePoints(start, end, sNode, tNode, offset) {
|
||||
const sBBox = sNode && sNode.getBBox() ? sNode.getBBox() : getBBoxFromPoint(start);
|
||||
const tBBox = tNode && tNode.getBBox() ? tNode.getBBox() : getBBoxFromPoint(end);
|
||||
if (isBBoxesOverlapping(sBBox, tBBox)) { // source and target nodes are overlapping
|
||||
return simplifyPolyline(getSimplePolyline(start, end));
|
||||
}
|
||||
const sxBBox = getExpandedBBox(sBBox, offset);
|
||||
const txBBox = getExpandedBBox(tBBox, offset);
|
||||
if (isBBoxesOverlapping(sxBBox, txBBox)) { // the expanded bounding boxes of source and target nodes are overlapping
|
||||
return simplifyPolyline(getSimplePolyline(start, end));
|
||||
}
|
||||
const sPoint = getExpandedBBoxPoint(sxBBox, start);
|
||||
const tPoint = getExpandedBBoxPoint(txBBox, end);
|
||||
const lineBBox = getBBoxFromPoints([ sPoint, tPoint ]);
|
||||
const outerBBox = mergeBBox(sxBBox, txBBox);
|
||||
const sMixBBox = mergeBBox(sxBBox, lineBBox);
|
||||
const tMixBBox = mergeBBox(txBBox, lineBBox);
|
||||
let connectPoints = [];
|
||||
connectPoints = connectPoints.concat(
|
||||
getPointsFromBBox(sMixBBox)// .filter(p => !isPointIntersectBBox(p, txBBox))
|
||||
);
|
||||
connectPoints = connectPoints.concat(
|
||||
getPointsFromBBox(tMixBBox)// .filter(p => !isPointIntersectBBox(p, sxBBox))
|
||||
);
|
||||
const centerPoint = { x: (start.x + end.x) / 2, y: (start.y + end.y) / 2 };
|
||||
[
|
||||
lineBBox,
|
||||
sMixBBox,
|
||||
tMixBBox
|
||||
].forEach(bbox => {
|
||||
connectPoints = connectPoints.concat(
|
||||
getBBoxCrossPointsByPoint(bbox, centerPoint).filter(
|
||||
p => isPointOutsideBBox(p, sxBBox) && isPointOutsideBBox(p, txBBox)
|
||||
)
|
||||
);
|
||||
});
|
||||
[
|
||||
{ x: sPoint.x, y: tPoint.y },
|
||||
{ x: tPoint.x, y: sPoint.y }
|
||||
].forEach(p => {
|
||||
if (
|
||||
isPointOutsideBBox(p, sxBBox) && isPointOutsideBBox(p, txBBox)// &&
|
||||
// isPointInsideBBox(p, sMixBBox) && isPointInsideBBox(p, tMixBBox)
|
||||
) {
|
||||
connectPoints.push(p);
|
||||
}
|
||||
});
|
||||
connectPoints.unshift(sPoint);
|
||||
connectPoints.push(tPoint);
|
||||
connectPoints = filterConnectPoints(connectPoints, sxBBox, txBBox, outerBBox);
|
||||
const pathPoints = pathFinder(connectPoints, sPoint, tPoint, sBBox, tBBox, start, end);
|
||||
pathPoints.unshift(start);
|
||||
pathPoints.push(end);
|
||||
|
||||
return simplifyPolyline(pathPoints);
|
||||
}
|
||||
|
||||
// 折线
|
||||
Shape.registerEdge('polyline', {
|
||||
// 自定义边时的配置
|
||||
options: {
|
||||
// 默认配置
|
||||
default: {
|
||||
stroke: '#333',
|
||||
lineWidth: 1,
|
||||
radius: 0,
|
||||
offset: 5,
|
||||
x: 0,
|
||||
y: 0,
|
||||
// 文本样式配置
|
||||
labelCfg: {
|
||||
style: {
|
||||
fill: '#595959'
|
||||
}
|
||||
}
|
||||
},
|
||||
// 鼠标hover状态下的配置
|
||||
hover: {
|
||||
lineWidth: 3
|
||||
},
|
||||
// 选中边状态下的配置
|
||||
select: {
|
||||
lineWidth: 5
|
||||
}
|
||||
},
|
||||
shapeType: 'polyline',
|
||||
// 文本位置
|
||||
labelPosition: 'center',
|
||||
drawShape(cfg, group) {
|
||||
const shapeStyle = this.getShapeStyle(cfg);
|
||||
const keyShape = group.addShape('path', {
|
||||
className: 'edge-shape',
|
||||
attrs: shapeStyle
|
||||
});
|
||||
return keyShape;
|
||||
},
|
||||
getShapeStyle(cfg) {
|
||||
const customStyle = this.getCustomConfig(cfg) || {};
|
||||
const defaultConfig = customStyle.default;
|
||||
const style = Util.deepMix({}, this.options.default, defaultConfig, cfg.style);
|
||||
cfg = this.getPathPoints(cfg);
|
||||
this.radius = customStyle.radius;
|
||||
this.offset = customStyle.offset;
|
||||
const startPoint = cfg.startPoint;
|
||||
const endPoint = cfg.endPoint;
|
||||
const controlPoints = this.getControlPoints(cfg);
|
||||
let points = [ startPoint ]; // 添加起始点
|
||||
// 添加控制点
|
||||
if (controlPoints) {
|
||||
points = points.concat(controlPoints);
|
||||
}
|
||||
// 添加结束点
|
||||
points.push(endPoint);
|
||||
const source = cfg.sourceNode;
|
||||
const target = cfg.targetNode;
|
||||
let routeCfg = { radius: style.radius };
|
||||
if (!controlPoints) {
|
||||
routeCfg = { source, target, offset: style.offset, radius: style.radius };
|
||||
}
|
||||
const path = this.getPath(points, routeCfg);
|
||||
const attrs = Util.deepMix({}, this.options.default, defaultConfig, cfg.style, { path });
|
||||
return attrs;
|
||||
},
|
||||
getPath(points, routeCfg) {
|
||||
const { source, target, offset, radius } = routeCfg;
|
||||
if (!offset) {
|
||||
let path = [];
|
||||
if (radius) {
|
||||
path = getPathWithBorderRadiusByPolyline(points, radius);
|
||||
} else {
|
||||
Util.each(points, (point, index) => {
|
||||
if (index === 0) {
|
||||
path.push([ 'M', point.x, point.y ]);
|
||||
} else {
|
||||
path.push([ 'L', point.x, point.y ]);
|
||||
}
|
||||
});
|
||||
}
|
||||
return path;
|
||||
}
|
||||
if (radius) {
|
||||
const polylinePoints = simplifyPolyline(
|
||||
getPolylinePoints(points[0], points[points.length - 1], source, target, offset)
|
||||
);
|
||||
return getPathWithBorderRadiusByPolyline(polylinePoints, radius);
|
||||
}
|
||||
const polylinePoints = getPolylinePoints(points[0],
|
||||
points[points.length - 1], source, target, offset);
|
||||
return Util.pointsToPolygon(polylinePoints);
|
||||
},
|
||||
|
||||
update(cfg, item) {
|
||||
const group = item.getContainer();
|
||||
const shapeClassName = this.itemType + CLS_SHAPE_SUFFIX;
|
||||
const shape = group.findByClassName(shapeClassName);
|
||||
if (!cfg.style) {
|
||||
cfg.style = {};
|
||||
}
|
||||
const oriShapeAttrs = shape.attr();
|
||||
cfg.style.radius = cfg.style.radius || oriShapeAttrs.radius;
|
||||
cfg.style.offset = cfg.style.offset || oriShapeAttrs.offset;
|
||||
const shapeStyle = this.getShapeStyle(cfg);
|
||||
shape.attr(shapeStyle);
|
||||
const labelClassName = this.itemType + CLS_LABEL_SUFFIX;
|
||||
const label = group.findByClassName(labelClassName);
|
||||
// 此时需要考虑之前是否绘制了 label 的场景存在三种情况
|
||||
// 1. 更新时不需要 label,但是原先存在 label,此时需要删除
|
||||
// 2. 更新时需要 label, 但是原先不存在,创建节点
|
||||
// 3. 如果两者都存在,更新
|
||||
if (!cfg.label) {
|
||||
label && label.remove();
|
||||
} else {
|
||||
if (!label) {
|
||||
const newLabel = this.drawLabel(cfg, group);
|
||||
newLabel.set('className', labelClassName);
|
||||
} else {
|
||||
const labelCfg = cfg.labelCfg || {};
|
||||
const labelStyle = this.getLabelStyle(cfg, labelCfg, group);
|
||||
/**
|
||||
* fixme g中shape的rotate是角度累加的,不是label的rotate想要的角度
|
||||
* 由于现在label只有rotate操作,所以在更新label的时候如果style中有rotate就重置一下变换
|
||||
* 后续会基于g的Text复写一个Label出来处理这一类问题
|
||||
*/
|
||||
label.resetMatrix();
|
||||
label.attr(labelStyle);
|
||||
}
|
||||
}
|
||||
}
|
||||
}, 'single-line');
|
@ -8,4 +8,5 @@ const Shape = require('./shape');
|
||||
require('./node');
|
||||
require('./edge');
|
||||
require('./nodes');
|
||||
require('./edges');
|
||||
module.exports = Shape;
|
||||
|
@ -6,6 +6,25 @@ const G = require('@antv/g/lib');
|
||||
const BaseUtil = require('./base');
|
||||
const vec2 = BaseUtil.vec2;
|
||||
|
||||
/**
|
||||
* 替换字符串中的字段.
|
||||
* @param {String} str 模版字符串
|
||||
* @param {Object} o json data
|
||||
* @param {RegExp} [regexp] 匹配字符串的正则表达式
|
||||
*/
|
||||
|
||||
function substitute(str, o) {
|
||||
if (!str || !o) {
|
||||
return str;
|
||||
}
|
||||
return str.replace(/\\?\{([^{}]+)\}/g, function(match, name) {
|
||||
if (match.charAt(0) === '\\') {
|
||||
return match.slice(1);
|
||||
}
|
||||
return (o[name] === undefined) ? '' : o[name];
|
||||
});
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
getSpline(points) {
|
||||
const data = [];
|
||||
@ -37,5 +56,33 @@ module.exports = {
|
||||
point.x += perpendicular[0];
|
||||
point.y += perpendicular[1];
|
||||
return point;
|
||||
},
|
||||
/**
|
||||
* 点集转化为Path多边形
|
||||
* @param {Array} points 点集
|
||||
* @param {Boolen} z 是否封闭
|
||||
* @return {Array} Path
|
||||
*/
|
||||
pointsToPolygon(points, z) {
|
||||
if (!points.length) {
|
||||
return '';
|
||||
}
|
||||
let path = '';
|
||||
let str = '';
|
||||
|
||||
for (let i = 0, length = points.length; i < length; i++) {
|
||||
const item = points[i];
|
||||
if (i === 0) {
|
||||
str = 'M{x} {y}';
|
||||
} else {
|
||||
str = 'L{x} {y}';
|
||||
}
|
||||
path += substitute(str, item);
|
||||
}
|
||||
|
||||
if (z) {
|
||||
path += 'Z';
|
||||
}
|
||||
return path;
|
||||
}
|
||||
};
|
||||
|
Loading…
Reference in New Issue
Block a user