feat(plugin): add plugin

This commit is contained in:
yilin.qyl 2019-03-20 15:06:36 +08:00
parent 135bce34ec
commit a8a93c398d
8 changed files with 343 additions and 9 deletions

View File

@ -25,7 +25,7 @@
x: 300,
y: 200,
shape: 'background-animate',
color: 'red',
color: '#b5b5b5',
size: 20,
label: '背景动画',
labelCfg: {
@ -66,7 +66,7 @@
target: 'node4'
}
]};
// 放大、变小动画
G6.registerNode('circle-animate', {
afterDraw(cfg, group) {
@ -102,7 +102,7 @@
x: 0,
y: 0,
r,
fill: 'blue', // 为了显示清晰,随意设置了颜色
fill: cfg.color, // 为了显示清晰,随意设置了颜色
opacity: 0.6
}
});
@ -113,7 +113,7 @@
x: 0,
y: 0,
r,
fill: 'green',
fill: cfg.color,
opacity: 0.6
}
});
@ -176,4 +176,4 @@
graph.render();
</script>
</body>
</html>
</html>

View File

@ -11,8 +11,8 @@
<script src="./assets/sankey.js"></script>
<script src="./assets/jquery-3.2.1.min.js"></script>
<script>
const width = 960;
const height = 500;
const width = window.innerWidth;
const height = window.innerHeight;
const colors = ['#FD8C3D', '#D83F43', '#F7BED6', '#E487C7', '#46A848', '#D83F43', '#3B85BA', '#48335B', '#B7CDE9'];
G6.registerEdge('sankey', {

View File

@ -18,18 +18,27 @@
<body>
<div id="mountNode"></div>
<script src="../build/g6.js"></script>
<script src="../build/minimap.js"></script>
<script src="./assets/d3-4.13.0.min.js"></script>
<script src="./assets/jquery-3.2.1.min.js"></script>
<script>
const minimap = new Minimap({ size: [ 200, 160 ] });
const graph = new G6.Graph({
container: 'mountNode',
width: 1000,
height: 800,
autoPaint: false,
plugins: [ minimap ],
modes: {
default: ['drag-canvas', {
type: 'tooltip',
formatText: model => { return model.name}
}, {
type: 'edge-tooltip',
formatText: (model, e) => {
const edge = e.item;
return '来源:' + edge.getSource().getModel().name + '<br/>去向:' + edge.getTarget().getModel().name;
}
}]
},
defaultNode: {
@ -53,7 +62,7 @@
}
},
edgeStyle: {
default: { stroke: '#e2e2e2' },
default: { stroke: '#e2e2e2', lineAppendWidth: 2 },
highlight: { stroke: '#999' }
}
});

44
plugins/base.js Normal file
View File

@ -0,0 +1,44 @@
const deepMix = require('@antv/util/lib/deep-mix');
const each = require('@antv/util/lib/each');
const wrapBehavior = require('@antv/util/lib/event/wrap-behavior');
class PluginBase {
constructor(cfgs) {
this._cfgs = deepMix(this.getDefaultCfg(), cfgs);
}
getDefaultCfg() {
return {};
}
init(graph) {
const self = this;
self.set('graph', graph);
const events = self.getEvents();
const bindEvents = [];
each(events, (v, k) => {
const event = wrapBehavior(self, v);
bindEvents[k] = event;
graph.on(k, event);
});
this._events = bindEvents;
}
getEvents() {
return {};
}
get(key) {
return this._cfgs[key];
}
set(key, val) {
this._cfgs[key] = val;
}
destroy() {
const graph = this.get('graph');
each(this._events, (v, k) => {
graph.off(k, v);
});
this._events = null;
this._cfgs = null;
this.destroyed = true;
}
}
module.exports = PluginBase;

5
plugins/index.js Normal file
View File

@ -0,0 +1,5 @@
const G6Plugins = {
minimap: require('./minimap')
};
module.exports = G6Plugins;

242
plugins/minimap/index.js Normal file
View File

@ -0,0 +1,242 @@
const G = require('@antv/g/lib');
const Base = require('../base');
const isString = require('@antv/util/lib/type/is-string');
const isNil = require('@antv/util/lib/type/is-nil');
const createDOM = require('@antv/util/lib/dom/create-dom');
const modifyCSS = require('@antv/util/lib/dom/modify-css');
const each = require('@antv/util/lib/each');
const max = Math.max;
class Minimap extends Base {
init(graph) {
super.init(graph);
this.initContainer();
}
getDefaultCfg() {
return {
container: null,
className: 'g6-minimap',
viewportClassName: 'g6-minimap-viewport',
keyShapeOnly: false,
viewportStyle: {
stroke: '#1890ff',
lineWidth: 2,
x: 0,
y: 0,
width: 200,
height: 120
},
size: [ 200, 120 ]
};
}
getEvents() {
return { beforepaint: 'updateCanvas' };
}
initContainer() {
const self = this;
const graph = self.get('graph');
const size = self.get('size');
const className = self.get('className');
let container = self.get('container');
if (isString(container)) {
container = document.getElementById(container);
}
if (container) {
container.classList.add(className);
modifyCSS(container, {
width: size[0] + 'px',
height: size[1] + 'px'
});
} else {
container = createDOM('<div class="' + className + '" style="width:' + size[0] + 'px; height:' + size[1] + 'px"></div>');
graph.get('container').appendChild(container);
}
self.set('container', container);
const containerDOM = createDOM('<div class="g6-minimap-container"></div>');
container.appendChild(containerDOM);
const canvas = new G.Canvas({
containerDOM,
width: size[0],
height: size[1],
pixelRatio: graph.get('pixelRatio')
});
self.set('canvas', canvas);
self.updateCanvas();
}
initViewport() {
const cfgs = this._cfgs;
const size = cfgs.size;
const graph = cfgs.graph;
const pixelRatio = graph.get('pixelRatio') || graph.get('canvas').get('pixelRatio');
const widthRatio = graph.get('width') / size[0] * pixelRatio;
const heightRatio = graph.get('height') / size[1] * pixelRatio;
const canvas = this.get('canvas');
const containerDOM = canvas.get('containerDOM');
const viewport = createDOM('<div class="' + cfgs.viewportClassName + '" style="position:absolute;left:0;top:0;box-sizing:border-box;border: 2px solid #1980ff"></div>');
let x, // 计算拖拽水平方向距离
y, // 计算拖拽垂直方向距离
dragging, // 是否在拖拽minimap的视口
left, // 缓存viewport当前对于画布的x
top, // 缓存viewport当前对于画布的y
width, // 缓存viewport当前宽度
height; // 缓存viewport当前高度
containerDOM.addEventListener('mousedown', e => {
if (e.target !== viewport) {
return;
}
// 如果视口已经最大了,不需要拖拽
const style = viewport.style;
left = parseInt(style.left, 10);
top = parseInt(style.top, 10);
width = parseInt(style.width, 10);
height = parseInt(style.height, 10);
if (width >= size[0] || height >= size[1]) {
return;
}
dragging = true;
x = e.clientX;
y = e.clientY;
}, false);
containerDOM.addEventListener('mousemove', e => {
if (!dragging || isNil(e.clientX) || isNil(e.clientY)) {
return;
}
let dx = x - e.clientX;
let dy = y - e.clientY;
// 若视口移动到最左边或最右边了,仅移动到边界
if (left - dx < 0) {
dx = left;
} else if (left - dx + width > size[0]) {
dx = left + width - size[0];
}
// 若视口移动到最上或最下边了,仅移动到边界
if (top - dy < 0) {
dy = top;
} else if (top - dy + height > size[1]) {
dy = top + height - size[1];
}
left -= dx;
top -= dy;
// 先移动视口,避免移动到边上以后出现视口闪烁
modifyCSS(viewport, {
left: left + 'px',
top: top + 'px'
});
graph.translate(dx * widthRatio, dy * heightRatio);
x = e.clientX;
y = e.clientY;
}, false);
containerDOM.addEventListener('mouseleave', () => {
dragging = false;
}, false);
containerDOM.addEventListener('mouseup', () => {
dragging = false;
}, false);
this.set('viewport', viewport);
containerDOM.appendChild(viewport);
}
updateCanvas() {
const size = this.get('size');
const graph = this.get('graph');
const canvas = this.get('canvas');
// 根据cfgs更新画布内容
if (this.get('keyShapeOnly')) {
this._updateKeyShapes();
} else {
this._updateGraphShapes();
}
// 更新minimap视口
this._updateViewport();
// 刷新后bbox可能会变需要重置画布矩阵以缩放到合适的大小
const bbox = canvas.getBBox();
const width = max(bbox.width, graph.get('width'));
const height = max(bbox.height, graph.get('height'));
const pixelRatio = canvas.get('pixelRatio');
canvas.resetMatrix();
canvas.scale(size[0] / width * pixelRatio, size[1] / height * pixelRatio);
canvas.draw();
}
// 仅在minimap上绘制keyShape
// FIXME 如果用户自定义绘制了其他内容minimap上就无法画出
_updateKeyShapes() {
const graph = this._cfgs.graph;
const canvas = this.get('canvas');
const group = canvas.get('children')[0] || canvas.addGroup();
const nodes = graph.getNodes();
const edges = graph.getEdges();
canvas.get('children');
// 边可以直接使用keyShape
each(edges, edge => {
group.add(edge.get('keyShape').clone());
});
// 节点需要group配合keyShape
each(nodes, node => {
const parent = group.addGroup();
parent.setMatrix(node.get('group').attr('matrix'));
parent.add(node.get('keyShape').clone());
});
}
// 将主图上的图形完全复制到小图
_updateGraphShapes() {
const cfgs = this._cfgs;
const graph = cfgs.graph;
const canvas = this.get('canvas');
const graphGroup = graph.get('group');
const clonedGroup = graphGroup.clone();
clonedGroup.resetMatrix();
canvas.get('children')[0] = clonedGroup;
}
// 绘制minimap视口
_updateViewport() {
const size = this._cfgs.size;
const graph = this._cfgs.graph;
const matrix = graph.get('group').getMatrix();
const topLeft = graph.getPointByCanvas(0, 0);
const viewport = this.get('viewport');
if (!viewport) {
this.initViewport();
}
// viewport宽高,左上角点的计算
const width = matrix[0] >= 1 ? size[0] / matrix[0] : size[0];
const height = matrix[4] >= 1 ? size[1] / matrix[4] : size[1];
const left = topLeft.x > 0 ? topLeft.x * size[0] / graph.get('width') : 0;
const top = topLeft.y > 0 ? topLeft.y * size[1] / graph.get('height') : 0;
modifyCSS(viewport, {
left: left + 'px',
top: top + 'px',
width: width + 'px',
height: height + 'px'
});
}
/**
* 获取minimap的画布
* @return {object} G的canvas实例
*/
getCanvas() {
return this.get('canvas');
}
/**
* 获取minimap的窗口
* @return {object} 窗口的dom实例
*/
getViewport() {
return this.get('viewport');
}
/**
* 获取minimap的容器dom
* @return {object} dom
*/
getContainer() {
return this.get('container');
}
destroy() {
const container = this.get('canvas');
this.get('canvas').destroy();
container.innerHTML = '';
super.destroy();
}
}
module.exports = Minimap;

View File

@ -48,6 +48,10 @@ class Graph extends EventEmitter {
* @type Array
*/
mode: [],
/**
* 注册插件
*/
plugins: [],
/**
* source data
* @type object
@ -185,6 +189,7 @@ class Graph extends EventEmitter {
const modeController = new Controller.Mode(this);
const itemController = new Controller.Item(this);
this.set({ eventController, viewController, modeController, itemController });
this._initPlugins();
}
_initCanvas() {
let container = this.get('container');
@ -216,6 +221,14 @@ class Graph extends EventEmitter {
}
this.set('group', group);
}
_initPlugins() {
const self = this;
Util.each(self.get('plugins'), plugin => {
if (!plugin.destroyed && plugin.init) {
plugin.init(self);
}
});
}
get(key) {
return this._cfg[key];
}
@ -907,6 +920,9 @@ class Graph extends EventEmitter {
*/
destroy() {
this.clear();
Util.each(this.get('plugins'), plugin => {
plugin.destroy();
});
this.get('eventController').destroy();
this.get('itemController').destroy();
this.get('modeController').destroy();

View File

@ -1,11 +1,29 @@
const webpack = require('webpack');
const resolve = require('path').resolve;
const shelljs = require('shelljs');
const _ = require('lodash');
const entry = {
G6: './src/index.js'
G6: './src/index.js',
G6Plugins: './plugins/index.js'
};
shelljs.ls(resolve(__dirname, 'plugins')).forEach(pluginPath => {
if (pluginPath === 'base.js') return;
if (pluginPath !== 'index.js') {
const fileDirs = pluginPath.split('-');
let moduleName = '';
for (let i = 0; i < fileDirs.length; i++) {
const segment = fileDirs[i];
moduleName += (segment.charAt(0).toUpperCase() + segment.substring(1));
}
entry[moduleName] = `./plugins/${pluginPath}/index.js`;
} else {
const moduleName = 'plugins';
entry[moduleName] = './plugins/index.js';
}
});
module.exports = {
mode: 'production',
devtool: 'cheap-source-map',