@antv/g6
Version:
graph visualization frame work
943 lines (815 loc) • 24.4 kB
JavaScript
function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } }
function _possibleConstructorReturn(self, call) { if (!self) { throw new ReferenceError("this hasn't been initialised - super() hasn't been called"); } return call && (typeof call === "object" || typeof call === "function") ? call : self; }
function _inherits(subClass, superClass) { if (typeof superClass !== "function" && superClass !== null) { throw new TypeError("Super expression must either be null or a function, not " + typeof superClass); } subClass.prototype = Object.create(superClass && superClass.prototype, { constructor: { value: subClass, enumerable: false, writable: true, configurable: true } }); if (superClass) Object.setPrototypeOf ? Object.setPrototypeOf(subClass, superClass) : subClass.__proto__ = superClass; }
/**
* @fileOverview graph
* @author huangtonger@aliyun.com
*/
require('./extend/g/html');
require('./extend/g/canvas');
require('./extend/g/group');
require('./extend/g/shape');
require('./extend/g/root-group');
require('./extend/g/html');
var Base = require('./base');
var Item = require('./item/');
var Shape = require('./shape/');
var Util = require('./util/');
var G = require('@antv/g');
var LayoutMixin = require('./mixin/layout');
var MappingMixin = require('./mixin/mapping');
var QueryMixin = require('./mixin/query');
var EventMixin = require('./mixin/event');
var ModeMixin = require('./mixin/mode');
var FilterMixin = require('./mixin/filter');
var AnimateMixin = require('./mixin/animate');
var FitView = require('./mixin/fit-view');
var ForceFit = require('./mixin/force-fit');
var Mixins = [FilterMixin, MappingMixin, QueryMixin, AnimateMixin, ForceFit, LayoutMixin, FitView, EventMixin, ModeMixin];
var TAB_INDEX = 20;
var Graph = function (_Base) {
_inherits(Graph, _Base);
/**
* Access to the default configuration properties
* @return {object} default configuration
*/
Graph.prototype.getDefaultCfg = function getDefaultCfg() {
return {
/**
* Container could be dom object or dom id
* @type {object|string|undefined}
*/
container: undefined,
/**
* Canvas width
* @type {number|undefined}
* unit pixel if undefined force fit width
*/
width: undefined,
/**
* Canvas height
* @type {number|undefined}
* unit pixel if undefined force fit height
*/
height: undefined,
/**
* Plugins
* @type {array}
*/
plugins: [],
/**
* FontFamily
* @type {string}
*/
fontFamily: '"Helvetica Neue", Helvetica, "PingFang SC", "Hiragino Sans GB", "Microsoft YaHei", "微软雅黑", SimSun, "sans-serif"',
/**
* default node shape
* @type {string|undefined}
*/
nodeDefaultShape: undefined,
/**
* default edge shape
* @type {string|undefined}
*/
edgeDefaultShape: undefined,
/**
* default group shape
* @type {string|undefined}
*/
groupDefaultShape: undefined,
/**
* default edge node intersect box
* @type {string}
*/
defaultIntersectBox: 'circle',
render: 'canvas',
_controllers: {},
_timers: {},
_dataMap: {},
_itemMap: {},
_data: {},
_delayRunObj: {}
};
};
function Graph(inputCfg) {
_classCallCheck(this, Graph);
var cfg = {};
Mixins.forEach(function (Mixin) {
Util.mix(cfg, Mixin.CFG, inputCfg);
});
// plugin should init before all
var _this = _possibleConstructorReturn(this, _Base.call(this, cfg));
_this._pluginInit();
_this.emit('beforeinit');
_this._init();
_this.emit('afterinit');
return _this;
}
Graph.prototype._init = function _init() {
var _this2 = this;
this._initData();
this._initContainer();
this._initCanvas();
Mixins.forEach(function (Mixin) {
Mixin.INIT && _this2[Mixin.INIT]();
});
this.initEvent();
};
Graph.prototype.initEvent = function initEvent() {};
Graph.prototype._executeLayout = function _executeLayout(processer, nodes, edges, groups) {
if (Util.isFunction(processer)) {
processer(nodes, edges, this);
} else if (Util.isObject(processer)) {
processer.nodes = nodes;
processer.edges = edges;
processer.groups = groups;
processer.graph = this;
processer.execute();
}
};
Graph.prototype._pluginInit = function _pluginInit() {
var _this3 = this;
var plugins = this.get('plugins');
plugins.forEach(function (plugin) {
_this3._initPlugin(plugin);
});
};
Graph.prototype._initPlugin = function _initPlugin(plugin) {
plugin.graph = this;
plugin.init && plugin.init();
};
Graph.prototype._getTimer = function _getTimer(name) {
return this.get('_timers')[name];
};
Graph.prototype._setTimer = function _setTimer(name, value) {
this.get('_timers')[name] = value;
};
Graph.prototype._getController = function _getController(name) {
return this.get('_controllers')[name];
};
Graph.prototype._initContainer = function _initContainer() {
var container = this.get('container');
if (!container) {
// Compatible with id written
container = this.get('id');
}
if (container) {
if (Util.isString(container)) {
container = document.getElementById(container);
}
} else {
throw new Error('please set the container for the graph !');
}
var graphContainer = Util.createDOM('<div class="graph-container"></div>', {
position: 'relative'
});
container.appendChild(graphContainer);
this.set('_containerDOM', container);
this.set('_graphContainer', graphContainer);
};
Graph.prototype._initCanvas = function _initCanvas() {
var _this4 = this;
var graphContainer = this.get('_graphContainer');
var width = this.get('width');
var height = this.get('height');
var fontFamily = this.get('fontFamily');
var canvasCfg = {
width: width,
height: height,
fontFamily: fontFamily,
eventEnable: false,
containerDOM: graphContainer
};
var Canvas = this.getConstructor('Canvas');
var canvas = new Canvas(canvasCfg);
var frontCanvas = new Canvas(canvasCfg);
var frontEl = frontCanvas.get('el');
var htmlElementContaniner = graphContainer.appendChild(Util.createDOM('<div class="graph-container-html-Elements"></div>'));
canvas.on('beforedraw', function () {
_this4.emit('beforecanvasdraw');
});
frontEl.style.position = 'absolute';
frontEl.style.top = 0;
frontEl.style.left = 0;
htmlElementContaniner.style.overflow = 'hidden';
htmlElementContaniner.style.width = width + 'px';
htmlElementContaniner.style.height = height + 'px';
htmlElementContaniner.style.position = 'absolute';
htmlElementContaniner.style.top = 0;
htmlElementContaniner.style.left = 0;
this.set('_canvas', canvas);
this.set('_frontCanvas', frontCanvas);
this.set('_htmlElementContaniner', htmlElementContaniner);
var mouseEventWrapper = this.getMouseEventWrapper();
mouseEventWrapper.style.outline = 'none';
mouseEventWrapper.style['user-select'] = 'none';
mouseEventWrapper.setAttribute('tabindex', TAB_INDEX);
canvas.set('htmlElementContaniner', htmlElementContaniner);
var RootGroup = this.getConstructor('RootGroup');
var rootGroup = canvas.addGroup(RootGroup);
var frontRootGroup = frontCanvas.addGroup(RootGroup);
var itemGroup = rootGroup.addGroup();
this.set('_itemGroup', itemGroup);
this.set('_rootGroup', rootGroup);
this.set('_frontRootGroup', frontRootGroup);
};
Graph.prototype._initData = function _initData() {
this.set('_dataMap', {});
this.set('_itemMap', {
_nodes: [],
_edges: [],
_groups: [],
_guides: []
});
this.set('_data', {});
};
Graph.prototype._refresh = function _refresh() {};
Graph.prototype.getKeyboardEventWrapper = function getKeyboardEventWrapper() {
var keyboardEventWrapper = this.get('keyboardEventWrapper');
return keyboardEventWrapper ? keyboardEventWrapper : this.getMouseEventWrapper();
};
Graph.prototype.getMouseEventWrapper = function getMouseEventWrapper() {
return this.get('_htmlElementContaniner');
};
/**
* @param {object} plugin - plugin instance
*/
Graph.prototype.addPlugin = function addPlugin(plugin) {
var plugins = this.get('plugins');
this._initPlugin(plugin);
plugins.push(plugin);
};
/**
* @return {domobject} graphcontainer
*/
Graph.prototype.getGraphContainer = function getGraphContainer() {
return this.get('_graphContainer');
};
/**
* @param {string} type item type
* @param {array} models models
*/
Graph.prototype.addItems = function addItems(type, models) {
var _this5 = this;
this._addDatas(type, models);
var Type = Util.upperFirst(type);
var Constructor = Item[Type];
var itemMap = this.get('_itemMap');
var itemGroup = this.get('_itemGroup');
var dataMap = this.get('_dataMap');
var animate = this.get('animate');
var defaultIntersectBox = this.get('defaultIntersectBox');
if (!Constructor) {
throw new Error('please set valid item type!');
}
models.forEach(function (model) {
var item = new Constructor({
id: model.id,
type: type,
model: model,
group: itemGroup.addGroup(),
graph: _this5,
mapper: _this5._getController(type + 'Mapper'),
itemMap: itemMap,
animate: animate,
dataMap: dataMap,
defaultIntersectBox: defaultIntersectBox
});
itemMap[model.id] = item;
itemMap['_' + type + 's'].push(item);
});
};
/**
* @param {array} items remove items
*/
Graph.prototype.removeItems = function removeItems(items) {
var dataMap = this.get('_dataMap');
var itemMap = this.get('_itemMap');
items.forEach(function (item) {
delete dataMap[item.id];
delete itemMap[item.id];
Util.Array.remove(itemMap['_' + item.type + 's'], item);
item.destroy();
});
};
/**
* @param {object} item item
* @param {object} model update model
*/
Graph.prototype.updateItem = function updateItem(item, model) {
Util.mix(item.getModel(), model);
item.update();
};
Graph.prototype._addDatas = function _addDatas(type, models) {
var dataMap = this.get('_dataMap');
models.forEach(function (model) {
if (Util.isNil(model.id)) {
model.id = Util.guid();
}
if (dataMap[model.id]) {
throw new Error('id:' + model.id + ' has already been set, please set new one');
}
dataMap[model.id] = model;
});
};
Graph.prototype._drawInner = function _drawInner() {
var data = this.get('_data');
var itemGroup = this.get('_itemGroup');
var dataMap = this.get('_dataMap');
if (data.nodes) {
this.addItems('node', data.nodes);
}
if (data.groups) {
this.addItems('group', data.groups);
}
if (data.edges) {
this.addItems('edge', data.edges);
}
if (data.guides) {
this.addItems('guide', data.guides);
}
itemGroup.sortBy(function (child) {
var id = child.id;
var model = dataMap[id];
return model.index;
});
};
Graph.prototype._clearInner = function _clearInner() {
var items = this.getItems();
items.forEach(function (item) {
item && !item.destroyed && item.destroy();
});
};
/**
* @param {string} name option 1
* @return {function} function
*/
Graph.prototype.getConstructor = function getConstructor(name) {
var render = this.get('render');
if (render === 'svg') {
return G.svg[name];
}
return G.canvas[name];
};
/**
* @param {string} type item type
* @param {object} model data model
* @return {object} shapeObj
*/
Graph.prototype.getShapeObj = function getShapeObj(type, model) {
if (!Util.isObject(type)) {
var Type = Util.upperFirst(type);
var shapeManager = Shape[Type];
var defaultShape = this.get(type + 'DefaultShape');
return shapeManager.getShape(model.shape, defaultShape);
}
return type.getShapeObj();
};
/**
* @return {object} source data
*/
Graph.prototype.getSource = function getSource() {
return this.get('_sourceData');
};
/**
* @param {object} data source data
* @return {object} plain data
*/
Graph.prototype.parseSource = function parseSource(data) {
return data;
};
/**
* @return {G.Canvas} canvas
*/
Graph.prototype.getCanvas = function getCanvas() {
return this.get('_canvas');
};
/**
* @return {G.Group} rootGroup
*/
Graph.prototype.getRootGroup = function getRootGroup() {
return this.get('_rootGroup');
};
/**
* @return {G.Group} itemGroup
*/
Graph.prototype.getItemGroup = function getItemGroup() {
return this.get('_itemGroup');
};
/**
* @return {G.Group} frontRootGroup
*/
Graph.prototype.getFrontRootGroup = function getFrontRootGroup() {
return this.get('_frontRootGroup');
};
/**
* @return {G.Canvas} canvas
*/
Graph.prototype.getFrontCanvas = function getFrontCanvas() {
return this.get('_frontCanvas');
};
/**
* @param {object} data source data
* @return {Graph} this
*/
Graph.prototype.source = function source(data) {
this.emit('beforesource');
this.set('_data', data);
this.set('_sourceData', data);
this.emit('aftersource');
return this;
};
/**
* @return {Graph} this
*/
Graph.prototype.render = function render() {
this.emit('beforerender');
this.emit('beforedrawinner');
this._drawInner();
this.emit('afterdrawinner');
this.draw();
this.emit('afterrender');
return this;
};
/**
* @param {boolean} bool if force prevent animate
*/
Graph.prototype.forcePreventAnimate = function forcePreventAnimate(bool) {
this.set('forcePreventAnimate', bool);
};
/**
* @return {Graph} this
*/
Graph.prototype.reRender = function reRender() {
var data = this.get('_sourceData');
this.read(data);
return this;
};
/**
* @return {Graph} this
*/
Graph.prototype.destroy = function destroy() {
var canvas = this.get('_canvas');
var frontCanvas = this.get('_frontCanvas');
var graphContainer = this.get('_graphContainer');
var controllers = this.get('_controllers');
var timers = this.get('_timers');
var windowForceResizeEvent = this.get('_windowForceResizeEvent');
var plugins = this.get('plugins');
Util.each(timers, function (timer) {
clearTimeout(timer);
});
Util.each(controllers, function (controller) {
controller.destroy();
});
plugins.forEach(function (plugin) {
plugin.destroy && plugin.destroy();
});
canvas && canvas.destroy();
frontCanvas && frontCanvas.destroy();
graphContainer.destroy();
window.removeEventListener('resize', windowForceResizeEvent);
_Base.prototype.destroy.call(this);
return this;
};
/**
* @return {object} data
*/
Graph.prototype.save = function save() {
var itemGroup = this.get('_itemGroup');
var children = itemGroup.get('children');
var rst = {
nodes: [],
edges: [],
groups: [],
guides: []
};
children.forEach(function (child, index) {
var model = child.model;
if (model) {
var type = child.itemType;
var saveModel = Util.cloneDeep(model);
saveModel.index = index;
rst[type + 's'].push(saveModel);
}
});
rst.nodes.length === 0 && delete rst.nodes;
rst.edges.length === 0 && delete rst.edges;
rst.groups.length === 0 && delete rst.groups;
rst.guides.length === 0 && delete rst.guides;
return rst;
};
/**
* @param {string} type item type
* @param {object} model data model
* @return {Graph} this
*/
Graph.prototype.add = function add(type, model) {
var ev = {
action: 'add',
model: model
};
this.emit('beforechange', ev);
var itemMap = this.get('_itemMap');
this.addItems(type, [model]);
var item = itemMap[model.id];
item.getAllParents().forEach(function (parent) {
parent.update();
});
ev.item = item;
this.emit('afterchange', ev);
this.draw();
return item;
};
/**
* @param {String|Item} item target item
* @return {Graph} this
*/
Graph.prototype.remove = function remove(item) {
var _this6 = this;
item = this.getItem(item);
if (!item || item.destroyed) {
return;
}
var ev = {
action: 'remove',
item: item
};
this.emit('beforechange', ev);
if (item.isNode || item.isGroup) {
var edges = item.getEdges();
edges.forEach(function (edge) {
_this6.remove(edge);
});
}
if (item.isGroup) {
var children = item.getChildren();
children.forEach(function (child) {
_this6.remove(child);
});
}
this.removeItems([item]);
item.getAllParents().forEach(function (parent) {
parent.update();
});
this.emit('afterchange', ev);
this.draw();
return this;
};
/**
* @param {String|Item} item target item
* @param {object} model data model
* @return {Graph} this
*/
Graph.prototype.simpleUpdate = function simpleUpdate(item, model) {
this.updateItem(item, model);
this.draw();
return this;
};
/**
* @param {String|Item|Undefined} item target item
* @param {object} model data model
* @return {Graph} this
*/
Graph.prototype.update = function update(item, model) {
var itemMap = this.get('_itemMap');
item = this.getItem(item);
if (!item || item.destroyed) {
return;
}
var itemModel = item.getModel();
var originModel = Util.mix({}, itemModel);
var ev = {
action: 'update',
item: item,
originModel: originModel,
updateModel: model
};
var originParent = itemMap[originModel.parent];
if (originParent && originParent !== parent && Util.isGroup(originParent)) {
item.getAllParents().forEach(function (parent) {
parent.update();
});
}
model && this.emit('beforechange', ev);
this.updateItem(item, model);
// If the update nodes or group, update their parent
item.getAllParents().forEach(function (parent) {
parent.update();
});
// If the update nodes or group, update the connection edge
if ((item.isNode || item.isGroup) && !item.collapsedParent) {
var edges = item.getEdges();
edges.forEach(function (edge) {
edge.update();
});
}
// update group relative items
if (item.isGroup && model) {
item.deepEach(function (child) {
child.updateCollapsedParent();
if (child.collapsedParent) {
child.hide();
} else {
child.show();
}
child.update();
});
item.getInnerEdges().forEach(function (child) {
var bool = child.linkedItemVisible();
if (bool) {
child.show();
} else {
child.hide();
}
child.update();
});
}
model && this.emit('afterchange', ev);
this.draw();
return this;
};
/**
* change data
* @param {object} data source data
* @return {Graph} this
*/
Graph.prototype.read = function read(data) {
if (!data) {
throw new Error('please read valid data!');
}
var fitView = this.get('fitView');
var ev = {
action: 'changeData',
data: data
};
this.emit('beforechange', ev);
this.clear();
this.source(data);
this.render();
this.emit('afterchange', ev);
fitView && this.setFitView(fitView);
return this;
};
/**
* @return {Graph} this
*/
Graph.prototype.clear = function clear() {
this.emit('beforeclear');
this._clearInner();
this._initData();
this.emit('afterclear');
this.draw();
return this;
};
/**
* hide item
* @param {number} item input item
* @return {object} this
*/
Graph.prototype.hide = function hide(item) {
item = this.getItem(item);
item.hide();
if (item.isNode) {
item.getEdges().forEach(function (edge) {
edge.hide();
});
}
if (item.isGroup) {
item.getEdges().forEach(function (edge) {
edge.hide();
});
// item.getInnerEdges().forEach(edge => {
// edge.hide();
// });
item.deepEach(function (child) {
child.hide();
});
}
this.draw();
return this;
};
Graph.prototype._tryShowEdge = function _tryShowEdge(edge) {
var source = edge.getSource();
var target = edge.getTarget();
return (source.linkable && source.isVisible() || !source.linkable) && (target.linkable && target.isVisible() || !target.linkable) && edge.show();
};
/**
* show item
* @param {number} item input item
* @return {object} this
*/
Graph.prototype.show = function show(item) {
var _this7 = this;
item = this.getItem(item);
if (item.isEdge) {
this._tryShowEdge(item);
} else {
item.show();
}
if (item.isNode) {
item.getEdges().forEach(function (edge) {
_this7._tryShowEdge(edge);
});
}
if (item.isGroup) {
item.getEdges().forEach(function (edge) {
_this7._tryShowEdge(edge);
});
item.deepEach(function (child) {
child.show();
});
}
this.draw();
return this;
};
/**
* @return {Graph} this
*/
Graph.prototype.getWidth = function getWidth() {
return this.get('width');
};
/**
* @return {Graph} this
*/
Graph.prototype.getHeight = function getHeight() {
return this.get('height');
};
/**
* change canvas size
* @param {number} width input width
* @param {number} height input height
* @return {object} this
*/
Graph.prototype.changeSize = function changeSize(width, height) {
if (Math.abs(width) >= Infinity || Math.abs(height) >= Infinity) {
console.warn('size parameter more than the maximum');
return;
}
var canvas = this.get('_canvas');
var frontCanvas = this.get('_frontCanvas');
var htmlElementContaniner = this.get('_htmlElementContaniner');
if (width !== this.get('width') || height !== this.get('height')) {
this.emit('beforechangesize');
canvas.changeSize(width, height);
frontCanvas.changeSize(width, height);
htmlElementContaniner.css({
width: width + 'px',
height: height + 'px'
});
this.set('width', width);
this.set('height', height);
this.emit('afterchangesize');
this.draw();
}
return this;
};
/**
* item to front
* @param {object} item item
*/
Graph.prototype.toFront = function toFront(item) {
item = this.getItem(item);
var itemGroup = this.get('_itemGroup');
var group = item.getGraphicGroup();
Util.toFront(group, itemGroup);
this.draw();
};
/**
* item to back
* @param {object} item item
*/
Graph.prototype.toBack = function toBack(item) {
item = this.getItem(item);
var itemGroup = this.get('_itemGroup');
var group = item.getGraphicGroup();
Util.toBack(group, itemGroup);
this.draw();
};
/**
* set cantainer css
* @param {object} style container dom css
*/
Graph.prototype.css = function css(style) {
var graphContainer = this.getGraphContainer();
Util.modifyCSS(graphContainer, style);
};
/**
* save graph image
* @return {object} canvas dom
*/
Graph.prototype.saveImage = function saveImage() {
var box = this.getBBox();
var padding = this.getFitViewPadding();
return Util.graph2Canvas({
graph: this,
width: box.width + padding[1] + padding[3],
height: box.height + padding[0] + padding[2]
});
};
return Graph;
}(Base);
Mixins.forEach(function (Mixin) {
Util.mix(Graph.prototype, Mixin.AUGMENT);
});
module.exports = Graph;