本文作者:@猫神 viser-graph 地址:https://github.com/viserjs/viser/tree/master/packages viser-graph 官网demo:https://viserjs.gitee.io/demoHome.html
前言
自2014年MVVM框架开始冒出苗头,到现如今react、angular、vue三驾马车并驾齐驱。查看谷歌趋势、百度指数、npm生态三大流量来源,可以看出来angular 已经逐渐落后,react 一骑绝尘国外断层式领先、生态最完善,vue渐渐在国内占据主要份额,但并没有哪一个框架能真正一统江湖。
由此可见,我们框架使用者在图可视化开发时,我们面临几个问题:
- 如果没有基于框架封装的可视化库,需要采用函数式调用可视化库,放弃了MVVM框架的优势
- 如果切换框架,可能面临切换图可视化库,重新熟悉一套api
- 类似d3、g2 此类过程式语法开发,势必提升api 理解成本,提高开发门槛。
在这个背景下,viser-graph应运而生,提供一套api 完美解决3大框架开发者图可视化需求。 viser-graph底层基于g6(国内TOP1图可视化引擎),抽象出一套组件式图可视化开发模式。
viser-graph 的能力是完全与g6 对标,只要g6 能实现的viser-graph 通通满足你,还会增加一些小点心,提高开发效率哦。例如内置常用的utils 方法:
- strLen 计算字符长度
- ellipsisString 字符串过长以…呈现
当然这些常用方法,也期望大家的共建,大家的输入才能让viser-graph 更好用。
解密
下面来说说viser-graph 怎么从json转化成一张图的吧,以下皆以react 框架的实现为例,我们首先需要明确每一层是做什么事情:
- viser-graph-react:将组件解析成完整的json
viser-graph: 将json 对应成 g6 的api,渲染成图
viser-graph-react
首先我们拆分基础组件
graph:画布最外层组件,包括数据的传入、容器的定义、布局的定义等。
- node:节点的定义,包括节点的format(样式、大小) 等,目前组件的属性都封装的比较简单,满足基本的诉求。
- edge:边的定义,包括边的的format(样式、大小) 等,满足基本的诉求。
- zoom:画布缩放,包括当前缩放比率、最大缩放比率、最小缩放比例等。
详细可见 案例, 定义了一颗紧凑树,包括graph、node、edge的定义。
import * as React from 'react';
import { Graph, Node, Edge } from 'viser-graph-react';
const data = {
id: "Modeling Methods",
children: [
{
id: "Classification",
children: [
{ id: "Logistic regression" },
{ id: "Linear discriminant analysis" },
{ id: "Rules" },
{ id: "Decision trees" },
{ id: "Naive Bayes" },
{ id: "K nearest neighbor" },
{ id: "Probabilistic neural network" },
{ id: "Support vector machine" }
]
},
{
id: "Consensus",
children: [
{
id: "Models diversity",
children: [
{ id: "Different initializations" },
{ id: "Different parameter choices" },
{ id: "Different architectures" },
{ id: "Different modeling methods" },
{ id: "Different training sets" },
{ id: "Different feature sets" }
]
},
{
id: "Methods",
children: [
{ id: "Classifier selection" },
{ id: "Classifier fusion" }
]
},
{
id: "Common",
children: [
{ id: "Bagging" },
{ id: "Boosting" },
{ id: "AdaBoost" }
]
}
]
},
{
id: "Regression",
children: [
{ id: "Multiple linear regression" },
{ id: "Partial least squares" },
{ id: "Multi-layer feedforward neural network" },
{ id: "General regression neural network" },
{ id: "Support vector regression" }
]
}
]
};
const graph = {
data,
container: 'mount',
type: 'tree',
width: 500,
height: 500,
pixelRatio: 2,
renderer: 'svg',
fitView: true,
modes: {
default: ['collapse-expand', 'drag-canvas']
},
defaultNode: {
size: 26,
anchorPoints: [[ 0, 0.5 ], [ 1, 0.5 ]],
},
layout: {
type: 'compactBox',
direction: 'LR',
defalutPosition: [],
getId(d) { return d.id; },
getHeight() { return 16 },
getWidth() { return 16 },
getVGap() { return 10 },
getHGap() { return 100 }
}
};
const node = {
formatter: node => {
return {
size: 26,
style: {
fill: '#C6E5FF',
stroke: '#5B8FF9'
},
label: node.id,
labelCfg: {
offset: 10,
position: node.children && node.children.length > 0 ? 'left' : 'right'
}
}
}
}
const edge = {
formatter: () => {
return {
shape: 'cubic-horizontal',
color: '#A3B1BF',
}
},
}
export default class App extends React.Component {
constructor(props) {
super(props);
}
render() {
return (
<div>
<Graph {...graph}>
<Node {...node}/>
<Edge {...edge}/>
</Graph>
</div>
);
}
}
viser-graph-react 递归解析组件,解析出子组件的json,然后通过 context 传递子组件json给最外层的graph,组装成完整的图json( 如下 ),然后再调用 viser-graph 进行渲染。
{
data,
graph: {
container: 'mount',
type: 'tree',
width: 500,
height: 500,
pixelRatio: 2,
renderer: 'svg',
modes: {
default: ['collapse-expand', 'drag-canvas']
},
fitView: true,
layout: {
type: 'compactBox',
direction: 'LR',
defalutPosition: [],
getId(d) { return d.id; },
getHeight() { return 16 },
getWidth() { return 16 },
getVGap() { return 10 },
getHGap() { return 100 }
}
},
node: {
formatter: node => {
return {
size: 26,
anchorPoints: [[0,0.5], [1,0.5]],
style: {
fill: '#C6E5FF',
stroke: '#5B8FF9'
},
label: node.id,
labelCfg: {
offset: 10,
position: node.children && node.children.length > 0 ? 'left' : 'right'
}
}
}
},
edge: {
formatter: () => {
return {
shape: 'cubic-horizontal',
color: '#A3B1BF',
}
},
},
}
viser-graph
viser-graph的设计是将各子组件的json与g6的api 对应,调用g6 的api 进行绘制。可见如下:
// 文件:viser-graph/src/graph.ts
public render() {
// 初始化G6 实例
this.setGraph();
// 设置节点属性
this.setNode();
// 设置边属性
this.setEdge();
// 设置数据
this.setData();
// 设置缩放
this.setZoom();
// TODO: 后续可进一步拆分 tooltip layout plugin 等,避免graph 属性过于堆积
// 调用实例render方法进行渲染
this.graph.render();
// 渲染完成后进行事件绑定
this.setEvent();
}
viser-graph 这一层做的非常轻薄, 只是根据json 调用g6的api ,进行绘制。
其他 vue 、ng 框架也是类似,viser-graph-vue 、viser-graph-ng负责将组件解析成json,调用 viser-graph 进行最后的绘制。
无缝对接G6
除了我们封装的api 外,可能还有些场景需要直接操作g6 的api,那么我们怎么办?很简单,viser-graph-react 暴露了 G6 的入口,使用如下所示:
import * as React from 'react';
// GlobalG6 入口
import { Graph, Node, Edge, GlobalG6 } from 'viser-graph-react';
// 直接调用 GlobalG6 进行注册节点
GlobalG6.registerNode('file-node', {
draw: function draw(cfg, group) {
const keyShape = group.addShape('rect', {
attrs: {
x: cfg.x - 4,
y: cfg.y - 12,
fill: '#fff',
stroke: null
}
});
if (cfg.collapsed) {
group.addShape('marker', {
attrs: {
symbol: 'triangle',
x: cfg.x + 4,
y: cfg.y - 2,
r: 4,
fill: '#666'
}
});
} else if (cfg.children && cfg.children.length > 0) {
group.addShape('marker', {
attrs: {
symbol: 'triangle-down',
x: cfg.x + 4,
y: cfg.y - 2,
r: 4,
fill: '#666'
}
});
}
const shape = group.addShape('text', {
attrs: {
x: cfg.x + 15,
y: cfg.y + 4,
text: cfg.name,
fill: '#666',
fontSize: 16,
textAlign: 'left'
}
});
const bbox = shape.getBBox();
keyShape.attr({
width: bbox.width + 20,
height: bbox.height + 4
});
return keyShape;
}
});
// 直接调用 GlobalG6 进行注册边
GlobalG6.registerEdge('step-line', {
getControlPoints: function getControlPoints(cfg) {
const startPoint = cfg.startPoint;
const endPoint = cfg.endPoint;
return [{
x: startPoint.x,
y: endPoint.y
}];
}
}, 'polyline');
const data = {
id: '1',
name: 'src',
children: [{
id: '1-1',
name: 'behavior',
children: []
}, {
id: '1-3',
name: 'graph',
children: [{
id: '1-3-1',
name: 'controller',
children: []
}]
}, {
id: '1-5',
name: 'item',
children: []
}, {
id: '1-6',
name: 'shape',
children: [{
id: '1-6-2',
name: 'extend',
children: []
}]
}, {
id: '1-7',
name: 'util',
children: []
}]
};
const graph = {
data,
container: 'mount',
type: 'tree',
width: 500,
height: 500,
pixelRatio: 2,
renderer: 'svg',
fitView: true,
modes: {
default: ['collapse-expand', 'drag-canvas']
},
defaultNode: {
shape: 'file-node',
},
defaultEdge: {
style: {
stroke: '#A3B1BF'
}
},
layout: {
type: 'indented',
isHorizontal: true,
direction: 'LR',
indent: 30,
getHeight() { return 16 },
getWidth() { return 16 },
}
};
const node = {
formatter: node => {
return {
shape: 'file-node',
label: node.name
}
}
}
const edge = {
formatter: () => {
return {
shape: 'step-line',
style: {
stroke: '#A3B1BF'
}
}
},
}
export default class App extends React.Component {
constructor(props) {
super(props);
}
render() {
return (
<div>
<Graph {...graph}>
<Node {...node}/>
<Edge {...edge}/>
</Graph>
</div>
);
}
}
通过透传G6 入口,完整提供g6 的能力,不再受限于viser-graph-react封装的局限性,给开发带来更多的可能性。
结语
热烈欢迎大家来使用viser-graph,更热烈欢迎一起来开发的同学,如果发现配置有缺失,可以直接提交PR哈,也是非常简单的。期望大家能使用viser-graph迈入图可视化开发的大门,正向输入业务上的通用场景,帮助viser-graph更好的服务大家。
链接