function chart(whatwhat) {
const what = (whatwhat=="union" ? 1 : (whatwhat == "intersection" ? 2 : 0))
const root = tree(d3.hierarchy(dataDiff));
var allLeaves = root.leaves().map(function addInOutBounds(d){
d.data.parent = d;
d.data.inbound = [...d.data.ingressClasses.values()]
d.data.outbound = [...d.data.egressClasses.values()]
return d })
const linkData = allLeaves.flatMap(leaf => leaf.data.outbound.map(x => path(leaf, x.parent)))
var addedLeaves = root.leaves().map(function addInOutBounds(d){
d.data.parent = d;
d.data.inbound = [...d.data.ingressAdded.values()]
d.data.outbound = [...d.data.egressAdded.values()]
return d })
const linkDataAdded = addedLeaves.flatMap(leaf => leaf.data.outbound.map(x => path(leaf, x.parent)))
var removedLeaves = root.leaves().map(function addInOutBounds(d){
d.data.parent = d;
d.data.inbound = [...d.data.ingressRemoved.values()]
d.data.outbound = [...d.data.egressRemoved.values()]
return d })
const linkDataRemoved = removedLeaves.flatMap(leaf => leaf.data.outbound.map(x => path(leaf, x.parent)))
var leavesInBoth = root.leaves().map(function addInOutBounds(d){
d.data.parent = d;
d.data.inbound = [...d.data.ingressInBoth.values()]
d.data.outbound = [...d.data.egressInBoth.values()]
return d })
const linkDataInBoth = leavesInBoth.flatMap(leaf => leaf.data.outbound.map(x => path(leaf, x.parent)))
const svg = d3.create("svg")
.attr("viewBox", [-width / 2, -width / 2, width, width]);
// nodes == classes organized in packages
const node = svg.append("g")
.attr("font-family", "sans-serif")
.attr("font-size", 4)
.selectAll("g")
.data(allLeaves)
.join("g")
.attr("transform", d => `rotate(${d.x * 180 / Math.PI - 90}) translate(${d.y},0)`)
.append("text")
.attr("dy", "0.31em")
.attr("x", d => d.x < Math.PI ? 6 : -6)
.attr("text-anchor", d => d.x < Math.PI ? "start" : "end")
.attr("transform", d => d.x >= Math.PI ? "rotate(180)" : null)
.attr("fill", d=> what==0 ? (d.data.inMapOne? (d.data.inMapTwo? "#000" : "#500") : "#050"):
(what==2 ? ((d.data.inMapOne && d.data.inMapTwo) ? "#000" : "#ccc"): "#000"))
.text(d => (what==0? (d.data.inMapOne ? (d.data.inMapTwo ? "| " : "- ") : "+ "): "") + d.data.name)
.on("mouseover", textOver)
.on("mouseout", textOut)
.call(text => text.append("title").text(d => getText(d.data))
);
//links == class relationships
var link = null, linkOne = null, linkTwo = null;
if(what != 0) {
link = svg.append("g")
.attr("fill", "none")
.selectAll("path")
.data(d3.transpose((what == 2 ? linkDataInBoth : linkData).map(path => Array.from(path.split(k)))))
.join("path")
.style("mix-blend-mode", "darken")
.attr("opacity", 1.0)
.attr("stroke", (d, i) => d3.interpolateRdBu((d3.easeQuad(i / ((1 << k) - 1)))))
.attr("d", d => d.join(""))
} else {
link = svg.append("g")
.attr("fill", "none")
.selectAll("path")
.data(d3.transpose(linkDataInBoth.map(path => Array.from(path.split(k)))))
.join("path")
.style("mix-blend-mode", "darken")
.attr("opacity", 0.01)
.attr("stroke", (d, i) => d3.interpolateGreys(1-(d3.easeQuad(i / ((1 << k) - 1)))))
.attr("d", d => d.join(""))
linkOne = svg.append("g")
.attr("fill", "none")
.selectAll("path")
.data(d3.transpose(linkDataRemoved.map(path => Array.from(path.split(k)))))
.join("path")
.style("mix-blend-mode", "darken")
.attr("opacity", 1.0)
.attr("stroke-width", 1.7)
.attr("stroke", (d, i) => d3.interpolateOrRd(1-(d3.easeQuad(i / ((1 << k) - 1)))))
.attr("d", d => d.join(""))
linkTwo = svg.append("g")
.attr("fill", "none")
.selectAll("path")
.data(d3.transpose(linkDataAdded.map(path => Array.from(path.split(k)))))
.join("path")
.style("mix-blend-mode", "darken")
.attr("opacity", 1.0)
.attr("stroke-width", 1.7)
.attr("stroke", (d, i) => d3.interpolateGnBu((d3.easeQuad(i / ((1 << k) - 1)))))
.attr("d", d => d.join(""))
}
const overLink = svg.append("g")
.attr("fill", "none")
.selectAll("path")
.data((what == 2 ? linkDataInBoth : linkData).map(path => Array.from(path.split(1))))
.join("path")
.style("mix-blend-mode", "darken")
.attr("opacity", 0.0)
.attr("stroke-width", 1.5)
.attr("d", d => d.join(""))
function textOver(event, d) {
[link, linkOne, linkTwo].forEach( l=> { if (l!=null) l.style("opacity",0.05)});
overLink.style("opacity",0.0)
d3.select(this).attr("font-weight", "bold");
overLink.style("opacity", function (ol) {
if(ol[0].getSource()==d || ol[0].getTarget()==d) return .8; else
return 0
})
overLink.style("stroke", function (ol) {
if(what != 0) {//if not diff, blue called, red if calling
//if calling self, set color to purple
if(ol[0].getSource().data.id==d.data.id && ol[0].getTarget().data.id==d.data.id) return "#F3F";
if(ol[0].getSource()==d) return d3.interpolateRdBu(0.1);
if(ol[0].getTarget()==d) return d3.interpolateRdBu(.9);
return "#FFF"
} else {
return "#000"
}
})
}
function getText(node) {
return `${node.className} in package ${node.packageName}
- called from ${node.ingressClassesOne.size} classes in AppMap 1, total calls: ${node.countOne}
- called from ${node.ingressClassesTwo.size} classes in AppMap 2, total calls: ${node.countTwo}
coupling ingress diff: +${node.ingressAdded.size} classes, -${node.ingressRemoved.size} classes, | ${node.ingressInBoth.size} classes
- calls methods in ${node.egressClassesOne.size} classes in AppMap 1, total calls: ${node.callsOne}
- calls methods in ${node.egressClassesTwo.size} classes in AppMap 2, total calls: ${node.callsTwo}
coupling egress diff: +${node.egressAdded.size} classes, -${node.egressRemoved.size} classes, | ${node.egressInBoth.size} classes
`}
function textOut(event, d) {
[linkOne, linkTwo].forEach( l=> { if (l!=null) l.style("opacity",1.0)});
if(link!=null) link.style("opacity", (what == 0) ? 0.5 : 1.0)
overLink.style("opacity", 0.0)
d3.select(this).attr("font-weight", null);
}
return svg.node();
}