# element_explorer_v2.
py
import wikipediaapi
import [Link] as plt
import numpy as np
import tkinter as tk
from tkinter import ttk, messagebox, filedialog
import plotly.graph_objects as go
from mendeleev import element
from pubchempy import get_compounds
from datetime import datetime
import os
import json
import csv
import sys
import webbrowser
import platform
# ---------------- Config / Storage ---------------- #
APP_NAME = "Element Explorer v2"
CACHE_FILE = "element_cache.json"
FAV_FILE = "[Link]"
MAX_HISTORY = 25
# Make sure downloads path cross-platform
DOWNLOADS = [Link]([Link]("~"), "Downloads")
# ---------------- Hardcoded periodic table fallback ---------------- #
PERIODIC_TABLE = {
"Hydrogen": 1, "Helium": 2, "Lithium": 3, "Beryllium": 4, "Boron": 5,
"Carbon": 6, "Nitrogen": 7, "Oxygen": 8, "Fluorine": 9, "Neon": 10,
"Sodium": 11, "Magnesium": 12, "Aluminum": 13, "Silicon": 14, "Phosphorus":
15,
"Sulfur": 16, "Chlorine": 17, "Argon": 18, "Potassium": 19, "Calcium": 20,
# add more later if you want
}
# ---------------- Utilities: cache & favorites ---------------- #
def load_json_safe(path, default):
try:
with open(path, "r", encoding="utf-8") as f:
return [Link](f)
except Exception:
return default
def save_json_safe(path, obj):
try:
with open(path, "w", encoding="utf-8") as f:
[Link](obj, f, ensure_ascii=False, indent=2)
except Exception as e:
print("Failed to save JSON:", e)
cache = load_json_safe(CACHE_FILE, {})
favorites = load_json_safe(FAV_FILE, [])
def cache_put(name, data):
cache[[Link]()] = {"time": [Link]().isoformat(), "data": data}
save_json_safe(CACHE_FILE, cache)
def cache_get(name):
return [Link]([Link](), {}).get("data")
def add_favorite(element_name):
if element_name not in favorites:
[Link](element_name)
save_json_safe(FAV_FILE, favorites)
return True
return False
def remove_favorite(element_name):
if element_name in favorites:
[Link](element_name)
save_json_safe(FAV_FILE, favorites)
return True
return False
# ---------------- Fetch functions ---------------- #
def fetch_wikipedia_summary(element_name):
wiki = [Link](language="en", user_agent=f"{APP_NAME}/1.0
(student project)")
page = [Link](element_name)
if [Link]():
summary = [Link][:2000]
if "." in summary[1990:]:
return summary[:summary[:2000].rfind(".")+1]
return summary
return "No summary available."
def fetch_mendeleev_data(element_name):
try:
elem = element(element_name.capitalize())
data = {
"Atomic Number": elem.atomic_number,
"Symbol": [Link],
"Atomic Mass": f"{elem.atomic_weight:.2f} u" if elem.atomic_weight else
"N/A",
"Density": f"{[Link]} g/cm³" if [Link] else "N/A",
"Electronegativity": f"{elem.en_pauling}" if elem.en_pauling else "N/A",
"Oxidation States": f"{', '.join(map(str, [Link]))}" if
[Link] else "N/A",
"Melting Point": f"{elem.melting_point} K" if elem.melting_point else
"N/A",
"Boiling Point": f"{elem.boiling_point} K" if elem.boiling_point else "N/A",
"Electronic Configuration": [Link] if [Link] else "N/A",
}
return data
except Exception:
return {}
def fetch_pubchem_data(element_name):
try:
compound = get_compounds(element_name, 'name')[0]
data = {
"Molecular Weight": f"{compound.molecular_weight} u",
"InChI Key": [Link],
"SMILES": compound.isomeric_smiles,
}
return data
except Exception:
return {}
def fetch_element_data(element_name):
# check cache
cached = cache_get(element_name)
if cached:
return cached
# fetch from sources
wikipedia_summary = fetch_wikipedia_summary(element_name)
mendeleev_data = fetch_mendeleev_data(element_name)
pubchem_data = fetch_pubchem_data(element_name)
atomic_number = PERIODIC_TABLE.get(element_name.capitalize(), None)
if atomic_number and "Atomic Number" not in mendeleev_data:
mendeleev_data["Atomic Number"] = str(atomic_number)
result = {"summary": wikipedia_summary, "details": {**mendeleev_data,
**pubchem_data}}
cache_put(element_name, result)
return result
# ---------------- Visualizations (unchanged) ---------------- #
def plot_bohr_model(atomic_number):
num_electrons = atomic_number
orbitals = []
max_electrons = [2, 8, 18, 32, 50, 72, 98]
for max_e in max_electrons:
if num_electrons <= 0:
break
if num_electrons >= max_e:
[Link](max_e)
num_electrons -= max_e
else:
[Link](num_electrons)
break
fig, ax = [Link](figsize=(6, 6))
ax.set_xlim(-3, 3); ax.set_ylim(-3, 3); ax.set_aspect('equal'); [Link]('off')
for i, electrons in enumerate(orbitals):
circle = [Link]((0,0), i+1, fill=False, color="#2A6F97", linewidth=1.5)
ax.add_artist(circle)
for j in range(electrons):
angle = 2*[Link]*j/electrons
x,y = (i+1)*[Link](angle),(i+1)*[Link](angle)
[Link](x,y,'o', color="#FF6B6B", markersize=6)
[Link](0,0,'o', color="#FFD166", markersize=15)
[Link](f"Bohr Model — Z={atomic_number}")
[Link]()
def plot_orbital(orbital_type):
fig = [Link]()
u = [Link](0, 2*[Link], 100)
v = [Link](-1,1,100)
if orbital_type=='s':
x = [Link]([Link](u), [Link](1-v**2))
y = [Link]([Link](u), [Link](1-v**2))
z = [Link]([Link]([Link](u)), v)
fig.add_trace([Link](z=z, x=x, y=y, colorscale='Blues'))
elif orbital_type=='p':
x = [Link]([Link](u), v)
y = [Link]([Link](u), v)
z = [Link]([Link]([Link](u)), v)
fig.add_trace([Link](z=z, x=x, y=y, colorscale='Reds'))
elif orbital_type=='d':
x = [Link]([Link](u)**2 * [Link](2*u), v)
y = [Link]([Link](u)**2 * [Link](2*u), v)
z = [Link]([Link](u), v)
fig.add_trace([Link](z=z, x=x, y=y, colorscale='Greens'))
elif orbital_type=='f':
x = [Link]([Link](u)**3 * [Link](3*u), v)
y = [Link]([Link](u)**3 * [Link](3*u), v)
z = [Link]([Link](u), v)
fig.add_trace([Link](z=z, x=x, y=y, colorscale='Purples'))
fig.update_layout(title=f"{orbital_type.upper()} Orbital Visualization",
autosize=True)
[Link]()
# ---------------- Export helpers ---------------- #
def export_txt(report_text, element_name):
timestamp = [Link]().strftime("%Y%m%d_%H%M%S")
filename = f"{element_name}_Report_{timestamp}.txt"
filepath = [Link](DOWNLOADS, filename)
with open(filepath, "w", encoding="utf-8") as f:
[Link](report_text)
return filepath
def export_csv(details_dict, element_name):
timestamp = [Link]().strftime("%Y%m%d_%H%M%S")
filename = f"{element_name}_Report_{timestamp}.csv"
filepath = [Link](DOWNLOADS, filename)
with open(filepath, "w", newline='', encoding="utf-8") as csvfile:
w = [Link](csvfile)
[Link](["Property", "Value"])
for k,v in details_dict.items():
[Link]([k, v])
return filepath
# ---------------- UI — build main window ---------------- #
root = [Link]()
[Link](APP_NAME)
[Link]("1000x700")
[Link](bg="#F4FAFF")
# Fonts and colors (cross-platform safe)
TITLE_FONT = ("Helvetica", 15, "bold")
LABEL_FONT = ("Helvetica", 11)
TEXT_FONT = ("Helvetica", 11)
BUTTON_FONT = ("Helvetica", 10, "bold")
ACCENT = "#4ECDC4"
ACCENT2 = "#FF6B6B"
# Layout frames
top_frame = [Link](root, padding=10)
top_frame.pack(fill=tk.X)
main_frame = [Link](root, padding=10)
main_frame.pack(fill=[Link], expand=True)
status_frame = [Link](root)
status_frame.pack(fill=tk.X, side=[Link])
# Title
title_lbl = [Link](top_frame, text=APP_NAME, font=("Helvetica", 18, "bold"),
bg="#F4FAFF", fg="#23395B")
title_lbl.pack(side=[Link])
# Search area
search_lbl = [Link](top_frame, text="Enter element name:",
font=LABEL_FONT, bg="#F4FAFF")
search_lbl.pack(side=[Link], padx=(20,6))
search_box = [Link](top_frame, font=TEXT_FONT, width=30)
search_box.pack(side=[Link])
search_box.bind("<Return>", lambda e: on_search())
# helper: status label
status_var = [Link](value="Ready")
status_lbl = [Link](status_frame, textvariable=status_var, anchor="w",
bg="#E9F6FF")
status_lbl.pack(fill=tk.X)
# History & Favorites column
left_col = [Link](main_frame)
left_col.pack(side=[Link], fill=tk.Y)
history_lbl = [Link](left_col, text="Search History", font=LABEL_FONT,
bg="#F4FAFF")
history_lbl.pack(anchor=tk.W)
history_list = [Link](left_col, height=10, width=25)
history_list.pack(pady=4)
history_list.bind("<<ListboxSelect>>", lambda e: on_history_select())
fav_lbl = [Link](left_col, text="Favorites", font=LABEL_FONT, bg="#F4FAFF")
fav_lbl.pack(anchor=tk.W, pady=(12,0))
fav_list = [Link](left_col, height=6, width=25)
fav_list.pack(pady=4)
fav_list.bind("<<ListboxSelect>>", lambda e: on_fav_select())
# populate favorites list initially
def refresh_fav_list():
fav_list.delete(0, [Link])
for f in favorites:
fav_list.insert([Link], f)
refresh_fav_list()
# center column: result and visuals
center_col = [Link](main_frame)
center_col.pack(side=[Link], fill=[Link], expand=True, padx=10)
result_text = [Link](center_col, wrap=[Link], font=TEXT_FONT,
bg="#FFFFFF", height=20)
result_text.pack(fill=[Link], expand=True)
# small toolbar under result
toolbar = [Link](center_col)
[Link](fill=tk.X, pady=6)
def make_btn(text, cmd, bg=ACCENT):
b = [Link](toolbar, text=text, command=cmd, font=BUTTON_FONT,
bg=bg, fg="white", bd=0, padx=8, pady=6)
[Link](side=[Link], padx=4)
# hover
[Link]("<Enter>", lambda e, b=b: [Link](bg=ACCENT2 if bg==ACCENT
else ACCENT))
[Link]("<Leave>", lambda e, b=b: [Link](bg=bg))
return b
# visualization buttons
bohr_button = make_btn("Show Bohr Model", lambda: on_bohr(), bg=ACCENT)
s_button = make_btn("S Orbital", lambda: plot_orbital('s'))
p_button = make_btn("P Orbital", lambda: plot_orbital('p'))
d_button = make_btn("D Orbital", lambda: plot_orbital('d'))
f_button = make_btn("F Orbital", lambda: plot_orbital('f'))
# right column: periodic grid + compare area
right_col = [Link](main_frame, width=320)
right_col.pack(side=[Link], fill=tk.Y, padx=(8,0))
# periodic quick grid (first 20) — visually attractive
grid_frame = [Link](right_col, text="Periodic (Quick)", padding=8)
grid_frame.pack(fill=tk.X, pady=(0,10))
grid_elements =
["H","He","Li","Be","B","C","N","O","F","Ne","Na","Mg","Al","Si","P","S","Cl","Ar","K
","Ca"]
names_map = {k: v for v, k in zip(list(PERIODIC_TABLE.keys()),
list(PERIODIC_TABLE.keys()))} # we won't use mapping heavy here
def on_periodic_click(sym):
# try to get full name by searching cache or simple mapping
# simple approach: accept atomic symbol as name for fetch; mendeleev
accepts names mostly - we'll try common names for clicks
symbol_to_name = {
"H":"Hydrogen","He":"Helium","Li":"Lithium","Be":"Beryllium","B":"Boron","C":"Ca
rbon",
"N":"Nitrogen","O":"Oxygen","F":"Fluorine","Ne":"Neon","Na":"Sodium","Mg":"Ma
gnesium",
"Al":"Aluminum","Si":"Silicon","P":"Phosphorus","S":"Sulfur","Cl":"Chlorine","Ar":"
Argon",
"K":"Potassium","Ca":"Calcium"
}
name = symbol_to_name.get(sym, sym)
load_element(name)
# create grid of small buttons
r = 0; c = 0
for i, sym in enumerate(grid_elements):
b = [Link](grid_frame, text=sym, width=5, command=lambda s=sym:
on_periodic_click(s),
bg="#FFFFFF", fg="#23395B", bd=1)
[Link](row=r, column=c, padx=3, pady=3)
c += 1
if c >= 5:
c = 0; r += 1
# compare frame
comp_frame = [Link](right_col, text="Compare Elements", padding=8)
comp_frame.pack(fill=tk.X, pady=(4,10))
comp_a = [Link](comp_frame, width=12)
comp_b = [Link](comp_frame, width=12)
comp_a.grid(row=0, column=0, padx=4, pady=4)
comp_b.grid(row=0, column=1, padx=4, pady=4)
def on_compare():
a = comp_a.get().strip(); b = comp_b.get().strip()
if not a or not b:
[Link]("Compare", "Enter two elements to compare")
return
da = fetch_element_data(a); db = fetch_element_data(b)
# build simple compare popup
win = [Link](root); [Link](f"Compare: {a} vs {b}");
[Link]("700x420")
ta = [Link](win, wrap=[Link], font=TEXT_FONT)
[Link](fill=[Link], expand=True)
[Link]([Link], f"{a} — Summary:\n{da['summary']}\n\n")
[Link]([Link], "Details:\n")
for k,v in da['details'].items():
[Link]([Link], f"{k}: {v}\n")
[Link]([Link], "\n\n---\n\n")
[Link]([Link], f"{b} — Summary:\n{db['summary']}\n\n")
[Link]([Link], "Details:\n")
for k,v in db['details'].items():
[Link]([Link], f"{k}: {v}\n")
[Link](state="disabled")
compare_btn = [Link](comp_frame, text="Compare",
command=on_compare, bg=ACCENT, fg="white", font=BUTTON_FONT)
compare_btn.grid(row=1, column=0, columnspan=2, pady=6)
# favorites controls
fav_frame = [Link](right_col, text="Favorites", padding=8)
fav_frame.pack(fill=tk.X, pady=(4,10))
fav_add_btn = [Link](fav_frame, text="Add Current", command=lambda:
on_add_fav(), bg="#FFB400", fg="white")
fav_add_btn.pack(fill=tk.X, pady=4)
fav_remove_btn = [Link](fav_frame, text="Remove Selected",
command=lambda: on_remove_fav(), bg="#FF6B6B", fg="white")
fav_remove_btn.pack(fill=tk.X)
# ---------------- Core behaviors ---------------- #
history = []
def push_history(name):
if not name: return
if name in history:
[Link](name)
[Link](0, name)
if len(history) > MAX_HISTORY:
[Link]()
history_list.delete(0, [Link])
for h in history:
history_list.insert([Link], h)
def on_history_select():
sel = history_list.curselection()
if sel:
name = history_list.get(sel[0])
load_element(name)
def on_fav_select():
sel = fav_list.curselection()
if sel:
name = fav_list.get(sel[0])
load_element(name)
def on_add_fav():
cur = search_box.get().strip()
if not cur:
[Link]("Favorites", "Search/load an element first.")
return
if add_favorite(cur):
refresh_fav_list()
[Link]("Favorites", f"Added {cur} to favorites.")
else:
[Link]("Favorites", f"{cur} already in favorites.")
def on_remove_fav():
sel = fav_list.curselection()
if not sel:
[Link]("Favorites", "Select a favorite to remove.")
return
name = fav_list.get(sel[0])
if remove_favorite(name):
refresh_fav_list()
[Link]("Favorites", f"Removed {name} from favorites.")
def load_element(name):
# main loader - fetch and populate result_text
if not name:
return
status_var.set(f"Loading {name} ...")
root.update_idletasks()
data = fetch_element_data(name)
if not data:
[Link]("Error", f"No data found for {name}")
status_var.set("Ready")
return
# display
result_text.delete(1.0, [Link])
result_text.insert([Link], f"General Information:\n{data['summary']}\n\n")
result_text.insert([Link], "Detailed Data:\n")
for k,v in data['details'].items():
result_text.insert([Link], f"{k}: {v}\n")
result_text.see([Link])
search_box.delete(0, [Link])
search_box.insert(0, name)
push_history(name)
status_var.set("Loaded.")
# update bohr button command if atomic number available
atomic = data['details'].get("Atomic Number")
try:
atomic_int = int(atomic)
bohr_button.configure(command=lambda a=atomic_int:
plot_bohr_model(a))
except Exception:
bohr_button.configure(command=lambda: [Link]("Bohr",
"Atomic number not available."))
def on_search():
name = search_box.get().strip()
if not name:
[Link]("Search", "Please enter an element name.")
return
load_element(name)
# keyboard shortcut: Enter
def bind_shortcuts():
[Link]("<Control-s>", lambda e: on_export_txt())
[Link]("<Control-c>", lambda e: on_copy_results())
# copy results to clipboard
def on_copy_results():
txt = result_text.get(1.0, [Link])
try:
import pyperclip
[Link](txt)
[Link]("Copy", "Results copied to clipboard.")
except Exception:
# fallback
root.clipboard_clear()
root.clipboard_append(txt)
[Link]("Copy", "Results copied to clipboard (Tk fallback).")
# export to TXT
def on_export_txt():
cur = search_box.get().strip() or "element"
txt = result_text.get(1.0, [Link])
try:
path = export_txt(txt, cur)
[Link]("Export TXT", f"Saved to Downloads:\n{path}")
except Exception as e:
[Link]("Export TXT", f"Failed: {e}")
# export CSV (details only)
def on_export_csv():
cur = search_box.get().strip() or "element"
# build details dict
data = fetch_element_data(cur)
if not data:
[Link]("Export CSV", "No data to export.")
return
try:
path = export_csv(data['details'], cur)
[Link]("Export CSV", f"Saved to Downloads:\n{path}")
except Exception as e:
[Link]("Export CSV", f"Failed: {e}")
# hook buttons
make_btn("Export TXT (Downloads)", on_export_txt, bg="#2E86AB")
make_btn("Export CSV (Downloads)", on_export_csv, bg="#2A9D8F")
make_btn("Copy Results", on_copy_results, bg="#7B61FF")
# download / save button on toolbar replaced with text export to avoid pdf issues
download_btn = make_btn("Save Report (TXT)", on_export_txt, bg=ACCENT2)
# a tiny help / about
def on_about():
txt = f"{APP_NAME}\nA polished Element Explorer for CBSE project by you.\n\
nFeatures:\n- Wikipedia / Mendeleev / PubChem data\n- Bohr model + orbitals\n-
Quick periodic grid, compare, favorites, history\n- Export TXT / CSV (Downloads)\
n\nPlatform: {[Link]()} {[Link]()}\n"
[Link]("About", txt)
make_btn("About", on_about, bg="#495867")
# load initial state
bind_shortcuts()
status_var.set("Ready")
# menu bar (optional nice touch)
menubar = [Link](root)
filemenu = [Link](menubar, tearoff=0)
filemenu.add_command(label="Export TXT (Ctrl-S)", command=on_export_txt)
filemenu.add_command(label="Export CSV", command=on_export_csv)
filemenu.add_separator()
filemenu.add_command(label="Exit", command=[Link])
menubar.add_cascade(label="File", menu=filemenu)
helpmenu = [Link](menubar, tearoff=0)
helpmenu.add_command(label="About", command=on_about)
menubar.add_cascade(label="Help", menu=helpmenu)
[Link](menu=menubar)
# run the loop
[Link]()