@leafer-in/arrow
Version:
512 lines (428 loc) • 15.3 kB
JavaScript
import { LineData, decorateLeafAttr, attr, doBoundsType, doStrokeType, dataProcessor, registerUI, Line, PathArrow, PathCommandMap, PointHelper, DataHelper, isObject, Plugin, UI, Paint } from "@leafer-ui/draw";
function __decorate(decorators, target, key, desc) {
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc); else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
return c > 3 && r && Object.defineProperty(target, key, r), r;
}
typeof SuppressedError === "function" ? SuppressedError : function(error, suppressed, message) {
var e = new Error(message);
return e.name = "SuppressedError", e.error = error, e.suppressed = suppressed, e;
};
class ArrowData extends LineData {}
function arrowType(defaultValue) {
return decorateLeafAttr(defaultValue, key => attr({
set(value) {
if (this.__setAttr(key, value)) {
const data = this.__;
const useArrow = data.startArrow !== "none" || data.endArrow !== "none";
if (data.__useArrow !== useArrow) doBoundsType(this);
data.__useArrow = useArrow;
doStrokeType(this);
}
}
}));
}
let Arrow = class Arrow extends Line {
get __tag() {
return "Arrow";
}
constructor(data) {
super(data);
this.__.__useArrow = true;
}
static registerArrow(name, data) {
PathArrow.register(name, data);
}
};
__decorate([ dataProcessor(ArrowData) ], Arrow.prototype, "__", void 0);
__decorate([ arrowType("angle") ], Arrow.prototype, "endArrow", void 0);
Arrow = __decorate([ registerUI() ], Arrow);
const {M: M$1, L: L$1, C: C$1, Q: Q$1, O: O$1} = PathCommandMap;
const {rotate: rotate$1, copyFrom: copyFrom$1, scale: scale} = PointHelper;
const point = {};
const PathMatrixHelper = {
layout(data, x, y, scaleX, scaleY, rotation, origin) {
let command, i = 0, j, len = data.length;
while (i < len) {
command = data[i];
switch (command) {
case M$1:
case L$1:
setPoint$1(data, i + 1, x, y, scaleX, scaleY, rotation, origin);
i += 3;
break;
case C$1:
for (j = 1; j < 6; j += 2) setPoint$1(data, i + j, x, y, scaleX, scaleY, rotation, origin);
i += 7;
break;
case Q$1:
for (j = 1; j < 4; j += 2) setPoint$1(data, i + j, x, y, scaleX, scaleY, rotation, origin);
i += 5;
break;
case O$1:
data[i + 1] += x;
data[i + 2] += y;
if (scaleX) data[i + 3] *= scaleX;
if (rotation) {
data[i + 4] += rotation;
data[i + 5] += rotation;
}
i += 7;
break;
}
}
},
rotate(data, rotation, center) {
PathMatrixHelper.layout(data, 0, 0, 1, 1, rotation, center);
}
};
function setPoint$1(data, startIndex, x, y, scaleX, scaleY, rotation, origin) {
copyFrom$1(point, data[startIndex], data[startIndex + 1]);
if (rotation) rotate$1(point, rotation, origin);
if (scaleX) scale(point, scaleX, scaleY);
data[startIndex] = x + point.x;
data[startIndex + 1] = y + point.y;
}
const {layout: layout, rotate: rotate} = PathMatrixHelper;
const {getAngle: getAngle} = PointHelper;
const zero = {
x: 0
}, half = {
x: -.5
};
const angle = {
connect: zero,
offset: {
x: -.71,
bevelJoin: .36,
roundJoin: .22
},
path: [ 1, -3, -3, 2, 0, 0, 2, -3, 3 ]
};
const angleSide = {
connect: half,
offset: {
x: -1.207,
bevelJoin: .854,
roundJoin: .707
},
path: [ 1, -3, -3, 2, 0, 0, 2, -1, 0 ]
};
const triangleLinePath = [ 1, -3, 0, 2, -3, -2, 2, 0, 0, 2, -3, 2, 2, -3, 0 ];
const triangleFill = {
connect: zero,
offset: {
x: -.9,
bevelJoin: .624,
roundJoin: .4
},
path: [ ...triangleLinePath ]
};
const triangle = DataHelper.clone(triangleFill);
triangle.path = [ ...triangleLinePath, 1, -2.05, 1.1, 2, -2.05, -1.1 ];
triangle.dashPath = [ 1, -2, 0, 2, -.5, 0 ];
const triangleFlipFill = {
connect: zero,
offset: half,
path: [ ...triangleFill.path ]
};
const triangleFlip = {
connect: zero,
offset: half,
path: [ ...triangle.path ],
dashPath: [ 1, -2.5, 0, 2, -1, 0 ]
};
rotate(triangleFlipFill.path, 180, {
x: -1.5,
y: 0
});
rotate(triangleFlip.path, 180, {
x: -1.5,
y: 0
});
const arrowLinePath = [ 1, -3, 0, 2, -3.5, -1.8, 2, 0, 0, 2, -3.5, 1.8, 2, -3, 0 ];
const arrowFill = {
connect: zero,
offset: {
x: -1.1,
bevelJoin: .872,
roundJoin: .6
},
path: [ ...arrowLinePath ]
};
const arrow = DataHelper.clone(arrowFill);
arrow.path = [ ...arrowLinePath, 1, -2.25, .95, 2, -2.25, -.95 ];
arrow.dashPath = [ 1, -3, 0, 2, -.5, 0 ];
const circleLine = {
connect: {
x: -1.8
},
path: [ 1, 1.8, -.1, 2, 1.8, 0, 26, 0, 0, 1.8, 0, 359, 0 ]
};
const circleFill = {
connect: {
x: .5
},
path: [ ...circleLine.path ]
};
const circle = DataHelper.clone(circleFill);
circle.path = [ ...circleLine.path, 1, 0, 0, 26, 0, 0, 1, 0, 360, 0 ];
circle.dashPath = [ 1, -.5, 0, 2, .5, 0 ];
const squareLine = {
connect: {
x: -1.4
},
path: [ 1, -1.4, 0, 2, -1.4, -1.4, 2, 1.4, -1.4, 2, 1.4, 1.4, 2, -1.4, 1.4, 2, -1.4, 0 ]
};
const squareFill = {
path: [ ...squareLine.path ]
};
const square = {
path: [ ...squareLine.path, 2, -1.4, -.49, 2, 1, -.49, 1, -1.4, .49, 2, 1, .49 ]
};
const diamondLine = DataHelper.clone(squareLine);
diamondLine.connect.x = -1.9;
const diamondFill = DataHelper.clone(squareFill);
const diamond = DataHelper.clone(square);
rotate(diamondLine.path, 45);
rotate(diamondFill.path, 45);
rotate(diamond.path, 45);
const mark = {
offset: half,
path: [ 1, 0, -2, 2, 0, 2 ]
};
const arrows = {
angle: angle,
"angle-side": angleSide,
arrow: arrow,
triangle: triangle,
"triangle-flip": triangleFlip,
circle: circle,
"circle-line": circleLine,
square: square,
"square-line": squareLine,
diamond: diamond,
"diamond-line": diamondLine,
mark: mark
};
const fillArrows = {
triangle: triangleFill,
"triangle-flip": triangleFlipFill,
arrow: arrowFill,
circle: circleFill,
square: squareFill,
diamond: diamondFill
};
function getArrowPath(ui, arrow, from, to, size, connectOffset, hasDashPattern) {
let pathData, scale = 1, rotation = 0, fill;
const {strokeCap: strokeCap, strokeJoin: strokeJoin} = ui.__;
if (isObject(arrow)) {
if (arrow.type) {
scale = arrow.scale || 1;
rotation = arrow.rotation || 0;
pathData = arrows[arrow.type];
if (scale > 1) {
const fillData = fillArrows[arrow.type];
if (fillData) pathData = fillData, fill = true;
}
} else pathData = arrow;
} else {
pathData = arrows[arrow];
}
if (!fill) fill = pathData.fill;
const {offset: offset, connect: connect, path: path, dashPath: dashPath} = pathData;
let connectX = connect ? connect.x * scale : 0;
let offsetX = offset ? offset.x : 0;
const data = [ ...path ];
if (hasDashPattern && dashPath) data.push(...dashPath);
if (strokeCap !== "none") connectX -= .5;
if (offset) {
if (strokeJoin === "round" && offset.roundJoin) offsetX += offset.roundJoin; else if (strokeJoin === "bevel" && offset.bevelJoin) offsetX += offset.bevelJoin;
}
if (scale !== 1) layout(data, 0, 0, scale, scale);
if (offsetX) layout(data, offsetX, 0);
layout(data, to.x, to.y, size, size, getAngle(from, to) + rotation);
connectOffset.x = (connectX + offsetX) * size;
const arrowData = {
data: data
};
if (fill) arrowData.fill = fill;
return arrowData;
}
const {M: M, L: L, C: C, Q: Q, Z: Z, N: N, D: D, X: X, G: G, F: F, O: O, P: P, U: U} = PathCommandMap;
const {copy: copy, copyFrom: copyFrom, getDistancePoint: getDistancePoint, isSame: isSame} = PointHelper;
const {stintSet: stintSet} = DataHelper;
const connectPoint = {};
const first = {}, second = {};
const old = {}, last = {}, now = {};
const PathArrowModule = {
list: arrows,
fillList: fillArrows,
addArrows(ui, updateCache) {
const uData = ui.__;
const {startArrow: startArrow, endArrow: endArrow, __strokeWidth: strokeWidth, dashPattern: dashPattern, __pathForRender: data, cornerRadius: cornerRadius} = uData;
const clonePathForArrow = !cornerRadius;
if (!updateCache) uData.__strokeWidthCache = strokeWidth;
let startArrowPath, singleStartArrow, endArrowPath, singleEndArrow;
let command, i = 0, len = data.length, count = 0, useStartArrow = startArrow && startArrow !== "none";
while (i < len) {
command = data[i];
switch (command) {
case M:
case L:
if (count < 2 || i + 6 >= len) {
copy(old, now);
copyFrom(now, data[i + 1], data[i + 2]);
if (!count && useStartArrow) copy(first, now);
}
i += 3;
break;
case C:
if (count === 1 || i + 7 >= len - 3) {
copyPoints(data, last, now, i + 3);
old.x = data[i + 1], old.y = data[i + 2];
}
i += 7;
break;
case Q:
if (count === 1 || i + 5 >= len - 3) {
copyPoints(data, last, now, i + 1);
copy(old, last);
}
i += 5;
break;
case Z:
return;
case N:
i += 5;
break;
case D:
i += 9;
break;
case X:
i += 6;
break;
case G:
i += 9;
break;
case F:
i += 5;
break;
case O:
i += 7;
break;
case P:
i += 4;
break;
case U:
if (count === 1 || i + 6 >= len - 3) {
copyPoints(data, last, now, i + 1);
copy(old, last);
if (i + 6 !== len) {
now.x -= (now.x - last.x) / 10;
now.y -= (now.y - last.y) / 10;
}
}
i += 6;
break;
}
count++;
if (count === 1 && command !== M) return;
if (count === 2 && useStartArrow) copy(second, command === L ? now : isSame(old, first) ? last : old);
if (i === len) {
const path = uData.__pathForRender = clonePathForArrow ? [ ...data ] : data;
if (useStartArrow) {
startArrowPath = getArrowPath(ui, startArrow, second, first, strokeWidth, connectPoint, !!dashPattern);
singleStartArrow = startArrowPath.fill || dashPattern;
if (!singleStartArrow) path.push(...startArrowPath.data);
if (connectPoint.x) {
getDistancePoint(first, second, -connectPoint.x, true);
path[1] = second.x;
path[2] = second.y;
}
}
if (endArrow && endArrow !== "none") {
if (isSame(last, now)) copy(last, old);
endArrowPath = getArrowPath(ui, endArrow, last, now, strokeWidth, connectPoint, !!dashPattern);
singleEndArrow = endArrowPath.fill || dashPattern;
if (!singleEndArrow) path.push(...endArrowPath.data);
if (connectPoint.x) {
getDistancePoint(now, last, -connectPoint.x, true);
let index;
switch (command) {
case L:
index = i - 3 + 1;
break;
case C:
index = i - 7 + 5;
break;
case Q:
index = i - 5 + 3;
break;
case U:
index = i - 6 + 3;
break;
}
if (index) setPoint(path, last, index);
}
}
} else {
copy(last, now);
}
stintSet(uData, "__startArrowPath", singleStartArrow && startArrowPath);
stintSet(uData, "__endArrowPath", singleEndArrow && endArrowPath);
}
},
updateArrow(ui) {
const data = ui.__;
if (data.strokeScaleFixed) {
if (data.__strokeWidthCache !== data.__strokeWidth) {
ui.__updateRenderPath(true);
}
}
},
register(name, data, fillData) {
this.list[name] = data;
if (fillData) this.fillList[name] = data;
},
get(name) {
return this.list[name];
}
};
function copyPoints(data, from, to, startIndex) {
copyFrom(from, data[startIndex], data[startIndex + 1]);
copyFrom(to, data[startIndex + 2], data[startIndex + 3]);
}
function setPoint(data, point, startIndex) {
data[startIndex] = point.x;
data[startIndex + 1] = point.y;
}
Plugin.add("arrow");
UI.addAttr("startArrow", "none", arrowType);
UI.addAttr("endArrow", "none", arrowType);
Object.assign(PathArrow, PathArrowModule);
Object.assign(Paint, {
strokeArrow(stroke, ui, canvas, _renderOptions) {
const {__startArrowPath: __startArrowPath, __endArrowPath: __endArrowPath, dashPattern: dashPattern} = ui.__;
if (dashPattern) canvas.dashPattern = null;
if (__startArrowPath) {
canvas.beginPath();
ui.__drawPathByData(canvas, __startArrowPath.data);
canvas.stroke();
if (__startArrowPath.fill) {
canvas.fillStyle = stroke;
canvas.fill();
}
}
if (__endArrowPath) {
canvas.beginPath();
ui.__drawPathByData(canvas, __endArrowPath.data);
canvas.stroke();
if (__endArrowPath.fill) {
canvas.fillStyle = stroke;
canvas.fill();
}
}
}
});
export { Arrow, ArrowData, PathArrowModule, PathMatrixHelper, arrowType };