How to draw atoms and molecules using the super awesome D3.js library?
The full source code can be found on github.
Mike Bostock's this
example demonstrated a hacky way to add multiple links between 2 nodes. His approach when novel for a double bond will not easily scale for triple, quadruple bonds.
So, how to render higher bonds in a scalable and neat way?
The trick: The x, y positions of the links have to be manually updated in the "tick" functionality of a force directed graph.
The general idea to solve this problem is to offset the links from the center to an amount in lieu to the orientation of the bond.
Which means at each instance of the link orientation, the slope of the link must be calculated and the x, y components will accordingly be offseted.
Have scale functions for X and Y axes.
var thetaXScale = d3.scale.linear()
.range([0.1, this.bondThickness])
.domain([0, 90]);
var thetaYScale = d3.scale.linear()
.range([this.bondThickness, 0.1])
.domain([0, 90]);
A helper function which computes the slope of the line.
var compute_angle = function(x1, y1, x2, y2) {
var tan_theta = (parseFloat(y2) - parseFloat(y1)) / (parseFloat(x2) - parseFloat(x1));
var slope = Math.atan(tan_theta);
return slope;
}
The function which is called in the tick function of the force directed graph which returns the translate string with the x and y components.
The parameters to this function are 'd' which is the link data; 'dir' is the direction which means whether the link is to be offsetted to the left or right to the central line. Finally, 'bond_type' is whether the bond is single, double, triple, quadruple.
var compute_translation = function(d, dir, bond_type,) {
var x = 0;
var y = 0;
var slope = compute_angle(d.source.x, d.source.y, d.target.x, d.target.y) * 180 / Math.PI;
if (dir == 'left' && slope < 0) {
x = bond_type == "triple" ? -thetaXScale(Math.abs(slope)) * 3 : -thetaXScale(Math.abs(slope)) * 2;
y = bond_type == "triple" ? -thetaYScale(Math.abs(slope)) * 3 : -thetaYScale(Math.abs(slope)) * 2;
} else if (dir == 'left' && slope > 0) {
x = bond_type == "triple" ? thetaXScale(Math.abs(slope)) * 3 : thetaXScale(Math.abs(slope)) * 2;
y = bond_type == "triple" ? -thetaYScale(Math.abs(slope)) * 3 : -thetaYScale(Math.abs(slope)) * 2;
} else if (dir == 'right' && slope < 0) {
x = bond_type == "triple" ? thetaXScale(Math.abs(slope)) * 3 : thetaXScale(Math.abs(slope)) * 2;
y = bond_type == "triple" ? thetaYScale(Math.abs(slope)) * 3 : thetaYScale(Math.abs(slope)) * 2;
} else if (dir == 'right' && slope > 0) {
x = bond_type == "triple" ? -thetaXScale(Math.abs(slope)) * 3 : -thetaXScale(Math.abs(slope)) * 2;
y = bond_type == "triple" ? thetaYScale(Math.abs(slope)) * 3 : thetaYScale(Math.abs(slope)) * 2;
} else if (dir == 'left2' && slope < 0) {
x = -thetaXScale(Math.abs(slope)) * 6;
y = -thetaYScale(Math.abs(slope)) * 6;
} else if (dir == 'left2' && slope > 0) {
x = thetaXScale(Math.abs(slope)) * 6;
y = -thetaYScale(Math.abs(slope)) * 6;
} else if (dir == 'right2' && slope < 0) {
x = thetaXScale(Math.abs(slope)) * 6;
y = thetaYScale(Math.abs(slope)) * 6;
} else if (dir == 'right2' && slope > 0) {
x = -thetaXScale(Math.abs(slope)) * 6;
y = thetaYScale(Math.abs(slope)) * 6;
}
return "translate(" + x + "," + y + ")";
}
I have not only implemented double, triple or quadruple bonds.
What if different types of links (bonds) are to be rendered. For example, in chemistry, there are bonds like -
1) Dashed bonds (triangular shaped striped filled meaning bond is into-the-paper)
2) Wedged Bonds (triangular shaped solid filled meaning bond is out-of-paper)
The above logic is applicable to these type of bonds too. Only one side (either source or the destination) is to be offsetted left and right of the central line. And we will get a triangular bond.
Thanks.