Adding Graphs to a Django Website

thumbone

Bernd Wechner

Posted on March 10, 2022

Adding Graphs to a Django Website

Another journey, captured in my journal, that I think is worth sharing. To wit, a story (of learning and how that proceeds), and a summary (of how to add graphs to a Django website).

The story

I have a Django website on which I am presenting a table of data and some statistics on that table (maxima, minima, medians etc.). I wanted to add a histogram (a bar chart) that would summarise the frequency distribution of a value in one column of the table.

If you struggle with abstractions, I can help a little with specifics too. In my case, it is a table of events, and one of the columns records how many people attended the event. The histogram then would have the number of attendees across the bottom and each bar represent how often that number of attendees was recorded.

This is, in fact, just such a histogram:

A sample histogram

taken from a table that list events and has a count of attendees (players, as they are gaming events).

Now the existing page (no graph, just a table of events and some summary statistics) has a set of filters on it, so that you can look at a subset of all events - based on weekdays, or venue, or club, or host, or league, or duration or attendee counts etc ... A set of filters can be chosen, and a refresh button clicked and voilà, the data is updated in the blink of an eye (with an AJAX call back to the server that fetches data and updates the relevant page elements).

And that, then, is the context, the desire to plug a graph into such an existing Django page. And as usual, it proved to be, an adventure, with notes taken, and one worth sharing.

Choosing a tool

Such a journey starts with an idea and no rush. At least on this project it does, quite in contrast to many projects, notably those that need to monetise rapidly or beat the competition to market. But this project is a backburner on which notes are taken, and jobs tackled as time permits.

And it starts with the idea, and over time the idea collected three candidates:

Long story short, this list is not exhaustive, there will be more out there, and each of these tools takes a little reading and comparing to arrive at a choice.

Flot loses ground early, as it's jQuery dependence in an era of jQuery decline (in popularity) didn't strike me as an investment into the future.

Dash and Bokeh go head-to-head in a number of online comparisons, the best of which I found was Paul Iacomi's:

https://pauliacomi.com/2020/06/07/plotly-v-bokeh.html

I opted for Bokeh in the end based on those comparisons. You can spend a lot of time comparing a lot of alternatives, of course, and always end up taking a gamble and then investing in one. If you have more free time or are paid to, you can go one further and try several of them, compare them, and write up your experiences. I don't, and so I did some basic reading and said: Bokeh, you're the one.
You're The One!

The first graph

Bokeh themselves, provide a lovely little sample for a histogram:

https://docs.bokeh.org/en/latest/docs/gallery/bar_basic.html

And there are a number of online pages running through the basic steps of a Bokeh graph in Django ranging from the, IMHO, bad1, to the OK2.

But, assuming you have a Django site already (my situation and if you're reading here, possibly yours too - cf. the title) it is worth summarising the needs.

Getting the basics into place

  1. You'll need to install the Python libraries - in whatever context your Django (backend) site is running in, of course. It's as simple as pip install bokeh

  2. You'll need to register Bokeh with Django as an app. Add BokehApp to (the end of) your INSTALLED_APPS setting.

  3. Your view will need to provide a few things in context to the Django template for rendering. Specifically, and at least, to things you will produce in that view using Bokeh (more on that later):

    • A div string: Just a string that contains an HTML div element that will house the graph itself. You can give it any name you like in the context, but if you called it bokeh_graph_div you'd simply stick it in your template as {{ bokeh_graph_div | safe }} where you want the graph to be.
    • A script string: Just a string that contains an HTML script element which you put anywhere in your template (though it's customary to put these at the start or end of the template I guess). This is the script that will actually draw the graph onto a canvas it places in the div. Again, if you called this bokeh_graph_script in your context you'd include it in the template with {{ bokeh_graph_script | safe }} in your template. Note though this is just a tiny hook script, the bulk of the code that does that drawing and more, is in JavaScript libraries that you will need to include in your template as well.
  4. Your template will need to provide a few things too:

    • It will need to include the div and script strings described above.
    • It will need to include style sheets for the Bokeh elements. Basic, recommendation is to include a basic Bokeh style sheet, and another for its widgets and to source them from a CDN:

      <link href=”http://cdn.pydata.org/bokeh/release/bokeh-2.4.2.min.css" rel=”stylesheet” type=”text/css”>
      <link href=”http://cdn.pydata.org/bokeh/release/bokeh-widgets-2.4.2.min.css" rel=”stylesheet” type=”text/css”>
      
    • It will need to include the JavaScript that actually draws the graph and provides all of its features. Four includes seem commonly recommended:

      <script src="https://cdn.bokeh.org/bokeh/release/bokeh-2.4.2.min.js"></script>
      <script src="https://cdn.bokeh.org/bokeh/release/bokeh-widgets-2.4.2.min.js"></script>
      <script src="https://cdn.bokeh.org/bokeh/release/bokeh-tables-2.4.2.min.js"></script>
      <script src="https://cdn.bokeh.org/bokeh/release/bokeh-api-2.4.2.min.js"></script>
      

With all that in place, you actually have a graph! But for one thing, the details of generating its content, the div and script mentioned earlier.

Defining the graph's content

For a histogram, you'll need to prepare your data in two parallel lists one for the categories (X axis) and one for the values (Y axis).

In pro forma:

categories = [ ... ]
values = [ ... ]
Enter fullscreen mode Exit fullscreen mode

where these two lists have the same length.

Then there are two objects of interest to create (the div and script), left.

  1. Create a figure which will hold the graph. This can be as simple as:

    from bokeh.plotting import figure
    my_figure = figure()
    

    bokeh.plotting.figure has a pile of options you can play with of course, for sizing, colouring, gridding, labelling and more. What it doesn't deal with is data. It's like the backdrop into which we can now drop our data somehow.

  2. Plonk your data onto the figure. Here I'm interested in a histogram and the glyph of choice is the vbar and dropping it into the figure can be as simple as:

    bars = my_figure.vbar(x=cateories, top=values)
    
  3. Build the Django context variables. There's a single method (bokeh.embed.components) that will create them for you from your figure:

    from bokeh.embed import components
    bokeh_graph_script, bokeh_graph_div = components(my_figure)
    

    and then in your view, just add them to the context delivered to the template. A good tutorial will help, but I'm assuming you have a Django site you're dropping this into and so it might simply resemble:

    context.update({"bokeh_graph_script": bokeh_graph_script,
                "bokeh_graph_div": bokeh_graph_div})
    

Now you will see a lovely histogram of your values vs. categories in the page where you put the div. And you can look at the options for bokeh.plotting.figure and bokeh.plotting.figure.vbar to make it look nice and the way you like. Oh, and heck if you want something more than a histogram there are plenty of other glyphs to choose from.

A specific example that worked for me

With all that in mind an example I had running and liked:

from bokeh.plotting import figure
from bokeh.embed import components

def view_Events(request):
    # Collect the categories and values
    (players, frequency) = Event.frequency("players", events)

    # Create the figure
    plot = figure(height=350,
                  x_axis_label="Count of Players",
                  y_axis_label="Number of Events",
                  background_fill_alpha=0,
                  border_fill_alpha=0,
                  tools="pan,wheel_zoom,box_zoom,save,reset")

    # And the bars
    bars = plot.vbar(x=players, top=frequency, width=0.9)

    # Build the context variables
    graph_script, graph_div = components(plot)

    # Add them to context
    context = {"graph_script": graph_script,
               "graph_div": graph_div}

    # Render the view
    return render(request, 'events.html', context=context)
Enter fullscreen mode Exit fullscreen mode

That is in fact the code I used, with distracting details removed (as in, this view also contains a table of events and a table of stats and a pile of filtering controls and such which have been removed from this sample). Similarly, if I reduce the template events.html by removing all that distracting detail it looks like:

{% extends "base.html" %}

{% block styles %}
     <link href=”http://cdn.pydata.org/bokeh/release/bokeh-2.4.2.min.css" rel=”stylesheet” type=”text/css”>
     <link href=”http://cdn.pydata.org/bokeh/release/bokeh-widgets-2.4.2.min.css" rel=”stylesheet” type=”text/css”>   
{% endblock %}

{% block content %}
    <h1>Graph of Event Attendance</h1>
    {{ graph_div | safe }}
{% endblock %}

{% block scripts %}
    <script src="https://cdn.bokeh.org/bokeh/release/bokeh-2.4.2.min.js"></script>
    <script src="https://cdn.bokeh.org/bokeh/release/bokeh-widgets-2.4.2.min.js"></script>
    <script src="https://cdn.bokeh.org/bokeh/release/bokeh-tables-2.4.2.min.js"></script>
    <script src="https://cdn.bokeh.org/bokeh/release/bokeh-api-2.4.2.min.js"></script>
    {{ graph_script | safe }}
{% endblock %}
Enter fullscreen mode Exit fullscreen mode

When base.html is a Django template with placeholders for styles, content and scripts.

Making it interactive

Here, the adventure truly begins. Bokeh is a rich library with high interactivity touted as one of its assets. The central feature of that is being able to update data in the graph in response to user requests.

Alas, it proved not to be so simple at all, very poorly documented, and requiring some reverse engineering. Which is precisely where a journal comes in handy and why it becomes useful to document.

And this story is long enough so the next chapter will come ... next.

Comig soon


Pointing dude photo created by drobotdean - www.freepik.com
Coming soon photo created by rawpixel.com - www.freepik.com


  1. In case you don't like random judgements passed on websites, or, you're the unfortunate author of that one, here's why it's bad: The code blocks are all inconsistent in width and style and font size, the whole thing is as a consequence a jarring bother to try and read, and for my needs it repeats unnecessarily all the Django hoo-ha. A better page would just say "Set up a Django project" linking to another page that explains how - written by same author or someone else, they abound, and then get into the meaty stuff that the title promises, namely "Integrating Bokeh visualisations into Django Projects". Surprisingly enough if I'm drawn by that title (and I was), I know what a Django project is and I have one (or a few) and I'm looking to integrate Bokeh visualisations not have my hand held with clumsily formatted code examples through the basics of getting to where I already am, with a Django project. 

  2. This one is OK because the code blocks and images are at least in tune with the article. It's short of Great because it falls behind an annoying login demand (which you can lose simply by disabling Javascript with any old browser addon and reloading the page) and because it repeats the annoying handhold through setting up a Django site. Which is not annoying if that's what you need, but it's not what you need when you click on an article titled "Integrating Bokeh Visualisations in Django Application" - because if you click on that it's because you have a Django application and want to integrate Bokeh visualisation into it.  

    I failed categorically to find a page I'd class as Good alas - and admit I find the web, driven by this endless need for publishing and novelty and enormous population of wannabe writers clamouring for attention just does reruns of the same mediocre stuff over and over!

    As an astute reader you will, of course, have noted that I am writing on-line about my disappointment with the bulk of on-line writing and the deep irony of that. I do to.

    Of course, I am not clamouring for attention, not doing reruns of the same mediocre stuff, over and over, nor assuming with every story that you are feree of context and a total noob, while at he same time trying to ensure I link all jargon and enw ideas to explanatory pages. Nor am I begging you for your likes, subscribes or whatever - I am just happy to share stories and notes now and then as much to myself and for you, and because I believe that learning (and particularly retention) is driven by story, and there are enough dry facts out there, but my story is often one of finding dry facts the hard way (because I could not find any documentation on it) and involve exploration and often reverse engineering. These are the stories I most like having clearly recorded.

💖 💪 🙅 🚩
thumbone
Bernd Wechner

Posted on March 10, 2022

Join Our Newsletter. No Spam, Only the good stuff.

Sign up to receive the latest update from our blog.

Related