简介
完整项目资源链接:https://download.csdn.net/download/m0_46573428/87796553
项目详细信息请看:毕业设计:Vue3+FastApi+Python+Neo4j实现主题知识图谱网页应用——前言_人工智能技术小白修炼手册的博客-CSDN博客
数据标注部分涉及的主要功能是与标注工具相关的数据导入、数据浏览和数据保存等。具体而言,包括以下几个部分:
-
数据导入部分:包括数据预处理和数据存储两个阶段。数据预处理阶段调用了多种函数来对原始数据进行清洗和转换,以便在页面加载时更好地展示。其中包括读取Excel文件并将其转换为Json格式的数据,以及将Json格式的数据转换为特定格式的节点图数据。数据存储阶段则针对每个数据库创建DataFiles文件,将Json格式的数据写入到该文件中。
-
数据浏览部分:通过API接口实现,可以在前端页面上实时查询数据库的基本信息、所有数据集的列表以及每个数据集的详细信息。具体而言,可以查看数据集的总数、已标注数据的数量和数据集的名称和ID等信息,还可以查看每个数据集下的数据节点列表,包括数据节点的文本及其所对应的节点和关系信息。
-
数据保存部分:用于保存前端标注的数据。具体而言,函数可以将前端传来的标注数据按照一页一页地保存,每次只保存一个段落的数据。如果保存的数据与数据集中的文本相似,则会更新该文本的“nodes”和“rels”数据,并将更新后的数据再次写入到数据文件中。
主要代码
1. HTML部分
HTML代码渲染出了一个具有栏目布局的页面,包括左侧的属性栏、中间的标注区和右侧的图片展示区等,其中各个部分的具体功能与作用如下:
-
左侧属性栏:提供可拖动调整宽度的按钮和标签/属性列表,用户可以点击标签/属性名称将其添加到实体属性编辑区。
-
中间标注区:数据节点展示和编辑区,包括节点属性编辑区和关系列表,用于观察和编辑实体节点、关系和属性等,并且支持翻页查看和数据保存功能。
-
右侧图片展示区:用于展示相关实体或关系的图片信息。
2. JavaScript部分
JavaScript部分为Vue.js组件中的具体实现部分,其中包含了处理用户交互的各种函数,以及一些初始化和配置的代码。这些函数大致可分为以下几类:
-
数据标注:负责在节点展示区中添加和编辑实体节点、关系和属性等, 具体函数包括addNode()、addRels()和addProperties()等。
-
数据删除:可删除指定节点或关系的属性或整个节点或关系等,具体函数包括discardPro()和discardRel()等。
-
页面翻页:用于在标注区域之间进行翻页操作,具体函数有goPrev()和goNext()。
-
数据保存:将标注结果序列化并上传到服务器保存,具体函数为Save()。
-
数据查询:从服务器获取已有的标注数据集,具体函数为browse()。
其中所有的方法和函数都通过Vue.js组件实例中的data对象保存了需要使用的数据,成员变量包括labels和properties(标签和属性名称列表)、id(数据库中数据集的ID)、index(当前数据节点的下标),以及实际存储数据的nodes和rels。
JS部分方法说明
以下是对上述提到的JavaScript中的函数进行展开介绍:
1. addNode()
addNode()函数用于向当前数据集的节点列表中添加一个新的实体节点,并将该节点的属性设置为空对象。具体实现如下:
addNode: function (text) {
this.id++;
var newNode = {
名称: text,
};
this.dataAll[0][this.index].nodes["node" + this.id.toString()] = newNode;
},
函数会将新节点的名称设为text,并在dataAll的第一个元素中的nodes对象中新增一个node,命名格式为“node”+id。
2. addProperties()
addProperties()函数用于在选定的实体节点上添加一个新的属性,并将该属性默认值设为空字符串。具体实现如下:
addProperties: function (propName) {
var propsIndex = this.tmpIndex + 1;
this.dataAll[0][this.index].nodes[
"node" + propsIndex.toString()
][propName] = "";
},
函数会在实际存储数据的节点列表中找到所选的节点,对其新增一个键值对属性,键名为propName,初始值为空字符串。
3. addRels()
addRels()函数用于向当前数据集的关系列表中添加一条关系记录,并在关系记录中填入起始节点、结束节点和关系名称等信息。具体实现如下:
addRels: function () {
this.id++;
var rel = {
start: "",
end: "",
rel_name: "",
};
this.dataAll[0][this.index].rels["rel" + this.id.toString()] = rel;
},
函数首先将id值加1,然后创建一条新的关系记录,其中包含了起始节点、结束节点和关系名称等信息,最后在实际存储数据的rels对象中新增一条关系记录。
4. discardPro()
discardPro()函数用于删除指定节点上的属性键名及其对应键值。具体实现如下:
discardPro: function (i, ikey) {
i = i + 1;
delete this.dataAll[0][this.index].nodes["node" + i.toString()][ikey];
},
函数通过接收两个参数,分别为需要删除属性的节点所在的下标i,以及需要删除的属性键名ikey。具体逻辑为找到需要删除的节点,根据传入的i变量取出该节点,再据调用时传入的ikey变量删除该节点的参数。
5. discardRel()
discardRel()函数用于删除当前数据节点中某一条关系记录,究竟要删除第几条记录由参数i决定。具体代码如下:
discardRel: function (i) {
i = i + 1;
delete this.dataAll[0][this.index].rels["rel" + i.toString()];
},
函数通过将传入的参数i加1后作为被删关系的下标,再据此删除dataAll中rels的相应关系记录。
6. goPrev()
goPrev()函数用于在多组数据之间进行翻页操作,向前翻页。具体实现如下:
goPrev: function () {
if (this.index > 0) this.index--;
},
函数检查当前数据节点的下标index是否大于0,如果是则将其减1,表示向前进入上一组数据。
7. goNext()
goNext()函数用于在多组数据之间进行翻页操作,向后翻页。具体实现如下:
goNext: function () {
if (this.index + 1 < this.dataAll[0].length) this.index++;
},
函数检查当前数据节点的下标加1后是否小于dataAll中元素的个数,如果是,则将其加1,表示向后进入下一组数据。
8. Save()
Save()函数用于保存用户标注的数据,将当前数据集序列化为JSON格式并上传到服务器进行保存。具体实现如下:
Save: function () {
var json_body = JSON.stringify(this.dataAll[0]);
axios.get("http://localhost:8080/save", {
params: {
id: this.id,
text: this.dataAll[0][this.index].text,
data_text: json_body,
},
});
},
函数首先调用JSON.stringify()方法将当前标注完成的数据序列化为JSON字符串,然后结合axios发送请求将数据上传到服务器上的save接口。其中params对象中包含了需要上传的数据,即当前数据集的id、节点名称和标注内容等。
9. browse()
browse()函数用于从服务器查询已有的数据集,将查询到的数据集更新到dataAll变量中。
返回的数据类型为JSON格式,其中data属性包含了服务器返回的数据,如下示例:
{"status": "success","data": [{"id": 1,"text": "数据集1","nodes": {"node1": { "名称": "实体1", "属性1": "属性值1", "属性2": "属性值2" },"node2": { "名称": "实体2", "属性1": "", "属性3": "属性值3" }},"rels": {"rel1": { "start": "node1", "end": "node2", "rel_name": "关系1" }}},{"id": 2,"text": "数据集2","nodes": {"node1": { "名称": "实体1", "属性1": "属性值1", "属性3": "" },"node2": { "名称": "实体2", "属性2": "属性值2", "属性3": "属性值3" }},"rels": {"rel1": { "start": "node1", "end": "node2", "rel_name": "关系1" },"rel2": { "start": "node2", "end": "node1", "rel_name": "关系2" }}}]}
函数通过axios向服务器发送请求,获取存储在服务器上的JSON格式的数据集信息。然后将dataAll第一个元素(即数据节点列表)清空,并根据响应数据中的id、text、nodes和rels等属性更新dataAll中的数据节点列表。
10. 其他函数
除了上述的核心函数,JavaScript代码中还包含了一些其他的函数,如根据id获取特定数据集信息的get_data()函数、初始化Vue组件实例的init()函数和映射单个节点到编辑区的mapNode()函数等。这些函数在整个组件中起到辅助和初始化配置的作用。
前端
Html
<template>
<div>
<div id="main">
<textarea
@mouseup="mouseSelect()"
class="text"
v-model="dataAll[0][index].text"
></textarea>
<div class="four">
<div>
<h1>标签标注</h1>
<div style="height: 180px; overflow: scroll; margin-top: 10px">
<ul>
<li
@dblclick="addNode(item)"
v-for="(item, index) in labels"
:key="index"
>
{{ item }}
</li>
</ul>
</div>
</div>
<div>
<h1>属性标注</h1>
<div style="height: 180px; overflow: scroll; margin-top: 10px">
<ul>
<li
@dblclick="addProperties(item)"
v-for="(item, index) in properties"
:key="index"
>
{{ item }}
</li>
</ul>
</div>
</div>
<div>
<h1>实体</h1>
<div style="height: 180px; overflow: scroll; margin-top: 10px">
<ul class="ShiTi">
<li
v-for="(item, key, index) in dataAll[0][index].nodes"
:key="index"
>
<p
@dblclick="discardNode(index)"
@click="getTmpIndex(index)"
:class="index == tmpIndex ? 'selected' : ''"
>
{{ item.名称 }}
</p>
<p
@dblclick="discardPro(index, ikeys)"
v-for="(iitem, ikeys, iindex) in item"
:key="iindex"
>
<span> {{ ikeys }}:</span>
<span>{{ iitem }}</span>
</p>
</li>
</ul>
</div>
</div>
<div>
<h1 style="margin-bottom: 10px">关系</h1>
<select v-model="relType">
<option value="生产研发">生产研发</option>
<option value="属于">属于</option>
<option value="产国">产国</option>
</select>
<span class="add" @click="addRels()">新增</span>
<div style="height: 145px; overflow: scroll; margin-top: 10px">
<ul class="GuanXi">
<li
v-for="(item, key, index) in dataAll[0][index].rels"
:key="index"
>
<input v-model="item.start" />
<!-- <p>{{ dataAll[0][index].rels[key].start }}</p> -->
<!-- <input :value="dataAll[0][index].rels[key].start" /> -->
<p @dblclick="discardRel(index)">{{ item.rel_name }}</p>
<!-- <input :value="dataAll[0][index].rels[key]['end']" /> -->
<input v-model="item.end" />
</li>
</ul>
</div>
</div>
</div>
<img
@click="goPrev()"
style="cursor: pointer"
src="../../assets/left.svg"
/>
<img
@click="goNext()"
style="cursor: pointer"
src="../../assets/right.svg"
/>
<button @click="Save()">保 存</button>
</div>
</div>
</template>
Script
<script>
export default {
name: "Label",
props: {},
data() {
return {
tmpText: "",
labels: [
"XXX",
"XXX",
"XXX",
"XXX",
"XXX",
"XXX",
],
properties: [
"XXX",
"XXX",
"XXX",
"XXX",
"XXX",
"XXX",
],
nodes: [],
rels: [],
relType: "生产研发",
id: 1,
dataAll: [[{ text: "ceshi", nodes: { node1: {} }, rels: { rel1: {} } }]],
index: 0,
tmpIndex: -1,
dataText: "",
dataNodes: {},
dataRels: {},
};
},
methods: {
// 数据标注:双击删除属性
discardPro: function (i, ikey) {
i = i + 1;
delete this.dataAll[0][this.index].nodes["node" + i.toString()][ikey];
},
// 数据标注:双击删除关系
discardRel: function (i) {
i = i + 1;
console.log(i);
delete this.dataAll[0][this.index].rels["rel" + i.toString()];
console.log(this.dataAll[0][this.index].rels);
var count = 1;
var tmp = this.dataAll[0][this.index].rels;
this.dataAll[0][this.index].rels = {};
for (let key in tmp) {
this.dataAll[0][this.index].rels["rel" + count.toString()] = tmp[key];
count++;
};
console.log(this.dataAll[0][this.index].rels);
},
// 数据标注:双击删除实体
discardNode: function (i) {
i = i + 1;
delete this.dataAll[0][this.index].nodes["node" + i.toString()];
var count = 1;
var tmp = {};
tmp = this.dataAll[0][this.index].nodes;
this.dataAll[0][this.index].nodes = {};
for (let key in tmp) {
this.dataAll[0][this.index].nodes["node" + count.toString()] = tmp[key];
count++;
}
console.log("删除了");
console.log(this.dataAll[0][this.index].nodes);
},
// 数据标注:双击增加实体
addNode: function (item) {
console.log(this.dataAll[0][this.index]);
console.log(Object.keys(this.dataAll[0][this.index].nodes).length);
var nodesLength =
Object.keys(this.dataAll[0][this.index].nodes).length + 1;
this.dataAll[0][this.index].nodes["node" + nodesLength.toString()] = {
label: [item],
名称: this.tmpText,
};
},
// 数据标注:增加关系
addRels: function () {
this.rels.push();
var relsLength = Object.keys(this.dataAll[0][this.index].rels).length + 1;
console.log(this.index, relsLength, this.relType);
this.dataAll[0][this.index].rels["rel" + relsLength.toString()] = {
start: "",
end: "",
rel_name: this.relType,
};
},
// 数据标注:双击增加属性
addProperties: function (item) {
var i = this.tmpIndex + 1;
this.dataAll[0][this.index].nodes["node" + i.toString()][item] =
this.tmpText;
console.log(
item,
this.tmpText,
this.dataAll[0][this.index].nodes["node" + i.toString()]
);
},
getTmpIndex: function (i) {
if (this.tmpIndex == i) {
this.tmpIndex = -1;
} else if (this.tmpIndex != i) {
this.tmpIndex = i;
}
},
// 数据标注:点击前翻
goPrev: function () {
this.index--;
if (this.index < 0) {
this.index = this.dataAll[0].length - 1;
}
var count = 1;
var tmp = {};
tmp = this.dataAll[0][this.index].nodes;
this.dataAll[0][this.index].nodes = {};
for (let key in tmp) {
this.dataAll[0][this.index].nodes["node" + count.toString()] = tmp[key];
count++;
};
console.log(this.index);
},
// 数据标注:点击后翻
goNext: function () {
this.index++;
if (this.index > this.dataAll[0].length - 1) {
this.index = 0;
}
var count = 1;
var tmp = {};
tmp = this.dataAll[0][this.index].nodes;
this.dataAll[0][this.index].nodes = {};
for (let key in tmp) {
this.dataAll[0][this.index].nodes["node" + count.toString()] = tmp[key];
count++;
};
console.log(this.dataAll[0].length);
console.log(this.index);
},
mouseSelect: function () {
this.tmpText = window.getSelection().toString();
console.log(this.tmpText);
},
browse: function () {
this.axios
.get("http://localhost:8000/Browse?database_id=" + this.id)
.then((res) => {
if (res.data == "数据为空") {
alert("数据集为空");
}
console.log("Label的:", res.data);
this.dataAll = res.data;
var count = 1;
var tmp = {};
tmp = this.dataAll[0][this.index].nodes;
this.dataAll[0][this.index].nodes = {};
for (let key in tmp) {
this.dataAll[0][this.index].nodes["node" + count.toString()] =
tmp[key];
count++;
}
})
.catch((error) => {
console.log(error);
alert("Browse 失败");
});
},
// 数据处理:数据保存
Save: function () {
console.log(this.dataAll);
var data = JSON.stringify(this.dataAll);
console.log(data);
this.axios
.get(
"http://localhost:8000/DataSave?dataInput=" +
data +
"&database_id=" +
this.id
)
.then((res) => {
alert(res.data.msg);
})
.catch((error) => {
alert("DataSave failed!");
console.log(error);
});
},
},
mounted() {
console.log(this.dataAll[0][this.index].text);
this.id = this.$route.params.id;
this.browse();
},
};
</script>
Css
<style scoped>
* {
color: #555;
}
#main {
text-align: center;
}
select {
padding-left: 5px;
width: 90px;
border: #ddd solid 1px;
border-radius: 5px;
background: #eee;
font-size: 14px;
}
.selected {
background-color: #ddd;
}
.add {
font-size: 14px;
color: #444;
margin-left: 10px;
font-size: bold;
}
.add:hover {
cursor: pointer;
}
textarea {
color: #444;
background: #eee;
resize: none;
}
.text {
float: left;
margin: 30px 0px 30px 30px;
padding: 10px;
font-size: 16px;
height: 140px;
width: 90%;
border: #ccc solid 1px;
border-radius: 10px;
float: left;
}
.four > div {
float: left;
margin-left: 29px;
margin-bottom: 25px;
border: #ccc solid 1px;
height: 230px;
width: 20.4%;
border-radius: 10px;
}
.four > div > div > ul > li {
list-style: none;
cursor: pointer;
font-size: 14px;
}
.four > div > div > ul > li:hover {
background: #ddd;
}
.four > div > h1 {
margin-top: 10px;
}
.ShiTi > li > p {
color: #486e53;
font-weight: bold;
font-size: 14px;
text-align: left;
margin-left: 10px;
}
.ShiTi > li > p > span:first-child {
font-size: 13px;
font-weight: bold;
}
.ShiTi > li > p > span:nth-child(2) {
font-size: 13px;
font-weight: 400;
}
.GuanXi > li {
border: #ddd solid 1px;
height: 49px;
width: 130px;
margin-left: 10px;
margin-bottom: 5px;
border-radius: 3px;
}
.GuanXi > li > p {
line-height: 18px;
font-size: 13px;
}
.GuanXi > li > input {
text-align: center;
border: none;
font-size: 13px;
height: 30%;
width: 100%;
font-size: 14px;
color: #363636;
background: #eee;
}
.GuanXi > li > p:nth-child(2) {
color: #486e53;
font-weight: bold;
}
img {
float: left;
height: 40px;
margin-left: 100px;
margin-right: 60px;
}
button {
cursor: pointer;
float: left;
border: none;
background: rgb(107, 146, 77);
color: white;
padding: 8px 10px;
font-weight: bold;
border-radius: 15px;
margin-left: 136px;
width: 190px;
}
</style>
后端
Browse(database_id)
以下代码为Python函数Browse(database_id)的定义及解释说明,具体如下:
1. 函数功能描述
该函数的作用是从数据库中读取指定database_id下的所有DataFiles文件中的数据,并转换为Json格式。函数返回Json对象组成的列表,表示Database下全部DataFiles所包含的标注数据。
2. 函数参数说明
函数需要一个参数:database_id,为需要读取哪个数据库下的DataFiles数据。
3. 函数返回值
函数返回值是一个Json对象组成的列表,表示Database下全部DataFiles所包含的标注数据。
4. 函数实现逻辑
首先调用DataExport()函数获取对应database_id下的所有DataFiles文件的路径列表files_list,然后针对每一个DataFiles文件,读取其内容并将其转化为Json格式的列表,并添加至data_list中,最终返回该列表。
5. 其他说明
在函数中使用了其他两个函数:DataExport()和changeJsonFormat()。其中DataExport(database_id)函数用于获取指定database_id下的DataFiles文件列表的完整路径,而changeJsonFormat()则用于将读取的文件内容字符串转化为Json对象。
# 读取数据库下DataFiles的所有文件数据
def Browse(database_id):
"""
作用:读取数据库下DataFiles的所有文件数据
输入:database_id
输出:数据库下DataFiles的所有文件数据
"""
data_list = []
data = ''
files_list = DataExport(database_id)
name = FindDatabase(database_id)
for f_path in files_list:
c = ''
for f in open(f_path, 'r', encoding='utf-8'):
c += f
# print(c)
data_list.append(changeJsonFormat(c))
if len(data_list) == 0:
return '数据为空'
print('Browse', '成功')
return data_list
# print(Browse(database_id = '1003172907'))
DataSave(dataInput, database_id)
以下代码为Python函数DataSave(dataInput, database_id)的定义及解释说明,具体如下:
1. 函数功能描述
该函数的作用是保存前端标注的数据。\n 这里假设数据一段一段地进行保存。函数先将前端传来的数据项按照段落(即一页)来存储,每次只保存一个段落的数据。函数先清空已有的数据文件,然后将新的数据写入到文件中,以字符串的形式写入每一行。
然后,函数会检查每个段落标注数据与数据集中已有的文本是否相似。如果有相似的文本,则更新该文本对应的“nodes”和“rels”数据,并将更新后的数据再次写入到数据文件中。
最后,函数会打印执行结果提示信息。
2. 函数参数说明
函数需要两个参数:dataInput和database_id,分别表示前端传来的标注数据和该数据所在的数据库ID。
3. 函数返回值
函数并没有返回任何值。
4. 函数实现逻辑
首先调用DataPath(database_id)函数获取对应database_id下的DataFiles文件路径列表files_list,接着遍历每个文件,使用"w"模式打开文件清空原内容,接着遍历每个段落数据,使用"a"模式打开文件并将段落数据按行写入文件中。
接着,函数针对每个段落数据,遍历files_list文件列表,读取文件内容,逐行解析为Json格式的对象,并将这些对象存放在一个列表data_list中。随后,函数会检查保存的数据是否与数据集的已有文本相似。如果文本相似,则更新该文本对应的节点("nodes")和关系("rels")信息,并将更新后的数据写入数据文件中。
最后,再次使用"a"模式打开文件并将更新后的数据按行写入文件中,以覆盖原有的数据。
5. 其他说明
函数中使用了其他三个函数:DataPath()、Eval()和Print()。其中DataPath(database_id)函数用于获取指定database_id下的DataFiles文件路径列表,而Eval()函数用于将读取的文件内容字符串解析为Json对象。Print()方法用于在控制台打印函数执行成功的提示信息。
# 保存前端标注的数据,每次保存一个段落(即一页)的数据
def DataSave(dataInput, database_id):
"""
作用:保存前端标注的数据
输入:前端传来的标注数据、数据集id
输出:
"""
files_list = DataPath(database_id)
# 遍历数据集中的DataFiles,只有一个
for f_path in files_list:
# 清空原文件
with open(f_path, 'w', encoding='utf-8') as f:
f.write('')
# 写入新内容
for i in dataInput:
with open(f_path, 'a', encoding='utf-8') as f:
f.write(json.dumps(i, ensure_ascii=False))
f.write('\n')
print('DataSave', '成功')
# 读取段落数据的key(“id”)和value
for key, item in dataInput.items():
# 遍历数据集中的DataFiles
for f_path in files_list:
data_list = []
# 遍历file里的data(按行读取)
for f in open(f_path, 'r', encoding='utf-8'):
data_list.append(eval(f))
for i in data_list:
data = i
for original_key, original_item in i.items():
# 匹配输入数据的文本与数据集中的文本,匹配相似率大于0.95,则用新数据覆盖旧文本中的"nodes"和"rels"数据
if item['oid'] == original_item['oid']:
data_list.remove(i)
data['id']['text'] = item['text']
data['id']['nodes'] = item['nodes']
data['id']['rels'] = item['rels']
data_list = [data] + data_list
# 清空原文件
with open(f_path, 'w', encoding='utf-8') as f:
f.write('')
# 写入新内容
for i in data_list:
with open(f_path, 'a', encoding='utf-8') as f:
f.write(json.dumps(i, ensure_ascii=False))
f.write('\n')