Plotly without Dash
While Plotly’s Dash looks like a great project for getting data visualization dashboards up and running quickly, it may not always be the best option, especially for dashboards that over time evolve into complex apps.
So I’ve been looking into harnessing the power of Plotly’s excellent graphing tools, while running technologies that can grow with the app. This post will be somewhat of a note-to-self, to keep track of the viable options for utilizing Plotly on standard technology stacks.
Flask + Plotly Express
Plotly Express is a high level Python wrapper for Plotly.py. Using nothing but Python, HTML and CSS, we can generate websites with graphs generated by Plotly.
First, install Flask, and install the dependencies we’ll need: pip3 install flask plotly
. Next, create the folder /templates
, and add a file graph.html
with this content:
<html>
<head>
<script src="https://cdn.plot.ly/plotly-latest.min.js"></script>
</head>
<body>
<h1>Plotly Demo</h1>
{% block content %}
{{plot | safe }}
{% endblock %}
</body>
</html>
Next, make sure your app looks something like this:
from flask import Flask, render_template
import plotly.express as px
app = Flask(__name__)
@app.route("/")
def express():
df = px.data.iris()
fig = px.scatter(df, x="sepal_width", y="sepal_length", color="species")
plot_as_string = plotly.offline.plot(fig, include_plotlyjs=False, output_type='div')
return render_template("graph.html", plot=plot_as_string)
Type FLASK_APP=app flask run
to run the app, and visit http://localhost:5000 in your favorite web browser. Congratulations! You now have a simple, lightweight Python based server that renders beautiful Plotly graphs.
REST API + React + Plotly
Next, we’ll take a look at a building the user interface in React.
Backend
Our backend will consist of a REST API served by Flask. Setup:
mkdir backend
cd backend
python3 -m venv env
source env/bin/activate
pip install flask pandas flask-cors
Create the file backend/api.py
, and fill it with this content:
from flask import Flask
import pandas as pd
import json
from flask_cors import CORS, cross_origin
app = Flask(__name__)
cors = CORS(app)
app.config['CORS_HEADERS'] = 'Content-Type'
@app.route("/")
@cross_origin()
def our_first_graph():
data = [
{
"x": [1, 2, 3],
"y": [2, 6, 0],
"type": 'scatter',
"mode": 'lines+markers',
"marker": {"color": 'red'},
},
{"type": 'bar', "x": [1, 2, 3], "y": [2, 5, 3]},
]
data_as_json = json.dumps(data)
return data_as_json
Now, start the server: FLASK_APP=api flask run
.
Frontend
The frontend will be a standard React application. Setup:
mkdir frontend; cd frontend
npm install react-plotly.js plotly.js axios
npx create-react-app gui
Run cd gui/src
. Create the folder utils, and add API.js
:
import axios from "axios";
export default axios.create({
baseURL: "http://localhost:5000/",
responseType: "json"
});
Create the file Graph.js
:
import React, {useState, useEffect} from 'react';
import Plot from 'react-plotly.js';
import api from './utils/api';
export default function Graph() {
const [dataFromApi, setDataFromApi] = useState([]);
useEffect(() => {
async function fetchData() {
let userData = await api.get('/')
setDataFromApi(userData.data)
}
fetchData();
}, []);
return (
<Plot
data={dataFromApi}
layout={ {width: 320, height: 240, title: 'A Fancy Plot'} }
/>
);
}
Adjust App.js
to look something like this:
import './App.css';
import Graph from './Graph';
function App() {
return (
<div className="App">
<header className="App-header">
<Graph />
</header>
</div>
);
}
export default App;
Now, start the server like this: BROWSER=none npm start
. Open http://localhost:3000 in a browser, and see the result.
Final thoughts
We’ve looked at two – of many – approaches to building apps that use Plotly’s excellent libraries to visualize data. The two approaches both has their pros and cons. Using Plotly Express, we bundle both backend and frontend into a single server, which makes deployment quite a lot simpler.
On the other hand, separating the backend and frontend like we did with REST API + React, we it’s easier to grow, add or even replace either the frontend or backend. The downside though is a more complex setup with more moving parts and somewhat more difficult deployment.