Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Feature: only one element can be choose #23

Open
unkcpz opened this issue Nov 8, 2021 · 1 comment
Open

Feature: only one element can be choose #23

unkcpz opened this issue Nov 8, 2021 · 1 comment

Comments

@unkcpz
Copy link

unkcpz commented Nov 8, 2021

Like in the aiidalab sssp app and commonwf-oxides app, we want the periodic table can only have one element been chosen.
The following code works well for my sssp app, I assume it can be inside the widget and set by option with the periodic table instance.

The code now implemented is (copy from commonwf-oxides):

last_selected = ipw_periodic.selected_elements
def on_element_select(event):
    global last_selected

    if event['name'] == 'selected_elements' and event['type'] == 'change':
        if tuple(event['new'].keys()) == ('Du', ):
            last_selected = event['old']
        elif tuple(event['old'].keys()) == ('Du', ):
            #print(last_selected, event['new'])
            if len(event['new']) != 1:
                # Reset to only one element only if there is more than one selected,
                # to avoid infinite loops
                newly_selected = set(event['new']).difference(last_selected)
                # If this is empty it's ok, unselect all
                # If there is more than one, that's weird... to avoid problems, anyway, I pick one of the two
                if newly_selected:
                    ipw_periodic.selected_elements = {list(newly_selected)[0]: 0}
                else:
                    ipw_periodic.selected_elements = {}
                # To have the correct 'last' value for next calls
                last_selected = ipw_periodic.selected_elements
            replot()

ipw_periodic.observe(on_element_select)
@unkcpz
Copy link
Author

unkcpz commented Jun 10, 2022

Hi @dou-du, I extend this as a class. I think we can have a discussion and make the decision on how to integrate _on_element_select so can be controlled with one parameter.

class PeriodicTable(ipw.VBox):
    """Wrapper-widget for PTableWidget"""

    selected_element = traitlets.Unicode(allow_none=True)

    def __init__(self, cache_folder, **kwargs):
        self._disabled = kwargs.get("disabled", False)
        self._cache_folder = cache_folder

        self.ptable = PTableWidget(states=1, selected_colors=["green"], **kwargs)
        self._last_selected = None
        self.ptable.observe(self._on_element_select)

        # if cache empty run update: first time
        if os.path.exists(os.path.join(cache_folder, _DB_FOLDER)):
            self._update_db(download=False)
        else:
            self._update_db(download=True)

        disable_elements = [
            e for e in self.ptable.allElements if e not in self.elements
        ]
        self.ptable.disabled_elements = disable_elements
        db_update = ipw.Button(
            description="Update Database.",
        )
        db_update.on_click(self._update_db)

        super().__init__(
            children=(
                self.ptable,
                db_update,
            ),
            layout=kwargs.get("layout", {}),
        )

    def _on_element_select(self, event):
        if event["name"] == "selected_elements" and event["type"] == "change":
            if tuple(event["new"].keys()) == ("Du",):
                self._last_selected = event["old"]
            elif tuple(event["old"].keys()) == ("Du",):
                if len(event["new"]) != 1:
                    # Reset to only one element only if there is more than one selected,
                    # to avoid infinite loops
                    newly_selected = set(event["new"]).difference(self._last_selected)
                    # If this is empty it's ok, unselect all
                    # If there is more than one, that's weird... to avoid problems, anyway, I pick one of the two
                    if newly_selected:
                        element = list(newly_selected)[0]
                        self.ptable.selected_elements = {element: 0}
                        self.selected_element = element
                    else:
                        self.ptable.selected_elements = {}
                        self.selected_element = None
                    # To have the correct 'last' value for next calls
                    self._last_selected = self.ptable.selected_elements
                else:
                    # first time set: len(event['new']) -> 1
                    self.selected_element = list(event["new"])[0]

    def _update_db(self, _=None, download=True):
        """update cached db fetch from remote. and update ptable"""
        # download from remote
        if download:
            self._download(self._cache_folder)

        self.elements = self._get_enabled_elements(self._cache_folder)
        disable_elements = [
            e for e in self.ptable.allElements if e not in self.elements
        ]
        self.ptable.disabled_elements = disable_elements

    @staticmethod
    def _get_enabled_elements(cache_folder):
        elements = []
        for fn in os.listdir(os.path.join(cache_folder, _DB_FOLDER)):
            if "band" not in fn:
                elements.append(fn.split(".")[0])

        return elements

    @staticmethod
    def _download(cache_folder):
        """
        The original sssp_db folder is deleted and re-downloaded from
        source and extracted.

        :params cache_folder: folder where cache stored
        """
        # Purge whole db folder filst
        db_dir = f"{cache_folder}/sssp_db"
        if os.path.exists(db_dir) and os.path.isdir(db_dir):
            shutil.rmtree(db_dir)

        # download DB tar file from source
        tar_file = f"{cache_folder}/sssp_db.tar.gz"
        request.urlretrieve(_DB_URL, tar_file)

        # decompress to the db folder
        tar = tarfile.open(tar_file)
        os.chdir(cache_folder)
        tar.extractall()
        tar.close()

        os.remove(tar_file)

    @property
    def value(self) -> dict:
        """Return value for wrapped PTableWidget"""

        return not self.select_any_all.value, self.ptable.selected_elements.copy()

    @property
    def disabled(self) -> None:
        """Disable widget"""
        return self._disabled

    @disabled.setter
    def disabled(self, value: bool) -> None:
        """Disable widget"""
        if not isinstance(value, bool):
            raise TypeError("disabled must be a boolean")

        self.select_any_all.disabled = self.ptable.disabled = value

    def reset(self):
        """Reset widget"""
        self.select_any_all.value = False
        self.ptable.selected_elements = {}
        self.selected_element = None

    def freeze(self):
        """Disable widget"""
        self.disabled = True

    def unfreeze(self):
        """Activate widget (in its current state)"""
        self.disabled = False

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

1 participant