A nice simple dashboard to view data files

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

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')
    @pn.depends("new_dataset_input.value", watch = True)
    def _parse_new_data_set(self):
        value = self.new_dataset_input.value
        if not value:
        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)
            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, 
    def __panel__(self):
        return pn.template.FastListTemplate(
            sidebar = ["Upload 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",

#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()
elif __name__.startswith("bokeh"):
    app = ReactiveTable()