console
class Point {
constructor(x, y) {
this.x = x
this.y = y
}
X = () => { return this.x }
Y = () => { return this.y }
}
let calcMoveLine = (line, offset) => {
let [p1, p2] = line
// let toggle = false
// if (p1.Y() < p2.Y()) {
// let p = p1
// p1 = p2
// p2 = p
// toggle = true
// }
// let lVer = p2.Y() - p1.Y() // vertical
// let lHor = p2.X() - p1.X() // horizontal
let p1p2 = [p1.X()-p2.X(), p1.Y()-p2.Y()]
let len = Math.hypot(p1p2[0], p1p2[1])
let n = null
// if (p1.Y() > p2.Y()){}
if (p1.Y() < p2.Y()) {
n = new Point((p2.Y() - p1.Y())/len, (p1.X() - p2.X())/len)
} else {
n = new Point((p1.Y() - p2.Y())/len, (p2.X() - p1.X())/len)
}
let newline = [
new Point(p1.X() + n.X() * offset, p1.Y() + n.Y() * offset),
new Point(p2.X() + n.X() * offset, p2.Y() + n.Y() * offset)
]
return [
new Point(p1.X() + n.X() * offset, p1.Y() + n.Y() * offset),
new Point(p2.X() + n.X() * offset, p2.Y() + n.Y() * offset)
]
}
let calcMoveLine2 = (line, offset) => {
let [p1, p2] = line
let lVer = p2.Y() - p1.Y() // vertical
let lHor = p2.X() - p1.X() // horizontal
let len = Math.hypot(lVer, lHor)
let rad = offset*1.0/len
let ver = lVer*rad*-1
let hor = lHor*rad*-1
return [
new Point(p1.X()+ver, p1.Y()+hor),
new Point(p2.X()+ver, p2.Y()+hor),
]
}
let _index = 1
let img = new Image()
img.src = 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAA8AAAAPCAYAAAA71pVKAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAAIBJREFUeNpiYBj04P////xAnADEzDAxJiI1egOpC0DMzsjI+JdY27SBeP9/CHgFxJzEaBIA4qb/qKCEGI0pQPwcTeN7grYCFcz4jx1MJ9afaUD8Ek3zc1KiRQiIJ6MZkEZq3BoA8Qmo5qfkJpAYkGZQYJJrACj6EpFT2MABgAADADTCu3dUzSmkAAAAAElFTkSuQmCC'
let drawLine = (ctx, lines) => {
ctx.beginPath()
ctx.lineCap = 'round'
lines.forEach((line) => {
// ctx.beginPath()
// layer 1
ctx.moveTo(line[0].X(), line[0].Y())
ctx.lineTo(line[1].X(), line[1].Y())
ctx.lineWidth = 10
ctx.strokeStyle = '#0A404A'
})
ctx.stroke()
ctx.closePath()
ctx.beginPath()
ctx.lineCap = 'round'
lines.forEach((line) => {
// layer 2
let [d1, d2] = calcMoveLine(line, 2)
ctx.moveTo(line[0].X(), line[0].Y())
ctx.lineTo(line[1].X(), line[1].Y())
ctx.lineWidth = 6
ctx.strokeStyle = '#146573'
})
ctx.stroke()
ctx.closePath()
ctx.beginPath()
ctx.lineCap = 'round'
lines.forEach((line) => {
// layer 3
let [d3, d4] = calcMoveLine(line, 0)
ctx.lineWidth = 1
ctx.strokeStyle = '#1F9EBD'
ctx.moveTo(line[0].X(), line[0].Y())
ctx.lineTo(line[1].X(), line[1].Y())
ctx.stroke()
})
lines.forEach(([p1, p2]) => {
var offset = (_index%11) * 0.1;
// arrowTo(ctx,
// { x: p1.X(), y: p1.Y() },
// { x: p2.X(), y: p2.Y() },
// { offset: offset, color: "white", justifyAlign: false, lineWidth:0, arrowLineWidth: 0 }
// );
console.log(offset)
generatePoints(p1, p2, 20, ctx , offset, img)
_index = ++_index>=10 ? 0 : _index
})
ctx.closePath()
}
let canvas = document.getElementById('c1')
let c2 = document.getElementById('c2')
canvas.width = 450
canvas.height = 700
c2.width= 450
c2.height = 700
let ctx = canvas.getContext('2d')
let ctx2 = c2.getContext('2d')
ctx2.fillStyle = '#ccc'
ctx2.fillRect(0, 0, 450, 700)
// ctx.scale(2, 2)
draw = () => {
ctx.clearRect(0, 0, 700, 700)
ctx.moveTo(-10, 0)
ctx.lineTo(10, 0)
ctx.moveTo(0, -10)
ctx.lineTo(0, 10)
ctx.stroke()
ctx.translate(200,200)
drawLine(ctx, [
[new Point(250, 200.0), new Point(200.0, 50.0)],
[new Point(100, 50.0), new Point(200.0, 50.0)],
[new Point(150, 50.0), new Point(100.0, 150.0)],
[new Point(250, 150.0), new Point(100.0, 50.0)],
// [new Point(100.0, 210.0), new Point(10.0, 10.0)],
// [new Point(100, 210), new Point(300, 200)]
])
ctx2.clearRect(0, 0, 350, 700)
ctx2.drawImage(canvas, 0, 0)
// requestAnimationFrame(draw)
}
// setInterval(draw, 5000)
draw()
function generatePoints (startP, endP, stepSize = 30, ctx, aniOffset = 0.5, img) {
let radA = Math.atan((endP.Y() - startP.Y()) / (endP.X() - startP.X()))
// radA = Math.atan2(startP, endP)
if ((endP.X() - startP.X()) < 0) {
radA += Math.PI
}
let p1p2 = [startP.X() - endP.X(), startP.Y() - endP.Y()]
let dist = Math.hypot(p1p2[0], p1p2[1])
// console.log('draw arrow: (' + startP.X() + ',' + startP.Y() + ') - (' + endP.X() + ',' + endP.Y() + ') = ' + dist, radA)
// const dist = calcDist(startP, endP)
let points = []
const steps = dist / stepSize
const drawImg = (pX, pY) => {
if (img && ctx) {
ctx.save()
ctx.translate(pX, pY) // consider img position and imgWidth/Height.
ctx.rotate(radA)
// ctx.drawImage(img, pX, pY)
ctx.scale(0.5, 0.5)
ctx.drawImage(img, -img.width / 2, -img.height / 2)
ctx.restore()
}
}
// gen points by stepSize.. if enable corner arrow, start s with (0~1) float number.
for (let s = aniOffset; s <= steps; s += 1) {
const pX = Math.round(startP.X() + s * stepSize * Math.cos(radA))
const pY = Math.round(startP.Y() + s * stepSize * Math.sin(radA))
// console.log('arrow coorinate ', pX, pY)
points.push([pX, pY])
drawImg(pX, pY)
// arrowTo(ctx, { x: 200, y: 400 }, { x: 500, y: 400 }, { offset: offset, color: "white", justifyAlign: false });
}
// console.warn(`icon Number: ${points.length}`);
return points
}
// var offset = (_index % 3) * 5;
// arrowTo(ctx, { x: 200, y: 400 }, { x: 500, y: 400 }, { offset: offset, color: "white", justifyAlign: false });
// arrowTo(ctx, { x: 500, y: 400 }, { x: 450, y: 580 }, { offset: offset, color: "red", justifyAlign: false });
// arrowTo(ctx, { x: 450, y: 580 }, { x: 160, y: 580 }, { offset: offset, color: "red", justifyAlign: false });
// arrowTo(ctx, { x: 160, y: 580 }, { x: 200, y: 400 }, { offset: offset, color: "red", justifyAlign: false });
// 画两点间箭头
// ctx - 画布上下文(我理解成画笔)
// p1 - 起点
// p2 - 终点
// arrowOptions -- 画箭头的选项
function arrowTo(ctx, p1, p2, arrowOptions) {
// 初始化参数
var opts = {
startOffset: 5, // 起点的留空长度
endOffset: 5, // 终点的留空长度
offset: 0, // 偏移位(模拟动画效果用, 使用时建议将justifyAlign设为false)
color: '#E6E6FA', // 默认颜色
activeIndex: -1, // 高亮箭头的索引, 超出回到一圈起始位置。(默认-1,不做高亮处理)
activeColor: "#00FF00", // 高亮颜色(Highligh Color)
stepLength: 10, // 间隔(步长)
justifyAlign: true, // 两端对齐(两边撑满, 配合activeIndex > 0时使用)
arrowLength: 0, // 箭头长度(柄到顶点)
arrowTheta: 25, // 箭头两边的夹角(度数)
arrowHeadlen: 6, // 箭头两边斜边长度
arrowLineWidth: 1, // 画箭头的线宽度
lineWidth: 1, // 两点间的连丝宽度(>0时,有效)
};
if (arrowOptions !== undefined && arrowOptions !== null) {
opts = Object.assign(opts, arrowOptions);
}
// 画连结两点的线
if (opts.lineWidth > 0) {
ctx.beginPath();
ctx.moveTo(p1.x, p1.y);
ctx.lineTo(p2.x, p2.y);
//颜色,线宽
ctx.strokeStyle = opts.color;
ctx.lineWidth = opts.lineWidth;
ctx.stroke();
ctx.closePath();
}
// 计两点距离
var len = Math.floor(Math.sqrt(Math.pow(p1.x - p2.x, 2) + Math.pow(p1.y - p2.y, 2)));
// 计算画多少个箭头(注意:最后一个箭头是不需要间隔(步长),所以可用长度要加一个opts.stepLength)
var loops = Math.floor((len - (opts.startOffset + opts.offset + opts.endOffset) + opts.stepLength) / (opts.arrowLength + opts.stepLength));
// 两端对齐(两边撑满),重算步长
if (opts.justifyAlign === true) {
opts.stepLength = (len - (opts.startOffset + opts.offset + opts.endOffset) - (opts.arrowLength * loops)) / (loops - 1);
}
// 高亮箭头的索引, 超出回到一圈起始位置。(用于动画效果)
var highlightIndex = 0; // 0 - 无动画效果
if (opts.activeIndex > 0) {
if ((opts.activeIndex % loops) === 0) {
highlightIndex = loops;
}
else {
highlightIndex = opts.activeIndex % loops;
}
}
var hudu = Math.atan2(p1.y - p2.y, p2.x - p1.x); // 计算p1, p2两点的倾斜度(弧度)。(注意参数:p1.y - p2.y, p2.x - p1.x, 请勿搞错)
var p0 = { x: p1.x, y: p1.y }; // 原点坐标, 作为圆心。 (辅助计算箭头起点(柄)与顶点的坐标)
var r; // 半径。 (辅助计算箭头起点(柄)与顶点的坐标)
var color;
for (var i = 0; i < loops; i++) {
// 箭头起点(柄)
r = (opts.startOffset + opts.offset) + (opts.arrowLength + opts.stepLength) * i; // 原点到箭头起点(柄)的半径
p1 = {
x: p0.x + Math.cos(hudu) * r,
y: p0.y - Math.sin(hudu) * r
};
// 箭头终点(顶点)
r = r + opts.arrowLength; // 原点到箭头顶点(柄)的半径
p2 = {
x: p0.x + Math.cos(hudu) * r,
y: p0.y - Math.sin(hudu) * r
};
// 画一个箭头
if (highlightIndex > 0 && i === (highlightIndex - 1)) {
color = opts.activeColor; //高亮箭头(动画效果)
}
else {
color = opts.color;
}
drawArrow(ctx, p1, p2, opts.arrowTheta, opts.arrowHeadlen, opts.arrowLineWidth, color);
}
}
// 画箭头
// ctx - 画布上下文(我理解成画笔)
// p1 - 起点
// p2 - 终点
// theta -- 夹角theta (是度数,不是弧度)
// headlen -- 斜边长度
// width -- 线宽
// color -- 颜色
function drawArrow(ctx, p1, p2, theta, headlen, width, color) {
theta = (theta !== undefined && theta !== null) ? theta : 25; //夹角(度数)
headlen = (headlen !== undefined && headlen !== null) ? headlen : 6; //斜边长度
width = (width !== undefined && width !== null) ? width : 1; //线宽
color = (color !== undefined && color !== null) ? color : '#000'; //颜色
var angle = Math.atan2(p1.y - p2.y, p1.x - p2.x) * 180 / Math.PI, //倾斜度(度数)
angle1 = (angle + theta) * Math.PI / 180, //夹角1
angle2 = (angle - theta) * Math.PI / 180, //夹角2
topX = headlen * Math.cos(angle1), //箭头上面点, X偏移位
topY = headlen * Math.sin(angle1), //箭头上面点, Y偏移位
botX = headlen * Math.cos(angle2), //箭头下面点, X偏移位
botY = headlen * Math.sin(angle2); //箭头下面点, Y偏移位
ctx.save();
ctx.beginPath();
//连结两点的线
// ctx.moveTo(p1.x, p1.y);
// ctx.lineTo(p2.x, p2.y);
//终点箭头的两侧
var arrowX = p2.x + topX;
var arrowY = p2.y + topY;
ctx.moveTo(arrowX, arrowY);
ctx.lineTo(p2.x, p2.y); //终点
let arrowX2 = p2.x + botX;
let arrowY2 = p2.y + botY;
ctx.lineTo(arrowX2, arrowY2);
ctx.lineTo(arrowX, arrowY);
//颜色,线宽
ctx.strokeStyle = color;
ctx.lineWidth = width;
ctx.stroke();
ctx.restore();
}
// 为Object扩展assign方法(合拼多个对象的属性,返回一个新对象)
if (!Object.assign) {
Object.defineProperty(Object, "assign", {
enumerable: false,
configurable: true,
writable: true,
value: function (target, firstSource) {
"use strict";
if (target === undefined || target === null)
throw new TypeError("Cannot convert first argument to object");
var to = Object(target);
for (var i = 1; i < arguments.length; i++) {
var nextSource = arguments[i];
if (nextSource === undefined || nextSource === null) continue;
var keysArray = Object.keys(Object(nextSource));
for (var nextIndex = 0, len = keysArray.length; nextIndex < len; nextIndex++) {
var nextKey = keysArray[nextIndex];
var desc = Object.getOwnPropertyDescriptor(nextSource, nextKey);
if (desc !== undefined && desc.enumerable) to[nextKey] = nextSource[nextKey];
}
}
return to;
}
})
}
<canvas id='c1'></canvas>
<canvas id='c2'></canvas>