649 lines
24 KiB
JavaScript
649 lines
24 KiB
JavaScript
var docEl = document.documentElement,
|
|
bodyEl = document.getElementsByTagName('body')[0];
|
|
|
|
var width = window.innerWidth || docEl.clientWidth || bodyEl.clientWidth,
|
|
height = window.innerHeight || docEl.clientHeight || bodyEl.clientHeight;
|
|
|
|
function fitscreen(nodes, width, height) {
|
|
var minX = nodes[0].x;
|
|
var maxX = nodes[0].x;
|
|
var minY = nodes[0].y;
|
|
var maxY = nodes[0].y;
|
|
var offsetX = 0, offsetY = 0;
|
|
|
|
nodes.forEach(function (e, i) {
|
|
if (e.x > maxX) {
|
|
maxX = e.x
|
|
}
|
|
if (e.x < minX) {
|
|
minX = e.x
|
|
}
|
|
if (e.y < minY) {
|
|
minY = e.y
|
|
}
|
|
if (e.y < minY) {
|
|
minY = e.y
|
|
}
|
|
});
|
|
|
|
if (minX < 0) {
|
|
offsetX = - minX;
|
|
minX += offsetX;
|
|
maxX += offsetX;
|
|
}
|
|
|
|
if (minY < 0) {
|
|
offsetY = -minY;
|
|
minY += offsetY
|
|
maxY += offsetY;
|
|
}
|
|
|
|
nodes.forEach(function (e, i) {
|
|
e.x += offsetX;
|
|
e.y += offsetY;
|
|
e.x = (e.x - minX) * width * 0.8 / (maxX - minX) + 0.25 * width;
|
|
e.y = (e.y - minY) * height * 0.8 / (maxY - minY) + 0.25 * height;
|
|
});
|
|
|
|
return nodes;
|
|
}
|
|
|
|
document.onload = (function (d3, saveAs, Blob, undefined) {
|
|
// define graphcreator object
|
|
var GraphCreator = function (svg, nodes, edges) {
|
|
var thisGraph = this;
|
|
thisGraph.idct = 0;
|
|
|
|
thisGraph.nodes = nodes || [];
|
|
thisGraph.edges = edges || [];
|
|
|
|
thisGraph.state = {
|
|
selectedNode: null,
|
|
selectedEdge: null,
|
|
mouseDownNode: null,
|
|
mouseDownLink: null,
|
|
justDragged: false,
|
|
justScaleTransGraph: false,
|
|
lastKeyDown: -1,
|
|
shiftNodeDrag: false,
|
|
selectedText: null
|
|
};
|
|
|
|
// define arrow markers for graph links
|
|
var defs = svg.append('svg:defs');
|
|
defs.append('svg:marker')
|
|
.attr('id', 'end-arrow')
|
|
.attr('viewBox', '0 -5 10 10')
|
|
.attr('refX', "20")
|
|
.attr('markerWidth', 3.5)
|
|
.attr('markerHeight', 3.5)
|
|
.attr('orient', 'auto')
|
|
.append('svg:path')
|
|
.attr('d', 'M0,-5L10,0L0,5');
|
|
|
|
// define arrow markers for leading arrow
|
|
defs.append('svg:marker')
|
|
.attr('id', 'mark-end-arrow')
|
|
.attr('viewBox', '0 -5 10 10')
|
|
.attr('refX', 7)
|
|
.attr('markerWidth', 3.5)
|
|
.attr('markerHeight', 3.5)
|
|
.attr('orient', 'auto')
|
|
.append('svg:path')
|
|
.attr('d', 'M0,-5L10,0L0,5');
|
|
|
|
thisGraph.svg = svg;
|
|
thisGraph.svgG = svg.append("g")
|
|
.classed(thisGraph.consts.graphClass, true);
|
|
var svgG = thisGraph.svgG;
|
|
|
|
// displayed when dragging between nodes
|
|
thisGraph.dragLine = svgG.append('svg:path')
|
|
.attr('class', 'link dragline hidden')
|
|
.attr('d', 'M0,0L0,0')
|
|
.style('marker-end', 'url(#mark-end-arrow)');
|
|
|
|
// svg nodes and edges
|
|
thisGraph.paths = svgG.append("g").selectAll("g");
|
|
thisGraph.circles = svgG.append("g").selectAll("g");
|
|
|
|
thisGraph.drag = d3.behavior.drag()
|
|
.origin(function (d) {
|
|
return { x: d.x, y: d.y };
|
|
})
|
|
.on("drag", function (args) {
|
|
thisGraph.state.justDragged = true;
|
|
thisGraph.dragmove.call(thisGraph, args);
|
|
})
|
|
.on("dragend", function () {
|
|
// todo check if edge-mode is selected
|
|
});
|
|
|
|
// listen for key events
|
|
d3.select(window).on("keydown", function () {
|
|
thisGraph.svgKeyDown.call(thisGraph);
|
|
})
|
|
.on("keyup", function () {
|
|
thisGraph.svgKeyUp.call(thisGraph);
|
|
});
|
|
svg.on("mousedown", function (d) { thisGraph.svgMouseDown.call(thisGraph, d); });
|
|
svg.on("mouseup", function (d) { thisGraph.svgMouseUp.call(thisGraph, d); });
|
|
|
|
// listen for dragging
|
|
var dragSvg = d3.behavior.zoom()
|
|
.on("zoom", function () {
|
|
if (d3.event.sourceEvent.shiftKey) {
|
|
// TODO the internal d3 state is still changing
|
|
return false;
|
|
} else {
|
|
thisGraph.zoomed.call(thisGraph);
|
|
}
|
|
return true;
|
|
})
|
|
.on("zoomstart", function () {
|
|
var ael = d3.select("#" + thisGraph.consts.activeEditId).node();
|
|
if (ael) {
|
|
ael.blur();
|
|
}
|
|
if (!d3.event.sourceEvent.shiftKey) d3.select('body').style("cursor", "move");
|
|
})
|
|
.on("zoomend", function () {
|
|
d3.select('body').style("cursor", "auto");
|
|
});
|
|
|
|
svg.call(dragSvg).on("dblclick.zoom", null);
|
|
|
|
// listen for resize
|
|
window.onresize = function () { thisGraph.updateWindow(svg); };
|
|
|
|
// handle download data
|
|
d3.select("#download-input").on("click", function () {
|
|
var saveEdges = [];
|
|
thisGraph.edges.forEach(function (val, i) {
|
|
saveEdges.push({ source: val.source.id, target: val.target.id });
|
|
});
|
|
var blob = new Blob([window.JSON.stringify({ "nodes": thisGraph.nodes, "edges": saveEdges })], { type: "text/plain;charset=utf-8" });
|
|
saveAs(blob, "mydag.json");
|
|
});
|
|
|
|
|
|
// handle uploaded data
|
|
d3.select("#upload-input").on("click", function () {
|
|
document.getElementById("hidden-file-upload").click();
|
|
});
|
|
d3.select("#hidden-file-upload").on("change", function () {
|
|
if (window.File && window.FileReader && window.FileList && window.Blob) {
|
|
var uploadFile = this.files[0];
|
|
var filereader = new window.FileReader();
|
|
|
|
filereader.onload = function () {
|
|
var txtRes = filereader.result;
|
|
// TODO better error handling
|
|
try {
|
|
var jsonObj = JSON.parse(txtRes);
|
|
jsonObj.nodes = fitscreen(jsonObj.nodes, width, height)
|
|
thisGraph.deleteGraph(true);
|
|
thisGraph.nodes = jsonObj.nodes;
|
|
thisGraph.setIdCt(jsonObj.nodes.length);
|
|
var newEdges = jsonObj.edges;
|
|
newEdges.forEach(function (e, i) {
|
|
newEdges[i] = {
|
|
source: thisGraph.nodes.filter(function (n) { return n.id == e.source; })[0],
|
|
target: thisGraph.nodes.filter(function (n) { return n.id == e.target; })[0]
|
|
};
|
|
});
|
|
thisGraph.edges = newEdges;
|
|
thisGraph.updateGraph();
|
|
} catch (err) {
|
|
window.alert("Error parsing uploaded file\nerror message: " + err.message);
|
|
return;
|
|
}
|
|
};
|
|
filereader.readAsText(uploadFile);
|
|
|
|
} else {
|
|
alert("Your browser won't let you save this graph -- try upgrading your browser to IE 10+ or Chrome or Firefox.");
|
|
}
|
|
|
|
});
|
|
|
|
// handle delete graph
|
|
d3.select("#delete-graph").on("click", function () {
|
|
thisGraph.deleteGraph(false);
|
|
});
|
|
};
|
|
|
|
GraphCreator.prototype.setIdCt = function (idct) {
|
|
this.idct = idct;
|
|
};
|
|
|
|
GraphCreator.prototype.consts = {
|
|
selectedClass: "selected",
|
|
connectClass: "connect-node",
|
|
circleGClass: "conceptG",
|
|
graphClass: "graph",
|
|
activeEditId: "active-editing",
|
|
BACKSPACE_KEY: 8,
|
|
DELETE_KEY: 46,
|
|
ENTER_KEY: 13,
|
|
nodeRadius: 25
|
|
};
|
|
|
|
/* PROTOTYPE FUNCTIONS */
|
|
|
|
GraphCreator.prototype.dragmove = function (d) {
|
|
var thisGraph = this;
|
|
if (thisGraph.state.shiftNodeDrag) {
|
|
thisGraph.dragLine.attr('d', 'M' + d.x + ',' + d.y + 'L' + d3.mouse(thisGraph.svgG.node())[0] + ',' + d3.mouse(this.svgG.node())[1]);
|
|
} else {
|
|
d.x += d3.event.dx;
|
|
d.y += d3.event.dy;
|
|
thisGraph.updateGraph();
|
|
}
|
|
};
|
|
|
|
GraphCreator.prototype.deleteGraph = function (skipPrompt) {
|
|
var thisGraph = this,
|
|
doDelete = true;
|
|
if (!skipPrompt) {
|
|
doDelete = window.confirm("Press OK to delete this graph");
|
|
}
|
|
if (doDelete) {
|
|
thisGraph.nodes = [];
|
|
thisGraph.edges = [];
|
|
thisGraph.updateGraph();
|
|
}
|
|
};
|
|
|
|
/* select all text in element: taken from http://stackoverflow.com/questions/6139107/programatically-select-text-in-a-contenteditable-html-element */
|
|
GraphCreator.prototype.selectElementContents = function (el) {
|
|
var range = document.createRange();
|
|
range.selectNodeContents(el);
|
|
var sel = window.getSelection();
|
|
sel.removeAllRanges();
|
|
sel.addRange(range);
|
|
};
|
|
|
|
|
|
/* insert svg line breaks: taken from http://stackoverflow.com/questions/13241475/how-do-i-include-newlines-in-labels-in-d3-charts */
|
|
GraphCreator.prototype.insertTitleLinebreaks = function (gEl, title) {
|
|
var words = title.split(/\s+/g),
|
|
nwords = words.length;
|
|
var el = gEl.append("text")
|
|
.attr("text-anchor", "middle")
|
|
.attr("dy", "-" + (nwords - 1) * 7.5);
|
|
|
|
for (var i = 0; i < words.length; i++) {
|
|
var tspan = el.append('tspan').text(words[i]);
|
|
if (i > 0)
|
|
tspan.attr('x', 0).attr('dy', '15');
|
|
}
|
|
};
|
|
|
|
|
|
// remove edges associated with a node
|
|
GraphCreator.prototype.spliceLinksForNode = function (node) {
|
|
var thisGraph = this,
|
|
toSplice = thisGraph.edges.filter(function (l) {
|
|
return (l.source === node || l.target === node);
|
|
});
|
|
toSplice.map(function (l) {
|
|
thisGraph.edges.splice(thisGraph.edges.indexOf(l), 1);
|
|
});
|
|
};
|
|
|
|
GraphCreator.prototype.replaceSelectEdge = function (d3Path, edgeData) {
|
|
var thisGraph = this;
|
|
d3Path.classed(thisGraph.consts.selectedClass, true);
|
|
if (thisGraph.state.selectedEdge) {
|
|
thisGraph.removeSelectFromEdge();
|
|
}
|
|
thisGraph.state.selectedEdge = edgeData;
|
|
};
|
|
|
|
GraphCreator.prototype.replaceSelectNode = function (d3Node, nodeData) {
|
|
var thisGraph = this;
|
|
d3Node.classed(this.consts.selectedClass, true);
|
|
if (thisGraph.state.selectedNode) {
|
|
thisGraph.removeSelectFromNode();
|
|
}
|
|
thisGraph.state.selectedNode = nodeData;
|
|
};
|
|
|
|
GraphCreator.prototype.removeSelectFromNode = function () {
|
|
var thisGraph = this;
|
|
thisGraph.circles.filter(function (cd) {
|
|
return cd.id === thisGraph.state.selectedNode.id;
|
|
}).classed(thisGraph.consts.selectedClass, false);
|
|
thisGraph.state.selectedNode = null;
|
|
};
|
|
|
|
GraphCreator.prototype.removeSelectFromEdge = function () {
|
|
var thisGraph = this;
|
|
thisGraph.paths.filter(function (cd) {
|
|
return cd === thisGraph.state.selectedEdge;
|
|
}).classed(thisGraph.consts.selectedClass, false);
|
|
thisGraph.state.selectedEdge = null;
|
|
};
|
|
|
|
GraphCreator.prototype.pathMouseDown = function (d3path, d) {
|
|
var thisGraph = this,
|
|
state = thisGraph.state;
|
|
d3.event.stopPropagation();
|
|
state.mouseDownLink = d;
|
|
|
|
if (state.selectedNode) {
|
|
thisGraph.removeSelectFromNode();
|
|
}
|
|
|
|
var prevEdge = state.selectedEdge;
|
|
if (!prevEdge || prevEdge !== d) {
|
|
thisGraph.replaceSelectEdge(d3path, d);
|
|
} else {
|
|
thisGraph.removeSelectFromEdge();
|
|
}
|
|
};
|
|
|
|
// mousedown on node
|
|
GraphCreator.prototype.circleMouseDown = function (d3node, d) {
|
|
var thisGraph = this,
|
|
state = thisGraph.state;
|
|
d3.event.stopPropagation();
|
|
state.mouseDownNode = d;
|
|
if (d3.event.shiftKey) {
|
|
state.shiftNodeDrag = d3.event.shiftKey;
|
|
// reposition dragged directed edge
|
|
thisGraph.dragLine.classed('hidden', false)
|
|
.attr('d', 'M' + d.x + ',' + d.y + 'L' + d.x + ',' + d.y);
|
|
return;
|
|
}
|
|
};
|
|
|
|
/* place editable text on node in place of svg text */
|
|
GraphCreator.prototype.changeTextOfNode = function (d3node, d) {
|
|
var thisGraph = this,
|
|
consts = thisGraph.consts,
|
|
htmlEl = d3node.node();
|
|
d3node.selectAll("text").remove();
|
|
var nodeBCR = htmlEl.getBoundingClientRect(),
|
|
curScale = nodeBCR.width / consts.nodeRadius,
|
|
placePad = 5 * curScale,
|
|
useHW = curScale > 1 ? nodeBCR.width * 0.71 : consts.nodeRadius * 1.42;
|
|
// replace with editableconent text
|
|
var d3txt = thisGraph.svg.selectAll("foreignObject")
|
|
.data([d])
|
|
.enter()
|
|
.append("foreignObject")
|
|
.attr("x", nodeBCR.left + placePad)
|
|
.attr("y", nodeBCR.top + placePad)
|
|
.attr("height", 2 * useHW)
|
|
.attr("width", useHW)
|
|
.append("xhtml:p")
|
|
.attr("id", consts.activeEditId)
|
|
.attr("contentEditable", "true")
|
|
.text(d.title)
|
|
.on("mousedown", function (d) {
|
|
d3.event.stopPropagation();
|
|
})
|
|
.on("keydown", function (d) {
|
|
d3.event.stopPropagation();
|
|
if (d3.event.keyCode == consts.ENTER_KEY && !d3.event.shiftKey) {
|
|
this.blur();
|
|
}
|
|
})
|
|
.on("blur", function (d) {
|
|
d.title = this.textContent;
|
|
thisGraph.insertTitleLinebreaks(d3node, d.title);
|
|
d3.select(this.parentElement).remove();
|
|
});
|
|
return d3txt;
|
|
};
|
|
|
|
// mouseup on nodes
|
|
GraphCreator.prototype.circleMouseUp = function (d3node, d) {
|
|
var thisGraph = this,
|
|
state = thisGraph.state,
|
|
consts = thisGraph.consts;
|
|
// reset the states
|
|
state.shiftNodeDrag = false;
|
|
d3node.classed(consts.connectClass, false);
|
|
|
|
var mouseDownNode = state.mouseDownNode;
|
|
|
|
if (!mouseDownNode) return;
|
|
|
|
thisGraph.dragLine.classed("hidden", true);
|
|
|
|
if (mouseDownNode !== d) {
|
|
// we're in a different node: create new edge for mousedown edge and add to graph
|
|
var newEdge = { source: mouseDownNode, target: d };
|
|
var filtRes = thisGraph.paths.filter(function (d) {
|
|
if (d.source === newEdge.target && d.target === newEdge.source) {
|
|
thisGraph.edges.splice(thisGraph.edges.indexOf(d), 1);
|
|
}
|
|
return d.source === newEdge.source && d.target === newEdge.target;
|
|
});
|
|
if (!filtRes[0].length) {
|
|
thisGraph.edges.push(newEdge);
|
|
thisGraph.updateGraph();
|
|
}
|
|
} else {
|
|
// we're in the same node
|
|
if (state.justDragged) {
|
|
// dragged, not clicked
|
|
state.justDragged = false;
|
|
} else {
|
|
// clicked, not dragged
|
|
if (d3.event.shiftKey) {
|
|
// shift-clicked node: edit text content
|
|
var d3txt = thisGraph.changeTextOfNode(d3node, d);
|
|
var txtNode = d3txt.node();
|
|
thisGraph.selectElementContents(txtNode);
|
|
txtNode.focus();
|
|
} else {
|
|
if (state.selectedEdge) {
|
|
thisGraph.removeSelectFromEdge();
|
|
}
|
|
var prevNode = state.selectedNode;
|
|
|
|
if (!prevNode || prevNode.id !== d.id) {
|
|
thisGraph.replaceSelectNode(d3node, d);
|
|
} else {
|
|
thisGraph.removeSelectFromNode();
|
|
}
|
|
}
|
|
}
|
|
}
|
|
state.mouseDownNode = null;
|
|
return;
|
|
|
|
}; // end of circles mouseup
|
|
|
|
// mousedown on main svg
|
|
GraphCreator.prototype.svgMouseDown = function () {
|
|
this.state.graphMouseDown = true;
|
|
};
|
|
|
|
// mouseup on main svg
|
|
GraphCreator.prototype.svgMouseUp = function () {
|
|
var thisGraph = this,
|
|
state = thisGraph.state;
|
|
if (state.justScaleTransGraph) {
|
|
// dragged not clicked
|
|
state.justScaleTransGraph = false;
|
|
} else if (state.graphMouseDown && d3.event.shiftKey) {
|
|
// clicked not dragged from svg
|
|
var xycoords = d3.mouse(thisGraph.svgG.node()),
|
|
d = { id: thisGraph.idct, title: (this.idct).toString(), x: xycoords[0], y: xycoords[1] };
|
|
thisGraph.idct++;
|
|
thisGraph.nodes.push(d);
|
|
thisGraph.updateGraph();
|
|
// make title of text immediently editable
|
|
var d3txt = thisGraph.changeTextOfNode(thisGraph.circles.filter(function (dval) {
|
|
return dval.id === d.id;
|
|
}), d),
|
|
txtNode = d3txt.node();
|
|
thisGraph.selectElementContents(txtNode);
|
|
txtNode.focus();
|
|
} else if (state.shiftNodeDrag) {
|
|
// dragged from node
|
|
state.shiftNodeDrag = false;
|
|
thisGraph.dragLine.classed("hidden", true);
|
|
}
|
|
state.graphMouseDown = false;
|
|
};
|
|
|
|
// keydown on main svg
|
|
GraphCreator.prototype.svgKeyDown = function () {
|
|
var thisGraph = this,
|
|
state = thisGraph.state,
|
|
consts = thisGraph.consts;
|
|
// make sure repeated key presses don't register for each keydown
|
|
if (state.lastKeyDown !== -1) return;
|
|
|
|
state.lastKeyDown = d3.event.keyCode;
|
|
var selectedNode = state.selectedNode,
|
|
selectedEdge = state.selectedEdge;
|
|
|
|
switch (d3.event.keyCode) {
|
|
case consts.BACKSPACE_KEY:
|
|
case consts.DELETE_KEY:
|
|
d3.event.preventDefault();
|
|
if (selectedNode) {
|
|
thisGraph.nodes.splice(thisGraph.nodes.indexOf(selectedNode), 1);
|
|
thisGraph.spliceLinksForNode(selectedNode);
|
|
state.selectedNode = null;
|
|
thisGraph.updateGraph();
|
|
} else if (selectedEdge) {
|
|
thisGraph.edges.splice(thisGraph.edges.indexOf(selectedEdge), 1);
|
|
state.selectedEdge = null;
|
|
thisGraph.updateGraph();
|
|
}
|
|
break;
|
|
}
|
|
};
|
|
|
|
GraphCreator.prototype.svgKeyUp = function () {
|
|
this.state.lastKeyDown = -1;
|
|
};
|
|
|
|
// call to propagate changes to graph
|
|
GraphCreator.prototype.updateGraph = function () {
|
|
|
|
var thisGraph = this,
|
|
consts = thisGraph.consts,
|
|
state = thisGraph.state;
|
|
|
|
thisGraph.paths = thisGraph.paths.data(thisGraph.edges, function (d) {
|
|
return String(d.source.id) + "+" + String(d.target.id);
|
|
});
|
|
var paths = thisGraph.paths;
|
|
// update existing paths
|
|
paths.style('marker-end', 'url(#end-arrow)')
|
|
.classed(consts.selectedClass, function (d) {
|
|
return d === state.selectedEdge;
|
|
})
|
|
.attr("d", function (d) {
|
|
return "M" + d.source.x + "," + d.source.y + "L" + d.target.x + "," + d.target.y;
|
|
});
|
|
|
|
// add new paths
|
|
paths.enter()
|
|
.append("path")
|
|
.style('marker-end', 'url(#end-arrow)')
|
|
.classed("link", true)
|
|
.attr("d", function (d) {
|
|
return "M" + d.source.x + "," + d.source.y + "L" + d.target.x + "," + d.target.y;
|
|
})
|
|
.on("mousedown", function (d) {
|
|
thisGraph.pathMouseDown.call(thisGraph, d3.select(this), d);
|
|
}
|
|
)
|
|
.on("mouseup", function (d) {
|
|
state.mouseDownLink = null;
|
|
});
|
|
|
|
// remove old links
|
|
paths.exit().remove();
|
|
|
|
// update existing nodes
|
|
thisGraph.circles = thisGraph.circles.data(thisGraph.nodes, function (d) { return d.id; });
|
|
thisGraph.circles.attr("transform", function (d) { return "translate(" + d.x + "," + d.y + ")"; });
|
|
|
|
// add new nodes
|
|
var newGs = thisGraph.circles.enter()
|
|
.append("g");
|
|
|
|
newGs.classed(consts.circleGClass, true)
|
|
.attr("transform", function (d) { return "translate(" + d.x + "," + d.y + ")"; })
|
|
.on("mouseover", function (d) {
|
|
if (state.shiftNodeDrag) {
|
|
d3.select(this).classed(consts.connectClass, true);
|
|
}
|
|
})
|
|
.on("mouseout", function (d) {
|
|
d3.select(this).classed(consts.connectClass, false);
|
|
})
|
|
.on("mousedown", function (d) {
|
|
thisGraph.circleMouseDown.call(thisGraph, d3.select(this), d);
|
|
})
|
|
.on("mouseup", function (d) {
|
|
thisGraph.circleMouseUp.call(thisGraph, d3.select(this), d);
|
|
})
|
|
.call(thisGraph.drag);
|
|
|
|
newGs.append("circle")
|
|
.attr("r", String(consts.nodeRadius));
|
|
|
|
newGs.each(function (d) {
|
|
thisGraph.insertTitleLinebreaks(d3.select(this), d.title);
|
|
});
|
|
|
|
// remove old nodes
|
|
thisGraph.circles.exit().remove();
|
|
};
|
|
|
|
GraphCreator.prototype.zoomed = function () {
|
|
this.state.justScaleTransGraph = true;
|
|
d3.select("." + this.consts.graphClass)
|
|
.attr("transform", "translate(" + d3.event.translate + ") scale(" + d3.event.scale + ")");
|
|
};
|
|
|
|
GraphCreator.prototype.updateWindow = function (svg) {
|
|
var docEl = document.documentElement,
|
|
bodyEl = document.getElementsByTagName('body')[0];
|
|
var x = window.innerWidth || docEl.clientWidth || bodyEl.clientWidth;
|
|
var y = window.innerHeight || docEl.clientHeight || bodyEl.clientHeight;
|
|
svg.attr("width", x).attr("height", y);
|
|
};
|
|
|
|
/**** MAIN ****/
|
|
|
|
// warn the user when leaving
|
|
// window.onbeforeunload = function(){
|
|
// return "Make sure to save your graph locally before leaving";
|
|
// };
|
|
|
|
// initial node data
|
|
var obj = JSON.parse('{"nodes": [{"title": "0", "id": 0, "x": 38, "y": 20}, {"title": "1", "id": 1, "x": 39, "y": 26}, {"title": "2", "id": 2, "x": 40, "y": 25}, {"title": "3", "id": 3, "x": 36, "y": 23}, {"title": "4", "id": 4, "x": 33, "y": 10}, {"title": "5", "id": 5, "x": 37, "y": 12}, {"title": "6", "id": 6, "x": 38, "y": 13}, {"title": "7", "id": 7, "x": 37, "y": 20}, {"title": "8", "id": 8, "x": 41, "y": 9}, {"title": "9", "id": 9, "x": 41, "y": 13}, {"title": "10", "id": 10, "x": 36, "y": -5}, {"title": "11", "id": 11, "x": 38, "y": 15}, {"title": "12", "id": 12, "x": 38, "y": 15}, {"title": "13", "id": 13, "x": 37, "y": 15}, {"title": "14", "id": 14, "x": 35, "y": 14}, {"title": "15", "id": 15, "x": 39, "y": 19}], "edges": [{"source": 8, "target": 10}, {"source": 10, "target": 4}, {"source": 4, "target": 14}, {"source": 14, "target": 0}, {"source": 0, "target": 7}, {"source": 7, "target": 3}, {"source": 3, "target": 1}, {"source": 1, "target": 2}, {"source": 2, "target": 15}, {"source": 15, "target": 11}, {"source": 11, "target": 12}, {"source": 12, "target": 13}, {"source": 13, "target": 5}, {"source": 5, "target": 6}, {"source": 6, "target": 9}, {"source": 9, "target": 8}]}')
|
|
obj.nodes = fitscreen(obj.nodes, width, height);
|
|
|
|
var newEdges = obj.edges;
|
|
newEdges.forEach(function (e, i) {
|
|
newEdges[i] = {
|
|
source: obj.nodes.filter(function (n) { return n.id == e.source; })[0],
|
|
target: obj.nodes.filter(function (n) { return n.id == e.target; })[0]
|
|
};
|
|
});
|
|
obj.edges = newEdges;
|
|
|
|
/** MAIN SVG **/
|
|
var svg = d3.select("body").append("svg")
|
|
.attr("width", width)
|
|
.attr("height", height);
|
|
var graph = new GraphCreator(svg, obj.nodes, obj.edges);
|
|
graph.setIdCt(obj.nodes.length);
|
|
graph.updateGraph();
|
|
|
|
})(window.d3, window.saveAs, window.Blob);
|