Skip to content

Commit

Permalink
fix(g-canvas): bbox calculation should be correct for path with angle,
Browse files Browse the repository at this point in the history
…close #232
  • Loading branch information
dengfuping committed Oct 29, 2019
1 parent 94573f0 commit 89f680a
Show file tree
Hide file tree
Showing 3 changed files with 131 additions and 4 deletions.
3 changes: 2 additions & 1 deletion packages/g-canvas/src/shape/path.ts
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,8 @@ class Path extends ShapeBase {

getInnerBox(attrs) {
const segments = this.getSegments();
return pathUtil.getPathBox(segments);
const lineWidth = this.getHitLineWidth();
return pathUtil.getPathBox(segments, lineWidth);
}

getSegments() {
Expand Down
68 changes: 65 additions & 3 deletions packages/g-canvas/src/util/path.ts
Original file line number Diff line number Diff line change
Expand Up @@ -31,10 +31,13 @@ function hasArc(path) {
function getSegments(path) {
const segments = [];
let currentPoint = [0, 0]; // 当前图形
let nextParams = null; // 下一节点的 path 参数
let startMovePoint = [0, 0]; // 开始 M 的点,可能会有多个
let lastStartMovePointIndex = 0; // 最近一个开始点 M 的索引
const count = path.length;
for (let i = 0; i < count; i++) {
const params = path[i];
nextParams = path[i + 1];
const command = params[0];
// 数学定义上的参数,便于后面的计算
const segment = {
Expand All @@ -45,6 +48,7 @@ function getSegments(path) {
switch (command) {
case 'M':
startMovePoint = [params[1], params[2]];
lastStartMovePointIndex = i;
break;
case 'A':
const arcParams = getArcParams(currentPoint, params);
Expand All @@ -56,22 +60,26 @@ function getSegments(path) {
// 有了 Z 后,当前节点从开始 M 的点开始
if (command === 'Z') {
currentPoint = startMovePoint;
nextParams = path[lastStartMovePointIndex + 1];
} else {
const len = params.length;
currentPoint = [params[len - 2], params[len - 1]];
}
segment['currentPoint'] = currentPoint;
const nextPoint = nextParams ? [nextParams[nextParams.length - 2], nextParams[nextParams.length - 1]] : null;
segment['nextPoint'] = nextPoint;
segments.push(segment);
}
return segments;
}

function getPathBox(segments) {
function getPathBox(segments, lineWidth) {
let xArr = [];
let yArr = [];
const segmentsWithAngle = [];
for (let i = 0; i < segments.length; i++) {
const segment = segments[i];
const { currentPoint, params, prePoint } = segment;
const { currentPoint, params, prePoint, nextPoint } = segment;
let box;
switch (segment.command) {
case 'Q':
Expand Down Expand Up @@ -102,12 +110,66 @@ function getPathBox(segments) {
xArr.push(box.x, box.x + box.width);
yArr.push(box.y, box.y + box.height);
}
if (segment.command === 'L' && segment.nextPoint) {
segmentsWithAngle.push(segment);
}
}
// bbox calculation should ignore NaN for path attribute
// ref: https://github.com/antvis/g/issues/210
xArr = xArr.filter((item) => !Number.isNaN(item));
yArr = yArr.filter((item) => !Number.isNaN(item));
return getBBoxByArray(xArr, yArr);
let minX = Math.min.apply(null, xArr);
let minY = Math.min.apply(null, yArr);
let maxX = Math.max.apply(null, xArr);
let maxY = Math.max.apply(null, yArr);
if (segmentsWithAngle.length === 0) {
return {
x: minX,
y: minY,
width: maxX - minX,
height: maxY - minY,
};
}
for (let i = 0; i < segmentsWithAngle.length; i++) {
const segment = segmentsWithAngle[i];
const { currentPoint } = segment;
let extra;
if (currentPoint[0] === minX) {
extra = getExtraFromSegmentWithAngle(segment, lineWidth);
minX = minX - extra;
} else if (currentPoint[0] === maxX) {
extra = getExtraFromSegmentWithAngle(segment, lineWidth);
maxX = maxX + extra;
}
if (currentPoint[1] === minY) {
extra = getExtraFromSegmentWithAngle(segment, lineWidth);
minY = minY - extra;
} else if (currentPoint[1] === maxY) {
extra = getExtraFromSegmentWithAngle(segment, lineWidth);
maxY = maxY + extra;
}
}
return {
x: minX,
y: minY,
width: maxX - minX,
height: maxY - minY,
};
}

// 获取 L segment 的外尖角与内夹角的距离 + 二分之一线宽
function getExtraFromSegmentWithAngle(segment, lineWidth) {
const { prePoint, currentPoint, nextPoint } = segment;
const currentAndPre = Math.pow(currentPoint[0] - prePoint[0], 2) + Math.pow(currentPoint[1] - prePoint[1], 2);
const currentAndNext = Math.pow(currentPoint[0] - nextPoint[0], 2) + Math.pow(currentPoint[1] - nextPoint[1], 2);
const preAndNext = Math.pow(prePoint[0] - nextPoint[0], 2) + Math.pow(prePoint[1] - nextPoint[1], 2);
// 以 currentPoint 为顶点的夹角
const angleCurrent = Math.acos(
(currentAndPre + currentAndNext - preAndNext) / (2 * Math.sqrt(currentAndPre) * Math.sqrt(currentAndNext))
);
// 这里不考虑在水平和垂直方向的投影,直接使用最大差值
// 由于上层统一加减了二分之一线宽,这里需要进行弥补
return lineWidth * (1 / Math.sin(angleCurrent / 2)) + lineWidth / 2 || 0;
}

function isPointInStroke(segments, lineWidth, x, y) {
Expand Down
64 changes: 64 additions & 0 deletions packages/g-canvas/tests/bugs/issue-232-spec.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
const expect = require('chai').expect;
import Canvas from '../../src/canvas';

const dom = document.createElement('div');
document.body.appendChild(dom);
dom.id = 'c1';

describe('#232', () => {
const canvas = new Canvas({
container: dom,
width: 600,
height: 800,
});

it('bbox calculation should be correct for path with angle', (done) => {
const shape = canvas.addShape('path', {
attrs: {
path: [
['M', 73.82280392116971, 388],
['L', 112.19659169514973, 349.2],
['L', 150.57037946912976, 368.6],
['M', 227.31795501708984, 116.40000000000003],
['L', 265.6917427910699, 271.6],
['L', 304.0655305650499, 27.159999999999968],
['L', 342.4393183390299, 155.20000000000002],
['L', 380.81310611300995, 0],
],
stroke: '#1890ff',
lineWidth: 2,
},
});
let bbox = shape.getBBox();
expect(bbox.minX).eqls(72.82280392116971);
expect(bbox.minY).eqls(-1);
expect(bbox.maxX).eqls(381.8131061130099);
expect(bbox.maxY).eqls(389);
shape.animate(
{
path: [
['M', 73.82280392116971, 337.56],
['L', 112.19659169514973, 256.08],
['L', 150.57037946912976, 368.6],
['L', 188.9441672431098, 256.08],
['L', 227.31795501708984, 310.4],
['L', 265.6917427910699, 360.84],
['L', 304.0655305650499, 298.76],
['L', 342.4393183390299, 38.80000000000001],
['L', 380.81310611300995, 376.36],
],
},
{
duration: 500,
}
);
setTimeout(() => {
bbox = shape.getBBox();
expect(bbox.minX).eqls(72.82280392116971);
expect(bbox.minY).eqls(21.357188662795615);
expect(bbox.maxX).eqls(381.8131061130099);
expect(bbox.maxY).eqls(377.36);
done();
}, 600);
});
});

0 comments on commit 89f680a

Please sign in to comment.