chart = {
var element = html`
<svg width=${width + margin.left + margin.right} height=${height+ margin.top + margin.bottom} style="border: 1px solid black;">
</svg>`;
const svg = d3.select(element)
.append("svg")
.append("g")
.attr("transform", `translate(${margin.left},${margin.top})`);
const links_spaces = data.edges.map(d => Object.create(d));
const links_output = data.links.map(d => Object.create(d));
svg.append('defs').append('marker')
.attr("id",'arrowhead')
.attr('viewBox','-0 -5 10 10')
.attr('refX',15)
.attr('refY',-0.5)
.attr('orient','auto')
.attr('markerWidth',6)
.attr('markerHeight',6)
.attr('xoverflow','visible')
.append('svg:path')
.attr('d', 'M 0,-5 L 10 ,0 L 0,5')
.attr('fill', 'black')
.style('stroke','black');
// Map nodes to circles
var node = svg.selectAll(".node")
.data(data.vertices)
.enter().append("g")
.attr("class", function (d) {
if (d.group === "space") {
return "space node";
} else {
return "device node";
}
})
.call(drag(simulation));
node.filter(function(d){
if (d.group==='space'){return d}
}).append("rect")
.attr("width", 40)
.attr("height", 40)
.attr('fill', d => colorScale(d.group))
.style('stroke',d=> d.attacked == true ? 'red' : 'black')
.attr("class", function (d) {
return "node type" + d.group
});
node.filter(function(d){
if (d.group==='device' || d.group ==='outside'){return d}
}).append("circle")
.attr("class", function (d) {
return "node type" + d.group
})
.attr("r", 25)
.attr("fill", d => colorScale(d.group))
.style('stroke',d=> d.attacked == true ? 'red' : 'black')
// let node = svg.append("g")
// .selectAll("circle").data(data.vertices).join('rect')
// .attr('fill', d => colorScale(d.group))
// .attr("width", 20)
// .attr("height", 20)
// .call(drag(simulation))
// .style('stroke',d=> d.attacked == true ? 'red' : null)
// .call(nodes => nodes.append("text").text(d => d.name));
// let node = svg.append("g")
// .selectAll("circle").data(data.vertices).join('circle')
// .attr('fill', d => colorScale(d.group))
// .attr('r', 30)
// .call(drag(simulation))
// .style('stroke',d=> d.attacked == true ? 'red' : null)
// .call(nodes => nodes.append("text").text(d => d.name));
// const link_output = svg.append("g")
// .attr("fill", "none")
// .attr("stroke-width", 2)
// .selectAll("path")
// .data(links_output)
// .join("path")
// .attr("stroke", "black")
// .attr("marker-end", 'url(#arrowhead)');
let link = svg.append("g")
.attr("stroke", "#999")
.attr("stroke-opacity", 1)
.selectAll("line")
.data(links_spaces)
.join("line")
// .style("stroke",d=> d.group == 3? "green":"grey")
.style("stroke","grey")
.style("stroke-dasharray", d => d.group == 2 ? 4 : 0)
.attr("stroke-width", d=>d.group===3 ? 0.5:1.5)
.style("pointer-events", "none");
link.filter((function(d){
if (d.group == 3){return d}
})).attr("marker-end", 'url(#arrowhead)');
const edgepaths = svg.selectAll(".edgepath") //make path go along with the link provide position for link labels
.data(data.links)
.enter()
.append('path')
.attr('class', 'edgepath')
.attr('fill-opacity', 0)
.attr('stroke-opacity', 0)
.attr('id', function (d, i) {return 'edgepath' + i})
.style("pointer-events", "none");
const edgelabels = svg.selectAll(".edgelabel")
.data(data.links)
.enter()
.append('text')
.style("pointer-events", "none")
.attr('class', 'edgelabel')
.attr('id', function (d, i) {return 'edgelabel' + i})
.attr('font-size', 14)
.attr('fill', '#aaa');
edgelabels.append('textPath') //To render text along the shape of a <path>, enclose the text in a <textPath> element that has an href attribute with a reference to the <path> element.
.attr('xlink:href', function (d, i) {return '#edgepath' + i})
.style("text-anchor", "middle")
.style("pointer-events", "none")
.attr("startOffset", "50%")
.text(d => d.attack);
const texts_widgets = svg
.selectAll(".id")
.data(data.vertices)
.enter()
.append("text")
.attr("class", "labels")
.attr("font-family", "bebas neue")
.attr("font-size", 13)
.attr("text-anchor", "middle")
.attr("dy", "0.35em")
.style('fill', 'black')
.attr("id", d => d.id)
.text(d => d.name)
.call(drag(simulation));
// Callback after every iteration of the simulation
simulation.on("tick", () => {
// Use D3 here to modify DOM objects based on the updated graph vertices' x,y from the simulation...
// node.attr('cx', v => v.x).attr('cy', v => v.y);
node.attr("transform", function (d) {
return "translate(" + d.x + "," + d.y + ")";
});
texts_widgets.attr("x", d => d.x).attr("y", d => d.y);
link
.attr("x1", d => d.source.x)
.attr("y1", d => d.source.y)
.attr("x2", d => d.target.x)
.attr("y2", d => d.target.y);
// link_output.attr("d", linkArc);
edgepaths.attr('d', d => 'M ' + d.source.x + ' ' + d.source.y + ' L ' + d.target.x + ' ' + d.target.y);
});
simulation.on('tick')() // call it at startup
// invalidation.then(() => simulation.stop());
const legend_g = svg.selectAll(".legend")
.data(colorScale.domain().filter(function(value, index, arr){
return value !="space";
})) // removing space from the domain because it needs to be a rectangle
.enter().append("g")
.attr("transform", (d, i) => `translate(${width-50},${i * 20})`);
legend_g.append("circle")
.attr("cx", 0)
.attr("cy", 0)
.attr("r", 5)
.attr('stroke','black')
.attr("fill", colorScale);
legend_g.append("text")
.attr("x", 10)
.attr("y", 5)
.text(d => d);
const legend_rect = svg.append("g")
//.attr("transform", (d, i) => `translate(${width},${i * 20})`);
.attr("transform", `translate(${width-50}, 40)`);
legend_rect.append("rect")
.attr("x",-5)
.attr('y',-5)
.attr('width',10)
.attr('height',10)
.attr('fill',"#ccebc5")
.attr('stroke','black')
legend_rect.append("text")
.attr("x", 10)
.attr("y", 5)
.text("space");
const legend_g2 = svg.append("g")
//.attr("transform", (d, i) => `translate(${width},${i * 20})`);
.attr("transform", `translate(${width-60}, 80)`);
legend_g2.append("line")
.attr("x1", 0)
.attr("y1", 0)
.attr("x2", 20)
.attr("y2", 0)
.style("stroke", "grey")
.attr('stroke-width',1.5)
legend_g2.append("text")
.attr("x",22)
.attr("y",0)
.text("physical edge");
legend_g2.append("line")
.attr("x1", 0)
.attr("y1", 25)
.attr("x2", 20)
.attr("y2", 25)
.style("stroke", "grey")
.style("stroke-dasharray",4)
legend_g2.append("text")
.attr("x",24)
.attr("y",25)
.text("cyber edge");
legend_g2.append("line")
.attr("x1", 0)
.attr("y1", 50)
.attr("x2", 20)
.attr("y2", 50)
.style("stroke", "green")
.style('stroke-width', 0.5)
legend_g2.append("text")
.attr("x",25)
.attr("y",50)
.text("containment edge");
legend_g2.append("line")
.attr("x1", 0)
.attr("y1", 75)
.attr("x2", 20)
.attr("y2", 75)
.style("stroke", "black")
.style('stroke-width', 2)
legend_g2.append("text")
.attr("x",25)
.attr("y",77)
.text("attack path");
return element;
}