首页 > 其他分享 >jointjs 入门

jointjs 入门

时间:2023-05-16 10:13:55浏览次数:61  
标签:入门 连接点 graph 元素 label jointjs elementView model

链接http://www.bjhee.com/jointjs.html

用JointJS做一个简单的功能控制图

2019年1月30日 

前端 文章目录

继上一篇介绍了GoJS之后,继续研究JS的绘图工具,毕竟GoJS有些小贵。这次选择了JointJS,完全开源,它还有一个商业版本叫Raddit,功能更强大。不过就我的需求场景,开源的Joint就足够了。接下来,我们看看它是怎么使用的。

JointJS是基于Backbone开发的,所以使用Joint之前,要先引入Backbone的相关依赖,所以我们的HTML文件是这样的:

<!DOCTYPE html>
<html>
<head>
  <meta http-equiv="content-type" content="text/html; charset=UTF-8">
  <title>JointJS Sample</title>
  <meta name="viewport" content="width=device-width, initial-scale=1">

  <link rel="stylesheet" type="text/css" href="https://cdnjs.cloudflare.com/ajax/libs/jointjs/2.2.1/joint.min.css">
  <!-- 下面的顺序不能变 -->
  <script type="text/javascript" src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.js"></script>
  <script type="text/javascript" src="https://cdnjs.cloudflare.com/ajax/libs/lodash.js/4.17.11/lodash.js"></script>
  <script type="text/javascript" src="https://cdnjs.cloudflare.com/ajax/libs/backbone.js/1.3.3/backbone.js"></script>
  <script type="text/javascript" src="https://cdnjs.cloudflare.com/ajax/libs/jointjs/2.2.1/joint.min.js"></script>

  <!-- 这是自己的JS文件 -->
  <script type="text/javascript" src="js/main.js"></script>
</head>
<body>
  <!-- 这里是画布 -->
  <div id="myGraph"></div>
</body>
</html>

这里我都用CDN,也可以将相应的库载到本地,但需要注意的是JS的加载顺序不能改。接下来,我们写一个官方的HelloWorld:

window.onload=function() {
    var graph = new joint.dia.Graph;    // 创建画板,所有图上的元素都在画板里
    var paper = new joint.dia.Paper({   // 创建画板上的画布,画布是用来渲染画板
        el: document.getElementById('myGraph'),  // 指向HTML里ID为"myGraph"的元素
        model: graph,    // 指定画板
        width: 600,      // 画布宽600像素
        height: 100,     // 画布高100像素
        gridSize: 1,     // 画布上元素拖动时步进的像素,默认1,设的高方便对齐
        background: {    // 画布背景色
            color: 'rgba(0, 0, 0, 0.1)'
        },
    });

    // 创建一个矩形
    var rect = new joint.shapes.standard.Rectangle();
    rect.position(100, 30);    // 矩形左上角的位置,x:100,y:30,单位像素
    rect.resize(100, 40);      // 矩形大小,宽100,高40,单位像素
    rect.attr({
        body: {
            fill: 'blue'       // 填充色
        },
        label: {
            text: 'Hello',     // 矩形上显示的文字
            fill: 'white'      // 文字颜色
        }
    });

    rect.addTo(graph);          // 将上面定义的矩形加入到画板中
    var rect2 = rect.clone();   // 复制一个相同的矩形
    rect2.translate(300, 0);    // 将矩形在水平方向上向右移动300像素
    rect2.attr('label/text', 'World!');  // 设置矩形2上的文字
    rect2.addTo(graph);         // 将矩形2加入到画板中

    // 创建一条连线
    var link = new joint.shapes.standard.Link();
    link.source(rect);   // 连线头为矩形1
    link.target(rect2);  // 连线尾为矩形2
    link.addTo(graph);   // 将上面定义的连线加入到画板中
}

从上例里,我们可以看出,使用JointJS开发主要的步骤就是创建画板、创建画布、创建图形,然后将图形置于画板中。上面的例子执行后会得到下面的图像。

HelloWorld!

JointJS中的图形主要有两类,一是”元素”(Element),由构造函数创建。上例中的矩形就是JointJS库提供的标准元素,其构造函数为joint.shapes.standard.Rectangle。元素创建后,可以设置各种参数,比如位置,大小,风格等。JointJS提供了丰富的内置元素如矩形,圆形,椭圆等,可以参考API文档。同时,我们可以通过扩展joint.dia.Element类来自定义元素。

另一类图形是”连接”(Link),用来将两个”元素”连起来,一般显示为一条连线。上例中的连线是通过JointJS库提供的标准构造函数joint.shapes.standard.Link来创建的,创建后设置其”头”(Source)和”尾”(Target),即可将两个”元素”连起来。我们也可以设置”连接”的各种参数,如颜色,箭头,标签文字等。

HelloWorld之后我们来做一个简单的自动化功能控制图吧,HTML部分不变,我们来写JS部分。

  • 第一步定义画板和画布
var graph = new joint.dia.Graph;    // 创建画板,所有图上的元素都在画板里
var paper = new joint.dia.Paper({   // 创建画板上的画布,画布是用来渲染画板
    el: $('#myGraph'),  // 指向HTML里ID为"myGraph"的元素
    model: graph,       // 指定画板
    width: 600,         // 画布宽600像素
    height: 300,        // 画布高300像素
    gridSize: 5,        // 画布上元素拖动时步进的为5像素,默认1
    drawGrid: true,     // 显示步进点,方便对齐
    background: {       // 画布背景色
        color: 'rgba(0, 0, 0, 0.1)'
    },

    // 连接线风格
    defaultLink: new joint.shapes.logic.Wire({
        connector: { name: 'jumpover' },  // 当两根连线交叉时,其中一根跳过
    }),
    linkPinning: false,   // 连线必须连到某个元素,即不允许连到空白处
    snapLinks: {
        radius: 25          // 距离元素连接点25像素时自动连接上
    },

    // 验证连线是否允许,
    validateConnection: function(viewSource, magnetSource, viewTarget, magnetTarget, end, viewLink) {
        if (end === 'target') {
            // 连线目标必须时一个"in"类型连接点
            if (!magnetTarget || !magnetTarget.getAttribute('port-group') || magnetTarget.getAttribute('port-group').indexOf('in') < 0) {
                return false;
            }

            // 检查连接点是否已经有连线接入,不允许多重接入
            var portUsed = this.model.getLinks().some(function(link) {
                return (link.id !== viewLink.model.id &&
                  link.get('target').id === viewTarget.model.id &&
                  link.get('target').port === magnetTarget.getAttribute('port'));
            });
            return !portUsed;

        } else { // end === 'source'
            // 连线起始点必须时一个"out"类型连接点
            return magnetSource && magnetSource.getAttribute('port-group') && magnetSource.getAttribute('port-group').indexOf('out') >= 0;
        }
    },
});

上面的属性有些多,但大部分都好理解,主要是验证函数validateConnection(),其返回truefalse,用来决定连线是否被允许。传入的参数viewSourceviewTarget分别是画线过程中鼠标按钮释放时,当前连线头和尾的元素;而参数magnetSourcemagnetTarget分别是当前连线头和尾的”连接点”(port)。”连接点”的概念下面会讲到。

  • 第二步定义基本图例元素
    这里,我们创建了与、或、非,三个基本元素,因为是图例元素,所以不允许连入连出。我们使用标准库提供的”joint.shapes.devs.Model”元素,因为它很方便设置”连接点”(port)。
// 创建基础元件模板
var gateTemplate = new joint.shapes.devs.Model({
  position: {  // 默认位置
    x: 0,
    y: 0
  },
  size: {   // 默认大小
    width: 50,
    height: 60
  },
  // "连接点"(port)的风格
  portMarkup: '<rect class="joint-port-body" width="10" height="3" style="fill:black" />',
  // "连接点"(port)标签文字的显示风格
  portLabelMarkup: '<text class="port-label joint-port-label" font-size="10" y="0" fill="#000" /> ',
  ports: {  // 定义连接点
    groups: {
      'in': {  // "入"连接点的属性和风格
        attrs: {
          '.port-body': {  // 这是JointJS类库预定义的连接点属性类
            magnet: 'passive',   // 该连接点只入不出
          },
          '.joint-port-body': {  // 这是JointJS类库预定义的连接点风格类
            x:-10   // "入"连接点左移10个像素,这样可以显示在元素外部
          }
        },
        label: {
          position: {
            args: {x:18},  // 标签右移,这样可以显示在元素内部
          },
        },
      },
      'out': {
        label: {  // "出"连接点的属性和风格
          position: {
            args: {x:-23},  // 标签左移,这样可以显示在元素内部
          },
        },
      }
    }
  },
  attrs: {
    '.label': {
      'type': 'primary',  // 自定义一个图例属性,后面事件操作时判断用
      fontSize: 12,       // 标签字体
      'ref-x': .5,        // 标签相对于元素的水平位置
      'ref-y': .05        // 标签相对于元素的垂直位置
    },
  }
});

// 生成"与"元素,两个"入"连接点,一个"出"连接点,显示"And"字样标签
function genAndPr() {
  return gateTemplate.clone().set('inPorts', ['IN1', 'IN2']).set('outPorts', ['OUT']).attr('.label/text', 'And');
}

// 生成"或"元素,两个"入"连接点,一个"出"连接点,显示"Or"字样标签
function genOrPr() {
  return gateTemplate.clone().set('inPorts', ['IN1', 'IN2']).set('outPorts', ['OUT']).attr('.label/text', 'Or');
}

// 生成"非"元素,一个"入"连接点,一个"出"连接点,显示"Not"字样标签
function genNotPr() {
  return gateTemplate.clone().set('inPorts', ['IN ']).set('outPorts', ['OUT']).attr('.label/text', 'Not');;
}

// 图例加入到画板左侧
graph.addCell(genAndPr().translate(20, 20));
graph.addCell(genOrPr().translate(20, 120));
graph.addCell(genNotPr().translate(20, 220));

// 添加一个分割栏将图例和绘图区域分开
var separator = new joint.shapes.standard.Polyline();
separator.resize(5, 400);
separator.position(95, 0);
separator.addTo(graph);

上面代码写得挺粗燥的,高手见笑。其实可以更好的抽象,只是懒得弄了。上面关键的概念就是”连接点”(port),JointJS库中joint.shapes.devs.Model元素默认支持”入”(in)和”出”(out)两种连接点,分别显示在元素图形的左边和右边,多个连接点会自动排列。通过前一段代码中paperlinkPinning属性设置,可以要求”连线”只允许接在”连接点”上。上面我们定义了”连接点”风格为一个长10px3px的水平线。上例中,因为我重写了”连接点”的标记portMarkup,去掉了允许连线的属性,所以这些连接点目前都无法被连线。

  • 第三步定义绘图元素
function genAnd() {
  return genAndPr().set('portMarkup', '<rect class="port-body joint-port-body" width="10" height="2" style="fill:black" />').attr('.label/type', 'instance');
}
function genOr() {
  return genOrPr().set('portMarkup', '<rect class="port-body joint-port-body" width="10" height="2" style="fill:black" />').attr('.label/type', 'instance');
}
function genNot() {
  return genNotPr().set('portMarkup', '<rect class="port-body joint-port-body" width="10" height="2" style="fill:black" />').attr('.label/type', 'instance');
}

上面的代码还是很好理解的,我将图例元素中的portMarkup改了,其实就是增加了css类port-body,这是JointJS库中预定义的。该类中设置了”元素”的magnet属性,设为true时可入可出;passive时只入不出;false时不能连线。这样,我们的绘图元素就可以接上连线了。另外,这里将自定义属性.label/type改为instance主要是后面的事件判断用。

  • 最后,我们定义鼠标事件,来支持将图例元素拖入绘图区域
paper.on({  // JointJS事件都定义在画布上
  // 当鼠标左键按下时
  'element:pointerdown': function(elementView, evt) {
      // 当图例元素被拖走时,在原来的位置创建一个新的图例元素
      if (elementView.model.attr('.label/type') == 'primary') {
        var type = elementView.model.attr('.label/text');
        if (type == 'And') {
          graph.addCell(genAndPr().translate(20, 20));
        } else if (type == 'Or') {
          graph.addCell(genOrPr().translate(20, 120));
        } else if (type == 'Not') {
          graph.addCell(genNotPr().translate(20, 220));
        }
        // 被拖动的元素挪到图层的最上层,这样可以遮盖现有元素
        elementView.model.toFront();
      } else if (elementView.model.attr('.label/type') == 'instance') {
        // 对于绘图元素,记住其被拖动时的起始点,当拖动位置超出绘图区域时,可以回到原点
        evt.data = elementView.model.position();
      }
  },

  // 当鼠标左键抬起时
  'element:pointerup': function(elementView, evt, x, y) {
    if (elementView.model.attr('.label/type') == 'primary') {
      // 对于图例元素,当其被拖入绘图区域时,则在该位置创建一个新的绘图元素,并删除被拖动的图例元素
      if (elementView.model.position().x > 105) {
        var type = elementView.model.attr('.label/text');
        if (type == 'And') {
          graph.addCell(genAnd().translate(elementView.model.position().x, elementView.model.position().y));
        } else if (type == 'Or') {
          graph.addCell(genOr().translate(elementView.model.position().x, elementView.model.position().y));
        } else if (type == 'Not') {
          graph.addCell(genNot().translate(elementView.model.position().x, elementView.model.position().y));
        }
      }
      // 删除当前被拖动的元素
      graph.removeCells(elementView.model);
    } else {
      // 对于绘图元素,当其被拖出绘图区域时,则将其移回原点
      if (elementView.model.position().x < 110) {
        elementView.model.position(evt.data.x, evt.data.y);
      }
    }
  },

  // 当鼠标左键双击时
  'element:pointerdblclick': function(elementView, evt) {
    // 双击绘图元素则删除该元素,相应的连线也会被自动删除
    if (elementView.model.attr('.label/type') == 'instance') {
      elementView.model.remove();
    }
  },
})

JointJS的事件都定义在画布paper上,可以参考这里的说明。事件的种类很多,可以在”元素”、”连线”或”空白处”上监听,可以是各种鼠标事件,这里不赘述了。大部分事件都接受4个参数:

  • “cellView”(或叫”elementView”) – 事件监听的主体,可以通过elementView.model来获得元素对象,并对其做各种设置
  • “evt” – 保存信息用于在事件间传数据
  • “x”和”y” – 记录事件发生时鼠标的位置

上例中的事件函数,定义了将图例元素拖入绘图区域,并创建一个新的绘图元素的过程。

保存上面的代码并在浏览器里打开,大家应该可以看到如下内容。JoinJS Sample

JointJS源码托管在Github中。更详细的开发API文档可以在官方API文档中找到。 本文中的示例代码,可以在这里下载

标签:入门,连接点,graph,元素,label,jointjs,elementView,model
From: https://www.cnblogs.com/sweeeper/p/17404027.html

相关文章

  • AI绘画Stable Diffusion整合包V4三分钟超快速入门!
    本次整合包升级内容StableDiffusion整合包V4下载-torch2、xformers0.0.17、cudnn8.8打开无需任何操作即可满速(包括40系显卡-升级其它各种依赖版本-预置了Tagger(图反推关键词)的模型-预置了ControlNet、MultiDiffusion插件-优化了一些其他设置整合包只是打包了运行必须的py......
  • kettle从入门到精通 第十九课 kettle 资源仓库
    1、kettle里面的资源仓库的意思就是存放转换(.ktr)或者job(.kjb)文件的地方。通过spoon客户端右上角可以进行设置资源仓库。 2、kettle的资源仓库有三种方式1)本地文件存储,此种方式最简单,适用于本地单人开发测试。2)数据库db存储,此种方式适合团队协作,可以将文件存储到数据库里面......
  • nodejs 入门基本概念
    nodejs的诞生  Node.js是2009的时候由大神RyanDahl开发的。Ryan的本职工作是用C++写服务器,后来他总结出一个经验,一个高性能服务器应该是满足“事件驱动,非阻塞I/O”模型的。C++开发起来比较麻烦,于是Ryan就想找一种更高级的语言,以便快速开发。  Ryan发现JS语......
  • Docker入门与实战-Docker镜像的使用
    Docker入门与实战二.Docker镜像的使用1.获取镜像​ 命令:docker[image]pullimage-name[:tag]​ 说明:​ name为镜像仓库名称,严格来说,该name应该由注册服务器地址+镜像仓库名称组成,这样做可以避免不同仓库(注册服务器)下的镜像名称冲突的问 题,如果该注册服务器地......
  • Intellij Idea教程_编程入门自学教程_菜鸟教程-免费教程分享
    教程简介IDEA全称IntelliJIDEA,是java编程语言的集成开发环境。IntelliJ在业界被公认为最好的Java开发工具,尤其在智能代码助手、代码自动提示、重构、JavaEE支持、各类版本工具(git、svn等)、JUnit、CVS整合、代码分析、创新的GUI设计等方面的功能可以说是超常的。IDEA是JetBra......
  • Flask快速入门3
    十一,FlaskCookies Cookie以文本文件的形式存储在客户端的计算机上。其目的是记住和跟踪与客户使用相关的数据,以获得更好的访问者体验和网站统计信息。 Request对象包含Cookie的属性。它是所有cookie变量及其对应值的字典对象,客户端已传输。除此之外,cookie还存储其网站的到期时间......
  • Python基础语法入门
    Python基础语法入门1、Python的注释符号1、什么是注释?#学习任何一门代码,先学注释,注释是代码之母注释就是对一段代码的解释说明,它不会参与到代码的运行,只是起到提示作用2、如何注释?2.1、#单行注释#它可以使用快捷键帮助我们把代码写的更加规范快捷键:Ctrl+alt+1(格式......
  • Vue入门浅析
    title:vue入门浅析author:Sun-Winddate:May14,2022写这篇博文的目的在于为初学vue的同学对vue有一些更进一步的了解读这篇博文前,您应该至少安装了vue环境,能在本地运行一个简单的demo本文将浅析vue项目工程的结构,以及用npm运行项目的过程中发生的一些事件注明:该文本应在......
  • 1、基础入门
    一、域名什么是域名?域名在哪里注册?什么是二级域名?以及多级域名?域名发现对于安全的意义?答:(1)域名是由一串用点分割的名字组成的internet上某一台计算机或计算机组的名称,用于在数据传输时标识计算机的电子方位。(2)域名注册(3)什么是二级域名?分为国际二级域名和国家二级......
  • FEniCSx入门——解Poisson方程
    一、Poisson方程 可转换为线性弱形式 其中,u:trialfunction是近似解v:testfunction是Poisson方程两边同乘的函数,用于转换为弱形式。 在Fenics中解PDE时,必须进行下面几个步骤:1、将PDE问题转换成离散变分问题:即寻找u∈V,使得 2、选择V和V_hat空间,换句话说就是......