From 4ac8a33b32b9bd50af38f70c13e1c93b6bd4d37a Mon Sep 17 00:00:00 2001 From: Miki Bonacci Date: Mon, 9 Dec 2024 10:39:25 +0000 Subject: [PATCH] more refactoring --- .../app/widgets/euphonicmodel.py | 15 +- .../app/widgets/euphonicwidget.py | 209 +++--------- .../widgets}/structurefactorwidget.py | 58 +--- .../utils/euphonic/data/structure_factors.py | 4 +- .../{ => detached_app}/Detached_app.ipynb | 0 .../{ => detached_app}/static/style.css | 0 .../{ => detached_app}/static/welcome.jinja | 0 .../euphonic/detached_app/uploadwidgets.py | 312 ++++++++++++++++++ 8 files changed, 387 insertions(+), 211 deletions(-) rename src/aiidalab_qe_vibroscopy/{utils/euphonic => app/widgets}/structurefactorwidget.py (88%) rename src/aiidalab_qe_vibroscopy/utils/euphonic/{ => detached_app}/Detached_app.ipynb (100%) rename src/aiidalab_qe_vibroscopy/utils/euphonic/{ => detached_app}/static/style.css (100%) rename src/aiidalab_qe_vibroscopy/utils/euphonic/{ => detached_app}/static/welcome.jinja (100%) create mode 100644 src/aiidalab_qe_vibroscopy/utils/euphonic/detached_app/uploadwidgets.py diff --git a/src/aiidalab_qe_vibroscopy/app/widgets/euphonicmodel.py b/src/aiidalab_qe_vibroscopy/app/widgets/euphonicmodel.py index f625c3f..500bd65 100644 --- a/src/aiidalab_qe_vibroscopy/app/widgets/euphonicmodel.py +++ b/src/aiidalab_qe_vibroscopy/app/widgets/euphonicmodel.py @@ -2,17 +2,21 @@ import traitlets as tl import copy -from aiidalab_qe_vibroscopy.utils.euphonic.data_manipulation.intensity_maps import ( +from aiidalab_qe_vibroscopy.utils.euphonic.data.structure_factors import ( AttrDict, produce_bands_weigthed_data, produce_powder_data, generated_curated_data, - par_dict, - par_dict_powder, export_euphonic_data, generate_force_constant_instance, ) +from aiidalab_qe_vibroscopy.utils.euphonic.data.parameters import ( + parameters_single_crystal, + parameters_powder, +) + + from aiidalab_qe_vibroscopy.utils.euphonic.tab_widgets.euphonic_q_planes_widgets import ( produce_Q_section_modes, produce_Q_section_spectrum, @@ -21,7 +25,7 @@ from aiidalab_qe.common.mvc import Model -class EuphonicBaseResultsModel(Model): +class EuphonicResultsModel(Model): """Model for the neutron scattering results panel.""" # Here we mode all the model and data-controller, i.e. all the data and their @@ -73,6 +77,9 @@ def reset( def fetch_data(self): """Fetch the data from the database or from the uploaded files.""" # 1. from aiida, so we have the node + if hasattr(self, "fc"): + # we already have the data (this happens if I clone the model with already the data inside) + return if self.node: ins_data = export_euphonic_data(self.node) self.fc = ins_data["fc"] diff --git a/src/aiidalab_qe_vibroscopy/app/widgets/euphonicwidget.py b/src/aiidalab_qe_vibroscopy/app/widgets/euphonicwidget.py index 2a3ad09..8808fb3 100644 --- a/src/aiidalab_qe_vibroscopy/app/widgets/euphonicwidget.py +++ b/src/aiidalab_qe_vibroscopy/app/widgets/euphonicwidget.py @@ -1,117 +1,20 @@ import pathlib import tempfile - - +import ipywidgets as ipw from IPython.display import display -import ipywidgets as ipw +from aiidalab_qe.common.widgets import LoadingWidget -# from ..euphonic.bands_pdos import * -from aiidalab_qe_vibroscopy.utils.euphonic.data_manipulation.intensity_maps import ( +from aiidalab_qe_vibroscopy.utils.euphonic.data.structure_factors import ( generate_force_constant_instance, - export_euphonic_data, # noqa: F401 -) -from aiidalab_qe_vibroscopy.utils.euphonic.tab_widgets.euphonic_single_crystal_widgets import ( - SingleCrystalFullWidget, -) -from aiidalab_qe_vibroscopy.utils.euphonic.tab_widgets.euphonic_powder_widgets import ( - PowderFullWidget, ) -from aiidalab_qe_vibroscopy.utils.euphonic.tab_widgets.euphonic_q_planes_widgets import ( - QSectionFullWidget, -) - - -from aiidalab_qe.common.widgets import LoadingWidget -###### START for detached app: - - -# Upload buttons -class UploadPhonopyYamlWidget(ipw.FileUpload): - def __init__(self, **kwargs): - super().__init__( - description="upload phonopy YAML file", - multiple=False, - layout={"width": "initial"}, - ) - -class UploadForceConstantsHdf5Widget(ipw.FileUpload): - def __init__(self, **kwargs): - super().__init__( - description="upload force constants HDF5 file", - multiple=False, - layout={"width": "initial"}, - ) - - -class UploadPhonopyWidget(ipw.HBox): - def __init__(self, **kwargs): - self.upload_phonopy_yaml = UploadPhonopyYamlWidget(**kwargs) - self.upload_phonopy_hdf5 = UploadForceConstantsHdf5Widget(**kwargs) - - self.reset_uploads = ipw.Button( - description="Discard uploaded files", - icon="pencil", - button_style="warning", - disabled=False, - layout=ipw.Layout(width="auto"), - ) - - super().__init__( - children=[ - self.upload_phonopy_yaml, - self.upload_phonopy_hdf5, - self.reset_uploads, - ], - **kwargs, - ) - - def _read_phonopy_files(self, fname, phonopy_yaml_content, fc_hdf5_content=None): - suffix = "".join(pathlib.Path(fname).suffixes) - - with tempfile.NamedTemporaryFile(suffix=suffix) as temp_yaml: - temp_yaml.write(phonopy_yaml_content) - temp_yaml.flush() - - if fc_hdf5_content: - with tempfile.NamedTemporaryFile(suffix=".hdf5") as temp_file: - temp_file.write(fc_hdf5_content) - temp_file.flush() - temp_hdf5_name = temp_file.name - - try: - fc = generate_force_constant_instance( - path=pathlib.Path(fname), - summary_name=temp_yaml.name, - fc_name=temp_hdf5_name, - ) - except ValueError: - return None - - return fc - else: - temp_hdf5_name = None - - try: - fc = generate_force_constant_instance( - path=pathlib.Path(fname), - summary_name=temp_yaml.name, - # fc_name=temp_hdf5_name, - ) - except ValueError: - return None - - return fc - - -#### END for detached app +from aiidalab_qe_vibroscopy.app.widgets.structurefactorwidget import EuphonicStructureFactorWidget ##### START OVERALL WIDGET TO DISPLAY EVERYTHING: - -class EuphonicSuperWidget(ipw.VBox): +class EuphonicWidget(ipw.VBox): """ Widget that will include everything, from the upload widget to the tabs with single crystal and powder predictions. @@ -119,7 +22,7 @@ class EuphonicSuperWidget(ipw.VBox): """ def __init__( - self, mode="aiidalab-qe app plugin", model=None, node=None, fc=None, q_path=None + self, model: EuphonicResultsModel, node=None, detached_app = False, **kwargs, ): """ Initialize the Euphonic utility class. @@ -147,23 +50,20 @@ def __init__( fc : optional Force constants if provided. """ - - self.mode = mode + + super().__init__() + self._model = model # this is the single crystal model. - self._model.node = node + if node: self._model.vibro = node + self._model.detached_app = detached_app self._model.fc_hdf5_content = None self.rendered = False - super().__init__() - def render(self): if self.rendered: return - - self.upload_widget = UploadPhonopyWidget() - self.upload_widget.reset_uploads.on_click(self._on_reset_uploads_button_clicked) - + self.tab_widget = ipw.Tab() self.tab_widget.layout.display = "none" self.tab_widget.set_title(0, "Single crystal") @@ -178,31 +78,64 @@ def render(self): disabled=True, layout=ipw.Layout(width="auto"), ) - self.plot_button.on_click(self._on_first_plot_button_clicked) + self.plot_button.on_click(self._render_for_real) self.loading_widget = LoadingWidget("Loading INS data") self.loading_widget.layout.display = "none" - if self.mode == "aiidalab-qe app plugin": - self.upload_widget.layout.display = "none" + if not self._model.detached_app: self.plot_button.disabled = False else: + from aiidalab_qe_vibroscopy.utils.euphonic.detached_app.uploadwidgets import UploadPhonopyWidget + self.upload_widget = UploadPhonopyWidget() + self.upload_widget.reset_uploads.on_click(self._on_reset_uploads_button_clicked) self.upload_widget.children[0].observe(self._on_upload_yaml, "_counter") self.upload_widget.children[1].observe(self._on_upload_hdf5, "_counter") - + self.children += (self.upload_widget,) + self.download_widget = DownloadYamlHdf5Widget(model=self._model) self.download_widget.layout.display = "none" - self.children = [ - self.upload_widget, + self.children += ( self.plot_button, - self.loading_widget, self.tab_widget, self.download_widget, - ] - + self.loading_widget, + ) + self.rendered = True + def _render_for_real(self, change=None): + # It creates the widgets + self.plot_button.layout.display = "none" + self.loading_widget.layout.display = "block" + + self._model.fetch_data() # should be in the model, but I can do it here once for all and then clone the model. + powder_model = self._model._clone() + qsection_model = self._model._clone() + + # I first initialise this widget, to then have the 0K ref for the other two. + # the model is passed to the widget. For the other two, I need to generate the model. + singlecrystalwidget = SingleCrystalFullWidget(model=self._model) + + # I need to generate the models for the other two widgets. + self._model._inject_single_crystal_settings() + powder_model._inject_powder_settings() + qsection_model._inject_qsection_settings() + + self.tab_widget.children = ( + singlecrystalwidget, + PowderFullWidget(model=powder_model), + QSectionFullWidget(model=qsection_model), + ) + + for widget in self.tab_widget.children: + widget.render() # this is the render method of the widget. + + self.loading_widget.layout.display = "none" + self.tab_widget.layout.display = "block" + self.download_widget.layout.display = "block" + def _on_reset_uploads_button_clicked(self, change): self.upload_widget.upload_phonopy_yaml.value.clear() self.upload_widget.upload_phonopy_yaml._counter = 0 @@ -235,39 +168,7 @@ def _on_upload_hdf5(self, change): self._model.fc_hdf5_content = self.upload_widget.children[1].value[ fname ]["content"] - - def _on_first_plot_button_clicked(self, change=None): # basically the render. - # It creates the widgets - self.plot_button.layout.display = "none" - self.loading_widget.layout.display = "block" - - self._model.fetch_data() # should be in the model. - powder_model = self._model._clone() - qsection_model = self._model._clone() - - # I first initialise this widget, to then have the 0K ref for the other two. - # the model is passed to the widget. For the other two, I need to generate the model. - singlecrystalwidget = SingleCrystalFullWidget(model=self._model) - - # I need to generate the models for the other two widgets. - self._model._inject_single_crystal_settings() - powder_model._inject_powder_settings() - qsection_model._inject_qsection_settings() - - self.tab_widget.children = ( - singlecrystalwidget, - PowderFullWidget(model=powder_model), - QSectionFullWidget(model=qsection_model), - ) - - for widget in self.tab_widget.children: - widget.render() # this is the render method of the widget. - - self.loading_widget.layout.display = "none" - self.tab_widget.layout.display = "block" - self.download_widget.layout.display = "block" - - + class DownloadYamlHdf5Widget(ipw.HBox): def __init__(self, model): self._model = model diff --git a/src/aiidalab_qe_vibroscopy/utils/euphonic/structurefactorwidget.py b/src/aiidalab_qe_vibroscopy/app/widgets/structurefactorwidget.py similarity index 88% rename from src/aiidalab_qe_vibroscopy/utils/euphonic/structurefactorwidget.py rename to src/aiidalab_qe_vibroscopy/app/widgets/structurefactorwidget.py index acda557..51857fc 100644 --- a/src/aiidalab_qe_vibroscopy/utils/euphonic/structurefactorwidget.py +++ b/src/aiidalab_qe_vibroscopy/app/widgets/structurefactorwidget.py @@ -1,3 +1,6 @@ +# ADD ALL THE IMPORTS. + + class EuphonicStructureFactorWidget(ipw.VBox): """Description. @@ -5,10 +8,10 @@ class EuphonicStructureFactorWidget(ipw.VBox): in all the three cases: single crystal, powder, and q-section.... """ - def __init__(self, model, spectrum_type = "single_crystal", detached_app = False, **kwargs): + def __init__(self, model, node=None, spectrum_type = "single_crystal", detached_app = False, **kwargs): super().__init__() - node self._model = model + if node: self._model.vibro = node self._model.spectrum_type = spectrum_type self._model.detached_app = detached_app self.rendered = False @@ -21,55 +24,6 @@ def render(self): if self.rendered: return - self.tab_widget = ipw.Tab() - self.tab_widget.layout.display = "none" - self.tab_widget.set_title(0, "Single crystal") - self.tab_widget.set_title(1, "Powder sample") - self.tab_widget.set_title(2, "Q-plane view") - self.tab_widget.children = () - - self.plot_button = ipw.Button( - description="Initialise INS data", - icon="pencil", - button_style="primary", - disabled=True, - layout=ipw.Layout(width="auto"), - ) - self.plot_button.on_click(self._render_for_real) - - self.loading_widget = LoadingWidget("Loading INS data") - self.loading_widget.layout.display = "none" - - if not self._model.detached_app: - self.plot_button.disabled = False - else: - self.upload_widget = UploadPhonopyWidget() - self.upload_widget.reset_uploads.on_click(self._on_reset_uploads_button_clicked) - self.upload_widget.children[0].observe(self._on_upload_yaml, "_counter") - self.upload_widget.children[1].observe(self._on_upload_hdf5, "_counter") - self.children += (self.upload_widget,) - - self.download_widget = DownloadYamlHdf5Widget(model=self._model) - self.download_widget.layout.display = "none" - - self.children += ( - self.plot_button, - self.loading_widget, - self.tab_widget, - self.download_widget, - ) - - # NOTE: we initialise here the figure widget, but we do not plot anything yet. - # this is useful to call the init_view method, which contains the update for the figure. - self.fig = go.FigureWidget() - - self.rendered = True - - def _render_for_real(self, change=None): - - self.plot_button.layout.display = "none" - self.loading_widget.layout.display = "block" - self._init_view() slider_intensity = ipw.FloatRangeSlider( @@ -345,6 +299,8 @@ def _render_for_real(self, change=None): self.children += ( ... ) + + self.rendered = True def _init_view(self, _=None): self._model.fetch_data() diff --git a/src/aiidalab_qe_vibroscopy/utils/euphonic/data/structure_factors.py b/src/aiidalab_qe_vibroscopy/utils/euphonic/data/structure_factors.py index edb65d9..6634dd4 100644 --- a/src/aiidalab_qe_vibroscopy/utils/euphonic/data/structure_factors.py +++ b/src/aiidalab_qe_vibroscopy/utils/euphonic/data/structure_factors.py @@ -178,7 +178,7 @@ def join_q_paths(coordinates: list, labels: list, delta_q=0.1, G=[0, 0, 0]): def produce_bands_weigthed_data( - params: Optional[List[str]] = parameters, + params: Optional[List[str]] = None, fc: ForceConstants = None, linear_path=None, plot=False, @@ -360,7 +360,7 @@ def produce_bands_weigthed_data( def produce_powder_data( - params: Optional[List[str]] = parameters_powder, + params: Optional[List[str]] = None, fc: ForceConstants = None, plot=False, linear_path=None, diff --git a/src/aiidalab_qe_vibroscopy/utils/euphonic/Detached_app.ipynb b/src/aiidalab_qe_vibroscopy/utils/euphonic/detached_app/Detached_app.ipynb similarity index 100% rename from src/aiidalab_qe_vibroscopy/utils/euphonic/Detached_app.ipynb rename to src/aiidalab_qe_vibroscopy/utils/euphonic/detached_app/Detached_app.ipynb diff --git a/src/aiidalab_qe_vibroscopy/utils/euphonic/static/style.css b/src/aiidalab_qe_vibroscopy/utils/euphonic/detached_app/static/style.css similarity index 100% rename from src/aiidalab_qe_vibroscopy/utils/euphonic/static/style.css rename to src/aiidalab_qe_vibroscopy/utils/euphonic/detached_app/static/style.css diff --git a/src/aiidalab_qe_vibroscopy/utils/euphonic/static/welcome.jinja b/src/aiidalab_qe_vibroscopy/utils/euphonic/detached_app/static/welcome.jinja similarity index 100% rename from src/aiidalab_qe_vibroscopy/utils/euphonic/static/welcome.jinja rename to src/aiidalab_qe_vibroscopy/utils/euphonic/detached_app/static/welcome.jinja diff --git a/src/aiidalab_qe_vibroscopy/utils/euphonic/detached_app/uploadwidgets.py b/src/aiidalab_qe_vibroscopy/utils/euphonic/detached_app/uploadwidgets.py new file mode 100644 index 0000000..2a3ad09 --- /dev/null +++ b/src/aiidalab_qe_vibroscopy/utils/euphonic/detached_app/uploadwidgets.py @@ -0,0 +1,312 @@ +import pathlib +import tempfile + + +from IPython.display import display + +import ipywidgets as ipw + +# from ..euphonic.bands_pdos import * +from aiidalab_qe_vibroscopy.utils.euphonic.data_manipulation.intensity_maps import ( + generate_force_constant_instance, + export_euphonic_data, # noqa: F401 +) +from aiidalab_qe_vibroscopy.utils.euphonic.tab_widgets.euphonic_single_crystal_widgets import ( + SingleCrystalFullWidget, +) +from aiidalab_qe_vibroscopy.utils.euphonic.tab_widgets.euphonic_powder_widgets import ( + PowderFullWidget, +) +from aiidalab_qe_vibroscopy.utils.euphonic.tab_widgets.euphonic_q_planes_widgets import ( + QSectionFullWidget, +) + + +from aiidalab_qe.common.widgets import LoadingWidget +###### START for detached app: + + +# Upload buttons +class UploadPhonopyYamlWidget(ipw.FileUpload): + def __init__(self, **kwargs): + super().__init__( + description="upload phonopy YAML file", + multiple=False, + layout={"width": "initial"}, + ) + + +class UploadForceConstantsHdf5Widget(ipw.FileUpload): + def __init__(self, **kwargs): + super().__init__( + description="upload force constants HDF5 file", + multiple=False, + layout={"width": "initial"}, + ) + + +class UploadPhonopyWidget(ipw.HBox): + def __init__(self, **kwargs): + self.upload_phonopy_yaml = UploadPhonopyYamlWidget(**kwargs) + self.upload_phonopy_hdf5 = UploadForceConstantsHdf5Widget(**kwargs) + + self.reset_uploads = ipw.Button( + description="Discard uploaded files", + icon="pencil", + button_style="warning", + disabled=False, + layout=ipw.Layout(width="auto"), + ) + + super().__init__( + children=[ + self.upload_phonopy_yaml, + self.upload_phonopy_hdf5, + self.reset_uploads, + ], + **kwargs, + ) + + def _read_phonopy_files(self, fname, phonopy_yaml_content, fc_hdf5_content=None): + suffix = "".join(pathlib.Path(fname).suffixes) + + with tempfile.NamedTemporaryFile(suffix=suffix) as temp_yaml: + temp_yaml.write(phonopy_yaml_content) + temp_yaml.flush() + + if fc_hdf5_content: + with tempfile.NamedTemporaryFile(suffix=".hdf5") as temp_file: + temp_file.write(fc_hdf5_content) + temp_file.flush() + temp_hdf5_name = temp_file.name + + try: + fc = generate_force_constant_instance( + path=pathlib.Path(fname), + summary_name=temp_yaml.name, + fc_name=temp_hdf5_name, + ) + except ValueError: + return None + + return fc + else: + temp_hdf5_name = None + + try: + fc = generate_force_constant_instance( + path=pathlib.Path(fname), + summary_name=temp_yaml.name, + # fc_name=temp_hdf5_name, + ) + except ValueError: + return None + + return fc + + +#### END for detached app + + +##### START OVERALL WIDGET TO DISPLAY EVERYTHING: + + +class EuphonicSuperWidget(ipw.VBox): + """ + Widget that will include everything, + from the upload widget to the tabs with single crystal and powder predictions. + In between, we trigger the initialization of plots via a button. + """ + + def __init__( + self, mode="aiidalab-qe app plugin", model=None, node=None, fc=None, q_path=None + ): + """ + Initialize the Euphonic utility class. + Parameters: + ----------- + mode : str, optional + The mode of operation, default is "aiidalab-qe app plugin". + fc : optional + Force constants, default is None. + q_path : optional + Q-path for phonon calculations, default is None. If Low-D system, this can be provided. + It is the same path obtained for the PhonopyCalculation of the phonopy_bands. + Attributes: + ----------- + mode : str + The mode of operation. + upload_widget : UploadPhonopyWidget + Widget for uploading phonopy files. + fc_hdf5_content : None + Content of the force constants HDF5 file. + tab_widget : ipw.Tab + Tab widget for different views. + plot_button : ipw.Button + Button to initialize INS data. + fc : optional + Force constants if provided. + """ + + self.mode = mode + self._model = model # this is the single crystal model. + self._model.node = node + self._model.fc_hdf5_content = None + + self.rendered = False + + super().__init__() + + def render(self): + if self.rendered: + return + + self.upload_widget = UploadPhonopyWidget() + self.upload_widget.reset_uploads.on_click(self._on_reset_uploads_button_clicked) + + self.tab_widget = ipw.Tab() + self.tab_widget.layout.display = "none" + self.tab_widget.set_title(0, "Single crystal") + self.tab_widget.set_title(1, "Powder sample") + self.tab_widget.set_title(2, "Q-plane view") + self.tab_widget.children = () + + self.plot_button = ipw.Button( + description="Initialise INS data", + icon="pencil", + button_style="primary", + disabled=True, + layout=ipw.Layout(width="auto"), + ) + self.plot_button.on_click(self._on_first_plot_button_clicked) + + self.loading_widget = LoadingWidget("Loading INS data") + self.loading_widget.layout.display = "none" + + if self.mode == "aiidalab-qe app plugin": + self.upload_widget.layout.display = "none" + self.plot_button.disabled = False + else: + self.upload_widget.children[0].observe(self._on_upload_yaml, "_counter") + self.upload_widget.children[1].observe(self._on_upload_hdf5, "_counter") + + self.download_widget = DownloadYamlHdf5Widget(model=self._model) + self.download_widget.layout.display = "none" + + self.children = [ + self.upload_widget, + self.plot_button, + self.loading_widget, + self.tab_widget, + self.download_widget, + ] + + self.rendered = True + + def _on_reset_uploads_button_clicked(self, change): + self.upload_widget.upload_phonopy_yaml.value.clear() + self.upload_widget.upload_phonopy_yaml._counter = 0 + self.upload_widget.upload_phonopy_hdf5.value.clear() + self.upload_widget.upload_phonopy_hdf5._counter = 0 + + self.plot_button.layout.display = "block" + self.plot_button.disabled = True + + self.tab_widget.children = () + + self.tab_widget.layout.display = "none" + + def _on_upload_yaml(self, change): + if change["new"] != change["old"]: + for fname in self.upload_widget.children[ + 0 + ].value.keys(): # always one key because I allow only one file at the time. + self.fname = fname + self._model.phonopy_yaml_content = self.upload_widget.children[0].value[ + fname + ]["content"] + + if self.plot_button.disabled: + self.plot_button.disabled = False + + def _on_upload_hdf5(self, change): + if change["new"] != change["old"]: + for fname in self.upload_widget.children[1].value.keys(): + self._model.fc_hdf5_content = self.upload_widget.children[1].value[ + fname + ]["content"] + + def _on_first_plot_button_clicked(self, change=None): # basically the render. + # It creates the widgets + self.plot_button.layout.display = "none" + self.loading_widget.layout.display = "block" + + self._model.fetch_data() # should be in the model. + powder_model = self._model._clone() + qsection_model = self._model._clone() + + # I first initialise this widget, to then have the 0K ref for the other two. + # the model is passed to the widget. For the other two, I need to generate the model. + singlecrystalwidget = SingleCrystalFullWidget(model=self._model) + + # I need to generate the models for the other two widgets. + self._model._inject_single_crystal_settings() + powder_model._inject_powder_settings() + qsection_model._inject_qsection_settings() + + self.tab_widget.children = ( + singlecrystalwidget, + PowderFullWidget(model=powder_model), + QSectionFullWidget(model=qsection_model), + ) + + for widget in self.tab_widget.children: + widget.render() # this is the render method of the widget. + + self.loading_widget.layout.display = "none" + self.tab_widget.layout.display = "block" + self.download_widget.layout.display = "block" + + +class DownloadYamlHdf5Widget(ipw.HBox): + def __init__(self, model): + self._model = model + + self.download_button = ipw.Button( + description="Download phonopy data", + icon="pencil", + button_style="primary", + disabled=False, + layout=ipw.Layout(width="auto"), + ) + self.download_button.on_click(self._download_data) + + super().__init__( + children=[ + self.download_button, + ], + ) + + def _download_data(self, _=None): + """ + Download both the phonopy.yaml and fc.hdf5 files. + """ + phonopy_yaml, fc_hdf5 = self._model.produce_phonopy_files() + self._download(payload=phonopy_yaml, filename="phonopy" + ".yaml") + self._download(payload=fc_hdf5, filename="fc" + ".hdf5") + + @staticmethod + def _download(payload, filename): + from IPython.display import Javascript + + javas = Javascript( + """ + var link = document.createElement('a'); + link.href = 'data:text/json;charset=utf-8;base64,{payload}' + link.download = "{filename}" + document.body.appendChild(link); + link.click(); + document.body.removeChild(link); + """.format(payload=payload, filename=filename) + ) + display(javas)