I recently created a code for plotting shaded dials (figures that look like gauges or speedometers) in python and I thought I’d share my code here. The dials are well suited to plot things such as risk or maybe the probability of meeting a set of robustness criteria across a range of decision variables (shameless plug: if you’re at EWRI this week, come check out my talk: Conflicts in Coalitions, Wednesday morning at 8:30 in Northstar B for which I created these figures).
As hinted at above, I originally created the plot to show bivariate data, with one variable plotted as the location on the dial and the other as the color. You could also plot the same variable as both color and location if you wanted to emphasize the meaning of increasing value on the dial. An example dial created with the code is shown below.
The color distribution, location of arrow and labeling of the gauge and colorbar are all fully customizable. I created the figure by first making a pie chart using marplotlib, inscribing a small white circle in the middle and then cropping the image in half using the Python image processing library (PIL also known as Pillow). The arrow is created using the matplotlib “arrow” function and will point to a specified location on the dial. The code is created such that you can add an array of any length to specify your colors, the array does not have to be monotonic like the one shown above, but will accept any values between zero and one (if your data is not in this range I’d suggest normalizing).
Annotated code is below:
import matplotlib.pyplot as plt from matplotlib import cm, gridspec import numpy as np import math from PIL import Image from mpl_toolkits.axes_grid1 import make_axes_locatable # set your color array and name of figure here: dial_colors = np.linspace(0,1,1000) # using linspace here as an example figname = 'myDial' # specify which index you want your arrow to point to arrow_index = 750 # create labels at desired locations # note that the pie plot ploots from right to left labels = [' ']*len(dial_colors)*2 labels = '100' labels = '75' labels = '50' labels = '25' labels = '0' # function plotting a colored dial def dial(color_array, arrow_index, labels, ax): # Create bins to plot (equally sized) size_of_groups=np.ones(len(color_array)*2) # Create a pieplot, half white, half colored by your color array white_half = np.ones(len(color_array))*.5 color_half = color_array color_pallet = np.concatenate([color_half, white_half]) cs=cm.RdYlBu(color_pallet) pie_wedge_collection = ax.pie(size_of_groups, colors=cs, labels=labels) i=0 for pie_wedge in pie_wedge_collection: pie_wedge.set_edgecolor(cm.RdYlBu(color_pallet[i])) i=i+1 # create a white circle to make the pie chart a dial my_circle=plt.Circle( (0,0), 0.3, color='white') ax.add_artist(my_circle) # create the arrow, pointing at specified index arrow_angle = (arrow_index/float(len(color_array)))*3.14159 arrow_x = 0.2*math.cos(arrow_angle) arrow_y = 0.2*math.sin(arrow_angle) ax.arrow(0,0,-arrow_x,arrow_y, width=.02, head_width=.05, \ head_length=.1, fc='k', ec='k') # create figure and specify figure name fig, ax = plt.subplots() # make dial plot and save figure dial(dial_colors, arrow_index, labels, ax) ax.set_aspect('equal') plt.savefig(figname + '.png', bbox_inches='tight') # create a figure for the colorbar (crop so only colorbar is saved) fig, ax2 = plt.subplots() cmap = cm.ScalarMappable(cmap='RdYlBu') cmap.set_array([min(dial_colors), max(dial_colors)]) cbar = plt.colorbar(cmap, orientation='horizontal') cbar.ax.set_xlabel("Risk") plt.savefig('cbar.png', bbox_inches='tight') cbar = Image.open('cbar.png') c_width, c_height = cbar.size cbar = cbar.crop((0, .8*c_height, c_width, c_height)).save('cbar.png') # open figure and crop bottom half im = Image.open(figname + '.png') width, height = im.size # crop bottom half of figure # function takes top corner <span data-mce-type="bookmark" id="mce_SELREST_start" data-mce-style="overflow:hidden;line-height:0" style="overflow:hidden;line-height:0" >&#65279;</span>and bottom corner coordinates # of image to keep, (0,0) in python images is the top left corner im = im.crop((0, 0, width+c_width, int(height/2.0))).save(figname + '.png')
Other ways of doing this from around the web
This code was my way of making a dial plot, and I think it works well for plotting gradients on the dial. In the course of writing this I came across a couple similar codes, I’m listing them below. They both have advantages if you want to plot a small number of colors on your dial but I had trouble getting them to scale.
Here’s an example that creates dials using matplotlib patches, this method looks useful for plotting a small number of categorical data, I like the customization of the labels: http://nicolasfauchereau.github.io/climatecode/posts/drawing-a-gauge-with-matplotlib/
Here’s another alternative using the plotly library, I like the aesthetics but if you’re unfamiliar with plotly there’s a lot to learn before you can nicely customize the final product: https://plot.ly/python/gauge-charts/