Git Product home page Git Product logo

Comments (3)

ppillot avatar ppillot commented on June 11, 2024

It seems that the opacity is controlled by the property checked of each pocket object in the pockets array.
When this value changes, I suspect that the pockets array changes as well.
pockets is reactive dependency of 2 different useEffect: one that sets the opacity, and the other one that loads the pocket data.

When pockets changes both useEffect are executed: the second one changes the opacity of representations, and the first one reloads data from server. When the data from server is fetched, setPocketData is executed.
This changes pocketData so memoizedPocketData gets a new value (because it has pocketData as a dependency; note that this memoization is never effective there)
Next, the useEffect that depends on memoizedPocketData is executed as well, pocket representations are removed and recreated with an opacity of 0.3.

IMO, this bug is not related to NGL, but is related to React intricacies. Cascades of useEffect make the execution of the code difficult to reason about.
To circumvent this, I would suggest 2 options:

  • regarding the pocket objects: split the data part from the controls part. Like that a change in the cheched property would not cause the refetch of data and re-creation of the pockets
  • second option would be to keep the same data structure, but to control the whole pocket management in a single useEffect. To avoid refetch/rerenders, you can use the name property for the representation and check wether they have already been created or not. You'd have to manage the internal state of the NGL stage by yourself to avoid unneeded recomputations. I personally prefer doing that, rather than let React logics take care of the NGL state because React philosophy is to re-render components multiple times to sync with state which is acceptable for the DOM, but not suitable for 3D rendering and scientific computation

from ngl.

hellhorse123 avatar hellhorse123 commented on June 11, 2024

I created a separate component without any additional logic to try to implement the desired behavior. The screen recording shows that with all attempts to trigger only one mutable pocket object, I have a loading file on both pocket files:

Screen.Recording.2024-01-28.at.11.56.42.mov

Code:


import { useState, useEffect, useRef } from "react";
import * as NGL from "ngl";
import pdbUrl from "../../assets/test-pockets/3hi7.pdb";
import pocket1Url from "../../assets/test-pockets/pocket1.sdf";
import pocket2Url from "../../assets/test-pockets/pocket2.sdf";

const TestOpacity = () => {
  const containerRef = useRef();
  const stageRef = useRef();
  const pocketReps = useRef([]); // To store pocket representations

  const [pocketsArray, setPocketsArray] = useState([
    { url: pocket1Url, checked: false, color: "red", name: 'red' },
    { url: pocket2Url, checked: false, color: 'blue', name: 'blue' },
  ]);

  const prevPocketsRef = useRef();

  // Initialize NGL Stage
  useEffect(() => {
    const stage = new NGL.Stage(containerRef.current, {
      backgroundColor: "#ededed",
    });
    stageRef.current = stage;

    // Cleanup function
    return () => {
      if (stage) {
        stage.viewer.wrapper.remove();
        stage.viewer.renderer.forceContextLoss();
        stage.dispose();
      }
    };
  }, []);

  // Load main PDB file
  useEffect(() => {
    if (pdbUrl && stageRef.current) {
      stageRef.current
        .loadFile(pdbUrl, {
          defaultRepresentation: true,
        })
        .then((o) => {
          console.log("Loaded object:", o);
          o.addRepresentation("cartoon");
          o.autoView();
        })
        .catch((error) => {
          console.error("Error loading file:", error);
        });
    }
  }, [pdbUrl]);

 // Load pockets only once or when URLs change
  useEffect(() => {
    if (stageRef.current) {
      pocketsArray.forEach((pocket, index) => {
        stageRef.current
          .loadFile(pocket.url)
          .then((o) => {
            const rep = o.addRepresentation("surface", {
              color: pocket.color,
              opacity: pocket.checked ? 1 : 0.3,
            });
            pocketReps.current[index] = rep;
          })
          .catch((error) => {
            console.error("Error loading pocket:", error);
          });
      });
    }

    const currentPocketReps = pocketReps.current;

    // Cleanup function
    return () => {
        currentPocketReps.forEach(rep => rep && rep.dispose());
    };
  }, [pocketsArray.map(pocket => pocket.url)]);



  // Update opacity when checked state changes
  useEffect(() => {
    pocketsArray.forEach((pocket, index) => {
      const rep = pocketReps.current[index];
      if (rep && prevPocketsRef.current && prevPocketsRef.current[index].checked !== pocket.checked) {
        rep.setParameters({ opacity: pocket.checked ? 1 : 0.3 });
      }
    });

    prevPocketsRef.current = pocketsArray;
  }, [pocketsArray.map(pocket => pocket.checked)]); 



  // Toggle checked state
  const toggleChecked = (index) => {
    setPocketsArray((currentPockets) =>
      currentPockets.map((pocket, idx) =>
        idx === index ? { ...pocket, checked: !pocket.checked } : pocket
      )
    );
  };

  return (
    <div>
      <div>
        {pocketsArray.map((pocket, index) => (
          <label key={index}>
            <input
              type="checkbox"
              checked={pocket.checked}
              onChange={() => toggleChecked(index)}
            />
            Pocket {pocket.name}
          </label>
        ))}
      </div>
      <div ref={containerRef} style={{ width: "90vw", height: "90vh" }} />
    </div>
  );
};

export default TestOpacity;

from ngl.

ppillot avatar ppillot commented on June 11, 2024

This line of code in the dependency list of the useEffect creates a new reference at each rerender because it always returns a new array:
pocketsArray.map(pocket => pocket.url)

These questions have nothing to do with bugs or features in NGL. I can't dedicate time to reviewing third party code

from ngl.

Related Issues (20)

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.