Dashboard Tutorial (I): Flask and Chart.js | Towards Data Science

When it comes to creating the web with Python, Flask is often the common backend applied by developers. Flask enables HTTP requests’ management and templates rendering. For this blog, we’ll walk through the basic structure of flask and how flask is able to render the template from other webpages. Besides, you’ll learn to load a comma-separated value (CSV) file and visualize it using Chart.js.
In this article, you will learn:
(1) Creating a Basic Flask Application
(2) Chart.JS plot
First, we create a .py file to import flask packages and set up flask configuration. Flask would call HTML file from the templates folder without any specified path. In order to direct flask to the right folder, we specify template_folder and static_folder path. In the static folder, it normally stores javascript, CSS files, and images. In the template folder, it normally stores HTML files. Besides, we set app.config equal True for the terminal to show the error messages. In addition, the error message would be popped out in the console under the inspect element.
import flaskapp = flask.Flask(__name__, static_url_path='',            static_folder='static',            template_folder='template')app.config["DEBUG"] = True
Then, we move on to define the route for the home page. We are able to give a webpage name that can be passed into HTML. Render_template function would call the HTML file and display it on the specified webpage route. For the second function of user, the name tag embedded in the route can be passed into HTML. For instance, when (http://127.0.0.1:5000/home) is given, the content would be “Hello home”. Also, users are able to assign the route of HTML webpage by using redirect function and put the web page's name in url_for function.
from flask import render_template, redirect, url_for@app.route("/home")def home():    return render_template("home.html")@app.route("/<name>")def user(name):    return f"Hello-- {name}!"@app.route("/admin")def admin():    return redirect(url_for("home"))
While the above set-up is completed, you are ready to run flask application by initiating the host and port.
if __name__ == '__main__':    app.run(host="localhost", port=8000, debug=True)
Chart.JS becomes a popular and powerful data visualization library. The library adds its flexibility for users to customize the plots by feeding certain parameters, which meets users’ expectations. The main advantage of creating plots from Chart.js is the basic code structure that allows users to display plots without creating numerous lines of codes.
Before getting started, make sure to call the Chart.js files either by calling the webpage or specifying the local directory with the downloaded files.
<script src="https://cdnjs.cloudflare.com/ajax/libs/Chart.js/2.5.0/Chart.min.js"></script>
We also need to create a canvas node to render the chart and specify the size of the plot with its width and height.
<canvas id="myChart" width="400" height="400"></canvas>
For the following 4 plots examples using Chart.js, I mainly use the Epidemiological Data from the COVID-19 Outbreak in Canada data source. The data can be accessed with its link.
First, we will load the CSV file by calling the d3 library and the file would be rendered into makeChart function. Next, we define the function with the data parameter and map the columns of data into certain variables.
d3 .csv(“/file/Bar_cases.csv”) .then(makeChart);function makeChart(data) {      var country = data.map(function(d) {return d.province;});      var value = data.map(function(d) {return d.cumulative_cases;});
Then, we create the chart by calling the canvas id through getElementById. Bar Chart is simply generated by specifying the type as the bar (to flip the direction of the bars, set type to horizontalBar). The label of the bar chart is set with the provinces’ names and data are the cumulative cases in May. You are able to create legend and title through parameters.
// Bar chart      new Chart(document.getElementById("myCanvas"), {          type: 'bar',          data: {            labels: country,            datasets: [              {                label: "Population (millions)",                backgroundColor: ["#3e95cd", "#8e5ea2","#3cba9f","#e8c3b9","#c45850"],                data: value              }            ]          },          options: {            legend: { display: false },            title: {              display: true,              text: 'Total Confirmed Cases of COVID in May'            }          }      });
Through the bar chart below, we can see the information by the hovered information. Quebec has the most people infected with COVID among 5 provinces, while Ontario is the second highest one.
First, we need to prepare the dataset for the line plot. To create the time-series line plot, we need to filter monthly confirmed cases of COVID19 for each province. OnlyUnique function is to generate the unique values from an array. forEach function is able to locate the same index on the multiple arrays through the iteration of values and indexes on an array.
// get unique value of array    function onlyUnique(value, index, self) {    return self.indexOf(value) === index;      }    var mon_unique = month.filter( onlyUnique );province.forEach((pro, index) => {      if (pro =="Alberta") {      ab_val.push(value[index]);    } else if (pro =="BC") {      BC_val.push(value[index]);    } else if (pro =="Manitoba") {      mb_val.push(value[index]);    } else if (pro =="New Brunswick") {      nb_val.push(value[index]);    }     else {      nl_val.push(value[index]);    }    });
Line Chart is simply generated by specifying the type as the line. For each line, we can feed the label and data wrapped in a dataset list. By default, the area under each line is filled with color, covering the area between the line and the x-axis. The color can be removed by specifying (fill: false). Also, the color can be specified with the borderColor parameter.
// Line chart    new Chart(document.getElementById("myCanvas"), {  type: 'line',  data: {    labels: mon_unique,    datasets: [{        data: ab_val,        label: "Alberta",        borderColor: "#3e95cd",        fill: false      }, {        data: BC_val,        label: "BC",        borderColor: "#8e5ea2",        fill: false      }, {        data: mb_val,        label: "Manitoba",        borderColor: "#3cba9f",        fill: false      }, {        data: nb_val,        label: "New Brunswick",        borderColor: "#e8c3b9",        fill: false      }, {        data: nl_val,        label: "NL",        borderColor: "#c45850",        fill: false      }    ]  },  options: {    title: {      display: true,      text: 'Positive Cases of COVID in provinces of Canada'    },    hover: {     mode: 'index',     intersect: true    },  }});
From the line plot below, 4 provinces except BC are reported with 0 confirmed cases in January and February. From mid-February, there is a gradual increase in confirmed cases in Alberta and BC. Starting in March, a substantial increase of positive cases appear in Alberta as the AB province is the top among the 5 provinces. On the other hand, provinces like MB, NB, and NL are reported with low confirmed COVID cases. In May, the curve is flattened with fewer COVID cases.
First, we need to prepare the dataset for the doughnut plot. Each province array is processed and have the final input of the cumulative cases in May.
//donut chart, get the end of MAy & display the cumulative cases of 5 provinces// get unique value of array    function onlyUnique(value, index, self) {    return self.indexOf(value) === index;      }    var pro_unique = province.filter( onlyUnique );    var ab_val = []    var BC_val = []    var mb_val = []    var nb_val = []    var nl_val = []date.forEach((i_dt, index) => {      if (i_dt == '2020-05-31' && province[index] =="Alberta") {      ab_val.push(value[index]);    } else if (i_dt == '2020-05-31' && province[index] =="BC") {      BC_val.push(value[index]);    } else if (i_dt == '2020-05-31' && province[index] =="Manitoba") {      mb_val.push(value[index]);    } else if (i_dt == '2020-05-31' && province[index] =="New Brunswick") {      nb_val.push(value[index]);    }     else if (i_dt == '2020-05-31' && province[index] =="NL") {     nl_val.push(value[index]);    }    });
There are a total of 6 buttons. Button randomizeData enables data randomization while the area of each label can appear in proportion. For the other 4 buttons, they support the function to add or remove the data. Besides, the button like Semi/Full Circle, allows the circle to expand and shrink into fall and half size. Once every button is clicked, it will call the Doughnut.update() function to have the plot updated.
<button id="randomizeData">Randomize Data</button><button id="addDataset">Add Dataset</button><button id="removeDataset">Remove Dataset</button><button id="addData">Add Data</button><button id="removeData">Remove Data</button><button id="changeCircleSize">Semi/Full Circle</button>window.chartColors = {      red: 'rgb(255, 99, 132)',      orange: 'rgb(255, 159, 64)',      yellow: 'rgb(255, 205, 86)',      green: 'rgb(75, 192, 192)',      blue: 'rgb(54, 162, 235)',      purple: 'rgb(153, 102, 255)',      grey: 'rgb(201, 203, 207)'    };var config = {   type: 'doughnut',   data: {    datasets: [{     data: [      ab_val,      BC_val,      mb_val,      nb_val,      nl_val,     ],     backgroundColor: [      window.chartColors.red,      window.chartColors.orange,      window.chartColors.yellow,      window.chartColors.green,      window.chartColors.blue,     ],     label: 'Dataset 1'    }],    labels: pro_unique   },   options: {    responsive: true,    legend: {     position: 'top',    },    title: {     display: true,     text: 'Total counts of confirmed cases in Canada'    },    animation: {     animateScale: true,     animateRotate: true    }   }  };window.onload = function() {   var ctx = document.getElementById('chart-area').getContext('2d');   window.myDoughnut = new Chart(ctx, config);  };document.getElementById('randomizeData').addEventListener('click', function() {   config.data.datasets.forEach(function(dataset) {    dataset.data = dataset.data.map(function() {     return randomScalingFactor();    });   });window.myDoughnut.update();  });var colorNames = Object.keys(window.chartColors);  document.getElementById('addDataset').addEventListener('click', function() {   var newDataset = {    backgroundColor: [],    data: [],    label: 'New dataset ' + config.data.datasets.length,   };for (var index = 0; index < config.data.labels.length; ++index) {    newDataset.data.push(randomScalingFactor());var colorName = colorNames[index % colorNames.length];    var newColor = window.chartColors[colorName];    newDataset.backgroundColor.push(newColor);   }config.data.datasets.push(newDataset);   window.myDoughnut.update();  });document.getElementById('addData').addEventListener('click', function() {   if (config.data.datasets.length > 0) {    config.data.labels.push('data #' + config.data.labels.length);var colorName = colorNames[config.data.datasets[0].data.length % colorNames.length];    var newColor = window.chartColors[colorName];config.data.datasets.forEach(function(dataset) {     dataset.data.push(randomScalingFactor());     dataset.backgroundColor.push(newColor);    });window.myDoughnut.update();   }  });document.getElementById('removeDataset').addEventListener('click', function() {   config.data.datasets.splice(0, 1);   window.myDoughnut.update();  });document.getElementById('removeData').addEventListener('click', function() {   config.data.labels.splice(-1, 1); // remove the label firstconfig.data.datasets.forEach(function(dataset) {    dataset.data.pop();    dataset.backgroundColor.pop();   });window.myDoughnut.update();  });document.getElementById('changeCircleSize').addEventListener('click', function() {   if (window.myDoughnut.options.circumference === Math.PI) {    window.myDoughnut.options.circumference = 2 * Math.PI;    window.myDoughnut.options.rotation = -Math.PI / 2;   } else {    window.myDoughnut.options.circumference = Math.PI;    window.myDoughnut.options.rotation = -Math.PI;   }window.myDoughnut.update();  });
From the plot below, we can see that province of New Brunswick has the largest confirmed cases of COVID while Manitoba also shares a similar proportion of confirmed cases. For plot 4 below, it shows the different display of doughnut chart in a semi-circle layout.
First, we need to prepare the dataset for the Bar Line Chart. This chart mainly places focus on the cumulative_cases, cumulative_recovered, cumulative_deaths, and active_cases_change of COVID in Alberta from January to June. To make the values fairly distributed in the same range, I process the cumulative_cases and cumulative_recovered columns and take logs on the arrays.
// get unique value of array  function onlyUnique(value, index, self) {  return self.indexOf(value) === index;    }  var date_unique = date.filter( onlyUnique );  var ab_case = []  var ab_recover = []  var ab_deaths = []  var ab_case_mean = []  var ab_case_log = []  var ab_recover_log = []province.forEach((i_pro, index) => {    if (i_pro =="Alberta") {    ab_case.push(case1[index]);    ab_recover.push(recover[index]);    ab_deaths.push(deaths[index]);    ab_case_mean.push(case_mean[index]);  }  });// take log on ab_case, ab_recoverab_case.forEach((i_ab, index) => {      ab_recover_log.push(Math.log(ab_recover[index]));      ab_case_log.push(Math.log(i_ab));  });
In Chat.js, it’s flexible to create mixed charts that are a combination of two or more different chart types. For the following example, I create the bar-line chart with separated specification of type parameter.
new Chart(document.getElementById("canvas"), {    type: 'bar',    data: {        datasets: [{            label: 'log on cumulative_cases in Alberta',            backgroundColor: window.chartColors.red,            data: ab_case_log,            borderColor: 'white',        borderWidth: 2        }, {            label: 'log on cumulative_recovered in Alberta',            backgroundColor: window.chartColors.green,            data: ab_recover_log,            borderColor: 'white',        borderWidth: 2        },{            label: 'cumulative_deaths in Alberta',            backgroundColor: window.chartColors.purple,            data: ab_deaths,            borderColor: 'white',        borderWidth: 2        },        {            label: 'Month average of active cases in Alberta',            data: ab_case_mean,            fill: false,            borderColor:window.chartColors.blue,            borderWidth: 2,            // Changes this dataset to become a line            type: 'line'        }],        labels: date_unique    },    options: {      title: {        display: true,        text: 'Positive Cases of COVID in provinces of Canada'      },      hover: {            mode: 'index',            intersect: true          },    }});
From the bar-line chart below, we can see a sharp increase in the average active cases in April and a dramatic decrease in the average active cases in May. From May to June, even though the number of active cases remains negative, there’s an upward curve showing that people infected with COVID get fewer and fewer.
Flask is a powerful backend that works well with HTML and javascript files. Once the directory is specified with the exact folder, the application would render the page by the specified route.
Chart.JS is a visualization library that supports numerous plots like Bar charts, Line Charts, Area Charts, Doughnut charts, etc. The best thing is that you are able to customize the layout like the hovered information, scale range, x and y-axis labels, and so on.
Chart.JS Documentation Website: https://www.chartjs.org/