feat: update svg minimap && fix svg dom event

This commit is contained in:
huangtong.ht 2018-10-12 14:34:20 +08:00
parent ce239d1033
commit 5f681d8d72
13 changed files with 228 additions and 219 deletions

View File

@ -96,9 +96,6 @@
renderer: 'svg',
plugins: [ minimap ]
});
graph.node({
shape: 'custom'
});
graph.read(data);
</script>
</body>

View File

@ -54,6 +54,7 @@
container: 'mountNode',
fitView: 'cc',
height: window.innerHeight,
renderer: 'svg',
plugins: [ minimap ]
});
graph.read(data);

View File

@ -54,57 +54,12 @@ const data = {
children: [
{
name: 'Classification',
children: [
{ name: 'Logistic regression' },
{ name: 'Linear discriminant analysis' },
{ name: 'Rules' },
{ name: 'Decision trees' },
{ name: 'Naive Bayes' },
{ name: 'K nearest neighbor' },
{ name: 'Probabilistic neural network' },
{ name: 'Support vector machine' },
],
},
{
name: 'Consensus',
children: [
{
name: 'Models diversity',
children: [
{ name: 'Different initializations' },
{ name: 'Different parameter choices' },
{ name: 'Different architectures' },
{ name: 'Different modeling methods' },
{ name: 'Different training sets' },
{ name: 'Different feature sets' },
],
},
{
name: 'Methods',
children: [
{ name: 'Classifier selection' },
{ name: 'Classifier fusion' },
],
},
{
name: 'Common',
children: [
{ name: 'Bagging' },
{ name: 'Boosting' },
{ name: 'AdaBoost' },
],
},
],
},
{
name: 'Regression',
children: [
{ name: 'Multiple linear regression' },
{ name: 'Partial least squares' },
{ name: 'Multi-layer feedforward neural network' },
{ name: 'General regression neural network' },
{ name: 'Support vector regression' },
],
},
],
};
@ -121,8 +76,8 @@ var layout = new G6.Layouts.CompactBoxTree({
});
var tree = new G6.Tree({
id: 'mountNode', // 容器ID
renderer: 'svg',
height: 300, // 画布高
renderer: 'svg',
plugins: [
new G6.Plugins['tool.minimap']({
container: 'minimap',
@ -139,11 +94,11 @@ G6.registerNode('transactionNode', {
},
draw(item) {
const group = item.getGraphicGroup();
const { name, id, cnt } = item.getModel();
const { name, id, cnt,color } = item.getModel();
const width = 100;
const height = 40;
const html = G6.Util.createDOM(`
<div style="border: 1px solid #CCC;height:38px;text-align:center;">
<div style="border: 1px solid #CCC;height:38px;text-align:center;background: ${color}">
<strong class="main-text">${name}</strong>
</div>
`);
@ -163,18 +118,19 @@ G6.registerNode('transactionNode', {
tree.node({
shape: 'transactionNode',
});
tree.edge({
shape: 'polyline',
});
tree.on('node:mouseenter', ev => {
console.log('mouseenter');
tree.update(ev.item, {
color: 'orange',
});
});
tree.on('node:mouseleave', ev => {
console.log('mouseleave');
tree.update(ev.item, {
color: '',
color: 'blue',
});
});
tree.read({

View File

@ -106,7 +106,7 @@
"run": []
},
"dependencies": {
"@antv/g": "~3.2.0",
"@antv/g": "~3.3.0",
"@antv/component": "~0.0.4",
"@antv/hierarchy": "~0.3.13",
"@antv/scale": "^0.0.1",

View File

@ -17,34 +17,15 @@ class Plugin {
},
...this.options
});
graph.on('afterchange', () => {
minimap.renderBackground();
minimap.renderViewPort();
});
graph.on('afterlayout', () => {
minimap.renderBackground();
minimap.renderViewPort();
});
graph.on('afterviewportchange', () => {
minimap.renderViewPort();
});
graph.on('afterfilter', () => {
minimap.renderBackground();
minimap.renderViewPort();
});
this.renderBackground = () => {
minimap.renderBackground();
};
this.renderViewPort = () => {
minimap.renderViewPort();
};
minimap.bindGraph(graph);
this.minimap = minimap;
}
destroy() {
this.minimap.destroy();
}
}
G6.Plugins['tool.minimap'] = Plugin;
G6.Components.Minimap = Minimap;
module.exports = Plugin;

View File

@ -104,55 +104,72 @@ class Minimap {
this._initContainer();
this._initMiniMap();
this._bindEvent();
this._assignRenderBackground();
this._assignDebounceRender();
}
_assignRenderBackground() {
this.renderBackground = Util.debounce(graph => {
if (!graph) {
graph = this.getGraph();
}
const miniMapCanvas = this.miniMapCanvas;
const width = this.width;
const height = this.height;
const toSmallNodes = [];
const minSize = 2;
Util.graph2Canvas({
graph,
width,
height,
canvas: miniMapCanvas,
beforeTransform(minimapMatrix) {
const minimapScale = minimapMatrix[0];
const nodes = graph.getNodes();
nodes.forEach(node => {
const bbox = node.getBBox();
const model = node.getModel();
const width = bbox.width;
if (width * minimapScale < minSize) {
const group = node.getGraphicGroup();
const originMatrix = Util.clone(group.getMatrix());
group.transform([
[ 't', -model.x, -model.y ],
[ 's', minSize / (width * minimapScale), minSize / (width * minimapScale) ],
[ 't', model.x, model.y ]
]);
toSmallNodes.push({
item: node,
originMatrix
});
}
});
},
afterTransform() {
toSmallNodes.forEach(({ item, originMatrix }) => {
item.getGraphicGroup().setMatrix(originMatrix);
});
}
});
this.miniMapMatrix = miniMapCanvas.matrix;
_assignDebounceRender() {
this.debounceRender = Util.debounce(() => {
this.renderBackground();
this.renderViewPort();
}, 32);
}
bindGraph(graph) {
graph.on('afterchange', () => {
this.debounceRender();
});
graph.on('afterlayout', () => {
this.debounceRender();
});
graph.on('afterviewportchange', () => {
this.renderViewPort();
});
graph.on('afterfilter', () => {
this.debounceRender();
});
}
renderBackground(graph) {
if (!graph) {
graph = this.getGraph();
}
const miniMapCanvas = this.miniMapCanvas;
const width = this.width;
const height = this.height;
const toSmallNodes = [];
const minSize = 2;
graph.saveImage({
graph,
width,
height,
canvas: miniMapCanvas,
beforeTransform(minimapMatrix) {
const minimapScale = minimapMatrix[0];
const nodes = graph.getNodes();
nodes.forEach(node => {
const bbox = node.getBBox();
const model = node.getModel();
const width = bbox.width;
if (width * minimapScale < minSize) {
const group = node.getGraphicGroup();
const originMatrix = Util.clone(group.getMatrix());
group.transform([
[ 't', -model.x, -model.y ],
[ 's', minSize / (width * minimapScale), minSize / (width * minimapScale) ],
[ 't', model.x, model.y ]
]);
toSmallNodes.push({
item: node,
originMatrix
});
}
});
},
afterTransform() {
toSmallNodes.forEach(({ item, originMatrix }) => {
item.getGraphicGroup().setMatrix(originMatrix);
});
}
});
this.miniMapMatrix = miniMapCanvas.matrix;
}
_bindEvent() {
const controlLayer = this.controlLayer;
let miniMapViewPortActived = false;

View File

@ -32,6 +32,16 @@ const MouseEventTypes = [ EVENT.DBLCLICK, EVENT.MOUSEDOWN, EVENT.MOUSEUP, EVENT.
EVENT.CONTEXTMENU, EVENT.MOUSEWHEEL ];
const KeyboardEventTypes = [ EVENT.KEYDOWN, EVENT.KEYUP, EVENT.KEYPRESS ];
const CANVAS = 'canvas:';
function parentNodeHasTag(n, t) {
let p = n.parentNode;
while (p) {
if (p.tagName === t) {
return true;
}
p = p.parentNode;
}
return false;
}
class Controller extends Base {
constructor(cfg) {
super(cfg);
@ -76,6 +86,14 @@ class Controller extends Base {
const _events = this._domEvents;
Util.each(MouseEventTypes, item => {
_events.push(Util.addEventListener(el, item, ev => {
// Compatible with SVG
if (ev.type === EVENT.MOUSEENTER) {
if (ev.fromElement) {
if (!ev.fromElement.parentNode || parentNodeHasTag(ev.fromElement, 'foreignObject')) {
return;
}
}
}
const oldEventObj = this._currentEventObj;
this._oldEventObj = oldEventObj;
this._processEventObj(ev);

View File

@ -7,45 +7,15 @@
const Util = require('../../util/');
const G = require('@antv/g/lib');
const domToImage = require('dom-to-image');
const Mixin = function() {};
Util.augment(Mixin, {
createPath() {},
// temporary solution
isPointInPath(x, y) {
if (this._cfg.el) {
const box = this._cfg.el.getBBox();
if (x <= box.x + box.width && y <= box.y + box.height && x >= box.x && y >= box.y) {
return true;
}
}
return false;
},
// draw dom when canvas renderer
drawInner(context) {
let { html, x, y, width, height } = this._attrs;
const canvas = this.get('canvas');
const el = canvas.get('el');
const tm = Util.clone(this.getTotalMatrix());
if (Util.isString(html)) {
html = Util.createDOM(html);
} else {
html = html.cloneNode(true);
}
el.parentNode.appendChild(html);
domToImage.toPng(html, {
width,
height
})
.then(dataUrl => {
const img = new Image();
img.src = dataUrl;
img.onload = () => {
context.setTransform(tm[0], tm[1], tm[3], tm[4], tm[6], tm[7]);
context.drawImage(img, x, y, width, height);
};
});
html.remove();
const { x, y, width, height } = this._attrs;
context.setTransform(tm[0], tm[1], tm[3], tm[4], tm[6], tm[7]);
context.drawImage(this.domImage, x, y, width, height);
}
});

View File

@ -10,6 +10,7 @@ const Base = require('./base');
const Item = require('./item/');
const Shape = require('./shape/');
const Util = require('./util/');
const Graph2Canvas = require('./helper/graph2canvas');
const G = require('@antv/g/lib');
const LayoutMixin = require('./mixin/layout');
const MappingMixin = require('./mixin/mapping');
@ -786,17 +787,19 @@ class Graph extends Base {
}
/**
* save graph image
* @param {object} options - save options
* @return {object} canvas dom
*/
saveImage() {
saveImage(options) {
const box = this.getBBox();
const padding = this.getFitViewPadding();
return Util.graph2Canvas({
const graph2Canvas = new Graph2Canvas({
graph: this,
width: box.width + padding[1] + padding[3],
height: box.height + padding[0] + padding[2]
height: box.height + padding[0] + padding[2],
...options
});
return graph2Canvas.toCanvas();
}
}
Mixins.forEach(Mixin => {

124
src/helper/graph2canvas.js Normal file
View File

@ -0,0 +1,124 @@
/**
* @fileOverview dom to canvas
* @author huangtonger@aliyun.com
*/
const Util = require('../util/');
const G = require('@antv/g/lib');
const domToImage = require('dom-to-image');
class Graph2Canvas {
constructor(options) {
this.options = {
graph: null,
width: null,
height: null,
canvas: null,
beforeTransform() {},
afterTransform() {},
drawCount: 0,
...options
};
}
getCanvas() {
let { width, height, canvas } = this.options;
if (!canvas) {
const containerDOM = Util.createDOM('<canvas></canvas>');
canvas = new G.Canvas({
containerDOM,
width,
height
});
}
if (!canvas.drawCount) {
canvas.drawCount = 0;
}
return canvas;
}
/**
* draw canvas
* @param {object} canvas item
* @param {function} callback item
*/
drawInner(canvas, callback) {
const { graph } = this.options;
const graphCanvas = graph.getCanvas();
const graphRenderer = graph.get('renderer');
const drawCount = canvas.drawCount;
if (graphRenderer === 'svg') {
const domShapes = [];
graphCanvas.deepEach(element => {
const type = element.get('type');
type === 'dom' && domShapes.push(element);
});
if (domShapes.length > 0) {
domShapes.forEach(domShape => {
domShape.domImageOnload = false;
const el = domShape.get('el');
const width = domShape.attr('width');
const height = domShape.attr('height');
domToImage.toPng(el, {
width,
height
})
.then(dataUrl => {
const img = new Image();
img.src = dataUrl;
img.onload = () => {
if (drawCount === canvas.drawCount - 1) {
domShape.domImage = img;
domShape.domImageOnload = true;
for (let i = 0; i < domShapes.length; i++) {
const subShape = domShapes[i];
if (!subShape.domImageOnload || subShape.get('destroyed')) {
break;
}
if (subShape.domImageOnload && i === domShapes.length - 1) {
callback();
}
}
}
};
});
});
} else {
callback();
}
} else {
callback();
}
canvas.drawCount += 1;
}
toCanvas() {
const { graph,
width,
height,
beforeTransform,
afterTransform } = this.options;
const canvas = this.getCanvas();
const graphBBox = graph.getBBox();
const matrixCache = Util.cloneDeep(graph.getMatrix());
const padding = graph.getFitViewPadding();
const graphCanvas = graph.getCanvas();
const matrix = Util.getAutoZoomMatrix({
minX: 0,
minY: 0,
maxX: width,
maxY: height
}, graphBBox, padding);
this.drawInner(canvas, () => {
const children = graphCanvas.get('children');
canvas.set('children', children);
beforeTransform(matrix, matrixCache);
graph.setMatrix(matrix);
canvas.draw();
graph.setMatrix(matrixCache);
afterTransform(matrix, matrixCache);
});
canvas.matrix = matrix;
return canvas.get('el');
}
}
module.exports = Graph2Canvas;

View File

@ -15,6 +15,7 @@ const G6 = {
Layouts: require('./layouts/'),
G,
Plugins: {},
Components: {},
Global,
Shape,
registerNode: Shape.registerNode,

View File

@ -4,9 +4,6 @@
*/
const BaseUtil = require('./base');
const DomUtil = require('./dom');
const GraphicUtil = require('./graphic');
const G = require('@antv/g/lib');
module.exports = {
/**
@ -32,55 +29,5 @@ module.exports = {
*/
isGroup(item) {
return item && BaseUtil.isObject(item) && item.type === 'group';
},
/**
* graph to Canvas
* @param {object} options item
* @return {domobject} canvas
*/
graph2Canvas(options) {
options = BaseUtil.mix({
graph: null,
width: null,
height: null,
canvas: null,
beforeTransform() {},
afterTransform() {}
}, options);
let { graph,
width,
height,
canvas,
beforeTransform,
afterTransform } = options;
const graphCanvas = graph.getCanvas();
const graphBBox = graph.getBBox();
const padding = graph.getFitViewPadding();
const children = graphCanvas.get('children');
const matrixCache = BaseUtil.cloneDeep(graph.getMatrix());
if (!canvas) {
const containerDOM = DomUtil.createDOM('<canvas></canvas>');
canvas = new G.Canvas({
containerDOM,
width,
height
});
}
const matrix = GraphicUtil.getAutoZoomMatrix({
minX: 0,
minY: 0,
maxX: width,
maxY: height
}, graphBBox, padding);
beforeTransform(matrix, matrixCache);
graph.setMatrix(matrix);
canvas.set('children', children);
canvas.matrix = matrix;
// canvas.draw();
canvas._cfg.painter.beforeDraw();
canvas._cfg.painter._drawGroup(canvas);
graph.setMatrix(matrixCache);
afterTransform(matrix, matrixCache);
return canvas.get('el');
}
};

View File

@ -72,12 +72,6 @@ describe('minimap test', () => {
it('after layout', () => {
graph.layout();
});
it('renderBackground', () => {
minimap.renderBackground();
});
it('renderViewPort', () => {
minimap.renderViewPort();
});
it('over min node size', () => {
graph.update('node1', {
x: 10000