chart = {
const data = data1;
const links = data.links.map(d => Object.create(d));
const nodes = data.nodes.map(d => Object.create(d));
const [width, height] = [1000, 1000];
const svg = d3.select(DOM.svg(width, height));
const aiScoreMinDiff = 0.2;
let linkedByIndex = {};
data.links.forEach(d => {
linkedByIndex[`${d.source},${d.target}`] = true;
});
let nodesById = {};
data.nodes.forEach(d => {
nodesById[d.id] = {...d
};
})
const isConnectedAsSource = (a, b) => linkedByIndex[`${a},${b}`];
const isConnectedAsTarget = (a, b) => linkedByIndex[`${b},${a}`];
const isConnected = (a, b) => isConnectedAsTarget(a, b) || isConnectedAsSource(a, b) || a === b;
const isEqual = (a, b) => a === b;
const nodeRadius = d => Math.sqrt(d.support) + 3;
const baseGroup = svg.append("g");
function zoomed() {
baseGroup.attr("transform", d3.event.transform);
}
const zoom = d3.zoom()
.scaleExtent([0.2, 8])
.on("zoom", zoomed);
svg.call(zoom);
let ifClicked = false;
const simulation = d3.forceSimulation()
.force("link", d3.forceLink().id(function (d) {
return d.id;
}).strength(0.3))
.force("charge", d3.forceManyBody())
.force("y", d3.forceY(height / 2).strength(0.1))
.force("x", d3.forceX(d => {
if (d.level === 1) {
return width / 6 - 50;
}
return width / 6 * d.level;
}).strength(0.95))
.force("collide", d3.forceCollide().radius(d => nodeRadius(d) + 1).iterations(2));
const link = baseGroup.append("g")
.selectAll("line")
.data(links)
.join("line")
.classed('link', true)
.style('stroke', d => {
const aiScoreDiff = nodesById[d.target].aiScore - nodesById[d.source].aiScore;
if (Math.abs(aiScoreDiff) < aiScoreMinDiff) {
return '#999';
} else {
if (aiScoreDiff > 0) {
return "#84c942";
} else {
return "#e85335";
}
}
})
.style("stroke-opacity", d => {
const aiScoreDiff = nodesById[d.target].aiScore - nodesById[d.source].aiScore;
if (Math.abs(aiScoreDiff) < aiScoreMinDiff) {
return 0.5;
} else {
return 1;
}
})
.style('stroke-width', d => {
const aiScoreDiff = nodesById[d.target].aiScore - nodesById[d.source].aiScore;
if (Math.abs(aiScoreDiff) < aiScoreMinDiff) {
return 0.5;
} else {
return 1.5;
}
});
const node = baseGroup.append("g")
.selectAll("circle")
.data(nodes)
.join("circle")
.classed('node', true)
.attr("r", d => nodeRadius(d))
.attr("fill", nodeColor);
const rightClickItems = ['Expand sub-network of the pattern', 'Mark the pattern', 'Mark compound population'];
const menuItems = baseGroup.selectAll(".menuitems")
.data(rightClickItems)
.join('g')
.classed('menuitems', true)
.attr('visibility', "hidden")
.attr('transform', `translate(${0}, ${0})`)
menuItems.append('rect')
.attr('x', 0)
.attr('y', (d, i) => i * 20)
.attr('width', 180)
.attr('height', 20);
menuItems.append('text')
.text(d => d)
.attr('x', 3)
.attr('y', (d, i) => 13 + i * 20)
.style('fill', 'black')
.style('font-size', '11px');
function ticked() {
link
.attr("x1", function (d) {
return d.source.x;
})
.attr("y1", function (d) {
return d.source.y;
})
.attr("x2", function (d) {
return d.target.x;
})
.attr("y2", function (d) {
return d.target.y;
});
node
.attr("cx", function (d) {
return d.x;
})
.attr("cy", function (d) {
return d.y;
});
}
simulation
.nodes(nodes)
.on("tick", ticked);
simulation.force("link")
.links(links);
const mouseOverFunction = d => {
tooltip.style("visibility", "visible")
.html(() => {
const content = `<strong>Pattern:</strong> <span>{${d.id.replace(/-/g, ',')}}</span>` + '<br>' +
`<strong>aiScore:</strong> <span>${d3.format('.2f')(d.aiScore)}</span>`;
return content;
});
if (ifClicked) return;
node
.transition(500)
.style('opacity', o => {
const isConnectedValue = isConnected(o.id, d.id);
if (isConnectedValue) {
return 1.0;
}
return 0.1;
});
link
.transition(500)
.style('stroke-opacity', o => {
return (o.source === d || o.target === d ? 1 : 0.1)
})
.transition(500)
.attr('marker-end', o => (o.source === d || o.target === d ? 'url(#arrowhead)' : 'url()'));
};
const mouseOutFunction = d => {
tooltip.style("visibility", "hidden");
if (ifClicked) return;
node
.transition(500)
.style('opacity', 1);
link
.transition(500)
.style("stroke-opacity", d => {
const aiScoreDiff = nodesById[d.target.id].aiScore - nodesById[d.source.id].aiScore;
if (Math.abs(aiScoreDiff) < aiScoreMinDiff) {
return 0.5;
} else {
return 1;
}
})
.style('stroke-width', d => {
const aiScoreDiff = nodesById[d.target.id].aiScore - nodesById[d.source.id].aiScore;
if (Math.abs(aiScoreDiff) < aiScoreMinDiff) {
return 0.5;
} else {
return 1.5;
}
});
};
const mouseClickFunction = d => {
d3.event.stopPropagation();
menuItems.attr('visibility', "hidden");
ifClicked = true;
node
.transition(500)
.style('opacity', 1)
link
.transition(500);
node
.transition(500)
.style('opacity', o => {
const isConnectedValue = isConnected(o.id, d.id);
if (isConnectedValue) {
return 1.0;
}
return 0.1
})
link
.transition(500)
.style('stroke-opacity', o => (o.source === d || o.target === d ? 1 : 0.1))
.transition(500)
.attr('marker-end', o => (o.source === d || o.target === d ? 'url(#arrowhead)' : 'url()'));
};
const rightClickActions = (d, menuItem, i) => {
if (i === 0) {
};
menuItems.attr('visibility', "hidden");
};
const rightClickFunction = d => {
d3.event.preventDefault();
ifClicked = true;
node
.transition(500)
.style('opacity', 1)
link
.transition(500);
node
.transition(500)
.style('opacity', o => {
const isConnectedValue = isConnected(o.id, d.id);
if (isConnectedValue) {
return 1.0;
}
return 0.1
})
link
.transition(500)
.style('stroke-opacity', o => (o.source === d || o.target === d ? 1 : 0.1))
.transition(500)
.attr('marker-end', o => (o.source === d || o.target === d ? 'url(#arrowhead)' : 'url()'));
tooltip.style("visibility", "hidden");
const position = {
x: d.x,
y: d.y
};
menuItems.attr('visibility', "visible")
.attr('transform', `translate(${position.x}, ${position.y})`)
.on('click', (menuItem, i) => rightClickActions(d, menuItem, i));
};
node.on('mouseover', mouseOverFunction)
.on('mouseout', mouseOutFunction)
.on('click', mouseClickFunction)
.on('contextmenu', rightClickFunction)
.on('mousemove', () => tooltip.style("top", (d3.event.pageY - 10) + "px").style("left", (d3.event.pageX + 10) + "px"));
svg.on('click', () => {
ifClicked = false;
node
.transition(500)
.style('opacity', 1);
link
.transition(500)
.style("stroke-opacity", 0.5)
menuItems.attr('visibility', "hidden");
});
return svg.node();
}