Skip to content

Commit

Permalink
fix(g-canvas): bbox calculation for path with angle should be accurate,
Browse files Browse the repository at this point in the history
  • Loading branch information
dengfuping committed Nov 5, 2019
1 parent ce330c9 commit f9b6694
Show file tree
Hide file tree
Showing 4 changed files with 98 additions and 14 deletions.
54 changes: 44 additions & 10 deletions packages/g-canvas/src/util/path.ts
Original file line number Diff line number Diff line change
Expand Up @@ -57,15 +57,31 @@ function getSegments(path) {
default:
break;
}
// 有了 Z 后,当前节点从开始 M 的点开始
if (command === 'Z') {
// 有了 Z 后,当前节点从开始 M 的点开始
currentPoint = startMovePoint;
// 如果当前点的命令为 Z,相当于当前点为最近一个 M 点,则下一个点直接指向最近一个 M 点的下一个点
nextParams = path[lastStartMovePointIndex + 1];
} else {
const len = params.length;
currentPoint = [params[len - 2], params[len - 1]];
}
if (nextParams && nextParams[0] === 'Z') {
// 如果下一个点的命令为 Z,则下一个点直接指向最近一个 M 点
nextParams = path[lastStartMovePointIndex];
if (segments[lastStartMovePointIndex]) {
// 如果下一个点的命令为 Z,则最近一个 M 点的前一个点为当前点
segments[lastStartMovePointIndex].prePoint = currentPoint;
}
}
segment['currentPoint'] = currentPoint;
// 如果当前点与最近一个 M 点相同,则最近一个 M 点的前一个点为当前点的前一个点
if (
segments[lastStartMovePointIndex] &&
isSamePoint(currentPoint, segments[lastStartMovePointIndex].currentPoint)
) {
segments[lastStartMovePointIndex].prePoint = segment.prePoint;
}
const nextPoint = nextParams ? [nextParams[nextParams.length - 2], nextParams[nextParams.length - 1]] : null;
segment['nextPoint'] = nextPoint;
segments.push(segment);
Expand All @@ -79,7 +95,7 @@ function getPathBox(segments, lineWidth) {
const segmentsWithAngle = [];
for (let i = 0; i < segments.length; i++) {
const segment = segments[i];
const { currentPoint, params, prePoint, nextPoint } = segment;
const { currentPoint, params, prePoint } = segment;
let box;
switch (segment.command) {
case 'Q':
Expand Down Expand Up @@ -110,7 +126,7 @@ function getPathBox(segments, lineWidth) {
xArr.push(box.x, box.x + box.width);
yArr.push(box.y, box.y + box.height);
}
if (segment.command === 'L' && segment.nextPoint) {
if ((segment.command === 'L' || segment.command === 'M') && segment.prePoint && segment.nextPoint) {
segmentsWithAngle.push(segment);
}
}
Expand All @@ -136,17 +152,17 @@ function getPathBox(segments, lineWidth) {
let extra;
if (currentPoint[0] === minX) {
extra = getExtraFromSegmentWithAngle(segment, lineWidth);
minX = minX - extra;
minX = minX - extra.xExtra;
} else if (currentPoint[0] === maxX) {
extra = getExtraFromSegmentWithAngle(segment, lineWidth);
maxX = maxX + extra;
maxX = maxX + extra.xExtra;
}
if (currentPoint[1] === minY) {
extra = getExtraFromSegmentWithAngle(segment, lineWidth);
minY = minY - extra;
minY = minY - extra.yExtra;
} else if (currentPoint[1] === maxY) {
extra = getExtraFromSegmentWithAngle(segment, lineWidth);
maxY = maxY + extra;
maxY = maxY + extra.yExtra;
}
}
return {
Expand All @@ -157,22 +173,40 @@ function getPathBox(segments, lineWidth) {
};
}

// 判断两个点是否重合
function isSamePoint(point1, point2) {
return point1[0] === point2[0] && point1[1] === point2[1];
}

// 获取 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(
const currentAngle = Math.acos(
(currentAndPre + currentAndNext - preAndNext) / (2 * Math.sqrt(currentAndPre) * Math.sqrt(currentAndNext))
);
if (Math.sin(angleCurrent) === 0) {
if (Math.sin(currentAngle) === 0) {
return 0;
}
let xAngle = Math.abs(Math.atan2(nextPoint[1] - currentPoint[1], nextPoint[0] - currentPoint[0]));
let yAngle = Math.abs(Math.atan2(nextPoint[0] - currentPoint[0], nextPoint[1] - currentPoint[1]));
// 将夹角转为锐角
xAngle = xAngle > Math.PI / 2 ? Math.PI - xAngle : xAngle;
yAngle = yAngle > Math.PI / 2 ? Math.PI - yAngle : yAngle;
// 这里不考虑在水平和垂直方向的投影,直接使用最大差值
// 由于上层统一加减了二分之一线宽,这里需要进行弥补
return lineWidth * (1 / Math.sin(angleCurrent / 2)) + lineWidth / 2 || 0;
const extra = {
// 水平方向投影
xExtra:
Math.cos(currentAngle / 2 - xAngle) * ((lineWidth / 2) * (1 / Math.sin(currentAngle / 2))) - lineWidth / 2 || 0,
// 垂直方向投影
yExtra:
Math.cos(yAngle - currentAngle / 2) * ((lineWidth / 2) * (1 / Math.sin(currentAngle / 2))) - lineWidth / 2 || 0,
};
return extra;
}

function isPointInStroke(segments, lineWidth, x, y) {
Expand Down
6 changes: 3 additions & 3 deletions packages/g-canvas/tests/bugs/issue-232-spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,7 @@ describe('#232', () => {
['L', 265.6917427910699, 360.84],
['L', 304.0655305650499, 298.76],
['L', 342.4393183390299, 38.80000000000001],
['L', 380.81310611300995, 376.36],
['L', 388.81310611300995, 376.36],
],
},
{
Expand All @@ -55,8 +55,8 @@ describe('#232', () => {
setTimeout(() => {
bbox = shape.getBBox();
expect(bbox.minX).eqls(72.82280392116971);
expect(bbox.minY).eqls(21.357188662795615);
expect(bbox.maxX).eqls(381.8131061130099);
expect(bbox.minY).eqls(31.487390015101695);
expect(bbox.maxX).eqls(386.8131061130099);
expect(bbox.maxY).eqls(377.36);
done();
}, 600);
Expand Down
50 changes: 50 additions & 0 deletions packages/g-canvas/tests/bugs/issue-252-spec.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
const expect = require('chai').expect;
import Canvas from '../../src/canvas';

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

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

// 规则形状水平和垂直方向没有凸起角
it('bbox calculation for path with regular angle should be correct', () => {
const shape = canvas.addShape({
type: 'path',
attrs: {
lineWidth: 2,
path: [['M', 75, 200], ['L', 75, 100], ['L', 175, 100], ['L', 175, 200], ['L', 75, 200]],
stroke: 'red',
},
});

const bbox = shape.getBBox();
expect(bbox.minX).eqls(74);
expect(bbox.minY).eqls(99);
expect(bbox.maxX).eqls(176);
expect(bbox.maxY).eqls(201);
});

// 水平和垂直方向具有凸起角
it.only('bbox calculation for path with prominent angle should be correct', () => {
const shape = canvas.addShape({
type: 'path',
attrs: {
lineWidth: 2,
path: [['M', 100, 100], ['L', 200, 200], ['L', 100, 300], ['L', 0, 200], ['Z']],
stroke: 'red',
},
});

const bbox = shape.getBBox();
expect(bbox.minX).eqls(-1.4142135623730951);
expect(bbox.minY).eqls(98.58578643762691);
expect(bbox.maxX).eqls(201.4142135623731);
expect(bbox.maxY).eqls(301.4142135623731);
});
});
2 changes: 1 addition & 1 deletion packages/g-canvas/tests/bugs/issue-254-spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ describe('#254', () => {
height: 600,
});

it('bbox calculation for path should consider angle of 0 and PI', () => {
it('bbox calculation for path should be correct when angle is 0 and π', () => {
const shape = canvas.addShape({
type: 'path',
attrs: {
Expand Down

0 comments on commit f9b6694

Please sign in to comment.