您的位置:首页 > Web前端 > JavaScript

使用gojs制作一个具备文件读写功能的家谱网页

2018-03-17 01:07 856 查看
效果

一些功能

数据结构

实现

文件结构

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;
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: 
相关文章推荐