-
-
Notifications
You must be signed in to change notification settings - Fork 7.9k
Improve Gradient bar example #14057
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Improve Gradient bar example #14057
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,7 +1,21 @@ | ||
""" | ||
============ | ||
Gradient Bar | ||
============ | ||
======================== | ||
Bar chart with gradients | ||
======================== | ||
|
||
Matplotlib does not natively support gradients. However, we can emulate a | ||
gradient-filled rectangle by an `.AxesImage` of the right size and coloring. | ||
|
||
In particular, we use a colormap to generate the actual colors. It is then | ||
sufficient to define the underlying values on the corners of the image and | ||
let bicubic interpolation fill out the area. We define the gradient direction | ||
by a unit vector *v*. The values at the corners are then obtained by the | ||
lengths of the projections of the corner vectors on *v*. | ||
|
||
A similar approach can be used to create a gradient background for an axes. | ||
In that case, it is helpful to uses Axes coordinates | ||
(`extent=(0, 1, 0, 1), transform=ax.transAxes`) to be independent of the data | ||
coordinates. | ||
|
||
""" | ||
import matplotlib.pyplot as plt | ||
|
@@ -10,12 +24,44 @@ | |
np.random.seed(19680801) | ||
|
||
|
||
def gbar(ax, x, y, width=0.5, bottom=0): | ||
X = [[.6, .6], [.7, .7]] | ||
def gradient_image(ax, extent, direction=0.3, cmap_range=(0, 1), **kwargs): | ||
""" | ||
Draw a gradient image based on a colormap. | ||
|
||
Parameters | ||
---------- | ||
ax : Axes | ||
The axes to draw on. | ||
extent | ||
The extent of the image as (xmin, xmax, ymin, ymax). | ||
By default, this is in Axes coordinates but may be | ||
changed using the *transform* kwarg. | ||
direction : float | ||
The direction of the gradient. This is a number in | ||
range 0 (=vertical) to 1 (=horizontal). | ||
cmap_range : float, float | ||
The fraction (cmin, cmax) of the colormap that should be | ||
used for the gradient, where the complete colormap is (0, 1). | ||
**kwargs | ||
Other parameters are passed on to `.Axes.imshow()`. | ||
In particular useful is *cmap*. | ||
""" | ||
phi = direction * np.pi / 2 | ||
v = np.array([np.cos(phi), np.sin(phi)]) | ||
X = np.array([[v @ [1, 0], v @ [1, 1]], | ||
[v @ [0, 0], v @ [0, 1]]]) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Just a style/judgment thing. I think this is
or alternatively, just don't bother with dot products at all
(either fully vectorize the thing, or don't vectorize it at all.) There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I've chosen this notation intentionally to illustrate the projection of the corners on v. The upper does not produce the same result (I assume it's some axis ordering, but didn't investigate). The lower does work, but obscures what the calculation actually does. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Ah, I didn't see it as a projection; indeed, that makes things clearer. |
||
a, b = cmap_range | ||
X = a + (b - a) / X.max() * X | ||
im = ax.imshow(X, extent=extent, interpolation='bicubic', | ||
vmin=0, vmax=1, **kwargs) | ||
return im | ||
|
||
|
||
def gradient_bar(ax, x, y, width=0.5, bottom=0): | ||
for left, top in zip(x, y): | ||
right = left + width | ||
ax.imshow(X, interpolation='bicubic', cmap=plt.cm.Blues, | ||
extent=(left, right, bottom, top), alpha=1) | ||
gradient_image(ax, extent=(left, right, bottom, top), | ||
cmap=plt.cm.Blues_r, cmap_range=(0, 0.8)) | ||
|
||
|
||
xmin, xmax = xlim = 0, 10 | ||
|
@@ -24,13 +70,13 @@ def gbar(ax, x, y, width=0.5, bottom=0): | |
fig, ax = plt.subplots() | ||
ax.set(xlim=xlim, ylim=ylim, autoscale_on=False) | ||
|
||
X = [[.6, .6], [.7, .7]] | ||
ax.imshow(X, interpolation='bicubic', cmap=plt.cm.copper, | ||
extent=(xmin, xmax, ymin, ymax), alpha=1) | ||
# background image | ||
gradient_image(ax, direction=0, extent=(0, 1, 0, 1), transform=ax.transAxes, | ||
cmap=plt.cm.Oranges, cmap_range=(0.1, 0.6)) | ||
|
||
N = 10 | ||
x = np.arange(N) + 0.25 | ||
x = np.arange(N) + 0.15 | ||
y = np.random.rand(N) | ||
gbar(ax, x, y, width=0.7) | ||
gradient_bar(ax, x, y, width=0.7) | ||
ax.set_aspect('auto') | ||
plt.show() |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Just passing the angle (in degrees or in radians, up to you) seems simpler (in particular to explain)?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It would be the angle only for square images. In general, it's a (nonlinear monotoneous) parametrization of the angle due to the distortion. I did not bother to untangle the transformation from
extent
; and I didn't want to raise false expectations.If you want to make real angles work, you are welcome to provide a PR. OTOH, maybe one does not want real angles - probably depends on the application. 0.5 is now always a gradient along the diagonal of the image.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Good point, let's just leave it as it is.