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", () => {
nodes.forEach(node => {
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);
}
})
})
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()
}