diff --git a/src/aiidalab_sssp/assets/styles/explore.css b/src/aiidalab_sssp/assets/styles/explore.css new file mode 100644 index 0000000..3aa78ae --- /dev/null +++ b/src/aiidalab_sssp/assets/styles/explore.css @@ -0,0 +1,26 @@ +.v-application .title { + text-align: center; + font-size: 2em !important; + margin-top: 0.5em; +} + +.legend { + display: grid; + grid-template-columns: repeat(4, minmax(250px, 1fr)); + justify-content: center; + align-items: center; + margin: 1em auto; +} +.legend .legend-item-marker { + width: 1em; + height: 1em; + border-radius: 50%; + margin-right: 0.5em; +} +.legend .legend-item { + display: flex; + align-items: center; + margin-right: 1em; + width: fit-content; + font-size: 14px; +} \ No newline at end of file diff --git a/src/aiidalab_sssp/assets/styles/table.css b/src/aiidalab_sssp/assets/styles/table.css new file mode 100644 index 0000000..ea79fd3 --- /dev/null +++ b/src/aiidalab_sssp/assets/styles/table.css @@ -0,0 +1,90 @@ +:root { + --element-box-size: 55px; +} + +.ptable-outer { + display: flex; + text-align: center; + align-items: center; + justify-content: center; + width: fit-content; + margin: 0 auto; +} + +.v-application .ptable { + display: flex !important; + row-gap: 12px; +} +.v-application .ptable .elements { + display: grid !important; + grid-template-columns: repeat(17, 1fr) 1fr; +} + +.element { + height: var(--element-box-size); + width: var(--element-box-size); + margin: 1px; + user-select: none; + position: relative; + border-radius: 20%; + cursor: pointer; + display: flex; + flex-direction: column; + justify-content: center; + align-items: center; + color: white; + text-decoration: none; +} +.element.element-2 { + grid-column-start: 18; +} +.element.element-5 { + grid-column-start: 13; +} +.element.element-13 { + grid-column-start: 13; +} +.element.element-57 { + grid-column-start: 4; +} +.element.element-89 { + grid-column-start: 4; +} +.element.disabled { + pointer-events: none; +} +.element:hover { + filter: brightness(85%); + transform: scale(1.2); + z-index: 10; +} +.element .element-symbol { + font-size: calc(0.32 * var(--element-box-size)); + line-height: 1; +} +.element .element-info { + font-size: 13px; + line-height: 1; +} + +.star-placeholder { + display: flex; + font-weight: bold; + align-self: center; + justify-self: center; + grid-column-start: 3; +} + +@media screen and (max-width: 1280px) { + :root { + --element-box-size: 40px; + } + + .element_symbol { + font-size: calc(0.4 * var(--element-box-size)); + } + + .element-info { + display: none; + } +} \ No newline at end of file diff --git a/src/aiidalab_sssp/components/__init__.py b/src/aiidalab_sssp/components/__init__.py index 6ddd1cf..e69de29 100644 --- a/src/aiidalab_sssp/components/__init__.py +++ b/src/aiidalab_sssp/components/__init__.py @@ -1,2 +0,0 @@ -from .header import Header # noqa -from .layout import Layout # noqa diff --git a/src/aiidalab_sssp/components/ptable/__init__.py b/src/aiidalab_sssp/components/ptable/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/src/aiidalab_sssp/components/ptable/details.py b/src/aiidalab_sssp/components/ptable/details.py new file mode 100644 index 0000000..98d2752 --- /dev/null +++ b/src/aiidalab_sssp/components/ptable/details.py @@ -0,0 +1,14 @@ +from __future__ import annotations + +from solara import Text, VBox +from solara.core import component + +from aiidalab_sssp.models.element import ElementModel + + +@component +def DetailsBox(element: ElementModel | None): + if element: + with VBox(classes=["border details-box"]): + Text(text=element.symbol) + Text(text=element.number) diff --git a/src/aiidalab_sssp/components/ptable/element.py b/src/aiidalab_sssp/components/ptable/element.py new file mode 100644 index 0000000..6d85eaf --- /dev/null +++ b/src/aiidalab_sssp/components/ptable/element.py @@ -0,0 +1,33 @@ +from __future__ import annotations + +import typing as t + +from solara import HTML, Div, Text +from solara.core import component + +from aiidalab_sssp.models.element import ElementModel + + +@component +def Element( + element: ElementModel, + on_hover: t.Callable[[ElementModel], None], +): + classes = ["element", f"element-{element.number}"] + if element.disabled: + classes.append("disabled") + + with Div( + classes=classes, + style={"background-color": element.background}, + ) as element_box: + Text(text=element.symbol, classes=["element-symbol"]) + if not element.disabled: + with Div(classes=["element-info"]): + HTML("span", f"{int(element.wfc)}", classes=["element-wfc"]) + HTML("sub", f"({int(element.rho)})", classes=["element-rho"]) + + if not element.disabled: + element_box.on("mouseover", lambda: on_hover(element)) + + return element_box diff --git a/src/aiidalab_sssp/components/ptable/table.py b/src/aiidalab_sssp/components/ptable/table.py new file mode 100644 index 0000000..ee52e59 --- /dev/null +++ b/src/aiidalab_sssp/components/ptable/table.py @@ -0,0 +1,48 @@ +from __future__ import annotations + +import typing as t + +from reacton import use_state +from solara import Div, Style, Text, VBox +from solara.core import component + +from aiidalab_sssp.assets.styles import css +from aiidalab_sssp.components.ptable.details import DetailsBox +from aiidalab_sssp.components.ptable.element import Element +from aiidalab_sssp.models.element import ElementModel + + +@component +def PTable(elements: list[ElementModel]): + hovered_element, set_hovered_element = use_state(t.cast(ElementModel | None, None)) + + def on_hover(element: ElementModel): + set_hovered_element(element) + + def Placeholder(n: int): + with Div(classes=["star-placeholder"]): + Text("★" * n) + + def Table(): + def Elements(start, end): + for element in elements[start:end]: + Element(element=element, on_hover=on_hover) + + with VBox(classes=["container ptable"]): + with Div(classes=["elements"]): + Elements(0, 56) + Placeholder(1) + Elements(71, 88) + Placeholder(2) + Elements(103, 118) + with Div(classes=["elements rare-earth"]): + Placeholder(1) + Elements(56, 71) + Placeholder(2) + Elements(88, 103) + + Style(css / "table.css") + + with Div(classes=["container ptable-outer"]): + DetailsBox(element=hovered_element) + Table() diff --git a/src/aiidalab_sssp/models/__init__.py b/src/aiidalab_sssp/models/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/src/aiidalab_sssp/models/element.py b/src/aiidalab_sssp/models/element.py new file mode 100644 index 0000000..16d8b8e --- /dev/null +++ b/src/aiidalab_sssp/models/element.py @@ -0,0 +1,15 @@ +from dataclasses import dataclass + + +@dataclass(frozen=True) +class ElementModel: + number: int + symbol: str + dual: float = 0.0 + wfc: float = 0.0 + rho: float = 0.0 + md5: str = "" + filename: str = "" + pseudopotential: str = "" + background: str = "#dddddd" + disabled: bool = False diff --git a/src/aiidalab_sssp/pages/__init__.py b/src/aiidalab_sssp/pages/__init__.py index 381ff8d..e69de29 100644 --- a/src/aiidalab_sssp/pages/__init__.py +++ b/src/aiidalab_sssp/pages/__init__.py @@ -1,60 +0,0 @@ -import solara -from solara.alias import rv - -from aiidalab_sssp.pages import explore, verify -from aiidalab_sssp.data import articles, names -from aiidalab_sssp.components import banner - - -@solara.component -def PeopleCard(name): - with solara.Card(f"Employee of the Month: {name}") as main: - with rv.CardText(): - solara.Markdown( - """ - * Department: foo - * Skills: bar, baz - """ - ) - with solara.Link(f"/people/{name}"): - solara.Button("View employee", text=True, icon_name="mdi-profile") - return main - - -@solara.component -def Layout(children=[]): - router = solara.use_context(solara.routing.router_context) - with solara.VBox() as navigation: - with rv.List(dense=True): - with rv.ListItemGroup(v_model=router.path): - with solara.Link(solara.resolve_path("/")): - with solara.ListItem("Home", icon_name="mdi-home", value="/"): - pass - with solara.ListItem("tabular data", icon_name="mdi-database"): - for name in names: - pathname = f"/tabular/{name}" - with solara.Link(solara.resolve_path(pathname)): - solara.ListItem(name, value=pathname) - with solara.ListItem("Articles", icon_name="mdi-book-open"): - for name, article_ in articles.items(): - pathname = f"/article/{name}" - with solara.Link(solara.resolve_path(pathname)): - solara.ListItem(article_.title, value=pathname) - - with solara.AppLayout(navigation=navigation, title="Solara demo", children=children) as main: - pass - return main - - -@solara.component -def Page(): - with solara.VBox() as main: - solara.Title("Standard Solid-State Pseudopotential (SSSP) » Home") - with solara.ColumnsResponsive(12): - banner.Overview() - with solara.ColumnsResponsive([6, 6], small=[12]): - explore.Overview() - verify.Overview() - - return main - diff --git a/src/aiidalab_sssp/pages/explore.py b/src/aiidalab_sssp/pages/explore.py new file mode 100644 index 0000000..160f042 --- /dev/null +++ b/src/aiidalab_sssp/pages/explore.py @@ -0,0 +1,57 @@ +from __future__ import annotations + +import json +from pathlib import Path + +from solara import HTML, Div, Style, Text, VBox +from solara.core import component + +from aiidalab_sssp.assets.styles import css +from aiidalab_sssp.components.ptable.table import PTable +from aiidalab_sssp.models.element import ElementModel + + +@component +def Page(): + symbols: list[str] = json.loads(Path("data/symbols.json").read_text()) + data: dict[str, dict] = json.loads(Path("data/sssp_efficiency.json").read_text()) + metadata: dict[str, dict] = json.loads(Path("data/metadata.json").read_text()) + + elements = [ + ElementModel( + number=number, + symbol=symbol, + wfc=data[symbol]["cutoff"], + rho=data[symbol]["rho_cutoff"], + dual=data[symbol]["dual"], + md5=data[symbol]["md5"], + filename=data[symbol]["filename"], + pseudopotential=data[symbol]["pseudopotential"], + background=metadata[data[symbol]["pseudopotential"]]["background_color"], + disabled=data[symbol]["disabled"] if "disabled" in data[symbol] else False, + ) + if symbol in data + else ElementModel( + number=number, + symbol=symbol, + disabled=True, + ) + for number, symbol in enumerate(symbols, 1) + ] + + def Legend(): + with Div(classes=["legend"]): + for pp in metadata.values(): + with Div(classes=["legend-item"]): + HTML( + classes=["legend-item-marker"], + style={"background-color": pp["background_color"]}, + ) + Text(text=pp["display_name"]) + + Style(css / "explore.css") + + with VBox(): + Text(text="SSSP Efficiency (v1.3.0)", classes=["title"]) + Legend() + PTable(elements) diff --git a/src/aiidalab_sssp/pages/verify.py b/src/aiidalab_sssp/pages/verify.py new file mode 100644 index 0000000..e69de29