Делаю редактор локаций на основе vis.js и столкнулся с проблемой:
Соединения, добавленные динамически (а это мне, собственно, и нужно) нормально отображаются при рендере, но при экспорте их, как будто и нет. Помогите, пожалуйста, гуру...
<!DOCTYPE html>
<html>
<head>
<meta charset="windows-1251"/>
<meta name="viewport" content="width=device-width, initial-scale=1">
<style>
#graph {
width: 100%;
height: 60vh;
border: 1px solid lightgray;
}
#positions {
width: 100%;
min-height: 32vh;
height: 100%;
}
table.legend_table {
font-size: 11px;
border-width:1px;
border-color:#d3d3d3;
border-style:solid;
}
table.legend_table,td {
border-width:1px;
border-color:#d3d3d3;
border-style:solid;
padding: 2px;
}
div.table_content {
width:80px;
text-align:center;
}
div.table_description {
width:100px;
}
#operation {
font-size:28px;
}
#network-popUp {
display:none;
position:absolute;
top:350px;
left:170px;
z-index:299;
width:250px;
height:120px;
background-color: #f9f9f9;
border-style:solid;
border-width:3px;
border-color: #5394ed;
padding:10px;
text-align: center;
}
</style>
</head>
<body>
<script type="text/javascript" src="https://unpkg.com/vis@4.21.0-EOL/dist/vis.js"></script>
<link href="https://unpkg.com/vis@4.21.0-EOL/dist/vis.css" rel="stylesheet" type="text/css" />
<div id="network-popUp">
<span id="operation">Узел</span> <br>
<table style="margin:auto;"><tr>
<td>ID</td><td><input id="node-id" value="new value" /></td>
</tr>
<tr>
<td>Название</td><td><input id="node-label" value="new value" /></td>
</tr></table>
<input type="button" value="Сохранить" id="saveButton" />
<input type="button" value="Отмена" id="cancelButton" />
</div>
<br />
<div id="graph"></div>
<div style="width:100%; text-align: center;">
<button style="display: inline-block;" id="generate-graph">Сгенерировать карту</button>
<button style="display: inline-block;" id="extract-positions" onclick="exportNetwork()">Экспорт карты</button>
<button style="display: inline-block;" id="load-positions" onclick="importNetwork()">Импорт карты</button>
</div>
<textarea id="positions"></textarea>
<script id="code">
var importButton;
var exportButton;
var nodes = []
var edges = []
importButton = document.getElementById('load-positions');
exportButton = document.getElementById('extract-positions');
const exportArea = document.getElementById('positions')
const container = document.getElementById('graph')
function resizeExportArea() {
exportArea.style.height = (1 + exportArea.scrollHeight) + "px";
}
const data = {
nodes: new vis.DataSet(nodes),
edges: new vis.DataSet(edges),
}
const options = {
locale: 'ru',
layout: {
improvedLayout: false,
},
"edges": {
"arrows": {
"to": {
"enabled": true
},
"from": {
"enabled": false
}
},
"dashes": false,
"shadow": {
"enabled": false
},
},
"interaction": {
"multiselect": true,
"navigationButtons": true
},
physics: {
barnesHut: {
gravitationalConstant: 0,
centralGravity: 0,
springLength: 0,
springConstant: 0,
damping: 0
},
repulsion: {
centralGravity: 0,
springLength: 0,
springConstant: 0,
nodeDistance: 0,
damping: 0
},
hierarchicalRepulsion: {
centralGravity: 0,
springLength: 0,
springConstant: 0,
nodeDistance: 0,
damping: 0
}
},
"nodes": {
"borderWidth": 0,
"borderWidthSelected": 0,
"color": {
"highlight": {},
"hover": {}
},
"shape": "box",
"shapeProperties": {
"borderRadius": 2
},
},
manipulation: {
addNode: function(data, callback) {
// filling in the popup DOM elements
document.getElementById('operation').innerHTML = "Добавить локацию";
document.getElementById('node-id').value = data.id;
document.getElementById('node-label').value = data.label;
document.getElementById('saveButton').onclick = saveData.bind(this, data, callback);
document.getElementById('cancelButton').onclick = clearPopUp.bind();
document.getElementById('network-popUp').style.display = 'block';
},
editNode: function(data, callback) {
// filling in the popup DOM elements
document.getElementById('operation').innerHTML = "Редактировать локацию";
document.getElementById('node-id').value = data.id;
document.getElementById('node-label').value = data.label;
document.getElementById('saveButton').onclick = saveData.bind(this, data, callback);
document.getElementById('cancelButton').onclick = cancelEdit.bind(this, callback);
document.getElementById('network-popUp').style.display = 'block';
},
addEdge: function(data, callback) {
if (data.from == data.to) {
var r = confirm("Do you want to connect the node to itself?");
if (r == true) {
callback(data);
}
} else {
callback(data);
}
}
}
}
let network = null
function clearPopUp() {
document.getElementById('saveButton').onclick = null;
document.getElementById('cancelButton').onclick = null;
document.getElementById('network-popUp').style.display = 'none';
}
function cancelEdit(callback) {
clearPopUp();
callback(null);
}
function saveData(data, callback) {
data.id = document.getElementById('node-id').value;
data.label = document.getElementById('node-label').value;
clearPopUp();
callback(data);
}
function initGraph() {
network = new vis.Network(container, data, options)
}
function getNodeData(data) {
var networkNodes = [];
data.forEach(function(elem, index, array) {
networkNodes.push({
id: elem.id,
label: elem.label,
x: elem.x,
y: elem.y,
connections: elem.connections
});
//console.log(networkNodes);
});
//
return new vis.DataSet(networkNodes);
}
function getNodeById(data, id) {
for (var n = 0; n < data.length; n++) {
if (data[n].id == id) { // double equals since id can be numeric or string
return data[n];
}
};
throw 'Не найден id \'' + id + '\' в данных';
}
function getEdgeData(data) {
var networkEdges = [];
data.forEach(function(node) {
// add the connection
node.connections.forEach(function(connId, cIndex, conns) {
networkEdges.push({
from: node.id,
to: connId
});
let cNode = getNodeById(data, connId);
var elementConnections = cNode.connections;
});
});
return new vis.DataSet(networkEdges);
}
function objectToArray(obj) {
return Object.keys(obj).map(function(key) {
obj[key].id = key;
return obj[key];
});
}
function addConnections(elem, index) {
elem.connections = network.getConnectedNodes(index);
console.log(elem.connections)
}
function clearOutputArea() {
exportArea.value = "";
}
function extend(first, second) {
for (var secondProp in second) {
var secondVal = second[secondProp];
if (secondVal && Object.prototype.toString.call(secondVal) === "[object Object]") {
first[secondProp] = first[secondProp] || {};
extend(first[secondProp], secondVal);
} else {
first[secondProp] = secondVal;
}
}
return first;
};
function exportNetwork() {
clearOutputArea();
var nodes = objectToArray(network.getPositions());
nodes.forEach(addConnections);
var nodeslabels = data.nodes.map(({
label
}) => ({
label
}))
var exportValue = JSON.stringify(extend(nodeslabels, nodes), null, 2)
exportArea.value = exportValue;
resizeExportArea();
}
function importNetwork() {
var inputValue = exportArea.value;
var inputData = JSON.parse(inputValue);
var data = {
nodes: getNodeData(inputData),
edges: getEdgeData(inputData)
}
network = new vis.Network(container, data, options)
resizeExportArea();
}
document.getElementById('generate-graph').addEventListener('click', initGraph)
</script>
</body>
</html>
Для наглядности скриншот (стрелками пометил места, где должны быть данные, но их нет)
Должно работать вот так (Это я вручную в JSON прописал связи и импортировал):