3dmol / 3Dmol.js

WebGL accelerated JavaScript molecular graphics library
https://3dmol.org/
Other
794 stars 194 forks source link

Colour existing surface without having to remove, and add new surface? #729

Closed JavierSanchez-Utges closed 11 months ago

JavierSanchez-Utges commented 11 months ago

Is your feature request related to a problem? Please describe. I am new to 3DMol.js, and I really appreciate the work. Just moved from JSMol, and happy with the speed and graphics. However, I can't find a way to colour an existing surface without removing all surfaces, and creating new surfaces. E.g., I have a white surface, and then I want to colour 10 residues in red, then 10 in blue, etc. Then all back to white, etc.

This might exist, and I just can't find it. I thought it might have to do with the surfaceColor attribute (https://3dmol.csb.pitt.edu/doc/specs.ts.html line 25), but I can't figure it out.

Describe the solution you'd like I want to be able to colour the existing surface of given residues, without adding/removing any surfaces. This is to make it more dynamic. I see on my web app what is taking longest is adding the surface.

Describe alternatives you've considered(Optional) I am now removing and adding different surfaces, e.g., going from all: white --> mySel: red, not mySel: white, I remove all surfaces, then I add surface for mySel in red, and another for not mySel (using invert:true) in white.

Additional context(Optional) This is how I am doing currently what I just explained on the above paragraph:

if (surfaceVisible) { viewer.removeAllSurfaces(); viewer.addSurface( // adds coloured surface to binding site $3Dmol.SurfaceType.ISO, {opacity: 0.9, color: "red"}, {resi: rowId, hetflag: false}, {resi: rowId, hetflag: false} ); viewer.addSurface( // adds white surface to rest of protein $3Dmol.SurfaceType.ISO, {opacity: 0.7, color: 'white'}, {resi: rowId, invert: true, hetflag: false}, {hetflag: false}, ); }

Please close your issue when you feel it has been adequately resolved

dkoes commented 11 months ago

See setSurfaceMaterialStyle: https://3dmol.org/doc/GLViewer.html#setSurfaceMaterialStyle

https://3dmol.org/tests/auto/generate_test.cgi?test=test87

Note in order to change the geometry of the surface (e.g., go from VDW to SAS) you must recreate the surface.

JavierSanchez-Utges commented 11 months ago

That is great. Exactly what I needed. Thank you very much.

In my case, I might want to highlight the surface of individual residues, one at a time, e.g., when hovering over a row of a given protein residue.  Right now, what I am doing is like I said yesterday remove + create surface for individual residue. Do you think it would be scalable to create surface for each residue, after the viewer is created, and then just show or hide that surface (hide with opacity: 0)? Or having a surface per residue would be too slow?

Many thanks.

dkoes commented 11 months ago

I don't think having a bunch of small surfaces would be problematic, and showing/hiding them should be more efficient than recreating them on the fly.

JavierSanchez-Utges commented 11 months ago

That is very helpful. I have not tried yet the 1-surf-per-residue approach, but the original issue was solved with your first response, so closing the issue. Many thanks for the quick support and the great program!

jacobdurrant commented 1 month ago

I have a question related to this issue. Let me know if I should open a separate ticket.

When creating a new surface like this:

let surfId = await viewer.addSurface($3Dmol.SurfaceType.VDW, {
    colorscheme: "chain"
});

I can use a style that includes the colorscheme keyword. But when changing the style like this:

viewer.setSurfaceMaterialStyle(surfId, {
    colorscheme: 'ssJmol'
});

The style is not updated. However, the style does update if using the color keyword instead:

viewer.setSurfaceMaterialStyle(surfId, {
    color: "green"
});

Is it possible to update the surface style using colorscheme?

Here's a complete minimal example, in case it helps:

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>3Dmol.js Example</title>
    <script src="https://3dmol.csb.pitt.edu/build/3Dmol-min.js"></script>
    <script src="https://code.jquery.com/jquery-3.6.0.min.js"></script>

</head>
<body>
    <div style="width: 100%; height: 500px;" id="viewer"></div>

    <script>
        // Initialize the 3Dmol.js viewer
        var viewer = $3Dmol.createViewer("viewer", {
            backgroundColor: 'white'
        });

        async function wait(ms) {
            return new Promise(resolve => {
                setTimeout(resolve, ms);
            });
        }

        // Load the PDB structure (1XDN)
        $.ajax({
            url: "https://files.rcsb.org/download/1XDN.pdb",
            success: async function(data) {
                viewer.addModel(data, "pdb");

                // Add a surface representation
                let surfId = await viewer.addSurface($3Dmol.SurfaceType.VDW, {
                    colorscheme: "chain"
                });

                // Render the scene
                viewer.zoomTo();
                viewer.render();

                await wait(500);
                alert("Get ready to change the protein green...")
                viewer.setSurfaceMaterialStyle(surfId, {
                    color: "green"
                });
                viewer.render();
                // Works!

                await wait(500);
                alert("Try to color by chain again...")
                viewer.setSurfaceMaterialStyle(surfId, {
                    colorscheme: "chain"
                });
                viewer.render();
                // Fails!

                await wait(500);
                alert("Try to color by secondary structure...")
                viewer.setSurfaceMaterialStyle(surfId, {
                    colorscheme: 'ssJmol'
                });
                viewer.render();
                // Fails!

                await wait(500);
                alert("Color red...")
                viewer.setSurfaceMaterialStyle(surfId, {
                    color: 'red'
                });
                viewer.render();
                // Works!

                // Just to further prove that colorscsheme is available when the
                // surface is created...
                await wait(500);
                alert("Color by secondary structure...")
                viewer.removeSurface(surfId);
                surfId = await viewer.addSurface($3Dmol.SurfaceType.VDW, {
                    colorscheme: 'ssJmol'
                });
                viewer.render();
                // Works!

            }
        });
    </script>
</body>
</html>

Thanks!

JavierSanchez-Utges commented 1 month ago

Hi, Jacob. Unsure about this one, but it might be because the surface does not have the property ss of secondary structure that the colorscheme uses to colour: ssJmol: { prop: "ss", map: ssColors.Jmol },

https://3dmol.csb.pitt.edu/doc/colors.ts.html.

When I used this, I created a property for my atoms:

resultTuples.forEach(([modId, chain, resi, pBs]) => {

                var mySel = viewer.models[modId].atoms.filter(atom => atom.chain === chain & atom.resi === resi);
                mySel.forEach(atom => {atom.properties["bs"] = pBs;});
            });

Then I created a colour scheme:

myScheme = {prop: "bs", map: myMap}

which I used to colour the atoms: viewer.addStyle({and:[{hetflag: true}, {not:{resn: "HOH"}}, {not: {properties:{ bs: -1}}}]}, {stick: {hidden: true, colorscheme: myScheme, radius: stickRadius}});

PS: Is this for POVME?

dkoes commented 1 month ago

The surface doesn't retain its connection to the atoms that form it so the only permitted style changes can't depend on atom properties.

JavierSanchez-Utges commented 1 month ago

@jacobdurrant you could create 3 independent surfaces, one for each ss type and assign the specific colour to match that colour scheme. I use this to make so:

surfsDict["superposition"][key] = viewer.addSurface(
                    $3Dmol.SurfaceType.ISO,
                    {
                        color: siteColor,
                        opacity: surfaceHiddenOpacity,
                    },
                    {resi: PDBResNums}, // residues with Helix
                    {resi: PDBResNums}, // all protein residues, or residues with helix
                );

Depending on what you put on the allsel, these would be independent surfaces that you could visualise independently, or one whole surface where different regions are coloured in different colours, or that is my understanding. More info here: https://3dmol.csb.pitt.edu/doc/GLViewer.html#addSurface

Screenshot 2024-08-26 at 19 40 42

This is an example of when atomsel == allsel. The surface is enclosed. Hope it helps!

jacobdurrant commented 1 month ago

Hi David and Javier,

Thanks so much for all your help in answering this question. It makes sense that the atom information wouldn't remain associated with the surface vertices.

Javier, I really like your innovative solution. I'm looking for something more generic, though, that I can apply to any style. I think I'll just continue my current approach of destroying the old surface and creating a new one with the new style. I'll definitely keep in mind that it's possible to create subsurfaces like that,though. That's very helpful.

Javier, since you asked, this is actually for a planned update to my MolModa software: https://durrantlab.pitt.edu/molmoda/ 3dmol.js has been a great help in that project.

Thanks again. I really appreciate all your advice.

dkoes commented 1 month ago

I've pushed code to support changing surface colorschemes using the associated atom properties. For example: https://3dmol.org/tests/auto/generate_test.cgi?test=surfacechange