Adding arrows to parametric plots in matplotlib - python

I'm doing simulations where multiple variables vary in time. Occasionally, it is useful to plot variables not against the time axis (x(t) versus t) but against each other (x(t) versus y(t)).
In these cases, it'd be nice if I could add some sort of arrows (overlaid on the curve) indicating the direction of time flow.
My question: does anybody know of an easy or built-in method to do this, or should I hack something together myself?

Try this (from the matplotlib cookbook http://www.scipy.org/Cookbook/Matplotlib/Arrows):
from pylab import *
from numarray import *
x = arange(10)
y = x
# Plot junk and then a filled region
plot(x, y)
# Now lets make an arrow object
arr = Arrow(2, 2, 1, 1, edgecolor='white')
# Get the subplot that we are currently working on
ax = gca()
# Now add the arrow
ax.add_patch(arr)
# We should be able to make modifications to the arrow.
# Lets make it green.
arr.set_facecolor('g')

Related

Adjusting the position of an xticklabel in matplotlib has no effect in x-direction

Using matplotlib 2.2.2 with gridspec in Python 3.6.5, I created a huge plot for a research paper with several subplots. The axes objects are stored in a dictionary called axes. This dictionary is passed to the function adjust_xticklabels(), which is supposed to align the first xticklabel slightly to the right and the last xticklabel slightly to the left in each subplot, such that the xticklabels of neighbouring plots dont get in the way of each other. The function is defined as:
def adjust_xticklabels(axes, rate = 0.1):
for ax in axes.values():
left, right = ax.get_xlim() # get boundaries
dist = right-left # get distance
xtl = ax.get_xticklabels()
if len(xtl) > 1:
xtl[0].set_position((left + rate*dist, 0.)) # (x, y), shift right
xtl[-1].set_position((right - rate*dist, 0.)) # shift left
Calling it has no effect. Of course I also tried it with ridiculously high values. However, is has an effect in y-direction, for instance in case of setting xtl[0].set_position((0.3, 0.3)).
A simple reproduction:
ax = plt.subplot(111)
ax.plot(np.arange(10))
xtl = ax.get_xticklabels()
xtl[4].set_position((0.3, 0.3)) # wlog, 4 corresponds to 6
I spent quite a while on trying to figure out if this is a feature or a bug. Did I miss something or is this a bug? Is there any other way to do the same thing?
This is a feature, no bug. The ticklabels are positionned at drawtime to sit at the correct locations according to the ticker in use. This ensures that the label always sits where the corresponding tick is located. If you change the limits, move or zoom the plot, the label always follows those changes.
You are usually not meant to change this location, but you may, by adding a custom transform to it. This is described in
Moving matplotlib xticklabels by pixel value. The general idea is to set a translating transformation on the label. E.g. to translate the second label by 20 pixels to the right,
import matplotlib.transforms as mtrans
# ...
trans = mtrans.Affine2D().translate(20, 0)
label = ax.get_xticklabels()[1]
label.set_transform(label.get_transform()+trans)

How to control the cell size of a pyplot pcolor heatmap?

I have a pair of lists of numbers representing points in a 2-D space, and I want to represent the y/x ratios for these points as a 1-dimensional heatmap, with a diverging color map centered around 1, or the logs of my ratios, with a diverging color map centered around 0.
How do I do that?
My current attempt (borrowing somewhat from Heatmap in matplotlib with pcolor?):
from matplotlib import numpy as np
import matplotlib.pyplot as plt
# There must be a better way to generate arrays of random values
x_values = [np.random.random() for _ in range(10)]
y_values = [np.random.random() for _ in range(10)]
labels = list("abcdefghij")
ratios = np.asarray(y_values) / np.asarray(x_values)
axis = plt.gca()
# I transpose the array to get the points arranged vertically
heatmap = axis.pcolor(np.log2([ratios]).T, cmap=plt.cm.PuOr)
# Put labels left of the colour cells
axis.set_yticks(np.arange(len(labels)) + 0.5, minor=False)
# (Not sure I get the label order correct...)
axis.set_yticklabels(labels)
# I don't want ticks on the x-axis: this has no meaning here
axis.set_xticks([])
plt.show()
Some points I'm not satisfied with:
The coloured cells I obtain are horizontally-elongated rectangles. I would like to control the width of these cells and obtain a column of cells.
I would like to add a legend for the color map. heatmap.colorbar = plt.colorbar() fails with RuntimeError: No mappable was found to use for colorbar creation. First define a mappable such as an image (with imshow) or a contour set (with contourf).
One important point:
matplotlib/pyplot always leaves me confused: there seems to be a lot of ways to do things and I get lost in the documentation. I never know what would be the "clean" way to do what I want: I welcome suggestions of reading material that would help me clarify my very approximative understanding of these things.
Just 2 more lines:
axis.set_aspect('equal') # X scale matches Y scale
plt.colorbar(mappable=heatmap) # Tells plt where it should find the color info.
Can't answer your final question very well. Part of it is due to we have two branches of doing things in matplotlib: the axis way (axis.do_something...) and the MATLAB clone way plt.some_plot_method. Unfortunately we can't change that, and it is a good feature for people to migrate into matplotlib. As far as the "Clean way" is concerned, I prefer to use whatever produces the shorter code. I guess that is inline with Python motto: Simple is better than complex and Readability counts.

Drawing a colorbar aside a line plot, using Matplotlib

I'm trying to add a color bar in a graph, but I don't understand how it works. The problem is that I make my own colorcode by:
x = np.arange(11)
ys = [i+x+(i*x)**2 for i in range(11)]
colors = cm.rainbow(np.linspace(0, 1, len(ys)))
and colors[i] will give me a new color. Then I use (homemade) functions to select the relevant data and plot them accordingly. This would look something like this:
function(x,y,concentration,temperature,1,37,colors[0])
function(x,y,concentration,temperature,2,37,colors[1])
# etc
Now I want to add the colors in a color bar, with labels I can change. How do I do this?
I have seen several examples where you plot all the data as one array, with automated color bars, but here I plot the data one by one (by using functions to select the relevant data).
EDIT:
function(x,y,concentration,temperature,1,37,colors[0]) looks like this (simplified):
def function(x,y,c,T,condition1,condition2,colors):
import matplotlib.pyplot as plt
i=0
for element in c:
if element == condition1:
if T[i]==condition2:
plt.plot(x,y,color=colors,linewidth=2)
i=i+1
return
Drawing a colorbar aside a line plot
Please map my solution (I used simply 11 sines of different amplitudes) to your problem (as I told you, it is difficult to understand from what you wrote in your Q).
import matplotlib
import numpy as np
from matplotlib import pyplot as plt
# an array of parameters, each of our curves depend on a specific
# value of parameters
parameters = np.linspace(0,10,11)
# norm is a class which, when called, can normalize data into the
# [0.0, 1.0] interval.
norm = matplotlib.colors.Normalize(
vmin=np.min(parameters),
vmax=np.max(parameters))
# choose a colormap
c_m = matplotlib.cm.cool
# create a ScalarMappable and initialize a data structure
s_m = matplotlib.cm.ScalarMappable(cmap=c_m, norm=norm)
s_m.set_array([])
# plotting 11 sines of varying amplitudes, the colors are chosen
# calling the ScalarMappable that was initialised with c_m and norm
x = np.linspace(0,np.pi,31)
for parameter in parameters:
plt.plot(x,
parameter*np.sin(x),
color=s_m.to_rgba(parameter))
# having plotted the 11 curves we plot the colorbar, using again our
# ScalarMappable
plt.colorbar(s_m)
# That's all, folks
plt.show()
Example
Acknowledgements
A similar problem, about a scatter plot
Update — April 14, 2021
With recent versions of Matplotlib, the statement s_m.set_array([]) is not required any more. On the other hand, it does no harm.
When plotting, in place of color=s_m.to_rgba(parameter) one may want to use the (slightly) more obvious color=c_m(norm(parameter)).

Symmetrical Log color scale in matplotlib contourf plot

How do I create a contour plot with a symlog (symmetrical log) scale for the contours. i.e. a log scale that shows both negative and positive values.
One possibility would be to work off of this example:
http://matplotlib.org/examples/pylab_examples/contourf_log.html
Which gives this recipe for a log scale:
from matplotlib import pyplot, ticker
cs = pyplot.contourf(X, Y, z, locator=ticker.LogLocator())
However, this doesn't allow for negative values. There is a ticker.SymmetricalLogLocator(), which may be the solution, but it doesn't seem to have much documentation.
EDIT:
To clarify (since requesting negative values on a log scale may sound nonsensical), what I want is the same as the "symlog" scale provided on matplotlib axes. The plot below, (taken from another stack exchange post), shows symlog on the x-axis. It is a "log" scale, but handles negative values in a way that is clear to the viewer.
I want the same sort of scaling, but for the colorscale on contour or contourf.
I stumbled across this thread trying to do the same thing, i.e plotting a large range of values in both the positive and negative direction. In addition I wanted to have a granularity as fine as in imshow.
It turns out you can have that using "ticker.MaxNLocator(nbins)" where nbins can be set high to have a fine granularity, e.g. set nbins to 100.
I also wanted to have a nice Latex style ticker formatting, for which I found a solution on StackOverflow a while ago.
I will just post this code snippet here from one of the classes it is part of so that anyone who might want can get the basic idea about how it's working. I use this solution to generate multiple plots as shown in the image below.
import matplotlib.pyplot as plt
import matplotlib.ticker as ticker
# function for nice Latex style tick formatting
# copied from
# http://stackoverflow.com/questions/25983218/
# scientific-notation-colorbar-in-matplotlib
# output formating for colorbar in 2D plots
def fmt(x, pos):
a, b = '{:.2e}'.format(x).split('e')
b = int(b)
return r'${} \times 10^{{{}}}$'.format(a, b)
# A confourf function I use inside one of my classes
# mainly interesting are the "plot" and "cbar" lines
def Make2DSubPlot(self, posIdent, timeIdx,typeIdx):
plt.subplot(posIdent)
y = self.radPos
x = self.axPos
z = self.fieldList[timeIdx][typeIdx]
plot = plt.contourf(x, y, z, locator=ticker.MaxNLocator(100), \
aspect='auto',origin='lower')
cbar = plt.colorbar(plot, orientation='vertical', \
format=ticker.FuncFormatter(fmt))
cbar.ax.set_ylabel(self.labelList[typeIdx])
plt.xlabel(self.labelList[self.iax])
plt.ylabel(self.labelList[self.iax])

Draw connecting line to points with a zero ordinate on a log scale with matplotlib?

Is it possible to plot the connecting line to points whose y value is zero on a log scale in matplotlib?
I have some data that I want to plot with a log scale on the y-axis. The y values for some of the data lie at zero. I realize it's not possible for matplotlib to plot these points on a log scale, but I really wish it would draw the connecting line from the previous point or to the next point (if either are non-zero).
One solution would be to simply replace all zeros with some TINY number. I'd rather not do this.
What matplotlib draws:
What I'd like it to draw:
I'd be looking to solve this by using the 'symlog' option on the y axis instead of 'log'. There's then a linthreshy arg which lets you specify
"The range within which the plot is linear (to avoid having the plot
go to infinity around zero).".
In fact it's exactly this sort of issue the option seems designed to deal with. It can look a bit goofy having this weird linear zone along the bottom of your log scale plot, but you can make it pretty small.
You could always appened an extra point to the bottom of the graph by pulling out the coordinates from your current figure:
import numpy as np
import pylab as plt
# Create some sample data like yours
X = np.linspace(0,3,100)
Y = np.exp(-X)
def semilogy_to_bottom(X,Y):
# Plot once to move axes and remove plot
P, = plt.semilogy(X,Y)
plt.gca().lines.remove(P)
# Find the bottom of the graph
y_min = plt.gca().get_ylim()[0]
# Add a new point
X2 = np.concatenate((X,[X[-1]]))
Y2 = np.concatenate((Y,[y_min]))
plt.semilogy(X2,Y2)
semilogy_to_bottom(X,Y)
plt.xlim(0,5)
plt.show()

Categories

Resources