Patrick's Software Blog

Learning Python Web Application Development with Flask

Creating Charts with Chart.js in a Flask Application

Introduction

As I’ve been learning about developing websites using Python and Flask, I’ve had to learn a decent amount of HTML and CSS in order to make the web pages look decent.  Bootstrap has been the primary framework that I’ve been using to make the web pages look good.  Up to this point, I’ve avoiding doing anything with Javascript as I’ve been satisfied with the combination of HTML/CSS/Bootstrap for the websites that I’ve been developing.

My main motivation for learning Javascript is that I wanted to be able to generate charts/graphs in my web applications.  I found the Chart.js library to be awesome, so that was the motivation for learning Javascript!  Chart.js has almost 27,000 stars on GitHub as of mid-December 2016. Based on my experience, I would highly recommend learning the basics of Javascript first (I thought the Codecademy course called Learning Javascript provided a great introduction).
 
This blog post provides an introduction to Chart.js and provides three examples (of increasing complexity) of line charts.   The documentation for Chart.js is excellent, so this blog post should be considered a supplement to the documentation with a focus on using Chart.js within a Flask application.
 
The source code from this blog post can be found on GitLab with separate tags (example1, example2, and example3) for each example: https://gitlab.com/patkennedy79/flask_chartjs_example

Structure of the Application

The structure of this application will be simple:

.
├── README.md
├── app.py
├── requirements.txt
├── static
│   └── Chart.min.js
└── templates
    ├── chart.html
    ├── line_chart.html
    └── time_chart.html

While there is the option to use a CDN (Content Delivery Network) site to get the Chart.js library, I’ve chosen to just download the library and store it in the static/ directory. In the third example, I’ll show the other method of using a CDN for retrieving the Moment.js library.

Within the templates directory, there are separate template files for each of the three examples:

  • Example #1 – chart.html
  • Example #2 – line_chart.html
  • Example #3 – time_chart.html

Example 1: Simple Line Chart

The first example illustrates utilizing a simple Flask application that originates the data to be displayed by Chart.js as a simple line chart.  I’ll be focusing on the Javascript side of this application, but please refer to my Flask Tutorial for a more detailed description of how to create a Flask application.

Flask Application

The Flask application is contained within the app.py file:
 

from flask import Flask
from flask import render_template
from datetime import time


app = Flask(__name__)


@app.route("/simple_chart")
def chart():
    legend = 'Monthly Data'
    labels = ["January", "February", "March", "April", "May", "June", "July", "August"]
    values = [10, 9, 8, 7, 6, 4, 7, 8]
    return render_template('chart.html', values=values, labels=labels, legend=legend)


if __name__ == "__main__":
    app.run(debug=True)

This file creates a new Flask application which has a single route (‘/simple_chart’) that will render the chart.html file. The data being passed to the chart.html template is a set of values for the first 8 months of the year (just for illustrative purposes).

Template File (including Javascript)

The template file (chart.html) is a combination of a number of languages:

  • HTML
  • Jinja2 template scripts
  • Javascript

In order to use the Chart.js library, the ‘Chart.min.js’ file needs to be specified in the ‘head’ section:

  <head>
    <meta charset="utf-8" />
    <title>Chart.js Example</title>
    <!-- import plugin script -->
    <script src='static/Chart.min.js'></script>
  </head>

The chart is then defined as a HTML5 canvas element:

    <h1>Simple Line Chart</h1>
    <!-- bar chart canvas element -->
    <canvas id="myChart" width="600" height="400"></canvas>
    <p id="caption">The chart is displaying a simple line chart.</p>

Finally, the Javascript section of this file does the following in order:

  1. Defines the global parameters that apply to all charts
  2. Defines the chart data for this specific chart
  3. Gets the HTML canvas element
  4. Creates the chart to be displayed in the canvas element

Here are the contents of the ‘script’ section containing the Javascript that utilizes the Chart.js library:

      // Global parameters:
      // do not resize the chart canvas when its container does (keep at 600x400px)
      Chart.defaults.global.responsive = false;

      // define the chart data
      var chartData = {
        labels : [{% for item in labels %}
                   "{{item}}",
                  {% endfor %}],
        datasets : [{
            label: '{{ legend }}',
            fill: true,
            lineTension: 0.1,
            backgroundColor: "rgba(75,192,192,0.4)",
            borderColor: "rgba(75,192,192,1)",
            borderCapStyle: 'butt',
            borderDash: [],
            borderDashOffset: 0.0,
            borderJoinStyle: 'miter',
            pointBorderColor: "rgba(75,192,192,1)",
            pointBackgroundColor: "#fff",
            pointBorderWidth: 1,
            pointHoverRadius: 5,
            pointHoverBackgroundColor: "rgba(75,192,192,1)",
            pointHoverBorderColor: "rgba(220,220,220,1)",
            pointHoverBorderWidth: 2,
            pointRadius: 1,
            pointHitRadius: 10,
            data : [{% for item in values %}
                      {{item}},
                    {% endfor %}],
            spanGaps: false
        }]
      }

      // get chart canvas
      var ctx = document.getElementById("myChart").getContext("2d");

      // create the chart using the chart canvas
      var myChart = new Chart(ctx, {
        type: 'line',
        data: chartData,
      });

Most of the parameters that are set in this section are straight from the Chart.js documentation, so please refer to the documentation for descriptions of each parameter being set for this chart.

The key parameter that I changed from the default value is the only parameter defined in the global settings section (‘responsive’). I recommend setting this parameter to ‘false’ to ensure that the size of the chart is not resized, but is maintained at 600x400px (good size for viewing on a laptop/desktop). This parameter can be easily changed, but it does provide an example of setting a global parameter that will be applied to all Chart objects in your application.

Running the Application

In order to run the application, go to a terminal and navigate to the top-level directory of the project. Next, run:

$ python app.py

Then go to your favorite web browser and navigate to ‘http://localhost:5000/simple_chart’. You should see the following web page:

Excellent! We’ve been able to create a simple chart that ties in data being passed from a Flask application to a template utilizing the Chart.js template.

Example 2: Adding Callback Functions to a Line Chart

As you start to read through the documentation for Chart.js, you’ll definitely notice that there are a ton of amazing features of the library. One aspect that I really thought was cool was being able to define callback functions when certain actions occur (such as clicking on a part of the chart). In this second example, I’ll provide two examples of callback functions:

  1. Callback function to update the caption that gets displayed for each data point
  2. Callback function to update the selected data point on the chart

Flask Application

The Flask application (defined in the app.py file) is just updated to add a new route:

@app.route("/line_chart")
def line_chart():
    legend = 'Temperatures'
    temperatures = [73.7, 73.4, 73.8, 72.8, 68.7, 65.2,
                    61.8, 58.7, 58.2, 58.3, 60.5, 65.7,
                    70.2, 71.4, 71.2, 70.9, 71.3, 71.1]
    times = ['12:00PM', '12:10PM', '12:20PM', '12:30PM', '12:40PM', '12:50PM',
             '1:00PM', '1:10PM', '1:20PM', '1:30PM', '1:40PM', '1:50PM',
             '2:00PM', '2:10PM', '2:20PM', '2:30PM', '2:40PM', '2:50PM']
    return render_template('line_chart.html', values=temperatures, labels=times, legend=legend)

This new route defines a more complex set of data to display. I’ll show a better method for displaying time-based data in the third example.

Adding a Callback for Updating the Caption

The template file (line_chart.html) that is used for this example utilized the previous template (chart.html) as a base. The Javascript code that creates the chart is updated to add a callback function for changing the caption that is displayed when the user hovers over a data point:

      // create the chart using the chart canvas
      var myChart = new Chart(ctx, {
        type: 'line',
        data: chartData,
        options: {
          tooltips: {
            enabled: true,
            mode: 'single',
            callbacks: {
              label: function(tooltipItems, data) {
                       return tooltipItems.yLabel + ' degrees';
                     }
            }
          },
        }
      });

This Javascript code defines a callback function that adds the word ‘degrees’ to the temperature that gets displayed in the caption.

Adding a Callback for the Selected Data Point

The Javascript code that creates the callback function to update the text for which data point (using a zero-based index) has been selected starts by creating a variable for identifying the canvas:

      // get chart canvas
     var holder = document.getElementById("myChart");

Next, a variable is created for identifying the text to be updated:

      // get the text element below the chart
      var pointSelected = document.getElementById("pointSelected");

Finally, the callback function is defined:

      // create a callback function for updating the selected index on the chart
      holder.onclick = function(evt){
        var activePoint = myChart.getElementAtEvent(evt);
       pointSelected.innerHTML = 'Point selected... index: ' + activePoint[0]._index;
      };

The callback function gets called whenever the user clicks on a data point on the chart and it updates the text below the chart indicating the index of the data point that was selected. Additionally, you could include some logs to see what additional information is available from the activePoint variable:

        console.log(activePoint);
        console.log('x:' + activePoint[0]._view.x);
        console.log('maxWidth: ' + activePoint[0]._xScale.maxWidth);
        console.log('y: ' + activePoint[0]._view.y);

Running the Application

Just like with the previous application, you run the application through the terminal by navigating to the top-level directory of the project and running:

$ python app.py

Then go to your favorite web browser and navigate to ‘http://localhost:5000/line_chart’. You should see the following web page:

One of the great features of Chart.js is the flexibility in terms of what you can do and I’m just providing a few examples of what can be done with callback functions.

Example 3: Time-based Chart

The biggest problem with example #2 is that the way that the time data is just being defined as a string. A better way to approach this is to utilize the time module on the Python/Flask side of the application and the Moment.js library on the Javascript side of the application. The Moment.js library provides the data structures and functions for easily working with time values.

The Chart.js documentation provides some recommendations on using the Chart.js library with Moment.js. I prefer the method of explicitly including Moment.js prior to Chart.js in the section of the template file. This guarantees that Moment.js is only included once and the order of loading the library is explicitly defined.

As with example 2, this example is building off of the previous examples by providing additional functionality.

Updates to the Flask Application

The first update to the Flask side (defined in app.py) is to import the time class from the datetime module:

from datetime import time

Next, a new route is defined for ‘/time_chart’, which defines a number of pre-canned temperature values and then a list of times in the hh:mm:ss format:

@app.route("/time_chart")
def time_chart():
    legend = 'Temperatures'
    temperatures = [73.7, 73.4, 73.8, 72.8, 68.7, 65.2,
                    61.8, 58.7, 58.2, 58.3, 60.5, 65.7,
                    70.2, 71.4, 71.2, 70.9, 71.3, 71.1]
    times = [time(hour=11, minute=14, second=15),
             time(hour=11, minute=14, second=30),
             time(hour=11, minute=14, second=45),
             time(hour=11, minute=15, second=00),
             time(hour=11, minute=15, second=15),
             time(hour=11, minute=15, second=30),
             time(hour=11, minute=15, second=45),
             time(hour=11, minute=16, second=00),
             time(hour=11, minute=16, second=15),
             time(hour=11, minute=16, second=30),
             time(hour=11, minute=16, second=45),
             time(hour=11, minute=17, second=00),
             time(hour=11, minute=17, second=15),
             time(hour=11, minute=17, second=30),
             time(hour=11, minute=17, second=45),
             time(hour=11, minute=18, second=00),
             time(hour=11, minute=18, second=15),
             time(hour=11, minute=18, second=30)]
    return render_template('time_chart.html', values=temperatures, labels=times, legend=legend)

Updates to Javascript

On the Javascript side of the application, the time values being used as the x-axis of the chart are now going to utilize the Moment.js library.

In order to utilize the Moment.js library, it needs to be included in the ‘head’ section of the template file. The Chart.js documentation specifies that Moment.js needs to be included prior to Chart.js:

  <head>
    <meta charset="utf-8" />
    <title>Chart.js Example</title>
    <!-- import plugin script -->
    <script src="http://cdnjs.cloudflare.com/ajax/libs/moment.js/2.13.0/moment.min.js"></script>
    <script src='static/Chart.min.js'></script>
  </head>

It may look strange to have the Moment.js library being retrieved from a CDN and the Chart.js library being imported straight from the project files, but I wanted to include both methods for including a library for this example.

The first update within the ‘script’ section is to define a new function to convert a time element passed in from the Flask application into a Moment.js structure (of the format hh:mm:ss):

      var timeFormat = 'hh:mm:ss';

      function newDateString(hours, minutes, seconds) {
		return moment().hour(hours).minute(minutes).second(seconds).format(timeFormat);
	  }

This new function (newDateString) is utilized for defining the labels of the chart:

      // define the chart data
      var chartData = {
        labels : [{% for item in labels %}
                   newDateString( {{item.hour}}, {{item.minute}}, {{item.second}} ),
                  {% endfor %}],
      …

That’s all the updates needed!

Running the Application

Just like with the previous application, you run the application through the terminal by navigating to the top-level directory of the project and running:

$ python app.py

Then go to your favorite web browser and navigate to ‘http://localhost:5000/time_chart’. You should see the following web page:

Conclusion

In retrospect, my first experience with Javascript was quite enjoyable. I think I was very fortunate to find such a well constructed and well documented library like Chart.js as my first experience with Javascript. The documentation for Chart.js is excellent and it provided a great resource for getting started with the library and for doing more complex things with the library (like callback functions).

I would highly recommend learning the basics of Javascript before diving into trying to utilize a Javascript library. I’ve had limited exposure to Javascript, so the Codecademy course called Learning Javascript) provided a great base for understanding the language and its syntax.

My initial feeling about working with the Javascript language is that is is less enjoyable to code in than Python. Working with the Python ecosystem is just such an enjoyable experience, in my opinion. With all that said, I can really see how Javascript can be powerful for developing richer client-side experiences. Therefore, I’m eager to continue learning about Javascript and my next adventure will be with learning about the Vue.js framework.
 

3 Comments

  1. Thanks that helped me to get started.

  2. Well done Patrick, very nicely put together and explained. Helped me a lot and I’m also learning JS.

    • patkennedy79@gmail.com

      July 25, 2017 at 7:08 pm

      Thanks for the nice comment! I’ve been enjoying learning about Javascript, especially the Vue.js framework.

Leave a Reply

Your email address will not be published.

*