APP下载

基于SVG的思维导图的系统实现

2016-05-11任志豪赖源劲

现代计算机 2016年9期
关键词:实现思维导图

任志豪,赖源劲

(华南师范大学软件学院,广州 510631)



基于SVG的思维导图的系统实现

任志豪,赖源劲

(华南师范大学软件学院,广州510631)

摘要:基于SVG实现的绘图技术,具有对象化和易于交互的特点,适合用于思维导图在线绘制的实现。基于SVG实现的思维导图绘制系统,其基础功能有思维导图节点及边的绘制、增删、位置变换和拖动,其中的关键技术点包括图形的位置计算和渲染等。

关键词:思维导图;SVG;实现

0 引言

随着信息时代的飞速发展,人们在日常工作或者学习中越来越需要记录信息和知识,使用思维导图可为人们更高效地记录和归纳信息。思维导图又称心智图,它从中心主题出发,发散关键点,以简洁的信息表示整个结构。一个典型的思维导图由多个节点所组成,一个节点也可引申出多个子节点,体现了更清晰的分类思想,通过合理的分类方法,可以清晰地整理任务和快速定位信息的关键点。本文的主要内容则是如何实现一个思维导图的Web应用。

该系统使用SVG技术绘制思维导图的图形。SVG是可伸缩矢量图形(Scalable Vector Graphics),用于描述二维矢量的一种图形格式。SVG是一种基于XML的语言,可以在XML或者HTML中嵌入SVG。SVG的事件模型基于SVG图形元素上,所以SVG具有很好的交互性。目前的已有的思维导图Web应用中,某些网站的思维导图应用使用了Canvas技术,而Canvas是像素级别的图形绘制技术,不太适合于思维导图这种对象级别的图形绘制中;另外一些网站的思维导图应用使用了Flash技术,但Flash技术比较繁琐,性能不高,而且Flash需要下载插件,在一部分的操作系统不能使用。与上述两种技术相比,SVG将图像当成对象,可将思维导图中节点和线等图形表现为对象,事件模型基于图形元素上的特性使得SVG更适合用于动态交互的应用。目前兼容SVG的浏览器有IE9以及其他主流的浏览器,使得SVG的应用具有较好的移植性。该系统使用一个SVG图形库——Raphael.js提供的API来绘制思维导图应用。

1 系统功能与系统结构

该系统的主要功能有:添加节点、删除节点和拖动节点。

该系统采用面向对象的设计方式,如图1所示,当中有4个主要的对象类型:Node、Edge、Graph和Renderer,前3个对象类型属于数据层,第4个对象类型属于渲染层。Node和Edge分别对应于思维导图的节点和边,每一个节点都有一个父节点引用与父节点相连的父边引用,用于表示节点与节点之间的关系。Graph 与Node之间是聚合关系,它拥有思维导图根节点和所有节点的列表。Renderer用于渲染图像,只有Graph拥有一个Render,Render负责具体的渲染操作,使得在数据有改变时才做出相应的渲染,这样的层次划分可以使得数据操作和视图渲染不会耦合在一起。只有Graph模块暴露给客户端使用,减少了绘图系统和客户端的耦合度。

图1 

2 绘制基础元素图形的实现

Raphael.js提供绘制基本图形的功能,例如长方形,圆形。这些基本图形可以组在一起形成集合,对这个集合进行的操作即对集合内所有元素进行操作,在Raphael.js中,这个集合称为Set。

2.1节点的绘制

一个节点由一个文本和长方形组成,节点的位置由左上角坐标决定。为了让文本在长方形中在长方形区域里水平垂直居中,先绘制文本,求得文本所占区域的宽度和长度,然后加上长方形的上下补白,即可求得长方形的宽度和长度,再将文本位移二分之一的长方形宽度和高度即可让文本在长方形区域中水平垂直居中。最后将长方形设置为新的宽度和高度。实现代码如下:

function drawNode(paper, x, y){ // paper为Raphael对象

var label = paper.text(x, y, '节点');

var textBox = label.getBBox();

var nodeXPadding = 40; //长方形的x补白

var nodeYPadding = 20; //长方形的y补白

//得到矩形的长度

var rectWidth = textBox.width + nodeXPadding;

var rectHeight = textBox.height + nodeYPadding;

var rect = paper.rect(x, y, 1, 1, 4);

label.toFront();

label.attr({

x: x + rectWidth * 0.5,

y: y + rectHeight * 0.5,

'font-size': 14

});

rect.attr({

width: rectWidth,

height: rectHeight,

'stroke': '#808080',

'stroke-width': 1

});

return paper.set().push(label).push(rect);

}

2.2连线的绘制

如果节点间的连线都用直线,思维导图整体上会不太美观。于是,为了让连线更好看,在根结点与第一层节点间使用贝塞尔曲线,其他的连线都使用“三段连线”。

(1)贝塞尔曲线的绘制

①贝塞尔曲线

贝塞尔曲线是应用于二维图形应用程序的数学曲线,通过计算贝塞尔曲线的计算公式,即可精确地绘制出复杂的曲线。贝塞尔曲线有三种重要的点:起始点、终止点和控制点。下面以二阶贝塞尔曲线为例说明其原理,如图2所示,设P0为起始点,P1为控制点,P2为终止点,在P0到P1的线段上作点Q0,在P1到P2的线段上作点Q1,在Q0和Q1的线段上作点Q2

图2 

引入参数t,使以下比例成立

求得Q2为:

Q2=(1-t)2P0+2t(1-t)P1+

当t的范围在0到1时,表示由三个顶点P0、P1、P2即可描绘一条二阶的贝塞尔曲线。

②根节点与第一层节点的连线

为了美观,根结点与第一层节点使用贝塞尔曲线,起始点为根节点的中心点,终止点为第一层节点的中心点,引入参数k1和k2,再加上起始点和终止点的坐标来求出控制点。Raphael.js的Paper.path()提供了绘制贝塞尔曲线的方法。实现代码如下:

// paper为Raphael对象,source为源节点的set集合

// target为目标节点的set集合

function drawCurve(paper, source, target){

var sourceBox = source.getBBox();

var targetBox = target.getBBox();

//求出起始点、控制点、终止点

var x1 =(sourceBox.x + sourceBox.x2)/2;

var y1 =(sourceBox.y + sourceBox.y2)/2;

var x2 =(targetBox.x + targetBox.x2)/2;

var y2 =(targetBox.y + targetBox.y2)/2;

var k1 = 0.8;

var k2 = 0.2;

var pathPoints = {

x1: x1,

y1: y1,

x2: x2,

y2: y2,

x3: x2 - k1 *(x2 - x1),

y3: y2 - k2 *(y2 - y1)

};

var edgePath = paper.path(Raphael.fullfill(

"M{x1},{y1}Q{x3},{y3},{x2},{y2}",pathPoints)); edgePath.attr({

'stroke': '#999',

'stroke-width': 2

});

edgePath.toBack();

return paper.set().push(edgePath);

}

(2)“三段连线”的绘制

除了根节点与第一层的连线,其他连线都采用“三段连线”。如图3所示,“三段连线”由path1、path2、path3组成,这样的连线可以使得两层以外的节点看起来更紧凑,更美观。

图3 

3 添加和删除节点时节点位置渲染

3.1添加和删除节点时的渲染

添加和删除节点功能的实现的关键点在于如何计算出新增的节点及剩余节点的新的坐标位置。在该系统的实现中,某一节点的位置仅受到父节点的当前位置、兄弟节点的数目和子节点的数目的影响。当添加一个新节点的时候,需要计算出新节点的位置和新节点的兄弟节点的新位置。当删除一个节点的时候,需要计算剩余兄弟节点的新位置。

3.2渲染子节点的垂直位置

当添加或删除一个节点时,为了使该节点所在层次的所有子节点相对于父节点垂直居中,会重新渲染子节点的位置,子节点的位置具体计算方式如下。

图4 

如图4所示,设父节点的中心点F坐标为(hfx, hfy),父节点与子节点的横向距离interval,父节点的宽为parentWidth。作水平线段FC,C点的横坐标即为子节点的横坐标childX。

每个子节点上下之间都有补白padding。设area-Height的值为所有子节点的高度和补白的高度,迭代所有子节点,即可求得areaHeight。然后在线段FC的C点上作一条高度为areaHeight的垂直平分线AB。

通过迭代子节点的高度可以求出每个子节点的垂直位置childY。设一个节点的高度加上上下补白称为节点区域,startY为每一轮迭代中当前子节点的节点区域垂直坐标。在每一轮迭代中,当前子节点的childY等于当前子节点的startY加上一个padding。根据求得每个子节点的左上角坐标(childX, childY)即可渲染对应子节点。实现代码如下:

function renderChild(father, padding, interval){

var hfx, //父节点的中心x轴坐标

hfy, //父节点的中心y轴坐标

chilldren, //子节点数组

childX, //子节点的x轴坐标

startY, //子节点区域的起始坐标

areaHeight = 0; //所有子节点的高度加补白

hfx = father.x + interval;

hfy = father.y + interval;

children.forEach(function(child){

"use strict";

var childY = startY + padding;

//已经求得当前子节点坐标(childX, childY),在这里作渲染操作

renderNode(child, childX, childY);

startY += getNodeHeight(child)+ padding * 2;

});

}

function getNodeHeight(){} //获取节点高度

function renderNode(node, x, y){} //渲染节点,如果节点不存在则新建节点,否则移动节

3.3调整父节点的同级节点的垂直位置

添加或删除节点会影响父节点的同级节点的垂直位置。如图5所示,当添加一个节点时,该节点的父节点的同级节点需要被“撑开”:设该节点的1/2的区域高度为moveY,父节点的同级节点中,比父节点高的向上偏移一个moveY,比其低的向下偏移一个moveY,偏移操作可以调用svg的transform方法垂直平移节点。父节点的父节点的同级节点也做相同的处理,一直递归到根节点为止。当删除一个节点时,节点的父节点的同级节点会被“压低”,“压低”操作和上述操作相似。注意,当增加第一个子节点和删除最后一个节点时,不会进行“撑开”或者“压低”操作。

图5 

4 拖动节点

4.1根节点的拖动

当拖动根节点时,整个思维导图也会移动。实现这种效果的其中一个方案就是移动整个思维导图内的节点和连线。单个svg图形的移动操作需要绑定mouseup、mousedown和mousemove三个事件,在移动时会不断地触发mousemove事件重新渲染svg图形的位置,因此,移动svg图形操作的性能并不高。移动单个svg对象不会有很大的性能瓶颈,但是如果一次性移动多个svg对象,在移动时会有明显的“卡顿”感觉,严重影响用户体验。

实现整个思维导图移动的另一个方案就是改变svg区域的视野。在svg中,视野是观察世界的一个矩形区域,而世界是无穷大的。当改变视野的坐标时,视野中观察到的物体的位置看上去好像是改变了,实际上是物体在世界上的位置没有改变,而是观察世界的矩形区域移动到了另外一个位置。如图6和图7所示,一开始物体在视野内的中央,当将视野向右下移动时,物体的位置变成了在视野内的左上角,效果上和将整个视图左上角拖动是一样的。

svg标签提供了viewbox属性来控制视野的范围,而Raphale.js提供一个setViewBox的方法更便捷地控制视野。拖动根结点时改变svg视野的实现与上一个方案相比,既能达到拖动整个思维导图的效果,性能上又能大幅度地提高。

4.2非根节点的拖动

本文实现的系统将拖动非根节点用于改变父子关系:节点拖动结束后,如果该节点与另一个节点重叠,则使重叠节点变为该节点的父节点,否则,节点回到原来的位置。

图6 

图7 

一个节点是Raphael.js的set对象,set对象可以监听鼠标事件。监听每个非根节点的mouseup、mousemove和movedown事件,分别对应按下鼠标、鼠标移动、放下鼠标三个状态。在按下鼠标状态下,会以当前节点为原型克隆一个节点用于占位。在拖动鼠标状态下,通过改变节点的坐标实现节点位置改变。在放下鼠标状态下,会判断当前拖动的节点是否与其他节点重叠,通过Raphael.js的isBBoxIntersect函数即可判断两个节点是否重叠,如果重叠,则改变节点的父子关系,实现流程如图8:

图8 

5 结语

本文主要阐述了如何使用SVG实现思维导图,SVG具有对象化的特点,使得很容易地将思维导图的节点与SVG对象对应起来,且基于SVG对象所实现的事件机制,令绘图时的交互实现变得简单和高效。本文实现的系统将数据层和渲染层分离开来,让数据和视图解耦开来。本文介绍了如何绘制思维导图的两个基本元素——节点和边;添加或删除节点时各个节点位置都会有所改变,如何计算和渲染节点的位置是实现添删节点功能的关键;本文还阐述了实现拖动节点改变父子关系的关键点。

参考文献:

[1]高科.基于HTML5的数据可视化实现方法研究[J].科技传播,2013,01:186-187.

[2]朱文.基于HTML5Canvas技术的在线图像处理方法的研究[D].华南理工大学,2013.

[3]高峰,谈俊忠. Java Script在基于SVG的网络地图中的应用[J].江西师范大学学报(自然科学版),2004,03:262-265.

[4]徐曼.基于HTML5的统计图表系统的研究与设计[D].武汉科技大学,2012.

[5]管英祥,任渊博,向为锋.基于HTML Canvas的电磁态势绘制方法[J].电脑知识与技术,2015,14:68-70.

[6]王乔俊,何原荣,李佳楠.基于SVG的旅游地图符号库的设计与实现[J].湖北科技学院学报,2015,08:21-23.

[7]宋善德,熊展志,李卫国,唐咸峰.基于SVG的矢量图形编辑器的设计与实现[J].计算机工程与科学,2003,02:91-94.

任志豪(1993-),男,广东花都人,本科生,现就读于华南师范大学,研究方向为软件工程

赖源劲(1994-),男,广东番禺人,本科生,现就读于华南师范大学,研究方向为数据库

Implementation of Mind Map System Based on SVG

REN Zhi-hao,LAI Yuan-jin
(Software College, South China Normal University, Guangzhou 501631)

Abstract:The graphics technology which based on SVG, has characteristics of objectivity and convenient interaction, so it is suitable for the realization of the online mind map drawing. The basics functions of the mind map system based on the SVG include rendering node and edge, adding and removing nodes, changing nodes' position, dragging nodes. The key technical points of theses functions include calculating and rendering the nodes position.

Keywords:Mind Map; SVG; Realization

收稿日期:2015-12-31修稿日期:2016-03-02

作者简介:book=75,ebook=76

文章编号:1007-1423(2016)09-0070-06

DOI:10.3969/j.issn.1007-1423.2016.09.018

基金项目:国家级大学生创新训练计划(No.2014115)

猜你喜欢

实现思维导图
信息系统安全评价系统设计及实现
高校声像档案数字化管理的实现路径
办公室人员尚需制定个人发展规划
苏州信息学院教务管理系统的设计与实现
思维可视化技术应用于课堂教学
论思维导图在初中英语阅读教学的应用
思维导图在初中物理概念课教学中的应用
思维导图软件辅助初中数学教学的应用研究
巧用思维导图提高初中英语课堂教学有效性的探究
浅析铁路通信传输的构成及实现方法