213 lines
6.9 KiB
JavaScript
213 lines
6.9 KiB
JavaScript
|
var svg = d3.select("svg#conversation-animation");
|
||
|
svg.attr("width", document.body.clientWidth);
|
||
|
svg.attr("height", document.body.clientHeight);
|
||
|
|
||
|
svg.append("defs").selectAll("marker")
|
||
|
.data(["arrowhead", "arrowhead_actor"])
|
||
|
.enter().append("marker")
|
||
|
.attr("id", function(d) { return d; })
|
||
|
.attr("viewBox", "0 -5 10 10")
|
||
|
.attr("refX", function (d) { return d === "arrowhead_actor" ? 125 : 25; })
|
||
|
.attr("refY", 0)
|
||
|
.attr("markerWidth", 6)
|
||
|
.attr("markerHeight", 6)
|
||
|
.attr("orient", "auto")
|
||
|
.append("path")
|
||
|
.attr("d", "M0,-5L10,0L0,5 L0, -5")
|
||
|
.style("stroke", "black")
|
||
|
.style("opacity", "1");
|
||
|
|
||
|
var width = +svg.attr("width"),
|
||
|
height = +svg.attr("height");
|
||
|
|
||
|
var color = d3.scaleOrdinal(d3.schemeCategory20);
|
||
|
|
||
|
var zoom = d3.zoom()
|
||
|
.scaleExtent([1 / 16, 2])
|
||
|
.wheelDelta(function () { return -d3.event.deltaY * (d3.event.deltaMode ? 60 : 1) / 500; })
|
||
|
.filter(function () { return d3.event.type === 'wheel' || d3.event.button === 1; });
|
||
|
|
||
|
var initialTransform = d3.zoomIdentity.translate(width/2, height/2).scale(0.5);
|
||
|
|
||
|
svg.append("rect")
|
||
|
.attr("width", width)
|
||
|
.attr("height", height)
|
||
|
.style("fill", "none")
|
||
|
.style("pointer-events", "all")
|
||
|
.call(zoom)
|
||
|
.call(zoom.transform, initialTransform);
|
||
|
|
||
|
var g = svg.append("g").attr("transform", initialTransform),
|
||
|
link = g.append("g").attr("stroke", "#000").attr("stroke-width", 1.5).selectAll(".link"),
|
||
|
node = g.append("g").attr("stroke", "#fff").attr("stroke-width", 1.5).selectAll(".node");
|
||
|
|
||
|
zoom.on("zoom", function () { g.attr("transform", d3.event.transform) });
|
||
|
|
||
|
var simulation = d3.forceSimulation()
|
||
|
.force("link", d3.forceLink()
|
||
|
.distance(function(d) {
|
||
|
if (d.target.type === "topic" && d.source.type === "actor") return 100;
|
||
|
if (d.target.type === "topic" || d.source.type === "topic") return 5;
|
||
|
if (d.target.type === "assertion" && d.source.type === "endpoint") return 10;
|
||
|
if (d.target.type === "facet" && d.source.type === "facet") return 50;
|
||
|
if (d.target.type === "facet" && d.source.type === "actor") return 50;
|
||
|
if (d.target.type === "actor" && d.source.type === "actor") return 500;
|
||
|
return 100;
|
||
|
})
|
||
|
.strength(function(d) {
|
||
|
if (d.target.type === "actor" && d.source.type === "actor") return 0.1;
|
||
|
return 0.5;
|
||
|
})
|
||
|
.id(function(d) { return d.id; }))
|
||
|
.force("charge", d3.forceManyBody())
|
||
|
.force("center", d3.forceCenter(0, 0))
|
||
|
.on("tick", function () {
|
||
|
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.attr("transform", function(d) { return "translate(" + d.x + "," + d.y + ")"; });
|
||
|
});
|
||
|
|
||
|
// function mkActor(n,c) { return {type: 'actor', id: n, color: c}; }
|
||
|
// function mkAssertion(n) { return {type: 'assertion', id: n}; }
|
||
|
// function mkLink(s,t,v) { return {source: s, target: t, value: v || 1}; }
|
||
|
|
||
|
var matchWeight = 10;
|
||
|
|
||
|
var graph = dataspaceContents;
|
||
|
|
||
|
// var clientCounter = 1;
|
||
|
// function clickHandler() {
|
||
|
// var currentCounter = clientCounter;
|
||
|
// clientCounter++;
|
||
|
// var c = "Client" + currentCounter;
|
||
|
// var n = "name" + currentCounter;
|
||
|
|
||
|
// graph.nodes.push(mkActor(c, "#FFC0C0"));
|
||
|
// graph.nodes.push(mkAssertion(entry(n,"127.0.0."+currentCounter)));
|
||
|
// graph.nodes.push(mkAssertion(obs(entry(n,"*"))));
|
||
|
// graph.nodes.push(mkAssertion(obs(laterThan(currentCounter))));
|
||
|
|
||
|
// graph.edges.push(mkLink("Cache", obs(entry(n,"*"))));
|
||
|
// graph.edges.push(mkLink("Server", entry(n,"127.0.0."+currentCounter)));
|
||
|
// graph.edges.push(mkLink(c, obs(entry(n,"*"))));
|
||
|
// graph.edges.push(mkLink("Cache", obs(laterThan(currentCounter))));
|
||
|
|
||
|
// graph.edges.push(mkLink(entry(n,"127.0.0."+currentCounter), obs(entry(n,"*")), matchWeight));
|
||
|
// graph.edges.push(mkLink(obs(entry(n,"*")), obs(obs(entry("*","*"))), matchWeight));
|
||
|
// graph.edges.push(mkLink(obs(laterThan(currentCounter)), obs(obs(laterThan("*"))), matchWeight));
|
||
|
|
||
|
// setTimeout(function () {
|
||
|
// graph.nodes.splice(graph.nodes.findIndex(function (n) { return n.id === c; }), 1);
|
||
|
// graph.edges.splice(graph.edges.findIndex(function (l) { return l.source.id === c; }), 1);
|
||
|
// setupGraph();
|
||
|
// }, 3000);
|
||
|
|
||
|
// setupGraph();
|
||
|
// }
|
||
|
|
||
|
// svg.on("click", clickHandler);
|
||
|
// svg.on("doubleclick", clickHandler);
|
||
|
|
||
|
function setupGraph() {
|
||
|
node = node.data(graph.nodes, function(d) { return d.id; });
|
||
|
node.exit().remove();
|
||
|
|
||
|
var group = node.enter().append("g")
|
||
|
.attr("class", function(d) { return d.type; })
|
||
|
.call(d3.drag()
|
||
|
.on("start", dragstarted)
|
||
|
.on("drag", dragged)
|
||
|
.on("end", dragended));
|
||
|
|
||
|
group.append("circle")
|
||
|
.attr("r", function(d) {
|
||
|
switch (d.type) {
|
||
|
case "actor": return 60;
|
||
|
case "facet": return 20;
|
||
|
case "endpoint": return 10;
|
||
|
default: return 5;
|
||
|
}
|
||
|
})
|
||
|
.attr("fill", function(d) {
|
||
|
switch (d.type) {
|
||
|
case "actor": return "green";
|
||
|
case "facet": return "cyan";
|
||
|
case "topic": return "blue";
|
||
|
case "endpoint": return "yellow";
|
||
|
default: return d.color || "red";
|
||
|
}
|
||
|
})
|
||
|
.on("click", function(d) {
|
||
|
var text = d3.select('#label_' + d.id);
|
||
|
text.style('opacity', text.style('opacity') == 1 ? 0 : 1);
|
||
|
release_pinning(d);
|
||
|
})
|
||
|
|
||
|
group.append("text")
|
||
|
.attr("id", function(d) { return 'label_' + d.id; })
|
||
|
.attr("stroke", "none")
|
||
|
.attr("text-anchor", function(d) {
|
||
|
switch (d.type) {
|
||
|
case "actor": return "middle";
|
||
|
default: return "start";
|
||
|
}
|
||
|
})
|
||
|
.attr("dx", function(d) {
|
||
|
switch (d.type) {
|
||
|
case "actor": return 0;
|
||
|
case "facet": return 20;
|
||
|
default: return 12;
|
||
|
}
|
||
|
})
|
||
|
.attr("dy", ".35em")
|
||
|
.style("pointer-events", "none")
|
||
|
.style("opacity", 0)
|
||
|
.text(function(d) { return d.label; });
|
||
|
|
||
|
node = group.merge(node);
|
||
|
|
||
|
link = link.data(graph.edges, function(d) { return d.source.id + "-" + d.target.id; });
|
||
|
link.exit().remove();
|
||
|
link = link.enter().append("line")
|
||
|
.attr("stroke-width", 1)
|
||
|
.style("marker-end", function (d) {
|
||
|
if (!(d.dir === 'forward' || d.dir === 'both')) return void 0;
|
||
|
// TODO: gross! Why don't I get d.target being an object here?
|
||
|
if (d.target.startsWith('ac_')) return "url('#arrowhead_actor')";
|
||
|
return "url('#arrowhead')";
|
||
|
})
|
||
|
.merge(link);
|
||
|
|
||
|
// Update and restart the simulation.
|
||
|
simulation.nodes(graph.nodes);
|
||
|
simulation.force("link").links(graph.edges);
|
||
|
simulation.alpha(3).restart();
|
||
|
}
|
||
|
|
||
|
function dragstarted(d) {
|
||
|
if (!d3.event.active) simulation.alphaTarget(3).restart();
|
||
|
d.fx = d.x;
|
||
|
d.fy = d.y;
|
||
|
}
|
||
|
|
||
|
function dragged(d) {
|
||
|
d.fx = d3.event.x;
|
||
|
d.fy = d3.event.y;
|
||
|
}
|
||
|
|
||
|
function dragended(d) {
|
||
|
if (!d3.event.active) simulation.alphaTarget(0);
|
||
|
// d.fx = null;
|
||
|
// d.fy = null;
|
||
|
}
|
||
|
|
||
|
function release_pinning(d) {
|
||
|
d.fx = null;
|
||
|
d.fy = null;
|
||
|
}
|
||
|
|
||
|
setupGraph();
|