|
|
@ -1,6 +1,8 @@ |
|
|
|
const canvas = document.getElementById("canvas"); |
|
|
|
const canvas = document.getElementById("canvas"); |
|
|
|
|
|
|
|
|
|
|
|
// Layouts
|
|
|
|
//-----------------------------------------------------------------------------
|
|
|
|
|
|
|
|
//-----------------------------Layouts-----------------------------------------
|
|
|
|
|
|
|
|
//-----------------------------------------------------------------------------
|
|
|
|
var width = canvas.offsetWidth; |
|
|
|
var width = canvas.offsetWidth; |
|
|
|
var height = canvas.offsetHeight; |
|
|
|
var height = canvas.offsetHeight; |
|
|
|
|
|
|
|
|
|
|
@ -54,6 +56,7 @@ const edge_text_layout = { |
|
|
|
align: 'center' |
|
|
|
align: 'center' |
|
|
|
}; |
|
|
|
}; |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
//-----------------------------------------------------------------------------
|
|
|
|
//-----------------------------------------------------------------------------
|
|
|
|
//-------------------------------Init------------------------------------------
|
|
|
|
//-------------------------------Init------------------------------------------
|
|
|
|
//-----------------------------------------------------------------------------
|
|
|
|
//-----------------------------------------------------------------------------
|
|
|
@ -62,6 +65,7 @@ var node_layer = new Konva.Layer(); |
|
|
|
|
|
|
|
|
|
|
|
var konva_edges = []; |
|
|
|
var konva_edges = []; |
|
|
|
var konva_nodes = []; |
|
|
|
var konva_nodes = []; |
|
|
|
|
|
|
|
|
|
|
|
var graph_copy = {}; |
|
|
|
var graph_copy = {}; |
|
|
|
|
|
|
|
|
|
|
|
var state = "idle"; |
|
|
|
var state = "idle"; |
|
|
@ -73,10 +77,9 @@ var path_elements = []; |
|
|
|
// (additions) auto layout
|
|
|
|
// (additions) auto layout
|
|
|
|
var auto_layout = false; |
|
|
|
var auto_layout = false; |
|
|
|
|
|
|
|
|
|
|
|
function initialisation(){ |
|
|
|
function initialization() { |
|
|
|
stage.add(edge_layer); |
|
|
|
stage.add(edge_layer); |
|
|
|
stage.add(node_layer); |
|
|
|
stage.add(node_layer); |
|
|
|
|
|
|
|
|
|
|
|
// avoid flickering
|
|
|
|
// avoid flickering
|
|
|
|
edge_layer.hide(); |
|
|
|
edge_layer.hide(); |
|
|
|
|
|
|
|
|
|
|
@ -89,7 +92,6 @@ function initialisation(){ |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
// (additions) auto layout
|
|
|
|
// (additions) auto layout
|
|
|
|
|
|
|
|
|
|
|
|
if (sessionStorage.getItem("auto_layout") !== null) { |
|
|
|
if (sessionStorage.getItem("auto_layout") !== null) { |
|
|
|
// Restore the contents of the text field
|
|
|
|
// Restore the contents of the text field
|
|
|
|
auto_layout = (sessionStorage.getItem("auto_layout") == 'true'); |
|
|
|
auto_layout = (sessionStorage.getItem("auto_layout") == 'true'); |
|
|
@ -101,13 +103,12 @@ function initialisation(){ |
|
|
|
for (var i = 0; i < 1000; i++) { |
|
|
|
for (var i = 0; i < 1000; i++) { |
|
|
|
force_directed_graph(); |
|
|
|
force_directed_graph(); |
|
|
|
} |
|
|
|
} |
|
|
|
node_layer.draw(); |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
edge_layer.show(); |
|
|
|
edge_layer.show(); |
|
|
|
|
|
|
|
|
|
|
|
} |
|
|
|
} |
|
|
|
initialisation(); |
|
|
|
initialization(); |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
//-----------------------------------------------------------------------------
|
|
|
|
//-----------------------------------------------------------------------------
|
|
|
|
//------------------------------utils------------------------------------------
|
|
|
|
//------------------------------utils------------------------------------------
|
|
|
|
//-----------------------------------------------------------------------------
|
|
|
|
//-----------------------------------------------------------------------------
|
|
|
@ -116,16 +117,24 @@ function sort(array) { |
|
|
|
return a - b; |
|
|
|
return a - b; |
|
|
|
}) |
|
|
|
}) |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
function show_error(msg) { |
|
|
|
|
|
|
|
let infobar = document.querySelector("#infobar"); |
|
|
|
|
|
|
|
infobar.innerHTML = msg; |
|
|
|
|
|
|
|
infobar.classList.add("active"); |
|
|
|
|
|
|
|
window.setTimeout(function() { |
|
|
|
|
|
|
|
infobar.classList.remove("active"); |
|
|
|
|
|
|
|
}, 4000); |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
//-----------------------------------------------------------------------------
|
|
|
|
//-----------------------------------------------------------------------------
|
|
|
|
//---------------------------draw methods--------------------------------------
|
|
|
|
//---------------------------draw methods--------------------------------------
|
|
|
|
//-----------------------------------------------------------------------------
|
|
|
|
//-----------------------------------------------------------------------------
|
|
|
|
function draw_new_node(name, x, y) { |
|
|
|
function draw_new_node(name, x, y) { |
|
|
|
const pos = { |
|
|
|
|
|
|
|
x: x, |
|
|
|
|
|
|
|
y: y |
|
|
|
|
|
|
|
}; |
|
|
|
|
|
|
|
let group = new Konva.Group({ |
|
|
|
let group = new Konva.Group({ |
|
|
|
...pos, |
|
|
|
x: x, |
|
|
|
|
|
|
|
y: y, |
|
|
|
draggable: true, |
|
|
|
draggable: true, |
|
|
|
dragBoundFunc: function(pos) { |
|
|
|
dragBoundFunc: function(pos) { |
|
|
|
return { |
|
|
|
return { |
|
|
@ -136,7 +145,10 @@ function draw_new_node(name, x, y) { |
|
|
|
id: name |
|
|
|
id: name |
|
|
|
}); |
|
|
|
}); |
|
|
|
let box = new Konva.Circle(node_layout); |
|
|
|
let box = new Konva.Circle(node_layout); |
|
|
|
let simpleText = new Konva.Text({...node_text_layout, text: name}); |
|
|
|
let simpleText = new Konva.Text({ |
|
|
|
|
|
|
|
...node_text_layout, |
|
|
|
|
|
|
|
text: name |
|
|
|
|
|
|
|
}); |
|
|
|
group.add(box); |
|
|
|
group.add(box); |
|
|
|
group.add(simpleText); |
|
|
|
group.add(simpleText); |
|
|
|
group.on("click", on_node_click); |
|
|
|
group.on("click", on_node_click); |
|
|
@ -171,7 +183,10 @@ function draw_new_edge(edge) { |
|
|
|
id: edge["start"] + edge["end"] |
|
|
|
id: edge["start"] + edge["end"] |
|
|
|
}); |
|
|
|
}); |
|
|
|
let line = new Konva.Line(edge_layout); |
|
|
|
let line = new Konva.Line(edge_layout); |
|
|
|
let simpleText = new Konva.Text({...edge_text_layout, text: weight}); |
|
|
|
let simpleText = new Konva.Text({ |
|
|
|
|
|
|
|
...edge_text_layout, |
|
|
|
|
|
|
|
text: weight |
|
|
|
|
|
|
|
}); |
|
|
|
|
|
|
|
|
|
|
|
graph_copy[edge["start"]].push(edge["end"]); |
|
|
|
graph_copy[edge["start"]].push(edge["end"]); |
|
|
|
graph_copy[edge["end"]].push(edge["start"]); |
|
|
|
graph_copy[edge["end"]].push(edge["start"]); |
|
|
@ -194,6 +209,7 @@ function update_edge(konva_edge) { |
|
|
|
let end_node = konva_edge.attrs.end_node; |
|
|
|
let end_node = konva_edge.attrs.end_node; |
|
|
|
let size = 15 + text.text().length * 15; |
|
|
|
let size = 15 + text.text().length * 15; |
|
|
|
let line_length = Math.sqrt((start_node.getX() - end_node.getX()) ** 2 + (start_node.getY() - end_node.getY()) ** 2) - size; |
|
|
|
let line_length = Math.sqrt((start_node.getX() - end_node.getX()) ** 2 + (start_node.getY() - end_node.getY()) ** 2) - size; |
|
|
|
|
|
|
|
|
|
|
|
line.points([start_node.getX(), start_node.getY(), end_node.getX(), end_node.getY()]); |
|
|
|
line.points([start_node.getX(), start_node.getY(), end_node.getX(), end_node.getY()]); |
|
|
|
line.dash([line_length / 2, size, line_length / 2]); |
|
|
|
line.dash([line_length / 2, size, line_length / 2]); |
|
|
|
text.x((start_node.getX() + end_node.getX()) / 2 - node_size); |
|
|
|
text.x((start_node.getX() + end_node.getX()) / 2 - node_size); |
|
|
@ -263,21 +279,44 @@ function on_node_click(e) { |
|
|
|
draw_selected_nodes(); |
|
|
|
draw_selected_nodes(); |
|
|
|
}; |
|
|
|
}; |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
//-----------------------------------------------------------------------------
|
|
|
|
|
|
|
|
//---------------------------REST methods--------------------------------------
|
|
|
|
|
|
|
|
//-----------------------------------------------------------------------------
|
|
|
|
|
|
|
|
function post(endpoint, data) { |
|
|
|
|
|
|
|
return fetch(url + endpoint, { |
|
|
|
|
|
|
|
method: 'POST', |
|
|
|
|
|
|
|
body: JSON.stringify(data), |
|
|
|
|
|
|
|
headers: { |
|
|
|
|
|
|
|
'Content-Type': 'application/json' |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
}).then(res => res.json()) |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
function create_node() { |
|
|
|
function create_node() { |
|
|
|
if (state != "create_node") { |
|
|
|
if (state != "create_node") { |
|
|
|
state = "create_node"; |
|
|
|
state = "create_node"; |
|
|
|
// let mousePos = stage.getPointerPosition();
|
|
|
|
// let mousePos = stage.getPointerPosition();
|
|
|
|
let pos = {x: width/2, y: height/2}; |
|
|
|
let pos = { |
|
|
|
|
|
|
|
x: width / 2, |
|
|
|
|
|
|
|
y: height / 2 |
|
|
|
|
|
|
|
}; |
|
|
|
create_input(pos.x, pos.y).then(function(text) { |
|
|
|
create_input(pos.x, pos.y).then(function(text) { |
|
|
|
|
|
|
|
post("nodes", { |
|
|
|
|
|
|
|
name: text |
|
|
|
|
|
|
|
}) |
|
|
|
|
|
|
|
.then(function(res) { |
|
|
|
|
|
|
|
if (res["error"]) { |
|
|
|
|
|
|
|
show_error(res["error"]) |
|
|
|
|
|
|
|
} else { |
|
|
|
draw_new_node(text, pos.x, pos.y); |
|
|
|
draw_new_node(text, pos.x, pos.y); |
|
|
|
|
|
|
|
} |
|
|
|
state = "idle"; |
|
|
|
state = "idle"; |
|
|
|
|
|
|
|
}) |
|
|
|
|
|
|
|
.catch(error => console.error('Error:', error)); |
|
|
|
}, function() {state = "idle"}); |
|
|
|
}, function() {state = "idle"}); |
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
function create_edge() { |
|
|
|
function create_edge() { |
|
|
|
if (selected.length < 2) { |
|
|
|
if (selected.length < 2) { |
|
|
|
show_error("Select at least 2 nodes."); |
|
|
|
show_error("Select at least 2 nodes."); |
|
|
@ -286,6 +325,15 @@ function create_edge() { |
|
|
|
let x = (selected[0].getX() + selected[1].getX()) / 2; |
|
|
|
let x = (selected[0].getX() + selected[1].getX()) / 2; |
|
|
|
let y = (selected[0].getY() + selected[1].getY()) / 2; |
|
|
|
let y = (selected[0].getY() + selected[1].getY()) / 2; |
|
|
|
create_input(x, y).then(function(text) { |
|
|
|
create_input(x, y).then(function(text) { |
|
|
|
|
|
|
|
post("edges", { |
|
|
|
|
|
|
|
start: selected[0].id(), |
|
|
|
|
|
|
|
end: selected[1].id(), |
|
|
|
|
|
|
|
weight: text |
|
|
|
|
|
|
|
}) |
|
|
|
|
|
|
|
.then(function(res) { |
|
|
|
|
|
|
|
if (res["error"]) { |
|
|
|
|
|
|
|
show_error(res["error"]) |
|
|
|
|
|
|
|
} else { |
|
|
|
draw_new_edge({ |
|
|
|
draw_new_edge({ |
|
|
|
"start": selected[0].children[1].text(), |
|
|
|
"start": selected[0].children[1].text(), |
|
|
|
"end": selected[1].children[1].text(), |
|
|
|
"end": selected[1].children[1].text(), |
|
|
@ -293,45 +341,74 @@ function create_edge() { |
|
|
|
}); |
|
|
|
}); |
|
|
|
selected = []; |
|
|
|
selected = []; |
|
|
|
draw_selected_nodes(); |
|
|
|
draw_selected_nodes(); |
|
|
|
},function(){}); |
|
|
|
|
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
}) |
|
|
|
|
|
|
|
.catch(error => console.error('Error:', error)); |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
}, function() {}); |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
function create_input(x, y) { |
|
|
|
function create_input(x, y) { |
|
|
|
return new Promise(function(resolve, reject) { |
|
|
|
return new Promise(function(resolve, reject) { |
|
|
|
|
|
|
|
var container = document.createElement("div"); |
|
|
|
|
|
|
|
container.classList.add("background"); |
|
|
|
var input = document.createElement("input"); |
|
|
|
var input = document.createElement("input"); |
|
|
|
|
|
|
|
var clear = function() { |
|
|
|
|
|
|
|
container.remove(); |
|
|
|
|
|
|
|
stage.listening(true); |
|
|
|
|
|
|
|
focus = true; |
|
|
|
|
|
|
|
} |
|
|
|
input.setAttribute("type", "text"); |
|
|
|
input.setAttribute("type", "text"); |
|
|
|
input.style.left = x + "px"; |
|
|
|
input.style.left = x + "px"; |
|
|
|
input.style.top = y + "px"; |
|
|
|
input.style.top = y + "px"; |
|
|
|
canvas.appendChild(input); |
|
|
|
canvas.appendChild(container); |
|
|
|
|
|
|
|
container.appendChild(input); |
|
|
|
input.focus(); |
|
|
|
input.focus(); |
|
|
|
stage.listening(false); |
|
|
|
stage.listening(false); |
|
|
|
|
|
|
|
focus = false; |
|
|
|
window.addEventListener("keyup", function(event) { |
|
|
|
window.addEventListener("keyup", function(event) { |
|
|
|
if (event.key === "Escape") { |
|
|
|
if (event.key === "Escape") { |
|
|
|
input.remove(); |
|
|
|
|
|
|
|
stage.listening(true); |
|
|
|
|
|
|
|
reject(); |
|
|
|
reject(); |
|
|
|
|
|
|
|
clear(); |
|
|
|
} |
|
|
|
} |
|
|
|
}); |
|
|
|
}); |
|
|
|
input.addEventListener("keyup", function(event) { |
|
|
|
input.addEventListener("keyup", function(event) { |
|
|
|
if (event.key === "Enter") { |
|
|
|
if (event.key === "Enter") { |
|
|
|
resolve(input.value); |
|
|
|
resolve(input.value); |
|
|
|
input.remove(); |
|
|
|
clear(); |
|
|
|
stage.listening(true); |
|
|
|
|
|
|
|
} |
|
|
|
} |
|
|
|
}); |
|
|
|
}); |
|
|
|
}); |
|
|
|
}); |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
function find_path(){ |
|
|
|
function find_path(data) { |
|
|
|
draw_path(elem); |
|
|
|
if (data.length < 2) { |
|
|
|
|
|
|
|
show_error("Select at least 2 nodes."); |
|
|
|
|
|
|
|
return; |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
console.log(url + "paths/" + data[0].attrs.id + "/" + data[1].attrs.id); |
|
|
|
|
|
|
|
fetch(url + "paths/" + data[0].attrs.id + "/" + data[1].attrs.id).then(res => res.json()) |
|
|
|
|
|
|
|
.then(function(res) { |
|
|
|
|
|
|
|
if (res["error"]) { |
|
|
|
|
|
|
|
show_error(res["error"]); |
|
|
|
|
|
|
|
} else { |
|
|
|
|
|
|
|
selected = []; |
|
|
|
|
|
|
|
draw_path(res["path"]); |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
}) |
|
|
|
|
|
|
|
.catch(error => console.error('Error:', error)); |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
//-----------------------------------------------------------------------------
|
|
|
|
|
|
|
|
//------------------------background methods-----------------------------------
|
|
|
|
|
|
|
|
//-----------------------------------------------------------------------------
|
|
|
|
function update_edges(edges) { |
|
|
|
function update_edges(edges) { |
|
|
|
for (id in edges) { |
|
|
|
for (id in edges) { |
|
|
|
update_edge(edges[id]); |
|
|
|
update_edge(edges[id]); |
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
node_layer.on('beforeDraw', function() { |
|
|
|
node_layer.on('beforeDraw', function() { |
|
|
|
update_edges(konva_edges); |
|
|
|
update_edges(konva_edges); |
|
|
|
}); |
|
|
|
}); |
|
|
@ -346,10 +423,8 @@ function fitStageIntoParentContainer() { |
|
|
|
} |
|
|
|
} |
|
|
|
window.addEventListener('resize', fitStageIntoParentContainer); |
|
|
|
window.addEventListener('resize', fitStageIntoParentContainer); |
|
|
|
|
|
|
|
|
|
|
|
//-----------------------------------------------------------------------------
|
|
|
|
|
|
|
|
//---------------------(additions) auto layout---------------------------------
|
|
|
|
|
|
|
|
//-----------------------------------------------------------------------------
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
//---------------------(additions) auto layout---------------------------------
|
|
|
|
function toggle_auto_layout() { |
|
|
|
function toggle_auto_layout() { |
|
|
|
auto_layout = !auto_layout; |
|
|
|
auto_layout = !auto_layout; |
|
|
|
document.querySelector("[data-activeon=auto_layout]").classList.toggle("active"); |
|
|
|
document.querySelector("[data-activeon=auto_layout]").classList.toggle("active"); |
|
|
@ -415,22 +490,13 @@ function force_directed_graph(spring_length = 300, step = 0.004){ |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
var anim = new Konva.Animation(function(frame) { |
|
|
|
var anim = new Konva.Animation(function(frame) { |
|
|
|
if (auto_layout) { |
|
|
|
if (auto_layout && focus) { |
|
|
|
const spring_length = 300; |
|
|
|
|
|
|
|
const step = 0.00025 * frame.timeDiff; |
|
|
|
const step = 0.00025 * frame.timeDiff; |
|
|
|
force_directed_graph(spring_length, step) |
|
|
|
force_directed_graph(300, step) |
|
|
|
} |
|
|
|
} |
|
|
|
}, node_layer); |
|
|
|
}, node_layer); |
|
|
|
anim.start(); |
|
|
|
anim.start(); |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
function show_error(msg){ |
|
|
|
|
|
|
|
let infobar = document.querySelector("#infobar"); |
|
|
|
|
|
|
|
infobar.innerHTML = msg; |
|
|
|
|
|
|
|
infobar.classList.add("active"); |
|
|
|
|
|
|
|
window.setTimeout(function(){ infobar.classList.remove("active"); }, 4000); |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// bug: this avoids glitches
|
|
|
|
// bug: this avoids glitches
|
|
|
|
window.onfocus = function () {auto_layout = true;}; |
|
|
|
window.onfocus = function() {focus = true;}; |
|
|
|
window.onblur = function () {auto_layout = false;}; |
|
|
|
window.onblur = function() {focus = false;}; |
|
|
|