I created the Django view 'graph' with the aim of displaying an image created from the matplotlib.pyplot module. I wrote my function plot_bubbles (returns a matplotlib.figure.Figure object) in the script data_analysis.py which is imported in the views.py script.
Tkinter can only run on the main thread, my webpage works as expected the first time that I request it but it does not display the image when refreshed or requested again. My understanding is that Django operates the code on a new thread when the view is requested again.
I tried to use the Queue class and populate it with the function that returns my figure as described in this answer Execute Python function in Main thread from call in Dummy thread . Here is my views script.
from django.http import HttpResponse
from . import data_analysis
import Queue
import threading
q = Queue.Queue()
def graph(request):
parties = ["Conservative Party", "Labour Party", "Green Party", "UKIP"]
def from_other_thread(graph_function):
q.put(graph_function)
def main_thread_execute():
callback = q.get()
fig = callback
return fig
def grapher(arguments, area_variable):
data_analysis.plt.close('all')
from_other_thread(data_analysis.plot_bubbles(arguments, area_variable))
t = threading.Thread(target = grapher, args=(parties, data_analysis.all_data['2015 Pay']))
t.start()
t.join()
fig = main_thread_execute()
response = HttpResponse(content_type='image/png')
fig.savefig(response, format='png')
return response
The aim is to run the function in the main thread so that Tkinter can actually work and the image be created I want the image to be created every time that the url is requested as I will let the user choose the variables that he wants to visualize via a form, passing them as the arguments of the plot_bubbles function.
I am a beginner in django and never used multi-threading in my code, thank you for reading this. Any explanation on your solution is greatly appreciated.
EDIT
Threading is not necessarily needed. The problem was originating from the way that my data_analysis script was generating the plot. In particular the code called for the method matplotlib.pyplot.subplots http://matplotlib.org/api/pyplot_api.html#matplotlib.pyplot.subplots to generate a figure with 4 axes objects as in here
import matplotlib.pyplot as plt
fig, ax = plt.subplots(2,2, figsize(15,12))
This seemed to cause Tkinter to not operate on the main loop, I have not fully understood the reason yet. This is how the code looks now
#!/usr/bin/python
# -*- coding: utf-8 -*-
import pandas as pd
import numpy as np
from pandas import Series, DataFrame
from pylab import figure, axes, plot, title, subplots
import statsmodels.api as sm
from sqlalchemy import create_engine
from matplotlib.backends.backend_agg import FigureCanvasAgg
import matplotlib
# Load data from database into dataframe
engine = create_engine("postgresql://user_name:password@localhost:5432/brexit")
all_data = pd.read_sql('''SELECT * FROM records;''', engine, index_col='Borough')
# Bubble Plot function creation
colors = np.random.rand(len(all_data.index))
area = []
def plot_bubbles(arguments, area_variable, space=0):
ah = iter(arguments)
eh = iter(arguments)
ih = iter(arguments)
kh = iter(arguments)
th = iter(arguments)
zh = iter(arguments)
mh = iter(arguments)
fig = figure(figsize=(30, 25))
ax1 = fig.add_subplot(2,2,1)
ax2 = fig.add_subplot(2,2,2)
ax3 = fig.add_subplot(2,2,3)
ax4 = fig.add_subplot(2,2,4)
collection = [ax1, ax2, ax3, ax4]
for x in area_variable:
#want the bubbles to have an average area of 40, add a factor to increase the variability in size
factor = ((x-area_variable.mean())**2/400)
area.append(factor*x*(40/area_variable.mean()))
for ax in collection:
orient = all_data[ah.next()]
ax.set_ylabel('Leave %')
ax.set_xlim([max(0, all_data[zh.next()].min()-all_data[mh.next()].min()/3),
all_data[ih.next()].max()+all_data[th.next()].max()/7])
results = sm.OLS(all_data['Leave votes'], sm.add_constant(orient)).fit()
X_plot = np.linspace(orient.min()-0.05, orient.max(), 100)
ax.plot(X_plot, X_plot*results.params[1] + results.params[0], )
for label, ori, leave in zip(all_data.index, orient, all_data['Leave votes']):
ax.annotate(label, xy=(ori, leave), xytext=(ori, leave+0.05),
arrowprops={'facecolor':'black', 'connectionstyle':'arc3,rad=0.3', 'arrowstyle':'simple'})
ax.scatter(orient, all_data['Leave votes'], s=area, c=colors, alpha=0.6)
ax.set_title(kh.next())
fig.subplots_adjust(hspace=space, wspace=space)
return fig
With the change in the way the figure and axes are created the problem is solved. Would be interesting if someone had an explanation as to why this is the case.