- Go backend (server/)
- Frontend (web/, server/static/)
- Database and deployment files
- Scripts and docs
Co-Authored-By: 狸花猫/Claude-Qwen3.6-Plus 🐾
344 lines
14 KiB
JavaScript
344 lines
14 KiB
JavaScript
"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
|