From f2e956daf4c561c9f072d360224969c607b93573 Mon Sep 17 00:00:00 2001 From: Chiu Peter Date: Thu, 6 Nov 2025 12:01:49 -0800 Subject: [PATCH 1/3] update comp-conc panel and filter solid tip --- crystal_toolkit/components/pourbaix.py | 114 ++++++++++++++++++------- 1 file changed, 84 insertions(+), 30 deletions(-) diff --git a/crystal_toolkit/components/pourbaix.py b/crystal_toolkit/components/pourbaix.py index 89e63b26..490710af 100644 --- a/crystal_toolkit/components/pourbaix.py +++ b/crystal_toolkit/components/pourbaix.py @@ -440,11 +440,10 @@ def _sub_layouts(self) -> dict[str, Component]: default=self.default_state["filter_solids"], label="Filter Solids", help_str="Whether to filter solid phases by stability on the compositional phase diagram. " - "The practical consequence of this is that highly oxidized or reduced phases that " - "might show up in experiments due to kinetic limitations on oxygen/hydrogen evolution " - "won't appear in the diagram, but they are not actually “stable” (and are frequently " - "overstabilized from DFT errors). Hence, including only the stable solid phases generally " - "leads to the most accurate Pourbaix diagrams.", + "The practical consequence of this is that we only include materials that are predicted to " + "be thermodynamically stable at RT within the limitations of DFT. Notably, there may be " + "disagreements with experiments e.g., highly oxidized or reduced phases, which are kinetically " + "stabilized through surface passivation.", ), html.Div( [ @@ -454,23 +453,40 @@ def _sub_layouts(self) -> dict[str, Component]: id=self.id("invalid-comp-alarm"), message="Illegal composition entry!", ), - html.H5( - "Composition", - id=self.id("composition-title"), - style={"fontWeight": "bold"}, + html.Div( + [ + html.H5( + "Composition Control", + style={ + "fontWeight": "bold", + "textAlign": "center", + }, + ), + html.H5( + "Composition of", + id=self.id("composition-title"), + # style={"fontWeight": "bold"}, + ), + ] ), dcc.Input( id=self.id("comp-text"), type="text", - # placeholder="composition e.g. 1:1:1", - ), - html.Button( - "Update", - id=self.id("comp-btn"), + style={ + "textAlign": "center", + "width": "10rem", + "marginRight": "0.2rem", + "marginBottom": "0.2rem", + "height": "36px", + "fontSize": "14px", + }, ), ctl.Block(html.Div(id=self.id("display-composition"))), - html.Br(), - html.Br(), + html.Hr( + style={ + "backgroundColor": "#C5C5C6", + } + ), dcc.Store(id=self.id("elements-store")), ], id=self.id("comp-panel"), @@ -486,8 +502,18 @@ def _sub_layouts(self) -> dict[str, Component]: id=self.id("conc-panel"), style={"display": "none"}, ), - html.Div(id=self.id("element_specific_controls")), - ] + html.Div( + id=self.id("element_specific_controls"), + ), + html.Button( + "Update", + id=self.id("comp-conc-btn"), + style={"display": "none"}, + ), + ], + style={ + "backgroundColor": "#F1F1F5", + }, ), self.get_bool_input( "show_heatmap", # kwarg_label @@ -629,6 +655,8 @@ def update_heatmap_choices(entries, mat_detials, filter_solids): Output(self.id("elements-store"), "data"), Output(self.id("comp-text"), "value"), Output(self.id("composition-title"), "children"), + Output(self.id("comp-conc-btn"), "children"), + Output(self.id("comp-conc-btn"), "style"), Input(self.id(), "data"), prevent_initial_call=True, ) @@ -662,7 +690,7 @@ def update_element_specific_sliders( default=1e-6, min=MIN_CONCENTRATION, max=MAX_CONCENTRATION, - label=f"Concentration of {element} ion", + label=f"concentration of {element} ion", style={"width": "10rem", "fontSize": "14px"}, ) ] @@ -674,27 +702,43 @@ def update_element_specific_sliders( comp_conc_controls += comp_inputs ion_label = ( - "Set Ion Concentrations (M)" + "Ion Concentrations Control" if len(elements) > 1 - else "Set Ion Concentration" + else "Ion Concentration Control" + ) + + comp_conc_controls.append( + html.H5( + ion_label, + style={"fontWeight": "bold", "textAlign": "center"}, + ), + ) + comp_conc_controls.append( + html.H6( + f"💡 Set the range between {MIN_CONCENTRATION} and {MAX_CONCENTRATION} (M)" + ) ) - comp_conc_controls.append(ctl.Label(ion_label)) comp_conc_controls += conc_inputs - # + # comp_panel_style comp_panel_style = {"display": "none"} if len(elements) > 1: comp_panel_style = {"display": "block"} - # + # elements store elements = [element.symbol for element in elements] - # + # default_comp default_comp = ":".join(["1" for _ in elements]) - # - title = "Composition of " + ":".join(elements) + # composition title + title = "💡 Composition of " + ":".join(elements) + + # update_string + update_string = "Concentration update" + if len(elements) > 1: + update_string = "Composition & concentration update" return ( html.Div(comp_conc_controls), @@ -702,6 +746,8 @@ def update_element_specific_sliders( elements, default_comp, title, + update_string, + {"display": "block"}, ) @cache.memoize(timeout=5 * 60) @@ -715,11 +761,14 @@ def get_pourbaix_diagram(pourbaix_entries, **kwargs): Output(self.id("display-composition"), "children"), Input(self.id(), "data"), Input(self.id("display-composition"), "children"), - Input(self.get_all_kwargs_id(), "value"), - Input(self.id("comp-btn"), "n_clicks"), + State(self.get_all_kwargs_id(), "value"), + Input(self.id("comp-conc-btn"), "n_clicks"), State(self.id("elements-store"), "data"), State(self.id("comp-text"), "value"), Input(self.id("element_specific_controls"), "children"), + Input(self.get_kwarg_id("filter_solids"), "value"), + Input(self.get_kwarg_id("show_heatmap"), "value"), + Input(self.get_kwarg_id("heatmap_choice"), "value"), prevent_initial_call=True, ) def make_figure( @@ -730,11 +779,14 @@ def make_figure( elements, comp_text, dependency2, + dependency3, + dependency4, + dependency5, ) -> go.Figure: if pourbaix_entries is None: raise PreventUpdate - # check if composition input + # Only update if n_clicks: raw_comp_list = comp_text.split(":") else: @@ -757,6 +809,7 @@ def make_figure( return (self.get_figure_div(), True, False, "") kwargs = self.reconstruct_kwargs_from_state() + print(kwargs) pourbaix_entries = self.from_data(pourbaix_entries) @@ -808,6 +861,7 @@ def make_figure( pourbaix_diagram, heatmap_entry=heatmap_entry, ) + return ( self.get_figure_div(figure=figure), False, From e624fd6adc500b37824c10d8badf453fbca9f8ea Mon Sep 17 00:00:00 2001 From: Chiu Peter Date: Thu, 20 Nov 2025 17:48:40 -0800 Subject: [PATCH 2/3] rename callback arguments --- crystal_toolkit/components/pourbaix.py | 13 ++++++------- 1 file changed, 6 insertions(+), 7 deletions(-) diff --git a/crystal_toolkit/components/pourbaix.py b/crystal_toolkit/components/pourbaix.py index 490710af..de71c388 100644 --- a/crystal_toolkit/components/pourbaix.py +++ b/crystal_toolkit/components/pourbaix.py @@ -747,7 +747,7 @@ def update_element_specific_sliders( default_comp, title, update_string, - {"display": "block"}, + {"display": "block", "height": "36px"}, ) @cache.memoize(timeout=5 * 60) @@ -773,15 +773,15 @@ def get_pourbaix_diagram(pourbaix_entries, **kwargs): ) def make_figure( pourbaix_entries, - dependency, + display_composition, kwargs, n_clicks, elements, comp_text, - dependency2, - dependency3, - dependency4, - dependency5, + element_specific_controls, + filter_solids, + show_heatmap, + heatmap_choice, ) -> go.Figure: if pourbaix_entries is None: raise PreventUpdate @@ -809,7 +809,6 @@ def make_figure( return (self.get_figure_div(), True, False, "") kwargs = self.reconstruct_kwargs_from_state() - print(kwargs) pourbaix_entries = self.from_data(pourbaix_entries) From fa71ef20656d6c832666c58fc85c3436f1f0684e Mon Sep 17 00:00:00 2001 From: Chiu Peter Date: Fri, 21 Nov 2025 14:13:19 -0800 Subject: [PATCH 3/3] center the input and adjust the space --- crystal_toolkit/components/pourbaix.py | 104 +++++++++++++++++-------- 1 file changed, 71 insertions(+), 33 deletions(-) diff --git a/crystal_toolkit/components/pourbaix.py b/crystal_toolkit/components/pourbaix.py index de71c388..84e3e743 100644 --- a/crystal_toolkit/components/pourbaix.py +++ b/crystal_toolkit/components/pourbaix.py @@ -42,6 +42,7 @@ MAX_PH = 16 MIN_V = -4 MAX_V = 4 +PANEL_LINE_HEIGHT = "2em" class PourbaixDiagramComponent(MPComponent): @@ -127,6 +128,16 @@ class PourbaixDiagramComponent(MPComponent): } ) + @staticmethod + def create_centered_object(content): + return html.Div( + content, + style={ + "display": "flex", + "justifyContent": "center", + }, + ) + @staticmethod def get_figure( pourbaix_diagram: PourbaixDiagram, heatmap_entry=None, show_water_lines=True @@ -460,6 +471,7 @@ def _sub_layouts(self) -> dict[str, Component]: style={ "fontWeight": "bold", "textAlign": "center", + "flex": "0 0 100%", }, ), html.H5( @@ -467,21 +479,36 @@ def _sub_layouts(self) -> dict[str, Component]: id=self.id("composition-title"), # style={"fontWeight": "bold"}, ), - ] - ), - dcc.Input( - id=self.id("comp-text"), - type="text", + ], style={ - "textAlign": "center", - "width": "10rem", - "marginRight": "0.2rem", - "marginBottom": "0.2rem", - "height": "36px", - "fontSize": "14px", + "line-height": PANEL_LINE_HEIGHT, + "display": "flex", + "flexWrap": "wrap", + "justifyContent": "center", }, ), - ctl.Block(html.Div(id=self.id("display-composition"))), + PourbaixDiagramComponent.create_centered_object( + dcc.Input( + id=self.id("comp-text"), + className="input", + type="text", + style={ + "textAlign": "center", + "width": "10rem", + "marginRight": "0.2rem", + "marginBottom": "0.2rem", + "height": "36px", + "fontSize": "14px", + }, + ), + ), + ctl.Block( + PourbaixDiagramComponent.create_centered_object( + html.Div( + id=self.id("display-composition"), + ) + ) + ), html.Hr( style={ "backgroundColor": "#C5C5C6", @@ -505,16 +532,19 @@ def _sub_layouts(self) -> dict[str, Component]: html.Div( id=self.id("element_specific_controls"), ), - html.Button( - "Update", - id=self.id("comp-conc-btn"), - style={"display": "none"}, + PourbaixDiagramComponent.create_centered_object( + html.Button( + "Update", + id=self.id("comp-conc-btn"), + style={"display": "none"}, + ), ), ], style={ "backgroundColor": "#F1F1F5", }, ), + html.Br(), self.get_bool_input( "show_heatmap", # kwarg_label # state=self.default_state, @@ -679,27 +709,26 @@ def update_element_specific_sliders( # exclude O and H elements = elements - ELEMENTS_HO - comp_inputs = [] conc_inputs = [] for element in sorted(elements): - conc_input = html.Div( - [ - self.get_numerical_input( - f"conc-{element}", - default=1e-6, - min=MIN_CONCENTRATION, - max=MAX_CONCENTRATION, - label=f"concentration of {element} ion", - style={"width": "10rem", "fontSize": "14px"}, - ) - ] + conc_input = PourbaixDiagramComponent.create_centered_object( + self.get_numerical_input( + f"conc-{element}", + default=1e-6, + min=MIN_CONCENTRATION, + max=MAX_CONCENTRATION, + label=f"concentration of {element} ion", + style={ + "width": "10rem", + "fontSize": "14px", + }, + ) ) conc_inputs.append(conc_input) comp_conc_controls = [] - comp_conc_controls += comp_inputs ion_label = ( "Ion Concentrations Control" @@ -714,8 +743,10 @@ def update_element_specific_sliders( ), ) comp_conc_controls.append( - html.H6( - f"💡 Set the range between {MIN_CONCENTRATION} and {MAX_CONCENTRATION} (M)" + PourbaixDiagramComponent.create_centered_object( + html.H6( + f"💡 Set the range between {MIN_CONCENTRATION} and {MAX_CONCENTRATION} (M)" + ) ) ) @@ -724,7 +755,9 @@ def update_element_specific_sliders( # comp_panel_style comp_panel_style = {"display": "none"} if len(elements) > 1: - comp_panel_style = {"display": "block"} + comp_panel_style = { + "display": "block", + } # elements store elements = [element.symbol for element in elements] @@ -741,7 +774,12 @@ def update_element_specific_sliders( update_string = "Composition & concentration update" return ( - html.Div(comp_conc_controls), + html.Div( + comp_conc_controls, + style={ + "line-height": PANEL_LINE_HEIGHT, + }, + ), comp_panel_style, elements, default_comp,