问题
I don't know about you people but I love me some Gnuplot. Properly used, that software produces beautiful images, charming in their simplicity and clarity, that I am very fond of.
For no particular reason, one day I caught myself thinking how good would it be if I could create pictures of such cartoonish charm and vibrant clarity to go into my papers and personal scientific journal. So went head first into a batshit project to code a gnuplot-based molecule visualizer.
So far it is tailor made for my specific type of molecule. Basically covalently bonded atoms that form ligands, which themselves interact with some central metal ions via coordination bonds. I have managed to arrive at a pretty good working concept, pictured down below.
In it, the dotted lines denote a coordination bond with a metallic ion of Europium, colored in light cyan, the solid lines are covalent bonds between atoms. Red is oxygen, blue is nitrogen, white is hydrogen and grey is carbon. So far so good, seems pretty solid and very much in line with what I wanted.
So how do I do it, I hear you asking? Well that is pretty simple, actually. I plot things one at a time. First I plot the connectivity pattern of the dotted lines, like so:
Then I paint in the covalent bonds:
Each of the steps requires one or more separate files. The connectivity of each ligand is stored in a separate "bondfile" and the dotted conectivity pattern is in a file all of itself. The positions of the atoms with the color that they have is placed in yet another file. One for each ligand and one for the central metal.
Then I have a separate file for the atoms of the metal and of each ligand, where I say what color they are. The fact that the atoms are placed over the black dots is what gives the charming black layout around the points, otherwise they have no contour line.
THE ISSUE
The issue arises when you want to rotate the complex to get a better angle to save into the picture. In ordert o illustrate the problem I am going to show it in action with the picture of a single ligand. Let's take the bipyridine (the one with the nitrogens, there are two of them)
So here is a bipyridine in what I think is its optimal angle:
Now suppose we turn the bipyridine along the axis shown in the figure below.
Now the problem shows up. Because some atoms that should be in the behind plane are in fact in front of the entire thing, revealing that gnuplot actually don't have perspective. Or, at the very least, that it indeed have but I am using it incorrectly.
So far so good. I didn't expect it to have perspective automagically since this is not what it was originally made for. However, that means that gnuplot "splot" does a somewhat fake 3D plotting and that the actual relative positions of the points in space matters little.
So my question is, for all of you gnuplot/perspective savants out there:Is there a way to cleverly circumvent this limitation?
I am interested in any method, however involved it may be as long as it is feasible within the limitations of gnuplot itself.
回答1:
Heh. I'm a molecular graphics wonk myself, having written viewers and visualization tools since grad student days in the 1970s. And you know what? I really dislike the use of perspective in molecular graphics. So much so that I'll call the absence in gnuplot a feature rather than a limitation.
There is demo molecule.dem
in the gnuplot collection showing simple molecular graphics. It works better in the development version of gnuplot (5.3), where you can use plotting style "with circles" rather than "with points" for the atoms. Here you go:
set title "GM1 pentasaccharide ball-and-stick representation"
set hidden3d
set border 0
unset tics
unset key
set title offset 0, screen -0.85
set view equal xyz
set view 348, 163, 1.64872, 1.14
set style fill transparent solid 0.9 border -1
atomcolor(name) = name[1:1] eq "O" ? 0xdd2222 : name [1:1] eq "N" ? 0x4444ff : 0x888888
splot 'GM1_sugar.pdb' using 6:7:8:(0.6):(atomcolor(strcol(3))) with circles fc rgb var, \
'GM1_bonds.r3d' using 1:2:3:($5-$1):($6-$2):($7-$3) with vectors nohead lw 3 lc "black"
Notes:
- The atom positions are read directly from a PDB file
- Atom coloring is generated from the atom name (gray for carbon, blue for nitrogen, etc)
- The bonds were generated from the same PDB file by the "bonds" utility in the Raster3D molecular graphics package
- Occlusion of the atoms in back by the ones in front is handled by "set hidden3d"
- Occlusion of the bonds is less satisfactory because the line segment is drawn all the way to the atom center whereas visually it would look better to terminate at the projected spherical surface of the atom.
- Visual impression of depth is further helped by making the atoms partially transparent.
回答2:
Some time ago, I tried something similar with gnuplot. Apparently, points and lines do not respect the 3D-order. However, it will work if you draw with surfaces, i.e. atoms=spheres and bonds=cylinders. The input is a list of atom positions and a bond list. You plot the spheres and cylinders at the atom and bond positions, respectively. For the bonds you have to apply some rotation matrix. Unfortunately, gnuplot does not support vector and matrix operations (at least I couldn't find it) that's why the code is a bit lengthy and confusing, but it seems to work. My guess is if the molecule has many more atoms the plotting might get a bit slow. I don't know where the black dots at the bottom of the spheres are coming from. The following code was tested under Win7/64, gnuplot 5.2.6 using a wxt terminal.
Code:
### draw a molecule
reset session
set view equal xyz
set view 45,45
set border 4095
unset tics
unset colorbox
unset key
set margins 0,0,0,0
set style fill solid 1.0 noborder
set pm3d depthorder noborder
set pm3d lighting specular 0.5
set parametric
# sphere (atom prototype)
set isosamples 24
set samples 24
set urange [-pi/2:pi/2]
set vrange [0:2*pi]
Radius = 1
set table $Sphere
splot Radius*cos(u)*cos(v), Radius*cos(u)*sin(v), Radius*sin(u)
unset table
# cylinder (bond prototype)
set isosamples 2
set samples 12
set urange [-pi:pi]
set vrange [0.2:0.8]
BondRadius = 0.05
set table $Cylinder
splot BondRadius*cos(u), BondRadius*(sin(u)), v
unset table
unset parametric
# AtomNo., AtomType, X, Y, Z
$Atoms <<EOD
1 6 -2.0625 0.0000 0.0000
2 6 -1.6500 -0.7145 0.0000
3 7 -0.8250 -0.7145 0.0000
4 6 -0.4125 0.0000 0.0000
5 6 -0.8250 0.7145 0.0000
6 6 -1.6500 0.7145 0.0000
7 6 0.4125 0.0000 0.0000
8 7 0.8250 -0.7145 0.0000
9 6 1.6500 -0.7145 0.0000
10 6 2.0625 0.0000 0.0000
11 6 1.6500 0.7145 0.0000
12 6 0.8250 0.7145 0.0000
13 1 2.0625 1.4289 0.0000
14 1 0.4125 1.4289 0.0000
15 1 2.8875 0.0000 0.0000
16 1 2.0625 -1.4289 0.0000
17 1 -0.4125 1.4289 0.0000
18 1 -2.0625 1.4289 0.0000
19 1 -2.8875 0.0000 0.0000
20 1 -2.0625 -1.4289 0.0000
EOD
# Bond list
# BondNo., Atom1, Atom2
$Bondlist <<EOD
1 1 2
2 2 3
3 3 4
4 4 5
5 5 6
6 6 1
7 4 7
8 7 8
9 8 9
10 9 10
11 10 11
12 11 12
13 12 7
14 11 13
15 12 14
16 10 15
17 9 16
18 5 17
19 6 18
20 1 19
21 2 20
EOD
AtomType(atom) = int(word($Atoms[atom],2))
AtomName(n) = n==1 ? "H" : n==6 ? "C" : n==7 ? "N" : n==8 ? "O" : "X"
AtomSizeScaling = 0.12
AtomSize(n) = AtomSizeScaling*(n==1 ? 1.5 : n==6 ? 2.5 : n==7 ? 3.0 : n==8 ? 2.5 : 2.0)
AtomColor(n) = n==1 ? 1 : n==6 ? 6 : n==7 ? 7 : n==8 ? 8 : 0
Atom(atom,axis) = word($Atoms[atom],axis+2) + (column(axis)*AtomSize(AtomType(atom)))
AtomPos(atom,axis) = word($Atoms[atom],axis+2)
set palette defined (-1 "#cccccc", 0 "#000000", 1 "#ffffff", 6 "#888888", 7 "#0000ff", 8 "#ff0000")
BondStart(i) = int(word($BondList[i],2))
BondEnd(i) = int(word($BondList[i],3))
# Bond direction vector
V_x(bond) = AtomPos(BondEnd(i),1) - AtomPos(BondStart(i),1)
V_y(bond) = AtomPos(BondEnd(i),2) - AtomPos(BondStart(i),2)
V_z(bond) = AtomPos(BondEnd(i),3) - AtomPos(BondStart(i),3)
# rotation axis vector normalized, (cross-product of V and z-axis)
R_x(bond) = V_y(bond)/sqrt(V_x(bond)**2 + V_y(bond)**2)
R_y(bond) = -V_x(bond)/sqrt(V_x(bond)**2 + V_y(bond)**2)
R_z(bond) = 0
# rotation angle (between V and z-axis)
A(bond) = -acos(V_z(bond)/sqrt(V_x(bond)**2+V_y(bond)**2+V_z(bond)**2))
# Rotation of points
P_x(bond,x,y,z) = x*(R_x(bond)*R_x(bond)*(1-cos(A(bond)))+ cos(A(bond))) + \
y*(R_x(bond)*R_y(bond)*(1-cos(A(bond)))-R_z(bond)*sin(A(bond))) + \
z*(R_x(bond)*R_z(bond)*(1-cos(A(bond)))+R_y(bond)*sin(A(bond))) + \
AtomPos(BondStart(i),1)
P_y(bond,x,y,z) = x*(R_y(bond)*R_x(bond)*(1-cos(A(bond)))+R_z(bond)*sin(A(bond))) + \
y*(R_y(bond)*R_y(bond)*(1-cos(A(bond)))+ cos(A(bond))) + \
z*(R_y(bond)*R_z(bond)*(1-cos(A(bond)))-R_x(bond)*sin(A(bond))) + \
AtomPos(BondStart(i),2)
P_z(bond,x,y,z) = x*(R_z(bond)*R_x(bond)*(1-cos(A(bond)))-R_y(bond)*sin(A(bond))) + \
y*(R_z(bond)*R_y(bond)*(1-cos(A(bond)))+R_x(bond)*sin(A(bond))) + \
z*(R_z(bond)*R_z(bond)*(1-cos(A(bond)))+ cos(A(bond))) + \
AtomPos(BondStart(i),3)
set cbrange [-1:8]
splot \
for [i=1:|$BondList|] $Cylinder u \
(P_x(i,$1,$2,$3)):(P_y(i,$1,$2,$3)):(P_z(i,$1,$2,$3)):(-1) w pm3d, \
for [i=1:|$Atoms|] $Sphere u (Atom(i,1)):(Atom(i,2)):(Atom(i,3)):(AtomType(i)) w pm3d,\
### end of code
Result:
来源:https://stackoverflow.com/questions/57895078/how-to-correctly-add-perspective-to-a-gnuplot-3d-connected-point-cloud-represent