Public
Edited
Nov 23, 2022
2 forks
Insert cell
Insert cell
Insert cell
chart = {
const imageIndentFactor = 0.1
const links = switchingGraph.links.map(d => Object.create(d))
const nodes = switchingGraph.nodes.map(d => Object.create(d))
const simulation = d3.forceSimulation(nodes)
.force("link", d3.forceLink(links)
.id(d => d.id)
.distance(d => d["value"]/frequencyDivisionFactor)
.strength(d => d["value"]/frequencyDivisionFactor))
.force("charge", d3.forceManyBody().strength(chargeForce))
.force("x", d3.forceX())
.force("y", d3.forceY())
.force("bounding-circle", () => { //radial bounding circle
nodes.forEach(node => {
// if node is outside of the bounding circle,
// move node just inside circle along same polar axis
if (distance([node.x, node.y], [x(0.5), y(0.5)]) > forceLayoutRadius) {
const theta = Math.atan((node.y - y(0.5)) / (node.x - x(0.5)));
node.x = x(0.5) + forceLayoutRadius * Math.cos(theta) * (node.x < x(0.5) ? -1 : 1);
node.y = y(0.5) + forceLayoutRadius * Math.sin(theta) * (node.x < x(0.5) ? -1 : 1);
}
})
})
//.alphaMin(0)
const svg = d3.create("svg")
.attr("viewBox", [-canvasParam.width * forceLayoutXOriginPercentage, -canvasParam.height * forceLayoutYOriginPercentage - li.h, canvasParam.width, canvasParam.height])
.style("font-size", `${fontSize} ${fontMeasure}`)
.style("background-color", bgColor)
const link = svg.append("g")
.attr("fill", "none")
.selectAll("path")
.data(links)
.join("path")
.attr("stroke", d => color(getEntityFromLabel(d.source.id,1)))
.attr("stroke-width", d => linkThicknessScale(d["value"]) )
.attr("class", (d) => `links both ${standardise_label(d.source.id)} ${standardise_label(d.target.id)}`)

const edgepaths = svg.selectAll(".edgepath")
.data(links)
.enter()
.append("path")
.attr("class", (d) => `edgepath ${standardise_label(d.source.id)} ${standardise_label(d.target.id)}`)
.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(links)
.enter()
.append("text")
.style("visibility", "hidden")
.style("pointer-events", "none")
.attr("class", (d) => `edgelabel ${standardise_label(d.source.id)} ${standardise_label(d.target.id)}`)
.attr("id", function(d, i) {
return "edgelabel" + i
})
.attr("font-size", function(d, i) {
return d["value"] > 0 ? fontSize : 0
})
.attr("font-weight", "bold")
.attr("fill", "#000000")
.attr("dy", fontSize * 1 / 3)
edgelabels.append("textPath")
.attr("xlink:href", function(d, i) {
return "#edgepath" + i
})
.style("text-anchor", "middle")
.style("pointer-events", "none")
.attr("class", "arrows")
.attr("startOffset", "50%")
.text(">>")
simulation.on("tick", () => {
link.attr("d", linkArc)
edgepaths.attr("d", linkArc)
edgelabels.attr("d", linkArc)
node.attr("transform", d => `translate(${d.x},${d.y})`)
})
const node = svg.append("g")
.attr("fill", "currentColor")
.attr("stroke-linecap", "round")
.attr("stroke-linejoin", "round")
.selectAll("g")
.data(nodes)
.join("g")
.attr("class", function(d) {
let clickable = switchingGraph.links.map((l) => l.source).indexOf(d.id) > -1? "clickable": ""
return `node ${standardise_label(d.id)} ${minBaseValidation(d, clickable)}`
})
.attr("id", d => `node_${d.index}`)
//.call(drag(this))
.call(drag(simulation))
.on("click", connectedNodes)
node.append("circle")
.classed("color-fill", true)
.attr("fill", d => color(getEntityFromLabel(d.id,1)))
.attr("r", d => radiusScale(d["value"]) )
// Append images
if(typeof(dataMockDisplayr.imageUrls) != "undefined") {
node.append("svg:image")
.attr('class', "node-image")
.attr("xlink:href", (d) => imageMap.get(getEntityFromLabel(d.id,0)).url )
.attr("x", (d) => -0.5 * ((1-imageIndentFactor)*radiusScale(d["value"]) * 2) / (Math.pow(2, 0.5)) )
.attr("y", (d) => -0.5 * ((1-imageIndentFactor)*radiusScale(d["value"]) * 2) / (Math.pow(2, 0.5)) )
.attr("height", (d) => ((1-imageIndentFactor)*radiusScale(d["value"]) * 2) / (Math.pow(2, 0.5)) )
.attr("width", (d) => ((1-imageIndentFactor)*radiusScale(d["value"]) * 2) / (Math.pow(2, 0.5)) )
.attr("overflow", "hidden")
.attr("opacity", 0.6)
/* .attr("width",(d) => `scale(${Math.pow( Math.pow( (1 - d3.max([getServiceSVG(d.id)["radiusIndentX"],getServiceSVG(d.id)["radiusIndentY"]]))*radiusScale(d["value"]),2) * 2 , 0.5)/getServiceSVG(d.id)["svgWidth"]})`)*/
}
node.append("circle")
.classed("image-mask", true)
.attr("stroke", "white")
.attr("stroke-width", 1.5)
.attr("fill", "transparent")
.attr("r", d => radiusScale(d["value"]) )
.on("mouseover", handleMouseOver)
.on("mouseout", handleMouseOut)

node.append("text")
.attr("x", 8)
.attr("y", "0.31em")
.text(d => getPercentageFrequency(
getReadableLabel(d.id), //entity
100*d["value"]/switchingGraph.nodesBase, //percent
nodeLabelStatsPctDecimal, //percent decimal
d["value"], //frequency
nodeLabelStats //type
)
)
.clone(true).lower()
.attr("fill", "none")
.attr("stroke", "white")
.attr("stroke-width", 3)

drawLegend(
svg, //svg
li, //li
false, //selectedNode
false //hoveredNode
)
//Create an array logging what is connected to what
let linkedByIndex = {}
switchingGraph.nodes.forEach(function(d, i) {
linkedByIndex[i + "," + i] = 1
})
switchingGraph.links.forEach(function(d) {
linkedByIndex[getNodeIndex(switchingGraph.nodes, d.source) + "," + getNodeIndex(switchingGraph.nodes, d.target)] = 1;
})

function resetNodes(hoveredNode) {

let allNodesUI = d3.selectAll(".node")
node.style("visibility", "visible")
link.style("visibility", "visible")
edgelabels.style("visibility", "hidden")

drawLegend(
svg, //svg
li, //li
false, //selectedNode
hoveredNode //hoveredNode
)
allNodesUI.classed("selected", false)
d3.selectAll(".node").each(function(selected) {
let selectedNodeByIndex = nodes.filter(n => n.index == selected.index)[0];
let num = selectedNodeByIndex["value"]
let perc = 100*num/switchingGraph.nodesBase
d3.selectAll(`#node_${selected.index} circle`)
.transition()
.duration(1500)
.ease(d3.easeElastic)
.attr("r", radiusScale(num) )
if(typeof(dataMockDisplayr.imageUrls) != "undefined") {
d3.selectAll(`#node_${selected.index} .node-image`)
.attr("x", function (d, i) {
return -0.5 * ((1-imageIndentFactor)*radiusScale(num) * 2) / (Math.pow(2, 0.5));
})
.attr("y", function (d, i) {
return -0.5 * ((1-imageIndentFactor)*radiusScale(num) * 2) / (Math.pow(2, 0.5));
})
.attr("height", (d) => ((1-imageIndentFactor)*radiusScale(num) * 2) / (Math.pow(2, 0.5)) )
.attr("width", (d) => ((1-imageIndentFactor)*radiusScale(num) * 2) / (Math.pow(2, 0.5)) )
}

d3.selectAll(`#node_${selected.index} text`)
.text(
getPercentageFrequency(
getReadableLabel(selectedNodeByIndex.id), //entity
perc, //percent
nodeLabelStatsPctDecimal, //percent decimal
num, //frequency
nodeLabelStats //type
)
)
}) //end .each
d3.selectAll("svg.main")
.classed("focused", false)
}
function connectedNodes() {
let d = d3.select(this).node().__data__; // ...or only as a child of the current DOM element
//console.log(this, d)
let nodeUI = d3.select(this)
let selectedNode = switchingGraph.nodes[d.index]
let allNodesUI = d3.selectAll(".node")
if (nodeUI.classed("clickable")) {
if (!nodeUI.classed("selected")) {
//UI
let sourceNodes = []
//hide all but the neighbouring nodes
//hide edgelabels of nodes, links where target is not the clicked
node.style("visibility", function(o) {
neighboring(linkedByIndex, d, o) ? sourceNodes.push(o) : "";
return neighboring(linkedByIndex, d, o) ? "visible" : "hidden"
})
link.style("visibility", function(o) {
//console.log(d, o)
return (d.index == o.source.index ? "visible" : "hidden")
})
edgelabels.style("visibility", function(o) {
return (d.index == o.source.index ? "visible" : "hidden")
})
allNodesUI.classed("selected", false)
nodeUI.classed("selected", true)
//all unselected nodes UI
d3.selectAll(".node").each(function(selected) {
let filteredLink = getSwitchingGraphLink(
switchingGraph.links,
selectedNode.id,
switchingGraph.nodes.filter(n => n.index == selected.index)[0].id)
let filteredNode = nodes.filter(n => n.index == selected.index)[0]
let num = filteredLink? filteredLink["value"]: 0
let perc = 100*num/selectedNode["value"]
d3.selectAll(`#node_${selected.index} circle`)
.transition()
.duration(1500)
.ease(d3.easeElastic)
.attr("r", radiusScale(num) )

if(typeof(dataMockDisplayr.imageUrls) != "undefined") {
d3.selectAll(`#node_${selected.index} .node-image`)
.attr("x", function (d, i) {
return -0.5 * ((1-imageIndentFactor)*radiusScale(num) * 2) / (Math.pow(2, 0.5));
})
.attr("y", function (d, i) {
return -0.5 * ((1-imageIndentFactor)*radiusScale(num) * 2) / (Math.pow(2, 0.5));
})
.attr("height", (d) => ((1-imageIndentFactor)*radiusScale(num) * 2) / (Math.pow(2, 0.5)) )
.attr("width", (d) => ((1-imageIndentFactor)*radiusScale(num) * 2) / (Math.pow(2, 0.5)) )
}

d3.selectAll(`#node_${selected.index} text`)
.text(
getPercentageFrequency(
getReadableLabel(filteredNode.id), //entity
perc, //percent
nodeLabelStatsPctDecimal, //percent decimal
num, //frequency
nodeLabelStats //type
)
)
}) //end .each
//seleced node UI
nodeUI
.select("circle")
.transition()
.duration(1500)
.ease(d3.easeElastic)
.attr("r",
radiusScale(d["value"])
)
nodeUI
.selectAll("text")
.text(`${getReadableLabel(selectedNode.id)}:${(d["value"]).toFixed(0)}`)
d3.selectAll("svg.main")
.classed("focused", true)
drawLegend(
svg, //svg
li, //li
selectedNode.id, //selectedNode
false //hoveredNode
)
} //end if (!nodeUI.classed("selected"))
else {
resetNodes(selectedNode.id)
}
} //endif (nodeUI.classed("clickable"))
} // end connectedNodes()

return svg.node()
}
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
function handleMouseOver(d,i) { // Add interactivity
let nodeIndex = d.target.__data__.hasOwnProperty("index")? d.target.__data__.index : getNodeIndex(switchingGraph.nodes, d.target.__data__.impacted)
//hovered rank interaction
d3.select(`#rank_node_${nodeIndex} circle`)
.attr("fill", "grey")
d3.select(`text#ranktext_node_${nodeIndex}`)
.attr("fill", "#fff")

//node interaction
d3.select(`#node_${nodeIndex} circle.image-mask`)
.attr("stroke", "grey")
}
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
colors = typeof dataMockDisplayr.colors != "undefined"? d3.index(dataMockDisplayr.colors, o => o._row): null
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
nodeListMapped = dataMockDisplayr.nodeListMapped
Insert cell
Insert cell
nodesBase = dataMockDisplayr.nodesBase

Insert cell
minBase = dataMockDisplayr.minBase
Insert cell
linksMatrixMapped = dataMockDisplayr.linksMatrixMapped.map(o => {
return {
...o,
significant: typeof o["significant"] != "undefined"? o["significant"] == " TRUE"? 1: 0: 0
}
}
)
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
hifi = FileAttachment("hifi.png").url()
Insert cell
speaker = FileAttachment("speaker.png").url()
Insert cell
images = {
const entities = Object.keys(dataMockDisplayr.imageUrls)
return entities.map(o => { return { name: o, url: dataMockDisplayr.imageUrls[o][0]} })
}
/*[
{name: "hifi", url: hifi},
{name: "speaker", url: speaker}
]*/
Insert cell
imageMap = d3.index(images, (o) => o.name)
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