关于图:算法图

图的基础知识图是一种十分重要的数据结构,用来示意物体与物体之间的关系。图由若干节点及节点之间的边组成。确定图中的节点和边是利用图相干算法解决问题的前提。通常,物体对应图中的节点,如果两个物体存在某种关系,那么它们在图中对应的节点有一条边相连。 图能够分为有向图和无向图。如果给图的每条边规定一个方向,那么这样的图就是有向图,它的边为有向边。有向边就像城市里的单向路,只能沿着一个方向后退。与之相同的是无向图,无向图中的边都没有方向,它的边称为无向边。 通常,图能够用邻接表或邻接矩阵示意。邻接表为图中的每个节点创立一个容器,第i个容器保留所有与第i个节点相邻的节点。 邻接表如果一个图中有n个节点,那么它的邻接矩阵M的大小是n×n。如果节点i和节点j之间有一条边,那么Mi等于1;反之,如果节点i和节点j之间没有边,那么Mi等于0。 邻接矩阵如果一个图是用邻接矩阵示意的,那么判断两个节点之间是否有边相连就非常简单,只须要判断矩阵中对应地位是1还是0即可,工夫复杂度为O(1)。但如果一个图中的节点数目十分大但比拟稠密(大部分节点之间没有边),那么邻接表的空间效率更高。 图还能够分为有权图和无权图。在有权图中,每条边都有一个数值权重,用来示意两个节点的某种关系,如两个节点的间隔等。在无权图中所有的边都没有权重。 图的搜寻在图中搜寻,如找出一条从起始节点到指标节点的门路或遍历所有节点,是与图相干的最重要的算法。依照搜寻程序不同能够将搜索算法分为广度优先搜寻和深度优先搜寻。 广度优先搜寻深度优先搜寻技巧广度优先搜寻和深度优先搜寻在算法面试中都是十分有用的工具,很多时候应用任意一种搜索算法就能解决某些与图相干的面试题。如果面试题要求在无权图中找出两个节点之间的最短距离,那么广度优先搜寻可能是更适合的算法。如果面试题要求找出符合条件的门路,那么深度优先搜寻可能是更适合的算法。 后面介绍了如何实现树的广度优先搜寻和深度优先搜寻。树也能够看成图。实际上,树是一类非凡的图,树中肯定不存在环。但图不一样,图中可能蕴含环。 防止死循环的方法是记录曾经搜寻过的节点,在拜访一个节点之前先判断该节点之前是否曾经拜访过,如果之前拜访过那么这次就略过不再反复拜访。 假如一个图有v个节点、e条边。不论是采纳广度优先搜寻还是深度优先搜寻,每个节点都只会拜访一次,并且会沿着每条边判断与某个节点相邻的节点是否曾经拜访过,因而工夫复杂度是O(v+e)。 最大的岛屿陆地岛屿地图能够用由0、1组成的二维数组示意,程度或竖直方向相连的一组1示意一个岛屿,请计算最大的岛屿的面积(即岛屿中1的数目)。例如,在图15.5中有4个岛屿,其中最大的岛屿的面积为5。 深度优先遍历/** * @param {number[][]} grid * @return {number} */var maxAreaOfIsland = function(grid) { const rows = grid.length; const cols = grid[0].length let visited = new Array(rows).fill().map(() =>new Array(cols).fill(0)); let maxArea = 0; for(let i = 0; i < rows; i++) { for(let j = 0; j < cols; j++) { if(grid[i][j] == 1 && !visited[i][j]) { const area = getArea(grid, visited, i, j) maxArea = Math.max(maxArea, area) } } } return maxArea;};var getArea = function(grid, visited, i, j) { let area = 1; visited[i][j] = true; let dirs = [ [-1, 0], [1, 0], [0, -1], [0, 1] ] for(const dir of dirs) { const r = i + dir[0]; const c = j + dir[1]; if(r>=0 && r < grid.length && c>=0 && c < grid[0].length && grid[r][c] === 1 && !visited[r][c]) { area += getArea(grid, visited, r, c) } } return area;};广度优先遍历拓扑排序(TODO)并查集(TODO)

May 28, 2022 · 1 min · jiezi

关于图:面试中图论都考什么这篇文章告诉你

图图论〔Graph Theory〕是数学的一个分支。它以图为钻研对象。图论中的图是由若干给定的点及连贯两点的线所形成的图形,这种图形通常用来形容某些事物之间的某种特定关系,用点代表事物,用连贯两点的线示意相应两个事物间具备这种关系。 如下就是一种逻辑上的图构造: 图是一种最简单的数据结构,后面讲的数据结构都能够看成是图的特例。那为什么不都用图就好了,还要分那么多种数据结构呢? 这是因为很多时候不须要用到那么简单的性能,图的很多个性都不具备,如果抽象地都称为图那么十分不利于沟通。你想你和他人沟通总不至于说这道题是考查一种非凡的图,这种图。。。。 这未免太啰嗦了,因而给其余图的非凡的图起了非凡的名字,这样就不便沟通了。直到遇到了非常复杂的状况,咱们才会用到 ”真正“的图。 后面章节提到了数据结构就是为了算法服务的,数据结构就是存储数据用的,目标是为了更高效。 那么什么时候须要用图来存储数据,在这种状况图高效在哪里呢?答案很简略,那就是如果你用其余简略的数据结构无奈很好地进行存储,就应该应用图了。 比方咱们须要存储一种双向的敌人关系,并且这种敌人关系是多对多的,那就肯定要用到图,因为其余数据结构无奈模仿。 基本概念无向图 & 有向图〔Undirected Graph & Deriected Graph〕后面提到了二叉树齐全能够实现其余树结构,相似地,有向图也齐全能够实现无向图和混合图,因而有向图的钻研始终是重点考查对象。 本文讲的所有图都是有向图。 后面提到了咱们用连贯两点的线示意相应两个事物间具备这种关系。因而如果两个事物间的关系是有方向的,就是有向图,否则就是无向图。比方:A 意识 B,那么 B 不肯定意识 A。那么关系就是单向的,咱们须要用有向图来示意。因为如果用无向图示意,咱们无奈辨别 A 和 B 的边示意的是 A 意识 B 还是 B 意识 A。 习惯上,咱们画图的时候用带箭头的示意有向图,不带箭头的示意无向图。 有权图 & 无权图〔Weighted Graph & Unweighted Graph〕如果边是有权重的是有权图(或者带权图),否则是无权图(或不带权图)。那么什么是有权重呢?比方汇率就是一种有权重的逻辑图。1 货币 A 兑换 5 货币 B,那么咱们 A 和 B 的边的权重就是 5。而像敌人这种关系,就可以看做一种不带权的图。 入度 & 出度〔Indegree & Outdegree〕有多少边指向节点 A,那么节点 A 的入度就是多少。同样地,有多少边从 A 收回,那么节点 A 的出度就是多少。 依然以下面的图为例,这幅图的所有节点的入度和出度都为 1。 ...

November 9, 2021 · 13 min · jiezi

关于图:带你看论文丨全局信息对于图网络文档解析的影响

摘要:文档了解着重于从非结构化文档中辨认并提取键值对信息,并将其输入为结构化数据。在过往的信息提取中,大多数工作仅仅只关注于提取文本的实体关系,因而并不适用于间接用于文档了解上。本文分享自华为云社区《论文解读系列十三:全局信息对于图网络文档解析的影响》,作者:一笑倾城 。 1 背景介绍文档了解着重于从非结构化文档中辨认并提取键值对信息,并将其输入为结构化数据。在过往的信息提取中,大多数工作仅仅只关注于提取文本的实体关系,并不适用于间接用于文档了解上。 在ICDAR2019的较量上,参赛者被要求从发票收据等文档中提取键值对信息。因而本文提出了一种蕴含了全局信息,并且联合了视觉信息的图网络结构,来实现从非结构化文档中提取要害信息的工作。 2 网络结构本文将文档了解工作转化为图节点分类工作。对于文本的全局和部分信息获取: 应用CLS抓取全局文本序列的分类信息,生成w0,并将其与每个独自文本(w1,w2…,wn)放在同一输出向量中。通过BERT模型,独立地对每个元素进行编码,这样模型领有了部分和全局信息,同时也能对全局和部分文本进行embedding 对于图片的全局和部分信息获取:应用的是类似的办法,不过是基于CNN网络来捕获全局和部分的图像特色 文本和图像特色拼接:将图像特色和文本特色进行特色交融(concat) 网络构建: 给定文档内的一组文本段,构建一个虚构的全局节点作为信息沟通枢纽,这样每两个非相邻节点之间也是two-hop neighbors, 缩小信息沟通损失的同时全局信息也能很间接输入到部分节点上。 聚合街坊使得每一个节点与two-hop neighbors两两之间通过激活函数(leaky-relu)进行模型参数更新,并且应用了K-attention来进步模型的能力(通过多个attention而后合并所有attention的机制) 信息提取: 3 试验后果在阿里巴巴天池比赛的数据及上成果。 相干融化试验:移除视觉特色后,在天池数据以及SROIE上,能显著看出视觉特色能够在提取结构化信息的问题上施展重要的作用。同理,删除全局节点也升高了模型精度,也验证了全局连贯在图构造中的重要性。 点击关注,第一工夫理解华为云陈腐技术~

August 9, 2021 · 1 min · jiezi

盘点一些惊世骇俗的壁纸网站!多年珍藏干货!

前言首先承认一下,我自己就是其中一个壁纸站站长。自建站至今,阅览壁纸网站无数,收藏列表满满一堆优质的壁纸站。今天就来一波商业互吹,看看在9102年哪些壁纸站可以清新脱俗。正文PS:排名不分先后一、极简壁纸官网:https://bz.zzzmh.cn分类:精选、小姐姐、二次元、4K超清、更多分类说明:图片种类丰富,分辨率1~8K,图片来源wallhaven推荐:★ ★ ★ ★ ★二、必应壁纸官网:https://bing.ioliu.cn分类:首页、下载榜说明:出自前端大神之手,以假乱真的设计。界面干净漂亮,图片来源必应每日图片。推荐:★ ★ ★ ★ ★三、千叶网官网:http://qianye88.com分类:4k动漫、4k美女、4k风景、4k游戏 等太多不一一举例说明:设计好看、功能全面,分辨率高。小吐槽:下载要先登录。推荐:★ ★ ★ ★ ☆四、pexels官网:https://www.pexels.com分类:摄影为主说明:据说是全球顶尖高清摄影图片分享网站,免费授权商用推荐:★ ★ ★ ★ ★五、unsplash官网:https://unsplash.com分类:各种都有说明:纯粹、简单、优雅,高质量无版权图片分享圣地推荐:★ ★ ★ ★ ★六、pixabay官网:https://pixabay.com分类:摄影为主说明:提供海量免费高清图片、插画、矢量素材,图片超美,支持中文好评!推荐:★ ★ ★ ★ ★

April 18, 2019 · 1 min · jiezi

mxGraph 入门实例教程

在上一篇文章 《记一次绘图框架技术选型: jsPlumb VS mxGraph》 中,提到了我为什么要去学习 mxGraph。在入门时我遇到了以下几个问题官方文档偏向理论,没能较好地结合代码进行讲解虽然官方给出的例子很多,但没有说明阅读顺序,对刚入门的我不知道应该从哪开始阅读通过搜索引擎搜索 “mxGraph教程” 没能得到太大帮助通过自己对着官方文档死磕了一段时间并在公司项目中进行实践后,慢慢开始掌握这个框架的使用。下面我就根据我的学习经验写一篇比较适合入门的文章。官方列了比较多文档,其中下面这几份是比较有用的。mxGraph Tutorial,这份文档主要讲述整个框架的组成mxGraph User Manual – JavaScript Client,这份文档对一些重要的概念进行讲解,以及介绍一些重要的 API在线实例,这些实例的源码都在这里有API 文档,这是最重要的一份文档,在接下来的教程我不会对接口作详细讲述,你可以在这里对相关接口作深入了解在看完我的文章后希望系统地学习 mxGraph 还是要去阅读这些文档的,现在可以暂时不看。因为刚开始就堆这么多理论性的东西,对入门没有好处。这篇教程分为两部分,第一部分结合我写的一些例子讲解基础知识。第二部分则利用第一部分讲解的知识开发一个小项目 pokemon-diagram。本教程会使用到 ES6 语法,而第二部分的项目是用 Vue 写的。阅读本教程需要你掌握这两项预备知识。引入使用 script 引入我们来分析一下官方的 HelloWorld 实例是怎样通过 script 标签引入 mxGraph 的<!DOCTYPE html><html lang=“en”><head> <meta charset=“UTF-8”> <title>Hello World</title></head><body><div id=“graphContainer”></div></body><script>mxBasePath = ‘../src’;</script><script src="../src/js/mxClient.js"></script><script> // ……</script></html>首先要声名一个全局变量 mxBasePath 指向一个路径,然后引入 mxGraph。mxBasePath 指向的路径作为 mxGraph 的静态资源路径。上图是 HelloWorld 项目的 mxBasePah,这些资源除了 js 目录 ,其他目录下的资源都是 mxGraph 运行过程中所需要的,所以要在引入 mxGraph 前先设置 mxBasePath。 再来看看 javascript 目录下有两个 mxClient.js 版本。 一个在 javascript/src/js/mxClient.js ,另一个在 javascript/mxClient.js,后者是前者打包后的版本,所以两者是可以替换使用的。如果你的项目是使用 script 标签引入 mxGraph,可以参考我这个库。模块化引入模块化引入可以参考 pokemon-diagram 的这个文件 static/mxgraph/index.js/*** 引入 mxgraph /// src/graph/index.jsimport mx from ‘mxgraph’;const mxgraph = mx({ mxBasePath: ‘/static/mxgraph’,});//fix BUG https://github.com/jgraph/mxgraph/issues/49window['mxGraph'] = mxgraph.mxGraph;window[‘mxGraphModel’] = mxgraph.mxGraphModel;window[‘mxEditor’] = mxgraph.mxEditor;window[‘mxGeometry’] = mxgraph.mxGeometry;window[‘mxDefaultKeyHandler’] = mxgraph.mxDefaultKeyHandler;window[‘mxDefaultPopupMenu’] = mxgraph.mxDefaultPopupMenu;window[‘mxStylesheet’] = mxgraph.mxStylesheet;window[‘mxDefaultToolbar’] = mxgraph.mxDefaultToolbar;export default mxgraph;/ 在其他模块中使用 /// src/graph/Graph.jsimport mxgraph from ‘./index’;const { mxGraph, mxVertexHandler, mxConstants, mxCellState, /……/} = mxgraph;这里有两点需要注意的mx 方法传入的配置项 mxBasePath 指向的路径一定要是一个可以通过 url 访问的静态资源目录。举个例子,pokemon-diagram 的 static 目录是个静态资源目录,该目录下有 mxgraph/css/common.css 这么个资源,通过http://localhost:7777 可以访问 pokemon-diagram 应用,那么通过 http://localhost:7777/static/mxgraph/css/common.css 也应该是可以访问 common.css 才对如果你是通过 script 标签引入 mxGraph,是不需要绑定全局变量那段代码的。模块化引入要使用这段代码是因为,mxGraph 这个框架有些代码是通过 window.mxXXX 对以上属性进行访问的,如果不做全局绑定使用起来会有点问题。这是官方一个未修复的 BUG,详情可以查阅上面代码注释的 issue基础知识这部分会使用到我自己编写的一些例子。大家可以先把代码下载下来,这些例子都是不需要使用 node 运行的,直接双击打开文件在浏览器运行即可。CellCell 在 mxGraph 中可以代表组(Group)、节点(Vertex)、边(Edge),mxCell 这个类封装了 Cell 的操作,本教程不涉及到组的内容。下文若出现 Cell 字眼可以当作 节点 或 边。事务官方的 HelloWorld 的例子向我们展示了如何将节点插入到画布。比较引人注意的是 beginUpdate 与 endUpdate 这两个方法,这两个方法在官方例子中出镜频率非常高,我们来了解一下他们是干嘛用的,嗯,真是只是了解一下就可以了,因为官方对两个方法的描述对入门者来说真的是比较晦涩难懂,而且我在实际开发中基本用不上这两个方法。可以等掌握这个框架基本使用后再回过头来研究。下面的描述来源这个文档,我来简单概括一下有关这两个方法的相关信息。beginUpdate、endUpdate 用于创建一个事务,一次 beginUpdate 必须对应一次 endUpdate为了保证,假如 beginUpdate 执行失败,endUpdate 永远不会被调用,beginUpdate 一定要放到 try 块之外为了保证,假如 try 块内更新失败,endUpdate 也一定被调用,beginUpdate一定要放到 finally 块使用 beginUpdate 与 endUpdate 可提高更新视图性能,框架内部做撤消/重做管理也需要 beginUpdate、endUpdate按照官方这个说明,如果我不需要撤消/重做功能,是不是可以不使用这两个方法呢。我试着把这两个方法从 HelloWorld 例子的代码中删掉,结果程序还是可以正常运行。insertVertexmxGraph.prototype.insertVertex = function(parent, id, value, x, y, width, height, style, relative) { // 设置 Cell 尺寸及位置信息 var geometry = new mxGeometry(x, y, width, height); geometry.relative = (relative != null) ? relative : false; // 创建一个 Cell var vertex = new mxCell(value, geometry, style); // … // 标识这个 Cell 是一个节点 vertex.setVertex(true); // … // 在画布上添加这个 Cell return this.addCell(vertex, parent);};上面是经简化后的 insertVertex 方法。 insertVertex 做了三件事,先是设置几何信息,然后创建一个节点,最后将这个节点添加到画布。insertEdge 与 insertVertex 类似,中间过程会调用 vertex.setEdge(true) 将 Cell 标记为边。从这里我们也可以得知无论节点还是边在 mxGraph 中都是由 mxCell 类表示,只是在该类内部标识当前 Cell 是 节点 还是 边。mxGeometryfunction mxGeometry(x,y,width,height){}mxGeometry 类表示 Cell 的几何信息,宽高比较好理解,只对节点有意义,对边没意义。下面通过 02.geometry.html 这个例子说明如x、y的作用。mxGeometry 还有一个很重要的布尔属性 relative,relative 为 false 的节点,表示以画布左上角为基点进行定位,x、y 使用的是绝对单位上一小节提到 insertVertex 内部会创建 mxGeometry 类。使用 mxGraph.insertVertex 会创建一个 mxGeometry.relative 为 false 的节点,如 A 节点relative 为 true 的节点,表示以父节点左上角为基点进行定位,x、y 使用的是相对单位使用 mxGraph.insertVertex 会创建一个 relative 为 false 的节点。如果你要将一个节点添加到另一个节点中需要在该方法调用的第9个参数传入 true,将 relative 设置为 true。这时子节点使用相对坐标系,以父节点左上角作为基点,x、y 取值范围都是 [-1,1]。如 C节点 相对 B节点定位。relative 为 true 的边,x、y 用于定位 label使用 mxGraph.insertEdge 会创建一条 relative 为 true 的边。x、y 用于定位线条上的 label,x 取值范围是 [-1,1],-1 为起点,0 为中点,1 为终点。y 表示 label 在边的正交线上移到的距离。第三个例子能帮忙大家理解这种情况。const e1 = graph.insertEdge(parent, null, ‘30%’, v1, v2);e1.geometry.x = 1;e1.geometry.y = 100;设置样式由 03.stylesheet.html 这个例子我们得知 mxGraph 提供两种设置样式的方式。第一种是设置全局样式。mxStylesheet 类用于管理图形样式,通过 graph.getStylesheet() 可以获取当前图形的 mxStylesheet 对象。mxStylesheet 对象的 styles 属性也是一个对象,该对象默认情况下包含两个对象defaultVertexStyle、defaultEdgeStyle,修改这两个对象里的样式属性对所有线条/节点都生效。第二种是命名样式。先创建一个样式对象,然后使用 mxStylesheet.putCellStyle 方法为 mxStylesheet.styles 添加该样式对象并命名。在添加 Cell 的时候,将样式写在参数中。格式如下[stylename;|key=value;]分号前可以跟命名样式名称或者一个样式的 key、value 对。ROUNDED 是一个内置的命名样式,对节点设置有圆角效果,对边设置则边的拐弯处为圆角。例子中设置折线有一个需要注意的地方。// 设置拖拽边的过程出现折线,默认为直线graph.connectionHandler.createEdgeState = function () { const edge = this.createEdge(); return new mxCellState(graph.view, edge, graph.getCellStyle(edge));};虽然调用 insertEdge 方法时已经设置了线条为折线,但是在拖拽边过程中依然是直线。上面这段代码重写了 createEdgeState 方法,将拖动中的边样式设置成与静态时的边样式一致,都是折线。查看样式效果小技巧mxGraph 所有样式在这里可以查看,打开网站后可以看到以 STYLE_ 开头的是样式常量。但是这些样式常量并不能展示样式的效果。下面教大家一个查看样式效果的小技巧,使用 draw.io 或 GraphEditor (这两个应该都是使用 mxGraph 进行开发的) 的 Edit Style 功能可以查看当前 Cell 样式。比如现在我想将边的样式设置成:折线、虚线、绿色、拐弯为圆角、粗3pt。在 Style 面板手动修改样式后,再点击 Edit Style 就可以看到对应的样式代码。为了方便观察我手动格式化了样式,注意最后一行以 entry 或 exit 开头的样式代表的是边出口/入口的靶点坐标,下一小节会进行讲解。靶点关于如何设置靶点可以参考 04.anchors.html ,下面也是以这个 Demo 进行讲解两个用户操作的例子,对比不同的操作对于获取靶点信息的影响。将鼠标悬浮中 A 节点中心,待节点高亮时连接到 B 节点的一个靶点上然后将 A 节点拖拽到 B 节点右边可以看到如果从图形中心拖出线条,这时边的出口值 exit 为空,只有入口值 entry。如果拖动节点 mxGraph 会智能地调整线条出口方向。如节点 A 的连接靶点原来是在右边,节点拖动到节点 B 右边后靶点也跟着发生了变化,跑到了左边,而节点 B 的连接靶点一直没变。这次将鼠标悬浮到 A 节点的一个靶点,待靶点高亮时连接到 B 节点的一个靶点上然后将 A 节点拖拽到 B 节点右边可以看到这次所有值都有了,连接后拖动节点 A,连接靶点的位置也固定不变,mxGraph 不像第一个例子一样调整连接靶点位置。之所以产生这样的差异是因为第一个例子的边是从节点中心拖出的,并没有出口靶点的信息,而第二个例子则是明确地从一个靶点中拖出一条边。面向对象编程mxGraph 框架是使用面向对象的方式进行编写的,该框架所有类带 mx 前缀。在接下来的例子你会看到很多这种形式的方法重写(Overwrite)。const oldBar = mxFoo.prototype.bar;mxFoo.prototype.bar = function (…args)=> { // ….. oldBar.apply(this,args); // …..};节点组合这一小节通过 05.consistuent.html 这个例子,讲解节点组合需要注意的地方。组合节点后默认情况下,父节点是可折叠的,要关闭折叠功能需要将 foldingEnabled 设为 false。graph.foldingEnabled = false;如果希望在改变父节点尺寸时,子节点与父节点等比例缩放,需要开启 recursiveResize。graph.recursiveResize = true;下面是这个例子最重要的两段代码。/* * Redirects start drag to parent. */const getInitialCellForEvent = mxGraphHandler.prototype.getInitialCellForEvent;mxGraphHandler.prototype.getInitialCellForEvent = function (me) { let cell = getInitialCellForEvent.apply(this, arguments); if (this.graph.isPart(cell)) { cell = this.graph.getModel().getParent(cell); } return cell;};// Redirects selection to parentgraph.selectCellForEvent = function (cell) { if (this.isPart(cell)) { mxGraph.prototype.selectCellForEvent.call(this, this.model.getParent(cell)); return; } mxGraph.prototype.selectCellForEvent.apply(this, arguments);};这两个方法重写(Overwrite)了原方法,思路都是判断如果该节点是子节点则替换成父节点去执行剩下的逻辑。getInitialCellForEvent 在鼠标按下(mousedown事件,不是click事件)时触发,如果注释掉这段代码,不使用父节点替换,当发生拖拽时子节点会被单独拖拽,不会与父节点联动。使用父节点替换后,原本子节点应该被拖拽,现在变成了父节点被拖拽,实现联动效果。selectCellForEvent 其实是 getInitialCellForEvent 内部调用的一个方法。这个方法的作用是将 cell 设置为 selectionCell,设置后可通过 mxGraph.getSelectionCell 可获取得该节点。与 getInitialCellForEvent 同理,如果不使用父节点替换,则 mxGraph.getSelectionCell 获取到的会是子节点。项目实战我们会使用到 mxGraph.getSelectionCell 这个接口。项目实战这部分我主要挑一些这个项目比较重要的点进行讲解。写一个节点组合下面以项目的这个节点为例,讲解如何组合节点const insertVertex = (dom) => { // … const nodeRootVertex = new mxCell(‘鼠标双击输入’, new mxGeometry(0, 0, 100, 135), node;image=${src}); nodeRootVertex.vertex = true; // … const title = dom.getAttribute(‘alt’); const titleVertex = graph.insertVertex(nodeRootVertex, null, title, 0.1, 0.65, 80, 16, ‘constituent=1;whiteSpace=wrap;strokeColor=none;fillColor=none;fontColor=#e6a23c’, true); titleVertex.setConnectable(false); const normalTypeVertex = graph.insertVertex(nodeRootVertex, null, null, 0.05, 0.05, 19, 14, normalType;constituent=1;fillColor=none;image=/static/images/normal-type/forest.png, true); normalTypeVertex.setConnectable(false); // …..};单单 nodeRootVertex 就是长这个样子。通过设置自定义的 node 样式(见 Graph 类 _putVertexStyle 方法)与 image 属性设置图片路径配合完成。因为默认情况下一个节点只能有一个文本区和一个图片区,要增加额外的文本和图片就需要组合节点。在 nodeRootVertex 上加上 titleVertex 文本节点和 normalTypeVertex 图片节点,最终达到这个效果。有时需要为不同子节点设置不同的鼠标悬浮图标,如本项目鼠标悬浮到 normalTypeVertex 时鼠标变为手形,参考 AppCanvas.vue 的 setCursor 方法,重写 mxGraph.prototype.getCursorForCell 可以实现这个功能。const setCursor = () => { const oldGetCursorForCell = mxGraph.prototype.getCursorForCell; graph.getCursorForCell = function (…args) { const [cell] = args; return cell.style.includes(’normalType’) ? ‘pointer’ : oldGetCursorForCell.apply(this, args); };};编辑内容下面这段代码是编辑内容比较常用的设置// 编辑时按回车键不换行,而是完成输入this.setEnterStopsCellEditing(true);// 编辑时按 escape 后完成输入mxCellEditor.prototype.escapeCancelsEditing = false;// 失焦时完成输入mxCellEditor.prototype.blurEnabled = true;默认情况下输入内容时如果按回车键内容会换行,但有些场景有禁止换行的需求,希望回车后完成输入,通过graph.setEnterStopsCellEditing(true) 设置可以满足需求。重点说说 mxCellEditor.prototype.blurEnabled 这个属性,默认情况下如果用户在输入内容时鼠标点击了画布之外的不可聚焦区域(div、section、article等),节点内的编辑器是不会失焦的,这导致了 LABEL_CHANGED 事件不会被触发。但在实际项目开发中一般我们会期望,如果用户在输入内容时鼠标点击了画布之外的地方就应该算作完成一次输入,然后通过被触发的 LABEL_CHANGED 事件将修改后的内容同步到服务端。通过 mxCellEditor.prototype.blurEnabled = true 这行代码设置可以满足我们的需求。可换行的 labelconst titleVertex = graph.insertVertex(nodeRootVertex, null, title, 0.1, 0.65, 80, 16, ‘constituent=1;whiteSpace=wrap;strokeColor=none;fillColor=none;fontColor=#e6a23c’, true);对于非输入的文本内容,默认情况下即便文本超出容器宽度也是不会换行的。我们项目中宽度为 80 的 titleVertex 正是这样一个例子。要设置换行需要做两件事,第一是通过这行代码 mxGraph.setHtmlLabels(true),使用 html 渲染文本(mxGraph 默认使用 svg的text 标签渲染文本)。第二是像上面的 titleVertex 的样式设置一样,添加一句 whiteSpace=wrap。Model现在介绍一下 Model 这个概念,Model 是当前图形的数据结构化表示。mxGraphModel 封装了 Model 的相关操作。你可以启动项目,画一个这样的图,然后点击输出XML。为了保的 xml 与下面的一致,需要先拖出智爷,再拖出超级皮卡丘,最后连接边。控制台应该输出这样一份 xml<mxGraphModel> <root> <mxCell id=“0”/> <mxCell id=“1” parent=“0”/> <mxCell id=“4” value=“Hello” style=“node;image=/static/images/ele/ele-005.png” vertex=“1” data="{&quot;id&quot;:1,&quot;element&quot;:{&quot;id&quot;:1,&quot;icon&quot;:&quot;ele-005.png&quot;,&quot;title&quot;:&quot;智爷&quot;},&quot;normalType&quot;:&quot;water.png&quot;}" parent=“1”> <mxGeometry x=“380” y=“230” width=“100” height=“135” as=“geometry”/> </mxCell> …….. </root></mxGraphModel>每一个 mxCell 节点都有 parent 属性指向父节点。我们对 value=“Hello” 这个 mxCell 节点手动格式化。<mxCell id=“4” value=“Hello” style=“node;image=/static/images/ele/ele-005.png” vertex=“1” data="{&quot;id&quot;:1,&quot;element&quot;:{&quot;id&quot;:1,&quot;icon&quot;:&quot;ele-005.png&quot;,&quot;title&quot;:&quot;智爷&quot;},&quot;normalType&quot;:&quot;water.png&quot;}" parent=“1”> <mxGeometry x=“380” y=“230” width=“100” height=“135” as=“geometry”/></mxCell>data 值是原对象经 JSON.stringify 得到的,经转义后就变成了上面的样子。控制台还打印了一个 mxGraphModel 对象,对比上面的 xml 与 下图的节点对象,可以发现它们只是同一个 Model 的不同表现形式,xml 正是将 mxGraph.model 格式化而成的。事件本项目监听事件写在 AppCanvas.vue 的 _listenEvent 方法,可以在这个方法了解一些常用的事件。下图来自 mxGraph 类的方法调用依赖图,我们可以从这里看出整个框架的事件流动。监听事件本项目的 _listenEvent 方法用到两个事件监听对象。mxGraph 继承自 mxEventSource,使用父类的 addListener 方法可以将自身当作一个事件中心进行订阅/广播事件。mxGraph.getSelectionModel() 返回一个 mxGraphSelectionModel 对象,这个对象也是继承自 mxEventSource 有 mxEvent.UNDO、mxEvent.CHANGE 两个事件,通过监听 mxEvent.CHANGE 事件可以获取当前被选中的 Cell。ADD_CELLS 与 CELLS_ADD 的区别mxGraph 类有很多 XXX_CELLS、CELLS_XXXED 这种形式的事件,这部分我还没弄懂,下面仅以添加事件为例探讨这两类事件的区别。添加 Cell 的时候会触发两个事件 ADD_CELLS、CELLS_ADDED, 先触发 CELLS_ADDED 后触发 ADD_CELLS。ADD_CELLS 在 addCells 方法中触发,而 CELLS_ADDED 在 cellsAdded 方法中触发。而对于 addCells 与 cellsAdded 官方文档的说明并不能体现出两者的区别,再深究下去就要查阅源码了。按经验而言后触发的事件会携带更多的信息,所以平时开发我会监听 ADD_CELLS 事件。MOVE_CELLS、CELLS_MOVED、REMOVE_CELLS、CELLS_REMOVED 等事件与此类似。监听 Cell 添加事件从上面的方法调用依赖图中我们可以看到,insertVertex、insertEdge 最终都被当作 Cell 处理,在后续触发的事件也没有对 节点/边 进行区分,而是统一当作 Cell 事件。所以对于一个 Cell 添加事件,需要自己区别是添加了节点还是添加了边。graph.addListener(mxEvent.CELLS_ADDED, (sender, evt) => { const cell = evt.properties.cells[0]; if (graph.isPart(cell)) { return; } if (cell.vertex) { this.$message.info(‘添加了一个节点’); } else if (cell.edge) { this.$message.info(‘添加了一条线’); }});还有就是对于子节点添加到父节点的情况(如本项目将 titleVertex 、normalTypeVertex 添加到 nodeRootVertex)也是会触发 Cell 添加事件的。通常对于这些子节点不作处理,可以像 05.consistuent.html 一样用一个 isPart 判断过滤掉。自定义事件上面提到过 mxGraph 继承自 mxEventSource,调用父类的 fireEvent 可触发自定义事件。下面是一个简单的例子mxGraph.addListener(‘自定义事件A’,()=>{ // do something …..});// 触发自定义事件mxGraph.fireEvent(new mxEventObject(‘自定义事件A’);在本项目 Graph 类的 _configCustomEvent 方法我也实现了两个自定义事件。当边开始拖动时会触发 EDGE_START_MOVE 事件,当节点开始拖动时会触发 VERTEX_START_MOVE 事件。导出图片mxGraph 导出图片的思路是先在前端导出图形的 xml 及计算图形的宽高,然后将 xml、宽、高,这有三项数据发送给服务端,服务端也使用 mxGraph 提供的 API 将 xml 转换成图片。服务端如果是使用 Java 可以参考官方这个例子,下面主要介绍前端需要做的工作。导出图片可以使用 mxImageExport 类,该类的文档有一段可以直接拿来使用的代码。// …var xmlCanvas = new mxXmlCanvas2D(root);var imgExport = new mxImageExport();imgExport.drawState(graph.getView().getState(graph.model.root), xmlCanvas);var bounds = graph.getGraphBounds();var w = Math.ceil(bounds.x + bounds.width);var h = Math.ceil(bounds.y + bounds.height);var xml = mxUtils.getXml(root);// …但这段代码会将整块画布截图,而不是以最左上角的元素及最右下角的元素作为边界截图。如果你有以元素作为边界的需求,则需要调用 xmlCanvas.translate 调整裁图边界。//…..var xmlCanvas = new mxXmlCanvas2D(root);xmlCanvas.translate( Math.floor((border / scale - bounds.x) / scale), Math.floor((border / scale - bounds.y) / scale), );//…..完整截图代码可以参考本项目 Graph 类的 exportPicXML 方法。如果节点像我的项目一样使用到图片,而导出来的图片的节点没有图片。可以从两个方向排查问题,先检查发送的 xml 里的图片路径是否是可访问的,如下面是项目“导出图片”功能打印的 xml 里的一个图片标签。<image x=“484” y=“123” w=“72” h=“72” src=“http://localhost:7777/static/images/ele/ele-005.png” aspect=“0” flipH=“0” flipV=“0”/>要保证 http://localhost:7777/static/images/ele/ele-005.png 是可访问的。如果图片路径没问题再检查一下使用的图片格式,本来我在公司项目中节点内使用的图片是 svg 格式,导出图片失败,可能是 mxGraph 不支持这个格式,后来换成 png 之后问题就解决了。还有就是如果导出的图片里的节点的某些颜色跟设置的有差异,那可能是设置样式时写了3位数的颜色像 #fff,颜色一定要使用完整的6位,否则导出图片会有问题。参考mxGraph TutorialmxGraph User Manual – JavaScript ClientmxGraph API SpecificationmxGraph Javascript Examples ...

March 14, 2019 · 5 min · jiezi

Gremlin入门

Gremlin入门一、Gremlin简介Gremlin是Apache ThinkerPop框架下的图遍历语言,Gremlin是一种函数式数据流语言,可以使用户使用简洁的方式表述复杂的属性图的遍历或查询。每个Gremlin遍历由一系列步骤(可能存在嵌套)组成,每一步都在数据流(data stream)上执行一个原子操作。Gremlin 语言包括三个基本的操作:map-step:对数据流中的对象进行转换;filter-step:对数据流中的对象就行过滤;sideEffect-step:对数据流进行计算统计;Tinkerpop3 模型核心概念Graph: 维护节点&边的集合,提供访问底层数据库功能,如事务功能Element: 维护属性集合,和一个字符串label,表明这个element种类Vertex: 继承自Element,维护了一组入度,出度的边集合Edge: 继承自Element,维护一组入度,出度vertex节点集合.Property: kv键值对VertexProperty: 节点的属性,有一组健值对kv,还有额外的properties 集合。同时也继承自element,必须有自己的id, label.Cardinality: 「single, list, set」 节点属性对应的value是单值,还是列表,或者set。二、Gremlin查询示例先介绍一下图中比较核心的几个概念:Schema:Schema是一种描述语言,这里就是指所有属性和类型的集合,包括边和点的属性,边和点的Label等;属性类型(PropertyKey ):只边和点可以使用的属性类型;顶点类型(VertexLabel):顶点的类型,比如User,Car等;边类型(EdgeLabel):边的类型,比如know,use等;顶点(Vertex):就是图中的顶点,代表图中的一个节点;边(Edge):就是图中的边,连接两个节点,分为有向边和无向边;创建属性类型graph.schema().propertyKey(“name”).asText().ifNotExist().create()graph.schema().propertyKey(“age”).asInt().ifNotExist().create()graph.schema().propertyKey(“city”).asText().ifNotExist().create()graph.schema().propertyKey(“lang”).asText().ifNotExist().create()graph.schema().propertyKey(“date”).asText().ifNotExist().create()graph.schema().propertyKey(“price”).asInt().ifNotExist().create()创建顶点类型person = graph.schema().vertexLabel(“person”).properties(“name”, “age”, “city”).primaryKeys(“name”).ifNotExist().create()software = graph.schema().vertexLabel(“software”).properties(“name”, “lang”, “price”).primaryKeys(“name”).ifNotExist().create()创建边类型knows = graph.schema().edgeLabel(“knows”).sourceLabel(“person”).targetLabel(“person”).properties(“date”).ifNotExist().create()created = graph.schema().edgeLabel(“created”).sourceLabel(“person”).targetLabel(“software”).properties(“date”, “city”).ifNotExist().create()创建顶点和边marko = graph.addVertex(T.label, “person”, “name”, “marko”, “age”, 29, “city”, “Beijing”)vadas = graph.addVertex(T.label, “person”, “name”, “vadas”, “age”, 27, “city”, “Hongkong”)lop = graph.addVertex(T.label, “software”, “name”, “lop”, “lang”, “java”, “price”, 328)josh = graph.addVertex(T.label, “person”, “name”, “josh”, “age”, 32, “city”, “Beijing”)ripple = graph.addVertex(T.label, “software”, “name”, “ripple”, “lang”, “java”, “price”, 199)peter = graph.addVertex(T.label, “person”,“name”, “peter”, “age”, 29, “city”, “Shanghai”)marko.addEdge(“knows”, vadas, “date”, “20160110”)marko.addEdge(“knows”, josh, “date”, “20130220”)marko.addEdge(“created”, lop, “date”, “20171210”, “city”, “Shanghai”)josh.addEdge(“created”, ripple, “date”, “20151010”, “city”, “Beijing”)josh.addEdge(“created”, lop, “date”, “20171210”, “city”, “Beijing”)peter.addEdge(“created”, lop, “date”, “20171210”, “city”, “Beijing”)展示图g.V() //创建使用graph,查询使用g,其实g就是graph.traversal()查询点g.V().limit(5) // 查询所有点,但限制点的返回数量为5,也可以使用range(x, y)的算子,返回区间内的点数量。g.V().hasLabel(‘person’) // 查询点的label值为’person’的点。g.V(‘11’) // 查询id为‘11’的点。查询边g.E() // 查询所有边,不推荐使用,边数过大时,这种查询方式不合理,一般需要添加过滤条件或限制返回数量。g.E(‘55-81-5’) // 查询边id为‘55-81-5’的边。g.E().hasLabel(‘knows’) // 查询label为‘knows’的边。g.V(‘46’).outE(‘knows’) // 查询点id为‘46’所有label为‘knows’的边。查询属性g.V().limit(3).valueMap() // 查询点的所有属性(可填参数,表示只查询该点, 一个点所有属性一行结果)。g.V().limit(1).label() // 查询点的label。g.V().limit(10).values(’name’) // 查询点的name属性(可不填参数,表示查询所有属性, 一个点每个属性一行结果,只有value,没有key)。删除点g.V(‘600’).drop() // 删除ID为600的点。删除边g.E(‘501-502-0’).drop() //删除ID为“501-502-0”的边。查询二度好友和共同好友数//查询一度好友g.V(‘1500771’).out()//查询二度好友g.V(‘1500771’).out().out().dedup().not(hasId(‘1500771’))//查询共同好友数g.V(‘1500771’).out().out().hasId(‘2165197’).path().simplePath().count()此外,还有查询,遍历,过滤,路径,迭代,转换,排序,逻辑,统计,分支等语法,可以参考:http://tang.love/2018/11/15/g…。参考:http://tang.love/2018/11/15/g…https://hugegraph.github.io/h… ...

January 29, 2019 · 1 min · jiezi

【极简壁纸】桌面壁纸美图推荐_2019/01/27

极简壁纸是一个期望呈现出简单高效美观的壁纸网站。网站链接:https://bz.zzzmh.cn推荐几张最新更新的壁纸(图片发到这里会被压缩质量,建议访问网站下载原图)网站链接:https://bz.zzzmh.cn

January 27, 2019 · 1 min · jiezi

【极简壁纸】简单高效美观的壁纸网站

极简壁纸是一个期望呈现出简单高效美观的壁纸网站。灵感来源是爱壁纸APP,图片来源是wallhaven.cc极简壁纸网站建立于2018年5月1日。起初是只是由于站长个人兴趣爱好,酷爱找各种壁纸。网站链接:https://bz.zzzmh.cn界面展示首页精选:近期最火的图片精选 (与wallhaven的toplist同步)入口:极简壁纸 > 精选小姐姐:小姐姐、妹纸等相关的图片入口:极简壁纸 > 小姐姐二次元:二次元、动画、漫画、游戏CG等相关的图片入口:极简壁纸 > 二次元自然:自然、风景、摄影作品等相关的图片入口:极简壁纸 > 自然标签:按照标签分类,可以分别查看单独标签下的图片入口:极简壁纸 > 标签喜欢:每张图片右下角都有一个红心,点击红心"喜欢"该图片,所有喜欢的图片默认保存在浏览器本地,登录后可与云端同步。入口:极简壁纸 > 喜欢关于我们入口:极简壁纸 > 关于我们END如果喜欢的话可以来看看,地址 bz.zzzmh.cn ,对于网站有什么意见建议也欢迎回复留言。谢谢!

January 21, 2019 · 1 min · jiezi

leetcode讲解--841. Keys and Rooms

题目There are N rooms and you start in room 0. Each room has a distinct number in 0, 1, 2, …, N-1, and each room may have some keys to access the next room. Formally, each room i has a list of keys rooms[i], and each key rooms[i][j] is an integer in [0, 1, …, N-1] where N = rooms.length. A key rooms[i][j] = v opens the room with number v.Initially, all the rooms start locked (except for room 0). You can walk back and forth between rooms freely.Return true if and only if you can enter every room.Example 1:Input: [[1],[2],[3],[]]Output: trueExplanation: We start in room 0, and pick up key 1.We then go to room 1, and pick up key 2.We then go to room 2, and pick up key 3.We then go to room 3. Since we were able to go to every room, we return true.Example 2:Input: [[1,3],[3,0,1],[2],[0]]Output: falseExplanation: We can’t enter the room with number 2.Note:1 <= rooms.length <= 10000 <= rooms[i].length <= 1000The number of keys in all rooms combined is at most 3000.题目地址讲解这是一道典型的图的遍历,这里我采用了广度优先遍历,广度优先遍历可以用队列这种数据结构来实现。同时还需要一个Set用于记录已经访问过的room。感觉这道题还是比较简单的java代码class Solution { public boolean canVisitAllRooms(List<List<Integer>> rooms) { Set<Integer> set = new HashSet(); Queue<Integer> queue = new LinkedList<>(); for(Integer x:rooms.get(0)){ queue.offer(x); } while(!queue.isEmpty()){ int room = queue.poll(); if(set.contains(room)){ continue; }else{ set.add(room); List<Integer> list = rooms.get(room); if(list!=null && list.size()>0){ for(Integer x: list){ queue.offer(x); } } } } for(int i=1;i<rooms.size();i++){ if(!set.contains(i)){ return false; } } return true; }} ...

January 2, 2019 · 2 min · jiezi

leetcode讲解--959. Regions Cut By Slashes

题目In a N x N grid composed of 1 x 1 squares, each 1 x 1 square consists of a /, , or blank space. These characters divide the square into contiguous regions.(Note that backslash characters are escaped, so a \ is represented as “\”.)Return the number of regions.Example 1:Input:[ " /", “/ “]Output: 2Explanation: The 2x2 grid is as follows:Example 2:Input:[ " /”, " “]Output: 1Explanation: The 2x2 grid is as follows:Example 3:Input:[ “\/”, “/\"]Output: 4Explanation: (Recall that because \ characters are escaped, “\/” refers to /, and “/\” refers to /.)The 2x2 grid is as follows:Example 4:Input:[ “/\”, “\/"]Output: 5Explanation: (Recall that because \ characters are escaped, “/\” refers to /, and “\/” refers to /.)The 2x2 grid is as follows:Example 5:Input:[ “//”, “/ “]Output: 3Explanation: The 2x2 grid is as follows:Note:1 <= grid.length == grid[0].length <= 30grid[i][j] is either ‘/’, ‘', or ’ ‘.题目地址讲解这道题是关于图的题,用到了并查集这个数据结构。好久没用并查集了,我主要是参考了这个博客:https://zxi.mytechroad.com/bl…Java代码class Solution { public int regionsBySlashes(String[] grid) { int result = 0; int index=-1; List<DisJointSet> list = new ArrayList<>(); for(String s:grid){ for(Character c:s.toCharArray()){ for(int i=0;i<4;i++){ DisJointSet ds = new DisJointSet(); list.add(ds); } } } for(int j=0;j<grid.length;j++){ for(int i=0;i<grid[j].length();i++){ DisJointSet ds1 = list.get(++index); DisJointSet ds2 = list.get(++index); DisJointSet ds3 = list.get(++index); DisJointSet ds4 = list.get(++index); System.out.println(grid[j].charAt(i)); if(grid[j].charAt(i)==’ ‘){ DisJointSet.union(ds1, ds2); DisJointSet.union(ds2, ds3); DisJointSet.union(ds3, ds4); } if(grid[j].charAt(i)==’/’){ DisJointSet.union(ds1, ds4); DisJointSet.union(ds2, ds3); } if(grid[j].charAt(i)==’\’){ DisJointSet.union(ds1, ds2); DisJointSet.union(ds3, ds4); } if(i<grid[j].length()-1){ DisJointSet.union(ds2, list.get(index+4)); } if(j<grid.length-1){ DisJointSet.union(ds3, list.get(index+4*(grid[j].length()-1)+1)); } } } for(DisJointSet ds:list){ if(ds.find()==ds){ result++; } } return result; }}class DisJointSet{ public DisJointSet parent; public int rank; public DisJointSet(){ rank = 0; parent = this; } public DisJointSet find(){ if(parent!=this){ parent = parent.find(); } return parent; } public static void union(DisJointSet ds1, DisJointSet ds2){ DisJointSet ds1Root = ds1.find(); DisJointSet ds2Root = ds2.find(); if(ds1Root!=ds2Root){ if(ds1Root.rank>ds2Root.rank){ ds2Root.parent = ds1Root; }else if(ds1Root.rank<ds2Root.rank){ ds1Root.parent = ds2Root; }else if(ds1Root.rank==ds2Root.rank){ ds1Root.parent = ds2Root; ds2Root.rank++; } } } } ...

December 30, 2018 · 2 min · jiezi

leetcode讲解--797. All Paths From Source to Target

All Paths From Source to TargetGiven a directed, acyclic graph of N nodes. Find all possible paths from node 0 to node N-1, and return them in any order.The graph is given as follows: the nodes are 0, 1, …, graph.length - 1. graph[i] is a list of all nodes j for which the edge (i, j) exists.Example:Input: [[1,2], [3], [3], []] Output: [[0,1,3],[0,2,3]] Explanation: The graph looks like this:0—>1| |v v2—>3There are two paths: 0 -> 1 -> 3 and 0 -> 2 -> 3.Note:The number of nodes in the graph will be in the range [2, 15].You can print different paths in any order, but you should keep the order of nodes inside one path.这一题考察的是图的深度优先遍历,要注意的点是:回溯的时候如何清除掉record中的数据,只保留到当前节点的数据,之后的数据需要重新填充。刚开始我是用的一个record,企图通过清空当前节点之后的数据,后来发现不对,record必须是多个,否则我给result添加的record就会全部相同。所以只能通过new和深拷贝来完成这个目标。java代码class Solution { List<List<Integer>> result = new LinkedList<List<Integer>>(); public List<List<Integer>> allPathsSourceTarget(int[][] graph) { int index=0; for(int x:graph[index]){ List<Integer> record = new LinkedList<>(); record.add(0); record.add(x); depthFirst(graph, x, record); } return result; } public void depthFirst(int[][] graph, int index, List<Integer> record){ if(index==graph.length-1){ result.add(record); return; } if(graph[index]==null){ return; } for(int x:graph[index]){ record.add(x); int size = record.size(); depthFirst(graph, x, record); List<Integer> temp = new LinkedList<Integer>(); for(int i=0;i<size-1;i++){ temp.add(record.get(i)); } record = temp; } }}

December 19, 2018 · 1 min · jiezi