From 77392263a35dc3fe7b9bb6374397e7a2ed0cfe4c Mon Sep 17 00:00:00 2001 From: "Haoyu (Daniel)" Date: Wed, 8 May 2024 12:25:36 +0800 Subject: [PATCH 01/17] make background transparent --- pymatviz/structure_viz.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pymatviz/structure_viz.py b/pymatviz/structure_viz.py index fbc47fdb..5b78f358 100644 --- a/pymatviz/structure_viz.py +++ b/pymatviz/structure_viz.py @@ -128,7 +128,7 @@ def plot_structure_2d( standardize_struct: bool | None = None, axis: bool | str = "off", ) -> plt.Axes: - """Plot pymatgen structures in 2d with matplotlib. + """Plot pymatgen structures in 2D with matplotlib. Inspired by ASE's ase.visualize.plot.plot_atoms() https://wiki.fysik.dtu.dk/ase/ase/visualize/visualize.html#matplotlib @@ -372,7 +372,7 @@ class used to draw chemical bonds. Allowed are edgecolor, facecolor, color, (0.5 * radius) * direction if occupancy < 1 else (0, 0) ) - bbox = dict(facecolor="none", edgecolor="none", pad=1) + bbox = dict(facecolor="none", edgecolor="none", pad=1, alpha=0) bbox.update(site_labels_bbox or {}) txt_kwds = dict( ha="center", va="center", bbox=bbox, **(label_kwargs or {}) From ca80e320eb9781f0bf411e8ca120fb52f4c427b3 Mon Sep 17 00:00:00 2001 From: "Haoyu (Daniel)" Date: Wed, 8 May 2024 12:27:46 +0800 Subject: [PATCH 02/17] update MP API entrance --- examples/_generate_assets.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/examples/_generate_assets.py b/examples/_generate_assets.py index 3470e9cd..17b906ea 100644 --- a/examples/_generate_assets.py +++ b/examples/_generate_assets.py @@ -11,8 +11,8 @@ from matminer.datasets import load_dataset from monty.io import zopen from monty.json import MontyDecoder +from mp_api.client import MPRester from pymatgen.core.periodic_table import Element -from pymatgen.ext.matproj import MPRester from pymatgen.phonon.bandstructure import PhononBandStructureSymmLine as PhononBands from pymatgen.phonon.dos import PhononDos from tqdm import tqdm From 6b61e9ae8c7d3db1fbabb0fc6ad90b45f3bdd5b3 Mon Sep 17 00:00:00 2001 From: "Haoyu (Daniel)" Date: Wed, 8 May 2024 12:35:31 +0800 Subject: [PATCH 03/17] update structure by mp_id --- assets/struct-2d-mp-12712-Hf9Zr9Pd24-disordered.svg | 2 +- assets/struct-2d-mp-19017-Li4Mn0.8Fe1.6P4C1.6O16-disordered.svg | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/assets/struct-2d-mp-12712-Hf9Zr9Pd24-disordered.svg b/assets/struct-2d-mp-12712-Hf9Zr9Pd24-disordered.svg index a27896ac..aa5a2c76 100644 --- a/assets/struct-2d-mp-12712-Hf9Zr9Pd24-disordered.svg +++ b/assets/struct-2d-mp-12712-Hf9Zr9Pd24-disordered.svg @@ -1 +1 @@ - \ No newline at end of file + \ No newline at end of file diff --git a/assets/struct-2d-mp-19017-Li4Mn0.8Fe1.6P4C1.6O16-disordered.svg b/assets/struct-2d-mp-19017-Li4Mn0.8Fe1.6P4C1.6O16-disordered.svg index 31bae044..6a8766bd 100644 --- a/assets/struct-2d-mp-19017-Li4Mn0.8Fe1.6P4C1.6O16-disordered.svg +++ b/assets/struct-2d-mp-19017-Li4Mn0.8Fe1.6P4C1.6O16-disordered.svg @@ -1 +1 @@ - \ No newline at end of file + \ No newline at end of file From ab96bafe1d43cec5fd95165137582ce0917f8b02 Mon Sep 17 00:00:00 2001 From: "Haoyu (Daniel)" Date: Wed, 8 May 2024 12:37:46 +0800 Subject: [PATCH 04/17] update matbench structure --- assets/matbench-phonons-structures-2d.svg | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/assets/matbench-phonons-structures-2d.svg b/assets/matbench-phonons-structures-2d.svg index c78a8a64..8eb4babd 100644 --- a/assets/matbench-phonons-structures-2d.svg +++ b/assets/matbench-phonons-structures-2d.svg @@ -1 +1 @@ - \ No newline at end of file + \ No newline at end of file From fc04c3b276b363fd2c323b52dc70b1627a39fb6e Mon Sep 17 00:00:00 2001 From: "Haoyu (Daniel)" Date: Wed, 8 May 2024 16:18:15 +0800 Subject: [PATCH 05/17] add mp_api to data-src dep --- pyproject.toml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index f02c95f8..47851e36 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -48,7 +48,7 @@ test = [ "pytest-cov", "weasyprint", ] -data-src = ["matminer"] +data-src = ["matminer", "mp_api",] export-figs = ["kaleido"] gh-pages = ["jupyter", "lazydocs", "nbconvert"] # needed for pandas Stylers, see https://github.com/pandas-dev/pandas/blob/-/pyproject.toml @@ -105,7 +105,7 @@ lint.ignore = [ "PTH", "RUF001", # ambiguous-unicode-character-string "S311", - "SIM105", # Use contextlib.suppress(FileNotFoundError) instead of try-except-pass + "SIM105", # Use contextlib.suppress() instead of try-except-pass "TD", "TRY003", ] From ac82abc71ff81c029d79b1dc92110c2e33a4eabf Mon Sep 17 00:00:00 2001 From: "Haoyu (Daniel)" Date: Wed, 8 May 2024 18:17:31 +0800 Subject: [PATCH 06/17] update matbench fig --- assets/matbench-phonons-structures-2d.svg | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/assets/matbench-phonons-structures-2d.svg b/assets/matbench-phonons-structures-2d.svg index 8eb4babd..a57a3b51 100644 --- a/assets/matbench-phonons-structures-2d.svg +++ b/assets/matbench-phonons-structures-2d.svg @@ -1 +1 @@ - \ No newline at end of file + \ No newline at end of file From aa55947d21d17ecc1751964d1eaa8087b7759db2 Mon Sep 17 00:00:00 2001 From: "Haoyu (Daniel)" Date: Thu, 9 May 2024 16:07:14 +0800 Subject: [PATCH 07/17] tweak comments --- pymatviz/structure_viz.py | 70 +++++++++++++++++++++++---------------- 1 file changed, 42 insertions(+), 28 deletions(-) diff --git a/pymatviz/structure_viz.py b/pymatviz/structure_viz.py index 5b78f358..5134bc89 100644 --- a/pymatviz/structure_viz.py +++ b/pymatviz/structure_viz.py @@ -1,11 +1,15 @@ -"""2D plots of pymatgen structures with matplotlib.""" +"""2D plots of pymatgen structures with matplotlib. + +plot_structure_2d() and its helpers get_rot_matrix() and unit_cell_to_lines() were +inspired by ASE https://wiki.fysik.dtu.dk/ase/ase/visualize/visualize.html#matplotlib. +""" from __future__ import annotations import math import warnings from itertools import product -from typing import TYPE_CHECKING, Any, Literal +from typing import TYPE_CHECKING import matplotlib.pyplot as plt import numpy as np @@ -18,6 +22,7 @@ if TYPE_CHECKING: from collections.abc import Sequence + from typing import Any, Literal from numpy.typing import ArrayLike from pymatgen.core import Structure @@ -30,10 +35,6 @@ class ExperimentalWarning(Warning): warnings.simplefilter("once", ExperimentalWarning) -# plot_structure_2d() and its helpers get_rot_matrix() and unit_cell_to_lines() were -# inspired by ASE https://wiki.fysik.dtu.dk/ase/ase/visualize/visualize.html#matplotlib - - def _angles_to_rotation_matrix( angles: str, rotation: ArrayLike | None = None ) -> ArrayLike: @@ -52,8 +53,10 @@ def _angles_to_rotation_matrix( """ if rotation is None: rotation = np.eye(3) + + # Return initial rotation matrix if no angles if not angles: - return rotation.copy() # return initial rotation matrix if no angles + return rotation.copy() for angle in angles.split(","): radians = math.radians(float(angle[:-1])) @@ -127,6 +130,7 @@ def plot_structure_2d( bond_kwargs: dict[str, Any] | None = None, standardize_struct: bool | None = None, axis: bool | str = "off", + occlude_labels: bool = True, ) -> plt.Axes: """Plot pymatgen structures in 2D with matplotlib. @@ -137,7 +141,7 @@ def plot_structure_2d( For example, these two snippets should give very similar output: - ```py + ```python from pymatgen.ext.matproj import MPRester mp_19017 = MPRester().get_structure_by_material_id("mp-19017") @@ -182,10 +186,10 @@ def plot_structure_2d( colors, either a named color (str) or rgb(a) values like (0.2, 0.3, 0.6). Defaults to JMol colors (https://jmol.sourceforge.net/jscolors). scale (float, optional): Scaling of the plotted atoms and lines. Defaults to 1. - show_unit_cell (bool, optional): Whether to draw unit cell. Defaults to True. - show_bonds (bool | NearNeighbors, optional): Whether to draw bonds. If True, use + show_unit_cell (bool, optional): Whether to plot unit cell. Defaults to True. + show_bonds (bool | NearNeighbors, optional): Whether to plot bonds. If True, use pymatgen.analysis.local_env.CrystalNN to infer the structure's connectivity. - If False, don't draw bonds. If a subclass of + If False, don't plot bonds. If a subclass of pymatgen.analysis.local_env.NearNeighbors, use that to determine connectivity. Options include VoronoiNN, MinimumDistanceNN, OpenBabelNN, CovalentBondNN, dtc. Defaults to True. @@ -202,7 +206,7 @@ def plot_structure_2d( label_kwargs (dict, optional): Keyword arguments for matplotlib.text.Text like {"fontsize": 14}. Defaults to None. bond_kwargs (dict, optional): Keyword arguments for the matplotlib.path.Path - class used to draw chemical bonds. Allowed are edgecolor, facecolor, color, + class used to plot chemical bonds. Allowed are edgecolor, facecolor, color, linewidth, linestyle, antialiased, hatch, fill, capstyle, joinstyle. Defaults to None. standardize_struct (bool, optional): Whether to standardize the structure using @@ -213,6 +217,8 @@ class used to draw chemical bonds. Allowed are edgecolor, facecolor, color, axis (bool | str, optional): Whether/how to show plot axes. Defaults to "off". See https://matplotlib.org/stable/api/_as_gen/matplotlib.pyplot.axis for details. + occlude_labels (bool): Whether to include occlusion effect, + i.e. atoms in the foreground will occlude those in the background. Raises: ValueError: On invalid site_labels. @@ -228,7 +234,7 @@ class used to draw chemical bonds. Allowed are edgecolor, facecolor, color, f" the number of sites in the crystal ({len(struct)=})" ) - # default behavior in case of no user input is to standardize if any fractional + # Default behavior in case of no user input: standardize if any fractional # coordinates are negative has_sites_outside_unit_cell = any(any(site.frac_coords < 0) for site in struct) if standardize_struct is False and has_sites_outside_unit_cell: @@ -243,6 +249,8 @@ class used to draw chemical bonds. Allowed are edgecolor, facecolor, color, from pymatgen.symmetry.analyzer import SpacegroupAnalyzer struct = SpacegroupAnalyzer(struct).get_conventional_standard_structure() + + # Get default colors if colors is None: colors = jmol_colors @@ -257,15 +265,14 @@ class used to draw chemical bonds. Allowed are edgecolor, facecolor, color, else: # atomic_radii is assumed to be a map from element symbols to atomic radii # make sure all present elements are assigned a radius - missing = set(elements_at_sites) - set(atomic_radii) - if missing: + if missing := set(elements_at_sites) - set(atomic_radii): raise ValueError(f"atomic_radii is missing keys: {missing}") radii_at_sites = np.array( [atomic_radii[el] for el in elements_at_sites] # type: ignore[index] ) - n_atoms = len(struct) + # Generate lines for plotting unit cell rotation_matrix = _angles_to_rotation_matrix(rotation) unit_cell = struct.lattice.matrix @@ -280,6 +287,8 @@ class used to draw chemical bonds. Allowed are edgecolor, facecolor, color, unit_cell_lines = None cell_vertices = None + # Zip atoms and unit cell lines together + n_atoms = len(struct) n_lines = len(lines) positions = np.empty((n_atoms + n_lines, 3)) @@ -287,7 +296,7 @@ class used to draw chemical bonds. Allowed are edgecolor, facecolor, color, positions[:n_atoms] = site_coords positions[n_atoms:] = lines - # determine which lines should be hidden behind other objects + # Determine which unit cell line should be hidden behind other objects for idx in range(n_lines): this_layer = unit_cell_lines[z_indices[idx]] occluded_top = ((site_coords - lines[idx] + this_layer) ** 2).sum( @@ -299,6 +308,7 @@ class used to draw chemical bonds. Allowed are edgecolor, facecolor, color, if any(occluded_top & occluded_bottom): z_indices[idx] = -1 + # Apply rotation matrix positions = np.dot(positions, rotation_matrix) rotated_site_coords = positions[:n_atoms] @@ -320,14 +330,14 @@ class used to draw chemical bonds. Allowed are edgecolor, facecolor, color, unit_cell_lines = np.dot(unit_cell_lines, rotation_matrix)[:, :2] * scale special_site_labels = ("symbol", "species") - # sort positions by 3rd dim so we draw from back to front in z-axis (out-of-plane) + # Sort positions by 3rd dim so we plot from back to front in z-axis (out-of-plane) for idx in positions[:, 2].argsort(): xy = positions[idx, :2] start = 0 if idx < n_atoms: - # loop over all species on a site (usually just 1 for ordered sites) + # Loop over all species on a site (usually just 1 for ordered sites) for specie, occupancy in struct[idx].species.items(): - # strip oxidation state from element symbol (e.g. Ta5+ to Ta) + # Strip oxidation state from element symbol (e.g. Ta5+ to Ta) elem_symbol = specie.symbol radius = atomic_radii[elem_symbol] * scale # type: ignore[index] face_color = colors[elem_symbol] @@ -348,7 +358,7 @@ class used to draw chemical bonds. Allowed are edgecolor, facecolor, color, elif site_labels is False: txt = "" elif isinstance(site_labels, dict): - # try element incl. oxidation state as dict key first (e.g. Na+), + # Try element incl. oxidation state as dict key first (e.g. Na+), # then just element as fallback txt = site_labels.get( repr(specie), site_labels.get(elem_symbol, "") @@ -364,7 +374,7 @@ class used to draw chemical bonds. Allowed are edgecolor, facecolor, color, ) if site_labels: - # place element symbol half way along outer wedge edge for + # Place element symbol half way along outer wedge edge for # disordered sites half_way = 2 * np.pi * (start + occupancy / 2) direction = np.array([math.cos(half_way), math.sin(half_way)]) @@ -380,9 +390,11 @@ class used to draw chemical bonds. Allowed are edgecolor, facecolor, color, ax.text(*(xy + text_offset), txt, **txt_kwds) start += occupancy - else: # draw unit cell + + # Plot unit cell + else: cell_idx = idx - n_atoms - # only draw line if not obstructed by an atom + # Only plot lines not obstructed by an atom if z_indices[cell_idx] != -1: hxy = unit_cell_lines[z_indices[cell_idx]] path = PathPatch(Path((xy + hxy, xy - hxy))) @@ -404,16 +416,18 @@ class used to draw chemical bonds. Allowed are edgecolor, facecolor, color, ) # If structure doesn't have any oxidation states yet, guess them from chemical - # composition. Helps CrystalNN and other strategies to estimate better bond - # connectivity. Uses getattr on site.specie since it's often a pymatgen Element + # composition. Use CrystalNN and other strategies to better estimate bond + # connectivity. Use getattr on site.specie since it's often a pymatgen Element # which has no oxi_state if not any( hasattr(getattr(site, "specie", None), "oxi_state") for site in struct ): try: struct.add_oxidation_state_by_guess() - except ValueError: # fails for disordered structures - "Charge balance analysis requires integer values in Composition" + except ValueError as err: # fails for disordered structures + raise ValueError( + "Charge balance analysis requires integer values in Composition." + ) from err structure_graph = neighbor_strategy_cls().get_bonded_structure(struct) From f94226a9f44fae74fcca21647b7389df72ca5037 Mon Sep 17 00:00:00 2001 From: "Haoyu (Daniel)" Date: Fri, 10 May 2024 10:41:30 +0800 Subject: [PATCH 08/17] add zorder --- pymatviz/structure_viz.py | 35 +++++++++++++++++++++++------------ 1 file changed, 23 insertions(+), 12 deletions(-) diff --git a/pymatviz/structure_viz.py b/pymatviz/structure_viz.py index 5134bc89..f6d6a4a1 100644 --- a/pymatviz/structure_viz.py +++ b/pymatviz/structure_viz.py @@ -16,6 +16,7 @@ from matplotlib.patches import PathPatch, Wedge from matplotlib.path import Path from pymatgen.analysis.local_env import CrystalNN, NearNeighbors +from pymatgen.symmetry.analyzer import SpacegroupAnalyzer from pymatviz.utils import covalent_radii, jmol_colors @@ -29,7 +30,7 @@ class ExperimentalWarning(Warning): - """Used for experimental show_bonds feature.""" + """Warning for experimental features.""" warnings.simplefilter("once", ExperimentalWarning) @@ -246,8 +247,6 @@ class used to plot chemical bonds. Allowed are edgecolor, facecolor, color, elif standardize_struct is None: standardize_struct = has_sites_outside_unit_cell if standardize_struct: - from pymatgen.symmetry.analyzer import SpacegroupAnalyzer - struct = SpacegroupAnalyzer(struct).get_conventional_standard_structure() # Get default colors @@ -272,7 +271,7 @@ class used to plot chemical bonds. Allowed are edgecolor, facecolor, color, [atomic_radii[el] for el in elements_at_sites] # type: ignore[index] ) - # Generate lines for plotting unit cell + # Generate lines for unit cell rotation_matrix = _angles_to_rotation_matrix(rotation) unit_cell = struct.lattice.matrix @@ -312,6 +311,7 @@ class used to plot chemical bonds. Allowed are edgecolor, facecolor, color, positions = np.dot(positions, rotation_matrix) rotated_site_coords = positions[:n_atoms] + # Normalize wedge positions min_coords = (rotated_site_coords - radii_at_sites[:, None]).min(0) max_coords = (rotated_site_coords + radii_at_sites[:, None]).max(0) @@ -326,19 +326,23 @@ class used to plot chemical bonds. Allowed are edgecolor, facecolor, color, positions *= scale positions -= offset + # Rotate and scale unit cell lines if n_lines > 0: unit_cell_lines = np.dot(unit_cell_lines, rotation_matrix)[:, :2] * scale special_site_labels = ("symbol", "species") - # Sort positions by 3rd dim so we plot from back to front in z-axis (out-of-plane) + # Sort positions by 3rd dim to plot from back to front along z-axis (out-of-plane) for idx in positions[:, 2].argsort(): xy = positions[idx, :2] start = 0 if idx < n_atoms: # Loop over all species on a site (usually just 1 for ordered sites) - for specie, occupancy in struct[idx].species.items(): + for species, occupancy in struct[idx].species.items(): + # Define the occlusion order + zorder = positions[idx][2] + # Strip oxidation state from element symbol (e.g. Ta5+ to Ta) - elem_symbol = specie.symbol + elem_symbol = species.symbol radius = atomic_radii[elem_symbol] * scale # type: ignore[index] face_color = colors[elem_symbol] wedge = Wedge( @@ -348,23 +352,25 @@ class used to plot chemical bonds. Allowed are edgecolor, facecolor, color, 360 * (start + occupancy), facecolor=face_color, edgecolor="black", + zorder=zorder, ) ax.add_patch(wedge) + # Generate labels if site_labels == "symbol": txt = elem_symbol - elif site_labels in ("species", True): - txt = specie + elif site_labels in {"species", True}: + txt = species elif site_labels is False: txt = "" elif isinstance(site_labels, dict): # Try element incl. oxidation state as dict key first (e.g. Na+), # then just element as fallback txt = site_labels.get( - repr(specie), site_labels.get(elem_symbol, "") + repr(species), site_labels.get(elem_symbol, "") ) if txt in special_site_labels: - txt = specie if txt == "species" else elem_symbol + txt = species if txt == "species" else elem_symbol elif isinstance(site_labels, (list, tuple)): txt = site_labels[idx] # idx runs from 0 to n_atoms else: @@ -373,6 +379,7 @@ class used to plot chemical bonds. Allowed are edgecolor, facecolor, color, f"{', '.join(special_site_labels)}, dict, list)" ) + # Add labels if site_labels: # Place element symbol half way along outer wedge edge for # disordered sites @@ -383,10 +390,14 @@ class used to plot chemical bonds. Allowed are edgecolor, facecolor, color, ) bbox = dict(facecolor="none", edgecolor="none", pad=1, alpha=0) - bbox.update(site_labels_bbox or {}) + bbox |= site_labels_bbox or {} + txt_kwds = dict( ha="center", va="center", bbox=bbox, **(label_kwargs or {}) ) + if occlude_labels: + txt_kwds["zorder"] = zorder + ax.text(*(xy + text_offset), txt, **txt_kwds) start += occupancy From 2cb5952f51efbaa95b4f72bc944e0b77c6e21a20 Mon Sep 17 00:00:00 2001 From: "Haoyu (Daniel)" Date: Fri, 10 May 2024 11:16:36 +0800 Subject: [PATCH 09/17] fix zorder for cell and make occlude default --- pymatviz/structure_viz.py | 32 +++++++++++++++----------------- 1 file changed, 15 insertions(+), 17 deletions(-) diff --git a/pymatviz/structure_viz.py b/pymatviz/structure_viz.py index f6d6a4a1..4d789e23 100644 --- a/pymatviz/structure_viz.py +++ b/pymatviz/structure_viz.py @@ -131,7 +131,6 @@ def plot_structure_2d( bond_kwargs: dict[str, Any] | None = None, standardize_struct: bool | None = None, axis: bool | str = "off", - occlude_labels: bool = True, ) -> plt.Axes: """Plot pymatgen structures in 2D with matplotlib. @@ -218,8 +217,6 @@ class used to plot chemical bonds. Allowed are edgecolor, facecolor, color, axis (bool | str, optional): Whether/how to show plot axes. Defaults to "off". See https://matplotlib.org/stable/api/_as_gen/matplotlib.pyplot.axis for details. - occlude_labels (bool): Whether to include occlusion effect, - i.e. atoms in the foreground will occlude those in the background. Raises: ValueError: On invalid site_labels. @@ -298,12 +295,15 @@ class used to plot chemical bonds. Allowed are edgecolor, facecolor, color, # Determine which unit cell line should be hidden behind other objects for idx in range(n_lines): this_layer = unit_cell_lines[z_indices[idx]] + occluded_top = ((site_coords - lines[idx] + this_layer) ** 2).sum( 1 ) < radii_at_sites**2 + occluded_bottom = ((site_coords - lines[idx] - this_layer) ** 2).sum( 1 ) < radii_at_sites**2 + if any(occluded_top & occluded_bottom): z_indices[idx] = -1 @@ -335,12 +335,10 @@ class used to plot chemical bonds. Allowed are edgecolor, facecolor, color, for idx in positions[:, 2].argsort(): xy = positions[idx, :2] start = 0 + if idx < n_atoms: # Loop over all species on a site (usually just 1 for ordered sites) for species, occupancy in struct[idx].species.items(): - # Define the occlusion order - zorder = positions[idx][2] - # Strip oxidation state from element symbol (e.g. Ta5+ to Ta) elem_symbol = species.symbol radius = atomic_radii[elem_symbol] * scale # type: ignore[index] @@ -352,14 +350,14 @@ class used to plot chemical bonds. Allowed are edgecolor, facecolor, color, 360 * (start + occupancy), facecolor=face_color, edgecolor="black", - zorder=zorder, + zorder=z_indices[idx], ) ax.add_patch(wedge) # Generate labels if site_labels == "symbol": txt = elem_symbol - elif site_labels in {"species", True}: + elif site_labels in ("species", True): txt = species elif site_labels is False: txt = "" @@ -393,11 +391,12 @@ class used to plot chemical bonds. Allowed are edgecolor, facecolor, color, bbox |= site_labels_bbox or {} txt_kwds = dict( - ha="center", va="center", bbox=bbox, **(label_kwargs or {}) + ha="center", + va="center", + zorder=z_indices[idx], + bbox=bbox, + **(label_kwargs or {}), ) - if occlude_labels: - txt_kwds["zorder"] = zorder - ax.text(*(xy + text_offset), txt, **txt_kwds) start += occupancy @@ -408,7 +407,7 @@ class used to plot chemical bonds. Allowed are edgecolor, facecolor, color, # Only plot lines not obstructed by an atom if z_indices[cell_idx] != -1: hxy = unit_cell_lines[z_indices[cell_idx]] - path = PathPatch(Path((xy + hxy, xy - hxy))) + path = PathPatch(Path((xy + hxy, xy - hxy)), zorder=z_indices[cell_idx]) ax.add_patch(path) if show_bonds: @@ -435,10 +434,9 @@ class used to plot chemical bonds. Allowed are edgecolor, facecolor, color, ): try: struct.add_oxidation_state_by_guess() - except ValueError as err: # fails for disordered structures - raise ValueError( - "Charge balance analysis requires integer values in Composition." - ) from err + except ValueError: # fails for disordered structures + # Charge balance analysis requires integer values in Composition + pass structure_graph = neighbor_strategy_cls().get_bonded_structure(struct) From 7ad3517d343eb2043516a5a453dd7fb6bf57af4a Mon Sep 17 00:00:00 2001 From: "Haoyu (Daniel)" Date: Fri, 10 May 2024 11:21:18 +0800 Subject: [PATCH 10/17] update figures --- assets/matbench-phonons-structures-2d.svg | 2 +- assets/struct-2d-mp-12712-Hf9Zr9Pd24-disordered.svg | 8 +++++++- ...ruct-2d-mp-19017-Li4Mn0.8Fe1.6P4C1.6O16-disordered.svg | 8 +++++++- 3 files changed, 15 insertions(+), 3 deletions(-) diff --git a/assets/matbench-phonons-structures-2d.svg b/assets/matbench-phonons-structures-2d.svg index a57a3b51..cb748249 100644 --- a/assets/matbench-phonons-structures-2d.svg +++ b/assets/matbench-phonons-structures-2d.svg @@ -1 +1 @@ - \ No newline at end of file + \ No newline at end of file diff --git a/assets/struct-2d-mp-12712-Hf9Zr9Pd24-disordered.svg b/assets/struct-2d-mp-12712-Hf9Zr9Pd24-disordered.svg index aa5a2c76..2d81b4a4 100644 --- a/assets/struct-2d-mp-12712-Hf9Zr9Pd24-disordered.svg +++ b/assets/struct-2d-mp-12712-Hf9Zr9Pd24-disordered.svg @@ -1 +1,7 @@ - \ No newline at end of file + + + + + + + \ No newline at end of file diff --git a/assets/struct-2d-mp-19017-Li4Mn0.8Fe1.6P4C1.6O16-disordered.svg b/assets/struct-2d-mp-19017-Li4Mn0.8Fe1.6P4C1.6O16-disordered.svg index 6a8766bd..285fddde 100644 --- a/assets/struct-2d-mp-19017-Li4Mn0.8Fe1.6P4C1.6O16-disordered.svg +++ b/assets/struct-2d-mp-19017-Li4Mn0.8Fe1.6P4C1.6O16-disordered.svg @@ -1 +1,7 @@ - \ No newline at end of file + + + + + + + \ No newline at end of file From d3f08b1c5b8b754302d618999a42fcaa2e72ca97 Mon Sep 17 00:00:00 2001 From: "Haoyu (Daniel)" Date: Fri, 10 May 2024 11:45:32 +0800 Subject: [PATCH 11/17] fix occlusion order --- pymatviz/structure_viz.py | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/pymatviz/structure_viz.py b/pymatviz/structure_viz.py index 4d789e23..6540aba7 100644 --- a/pymatviz/structure_viz.py +++ b/pymatviz/structure_viz.py @@ -335,12 +335,14 @@ class used to plot chemical bonds. Allowed are edgecolor, facecolor, color, for idx in positions[:, 2].argsort(): xy = positions[idx, :2] start = 0 + zorder = positions[idx][2] if idx < n_atoms: # Loop over all species on a site (usually just 1 for ordered sites) for species, occupancy in struct[idx].species.items(): # Strip oxidation state from element symbol (e.g. Ta5+ to Ta) elem_symbol = species.symbol + radius = atomic_radii[elem_symbol] * scale # type: ignore[index] face_color = colors[elem_symbol] wedge = Wedge( @@ -350,7 +352,7 @@ class used to plot chemical bonds. Allowed are edgecolor, facecolor, color, 360 * (start + occupancy), facecolor=face_color, edgecolor="black", - zorder=z_indices[idx], + zorder=zorder, ) ax.add_patch(wedge) @@ -393,7 +395,7 @@ class used to plot chemical bonds. Allowed are edgecolor, facecolor, color, txt_kwds = dict( ha="center", va="center", - zorder=z_indices[idx], + zorder=zorder, bbox=bbox, **(label_kwargs or {}), ) @@ -407,7 +409,7 @@ class used to plot chemical bonds. Allowed are edgecolor, facecolor, color, # Only plot lines not obstructed by an atom if z_indices[cell_idx] != -1: hxy = unit_cell_lines[z_indices[cell_idx]] - path = PathPatch(Path((xy + hxy, xy - hxy)), zorder=z_indices[cell_idx]) + path = PathPatch(Path((xy + hxy, xy - hxy)), zorder=zorder) ax.add_patch(path) if show_bonds: From 066aa8521df7fffae1561fbc41b88eb2d5d771b6 Mon Sep 17 00:00:00 2001 From: "Haoyu (Daniel)" Date: Fri, 10 May 2024 11:47:57 +0800 Subject: [PATCH 12/17] update figures --- assets/matbench-phonons-structures-2d.svg | 2 +- assets/struct-2d-mp-12712-Hf9Zr9Pd24-disordered.svg | 4 ++-- .../struct-2d-mp-19017-Li4Mn0.8Fe1.6P4C1.6O16-disordered.svg | 4 ++-- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/assets/matbench-phonons-structures-2d.svg b/assets/matbench-phonons-structures-2d.svg index cb748249..3cf2111a 100644 --- a/assets/matbench-phonons-structures-2d.svg +++ b/assets/matbench-phonons-structures-2d.svg @@ -1 +1 @@ - \ No newline at end of file + \ No newline at end of file diff --git a/assets/struct-2d-mp-12712-Hf9Zr9Pd24-disordered.svg b/assets/struct-2d-mp-12712-Hf9Zr9Pd24-disordered.svg index 2d81b4a4..c16b9bc5 100644 --- a/assets/struct-2d-mp-12712-Hf9Zr9Pd24-disordered.svg +++ b/assets/struct-2d-mp-12712-Hf9Zr9Pd24-disordered.svg @@ -1,7 +1,7 @@ - + - \ No newline at end of file + \ No newline at end of file diff --git a/assets/struct-2d-mp-19017-Li4Mn0.8Fe1.6P4C1.6O16-disordered.svg b/assets/struct-2d-mp-19017-Li4Mn0.8Fe1.6P4C1.6O16-disordered.svg index 285fddde..d566689d 100644 --- a/assets/struct-2d-mp-19017-Li4Mn0.8Fe1.6P4C1.6O16-disordered.svg +++ b/assets/struct-2d-mp-19017-Li4Mn0.8Fe1.6P4C1.6O16-disordered.svg @@ -1,7 +1,7 @@ - + - \ No newline at end of file + \ No newline at end of file From 1eaa88f1ff6dad6af08b63f76797217ca960be35 Mon Sep 17 00:00:00 2001 From: Haoyu Yang Date: Fri, 10 May 2024 13:47:28 +0800 Subject: [PATCH 13/17] drop bbox completely --- pymatviz/structure_viz.py | 4 ---- 1 file changed, 4 deletions(-) diff --git a/pymatviz/structure_viz.py b/pymatviz/structure_viz.py index 6540aba7..55231f76 100644 --- a/pymatviz/structure_viz.py +++ b/pymatviz/structure_viz.py @@ -389,14 +389,10 @@ class used to plot chemical bonds. Allowed are edgecolor, facecolor, color, (0.5 * radius) * direction if occupancy < 1 else (0, 0) ) - bbox = dict(facecolor="none", edgecolor="none", pad=1, alpha=0) - bbox |= site_labels_bbox or {} - txt_kwds = dict( ha="center", va="center", zorder=zorder, - bbox=bbox, **(label_kwargs or {}), ) ax.text(*(xy + text_offset), txt, **txt_kwds) From 419f5d8e5db427c4dd3e202314aebb2e8c55ba98 Mon Sep 17 00:00:00 2001 From: Haoyu Yang Date: Fri, 10 May 2024 13:52:11 +0800 Subject: [PATCH 14/17] breaking: remove `site_labels_bbox` --- pymatviz/structure_viz.py | 3 --- 1 file changed, 3 deletions(-) diff --git a/pymatviz/structure_viz.py b/pymatviz/structure_viz.py index 55231f76..9cf70666 100644 --- a/pymatviz/structure_viz.py +++ b/pymatviz/structure_viz.py @@ -126,7 +126,6 @@ def plot_structure_2d( | Literal["symbol", "species"] | dict[str, str | float] | Sequence[str | float] = True, - site_labels_bbox: dict[str, Any] | None = None, label_kwargs: dict[str, Any] | None = None, bond_kwargs: dict[str, Any] | None = None, standardize_struct: bool | None = None, @@ -201,8 +200,6 @@ def plot_structure_2d( number of sites in the crystal. If a string, must be "symbol" or "species". "symbol" hides the oxidation state, "species" shows it (equivalent to True). Defaults to True. - site_labels_bbox (dict, optional): Keyword arguments for matplotlib.text.Text - bbox like {"facecolor": "white", "alpha": 0.5}. Defaults to None. label_kwargs (dict, optional): Keyword arguments for matplotlib.text.Text like {"fontsize": 14}. Defaults to None. bond_kwargs (dict, optional): Keyword arguments for the matplotlib.path.Path From 184d75abc6ad230f57af627edfb00e20cef9adf3 Mon Sep 17 00:00:00 2001 From: Haoyu Yang Date: Fri, 10 May 2024 14:00:07 +0800 Subject: [PATCH 15/17] remove `site_labels_bbox` from unit test --- tests/test_structure_viz.py | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/tests/test_structure_viz.py b/tests/test_structure_viz.py index 668de50e..9bb30d0b 100644 --- a/tests/test_structure_viz.py +++ b/tests/test_structure_viz.py @@ -1,6 +1,6 @@ from __future__ import annotations -from typing import TYPE_CHECKING, Any +from typing import TYPE_CHECKING import matplotlib.pyplot as plt import pandas as pd @@ -69,14 +69,10 @@ def test_plot_structure_2d_axis(axis: str | bool) -> None: "site_labels", [True, False, "symbol", "species", {"Fe": "Iron"}, {"Fe": 1.0}, ["Fe", "O"]], ) -@pytest.mark.parametrize("site_labels_bbox", [None, {}, {"boxstyle": "round"}]) def test_plot_structure_2d_site_labels( site_labels: bool | str | dict[str, str | float] | Sequence[str], - site_labels_bbox: dict[str, Any] | None, ) -> None: - ax = plot_structure_2d( - disordered_struct, site_labels=site_labels, site_labels_bbox=site_labels_bbox - ) + ax = plot_structure_2d(disordered_struct, site_labels=site_labels) if site_labels is False: assert not ax.axes.texts else: From faece9f57af1148ff7a69abdda84c8efea4ff5a3 Mon Sep 17 00:00:00 2001 From: Janosh Riebesell Date: Fri, 10 May 2024 08:33:08 -0400 Subject: [PATCH 16/17] move ExperimentalWarning to pymatviz/utils.py --- pymatviz/structure_viz.py | 30 +++++++++++------------------- pymatviz/utils.py | 8 ++++++++ 2 files changed, 19 insertions(+), 19 deletions(-) diff --git a/pymatviz/structure_viz.py b/pymatviz/structure_viz.py index 9cf70666..0a33f984 100644 --- a/pymatviz/structure_viz.py +++ b/pymatviz/structure_viz.py @@ -18,7 +18,7 @@ from pymatgen.analysis.local_env import CrystalNN, NearNeighbors from pymatgen.symmetry.analyzer import SpacegroupAnalyzer -from pymatviz.utils import covalent_radii, jmol_colors +from pymatviz.utils import ExperimentalWarning, covalent_radii, jmol_colors if TYPE_CHECKING: @@ -29,13 +29,6 @@ from pymatgen.core import Structure -class ExperimentalWarning(Warning): - """Warning for experimental features.""" - - -warnings.simplefilter("once", ExperimentalWarning) - - def _angles_to_rotation_matrix( angles: str, rotation: ArrayLike | None = None ) -> ArrayLike: @@ -86,28 +79,27 @@ def unit_cell_to_lines(cell: ArrayLike) -> tuple[ArrayLike, ArrayLike, ArrayLike - z-indices that sort plot elements into out-of-plane layers - lines used to plot the unit cell """ - n_lines = 0 + n_lines = n1 = 0 segments = [] - for c in range(3): - norm = math.sqrt(sum(cell[c] ** 2)) + for idx in range(3): + norm = math.sqrt(sum(cell[idx] ** 2)) segment = max(2, int(norm / 0.3)) segments.append(segment) n_lines += 4 * segment lines = np.empty((n_lines, 3)) - z_indices = np.empty(n_lines, int) + z_indices = np.empty(n_lines, dtype=int) unit_cell_lines = np.zeros((3, 3)) - n1 = 0 - for c in range(3): - segment = segments[c] - dd = cell[c] / (4 * segment - 2) - unit_cell_lines[c] = dd + for idx in range(3): + segment = segments[idx] + dd = cell[idx] / (4 * segment - 2) + unit_cell_lines[idx] = dd P = np.arange(1, 4 * segment + 1, 4)[:, None] * dd - z_indices[n1:] = c + z_indices[n1:] = idx for i, j in [(0, 0), (0, 1), (1, 0), (1, 1)]: n2 = n1 + segment - lines[n1:n2] = P + i * cell[c - 2] + j * cell[c - 1] + lines[n1:n2] = P + i * cell[idx - 2] + j * cell[idx - 1] n1 = n2 return lines, z_indices, unit_cell_lines diff --git a/pymatviz/utils.py b/pymatviz/utils.py index 30a8c822..0358e627 100644 --- a/pymatviz/utils.py +++ b/pymatviz/utils.py @@ -3,6 +3,7 @@ from __future__ import annotations import ast +import warnings from contextlib import contextmanager from functools import partial, wraps from os.path import dirname @@ -79,6 +80,13 @@ element_symbols[Z] = symbol +class ExperimentalWarning(Warning): + """Warning for experimental features.""" + + +warnings.simplefilter("once", ExperimentalWarning) + + def pretty_label(key: str, backend: Backend) -> str: """Map metric keys to their pretty labels.""" if backend not in VALID_BACKENDS: From 8a4a32cd1a7f4d7f653fef2ab65a25d929e8e27f Mon Sep 17 00:00:00 2001 From: Janosh Riebesell Date: Fri, 10 May 2024 08:38:04 -0400 Subject: [PATCH 17/17] restore oxi states in matbench-phonons-structures-2d.svg --- assets/matbench-phonons-structures-2d.svg | 2 +- examples/_generate_assets.py | 10 ++++++++-- 2 files changed, 9 insertions(+), 3 deletions(-) diff --git a/assets/matbench-phonons-structures-2d.svg b/assets/matbench-phonons-structures-2d.svg index 3cf2111a..62c9a57d 100644 --- a/assets/matbench-phonons-structures-2d.svg +++ b/assets/matbench-phonons-structures-2d.svg @@ -1 +1 @@ - \ No newline at end of file + \ No newline at end of file diff --git a/examples/_generate_assets.py b/examples/_generate_assets.py index 17b906ea..1dddb553 100644 --- a/examples/_generate_assets.py +++ b/examples/_generate_assets.py @@ -12,6 +12,7 @@ from monty.io import zopen from monty.json import MontyDecoder from mp_api.client import MPRester +from pymatgen.core import Structure from pymatgen.core.periodic_table import Element from pymatgen.phonon.bandstructure import PhononBandStructureSymmLine as PhononBands from pymatgen.phonon.dos import PhononDos @@ -61,6 +62,8 @@ px.defaults.template = "pymatviz_white" pio.templates.default = "pymatviz_white" +struct: Structure # for type hinting + # Random classification data np.random.seed(42) rand_clf_size = 100 @@ -338,8 +341,11 @@ title = f"{len(axs.flat)} Matbench phonon structures" fig.suptitle(title, fontweight="bold", fontsize=20) -for row, ax in zip(df_phonons.itertuples(), axs.flat): - idx, struct, *_, spg_num = row +for idx, (row, ax) in enumerate(zip(df_phonons.itertuples(), axs.flat), start=1): + struct = row.structure + spg_num = struct.get_space_group_info()[1] + struct.add_oxidation_state_by_guess() + plot_structure_2d( struct, ax=ax,