Trying to shade the bars in this chart based on the confidence that a selected y-value (represented by the red line) lies within a confidence interval. See recolorBars()
method in the class example below.
While I understand colormaps, Normalize()
, and ScalarMappable()
, I'm stumped on which values to pass to Normalize() to create a color and shade for each bar.
Here's what my chart looks like when first generated.
To generate the chart pictured above, call chart = interactiveChart()
. The interactivity is based on click events, which trigger the setAxHLine()
callback to set a red horizontal bar at the selected Y-val position. Eventually this method will also trigger the recolorBars()
method.
Tested Code Example: (Notice that this is set up to run in a Jupyter notebook.)
%matplotlib notebook
# setup the environment
import pandas as pd
import numpy as np
import statsmodels.stats.api as sms # for confidence intervals
from scipy.stats import sem # another confidence interval shorthand
import matplotlib.cm as cm
import matplotlib.colors as col
import matplotlib.pyplot as plt
import mpl_toolkits.axes_grid1.inset_locator as mpl_il
from matplotlib.widgets import Button, Slider
# from matplotlib.ticker import FormatStrFormatter, ScalarFormatterclass interactiveBarChart:"""A base class that can be used for creating clicable charts and solvingthe challenges of interpreting plots with confidence intervals."""# basic greys: lighter for regular, darker for emphasisgreys = ['#afafaf','#7b7b7b'] # ticks and boxes, arrows, legend ticks and text# horizontal bar: nice redhorzo_bar = '#004a80'# set bar colormapcmap = cm.get_cmap('RdBu')# instantiate the classdef __init__(self): """Initialize the data and a new figure."""# seed for data.np.random.seed(12345)# get some data to plotself.df = pd.DataFrame(np.c_[np.random.normal(33500,150000,3650), # np.c_ class to transpose arraynp.random.normal(41000,90000,3650), np.random.normal(41000,120000,3650), np.random.normal(48000,55000,3650)], columns=[1992,1993,1994,1995])# get mean values to plotself.means = self.df.mean() # calculate confidence interval high and lowself.c_i = [ sms.DescrStatsW(self.df[i]).tconfint_mean() for i in self.df.columns ]# calculate the interval whole numberself.intervals = [ invl[-1] - invl[0] for invl in self.c_i ] # plot the bar chart and make a reference to the rectanglesself.rects = plt.bar(range(len(self.df.columns)), self.means,yerr=self.df.sem().values*1.96,align='center', alpha=0.8, color=self.greys[0],error_kw=dict(ecolor='gray', lw=2, capsize=7, capthick=2))# set up a starting axhlineself.horzo_slider = plt.axhline(y=40000, xmin=-.1, clip_on=False, zorder=1, color='#e82713')## TICKS AND TEXT AND SPINESplt.title('Confidence Interval Interactivity: Click the Chart To Recolor', color=self.greys[1])plt.xticks(range(len(self.df.columns)), self.df.columns) # do some formatting self.formatArtists(plt.gca())## EVENT HANDLING# reference the axes and setup pick eventsplt.gcf().canvas.mpl_connect('button_press_event', self.setAxHLine)def formatArtists(self, ax):"""Does some recoloring and formatting of the ticks, labels, and spines.Receives the axes of the current figure."""# recolor the ticksax.xaxis.set_tick_params(which='major', colors=self.greys[1])ax.yaxis.set_tick_params(which='major', colors=self.greys[1])# recolor the spinesfor pos in ['top', 'right', 'bottom', 'left']:ax.spines[pos].set_edgecolor(self.greys[0])## EVENT HANDLERSdef setAxHLine(self, event): """Handle the logic for handling bar coloring when the slider is moved up or down over the confidence intervals."""# remove first axhlineself.horzo_slider.remove()self.horzo_slider = plt.axhline(y=event.ydata, xmin=-.1, clip_on=False, zorder=1, color='#e82713')# self.recolorBars(event)def recolorBars(self, event):"""Handles all recoloring of the bars based on the confidence that the selected y-value is within a given interval on the chart.This function is called on a button press event and receives that data as an argument.""" # get the yval y = event.ydata# how to determine the shades ?
# abs_diffs = [ abs((mean + conf)-y|) for mean, conf in zip(self.means, self.intervals) ]# how to pass in the map to get the colors to apply to the bars?
# colors = [ cm.ScalarMappable(norm=col.Normalize(vmin=i[0] , vmax=i[-1]), cmap=self.cmap) for i in self.c_i ]# apply the colors in a list comprehension# [ rect.set_color(color) for rect, color in zip(self.rects, colors) ]def showPlot(self):"""Convenience if not using the inline display setup %matplotlib notebook"""plt.show()