知识图谱项目是一个强视觉交互性的关系图可视化分析系统,很多模块都会涉及到对节点和关系的增删改查操作,常规的列表展示类数据通过表格展示,表单新增或编辑,而图谱类项目通常需要关系图(力导向图:又叫力学图、力导向布局图,是绘图的一种算法,关系图一般采用这种布局方式)去展示,节点和关系的新增编辑也需要前端去做一些复杂的交互设计。除此之外还有节点和关系的各种布局算法,大量数据展示的性能优化,节点动态展开时的局部布局渲染,画布的可扩展性,样式的自定义等等诸多技术难点。目前国内使用最多的两个已开源的前端可视化框架:阿里的AntV、百度的Echarts对于关系图的支持都比较弱,不能完全满足项目中的需求。
在之前的两个图谱demo项目中我一直是使用的D3.js这个前端最流行的可视化图库。D3.js也是比较强大的图库,但是它提供的API都是偏底层的,文档也不友好,比较难上手,实现一个简单的功能也需要大量的代码,编码效率并不是很高,各个版本之间兼容性也很差,并且使用SVG渲染画布在大量节点显示的时候有性能瓶颈。
直到我遇见了Cytoscape.js——一个非常适合做知识图谱的前端可视化图库。
cytoscape是一个网络图的可视化工具,大量的生物分子/基因领域的公司用它来做可视化分析。由于它的通用性,慢慢的也被其他领域的人用来做网络的可视化和分析。cytoscape分为两种,一种叫做cytoscapedesktop,是一个桌面软件,可以把数据导入然后生成可视化的网络图进行分析;另一种叫做cytoscape.js,是一个javascript库,主要给开发人员使用,来在网页上生成可视化的网络图。我们要用的是后者。
Cytoscape.js允许你轻松显示和操作丰富的交互式图形。由于Cytoscape.js允许用户与图形进行交互,并且库允许客户端挂接到用户事件,因此Cytoscape.js可以轻松集成到你的应用程序中,尤其是因为Cytoscape.js支持桌面浏览器(例如Chrome)和移动浏览器,就像在iPad上一样。Cytoscape.js包含了开箱即用的所有手势,包括捏缩放,框选择,平移等。
Cytoscape.js还考虑了图分析:该库包含图论中的许多有用功能。你可以在Node.js上无头使用Cytoscape.js在终端或Web服务器上进行图形分析。
Cytoscape.js支持许多不同的图论用例。它支持有向图,无向图,混合图,循环,多图,复合图(一种超图),等等。
兼容所有现代浏览器、具有ES5和canvas支持的旧版浏览器。
CND引入
如果要从CDN使用Cytoscape.js,请使用以下CDN之一:
NPM安装
npminstallcytoscape
bowerinstallcytoscape
importcytoscapefrom'cytoscape';
varcytoscape=require('cytoscape');
require(['cytoscape'],function(cytoscape){//...});
核心提供了几种访问图中元素的函数。这些函数中的每一个都返回一个集合,即图形中的一组元素。集合上有可用的函数,这些函数使开发者可以过滤集合,对集合执行操作,遍历有关集合的图,获取有关集合中元素的数据等等。
有几种类型可以执行不同的功能,在文档中用来表示这些类型的变量名概述如下:
默认情况下,函数会将引用返回给调用对象,以允许链接(例如obj.fn1().fn2().fn3())。除非另有说明,否则函数可以这种方式链接,除非指定了不同的返回值。这适用于核心和集合。
核心对象是图形的界面。这是进入Cytoscape.js的入口:通过此对象可以访问库的所有功能。
创建核心:
varcy=cytoscape({/*options*/});
你可以初始化内核,而无需任何选项。如果要将Cytoscape用作可视化对象,则需要containerDOM元素,例如:
varcy=cytoscape({container:document.getElementById('cy')});集合集合包含一组节点和边。调用函数会将函数应用于集合中的所有元素。从集合中读取值时,例如eles.data()将返回集合中第一个元素的值。例如:
varweight=cy.nodes().data("weight");console.log(cy.nodes()[0].data("weight")+'=='+weight);//weightisthefirstele'sweight通过使用选择器将集合缩小到一个元素(即eles.size()===1)或eles.eq()函数,可以确保你正在读取所需的元素。
Cytoscape.js实例具有许多可以在初始化时设置的选项。下面概述了它们的默认值。
请注意,所有内容都是可选的。默认情况下,你将获得带有默认样式表的空图。为了方便起见,浏览器外部的环境(例如Node.js)自动设置为无头。
varcy=cytoscape({//verycommonlyusedoptionscontainer:undefined,elements:[/*...*/],style:[/*...*/],layout:{name:'grid'/*,...*/},//initialviewportstate:zoom:1,pan:{x:0,y:0},//interactionoptions:minZoom:1e-50,maxZoom:1e50,zoomingEnabled:true,userZoomingEnabled:true,panningEnabled:true,userPanningEnabled:true,boxSelectionEnabled:true,selectionType:'single',touchTapThreshold:8,desktopTapThreshold:4,autolock:false,autoungrabify:false,autounselectify:false,//renderingoptions:headless:false,styleEnabled:true,hideEdgesOnViewport:false,textureOnViewport:false,motionBlur:false,motionBlurOpacity:0.2,wheelSensitivity:1,pixelRatio:'auto'});
非常常用的选项:
container:一个HTMLDOM元素,呈现图形的容器。如果Cytoscape.js无头运行,则未指定。该容器应为空div。
初始视口状态:
zoom:图形的初始缩放级别。确保fit在布局中禁用视口操纵选项,例如,以便在应用布局时不会覆盖它。你可以设置options.minZoom和options.maxZoom设置缩放级别的限制。
pan:图形的初始平移位置。请确保fit在布局中禁用视口操纵选项,例如,以便在应用布局时不会覆盖它。
互动选项:
minZoom:图形缩放级别的最小界限。视口的缩放比例不能小于此缩放级别。
maxZoom:图形的缩放级别的最大界限。视口的缩放比例不能大于此缩放级别。
zoomingEnabled:是否通过用户事件和以编程方式启用了缩放图形。
userZoomingEnabled:是否允许用户事件(例如,鼠标滚轮,捏缩放)缩放图形。缩放的程序更改不受此选项的影响。
panningEnabled:是否通过用户事件和以编程方式启用了平移图表。
userPanningEnabled:是否允许用户事件(例如,拖动图形背景)来平移图形。平移的编程更改不受此选项的影响。
boxSelectionEnabled:是否启用了框选择(即,拖动框叠加并释放它以进行选择)。如果在启用了平移的同时启用了该功能,则用户必须使用修饰键(Shift,Alt,Control或Command)才能使用框选择。
selectionType:一个字符串,指示用户输入的选择行为。对于'additive',用户做出的新选择将添加到当前选择的元素集中。对于'single',用户进行的新选择将成为当前所选元素的整个集合(即,未选择先前的元素)。
touchTapThreshold&desktopTapThreshold:一个非负整数,指示用户在轻击手势期间分别在触摸设备和台式设备上可以移动的最大允许距离。这使用户更容易点击。这些值具有默认值,因此除非有充分的理由,否则不建议更改这些选项。较大的值几乎肯定会产生不良后果。
autoungrabify:默认情况下是否应取消节点的节点化(用户不可抓取)(如果true,则覆盖单个节点状态)。
autolock:默认情况下是否应锁定节点(完全不可拖动)(如果true,则覆盖单个节点状态)。
autounselectify:默认情况下是否应取消选择节点(不可变的选择状态)(如果true,则覆盖单个元素的状态)。
渲染选项:
headless:一个方便的选项,用于初始化实例以使其无头运行。你无需在隐式无头的环境(例如Node.js)中进行设置。但是,headless:true如果要在浏览器中使用无头实例,则设置起来很方便。
hideEdgesOnViewport:渲染提示,当设置为时,true将使渲染器在操纵视口时不渲染边缘。这使得平移,缩放,拖动等等对于大型图形的响应更加灵敏。由于性能增强,现在该选项几乎没有意义。
textureOnViewport:一种渲染提示,设置为时,true使渲染器在平移和缩放过程中使用纹理而不是绘制元素,从而使大型图形更具响应性。由于性能增强,现在该选项几乎没有意义。
motionBlur:渲染提示,设置为时,true使渲染器使用运动模糊效果使帧之间的过渡看起来更平滑。这可以提高大型图的感知性能。由于性能增强,现在该选项几乎没有意义。
motionBlurOpacity:当时motionBlur:true,此值控制运动模糊帧的不透明度。较高的值会使运动模糊效果更加明显。由于性能增强,现在该选项几乎没有意义。
wheelSensitivity:更改变焦时的滚轮灵敏度。这是一个乘法修饰符。因此,介于0和1之间的值会降低灵敏度(缩放较慢),而大于1的值会提高灵敏度(缩放较快)。此选项设置为一个合理的值,该值对于Linux,Mac和Windows上的主流鼠标(Apple,Logitech,Microsoft)非常有效。如果默认值在你的特定系统上看起来太快或太慢,则你的操作系统或专用鼠标可能具有非默认鼠标设置。除非你的应用只能在特定硬件上运行,否则你不应更改此值。否则,冒着使大多数用户缩放得太慢或太快的风险。
pixelRatio:使用手动设置的值(1.0建议,如果设置)覆盖屏幕像素比率。通过减少需要渲染的有效区域,可以将其用于提高高密度显示器的性能,尽管在最新的浏览器版本中,此需求要小得多。如果要使用硬件的实际像素比率,则可以设置pixelRatio:'auto'(默认)。
下面着重介绍一下cytoscape.js中几个非常重要且功能强大的模块,也是我在知识图谱项目中使用最多的模块。
一些注意事项:选择器的功能类似于DOM元素上的CSS选择器,但Cytoscape.js中的选择器可用于图形元素的集合。注意,如果选择器被指定为函数的参数,那么可以使用els.filter()样式的过滤器函数来代替选择器
cy.$('#j').neighborhood(function(ele){returnele.isEdge();});
选择器可以组合在一起,以在Cytoscape.js中进行强大的查询,例如:
//getallnodeswithweightmorethan50andheightstrictlylessthan180cy.elements("node[weight>=50][height<180]");
选择器可以用逗号连接在一起(有效地创建逻辑或):
//getnodejandtheedgescomingoutfromitcy.elements('node#j,edge[source="j"]');
重要的是要注意,字符串需要用引号引起来:
//cy.filter('node[name=Jerry]');//thisdoesn'tworkcy.filter('node[name="Jerry"]');//butthisdoes
请注意,对于ID,字段名称等,需要转义一些字符:
cy.filter('#some\\$funky\\@id');
这些字符的一些示例包括(^$\/()|+*[]{},.)。尽量避免使用非字母数字字符作为字段名称和ID,以使事情变得简单。如果必须对ID使用特殊字符,请使用数据选择器而不是ID选择器:
cy.filter('[id="some$funky@id"]');
node,edge或*(组选择器)基于组匹配元素(node对应节点,edge对应边缘,*对应所有)。
.className匹配具有指定类的元素(例如,.foo用于名为“foo”的类的元素)。
#id匹配具有匹配ID的元素(例如#foo等效于[id='foo'])
[name]如果元素具有定义的指定数据属性(即未定义)undefined(例如,[foo]对于名为“foo”的属性),则匹配元素。在此,null被认为是定义值。
[^name]如果未定义指定的数据属性undefined(例如[^foo]),则匹配元素。在此,null被认为是定义值。
[name=value]如果元素的数据属性与指定值匹配(例如[foo='bar']或[num=2]),则匹配元素。
[name!=value]如果元素的数据属性与指定值不匹配(例如[foo!='bar']或[num!=2]),则匹配元素。
[name>value]如果元素的数据属性大于指定值(例如[foo>'bar']或[num>2]),则与之匹配。
[name>=value]如果元素的数据属性大于或等于指定值(例如[foo>='bar']或[num>=2]),则匹配它们。
[name [name<=value]如果元素的数据属性小于或等于指定值(例如[foo<='bar']或[num<=2]),则匹配它们。 [name*=value]如果元素的数据属性包含指定的值作为子字符串(例如[foo*='bar']),则与之匹配。 [name^=value]如果元素的数据属性以指定值(例如[foo^='bar'])开头,则与之匹配。 [name$=value]如果元素的数据属性以指定值(例如[foo$='bar'])结尾,则与之匹配。 @(数据属性操作者调节剂)预先考虑的操作,使得不区分大小写(例如[foo@$='ar'],[foo@>='a'],[foo@='bar']) !(数据属性运算符修饰符)加在运算符之前,以便取反(例如[foo!$='ar'],[foo!>='a']) [[]](元数据括号)使用双方括号代替方括号,以匹配元数据而不是数据(例如,[[degree>2]]匹配度数大于2的元素)。所支持的特性包括degree,indegree,和outdegree。 >(子选择器)匹配父节点的直接子代(例如node>node)。 空格(后代选择器)匹配父节点的后代(例如nodenode)。 $(主题选择器)设置选择器的主题(例如$node>node,选择父节点而不是子节点)。 动画 选择 锁定 样式 用户互动: 图表内或图表外 复合节点 边缘 Cytoscape.js中的样式尽可能遵循CSS约定。在大多数情况下,属性与其对应的CSS同名具有相同的名称和行为。但是,CSS中的属性不足以指定图形某些部分的样式。在这种情况下,将引入Cytoscape.js特有的其他属性。 示例 字符串格式:cytoscape({container:document.getElementById('cy'),//...style:'node{background-color:green;}'//probablypreviouslyloadedviaajaxratherthanhardcoded//,...});JSON格式:cytoscape({container:document.getElementById('cy'),//...style:[{selector:'node',style:{'background-color':'red'}}//,...]//,...});函数格式:cytoscape({container:document.getElementById('cy'),//...style:cytoscape.stylesheet().selector('node').style({'background-color':'blue'})//...//,...}); 还可以选择使用css代替style,例如.selector(...).css(...)或{selector:...,css:...}。 所有发生在元素上的事件都会冒泡到复合父元素,然后到达核心。当你在监听核心的时候,你必须考虑到这一点,这样你才能区分发生在背景上的事件和发生在元素上的事件。使用eventObj.target表示事件的发起者(即eventObj.target===cy||eventObj.target===someEle)。 这些是正常的浏览器事件,你可以通过Cytoscape.js进行监听。你可以在核心和集合上监听这些事件。 你还可以使用一些更高级别的事件,因此你不必为鼠标输入设备和触摸设备监听其他事件。 这些事件是Cytoscape.js自定义的。你可以在集合上监听这些事件。 这些事件是Cytoscape.js的自定义事件,它们发生在核心上。 每个布局都有其自己的算法,用于设置每个节点的位置。此算法影响图形的整体形状和边的长度。可以通过设置布局的选项来自定义布局的算法。因此,可以通过适当设置布局选项来控制边缘长度。 对于力导向(物理)布局,通常可以选择为每个边缘设置权重以影响相对边缘长度。边缘长度也可能受诸如间距系数,角度和避免重叠等选项的影响。设置边缘长度取决于特定的布局,某些布局将允许比其他布局更精确的边缘长度。 Cytoscape.js自带了一下几种布局: null:空布局将所有节点放在(0,0)处,这对于调试非常有用。 letoptions={name:'null',ready:function(){},//onlayoutreadystop:function(){}//onlayoutstop};cy.layout(options); random:随机布局将节点放置在视口内的随机位置。 preset:预设布局将节点放置在手动指定的位置。 letoptions={name:'preset',positions:undefined,//mapof(nodeid)=>(positionobj);orfunction(node){returnsomPos;}zoom:undefined,//thezoomleveltoset(probwantfit=falseifset)pan:undefined,//thepanleveltoset(probwantfit=falseifset)fit:true,//whethertofittoviewportpadding:30,//paddingonfitanimate:false,//whethertotransitionthenodepositionsanimationDuration:500,//durationofanimationinmsifenabledanimationEasing:undefined,//easingofanimationifenabledanimateFilter:function(node,i){returntrue;},//afunctionthatdetermineswhetherthenodeshouldbeanimated.Allnodesanimatedbydefaultonanimateenabled.Non-animatednodesarepositionedimmediatelywhenthelayoutstartsready:undefined,//callbackonlayoutreadystop:undefined,//callbackonlayoutstoptransform:function(node,position){returnposition;}//transformagivennodeposition.Usefulforchangingflowdirectionindiscretelayouts};cy.layout(options); 该布局有一个非常实用的地方是,当你保存画布并保存了节点元素的位置后,再加载已保存的画布的时候,可以采用此布局方式。 grid:网格布局将节点放置在一个间隔均匀的网格中。 circle:圆圈布局将节点放在圆圈中。 concentric:同心圆布局将节点放置在同心圆中,基于你指定的将节点隔离到各个级别的度量。此布局在ele.scratch()中设置同心值。 breadthfirst:广度优先布局基于图的广度优先遍历将节点置于层次结构中。 cose:cose(复合弹簧嵌入器)布局使用物理模拟对图形进行布局。它适用于非复合图,并且具有附加逻辑以很好地支持复合图。 因为它主要是做网络图或者叫关系图分析,所以非常适合做知识图谱项目,它功能齐全,且使用简单,内置了很多常用的布局算法,路径算法,复合图形,还有丰富的第三方插件,基本上能满足你在项目中的各种需求。 我现在也只是使用了它很少一部分的功能,大量的高级用法还需要继续深入研究。 本文大量内容都是节选自官方文档翻译过来的,不免会有不太严谨的地方欢迎指出,如果你也对它感兴趣可以访问它的官方文档和github主页: