使用gojs制作一个具备文件读写功能的家谱网页
2018-03-17 01:07
856 查看
效果
一些功能
数据结构
实现
文件结构
edit.html
edit.js
双击空白创建一个节点
也可以导入已有文件
查看节点信息:可以查看到性别,生卒年,孩子数,孙子数,后代数等的信息
搜索功能:可按名字,性别,生年卒年等进行搜索,结果高亮
编写节点信息:相应节点颜色信息被更新
拖动节点:可使其成为某人的后代
拖动子树可以任意改变后代关系
右击节点:有以下操作:
“斩断”功能:将子树变为另一棵树
“除名”功能:去掉节点名字
“驱逐”功能:消灭当前节点,其子树生成新的树
“灭门”功能:遍历删除当前节点与其后代
可随时下载当前图表的数据文件。
树的查找算法:遍历算法从数据库中遍历属性 parents 是该节点的 key 即可 得到该节点的孩子,并将孩子存入到栈中,如果栈不为空,递归调用。
树的插入算法:首先找到插入的位置,要么向左,要么向右,直到找到空结点, 即为插入位置,如果找到了相同值的结点,插入失败。
树的删除算法:相对查找和插入复杂一点,根据待删除结点的孩子情况,分三种情况:没有孩子,只有一个孩子,有两个孩子。没有孩子的情况,其父结点指向空,删除该结点。有一个孩子的情况,其父结点指向其孩子,删除该结点。有两个孩子的情况,当前结点与左子树中最大的元素交换,然后删除当前结点。左子树最大的元素一定是叶子结点,交换后,当前结点即为叶子结点,删除参考没有孩子的情况。另一种方法是,当前结点与右子树中最小的元素交换,然后删除当前结点。
为了符合家谱增删改查的基本功能和一些扩展功能,我选择了orgChartEditor模型,还会用到编辑框DataInspector组件,并在DataInspector介绍页面获取依赖的 DataInspector.css和DataInspector.js。
还要获取最重要的go.js文件。
主要工作是完成edit.html和edit.js。
这里用到了html5的
一些功能
数据结构
实现
文件结构
edit.html
edit.js
效果
一些功能
操作过程中,随时可以用 Ctrl + Z, Ctrl + Y 撤销或回退操作双击空白创建一个节点
也可以导入已有文件
查看节点信息:可以查看到性别,生卒年,孩子数,孙子数,后代数等的信息
搜索功能:可按名字,性别,生年卒年等进行搜索,结果高亮
编写节点信息:相应节点颜色信息被更新
拖动节点:可使其成为某人的后代
拖动子树可以任意改变后代关系
右击节点:有以下操作:
“斩断”功能:将子树变为另一棵树
“除名”功能:去掉节点名字
“驱逐”功能:消灭当前节点,其子树生成新的树
“灭门”功能:遍历删除当前节点与其后代
可随时下载当前图表的数据文件。
数据结构
数据储存使用双亲表示法,由于树中的每个结点都有唯一的一个双亲结点,所以可用一组连续的存储空间(一维数组)存储树中的各个结点,数组中的一个元素表示树中的一个结点,每个结点含两个域,数据域存放结点本身信息,双亲域指示本结点的双亲结点在数组中位置。树的查找算法:遍历算法从数据库中遍历属性 parents 是该节点的 key 即可 得到该节点的孩子,并将孩子存入到栈中,如果栈不为空,递归调用。
树的插入算法:首先找到插入的位置,要么向左,要么向右,直到找到空结点, 即为插入位置,如果找到了相同值的结点,插入失败。
树的删除算法:相对查找和插入复杂一点,根据待删除结点的孩子情况,分三种情况:没有孩子,只有一个孩子,有两个孩子。没有孩子的情况,其父结点指向空,删除该结点。有一个孩子的情况,其父结点指向其孩子,删除该结点。有两个孩子的情况,当前结点与左子树中最大的元素交换,然后删除当前结点。左子树最大的元素一定是叶子结点,交换后,当前结点即为叶子结点,删除参考没有孩子的情况。另一种方法是,当前结点与右子树中最小的元素交换,然后删除当前结点。
实现
gojs是一个基于原生js的图表图形框架,兼容各浏览器,而且上手容易,gojs官网里有丰富的样例。为了符合家谱增删改查的基本功能和一些扩展功能,我选择了orgChartEditor模型,还会用到编辑框DataInspector组件,并在DataInspector介绍页面获取依赖的 DataInspector.css和DataInspector.js。
还要获取最重要的go.js文件。
文件结构
│ edit.html │ ├─css │ DataInspector.css │ └─js DataInspector.js edit.js go.js
主要工作是完成edit.html和edit.js。
edit.html
<!DOCTYPE html> <html> <head> <meta name="viewport" content="width=device-width, initial-scale=1"> <title>家谱</title> <meta charset="UTF-8"> <script src="js/go.js"></script> <link rel="stylesheet" href="css/DataInspector.css"/> <script src="js/DataInspector.js"></script> <script src="js/edit.js"></script> </head> <body style="background-color: #696969; overflow: hidden;"> <div style="position: fixed; z-index: 999"> <select id="query"> <option value="key">key</option> <option value="名字">名字</option> <option value="parent">parent</option> <option value="性别">性别</option> <option value="双亲">双亲</option> <option value="生年">生年</option> <option value="卒年"> 13dfc 卒年</option> <option value="在世">在世</option> </select> <input type="text" id="mySearch"> <span id="answerCount" style="color: white;"></span> <div> <div id="myInspector"></div> </div> <div> <button id="refresh">刷新</button> <button onclick="Save()">下载</button> <input type="file" name="fleUpload" id="fleUpload" onChange="load(this.files);" style="color: #696969"/> </div> </div> <div id="sample"> <div id="myDiagramDiv" style="background-color: #696969; height: 500px"></div> </div> </body> </html>
这里用到了html5的
<input type="file" />,来实现文件的读取。
edit.js
function init() { var $ = go.GraphObject.make; myDiagram = $(go.Diagram, "myDiagramDiv", { initialContentAlignment: go.Spot.Center, validCycle: go.Diagram.CycleDestinationTree, "clickCreatingTool.archetypeNodeData": {}, "clickCreatingTool.insertPart": function(loc) { this.archetypeNodeData = { key: getNextKey(), parent: "", 名字: "(佚名)", 性别: "", 双亲: "", 生年: "", 卒年: "", 备注: "" }; return go.ClickCreatingTool.prototype.insertPart.call(this, loc) }, layout: $(go.TreeLayout, { sorting: go.TreeLayout.SortingAscending, comparer: function(a, b) { var av = a.node.data.生年; var bv = b.node.data.生年; if (av < bv) { return -1 } if (av > bv) { return 1 } return 0 }, treeStyle: go.TreeLayout.StyleLastParents, arrangement: go.TreeLayout.ArrangementHorizontal, angle: 90, layerSpacing: 35, alternateAngle: 90, alternateLayerSpacing: 35, alternateAlignment: go.TreeLayout.AlignmentBus, alternateNodeSpacing: 20 }), "undoManager.isEnabled": true }); fitBrowserWindow(); myDiagram.add($(go.Part, "Table", { position: new go.Point(-100,0), selectable: false }, $(go.Panel, "Horizontal", { row: 0, alignment: go.Spot.Left }, $(go.Shape, "Rectangle", { desiredSize: new go.Size(30,30), fill: "#0099CC", margin: 5, stroke: "#0099CC" }), $(go.TextBlock, "男", { font: "500 15px Droid Serif, sans-serif", stroke: "white" })), $(go.Panel, "Horizontal", { row: 1, alignment: go.Spot.Left }, $(go.Shape, "Rectangle", { desiredSize: new go.Size(30,30), fill: "#FF6666", margin: 5, stroke: "#FF6666" }), $(go.TextBlock, "女", { font: "500 15px Droid Serif, sans-serif", stroke: "white" })), $(go.Panel, "Horizontal", { row: 2, alignment: go.Spot.Left }, $(go.Shape, "Rectangle", { desiredSize: new go.Size(30,30), fill: "#FFA100", margin: 5, stroke: "#FFA100" }), $(go.TextBlock, "未知", { font: "500 15px Droid Serif, sans-serif", stroke: "white" })))); myDiagram.addDiagramListener("Modified", function(e) { var button = document.getElementById("SaveButton"); if (button) { button.disabled = !myDiagram.isModified } var idx = document.title.indexOf("*"); if (myDiagram.isModified) { if (idx < 0) { document.title += "*" } } else { if (idx >= 0) { document.title = document.title.substr(0, idx) } } }); function getNextKey() { var key = nodeIdCounter; while (myDiagram.model.findNodeDataForKey(key) !== null) { key = nodeIdCounter++ } return key } var nodeIdCounter = 1; function nodeDoubleClick(e, obj) { var clicked = obj.part; if (clicked !== null) { var thisemp = clicked.data; myDiagram.startTransaction("add employee"); var Yparent = myDiagram.model.findNodeDataForKey(thisemp.key); var newemp; newemp = { key: getNextKey(), 名字: "(佚名)", parent: thisemp.key, 性别: "", 双亲: "", 生年: "", 卒年: "", 备注: "" }; myDiagram.model.addNodeData(newemp); myDiagram.commitTransaction("add employee") } } function mayWorkFor(node1, node2) { if (!(node1 instanceof go.Node)) { return false } if (node1 === node2) { return false } if (node2.isInTreeOf(node1)) { return false } return true } function textStyle() { return { font: "9pt Segoe UI,sans-serif", stroke: "white" } } function tooltipTextConverter(person) { var str = ""; var YnewJson = myDiagram.model.toJSON(); var result = JSON.parse(YnewJson); var Yparent = myDiagram.model.findNodeDataForKey(person.parent); var Yself = myDiagram.model.findNodeDataForKey(person.key); var YchildNumber = 0; var YgrandChildNumber = 0; var YallChild = 0; for (var i = 1; i <= result.nodeDataArray.length; i++) { var Ytemp = myDiagram.model.findNodeDataForKey(i); if (Ytemp.parent === undefined) { continue } else { if (Ytemp.parent == Yself.key) { YchildNumber++ } } } var Ytemps = []; for (var i = 1; i <= result.nodeDataArray.length; i++) { var Ytemp = myDiagram.model.findNodeDataForKey(i); if (Ytemp.parent === undefined) { continue } else { if (Ytemp.parent == Yself.key) { Ytemps.push(Ytemp.key) } } } while (Ytemps.length != 0) { var hello = Ytemps.pop(); var Ytemp1 = myDiagram.model.findNodeDataForKey(hello); for (var i = 1; i <= result.nodeDataArray.length; i++) { var Ytemp = myDiagram.model.findNodeDataForKey(i); if (Ytemp.parent === undefined) { continue } else { if (Ytemp.parent == Ytemp1.key) { YgrandChildNumber++ } } } } var Ytemp2s = []; Ytemp2s.push(person.key); do { var hello = Ytemp2s.pop(); var Ytemp1 = myDiagram.model.findNodeDataForKey(hello); for (var i = 1; i <= result.nodeDataArray.length; i++) { var Ytemp = myDiagram.model.findNodeDataForKey(i); if (Ytemp.parent === undefined) { continue } else { if (Ytemp.parent == Ytemp1.key) { YallChild++; Ytemp2s.push(Ytemp.key) } } } } while (Ytemp2s.length != 0);str += "名字: " + person.名字; if (person.性别 !== undefined) { str += "\n性别: " + person.性别 } if (person.双亲 !== undefined && person.parent !== undefined && Yparent.性别 === "男") { str += "\n父亲: " + Yparent.名字 + "\n母亲: " + person.双亲 } if (person.双亲 !== undefined && person.parent !== undefined && Yparent.性别 === "女") { str += "\n母亲: " + Yparent.名字 + "\n父亲: " + person.双亲 } if (person.生年 !== undefined) { str += "\n生年: " + person.生年 } if (person.卒年 !== undefined) { str += "\n卒年: " + person.卒年 } str += "\n孩子数: " + YchildNumber; str += "\n孙子数: " + YgrandChildNumber; str += "\n后代数: " + YallChild; return str } var tooltiptemplate = $(go.Adornment, "Auto", $(go.Shape, "Rectangle", { fill: "whitesmoke", stroke: "black" }), $(go.TextBlock, { font: "bold 8pt Helvetica, bold Arial, sans-serif", wrap: go.TextBlock.WrapFit, margin: 5 }, new go.Binding("text","",tooltipTextConverter))); function genderBrushConverter(gender) { if (gender === "男") { return "#0099CC" } if (gender === "女") { return "#FF6666" } return "#FFA100" } myDiagram.nodeTemplate = $(go.Node, "Auto", new go.Binding("text","生年"), { toolTip: tooltiptemplate }, { doubleClick: nodeDoubleClick }, { mouseDragEnter: function(e, node, prev) { var diagram = node.diagram; var selnode = diagram.selection.first(); if (!mayWorkFor(selnode, node)) { return } var shape = node.findObject("SHAPE"); if (shape) { shape._prevFill = shape.fill; shape.fill = "darkred" } }, mouseDragLeave: function(e, node, next) { var shape = node.findObject("SHAPE"); if (shape && shape._prevFill) { shape.fill = shape._prevFill } }, mouseDrop: function(e, node) { var diagram = node.diagram; var selnode = diagram.selection.first(); if (mayWorkFor(selnode, node)) { var link = selnode.findTreeParentLink(); if (link !== null) { link.fromNode = node } else { diagram.toolManager.linkingTool.insertLink(node, node.port, selnode, selnode.port) } } } }, new go.Binding("layerName","isSelected",function(sel) { return sel ? "Foreground" : "" } ).ofObject(), $(go.Shape, "Rectangle", { name: "SHAPE", fill: null, strokeWidth: 2, stroke: null, portId: "", fromLinkable: true, toLinkable: true, cursor: "pointer" }, new go.Binding("stroke","isHighlighted",function(h) { return h ? "white" : "#696969" } ).ofObject(), new go.Binding("fill","性别",genderBrushConverter)), $(go.Panel, "Horizontal", $(go.Panel, "Table", { maxSize: new go.Size(150,999), margin: new go.Margin(6,10,0,10), defaultAlignment: go.Spot.Left }, $(go.RowColumnDefinition, { column: 2, width: 4 }), $(go.TextBlock, textStyle(), { row: 0, column: 0, columnSpan: 5, font: "12pt Segoe UI,sans-serif", editable: true, isMultiline: false, minSize: new go.Size(10,16) }, new go.Binding("text","名字").makeTwoWay()), $(go.TextBlock, textStyle(), { row: 1, column: 0 }, new go.Binding("text","key",function(v) { return "key: " + v } )), $(go.TextBlock, textStyle(), { row: 1, column: 3, }, new go.Binding("text","parent",function(v) { return "parent: " + v } )), $(go.TextBlock, textStyle(), { row: 2, column: 0, }, new go.Binding("text","生年",function(v) { return "(" + v } )), $(go.TextBlock, textStyle(), { row: 2, column: 1, }, new go.Binding("text","卒年",function(v) { return " - " + (v ? v : " ") + ")" } )), $(go.TextBlock, textStyle(), { row: 3, column: 0, columnSpan: 5, font: "italic 9pt sans-serif", wrap: go.TextBlock.WrapFit, editable: true, minSize: new go.Size(10,14) }, new go.Binding("text","备注").makeTwoWay())))); myDiagram.nodeTemplate.contextMenu = $(go.Adornment, "Vertical", $("ContextMenuButton", $(go.TextBlock, "- 斩断 -"), { click: function(e, obj) { var node = obj.part.adornedPart; if (node !== null) { var thisemp = node.data; myDiagram.startTransaction("斩断"); myDiagram.model.setDataProperty(thisemp, "parent", "0"); myDiagram.commitTransaction("斩断") } } }), $("ContextMenuButton", $(go.TextBlock, "- 除名 -"), { click: function(e, obj) { var node = obj.part.adornedPart; if (node !== null) { var thisemp = node.data; myDiagram.startTransaction("vacate"); myDiagram.model.setDataProperty(thisemp, "名字", "(已除名)"); myDiagram.commitTransaction("vacate") } } }), $("ContextMenuButton", $(go.TextBlock, "- 驱逐 -"), { click: function(e, obj) { var node = obj.part.adornedPart; if (node !== null) { myDiagram.startTransaction("reparent remove"); var chl = node.findTreeChildrenNodes(); while (chl.next()) { var emp = chl.value; myDiagram.model.setParentKeyForNodeData(emp.data, 0) } myDiagram.model.removeNodeData(node.data); myDiagram.commitTransaction("reparent remove") } } }), $("ContextMenuButton", $(go.TextBlock, "- 灭门 -"), { click: function(e, obj) { var node = obj.part.adornedPart; if (node !== null) { myDiagram.startTransaction("remove dept"); myDiagram.removeParts(node.findTreeParts()); myDiagram.commitTransaction("remove dept") } } })); myDiagram.linkTemplate = $(go.Link, { routing: go.Link.Orthogonal, corner: 5, selectable: false }, $(go.Shape, { strokeWidth: 3, stroke: "#00FF00" })); myDiagram.model = go.Model.fromJson({ "class": "go.TreeModel", "nodeDataArray": [] }); if (window.Inspector) { myInspector = new Inspector("myInspector",myDiagram,{ properties: { "key": { readOnly: true }, "备注": {}, "parent": {} } }) } } function doSave(value, type, name) { var blob; if (typeof window.Blob == "function") { blob = new Blob([value],{ type: type }) } else { var BlobBuilder = window.BlobBuilder || window.MozBlobBuilder || window.WebKitBlobBuilder || window.MSBlobBuilder; var bb = new BlobBuilder(); bb.append(value); blob = bb.getBlob(type) } var URL = window.URL || window.webkitURL; var bloburl = URL.createObjectURL(blob); var anchor = document.createElement("a"); if ("download"in anchor) { anchor.style.visibility = "hidden"; anchor.href = bloburl; anchor.download = name; document.body.appendChild(anchor); var evt = document.createEvent("MouseEvents"); evt.initEvent("click", true, true); anchor.dispatchEvent(evt); document.body.removeChild(anchor) } else { if (navigator.msSaveBlob) { navigator.msSaveBlob(blob, name) } else { location.href = bloburl } } } function Save() { doSave(myDiagram.model.toJson(), "text/latex", "家谱.json"); myDiagram.isModified = false } function load(f) { if (typeof FileReader == "undefined") { alert("检测到您的浏览器不支持FileReader对象!") } var tmpFile = f[0]; var reader = new FileReader(); reader.readAsText(tmpFile); reader.onload = function(e) { myDiagram.model = go.Model.fromJson(e.target.result) } } function refresh() { myDiagram.model = go.Model.fromJson(myDiagram.model.toJson()) } function fitBrowserWindow() { myDiagram.div.style.height = window.innerHeight + "px"; myDiagram.requestUpdate() } function searchDiagram() { document.getElementById("answerCount").innerText = ""; var input = document.getElementById("mySearch"); if (!input) { return } input.focus(); var regex = new RegExp(input.value,"i"); myDiagram.startTransaction("highlight search"); myDiagram.clearHighlighteds(); if (input.value) { var queryObj = {}; if (document.getElementById("query").value != "在世") { queryObj[document.getElementById("query").value] = regex } else { queryObj.生年 = function(n) { return n <= input.value && n != "" } ; queryObj.卒年 = function(n) { return n >= input.value | n == "" } } var results = myDiagram.findNodesByExample(queryObj); myDiagram.highlightCollection(results); if (results.count > 0) { myDiagram.centerRect(results.first().actualBounds) } document.getElementById("answerCount").innerText = "共找到" + results.count + "个结果" } myDiagram.commitTransaction("highlight search") } window.onload = function() { init(); document.getElementById("mySearch").addEventListener("input", searchDiagram); document.getElementById("query").addEventListener("change", searchDiagram); document.getElementById("refresh").addEventListener("click", refresh) } ; window.onresize = fitBrowserWindow;
相关文章推荐
- 系统搜索功能不能使用,弹出“无法找到运行搜索助理需要的一个文件”
- 使用基本字节输入流,字节输出流一次读写一个字节来复制文本 文件
- Fileatream表示文件流,它能够打开和关闭文件,并对文件进行单字节的读写操作。 StreamReader和StreamWriter以文本方式对流进行读写操作。建立一个文本文件,分别使用上面两种方
- 将多个网页制作成一个CHM文件
- 使用 jquery 的 上传文件插件 uploadify 3.1 配合 java 来做一个简单的文件上次功能。并且在界面上有radio 的选择内容也要上传
- 使用CRichEditCtrl的stream功能读写文件
- 使用终端shell命令批量修改一个文件下的所有文件的读写权限
- 使用Java的多线程和IO流写一个文件复制功能类
- jQuery使用load()方法载入另外一个网页文件内的指定标签内容到div标签的方法
- 网络爬虫,用C#做一个网络爬虫demo,功能有保存网页、图片、js文件、等等其他的文件。有界面显示,有代码注释。
- Web基础入门(表格)-使用表格嵌套的形式制作一个简单的网页布局
- 使用JSP制作一个超简单的网页计算器的实例分享
- 如何使用CubeMx制作一个基于SD卡的文件系统工程
- 一个使用微软Azure blob实现文件下载功能的实例-附带源文件
- 系统搜索功能不能使用,弹出“无法找到运行搜索助理需要的一个文件”
- ASP网站数据采集程序制作:一个采集入库生成本地文件的几个FUCTION(可用来生成HTML静态网页)
- memalign vs malloc - 使用O_DIRECT参数open一个文件并读写
- 利用C语言文件读写做的一个文件拷贝功能
- 使用IO技术,创建一个目录,然后复制一个文件到该目录!实现复制的功能。(在博客园上传的第一份代码)
- [转]Ultra Fractal教程系列44——动画功能的使用02——制作一个缩放动画