chart = {
const links = graphData.links;
const nodes = graphData.nodes;
const svg = d3.select(DOM.svg(width, height));
const layout = cola.d3adaptor(d3)
.size([width, height])
.nodes(nodes)
.links(links)
.jaccardLinkLengths(60, 0.7)
.start(30);
function linkO(d) {return .3 + Math.log10(d.value)*.3}
function linkW(d) {return .7 + Math.log10(d.value)*.6}
const marker = svg.append("defs").selectAll("marker")
.data(links)
.join("marker")
.attr("id", d => `arrow-${d.id}`)
.attr("viewBox", "0 -5 10 10")
.attr("refX", 15)
.attr("refY", -0.5)
.attr("markerWidth", 6)
.attr("markerHeight", 6)
.attr("orient", "auto")
.attr("stroke-width", .01)
.attr("opacity", .8)
.append("path")
.attr("fill", d => color(d.source.group))
.attr("stroke", d => color(d.source.group))
.attr("stroke-width", .01)
.attr("opacity", 0.8)
.attr("d", "M0,-5L10,0L0,5")
// Links
const link = svg.append("g")
.attr("stroke", "#999")
.attr("fill", "none")
.attr("stroke-width", 0.5)
.selectAll("line")
.data(links)
.enter()
.append("line")
.attr("stroke", d => color(d.source.group))
.attr("stroke-width", d=> linkW(d))
.attr("stroke-opacity", d=> linkO(d))
.attr("marker-end", d => `url(${new URL(`#arrow-${d.id}`, location)})`);
//hover-over spots in mid-links
const linkDot = svg.append("g")
.attr("stroke", "#889")
.attr("stroke-width", 0)
.selectAll("circle")
.data(links)
.enter().append("circle")
.attr("r", d => 10)
.attr("fill", "#889")
.attr("opacity", .04)
linkDot.append("title")
.text(d => d.source.className + " -> "+d.target.className +"\n" + d.value + "x");
//nodes
const node = svg.append("g")
.selectAll("circle")
.data(nodes)
.enter().append("circle")
.attr("r", d => 5 + Math.log2(d.count))
.attr("fill", d => color(d.group))
.attr("stroke", "#333")
.attr("stroke-width", .5)
.attr("opacity", .8)
.call(layout.drag);
node.append("title")
.text(d => d.className
+ " (called "+d.count+"x)\n"
+ d.packageName
//+ " - group " + d.group
+ "\n---\nIngress method calls: \n"
+ (function methods(iter){ let item = iter.next(); return item.done ? "" : " - "
+ item.value[0] + "(): " + item.value[1].count +"x\n" + methods(iter); })(d.inboundMethods.entries()))
// mouse-over event handlers
node.on('mouseover', function (d) {
// Highlight the nodes: every node is green except of him
node.style('stroke', "#333")
.style("stroke-width", 0.5)
.style('opacity',.4)
d3.select(this)
.style("stroke-width", 2.0)
.style('stroke', "#f3f")
.style("opacity", 1.0)
// Highlight the connections
link
.style('stroke-width', function (link_d) {
return link_d.source.id === d.id || link_d.target.id === d.id ? 1.5 + Math.log10(link_d.value) :
.4;})
.style('stroke-opacity', function (link_d) {
return link_d.source.id === d.id || link_d.target.id === d.id ? 1.0 :
.3;})
});
node.on('mouseout', function (d) {
node.style('stroke', "#333")
.style("stroke-width", 0.5)
.style("opacity", .8)
link.style("stroke-width", link_d => linkW(link_d))
.style('stroke-opacity', link_d => linkO(link_d))
.style("stroke", linkd_d => color(d.source.group))
marker.style("stroke", color(d.source.group))
.style("fill", color(d.source.group))
});
// highlight a link when moving over its mid-section
linkDot.on('mouseover', function (d) {
node
.style('opacity', function (dd) {
return d.source.id === dd.id || d.target.id === dd.id ? 1.0 : .4;})
.style('stroke-width', function (dd) {
return d.source.id === dd.id || d.target.id === dd.id ? 2.0 : .5;})
.style('stroke', function (dd) {
return d.source.id === dd.id || d.target.id === dd.id ? "#f3f" : "#333";})
link
.style('stroke', function (link_d) {
return link_d.id === d.id ? color(link_d.source.group) : color(link_d.source.group);})
.style('stroke-width', function (link_d) {
return link_d.id === d.id ? 1.5 + Math.log10(link_d.value) : .4 ;})
.style('stroke-opacity', function (link_d) {
return link_d.id === d.id ? 1.0 : .3 })
marker.style("stroke", link_d => color(link_d.source.group))
.style("fill", link_d => color(link_d.source.group))
});
linkDot.on('mouseout', function (d) {
node.style('stroke', "#333")
.style("stroke-width", 0.5)
.style("opacity", .8)
link.style("stroke", link_d => color(link_d.source.group))
.style("stroke-width", link_d => linkW(link_d))
.style('stroke-opacity', link_d => linkO(link_d))
marker.style("stroke", link_d => color(link_d.source.group))
.style("fill", link_d => color(link_d.source.group))
})
layout.on("tick", () => {
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);
node
.attr("cx", d => d.x)
.attr("cy", d => d.y);
linkDot
.attr("cx", d => (d.source.x + d.target.x)/2)
.attr("cy", d => (d.source.y + d.target.y)/2);
});
//legend
const scale = d3.scaleOrdinal()
.domain(Array.from(graphData.index_packages.keys()))
.range(colors)
const legend = d3Legend
.legendColor()
.shape("path", d3.symbol().type(d3.symbolCircle).size(50)()) //.shape("circle")
.shapePadding(20)
.labelOffset(5)
.scale(scale)
.labels(Array.from(graphData.index_packages.keys()));
svg.append("g")
.attr("class", "legend_auto")
.attr('fill',"#444")
.attr("font-family", "'Work Sans', sans-serif")
.attr('font-size', 12)
.attr("transform", `translate(${width - 200 + 5}, 20)`)
.call(legend)
invalidation.then(() => layout.stop());
return svg.node();
}