11from __future__ import annotations
22
3+ import logging
34import re
45
56import numpy as np
67import plotly .graph_objects as go
78from dash import dcc , html
8- from dash .dependencies import Component , Input , Output
9+ from dash .dependencies import Component , Input , Output , State
910from dash .exceptions import PreventUpdate
1011from frozendict import frozendict
1112from pymatgen .analysis .pourbaix_diagram import PREFAC , PourbaixDiagram
2122except ImportError :
2223 ELEMENTS_HO = {Element ("H" ), Element ("O" )}
2324
25+
26+ logger = logging .getLogger (__name__ )
2427__author__ = "Joseph Montoya"
25282629
3336WIDTH = 700 # in px
3437MIN_CONCENTRATION = 1e-8
3538MAX_CONCENTRATION = 5
39+ MIN_PH = - 2
40+ MAX_PH = 16
41+ MIN_V = - 4
42+ MAX_V = 4
3643
3744
3845class PourbaixDiagramComponent (MPComponent ):
@@ -51,13 +58,13 @@ class PourbaixDiagramComponent(MPComponent):
5158 "titlefont" : {"color" : "#000000" , "size" : 24.0 },
5259 "type" : "linear" ,
5360 "zeroline" : False ,
54- "range" : [- 2 , 16 ],
61+ "range" : [MIN_PH , MAX_PH ],
5562 },
5663 yaxis = {
5764 "title" : "Applied Potential (V vs. SHE)" ,
5865 "anchor" : "x" ,
5966 "mirror" : "ticks" ,
60- "range" : [- 2 , 4 ],
67+ "range" : [MIN_V , MAX_V ],
6168 "showgrid" : False ,
6269 "showline" : True ,
6370 "side" : "left" ,
@@ -454,8 +461,8 @@ def get_figure(
454461
455462 # Get data for heatmap
456463 if heatmap_entry is not None :
457- ph_range = np .arange (- 2 , 16 .001 , 0.1 )
458- v_range = np .arange (- 2 , 4 .001 , 0.1 )
464+ ph_range = np .arange (MIN_PH , MAX_PH + 0 .001 , 0.1 )
465+ v_range = np .arange (MIN_V , MAX_V + 0 .001 , 0.1 )
459466 ph_mesh , v_mesh = np .meshgrid (ph_range , v_range )
460467 decomposition_e = pourbaix_diagram .get_decomposition_energy (
461468 heatmap_entry , ph_mesh , v_mesh
@@ -514,11 +521,15 @@ def get_figure(
514521 hoverinfo = "text" ,
515522 name = f"{ heatmap_formula } ({ heatmap_entry .entry_id } ) Heatmap" ,
516523 showlegend = True ,
524+ contours = dict (
525+ start = 0 ,
526+ end = 1 ,
527+ ),
517528 )
518529 data .append (h_map )
519530
520531 if show_water_lines :
521- ph_range = [- 2 , 16 ]
532+ ph_range = [MIN_PH , MAX_PH ]
522533 # hydrogen line
523534 data .append (
524535 go .Scatter (
@@ -576,6 +587,33 @@ def _sub_layouts(self) -> dict[str, Component]:
576587 ),
577588 html .Div (
578589 [
590+ html .Div (
591+ [
592+ dcc .ConfirmDialog (
593+ id = self .id ("invalid-comp-alarm" ),
594+ message = "Illegal composition entry!" ,
595+ ),
596+ html .H5 (
597+ "Composition" ,
598+ id = self .id ("composition-title" ),
599+ style = {"fontWeight" : "bold" },
600+ ),
601+ dcc .Input (
602+ id = self .id ("comp-text" ),
603+ type = "text" ,
604+ # placeholder="composition e.g. 1:1:1",
605+ ),
606+ html .Button (
607+ "Update" ,
608+ id = self .id ("comp-btn" ),
609+ ),
610+ html .Br (),
611+ html .Br (),
612+ dcc .Store (id = self .id ("elements-store" )),
613+ ],
614+ id = self .id ("comp-panel" ),
615+ style = {"display" : "none" },
616+ ),
579617 html .Div (id = self .id ("element_specific_controls" )),
580618 ctl .Block (html .Div (id = self .id ("display-composition" ))),
581619 ]
@@ -625,7 +663,10 @@ def _sub_layouts(self) -> dict[str, Component]:
625663
626664 def layout (self ) -> html .Div :
627665 return html .Div (
628- children = [self ._sub_layouts ["options" ], self ._sub_layouts ["graph" ]]
666+ children = [
667+ self ._sub_layouts ["options" ],
668+ self ._sub_layouts ["graph" ],
669+ ]
629670 )
630671
631672 def generate_callbacks (self , app , cache ) -> None :
@@ -638,8 +679,6 @@ def update_heatmap_choices(entries, mat_detials):
638679 if not entries :
639680 raise PreventUpdate
640681
641- print ("should be 4" )
642-
643682 options = []
644683 for entry in entries :
645684 if entry ["entry_id" ].startswith ("mp" ):
@@ -695,6 +734,10 @@ def update_heatmap_choices(entries, mat_detials):
695734
696735 @app .callback (
697736 Output (self .id ("element_specific_controls" ), "children" ),
737+ Output (self .id ("comp-panel" ), "style" ),
738+ Output (self .id ("elements-store" ), "data" ),
739+ Output (self .id ("comp-text" ), "value" ),
740+ Output (self .id ("composition-title" ), "children" ),
698741 Input (self .id (), "data" ),
699742 # Input(self.get_kwarg_id("heatmap_choice"), "value"),
700743 # State(self.get_kwarg_id("show_heatmap"), "value"),
@@ -709,7 +752,6 @@ def update_element_specific_sliders(
709752 if not entries :
710753 raise PreventUpdate
711754
712- print ("should be 1" )
713755 elements = set ()
714756
715757 # kwargs = self.reconstruct_kwargs_from_state()
@@ -727,12 +769,13 @@ def update_element_specific_sliders(
727769 # exclude O and H
728770 elements = elements - ELEMENTS_HO
729771
730- comp_defaults = {element : 1 / len (elements ) for element in elements }
772+ # comp_defaults = {element: 1 / len(elements) for element in elements}
731773
732774 comp_inputs = []
733775 conc_inputs = []
734776 for element in sorted (elements ):
735777 if len (elements ) > 1 :
778+ """
736779 comp_input = html.Div(
737780 [
738781 self.get_slider_input(
@@ -745,6 +788,7 @@ def update_element_specific_sliders(
745788 ]
746789 )
747790 comp_inputs.append(comp_input)
791+ """
748792
749793 conc_input = html .Div (
750794 [
@@ -778,7 +822,25 @@ def update_element_specific_sliders(
778822
779823 comp_conc_controls += conc_inputs
780824
781- return html .Div (comp_conc_controls )
825+ #
826+ comp_panel_style = {"display" : "block" }
827+
828+ #
829+ elements = [element .symbol for element in elements ]
830+
831+ #
832+ default_comp = ":" .join (["1" for _ in elements ])
833+
834+ #
835+ title = "Composition of " + ":" .join (elements )
836+
837+ return (
838+ html .Div (comp_conc_controls ),
839+ comp_panel_style ,
840+ elements ,
841+ default_comp ,
842+ title ,
843+ )
782844
783845 """
784846 @app.callback(
@@ -873,15 +935,12 @@ def update_element_specific_sliders(entries, heatmap_choice, show_heatmap):
873935 def update_displayed_composition (dependency ): # **kwargs):
874936 kwargs = self .reconstruct_kwargs_from_state ()
875937
876- print ("should be 2" )
877-
878938 comp_dict = {}
879939 for key , val in kwargs .items ():
880940 if "comp" in key : # keys are encoded like "comp-Ag"
881941 el = key .split ("-" )[1 ]
882942 comp_dict [el ] = val
883943 comp_dict = comp_dict or None
884- print (comp_dict )
885944 if not comp_dict :
886945 return ""
887946
@@ -899,25 +958,48 @@ def update_displayed_composition(dependency): # **kwargs):
899958
900959 @cache .memoize (timeout = 5 * 60 )
901960 def get_pourbaix_diagram (pourbaix_entries , ** kwargs ):
902- print ("yeee" )
903- print (kwargs )
904961 return PourbaixDiagram (pourbaix_entries , ** kwargs )
905962
906963 @app .callback (
907964 Output (self .id ("graph" ), "figure" ),
965+ Output (self .id ("invalid-comp-alarm" ), "displayed" ),
908966 Input (self .id (), "data" ),
909967 Input (self .id ("display-composition" ), "children" ),
910968 Input (self .get_all_kwargs_id (), "value" ),
969+ Input (self .id ("comp-btn" ), "n_clicks" ),
970+ State (self .id ("elements-store" ), "data" ),
971+ State (self .id ("comp-text" ), "value" ),
972+ prevent_initial_call = True ,
911973 )
912- def make_figure (pourbaix_entries , dependency , kwargs ) -> go .Figure :
974+ def make_figure (
975+ pourbaix_entries , dependency , kwargs , n_clicks , elements , comp_text
976+ ) -> go .Figure :
913977 # show_heatmap, heatmap_choice, filter_solids
978+
914979 if pourbaix_entries is None :
915980 raise PreventUpdate
916981
917- print ("should be 3" )
982+ # check if composition input
983+ if n_clicks :
984+ raw_comp_list = comp_text .split (":" )
985+ else :
986+ raw_comp_list = [1 / len (elements ) for _ in elements ]
987+
988+ if len (raw_comp_list ) != len (elements ):
989+ logger .error ("Invalid composition input!" )
990+ return go .Figure (
991+ layout = {** PourbaixDiagramComponent .empty_plot_style }
992+ ), True
993+ try :
994+ # avoid direct type casting because string inputs may raise errors
995+ comp_list = [float (t ) for t in raw_comp_list ]
996+ except Exception :
997+ logger .error ("Invalid composition input!" )
998+ return go .Figure (
999+ layout = {** PourbaixDiagramComponent .empty_plot_style }
1000+ ), True
9181001
9191002 kwargs = self .reconstruct_kwargs_from_state ()
920- print (kwargs )
9211003
9221004 pourbaix_entries = self .from_data (pourbaix_entries )
9231005
@@ -943,13 +1025,17 @@ def make_figure(pourbaix_entries, dependency, kwargs) -> go.Figure:
9431025 comp_dict = {}
9441026 # e.g. kwargs contains {"comp-Ag": 0.5, "comp-Fe": 0.5},
9451027 # essentially {slider_name: slider_value}
1028+ """
9461029 for key, val in kwargs.items():
9471030 if "comp" in key: # keys are encoded like "comp-Ag"
9481031 el = key.split("-")[1]
9491032 comp_dict[el] = val
1033+ """
1034+ comp_dict = {}
1035+ for comp_val , element in zip (comp_list , elements ):
1036+ comp_dict [element ] = comp_val
1037+
9501038 comp_dict = comp_dict or None
951- print ("yee2" )
952- print (comp_dict )
9531039 conc_dict = {}
9541040 # e.g. kwargs contains {"conc-Ag": 1e-6, "conc-Fe": 1e-4},
9551041 # essentially {slider_name: slider_value}
@@ -958,8 +1044,6 @@ def make_figure(pourbaix_entries, dependency, kwargs) -> go.Figure:
9581044 el = key .split ("-" )[1 ]
9591045 conc_dict [el ] = val
9601046 conc_dict = conc_dict or None
961- print ("yeee2" )
962- print (conc_dict )
9631047 pourbaix_diagram = get_pourbaix_diagram (
9641048 pourbaix_entries ,
9651049 comp_dict = comp_dict ,
@@ -974,11 +1058,10 @@ def make_figure(pourbaix_entries, dependency, kwargs) -> go.Figure:
9741058 conc_dict ,
9751059 comp_dict ,
9761060 )
977-
9781061 return self .get_figure (
9791062 pourbaix_diagram ,
9801063 heatmap_entry = heatmap_entry ,
981- )
1064+ ), False
9821065
9831066 # TODO
9841067 # def graph_layout(self):
0 commit comments