A button to upload csv file and plot it

Hi,

I’m trying to create a panel which i’ll be running on a local server (with panel serve “name.ipynb” --port —>open in browser)

I want to have a FileInput button and a figure under it which will update according to file uploaded.
the process is as follows: click on upload button->choose csv file->csv processing->plot results.
I need the csv data to be available for further operations.
i’ve managed to do this with Dash but as far as i can tell, the dash api lacks the option to get current state of a legend in graph (specifically, i need to know which plots are hidden and which plots are shown)

I tried different implementations methods and callbacks with panel but none is working.
can someone provide a working example of such button please?

below is a working code i’ve written with dash. this is the desired behaviour i want to implement with panel.

import plotly.offline as pyo
import plotly.graph_objs as go
import pandas as pd
import base64
import io
from dash.dependencies import Input, Output, State
import dash
import dash_core_components as dcc
import dash_html_components as html
import threading
from test_ui import Ui_MainWindow


def run_dash():
    external_stylesheets = ['https://codepen.io/chriddyp/pen/bWLwgP.css']

    app = dash.Dash(__name__, external_stylesheets=external_stylesheets)
    app.layout = html.Div(children=[
        html.H1(children='Hello Dash'),
        html.Div(children='''
            Dash: A web application framework for Python.
        '''),
        dcc.Upload(
            id='upload',
            children=html.Div([
                'Drag and Drop or ',
                html.A('Select a File')
            ]),
            style={
                'width': '100%',
                'height': '60px',
                'lineHeight': '60px',
                'borderWidth': '1px',
                'borderStyle': 'dashed',
                'borderRadius': '5px',
                'textAlign': 'center',
                'margin': '10px'
            }
        )
        ,
        dcc.Graph(
            id='example-graph',
        )

    ])
    return app


def parse_contents(contents, filename):
    content_type, content_string = contents.split(',')
    decoded = base64.b64decode(content_string)
    try:
        if 'csv' in filename:
            # Assume that the user uploaded a CSV file
            df = pd.read_csv(
                io.StringIO(decoded.decode('utf-8')))
        elif 'xls' in filename:
            # Assume that the user uploaded an excel file
            df = pd.read_excel(io.BytesIO(decoded))
    except Exception as e:
        print(e)
        return html.Div([
            'There was an error processing this file.'
        ])
    return df


def setupFilteredDataframe(df):
    assert ("Voltage" in df.columns) and ("Time" in df.columns), "no columns voltage and time"
    df = (df.loc[df['Voltage'] != 'Invalid/Calib']).copy(deep=True)
    df['Voltage'] = df['Voltage'].apply(lambda x: float(x))
    df.reset_index()

    traces = []

    if "FubId" in df.columns:
        """filter it to contain measurments from a single vdm.
        then set the transformed and filtered data for next steps"""
        df = df[['FubId', 'Time', 'Voltage']]
        fubIds = df['FubId'].copy(deep=True)
        fubIds = fubIds.drop_duplicates()
        lFubIds = fubIds.tolist()

        for fub in lFubIds:
            curr_df = df.loc[df['FubId'] == fub].copy(deep=True)
            traces.append(go.Scatter(
                x=curr_df['Time'],
                y=curr_df['Voltage'],
                mode='lines',
                name=fub
            ))
    else:
        curr_df = df
        traces.append(go.Scatter(
            x=curr_df['Time'],
            y=curr_df['Voltage'],
            mode='lines',
            name='Waveform'))
    return traces

def printAppLayout():
    print(app.layout)



app = run_dash()
# callback figure creation
@app.callback(Output('example-graph', 'figure'),
              [Input('upload', 'contents')],
              [State('upload', 'filename')])
def update_output(contents, filename):
    if contents is not None:
        dff = parse_contents(contents, filename)
        print(dff.head())
        data = setupFilteredDataframe(dff)
        return {
            'data': data,
            'layout': dict(
                title='Time series with range slider and selectors',
                xaxis=dict(rangeslider=dict(visible=True)))
        }
    else:
        return {}


if __name__ == '__main__':

    app.run_server(debug=True,port=3003)
1 Like

Hi @avivazran

Welcome to the community. I’ve started creating an example for you. You can find it here

I think you can get the inspiration you need now. I will finish it and nicefy it later. I’ve filed a pull request on it here https://github.com/holoviz/panel/pull/1193

1 Like

I have cleaned up the example and moved the notebook on request of Philipp.

I’ve added a show button to the app so to start running it in the server.

I’ve shared a tweet. Feel free to share if you like https://twitter.com/MarcSkovMadsen/status/1243405883175911426?s=20

2 Likes

@Marc this is great!!
To me the natural follow-up would be to have the ability to export the manipulated DataFrame to a text file.
I think this would make the web app really powerful, as one could deliver to (clients, colleagues, etcetera) a MVP where they could see the power of work with their own data and get output they can further manipulate elsewhere…

Have you tried that yet?

1 Like

Hi @mycarta

Thanks for the kind words. Yes. I have tried providing download functionality.

I actually contributed an example to the gallery about it https://panel.holoviz.org/gallery/simple/file_download_examples.html#simple-gallery-file-download-examples

Currently it only holds an example of Excel download. But over time I hope it will be extended to csv, parquet, json and other common data formats.

And yes I also think its really powerful. I see people building apps where they can drag and drop a file. Python transforms and plots it and the users can download if they want to explore further. Its the kind of tools that are difficult to find “funding” for but are very easy to create in Panel if you know how.

1 Like

WOW
Thanks Mark, this is really transformative stuff, at least for me as someone that does a lot of scientific computing but is an amateur ‘developer’ (I am even embarrassed to use that word in the same sentence containing “me”)…

2 Likes