How to draw a scatter plot on mapbox using the CSV file upload by the user

Hi, I try to create an app that draws a scatter plot on mapbox using the file upload by the user. Can anyone teach me how to do it?

Below is the code I write but it is a mess:

import base64
import io
import dash
from dash.dependencies import Input, Output, State
import dash_core_components as dcc
import dash_html_components as html
import dash_table
import pandas as pd
import plotly.graph_objs as go

external_stylesheets = ['https://codepen.io/chriddyp/pen/bWLwgP.css']

app = dash.Dash(__name__, external_stylesheets=external_stylesheets)

token = 'pk.eyJ1IjoieXVlY2hlYW45NiIsImEiOiJjazFuOWE1ZXMwNGQwM2JxZmdjbmwwZm9oIn0.aXI4zq7ubVYbUEv9K7rDmQ'

app.layout = html.Div([
    dcc.Upload(
        id='datatable-upload',
        children=html.Div([
            'Drag and Drop or ',
            html.A('Select Files')
        ]),
        style={
            'width': '100%', 'height': '60px', 'lineHeight': '60px',
            'borderWidth': '1px', 'borderStyle': 'dashed',
            'borderRadius': '5px', 'textAlign': 'center', 'margin': '10px'
        },
    ),    
    dash_table.DataTable(id='datatable-upload-container'),
    html.Div(dcc.Graph(id="my-graph"))
])


def parse_contents(contents, filename):
    content_type, content_string = contents.split(',')
    decoded = base64.b64decode(content_string)
    if 'csv' in filename:
        # Assume that the user uploaded a CSV file
        return pd.read_csv(
            io.StringIO(decoded.decode('utf-8')))
    elif 'xls' in filename:
        # Assume that the user uploaded an excel file
        return pd.read_excel(io.BytesIO(decoded))


@app.callback(Output('datatable-upload-container', 'data'),
              [Input('datatable-upload', 'contents')],
              [State('datatable-upload', 'filename')])
def update_output(contents, filename):
    if contents is None:
        return [{}]
    df = parse_contents(contents, filename)
    data = df.to_dict('records')
    return data


@app.callback(Output("my-graph", "figure"),           
             [Input('datatable-upload-container', 'data')])
def update_figure(rows):
        df= pd.DataFrame(rows)
        trace= go.Scattermapbox(lat=df["lat"], lon=df["long"], mode='markers', marker=go.Marker(
            size=14, text = 'Test',hoverinfo = 'text'))
        return {"data": trace,
                "layout": go.Layout(autosize=True, hovermode='closest', showlegend=False, height=700,  
                                    mapbox={'accesstoken': token, 'zoom': 3, 'style' : 'outdoors'
                                           })}

if __name__ == '__main__':
    app.run_server(debug=False)

I really need someone to teach me how to do it.

I think you’re pretty close - two things I see are:

  • your DataTable needs columns specified - so you can make update_output into a multi-output callback like
@app.callback(
    [Output('datatable-upload-container', 'data'),
     Output('datatable-upload-container', 'columns')],
    [Input('datatable-upload', 'contents')],
    [State('datatable-upload', 'filename')])
def update_output(contents, filename):
    if contents is None:
        return [{}]
    df = parse_contents(contents, filename)
    data = df.to_dict('records')
    columns = [{"name": i, "id": i} for i in df.columns]
    return data, columns
  • figure.data should be a list of traces, not a single trace, so return {"data": [trace], ...

If something is still going wrong, feel free to post some details of the errors you’re seeing.

1 Like

Hi @alexcjohnson thanks for the reply, so I change the code with what you teach me and get two errors which are:

error1

error2

The ‘lat’ is the header of the column which contains latitude value in the CSV file upload by the user. Bu I don’t know why it got an error.

The lower error came first, the upper one most likely flowed from it. As that lower error says, it was expecting 2 values returned (data and columns) and only got one - see the last line in the callback I posted:

return data, columns

does yours look like this?

1 Like

@alexcjohnson thanks for the help. I solved both errors. For the lower error, need to return one more empty list:

if contents is None:
        return [{}], []

and for the upper error just need to create statement if df.empty ‘lat’=[]

1 Like