Spaces:
Build error
Build error
Update app.py
Browse files
app.py
CHANGED
@@ -7,6 +7,23 @@ import numpy as np
|
|
7 |
import matplotlib.pyplot as plt
|
8 |
from periodictable import elements
|
9 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
10 |
# -----------------------------
|
11 |
# Data
|
12 |
# -----------------------------
|
@@ -65,23 +82,23 @@ def classify_category(el) -> str:
|
|
65 |
return "alkali"
|
66 |
if el.block == "s" and el.group == 2:
|
67 |
return "alkaline-earth"
|
68 |
-
if el.block == "p" and el.group in (13, 14, 15, 16) and el.metallic:
|
69 |
-
return "post-transition"
|
70 |
-
if el.block == "d":
|
71 |
-
return "transition"
|
72 |
if el.block == "p" and el.group == 17:
|
73 |
return "halogen"
|
74 |
if el.block == "p" and el.group == 18:
|
75 |
return "noble-gas"
|
76 |
-
if el.block == "
|
77 |
-
return "
|
78 |
if el.block == "f" and 57 <= el.number <= 71:
|
79 |
return "lanthanide"
|
80 |
if el.block == "f" and 89 <= el.number <= 103:
|
81 |
return "actinide"
|
|
|
|
|
|
|
|
|
82 |
except Exception:
|
83 |
pass
|
84 |
-
return "
|
85 |
|
86 |
def build_elements_df() -> pd.DataFrame:
|
87 |
rows = []
|
@@ -94,45 +111,64 @@ def build_elements_df() -> pd.DataFrame:
|
|
94 |
"symbol": el.symbol,
|
95 |
"name": el.name.title(),
|
96 |
"period": getattr(el, "period", None),
|
97 |
-
"group": getattr(el, "group", None),
|
98 |
"block": getattr(el, "block", None),
|
99 |
-
"mass": getattr(el, "mass", None),
|
100 |
-
"density": getattr(el, "density", None),
|
101 |
-
"electronegativity": getattr(el, "electronegativity", None),
|
102 |
-
"boiling_point": getattr(el, "boiling_point", None),
|
103 |
-
"melting_point": getattr(el, "melting_point", None),
|
104 |
-
"vdw_radius": getattr(el, "vdw_radius", None),
|
105 |
-
"covalent_radius": getattr(el, "covalent_radius", None),
|
106 |
"category": classify_category(el),
|
107 |
"is_radioactive": bool(getattr(el, "radioactive", False)),
|
108 |
}
|
109 |
rows.append(data)
|
110 |
-
|
111 |
-
return df
|
112 |
|
113 |
DF = build_elements_df()
|
114 |
|
115 |
# -----------------------------
|
116 |
-
#
|
|
|
|
|
117 |
# -----------------------------
|
118 |
MAX_GROUP = 18
|
119 |
MAX_PERIOD = 7
|
120 |
GRID: List[List[Optional[int]]] = [[None for _ in range(MAX_GROUP)] for _ in range(MAX_PERIOD)]
|
121 |
|
122 |
-
for
|
123 |
-
|
124 |
-
|
125 |
-
|
126 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
127 |
continue
|
128 |
-
|
129 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
130 |
|
|
|
131 |
LAN = [int(z) for z in DF["Z"] if 57 <= int(z) <= 71]
|
132 |
ACT = [int(z) for z in DF["Z"] if 89 <= int(z) <= 103]
|
133 |
|
134 |
# -----------------------------
|
135 |
-
# Plotting
|
136 |
# -----------------------------
|
137 |
def plot_trend(trend_df: pd.DataFrame, prop_key: str, Z: int, symbol: str):
|
138 |
fig, ax = plt.subplots()
|
@@ -198,13 +234,13 @@ def element_info(z_or_symbol: str):
|
|
198 |
f"Period {int(row['period']) if not pd.isna(row['period']) else '—'}, "
|
199 |
f"Group {row['group'] if row['group'] is not None else '—'}, "
|
200 |
f"Block {row['block']} | Category: {row['category'].replace('-', ' ').title()}",
|
201 |
-
f"Atomic mass: {row['mass'] if row['mass'] else '—'} u",
|
202 |
-
f"Density: {row['density'] if row['density'] else '—'} g/cm³",
|
203 |
-
f"Electronegativity: {row['electronegativity'] if row['electronegativity'] else '—'} (Pauling)",
|
204 |
-
f"Melting point: {row['melting_point'] if row['melting_point'] else '—'} K | "
|
205 |
-
f"Boiling point: {row['boiling_point'] if row['boiling_point'] else '—'} K",
|
206 |
-
f"vdW radius: {row['vdw_radius'] if row['vdw_radius'] else '—'} pm | "
|
207 |
-
f"Covalent radius: {row['covalent_radius'] if row['covalent_radius'] else '—'} pm",
|
208 |
f"Radioactive: {'Yes' if row['is_radioactive'] else 'No'}",
|
209 |
]
|
210 |
info_text = "\n".join(props_lines)
|
@@ -226,7 +262,7 @@ def search_element(query: str):
|
|
226 |
return element_info(query)
|
227 |
|
228 |
# -----------------------------
|
229 |
-
# UI (
|
230 |
# -----------------------------
|
231 |
with gr.Blocks(title="Interactive Periodic Table") as demo:
|
232 |
gr.Markdown("# 🧪 Interactive Periodic Table\nClick an element or search by symbol/name/atomic number.")
|
@@ -239,24 +275,21 @@ with gr.Blocks(title="Interactive Periodic Table") as demo:
|
|
239 |
info = gr.Textbox(label="Properties", lines=10, interactive=False)
|
240 |
facts = gr.Markdown("Select an element to see fun facts.")
|
241 |
trend = gr.Plot()
|
242 |
-
|
243 |
search.submit(search_element, inputs=[search], outputs=[info, facts, trend])
|
244 |
|
245 |
gr.Markdown("### Trend heatmap")
|
246 |
prop = gr.Dropdown(choices=[k for k, _ in NUMERIC_PROPS], value="electronegativity", label="Property")
|
247 |
heat = gr.Plot()
|
248 |
prop.change(lambda k: plot_heatmap(k), inputs=[prop], outputs=[heat])
|
249 |
-
|
250 |
-
# Initialize heatmap on load
|
251 |
demo.load(lambda: plot_heatmap("electronegativity"), outputs=[heat])
|
252 |
|
253 |
-
# Grid of element buttons
|
254 |
with gr.Column(scale=2):
|
255 |
gr.Markdown("### Main Table")
|
|
|
256 |
with gr.Row():
|
257 |
for g in range(1, 19):
|
258 |
gr.Markdown(f"**{g}**")
|
259 |
-
|
260 |
for r in range(MAX_PERIOD):
|
261 |
with gr.Row():
|
262 |
for c in range(MAX_GROUP):
|
@@ -285,3 +318,4 @@ with gr.Blocks(title="Interactive Periodic Table") as demo:
|
|
285 |
|
286 |
if __name__ == "__main__":
|
287 |
demo.launch()
|
|
|
|
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 |
+
try:
|
23 |
+
return float(x)
|
24 |
+
except Exception:
|
25 |
+
return np.nan
|
26 |
+
|
27 |
# -----------------------------
|
28 |
# Data
|
29 |
# -----------------------------
|
|
|
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:
|
94 |
return "actinide"
|
95 |
+
if el.block == "p" and not el.metallic:
|
96 |
+
return "nonmetal"
|
97 |
+
if el.block == "p" and el.metallic:
|
98 |
+
return "post-transition"
|
99 |
except Exception:
|
100 |
pass
|
101 |
+
return "post-transition" if getattr(el, "metallic", False) else "nonmetal"
|
102 |
|
103 |
def build_elements_df() -> pd.DataFrame:
|
104 |
rows = []
|
|
|
111 |
"symbol": el.symbol,
|
112 |
"name": el.name.title(),
|
113 |
"period": getattr(el, "period", None),
|
114 |
+
"group": getattr(el, "group", None), # may be None for many
|
115 |
"block": getattr(el, "block", None),
|
116 |
+
"mass": to_float(getattr(el, "mass", None)),
|
117 |
+
"density": to_float(getattr(el, "density", None)),
|
118 |
+
"electronegativity": to_float(getattr(el, "electronegativity", None)),
|
119 |
+
"boiling_point": to_float(getattr(el, "boiling_point", None)),
|
120 |
+
"melting_point": to_float(getattr(el, "melting_point", None)),
|
121 |
+
"vdw_radius": to_float(getattr(el, "vdw_radius", None)),
|
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 |
+
# Build a robust grid (no reliance on group from the lib)
|
133 |
+
# Rules: s->groups 1-2, d->3..12, p->13..18; period 1 special (H at 1, He at 18)
|
134 |
+
# f-block shown separately.
|
135 |
# -----------------------------
|
136 |
MAX_GROUP = 18
|
137 |
MAX_PERIOD = 7
|
138 |
GRID: List[List[Optional[int]]] = [[None for _ in range(MAX_GROUP)] for _ in range(MAX_PERIOD)]
|
139 |
|
140 |
+
for period in range(1, MAX_PERIOD + 1):
|
141 |
+
rows = DF[DF["period"] == period].sort_values("Z")
|
142 |
+
s = rows[rows["block"] == "s"]["Z"].tolist()
|
143 |
+
d = rows[rows["block"] == "d"]["Z"].tolist()
|
144 |
+
p = rows[rows["block"] == "p"]["Z"].tolist()
|
145 |
+
# Period 1 special case
|
146 |
+
if period == 1:
|
147 |
+
# Expect H then He
|
148 |
+
if len(s) >= 1:
|
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 (lanthanides/actinides)
|
167 |
LAN = [int(z) for z in DF["Z"] if 57 <= int(z) <= 71]
|
168 |
ACT = [int(z) for z in DF["Z"] if 89 <= int(z) <= 103]
|
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()
|
|
|
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: {row['mass'] if not pd.isna(row['mass']) else '—'} u",
|
238 |
+
f"Density: {row['density'] if not pd.isna(row['density']) else '—'} g/cm³",
|
239 |
+
f"Electronegativity: {row['electronegativity'] if not pd.isna(row['electronegativity']) else '—'} (Pauling)",
|
240 |
+
f"Melting point: {row['melting_point'] if not pd.isna(row['melting_point']) else '—'} K | "
|
241 |
+
f"Boiling point: {row['boiling_point'] if not pd.isna(row['boiling_point']) else '—'} K",
|
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)
|
|
|
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("# 🧪 Interactive Periodic Table\nClick an element or search by symbol/name/atomic number.")
|
|
|
275 |
info = gr.Textbox(label="Properties", lines=10, interactive=False)
|
276 |
facts = gr.Markdown("Select an element to see fun facts.")
|
277 |
trend = gr.Plot()
|
|
|
278 |
search.submit(search_element, inputs=[search], outputs=[info, facts, trend])
|
279 |
|
280 |
gr.Markdown("### Trend heatmap")
|
281 |
prop = gr.Dropdown(choices=[k for k, _ in NUMERIC_PROPS], value="electronegativity", label="Property")
|
282 |
heat = gr.Plot()
|
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 |
+
# Grid of element buttons
|
293 |
for r in range(MAX_PERIOD):
|
294 |
with gr.Row():
|
295 |
for c in range(MAX_GROUP):
|
|
|
318 |
|
319 |
if __name__ == "__main__":
|
320 |
demo.launch()
|
321 |
+
|