177 lines
5.4 KiB
JavaScript
177 lines
5.4 KiB
JavaScript
|
d3.select("#graph_body")
|
||
|
.append("div")
|
||
|
.attr("style", "width: 100%; height: 100%; min-height: 0px; flex-grow: 1")
|
||
|
.append("svg")
|
||
|
.attr("preserveAspectRatio", "xMinYMin meet")
|
||
|
.classed("svg-content-responsive", true);
|
||
|
let graph = d3.select("svg");
|
||
|
let svg = graph.append('g');
|
||
|
let margin = {top: 20, right: 20, bottom: 35, left: 40};
|
||
|
|
||
|
function bbox() {
|
||
|
let ret = graph.node().parentNode.getBoundingClientRect();
|
||
|
return ret;
|
||
|
}
|
||
|
let _bbox = bbox();
|
||
|
|
||
|
const colors = ['aqua', 'yellow', 'fuchsia', 'white', 'teal', 'olive', 'purple', 'gray', 'blue', 'lime', 'red', 'silver', 'navy', 'green', 'maroon'];
|
||
|
const n_colors = colors.length;
|
||
|
|
||
|
const view = svg.append("rect")
|
||
|
.attr("class", "view")
|
||
|
.attr("x", 0)
|
||
|
.attr("y", 0);
|
||
|
|
||
|
|
||
|
function convert_data(nodes_raw) {
|
||
|
let edges = [];
|
||
|
let node_id = new Map();
|
||
|
nodes = [];
|
||
|
for (let i in nodes_raw) {
|
||
|
node_id.set(nodes_raw[i], i);
|
||
|
nodes.push({id: i, color: 0, data: nodes_raw[i]});
|
||
|
}
|
||
|
for (const node of nodes_raw) {
|
||
|
const to = node_id.get(node);
|
||
|
for (const input of node.inputs) {
|
||
|
const from = node_id.get(input);
|
||
|
let name = input.name;
|
||
|
let link_name = node.input_translation.get(name);
|
||
|
edges.push({
|
||
|
source: from,
|
||
|
target: to,
|
||
|
name: link_name
|
||
|
});
|
||
|
}
|
||
|
}
|
||
|
return {
|
||
|
nodes: nodes,
|
||
|
links: edges
|
||
|
}
|
||
|
}
|
||
|
|
||
|
function create_svg(data, redraw_func) {
|
||
|
// Initialize the links
|
||
|
var link = svg
|
||
|
.selectAll("line")
|
||
|
.data(data.links)
|
||
|
.enter()
|
||
|
.append("line")
|
||
|
.style("stroke", "#aaa")
|
||
|
|
||
|
// Initialize the nodes
|
||
|
let node = svg
|
||
|
.selectAll("g")
|
||
|
.data(data.nodes);
|
||
|
|
||
|
let node_enter = node.enter()
|
||
|
.append('g')
|
||
|
|
||
|
let circles = node_enter.append("circle")
|
||
|
.attr("r", 20)
|
||
|
.style("fill", ({id, color, data}) => colors[color])
|
||
|
|
||
|
node_enter.append('text')
|
||
|
.attr("dx", -20)
|
||
|
.attr("dy", -22)
|
||
|
.style('fill', 'white')
|
||
|
.text(({id, color, data}) => data.name);
|
||
|
|
||
|
// Let's list the force we wanna apply on the network
|
||
|
var simulation = d3.forceSimulation(data.nodes) // Force algorithm is applied to data.nodes
|
||
|
.force("link", d3.forceLink().strength(0.1) // This force provides links between nodes
|
||
|
.id(function(d) { return d.id; }) // This provide the id of a node
|
||
|
.links(data.links) // and this the list of links
|
||
|
)
|
||
|
.force("charge", d3.forceManyBody().strength(-400)) // This adds repulsion between nodes. Play with the -400 for the repulsion strength
|
||
|
//.force("center", d3.forceCenter(_bbox.width / 2, _bbox.height / 2).strength(0.1)) // This force attracts nodes to the center of the svg area
|
||
|
.on("tick", ticked);
|
||
|
// This function is run at each iteration of the force algorithm, updating the nodes position.
|
||
|
let scale_transform = {k: 1, x: 0, y: 0}
|
||
|
function ticked() {
|
||
|
link
|
||
|
.attr("x1", function(d) { return d.source.x; })
|
||
|
.attr("y1", function(d) { return d.source.y; })
|
||
|
.attr("x2", function(d) { return d.target.x; })
|
||
|
.attr("y2", function(d) { return d.target.y; });
|
||
|
|
||
|
node_enter.attr("transform", function (d) { return 'translate('+scale_transform.x+','+scale_transform.y+') scale('+scale_transform.k+') translate('+d.x+','+d.y+')' })
|
||
|
}
|
||
|
|
||
|
const drag = d3.drag()
|
||
|
.on("start", dragstart)
|
||
|
.on("drag", dragged);
|
||
|
|
||
|
node_enter.call(drag).on('click', click);
|
||
|
function click(event, d) {
|
||
|
if (event.ctrlKey) {
|
||
|
// Color cycle.
|
||
|
d.color = (d.color + 1) % n_colors;
|
||
|
d3.select(this).selectAll('circle').style("fill", ({id, color, data}) => colors[color])
|
||
|
}
|
||
|
else {
|
||
|
delete d.fx;
|
||
|
delete d.fy;
|
||
|
d3.select(this).classed("fixed", false);
|
||
|
simulation.alpha(0.5).restart();
|
||
|
}
|
||
|
}
|
||
|
|
||
|
function dragstart() {
|
||
|
d3.select(this).classed("fixed", true);
|
||
|
}
|
||
|
function dragged(event, d) {
|
||
|
d.fx = event.x;
|
||
|
d.fy = event.y;
|
||
|
simulation.alpha(0.5).restart();
|
||
|
}
|
||
|
|
||
|
const zoom = d3.zoom()
|
||
|
.scaleExtent([0.01, 10])
|
||
|
.translateExtent([[-10000, -10000], [10000, 10000]])
|
||
|
.filter(filter)
|
||
|
.on("zoom", zoomed);
|
||
|
view.call(zoom);
|
||
|
|
||
|
function zoomed({ transform }) {
|
||
|
link.attr('transform', transform);
|
||
|
scale_transform = transform;
|
||
|
node_enter.attr("transform", function (d) { return 'translate('+scale_transform.x+','+scale_transform.y+') scale('+scale_transform.k+') translate('+d.x+','+d.y+')' })
|
||
|
redraw_func();
|
||
|
}
|
||
|
// prevent scrolling then apply the default filter
|
||
|
function filter(event) {
|
||
|
event.preventDefault();
|
||
|
return (!event.ctrlKey || event.type === 'wheel') && !event.button;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
set_export_button(svg, 'saveButton', 'saveLink');
|
||
|
|
||
|
(async function() {
|
||
|
|
||
|
// JANKY
|
||
|
while (edit_id_output === undefined) {
|
||
|
await sleep(500);
|
||
|
}
|
||
|
|
||
|
function redraw() {
|
||
|
_bbox = bbox();
|
||
|
graph.attr("viewBox", [0, 0, _bbox.width, _bbox.height]);
|
||
|
view.attr("width", _bbox.width - 1)
|
||
|
.attr("height", _bbox.height - 1);
|
||
|
}
|
||
|
|
||
|
d3.select(window)
|
||
|
.on("resize", function() {
|
||
|
redraw();
|
||
|
});
|
||
|
redraw();
|
||
|
|
||
|
const data = convert_data(all_nodes);
|
||
|
create_svg(data, redraw);
|
||
|
|
||
|
console.log("render");
|
||
|
|
||
|
})();
|