Advanced horizontal bar chart with Python? - python

I want to make a graph like the two below.
How can I achieve that with python? I am sorry that I can´t provide any implementation because I don´t have any idea at all. I think my question is something different to this.
https://matplotlib.org/gallery/lines_bars_and_markers/barh.html#sphx-glr-gallery-lines-bars-and-markers-barh-py
Could someone give me some suggestions with just some simple numbers?

The tutorial for vertical gradient bars can be adapted to draw horizontal bars with the darkest spot in the middle:
import matplotlib.pyplot as plt
import matplotlib.patches as mpatches
import matplotlib.colors as mcolors
import numpy as np
def hor_gradient_image(ax, extent, darkest, **kwargs):
'''
puts a horizontal gradient in the rectangle defined by extent (x0, x1, y0, y1)
darkest is a number between 0 (left) and 1 (right) setting the spot where the gradient will be darkest
'''
ax = ax or plt.gca()
img = np.interp(np.linspace(0, 1, 100), [0, darkest, 1], [0, 1, 0]).reshape(1, -1)
return ax.imshow(img, extent=extent, interpolation='bilinear', vmin=0, vmax=1, **kwargs)
def gradient_hbar(y, x0, x1, ax=None, height=0.8, darkest=0.5, cmap=plt.cm.PuBu):
hor_gradient_image(ax, extent=(x0, x1, y - height / 2, y + height / 2), cmap=cmap, darkest=darkest)
rect = mpatches.Rectangle((x0, y - height / 2), x1 - x0, height, edgecolor='black', facecolor='none')
ax.add_patch(rect)
# cmap = mcolors.LinearSegmentedColormap.from_list('turq', ['paleturquoise', 'darkturquoise'])
cmap = mcolors.LinearSegmentedColormap.from_list('turq', ['#ACFAFA', '#3C9E9E'])
fig, ax = plt.subplots()
for y in range(1, 11):
x0, x1 = np.sort(np.random.uniform(1, 9, 2))
gradient_hbar(y, x0, x1, ax=ax, height=0.7, darkest=0.5, cmap=cmap)
ax.set_aspect('auto')
ax.use_sticky_edges = False
ax.autoscale(enable=True, tight=False)
ax.grid(axis='x')
plt.show()

Related

Matplotlib, plot a vector of numbers as a rectangle filled with numbers

So let's say I have a vector of numbers.
np.random.randn(5).round(2).tolist()
[2.05, -1.57, 1.07, 1.37, 0.32]
I want a draw a rectangle that shows this elements as numbers in a rectangle.
Something like this:
Is there an easy way to do this in matplotlib?
A bit convoluted but you could take advantage of seaborn.heatmap, creating a white colormap:
import seaborn as sns
import numpy as np
import matplotlib.pyplot as plt
from matplotlib.colors import LinearSegmentedColormap
data = np.random.randn(5).round(2).tolist()
linewidth = 2
ax = sns.heatmap([data], annot=True, cmap=LinearSegmentedColormap.from_list('', ['w', 'w'], N=1),
linewidths=linewidth, linecolor='black', square=True,
cbar=False, xticklabels=False, yticklabels=False)
plt.tight_layout()
plt.show()
In this case, the external lines won't be as thick as the internal ones. If needed, this can be fixed with:
ax.axhline(y=0, color='black', lw=linewidth*2)
ax.axhline(y=1, color='black', lw=linewidth*2)
ax.axvline(x=0, color='black', lw=linewidth*2)
ax.axvline(x=len(data), color='black', lw=linewidth*2)
Edit: avoid these lines and add clip_on=False to sns.heatmap (thanks/credit #JohanC)
Output:
We can add rectangles , and annotate them in a for loop.
from matplotlib import pyplot as plt
import numpy as np
# Our numbers
nums = np.random.randn(5).round(2).tolist()
# rectangle_size
rectangle_size = 2
# We want rectangles look squared, you can change if you want
plt.rcParams["figure.figsize"] = [rectangle_size * len(nums), rectangle_size]
plt.rcParams["figure.autolayout"] = True
fig = plt.figure()
ax = fig.add_subplot(111)
for i in range(len(nums)):
# We are adding rectangles
# You can change colors as you wish
plt.broken_barh([(rectangle_size * i, rectangle_size)], (0, rectangle_size), facecolors='white', edgecolor='black'
,linewidth = 1)
# We are calculating where to annotate numbers
cy = rectangle_size / 2.0
cx = rectangle_size * i + cy
# Annotation You can change color,font, etc ..
ax.annotate(str(nums[i]), (cx, cy), color='black', weight='bold', fontsize=20, ha='center', va='center')
# For squared look
plt.xlim([0, rectangle_size*len(nums)])
plt.ylim([0, rectangle_size])
# We dont want to show ticks
plt.axis('off')
plt.show()
One way using the Rectangle patch is:
import numpy as np
from matplotlib import pyplot as plt
from matplotlib.patches import Rectangle
x = np.random.randn(5).round(2).tolist()
fig, ax = plt.subplots(figsize=(9, 2)) # make figure
dx = 0.15 # edge size of box
buf = dx / 10 # buffer around edges
# set x and y limits
ax.set_xlim([0 - buf, len(x) * dx + buf])
ax.set_ylim([0 - buf, dx + buf])
# set axes as equal and turn off axis lines
ax.set_aspect("equal")
ax.axis("off")
# draw plot
for i in range(len(x)):
# create rectangle with linewidth=4
rect = Rectangle((dx * i, 0), dx, dx, facecolor="none", edgecolor="black", lw=4)
ax.add_patch(rect)
# get text position
x0, y0 = dx * i + dx / 2, dx / 2
# add text
ax.text(
x0, y0, f"{x[i]}", color="black", ha="center", va="center", fontsize=28, fontweight="bold"
)
fig.tight_layout()
fig.show()
which gives:

How to relate size parameter of .scatter() with radius?

I want to draw some circles using `ax3.scatter(x1, y1, s=r1 , facecolors='none', edgecolors='r'), where:
x1 and y1 are the coordinates of these circles
r1 is the radius of these circles
I thought typing s = r1 I would get the correct radius, but that's not the case.
How can I fix this?
If you change the value of 'r' (now 5) to your desired radius, it works. This is adapted from the matplotlib.org website, "Scatter Plots With a Legend". Should be scatter plots with attitude!
import numpy as np
import matplotlib.pyplot as plt
np.random.seed(19680801)
fig, ax = plt.subplots()
for color in ['tab:blue', 'tab:orange', 'tab:green']:
r = 5 #radius
n = 750 #number of circles
x, y = np.random.rand(2, n)
#scale = 200.0 * np.random.rand(n)
scale = 3.14159 * r**2 #CHANGE r
ax.scatter(x, y, c=color, s=scale, label=color,
alpha=0.3, edgecolors='none')
ax.legend()
ax.grid(True)
plt.show()

Matplotlib pie chart wedges using color gradient

I am trying to create a pie chart with each wedge having a different color gradient (e.g., yellow-green) instead of a single color (e.g., green). To further explain, the gradient should be set along the radius and not the circumference of the pie.
Tried many options and did some research online but couldn't find a direct solution to this.
Is there a library or approach I should take to achieve this?
Thanks in advance.
You can create an image with the desired gradient, and position and clip it via each wedge. LinearSegmentedColormap.from_list() interpolates between given colors.
Here is an example:
import matplotlib.pyplot as plt
from matplotlib.colors import LinearSegmentedColormap
import numpy as np
fig, ax = plt.subplots()
sizes = np.random.uniform(10, 20, 4)
color_combos = [('yellow', 'green'), ('red', 'navy'), ('yellow', 'crimson'), ('lime', 'red')]
wedges, texts = ax.pie(sizes, labels=['alpha', 'beta', 'gamma', 'delta'])
xlim = ax.get_xlim()
ylim = ax.get_ylim()
for wedge, color_combo in zip(wedges, color_combos):
wedge.set_facecolor('none')
wedge.set_edgecolor('black')
print(wedge.theta1, wedge.theta2)
bbox = wedge.get_path().get_extents()
x0, x1, y0, y1 = bbox.xmin, bbox.xmax, bbox.ymin, bbox.ymax
x = np.linspace(x0, x1, 256)[np.newaxis, :]
y = np.linspace(y0, y1, 256)[:, np.newaxis]
# fill = np.sqrt(x ** 2 + y ** 2) # for a gradient along the radius, needs vmin=0, vmax=1
fill = np.degrees(np.pi - np.arctan2(y, -x))
gradient = ax.imshow(fill, extent=[x0, x1, y0, y1], aspect='auto', origin='lower',
cmap=LinearSegmentedColormap.from_list('', color_combo),
vmin=wedge.theta1, vmax=wedge.theta2)
gradient.set_clip_path(wedge)
ax.set_xlim(xlim)
ax.set_ylim(ylim)
ax.set_aspect('equal')
plt.show()
At the left an example of a gradient along the angle, at the right a gradient along the radius.

How to draw a scatter map using customized colors [duplicate]

An image is worth a thousand words :
https://www.harrisgeospatial.com/docs/html/images/colorbars.png
I want to obtain the same color bar than the one on the right with matplotlib.
Default behavior use the same color for "upper"/"lower" and adjacent cell...
Thank you for your help!
Here is the code I have:
import numpy as np
import matplotlib.pyplot as plt
import matplotlib.colors as colors
N = 100
X, Y = np.mgrid[-3:3:complex(0, N), -2:2:complex(0, N)]
Z1 = np.exp(-X**2 - Y**2)
Z2 = np.exp(-(X - 1)**2 - (Y - 1)**2)
Z = (Z1 - Z2) * 2
fig, ax = plt.subplots(1, 1, figsize=(8, 8))
# even bounds gives a contour-like effect
bounds = np.linspace(-1, 1, 10)
norm = colors.BoundaryNorm(boundaries=bounds, ncolors=256)
pcm = ax.pcolormesh(X, Y, Z,
norm=norm,
cmap='RdBu_r')
fig.colorbar(pcm, ax=ax, extend='both', orientation='vertical')
In order to have the "over"/"under"-color of a colormap take the first/last color of that map but still be different from the last color inside the colormapped range you can get one more color from a colormap than you have boundaries in the BoundaryNorm and use the first and last color as the respective colors for the "over"/"under"-color.
import numpy as np
import matplotlib.pyplot as plt
import matplotlib.colors as mcolors
N = 100
X, Y = np.mgrid[-3:3:complex(0, N), -2:2:complex(0, N)]
Z1 = np.exp(-X**2 - Y**2)
Z2 = np.exp(-(X - 1)**2 - (Y - 1)**2)
Z = (Z1 - Z2) * 2
fig, ax = plt.subplots(1, 1, figsize=(8, 8))
# even bounds gives a contour-like effect
bounds = np.linspace(-1, 1, 11)
# get one more color than bounds from colormap
colors = plt.get_cmap('RdBu_r')(np.linspace(0,1,len(bounds)+1))
# create colormap without the outmost colors
cmap = mcolors.ListedColormap(colors[1:-1])
# set upper/lower color
cmap.set_over(colors[-1])
cmap.set_under(colors[0])
# create norm from bounds
norm = mcolors.BoundaryNorm(boundaries=bounds, ncolors=len(bounds)-1)
pcm = ax.pcolormesh(X, Y, Z, norm=norm, cmap=cmap)
fig.colorbar(pcm, ax=ax, extend='both', orientation='vertical')
plt.show()
As suggested in my comment you can change the color map with
pcm = ax.pcolormesh(X, Y, Z, norm=norm, cmap='rainbow_r')
That gives:
You can define your own color map as shown here: Create own colormap using matplotlib and plot color scale

Setting the size of a matplotlib ColorbarBase object

I have an patch collection that I'd like to display a color map for. Because of some manipulations I do on top of the colormap, it's not possible for me to define it using a matplotlib.colorbar instance. At least not as far as I can tell; doing so strips some manipulations I do with my colors that blank out patches lacking data:
cmap = matplotlib.cm.YlOrRd
colors = [cmap(n) if pd.notnull(n) else [1,1,1,1]
for n in plt.Normalize(0, 1)([nullity for _, nullity in squares])]
# Now we draw.
for i, ((min_x, max_x, min_y, max_y), _) in enumerate(squares):
square = shapely.geometry.Polygon([[min_x, min_y], [max_x, min_y],
[max_x, max_y], [min_x, max_y]])
ax0.add_patch(descartes.PolygonPatch(square, fc=colors[i],
ec='white', alpha=1, zorder=4))
So I define a matplotlib.colorbar.ColorbarBase instance instead, which works:
matplotlib.colorbar.ColorbarBase(ax1, cmap=cmap, orientation='vertical',
norm=matplotlib.colors.Normalize(vmin=0, vmax=1))
Which results in e.g.:
The problem I have is that I want to reduce the size of this colorbar (specifically, the shrink it down to a specific vertical size, say, 500 pixels), but I don't see any obvious way of doing this. If I had a colorbar instance, I could adjust this easily using its axis property arguments, but ColorbarBase lacks these.
For further reference:
The example my implementation is based on.
The source code in question (warning: lengthy).
The size and shape is defined with the axis. This is a snippet from code I have where I group 2 plots together and add a colorbar at the top independently. I played with the values in that add_axes instance until I got a size that worked for me:
cax = fig.add_axes([0.125, 0.925, 0.775, 0.0725]) #has to be as a list - starts with x, y coordinates for start and then width and height in % of figure width
norm = mpl.colors.Normalize(vmin = low_val, vmax = high_val)
mpl.colorbar.ColorbarBase(cax, cmap = self.cmap, norm = norm, orientation = 'horizontal')
The question may be a bit old, but I found another solution that can be of help for anyone who is not willing to manually create a colorbar axes for the ColorbarBase class.
The solution below uses the matplotlib.colorbar.make_axes class to create a dependent sub_axes from the given axes. That sub_axes can then be supplied for the ColorbarBase class for the colorbar creation.
The code is derived from the matplotlib code example describe in here
Here is a snippet code:
import numpy as np
import matplotlib.pyplot as plt
from matplotlib.colors import LinearSegmentedColormap
import matplotlib.colorbar as mcbar
from matplotlib import ticker
import matplotlib.colors as mcolors
# Make some illustrative fake data:
x = np.arange(0, np.pi, 0.1)
y = np.arange(0, 2 * np.pi, 0.1)
X, Y = np.meshgrid(x, y)
Z = np.cos(X) * np.sin(Y) * 10
colors = [(1, 0, 0), (0, 1, 0), (0, 0, 1)] # R -> G -> B
n_bins = [3, 6, 10, 100] # Discretizes the interpolation into bins
cmap_name = 'my_list'
fig, axs = plt.subplots(2, 2, figsize=(9, 7))
fig.subplots_adjust(left=0.02, bottom=0.06, right=0.95, top=0.94, wspace=0.05)
for n_bin, ax in zip(n_bins, axs.ravel()):
# Create the colormap
cm = LinearSegmentedColormap.from_list(cmap_name, colors, N=n_bin)
# Fewer bins will result in "coarser" colomap interpolation
im = ax.imshow(Z, interpolation='nearest', origin='lower', cmap=cm)
ax.set_title("N bins: %s" % n_bin)
cax, cbar_kwds = mcbar.make_axes(ax, location = 'right',
fraction=0.15, shrink=0.5, aspect=20)
cbar = mcbar.ColorbarBase(cax, cmap=cm,
norm=mcolors.Normalize(clip=False),
alpha=None,
values=None,
boundaries=None,
orientation='vertical', ticklocation='auto', extend='both',
ticks=n_bins,
format=ticker.FormatStrFormatter('%.2f'),
drawedges=False,
filled=True,
extendfrac=None,
extendrect=False, label='my label')
if n_bin <= 10:
cbar.locator = ticker.MaxNLocator(n_bin)
cbar.update_ticks()
else:
cbar.locator = ticker.MaxNLocator(5)
cbar.update_ticks()
fig.show()

Categories

Resources