What is the best solution for highlighting an area in a contourf plot?
I want the background to be opacity of 0.5 and the user chosen area to be normal. How can I achieve this?
In How to nicely plot clipped layered artists in matplotlib? Jake Vanderplas shows a way to draw a rectangle with a rectangular hole. The code can be adapted for your situation. The following example starts from a tutorial example, and highlights the third contour:
import numpy as np
import matplotlib.pyplot as plt
from matplotlib.path import Path
from matplotlib.patches import PathPatch
def DoubleRect(xy1, width1, height1,
xy2, width2, height2, **kwargs):
base = np.array([(0, 0), (0, 1), (1, 1), (1, 0), (0, 0)])
verts = np.vstack([xy1 + (width1, height1) * base,
xy2 + (width2, height2) * base[::-1],
xy1])
codes = 2 * ([Path.MOVETO] + 4 * [Path.LINETO]) + [Path.CLOSEPOLY]
return PathPatch(Path(verts, codes), **kwargs)
origin = 'lower'
delta = 0.025
x = y = np.arange(-3.0, 3.01, delta)
X, Y = np.meshgrid(x, y)
Z1 = np.exp(-X ** 2 - Y ** 2)
Z2 = np.exp(-(X - 1) ** 2 - (Y - 1) ** 2)
Z = (Z1 - Z2) * 2
fig, ax = plt.subplots()
contours = ax.contourf(X, Y, Z, 10, cmap=plt.cm.turbo, origin=origin)
# contours.collections[2].set_color('deepskyblue') # mark one contour
# calculate (or get) the coordinates of the hole
bbox = contours.collections[2].get_paths()[0].get_extents()
hole_xy, hole_width, hole_height = bbox.p0, bbox.width, bbox.height
# find the coordinates of the surrounding rectangle
xmin, xmax = ax.get_xlim()
ymin, ymax = ax.get_ylim()
full_rect = plt.Rectangle((xmin, ymin), xmax - xmin, ymax - ymin, color='black', alpha=0.5)
ax.add_patch(full_rect)
# create a rectangle with a hole to clip the surrounding rectangle
mask = DoubleRect((xmin, ymin), xmax - xmin, ymax - ymin,
hole_xy, hole_width, hole_height,
facecolor='none', edgecolor='none')
ax.add_patch(mask)
full_rect.set_clip_path(mask)
plt.show()
Instead of darkening the outside region, it could also be hatched (similar to the linked post). This would set the edge color of the mask to 'black', and create the full rectangle with hatching.
full_rect = plt.Rectangle((xmin, ymin), xmax - xmin, ymax - ymin, facecolor='none', edgecolor='black', hatch='//')
Related
I'm basing this question on a useful, yet closed question by Ian Roberts, and awesome answer by hayk.
How to make a wavy arrow that goes between specified points on a plt plot, with specified number of waves? Method should be compatible with many subplots to make it universal.
I give the code in the answer. It took me some time to figure out how to make it work on subplots, so you don't have to.
import numpy as np
import matplotlib.pyplot as plt
import matplotlib.path as mpath
import matplotlib.patches as mpatches
import matplotlib as mpl
def curly_arrow(start, end, ax,arr_size=1, n=5, col='gray', linew=1., width=0.1):
xmin, ymin = start
xmax, ymax = end
dist = np.sqrt((xmin - xmax) ** 2 + (ymin - ymax) ** 2)
n0 = dist / (2 * np.pi)
x = np.linspace(0, dist, 151) + xmin
y = width * np.sin(n * x / n0) + ymin
line = plt.Line2D(x, y, color=col, lw=linew)
del_x = xmax - xmin
del_y = ymax - ymin
ang = np.arctan2(del_y, del_x)
line.set_transform(mpl.transforms.Affine2D().rotate_around(xmin, ymin, ang) + ax.transData)
ax.add_line(line)
verts = np.array([[0, 1], [0, -1], [2, 0], [0, 1]]).astype(float) * arr_size
verts[:, 1] += ymax
verts[:, 0] += xmax
path = mpath.Path(verts)
patch = mpatches.PathPatch(path, fc=col, ec=col)
patch.set_transform(mpl.transforms.Affine2D().rotate_around(xmax, ymax, ang) + ax.transData)
return patch
then you can use it like this:
fig, (ax1, ax2) = plt.subplots(1,2)
ax1.add_patch(curly_arrow((0, 0.5), (0.6, 0.5),ax1, n=4, arr_size=0.1))
ax2.add_patch(curly_arrow((0, 0.5), (0.6, 0.5),ax2, n=3, arr_size=0.1))
I'm having trouble centering an arc on a circle.
Can anyone suggest how to write the code so that the center of the arc is always in the center of the circle I drew?
I add the arc to the graph with the code below. The variable used to draw the arc is the angle alpha.
arc_angles = linspace(0, alpha, 20)
arc_xs = radius * cos(arc_angles)
arc_ys = radius * sin(arc_angles)
plt.plot(arc_xs, -arc_ys, color='red', lw=3)
plt.gca().annotate('Arc', xy=(-radius /2, -radius /2), xycoords='data', fontsize=12, rotation=180+math.degrees(alpha))
I add all the code below.
from numpy.lib.function_base import angle
import logging
import math
import matplotlib.pyplot as plt
import numpy as np
from numpy import sin, cos, pi, linspace
logger = logging.getLogger(__name__)
def draw_section(h=0.16, d=0.2):
if validate_filling(h, d):
radius = d/2
plt.plot(0, 0, color='black', marker='o')
plt.gca().annotate('O (0, 0)', xy=(0 + radius/10, 0 + radius/10), xycoords='data', fontsize=12)
plt.xlim(-radius - 0.05, radius + 0.05)
plt.ylim(-radius, radius + 0.05)
plt.gca().set_aspect('equal')
# draw circle
angels = linspace(0 * pi, 2 * pi, 100)
xs = radius * cos(angels)
ys = radius * sin(angels)
plt.plot(xs, ys, color='brown')
# draw diameter
plt.plot(radius, 0, marker='o', color='blue')
plt.plot(-radius, 0, marker='o', color='blue')
plt.plot([radius, -radius], [0, 0])
plt.gca().annotate(f"Diameter={d}", xy=(radius/8, -radius/5), xycoords='data', fontsize=12)
# draw level of water
plt.plot(0, -radius, marker='o', color='purple')
plt.plot(0, h - radius, marker='o', color='purple')
plt.plot([0, 0], [-radius, h - radius], color='purple')
plt.gca().annotate('Water lvl', xy=(-radius/5, -radius/1.5), xycoords='data', fontsize=12, rotation=90)
# Draw arc as created by water level
chord = math.sqrt((radius ** 2 - ((h-radius) ** 2))) * 2
# calculate angle
alpha = math.acos((radius ** 2 + radius ** 2 - chord ** 2) / (2 * radius ** 2))
# Create arc
arc_angles = linspace(0, alpha, 20)
arc_xs = radius * cos(arc_angles)
arc_ys = radius * sin(arc_angles)
plt.plot(arc_xs, -arc_ys, color='red', lw=3)
plt.gca().annotate('Arc', xy=(-radius /2, -radius /2), xycoords='data', fontsize=12, rotation=180+math.degrees(alpha))
plt.show()
else:
logger.info(f"h cannot be greater than d.")
draw_section(h=0.30, d=0.5)
This is output:
Code output
The effect I want to get is a graph with the possibility of changing the water height in the pipe cross-section. The height of the water table should be a chord, always a horizontal line.
Cross section of pipe - effect I want to get
As I wrote above, I would like to draw an arc in the center of the circle to draw a chord from the endpoints marked by the arc.
effect I want to get
I have the following snippet of code to draw a best-fit line through a collections of points on a graph, and annotate it with the corresponding R2 value:
import matplotlib.pyplot as plt
import numpy as np
import scipy.stats
x = 50 * np.random.rand(20) + 50
y = 200 * np.random.rand(20)
plt.plot(x, y, 'o')
# k, n = np.polyfit(x, y, 1)
k, n, r, _, _ = scipy.stats.linregress(x, y)
line = plt.axline((0, n), slope=k, color='blue')
xy = line.get_xydata()
plt.annotate(
f'$R^2={r**2:.3f}$',
(xy[0] + xy[-1]) // 2,
xycoords='axes fraction',
ha='center', va='center_baseline',
rotation=k, rotation_mode='anchor',
)
plt.show()
I have tried various different (x,y) pairs, different xycoords and other keyword parameters in annotate but I haven't been able to get the annotation to properly appear where I want it. How do I get the text annotation to appear above the line with proper rotation, located either at the middle point of the line, or at either end?
1. Annotation coordinates
We cannot compute the coordinates using xydata here, as axline() just returns dummy xydata (probably due to the way matplotlib internally plots infinite lines):
print(line.get_xydata())
# array([[0., 0.],
# [1., 1.]])
Instead we can compute the text coordinates based on the xlim():
xmin, xmax = plt.xlim()
xtext = (xmin + xmax) // 2
ytext = k*xtext + n
Note that these are data coordinates, so they should be used with xycoords='data' instead of 'axes fraction'.
2. Annotation angle
We cannot compute the angle purely from the line points, as the angle will also depend on the axis limits and figure dimensions (e.g., imagine the required rotation angle in a 6x4 figure vs 2x8 figure).
Instead we should normalize the calculation to both scales to get the proper visual rotation:
rs = np.random.RandomState(0)
x = 50 * rs.rand(20) + 50
y = 200 * rs.rand(20)
plt.plot(x, y, 'o')
# save ax and fig scales
xmin, xmax = plt.xlim()
ymin, ymax = plt.ylim()
xfig, yfig = plt.gcf().get_size_inches()
k, n, r, _, _ = scipy.stats.linregress(x, y)
plt.axline((0, n), slope=k, color='blue')
# restore x and y limits after axline
plt.xlim(xmin, xmax)
plt.ylim(ymin, ymax)
# find text coordinates at midpoint of regression line
xtext = (xmin + xmax) // 2
ytext = k*xtext + n
# find run and rise of (xtext, ytext) vs (0, n)
dx = xtext
dy = ytext - n
# normalize to ax and fig scales
xnorm = dx * xfig / (xmax - xmin)
ynorm = dy * yfig / (ymax - ymin)
# find normalized annotation angle in radians
rotation = np.rad2deg(np.arctan2(ynorm, xnorm))
plt.annotate(
f'$R^2={r**2:.3f}$',
(xtext, ytext), xycoords='data',
ha='center', va='bottom',
rotation=rotation, rotation_mode='anchor',
)
I would like to plot a circle of a given outer radius which would have an empty hole of a given inner radius. Then the resulting ring would have a fade-out gradient fill, however starting not from the center of the circle, but from the border of the inner circle. In Photoshop it's called "glow", from what I know. Is something like this possible?
here's an image showing what I mean
You could create an image from a function that is zero inside the circle and goes from 1 to 0 on the outside.
Using a colormap that goes from fully transparent white to opaque red would not only interpolate the color but also the transparency.
Here is an example, placing some text to demonstrate the effect of the transparency.
import matplotlib.pyplot as plt
from matplotlib.colors import LinearSegmentedColormap
import numpy as np
inner_radius = 1
outer_radius = 3
center_x = 6
center_y = 4
halo_color = 'gold'
# center_color = 'none' # for an empty center
center_color = '#ff334466' ## redish with 25% alpha
xmin = center_x - outer_radius
xmax = center_x + outer_radius
ymin = center_y - outer_radius
ymax = center_y + outer_radius
x, y = np.meshgrid(np.linspace(xmin, xmax, 500), np.linspace(ymin, ymax, 500))
r = np.sqrt((x - center_x) ** 2 + (y - center_y) ** 2)
z = np.where(r < inner_radius, np.nan, np.clip(outer_radius - r, 0, np.inf))
cmap = LinearSegmentedColormap.from_list('', ['#FFFFFF00', halo_color])
cmap.set_bad(center_color)
plt.text(center_x, center_y, "Test", size=50, color='b')
plt.imshow(z, cmap=cmap, extent=[xmin, xmax, ymin, ymax], origin='lower', zorder=3)
plt.axis('equal')
plt.show()
I try to add colored rectangle to dendrogram results like as follow:
this is my dendrogram codes:
from scipy.cluster.hierarchy import dendrogram
...
plt.figure(figsize=(250, 100))
labelsize=20
ticksize=15
plt.title(file_name.split(".")[0], fontsize=labelsize)
plt.xlabel('stock', fontsize=labelsize)
plt.ylabel('distance', fontsize=labelsize)
dendrogram(
Z,
leaf_rotation=90., # rotates the x axis labels
leaf_font_size=8., # font size for the x axis labels
labels = corr.columns
)
pylab.yticks(fontsize=ticksize)
pylab.xticks(rotation=-90, fontsize=ticksize)
However, this is only add colorful line not a rectangle like in the above image. How can I create image like this?
Thanks
You can loop through the generated path collections and draw a bounding box.
Optionally, you could set the height to the color_threshold= parameter, which defaults to Z[:, 2].max() * 0.7.
The last collection is are the unclassified lines, so the example code below loops through all earlier collections.
import matplotlib.pyplot as plt
from scipy.cluster import hierarchy
import numpy as np
N = 15
ytdist = np.random.randint(10, 1000, N * (N + 1) // 2)
Z = hierarchy.linkage(ytdist)
fig, ax = plt.subplots(1, 1, figsize=(8, 3))
dn1 = hierarchy.dendrogram(Z, ax=ax)
for coll in ax.collections[:-1]: # the last collection is the ungrouped level
xmin, xmax = np.inf, -np.inf
ymax = -np.inf
for p in coll.get_paths():
box = p.get_extents()
(x0, _), (x1, y1) = p.get_extents().get_points()
xmin = min(xmin, x0)
xmax = max(xmax, x1)
ymax = max(ymax, y1)
rec = plt.Rectangle((xmin - 4, 0), xmax - xmin + 8, ymax*1.05,
facecolor=coll.get_color()[0], alpha=0.2, edgecolor="none")
ax.add_patch(rec)
plt.show()