diff --git a/setup.cfg b/setup.cfg index 190886ef1..c594b09fd 100644 --- a/setup.cfg +++ b/setup.cfg @@ -33,6 +33,7 @@ install_requires = importlib-resources~=5.2 aiida-wannier90-workflows==2.3.0 pymatgen==2024.5.1 + anywidget==0.9.13 python_requires = >=3.9 [options.packages.find] diff --git a/src/aiidalab_qe/app/result/components/viewer/structure/structure.py b/src/aiidalab_qe/app/result/components/viewer/structure/structure.py index 3cae7cb85..6ccf6f19b 100644 --- a/src/aiidalab_qe/app/result/components/viewer/structure/structure.py +++ b/src/aiidalab_qe/app/result/components/viewer/structure/structure.py @@ -1,4 +1,7 @@ +import ipywidgets as ipw + from aiidalab_qe.common.panel import ResultsPanel +from aiidalab_qe.common.widgets import TableWidget from aiidalab_widgets_base.viewers import StructureDataViewer from .model import StructureResultsModel @@ -9,7 +12,26 @@ def _render(self): if not hasattr(self, "widget"): structure = self._model.get_structure() self.widget = StructureDataViewer(structure=structure) - self.results_container.children = [self.widget] + # Select the Cell tab by default + self.widget.configuration_box.selected_index = 2 + self.table_description = ipw.HTML(""" +
+ You can click on a row to select an atom. Multiple atoms + can be selected by clicking on additional rows. To unselect + an atom, click on the selected row again. +
+ """) + self.atom_coordinates_table = TableWidget() + self._generate_table(structure.get_ase()) + self.results_container.children = [ + self.widget, + self.table_description, + self.atom_coordinates_table, + ] + self.atom_coordinates_table.observe(self._change_selection, "selected_rows") # HACK to resize the NGL viewer in cases where it auto-rendered when its # container was not displayed, which leads to a null width. This hack restores @@ -17,3 +39,30 @@ def _render(self): ngl = self.widget._viewer ngl._set_size("100%", "300px") ngl.control.zoom(0.0) + + def _generate_table(self, structure): + data = [ + [ + "Atom index", + "Chemical symbol", + "Tag", + "x (Å)", + "y (Å)", + "z (Å)", + ] + ] + positions = structure.positions + chemical_symbols = structure.get_chemical_symbols() + tags = structure.get_tags() + + for index, (symbol, tag, position) in enumerate( + zip(chemical_symbols, tags, positions), start=1 + ): + # Format position values to two decimal places + formatted_position = [f"{coord:.2f}" for coord in position] + data.append([index, symbol, tag, *formatted_position]) + self.atom_coordinates_table.data = data + + def _change_selection(self, _): + selected_indices = self.atom_coordinates_table.selected_rows + self.widget.displayed_selection = selected_indices diff --git a/src/aiidalab_qe/common/widgets.py b/src/aiidalab_qe/common/widgets.py index 5b101eb7d..f4da89cba 100644 --- a/src/aiidalab_qe/common/widgets.py +++ b/src/aiidalab_qe/common/widgets.py @@ -13,6 +13,7 @@ from threading import Event, Lock, Thread from time import time +import anywidget import ase import ipywidgets as ipw import numpy as np @@ -1219,3 +1220,85 @@ def _show_missing_information_warning(self): def _hide_missing_information_warning(self): self.children = self.previous_children + + +class TableWidget(anywidget.AnyWidget): + _esm = """ + function render({ model, el }) { + let domElement = document.createElement("div"); + el.classList.add("custom-table"); + let selectedIndices = []; + + function drawTable() { + const data = model.get("data"); + domElement.innerHTML = ""; + let innerHTML = '${header} | `).join('') + '
---|
${cell} | `).join('') + '