Skip to content

Commit

Permalink
0.8.8 POSCAR auto rendering
Browse files Browse the repository at this point in the history
  • Loading branch information
asaboor-gh committed Nov 7, 2024
1 parent d305b2a commit 09b12be
Show file tree
Hide file tree
Showing 7 changed files with 85 additions and 23 deletions.
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ Interactively select bandstructure path by clicking on high symmetry points on p

![KP](KP.png)

Apply operations on POSCAR and simultaneously view using plotly's `FigureWidget` or `WeasWidget` in Jupyterlab side by side.
Apply operations on POSCAR and simultaneously view using plotly's `FigureWidget` in Jupyterlab side by side.

![snip](op.png)

Expand Down
11 changes: 9 additions & 2 deletions docs/source/notebooks/quickstart.ipynb

Large diffs are not rendered by default.

4 changes: 2 additions & 2 deletions ipyvasp/_lattice.py
Original file line number Diff line number Diff line change
Expand Up @@ -1867,10 +1867,10 @@ def _fix_color_size(types, colors, sizes, default_size, backend=None):
if isinstance(colors,dict):
for k,v in colors.items():
cs[k]['color'] = v
elif colors is not None and isinstance(colors,(str,list,tuple,np.ndarray)):
elif isinstance(colors,(str,list,tuple,np.ndarray)):
for k in cs:
cs[k]['color'] = colors
else:
elif colors is not None:
raise TypeError("colors should be a single valid color or dict as {'Ga':'red','As':'blue',...}")
return cs

Expand Down
2 changes: 1 addition & 1 deletion ipyvasp/_version.py
Original file line number Diff line number Diff line change
@@ -1 +1 @@
__version__ = "0.8.7"
__version__ = "0.8.8"
8 changes: 6 additions & 2 deletions ipyvasp/core/plot_toolkit.py
Original file line number Diff line number Diff line change
Expand Up @@ -1015,14 +1015,18 @@ def iplot2widget(fig, fig_widget=None, template=None):

if fig_widget is None:
fig_widget = go.FigureWidget()
scene = fig.layout.scene # need to copy scane form given fig
elif not isinstance(fig_widget, go.FigureWidget):
raise ValueError("fig_widget must be FigureWidget")
else:
scene = fig_widget.layout.scene # keep scene from widget

fig_widget.data = [] # Clear previous data
if template is not None:
fig.layout.template = template # will make white flash if not done before

fig.layout.template = template # will make white flash if not done before
fig_widget.layout = fig.layout
fig_widget.layout.scene = scene # reset scene back

with fig_widget.batch_update():
for data in fig.data:
Expand Down
81 changes: 66 additions & 15 deletions ipyvasp/lattice.py
Original file line number Diff line number Diff line change
Expand Up @@ -250,11 +250,54 @@ def weas_viewer(poscar,
return w


class _AutoRenderer:
_figw = None
_kws = {}

def __init__(self, pc_instance):
self._pc = pc_instance

def on(self, template=None):
"Enable auto rendering. In Jupyterlab, you can use `Create New View for Output` to drag a view on side."
self.off()
type(self)._figw = iplot2widget(self._pc.iplot_lattice(**self._kws), fig_widget=self._figw,template=template)

def ip_display(that):
iplot2widget(that.iplot_lattice(**self._kws), fig_widget=self._figw, template=template)

type(self._pc)._ipython_display_ = ip_display

from ipywidgets import Button, VBox
btn = Button(description='Disable Auto Rendering',icon='close',layout={'width': 'max-content'})
btn.on_click(lambda btn: self.off())
type(self)._box = VBox([btn, self._figw])
return display(self._box)

def off(self):
"Disable auto rendering."
if hasattr(self, '_box'):
self._box.close()
type(self)._figw = None # no need to close figw, it raise warning, but now garbage collected

if hasattr(type(self._pc), '_ipython_display_'):
del type(self._pc)._ipython_display_

@_sig_kwargs(plat.iplot_lattice,('poscar_data',))
def update_params(self, **kwargs):
type(self)._kws = kwargs
if hasattr(type(self._pc), '_ipython_display_'):
self._pc._ipython_display_()

@property
def params(self):
return self._kws.copy() # avoid messing original

def __repr__(self):
return f"AutoRenderer({self._kws})"

class POSCAR:
_cb_instance = {} # Loads last clipboard data if not changed
_mp_instance = {} # Loads last mp data if not changed
_plotly_kws = {} # kwargs to pass to auto update figurewidget
_weas_kws = {}

def __init__(self, path=None, content=None, data=None):
"""
Expand All @@ -268,10 +311,16 @@ def __init__(self, path=None, content=None, data=None):
data : PoscarData object. This assumes positions are in fractional coordinates.
Prefrence order: data > content > path
Tip: You can use `self.auto_renderer.on()` to keep doing opertions and visualize while last line of any cell is a POSCAR object.
"""
self._path = Path(path or "POSCAR") # Path to file
self._content = content

if not hasattr(self, '_renderer'): # Only once
type(self)._renderer = _AutoRenderer(self) # assign to class
self._renderer._pc = self # keep latest refrence there too, for update_params on right one

if data:
self._data = serializer.PoscarData.validated(data)
else:
Expand All @@ -295,6 +344,19 @@ def __str__(self):
@property
def path(self):
return self._path

@property
def auto_renderer(self):
"""A renderer for auto viewing POSCAR when at last line of cell.
Use `auto_renderer.on()` to enable it.
Use `auto_renderer.off()` to disable it.
Use `auto_renderer.[params, update_params()]` to view and update parameters.
In Jupyterlab, you can use `Create New View for Output` to drag a view on side.
In VS Code, you can open another view of Notebook to see it on side while doing operations.
"""
return self._renderer

def to_ase(self):
"""Convert to ase.Atoms format. You need to have ase installed.
Expand Down Expand Up @@ -327,6 +389,8 @@ def view(self, viewer=None, **kwargs):
return plat.view_poscar(self.data, **kwargs)
elif viewer in "weas":
return weas_viewer(self, **kwargs)
elif viewer in "plotly":
return self.view_plotly(**kwargs)
elif viewer in "nglview":
return print(
f"Use `self.view_ngl()` for better customization in case of viewer={viewer!r}"
Expand All @@ -344,9 +408,7 @@ def view_ngl(self, **kwargs):
@_sub_doc(weas_viewer)
@_sig_kwargs(weas_viewer, ("poscar",))
def view_weas(self, **kwargs):
self.__class__._weas_kws = kwargs # attach to class, not self
return weas_viewer(self, **kwargs)


def view_kpath(self):
"Initialize a KpathWidget instance to view kpath for current POSCAR, and you can select others too."
Expand All @@ -357,13 +419,8 @@ def view_kpath(self):
@_sub_doc(plat.iplot_lattice)
@_sig_kwargs(plat.iplot_lattice, ("poscar_data",))
def view_plotly(self, **kwargs):
self.__class__._plotly_kws = kwargs # attach to class, not self
return iplot2widget(self.iplot_lattice(**kwargs))

def update_plotly(self, handle):
"Update plotly widget (already shown in notebook) after some operation on POSCAR with `handle` returned by `view_plotly`."
iplot2widget(self.iplot_lattice(**self._plotly_kws), fig_widget=handle)

@classmethod
def from_file(cls, path):
"Load data from POSCAR file"
Expand Down Expand Up @@ -504,12 +561,6 @@ def to_clipboard(self):
"Writes POSCAR to clipboard (as implemented by pandas library) for copy in other programs such as vim."
clipboard_set(self.content) # write to clipboard


def update_weas(self, handle):
"""Send result of any operation to view on opened weas widget handle.
Useful in Jupyterlab side panel to look operations results on POSCAR."""
handle.children = weas_viewer(self, **self._weas_kws).children # does not work properly otherwise

@property
def data(self):
"Data object in POSCAR."
Expand Down
Binary file modified op.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.

0 comments on commit 09b12be

Please sign in to comment.