zrender(Zlevel Render) 是一个轻量级的Canvas类库,绘制直线是很简单的事情,但是绘制中垂线还需借助到数学知识「三角函数」。

y ^

| B(x1, y1)

| /┆

| / ┆

| / ┆

| / ┆

|/----┼----+ F

/ C \┆

/| ┆\D

/ | ┆

——————————/——+—————┼———+————————> x

/ |O ┆ E

/ | ┆

/_____|_____┆

A(x, y) | G

|

设平面坐标系 xoy 中,有线段 A(x, y)、B(x1, y1),计算出垂直于 AB 线段中点 C 上一侧的长度为 n 的线段 CD 两端点坐标

1:算出线段 AB 的长度:

math?formula=%7CAB%7C%20%3D%20%5Csqrt%7B(x%20-%20x_1)%5E2%20%2B%20(y%20-%20y_1)%5E2%7D

2:算出中点 C 的坐标:

math?formula=xc%20%3D%20x_1%20%2B%20%5Cfrac%7Bx%20-%20x_1%7D%7B2%7D

math?formula=yc%20%3D%20y_1%20%2B%20%5Cfrac%7By%20-%20y_1%7D%7B2%7D

3:设过坐标原点 o 的长度为 n 的线段 OE,在 x 轴的正坐标上,则两点坐标为:O(0, 0),E(n, 0)

4:将 OE 平移至 C 点且 O 与 C 重合,得到 CF,则 F 点的坐标为:xf = n + xc; yf = yc

5:运用三角函数,计算各个角的关系

6:线段 AB 与 x 轴的夹角:

math?formula=%5Csin(%E2%88%A0BAG)%20%3D%20%5Csin(%E2%88%A0BCF)%20%3D%20%5Ccos(%E2%88%A0FCD)%20%3D%20%5Cfrac%7BBG%7D%7BAB%7D

math?formula=%5Ccos(%E2%88%A0BAG)%20%3D%20cos(%E2%88%A0BCF)%20%3D%20%5Csin(%E2%88%A0FCD)%20%3D%20%5Cfrac%7BAG%7D%7BAB%7D

7:通过三角函数关系可计算,XY平面中一点(x1,y1)经圆周旋转θ角度,得到点(x2,y2),公式如下

math?formula=x_2%20%3D%20x_1%20%5Ctimes%20%5Ccos(%CE%B8)%20-%20y_1%20%5Ctimes%20%5Csin(%CE%B8)

math?formula=y_2%20%3D%20x_1%20%5Ctimes%20%5Csin(%CE%B8)%20%2B%20y_1%20%5Ctimes%20%5Ccos(%CE%B8)

8:将 F 点经圆周旋转 ∠FCD 之后,得到 D 点

实现代码:

/**

* 绊线

* @ start[ ] 开始点坐标

* @ end[ ] 结束点坐标

* @ direction[ ] 箭头方向坐标(以一个点的坐标代替)

* @ around `(String)` 箭头包围方式:single - 单侧;double - 双侧

*

* @return

* 当 around 为 "single" 时,在绘制结束后,取 `shape.site` 可得到箭头在线段的哪一侧;

* 返回 "left" 或 "right"

*/

var TripWire = zrender.Path.extend({

type: 'TripWire',

shape: {

start: [0, 0],

end: [0, 0],

direction: [0, 0],

around: 'single', // or double, set the arrow site, single is left or right on the line, double is on the left and right

side: null

},

style: {

stroke: '#000',

fill: 'rgba(0, 0, 255, 0.2)'

},

buildPath: function (ctx, shape) {

// 起点坐标

var x1 = shape.start[0]

var y1 = shape.start[1]

// 终点坐标

var x2 = shape.end[0]

var y2 = shape.end[1]

// 方向坐标(单向有效)

var x3 = shape.direction[0]

var y3 = shape.direction[1]

ctx.moveTo(x1, y1)

ctx.lineTo(x2, y2)

// console.log("线段坐标", x1, y1, x2, y2)

console.log(shape.side)

if (!shape.side || shape.side == null || (shape.side !== 'left' && shape.side !== 'right')) {

// 计算箭头所在线段哪一侧,先计算一个点相对线段的向量,>0 左侧,<0 右侧,=0 线上

var vector = (x1 - x3) * (y2 - y3) - (y1 - y3) * (x2 - x3)

console.log(vector)

// 返回箭头所在方位

if (shape.around === 'single') {

if (vector < 0) {

shape.site = 'left'

} else {

shape.site = 'right'

}

}

} else {

shape.site = shape.side

}

// console.log("向量", vector);

// TODO

/**

* y ^

* | B(x1, y1)

* | /┆

* | / ┆

* | / ┆

* | / ┆

* |/----┼----+ F

* / C \┆

* /| ┆\D

* / | ┆

* ——————————/——+—————┼———+————————> x

* / |O ┆ E

* / | ┆

* /_____|_____┆

* A(x, y) | G

* |

*

* 设平面坐标系 xoy 中,有线段 A(x, y)、B(x1, y1),计算出垂直于 AB 线段中点 C 上一侧的长度为 n 的线段 CD 两端点坐标

* _________________________

* 1:算出线段 AB 的长度:|AB| = √ (x - x1)^2 + (y - y1)^2

*

* 2:算出中点 C 的坐标:xc = x1 + (x - x1) / 2; yc = y1 + (y - y1) / 2

*

* 3:设过坐标原点 o 的长度为 n 的线段 OE,在 x 轴的正坐标上,则两点坐标为:O(0, 0),E(n, 0)

*

* 4:将 OE 平移至 C 点且 O 与 C 重合,得到 CF,则 F 点的坐标为:xf = n + xc; yf = yc

*

* 5:运用三角函数,计算各个角的关系

*

* 6:线段 AB 与 x 轴的夹角:sin(∠BAG) = sin(∠BCF) = cos(∠FCD) = BG/AB;

* cos(∠BAG) = cos(∠BCF) = sin(∠FCD) = AG/AB

*

* 7:通过三角函数关系可计算,XY平面中一点(x1,y1)经圆周旋转θ角度,得到点(x2,y2),公式如下

* x2 = x1 * cos(θ) - y1 * sin(θ)

* y2 = x1 * sin(θ) + y1 * cos(θ)

*

* 8:将 F 点经圆周旋转 ∠FCD 之后,得到 D 点

*

*/

// 线段长度

var ABLength = Math.abs(Math.sqrt(Math.pow((x1 - x2), 2) + Math.pow((y1 - y2), 2)))

// console.log("leng", ABLength);

// 线段中心点

var center_x1 = (x2 - x1) / 2 + x1

var center_y1 = (y2 - y1) / 2 + y1

// console.log("center", center_x1, center_y1);

// 设长度为100px的线段 OE 经过坐标原点,且 O 点在坐标原点上,E点在 x 轴正坐标上,则这两点的坐标如下

var xO = 0

var yO = 0

var xE = 20

var yE = 0

// 箭头两个角的点:a, b

/**

* ∧

* a/__\ b

* ||

* ||

*/

var xArrowAngle_1 = xE - 5

var yArrowAngle_1 = yE + 3

var xArrowAngle_2 = xE - 5

var yArrowAngle_2 = yE - 3

// 设线段为直角三角形的斜边,则两条直角边长度为

var xLength = Math.abs(x1 - x2)

var yLength = Math.abs(y1 - y2)

// console.log("边长", xLength, yLength);

// 判断线段方向【注意,在此处坐标系为屏幕坐标系,Y 轴应与普通平面坐标系反向,即从上到下为正向】

// 计算线段与 x 轴的夹角三角函数

var cosAngle = yLength / ABLength

var sinAngle = xLength / ABLength

if (x1 < x2 && y1 < y2) {

// 左上到右下 ↘

sinAngle = -sinAngle

} else if (x1 > x2 && y1 > y2) {

// 右下到左上 ↖

sinAngle = -sinAngle

} else if (x1 < x2 && y1 > y2) {

// 左下到右上 ↗

} else if (x1 > x2 && y1 < y2) {

// 右上到左下 ↙

}

// console.log("三角", cosAngle, sinAngle);

// 将 OE 进行旋转得到 OF

var xF = xE * cosAngle - yE * sinAngle

var yF = xE * sinAngle + yE * cosAngle

// 旋转箭头的两个角

var xArrowAngle_1_B = xArrowAngle_1 * cosAngle - yArrowAngle_1 * sinAngle

var yArrowAngle_1_B = xArrowAngle_1 * sinAngle + yArrowAngle_1 * cosAngle

var xArrowAngle_2_B = xArrowAngle_2 * cosAngle - yArrowAngle_2 * sinAngle

var yArrowAngle_2_B = xArrowAngle_2 * sinAngle + yArrowAngle_2 * cosAngle

// console.log("旋转", xO, yO, xF, yF);

// 将 OF 平移至线段中心点,且 O 点与中心点重合,则新的线段坐标如下

xO = center_x1

yO = center_y1

xF = center_x1 + xF

yF = center_y1 + yF

// console.log("move OE", xO, yO, xF, yF);

// 平移箭头的两个角

xArrowAngle_1_B = center_x1 + xArrowAngle_1_B

yArrowAngle_1_B = center_y1 + yArrowAngle_1_B

xArrowAngle_2_B = center_x1 + xArrowAngle_2_B

yArrowAngle_2_B = center_y1 + yArrowAngle_2_B

if (shape.around === 'double') {

// 双向画线,先画第一侧

ctx.moveTo(xO, yO)

ctx.lineTo(xF, yF)

ctx.lineTo(xArrowAngle_1_B, yArrowAngle_1_B)

ctx.lineTo(xArrowAngle_2_B, yArrowAngle_2_B)

ctx.lineTo(xF, yF)

}

// 计算 F 点在线段的哪一侧,是否和光标点击点位于同一侧,或者双向画箭头

var vectorF = (x1 - xF) * (y2 - yF) - (y1 - yF) * (x2 - xF)

// if ((vector > 0 && vectorF < 0) || (vector < 0 && vectorF > 0) || shape.around == "double") {

if ((shape.site === 'right' && vectorF < 0) ||

(shape.site === 'left' && vectorF > 0) ||

shape.around === 'double') {

// 不同侧,旋转Angle之后再旋转180°

xF = xE * cosAngle - yE * sinAngle

yF = xE * sinAngle + yE * cosAngle

// 因为 sin(180) = 0; cos(180) = -1; 所以此处直接简写

xF = -xF

yF = -yF

xF = center_x1 + xF

yF = center_y1 + yF

// 旋转箭头的两个角

xArrowAngle_1_B = xArrowAngle_1 * cosAngle - yArrowAngle_1 * sinAngle

yArrowAngle_1_B = xArrowAngle_1 * sinAngle + yArrowAngle_1 * cosAngle

xArrowAngle_2_B = xArrowAngle_2 * cosAngle - yArrowAngle_2 * sinAngle

yArrowAngle_2_B = xArrowAngle_2 * sinAngle + yArrowAngle_2 * cosAngle

xArrowAngle_1_B = center_x1 - xArrowAngle_1_B

yArrowAngle_1_B = center_y1 - yArrowAngle_1_B

xArrowAngle_2_B = center_x1 - xArrowAngle_2_B

yArrowAngle_2_B = center_y1 - yArrowAngle_2_B

}

// 画线

ctx.moveTo(xO, yO)

ctx.lineTo(xF, yF)

ctx.lineTo(xArrowAngle_1_B, yArrowAngle_1_B)

ctx.lineTo(xArrowAngle_2_B, yArrowAngle_2_B)

ctx.lineTo(xF, yF)

}

})

Logo

魔乐社区(Modelers.cn) 是一个中立、公益的人工智能社区,提供人工智能工具、模型、数据的托管、展示与应用协同服务,为人工智能开发及爱好者搭建开放的学习交流平台。社区通过理事会方式运作,由全产业链共同建设、共同运营、共同享有,推动国产AI生态繁荣发展。

更多推荐