Spaces:
Build error
Build error
Update app.py
Browse files
app.py
CHANGED
|
@@ -1,32 +1,20 @@
|
|
| 1 |
-
import math
|
| 2 |
-
from typing import Dict, List, Optional
|
| 3 |
-
|
| 4 |
import gradio as gr
|
| 5 |
import pandas as pd
|
| 6 |
import numpy as np
|
| 7 |
import matplotlib.pyplot as plt
|
|
|
|
| 8 |
from periodictable import elements
|
| 9 |
|
| 10 |
-
#
|
| 11 |
-
# Helpers
|
| 12 |
-
# -----------------------------
|
| 13 |
def to_float(x):
|
| 14 |
-
"""Coerce periodictable numeric (incl. uncertainties) to plain float, else NaN."""
|
| 15 |
if x is None:
|
| 16 |
return np.nan
|
|
|
|
| 17 |
try:
|
| 18 |
-
# uncertainties.UFloat has .nominal_value
|
| 19 |
-
v = getattr(x, "nominal_value", x)
|
| 20 |
return float(v)
|
| 21 |
except Exception:
|
| 22 |
-
|
| 23 |
-
return float(x)
|
| 24 |
-
except Exception:
|
| 25 |
-
return np.nan
|
| 26 |
|
| 27 |
-
# -----------------------------
|
| 28 |
-
# Data
|
| 29 |
-
# -----------------------------
|
| 30 |
NUMERIC_PROPS = [
|
| 31 |
("mass", "Atomic mass (u)"),
|
| 32 |
("density", "Density (g/cm^3)"),
|
|
@@ -38,42 +26,40 @@ NUMERIC_PROPS = [
|
|
| 38 |
]
|
| 39 |
|
| 40 |
CURATED_FACTS: Dict[str, List[str]] = {
|
| 41 |
-
"H": ["Lightest element; ~74% of
|
| 42 |
-
"He": ["Inert
|
| 43 |
-
"Li": ["
|
| 44 |
-
"C": ["
|
| 45 |
-
"N": ["~78% of Earth's atmosphere is
|
| 46 |
-
"O": ["
|
| 47 |
-
"Na": ["
|
| 48 |
-
"Mg": ["
|
| 49 |
-
"
|
| 50 |
-
"
|
| 51 |
-
"
|
| 52 |
-
"
|
| 53 |
-
"
|
| 54 |
-
"
|
| 55 |
-
"
|
| 56 |
-
"
|
| 57 |
-
"
|
| 58 |
-
"
|
| 59 |
-
"
|
| 60 |
-
"
|
| 61 |
-
"
|
| 62 |
-
"Ne": ["Neon glows striking red-orange in discharge tubes—classic signs."],
|
| 63 |
-
"Xe": ["Xenon makes bright camera flashes and high-intensity lamps."],
|
| 64 |
}
|
| 65 |
|
| 66 |
GROUP_FACTS = {
|
| 67 |
-
"alkali": "Alkali metal: very reactive
|
| 68 |
-
"alkaline-earth": "Alkaline earth metal: reactive
|
| 69 |
-
"transition": "Transition metal:
|
| 70 |
-
"post-transition": "Post-transition metal: softer
|
| 71 |
-
"metalloid": "Metalloid:
|
| 72 |
-
"nonmetal": "Nonmetal:
|
| 73 |
-
"halogen": "Halogen: very reactive nonmetals;
|
| 74 |
-
"noble-gas": "Noble gas:
|
| 75 |
-
"lanthanide": "Lanthanide:
|
| 76 |
-
"actinide": "Actinide: radioactive
|
| 77 |
}
|
| 78 |
|
| 79 |
def classify_category(el) -> str:
|
|
@@ -82,12 +68,12 @@ def classify_category(el) -> str:
|
|
| 82 |
return "alkali"
|
| 83 |
if el.block == "s" and el.group == 2:
|
| 84 |
return "alkaline-earth"
|
|
|
|
|
|
|
| 85 |
if el.block == "p" and el.group == 17:
|
| 86 |
return "halogen"
|
| 87 |
if el.block == "p" and el.group == 18:
|
| 88 |
return "noble-gas"
|
| 89 |
-
if el.block == "d":
|
| 90 |
-
return "transition"
|
| 91 |
if el.block == "f" and 57 <= el.number <= 71:
|
| 92 |
return "lanthanide"
|
| 93 |
if el.block == "f" and 89 <= el.number <= 103:
|
|
@@ -106,12 +92,12 @@ def build_elements_df() -> pd.DataFrame:
|
|
| 106 |
el = elements[Z]
|
| 107 |
if el is None:
|
| 108 |
continue
|
| 109 |
-
|
| 110 |
"Z": el.number,
|
| 111 |
"symbol": el.symbol,
|
| 112 |
"name": el.name.title(),
|
| 113 |
"period": getattr(el, "period", None),
|
| 114 |
-
"group": getattr(el, "group", None),
|
| 115 |
"block": getattr(el, "block", None),
|
| 116 |
"mass": to_float(getattr(el, "mass", None)),
|
| 117 |
"density": to_float(getattr(el, "density", None)),
|
|
@@ -122,54 +108,35 @@ def build_elements_df() -> pd.DataFrame:
|
|
| 122 |
"covalent_radius": to_float(getattr(el, "covalent_radius", None)),
|
| 123 |
"category": classify_category(el),
|
| 124 |
"is_radioactive": bool(getattr(el, "radioactive", False)),
|
| 125 |
-
}
|
| 126 |
-
rows.append(data)
|
| 127 |
return pd.DataFrame(rows).sort_values("Z").reset_index(drop=True)
|
| 128 |
|
| 129 |
DF = build_elements_df()
|
| 130 |
|
| 131 |
-
#
|
| 132 |
-
#
|
| 133 |
-
|
| 134 |
-
#
|
| 135 |
-
|
| 136 |
-
|
| 137 |
-
|
| 138 |
-
|
| 139 |
-
|
| 140 |
-
|
| 141 |
-
|
| 142 |
-
|
| 143 |
-
|
| 144 |
-
|
| 145 |
-
|
| 146 |
-
|
| 147 |
-
|
| 148 |
-
|
| 149 |
-
GRID[0][0] = int(s[0]) # H -> group 1
|
| 150 |
-
if len(p) >= 1:
|
| 151 |
-
GRID[0][17] = int(p[-1]) # He -> group 18
|
| 152 |
-
continue
|
| 153 |
-
# s-block (usually 2)
|
| 154 |
-
if len(s) >= 1:
|
| 155 |
-
GRID[period - 1][0] = int(s[0]) # group 1
|
| 156 |
-
if len(s) >= 2:
|
| 157 |
-
GRID[period - 1][1] = int(s[1]) # group 2
|
| 158 |
-
# d-block (10 wide), only in periods >= 4
|
| 159 |
-
for i, z in enumerate(d):
|
| 160 |
-
if i < 10:
|
| 161 |
-
GRID[period - 1][2 + i] = int(z) # groups 3..12
|
| 162 |
-
# p-block (6 wide)
|
| 163 |
-
for i, z in enumerate(p[-6:]): # last 6 p-block in order
|
| 164 |
-
GRID[period - 1][12 + i] = int(z) # groups 13..18
|
| 165 |
|
| 166 |
-
# f-block lists (
|
| 167 |
-
LAN =
|
| 168 |
-
ACT =
|
| 169 |
|
| 170 |
-
#
|
| 171 |
-
# Plotting (Matplotlib -> gr.Plot)
|
| 172 |
-
# -----------------------------
|
| 173 |
def plot_trend(trend_df: pd.DataFrame, prop_key: str, Z: int, symbol: str):
|
| 174 |
fig, ax = plt.subplots()
|
| 175 |
ax.scatter(trend_df["Z"], trend_df[prop_key])
|
|
@@ -185,9 +152,10 @@ def plot_trend(trend_df: pd.DataFrame, prop_key: str, Z: int, symbol: str):
|
|
| 185 |
|
| 186 |
def plot_heatmap(property_key: str):
|
| 187 |
prop_label = dict(NUMERIC_PROPS)[property_key]
|
| 188 |
-
|
| 189 |
-
|
| 190 |
-
|
|
|
|
| 191 |
z = GRID[r][c]
|
| 192 |
if z is None:
|
| 193 |
continue
|
|
@@ -196,10 +164,10 @@ def plot_heatmap(property_key: str):
|
|
| 196 |
grid_vals[r, c] = float(val)
|
| 197 |
fig, ax = plt.subplots()
|
| 198 |
im = ax.imshow(grid_vals, origin="upper", aspect="auto")
|
| 199 |
-
ax.set_xticks(range(
|
| 200 |
-
ax.set_xticklabels([str(i) for i in range(1,
|
| 201 |
-
ax.set_yticks(range(
|
| 202 |
-
ax.set_yticklabels([str(i) for i in range(1,
|
| 203 |
ax.set_xlabel("Group")
|
| 204 |
ax.set_ylabel("Period")
|
| 205 |
ax.set_title(f"Periodic heatmap: {prop_label}")
|
|
@@ -207,9 +175,7 @@ def plot_heatmap(property_key: str):
|
|
| 207 |
fig.tight_layout()
|
| 208 |
return fig
|
| 209 |
|
| 210 |
-
#
|
| 211 |
-
# Callbacks
|
| 212 |
-
# -----------------------------
|
| 213 |
def element_info(z_or_symbol: str):
|
| 214 |
try:
|
| 215 |
if z_or_symbol.isdigit():
|
|
@@ -229,18 +195,19 @@ def element_info(z_or_symbol: str):
|
|
| 229 |
facts.append(GROUP_FACTS.get(row["category"], None))
|
| 230 |
facts = [f for f in facts if f]
|
| 231 |
|
|
|
|
|
|
|
|
|
|
| 232 |
props_lines = [
|
| 233 |
f"{row['name']} ({symbol}), Z = {Z}",
|
| 234 |
f"Period {int(row['period']) if not pd.isna(row['period']) else '—'}, "
|
| 235 |
f"Group {row['group'] if row['group'] is not None else '—'}, "
|
| 236 |
f"Block {row['block']} | Category: {row['category'].replace('-', ' ').title()}",
|
| 237 |
-
f"Atomic mass: {
|
| 238 |
-
f"Density: {
|
| 239 |
-
f"Electronegativity: {
|
| 240 |
-
f"Melting point: {row['melting_point']
|
| 241 |
-
f"
|
| 242 |
-
f"vdW radius: {row['vdw_radius'] if not pd.isna(row['vdw_radius']) else '—'} pm | "
|
| 243 |
-
f"Covalent radius: {row['covalent_radius'] if not pd.isna(row['covalent_radius']) else '—'} pm",
|
| 244 |
f"Radioactive: {'Yes' if row['is_radioactive'] else 'No'}",
|
| 245 |
]
|
| 246 |
info_text = "\n".join(props_lines)
|
|
@@ -249,7 +216,6 @@ def element_info(z_or_symbol: str):
|
|
| 249 |
prop_key = "electronegativity" if not pd.isna(row["electronegativity"]) else "mass"
|
| 250 |
trend_df = DF[["Z", "symbol", prop_key]].dropna()
|
| 251 |
fig = plot_trend(trend_df, prop_key, Z, symbol)
|
| 252 |
-
|
| 253 |
return info_text, facts_text, fig
|
| 254 |
|
| 255 |
def handle_button_click(z: int):
|
|
@@ -261,14 +227,12 @@ def search_element(query: str):
|
|
| 261 |
return gr.update(), gr.update(), gr.update()
|
| 262 |
return element_info(query)
|
| 263 |
|
| 264 |
-
#
|
| 265 |
-
# UI (Gradio 4.29.0 compatible)
|
| 266 |
-
# -----------------------------
|
| 267 |
with gr.Blocks(title="Interactive Periodic Table") as demo:
|
| 268 |
-
gr.Markdown("
|
| 269 |
|
| 270 |
with gr.Row():
|
| 271 |
-
# Inspector
|
| 272 |
with gr.Column(scale=1):
|
| 273 |
gr.Markdown("### Inspector")
|
| 274 |
search = gr.Textbox(label="Search (symbol/name/Z)", placeholder="e.g., C, Iron, 79")
|
|
@@ -283,16 +247,15 @@ with gr.Blocks(title="Interactive Periodic Table") as demo:
|
|
| 283 |
prop.change(lambda k: plot_heatmap(k), inputs=[prop], outputs=[heat])
|
| 284 |
demo.load(lambda: plot_heatmap("electronegativity"), outputs=[heat])
|
| 285 |
|
|
|
|
| 286 |
with gr.Column(scale=2):
|
| 287 |
gr.Markdown("### Main Table")
|
| 288 |
-
# Group headers (1..18)
|
| 289 |
with gr.Row():
|
| 290 |
for g in range(1, 19):
|
| 291 |
gr.Markdown(f"**{g}**")
|
| 292 |
-
|
| 293 |
-
for r in range(MAX_PERIOD):
|
| 294 |
with gr.Row():
|
| 295 |
-
for c in range(
|
| 296 |
z = GRID[r][c]
|
| 297 |
if z is None:
|
| 298 |
gr.Button("", interactive=False)
|
|
@@ -306,15 +269,13 @@ with gr.Blocks(title="Interactive Periodic Table") as demo:
|
|
| 306 |
with gr.Row():
|
| 307 |
for z in LAN:
|
| 308 |
sym = DF.loc[DF["Z"] == z, "symbol"].values[0]
|
| 309 |
-
|
| 310 |
-
|
| 311 |
-
outputs=[info, facts, trend])
|
| 312 |
with gr.Row():
|
| 313 |
for z in ACT:
|
| 314 |
sym = DF.loc[DF["Z"] == z, "symbol"].values[0]
|
| 315 |
-
|
| 316 |
-
|
| 317 |
-
outputs=[info, facts, trend])
|
| 318 |
|
| 319 |
if __name__ == "__main__":
|
| 320 |
demo.launch()
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
import gradio as gr
|
| 2 |
import pandas as pd
|
| 3 |
import numpy as np
|
| 4 |
import matplotlib.pyplot as plt
|
| 5 |
+
from typing import Dict, List, Optional
|
| 6 |
from periodictable import elements
|
| 7 |
|
| 8 |
+
# ---------- helpers ----------
|
|
|
|
|
|
|
| 9 |
def to_float(x):
|
|
|
|
| 10 |
if x is None:
|
| 11 |
return np.nan
|
| 12 |
+
v = getattr(x, "nominal_value", x) # handles uncertainties.UFloat
|
| 13 |
try:
|
|
|
|
|
|
|
| 14 |
return float(v)
|
| 15 |
except Exception:
|
| 16 |
+
return np.nan
|
|
|
|
|
|
|
|
|
|
| 17 |
|
|
|
|
|
|
|
|
|
|
| 18 |
NUMERIC_PROPS = [
|
| 19 |
("mass", "Atomic mass (u)"),
|
| 20 |
("density", "Density (g/cm^3)"),
|
|
|
|
| 26 |
]
|
| 27 |
|
| 28 |
CURATED_FACTS: Dict[str, List[str]] = {
|
| 29 |
+
"H": ["Lightest element; ~74% of visible matter is H in stars."],
|
| 30 |
+
"He": ["Inert and super light; cryogenics & balloons."],
|
| 31 |
+
"Li": ["Lithium-ion batteries power phones & EVs."],
|
| 32 |
+
"C": ["Diamond vs graphite = same element, different structure."],
|
| 33 |
+
"N": ["~78% of Earth's atmosphere is N₂."],
|
| 34 |
+
"O": ["~21% of air; essential for respiration."],
|
| 35 |
+
"Na": ["Reacts violently with water."],
|
| 36 |
+
"Mg": ["Bright white flame in flares."],
|
| 37 |
+
"Si": ["Semiconductor backbone."],
|
| 38 |
+
"Cl": ["Disinfectant; elemental Cl₂ is toxic."],
|
| 39 |
+
"Fe": ["Steel core; oxygen transport in blood (heme)."],
|
| 40 |
+
"Cu": ["Great conductor; forms green patina."],
|
| 41 |
+
"Ag": ["Highest electrical conductivity."],
|
| 42 |
+
"Au": ["Very unreactive; great for electronics/jewelry."],
|
| 43 |
+
"Hg": ["Liquid metal at room temp; toxic."],
|
| 44 |
+
"Pb": ["Dense, malleable; toxic—phase-out in fuels/paints."],
|
| 45 |
+
"U": ["Reactor fuel (U-235)."],
|
| 46 |
+
"Pu": ["Man-made in quantity; nuclear uses."],
|
| 47 |
+
"F": ["Most electronegative; extremely reactive."],
|
| 48 |
+
"Ne": ["Classic red-orange neon glow."],
|
| 49 |
+
"Xe": ["Used in bright flashes/HID lamps."],
|
|
|
|
|
|
|
| 50 |
}
|
| 51 |
|
| 52 |
GROUP_FACTS = {
|
| 53 |
+
"alkali": "Alkali metal: very reactive; forms +1 cations; reacts with water.",
|
| 54 |
+
"alkaline-earth": "Alkaline earth metal: reactive; forms +2 cations.",
|
| 55 |
+
"transition": "Transition metal: catalysts, colorful compounds, multiple oxidation states.",
|
| 56 |
+
"post-transition": "Post-transition metal: softer, lower melting than transition metals.",
|
| 57 |
+
"metalloid": "Metalloid: between metals and nonmetals; often semiconductors.",
|
| 58 |
+
"nonmetal": "Nonmetal: forms covalent compounds; huge biological roles.",
|
| 59 |
+
"halogen": "Halogen: very reactive nonmetals; −1 state; forms salts.",
|
| 60 |
+
"noble-gas": "Noble gas: inert, monatomic gases.",
|
| 61 |
+
"lanthanide": "Lanthanide: rare earths; magnets, lasers, phosphors.",
|
| 62 |
+
"actinide": "Actinide: radioactive; nuclear materials.",
|
| 63 |
}
|
| 64 |
|
| 65 |
def classify_category(el) -> str:
|
|
|
|
| 68 |
return "alkali"
|
| 69 |
if el.block == "s" and el.group == 2:
|
| 70 |
return "alkaline-earth"
|
| 71 |
+
if el.block == "d":
|
| 72 |
+
return "transition"
|
| 73 |
if el.block == "p" and el.group == 17:
|
| 74 |
return "halogen"
|
| 75 |
if el.block == "p" and el.group == 18:
|
| 76 |
return "noble-gas"
|
|
|
|
|
|
|
| 77 |
if el.block == "f" and 57 <= el.number <= 71:
|
| 78 |
return "lanthanide"
|
| 79 |
if el.block == "f" and 89 <= el.number <= 103:
|
|
|
|
| 92 |
el = elements[Z]
|
| 93 |
if el is None:
|
| 94 |
continue
|
| 95 |
+
rows.append({
|
| 96 |
"Z": el.number,
|
| 97 |
"symbol": el.symbol,
|
| 98 |
"name": el.name.title(),
|
| 99 |
"period": getattr(el, "period", None),
|
| 100 |
+
"group": getattr(el, "group", None),
|
| 101 |
"block": getattr(el, "block", None),
|
| 102 |
"mass": to_float(getattr(el, "mass", None)),
|
| 103 |
"density": to_float(getattr(el, "density", None)),
|
|
|
|
| 108 |
"covalent_radius": to_float(getattr(el, "covalent_radius", None)),
|
| 109 |
"category": classify_category(el),
|
| 110 |
"is_radioactive": bool(getattr(el, "radioactive", False)),
|
| 111 |
+
})
|
|
|
|
| 112 |
return pd.DataFrame(rows).sort_values("Z").reset_index(drop=True)
|
| 113 |
|
| 114 |
DF = build_elements_df()
|
| 115 |
|
| 116 |
+
# ---------- hardcoded main-grid layout (periods 1–7, groups 1–18) ----------
|
| 117 |
+
# None = empty cell; numbers = atomic numbers
|
| 118 |
+
GRID = [
|
| 119 |
+
# P1
|
| 120 |
+
[1, None, None, None, None, None, None, None, None, None, None, None, None, None, None, None, None, 2],
|
| 121 |
+
# P2
|
| 122 |
+
[3, 4, None, None, None, None, None, None, None, None, None, None, 5, 6, 7, 8, 9, 10],
|
| 123 |
+
# P3
|
| 124 |
+
[11, 12, None, None, None, None, None, None, None, None, None, None, 13, 14, 15, 16, 17, 18],
|
| 125 |
+
# P4
|
| 126 |
+
[19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36],
|
| 127 |
+
# P5
|
| 128 |
+
[37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54],
|
| 129 |
+
# P6 (La shown at group 3)
|
| 130 |
+
[55, 56, 57, 72, 73, 74, 75, 76, 77, 78, 79, 80, 81, 82, 83, 84, 85, 86],
|
| 131 |
+
# P7 (Ac shown at group 3)
|
| 132 |
+
[87, 88, 89, 104, 105, 106, 107, 108, 109, 110, 111, 112, 113, 114, 115, 116, 117, 118],
|
| 133 |
+
]
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 134 |
|
| 135 |
+
# f-block lists we display separately (omit La & Ac because they’re in the main grid)
|
| 136 |
+
LAN = list(range(58, 72)) # Ce..Lu
|
| 137 |
+
ACT = list(range(90, 104)) # Th..Lr
|
| 138 |
|
| 139 |
+
# ---------- plotting ----------
|
|
|
|
|
|
|
| 140 |
def plot_trend(trend_df: pd.DataFrame, prop_key: str, Z: int, symbol: str):
|
| 141 |
fig, ax = plt.subplots()
|
| 142 |
ax.scatter(trend_df["Z"], trend_df[prop_key])
|
|
|
|
| 152 |
|
| 153 |
def plot_heatmap(property_key: str):
|
| 154 |
prop_label = dict(NUMERIC_PROPS)[property_key]
|
| 155 |
+
max_period, max_group = len(GRID), len(GRID[0])
|
| 156 |
+
grid_vals = np.full((max_period, max_group), np.nan, dtype=float)
|
| 157 |
+
for r in range(max_period):
|
| 158 |
+
for c in range(max_group):
|
| 159 |
z = GRID[r][c]
|
| 160 |
if z is None:
|
| 161 |
continue
|
|
|
|
| 164 |
grid_vals[r, c] = float(val)
|
| 165 |
fig, ax = plt.subplots()
|
| 166 |
im = ax.imshow(grid_vals, origin="upper", aspect="auto")
|
| 167 |
+
ax.set_xticks(range(max_group))
|
| 168 |
+
ax.set_xticklabels([str(i) for i in range(1, max_group + 1)])
|
| 169 |
+
ax.set_yticks(range(max_period))
|
| 170 |
+
ax.set_yticklabels([str(i) for i in range(1, max_period + 1)])
|
| 171 |
ax.set_xlabel("Group")
|
| 172 |
ax.set_ylabel("Period")
|
| 173 |
ax.set_title(f"Periodic heatmap: {prop_label}")
|
|
|
|
| 175 |
fig.tight_layout()
|
| 176 |
return fig
|
| 177 |
|
| 178 |
+
# ---------- callbacks ----------
|
|
|
|
|
|
|
| 179 |
def element_info(z_or_symbol: str):
|
| 180 |
try:
|
| 181 |
if z_or_symbol.isdigit():
|
|
|
|
| 195 |
facts.append(GROUP_FACTS.get(row["category"], None))
|
| 196 |
facts = [f for f in facts if f]
|
| 197 |
|
| 198 |
+
def show(v): # nicer NaN -> —
|
| 199 |
+
return v if (v is not None and not pd.isna(v)) else "—"
|
| 200 |
+
|
| 201 |
props_lines = [
|
| 202 |
f"{row['name']} ({symbol}), Z = {Z}",
|
| 203 |
f"Period {int(row['period']) if not pd.isna(row['period']) else '—'}, "
|
| 204 |
f"Group {row['group'] if row['group'] is not None else '—'}, "
|
| 205 |
f"Block {row['block']} | Category: {row['category'].replace('-', ' ').title()}",
|
| 206 |
+
f"Atomic mass: {show(row['mass'])} u",
|
| 207 |
+
f"Density: {show(row['density'])} g/cm³",
|
| 208 |
+
f"Electronegativity: {show(row['electronegativity'])} (Pauling)",
|
| 209 |
+
f"Melting point: {show(row['melting_point'])} K | Boiling point: {show(row['boiling_point'])} K",
|
| 210 |
+
f"vdW radius: {show(row['vdw_radius'])} pm | Covalent radius: {show(row['covalent_radius'])} pm",
|
|
|
|
|
|
|
| 211 |
f"Radioactive: {'Yes' if row['is_radioactive'] else 'No'}",
|
| 212 |
]
|
| 213 |
info_text = "\n".join(props_lines)
|
|
|
|
| 216 |
prop_key = "electronegativity" if not pd.isna(row["electronegativity"]) else "mass"
|
| 217 |
trend_df = DF[["Z", "symbol", prop_key]].dropna()
|
| 218 |
fig = plot_trend(trend_df, prop_key, Z, symbol)
|
|
|
|
| 219 |
return info_text, facts_text, fig
|
| 220 |
|
| 221 |
def handle_button_click(z: int):
|
|
|
|
| 227 |
return gr.update(), gr.update(), gr.update()
|
| 228 |
return element_info(query)
|
| 229 |
|
| 230 |
+
# ---------- UI ----------
|
|
|
|
|
|
|
| 231 |
with gr.Blocks(title="Interactive Periodic Table") as demo:
|
| 232 |
+
gr.Markdown("Click an element or search by symbol/name/atomic number.")
|
| 233 |
|
| 234 |
with gr.Row():
|
| 235 |
+
# Inspector
|
| 236 |
with gr.Column(scale=1):
|
| 237 |
gr.Markdown("### Inspector")
|
| 238 |
search = gr.Textbox(label="Search (symbol/name/Z)", placeholder="e.g., C, Iron, 79")
|
|
|
|
| 247 |
prop.change(lambda k: plot_heatmap(k), inputs=[prop], outputs=[heat])
|
| 248 |
demo.load(lambda: plot_heatmap("electronegativity"), outputs=[heat])
|
| 249 |
|
| 250 |
+
# Main table
|
| 251 |
with gr.Column(scale=2):
|
| 252 |
gr.Markdown("### Main Table")
|
|
|
|
| 253 |
with gr.Row():
|
| 254 |
for g in range(1, 19):
|
| 255 |
gr.Markdown(f"**{g}**")
|
| 256 |
+
for r in range(len(GRID)):
|
|
|
|
| 257 |
with gr.Row():
|
| 258 |
+
for c in range(len(GRID[0])):
|
| 259 |
z = GRID[r][c]
|
| 260 |
if z is None:
|
| 261 |
gr.Button("", interactive=False)
|
|
|
|
| 269 |
with gr.Row():
|
| 270 |
for z in LAN:
|
| 271 |
sym = DF.loc[DF["Z"] == z, "symbol"].values[0]
|
| 272 |
+
gr.Button(sym).click(handle_button_click, inputs=[gr.Number(z, visible=False)],
|
| 273 |
+
outputs=[info, facts, trend])
|
|
|
|
| 274 |
with gr.Row():
|
| 275 |
for z in ACT:
|
| 276 |
sym = DF.loc[DF["Z"] == z, "symbol"].values[0]
|
| 277 |
+
gr.Button(sym).click(handle_button_click, inputs=[gr.Number(z, visible=False)],
|
| 278 |
+
outputs=[info, facts, trend])
|
|
|
|
| 279 |
|
| 280 |
if __name__ == "__main__":
|
| 281 |
demo.launch()
|