Dear Community,
I’m working on a Tree Map with an asymmetric color scale. While the rectangles represent the size of the samples the colors represents the one period change on a diverging color scale from positive change (blue) to no change (white) and negative change (red).
My question: How can I calibrate the color scale to display Zero in the middle of the scale? I have researched quite a few sources for this, but was not able to find the right code.
Thank you very much for your kind help - hereafter my code snippet with a random number generator.
import pandas as pd
import numpy as np
def query_data():
# create dataframe
df = pd.DataFrame(index=range(20))
SeriesName, df['SeriesName'] = [], ''
for x in range(len(df)):
SeriesName.append('SeriesName' + '_' + str(x+1))
df['SeriesName'] = SeriesName
df['SeriesSize'] = np.random.randint(10,1000,20)
df['SeriesChange'] = np.random.randint(-10,40,20)
return df.to_json(date_format='iso', orient='split')
def dataframe():
return pd.read_json(query_data(), orient='split')
import plotly as py
import plotly.graph_objs as go
import squarify
import matplotlib.cm as cm
from matplotlib import colors
from matplotlib.colors import LinearSegmentedColormap
# http://nbviewer.jupyter.org/github/empet/Plotly-plots/blob/master/Plotly-asymmetric-colorscales.ipynb
def colormap_to_colorscale(cmap):
return [ [k*0.1, colors.rgb2hex(cmap(k*0.1))] for k in range(11)]
def colorscale_from_list(alist, name):
cmap = LinearSegmentedColormap.from_list(name, alist)
colorscale=colormap_to_colorscale(cmap)
return cmap, colorscale
def normalize(x,a,b):
#if a>=b:
# raise ValueError('(a,b) is not an interval')
return float(x-a)/(b-a)
def asymmetric_colorscale(data, div_cmap, ref_point=0.0, step=0.05):
if isinstance(data, pd.DataFrame):
D = data.values
elif isinstance(data, np.ma.core.MaskedArray):
D=np.ma.copy(data)
else:
D=np.asarray(data, dtype=np.float)
dmin=np.nanmin(D)
dmax=np.nanmax(D)
#if not (dmin < ref_point < dmax):
# raise ValueError('data is not appropriate for a diverging colormap')
if dmax+dmin > 2.0*ref_point:
left=2*ref_point-dmax
right=dmax
s=normalize(dmin, left,right)
refp_norm=normalize(ref_point, left, right)
T=np.arange(refp_norm, s, -step).tolist()+[s]
T=T[::-1]+np.arange(refp_norm+step, 1, step).tolist()
else:
left=dmin
right=2*ref_point-dmin
s=normalize(dmax, left,right)
refp_norm=normalize(ref_point, left, right)
T=np.arange(refp_norm, 0, -step).tolist()+[0]
T=T[::-1]+np.arange(refp_norm+step, s, step).tolist()+[s]
L=len(T)
T_norm=[normalize(T[k],T[0],T[-1]) for k in range(L)] #normalize T values
return [[T_norm[k], colors.rgb2hex(div_cmap(T[k]))] for k in range(L)]
# asymmetric colorscale midpoint to zero to assign fillcolor
# https://matplotlib.org/gallery/userdemo/colormap_normalizations.html
class MidpointNormalize(colors.Normalize):
def __init__(self, vmin=None, vmax=None, midpoint=None, clip=False):
self.midpoint = midpoint
colors.Normalize.__init__(self, vmin, vmax, clip)
def __call__(self, value, clip=None):
x, y = [self.vmin, self.midpoint, self.vmax], [0, 0.5, 1]
return np.ma.masked_array(np.interp(value, x, y))
RedWhiteBlue=['#FF0D00','#FFFFFF','#0716FF']
fin_cmap, fin_cs = colorscale_from_list(RedWhiteBlue, 'fin_cmap')
def update_graph_treemap():
dff = dataframe()
# sort values according to squarify specifications
dff = dff.sort_values(['SeriesSize'], ascending=False)
dff = dff.reset_index(drop = True)
dff[['SeriesSize','SeriesChange']]=dff[['SeriesSize','SeriesChange']].apply(lambda x: pd.Series.round(x, 2))
tab = dff['SeriesChange'].tolist()
fin_asymm_cs = asymmetric_colorscale(tab, fin_cmap, ref_point=0.0, step=0.05)
norm = MidpointNormalize(midpoint=0., vmin=-max(dff['SeriesChange']), vmax=max(dff['SeriesChange']))
colorscale = cm.ScalarMappable(norm=norm, cmap=fin_cmap)
# create fillcolors
fillcolors=[]
for x in dff['SeriesChange']:
fillcolors.append(colors.to_hex(colorscale.to_rgba(x)))
values = []
values = dff['SeriesName'] + '<br>' + \
'SeriesSize: ' + dff['SeriesSize'].apply(str) + '<br>' + \
'SeriesChange: ' + dff['SeriesChange'].apply(str)
# create rectangles
x = 0.
y = 0.
width = 100.
height = 100.
normed = squarify.normalize_sizes(dff['SeriesSize'], width, height)
rects = squarify.squarify(normed, x, y, width, height)
shapes = []
annotations =[]
counter = 0
for r in rects:
shapes.append(
dict(
type = 'rect',
x0 = r['x'],
y0 = r['y'],
x1 = r['x']+r['dx'],
y1 = r['y']+r['dy'],
line = dict(width = 2, color = 'white'),
fillcolor = fillcolors[counter],
)
)
if counter < 14:
annotations.append(
dict(
x = r['x'],
y = r['y'] + (r['dy']),
text = dff['SeriesName'][counter],
showarrow = False,
xanchor = 'left',
yanchor = 'top',
)
)
counter = counter + 1
figure = {
'data': [
go.Scatter(
x = [ r['x']+(r['dx']/2) for r in rects ],
y = [ r['y']+(r['dy']/2) for r in rects ],
text = values,
hoverinfo = 'text',
mode = 'markers',
marker=dict(
size=0.1,
colorscale=fin_asymm_cs,
cmin = min(dff['SeriesChange']),
cmax = max(dff['SeriesChange']),
showscale = True,
colorbar = dict(
len = 1,
yanchor = 'middle',
outlinecolor = 'white',
thickness = 15,
ticklen=4
)
)
)
],
'layout': go.Layout(
autosize = True,
xaxis={'showgrid':False, 'zeroline':False, 'showticklabels': False, 'ticks':''},
yaxis={'showgrid':False, 'zeroline':False, 'showticklabels': False, 'ticks':''},
shapes=shapes,
annotations=annotations,
hovermode='closest',
)
}
return figure
py.offline.plot(update_graph_treemap(), filename='squarify-treemap')