Some pieces put together from @Marc & @Hoxbro in the forumss stappled togther, what I like most about this is how simple it is to load a file, keep track of what you have loaded and plot the variables in file inside of a nice dashboard. I have often struggled to load datasets and this makes short work of it in a nice way.
#A simple form to view multi column data in a nice dashboard from Panel, works extremley well for small datasets,
#couple of parts needed to be ammended pending on data file format going to be loaded marked with ***
#Insperation from
#@Hoxbro - https://discourse.holoviz.org/t/panel-table-doesnt-update/3137
#@Marc - https://discourse.holoviz.org/t/how-to-create-multiple-instances-of-object-and-display-methods/3157/2
#I apologise to the above for a messy butcher of their work and any messy code I have latterly added, however I find the combination so simply powerful
#with great results in just being able to see data at a glance whilst tracking the loaded datasets easily in app. For myself this had made loading and viewing a breeze and would like to share back!
from io import StringIO
import numpy as np
import pandas as pd
import panel as pn
import param
import holoviews as hv
from holoviews.operation import decimate
pn.extension("tabulator", sizing_mode="stretch_width")
import hvplot.pandas # noqa must be called after pn.extension
ACCENT_COLOR="#0072B5"
from bokeh.core.enums import MarkerType
markers = list(MarkerType)
class Dataset(param.Parameterized):
value = param.DataFrame()
class ReactiveTable(pn.viewable.Viewer):
x = param.ObjectSelector(label="x")
y = param.ObjectSelector(label="y")
new_dataset_input = param.ClassSelector(class_=pn.widgets.FileInput, constant=True)##
dataset = param.Selector()
def __init__(self, **params):
params["new_dataset_input"]=pn.widgets.FileInput(accept = '.csv')
super().__init__(**params)
self.param.dataset.objects=[]
@pn.depends("new_dataset_input.value", watch = True)
def _parse_new_data_set(self):
value = self.new_dataset_input.value
if not value:
return
string_io = StringIO(value.decode("utf8"))
dataset = Dataset(value=pd.read_csv(string_io), name=self.new_dataset_input.filename) #instantiate Dataset class and utilise value, dataframe can now be called with dataset.value
dataset.value['Date'] = pd.to_datetime(dataset.value['Date'], format='%d/%m/%Y %H:%M') #used instead of pandas read_csv parse_dates ***pay attention to file datetime format
existing_datasets = self.param.dataset.objects
self.param.dataset.objects=[*existing_datasets, dataset]
self.dataset = dataset
@pn.depends("dataset",watch=True) #use watch due to side effect because of no return,
def get_axis_variables_from_column_names(self):
#populate the x,y selection boxes with column names from dataset
self.param.x.objects = self.dataset.value.columns.values.tolist()
self.param.y.objects = self.dataset.value.columns.values.tolist()
# *** preset the x,y selection boxes here because I know what columns I have in the files I'm going to use for default...
self.y = self.param.y.objects[1]
self.x = self.param.x.objects[0]
@pn.depends("dataset") #if dataset changes load new data to table
def output(self):
if self.dataset:
return pn.widgets.Tabulator(self.dataset.value.head(100), theme="fast", height=800, pagination="remote", page_size=25)
else:
return "No Datasets Loaded"
#at the moment the param reloads the graph if you've zoomed kind of inconvienent but I've read .bind might be better suited here but as yet not sure how to rejig
@param.depends("x", "y","dataset")
def plot(self):
if self.dataset:
return self.dataset.value.hvplot(x=self.x, y=self.y,
grid=True,
min_height=800,
persist=True,
responsive=True,
color="red",
kind="scatter"
)
def __panel__(self):
return pn.template.FastListTemplate(
site="Holoviz",
sidebar = ["Upload Dataset",
self.new_dataset_input,
self.param.dataset,
"change axis variables",
self.param.y, self.param.x,
],
main = [pn.panel(self.plot),
pn.panel(self.output)#, loading_indicator=True) #disabled loading indicator, it's great but sometimes gets stuck in on position
],
title="Simply, An Underestimated Ecosystem of Tools",
theme="dark",
)
#Create random port automatically
from random import randrange
Port = randrange(4000, 8000)
#saves a lot of time changing between .servable and .show brilliant
if __name__ == "__main__":
app = ReactiveTable()
app.show(port=Port)
elif __name__.startswith("bokeh"):
app = ReactiveTable()
app.servable()