Published
Edited
May 19, 2022
Insert cell
md`# Input Graph VIZ 3`
Insert cell
Insert cell
Insert cell
data = {return {"vertices":[{'id': 'outside',
'name': 'outside',
'area': '1',
'level': '1',
'window': false,
'group':"outside",
'attacked': false},
{'id': 'room1',
'name': 'bedroom1',
'area': '1.2',
'level': '1',
'window': true,
'group':"space",
'attacked':false},
{'id': 'room2',
'name': 'bedroom2',
'area': '1.5',
'level': '1',
'window': true,
'group':"space",
'attacked':false},
{'id': 'livingroom',
'name': 'livingroom',
'area': '2',
'level': '1',
'window': true,
'group':"space",
'attacked':false},
{'name': 'gateway',
'type': 'central control',
'placement': 'livingroom',
'networks': ['WIFI', 'Z-Wave'],
'visibility': ['outside'],
'monitoring': [],
'group':"device",
'attacked':false},
{'name': 'smart speaker',
'type': 'controlled',
'placement': 'bedroom1',
'networks': ['WIFI', 'Z-Wave'],
'visibility': ['outside'],
'monitoring': [],
'group':"device",
'attacked':false},
{'name': 'smart lock',
'type': 'controlled',
'placement': 'livingroom',
'networks': ['WIFI', 'Z-Wave'],
'visibility': [],
'monitoring': [],
'group':"device",
'attacked':false},
{'name': 'smart camera',
'type': 'controlled',
'placement': 'bedroom1',
'networks': ['WIFI', 'Z-Wave'],
'visibility': [],
'monitoring': ['bedroom1'],
'group':"device",
'attacked':false}],
"edges":[
{"source":"bedroom2","target":"livingroom","distance":30,"group":1},
{"source":"smart speaker","target":"gateway","distance":30,"group":2},
{"source":"gateway","target":"smart lock","distance":10,"group":2},
{"source":"gateway","target":"smart camera","distance":30,"group":2},
{"source":"livingroom","target":"gateway","distance":10,"group":3},
{"source":"livingroom","target":"smart lock","distance":10,"group":3},
{"source":"bedroom1","target":"smart camera","distance":10,"group":3},
{"source":"bedroom1","target":"smart speaker","distance":10,"group":3}
],
"links":[{"source":"outside","target":"smart speaker","distance":20,"group":4,"attack":""},
{"source":"smart speaker","target":"gateway","distance":20,"group":4,"attack":""},
{"source":"gateway","target":"smart lock","distance":10,"group":4,"attack":""}
]}}
Insert cell
Insert cell
margin = ({top: 30, right: 100, bottom: 5, left: 5});
Insert cell
width = 900 - margin.left - margin.right
Insert cell
height = 600 - margin.top - margin.bottom
Insert cell
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)
// .attr("viewBox", [0, 0, width, height]);
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));
// Per-type markers, as they don't inherit styles.
// svg.append("defs").selectAll("marker")
// .data(types)
// .join("marker")
// .attr("id", d => `arrow-${d}`)
// .attr("viewBox", "0 -5 10 10")
// .attr("refX", 15)
// .attr("refY", -0.5)
// .attr("markerWidth", 6)
// .attr("markerHeight", 6)
// .attr("orient", "auto")
// .append("path")
// .attr("fill", "grey")
// .attr("d", "M0,-5L10,0L0,5");
svg.append('defs').append('marker')
.attr("id",'arrowhead')
.attr('viewBox','-0 -5 10 10') //the bound of the SVG viewport for the current SVG fragment. defines a coordinate system 10 wide and 10 high starting on (0,-5)
.attr('refX',15) // x coordinate for the reference point of the marker. If circle is bigger, this need to be bigger.
.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;
}
Insert cell
colorScale.domain().pop("space")
Insert cell
colorScale.domain().filter(function(value, index, arr){
return value !="space";
});
Insert cell
types = [1,2,3,4]
Insert cell
function linkArc(d) {
const r = Math.hypot(d.target.x - d.source.x, d.target.y - d.source.y);
return `
M${d.source.x},${d.source.y}
A${r},${r} 0 0,1 ${d.target.x},${d.target.y}
`;
}
Insert cell
data
Insert cell
output = 'smart speaker : 40.0 : Light command : outside, smart speaker, gateway, smart lock'
Insert cell
output_list = output.split(':')
Insert cell
// add target node
data1.vertices = data.vertices.map((item) => {
const updatedItem = {
...item,
target: item.name === output_list[0].trim(),
};
return updatedItem;
});


Insert cell
colorScale = d3.scaleOrdinal()
.domain(["outside","device","space"])
.range(['#fbb4ae','#b3cde3','#ccebc5']);
Insert cell
colorScale("outside")
Insert cell
colorScale("space")
Insert cell
simulation = {
var sim = d3.forceSimulation(data.vertices)
.force("link", d3.forceLink(data.edges.concat(data.links))
.id(v => v.name)
.distance(v => v.distance*10) // scaling the distance by a factor of 4 to cover the svg

)
// .force("charge", d3.forceManyBody().strength(-100))
.force("center", d3.forceCenter(width/2, height/2))
.force("collide", d3.forceCollide(30))
;
invalidation.then(() => sim.stop()); //Terminates old simulation before this cell is re-run.
return sim;
}
Insert cell
data.edges.concat(data.links)
Insert cell
data.edges
Insert cell
drag = simulation => {
function dragstarted(event) {
if (!event.active) simulation.alphaTarget(0.3).restart();
event.subject.fx = event.subject.x;
event.subject.fy = event.subject.y;
}
function dragged(event) {
event.subject.fx = event.x;
event.subject.fy = event.y;
}
function dragended(event) {
if (!event.active) simulation.alphaTarget(0);
event.subject.fx = null;
event.subject.fy = null;
}
return d3.drag()
.on("start", dragstarted)
.on("drag", dragged)
.on("end", dragended);
}
Insert cell
d3 = require("d3@6")
Insert cell

Purpose-built for displays of data

Observable is your go-to platform for exploring data and creating expressive data visualizations. Use reactive JavaScript notebooks for prototyping and a collaborative canvas for visual data exploration and dashboard creation.
Learn more