Below a basic example to showcase a possible solution .
But… yeah, it feels kind of ancient windows98-ish…
Dragging dropping of rows and columns, would render all buttons obsolete and kick this example into the 2020’s, where panel belongs.
Maybe, adding dragging the layout sizes with the mouse, etc. would be very useful when working on data (preparing it for Panel’s nice visualizers.)
Performance also seems to lack behind, but this is probably mainly due to the fact that this is my 1st attempt at taming Panel.
Feel free anyone to add/remove stuff that enhance performance. That would be much appreciated.
import numpy as np
import pandas as pd
import panel as pn
from panel.viewable import Viewer
import param
pn.extension(sizing_mode = 'stretch_width')
pn.extension('tabulator')
class Table_ColumnEditor(Viewer):
df = param.DataFrame(default = pd.DataFrame())
value = param.DataFrame(default = pd.DataFrame())
def __init__(self, df, **params):
super().__init__(**params)
self.df = df
cols_df = pd.DataFrame({'cols':self.df.columns}, index = range(len(self.df.columns)))
self.table = pn.widgets.Tabulator(pd.DataFrame())
self.src_col_table = pn.widgets.Tabulator(cols_df,
hidden_columns = ['index'],
editors = {'cols':None},
configuration = {'headerVisible': False,
#'movableRows':self.moveable_rows
}
)
self.trg_col_table = pn.widgets.Tabulator(pd.DataFrame({'cols':pd.Series(dtype='str')}),
hidden_columns = ['index'],
buttons={'remove': '<i class="fa fa-times"></i>'},
editors = {'cols':None},
configuration = {'headerVisible': False,
#'movableRows':self.moveable_rows
}
)
self.add_column_btn = pn.widgets.Button(name = '>')
self.add_order_up = pn.widgets.Button(name = 'up')
self.add_order_down = pn.widgets.Button(name = 'down')
self.trg_col_table.on_click(self.trg_col_table_click)
self.add_column_btn.on_click(self.add_rows)
self.add_order_up.on_click(self.move_rows)
self.add_order_down.on_click(self.move_rows)
self.trg_col_table.link(self.table , callbacks={'value':self.columns_changed})
self.table.link(self.value , callbacks={'value':self.value_changed})
self._layout = pn.Column(pn.Row(pn.Card(self.src_col_table,
title='SOURCE COLUMNS'),
self.add_column_btn,
pn.Card(pn.Column(pn.Row(self.add_order_up,
self.add_order_down
),
self.trg_col_table
),
title='TARGET COLUMNS'
)
),
pn.Card(self.table,
title='RESULT'
)
)
def __panel__(self):
return self._layout
def columns_changed(self, target, event):
self.table.value = self.df[self.trg_col_table.value['cols'].tolist()]
def value_changed(self, target, event):
self.value = self.table.value
def trg_col_table_click(self, o):
if o.column == 'remove':
self.remove_rows([o.row])
def add_rows(self, event):
src_selected_rows = self.src_col_table.value.loc[self.src_col_table.selection].values.tolist()
df = self.trg_col_table.value
df_items = df.values.tolist()
for src_selected_row in src_selected_rows:
if src_selected_row in df_items:
continue
df.loc[df.shape[0]] = src_selected_row
df.reset_index(drop=True, inplace=True)
self.trg_col_table.value = df
def remove_rows(self, remove_id_lst):
if not remove_id_lst:
return
df = self.trg_col_table.value
df.drop(remove_id_lst, axis=0, inplace=True)
df.reset_index(drop=True, inplace=True)
self.trg_col_table.value = df
def move_rows(self, event):
df = self.trg_col_table.value
ids = df.index.tolist()
ids_count = len(ids)
sel_ids = sorted(self.trg_col_table.selection)
sel_count = len(sel_ids)
new_sel_ids = []
if event.obj == self.add_order_down:
new_sel_ids = [x+1 if x+1<ids_count-(sel_count-1-i) else x for i,x in enumerate(sel_ids)]
elif event.obj == self.add_order_up:
new_sel_ids = [x-1 if x-1>=i else x for i,x in enumerate(sel_ids)]
popped = [ids.pop(x-i) for i,x in enumerate(sel_ids)]
[ids.insert(x, popped[i]) for i,x in enumerate(new_sel_ids)]
df = df.loc[ids]
df.reset_index(drop=True, inplace=True)
self.trg_col_table.value = df
self.trg_col_table.selection = new_sel_ids
if __name__=='__main__':
from pandas import util
view = Table_ColumnEditor(util.testing.makeMixedDataFrame(), name='Test')
view.show()