############################################################
# Periodic Table
############################################################
from .visualID_Eng import fg, bg, hl
from .core import centerTitle, centertxt
import mendeleev
[docs]
class TableauPeriodique:
"""
Classe permettant de manipuler et d'afficher les données du tableau périodique.
Cette classe utilise la bibliothèque 'mendeleev' pour récupérer les données chimiques
et 'bokeh' pour la visualisation interactive. Elle francise les noms et corrige
certaines classifications de familles chimiques.
Initialise l'objet TableauPeriodique en chargeant les données de la bibliothèque mendeleev.
"""
nomsFr=['Hydrogène','Hélium','Lithium','Béryllium','Bore','Carbone','Azote','Oxygène',
'Fluor','Néon','Sodium','Magnésium','Aluminium','Silicium','Phosphore','Soufre',
'Chlore','Argon','Potassium','Calcium','Scandium','Titane','Vanadium','Chrome',
'Manganèse','Fer','Cobalt','Nickel','Cuivre','Zinc','Gallium','Germanium',
'Arsenic','Sélénium','Brome','Krypton','Rubidium','Strontium','Yttrium',
'Zirconium','Niobium','Molybdène','Technétium','Ruthénium','Rhodium',
'Palladium','Argent','Cadmium','Indium',
'Étain','Antimoine','Tellure','Iode','Xénon','Césium','Baryum','Lanthane','Cérium',
'Praséodyme','Néodyme','Prométhium','Samarium','Europium','Gadolinium','Terbium',
'Dysprosium','Holmium','Erbium','Thulium','Ytterbium','Lutetium','Hafnium','Tantale',
'Tungstène','Rhénium','Osmium','Iridium','Platine','Or','Mercure','Thallium','Plomb',
'Bismuth','Polonium','Astate','Radon','Francium','Radium','Actinium','Thorium','Protactinium',
'Uranium','Neptunium','Plutonium','Americium','Curium','Berkelium','Californium','Einsteinium',
'Fermium','Mendelevium','Nobelium','Lawrencium','Rutherfordium','Dubnium','Seaborgium','Bohrium',
'Hassium','Meitnerium','Darmstadtium','Roentgenium','Copernicium','Nihonium','Flerovium',
'Moscovium','Livermorium','Tennesse','Oganesson',
]
trad = {'Nonmetals':'Non métal',
'Noble gases':'Gaz noble',
'Alkali metals':'Métal alcalin',
'Alkaline earth metals':'Métal alcalino-terreux',
'Metalloids':'Métalloïde',
'Halogens':'Halogène',
'Poor metals':'Métal pauvre',
'Transition metals':'Métal de transition',
'Lanthanides':'Lanthanide',
'Actinides':'Actinide',
'Metals':'Métal',
}
def __init__(self):
from mendeleev.vis import create_vis_dataframe
self.elements = create_vis_dataframe()
self.patch_elements()
[docs]
def patch_elements(self):
'''
Ce patch, appliqué à self.elements, créé par l'appel à create_vis_dataframe(), va servir à :
- ajouter des informations en français : les noms des éléments et des séries (familles) auxquelles ils appartiennent
- retirer les éléments du groupe 12 de la famille des métaux de transition, qui est le choix CONTESTABLE par défaut de la bibliothèque mendeleev
input :
elements est un dataframe pandas préalablement créé par la fonction create_vis_dataframe() de mendeleev.vis
output :
elements avec deux nouvelles colonnes name_seriesFr et nom, qui contient dorénavant les noms des éléments en français
+ correction des données name_series et series_id pour les éléments Zn, Cd, Hg, Cn
+ de nouvelles colonnes qui contiennent l'énergie de première ionisation et les isotopes naturels
'''
def series_eng2fr(s):
'''Correspondance entre nom des séries (familles) en anglais et en français'''
s = TableauPeriodique.trad[s]
return s
def name_eng2fr():
"""Remplace les noms anglais des éléments par leurs équivalents français."""
self.elements["nom"] = TableauPeriodique.nomsFr
return
def ajouter_donnees():
"""Récupère et fusionne des données supplémentaires comme l'énergie de première ionisation."""
import numpy as np
from mendeleev.fetch import fetch_table, fetch_ionization_energies
import pandas as pd
# dfElts = fetch_table("elements")
# display(dfElts)
dfEi1 = fetch_ionization_energies(degree = 1)
# display(dfEi1)
b = pd.DataFrame({'atomic_number':[x for x in range(1, 119)]})
dfEi1tot = pd.merge(left=dfEi1, right=b, on='atomic_number', how='outer').sort_values(by='atomic_number')
self.elements["Ei1"] = dfEi1tot["IE1"]
# les éléments du groupe 12 ne sont pas des métaux de transition
self.elements.loc[29,"name_series"] = 'Metals'
self.elements.loc[47,"name_series"] = 'Metals'
self.elements.loc[79,"name_series"] = 'Metals'
self.elements.loc[111,"name_series"] = 'Metals'
self.elements.loc[29,"series_id"] = 11
self.elements.loc[47,"series_id"] = 11
self.elements.loc[79,"series_id"] = 11
self.elements.loc[111,"series_id"] = 11
self.elements.loc[29,"color"] = "#bbd3a5"
self.elements.loc[47,"color"] = "#bbd3a5"
self.elements.loc[79,"color"] = "#bbd3a5"
self.elements.loc[111,"color"] = "#bbd3a5"
# english > français. Ajout d'une nouvelle colonne
self.elements["name_seriesFr"] = self.elements["name_series"].apply(series_eng2fr)
# english > français. Noms des éléments en français changés dans la colonne name
name_eng2fr()
ajouter_donnees()
return
[docs]
def prop(self,elt_id):
"""
Affiche les propriétés détaillées d'un élément chimique.
Args:
elt_id (str ou int): Symbole de l'élément (ex: 'O') ou numéro atomique (ex: 8).
"""
from mendeleev import element
elt = element(elt_id)
print(f"Nom de l'élement = {TableauPeriodique.nomsFr[elt.atomic_number-1]} ({elt.symbol}, Z = {elt.atomic_number})")
print(f"Nom en anglais = {elt.name}")
print(f"Origine du nom = {elt.name_origin}")
print()
print(f"CEF = {elt.ec} = {elt.econf}")
print(f"Nombre d'électrons célibataires = {elt.ec.unpaired_electrons()}")
print(f"Groupe {elt.group_id}, Période {elt.period}, bloc {elt.block}")
print(f"Famille = {self.elements.loc[elt.atomic_number-1,'name_seriesFr']}")
print()
print(f"Masse molaire = {elt.atomic_weight} g/mol")
isotopes = ""
X = elt.symbol
for i in elt.isotopes:
if i.abundance is not None:
isotopes = isotopes + str(i.mass_number)+ "^" + X + f"({i.abundance}%) / "
print("Isotopes naturels = ",isotopes[:-2])
print()
if elt.electronegativity(scale='pauling') is None:
print(f"Électronégativité de Pauling = Non définie")
else:
print(f"Électronégativité de Pauling = {elt.electronegativity(scale='pauling')}")
print(f"Énergie de 1ère ionisation = {elt.ionenergies[1]:.2f} eV")
if elt.electron_affinity is None:
print(f"Affinité électronique = Non définie")
else:
print(f"Afinité électronique = {elt.electron_affinity:.2f} eV")
print(f"Rayon atomique = {elt.atomic_radius:.1f} pm")
print()
print("▶ Description : ",elt.description)
print("▶ Sources : ",elt.sources)
print("▶ Utilisation : ",elt.uses)
print("---------------------------------------------------------------------------------------")
print()
[docs]
def afficher(self):
"""
Génère et affiche le tableau périodique interactif dans un notebook Jupyter.
Le tableau permet de visualiser les propriétés au survol de la souris.
"""
from bokeh.plotting import show, output_notebook
from mendeleev.vis import periodic_table_bokeh
# Toute cette partie du code est une copie du module bokeh de mendeleev.vis
# La fonction periodic_table_bokeh étant faiblement configurable avec des args/kwargs,
# elle est adaptée ici pour un affichage personnalisé
from collections import OrderedDict
import pandas as pd
from pandas.api.types import is_float_dtype
from bokeh.plotting import figure
from bokeh.models import HoverTool, ColumnDataSource, FixedTicker
from mendeleev.vis.utils import colormap_column
def periodic_table_bokeh(
elements: pd.DataFrame,
attribute: str = "atomic_weight",
cmap: str = "RdBu_r",
colorby: str = "color",
decimals: int = 3,
height: int = 800,
missing: str = "#ffffff",
title: str = "Periodic Table",
wide_layout: bool = False,
width: int = 1200,
):
"""
Use Bokeh backend to plot the periodic table. Adaptation by Romuald Poteau (romuald.poteau@univ-tlse3.fr) of the orignal periodic_table_bokeh() function of the mendeleev library
Args:
elements : Pandas DataFrame with the elements data. Needs to have `x` and `y`
columns with coordianates for each tile.
attribute : Name of the attribute to be displayed
cmap : Colormap to use, see matplotlib colormaps
colorby : Name of the column containig the colors
decimals : Number of decimals to be displayed in the bottom row of each cell
height : Height of the figure in pixels
missing : Hex code of the color to be used for the missing values
title : Title to appear above the periodic table
wide_layout: wide layout variant of the periodic table
width : Width of the figure in pixels
"""
if any(col not in elements.columns for col in ["x", "y"]):
raise ValueError(
"Coordinate columns named 'x' and 'y' are required "
"in 'elements' DataFrame. Consider using "
"'mendeleev.vis.utils.create_vis_dataframe' and try again."
)
# additional columns for positioning of the text
elements.loc[:, "y_anumber"] = elements["y"] - 0.3
elements.loc[:, "y_name"] = elements["y"] + 0.2
if attribute:
elements.loc[elements[attribute].notnull(), "y_prop"] = (
elements.loc[elements[attribute].notnull(), "y"] + 0.35
)
else:
elements.loc[:, "y_prop"] = elements["y"] + 0.35
ac = "display_attribute"
if is_float_dtype(elements[attribute]):
elements[ac] = elements[attribute].round(decimals=decimals)
else:
elements[ac] = elements[attribute]
if colorby == "attribute":
colored = colormap_column(elements, attribute, cmap=cmap, missing=missing)
elements.loc[:, "attribute_color"] = colored
colorby = "attribute_color"
# bokeh configuration
source = ColumnDataSource(data=elements)
TOOLS = "hover,save,reset"
fig = figure(
title=title,
tools=TOOLS,
x_axis_location="above",
x_range=(elements.x.min() - 0.5, elements.x.max() + 0.5),
y_range=(elements.y.max() + 0.5, elements.y.min() - 0.5),
width=width,
height=height,
toolbar_location="above",
toolbar_sticky=False,
)
fig.rect("x", "y", 0.9, 0.9, source=source, color=colorby, fill_alpha=0.6)
# adjust the ticks and axis bounds
fig.yaxis.bounds = (1, 7)
fig.axis[1].ticker.num_minor_ticks = 0
if wide_layout:
# Turn off tick labels
fig.axis[0].major_label_text_font_size = "0pt"
# Turn off tick marks
fig.axis[0].major_tick_line_color = None # turn off major ticks
fig.axis[0].ticker.num_minor_ticks = 0 # turn off minor ticks
else:
fig.axis[0].ticker = FixedTicker(ticks=list(range(1, 19)))
text_props = {
"source": source,
"angle": 0,
"color": "black",
"text_align": "center",
"text_baseline": "middle",
}
fig.text(
x="x",
y="y",
text="symbol",
text_font_style="bold",
text_font_size="15pt",
**text_props,
)
fig.text(
x="x", y="y_anumber", text="atomic_number", text_font_size="9pt", **text_props
)
fig.text(x="x", y="y_name", text="name", text_font_size="6pt", **text_props)
fig.text(x="x", y="y_prop", text=ac, text_font_size="7pt", **text_props)
fig.grid.grid_line_color = None
hover = fig.select(dict(type=HoverTool))
hover.tooltips = OrderedDict(
[
("nom", "@nom"),
("name", "@name"),
("famille", "@name_seriesFr"),
("numéro atomique", "@atomic_number"),
("masse molaire", "@atomic_weight"),
("rayon atomique", "@atomic_radius"),
("énergie de première ionisation", "@Ei1"),
("affinité électronique", "@electron_affinity"),
("EN Pauling", "@en_pauling"),
("CEF", "@electronic_configuration"),
]
)
return fig
output_notebook()
fig = periodic_table_bokeh(self.elements, colorby="color")
show(fig)