Skip to content

Commit 45c9975

Browse files
Chiu PeterChiu Peter
authored andcommitted
update layout
1 parent 548c6fa commit 45c9975

File tree

1 file changed

+169
-55
lines changed

1 file changed

+169
-55
lines changed

crystal_toolkit/components/pourbaix.py

Lines changed: 169 additions & 55 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55
import numpy as np
66
import plotly.graph_objects as go
77
from dash import dcc, html
8-
from dash.dependencies import Component, Input, Output, State
8+
from dash.dependencies import Component, Input, Output
99
from dash.exceptions import PreventUpdate
1010
from frozendict import frozendict
1111
from pymatgen.analysis.pourbaix_diagram import PREFAC, PourbaixDiagram
@@ -31,6 +31,8 @@
3131

3232
HEIGHT = 550 # in px
3333
WIDTH = 700 # in px
34+
MIN_CONCENTRATION = 1e-8
35+
MAX_CONCENTRATION = 5
3436

3537

3638
class PourbaixDiagramComponent(MPComponent):
@@ -405,50 +407,50 @@ def get_figure(
405407
)
406408
)
407409
layout.update({"annotations": []})
408-
else:
409-
# Add annotations to layout to make text more readable when displaying heatmaps
410-
411-
# TODO: this doesn't work yet; resolve or scrap
412-
# cmap = get_cmap(PourbaixDiagramComponent.colorscale)
413-
# def get_text_color(x, y):
414-
# """
415-
# Set text color based on whether background at that point is dark or light.
416-
# """
417-
# energy = pourbaix_diagram.get_decomposition_energy(entry, pH=x, V=y)
418-
# c = [int(c * 255) for c in cmap(energy)[0:3]]
419-
# # borrowed from crystal_toolkit.components.structure
420-
# # TODO: move to utility function and ensure correct attribution for magic numbers
421-
# if 1 - (c[0] * 0.299 + c[1] * 0.587 + c[2] * 0.114) / 255 < 0.5:
422-
# font_color = "#000000"
423-
# else:
424-
# font_color = "#ffffff"
425-
# #print(energy, c, font_color)
426-
# return font_color
427-
428-
def get_text_size(available_vertical_space):
429-
"""Set text size based on available vertical space."""
430-
return min(max(6 * available_vertical_space, 12), 20)
431-
432-
annotations = [
433-
{
434-
"align": "center",
435-
"bgcolor": "white",
436-
"font": {"color": "black", "size": get_text_size(height)},
437-
"opacity": 1,
438-
"showarrow": False,
439-
"text": label,
440-
"x": x,
441-
"xanchor": "center",
442-
"yanchor": "auto",
443-
# "xshift": -10,
444-
# "yshift": -10,
445-
"xref": "x",
446-
"y": y,
447-
"yref": "y",
448-
}
449-
for (x, y), label, height in zip(xy_data, labels, domain_heights)
450-
]
451-
layout.update({"annotations": annotations})
410+
# else:
411+
# Add annotations to layout to make text more readable when displaying heatmaps
412+
413+
# TODO: this doesn't work yet; resolve or scrap
414+
# cmap = get_cmap(PourbaixDiagramComponent.colorscale)
415+
# def get_text_color(x, y):
416+
# """
417+
# Set text color based on whether background at that point is dark or light.
418+
# """
419+
# energy = pourbaix_diagram.get_decomposition_energy(entry, pH=x, V=y)
420+
# c = [int(c * 255) for c in cmap(energy)[0:3]]
421+
# # borrowed from crystal_toolkit.components.structure
422+
# # TODO: move to utility function and ensure correct attribution for magic numbers
423+
# if 1 - (c[0] * 0.299 + c[1] * 0.587 + c[2] * 0.114) / 255 < 0.5:
424+
# font_color = "#000000"
425+
# else:
426+
# font_color = "#ffffff"
427+
# #print(energy, c, font_color)
428+
# return font_color
429+
430+
# def get_text_size(available_vertical_space):
431+
# """Set text size based on available vertical space."""
432+
# return min(max(6 * available_vertical_space, 12), 20)
433+
434+
# annotations = [
435+
# {
436+
# "align": "center",
437+
# "bgcolor": "white",
438+
# "font": {"color": "black", "size": get_text_size(height)},
439+
# "opacity": 1,
440+
# "showarrow": False,
441+
# "text": label,
442+
# "x": x,
443+
# "xanchor": "center",
444+
# "yanchor": "auto",
445+
# # "xshift": -10,
446+
# # "yshift": -10,
447+
# "xref": "x",
448+
# "y": y,
449+
# "yref": "y",
450+
# }
451+
# for (x, y), label, height in zip(xy_data, labels, domain_heights)
452+
# ]
453+
# layout.update({"annotations": annotations}) # shouldn't have annotation when heatmap_entry presents
452454

453455
# Get data for heatmap
454456
if heatmap_entry is not None:
@@ -562,7 +564,8 @@ def _sub_layouts(self) -> dict[str, Component]:
562564
[
563565
self.get_bool_input(
564566
"filter_solids",
565-
state=self.default_state,
567+
# state=self.default_state,
568+
default=self.default_state["filter_solids"],
566569
label="Filter Solids",
567570
help_str="Whether to filter solid phases by stability on the compositional phase diagram. "
568571
"The practical consequence of this is that highly oxidized or reduced phases that "
@@ -571,9 +574,16 @@ def _sub_layouts(self) -> dict[str, Component]:
571574
"overstabilized from DFT errors). Hence, including only the stable solid phases generally "
572575
"leads to the most accurate Pourbaix diagrams.",
573576
),
577+
html.Div(
578+
[
579+
html.Div(id=self.id("element_specific_controls")),
580+
ctl.Block(html.Div(id=self.id("display-composition"))),
581+
]
582+
),
574583
self.get_bool_input(
575584
"show_heatmap", # kwarg_label
576-
state=self.default_state,
585+
# state=self.default_state,
586+
default=self.default_state["show_heatmap"],
577587
label="Show Heatmap",
578588
help_str="Hide or show a heatmap showing the decomposition energy for a specific "
579589
"entry in this system.",
@@ -598,7 +608,6 @@ def _sub_layouts(self) -> dict[str, Component]:
598608
id=self.id("heatmap_choice_container"),
599609
style={"width": "250px"}, # better to assign a class for selection
600610
),
601-
html.Div(id=self.id("element_specific_controls")),
602611
]
603612
)
604613

@@ -629,6 +638,8 @@ def update_heatmap_choices(entries, mat_detials):
629638
if not entries:
630639
raise PreventUpdate
631640

641+
print("should be 4")
642+
632643
options = []
633644
for entry in entries:
634645
if entry["entry_id"].startswith("mp"):
@@ -682,6 +693,94 @@ def update_heatmap_choices(entries, mat_detials):
682693
),
683694
]
684695

696+
@app.callback(
697+
Output(self.id("element_specific_controls"), "children"),
698+
Input(self.id(), "data"),
699+
# Input(self.get_kwarg_id("heatmap_choice"), "value"),
700+
# State(self.get_kwarg_id("show_heatmap"), "value"),
701+
prevent_initial_call=True,
702+
)
703+
def update_element_specific_sliders(
704+
entries,
705+
): # , heatmap_choice, show_heatmap):
706+
"""
707+
When pourbaix entries input, add concentration and composition options
708+
"""
709+
if not entries:
710+
raise PreventUpdate
711+
712+
print("should be 1")
713+
elements = set()
714+
715+
# kwargs = self.reconstruct_kwargs_from_state()
716+
# heatmap_choice = kwargs.get("heatmap_choice", None)
717+
# show_heatmap = kwargs.get("show_heatmap", False)
718+
# heatmap_entry = None
719+
720+
for entry in entries:
721+
if entry["entry_id"].startswith("mp"):
722+
composition = Composition(entry["entry"]["composition"])
723+
elements.update(composition.elements)
724+
# if entry["entry_id"] == heatmap_choice:
725+
# heatmap_entry = entry
726+
727+
# exclude O and H
728+
elements = elements - ELEMENTS_HO
729+
730+
comp_defaults = {element: 1 / len(elements) for element in elements}
731+
732+
comp_inputs = []
733+
conc_inputs = []
734+
for element in sorted(elements):
735+
if len(elements) > 1:
736+
comp_input = html.Div(
737+
[
738+
self.get_slider_input(
739+
f"comp-{element}",
740+
default=comp_defaults[element],
741+
label=f"Composition of {element}",
742+
domain=[0, 1],
743+
step=0.01,
744+
)
745+
]
746+
)
747+
comp_inputs.append(comp_input)
748+
749+
conc_input = html.Div(
750+
[
751+
self.get_numerical_input(
752+
f"conc-{element}",
753+
default=1e-6,
754+
min=MIN_CONCENTRATION,
755+
max=MAX_CONCENTRATION,
756+
label=f"Concentration of {element} ion",
757+
style={"width": "10rem"},
758+
)
759+
]
760+
)
761+
762+
conc_inputs.append(conc_input)
763+
764+
comp_conc_controls = []
765+
# comp_conc_controls.append(
766+
# ctl.Block(html.Div(id=self.id("display-composition")))
767+
# )
768+
# if comp_inputs and (not show_heatmap) and (not heatmap_entry):
769+
# comp_conc_controls += comp_inputs
770+
comp_conc_controls += comp_inputs
771+
772+
ion_label = (
773+
"Set Ion Concentrations"
774+
if len(elements) > 1
775+
else "Set Ion Concentration"
776+
)
777+
comp_conc_controls.append(ctl.Label(ion_label))
778+
779+
comp_conc_controls += conc_inputs
780+
781+
return html.Div(comp_conc_controls)
782+
783+
"""
685784
@app.callback(
686785
Output(self.id("element_specific_controls"), "children"),
687786
Output(self.id("ext-link"), "hidden"),
@@ -763,21 +862,26 @@ def update_element_specific_sliders(entries, heatmap_choice, show_heatmap):
763862
)
764863
765864
return html.Div(comp_conc_controls), False, external_link
865+
"""
766866

767867
@app.callback(
768868
Output(self.id("display-composition"), "children"),
769-
Input(self.get_all_kwargs_id(), "value"),
869+
Input(self.id("element_specific_controls"), "children"),
870+
prevent_initial_call=True,
871+
# Input(self.get_all_kwargs_id(), "value"),
770872
)
771-
def update_displayed_composition(*args):
873+
def update_displayed_composition(dependency): # **kwargs):
772874
kwargs = self.reconstruct_kwargs_from_state()
773875

876+
print("should be 2")
877+
774878
comp_dict = {}
775879
for key, val in kwargs.items():
776880
if "comp" in key: # keys are encoded like "comp-Ag"
777881
el = key.split("-")[1]
778882
comp_dict[el] = val
779883
comp_dict = comp_dict or None
780-
884+
print(comp_dict)
781885
if not comp_dict:
782886
return ""
783887

@@ -795,23 +899,30 @@ def update_displayed_composition(*args):
795899

796900
@cache.memoize(timeout=5 * 60)
797901
def get_pourbaix_diagram(pourbaix_entries, **kwargs):
902+
print("yeee")
903+
print(kwargs)
798904
return PourbaixDiagram(pourbaix_entries, **kwargs)
799905

800906
@app.callback(
801907
Output(self.id("graph"), "figure"),
802908
Input(self.id(), "data"),
909+
Input(self.id("display-composition"), "children"),
803910
Input(self.get_all_kwargs_id(), "value"),
804911
)
805-
def make_figure(pourbaix_entries, *args) -> go.Figure:
912+
def make_figure(pourbaix_entries, dependency, kwargs) -> go.Figure:
913+
# show_heatmap, heatmap_choice, filter_solids
806914
if pourbaix_entries is None:
807915
raise PreventUpdate
808916

917+
print("should be 3")
918+
809919
kwargs = self.reconstruct_kwargs_from_state()
920+
print(kwargs)
810921

811922
pourbaix_entries = self.from_data(pourbaix_entries)
812923

813924
# Get heatmap id
814-
if kwargs["show_heatmap"] and kwargs.get("heatmap_choice"):
925+
if kwargs.get("show_heatmap") and kwargs.get("heatmap_choice"):
815926
# get Entry object based on the heatmap_choice, which is entry_id string
816927
heatmap_entry = next(
817928
entry
@@ -837,7 +948,8 @@ def make_figure(pourbaix_entries, *args) -> go.Figure:
837948
el = key.split("-")[1]
838949
comp_dict[el] = val
839950
comp_dict = comp_dict or None
840-
951+
print("yee2")
952+
print(comp_dict)
841953
conc_dict = {}
842954
# e.g. kwargs contains {"conc-Ag": 1e-6, "conc-Fe": 1e-4},
843955
# essentially {slider_name: slider_value}
@@ -846,13 +958,15 @@ def make_figure(pourbaix_entries, *args) -> go.Figure:
846958
el = key.split("-")[1]
847959
conc_dict[el] = val
848960
conc_dict = conc_dict or None
849-
961+
print("yeee2")
962+
print(conc_dict)
850963
pourbaix_diagram = get_pourbaix_diagram(
851964
pourbaix_entries,
852965
comp_dict=comp_dict,
853966
conc_dict=conc_dict,
854967
filter_solids=kwargs["filter_solids"],
855968
)
969+
856970
self.logger.debug( # noqa: PLE1205
857971
"Generated pourbaix diagram",
858972
len(pourbaix_entries),

0 commit comments

Comments
 (0)