Spaces:
Build error
Build error
Update app.py
Browse files
app.py
CHANGED
@@ -1,4 +1,3 @@
|
|
1 |
-
|
2 |
import math
|
3 |
from typing import Dict, List, Optional
|
4 |
|
@@ -8,6 +7,9 @@ import numpy as np
|
|
8 |
import matplotlib.pyplot as plt
|
9 |
from periodictable import elements
|
10 |
|
|
|
|
|
|
|
11 |
NUMERIC_PROPS = [
|
12 |
("mass", "Atomic mass (u)"),
|
13 |
("density", "Density (g/cm^3)"),
|
@@ -109,23 +111,33 @@ def build_elements_df() -> pd.DataFrame:
|
|
109 |
return df
|
110 |
|
111 |
DF = build_elements_df()
|
|
|
|
|
|
|
|
|
112 |
MAX_GROUP = 18
|
113 |
MAX_PERIOD = 7
|
114 |
-
|
115 |
GRID: List[List[Optional[int]]] = [[None for _ in range(MAX_GROUP)] for _ in range(MAX_PERIOD)]
|
|
|
116 |
for _, row in DF.iterrows():
|
117 |
-
period
|
118 |
-
|
|
|
|
|
|
|
119 |
continue
|
120 |
-
|
|
|
121 |
|
122 |
-
LAN = [z for z in DF["Z"] if 57 <= z <= 71]
|
123 |
-
ACT = [z for z in DF["Z"] if 89 <= z <= 103]
|
124 |
|
|
|
|
|
|
|
125 |
def plot_trend(trend_df: pd.DataFrame, prop_key: str, Z: int, symbol: str):
|
126 |
fig, ax = plt.subplots()
|
127 |
ax.scatter(trend_df["Z"], trend_df[prop_key])
|
128 |
-
# highlight
|
129 |
sel = trend_df.loc[trend_df["Z"] == Z, prop_key]
|
130 |
if not sel.empty and not pd.isna(sel.values[0]):
|
131 |
ax.scatter([Z], [sel.values[0]], s=80)
|
@@ -144,16 +156,15 @@ def plot_heatmap(property_key: str):
|
|
144 |
z = GRID[r][c]
|
145 |
if z is None:
|
146 |
continue
|
147 |
-
val = DF.loc[DF[
|
148 |
if not pd.isna(val):
|
149 |
grid_vals[r, c] = float(val)
|
150 |
-
|
151 |
fig, ax = plt.subplots()
|
152 |
im = ax.imshow(grid_vals, origin="upper", aspect="auto")
|
153 |
ax.set_xticks(range(MAX_GROUP))
|
154 |
-
ax.set_xticklabels([str(i) for i in range(1, MAX_GROUP+1)])
|
155 |
ax.set_yticks(range(MAX_PERIOD))
|
156 |
-
ax.set_yticklabels([str(i) for i in range(1, MAX_PERIOD+1)])
|
157 |
ax.set_xlabel("Group")
|
158 |
ax.set_ylabel("Period")
|
159 |
ax.set_title(f"Periodic heatmap: {prop_label}")
|
@@ -161,6 +172,9 @@ def plot_heatmap(property_key: str):
|
|
161 |
fig.tight_layout()
|
162 |
return fig
|
163 |
|
|
|
|
|
|
|
164 |
def element_info(z_or_symbol: str):
|
165 |
try:
|
166 |
if z_or_symbol.isdigit():
|
@@ -172,29 +186,33 @@ def element_info(z_or_symbol: str):
|
|
172 |
except Exception:
|
173 |
return f"Unknown element: {z_or_symbol}", None, None
|
174 |
|
175 |
-
row = DF.loc[DF[
|
176 |
-
symbol = row[
|
177 |
|
178 |
facts = []
|
179 |
facts.extend(CURATED_FACTS.get(symbol, []))
|
180 |
-
facts.append(GROUP_FACTS.get(row[
|
181 |
facts = [f for f in facts if f]
|
182 |
|
183 |
props_lines = [
|
184 |
f"{row['name']} ({symbol}), Z = {Z}",
|
185 |
-
f"Period {int(row['period'])
|
|
|
|
|
186 |
f"Atomic mass: {row['mass'] if row['mass'] else '—'} u",
|
187 |
f"Density: {row['density'] if row['density'] else '—'} g/cm³",
|
188 |
f"Electronegativity: {row['electronegativity'] if row['electronegativity'] else '—'} (Pauling)",
|
189 |
-
f"Melting point: {row['melting_point'] if row['melting_point'] else '—'} K |
|
190 |
-
f"
|
|
|
|
|
191 |
f"Radioactive: {'Yes' if row['is_radioactive'] else 'No'}",
|
192 |
]
|
193 |
info_text = "\n".join(props_lines)
|
194 |
facts_text = "\n• ".join(["Interesting facts:"] + facts) if facts else "No fact on file—still cool though!"
|
195 |
|
196 |
-
prop_key =
|
197 |
-
trend_df = DF[[
|
198 |
fig = plot_trend(trend_df, prop_key, Z, symbol)
|
199 |
|
200 |
return info_text, facts_text, fig
|
@@ -203,20 +221,45 @@ def handle_button_click(z: int):
|
|
203 |
return element_info(str(z))
|
204 |
|
205 |
def search_element(query: str):
|
206 |
-
query = (query or
|
207 |
if not query:
|
208 |
return gr.update(), gr.update(), gr.update()
|
209 |
return element_info(query)
|
210 |
|
|
|
|
|
|
|
211 |
with gr.Blocks(title="Interactive Periodic Table") as demo:
|
212 |
gr.Markdown("# 🧪 Interactive Periodic Table\nClick an element or search by symbol/name/atomic number.")
|
213 |
|
214 |
with gr.Row():
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
215 |
with gr.Column(scale=2):
|
216 |
gr.Markdown("### Main Table")
|
|
|
217 |
with gr.Row():
|
218 |
for g in range(1, 19):
|
219 |
gr.Markdown(f"**{g}**")
|
|
|
|
|
220 |
for r in range(MAX_PERIOD):
|
221 |
with gr.Row():
|
222 |
for c in range(MAX_GROUP):
|
@@ -224,41 +267,26 @@ with gr.Blocks(title="Interactive Periodic Table") as demo:
|
|
224 |
if z is None:
|
225 |
gr.Button("", interactive=False)
|
226 |
else:
|
227 |
-
sym = DF.loc[DF[
|
228 |
btn = gr.Button(sym)
|
229 |
-
btn.click(handle_button_click, inputs=[gr.Number(z, visible=False)],
|
230 |
-
|
231 |
|
232 |
gr.Markdown("### f-block (lanthanides & actinides)")
|
233 |
with gr.Row():
|
234 |
for z in LAN:
|
235 |
-
sym = DF.loc[DF[
|
236 |
btn = gr.Button(sym)
|
237 |
-
btn.click(handle_button_click, inputs=[gr.Number(z, visible=False)],
|
238 |
-
|
239 |
with gr.Row():
|
240 |
for z in ACT:
|
241 |
-
sym = DF.loc[DF[
|
242 |
btn = gr.Button(sym)
|
243 |
-
btn.click(handle_button_click, inputs=[gr.Number(z, visible=False)],
|
244 |
-
|
245 |
-
|
246 |
-
with gr.Column(scale=1):
|
247 |
-
search = gr.Textbox(label="Search (symbol/name/Z)", placeholder="e.g., C, Iron, 79")
|
248 |
-
info = gr.Textbox(label="Properties", lines=10, interactive=False)
|
249 |
-
facts = gr.Markdown("Select an element to see fun facts.")
|
250 |
-
trend = gr.Matplotlib()
|
251 |
-
|
252 |
-
search.submit(search_element, inputs=[search], outputs=[info, facts, trend])
|
253 |
-
|
254 |
-
gr.Markdown("### Trend heatmap")
|
255 |
-
prop = gr.Dropdown(choices=[k for k, _ in NUMERIC_PROPS], value="electronegativity", label="Property")
|
256 |
-
heat = gr.Matplotlib()
|
257 |
-
|
258 |
-
def heatmap_callback(property_key):
|
259 |
-
return plot_heatmap(property_key)
|
260 |
-
prop.change(heatmap_callback, inputs=[prop], outputs=[heat])
|
261 |
-
heat.update(plot_heatmap("electronegativity"))
|
262 |
|
263 |
if __name__ == "__main__":
|
264 |
demo.launch()
|
|
|
|
|
|
|
|
1 |
import math
|
2 |
from typing import Dict, List, Optional
|
3 |
|
|
|
7 |
import matplotlib.pyplot as plt
|
8 |
from periodictable import elements
|
9 |
|
10 |
+
# -----------------------------
|
11 |
+
# Data
|
12 |
+
# -----------------------------
|
13 |
NUMERIC_PROPS = [
|
14 |
("mass", "Atomic mass (u)"),
|
15 |
("density", "Density (g/cm^3)"),
|
|
|
111 |
return df
|
112 |
|
113 |
DF = build_elements_df()
|
114 |
+
|
115 |
+
# -----------------------------
|
116 |
+
# Safe grid mapping (skip None period/group)
|
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 _, row in DF.iterrows():
|
123 |
+
period = row["period"]
|
124 |
+
group = row["group"]
|
125 |
+
z = int(row["Z"])
|
126 |
+
# Skip rows with missing period or group
|
127 |
+
if pd.isna(period) or group is None:
|
128 |
continue
|
129 |
+
period = int(period)
|
130 |
+
GRID[period - 1][group - 1] = z
|
131 |
|
132 |
+
LAN = [int(z) for z in DF["Z"] if 57 <= int(z) <= 71]
|
133 |
+
ACT = [int(z) for z in DF["Z"] if 89 <= int(z) <= 103]
|
134 |
|
135 |
+
# -----------------------------
|
136 |
+
# Plotting helpers (Matplotlib)
|
137 |
+
# -----------------------------
|
138 |
def plot_trend(trend_df: pd.DataFrame, prop_key: str, Z: int, symbol: str):
|
139 |
fig, ax = plt.subplots()
|
140 |
ax.scatter(trend_df["Z"], trend_df[prop_key])
|
|
|
141 |
sel = trend_df.loc[trend_df["Z"] == Z, prop_key]
|
142 |
if not sel.empty and not pd.isna(sel.values[0]):
|
143 |
ax.scatter([Z], [sel.values[0]], s=80)
|
|
|
156 |
z = GRID[r][c]
|
157 |
if z is None:
|
158 |
continue
|
159 |
+
val = DF.loc[DF["Z"] == z, property_key].values[0]
|
160 |
if not pd.isna(val):
|
161 |
grid_vals[r, c] = float(val)
|
|
|
162 |
fig, ax = plt.subplots()
|
163 |
im = ax.imshow(grid_vals, origin="upper", aspect="auto")
|
164 |
ax.set_xticks(range(MAX_GROUP))
|
165 |
+
ax.set_xticklabels([str(i) for i in range(1, MAX_GROUP + 1)])
|
166 |
ax.set_yticks(range(MAX_PERIOD))
|
167 |
+
ax.set_yticklabels([str(i) for i in range(1, MAX_PERIOD + 1)])
|
168 |
ax.set_xlabel("Group")
|
169 |
ax.set_ylabel("Period")
|
170 |
ax.set_title(f"Periodic heatmap: {prop_label}")
|
|
|
172 |
fig.tight_layout()
|
173 |
return fig
|
174 |
|
175 |
+
# -----------------------------
|
176 |
+
# Callbacks
|
177 |
+
# -----------------------------
|
178 |
def element_info(z_or_symbol: str):
|
179 |
try:
|
180 |
if z_or_symbol.isdigit():
|
|
|
186 |
except Exception:
|
187 |
return f"Unknown element: {z_or_symbol}", None, None
|
188 |
|
189 |
+
row = DF.loc[DF["Z"] == Z].iloc[0].to_dict()
|
190 |
+
symbol = row["symbol"]
|
191 |
|
192 |
facts = []
|
193 |
facts.extend(CURATED_FACTS.get(symbol, []))
|
194 |
+
facts.append(GROUP_FACTS.get(row["category"], None))
|
195 |
facts = [f for f in facts if f]
|
196 |
|
197 |
props_lines = [
|
198 |
f"{row['name']} ({symbol}), Z = {Z}",
|
199 |
+
f"Period {int(row['period']) if not pd.isna(row['period']) else '—'}, "
|
200 |
+
f"Group {row['group'] if row['group'] is not None else '—'}, "
|
201 |
+
f"Block {row['block']} | Category: {row['category'].replace('-', ' ').title()}",
|
202 |
f"Atomic mass: {row['mass'] if row['mass'] else '—'} u",
|
203 |
f"Density: {row['density'] if row['density'] else '—'} g/cm³",
|
204 |
f"Electronegativity: {row['electronegativity'] if row['electronegativity'] else '—'} (Pauling)",
|
205 |
+
f"Melting point: {row['melting_point'] if row['melting_point'] else '—'} K | "
|
206 |
+
f"Boiling point: {row['boiling_point'] if row['boiling_point'] else '—'} K",
|
207 |
+
f"vdW radius: {row['vdw_radius'] if row['vdw_radius'] else '—'} pm | "
|
208 |
+
f"Covalent radius: {row['covalent_radius'] if row['covalent_radius'] else '—'} pm",
|
209 |
f"Radioactive: {'Yes' if row['is_radioactive'] else 'No'}",
|
210 |
]
|
211 |
info_text = "\n".join(props_lines)
|
212 |
facts_text = "\n• ".join(["Interesting facts:"] + facts) if facts else "No fact on file—still cool though!"
|
213 |
|
214 |
+
prop_key = "electronegativity" if not pd.isna(row["electronegativity"]) else "mass"
|
215 |
+
trend_df = DF[["Z", "symbol", prop_key]].dropna()
|
216 |
fig = plot_trend(trend_df, prop_key, Z, symbol)
|
217 |
|
218 |
return info_text, facts_text, fig
|
|
|
221 |
return element_info(str(z))
|
222 |
|
223 |
def search_element(query: str):
|
224 |
+
query = (query or "").strip()
|
225 |
if not query:
|
226 |
return gr.update(), gr.update(), gr.update()
|
227 |
return element_info(query)
|
228 |
|
229 |
+
# -----------------------------
|
230 |
+
# UI
|
231 |
+
# -----------------------------
|
232 |
with gr.Blocks(title="Interactive Periodic Table") as demo:
|
233 |
gr.Markdown("# 🧪 Interactive Periodic Table\nClick an element or search by symbol/name/atomic number.")
|
234 |
|
235 |
with gr.Row():
|
236 |
+
# Create the inspector FIRST so button clicks can target these components
|
237 |
+
with gr.Column(scale=1):
|
238 |
+
gr.Markdown("### Inspector")
|
239 |
+
search = gr.Textbox(label="Search (symbol/name/Z)", placeholder="e.g., C, Iron, 79")
|
240 |
+
info = gr.Textbox(label="Properties", lines=10, interactive=False)
|
241 |
+
facts = gr.Markdown("Select an element to see fun facts.")
|
242 |
+
trend = gr.Matplotlib()
|
243 |
+
|
244 |
+
search.submit(search_element, inputs=[search], outputs=[info, facts, trend])
|
245 |
+
|
246 |
+
gr.Markdown("### Trend heatmap")
|
247 |
+
prop = gr.Dropdown(choices=[k for k, _ in NUMERIC_PROPS], value="electronegativity", label="Property")
|
248 |
+
heat = gr.Matplotlib()
|
249 |
+
prop.change(lambda k: plot_heatmap(k), inputs=[prop], outputs=[heat])
|
250 |
+
|
251 |
+
# Initialize heatmap on load
|
252 |
+
demo.load(lambda: plot_heatmap("electronegativity"), outputs=[heat])
|
253 |
+
|
254 |
+
# Now build the grid of buttons
|
255 |
with gr.Column(scale=2):
|
256 |
gr.Markdown("### Main Table")
|
257 |
+
# Headers (groups 1-18)
|
258 |
with gr.Row():
|
259 |
for g in range(1, 19):
|
260 |
gr.Markdown(f"**{g}**")
|
261 |
+
|
262 |
+
# Element grid
|
263 |
for r in range(MAX_PERIOD):
|
264 |
with gr.Row():
|
265 |
for c in range(MAX_GROUP):
|
|
|
267 |
if z is None:
|
268 |
gr.Button("", interactive=False)
|
269 |
else:
|
270 |
+
sym = DF.loc[DF["Z"] == z, "symbol"].values[0]
|
271 |
btn = gr.Button(sym)
|
272 |
+
btn.click(handle_button_click, inputs=[gr.Number(z, visible=False)],
|
273 |
+
outputs=[info, facts, trend])
|
274 |
|
275 |
gr.Markdown("### f-block (lanthanides & actinides)")
|
276 |
with gr.Row():
|
277 |
for z in LAN:
|
278 |
+
sym = DF.loc[DF["Z"] == z, "symbol"].values[0]
|
279 |
btn = gr.Button(sym)
|
280 |
+
btn.click(handle_button_click, inputs=[gr.Number(z, visible=False)],
|
281 |
+
outputs=[info, facts, trend])
|
282 |
with gr.Row():
|
283 |
for z in ACT:
|
284 |
+
sym = DF.loc[DF["Z"] == z, "symbol"].values[0]
|
285 |
btn = gr.Button(sym)
|
286 |
+
btn.click(handle_button_click, inputs=[gr.Number(z, visible=False)],
|
287 |
+
outputs=[info, facts, trend])
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
288 |
|
289 |
if __name__ == "__main__":
|
290 |
demo.launch()
|
291 |
+
|
292 |
+
|