Vishwas1 commited on
Commit
665be16
·
verified ·
1 Parent(s): 3f75790

Upload 3 files

Browse files
Files changed (3) hide show
  1. README.md +36 -14
  2. app (1).py +299 -0
  3. requirements (1).txt +4 -0
README.md CHANGED
@@ -1,14 +1,36 @@
1
- ---
2
- title: ExplorePeriodicTable
3
- emoji: 🌍
4
- colorFrom: purple
5
- colorTo: red
6
- sdk: gradio
7
- sdk_version: 5.41.1
8
- app_file: app.py
9
- pinned: false
10
- license: mit
11
- short_description: Fun Program to explore periodic table
12
- ---
13
-
14
- Check out the configuration reference at https://huggingface.co/docs/hub/spaces-config-reference
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+
2
+ # 🧪 Interactive Periodic Table (Gradio)
3
+
4
+ A lightweight, click-to-explore periodic table you can deploy to a **Hugging Face Space**.
5
+
6
+ **Features**
7
+ - Click any element to view properties (mass, density, melting/boiling points, radii, etc.).
8
+ - Search by **symbol, name, or atomic number**.
9
+ - **Trend chart** for the selected element.
10
+ - **Periodic heatmap** for a chosen property (e.g., electronegativity).
11
+ - A couple of **fun facts** per element (group-based + curated highlights).
12
+
13
+ **Tech**
14
+ - [Gradio](https://www.gradio.app/) UI
15
+ - [`periodictable`](https://pypi.org/project/periodictable/) for element data
16
+ - `plotly` for charts
17
+
18
+ ## Deploy on Hugging Face Spaces
19
+
20
+ 1. Create a new Space → **Gradio** template.
21
+ 2. Add these files at the root of the repo:
22
+ - `app.py`
23
+ - `requirements.txt`
24
+ - `README.md` (optional)
25
+ 3. Commit. The Space will build and launch. If you see a build error, open the **Logs** tab.
26
+
27
+ **Tip:** Spaces sometimes sleep on free tiers; first load may take a bit while dependencies install.
28
+
29
+ ## Local development
30
+
31
+ ```bash
32
+ pip install -r requirements.txt
33
+ python app.py
34
+ ```
35
+
36
+ Then open the local Gradio URL in your browser.
app (1).py ADDED
@@ -0,0 +1,299 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+
2
+ import math
3
+ from dataclasses import asdict, dataclass
4
+ from typing import Dict, List, Optional, Tuple
5
+
6
+ import gradio as gr
7
+ import pandas as pd
8
+ import plotly.express as px
9
+
10
+ # External dependency:
11
+ # pip install periodictable
12
+ from periodictable import elements
13
+
14
+ # -----------------------------
15
+ # Data extraction helpers
16
+ # -----------------------------
17
+ NUMERIC_PROPS = [
18
+ ("mass", "Atomic mass (u)"),
19
+ ("density", "Density (g/cm^3)"),
20
+ ("electronegativity", "Pauling electronegativity"),
21
+ ("boiling_point", "Boiling point (K)"),
22
+ ("melting_point", "Melting point (K)"),
23
+ ("vdw_radius", "van der Waals radius (pm)"),
24
+ ("covalent_radius", "Covalent radius (pm)"),
25
+ ]
26
+
27
+ # Some curated quick facts. We'll augment with group-based facts so every element gets at least one.
28
+ CURATED_FACTS: Dict[str, List[str]] = {
29
+ "H": ["Lightest element; ~74% of the visible universe by mass is hydrogen in stars."],
30
+ "He": ["Inert, used in cryogenics and balloons; second lightest element."],
31
+ "Li": ["Batteries MVP: lithium-ion cells power phones and EVs."],
32
+ "C": ["Backbone of life; diamond and graphite are pure carbon with wildly different properties."],
33
+ "N": ["~78% of Earth's atmosphere is nitrogen (mostly N₂)."],
34
+ "O": ["Essential for respiration; ~21% of Earth's atmosphere."],
35
+ "Na": ["Sodium metal reacts violently with water—handle only under oil or inert gas."],
36
+ "Mg": ["Burns with a bright white flame; used in flares and fireworks."],
37
+ "Al": ["Light and strong; forms a protective oxide layer that resists corrosion."],
38
+ "Si": ["Silicon is the basis of modern electronics—hello, semiconductors."],
39
+ "Cl": ["Powerful disinfectant; elemental chlorine is toxic, compounds are widely useful."],
40
+ "Ar": ["Argon is used to provide inert atmospheres for welding and 3D printing."],
41
+ "Fe": ["Core of steel; iron is essential in hemoglobin for oxygen transport."],
42
+ "Cu": ["Excellent electrical conductor; iconic blue-green patina (verdigris)."],
43
+ "Ag": ["Highest electrical conductivity of all metals; historically used as currency."],
44
+ "Au": ["Very unreactive ('noble'); prized for electronics and jewelry."],
45
+ "Hg": ["Only metal that's liquid at room temperature; toxic—use with care."],
46
+ "Pb": ["Dense and malleable; toxicity led to phase-out from gasoline and paints."],
47
+ "U": ["Radioactive; used as nuclear reactor fuel (U-235)."],
48
+ "Pu": ["Man-made in quantity; key in certain nuclear technologies."],
49
+ "F": ["Most electronegative element; extremely reactive."],
50
+ "Ne": ["Neon glows striking red-orange in discharge tubes—classic signs."],
51
+ "Xe": ["Xenon makes bright camera flashes and high-intensity lamps."],
52
+ }
53
+
54
+ GROUP_FACTS = {
55
+ "alkali": "Alkali metal: very reactive soft metal; forms +1 cations and reacts with water.",
56
+ "alkaline-earth": "Alkaline earth metal: reactive (less than Group 1); forms +2 cations.",
57
+ "transition": "Transition metal: often good catalysts, colorful compounds, multiple oxidation states.",
58
+ "post-transition": "Post-transition metal: softer metals with lower melting points than transition metals.",
59
+ "metalloid": "Metalloid: properties between metals and nonmetals; often semiconductors.",
60
+ "nonmetal": "Nonmetal: tends to form covalent compounds; wide range of roles in biology and materials.",
61
+ "halogen": "Halogen: very reactive nonmetals; form salts with metals and −1 oxidation state.",
62
+ "noble-gas": "Noble gas: chemically inert under most conditions; monatomic gases.",
63
+ "lanthanide": "Lanthanide: f-block rare earths; notable for magnets, lasers, and phosphors.",
64
+ "actinide": "Actinide: radioactive f-block; includes nuclear fuel materials.",
65
+ }
66
+
67
+ # Map periodictable categories into the above buckets
68
+ def classify_category(el) -> str:
69
+ try:
70
+ if el.block == "s" and el.group == 1 and el.number != 1:
71
+ return "alkali"
72
+ if el.block == "s" and el.group == 2:
73
+ return "alkaline-earth"
74
+ if el.block == "p" and el.group in (13, 14, 15, 16) and el.metallic:
75
+ return "post-transition"
76
+ if el.block == "d":
77
+ return "transition"
78
+ if el.block == "p" and el.group == 17:
79
+ return "halogen"
80
+ if el.block == "p" and el.group == 18:
81
+ return "noble-gas"
82
+ if el.block == "p" and not el.metallic:
83
+ return "nonmetal"
84
+ if el.block == "f" and 57 <= el.number <= 71:
85
+ return "lanthanide"
86
+ if el.block == "f" and 89 <= el.number <= 103:
87
+ return "actinide"
88
+ except Exception:
89
+ pass
90
+ return "nonmetal" if not getattr(el, "metallic", False) else "post-transition"
91
+
92
+ # Build a dataframe of elements
93
+ def build_elements_df() -> pd.DataFrame:
94
+ rows = []
95
+ for Z in range(1, 119):
96
+ el = elements[Z]
97
+ if el is None:
98
+ continue
99
+ data = {
100
+ "Z": el.number,
101
+ "symbol": el.symbol,
102
+ "name": el.name.title(),
103
+ "period": getattr(el, "period", None),
104
+ "group": getattr(el, "group", None),
105
+ "block": getattr(el, "block", None),
106
+ "mass": getattr(el, "mass", None),
107
+ "density": getattr(el, "density", None),
108
+ "electronegativity": getattr(el, "electronegativity", None),
109
+ "boiling_point": getattr(el, "boiling_point", None),
110
+ "melting_point": getattr(el, "melting_point", None),
111
+ "vdw_radius": getattr(el, "vdw_radius", None),
112
+ "covalent_radius": getattr(el, "covalent_radius", None),
113
+ "category": classify_category(el),
114
+ "is_radioactive": bool(getattr(el, "radioactive", False)),
115
+ }
116
+ rows.append(data)
117
+ df = pd.DataFrame(rows).sort_values("Z").reset_index(drop=True)
118
+ return df
119
+
120
+ DF = build_elements_df()
121
+
122
+ # Layout positions: group (1-18) x period (1-7); f-block as two rows
123
+ MAX_GROUP = 18
124
+ MAX_PERIOD = 7
125
+
126
+ GRID: List[List[Optional[int]]] = [[None for _ in range(MAX_GROUP)] for _ in range(MAX_PERIOD)]
127
+ for _, row in DF.iterrows():
128
+ period, group, Z = int(row["period"]), row["group"], int(row["Z"])
129
+ if group is None:
130
+ continue
131
+ GRID[period-1][group-1] = Z
132
+
133
+ # f-block positions (lanthanides/actinides) - show in separate rows
134
+ LAN = [z for z in DF["Z"] if 57 <= z <= 71]
135
+ ACT = [z for z in DF["Z"] if 89 <= z <= 103]
136
+
137
+ # -----------------------------
138
+ # UI callbacks
139
+ # -----------------------------
140
+
141
+ def element_info(z_or_symbol: str):
142
+ # Accept atomic number or symbol
143
+ try:
144
+ if z_or_symbol.isdigit():
145
+ Z = int(z_or_symbol)
146
+ el = elements[Z]
147
+ else:
148
+ el = elements.symbol(z_or_symbol)
149
+ Z = el.number
150
+ except Exception:
151
+ return f\"Unknown element: {z_or_symbol}\", None, None
152
+
153
+ row = DF.loc[DF['Z'] == Z].iloc[0].to_dict()
154
+ symbol = row['symbol']
155
+ # Build facts
156
+ facts = []
157
+ facts.extend(CURATED_FACTS.get(symbol, []))
158
+ facts.append(GROUP_FACTS.get(row['category'], None))
159
+ facts = [f for f in facts if f]
160
+
161
+ # Properties text
162
+ props_lines = [
163
+ f\"{row['name']} ({symbol}), Z = {Z}\",
164
+ f\"Period {int(row['period'])}, Group {row['group']}, Block {row['block']} | Category: {row['category'].replace('-', ' ').title()}\",
165
+ f\"Atomic mass: {row['mass'] if row['mass'] else '—'} u\",
166
+ f\"Density: {row['density'] if row['density'] else '—'} g/cm³\",
167
+ f\"Electronegativity: {row['electronegativity'] if row['electronegativity'] else '—'} (Pauling)\",
168
+ f\"Melting point: {row['melting_point'] if row['melting_point'] else '—'} K | Boiling point: {row['boiling_point'] if row['boiling_point'] else '—'} K\",
169
+ f\"vdW radius: {row['vdw_radius'] if row['vdw_radius'] else '—'} pm | Covalent radius: {row['covalent_radius'] if row['covalent_radius'] else '—'} pm\",
170
+ f\"Radioactive: {'Yes' if row['is_radioactive'] else 'No'}\",
171
+ ]
172
+ info_text = \"\\n\".join(props_lines)
173
+ facts_text = \"\\n• \".join([\"Interesting facts:\"] + facts) if facts else \"No fact on file—still cool though!\"
174
+
175
+ # Trend plot (Atomic number vs selected property)
176
+ # We'll default to electronegativity if available, else mass.
177
+ prop_key = 'electronegativity' if not pd.isna(row['electronegativity']) else 'mass'
178
+ label = dict(NUMERIC_PROPS)[prop_key]
179
+ trend_df = DF[['Z', 'symbol', prop_key]].dropna()
180
+ fig = px.scatter(
181
+ trend_df, x='Z', y=prop_key, hover_name='symbol', title=f'{label} across the periodic table',
182
+ )
183
+ # Highlight selected element
184
+ fig.add_scatter(x=[Z], y=[row[prop_key]] if row[prop_key] else [None],
185
+ mode='markers+text', text=[symbol], textposition='top center')
186
+
187
+ return info_text, facts_text, fig
188
+
189
+ def handle_button_click(z: int):
190
+ return element_info(str(z))
191
+
192
+ def search_element(query: str):
193
+ query = (query or '').strip()
194
+ if not query:
195
+ return gr.update(), gr.update(), gr.update()
196
+ return element_info(query)
197
+
198
+ def heatmap(property_key: str):
199
+ prop_label = dict(NUMERIC_PROPS)[property_key]
200
+ # Create a pseudo-2D matrix for the s/p/d blocks (7x18) with property values
201
+ import numpy as np
202
+ grid_vals = np.full((MAX_PERIOD, MAX_GROUP), None, dtype=object)
203
+ for r in range(MAX_PERIOD):
204
+ for c in range(MAX_GROUP):
205
+ z = GRID[r][c]
206
+ if z is None:
207
+ continue
208
+ val = DF.loc[DF['Z'] == z, property_key].values[0]
209
+ grid_vals[r, c] = val if not pd.isna(val) else None
210
+
211
+ fig = px.imshow(
212
+ grid_vals.astype(float),
213
+ origin='upper',
214
+ labels=dict(color=prop_label, x='Group', y='Period'),
215
+ x=list(range(1, MAX_GROUP+1)),
216
+ y=list(range(1, MAX_PERIOD+1)),
217
+ title=f'Periodic heatmap: {prop_label}',
218
+ aspect='auto',
219
+ color_continuous_scale='Viridis'
220
+ )
221
+ return fig
222
+
223
+ # -----------------------------
224
+ # Build UI
225
+ # -----------------------------
226
+ with gr.Blocks(title="Interactive Periodic Table", css=\"\"\"
227
+ .button-cell {min-width: 40px; height: 40px; padding: 0.25rem; font-weight: 600;}
228
+ .symbol {font-size: 0.95rem;}
229
+ .small {font-size: 0.7rem; opacity: 0.8;}
230
+ .grid {display: grid; grid-template-columns: repeat(18, 1fr); gap: 4px;}
231
+ .fgrid {display: grid; grid-template-columns: repeat(15, 1fr); gap: 4px;}
232
+ .header {text-align:center; font-weight:700; margin: 0.5rem 0;}
233
+ \"\"\") as demo:
234
+ gr.Markdown(\"# 🧪 Interactive Periodic Table\\nClick an element or search by symbol/name/atomic number.\")
235
+
236
+ with gr.Row():
237
+ with gr.Column(scale=2):
238
+ gr.Markdown(\"### Main Table\")
239
+ main_buttons = []
240
+ with gr.Group():
241
+ with gr.Row():
242
+ gr.HTML('<div class=\"grid\">' + ''.join([f'<div class=\"header\">{g}</div>' for g in range(1, 19)]) + '</div>')
243
+ # Build button grid
244
+ rows = []
245
+ for r in range(MAX_PERIOD):
246
+ with gr.Row():
247
+ cells = []
248
+ for c in range(MAX_GROUP):
249
+ z = GRID[r][c]
250
+ if z is None:
251
+ btn = gr.Button(\"\", elem_classes=[\"button-cell\"])
252
+ btn.click(lambda: (gr.update(), gr.update(), gr.update()))
253
+ else:
254
+ sym = DF.loc[DF['Z'] == z, 'symbol'].values[0]
255
+ btn = gr.Button(sym, elem_classes=[\"button-cell\"])
256
+ btn.click(handle_button_click, inputs=[gr.Number(z, visible=False)], outputs=[
257
+ gr.Textbox(interactive=False), gr.Markdown(), gr.Plot()])
258
+ cells.append(btn)
259
+ rows.append(cells)
260
+ gr.Markdown(\"### f-block (lanthanides & actinides)\")
261
+ with gr.Row():
262
+ # Lanthanides row
263
+ lan_buttons = []
264
+ for z in LAN:
265
+ sym = DF.loc[DF['Z'] == z, 'symbol'].values[0]
266
+ btn = gr.Button(sym, elem_classes=[\"button-cell\"])
267
+ btn.click(handle_button_click, inputs=[gr.Number(z, visible=False)], outputs=[
268
+ gr.Textbox(interactive=False), gr.Markdown(), gr.Plot()])
269
+ lan_buttons.append(btn)
270
+ with gr.Row():
271
+ # Actinides row
272
+ act_buttons = []
273
+ for z in ACT:
274
+ sym = DF.loc[DF['Z'] == z, 'symbol'].values[0]
275
+ btn = gr.Button(sym, elem_classes=[\"button-cell\"])
276
+ btn.click(handle_button_click, inputs=[gr.Number(z, visible=False)], outputs=[
277
+ gr.Textbox(interactive=False), gr.Markdown(), gr.Plot()])
278
+ act_buttons.append(btn)
279
+
280
+ with gr.Column(scale=1):
281
+ gr.Markdown(\"### Inspector\")
282
+ search = gr.Textbox(label=\"Search (symbol/name/Z)\", placeholder=\"e.g., C, Iron, 79\" )
283
+ info = gr.Textbox(label=\"Properties\", lines=10, interactive=False)
284
+ facts = gr.Markdown(\"Select an element to see fun facts.\")
285
+ trend = gr.Plot()
286
+
287
+ search.submit(search_element, inputs=[search], outputs=[info, facts, trend])
288
+
289
+ gr.Markdown(\"### Trend heatmap\")
290
+ prop = gr.Dropdown(choices=[k for k, _ in NUMERIC_PROPS], value=\"electronegativity\", label=\"Property\")
291
+ heat = gr.Plot()
292
+ prop.change(heatmap, inputs=[prop], outputs=[heat])
293
+ # Initialize
294
+ heat.update(heatmap(\"electronegativity\"))
295
+
296
+ gr.Markdown(\"---\\nBuilt with **Gradio** + **periodictable**. Data completeness varies by element; some values may be missing.\")
297
+
298
+ if __name__ == \"__main__\":
299
+ demo.launch()
requirements (1).txt ADDED
@@ -0,0 +1,4 @@
 
 
 
 
 
1
+ gradio>=4.29.0
2
+ plotly>=5.22.0
3
+ pandas>=2.2.2
4
+ periodictable>=1.6.1