diff --git a/pyproject.toml b/pyproject.toml
index e543fb0..b7d65fc 100644
--- a/pyproject.toml
+++ b/pyproject.toml
@@ -42,8 +42,8 @@ dependencies = [
"ase~=3.22",
"pandas~=2.1",
"requests~=2.31",
- "widget_periodictable~=3.1",
"semver~=3.0",
+ "anywidget~=0.9",
]
[project.optional-dependencies]
diff --git a/src/ipyoptimade/subwidgets/_periodic_table/__init__.py b/src/ipyoptimade/subwidgets/_periodic_table/__init__.py
new file mode 100644
index 0000000..7eafccf
--- /dev/null
+++ b/src/ipyoptimade/subwidgets/_periodic_table/__init__.py
@@ -0,0 +1,132 @@
+import anywidget
+import traitlets as tl
+import pathlib
+import copy
+
+from .utils import CHEMICAL_ELEMENTS, color_as_rgb
+
+
+class PeriodicTableWidget(anywidget.AnyWidget):
+ _esm = pathlib.Path(__file__).parent / "index.js"
+ _css = pathlib.Path(__file__).parent / "style.css"
+
+ selected_elements = tl.Dict({}).tag(sync=True)
+ disabled_elements = tl.List([]).tag(sync=True)
+ display_names_replacements = tl.Dict({}).tag(sync=True)
+ disabled_color = tl.Unicode("gray").tag(sync=True)
+ unselected_color = tl.Unicode("pink").tag(sync=True)
+ states = tl.Int(1).tag(sync=True)
+ selected_colors = tl.List([]).tag(sync=True)
+ border_color = tl.Unicode("#cc7777").tag(sync=True)
+ disabled = tl.Bool(False, help="Enable or disable user changes.").tag(sync=True)
+ width = tl.Unicode("38px").tag(sync=True)
+ allElements = tl.List(CHEMICAL_ELEMENTS).tag(sync=True)
+
+ _STANDARD_COLORS = [
+ "#a6cee3",
+ "#b2df8a",
+ "#fdbf6f",
+ "#6a3d9a",
+ "#b15928",
+ "#e31a1c",
+ "#1f78b4",
+ "#33a02c",
+ "#ff7f00",
+ "#cab2d6",
+ "#ffff99",
+ ]
+
+ def __init__(
+ self,
+ states=1,
+ selected_elements=None,
+ disabled_elements=None,
+ disabled_color=None,
+ unselected_color=None,
+ selected_colors=[],
+ border_color=None,
+ width=None,
+ layout=None,
+ *args,
+ **kwargs,
+ ):
+ super().__init__(*args, **kwargs)
+ self.states = states if states else 1
+ self.selected_elements = selected_elements if selected_elements else {}
+ self.disabled_elements = disabled_elements if disabled_elements else []
+ self.disabled_color = disabled_color if disabled_color else "gray"
+ self.unselected_color = unselected_color if unselected_color else "pink"
+ self.selected_colors = (
+ selected_colors if selected_colors else self._STANDARD_COLORS
+ )
+ self.border_color = border_color if border_color else "#cc7777"
+ self.width = width if width else "38px"
+
+ if layout is not None:
+ self.layout = layout
+
+ if len(selected_colors) < states:
+ self.selected_colors = selected_colors + self._STANDARD_COLORS * (
+ 1 + (states - len(selected_colors)) // len(self._STANDARD_COLORS)
+ )
+ self.selected_colors = self.selected_colors[:states]
+
+ def set_element_state(self, elementName, state):
+ if elementName not in self.allElements:
+ raise tl.TraitError("Element not found")
+ if state not in range(self.states):
+ raise tl.TraitError("State value is wrong")
+ x = copy.deepcopy(self.selected_elements)
+ x[elementName] = state
+ self.selected_elements = x
+
+ @tl.validate("disabled_color", "unselected_color", "border_color")
+ def _color_change(self, proposal):
+ """Convert to rgb(X, Y, Z) type color"""
+ return color_as_rgb(proposal["value"])
+
+ @tl.validate("selected_colors")
+ def _selectedColors_change(self, proposal):
+ """Convert to rgb(X, Y, Z) type color"""
+ res = []
+ for color in proposal["value"]:
+ res.append(color_as_rgb(color))
+ return res
+
+ @tl.validate("selected_elements")
+ def _selectedElements_change(self, proposal):
+ for x, y in proposal["value"].items():
+ if x not in self.allElements and x != "Du":
+ raise tl.TraitError("Element not found")
+ if not isinstance(y, int) or y not in range(self.states):
+ raise tl.TraitError("State value is wrong")
+ return proposal["value"]
+
+ @tl.observe("disabled_elements")
+ def _disabledList_change(self, change):
+ for i in change["new"]:
+ if i in self.selected_elements:
+ del self.selected_elements[i]
+
+ @tl.observe("states")
+ def _states_change(self, change):
+ if change["new"] < 1:
+ raise tl.TraitError("State value cannot smaller than 1")
+ else:
+ if len(self.selected_colors) < change["new"]:
+ self.selected_colors = self.selected_colors + self._STANDARD_COLORS * (
+ 1
+ + (change["new"] - len(self.selected_colors))
+ // len(self._STANDARD_COLORS)
+ )
+ self.selected_colors = self.selected_colors[: change["new"]]
+ elif len(self.selected_colors) > change["new"]:
+ self.selected_colors = self.selected_colors[: change["new"]]
+
+ def get_elements_by_state(self, state):
+ if state not in range(self.states):
+ raise tl.TraitError("State value is wrong")
+ else:
+ return [
+ i for i in self.selected_elements if self.selected_elements[i] == state
+ ]
diff --git a/src/ipyoptimade/subwidgets/_periodic_table/index.js b/src/ipyoptimade/subwidgets/_periodic_table/index.js
new file mode 100644
index 0000000..c6a0c0d
--- /dev/null
+++ b/src/ipyoptimade/subwidgets/_periodic_table/index.js
@@ -0,0 +1,449 @@
+import _ from 'https://cdn.jsdelivr.net/npm/underscore@1.13.6/+esm'
+
+// List of lists of elements, used to render the periodic table
+// Only values accepted:
+// - strings (should be valid elements, not checked);
+// - empty strings (empty space, nothing rendered);
+// - '*' character (printed as a disabled element).
+// These assumptions are used both in the generation of the elementList
+// and in the template.
+const elementTable = [
+ ["H", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "He"],
+ [
+ "Li",
+ "Be",
+ "",
+ "",
+ "",
+ "",
+ "",
+ "",
+ "",
+ "",
+ "",
+ "",
+ "B",
+ "C",
+ "N",
+ "O",
+ "F",
+ "Ne"
+ ],
+ [
+ "Na",
+ "Mg",
+ "",
+ "",
+ "",
+ "",
+ "",
+ "",
+ "",
+ "",
+ "",
+ "",
+ "Al",
+ "Si",
+ "P",
+ "S",
+ "Cl",
+ "Ar"
+ ],
+ [
+ "K",
+ "Ca",
+ "Sc",
+ "Ti",
+ "V",
+ "Cr",
+ "Mn",
+ "Fe",
+ "Co",
+ "Ni",
+ "Cu",
+ "Zn",
+ "Ga",
+ "Ge",
+ "As",
+ "Se",
+ "Br",
+ "Kr"
+ ],
+ [
+ "Rb",
+ "Sr",
+ "Y",
+ "Zr",
+ "Nb",
+ "Mo",
+ "Tc",
+ "Ru",
+ "Rh",
+ "Pd",
+ "Ag",
+ "Cd",
+ "In",
+ "Sn",
+ "Sb",
+ "Te",
+ "I",
+ "Xe"
+ ],
+ [
+ "Cs",
+ "Ba",
+ "*",
+ "Hf",
+ "Ta",
+ "W",
+ "Re",
+ "Os",
+ "Ir",
+ "Pt",
+ "Au",
+ "Hg",
+ "Tl",
+ "Pb",
+ "Bi",
+ "Po",
+ "At",
+ "Rn"
+ ],
+ [
+ "Fr",
+ "Ra",
+ "#",
+ "Rf",
+ "Db",
+ "Sg",
+ "Bh",
+ "Hs",
+ "Mt",
+ "Ds",
+ "Rg",
+ "Cn",
+ "Nh",
+ "Fi",
+ "Mc",
+ "Lv",
+ "Ts",
+ "Og"
+ ],
+ ["", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", ""],
+ [
+ "",
+ "",
+ "*",
+ "La",
+ "Ce",
+ "Pr",
+ "Nd",
+ "Pm",
+ "Sm",
+ "Eu",
+ "Gd",
+ "Tb",
+ "Dy",
+ "Ho",
+ "Er",
+ "Tm",
+ "Yb",
+ "Lu"
+ ],
+ [
+ "",
+ "",
+ "#",
+ "Ac",
+ "Th",
+ "Pa",
+ "U",
+ "Np",
+ "Pu",
+ "Am",
+ "Cm",
+ "Bk",
+ "Cf",
+ "Es",
+ "Fm",
+ "Md",
+ "No",
+ "Lr"
+ ]
+]
+
+// Flat list of elements, used for validation and cleaning up of the
+// selectedElements list.
+const elementList = []
+for (const elementRow of elementTable) {
+ for (const elementName of elementRow) {
+ if (elementName === "" || elementName === "*") {
+ continue
+ } else {
+ elementList.push(elementName)
+ }
+ }
+}
+
+// TODO: move template to external file to make it more readable, see
+// http://codebeerstartups.com/2012/12/how-to-improve-templates-in-backbone-js-learning-backbone-js/
+const tableTemplate = _.template(
+ "<% for (let elementRow of elementTable) { " +
+ "print(\"
\"); " +
+ "for (let elementName of elementRow) { " +
+ 'if ( (elementName === "") || (elementName == "*" ) || (elementName == "#" ) ) { %>' +
+ ' <%= elementName %>' +
+ "<% } else { %>" +
+ ' ' +
+ ' noselect element-<%= elementName %><% if (selectedElements.includes(elementName) && (! disabledElements.includes(elementName)) ) { print(" elementOn"); } %>" ' +
+ 'style="width: <%= elementWidth %>; height: <%= elementWidth %>; ' +
+ 'border-color: <% if (disabled) { colors = borderColor.replace(/[^\\d,]/g, "").split(","); ' +
+ "red = Math.round(255 - 0.38 * ( 255 - parseInt(colors[0], 10) )); " +
+ "green = Math.round(255 - 0.38 * ( 255 - parseInt(colors[1], 10) )); " +
+ "blue = Math.round(255 - 0.38 * ( 255 - parseInt(colors[2], 10) )); " +
+ 'print("rgb(" + red.toString(10) + "," + green.toString(10) + "," + blue.toString(10) + ")"); ' +
+ "} else { print(borderColor); } %>; " +
+ "background-color: <% if (disabledElements.includes(elementName)) { color = disabledColor; } " +
+ "else if (selectedElements.includes(elementName)) { " +
+ "i = selectedElements.indexOf(elementName); color = selectedColors[selectedStates[i]]; " +
+ "} else { color = unselectedColor; } " +
+ 'if (disabled) { colors = color.replace(/[^\\d,]/g, "").split(","); ' +
+ "red = Math.round(255 - 0.38 * ( 255 - parseInt(colors[0], 10) )); " +
+ "green = Math.round(255 - 0.38 * ( 255 - parseInt(colors[1], 10) )); " +
+ "blue = Math.round(255 - 0.38 * ( 255 - parseInt(colors[2], 10) )); " +
+ 'print("rgb(" + red.toString(10) + "," + green.toString(10) + "," + blue.toString(10) + ")"); ' +
+ '} else { print(color); } %>"' +
+ // 'title="state: <% if (selectedElements.includes(elementName)) { i = selectedElements.indexOf(elementName); print(selectedStates[i]);} '+
+ // 'else if (disabledElements.includes(elementName)){print("disabled");} else {print("unselected");} %>" ><% '+
+ "><% print(displayNamesReplacements[elementName] || elementName); %>" +
+ '<% } }; print("
"); } %>'
+)
+
+class PeriodicTableView {
+
+ constructor({ el, model }) {
+ this.el = el
+ this.model = model
+ }
+
+
+ render() {
+ // add event listener
+
+ // I render the widget
+ this.rerenderScratch()
+
+ // I bind on_change events
+ this.model.on("change:selected_elements", this.rerenderScratch, this)
+ this.model.on("change:disabled_elements", this.rerenderScratch, this)
+ this.model.on(
+ "change:display_names_replacements",
+ this.rerenderScratch,
+ this
+ )
+ this.model.on("change:border_color", this.rerenderScratch, this)
+ this.model.on("change:width", this.rerenderScratch, this)
+ this.model.on("change:disabled", this.rerenderScratch, this)
+ }
+
+ rerenderScratch() {
+ // Re-render full widget when the list of selected elements
+ // changed from python
+ const selectedElements = this.model.get("selected_elements")
+ const disabledElements = this.model.get("disabled_elements")
+ const disabledColor = this.model.get("disabled_color")
+ const unselectedColor = this.model.get("unselected_color")
+ const selectedColors = this.model.get("selected_colors")
+ const newSelectedColors = selectedColors.slice()
+ const elementWidth = this.model.get("width")
+ const borderColor = this.model.get("border_color")
+
+ let newSelectedElements = []
+ const newSelectedStates = []
+
+ if ("Du" in selectedElements) {
+ return
+ }
+
+ for (const key in selectedElements) {
+ newSelectedElements.push(key)
+ newSelectedStates.push(selectedElements[key])
+ }
+
+ if (newSelectedElements.length !== newSelectedStates.length) {
+ return
+ }
+
+ // Here I want to clean up the two elements lists, to avoid
+ // to have unknown elements in the selectedElements, and
+ // to remove disabled Elements from the selectedElements list.
+ // I use s variable to check if anything changed, so I send
+ // back the data to python only if needed
+
+ const selectedElementsLength = newSelectedElements.length
+ // Remove disabled elements from the selectedElements list
+ newSelectedElements = _.difference(newSelectedElements, disabledElements)
+ // Remove unknown elements from the selectedElements list
+ newSelectedElements = _.intersection(newSelectedElements, elementList)
+
+ const changed = newSelectedElements.length !== selectedElementsLength
+
+ // call the update (to python) only if I actually removed/changed
+ // something
+ if (changed) {
+ // Make a copy before setting
+ // while (newSelectedElements.length > newSelectedStates.length){
+ // newSelectedStates.push(0);
+ // };
+
+ for (const key in selectedElements) {
+ if (!newSelectedElements.includes(key)) {
+ delete selectedElements[key]
+ }
+ }
+
+ this.model.set("selected_elements", selectedElements)
+ this.touch()
+ }
+
+ // Render the full widget using the template
+ this.el.innerHTML =
+ '' +
+ tableTemplate({
+ elementTable: elementTable,
+ displayNamesReplacements: this.model.get("display_names_replacements"),
+ selectedElements: newSelectedElements,
+ disabledElements: disabledElements,
+ disabledColor: disabledColor,
+ unselectedColor: unselectedColor,
+ selectedColors: newSelectedColors,
+ selectedStates: newSelectedStates,
+ elementWidth: elementWidth,
+ borderColor: borderColor,
+ disabled: this.model.get("disabled")
+ }) +
+ "
"
+
+ function myFunction(event) {
+ console.log("tttttt")
+ const classNames = _.map(event.target.classList, a => {
+ return a
+ })
+ const elementName = _.chain(classNames)
+ .filter(a => {
+ return a.startsWith("element-")
+ })
+ .map(a => {
+ return a.slice("element-".length)
+ })
+ .first()
+ .value()
+
+ const isOn = _.includes(classNames, "elementOn")
+ const isDisabled = _.includes(classNames, "periodic-table-disabled")
+ // If this button is disabled, do not do anything
+ // (Actually, this function should not be triggered if the button
+ // is disabled, this is just a safety measure)
+
+ const states = this.model.get("states")
+ const disabled = this.model.get("disabled")
+
+ if (disabled) {
+ return
+ }
+
+ // Check if we understood which element we are
+ if (typeof elementName !== "undefined") {
+ const currentList = this.model.get("selected_elements")
+ // NOTE! it is essential to duplicate the list,
+ // otherwise the value will not be updated.
+
+ let newList = []
+ const newStatesList = []
+
+ for (const key in currentList) {
+ newList.push(key)
+ newStatesList.push(currentList[key])
+ }
+
+ const num = newList.indexOf(elementName)
+
+ if (isOn) {
+ // remove the element from the selected_elements
+
+ if (newStatesList[num] < states - 1) {
+ newStatesList[num]++
+ currentList[elementName] = newStatesList[num]
+ } else {
+ newList = _.without(newList, elementName)
+ newStatesList.splice(num, 1)
+ delete currentList[elementName]
+ // Swap CSS state
+ event.target.classList.remove("elementOn")
+ }
+ } else if (!isDisabled) {
+ // add the element from the selected_elements
+ newList.push(elementName)
+ newStatesList.push(0)
+ currentList[elementName] = 0
+ // Swap CSS state
+ event.target.classList.add("elementOn")
+ } else {
+ return
+ }
+
+ // Update the model (send back data to python)
+ // I have to make some changes, since there is some issue
+ // for Dict in Traitlets, which cannot trigger the update
+ this.model.set("selected_elements", { Du: 0 })
+ this.touch()
+ this.model.set("selected_elements", currentList)
+ this.touch()
+ }
+ }
+
+ this.elementEntries = this.el.querySelectorAll(".periodic-table-entry")
+
+ // add listenter to each element
+ for (const elementEntry of this.elementEntries) {
+ console.log("tttt")
+ elementEntry.addEventListener("click", myFunction.bind(this))
+ }
+ }
+
+ destroy() {
+ // TODO: remove event listeners
+ }
+}
+
+function modelWithSerializers(model, serializers) {
+ return {
+ get(key) {
+ const value = model.get(key);
+ const serializer = serializers[key];
+ if (serializer) return serializer.deserialize(value);
+ return value;
+ },
+ set(key, value) {
+ const serializer = serializers[key];
+ if (serializer) value = serializer.serialize(value);
+ model.set(key, value);
+ },
+ on: model.on.bind(model),
+ save_changes: model.save_changes.bind(model),
+ send: model.send.bind(model),
+ }
+}
+
+async function render({ model, el }) {
+ const view = new PeriodicTableView({
+ el: el,
+ model: modelWithSerializers(model, {
+ // TODO: add serializers
+ }),
+ });
+ view.render();
+ return () => view.destroy();
+}
+
+export default { render }
\ No newline at end of file
diff --git a/src/ipyoptimade/subwidgets/_periodic_table/style.css b/src/ipyoptimade/subwidgets/_periodic_table/style.css
new file mode 100644
index 0000000..6777e01
--- /dev/null
+++ b/src/ipyoptimade/subwidgets/_periodic_table/style.css
@@ -0,0 +1,65 @@
+.periodic-table-entry {
+ border: 1px solid;
+ border-color: #cc7777;
+ border-radius: 3px;
+ width: 38px;
+ height: 38px;
+ display: table-cell;
+ text-align: center;
+ vertical-align: middle;
+ background-color: #ffaaaa;
+}
+
+.periodic-table-disabled {
+ border-radius: 3px;
+ width: 38px;
+ height: 38px;
+ display: table-cell;
+ text-align: center;
+ vertical-align: middle;
+ background-color: #999999;
+}
+
+.periodic-table-empty {
+ border: 0px;
+ width: 38px;
+ height: 38px;
+ display: table-cell;
+ text-align: center;
+ vertical-align: middle;
+}
+
+.periodic-table-row {
+ display: table-row;
+}
+
+.periodic-table-body {
+ display: table; border-spacing: 4px;
+}
+
+.periodic-table-entry:hover {
+ background-color: #cc7777;
+}
+
+.periodic-table-entry.elementOn {
+ background-color: #aaaaff;
+ border: 1px solid #7777cc;
+ border-radius: 4px;
+}
+
+
+.periodic-table-entry.elementOn:hover {
+ background-color: #7777cc;
+ border: 1px solid #7777cc;
+ border-radius: 4px;
+}
+
+.noselect {
+ -webkit-touch-callout: none; /* iOS Safari */
+ -webkit-user-select: none; /* Safari */
+ -khtml-user-select: none; /* Konqueror HTML */
+ -moz-user-select: none; /* Firefox */
+ -ms-user-select: none; /* Internet Explorer/Edge */
+ user-select: none; /* Non-prefixed version, currently
+ supported by Chrome and Opera */
+}
\ No newline at end of file
diff --git a/src/ipyoptimade/subwidgets/_periodic_table/utils.py b/src/ipyoptimade/subwidgets/_periodic_table/utils.py
new file mode 100644
index 0000000..ad48ca4
--- /dev/null
+++ b/src/ipyoptimade/subwidgets/_periodic_table/utils.py
@@ -0,0 +1,166 @@
+import re
+
+
+HTML_COLOR_MAP = {
+ "white": (255,) * 3,
+ "silver": tuple(round(0.75 * i) for i in (255,) * 3),
+ "gray": tuple(round(0.5 * i) for i in (255,) * 3),
+ "grey": tuple(round(0.5 * i) for i in (255,) * 3),
+ "black": (0,) * 3,
+ "red": (255, 0, 0),
+ "maroon": (round(0.5 * 255), 0, 0),
+ "yellow": (255, 255, 0),
+ "olive": tuple(round(0.5 * i) for i in (255, 255, 0)),
+ "lime": (0, 255, 0),
+ "green": (0, round(0.5 * 255), 0),
+ "aqua": (0, 255, 255),
+ "teal": tuple(round(0.5 * i) for i in (0, 255, 255)),
+ "blue": (0, 0, 255),
+ "navy": (0, 0, round(0.5 * 255)),
+ "fuchsia": (255, 0, 255),
+ "purple": tuple(round(0.5 * i) for i in (255, 0, 255)),
+ "pink": (255, 192, 203),
+}
+
+
+def color_as_rgb(color: str) -> str:
+ """Convert hex and named color to rgb formatting"""
+ if not color:
+ return ""
+
+ if re.match(r"#[a-fA-F0-9]{6}", color):
+ # Hex color
+ color = color.lstrip("#")
+ color = tuple(int(color[i : i + 2], 16) for i in (0, 2, 4))
+ elif re.match(r"rgb\([0-9]+,[0-9]+,[0-9]+\)", color):
+ # RGB color
+ return color
+ else:
+ # Color name
+ color = HTML_COLOR_MAP.get(color)
+
+ if color is None:
+ return ""
+ return "".join(f"rgb{color!r}".split(" "))
+
+
+CHEMICAL_ELEMENTS = [
+ "H",
+ "He",
+ "Li",
+ "Be",
+ "B",
+ "C",
+ "N",
+ "O",
+ "F",
+ "Ne",
+ "Na",
+ "Mg",
+ "Al",
+ "Si",
+ "P",
+ "S",
+ "Cl",
+ "Ar",
+ "K",
+ "Ca",
+ "Sc",
+ "Ti",
+ "V",
+ "Cr",
+ "Mn",
+ "Fe",
+ "Co",
+ "Ni",
+ "Cu",
+ "Zn",
+ "Ga",
+ "Ge",
+ "As",
+ "Se",
+ "Br",
+ "Kr",
+ "Rb",
+ "Sr",
+ "Y",
+ "Zr",
+ "Nb",
+ "Mo",
+ "Tc",
+ "Ru",
+ "Rh",
+ "Pd",
+ "Ag",
+ "Cd",
+ "In",
+ "Sn",
+ "Sb",
+ "Te",
+ "I",
+ "Xe",
+ "Cs",
+ "Ba",
+ "Hf",
+ "Ta",
+ "W",
+ "Re",
+ "Os",
+ "Ir",
+ "Pt",
+ "Au",
+ "Hg",
+ "Tl",
+ "Pb",
+ "Bi",
+ "Po",
+ "At",
+ "Rn",
+ "Fr",
+ "Ra",
+ "Rf",
+ "Db",
+ "Sg",
+ "Bh",
+ "Hs",
+ "Mt",
+ "Ds",
+ "Rg",
+ "Cn",
+ "Nh",
+ "Fi",
+ "Mc",
+ "Lv",
+ "Ts",
+ "Og",
+ "La",
+ "Ce",
+ "Pr",
+ "Nd",
+ "Pm",
+ "Sm",
+ "Eu",
+ "Gd",
+ "Tb",
+ "Dy",
+ "Ho",
+ "Er",
+ "Tm",
+ "Yb",
+ "Lu",
+ "Ac",
+ "Th",
+ "Pa",
+ "U",
+ "Np",
+ "Pu",
+ "Am",
+ "Cm",
+ "Bk",
+ "Cf",
+ "Es",
+ "Fm",
+ "Md",
+ "No",
+ "Lr",
+]
diff --git a/src/ipyoptimade/subwidgets/periodic_table.py b/src/ipyoptimade/subwidgets/periodic_table.py
index a2b8aee..166d2a2 100644
--- a/src/ipyoptimade/subwidgets/periodic_table.py
+++ b/src/ipyoptimade/subwidgets/periodic_table.py
@@ -1,16 +1,16 @@
import ipywidgets as ipw
-from widget_periodictable import PTableWidget
-
from ipyoptimade.logger import LOGGER
from ipyoptimade.utils import ButtonStyle
+from ._periodic_table import PeriodicTableWidget
+
__all__ = ("PeriodicTable",)
class PeriodicTable(ipw.VBox):
- """Wrapper-widget for PTableWidget"""
+ """Wrapper-widget for PeriodicTableWidget"""
def __init__(self, extended: bool = True, **kwargs):
self._disabled = kwargs.get("disabled", False)
@@ -30,7 +30,7 @@ def __init__(self, extended: bool = True, **kwargs):
layout={"width": "auto"},
disabled=self.disabled,
)
- self.ptable = PTableWidget(**kwargs)
+ self.ptable = PeriodicTableWidget(**kwargs)
self.ptable_container = ipw.VBox(
children=(self.select_any_all, self.ptable),
layout={
@@ -49,9 +49,9 @@ def __init__(self, extended: bool = True, **kwargs):
@property
def value(self) -> dict:
- """Return value for wrapped PTableWidget"""
+ """Return value for wrapped PeriodicTableWidget"""
LOGGER.debug(
- "PeriodicTable: PTableWidget.selected_elements = %r",
+ "PeriodicTable: PeriodicTableWidget.selected_elements = %r",
self.ptable.selected_elements,
)
LOGGER.debug(