# Applying a custom color scale with Plotly Boxplots

I was recently asked by my boss to apply a color scale to a series of Plotly Box plots I was working on for a Dash application. The notion was to plot multiple box plots on the same graph and have each box plot be darker or lighter based on it’s distance from the overall median in the dataset.

# The problem

The issue here is that Plotly only supports color scales for heatmaps, scatter plots & contour plots.

I will demonstrate the default behavior by plotting some generated data for Canucks players `+/- per game all time`

as follows:

Note: Complete code at the end of the post

The dashed green line represents the `NHL league average +/- per game at +2.0`

, what my boss wanted was to color each box plot darker or lighter based on how far away `each players median +/- `

value was from the `league average`

.

# The solution

Given that I was already in a Python environment, I chose to use the `colormap`

& `colormap normalizer`

features from the Matplotlib Python module.

First step was to import Matplotlib:

`import matplotlib.pyplot as plt`

import matplotlib

import pandas as pd

Then determine the `min`

and `max`

values in the dataset:

x_data = ['Henrik Sedin', 'Daniel Sedin',

'Brock Boeser', 'Elias Pettersen',

'Bo Horvat', 'Pavel Bure',]# Generate random data

y0 = np.random.randn(50)+4

y1 = np.random.randn(50)+4

y2 = np.random.randn(50)+2

y3 = np.random.randn(50)+2

y4 = np.random.randn(50)+1

y5 = np.random.randn(50)+3y_data = [y0,y1,y2,y3,y4,y5]df = pd.DataFrame(y_data)

vmin, vmax = df.min().min(), df.max().max()

Initialize the Matplotlib cmap & normalizer:

`norm = matplotlib.colors.Normalize(vmin=vmin, vmax=vmax)`

cmap = matplotlib.cm.get_cmap('GnBu') # green to blue color way

Find the median of each `y_data`

and use the normalizer to map it to an appropriate color based on the median value:

`for xd, yd in zip(x_data, y_data):`

median = np.median(yd) # find the median

color = 'rgb' + str(cmap(norm(median))[0:3]) # normalize

traces.append(go.Box(

y=yd,

name=xd,

boxpoints='all',

jitter=0.5,

whiskerwidth=0.2,

fillcolor=color, # add the box plot color

marker=dict(

size=2,

),

line=dict(width=1)

))

# VIOLA!

The resulting plot will render a darker `fillcolor`

the further positive the `players median +/-`

is from the `league average`

and vice versa.

# BONUS

Here is the complete code to get the result above:

from plotly import __version__

from plotly.offline import download_plotlyjs, init_notebook_mode, plot, iplot

init_notebook_mode(connected=True)

import plotly.graph_objs as go

import numpy as np

import pandas as pd

import matplotlib.pyplot as plt

import matplotlibx_data = ['Henrik Sedin', 'Daniel Sedin',

'Brock Boeser', 'Elias Pettersen',

'Bo Horvat', 'Pavel Bure',]y0 = np.random.randn(50)+4

y1 = np.random.randn(50)+4

y2 = np.random.randn(50)+2

y3 = np.random.randn(50)+2

y4 = np.random.randn(50)+1

y5 = np.random.randn(50)+3y_data = [y0,y1,y2,y3,y4,y5]traces = []df = pd.DataFrame(y_data)

vmin, vmax = df.min().min(), df.max().max()norm = matplotlib.colors.Normalize(vmin=vmin, vmax=vmax)

cmap = matplotlib.cm.get_cmap('GnBu')for xd, yd in zip(x_data, y_data):

median = np.median(yd)

color = 'rgb' + str(cmap(norm(median))[0:3])

traces.append(go.Box(

y=yd,

name=xd,

boxpoints='all',

jitter=0.5,

whiskerwidth=0.2,

fillcolor=color,

marker=dict(

size=2,

color='rgb(0, 0, 0)'

),

line=dict(width=1),

))layout = go.Layout(

title='Canucks all time +/- per game',

yaxis=dict(

autorange=True,

showgrid=True,

zeroline=True,

dtick=5,

gridcolor='rgb(255, 255, 255)',

gridwidth=1,

zerolinecolor='rgb(255, 255, 255)',

zerolinewidth=2,

),

margin=dict(

l=40,

r=30,

b=80,

t=100,

),

paper_bgcolor='rgb(243, 243, 243)',

plot_bgcolor='rgb(243, 243, 243)',

showlegend=False,

shapes= [{

'type': 'line',

'x0': -1,

'y0': 2,

'x1': 6,

'y1': 2,

'line': {

'color': 'rgb(50, 171, 96)',

'width': 4,

'dash': 'dashdot'

}

}]

)fig = go.Figure(data=traces, layout=layout)

iplot(fig)