Frontend requiremets finished

delete_edge
Ugo Finnendahl 5 years ago
parent 8494cc6992
commit e2bb890704
  1. 8
      frontend/assets/css/src/style.scss
  2. 2
      frontend/assets/css/style.min.css
  3. 256
      frontend/assets/js/canvas.js
  4. 28
      frontend/index.html

@ -37,6 +37,14 @@ main{
position: relative; position: relative;
height: 100%; height: 100%;
.background{
position: absolute;
top:0;
left:0;
background-color: rgba(0,0,0,0.5);
width: 100%;
height: 100%;
}
input{ input{
position: absolute; position: absolute;
border: none; border: none;

@ -1 +1 @@
*{margin:0;padding:0;box-sizing:border-box}body{font-family:Roboto}main{position:relative;height:100vh}main #infobar{position:absolute;top:0;width:100%;padding:0.4em;background-color:rgba(232,9,9,0.4);border:4px solid rgba(232,9,9,0.8);text-align:center;font-weight:bold;font-size:1.8em;z-index:1;transform:translateY(-100%);transition:transform 1s}main #infobar.active{transform:translateY(0%)}main .canvas{background-color:lightgrey;position:relative;height:100%}main .canvas input{position:absolute;border:none;padding:0.6em 1em;width:50px;transform:translateX(-50%) translateY(-50%)}main nav,main footer{display:flex;position:absolute;top:1em;left:1em;opacity:0.9}main nav button,main footer button{display:block;background-color:#939292;border:none;border-radius:1em;padding:1em 1em;min-width:8em;margin:0.4em;color:black;border:solid 2px #131313;box-shadow:2px 2px 8px rgba(0,0,0,0.5)}main nav button:focus,main footer button:focus{outline:none}main nav button::-moz-focus-inner,main footer button::-moz-focus-inner{border:0}main nav button:hover,main footer button:hover{background-color:#acacac;border-color:#000;cursor:pointer}main nav button.active,main footer button.active{background-color:#f08d19}main nav button h1,main footer button h1{text-align:center;font-size:1.4em;font-weight:normal;white-space:nowrap}main nav button img,main footer button img{display:block;margin:0 auto 0.5em auto;width:2em}main footer{top:auto;left:auto;bottom:1em;right:1em} *{margin:0;padding:0;box-sizing:border-box}body{font-family:Roboto}main{position:relative;height:100vh}main #infobar{position:absolute;top:0;width:100%;padding:0.4em;background-color:rgba(232,9,9,0.4);border:4px solid rgba(232,9,9,0.8);text-align:center;font-weight:bold;font-size:1.8em;z-index:1;transform:translateY(-100%);transition:transform 1s}main #infobar.active{transform:translateY(0%)}main .canvas{background-color:lightgrey;position:relative;height:100%}main .canvas .background{position:absolute;top:0;left:0;background-color:rgba(0,0,0,0.5);width:100%;height:100%}main .canvas input{position:absolute;border:none;padding:0.6em 1em;width:50px;transform:translateX(-50%) translateY(-50%)}main nav,main footer{display:flex;position:absolute;top:1em;left:1em;opacity:0.9}main nav button,main footer button{display:block;background-color:#939292;border:none;border-radius:1em;padding:1em 1em;min-width:8em;margin:0.4em;color:black;border:solid 2px #131313;box-shadow:2px 2px 8px rgba(0,0,0,0.5)}main nav button:focus,main footer button:focus{outline:none}main nav button::-moz-focus-inner,main footer button::-moz-focus-inner{border:0}main nav button:hover,main footer button:hover{background-color:#acacac;border-color:#000;cursor:pointer}main nav button.active,main footer button.active{background-color:#f08d19}main nav button h1,main footer button h1{text-align:center;font-size:1.4em;font-weight:normal;white-space:nowrap}main nav button img,main footer button img{display:block;margin:0 auto 0.5em auto;width:2em}main footer{top:auto;left:auto;bottom:1em;right:1em}

@ -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;
@ -31,8 +33,8 @@ const node_text_layout = {
y: 2 - node_size, y: 2 - node_size,
text: name, text: name,
fontSize: 22, fontSize: 22,
width: 2 * node_size-1, width: 2 * node_size - 1,
height: 2 * node_size-1, height: 2 * node_size - 1,
fontFamily: 'Roboto', fontFamily: 'Roboto',
fill: 'black', fill: 'black',
verticalAlign: 'middle', verticalAlign: 'middle',
@ -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,11 +92,10 @@ 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');
if (auto_layout){ if (auto_layout) {
document.querySelector("[data-activeon=auto_layout]").classList.add("active"); document.querySelector("[data-activeon=auto_layout]").classList.add("active");
} }
} }
@ -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);
@ -168,10 +180,13 @@ function draw_new_edge(edge) {
start_node: start_node, start_node: start_node,
end_node: end_node, end_node: end_node,
weight: weight, weight: weight,
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"]);
@ -192,8 +207,9 @@ function update_edge(konva_edge) {
let text = konva_edge.children[1]; let text = konva_edge.children[1];
let start_node = konva_edge.attrs.start_node; let start_node = konva_edge.attrs.start_node;
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);
@ -202,14 +218,14 @@ function update_edge(konva_edge) {
edge_layer.draw(); edge_layer.draw();
} }
function reset_node_style(){ function reset_node_style() {
for (let id in konva_nodes) { for (let id in konva_nodes) {
konva_nodes[id].children[0].strokeWidth(node_layout.strokeWidth); konva_nodes[id].children[0].strokeWidth(node_layout.strokeWidth);
konva_nodes[id].children[0].stroke("black"); konva_nodes[id].children[0].stroke("black");
} }
} }
function reset_edge_style(){ function reset_edge_style() {
for (let id in konva_edges) { for (let id in konva_edges) {
konva_edges[id].children[0].strokeWidth(edge_layout.strokeWidth); konva_edges[id].children[0].strokeWidth(edge_layout.strokeWidth);
konva_edges[id].children[0].stroke("black"); konva_edges[id].children[0].stroke("black");
@ -226,18 +242,18 @@ function draw_selected_nodes() {
node_layer.draw(); node_layer.draw();
} }
function draw_path(path){ function draw_path(path) {
let path_elements = []; let path_elements = [];
for (let id in path){ for (let id in path) {
path_elements.push(stage.findOne("#"+path[id])); path_elements.push(stage.findOne("#" + path[id]));
if (id == 0) { if (id == 0) {
continue continue
} }
let current_edge = stage.findOne("#"+path[id-1]+path[id]); let current_edge = stage.findOne("#" + path[id - 1] + path[id]);
if (current_edge){ if (current_edge) {
path_elements.push(current_edge); path_elements.push(current_edge);
}else{ } else {
path_elements.push(stage.findOne("#"+path[id]+path[id-1])); path_elements.push(stage.findOne("#" + path[id] + path[id - 1]));
} }
} }
reset_node_style(); reset_node_style();
@ -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";
}, function(){state = "idle"}); })
.catch(error => console.error('Error:', error));
}, 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");
@ -357,57 +432,57 @@ function toggle_auto_layout() {
} }
function normalize(vec) { function normalize(vec) {
let l = Math.sqrt(vec[0]**2+vec[1]**2); let l = Math.sqrt(vec[0] ** 2 + vec[1] ** 2);
return [vec[0]/l,vec[1]/l]; return [vec[0] / l, vec[1] / l];
} }
function in_edges(start_node, end_node){ function in_edges(start_node, end_node) {
let idx = graph_copy[start_node.attrs.id].indexOf(end_node.attrs.id); let idx = graph_copy[start_node.attrs.id].indexOf(end_node.attrs.id);
if (idx < 0){ if (idx < 0) {
return false; return false;
} }
return true; return true;
} }
function force_directed_graph(spring_length = 300, step = 0.004){ function force_directed_graph(spring_length = 300, step = 0.004) {
for (var idx in konva_nodes){ for (var idx in konva_nodes) {
if (konva_nodes[idx].attrs.dragging){ if (konva_nodes[idx].attrs.dragging) {
continue continue
} }
var x1 = konva_nodes[idx].getX(); var x1 = konva_nodes[idx].getX();
var y1 = konva_nodes[idx].getY(); var y1 = konva_nodes[idx].getY();
var rep = [0,0]; var rep = [0, 0];
var attr = [0,0]; var attr = [0, 0];
for (let jdx in konva_nodes){ for (let jdx in konva_nodes) {
if (idx == jdx){ if (idx == jdx) {
continue; continue;
} }
let x2 = konva_nodes[jdx].getX(); let x2 = konva_nodes[jdx].getX();
let y2 = konva_nodes[jdx].getY(); let y2 = konva_nodes[jdx].getY();
let dir = normalize([x2-x1,y2-y1]); let dir = normalize([x2 - x1, y2 - y1]);
let dist = ((x1-x2)**2+(y1-y2)**2); let dist = ((x1 - x2) ** 2 + (y1 - y2) ** 2);
if (in_edges(konva_nodes[idx], konva_nodes[jdx])){ if (in_edges(konva_nodes[idx], konva_nodes[jdx])) {
let f = (dist/spring_length); let f = (dist / spring_length);
attr[0] += f*dir[0]; attr[0] += f * dir[0];
attr[1] += f*dir[1]; attr[1] += f * dir[1];
} }
let f = (spring_length**2/Math.sqrt(dist)); let f = (spring_length ** 2 / Math.sqrt(dist));
rep[0] -= f*dir[0]; rep[0] -= f * dir[0];
rep[1] -= f*dir[1]; rep[1] -= f * dir[1];
} }
let new_x = x1+step*(rep[0]+attr[0]); let new_x = x1 + step * (rep[0] + attr[0]);
let new_y = y1+step*(rep[1]+attr[1]); let new_y = y1 + step * (rep[1] + attr[1]);
if (new_x < 2*node_size) { if (new_x < 2 * node_size) {
new_x += 10*step*((2*node_size-new_x)); new_x += 10 * step * ((2 * node_size - new_x));
} else if (new_x > width-2*node_size) { } else if (new_x > width - 2 * node_size) {
new_x += 10*step*((width-2*node_size-new_x)); new_x += 10 * step * ((width - 2 * node_size - new_x));
} }
if (new_y < 2*node_size) { if (new_y < 2 * node_size) {
new_y += 10*step*((2*node_size-new_y)); new_y += 10 * step * ((2 * node_size - new_y));
} else if (new_y > height-2*node_size) { } else if (new_y > height - 2 * node_size) {
new_y += 10*step*((height-2*node_size-new_y)); new_y += 10 * step * ((height - 2 * node_size - new_y));
} }
konva_nodes[idx].x(new_x); konva_nodes[idx].x(new_x);
konva_nodes[idx].y(new_y); konva_nodes[idx].y(new_y);
@ -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(300, step)
force_directed_graph(spring_length, 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;};

@ -20,7 +20,7 @@
<img src="{{ url_for('static', filename='imgs/drawing.svg') }}"> <img src="{{ url_for('static', filename='imgs/drawing.svg') }}">
<h1>Connect</h1> <h1>Connect</h1>
</button> </button>
<button type="button" onclick="find_path()"> <button type="button" onclick="find_path(selected)">
<img src="{{ url_for('static', filename='imgs/share.svg') }}"> <img src="{{ url_for('static', filename='imgs/share.svg') }}">
<h1>Find Path</h1> <h1>Find Path</h1>
</button> </button>
@ -32,29 +32,9 @@
</footer> </footer>
</main> </main>
<script> <script>
var nodes = ["Aachen", "Berlin", "Chemnitz", "Dresden", "Essen"] var nodes = {{ nodes|safe }};
var edges = [{ var edges = {{ edges|safe }};
"start": "Aachen", const url = "http://{{ request.host|safe }}/api/";
"end": "Berlin",
"weight": 800
},{
"start": "Dresden",
"end": "Berlin",
"weight": 800
},{
"start": "Essen",
"end": "Berlin",
"weight": 800
}, {
"start": "Chemnitz",
"end": "Dresden",
"weight": 5
},
{
"start": "Chemnitz",
"end": "Berlin",
"weight": 5
}]
</script> </script>
<script src="{{ url_for('static', filename='js/canvas.js')}}"></script> <script src="{{ url_for('static', filename='js/canvas.js')}}"></script>
</body> </body>

Loading…
Cancel
Save