百度脑图 基于 kityminder-editor,加入了第三方格式导入导出 (FreeMind, XMind, MindManager) 、文件储存、用户认证、文件分享、历史版本等业务逻辑。属于一个比较完整的产品。

kityminder-editor 基于 kityminder-core 搭建,依赖于 AngularJS,包含 UI 和热盒 hotbox 等方便用户输入的功能,简单来说,就是一款编辑器。

kityminder-core 是 kityminder 的核心部分,基于百度 FEX 开发的矢量图形库 kity。包含了脑图数据的可视化展现,简单编辑功能等所有底层支持。

kity 是一个基于 SVG 的矢量图形库,帮助你快速在页面上创建和使用矢量元素。类似于 D3 这种库,偏向于底层。

Kity 是一款矢量图形库,它的最底层特性是面向对象支持。众所周知JavaScript是弱类型语言,要想用它完成工程化的产出,就需要面向对象的支持。因此我们基于ES5构建了一套面向对象语言的实现,包括定义类、扩展类、继承以及混合。下图就是Kity类的继承关系。

类图参考价值不大,比较老

Kity的核心如下:

项目文件解析

kity 的整个文件结构如图所示,包括:

  • kity.js 用于导出所有模块
  • expose-kity.js kity 变量提升为 windows 对象
  • graphic 图形库,实现各种点、线、面
  • filter 滤镜库,实现一些高斯模糊等效果
  • core 核心库
  • animate 动画相关
  • kity.js 主要使用 seajs 的模块系统将整个项目需要使用到的方法或对象暴露出来,然后用 kity 来作为一个顶级对象,实现了类似于 namespace 的功能。

    expose-kity.js 的作用如上所示,对于普通引入方式,引入了生产环境的 kity.js 文件就可以直接使用 kity 变量了;对于用npm方式安装的直接 import 'kity' 即可。

    core 里面包含 browser.js class.js utils.js

    browser.js 主要提供了一些浏览器判断的方法,通过输出 kity.Browser 我们可以看到当前浏览器的一些属性。

    utils.js 见名知意,提供了一些常用的方法,如下:

    class.js 这个比较重要,提供了 kity 的 oop 支持,class的用法可以参考 demo4
    另外 pipe 函数的使用可以参考 demo1 demo2

    graphic 图形系统

    graphic 文件夹里面,有很多图形相关的文件。根据 部分文档 提供的信息,首先是画布与图形。

    画布(参考 demo6

    在 Kity 中,画布用 Paper 表示,它是最基本的绘图容器。我们进入到 paper.js 中,看一下它的实现:

    constructor 函数中,使用 createSVGNode 函数创建了 svg 的节点,然后使用 renderTo 方法渲染到指定的 HTML DOM 元素上

    初始化 Paper

    用下面的代码可以创建一个 Paper,并且会在指定的容器中渲染:

    const paper = new kity.Paper('container')
    // 或者直接传 Dom 对象:
    const paper = new Paper(document.body);
    

    执行可以看到 container 下已经有一个 svg 标签了

    设置宽高和视野(参考demo6demo12

    在 paper 中,有很多方法,比如设置宽高:

    paper.setWidth(800)
        .setHeight(600);
    

    设置视野:

  • setViewPort
  • 设置视窗。paper自己的方法,参数说明:(x坐标,y坐标,transform的放大缩小系数)
    x=0,y=0时,视窗在paper的中心点处,矩形看起来只有实际大小的一半,如图:

    paper.setViewPort(0, 0, 0.5);
    

    通过getViewPort方法可以获取如下信息:

    center值都为0;
    offset值为400和300,因为paper的宽高为800和600,所以offset值对应了视窗偏移点的坐标
    zoom值为0.5,缩小一半

    所以前面两个参数有点像在确定Paper原点的坐标。

  • setViewBox
  • 属于ViewBox类的方法,跟setViewPort方法类似但有很大差别

    paper.setViewBox(-800, -600, 1600, 1200);
    

    视野定义了 Paper 下图形的坐标系统。由四个值来定义:( x, y, width, height )。其中 x 和 y 确定 Paper 左上角的点在坐标系里的坐标,而 width 和 height 就表示 Paper 显示的坐标范围。

    ViewBox的理解感觉跟ViewPort是反着来的。在ViewPort中设置(0,0,0.5),我们可以理解为将图形移动到了画布的中心点,并缩小了一半。

    但用ViewBox完成这种效果,我们可以理解为图不动,画布在动。需要缩小图形为一半,我们就要将画布
    放大一倍;同样的,需要设置图形的顶点为画布中心点,我们需要将画布往负方向移动相应的距离。

    但实际上画布和图形都没有任何变化

    paper.js 中,我们并没有看到setViewBox 方法,这里是用了 mixins 来实现的。

     mixins: [ShapeContainer, EventHandler, Styled, ViewBox],
    

    Paper类混合了上面的这些类,比如上面提到的setViewBox方法是在viewbox.js中实现的。

    通过它们我们可以完成放大、缩小,图形在画布中位置的摆放等操作。

    图形管理(参考demo1

    因为 Paper 类混合了ShapeContainer类,所以它还是一个图形容器,可以向其添加和移除图形

    我们可以看到ShapeContainer类中实现了很多关于图形操作的方法,通过调用这些方法可以在paper上任意添加、移除图形.

    继续看 paper.js,里面还混入了EventHandler类,来实现paper的一些事件。

    paper.on('click', function (e) {
        const mouse = e.originEvent;
        //距离窗口的位置偏移
        console.log(mouse.clientX);
        console.log(mouse.clientY);
        //距离画布的位置偏移
        console.log(mouse.offsetX);
        console.log(mouse.offsetY);
    

    paper.js里面混入的Styled类是实现一些样式操作的。这个类比较简单,就实现了如下四个方法:

  • addClass
  • removeClass
  • hasClass
  • setStyle
  • paper.js里面,Paper类自己实现了一个addResource方法,可以向画布上添加一些需要使用的资源。比如:LinearGradientBrushRadialGradientBrushPatternBrush 等。

    往 Paper 添加和移除资源使用以下接口:

    //资源操作
    const brush = new kity.LinearGradient().pipe(function () {
        this.addStop(0, new kity.Color('red'));
        this.addStop(1, new kity.Color('blue'));
    paper.addResource(brush);
    rect.fill(brush);
    // 资源被移除后,矩形的填充会失效
    paper.removeResource(brush);
    

    Kity Graphic 内置了 PathRectEllipseCirclePolylinePolygonCurveBezier 等基本几何图形。

    Shape类是所有图形的基类,里面提供了一些获取、设置属性,图形变换等方法。同样,它也混入了一些类来完成一些其他的操作:

    mixins: [EventHandler, Styled, Data]
    

    在 svg 中,<path>元素用于定义一个路径。通过路径可以绘制出任何图形。

    所以Path类 是 Kity 中最强大的工具。其他的几何图形都是继承 Path 而来。Path 能识别 SVG 中定义的 Path Data 字符串格式。可以通过这样一个字符串构造 Path:

    //绘制一个封闭的红色三角形
    const triangle = new kity.Path('M150 0 L75 200 L225 200 Z').fill('red');
    
    Group(参考demo8

    使用 group 可以建立图形分组,将一些图形组合。

    然后可以直接对图形组进行图形操作。因为组本身也是一个图形(由其子元素组合),所以也可以被添加到组里。

    填充图形(参考demo9

    默认添加到 Paper 上的图形是不具有视觉呈现的,需要对其进行填充或描边。

    纯色填充,使用 Color

    要用一个颜色进行填充,可以:

    rect.fill( new Color('red') );
    // 或者直接使用字符串:
    rect.fill( 'red' );
    
    rect.fill(new kity.LinearGradient().pipe(function () {
    //添加关键颜色到具体位置,其中 0 表示渐变开始的位置,1 表示渐变结束的位置
    //参数说明:(位置,颜色,透明度)
    this.addStop(0, 'red', 1);
    this.addStop(0.5, 'yellow', 0.5);
    this.addStop(1, 'blue', 1);
    //设置填充的方向和范围,参数为坐标系坐标
    this.setStartPosition(0, 0);
    this.setEndPosition(0, 1);
    paper.addResource(this);
    
  • 使用 RadialGradientBrush 进行径向渐变填充
  • rect.fill(new kity.RadialGradient().pipe(function () {
    this.setCenter(0.5, 0.5);
    this.setRadius(0.5);
    this.setFocal(0.8, 0.2);
    this.addStop(0, 'red');
    this.addStop(1, 'blue');
    paper.addResource(this);
    
  • 使用 PatternBrush 进行图形填充,PatternBrush 是最灵活的画笔,它可以用图形填充图形。
  • rect.fill(new kity.Pattern().pipe(function () {
    const colors = ['red', 'blue', 'yelow', 'green'];
    this.setWidth(40);
    this.setHeight(40);
    this.addItem(new kity.Circle(5, 10, 10).fill(colors.shift()));
    this.addItem(new kity.Circle(5, 30, 10).fill(colors.shift()));
    this.addItem(new kity.Circle(5, 10, 30).fill(colors.shift()));
    this.addItem(new kity.Circle(5, 30, 30).fill(colors.shift()));
    paper.addResource(this);
    

    我们去看PatternBrush的源码,可以发现它混入了ShapeContainer类,所以它也是一个图形容器,我们可以向它里面添加一些图形。

    使用Pen 来绘制图形轮廓

    实现了 svg 的 stroke 属性

    rect.stroke(new kity.Pen().pipe(function () {
    this.setWidth(1); //设置画笔的粗细
    this.setDashArray([5, 5]); //设置画笔的段长和间隙长,不断循环。默认为 null,绘制实线
    this.setLineCap('butt');   //设置端点的样式,取值有:butt、round、suqare
    this.setLineJoin('round');  //设置转折点的样式,取值有:miter、round、bevel
    this.setColor('green');
    
    文字(参考demo10

    在 kity 中,与文字相关的有text.jstextcontent.jstextspan.js这三个文件。

    我们可以在kity.js中看到默认暴露了Text类和TextSpan类,TextContent类作为这两个类的基类,实现了文字操作的一些方法。然后TextContent类的基类又是Shape,所以他们都可以当做图形来处理。

    先看TextSpan类,它的实现很简单,就是在 svg 中创建了一个 tspan 标签,然后设置内容。

    既然它们都是图形类的子类,我们可以将它添加到画布上

    const textSpan = new kity.TextSpan('hello world').fill('red');
    paper.addShape(textSpan);
    

    画布上没有出现任何东西,但是在 HTML 中,我们可以看到已经渲染出了 tspan标签

    查看 MDN 关于 tspan 的解释,我们得知它是内含在 text 元素中,所以直接使用是没有任何效果的。

    所以我们来看一下Text类,它的构造函数跟 TextSpan 差不多,我们可以将它添加到 Paper 上看一下效果

    const text = new kity.Text('hello world').pipe(function () {
        this.setX(100);
        this.setY(100);
        this.setSize(36);
        this.fill('red');
    paper.addShape(text);
    

    画布上显示了 hello world,html元素里出现了text标签

    那么,TextSpanText的关系是怎样的呢?

    前面我们知道 tspan 标签是在 text 标签里面。我们去看Text类的实现,发现它混入了ShapeContainer类,所以它可以作为一个图形容器,添加TextSpan进去。它提供了一个addSpan方法,就是来完成这件事的。

    const text = new kity.Text().pipe(function () {
        this.addSpan(new kity.TextSpan('hello').fill('red'));
        this.addSpan(new kity.TextSpan('world').fill('blue'));
        this.setX(100);
        this.setY(100);
        this.setSize(36);
    

    另外,使用setPath方法可以使用文本路径来排列文字

    内置图形介绍(参考demo7

    前面我们接触到了 Rect 矩形和使用 Path 来绘制自定义图形,在 kity 中还内置了很多丰富的图形。

    Ellipse

    Ellipse 用于绘制一个椭圆,参数解释:(x轴半径,y轴半径,x轴坐标,y轴坐标)
    如果将x轴半径和y轴半径设置一样,就是一个圆形了

    const ellipse = new kity.Ellipse(100, 60, 400, 150).fill('green');
    paper.addShape(ellipse);
    

    Circle

    Circle 用于绘制一个圆形,参数解释:(半径,x轴坐标,y轴坐标)

    HyperLink 用于生成一个链接,也就是 a 标签。但 svg 中的 a 标签与 HTML 中还是有很大区别的,如果直接像下面这种写法,是无效的。

    <a href="http://www.baidu.com">百度</a>

    所以我们可以看到HyperLink类是混入了ShapeContainer类的,也就是说它是一个图形容器。我们可以在它里面添加一些图形,让该图形变成一个链接,比如Text

    const hyperlink = new kity.HyperLink('https://www.baidu.com').pipe(function () {
        this.addShape(new kity.Text('百度').pipe(function () {
            this.setX(300);
            this.setY(300);
            this.setSize(36);
            this.fill('blue');
        this.setTarget('_blank');
    paper.addShape(hyperlink);
    

    Image

    图片的使用十分简单,初始化参数为:(url,宽,高,x轴坐标,y轴坐标)

    //Image 图片
    const image = new kity.Image('http://img2.ph.126.net/vZ1r-IVk_j-cAi-noOuCEw==/760826862149437991.png', 200, 200, 100, 400);
    paper.addShape(image);
    

    其他重要属性和方法介绍(参考demo11

    getBoundaryBox

    Shape基类的方法,获取图形的边界盒子,返回一个 Box对象

    setBox

    Rect类的方法,设置一个盒子

    expand

    Box类的方法,扩展(或收缩)当前的盒子,返回新的盒子

    Box属性介绍

    我们添加一个Text,设置坐标为(100,100),并使用它的边界盒子属性绘制一个矩形。
    控制台打印一下Box的属性:

    我们用鼠标去选择文字,可以看到选中的区域刚好就是该矩形的区域。

    下面主要介绍这些属性值:

    首先,我们知道Text的坐标为(100,100),但我们可以看到x的值为100,y值确是83。

    那这个83是怎么来的呢,我们用尺子量一下就知道了。

    我们先纵向量100的距离,可以发现(100,100)刚好是文字底部的点坐标。

    所以,我们不难发现(100,83)为该Text盒子的顶点坐标。

    宽高就不用解释了,即盒子的宽高

    四个点坐标:topbottomleftright

    top:83left:100即盒子的顶点坐标,(100,83)

    right= left+width
    bottom=top+height

    cxcy为矩形区域的中心点坐标,所以:

    cx=left+width/2
    cy=top+height/2