How does one set the color of a line in matplotlib with scalar values provided at run time using a colormap (say jet)? I tried a couple of different approaches here and I think I'm stumped. values[] is a storted array of scalars. curves are a set of 1-d arrays, and labels are an array of text strings. Each of the arrays have the same length.
fig = plt.figure()
ax = fig.add_subplot(111)
jet = colors.Colormap('jet')
cNorm = colors.Normalize(vmin=0, vmax=values[-1])
scalarMap = cmx.ScalarMappable(norm=cNorm, cmap=jet)
lines = []
for idx in range(len(curves)):
line = curves[idx]
colorVal = scalarMap.to_rgba(values[idx])
retLine, = ax.plot(line, color=colorVal)
#retLine.set_color()
lines.append(retLine)
ax.legend(lines, labels, loc='upper right')
ax.grid()
plt.show()
The error you are receiving is due to how you define jet. You are creating the base class Colormap with the name 'jet', but this is very different from getting the default definition of the 'jet' colormap. This base class should never be created directly, and only the subclasses should be instantiated.
What you've found with your example is a buggy behavior in Matplotlib. There should be a clearer error message generated when this code is run.
This is an updated version of your example:
import matplotlib.pyplot as plt
import matplotlib.colors as colors
import matplotlib.cm as cmx
import numpy as np
# define some random data that emulates your indeded code:
NCURVES = 10
np.random.seed(101)
curves = [np.random.random(20) for i in range(NCURVES)]
values = range(NCURVES)
fig = plt.figure()
ax = fig.add_subplot(111)
# replace the next line
#jet = colors.Colormap('jet')
# with
jet = cm = plt.get_cmap('jet')
cNorm = colors.Normalize(vmin=0, vmax=values[-1])
scalarMap = cmx.ScalarMappable(norm=cNorm, cmap=jet)
print scalarMap.get_clim()
lines = []
for idx in range(len(curves)):
line = curves[idx]
colorVal = scalarMap.to_rgba(values[idx])
colorText = (
'color: (%4.2f,%4.2f,%4.2f)'%(colorVal[0],colorVal[1],colorVal[2])
)
retLine, = ax.plot(line,
color=colorVal,
label=colorText)
lines.append(retLine)
#added this to get the legend to work
handles,labels = ax.get_legend_handles_labels()
ax.legend(handles, labels, loc='upper right')
ax.grid()
plt.show()
Resulting in:
Using a ScalarMappable is an improvement over the approach presented in my related answer:
creating over 20 unique legend colors using matplotlib
I thought it would be beneficial to include what I consider to be a more simple method using numpy's linspace coupled with matplotlib's cm-type object. It's possible that the above solution is for an older version. I am using the python 3.4.3, matplotlib 1.4.3, and numpy 1.9.3., and my solution is as follows.
import matplotlib.pyplot as plt
from matplotlib import cm
from numpy import linspace
start = 0.0
stop = 1.0
number_of_lines= 1000
cm_subsection = linspace(start, stop, number_of_lines)
colors = [ cm.jet(x) for x in cm_subsection ]
for i, color in enumerate(colors):
plt.axhline(i, color=color)
plt.ylabel('Line Number')
plt.show()
This results in 1000 uniquely-colored lines that span the entire cm.jet colormap as pictured below. If you run this script you'll find that you can zoom in on the individual lines.
Now say I want my 1000 line colors to just span the greenish portion between lines 400 to 600. I simply change my start and stop values to 0.4 and 0.6 and this results in using only 20% of the cm.jet color map between 0.4 and 0.6.
So in a one line summary you can create a list of rgba colors from a matplotlib.cm colormap accordingly:
colors = [ cm.jet(x) for x in linspace(start, stop, number_of_lines) ]
In this case I use the commonly invoked map named jet but you can find the complete list of colormaps available in your matplotlib version by invoking:
>>> from matplotlib import cm
>>> dir(cm)
A combination of line styles, markers, and qualitative colors from matplotlib:
import itertools
import matplotlib as mpl
import matplotlib.pyplot as plt
N = 8*4+10
l_styles = ['-','--','-.',':']
m_styles = ['','.','o','^','*']
colormap = mpl.cm.Dark2.colors # Qualitative colormap
for i,(marker,linestyle,color) in zip(range(N),itertools.product(m_styles,l_styles, colormap)):
plt.plot([0,1,2],[0,2*i,2*i], color=color, linestyle=linestyle,marker=marker,label=i)
plt.legend(bbox_to_anchor=(1.05, 1), loc=2, borderaxespad=0.,ncol=4);
UPDATE: Supporting not only ListedColormap, but also LinearSegmentedColormap
import itertools
import matplotlib.pyplot as plt
Ncolors = 8
#colormap = plt.cm.Dark2# ListedColormap
colormap = plt.cm.viridis# LinearSegmentedColormap
Ncolors = min(colormap.N,Ncolors)
mapcolors = [colormap(int(x*colormap.N/Ncolors)) for x in range(Ncolors)]
N = Ncolors*4+10
l_styles = ['-','--','-.',':']
m_styles = ['','.','o','^','*']
fig,ax = plt.subplots(gridspec_kw=dict(right=0.6))
for i,(marker,linestyle,color) in zip(range(N),itertools.product(m_styles,l_styles, mapcolors)):
ax.plot([0,1,2],[0,2*i,2*i], color=color, linestyle=linestyle,marker=marker,label=i)
ax.legend(bbox_to_anchor=(1.05, 1), loc=2, borderaxespad=0.,ncol=3,prop={'size': 8})
U may do as I have written from my deleted account (ban for new posts :( there was). Its rather simple and nice looking.
Im using 3-rd one of these 3 ones usually, also I wasny checking 1 and 2 version.
from matplotlib.pyplot import cm
import numpy as np
#variable n should be number of curves to plot (I skipped this earlier thinking that it is obvious when looking at picture - sorry my bad mistake xD): n=len(array_of_curves_to_plot)
#version 1:
color=cm.rainbow(np.linspace(0,1,n))
for i,c in zip(range(n),color):
ax1.plot(x, y,c=c)
#or version 2: - faster and better:
color=iter(cm.rainbow(np.linspace(0,1,n)))
c=next(color)
plt.plot(x,y,c=c)
#or version 3:
color=iter(cm.rainbow(np.linspace(0,1,n)))
for i in range(n):
c=next(color)
ax1.plot(x, y,c=c)
example of 3:
Ship RAO of Roll vs Ikeda damping in function of Roll amplitude A44
Related
This question is adapted from this answer, however the solution provided does not work and following is my result. I am interested in adding individual title on the right side for individual subgraphs.
(p.s no matter how much offset for y-axis i provide the title seems to stay at the same y-value)
from matplotlib import pyplot as plt
import numpy as np
fig, axes = plt.subplots(nrows=2)
ax0label = axes[0].set_ylabel('Axes 0')
ax1label = axes[1].set_ylabel('Axes 1')
title = axes[0].set_title('Title')
offset = np.array([-0.15, 0.0])
title.set_position(ax0label.get_position() + offset)
title.set_rotation(90)
fig.tight_layout()
plt.show()
Something like this? This is the only other way i can think of.
from matplotlib import pyplot as plt
import numpy as np
fig, axes = plt.subplots(nrows=2)
ax0label = axes[0].set_ylabel('Axes 0')
ax1label = axes[1].set_ylabel('Axes 1')
ax01 = axes[0].twinx()
ax02 = axes[1].twinx()
ax01.set_ylabel('title')
ax02.set_ylabel('title')
fig.tight_layout()
plt.show()
Below is the plot I generated using axes.text option,
ax[0].text(row.TIMESTAMP, row.HIGH+(0.1*width),row['candlestick_pattern'], fontsize=5, rotation='vertical')
I'm trying to achieve the same output using TextPath and PathPatch, in order to increase/decrease the font size when I zoom in/out of the plot, and below is the code I have (taken from here and here )
textPath = TextPath((data_coord[1], -data_coord[0]), row['candlestick_pattern'], size=2)
pathPatch = PathPatch(textPath, color="black")
transform = mpl.transforms.Affine2D().rotate_deg(90) + ax[0].transData
pathPatch.set_transform(transform)
ax[0].add_patch(pathPatch)
Output with this is
You could see that the text is cramped into a very small region and its not what I want. I would want to set the font size to a smaller value and increase the width (in vertical mode - height) of the TextPath. Is that possible?
Below is the complete code with which we can reproduce the problem for the dataset here
import matplotlib as mpl
import matplotlib.pyplot as plt
from matplotlib.textpath import TextPath
from matplotlib.patches import PathPatch
from mplfinance.original_flavor import candlestick_ohlc
from matplotlib import transforms as tf
import pandas as pd
plotDf = pd.read_csv("data.csv")
plotDf.reset_index(inplace=True)
del plotDf['TIMESTAMP']
del plotDf['TOTTRDQTY']
fig, ax = plt.subplots(1)
candlestick_ohlc(ax,plotDf.values,width=0.6, \
colorup='green', colordown='red', alpha=0.8)
maxHigh = plotDf['HIGH'].max()
minLow = plotDf['LOW'].min()
width = maxHigh - minLow
threshold = (width)*0.6
for idx, row in plotDf.iterrows():
if (row['candlestick_pattern'] != 'NO_PATTERN'):
if (row.HIGH < (threshold+minLow)):
data_coord = (idx, row.HIGH+(0.1*width))
#ax.text(idx, row.HIGH+(0.1*width), row['candlestick_pattern'], fontsize=5, rotation='vertical')
else:
data_coord = (idx, row.LOW-(0.4*width))
#ax.text(idx, row.LOW-(0.4*width), row['candlestick_pattern'], fontsize=5, rotation='vertical')
textPath = TextPath((data_coord[1], -data_coord[0]), row['candlestick_pattern'], size=2)
pathPatch = PathPatch(textPath, color="black")
transform = mpl.transforms.Affine2D().rotate_deg(90) + ax.transData
pathPatch.set_transform(transform)
ax.add_patch(pathPatch)
fig.autofmt_xdate()
fig.tight_layout()
fig.suptitle("test", fontsize=16)
fig.set_size_inches(10.5, 10.5)
plt.subplots_adjust(top=0.95)
plt.show()
Apparently, your problem is a scaling problem. Messing around with .scale(x,y), ax.set_xlim and ax.set_ylim might allow you to "unsqueeze" the text. You can also try to set an anchor for your plot like done here:
ts = ax.transData
coords = ts.transform([0,0]) #<-anchor
tr = mpl.transforms.Affine2D().rotate_deg_around(coords[0],coords[1],90).scale(1,3) #<- scale
t = ts + tr
#<extra code>
pathPatch = PathPatch(textPath, color="black", transform = t)
EDIT
I tried many things, but I couldn't find a good way of doing it. I'll leave below what I tried and some resources that might help.
The way to properly use .rotate_deg_around would be like such:
ts = ax.transData
# ts = fig.dpi_scale_trans #this guy uses the fig scale, if you're interested
coords = ts.transform([data_coord[0],data_coord[1]])
converter = (coords[0]/data_coord[0], coords[1]/data_coord[1])
#plot the anchor points for visualization:
plt.plot(coords[0]/converter[0], coords[1]/converter[1],'xk')
tr = mpl.transforms.Affine2D().rotate_deg_around(coords[0]/converter[0],coords[1]/converter[1],90).scale(converter[0],converter[1])
pathPatch = PathPatch(textPath, color="black", transform = tr)
ax.add_patch(pathPatch)
Nonetheless, the results are still similar to what you had at the beginning:
It appears that TextPath does not behave like it should when using transform. Here .get_offset_transform is used, and it apparently fixes this sort of issue, but I was unable to use it since the plt has a Line type.
Also, you will see that if you increase the y axis in .scale, you can start to see the text, but it spreads the coordinates as well. One idea you can try is setting a good readable y scale (use ax.set_ylim to see your text) and then use that value as a divisor when setting the coordinates for your plot.
There are also some ideas here that might serve you.
I would like to produce a heatmap in Python, similar to the one shown, where the size of the circle indicates the size of the sample in that cell. I looked in seaborn's gallery and couldn't find anything, and I don't think I can do this with matplotlib.
It's the inverse. While matplotlib can do pretty much everything, seaborn only provides a small subset of options.
So using matplotlib, you can plot a PatchCollection of circles as shown below.
Note: You could equally use a scatter plot, but since scatter dot sizes are in absolute units it would be rather hard to scale them into the grid.
import numpy as np
import matplotlib.pyplot as plt
from matplotlib.collections import PatchCollection
N = 10
M = 11
ylabels = ["".join(np.random.choice(list("PQRSTUVXYZ"), size=7)) for _ in range(N)]
xlabels = ["".join(np.random.choice(list("ABCDE"), size=3)) for _ in range(M)]
x, y = np.meshgrid(np.arange(M), np.arange(N))
s = np.random.randint(0, 180, size=(N,M))
c = np.random.rand(N, M)-0.5
fig, ax = plt.subplots()
R = s/s.max()/2
circles = [plt.Circle((j,i), radius=r) for r, j, i in zip(R.flat, x.flat, y.flat)]
col = PatchCollection(circles, array=c.flatten(), cmap="RdYlGn")
ax.add_collection(col)
ax.set(xticks=np.arange(M), yticks=np.arange(N),
xticklabels=xlabels, yticklabels=ylabels)
ax.set_xticks(np.arange(M+1)-0.5, minor=True)
ax.set_yticks(np.arange(N+1)-0.5, minor=True)
ax.grid(which='minor')
fig.colorbar(col)
plt.show()
Here's a possible solution using Bokeh Plots:
import pandas as pd
from bokeh.palettes import RdBu
from bokeh.models import LinearColorMapper, ColumnDataSource, ColorBar
from bokeh.models.ranges import FactorRange
from bokeh.plotting import figure, show
from bokeh.io import output_notebook
import numpy as np
output_notebook()
d = dict(x = ['A','A','A', 'B','B','B','C','C','C','D','D','D'],
y = ['B','C','D', 'A','C','D','B','D','A','A','B','C'],
corr = np.random.uniform(low=-1, high=1, size=(12,)).tolist())
df = pd.DataFrame(d)
df['size'] = np.where(df['corr']<0, np.abs(df['corr']), df['corr'])*50
#added a new column to make the plot size
colors = list(reversed(RdBu[9]))
exp_cmap = LinearColorMapper(palette=colors,
low = -1,
high = 1)
p = figure(x_range = FactorRange(), y_range = FactorRange(), plot_width=700,
plot_height=450, title="Correlation",
toolbar_location=None, tools="hover")
p.scatter("x","y",source=df, fill_alpha=1, line_width=0, size="size",
fill_color={"field":"corr", "transform":exp_cmap})
p.x_range.factors = sorted(df['x'].unique().tolist())
p.y_range.factors = sorted(df['y'].unique().tolist(), reverse = True)
p.xaxis.axis_label = 'Values'
p.yaxis.axis_label = 'Values'
bar = ColorBar(color_mapper=exp_cmap, location=(0,0))
p.add_layout(bar, "right")
show(p)
One option is to use matplotlib's scatter plots with legends and grid. You can specify size of those circles with specifying the scales. You can also change the color of each circle. You should somehow specify X,Y values so that the circles sit straight on lines. This is an example I got from here:
volume = np.random.rayleigh(27, size=40)
amount = np.random.poisson(10, size=40)
ranking = np.random.normal(size=40)
price = np.random.uniform(1, 10, size=40)
fig, ax = plt.subplots()
# Because the price is much too small when being provided as size for ``s``,
# we normalize it to some useful point sizes, s=0.3*(price*3)**2
scatter = ax.scatter(volume, amount, c=ranking, s=0.3*(price*3)**2,
vmin=-3, vmax=3, cmap="Spectral")
# Produce a legend for the ranking (colors). Even though there are 40 different
# rankings, we only want to show 5 of them in the legend.
legend1 = ax.legend(*scatter.legend_elements(num=5),
loc="upper left", title="Ranking")
ax.add_artist(legend1)
# Produce a legend for the price (sizes). Because we want to show the prices
# in dollars, we use the *func* argument to supply the inverse of the function
# used to calculate the sizes from above. The *fmt* ensures to show the price
# in dollars. Note how we target at 5 elements here, but obtain only 4 in the
# created legend due to the automatic round prices that are chosen for us.
kw = dict(prop="sizes", num=5, color=scatter.cmap(0.7), fmt="$ {x:.2f}",
func=lambda s: np.sqrt(s/.3)/3)
legend2 = ax.legend(*scatter.legend_elements(**kw),
loc="lower right", title="Price")
plt.show()
Output:
I don't have enough reputation to comment on Delenges' excellent answer, so I'll leave my comment as an answer instead:
R.flat doesn't order the way we need it to, so the circles assignment should be:
circles = [plt.Circle((j,i), radius=R[j][i]) for j, i in zip(x.flat, y.flat)]
Here is an easy example to plot circle_heatmap.
from matplotlib import pyplot as plt
import pandas as pd
from sklearn.datasets import load_wine as load_data
from psynlig import plot_correlation_heatmap
plt.style.use('seaborn-talk')
data_set = load_data()
data = pd.DataFrame(data_set['data'], columns=data_set['feature_names'])
#data = df_corr_selected
kwargs = {
'heatmap': {
'vmin': -1,
'vmax': 1,
'cmap': 'viridis',
},
'figure': {
'figsize': (14, 10),
},
}
plot_correlation_heatmap(data, bubble=True, annotate=False, **kwargs)
plt.show()
I have the following code which produces a scatter plot with a colorbar:
#!/usr/bin/env python3
import numpy as np
import matplotlib.pyplot as plt
from matplotlib import rc
from matplotlib.ticker import *
import matplotlib.ticker as ticker
import matplotlib as mpl
import matplotlib.gridspec as gridspec
from list2nparr import list2nparr
# this part changes the fonts for latex handling
plt.rcParams['text.latex.preamble']=[r"\usepackage{lmodern}"]
plt.rcParams['text.usetex'] = True
plt.rcParams['font.family'] = 'lmodern'
plt.rcParams['font.size'] = 16
#==================================================================
fig,ax1 = plt.subplots()
data = list2nparr('radiant.txt')
lm = data[:,14]
bet = data[:,15]
b = data[:,18]
#
cm = plt.cm.get_cmap('jet')
sc2 = ax1.scatter(lm, bet, c=b, s=10, cmap=cm, edgecolor='none',rasterized=True)
# ==========================COLORBAR SPECS=========================
cb3 = fig.colorbar(sc2,ax = ax1, aspect=10)
cb3.ax.tick_params(labelsize=16)
cb3.set_label(r'$\beta = F_R/F_G$',size=18,labelpad=20)
cb3.formatter.set_powerlimits((0, 0))
cb3.ax.yaxis.set_major_locator(MaxNLocator(5,prune='upper')) # WHY DOES THIS LINE NOT WORK?
cb3.update_ticks()
# =======================SCATTER PLOT SPECS========================
ax1.set_ylabel('$b$, (deg)',fontsize=18,labelpad=0.5)
ax1.set_xlabel("$\lambda-\lambda_{\odot}$, (deg)",fontsize=18)
plt.savefig('test.eps', format='eps')
At some point, I am trying to format the ticks of the colorbar, requesting only five ticks while removing the uppermost label. This is illustrated in line 30, where it says: cb3.ax.yaxis.set_major_locator(MaxNLocator(5,prune='upper'))
However, this line seems to have no effect on the plot at all?
Any ideas what might be the reason for that?
EDIT
Use the locator when you create the colorbar:
cb3 = fig.colorbar(sc2,ax = ax1, aspect=10, ticks=MaxNLocator(5))
and remove this line:
cb3.ax.yaxis.set_major_locator(MaxNLocator(5,prune='upper'))
Old answers
Flip the order of these two lines:
cb3.update_ticks()
cb3.ax.yaxis.set_major_locator(MaxNLocator(5,prune='upper'))
and you should only five color intervals.
Alternatively, don't use set_major_locator at all and set the ticks directly in when making an instance:
cb3 = fig.colorbar(sc2,ax = ax1, aspect=10, ticks=[0, 2.5e-4, 5e-4, 7.5e-4, 1e-3 ])
I would like to know if someone who dominate more advanced matplotlib could help me in this one. I have a heatmap, which could be simulated with the following code:
import numpy as np
import string
from matplotlib import pylab as plt
def random_letter(chars=string.ascii_uppercase, size=2):
char_arr = np.array(list(chars))
if size > 27:
size = 27
np.random.shuffle(char_arr)
return char_arr[:size]
data = np.random.poisson(1, (174, 40))
y_labels = [', '.join(x for x in random_letter()) for _ in range(174)]
y_labels = sorted(y_labels)
fig, ax = plt.subplots()
fig.set_size_inches(11.7, 16.5)
heatmap = ax.pcolor(data,
cmap=plt.cm.Blues,
vmin=data.min(),
vmax=data.max(),
edgecolors='white')
ax.set_xticks(np.arange(data.shape[1])+.5, minor=False);
ax.set_yticks(np.arange(data.shape[0])+.5, minor=False);
ax.set_xticklabels(np.arange(40), rotation=90);
ax.set_yticklabels(y_labels, fontsize=5);
cb = fig.colorbar(heatmap, shrink=0.33, aspect=10)
My need is to draw lines over the heatmap, to separate features over the ytickslabels as I show in the following image (in which i draw by hand):
Any one knows how to programmatically code matplotlib to do that?
I'll take the liberty to do write the full solution for #tcaswell, actually it only takes 7 more lines:
xl, xh=ax.get_xlim()
left=xl-(xh-xl)*0.1 #10% extension on each side
right=xh+(xh-xl)*0.1
Lines=ax.hlines([5,10,15,20], left, right, color='r', linewidth=1.2)
Lines.set_clip_on(False)
ax.set_xlim((xl, xh))