Files
lan-manager/web/node_modules/@antv/g-canvas/lib/util/draw.js
openclaw 0a5f6a8047 Initial commit: Lan-manager project code
- Go backend (server/)
- Frontend (web/, server/static/)
- Database and deployment files
- Scripts and docs

Co-Authored-By: 狸花猫/Claude-Qwen3.6-Plus 🐾
2026-04-20 00:52:58 +08:00

344 lines
14 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.mergeView = exports.getMergedRegion = exports.getRefreshRegion = exports.refreshElement = exports.drawPath = exports.clearChanged = exports.checkChildrenRefresh = exports.checkRefresh = exports.drawChildren = exports.applyAttrsToContext = void 0;
var util_1 = require("@antv/util");
var parse_1 = require("./parse");
var arc_params_1 = require("./arc-params");
var util_2 = require("./util");
var ArrowUtil = require("../util/arrow");
var SHAPE_ATTRS_MAP = {
fill: 'fillStyle',
stroke: 'strokeStyle',
opacity: 'globalAlpha',
};
function applyAttrsToContext(context, element) {
var attrs = element.attr();
for (var k in attrs) {
var v = attrs[k];
// 转换一下不与 canvas 兼容的属性名
var name_1 = SHAPE_ATTRS_MAP[k] ? SHAPE_ATTRS_MAP[k] : k;
if (name_1 === 'matrix' && v) {
// 设置矩阵
context.transform(v[0], v[1], v[3], v[4], v[6], v[7]);
}
else if (name_1 === 'lineDash' && context.setLineDash) {
// 设置虚线,只支持数组形式,非数组形式不做任何操作
util_1.isArray(v) && context.setLineDash(v);
}
else {
if (name_1 === 'strokeStyle' || name_1 === 'fillStyle') {
// 如果存在渐变、pattern 这个开销有些大
// 可以考虑缓存机制,通过 hasUpdate 来避免一些运算
v = parse_1.parseStyle(context, element, v);
}
else if (name_1 === 'globalAlpha') {
// opacity 效果可以叠加,子元素的 opacity 需要与父元素 opacity 相乘
v = v * context.globalAlpha;
}
context[name_1] = v;
}
}
}
exports.applyAttrsToContext = applyAttrsToContext;
function drawChildren(context, children, region) {
for (var i = 0; i < children.length; i++) {
var child = children[i];
if (child.cfg.visible) {
child.draw(context, region);
}
else {
child.skipDraw();
}
}
}
exports.drawChildren = drawChildren;
// 这个地方的逻辑比较复杂简单画了一张图https://www.yuque.com/antv/ou292n/pcgt5g#OW1QE
function checkRefresh(canvas, children, region) {
var refreshElements = canvas.get('refreshElements');
// 先遍历需要刷新的元素,将这些元素的父元素也设置 refresh
util_1.each(refreshElements, function (el) {
if (el !== canvas) {
var parent_1 = el.cfg.parent;
while (parent_1 && parent_1 !== canvas && !parent_1.cfg.refresh) {
parent_1.cfg.refresh = true;
parent_1 = parent_1.cfg.parent;
}
}
});
if (refreshElements[0] === canvas) {
setChildrenRefresh(children, region);
}
else {
// 检查所有子元素是否可以刷新
checkChildrenRefresh(children, region);
}
}
exports.checkRefresh = checkRefresh;
// 检查所有的子元素是否应该更新
function checkChildrenRefresh(children, region) {
for (var i = 0; i < children.length; i++) {
var child = children[i];
if (child.cfg.visible) {
// 先判断 hasChanged因为它的优先级判断应该高于 refresh
if (child.cfg.hasChanged) {
// 如果节点发生了 change则需要级联设置子元素的 refresh
child.cfg.refresh = true;
if (child.isGroup()) {
setChildrenRefresh(child.cfg.children, region);
}
}
else if (child.cfg.refresh) {
// 如果当前图形/分组 refresh = true说明其子节点存在 changed
if (child.isGroup()) {
checkChildrenRefresh(child.cfg.children, region);
}
}
else {
// 这个分支说明此次局部刷新,所有的节点和父元素没有发生变化,仅需要检查包围盒(缓存)是否相交即可
var refresh = checkElementRefresh(child, region);
child.cfg.refresh = refresh;
if (refresh && child.isGroup()) {
// 如果需要刷新,说明子元素也需要刷新,继续进行判定
checkChildrenRefresh(child.cfg.children, region);
}
}
}
}
}
exports.checkChildrenRefresh = checkChildrenRefresh;
// 由于对改变的图形放入 refreshElements 时做了优化,判定父元素 changed 时不加入
// 那么有可能会出现 elements 都为空,所以最终 group
function clearChanged(elements) {
for (var i = 0; i < elements.length; i++) {
var el = elements[i];
el.cfg.hasChanged = false;
// 级联清理
if (el.isGroup() && !el.destroyed) {
clearChanged(el.cfg.children);
}
}
}
exports.clearChanged = clearChanged;
// 当某个父元素发生改变时,调用这个方法级联设置 refresh
function setChildrenRefresh(children, region) {
for (var i = 0; i < children.length; i++) {
var child = children[i];
if (!child.cfg.visible) {
continue;
}
// let refresh = true;
// 获取缓存的 bbox如果这个 bbox 还存在则说明父元素不是矩阵发生了改变
// const bbox = child.cfg.canvasBBox;
// if (bbox) {
// // 如果这时候
// refresh = intersectRect(bbox, region);
// }
child.cfg.refresh = true;
// 如果需要刷新当前节点,所有的子元素设置 refresh
if (child.isGroup()) {
setChildrenRefresh(child.get('children'), region);
}
}
}
function checkElementRefresh(shape, region) {
var bbox = shape.cfg.cacheCanvasBBox;
var isAllow = shape.cfg.isInView && bbox && util_2.intersectRect(bbox, region);
return isAllow;
}
// 绘制 path
function drawPath(shape, context, attrs, arcParamsCache) {
var path = attrs.path, startArrow = attrs.startArrow, endArrow = attrs.endArrow;
if (!path) {
return;
}
var currentPoint = [0, 0]; // 当前图形
var startMovePoint = [0, 0]; // 开始 M 的点,可能会有多个
var distance = {
dx: 0,
dy: 0,
};
context.beginPath();
for (var i = 0; i < path.length; i++) {
var params = path[i];
var command = params[0];
if (i === 0 && startArrow && startArrow.d) {
var tangent = shape.getStartTangent();
distance = ArrowUtil.getShortenOffset(tangent[0][0], tangent[0][1], tangent[1][0], tangent[1][1], startArrow.d);
}
else if (i === path.length - 2 && path[i + 1][0] === 'Z' && endArrow && endArrow.d) {
// 为了防止结尾为 Z 的 segment 缩短不起效,需要取最后两个 segment 特殊处理
var lastPath = path[i + 1];
if (lastPath[0] === 'Z') {
var tangent = shape.getEndTangent();
distance = ArrowUtil.getShortenOffset(tangent[0][0], tangent[0][1], tangent[1][0], tangent[1][1], endArrow.d);
}
}
else if (i === path.length - 1 && endArrow && endArrow.d) {
if (path[0] !== 'Z') {
var tangent = shape.getEndTangent();
distance = ArrowUtil.getShortenOffset(tangent[0][0], tangent[0][1], tangent[1][0], tangent[1][1], endArrow.d);
}
}
var dx = distance.dx, dy = distance.dy;
// V,H,S,T 都在前面被转换成标准形式
switch (command) {
case 'M':
context.moveTo(params[1] - dx, params[2] - dy);
startMovePoint = [params[1], params[2]];
break;
case 'L':
context.lineTo(params[1] - dx, params[2] - dy);
break;
case 'Q':
context.quadraticCurveTo(params[1], params[2], params[3] - dx, params[4] - dy);
break;
case 'C':
context.bezierCurveTo(params[1], params[2], params[3], params[4], params[5] - dx, params[6] - dy);
break;
case 'A': {
var arcParams = void 0;
// 为了加速绘制,可以提供参数的缓存,各个图形自己缓存
if (arcParamsCache) {
arcParams = arcParamsCache[i];
if (!arcParams) {
arcParams = arc_params_1.default(currentPoint, params);
arcParamsCache[i] = arcParams;
}
}
else {
arcParams = arc_params_1.default(currentPoint, params);
}
var cx = arcParams.cx, cy = arcParams.cy, rx = arcParams.rx, ry = arcParams.ry, startAngle = arcParams.startAngle, endAngle = arcParams.endAngle, xRotation = arcParams.xRotation, sweepFlag = arcParams.sweepFlag;
// 直接使用椭圆的 api
if (context.ellipse) {
context.ellipse(cx, cy, rx, ry, xRotation, startAngle, endAngle, 1 - sweepFlag);
}
else {
var r = rx > ry ? rx : ry;
var scaleX = rx > ry ? 1 : rx / ry;
var scaleY = rx > ry ? ry / rx : 1;
context.translate(cx, cy);
context.rotate(xRotation);
context.scale(scaleX, scaleY);
context.arc(0, 0, r, startAngle, endAngle, 1 - sweepFlag);
context.scale(1 / scaleX, 1 / scaleY);
context.rotate(-xRotation);
context.translate(-cx, -cy);
}
break;
}
case 'Z':
context.closePath();
break;
default:
break;
}
// 有了 Z 后,当前节点从开始 M 的点开始
if (command === 'Z') {
currentPoint = startMovePoint;
}
else {
var len = params.length;
currentPoint = [params[len - 2], params[len - 1]];
}
}
}
exports.drawPath = drawPath;
// 刷新图形元素(Shape 或者 Group)
function refreshElement(element, changeType) {
var canvas = element.get('canvas');
// 只有存在于 canvas 上时生效
if (canvas) {
if (changeType === 'remove') {
// 一旦 remove则无法在 element 上拿到包围盒
// destroy 后所有属性都拿不到,所以需要暂存一下
// 这是一段 hack 的代码
element._cacheCanvasBBox = element.get('cacheCanvasBBox');
}
// 防止反复刷新
if (!element.get('hasChanged')) {
// 但是始终要标记为 hasChanged便于后面进行局部渲染
element.set('hasChanged', true);
// 本来只有局部渲染模式下,才需要记录更新的元素队列
// if (canvas.get('localRefresh')) {
// canvas.refreshElement(element, changeType, canvas);
// }
// 但对于 https://github.com/antvis/g/issues/422 的场景,全局渲染的模式下也需要记录更新的元素队列
// 如果当前元素的父元素发生了改变,可以不放入队列,这句话大概能够提升 15% 的初次渲染性能
if (!(element.cfg.parent && element.cfg.parent.get('hasChanged'))) {
canvas.refreshElement(element, changeType, canvas);
if (canvas.get('autoDraw')) {
canvas.draw();
}
}
}
}
}
exports.refreshElement = refreshElement;
function getRefreshRegion(element) {
var region;
if (!element.destroyed) {
var cacheBox = element.get('cacheCanvasBBox');
var validCache = cacheBox && !!(cacheBox.width && cacheBox.height);
var bbox = element.getCanvasBBox();
var validBBox = bbox && !!(bbox.width && bbox.height);
// 是否是有效 bbox 判定,一些 NaN 或者 宽高为 0 的情况过滤掉
if (validCache && validBBox) {
region = util_2.mergeRegion(cacheBox, bbox);
}
else if (validCache) {
region = cacheBox;
}
else if (validBBox) {
region = bbox;
}
}
else {
// 因为元素已经销毁所以无法获取到缓存的包围盒
region = element['_cacheCanvasBBox'];
}
return region;
}
exports.getRefreshRegion = getRefreshRegion;
function getMergedRegion(elements) {
if (!elements.length) {
return null;
}
var minXArr = [];
var minYArr = [];
var maxXArr = [];
var maxYArr = [];
util_1.each(elements, function (el) {
var region = getRefreshRegion(el);
if (region) {
minXArr.push(region.minX);
minYArr.push(region.minY);
maxXArr.push(region.maxX);
maxYArr.push(region.maxY);
}
});
return {
minX: util_1.min(minXArr),
minY: util_1.min(minYArr),
maxX: util_1.max(maxXArr),
maxY: util_1.max(maxYArr),
};
}
exports.getMergedRegion = getMergedRegion;
function mergeView(region, viewRegion) {
if (!region || !viewRegion) {
return null;
}
// 不相交,则直接返回 null
if (!util_2.intersectRect(region, viewRegion)) {
return null;
}
return {
minX: Math.max(region.minX, viewRegion.minX),
minY: Math.max(region.minY, viewRegion.minY),
maxX: Math.min(region.maxX, viewRegion.maxX),
maxY: Math.min(region.maxY, viewRegion.maxY),
};
}
exports.mergeView = mergeView;
//# sourceMappingURL=draw.js.map