UNPKG

@antv/f6-plugin

Version:
648 lines (513 loc) 19.6 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.default = void 0; var _tslib = require("tslib"); var _util = require("@antv/util"); var _matrixUtil = require("@antv/matrix-util"); var _base = _interopRequireDefault(require("../base")); var _f6Ui = require("@antv/f6-ui"); function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } var max = Math.max; var transform = _matrixUtil.ext.transform; var DEFAULT_MODE = 'default'; var KEYSHAPE_MODE = 'keyShape'; var DELEGATE_MODE = 'delegate'; var MiniMap = /** @class */ function (_super) { (0, _tslib.__extends)(MiniMap, _super); function MiniMap(config) { var _this = _super.call(this, config) || this; /** * 主图更新的监听函数,使用 debounce 减少渲染频率 * e.g. 拖拽节点只会在松手后的 100ms 后执行 updateCanvas * e.g. render 时大量 addItem 也只会执行一次 updateCanvas */ _this.handleUpdateCanvas = (0, _util.debounce)(function (event) { var self = _this; if (self.destroyed) return; self.updateCanvas(); }, 100, false); return _this; } MiniMap.prototype.getDefaultCfgs = function () { return { container: null, className: '', viewportClassName: '', // Minimap 中默认展示和主图一样的内容,KeyShape 只展示节点和边的 key shape 部分,delegate表示展示自定义的rect,用户可自定义样式 type: 'default', padding: 50, size: [200, 120], delegateStyle: { fill: '#40a9ff', stroke: '#096dd9' }, refresh: true }; }; MiniMap.prototype.getEvents = function () { return { beforepaint: 'updateViewport', beforeanimate: 'disableRefresh', afteranimate: 'enableRefresh', viewportchange: 'disableOneRefresh' }; }; // 若是正在进行动画,不刷新缩略图 MiniMap.prototype.disableRefresh = function () { this.set('refresh', false); }; MiniMap.prototype.enableRefresh = function () { this.set('refresh', true); this.updateCanvas(); }; MiniMap.prototype.disableOneRefresh = function () { this.set('viewportChange', true); }; MiniMap.prototype.initViewport = function () { var _this = this; var cfgs = this._cfgs; var size = cfgs.size, graph = cfgs.graph; if (this.destroyed) return; // viewport 就是minimap的缩略图 拖拽方框 小程序模式下 不能使用createDom 使用createUi替代 var handleUI = this.get('container').query('.viewport'); // 计算拖拽水平方向距离 var x = 0; // 计算拖拽垂直方向距离 var y = 0; // 是否在拖拽minimap的视口 var dragging = false; // 缓存viewport当前对于画布的x var left = 0; // 缓存viewport当前对于画布的y var top = 0; // 缓存viewport当前宽度 var width = 0; // 缓存viewport当前高度 var height = 0; var ratio = 0; var zoom = 0; // 拖拽start事件 handleUI.on('panstart', function (e) { cfgs.refresh = false; // 如果视口已经最大了,不需要拖拽 left = parseInt(handleUI.getStyle('left'), 10); top = parseInt(handleUI.getStyle('top'), 10); width = parseInt(handleUI.getStyle('width'), 10); height = parseInt(handleUI.getStyle('height'), 10); if (width > size[0] || height > size[1]) { return; } zoom = graph.getZoom(); ratio = _this.get('ratio'); dragging = true; x = e.x; y = e.y; }); handleUI.on('panmove', function (e) { if (!dragging || (0, _util.isNil)(e.x) || (0, _util.isNil)(e.y)) { return; } var dx = x - e.x; var dy = y - e.y; // 若视口移动到最左边或最右边了,仅移动到边界 if (left - dx < 0 || left - dx + width >= size[0]) { dx = 0; } // 若视口移动到最上或最下边了,仅移动到边界 if (top - dy < 0 || top - dy + height >= size[1]) { dy = 0; } left -= dx; top -= dy; // graph 移动需要偏移量 dx/dy * 缩放比例才会得到正确的移动距离 graph.translate(dx * zoom / ratio, dy * zoom / ratio); x = e.x; y = e.y; }, false); handleUI.on('panend', function () { dragging = false; cfgs.refresh = true; }, false); this.set('viewport', handleUI); // 这里viewport的 key 先保留,下面继续使用 }; /** * 更新 viewport 视图 */ MiniMap.prototype.updateViewport = function () { if (this.destroyed) return; var ratio = this.get('ratio'); var totaldx = this.get('totaldx'); var totaldy = this.get('totaldy'); var graph = this.get('graph'); var size = this.get('size'); var graphWidth = graph.get('width'); var graphHeight = graph.get('height'); var topLeft = graph.getPointByCanvas(0, 0); var bottomRight = graph.getPointByCanvas(graphWidth, graphHeight); var viewport = this.get('viewport'); // viewport宽高,左上角点的计算 var width = (bottomRight.x - topLeft.x) * ratio; var height = (bottomRight.y - topLeft.y) * ratio; var left = topLeft.x * ratio + totaldx; var top = topLeft.y * ratio + totaldy; var right = left + width; var bottom = top + height; if (left < 0) { width += left; left = 0; } if (right >= size[0]) { width = width - (right - size[0]); } if (top < 0) { height += top; top = 0; } if (bottom > size[1]) { height = height - (bottom - size[1]); } // 缓存目前缩放比,在移动 minimap 视窗时就不用再计算大图的移动量 this.set('ratio', ratio); if (viewport) { var borderWidth = viewport.getStyle('borderWidth'); if (Math.floor(width) >= borderWidth * 2) { viewport.setStyle('left', left); viewport.setStyle('width', width); } if (Math.floor(height) >= borderWidth * 2) { viewport.setStyle('top', top); viewport.setStyle('height', height); } } }; /** * 将主图上的图形完全复制到小图 */ MiniMap.prototype.updateGraphShapes = function () { var graph = this._cfgs.graph; var graphGroup = graph.get('group'); if (graphGroup.destroyed) return; var clonedGroup = graphGroup.clone(); var groupCanvas = this.get('groupCanvas'); clonedGroup.resetMatrix(); groupCanvas.clear(); groupCanvas.add(clonedGroup); }; // 仅在 minimap 上绘制 keyShape // FIXME 如果用户自定义绘制了其他内容,minimap上就无法画出 MiniMap.prototype.updateKeyShapes = function () { var _this = this; var graph = this._cfgs.graph; var group = this.get('groupCanvas'); // canvas.get('children')[0] || canvas.addGroup(); (0, _util.each)(graph.getEdges(), function (edge) { _this.updateOneEdgeKeyShape(edge, group); }); (0, _util.each)(graph.getNodes(), function (node) { _this.updateOneNodeKeyShape(node, group); }); var combos = graph.getCombos(); if (combos && combos.length) { var comboGroup_1 = group.find(function (e) { return e.get('name') === 'comboGroup'; }) || group.addGroup({ name: 'comboGroup' }); setTimeout(function () { if (_this.destroyed) return; (0, _util.each)(combos, function (combo) { _this.updateOneComboKeyShape(combo, comboGroup_1); }); comboGroup_1 === null || comboGroup_1 === void 0 ? void 0 : comboGroup_1.sort(); comboGroup_1 === null || comboGroup_1 === void 0 ? void 0 : comboGroup_1.toBack(); _this.updateCanvas(); }, 250); } this.clearDestroyedShapes(); }; /** * 增加/更新单个元素的 keyShape * @param item ICombo 实例 */ MiniMap.prototype.updateOneComboKeyShape = function (item, comboGroup) { if (this.destroyed) return; var itemMap = this.get('itemMap') || {}; // 差量更新 minimap 上的一个节点,对应主图的 item var mappedItem = itemMap[item.get('id')]; var bbox = item.getBBox(); // 计算了节点父组矩阵的 bbox var cKeyShape = item.get('keyShape').clone(); var keyShapeStyle = cKeyShape.attr(); var attrs = { x: bbox.centerX, y: bbox.centerY }; if (!mappedItem) { mappedItem = cKeyShape; comboGroup.add(mappedItem); } else { attrs = Object.assign(keyShapeStyle, attrs); } var shapeType = mappedItem.get('type'); if (shapeType === 'rect' || shapeType === 'image') { attrs.x = bbox.minX; attrs.y = bbox.minY; } mappedItem.attr(attrs); if (!item.isVisible()) mappedItem.hide();else mappedItem.show(); mappedItem.exist = true; var zIndex = item.getModel().depth; if (!isNaN(zIndex)) mappedItem.set('zIndex', zIndex); itemMap[item.get('id')] = mappedItem; this.set('itemMap', itemMap); }; /** * 增加/更新单个元素的 keyShape * @param item INode 实例 */ MiniMap.prototype.updateOneNodeKeyShape = function (item, group) { var itemMap = this.get('itemMap') || {}; // 差量更新 minimap 上的一个节点,对应主图的 item var mappedItem = itemMap[item.get('id')]; var bbox = item.getBBox(); // 计算了节点父组矩阵的 bbox var cKeyShape = item.get('keyShape').clone(); var keyShapeStyle = cKeyShape.attr(); var attrs = { x: bbox.centerX, y: bbox.centerY }; if (!mappedItem) { mappedItem = cKeyShape; group.add(mappedItem); } else { attrs = Object.assign(keyShapeStyle, attrs); } var shapeType = mappedItem.get('type'); if (shapeType === 'rect' || shapeType === 'image') { attrs.x = bbox.minX; attrs.y = bbox.minY; } mappedItem.attr(attrs); if (!item.isVisible()) mappedItem.hide();else mappedItem.show(); mappedItem.exist = true; var zIndex = item.getModel().depth; if (!isNaN(zIndex)) mappedItem.set('zIndex', zIndex); itemMap[item.get('id')] = mappedItem; this.set('itemMap', itemMap); }; /** * Minimap 中展示自定义的rect,支持用户自定义样式和节点大小 */ MiniMap.prototype.updateDelegateShapes = function () { var _this = this; var graph = this._cfgs.graph; var group = this.get('groupCanvas'); // 差量更新 minimap 上的节点和边 (0, _util.each)(graph.getEdges(), function (edge) { _this.updateOneEdgeKeyShape(edge, group); }); (0, _util.each)(graph.getNodes(), function (node) { _this.updateOneNodeDelegateShape(node, group); }); var combos = graph.getCombos(); if (combos && combos.length) { var comboGroup_2 = group.find(function (e) { return e.get('name') === 'comboGroup'; }) || group.addGroup({ name: 'comboGroup' }); setTimeout(function () { if (_this.destroyed) return; (0, _util.each)(combos, function (combo) { _this.updateOneComboKeyShape(combo, comboGroup_2); }); comboGroup_2 === null || comboGroup_2 === void 0 ? void 0 : comboGroup_2.sort(); comboGroup_2 === null || comboGroup_2 === void 0 ? void 0 : comboGroup_2.toBack(); _this.updateCanvas(); }, 250); } this.clearDestroyedShapes(); }; MiniMap.prototype.clearDestroyedShapes = function () { var itemMap = this.get('itemMap') || {}; var keys = Object.keys(itemMap); if (!keys || keys.length === 0) return; for (var i = keys.length - 1; i >= 0; i--) { var shape = itemMap[keys[i]]; var exist = shape.exist; shape.exist = false; if (!exist) { shape.remove(); delete itemMap[keys[i]]; } } }; /** * 设置只显示 edge 的 keyShape * @param item IEdge 实例 */ MiniMap.prototype.updateOneEdgeKeyShape = function (item, group) { var itemMap = this.get('itemMap') || {}; // 差量更新 minimap 上的一个节点,对应主图的 item var mappedItem = itemMap[item.get('id')]; if (mappedItem) { var path = item.get('keyShape').attr('path'); mappedItem.attr('path', path); } else { mappedItem = item.get('keyShape').clone(); group.add(mappedItem); } if (!item.isVisible()) mappedItem.hide();else mappedItem.show(); mappedItem.exist = true; itemMap[item.get('id')] = mappedItem; this.set('itemMap', itemMap); }; /** * Minimap 中展示自定义的 rect,支持用户自定义样式和节点大小 * 增加/更新单个元素 * @param item INode 实例 */ MiniMap.prototype.updateOneNodeDelegateShape = function (item, group) { var delegateStyle = this.get('delegateStyle'); var itemMap = this.get('itemMap') || {}; // 差量更新 minimap 上的一个节点,对应主图的 item var mappedItem = itemMap[item.get('id')]; var bbox = item.getBBox(); // 计算了节点父组矩阵的 bbox if (mappedItem) { var attrs = { x: bbox.minX, y: bbox.minY, width: bbox.width, height: bbox.height }; mappedItem.attr(attrs); } else { mappedItem = group.addShape('rect', { attrs: (0, _tslib.__assign)({ x: bbox.minX, y: bbox.minY, width: bbox.width, height: bbox.height }, delegateStyle), name: 'minimap-node-shape' }); } if (!item.isVisible()) mappedItem.hide();else mappedItem.show(); mappedItem.exist = true; itemMap[item.get('id')] = mappedItem; this.set('itemMap', itemMap); }; MiniMap.prototype.init = function () { var _this = this; this.get('graph').on('afterupdateitem', this.handleUpdateCanvas); this.get('graph').on('afteritemstatechange', this.handleUpdateCanvas); this.get('graph').on('afteradditem', this.handleUpdateCanvas); this.get('graph').on('afterremoveitem', this.handleUpdateCanvas); this.get('graph').on('afterrender', this.handleUpdateCanvas); this.get('graph').on('afterlayout', this.handleUpdateCanvas); setTimeout(function () { _this.initContainer(); }); }; /** * 初始化 Minimap 的容器 */ MiniMap.prototype.initContainer = function () { var _a; var self = this; var graph = self.get('graph'); var size = self.get('size'); var className = self.get('className'); var viewportClassName = self.get('viewportClassName'); var getCss = self.get('getCss'); var containerHtml = "\n <div class='f6-minimap " + className + "'>\n <div class=\"f6-minimap-container\"></div>\n <div class=\"viewport " + viewportClassName + "\"></div>\n </div>"; var containerCss = "\n root {\n width: " + graph.getWidth() + ";\n height: " + graph.getHeight() + ";\n opacity: 0;\n pointer-events: none;\n }\n .f6-minimap{ \n position: absolute;\n left: 0;\n bottom: 0;\n width: " + size[0] + "; height: " + size[1] + ";\n } \n .f6-minimap-container{\n position: relative;\n background: rgba(0,0,0,0);\n width: " + size[0] + "; height: " + size[1] + ";\n }\n .viewport{\n position: absolute;\n border: 3 solid blue;\n width: 100;\n height: 100;\n background: rgba(0,0,0,0);\n z-index: 1000;\n }\n " + ((_a = getCss === null || getCss === void 0 ? void 0 : getCss()) !== null && _a !== void 0 ? _a : '') + "\n "; var uiGroup = graph.get('uiGroup'); var miniMapContainerUI = (0, _f6Ui.createUI)(containerHtml, containerCss, uiGroup); var background = miniMapContainerUI.query('.f6-minimap-container'); var group = background.gNode; var canvasGroup = group.addGroup(); this.set('groupCanvas', canvasGroup); this.set('container', miniMapContainerUI); self.updateCanvas(); this.initViewport(); }; MiniMap.prototype.updateCanvas = function () { if (this.destroyed) return; // 如果是在动画,则不刷新视图 var isRefresh = this.get('refresh'); if (!isRefresh) { return; } var graph = this.get('graph'); if (graph.get('destroyed')) { return; } // 如果是视口变换,也不刷新视图,但是需要重置视口大小和位置 if (this.get('viewportChange')) { this.set('viewportChange', false); this.updateViewport(); } var size = this.get('size'); // 用户定义的 minimap size var type = this.get('type'); // minimap 的类型 var padding = this.get('padding'); // 用户额定义的 minimap 的 padding switch (type) { case DEFAULT_MODE: this.updateGraphShapes(); break; case KEYSHAPE_MODE: this.updateKeyShapes(); break; case DELEGATE_MODE: // 得到的节点直接带有 x 和 y,每个节点不存在父 group,即每个节点位置不由父 group 控制 this.updateDelegateShapes(); break; default: break; } var group = this.get('groupCanvas'); // canvas.get('children')[0]; if (!group) return; group.resetMatrix(); // 重新计算所有子节点的matrix,从group开始重新计算totalmatrix,重置传递下来的matrix,保证计算canvasBBox的时候,相对左上角 group.applyMatrix([1, 0, 0, 0, 1, 0, 0, 0, 1]); var bbox = group.getCanvasBBox(); var graphBBox = graph.get('group').getCanvasBBox(); // 主图的 bbox var graphZoom = graph.getZoom() || 1; var width = graphBBox.width / graphZoom; var height = graphBBox.height / graphZoom; if (Number.isFinite(bbox.width)) { // 刷新后bbox可能会变,需要重置画布矩阵以缩放到合适的大小 width = max(bbox.width, width); height = max(bbox.height, height); } width += 2 * padding; height += 2 * padding; var ratio = Math.min(size[0] / width, size[1] / height); var matrix = [1, 0, 0, 0, 1, 0, 0, 0, 1]; var minX = 0; var minY = 0; // 平移到左上角 if (Number.isFinite(bbox.minX)) { minX = -bbox.minX; } if (Number.isFinite(bbox.minY)) { minY = -bbox.minY; } // 缩放到适合视口后, 平移到画布中心 var dx = (size[0] - (width - 2 * padding) * ratio) / 2; var dy = (size[1] - (height - 2 * padding) * ratio) / 2; matrix = transform(matrix, [['t', minX, minY], ['s', ratio, ratio], ['t', dx, dy] // 移动到画布中心 ]); group.setMatrix(matrix); // 更新minimap视口 this.set('ratio', ratio); this.set('totaldx', dx + minX * ratio); this.set('totaldy', dy + minY * ratio); this.set('dx', dx); this.set('dy', dy); this.updateViewport(); }; /** * 获取minimap的画布 * @return {GCanvas} G的canvas实例 */ MiniMap.prototype.getCanvas = function () { return this.get('canvas'); }; /** * 获取minimap的窗口 * @return {HTMLElement} 窗口的dom实例 */ MiniMap.prototype.getViewport = function () { return this.get('viewport'); }; /** * 获取minimap的容器dom * @return {HTMLElement} dom */ MiniMap.prototype.getContainer = function () { return this.get('container'); }; MiniMap.prototype.destroy = function () { var _a; (_a = this.get('container')) === null || _a === void 0 ? void 0 : _a.remove(); }; return MiniMap; }(_base.default); var _default = MiniMap; exports.default = _default;