# AUTOGENERATED! DO NOT EDIT! File to edit: Widgets.ipynb (unless otherwise specified).

__all__ = ['css_style', 'dark_colors', 'light_colors', 'simple_colors', 'default_colors', 'get_files_gui', 'InputGui',
           'generate_summary', 'VasprunApp', 'KPathApp']

# Cell
import os, textwrap
import json
from time import sleep

# Widgets Imports
from IPython.display import display, Markdown
from IPython import get_ipython #For SLides
import ipywidgets as ipw
from ipywidgets import Layout,Label,Button,Box,HBox,VBox,Dropdown,Text,Checkbox, SelectMultiple
from ipywidgets.embed import embed_minimal_html, dependency_state

# More exports
import numpy as np
import pandas as pd
import plotly.graph_objects as go

# Inside packages import to work both with package and jupyter notebook.
try:
    from pivotpy import g_utils as gu
    from pivotpy import vr_parser as vp
    from pivotpy import i_plots as ip
    from pivotpy import s_plots as sp
    from pivotpy import sio
except:
    import pivotpy.g_utils as gu
    import pivotpy.vr_parser as vp
    import pivotpy.i_plots as ip
    import pivotpy.s_plots as sp
    import pivotpy.sio as sio

# Cell
def css_style(colors_dict,_class = 'main_wrapper'):
    """Return style based on colors_dict available as pp.light_colors, pp.dark_colors etc"""
    return """<style>
    .{_class} h1,h2,h3,h4,h5,h6 {{
        color: {accent} !important;
    }}
    .{_class} .widget-label-basic {{
        background-color: transparent !important;
        color: {main_fg} !important;
    }}
    .{_class} .widget-text input {{
        background-color: {next_bg} !important;
        border-radius:20px !important;
        padding: 0px 10px 0px 10px !important;
        border: 1px solid {next_bg} !important;
        color: {main_fg} !important;
        }}
    .{_class} .widget-text input:focus {{
        border: 1px solid {hover_bg} !important;
        }}
    .{_class} .widget-text input:hover {{
        border: 1px solid {hover_bg} !important;
        }}
    .{_class} .widget-dropdown > select {{
        background-color: {next_bg} !important;
        border:none !important;
        border-bottom: 1px solid {hover_bg} !important;
        box-shadow: inset 0 -20px 10px -20px {hover_bg};
        color: {main_fg} !important;
    }}
    .{_class} .widget-dropdown > select:hover {{
        background-color: {hover_bg} !important;
    }}
    .{_class} .widget-dropdown > select > option {{
        color: {main_fg} !important;
        background-color: {next_bg} !important;
    }}
    .{_class} .widget-dropdown > select > option:focus {{
        background-color: {hover_bg} !important;
    }}
    .{_class} .widget-label {{
        color: {main_fg} !important;
    }}
    .{_class} .widget-html {{
        color: {main_fg} !important;
    }}
    .{_class} .widget-box, .{_class}.widget-box {{
        background-color: {main_bg} !important;
        border-radius:5px !important;
        padding:1px !important;
        border: 1px solid {next_bg} !important;
        box-shadow: 1px 1px 1px 1px {next_bg} !important;
    }}
    .{_class} .borderless, .{_class}.borderless {{
        border: 1px solid transparent !important;
        box-shadow: none !important;
        border-radius: 4px !important;
        margin:4px !important;
    }}
    .{_class} .marginless, .{_class}.marginless {{
        margin: 0px !important;
        border-radius: 0px !important;
    }}
    .{_class} .output, .{_class}.output {{
        color: {main_fg} !important;
        background-color: inherit !important;
    }}
    .{_class} .widget-tab, .{_class}.widget-tab {{
        background-color: {main_bg} !important;
        border: none !important;
        box-shadow: 1px 1px 1px 1px {next_bg} !important;
        padding: 0px 2px 2px 2px !important;
    }}
    .{_class} .widget-tab-contents, .{_class}.widget-tab > .widget-tab-contents {{
        width: 100%;
        box-sizing: border-box;
        margin: 0px !important;
        padding: 0px !important;
        flex-grow: 1;
        overflow: auto;
        border: none !important;
        background-color: {main_bg} !important;
    }}
    .{_class} .widget-tab > .p-TabBar .p-TabBar-tab, .{_class}.widget-tab > .p-TabBar .p-TabBar-tab {{
        background-color:{main_bg} !important;
        border: none !important;
        color: {accent} !important;
        font-weight: bold !important;
        font-size: 16px !important;
        font-family: "Times","serif" !important;
        text-align: center !important;
    }}
    .{_class} table {{
        color: {main_fg} !important;
        }}
    .{_class} tr:nth-child(odd) {{
        background-color: {next_bg} !important;
        }}
    .{_class} tr:nth-child(even) {{
        background-color: {main_bg} !important;
        }}
    .{_class} .widget-button,.widget-toggle-button {{
        color:  {accent} !important;
        min-width: max-content !important;
        background-color: {next_bg};
        border-radius: 5px !important;
    }}
    .{_class} tr:hover {{
        background-color: {hover_bg} !important;
        }}
    </style>
    """.format(**colors_dict,_class= _class)

dark_colors = {
 'main_fg'  : '#ABB2BF',
 'main_bg'  : '#21252B',
 'next_bg'  : '#282C34',
 'hover_bg' : '#414855',
 'accent'   : '#61AFEF'
}

light_colors = {
 'next_bg'  : 'white',
 'hover_bg' : '#abe4ff',
 'accent'   : 'navy',
 'main_bg'  : '#F3F3F3',
 'main_fg'  : 'black'
}
simple_colors = {
 'hover_bg' : 'lightgray',
 'accent'   : 'black',
 'main_bg'  : 'white',
 'main_fg'  : 'black',
 'next_bg'  : 'whitesmoke'
}
default_colors = {  # Adopt Jupyterlab theme
 'main_fg' : 'var(--jp-inverse-layout-color0,black)',
 'main_bg' : 'var(--jp-layout-color0,#F3F3F3)',
 'next_bg' : 'var(--jp-layout-color2,white)',
 'hover_bg': 'var(--jp-border-color1,lightblue)',
 'accent'  : 'var(--jp-brand-color2,navy)'
}

# Cell
def get_files_gui(auto_fill = 'vasprun.xml', theme_colors = None, height=320):
    """
    - Creates a GUI interface for files/folders filtering.
    - **Parmeters**
        - auto_fill  : Default is `vasprun.xml`, any file/folder.
        - theme_colors : None,Any of pivotpy.[dark,light,simple]_colors.
        - height     : Height of Grid box.
    - **Returns**
        - Tuple(GUI_gridbox,Files_Dropdown). Access second one by item itself.
    """
    files_w = ipw.Dropdown(continuous_update=False)
    pw = ipw.Text(value=os.getcwd())

    incldue_w = ipw.Text(value=auto_fill)

    excldue_w = ipw.Text()
    d_layout = Layout(width='30%')
    l_layout = Layout(width='19%')
    depth_w = ipw.Dropdown(options=[None,1,2,3,4,5],value=4,layout=d_layout)
    item_w = ipw.Dropdown(options=['Both','Files','Folders'],value='Files',layout=d_layout)
    item_box = ipw.HBox([ipw.Label('Depth: ',layout=l_layout),depth_w,ipw.Label('Type: ',layout=l_layout),item_w])
    item_box.add_class('borderless').add_class('marginless')

    applybtn_w = ipw.Button(description='Apply Filters')
    gci_output = ipw.Output(layout=Layout(height='{}px'.format(height-70)))
    label_head = ipw.HTML("<h3>Your Filtered Files List</h3>")


    def filter_gci(applybtn_w):
        applybtn_w.description = 'Applying...'
        applybtn_w.disabled = True
        if os.path.isdir(pw.value):
            path = pw.value
        else:
            with gci_output:
                print("Given path does not exists.")
                print("Falling back to PWD: {}".format(os.getcwd()))
            path = os.getcwd()
            pw.value = path
        gci = vp.Dict2Data({'children':[],'parent':path})

        if 'Files' in item_w.value:
            file_type = dict(filesOnly=True)
        elif 'Folders' in item_w.value:
            file_type = dict(dirsOnly=True)
        else:
            file_type = {}
        try:
            gci = gu.get_child_items(path=path, **file_type,
                           include= incldue_w.value,
                           exclude= excldue_w.value,
                           depth=depth_w.value)
        except:
            with gci_output:
                print('Something went wrong')
        # Enable before any error occur.
        applybtn_w.disabled = False
        files_w.options = [(name, os.path.join(gci.parent,name)) for name in gci.children]

        applybtn_w.description = 'Successful!'
        label_head.value = "<h3>From: {}</h3>".format(gci.parent)
        with gci_output:
            display(ipw.HTML("<h4>{} files found.</h4>".format(len(gci.children))))
            display(ipw.HTML("<ol>{}<ol>".format(''.join(['<li>{}</li>'.format(i) for i in gci.children]))))


        applybtn_w.description = 'Apply Filters'
        gci_output.clear_output(wait=True)

    applybtn_w.on_click(filter_gci)
    out_box = ipw.Box([gci_output])
    right_box = ipw.VBox([label_head,out_box])
    out_box.add_class('borderless')
    right_box.add_class('borderless')
    i_layout = Layout(width='99%')
    incldue_w.layout = i_layout
    excldue_w.layout = i_layout
    pw.layout = i_layout
    input_box = ipw.VBox([
        ipw.Label('Path to Project Folder',layout=i_layout),pw,
        ipw.Label('Items to Include (separate by |)',layout=i_layout),incldue_w,
        ipw.Label('Items to Exclude (separate by |)',layout=i_layout),excldue_w,
        item_box,
        applybtn_w],layout=Layout(width='330px'))

    _class = 'custom-'+''.join(np.random.randint(9,size=(23,)).astype(str)) #Random class
    html_style = css_style(theme_colors,_class = _class) if theme_colors else ''
    full_box = ipw.HBox([ipw.HTML(html_style),input_box, right_box],
                        layout=Layout(height='{}px'.format(height))).add_class(_class)

    full_box.add_class('borderless').add_class('marginless')
    return full_box, files_w

# Cell
class InputGui:
    def __init__(self,sys_info=None,theme_colors = None,height=400):
        """
        - Creates a GUI interface for input/selection of orbitals/elms projection.
        - **Parmeters**
            - theme_colors : None,Any of pivotpy.[dark,light,simple]_colors.
            - height     : Height of Grid box, Can set to None for auto-resizing.
            - sys_info   : `export_vasprun().sys_info`. Can change later using `self.update_options` mthod.
        - **Output Parameters**
            - output: Dictionary that contains kwargs for plot functions.
            - html  : A widget which can be used to bserve change in output, used in `VasprunApp`.
        """
        self.sys_info = sys_info if sys_info else vp.Dict2Data({'fields':['s'],
                                                'ElemIndex':[0,1],'ElemName':['A']})
        self.output = dict(elements = [[],[],[]],orbs = [[],[],[]],labels = ['','',''])

        layout = Layout(width='30%')
        l_width = Layout(width='20%')
        self.html = ipw.HTML() # For Display in Big App as well. Very important
        self.dds = {
            'elms': Dropdown(layout=layout),
            'orbs': Dropdown(layout=layout),
            'rgb' : Dropdown(options=[('Red',0),('Green',1),('Blue',2)],value=0,layout=layout)
            }
        self.texts = {
            'orbs' : Text(layout=layout,continuous_update=False),
            'elms' : Text(layout=layout,continuous_update=False),
            'label': Text(layout=layout,continuous_update=False)
            }
        self.update_options(self.sys_info) # In start if given

        _class = 'custom-'+''.join(np.random.randint(9,size=(23,)).astype(str)) #Random class
        html_style = css_style(theme_colors,_class = _class) if theme_colors else ''
        self.box = VBox([ipw.HTML(html_style),
                self.html,
                HBox([Label('Color: ',layout=l_width), self.dds['rgb'],
                      Label('Label: ',layout=l_width),self.texts['label']
                    ]).add_class('borderless').add_class('marginless'),
                HBox([Label('Ions: ',layout=l_width),self.dds['elms'],
                      Label('::>>:: ',layout=l_width),self.texts['elms']
                    ]).add_class('borderless').add_class('marginless'),
                HBox([Label('Orbs: ',layout=l_width),self.dds['orbs'],
                      Label('::>>:: ',layout=l_width),self.texts['orbs']
                    ]).add_class('borderless').add_class('marginless')
                ],layout=Layout(height="{}px".format(height))
                ).add_class(_class).add_class('marginless')


        #Obsever
        self.dds['rgb'].observe(self.__see_input,'value')
        self.texts['label'].observe(self.__read_pro,'value')
        self.texts['orbs'].observe(self.__read_pro,'value')
        self.texts['elms'].observe(self.__read_pro,'value')
        # Link
        ipw.dlink((self.dds['elms'],'value'),(self.texts['elms'],'value'))
        ipw.dlink((self.dds['orbs'],'value'),(self.texts['orbs'],'value'))

    def update_options(self,sys_info=None):
        if sys_info:
            orbs_opts = [(str(i)+': '+item,str(i)) for i,item in enumerate(sys_info.fields)]
            if len(sys_info.fields) == 9:
                orbs_opts = [*orbs_opts,('1-3: p','1-3'),('4-8: d','4-8')]
            if len(sys_info.fields) == 16:
                orbs_opts = [*orbs_opts,('9-15: f','9-15')]
            max_ind = len(sys_info.fields)-1
            orbs_opts = [('0-{}: All'.format(max_ind), "0-{}".format(max_ind)),*orbs_opts]
            inds = sys_info.ElemIndex
            ions_opts = [("{}-{}: {}".format(inds[i],inds[i+1]-1,item),"{}-{}".format(
                                    inds[i],inds[i+1]-1)) for i,item in enumerate(sys_info.ElemName)]
            self.dds['elms'].options = [*ions_opts,('0-{}: All'.format(inds[-1]-1),'0-{}'.format(inds[-1]-1))]
            self.dds['orbs'].options = orbs_opts
            self.sys_info = sys_info # Update it as well.

    def __read_pro(self,btn):
        def read(cell_value):
            _out = []
            for v in (cell_value.split(",") if cell_value else ''):
                if v and '-' in v:
                    i, f = [int(k) for k in v.split("-")]
                    _out = [*_out,*list(range(i,f+1))]
                elif v:
                    _out = [*_out,int(v)]
            return _out
        index = self.dds['rgb'].value
        self.output['elements'][index] = read(self.texts['elms'].value)
        self.output['orbs'][index] = read(self.texts['orbs'].value)
        _text,_ion,_orb = self.texts['label'].value,self.texts['elms'].value,self.texts['orbs'].value
        self.output['labels'][index] = _text if _text else "{}:{}".format(_ion,_orb)
        self.html.value = """<div style='border: 2px solid {0}!important;
                             background-color:{0} !important;'> </div>
                             """.format(self.dds['rgb'].label.lower())
        sleep(1)
        self.html.value = ''

    def __see_input(self,change):
        # Unobserve first to avoid overwriting
        self.texts['label'].unobserve(self.__read_pro,'value')
        self.texts['orbs'].unobserve(self.__read_pro,'value')
        self.texts['elms'].unobserve(self.__read_pro,'value')

        # Look up while not observing
        x = self.dds['rgb'].value
        self.texts['elms'].value = ','.join([str(i) for i in self.output['elements'][x]])
        self.texts['orbs'].value = ','.join([str(i) for i in self.output['orbs'][x]])
        self.texts['label'].value = self.output['labels'][x]
        # Observe Back Again
        self.texts['label'].observe(self.__read_pro,'value')
        self.texts['orbs'].observe(self.__read_pro,'value')
        self.texts['elms'].observe(self.__read_pro,'value')

    def show(self):
        return self.box

# Cell
#mouse event handler
def _click_data(sel_en_w,fermi_w,data_dict,fig,bd_w):
    def handle_click(trace, points, state):
        if(points.ys!=[]):
            e_fermi = (float(fermi_w.value) if fermi_w.value else 0)
            v_clicked = points.ys[0] if bd_w.value=='Bands' else points.xs[0]
            val = np.round(float(v_clicked) + e_fermi,4) #exact value
            for key in sel_en_w.options:
                if key in sel_en_w.value and key != 'None':
                    data_dict[key] = val # Assign value back
                if 'Fermi' in sel_en_w.value:
                    fermi_w.value = str(val) # change fermi
            # Shift Graph for Fermi value
            if 'Fermi' in sel_en_w.value:
                with fig.batch_animate():
                    for trace in fig.data: # Shift graph as Fermi Changes
                        if bd_w.value == 'Bands':
                            trace.y = [y - data_dict['Fermi'] + e_fermi for y in trace.y]
                        else:
                            trace.x = [x - data_dict['Fermi'] + e_fermi for x in trace.x]

            # Update Fermi, SO etc
            if data_dict['VBM'] and data_dict['CBM']:
                data_dict['E_gap'] = np.round(data_dict['CBM'] - data_dict['VBM'], 4)
            if data_dict['so_max'] and data_dict['so_min']:
                data_dict['Δ_SO'] = np.round(data_dict['so_max'] - data_dict['so_min'], 4)
            # Cycle energy types on graph click and chnage table as well unless it is None.
            if sel_en_w.value == 'CBM': # Avoid accidental SO calculation
                sel_en_w.value = 'None'
            if sel_en_w.value != 'None': # Keep usually as None
                _this = sel_en_w.options.index(sel_en_w.value)
                _next = _this + 1 if _this < len(sel_en_w.options) - 1 else 0
                sel_en_w.value = sel_en_w.options[_next] #To simulate as it changes

    for trace in fig.data:
        trace.on_click(handle_click)

# Display Table
def _tabulate_data(data_dict):
    new_dict = {k:v for k,v in data_dict.items() if v != '' and k not in ['sys','so_max','so_min','Fermi']}
    ls = list(new_dict.keys())
    ds = list(new_dict.values())

    if len(ls) % 2 != 0:
        ls.append('')
        ds.append('')

    tab_data = [ls[:int(len(ls)/2)],ds[:int(len(ls)/2)],ls[int(len(ls)/2):],ds[int(len(ls)/2):]]

    htm_string = """<style>table {border-collapse: collapse !important;
      min-width: 100% !important;
      margin: 1px 1px 1px 1px !important;
      font-size: small !important;
      font-family: "Times New Roman", "Times", "serif" !important;}
      th, td {text-align: center !important;
      padding: 0px 8px 0px 8px !important;}
      tr { width: 100% !important;}
      tr:nth-child(odd) {font-weight:bold !important;}
      </style>"""
    htm_string += "<table><tr>{}</tr></table>".format( '</tr><tr>'.join(
                   '<td>{}</td>'.format('</td><td>'.join(str(_) for _ in row)) for row in tab_data) )
    return htm_string

# Send Data
def _save_data(out_w1,data_dict):
    out_f = os.path.join(os.path.split(out_w1.value)[0],'result.json')
    vp.dump_dict(data_dict,dump_to='json',outfile=out_f)

# Cell
def _color_toggle(tog_w,fig,rd_btn):
    if tog_w.icon == 'toggle-off':
        tog_w.icon = 'toggle-on'
        with fig.batch_animate():
            if rd_btn.value == 'DOS':
                for trace in fig.data:
                    trace.fill='tozeroy'
            else:
                for trace in fig.data[:1]:
                    trace.mode='markers+lines'
                    trace.line.color='rgba(222,222,220,0.1)'
    else:
        tog_w.icon = 'toggle-off'
        with fig.batch_animate():
            if rd_btn.value == 'DOS':
                for trace in fig.data:
                    trace.fill=None
            else:
                for trace in fig.data[:1]:
                    trace.mode='lines'
                    trace.line.width=1.5
                    trace.line.color='skyblue'

# Cell
def generate_summary(paths_list=None):
    # Make Data Frame
    result_paths = []
    common_prefix = '' #placeholder
    if paths_list:
        common_prefix = os.path.commonprefix(paths_list)
        for item in paths_list:
            if item and os.path.isdir(item):
                result_paths.append(os.path.join(item,'result.json'))
            elif item and os.path.isfile(item):
                result_paths.append(os.path.join(os.path.split(item)[0],'result.json'))
    result_dicts = []
    for path in result_paths:
        try: _p_ = os.path.split(path)[0].split(common_prefix)[1]
        except: _p_ = '' #In current directory
        try:
            with open(path,'r') as f:
                l_d = json.load(f)
                l_d.update({'rel_path':_p_})
                result_dicts.append(l_d)

        except: pass
    out_dict = {} # placeholder
    if result_dicts:
        out_dict.update({k:[v] if v!='' else [np.nan] for k,v in result_dicts[0].items()})
        for i,d in enumerate(result_dicts):
            if i != 0:
                for k,v in d.items():
                    v = np.nan if v=='' else v
                    try: out_dict[k].append(v)
                    except:
                        out_dict.update({k:[np.nan for l in range(i)]}) #if not key before, add to all previous
                        out_dict[k].append(v) # Then append for current value
            # If next dictionary does not have key
            for k in out_dict.keys():
                if k not in d.keys():
                    out_dict[k].append(np.nan)
    try: out_dict.pop('Fermi',None) # Remove Fermi as not necessary
    except: pass

    df = pd.DataFrame(out_dict)
    if common_prefix:
        return df.style.set_caption("Root Path: {}".format(common_prefix)) # return with header
    return df #return simple

# Cell
class VasprunApp:
    """
    Display a GUI for vasp output analysis. `self.theme_colors` can be used to edit custom theme.
    - **Usage Example**

    ```python
    import pivotpy as pp
    va = pp.VasprunApp()
    va.cache_data = False #Turn off cache globally.
    va.evr_kws['elim'] = [-2,2] #Only Bands in this range will be included. Global accross project, can change anytime.
    va.evr_kws['try_pwsh'] = False #Defult is True. Tries to load Powershell exported data.
    va.ibands_kws['mode'] = 'bands' #Change graph mode from 'markers' to 'bands'. Setting it to 'lines' is not recommended in live graph, it could hang all UI.
    va.show() #Displays App and do work!
    va.theme_colors = pp.dark_colors #Set theme to dark externally and edit dictionary values to make your own theme
    va.splot(**kwargs) #Get matplotlib plot of current data.
    va.df #After you do some analysis and hit `Project Summary` button, get DataFrame.
    va.fig #Get current fig in Notebook cell.
    ```
    """
    output = ipw.Output().add_class('output')
    def __init__(self,height=580):
        self.height = height
        tab = ipw.Tab(layout = ipw.Layout(min_height=f'{height}px', max_height='100vh',
                                           min_width='700px', max_width='100vw')
            ).add_class('marginless').add_class('borderless') # Main Tab
        try:
            for i,item in enumerate(['Home','Graphs','STD(out/err)']):
                tab.set_title(i,item)
        except:
            tab.titles = ['Home','Graphs','STD(out/err)']
        self.main_class = 'custom-'+''.join(np.random.randint(9,size=(22,)).astype(str)) #Random class
        self.tab     = tab.add_class(self.main_class) # Main layout
        self.data   = None # Export vasprun object.
        self.__path = None # current path
        self.fig    = go.FigureWidget() # plotly's figure widget
        self.fig.update_layout(autosize=True)
        self.df     = None # Summary DataFrame
        self.result = {'sys':'','V':'','a':'','b':'','c':'','Fermi': None,
                     'VBM':'','CBM':'','so_max':'','so_min':''} # Table widget value

        self.files_gui,self.files_dd = get_files_gui(height=300)
        self.InGui  = InputGui(height=None)
        self.input  = {'E_Fermi':0} # Dictionary for input
        self.fig_gui = HBox() # Middle Tab
        self.theme_colors = light_colors.copy() # Avoid Modification
        # Permeannet Parameters
        self.idos_kws   = dict(colormap='RGB',tdos_color=(0.5, 0.95, 0),linewidth=2,fill_area=True,
                               spin='both',interp_nk={},title=None)
        self.ibands_kws = dict(mode='markers',skipk=None,max_width=6,title=None,interp_nk={})
        self.evr_kws = dict(skipk=None,elim=[],try_pwsh = True)
        self.cache_data = True

        l_btn = ipw.Layout(width='max-content')
        self.buttons = {'load_data' : Button(description='Load Data',layout=l_btn,tooltip='Load and Cache Data'),
                        'load_graph': Button(description='Load Graph',layout=l_btn,tooltip='Create Graph'),
                        'confirm'   : Button(description='Confirm Delete',layout=l_btn,icon='trash'),
                        'summary'   : Button(description='Project Summary',layout=l_btn,tootltip='Make DataFrame'),
                        'expand'    : Button(icon = "fa-expand",layout=l_btn,tooltip='Expand Fig'),
                        'toggle'    : Button(description='RGB',layout=l_btn,icon='toggle-on',tooltip='Toggle Colors'),
                        'save_fig'  : Button(description='Save Fig',icon='download',layout=l_btn,tooltip='')
                        }

        b_out = Layout(width='30%')
        en_options = ['Fermi','VBM','CBM','so_max','so_min','None']
        self.dds   = {'band_dos': Dropdown(options=['Bands','DOS'],value='Bands',
                                                layout= Layout(width='80px')),
                      'en_type' : Dropdown(options = en_options,value='None',layout=b_out),
                      'cache'   : Dropdown(options=['Table Data','PWD Cache','All Cache','None'],
                                            value='None',layout=b_out),
                      'theme'   : Dropdown(options=['Default','Light','Dark','Custom'],
                                            value='Default',layout=l_btn),
                      'style'   : Dropdown(options=["plotly", "plotly_white", "plotly_dark",
                                    "ggplot2", "seaborn", "simple_white", "none"],layout=l_btn),
                                    }

        self.texts = {'kticks': Text(value='',layout=b_out,continuous_update=False),
                      'ktickv': Text(value='',layout=b_out,continuous_update=False),
                      'kjoin' : Text(value='',layout=b_out,continuous_update=False),
                      'elim'  : Text(value='',layout=b_out,continuous_update=False),
                      'fermi' : Text(value='',layout=b_out,continuous_update=False),
                      'xyt'   : Text(value='',continuous_update=False)
                      }
        self.htmls = {'theme': ipw.HTML(css_style(light_colors,_class = self.main_class)),
                      'table': ipw.HTML()}

        # Observing
        self.InGui.html.observe(self.__update_input,"value")
        self.dds['band_dos'].observe(self.__update_input,"value")
        self.texts['fermi'].observe(self.__update_input,"value")
        self.texts['kjoin'].observe(self.__update_input,"value")
        self.texts['kticks'].observe(self.__update_xyt,"value")
        self.texts['ktickv'].observe(self.__update_xyt,"value")
        self.texts['elim'].observe(self.__update_xyt,"value")
        self.texts['xyt'].observe(self.__update_xyt)
        self.dds['theme'].observe(self.__update_theme,"value")
        self.dds['style'].observe(self.__update_plot_style,"value")
        self.files_dd.observe(self.__load_previous,"value")
        self.buttons['load_data'].on_click(self.__on_load)
        self.dds['band_dos'].observe(self.__figure_tab,"value")
        self.files_dd.observe(self.__update_table,'value')
        self.buttons['load_data'].observe(self.__update_table,'value')
        self.buttons['summary'].on_click(self.__df_out)
        self.buttons['confirm'].on_click(self.__deleter)
        self.buttons['toggle'].on_click(self.__tog_b)
        self.buttons['save_fig'].on_click(self.__save_connected)
        self.buttons['expand'].on_click(self.__expand_fig)
        self.buttons['load_graph'].on_click(self.__update_graph)
        self.dds['en_type'].observe(self.__update_table,"value") # This works from _click_data

        # Build Layout
        self.__build()

    def set_theme_colors(self,theme_colors):
        "Get self.theme_colors and after edit set back"
        if 'dds' in self.__dict__.keys():
            self.dds['theme'].value = 'Custom'
        if 'htmls' in self.__dict__.keys():
            self.htmls['theme'].value = css_style(theme_colors)

    def __figure_tab(self,change):
        l_out = Layout(width='20%')
        cache_box = HBox([Label('Delete Cache:'),self.dds['cache'],self.buttons['confirm']]
                        ).add_class('marginless')
        upper_box = VBox([
                HBox([Label('File:',layout=Layout(width='50px')),self.files_dd
                    ]).add_class('borderless').add_class('marginless'),
                HBox([Label('View:',layout=Layout(width='50px')),
                        self.dds['band_dos'],self.buttons['load_data']
                    ]).add_class('borderless').add_class('marginless')
                ]).add_class('marginless').add_class('borderless')

        points_box = HBox([Box([Label('E Type:',layout=l_out),
                                self.dds['en_type'],
                                Label('E-Fermi:',layout=l_out),
                                self.texts['fermi']
                    ]).add_class('marginless').add_class('borderless'),
                    self.buttons['load_graph']
                    ],layout=Layout(width='100%')).add_class('marginless')
        in_box = VBox([self.InGui.box,
                    ]).add_class('marginless').add_class('borderless')
        top_right = HBox([self.buttons['load_graph'],
                          Label('Style:'),
                          self.dds['style'],
                          Label('Theme:'),
                          self.dds['theme'],
                          self.buttons['expand']
                    ]).add_class('marginless')
        fig_box = Box([self.fig],layout=Layout(min_height='380px')).add_class('marginless')
        right_box = VBox([top_right,fig_box,self.htmls['table']
                 ],layout=Layout(min_width='60%')).add_class('marginless').add_class('borderless')


        if 'Bands' in self.dds['band_dos'].value:
            in_box.children = [Label('---------- Projections ----------'),self.InGui.box,
                               Label('---- Other Arguments/Options ----'),
                      VBox([HBox([Label('Ticks At: ',layout=l_out),
                                  self.texts['kticks'],
                                  Label('Labels: ',layout=l_out),
                                  self.texts['ktickv']
                                 ]).add_class('borderless').add_class('marginless'),
                      HBox([Label('Join At: ',layout=l_out),
                            self.texts['kjoin'],
                            Label('E Range: ',layout=l_out),
                            self.texts['elim']
                           ]).add_class('marginless').add_class('borderless')
                      ]).add_class('marginless')]
            right_box.children = [top_right,fig_box,points_box]
            self.buttons['toggle'].description = 'RGB'
        else:
            in_box.children = [Label('---------- Projections ----------'),self.InGui.box,
                               Label('---- Other Arguments/Options ----'),
                      HBox([Label('E Range:',layout=l_out),
                      self.texts['elim'],
                      Label('E-Fermi:',layout=l_out),
                      self.texts['fermi']
                      ]).add_class('marginless')]
            right_box.children = [top_right,fig_box,points_box]
            self.dds['en_type'].value = 'None' # no scatter collection in DOS.
            self.buttons['toggle'].description = 'Fill'


        left_box = VBox([upper_box,
                        in_box,
                        HBox([Label('X, Y, Title'),self.texts['xyt']]).add_class('borderless'),
                        HBox([Label('Options:'),self.buttons['summary'],self.buttons['save_fig'],self.buttons['toggle']]),
                        cache_box,
                        self.htmls['table']],layout=Layout(max_width='40%'),
                        ).add_class('marginless').add_class('borderless')
        self.fig_gui.children = (left_box,right_box)
        self.buttons['load_graph'].icon = 'fa-refresh'
        return self.fig_gui # Return for use in show

    @output.capture()
    def __build(self):
        intro_html = ipw.HTML("<h2>Pivotpy</h2><p>Filter files here and switch tab to Graphs. You can create cache ahead of time to load quickly while working. If anything does not seem to work, see the error in STD(out/err) tab. For large files, do `Export-VaspRun` in Powershell to access fast. <a href=https://massgh.github.io/pivotpy/Widgets.html#VasprunApp target='_blank'>See More</a></p><marquee style='color:blue'>Pivotpy GUI based on ipywidgets!</marquee>")
        header_box = HBox([intro_html,
                           Label('Theme:',layout=Layout(width='80px')),
                           self.dds['theme']
                    ]).add_class('marginless').add_class('borderless')
        summary_gui = HBox([ipw.Label(),
                            self.buttons['summary']]
                            ).add_class('borderless')
        intro_box = VBox([self.htmls['theme'],
                      header_box,
                      self.files_gui,
                      summary_gui]
                      ).add_class('marginless').add_class('borderless').add_class('marginless')
        self.fig_gui = self.__figure_tab(1) #Start
        self.tab.children = (intro_box,
                             self.fig_gui,
                             VBox([HBox([ipw.HTML("<h3>Functions Logging/Output</h3>"),
                                          Box([
                                          Label('Theme:',layout=Layout(width='80px')),
                                          self.dds['theme']],layout=Layout(left='50%')).add_class('borderless')
                                          ]).add_class('borderless').add_class('marginless'),
                                    VasprunApp.output])
                             )
        self.dds['style'].value = 'plotly' # to trigger first callback on graph.
        self.app = self.tab #Need to access it
        self.__update_theme(True) #Good option in jupyterlab for following theme.

    def show(self):
        return display(self.tab)

    def _ipython_display_(self):
        self.show()

    def __fill_ticks(self):
        kpath = os.path.join(os.path.split(self.files_dd.value)[0],'KPOINTS')
        tvs = sio.read_ticks(kpath) #ticks values segments
        if tvs['ktick_inds']: #If is must, if not present, avoid overwritting custom input
            self.texts['kticks'].value = ','.join([str(v) for v in tvs['ktick_inds']])
        if tvs['ktick_vals']:
            self.texts['ktickv'].value = ','.join(tvs['ktick_vals'])
        if tvs['kseg_inds']:
            self.texts['kjoin'].value = ','.join([str(v) for v in tvs['kseg_inds']])

    def __update_theme(self,change):
        if self.dds['theme'].value == 'Dark':
            self.htmls['theme'].value = css_style(dark_colors,_class=self.main_class)
            self.fig.update_layout(template='plotly_dark')
            self.dds['style'].value = 'plotly_dark'
        elif self.dds['theme'].value == 'Light':
            self.htmls['theme'].value = css_style(light_colors,_class=self.main_class)
            self.fig.update_layout(template='ggplot2')
            self.dds['style'].value = 'ggplot2'
        elif self.dds['theme'].value == 'Custom':
            self.htmls['theme'].value = css_style(simple_colors,_class=self.main_class)
            self.fig.update_layout(template='none')
            self.dds['style'].value = 'none'
        else:
            self.htmls['theme'].value = css_style(default_colors,_class=self.main_class)
            self.fig.update_layout(template='plotly')
            self.dds['style'].value = 'plotly'

    def __update_plot_style(self,change):
        self.fig.update_layout(template = self.dds['style'].value)

    @output.capture(clear_output=True,wait=True)
    def __load_previous(self,change):
        path = self.files_dd.value
        try:
            _dir = os.path.split(path)[0]
            r_f = os.path.join(_dir,'result.json')
            self.result = vp.load_from_dump(r_f,keep_as_dict=True)
            print('Previous Analysis loaded in Table for {}'.format(path))
        except:
            print('Previous Analysis does not exist for {}'.format(path))

    @output.capture(clear_output=True,wait=True)
    def __read_data(self,poscar=None,sys_info=None):
        if sys_info != None:
            self.result["sys"] = sys_info.SYSTEM
        if poscar != None:
            self.result["V"] = np.round(poscar.volume,5)
            a,b,c = np.round(np.linalg.norm(poscar.basis,axis=1),5)
            self.result["a"] = a
            self.result["b"] = b
            self.result["c"] = c

    @output.capture(clear_output=True,wait=True)
    def __on_load(self,button):
        self.__fill_ticks() # First fill ticks, then update input
        self.__update_input(change=None) # Fix input right here.
        self.__load_previous(change=button) # previous calculations.
        self.tab.selected_index = 2
        self.buttons['load_data'].description='Loading ...'
        _dir = os.path.split(self.files_dd.value)[0] # directory
        try:
            sys_info = vp.load_from_dump(os.path.join(_dir,'sys_info.pickle'))
            self.data = vp.load_from_dump(os.path.join(_dir,'vasprun.pickle'))
            print('Cache Loaded')
        except:
            print('Trying Loading from Python ...')
            self.data = vp.export_vasprun(self.files_dd.value, **self.evr_kws)
            if self.cache_data:
                print('Caching From: {}'.format(self.files_dd.value)) #Cache result
                vp.dump_dict(self.data.sys_info,outfile=os.path.join(_dir,'sys_info.pickle'))
                vp.dump_dict(self.data,outfile=os.path.join(_dir,'vasprun.pickle'))

            sys_info = self.data.sys_info # required here.
            print('Done')

        _ = self.__read_data(self.data.poscar,sys_info) # Update Table data on load
        self.texts['fermi'].value = str(sys_info.E_Fermi) # Needs each time new data loads up.

        self.tab.selected_index = 1
        # Revamp input dropdowns on load  ==========
        self.InGui.update_options(sys_info=sys_info) #Upadate elements/orbs/labels
        #===========================================
        self.buttons['load_data'].description='Load Data'
        self.__path = self.files_dd.value # Update in __on_load or graph to make sure data loads once
        self.buttons['load_data'].tooltip = "Current System\n{!r}".format(self.data.sys_info)
        self.buttons['load_graph'].icon = 'fa-refresh'

    @output.capture(clear_output=True,wait=True)
    def __update_input(self,change):
        self.input.update(self.InGui.output)
        elim_str  = [v for v in self.texts['elim'].value.split(',') if v!='']
        fermi_str = self.texts['fermi'].value
        self.input['E_Fermi'] = float(fermi_str) if fermi_str else 0 # Change now, keep zero else must.
        self.input['elim'] = [float(v) for v in elim_str if v!='-'][:2] if len(elim_str) >= 2 else None
        if self.dds['band_dos'].value == 'Bands':
            kjoin_str  = [v for v in self.texts['kjoin'].value.split(',') if v!='']
            kticks_str = [v for v in self.texts['kticks'].value.split(',') if v!='']
            ktickv_str = [v for v in self.texts['ktickv'].value.split(',') if v!='']
            self.input['kseg_inds'] = [int(v) for v in kjoin_str if v!='-'] if kjoin_str else None
            self.input['ktick_inds'] = [int(v) for v in kticks_str if v!='-'] if kticks_str else [0,-1]
            self.input['ktick_vals'] = [v for v in ktickv_str if v!=''] if ktickv_str else ['A','B']
        else:
            self.input = {k:v for k,v in self.input.items() if k not in ['ktick_inds','ktick_vals','kseg_inds']}
        #Update at last
        self.InGui.output = self.input
        self.buttons['load_graph'].tooltip = "Current Input\n{!r}".format(vp.Dict2Data(self.input))
        self.buttons['load_graph'].icon = 'fa-refresh'

    @output.capture(clear_output=True,wait=True)
    def __update_table(self,change):
        self.htmls['table'].value = _tabulate_data(self.result)
        _save_data(self.files_dd,self.result) # save data as well.

    @output.capture(clear_output=True,wait=True)
    def __df_out(self,btn):
        self.tab.selected_index = 2
        self.buttons['summary'].description = 'See STD(out/err) Tab'
        paths = [v for (k,v) in self.files_dd.options]
        df = generate_summary(paths_list=paths)
        display(df)
        self.df = df # Assign
        print('Get above DataFrame by app_name.df\nNote: app_name is variable name assigned to VasprunApp()')
        print('==================== OR =========================')
        _code = "import pivotpy as pp\npaths = ['{}']\ndf = pp.generate_summary(paths_list=paths)\ndf".format("',\n         '".join([str(p).replace('\\','/') for p in paths]))
        _code += "\n#ax = pp.get_axes()\n#df.plot(ax=ax,x='sys',y=['V','a'])"
        display(Markdown("```python\n{}\n```".format(_code)))
        self.buttons['summary'].description = 'Project Summary'

    @output.capture(clear_output=True,wait=True)
    def __deleter(self,btn):
        self.buttons['confirm'].description = 'Deleting ...'
        if self.files_dd.value:
            print('Deleting Selected Cache...')
            self.__clear_cache() # Deleting
            print('Done')
        self.__update_table(1) #Update when delete data
        self.buttons['confirm'].description='Confirm Delete'

    @output.capture(clear_output=True,wait=True)
    def __update_xyt(self,change):
        if self.texts['xyt'].value:
            xyt_text = self.texts['xyt'].value.split(',')
            try:
                self.fig.update_xaxes(title=xyt_text[0])
                self.fig.update_yaxes(title=xyt_text[1])
                self.fig.update_layout(title=xyt_text[2])
            except: pass #do nothing else

        if self.texts['ktickv'].value or self.texts['kticks'].value or self.texts['elim'].value:
            self.__update_input(change=None)
        if self.dds['band_dos'].value == 'Bands' and self.data:
            tickvals = [self.data.kpath[i] for i in self.input['ktick_inds']]
            self.fig.update_xaxes(ticktext=self.input['ktick_vals'], tickvals=tickvals)
        if self.texts['elim'].value and self.input['elim'] != None and len(self.input['elim']) == 2:
            if self.dds['band_dos'].value == 'Bands':
                self.fig.update_yaxes(range = self.input['elim'])
            else:
                self.fig.update_xaxes(range = self.input['elim'])

    @output.capture(clear_output=True,wait=True)
    def __tog_b(self,tog_w):
        _color_toggle(self.buttons['toggle'],self.fig,self.dds['band_dos'])

    @output.capture(clear_output=True,wait=True)
    def __save_connected(self,btn):
        s_p = os.path.split(self.files_dd.value)[0]
        filename = os.path.join(s_p,'ConnectedFig.html')
        filename = gu.prevent_overwrite(filename)
        self.buttons['save_fig'].description = 'Saving...'
        views = VBox([self.htmls['theme'],self.fig,self.htmls['table']],
                layout=Layout(width='500px',height='490px')).add_class('borderless')
        embed_minimal_html(filename, views=[views], state=dependency_state([views]))
        self.buttons['save_fig'].description = 'Save Fig'
        self.buttons['save_fig'].tooltip = 'Recently Saved\n{!r}'.format(filename)

    @output.capture(clear_output=True,wait=True)
    def __expand_fig(self,btn):
        self.tab.selected_index = 2
        self.dds['en_type'].value = 'None' # To avoid accidental clicks
        display(self.fig)
    # Garph
    @output.capture(clear_output=True,wait=True)
    def __update_graph(self,btn):
        path = self.files_dd.value
        if path:
            self.__fill_ticks() # First fill ticks, then update input
            self.__update_input(change=None) # Update input here as well
            self.tab.selected_index = 2
            self.fig.data = []
            if self.data and path == self.__path: # Same load and data exists, heeps in fast
                print('Data already loaded')
            else:
                try:
                    self.buttons['load_graph'].description = 'Loading pickle...'
                    print('Trying to Load Cache for Graph ...')
                    file = os.path.join(os.path.split(path)[0],'vasprun.pickle')
                    self.buttons['load_graph'].description = file
                    self.data = vp.load_from_dump(file)
                    self.buttons['load_graph'].description = 'Load Graph'
                except:
                    self.buttons['load_graph'].description = 'Loading export...'
                    print('No cache found. Loading from file {} ...'.format(path))
                    self.data = vp.export_vasprun(path, **self.evr_kws)
                self.buttons['load_graph'].description = "Load Graph"

            print('Done')

            self.__path  = self.files_dd.value #update here or __on_load. Useful to match things
            self.buttons['load_graph'].description = 'Load Graph'
            _ = self.__read_data(self.data.poscar,self.data.sys_info) # Update Table data
            # Do Not Read Fermi, its 0 or given by user
            if self.dds['band_dos'].value == 'Bands':
                fig_data = ip.iplot_rgb_lines(path_evr=self.data,**self.input,**self.ibands_kws)
            else:
                self.dds['en_type'].value = 'None' # Avoid random clicks
                fig_data = ip.iplot_dos_lines(path_evr=self.data,**self.input,**self.idos_kws) # input auto-modified

            self.tab.selected_index = 1
            with self.fig.batch_animate():
                for d in fig_data.data:
                    self.fig.add_trace(d)
                fig_data.layout.template = self.dds['style'].value # before layout to avoid color blink
                self.fig.layout = fig_data.layout

            _click_data(self.dds['en_type'],self.texts['fermi'],self.result,self.fig,self.dds['band_dos'])
            self.buttons['load_graph'].icon = 'fa-check'

    @output.capture(clear_output=True,wait=True)
    def __clear_cache(self):
        self.tab.selected_index = 2
        _dir = os.path.split(self.files_dd.value)[0]
        if 'Table' in self.dds['cache'].value:
            for k in self.result.keys(): # Avoid deleting V,a,b,Fermi
                if k not in ['sys','V','a','b','c','Fermi']:
                    self.result[k] = ''
        if 'PWD' in self.dds['cache'].value:
            _files = [os.path.join(_dir,f) for f in ['sys_info.pickle','vasprun.pickle']]
            _ = [[print("Deleting", _file),os.remove(_file)] for _file in _files if os.path.isfile(_file)]
        if 'All' in self.dds['cache'].value:
            for (key, value) in self.files_dd.options:
                _dir = os.path.split(value)[0]
                _files = [os.path.join(_dir,f) for f in ['sys_info.pickle','vasprun.pickle']]
                _ = [[print("Deleting", _file),os.remove(_file)] for _file in _files if os.path.isfile(_file)]
        self.tab.selected_index = 1

    def iplot(self,**kwargs):
        "Returns a detached interactive Figure. `kwargs` are passed to `iplot_rgb_lines` or `iplot_dos_lines` based on current figure. `kwargs` should exclude whatever inside `self.input` and `path_evr`"
        kwargs = {k:v for k,v in kwargs.items() if k not in self.input.keys() or k!='path_evr'}
        if self.dds['band_dos'].value == 'Bands':
            return ip.iplot_rgb_lines(path_evr=self.data,**self.input,**kwargs)
        else:
            return ip.iplot_dos_lines(path_evr=self.data,**self.input,**kwargs)

    def splot(self,**kwargs):
        "Returns matplotlib Axes.`kwargs` are passed to `splot_rgb_lines` or `splot_dos_lines` based on current figure. `kwargs` should exclude whatever inside `self.input` and `path_evr`"
        kwargs = {k:v for k,v in kwargs.items() if k not in self.input.keys() or k!='path_evr'}
        if self.dds['band_dos'].value == 'Bands':
            return sp.splot_rgb_lines(path_evr=self.data,**self.input,**kwargs)
        else:
            return sp.splot_dos_lines(path_evr=self.data,**self.input,**kwargs)

# Cell
class KPathApp:
    """View and trace path on BZ.
    - **Usage**
        > ka = KPathApp()
        > ka.show() #Display app
        > ka.splot() #get matplotlib figure
    """
    output = ipw.Output().add_class('output')
    def __init__(self,path='POSCAR'):
        self.path = path
        self.files_gui, self.files_dd = get_files_gui(auto_fill='POSCAR')
        self.files_dd.layout.width = '50%'
        self.main_class = 'custom-'+''.join(np.random.randint(9,size=(21,)).astype(str)) #Random class
        self.tab = ipw.Tab(children=[self.files_gui,Box([]),KPathApp.output]).add_class(self.main_class)
        self.tab.add_class('marginless').add_class('borderless')
        self.tab.layout = Layout(width='100%',min_width='600px',height='450px',min_height='450px')
        try:
            for i,title in enumerate(['Home','Main','STDERR']):
                self.tab.set_title(i,title)
        except:
            self.tab.titles = ['Home','Main','STDERR']
        self.app = self.tab
        self.fig = go.FigureWidget()
        self.fig.layout.template = 'plotly_white' #Forces to avoid colored patch in background
        self.bz = None
        self.kcsn = [] #KPOINTS, COORDS,SYMBOLS, N_per_interval and box symbol in dictionary per item
        self.buttons = {'delete':Button(description='Delete Selection'),
                        'add':Button(description='Add Point'),
                        'patch':Button(description='Split Path'),
                        'fig_up': Button(description='Update Figure'),
                        'theme': Button(description='Dark Theme')}
        self.sm = SelectMultiple(layout=Layout(width='100%'))
        self.texts = {'label':Text(description='Label, N',indent=False),
                      'kxyz':Text(description='kx, ky, kz',indent=False)}
        self.theme_html = ipw.HTML(css_style(light_colors,_class=self.main_class))

        self.buttons['delete'].on_click(self.__delete)
        self.buttons['add'].on_click(self.__add)
        self.buttons['patch'].on_click(self.__add_patch)
        self.buttons['theme'].on_click(self.__toggle_theme)
        self.buttons['fig_up'].on_click(self.__update_fig)
        self.texts['label'].on_submit(self.__label)
        self.texts['kxyz'].on_submit(self.__manual_k)

        self.__update_fig()
        self.__build()

    @output.capture(clear_output=True,wait=True)
    def __toggle_theme(self,change):
        _style = '''<style>.widget-select-multiple>select {{
            font-family: "Cascadia Code","Ubuntu Mono","SimSun-ExtB","Courier New";
            background:{next_bg};border-radius:0;color:{accent};border:none;
            height:auto;min-height:160px;padding:5px;margin:0px;overflow:auto;}}
        .widget-select-multiple>select>option:hover,
        .widget-select-multiple>select>option:focus{{background:{hover_bg};}}</style>'''
        if self.buttons['theme'].description == 'Dark Theme':
            self.theme_html.value = css_style(dark_colors,_class = self.main_class) + _style.format(**dark_colors)
            self.fig.layout.template = 'plotly_dark'
            self.fig.layout.paper_bgcolor = dark_colors['main_bg'] #important
            self.buttons['theme'].description = 'Light Theme'
        else:
            self.theme_html.value = css_style(light_colors,_class=self.main_class) + _style.format(**light_colors)
            self.fig.layout.template = 'plotly_white'
            self.fig.layout.paper_bgcolor = light_colors['main_bg']
            self.buttons['theme'].description = 'Dark Theme'

    @output.capture(clear_output=True,wait=True)
    def __manual_k(self,change):
        for i in self.sm.value:
            self.kcsn[i]['k'] = [float(v) for v in self.texts['kxyz'].value.split(',') if v != ''][:3]

        self.texts['kxyz'].value = '' # clean it
        self.__update_label()
        self.__update_selection() #Change on graph too

    def __label_at(self,i):
        self.kcsn[0]['b'], self.kcsn[-1]['b'] = '┌', '└'  #Avoid clashes
        _ln_ = f"─ {self.kcsn[i]['n']}" if self.kcsn[i]['n'] and i < (len(self.sm.options) - 1) else ""
        if self.kcsn[i]['k']:
            return "{0} {1} {2:>8.4f}{3:>8.4f}{4:>8.4f} {5}".format(self.kcsn[i]['b'],self.kcsn[i]['s'],*self.kcsn[i]['k'],_ln_)
        return f"{self.kcsn[i]['b']} {self.kcsn[i]['s']} {_ln_}"

    def __update_label(self):
        opt = list(self.sm.options)
        vs = self.sm.value # get before it goes away
        for i in vs:
            opt[i] = (self.__label_at(i),i)

        self.sm.options = tuple(opt)

    @output.capture(clear_output=True,wait=True)
    def __build(self):
        for k,b in self.buttons.items():
            b.layout.width = 'max-content'
        for k,t in self.texts.items():
            t.layout.width='85%'
        top_row = HBox([self.files_dd,self.buttons['fig_up']]).add_class('borderless')
        _buttons1 = HBox([self.buttons[b] for b in ['add','delete']]).add_class('borderless')
        _buttons2 = HBox([self.buttons[b] for b in ['patch','theme']]).add_class('borderless')
        self.tab.children = [self.tab.children[0],
                            HBox([
                                  VBox([self.theme_html,
                                        VBox([top_row,_buttons1,_buttons2],
                                        layout = Layout(min_height='140px')),
                                        Box([self.sm]).add_class('marginless').add_class('borderless'),
                                        *self.texts.values()],
                                  layout=Layout(min_width='320px')).add_class('borderless'),
                                  Box([self.fig]).add_class('borderless')],
                            layout=Layout(height='400px',width='auto')).add_class('borderless'),
                            self.tab.children[-1]]

    def show(self):
        return display(self.tab)

    def _ipython_display_(self):
        self.show()

    @output.capture(clear_output=True,wait=True)
    def __delete(self,change):
        v = self.sm.value
        for i, kp in enumerate(self.kcsn):
            self.kcsn[i]['b'] = '├' #Break Path Retrieve first
        self.kcsn = [k for i,k in enumerate(self.kcsn) if i not in v]
        self.sm.options = [(self.__label_at(i),i) for i,op in enumerate(self.kcsn)]

    @output.capture(clear_output=True,wait=True)
    def __add(self,change):
        if self.kcsn:
            self.kcsn.append({'k':[],'c':[],'s':'','n':'', 'b': '└'})
        else:
            self.kcsn.append({'k':[],'c':[],'s':'','n':'', 'b': '┌'})
        for i, kp in enumerate(self.kcsn[1:-1]):
            self.kcsn[i+1]['b'] = '├'

        self.sm.options = [*self.sm.options,('You just added me',len(self.sm.options))]
        self.sm.value = (len(self.sm.options) - 1,) # make available

    @output.capture(clear_output=True,wait=True)
    def __add_patch(self,change):
        vs = [v for v in self.sm.value if v > 1 and v < len(self.sm.options) - 1]
        opts = list(self.sm.options)
        for i,v in enumerate(self.sm.options):
            # Clean previous action
            if i > 0 and i < len(opts) - 1: #Avoid end points
                self.kcsn[i]['b'] = '├'
                opts[i] = (self.__label_at(i),i)
            #Patch before selection
            if i in vs:
                self.kcsn[i]['b'] = '┌'
                self.kcsn[i-1]['b'] = '└'
                opts[i] = (self.__label_at(i),i)
                opts[i-1] = (self.__label_at(i-1),i-1)

        self.sm.options = opts
        self.__update_selection()


    @output.capture(clear_output=True,wait=True)
    def get_coords_labels(self):
        "`coords` are calculated for current `bz` even if `kpoints` were from other one. Useful in case of same kind of Zones with just basis changed."
        if self.bz:
            for i, kp in enumerate(self.kcsn):
                self.kcsn[i]['c'] = sio.to_R3(self.bz.basis,kp['k']).tolist() if kp['k'] else []

        coords = [kp['c'] for kp in self.kcsn]
        labels = [kp['s'] for kp in self.kcsn]
        j = 0
        for p in self.get_patches()[:-1]:
            labels.insert(p.stop+j,'NaN')
            coords.insert(p.stop+j,[np.nan,np.nan,np.nan])
            j += 1

        coords = np.array([c for c in coords if c])
        labels = [l for l in labels if l]
        return coords,labels

    @output.capture(clear_output=True,wait=True)
    def __update_selection(self):
        coords,labels = self.get_coords_labels()
        with self.fig.batch_animate():
            for trace in self.fig.data:
                if 'path' in trace.name and coords.any():
                    trace.x = coords[:,0]
                    trace.y = coords[:,1]
                    trace.z = coords[:,2]
                    trace.text = labels

    @output.capture(clear_output=True,wait=True)
    def __click(self):
        def handle_click(trace, points, state):
            if points.ys != []:
                index = points.point_inds[0]
                kp = trace.hovertext[index]
                kp = [float(k) for k in kp.split('[')[1].split(']')[0].split()]
                cp = [trace.x[index],trace.y[index],trace.z[index]]
                for i in self.sm.value:
                    self.kcsn[i]['k'] = kp
                    self.kcsn[i]['c'] = cp

                self.__update_label()
                self.__update_selection()

        for trace in self.fig.data:
            if 'HSK' in trace.name:
                trace.on_click(handle_click)

    @output.capture(clear_output=True,wait=True)
    def __update_fig(self,change=None):
        if self.files_dd.value:
            self.path = self.files_dd.value
            self.bz = sio.get_bz(self.path)
            fig_data = sio.iplot_bz(self.bz,fill=False,color='red',background='rgba(1,1,1,0)')
            self.fig.data = []
            with self.fig.batch_animate():
                self.fig.add_trace(go.Scatter3d(x = [],y = [],z = [],
                    mode='lines+text+markers',name='path',text=[],
                    textfont_size=18))
                self.__update_selection() #Show previous path on current fig.
                for trace in fig_data.data:
                    self.fig.add_trace(trace)

                self.fig.layout = fig_data.layout
                self.fig.layout.autosize=True
                self.fig.layout.scene.aspectmode = 'data' #very important

            self.__click()


    @output.capture(clear_output=True,wait=True)
    def __label(self,change):
        for i in self.sm.value:
            inbox = self.texts['label'].value.split(',')
            self.kcsn[i]['s'] = 'Γ' if 'am' in inbox[0] else inbox[0] #fix gamma
            try: self.kcsn[i]['n'] = int(inbox[1])
            except: pass
            self.texts['label'].value = '' # Clear it
        self.__update_label()
        self.__update_selection()

    def get_patches(self):
        bs = [kp['b'] for kp in self.kcsn]
        patches = [*[i for i,b in enumerate(bs) if '┌' in b],len(self.kcsn)]
        _patches = [range(i,j) for i,j in zip(patches[:-1],patches[1:])]
        if _patches and len(_patches) <= 1:
            return []
        return _patches

    def get_data(self):
        obj = [{k:v for k,v in kp.items() if k != 'b'} for kp in self.kcsn]
        patches = self.get_patches()
        fmt_str = "{0:>16.10f}{1:>16.10f}{2:>16.10f} !{3} {4}"
        if patches:
            p_strs = ['\n'.join([fmt_str.format(
                                *self.kcsn[p]['k'],self.kcsn[p]['s'],self.kcsn[p]['n']
                                               ) for p in ps]) for ps in patches]
            return obj, '\n\n'.join(p_strs)
        else:
            return obj,'\n'.join([fmt_str.format(*kp['k'],kp['s'],kp['n']) for kp in self.kcsn])

    def get_kpath(self,n=5,weight=None,ibzkpt=None,outfile=None):
        "See Docs of pp.str2kpath for details."
        kws = dict(n=n,weight=weight,ibzkpt=ibzkpt,outfile=outfile)
        _, k_str = self.get_data()
        return sio.str2kpath(k_str,**kws)

    def splot(self,**kwargs):
        "Same as `pp.splot_bz` except it also plots path on BZ. `kwargs` are passed to `pp.splot_bz`"
        ax = sio.splot_bz(path_pos_bz=self.path,**kwargs)
        coords,labels = self.get_coords_labels()
        plane = kwargs.get('plane',None)
        if plane != None and plane in 'xyzxzyx':
            ind = 'xyzxzyx'.index(plane)
            arr = [0,1,2,0,2,1,0]
            ix,iy = arr[ind],arr[ind+1]
            coords = coords[:,[ix,iy]]
        if coords.any(): #To avoid errors if not coords
            ax.plot(*coords.T,'-o',color='blue',lw=0.8)
            _ = [ax.text(*vs,lab) for vs,lab in zip(coords,labels) if lab!='NaN']
        return ax
    def iplot(self):
        "Returns disconnected current plotly figure"
        return go.Figure(data=self.fig.data, layout=self.fig.layout)