|
|
|
@ -4,6 +4,158 @@ const height = canvas.offsetHeight; |
|
|
|
|
|
|
|
|
|
const node_size = 60; |
|
|
|
|
|
|
|
|
|
<<<<<<< HEAD |
|
|
|
|
// Layouts
|
|
|
|
|
var stage = new Konva.Stage({ |
|
|
|
|
container: canvas, |
|
|
|
|
width: width, |
|
|
|
|
height: height |
|
|
|
|
}); |
|
|
|
|
|
|
|
|
|
const node_layout = { |
|
|
|
|
radius: node_size, |
|
|
|
|
// fill: '#00D2FF',
|
|
|
|
|
fill: 'white', |
|
|
|
|
stroke: 'black', |
|
|
|
|
strokeWidth: 2, |
|
|
|
|
shadowColor: 'black', |
|
|
|
|
shadowBlur: 10, |
|
|
|
|
shadowOffset: { |
|
|
|
|
x: 2, |
|
|
|
|
y: 2 |
|
|
|
|
}, |
|
|
|
|
shadowOpacity: 0.3, |
|
|
|
|
}; |
|
|
|
|
|
|
|
|
|
const node_text_layout = { |
|
|
|
|
x: 1 - node_size, |
|
|
|
|
y: 2 - node_size, |
|
|
|
|
text: name, |
|
|
|
|
fontSize: 28, |
|
|
|
|
width: 2 * node_size, |
|
|
|
|
height: 2 * node_size, |
|
|
|
|
fontFamily: 'Calibri', |
|
|
|
|
fill: 'black', |
|
|
|
|
verticalAlign: 'middle', |
|
|
|
|
align: 'center' |
|
|
|
|
}; |
|
|
|
|
|
|
|
|
|
const edge_layout = { |
|
|
|
|
stroke: 'black', |
|
|
|
|
strokeWidth: 3, |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
const edge_text_layout = { |
|
|
|
|
fontSize: 28, |
|
|
|
|
width: 2 * node_size, |
|
|
|
|
height: 2 * node_size, |
|
|
|
|
fontFamily: 'Calibri', |
|
|
|
|
fill: 'black', |
|
|
|
|
verticalAlign: 'middle', |
|
|
|
|
align: 'center' |
|
|
|
|
}; |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
var edge_layer = new Konva.Layer(); |
|
|
|
|
var node_layer = new Konva.Layer(); |
|
|
|
|
|
|
|
|
|
var konva_edges = []; |
|
|
|
|
var konva_nodes = []; |
|
|
|
|
var graph_copy = {}; |
|
|
|
|
|
|
|
|
|
stage.add(edge_layer); |
|
|
|
|
stage.add(node_layer); |
|
|
|
|
|
|
|
|
|
for (id in nodes) { |
|
|
|
|
draw_node(nodes[id], node_size + Math.random() * (width - 2 * node_size), node_size + Math.random() * (height - 2 * node_size)); |
|
|
|
|
} |
|
|
|
|
for (id in edges) { |
|
|
|
|
draw_edge(edges[id]); |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
function update_edges(edges) { |
|
|
|
|
for (id in edges) { |
|
|
|
|
update_edge(edges[id]); |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
function sort(array) { |
|
|
|
|
return array.sort(function(a, b) { |
|
|
|
|
return a - b; |
|
|
|
|
}) |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
function draw_node(name, x, y) { |
|
|
|
|
const pos = { |
|
|
|
|
x: x, |
|
|
|
|
y: y |
|
|
|
|
}; |
|
|
|
|
let group = new Konva.Group({ |
|
|
|
|
...pos, |
|
|
|
|
draggable: true, |
|
|
|
|
dragBoundFunc: function(pos) { |
|
|
|
|
return { |
|
|
|
|
x: sort([node_size, pos.x, width - node_size])[1], |
|
|
|
|
y: sort([node_size, pos.y, height - node_size])[1], |
|
|
|
|
}; |
|
|
|
|
}, |
|
|
|
|
id: name |
|
|
|
|
}); |
|
|
|
|
let box = new Konva.Circle(node_layout); |
|
|
|
|
let simpleText = new Konva.Text({...node_text_layout, text: name}); |
|
|
|
|
group.add(box); |
|
|
|
|
group.add(simpleText); |
|
|
|
|
group.on("click", on_node_click); |
|
|
|
|
graph_copy[name] = []; |
|
|
|
|
konva_nodes.push(group); |
|
|
|
|
|
|
|
|
|
node_layer.add(group); |
|
|
|
|
node_layer.draw(); |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
function draw_edge(edge) { |
|
|
|
|
let start_node = stage.find('#' + edge["start"])[0]; |
|
|
|
|
let end_node = stage.find('#' + edge["end"])[0]; |
|
|
|
|
let weight = edge["weight"]; |
|
|
|
|
let group = new Konva.Group({ |
|
|
|
|
start_node: start_node, |
|
|
|
|
end_node: end_node, |
|
|
|
|
weight: weight, |
|
|
|
|
}); |
|
|
|
|
let line = new Konva.Line(edge_layout); |
|
|
|
|
let simpleText = new Konva.Text({...edge_text_layout, text: weight}); |
|
|
|
|
|
|
|
|
|
graph_copy[edge["start"]].push(edge["end"]); |
|
|
|
|
graph_copy[edge["end"]].push(edge["start"]); |
|
|
|
|
|
|
|
|
|
group.add(line); |
|
|
|
|
group.add(simpleText); |
|
|
|
|
konva_edges.push(group); |
|
|
|
|
update_edge(group); |
|
|
|
|
|
|
|
|
|
edge_layer.add(group); |
|
|
|
|
edge_layer.draw(); |
|
|
|
|
|
|
|
|
|
return group; |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
function update_edge(konva_edge) { |
|
|
|
|
let line = konva_edge.children[0]; |
|
|
|
|
let text = konva_edge.children[1]; |
|
|
|
|
let start_node = konva_edge.attrs.start_node; |
|
|
|
|
let end_node = konva_edge.attrs.end_node; |
|
|
|
|
let line_length = Math.sqrt((start_node.getX() - end_node.getX()) ** 2 + (start_node.getY() - end_node.getY()) ** 2) - node_size; |
|
|
|
|
|
|
|
|
|
line.points([start_node.getX(), start_node.getY(), end_node.getX(), end_node.getY()]); |
|
|
|
|
line.dash([line_length / 2, node_size, line_length / 2]); |
|
|
|
|
text.x((start_node.getX() + end_node.getX()) / 2 - node_size); |
|
|
|
|
text.y((start_node.getY() + end_node.getY()) / 2 - node_size), |
|
|
|
|
|
|
|
|
|
edge_layer.draw(); |
|
|
|
|
======= |
|
|
|
|
// Node layout
|
|
|
|
|
const node_layout = { |
|
|
|
|
radius: node_size, |
|
|
|
@ -126,10 +278,175 @@ function drawEdge(edge){ |
|
|
|
|
edge_layer.draw(); |
|
|
|
|
|
|
|
|
|
return group; |
|
|
|
|
>>>>>>> a37d0b7c98fd96835fceca28e11bfd696d605ed6 |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
node_layer.on('beforeDraw', function() { |
|
|
|
|
<<<<<<< HEAD |
|
|
|
|
update_edges(konva_edges); |
|
|
|
|
}); |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
var selected = []; |
|
|
|
|
|
|
|
|
|
function on_node_click(e) { |
|
|
|
|
e.cancelBubble = true; |
|
|
|
|
let mousePos = stage.getPointerPosition(); |
|
|
|
|
// if (state == "CREATE_EDGE"){
|
|
|
|
|
let idx = selected.indexOf(this); |
|
|
|
|
if (idx >= 0) { |
|
|
|
|
selected.splice(idx, 1); |
|
|
|
|
} else if (selected.length < 2) { |
|
|
|
|
selected.push(this); |
|
|
|
|
} else { |
|
|
|
|
selected.shift(); |
|
|
|
|
selected.push(this); |
|
|
|
|
} |
|
|
|
|
draw_selected_nodes(); |
|
|
|
|
// }
|
|
|
|
|
}; |
|
|
|
|
|
|
|
|
|
function draw_selected_nodes() { |
|
|
|
|
for (let id in konva_nodes) { |
|
|
|
|
konva_nodes[id].children[0].strokeWidth(node_layout.strokeWidth); |
|
|
|
|
} |
|
|
|
|
for (id in selected) { |
|
|
|
|
selected[id].children[0].strokeWidth(node_layout.strokeWidth + 4); |
|
|
|
|
} |
|
|
|
|
node_layer.draw(); |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
var state = "CREATE_NODE"; |
|
|
|
|
var old_state; |
|
|
|
|
|
|
|
|
|
stage.on('click', function(e) { |
|
|
|
|
if (state == "CREATE_NODE") { |
|
|
|
|
state = "CREATING_NODE"; |
|
|
|
|
let mousePos = stage.getPointerPosition(); |
|
|
|
|
create_input(mousePos.x, mousePos.y).then(function(text) { |
|
|
|
|
draw_node(text, mousePos.x, mousePos.y); |
|
|
|
|
state = "CREATE_NODE"; |
|
|
|
|
},function(){state = "CREATE_NODE"}); |
|
|
|
|
} |
|
|
|
|
}); |
|
|
|
|
|
|
|
|
|
function create_edge() { |
|
|
|
|
state = "CREATE_EDGE"; |
|
|
|
|
if (selected.length < 2) { |
|
|
|
|
alert("Select at least 2 nodes."); |
|
|
|
|
return; |
|
|
|
|
} |
|
|
|
|
let x = (selected[0].getX() + selected[1].getX()) / 2; |
|
|
|
|
let y = (selected[0].getY() + selected[1].getY()) / 2; |
|
|
|
|
create_input(x, y).then(function(text) { |
|
|
|
|
draw_edge({ |
|
|
|
|
"start": selected[0].children[1].text(), |
|
|
|
|
"end": selected[1].children[1].text(), |
|
|
|
|
"weight": text |
|
|
|
|
}); |
|
|
|
|
selected = []; |
|
|
|
|
draw_selected_nodes(); |
|
|
|
|
},function(){}); |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
function create_input(x, y) { |
|
|
|
|
return new Promise(function(resolve, reject) { |
|
|
|
|
var input = document.createElement("input"); |
|
|
|
|
input.setAttribute("type", "text"); |
|
|
|
|
input.style.left = x + "px"; |
|
|
|
|
input.style.top = y + "px"; |
|
|
|
|
canvas.appendChild(input); |
|
|
|
|
input.focus(); |
|
|
|
|
stage.listening(false); |
|
|
|
|
window.addEventListener("keyup", function(event) { |
|
|
|
|
if (event.key === "Escape") { |
|
|
|
|
input.remove(); |
|
|
|
|
stage.listening(true); |
|
|
|
|
reject(); |
|
|
|
|
} |
|
|
|
|
}); |
|
|
|
|
input.addEventListener("keyup", function(event) { |
|
|
|
|
if (event.key === "Enter") { |
|
|
|
|
resolve(input.value); |
|
|
|
|
input.remove(); |
|
|
|
|
stage.listening(true); |
|
|
|
|
} |
|
|
|
|
}); |
|
|
|
|
}); |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
function in_edges(start_node, end_node){ |
|
|
|
|
let idx = graph_copy[start_node.attrs.id].indexOf(end_node.attrs.id); |
|
|
|
|
if (idx < 0){ |
|
|
|
|
return false; |
|
|
|
|
} |
|
|
|
|
return true; |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
function normalize(vec) { |
|
|
|
|
let l = Math.sqrt(vec[0]**2+vec[1]**2); |
|
|
|
|
return [vec[0]/l,vec[1]/l]; |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
var auto_layout = "ENABLED"; |
|
|
|
|
|
|
|
|
|
function force_directed_graph(silent = false){ |
|
|
|
|
if (auto_layout == "ENABLED") { |
|
|
|
|
const spring_length = 350; |
|
|
|
|
const step = 0.025; |
|
|
|
|
for (var idx in konva_nodes){ |
|
|
|
|
var x1 = konva_nodes[idx].getX(); |
|
|
|
|
var y1 = konva_nodes[idx].getY(); |
|
|
|
|
// console.log(idx);
|
|
|
|
|
// console.log(konva_nodes[idx]);
|
|
|
|
|
var rep = [0,0]; |
|
|
|
|
var attr = [0,0]; |
|
|
|
|
for (let jdx in konva_nodes){ |
|
|
|
|
if (idx == jdx){ |
|
|
|
|
continue; |
|
|
|
|
} |
|
|
|
|
// console.log(idx,jdx);
|
|
|
|
|
let x2 = konva_nodes[jdx].getX(); |
|
|
|
|
let y2 = konva_nodes[jdx].getY(); |
|
|
|
|
let dir = normalize([x2-x1,y2-y1]); |
|
|
|
|
let dist = ((x1-x2)**2+(y1-y2)**2); |
|
|
|
|
if (in_edges(konva_nodes[idx], konva_nodes[jdx])){ |
|
|
|
|
// console.log(in_edges(konva_nodes[idx], konva_nodes[jdx]));
|
|
|
|
|
let f = (dist/spring_length); |
|
|
|
|
attr[0] += f*dir[0]; |
|
|
|
|
attr[1] += f*dir[1]; |
|
|
|
|
} |
|
|
|
|
let f = (spring_length**2/Math.sqrt(dist)); |
|
|
|
|
rep[0] -= f*dir[0]; |
|
|
|
|
rep[1] -= f*dir[1]; |
|
|
|
|
} |
|
|
|
|
let new_x = x1+step*(rep[0]+attr[0]); |
|
|
|
|
let new_y = y1+step*(rep[1]+attr[1]); |
|
|
|
|
if (new_x < 2*node_size) { |
|
|
|
|
new_x += (1-new_x/2*node_size); |
|
|
|
|
} |
|
|
|
|
konva_nodes[idx].x(sort([2*node_size, x1+step*(rep[0]+attr[0]), width-2*node_size])[1]); |
|
|
|
|
konva_nodes[idx].y(sort([2*node_size, y1+step*(rep[1]+attr[1]), height-2*node_size])[1]); |
|
|
|
|
if (!silent) { |
|
|
|
|
node_layer.draw(); |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
// force_directed_graph();
|
|
|
|
|
window.setInterval(force_directed_graph, 1000/50); |
|
|
|
|
for (var i = 0; i < 100; i++) { |
|
|
|
|
force_directed_graph(true); |
|
|
|
|
node_layer.draw(); |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
function change_state(new_state){ |
|
|
|
|
state = new_state; |
|
|
|
|
} |
|
|
|
|
======= |
|
|
|
|
updateEdges(konva_edges); |
|
|
|
|
}); |
|
|
|
|
|
|
|
|
@ -162,6 +479,7 @@ stage.on('click', function(e) { |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
>>>>>>> a37d0b7c98fd96835fceca28e11bfd696d605ed6 |
|
|
|
|
// // add cursor styling
|
|
|
|
|
// box.on('mouseover', function() {
|
|
|
|
|
// document.body.style.cursor = 'pointer';
|
|
|
|
|