Exemple #1
0
    def get_scene_and_legend(self) -> Tuple[Scene, Dict[str, str]]:

        legend = Legend(self.graph.structure,
                        color_scheme="VESTA",
                        cmap_range=None)
        legend.uniform_radius = 0.3

        scene = get_structure_graph_scene(
            self.graph,
            draw_image_atoms=True,
            bonded_sites_outside_unit_cell=False,
            hide_incomplete_edges=True,
            explicitly_calculate_polyhedra_hull=False,
            group_by_symmetry=True,
            draw_polyhedra=False,
            legend=legend)

        scene.name = "DefectStructureComponentScene"

        lattice: Lattice = self.graph.structure.lattice
        origin = -self.graph.structure.lattice.get_cartesian_coords(
            [0.5, 0.5, 0.5])
        scene_json = scene.to_json()
        for idx, i in enumerate(self.interstitials, 1):
            site = Site(species=DummySpecie(),
                        coords=lattice.get_cartesian_coords(i.frac_coords))
            interstitial_scene = site.get_scene(origin=origin)
            interstitial_scene.name = f"i{idx}"
            interstitial_scene.contents[0].contents[0].tooltip = f"i{idx}"
            scene_json["contents"].append(interstitial_scene.to_json())

        return scene_json, legend.get_legend()
    def test_msonable(self):

        legend = Legend(self.struct)
        legend_dict = legend.as_dict()
        legend_from_dict = Legend.from_dict(legend_dict)

        assert legend.get_legend() == legend_from_dict.get_legend()
Exemple #3
0
    def get_scene_and_legend(
        graph: Optional[Union[StructureGraph, MoleculeGraph]],
        name,
        color_scheme=DEFAULTS["color_scheme"],
        color_scale=None,
        radius_strategy=DEFAULTS["radius_strategy"],
        draw_image_atoms=DEFAULTS["draw_image_atoms"],
        bonded_sites_outside_unit_cell=DEFAULTS[
            "bonded_sites_outside_unit_cell"],
        hide_incomplete_bonds=DEFAULTS["hide_incomplete_bonds"],
        explicitly_calculate_polyhedra_hull=False,
        scene_additions=None,
        show_compass=DEFAULTS["show_compass"],
    ) -> Tuple[Scene, Dict[str, str]]:

        # default scene name will be name of component, "_ct_..."
        # strip leading _ since this will cause problems in JavaScript land
        scene = Scene(name=name[1:])

        if graph is None:
            return scene, {}

        struct_or_mol = StructureMoleculeComponent._get_struct_or_mol(graph)

        # TODO: add radius_scale
        legend = Legend(
            struct_or_mol,
            color_scheme=color_scheme,
            radius_scheme=radius_strategy,
            cmap_range=color_scale,
        )

        if isinstance(graph, StructureGraph):
            scene = graph.get_scene(
                draw_image_atoms=draw_image_atoms,
                bonded_sites_outside_unit_cell=bonded_sites_outside_unit_cell,
                hide_incomplete_edges=hide_incomplete_bonds,
                explicitly_calculate_polyhedra_hull=
                explicitly_calculate_polyhedra_hull,
                legend=legend,
            )
        elif isinstance(graph, MoleculeGraph):
            scene = graph.get_scene(legend=legend)

        scene.name = name

        if hasattr(struct_or_mol, "lattice"):
            axes = struct_or_mol.lattice._axes_from_lattice()
            # TODO: fix pop-in ?
            axes.visible = show_compass
            scene.contents.append(axes)

        if scene_additions:
            # TODO: need a Scene.from_json() to make this work
            raise NotImplementedError
            scene["contents"].append(scene_additions)

        return scene.to_json(), legend.get_legend()
Exemple #4
0
    def get_scene_and_legend(
        graph: Optional[Union[StructureGraph, MoleculeGraph]],
        color_scheme=DEFAULTS["color_scheme"],
        color_scale=None,
        radius_strategy=DEFAULTS["radius_strategy"],
        draw_image_atoms=DEFAULTS["draw_image_atoms"],
        bonded_sites_outside_unit_cell=DEFAULTS[
            "bonded_sites_outside_unit_cell"],
        hide_incomplete_bonds=DEFAULTS["hide_incomplete_bonds"],
        explicitly_calculate_polyhedra_hull=False,
        scene_additions=None,
        show_compass=DEFAULTS["show_compass"],
        group_by_site_property=None,
    ) -> Tuple[Scene, Dict[str, str]]:

        scene = Scene(name="StructureMoleculeComponentScene")

        if graph is None:
            return scene, {}

        struct_or_mol = StructureMoleculeComponent._get_struct_or_mol(graph)

        # TODO: add radius_scale
        legend = Legend(
            struct_or_mol,
            color_scheme=color_scheme,
            radius_scheme=radius_strategy,
            cmap_range=color_scale,
        )

        if isinstance(graph, StructureGraph):
            scene = graph.get_scene(
                draw_image_atoms=draw_image_atoms,
                bonded_sites_outside_unit_cell=bonded_sites_outside_unit_cell,
                hide_incomplete_edges=hide_incomplete_bonds,
                explicitly_calculate_polyhedra_hull=
                explicitly_calculate_polyhedra_hull,
                group_by_site_property=group_by_site_property,
                legend=legend,
            )
        elif isinstance(graph, MoleculeGraph):
            scene = graph.get_scene(legend=legend)

        scene.name = "StructureMoleculeComponentScene"

        if hasattr(struct_or_mol, "lattice"):
            axes = struct_or_mol.lattice._axes_from_lattice()
            axes.visible = show_compass
            scene.contents.append(axes)

        scene_json = scene.to_json()

        if scene_additions:
            # TODO: this might be cleaner if we had a Scene.from_json() method
            scene_json["contents"].append(scene_additions)

        return scene_json, legend.get_legend()
    def get_graph_data(graph, display_options):

        color_scheme = display_options.get("color_scheme", "Jmol")

        nodes = []
        edges = []

        struct_or_mol = StructureMoleculeComponent._get_struct_or_mol(graph)
        legend = Legend(struct_or_mol, color_scheme=color_scheme)

        for idx, node in enumerate(graph.graph.nodes()):

            # TODO: fix for disordered
            node_color = legend.get_color(
                struct_or_mol[node].species.elements[0],
                site=struct_or_mol[node])

            nodes.append({
                "id":
                node,
                "title":
                f"{struct_or_mol[node].species_string} site "
                f"({graph.get_coordination_of_site(idx)} neighbors)",
                "color":
                node_color,
            })

        for u, v, d in graph.graph.edges(data=True):

            edge = {"from": u, "to": v, "arrows": ""}

            to_jimage = d.get("to_jimage", (0, 0, 0))

            # TODO: check these edge weights
            if isinstance(struct_or_mol, Structure):
                dist = struct_or_mol.get_distance(u, v, jimage=to_jimage)
            else:
                dist = struct_or_mol.get_distance(u, v)
            edge["length"] = 50 * dist

            if to_jimage != (0, 0, 0):
                edge["arrows"] = "to"
                label = f"{dist:.2f} Å to site at image vector {to_jimage}"
            else:
                label = f"{dist:.2f} Å between sites"

            if label:
                edge["title"] = label

            # if 'weight' in d:
            #   label += f" {d['weight']}"

            edges.append(edge)

        return {"nodes": nodes, "edges": edges}
    def get_scene_and_legend(
        graph: Optional[Union[StructureGraph, MoleculeGraph]],
        color_scale=None,
        radius_strategy=DEFAULTS["radius_strategy"],
        draw_image_atoms=DEFAULTS["draw_image_atoms"],
        bonded_sites_outside_unit_cell=DEFAULTS[
            "bonded_sites_outside_unit_cell"],
        hide_incomplete_bonds=DEFAULTS["hide_incomplete_bonds"],
        explicitly_calculate_polyhedra_hull=False,
        scene_additions=None,
        show_compass=DEFAULTS["show_compass"],
    ) -> Tuple[Scene, Dict[str, str]]:

        scene = Scene(name="AceStructureMoleculeComponentScene")

        if graph is None:
            return scene, {}

        structure = StructureComponent._get_structure(graph)

        # TODO: add radius_scale
        legend = Legend(
            structure,
            color_scheme="VESTA",
            radius_scheme=radius_strategy,
            cmap_range=color_scale,
        )

        scene = graph.get_scene(
            draw_image_atoms=draw_image_atoms,
            bonded_sites_outside_unit_cell=bonded_sites_outside_unit_cell,
            hide_incomplete_edges=hide_incomplete_bonds,
            explicitly_calculate_polyhedra_hull=
            explicitly_calculate_polyhedra_hull,
            legend=legend,
        )

        scene.name = "StructureComponentScene"

        if hasattr(structure, "lattice"):
            axes = structure.lattice._axes_from_lattice()
            axes.visible = show_compass
            scene.contents.append(axes)

        scene = scene.to_json()
        if scene_additions:
            # TODO: need a Scene.from_json() to make this work
            # raise NotImplementedError
            scene["contents"].append(scene_additions)

        return scene, legend.get_legend()
Exemple #7
0
def get_scene_from_molecule(self,
                            origin=None,
                            legend: Optional[Legend] = None):
    """
    Create CTK objects for the lattice and sties
    Args:
        self:  Structure object
        origin: fractional coordinate of the origin
        legend: Legend for the sites

    Returns:
        CTK scene object to be rendered
    """

    origin = origin if origin else (0, 0, 0)

    legend = legend or Legend(self)

    primitives = defaultdict(list)

    for idx, site in enumerate(self):
        site_scene = site.get_scene(
            origin=origin,
            legend=legend,
        )

        for scene in site_scene.contents:
            primitives[scene.name] += scene.contents

    return Scene(
        name=self.composition.reduced_formula,
        contents=[Scene(name=k, contents=v) for k, v in primitives.items()],
        origin=origin,
    )
def get_molecule_graph_scene(
    self,
    origin=None,
    explicitly_calculate_polyhedra_hull=False,
    legend=None,
    draw_polyhedra=False,
) -> Scene:

    legend = legend or Legend(self.molecule)

    primitives = defaultdict(list)

    for idx, site in enumerate(self.molecule):

        connected_sites = self.get_connected_sites(idx)

        site_scene = site.get_scene(
            connected_sites=connected_sites,
            origin=origin,
            explicitly_calculate_polyhedra_hull=
            explicitly_calculate_polyhedra_hull,
            legend=legend,
            draw_polyhedra=draw_polyhedra,
        )
        for scene in site_scene.contents:
            primitives[scene.name] += scene.contents

    return Scene(
        name=self.molecule.composition.reduced_formula,
        contents=[Scene(name=k, contents=v) for k, v in primitives.items()],
        origin=origin if origin else (0, 0, 0),
    )
Exemple #9
0
        def update_scene_and_legend_and_colors(graph, display_options, scene_additions):
            if not graph or not display_options:
                raise PreventUpdate
            display_options = self.from_data(display_options)
            graph = self.from_data(graph)
            scene, legend = self.get_scene_and_legend(
                graph,
                name=self.id(),
                **display_options,
                scene_additions=scene_additions,
            )

            color_options = [
                {"label": "Jmol", "value": "Jmol"},
                {"label": "VESTA", "value": "VESTA"},
                {"label": "Accessible", "value": "accessible"},
            ]
            struct_or_mol = self._get_struct_or_mol(graph)
            site_props = Legend(struct_or_mol).analyze_site_props(struct_or_mol)
            for site_prop_type in ("scalar", "categorical"):
                if site_prop_type in site_props:
                    for prop in site_props[site_prop_type]:
                        color_options += [
                            {"label": f"Site property: {prop}", "value": prop}
                        ]

            return scene, legend, color_options
Exemple #10
0
def get_structure_scene(
    self,
    draw_image_atoms=True,
    legend: Optional[Legend] = None,
    origin=None,
) -> Scene:

    origin = origin or list(
        -self.lattice.get_cartesian_coords([0.5, 0.5, 0.5]))

    legend = legend or Legend(self)

    primitives = defaultdict(list)

    sites_to_draw = self._get_sites_to_draw(draw_image_atoms=draw_image_atoms)

    for (idx, jimage) in sites_to_draw:

        site_scene = self[idx].get_scene(origin=origin, legend=legend)
        for scene in site_scene.contents:
            primitives[scene.name] += scene.contents

    primitives["unit_cell"].append(self.lattice.get_scene(origin=origin))

    return Scene(
        name=self.composition.reduced_formula,
        contents=[Scene(name=k, contents=v) for k, v in primitives.items()],
        origin=origin,
    )
Exemple #11
0
    def test_get_radius(self):

        legend = Legend(self.struct, radius_scheme="uniform")

        assert legend.get_radius(sp=self.sp0) == 0.5

        legend = Legend(self.struct, radius_scheme="covalent")

        assert legend.get_radius(sp=self.sp1) == 0.66

        legend = Legend(self.struct, radius_scheme="specified_or_average_ionic")

        assert legend.get_radius(sp=self.sp2) == 0.94
    def get_scene_and_legend(self,
                             scene_additions=None
                             ) -> Tuple[Scene, Dict[str, str]]:

        legend = Legend(self.graph.structure,
                        color_scheme="VESTA",
                        radius_scheme="uniform",
                        cmap_range=None)
        legend.uniform_radius = 0.2

        scene = get_structure_graph_scene(
            self.graph,
            draw_image_atoms=True,
            bonded_sites_outside_unit_cell=False,
            hide_incomplete_edges=True,
            explicitly_calculate_polyhedra_hull=False,
            group_by_symmetry=False,
            legend=legend)

        scene.name = "DefectStructureComponentScene"

        # axes = graph.structure.lattice._axes_from_lattice()
        # axes.visible = True
        # scene.contents.append(axes)

        scene = scene.to_json()
        if scene_additions:
            scene["contents"].append(scene_additions)

        lattice = self.graph.structure.lattice
        origin = -self.graph.structure.lattice.get_cartesian_coords(
            [0.5, 0.5, 0.5])

        for name, frac_coords in self.vacancy_sites:
            site = Site(species=DummySpecie(name),
                        coords=lattice.get_cartesian_coords(frac_coords))
            vac_scene = site.get_scene(origin=origin)
            vac_scene.name = f"{name}_{frac_coords}"
            vac_scene.contents[0].contents[0].tooltip = name
            scene["contents"].append(vac_scene.to_json())

        return scene, legend.get_legend()
def get_structure_scene(
    self,
    origin: List[float] = None,
    legend: Optional[Legend] = None,
    draw_image_atoms: bool = True,
) -> Scene:
    """
    Create CTK objects for the lattice and sties
    Args:
        self:  Structure object
        origin: fractional coordinate of the origin
        legend: Legend for the sites
        draw_image_atoms: If true draw image atoms that are just outside the
        periodic boundary

    Returns:
        CTK scene object to be rendered
    """

    origin = origin or list(
        -self.lattice.get_cartesian_coords([0.5, 0.5, 0.5]))

    legend = legend or Legend(self)

    primitives = defaultdict(list)

    sites_to_draw = self._get_sites_to_draw(
        draw_image_atoms=draw_image_atoms, )

    for (idx, jimage) in sites_to_draw:

        site = self[idx]
        if jimage != (0, 0, 0):
            site = PeriodicSite(
                site.species,
                np.add(site.frac_coords, jimage),
                site.lattice,
                properties=site.properties,
            )

        site_scene = site.get_scene(legend=legend, )
        for scene in site_scene.contents:
            primitives[scene.name] += scene.contents

    primitives["unit_cell"].append(self.lattice.get_scene())

    return Scene(
        name="Structure",
        origin=origin,
        contents=[
            Scene(name=k, contents=v, origin=origin)
            for k, v in primitives.items()
        ],
    )
def get_structure_scene(
    self,
    origin=None,
    draw_image_atoms=True,
    bonded_sites_outside_unit_cell=False,
    legend: Optional[Legend] = None,
) -> Scene:

    legend = legend or Legend(self.structure)

    primitives = defaultdict(list)

    sites_to_draw = self._get_sites_to_draw(
        draw_image_atoms=draw_image_atoms,
        bonded_sites_outside_unit_cell=bonded_sites_outside_unit_cell,
    )

    for (idx, jimage) in sites_to_draw:

        site = self.structure[idx]
        if jimage != (0, 0, 0):
            connected_sites = self.get_connected_sites(idx, jimage=jimage)
            site = PeriodicSite(
                site.species,
                np.add(site.frac_coords, jimage),
                site.lattice,
                properties=site.properties,
            )
        else:
            connected_sites = self.get_connected_sites(idx)

        site_scene = site.get_scene(origin=origin, legend=legend)
        for scene in site_scene.contents:
            primitives[scene.name] += scene.contents

    primitives["unit_cell"].append(
        self.structure.lattice.get_scene(origin=origin))

    return Scene(
        name=self.structure.composition.reduced_formula,
        contents=[Scene(name=k, contents=v) for k, v in primitives.items()],
        origin=origin,
    )
def get_site_scene(
    self,
    connected_sites: List[ConnectedSite] = None,
    # connected_site_metadata: None,
    # connected_sites_to_draw,
    connected_sites_not_drawn: List[ConnectedSite] = None,
    hide_incomplete_edges: bool = False,
    incomplete_edge_length_scale: Optional[float] = 1.0,
    connected_sites_colors: Optional[List[str]] = None,
    connected_sites_not_drawn_colors: Optional[List[str]] = None,
    origin: Optional[List[float]] = None,
    draw_polyhedra: bool = True,
    explicitly_calculate_polyhedra_hull: bool = False,
    bond_radius: float = 0.1,
    legend: Optional[Legend] = None,
) -> Scene:
    """

    Args:
        connected_sites:
        connected_sites_not_drawn:
        hide_incomplete_edges:
        incomplete_edge_length_scale:
        connected_sites_colors:
        connected_sites_not_drawn_colors:
        origin:
        explicitly_calculate_polyhedra_hull:
        legend:

    Returns:

    """

    atoms = []
    bonds = []
    polyhedron = []

    legend = legend or Legend(self)

    # for disordered structures
    is_ordered = self.is_ordered
    phiStart, phiEnd = None, None
    occu_start = 0.0

    position = self.coords.tolist()

    for idx, (sp, occu) in enumerate(self.species.items()):

        if isinstance(sp, DummySpecie):

            cube = Cubes(positions=[position],
                         color=legend.get_color(sp, site=self),
                         width=0.4)
            atoms.append(cube)

        else:

            color = legend.get_color(sp, site=self)
            radius = legend.get_radius(sp, site=self)

            # TODO: make optional/default to None
            # in disordered structures, we fractionally color-code spheres,
            # drawing a sphere segment from phi_end to phi_start
            # (think a sphere pie chart)
            if not is_ordered:
                phi_frac_end = occu_start + occu
                phi_frac_start = occu_start
                occu_start = phi_frac_end
                phiStart = phi_frac_start * np.pi * 2
                phiEnd = phi_frac_end * np.pi * 2

            name = str(sp)
            if occu != 1.0:
                name += " ({}% occupancy)".format(occu)
            name += f" ({position[0]:.3f}, {position[1]:.3f}, {position[2]:.3f})"

            sphere = Spheres(
                positions=[position],
                color=color,
                radius=radius,
                phiStart=phiStart,
                phiEnd=phiEnd,
                clickable=True,
                tooltip=name,
            )
            atoms.append(sphere)

    if not is_ordered and not np.isclose(phiEnd, np.pi * 2):
        # if site occupancy doesn't sum to 100%, cap sphere
        sphere = Spheres(
            positions=[position],
            color="#ffffff",
            radius=self.properties["display_radius"][0],
            phiStart=phiEnd,
            phiEnd=np.pi * 2,
        )
        atoms.append(sphere)

    if connected_sites:

        # TODO: more graceful solution here
        # if ambiguous (disordered), re-use last color used
        site_color = color

        # TODO: can cause a bug if all vertices almost co-planar
        # necessary to include center site in case it's outside polyhedra
        all_positions = [self.coords]

        for idx, connected_site in enumerate(connected_sites):

            connected_position = connected_site.site.coords
            bond_midpoint = np.add(position, connected_position) / 2

            if connected_sites_colors:
                color = connected_sites_colors[idx]
            else:
                color = site_color

            cylinder = Cylinders(
                positionPairs=[[position, bond_midpoint.tolist()]],
                color=color,
                radius=bond_radius,
            )
            bonds.append(cylinder)
            all_positions.append(connected_position.tolist())

        if connected_sites_not_drawn and not hide_incomplete_edges:

            for idx, connected_site in enumerate(connected_sites_not_drawn):

                connected_position = connected_site.site.coords
                bond_midpoint = (incomplete_edge_length_scale *
                                 np.add(position, connected_position) / 2)

                if connected_sites_not_drawn_colors:
                    color = connected_sites_not_drawn_colors[idx]
                else:
                    color = site_color

                cylinder = Cylinders(
                    positionPairs=[[position, bond_midpoint.tolist()]],
                    color=color,
                    radius=bond_radius,
                )
                bonds.append(cylinder)
                all_positions.append(connected_position.tolist())

        # ensure intersecting polyhedra are not shown, defaults to choose by electronegativity
        not_most_electro_negative = map(
            lambda x:
            (x.site.specie < self.specie) or (x.site.specie == self.specie),
            connected_sites,
        )

        all_positions = [list(p) for p in all_positions]

        if (draw_polyhedra and len(connected_sites) > 3
                and not connected_sites_not_drawn
                and not any(not_most_electro_negative)):
            if explicitly_calculate_polyhedra_hull:

                try:

                    # all_positions = [[0, 0, 0], [0, 0, 10], [0, 10, 0], [10, 0, 0]]
                    # gives...
                    # .convex_hull = [[2, 3, 0], [1, 3, 0], [1, 2, 0], [1, 2, 3]]
                    # .vertex_neighbor_vertices = [1, 2, 3, 2, 3, 0, 1, 3, 0, 1, 2, 0]

                    vertices_indices = Delaunay(all_positions).convex_hull
                except Exception as e:
                    vertices_indices = []

                vertices = [
                    all_positions[idx]
                    for idx in chain.from_iterable(vertices_indices)
                ]

                polyhedron = [Surface(positions=vertices, color=site_color)]

            else:

                polyhedron = [
                    Convex(positions=all_positions, color=site_color)
                ]

    return Scene(
        self.species_string,
        [
            Scene("atoms", contents=atoms),
            Scene("bonds", contents=bonds),
            Scene("polyhedra", contents=polyhedron),
        ],
        origin=origin,
    )
Exemple #16
0
def get_structure_graph_scene(
    self,
    origin=None,
    draw_image_atoms=True,
    bonded_sites_outside_unit_cell=True,
    hide_incomplete_edges=False,
    incomplete_edge_length_scale=0.3,
    color_edges_by_edge_weight=True,
    edge_weight_color_scale="coolwarm",
    explicitly_calculate_polyhedra_hull=False,
    legend: Optional[Legend] = None,
    group_by_symmetry: bool = True,
) -> Scene:

    origin = origin or list(
        -self.structure.lattice.get_cartesian_coords([0.5, 0.5, 0.5]))

    legend = legend or Legend(self.structure)

    primitives = defaultdict(list)

    sites_to_draw = self._get_sites_to_draw(
        draw_image_atoms=draw_image_atoms,
        bonded_sites_outside_unit_cell=bonded_sites_outside_unit_cell,
    )

    color_edges = False
    if color_edges_by_edge_weight:

        weights = [e[2].get("weight") for e in self.graph.edges(data=True)]
        weights = np.array([w for w in weights if w])

        if any(weights):

            cmap = get_cmap(edge_weight_color_scale)

            # try to keep color scheme symmetric around 0
            weight_max = max([abs(min(weights)), max(weights)])
            weight_min = -weight_max

            def get_weight_color(weight):
                if not weight:
                    weight = 0
                x = (weight - weight_min) / (weight_max - weight_min)
                return "#{:02x}{:02x}{:02x}".format(
                    *[int(c * 255) for c in cmap(x)[0:3]])

            color_edges = True

    idx_to_wyckoff = {}
    if group_by_symmetry:
        sga = SpacegroupAnalyzer(self.structure)
        struct_sym = sga.get_symmetrized_structure()
        for equiv_idxs, wyckoff in zip(struct_sym.equivalent_indices,
                                       struct_sym.wyckoff_symbols):
            for idx in equiv_idxs:
                idx_to_wyckoff[idx] = wyckoff

    for (idx, jimage) in sites_to_draw:

        site = self.structure[idx]
        if jimage != (0, 0, 0):
            connected_sites = self.get_connected_sites(idx, jimage=jimage)
            site = PeriodicSite(
                site.species,
                np.add(site.frac_coords, jimage),
                site.lattice,
                properties=site.properties,
            )
        else:
            connected_sites = self.get_connected_sites(idx)

        connected_sites = [
            cs for cs in connected_sites
            if (cs.index, cs.jimage) in sites_to_draw
        ]
        connected_sites_not_drawn = [
            cs for cs in connected_sites
            if (cs.index, cs.jimage) not in sites_to_draw
        ]

        if color_edges:

            connected_sites_colors = [
                get_weight_color(cs.weight) for cs in connected_sites
            ]
            connected_sites_not_drawn_colors = [
                get_weight_color(cs.weight) for cs in connected_sites_not_drawn
            ]

        else:

            connected_sites_colors = None
            connected_sites_not_drawn_colors = None

        site_scene = site.get_scene(
            connected_sites=connected_sites,
            connected_sites_not_drawn=connected_sites_not_drawn,
            hide_incomplete_edges=hide_incomplete_edges,
            incomplete_edge_length_scale=incomplete_edge_length_scale,
            connected_sites_colors=connected_sites_colors,
            connected_sites_not_drawn_colors=connected_sites_not_drawn_colors,
            explicitly_calculate_polyhedra_hull=
            explicitly_calculate_polyhedra_hull,
            legend=legend,
        )
        for scene in site_scene.contents:
            if group_by_symmetry and scene.name == "atoms" and idx in idx_to_wyckoff:
                # will rename to e.g. atoms_N_4e
                scene.name = f"atoms_{site_scene.name}_{idx_to_wyckoff[idx]}"
                # this is a proof-of-concept to demonstrate hover labels, could create label
                # automatically from site properties instead
                scene.contents[
                    0].tooltip = f"{site_scene.name} ({idx_to_wyckoff[idx]})"
            primitives[scene.name] += scene.contents

    primitives["unit_cell"].append(self.structure.lattice.get_scene())

    return Scene(
        name="StructureGraph",
        origin=origin,
        contents=[
            Scene(name=k, contents=v, origin=origin)
            for k, v in primitives.items()
        ],
    )
def get_structure_graph_scene(
    self,
    origin=None,
    draw_image_atoms=True,
    bonded_sites_outside_unit_cell=True,
    hide_incomplete_edges=False,
    incomplete_edge_length_scale=0.3,
    color_edges_by_edge_weight=True,
    edge_weight_color_scale="coolwarm",
    explicitly_calculate_polyhedra_hull=False,
    legend: Optional[Legend] = None,
) -> Scene:

    origin = origin or list(
        -self.structure.lattice.get_cartesian_coords([0.5, 0.5, 0.5]))

    legend = legend or Legend(self.structure)

    primitives = defaultdict(list)

    sites_to_draw = self._get_sites_to_draw(
        draw_image_atoms=draw_image_atoms,
        bonded_sites_outside_unit_cell=bonded_sites_outside_unit_cell,
    )

    color_edges = False
    if color_edges_by_edge_weight:

        weights = [e[2].get("weight") for e in self.graph.edges(data=True)]
        weights = np.array([w for w in weights if w])

        if any(weights):

            cmap = get_cmap(edge_weight_color_scale)

            # try to keep color scheme symmetric around 0
            weight_max = max([abs(min(weights)), max(weights)])
            weight_min = -weight_max

            def get_weight_color(weight):
                if not weight:
                    weight = 0
                x = (weight - weight_min) / (weight_max - weight_min)
                return "#{:02x}{:02x}{:02x}".format(
                    *[int(c * 255) for c in cmap(x)[0:3]])

            color_edges = True

    for (idx, jimage) in sites_to_draw:

        site = self.structure[idx]
        if jimage != (0, 0, 0):
            connected_sites = self.get_connected_sites(idx, jimage=jimage)
            site = PeriodicSite(
                site.species,
                np.add(site.frac_coords, jimage),
                site.lattice,
                properties=site.properties,
            )
        else:
            connected_sites = self.get_connected_sites(idx)

        connected_sites = [
            cs for cs in connected_sites
            if (cs.index, cs.jimage) in sites_to_draw
        ]
        connected_sites_not_drawn = [
            cs for cs in connected_sites
            if (cs.index, cs.jimage) not in sites_to_draw
        ]

        if color_edges:

            connected_sites_colors = [
                get_weight_color(cs.weight) for cs in connected_sites
            ]
            connected_sites_not_drawn_colors = [
                get_weight_color(cs.weight) for cs in connected_sites_not_drawn
            ]

        else:

            connected_sites_colors = None
            connected_sites_not_drawn_colors = None

        site_scene = site.get_scene(
            connected_sites=connected_sites,
            connected_sites_not_drawn=connected_sites_not_drawn,
            hide_incomplete_edges=hide_incomplete_edges,
            incomplete_edge_length_scale=incomplete_edge_length_scale,
            connected_sites_colors=connected_sites_colors,
            connected_sites_not_drawn_colors=connected_sites_not_drawn_colors,
            explicitly_calculate_polyhedra_hull=
            explicitly_calculate_polyhedra_hull,
            legend=legend,
        )
        for scene in site_scene.contents:
            primitives[scene.name] += scene.contents

    primitives["unit_cell"].append(self.structure.lattice.get_scene())

    return Scene(
        name=self.structure.composition.reduced_formula,
        origin=origin,
        contents=[
            Scene(name=k, contents=v, origin=origin)
            for k, v in primitives.items()
        ],
    )
Exemple #18
0
def get_site_scene(
    self,
    connected_sites: List[ConnectedSite] = None,
    # connected_site_metadata: None,
    # connected_sites_to_draw,
    connected_sites_not_drawn: List[ConnectedSite] = None,
    hide_incomplete_edges: bool = False,
    incomplete_edge_length_scale: Optional[float] = 1.0,
    connected_sites_colors: Optional[List[str]] = None,
    connected_sites_not_drawn_colors: Optional[List[str]] = None,
    origin: Optional[List[float]] = None,
    draw_polyhedra: bool = True,
    explicitly_calculate_polyhedra_hull: bool = False,
    bond_radius: float = 0.1,
    draw_magmoms: bool = True,
    magmom_scale: float = 1.0,
    legend: Optional[Legend] = None,
) -> Scene:
    """

    Args:
        connected_sites:
        connected_sites_not_drawn:
        hide_incomplete_edges:
        incomplete_edge_length_scale:
        connected_sites_colors:
        connected_sites_not_drawn_colors:
        origin:
        explicitly_calculate_polyhedra_hull:
        legend:

    Returns:

    """

    atoms = []
    bonds = []
    polyhedron = []
    magmoms = []

    legend = legend or Legend(self)

    # for disordered structures
    is_ordered = self.is_ordered
    phiStart, phiEnd = None, None
    occu_start = 0.0

    position = self.coords.tolist()

    radii = [legend.get_radius(sp, site=self) for sp in self.species.keys()]
    max_radius = float(min(radii))

    for idx, (sp, occu) in enumerate(self.species.items()):

        if isinstance(sp, DummySpecie):

            cube = Cubes(
                positions=[position], color=legend.get_color(sp, site=self), width=0.4
            )
            atoms.append(cube)

        else:

            color = legend.get_color(sp, site=self)
            radius = legend.get_radius(sp, site=self)

            # TODO: make optional/default to None
            # in disordered structures, we fractionally color-code spheres,
            # drawing a sphere segment from phi_end to phi_start
            # (think a sphere pie chart)
            if not is_ordered:
                phi_frac_end = occu_start + occu
                phi_frac_start = occu_start
                occu_start = phi_frac_end
                phiStart = phi_frac_start * np.pi * 2
                phiEnd = phi_frac_end * np.pi * 2

            name = str(sp)
            if occu != 1.0:
                name += " ({}% occupancy)".format(occu)
            name += f" ({position[0]:.3f}, {position[1]:.3f}, {position[2]:.3f})"

            if self.properties:
                for k, v in self.properties.items():
                    name += f" ({k} = {v})"

            sphere = Spheres(
                positions=[position],
                color=color,
                radius=radius,
                phiStart=phiStart,
                phiEnd=phiEnd,
                clickable=True,
                tooltip=name,
            )
            atoms.append(sphere)

        # Add magmoms
        if draw_magmoms:
            if magmom := self.properties.get("magmom"):
                # enforce type
                magmom = np.array(Magmom(magmom).get_moment())
                magmom = 2 * magmom_scale * max_radius * magmom
                tail = np.array(position) - 0.5 * np.array(magmom)
                head = np.array(position) + 0.5 * np.array(magmom)

                arrow = Arrows(
                    positionPairs=[[tail, head]],
                    color="red",
                    radius=0.20,
                    headLength=0.5,
                    headWidth=0.4,
                    clickable=True,
                )
                magmoms.append(arrow)
Exemple #19
0
def get_structure_graph_scene(
    self,
    origin=None,
    draw_image_atoms=True,
    bonded_sites_outside_unit_cell=True,
    hide_incomplete_edges=False,
    incomplete_edge_length_scale=0.3,
    color_edges_by_edge_weight=False,
    edge_weight_color_scale="coolwarm",
    explicitly_calculate_polyhedra_hull=False,
    legend: Optional[Legend] = None,
    group_by_site_property: Optional[str] = None,
    bond_radius: float = 0.1,
) -> Scene:

    origin = origin or list(
        -self.structure.lattice.get_cartesian_coords([0.5, 0.5, 0.5]))

    legend = legend or Legend(self.structure)

    # we get primitives from each site individually, then
    # combine into one big Scene
    primitives = defaultdict(list)

    sites_to_draw = self._get_sites_to_draw(
        draw_image_atoms=draw_image_atoms,
        bonded_sites_outside_unit_cell=bonded_sites_outside_unit_cell,
    )

    color_edges = False
    if color_edges_by_edge_weight:

        weights = [e[2].get("weight") for e in self.graph.edges(data=True)]
        weights = np.array([w for w in weights if w])

        if any(weights):

            cmap = get_cmap(edge_weight_color_scale)

            # try to keep color scheme symmetric around 0
            weight_max = max([abs(min(weights)), max(weights)])
            weight_min = -weight_max

            def get_weight_color(weight):
                if not weight:
                    weight = 0
                x = (weight - weight_min) / (weight_max - weight_min)
                return "#{:02x}{:02x}{:02x}".format(
                    *[int(c * 255) for c in cmap(x)[0:3]])

            color_edges = True

    if group_by_site_property:
        # we will create sub-scenes for each group of atoms
        # for example, if the Structure has a "wyckoff" site property
        # this might be used to allow grouping by Wyckoff position,
        # this then changes mouseover/interaction behavior with this scene
        grouped_atom_scene_contents = defaultdict(list)

    for (idx, jimage) in sites_to_draw:

        site = self.structure[idx]
        if jimage != (0, 0, 0):
            connected_sites = self.get_connected_sites(idx, jimage=jimage)
            site = PeriodicSite(
                site.species,
                np.add(site.frac_coords, jimage),
                site.lattice,
                properties=site.properties,
            )
        else:
            connected_sites = self.get_connected_sites(idx)

        connected_sites = [
            cs for cs in connected_sites
            if (cs.index, cs.jimage) in sites_to_draw
        ]
        connected_sites_not_drawn = [
            cs for cs in connected_sites
            if (cs.index, cs.jimage) not in sites_to_draw
        ]

        if color_edges:

            connected_sites_colors = [
                get_weight_color(cs.weight) for cs in connected_sites
            ]
            connected_sites_not_drawn_colors = [
                get_weight_color(cs.weight) for cs in connected_sites_not_drawn
            ]

        else:

            connected_sites_colors = None
            connected_sites_not_drawn_colors = None

        site_scene = site.get_scene(
            connected_sites=connected_sites,
            connected_sites_not_drawn=connected_sites_not_drawn,
            hide_incomplete_edges=hide_incomplete_edges,
            incomplete_edge_length_scale=incomplete_edge_length_scale,
            connected_sites_colors=connected_sites_colors,
            connected_sites_not_drawn_colors=connected_sites_not_drawn_colors,
            explicitly_calculate_polyhedra_hull=
            explicitly_calculate_polyhedra_hull,
            legend=legend,
            bond_radius=bond_radius,
        )

        for scene in site_scene.contents:

            if group_by_site_property and scene.name == "atoms":

                group_name = f"{site.properties[group_by_site_property]}"
                scene.contents[0].tooltip = group_name
                grouped_atom_scene_contents[group_name] += scene.contents

            else:

                primitives[scene.name] += scene.contents

    if group_by_site_property:
        atoms_scenes: List[Scene] = []
        for k, v in grouped_atom_scene_contents.items():
            atoms_scenes.append(Scene(name=k, contents=v))
        primitives["atoms"] = atoms_scenes

    primitives["unit_cell"].append(self.structure.lattice.get_scene())

    # why primitives comprehension? just make explicit! more readable
    return Scene(
        name="StructureGraph",
        origin=origin,
        contents=[
            Scene(name=k, contents=v, origin=origin)
            for k, v in primitives.items()
        ],
    )
    def test_get_color(self):

        # test default

        legend = Legend(self.struct, color_scheme="VESTA")

        color = legend.get_color(self.sp0)
        assert color == "#ffcccc"

        # element-based color schemes shouldn't change if you supply a site
        color = legend.get_color(self.sp0, site=self.site0)
        assert color == "#ffcccc"

        color = legend.get_color(self.sp2)
        assert color == "#a67573"

        assert legend.get_legend()["colors"] == {
            "#a67573": "In",
            "#fe0300": "O",
            "#ffcccc": "H",
        }

        # test alternate

        legend = Legend(self.struct, color_scheme="Jmol")

        color = legend.get_color(self.sp0)
        assert color == "#ffffff"

        assert legend.get_legend()["colors"] == {
            "#a67573": "In",
            "#ff0d0d": "O",
            "#ffffff": "H",
        }

        # test coloring by site properties

        legend = Legend(self.struct, color_scheme="example_site_prop")

        color = legend.get_color(self.sp0, site=self.site0)
        assert color == "#b30326"

        color = legend.get_color(self.sp1, site=self.site1)
        assert color == "#dddcdb"

        color = legend.get_color(self.sp2, site=self.site2)
        assert color == "#7b9ef8"

        assert legend.get_legend()["colors"] == {
            "#7b9ef8": "-3.00",
            "#b30326": "5.00",
            "#dddcdb": "0.00",
        }

        # test accessible

        legend = Legend(self.struct, color_scheme="accessible")

        color = legend.get_color(self.sp0, site=self.site0)
        assert color == "#ffffff"

        color = legend.get_color(self.sp1, site=self.site1)
        assert color == "#d55e00"

        color = legend.get_color(self.sp2, site=self.site2)
        assert color == "#cc79a7"

        assert legend.get_legend()["colors"] == {
            "#cc79a7": "In",
            "#d55e00": "O",
            "#ffffff": "H",
        }

        # test disordered

        legend = Legend(self.struct_disordered)

        color = legend.get_color(self.site_d_sp0, site=self.site_d)
        assert color == "#a67573"

        color = legend.get_color(self.site_d_sp1, site=self.site_d)
        assert color == "#bfa6a6"

        assert legend.get_legend()["colors"] == {
            "#a67573": "In",
            "#bfa6a6": "Al",
            "#ff0d0d": "O",
            "#ffffff": "H",
        }

        # test categorical

        legend = Legend(self.struct,
                        color_scheme="example_categorical_site_prop")

        assert legend.get_legend()["colors"] == {
            "#377eb8": "8b",
            "#e41a1c": "4a"
        }

        # test pre-defined

        legend = Legend(self.struct_manual)

        assert legend.get_legend()["colors"] == {
            "#0000ff": "O2-",
            "#00ff00": "In",
            "#ff0000": "H",
        }