Line with dashes of multiple colours [duplicate] - matplotlib

Using dashed lines is nice because it gives a way to distinguish lines that doesn't rely on the reader being able to perceive differences in color. The trouble is that they only work if the details of the line are all larger than the dash pattern. The documentation for Matplotlib's Axes.plot function describes how to customize a line's color (with the color keyword) and how to customize the pattern of dashes (dash keyword). Is there a way to make the plot alternate between two different selectable colors instead of "there" and "not" with a single call to Axes.plot?
I can achieve this effect by plotting the same line twice, once with a solid line and then overplotting the same data with the dashed line, but that makes managing the alpha transparency complicated (translucent lines are desirable when there are several intersecting lines on one plot). The black and grey lines in the plot below were generated with these lines of code:
ax.plot(xv1, yv1, marker="None", linestyle="-", color=(0.8, 0.8, 0.8, 1.0))
ax.plot(xv1, yv1, marker="None", linestyle="--", color=(0.0, 0.0, 0.0, 1.0))
Edit: Another reason to desire that this be doable with a single call to Axis.plot is that it would display an example line correctly when creating a legend (the only remaining drawback I've found of the methods given in the answers).

Experimenting a bit with #WilliamMiller's beautiful answer, this can be extended to more than two colors:
import matplotlib.pyplot as plt
import numpy as np
x = np.linspace(0, np.pi*4, 100)
y = np.sin(x+np.pi/2)
z = np.sin(x+np.pi/4)
w = np.sin(x)
plt.plot(x, y, linestyle=(0, (5, 5)), color='gold')
plt.plot(x, y, linestyle=(5, (5, 5)), color='crimson')
plt.plot(x, z, linestyle=(0, (10, 30)), color='blueviolet')
plt.plot(x, z, linestyle=(10, (10, 30)), color='lime')
plt.plot(x, z, linestyle=(20, (10, 30)), color='fuchsia')
plt.plot(x, z, linestyle=(30, (10, 30)), color='coral')
plt.plot(x, w, linestyle=(0, (10, 20)), color='crimson', lw=3)
plt.plot(x, w, linestyle=(10, (10, 20)), color='lime', lw=3)
plt.plot(x, w, linestyle=(20, (10, 20)), color='deepskyblue', lw=3)
plt.show()

This doesn't accomplish what you are asking "with a single call to Axes.plot", but the desired behavior can be created by using two custom linestyles - one of which is offset by the width of the other's dash.
import matplotlib.pyplot as plt
import numpy as np
x = np.linspace(0, np.pi*4, 100)
y = np.sin(x)
plt.plot(x, y, linestyle=(0, (2, 2)))
plt.plot(x, y, linestyle=(2, (2, 2)))
plt.show()
The linestyle specification is (offset, (on_off_seq)) where on_off_seq is a tuple of one or more values, in pts.

In case you want legends on your plot you can do the following:
import numpy as np
import matplotlib.pyplot as plt
x = np.arange(0, 2*np.pi, 0.2)
y = np.cos(x)
blue_line, = plt.plot(x, y, linestyle='-', lw=3)
dashed_line, = plt.plot(x, y, linestyle=(2, (2, 2)), lw=3)
plt.legend([(blue_line, dashed_line)], ['data'])
Legends in matplotlib are very customizable, more interesting examples and information can be seen here.

Related

Show derivative on a plot line with matplotlib

I want to show a triangle on a line plot from matplotlib describing the derivative with some description text or plain value/name.
Let's say I'm plotting a very simple line:
from matplotlib import pyplot as plt
x = (1, 3, 7, 10)
y = (1, 3, 7, 10)
plt.plot(x, y, '-o', mfc='none')
# plt.show_derivative(x=x[1], y=y[1], name='$\gamma$') # <- What I want
plt.show()
And I want to explicitely show the derivative like this:
Wished result in red
another example
Is there a quick function or way to do this?
You cloud try something like this:
from matplotlib import pyplot as plt
def plotDerivative(x1, x2, y1, y2, name):
triangle = plt.Polygon([[x1, y1], [x2, y2], [x2, y1]], color='black', alpha=1, fill=False)
plt.gca().add_patch(triangle)
plt.text(x2 ,y1 , name, fontsize=20)
x = (1, 3, 7, 10)
y = (1, 3, 7, 10)
plt.plot(x, y, '-o', mfc='none')
plotDerivative(x[1] + x[1]/2, x[2]-x[1]/2, y[1]+y[1]/2, y[2]-y[1]/2, '$\gamma$')
plt.show()
There is maybe a better way, but it works
Example

align legend with axes

One recurring annoyance for me is trying to lign up matplotlib legends with the axes. To put them in the top right outside of the plot, it's usually recommended to do
plt.legend(bbox_to_anchor=(1.04, 1), loc="upper left")
like
import numpy
import matplotlib.pyplot as plt
x = numpy.linspace(-2, 2, 100)
y = x ** 2
plt.plot(x, y, "-", label="x**2")
plt.legend(bbox_to_anchor=(1.04, 1), loc="upper left")
plt.grid()
# plt.show()
plt.savefig("out.png", transparent=True)
This leaves a gap, though:
Fiddling around with the magic values, e.g., bbox_to_anchor=(1.04, 1.015), does move the legend up and down, but if you finally got it somewhat right after a thousand attempts, it's all messed up after resizing the figure.
Any hints on how to do this better?
You can achieve the desired alignment by using borderaxespad=0 while specifying the legend.
Complete code
import numpy
import matplotlib.pyplot as plt
x = numpy.linspace(-2, 2, 100)
y = x ** 2
plt.plot(x, y, "-", label="x**2")
plt.legend(bbox_to_anchor=(1.04, 1), borderaxespad=0) # <--- Here
plt.grid()

Labels on Gridspec [duplicate]

I'm facing a problem in showing the legend in the correct format using matplotlib.
EDIT: I have 4 subplots in a figure in 2 by 2 format and I want legend only on the first subplot which has two lines plotted on it. The legend that I got using the code attached below contained endless entries and extended vertically throughout the figure. When I use the same code using linspace to generate fake data the legend works absolutely fine.
import numpy as np
import matplotlib.pyplot as plt
import matplotlib.ticker as mtick
import os
#------------------set default directory, import data and create column output vectors---------------------------#
path="C:/Users/Pacman/Data files"
os.chdir(path)
data =np.genfromtxt('vrp.txt')
x=np.array([data[:,][:,0]])
y1=np.array([data[:,][:,6]])
y2=np.array([data[:,][:,7]])
y3=np.array([data[:,][:,9]])
y4=np.array([data[:,][:,11]])
y5=np.array([data[:,][:,10]])
nrows=2
ncols=2
tick_l=6 #length of ticks
fs_axis=16 #font size of axis labels
plt.rcParams['axes.linewidth'] = 2 #Sets global line width of all the axis
plt.rcParams['xtick.labelsize']=14 #Sets global font size for x-axis labels
plt.rcParams['ytick.labelsize']=14 #Sets global font size for y-axis labels
plt.subplot(nrows, ncols, 1)
ax=plt.subplot(nrows, ncols, 1)
l1=plt.plot(x, y2, 'yo',label='Flow rate-fan')
l2=plt.plot(x,y3,'ro',label='Flow rate-discharge')
plt.title('(a)')
plt.ylabel('Flow rate ($m^3 s^{-1}$)',fontsize=fs_axis)
plt.xlabel('Rupture Position (ft)',fontsize=fs_axis)
# This part is not working
plt.legend(loc='upper right', fontsize='x-large')
#Same code for rest of the subplots
I tried to implement a fix suggested in the following link, however, could not make it work:
how do I make a single legend for many subplots with matplotlib?
Any help in this regard will be highly appreciated.
If I understand correctly, you need to tell plt.legend what to put as legends... at this point it is being loaded empty. What you get must be from another source. I have quickly the following, and of course when I run fig.legend as you do I get nothing.
import numpy as np
import matplotlib.pyplot as plt
fig = plt.figure()
ax1 = fig.add_axes([0.1, 0.1, 0.4, 0.7])
ax2 = fig.add_axes([0.55, 0.1, 0.4, 0.7])
x = np.arange(0.0, 2.0, 0.02)
y1 = np.sin(2*np.pi*x)
y2 = np.exp(-x)
l1, l2 = ax1.plot(x, y1, 'rs-', x, y2, 'go')
y3 = np.sin(4*np.pi*x)
y4 = np.exp(-2*x)
l3, l4 = ax2.plot(x, y3, 'yd-', x, y4, 'k^')
fig.legend(loc='upper right', fontsize='x-large')
#fig.legend((l1, l2), ('Line 1', 'Line 2'), 'upper left')
#fig.legend((l3, l4), ('Line 3', 'Line 4'), 'upper right')
plt.show()
I'd suggest doing one by one, and then applying for all.
It is useful to work with the axes directly (ax in your case) when when working with subplots. So if you set up two plots in a figure and only wish to have a legend in your second plot:
t = np.linspace(0, 10, 100)
plt.figure()
ax1 = plt.subplot(2, 1, 1)
ax1.plot(t, t * t)
ax2 = plt.subplot(2, 1, 2)
ax2.plot(t, t * t * t)
ax2.legend('Cubic Function')
Note that when creating the legend, I am doing so on ax2 as opposed to plt. If you wish to create a second legend for the first subplot, you can do so in the same way but on ax1.

Matplotlib adding letter ticks

Is there a way to add two ticks (ex. two letters) along with existing ticks (numbers)?
I have:
but want to add two ticks (letters "a" and "b"). Running the following code deletes the numbers and leaves only letters, however I want to have both.
ax.set_xticks((a, b))
ax.set_xticklabels(('$a$', '$b$'), size='xx-large')
It's easiest to use annotate instead of placing ticks.
For example, let's start by approximately reproducing your example plot:
import numpy as np
import matplotlib.pyplot as plt
x = np.linspace(2.8, 11, 100)
y = 0.2 * np.exp(-0.05 * x**2) + 0.01
fig, ax = plt.subplots()
ax.fill_between(x, y, alpha=0.3, label='surface', lw=2,
edgecolor='orange', facecolor='goldenrod')
ax.plot(x, y, color='darkred', lw=2, label='interpolated polinom')
ax.legend(fancybox=True, shadow=True)
ax.grid(ls=':')
ax.set_ylabel(r'$F[N]$', size=18)
ax.set_xlabel(r'$h[cm]$', size=18)
ax.spines['bottom'].set(linewidth=2)
plt.show()
We could add the "sub ticks" you want with:
labelpad = ax.xaxis.labelpad + 15
for val, letter in [(2.8, 'a'), (11, 'b')]:
ax.annotate('${}$'.format(letter), xy=(val, 0), xytext=(0, -labelpad),
xycoords=('data', 'axes fraction'), textcoords='offset points',
ha='center', va='top', size=18)

Adding an arbitrary line to a matplotlib plot in ipython notebook

I'm rather new to both python/matplotlib and using it through the ipython notebook. I'm trying to add some annotation lines to an existing graph and I can't figure out how to render the lines on a graph. So, for example, if I plot the following:
import numpy as np
np.random.seed(5)
x = arange(1, 101)
y = 20 + 3 * x + np.random.normal(0, 60, 100)
p = plot(x, y, "o")
I get the following graph:
So how would I add a vertical line from (70,100) up to (70,250)? What about a diagonal line from (70,100) to (90,200)?
I've tried a few things with Line2D() resulting in nothing but confusion on my part. In R I would simply use the segments() function which would add line segments. Is there an equivalent in matplotlib?
You can directly plot the lines you want by feeding the plot command with the corresponding data (boundaries of the segments):
plot([x1, x2], [y1, y2], color='k', linestyle='-', linewidth=2)
(of course you can choose the color, line width, line style, etc.)
From your example:
import numpy as np
import matplotlib.pyplot as plt
np.random.seed(5)
x = np.arange(1, 101)
y = 20 + 3 * x + np.random.normal(0, 60, 100)
plt.plot(x, y, "o")
# draw vertical line from (70,100) to (70, 250)
plt.plot([70, 70], [100, 250], 'k-', lw=2)
# draw diagonal line from (70, 90) to (90, 200)
plt.plot([70, 90], [90, 200], 'k-')
plt.show()
It's not too late for the newcomers.
plt.axvline(x, color='r') # vertical
plt.axhline(x, color='r') # horizontal
It takes the range of y as well, using ymin and ymax.
Using vlines:
import numpy as np
np.random.seed(5)
x = arange(1, 101)
y = 20 + 3 * x + np.random.normal(0, 60, 100)
p = plot(x, y, "o")
vlines(70,100,250)
The basic call signatures are:
vlines(x, ymin, ymax)
hlines(y, xmin, xmax)
Rather than abusing plot or annotate, which will be inefficient for many lines, you can use matplotlib.collections.LineCollection:
import numpy as np
import matplotlib.pyplot as plt
from matplotlib.collections import LineCollection
np.random.seed(5)
x = np.arange(1, 101)
y = 20 + 3 * x + np.random.normal(0, 60, 100)
plt.plot(x, y, "o")
# Takes list of lines, where each line is a sequence of coordinates
l1 = [(70, 100), (70, 250)]
l2 = [(70, 90), (90, 200)]
lc = LineCollection([l1, l2], color=["k","blue"], lw=2)
plt.gca().add_collection(lc)
plt.show()
It takes a list of lines [l1, l2, ...], where each line is a sequence of N coordinates (N can be more than two).
The standard formatting keywords are available, accepting either a single value, in which case the value applies to every line, or a sequence of M values, in which case the value for the ith line is values[i % M].
Matplolib now allows for 'annotation lines' as the OP was seeking. The annotate() function allows several forms of connecting paths and a headless and tailess arrow, i.e., a simple line, is one of them.
ax.annotate("",
xy=(0.2, 0.2), xycoords='data',
xytext=(0.8, 0.8), textcoords='data',
arrowprops=dict(arrowstyle="-",
connectionstyle="arc3, rad=0"),
)
In the documentation it says you can draw only an arrow with an empty string as the first argument.
From the OP's example:
%matplotlib notebook
import numpy as np
import matplotlib.pyplot as plt
np.random.seed(5)
x = np.arange(1, 101)
y = 20 + 3 * x + np.random.normal(0, 60, 100)
plt.plot(x, y, "o")
# draw vertical line from (70,100) to (70, 250)
plt.annotate("",
xy=(70, 100), xycoords='data',
xytext=(70, 250), textcoords='data',
arrowprops=dict(arrowstyle="-",
connectionstyle="arc3,rad=0."),
)
# draw diagonal line from (70, 90) to (90, 200)
plt.annotate("",
xy=(70, 90), xycoords='data',
xytext=(90, 200), textcoords='data',
arrowprops=dict(arrowstyle="-",
connectionstyle="arc3,rad=0."),
)
plt.show()
Just as in the approach in gcalmettes's answer, you can choose the color, line width, line style, etc..
Here is an alteration to a portion of the code that would make one of the two example lines red, wider, and not 100% opaque.
# draw vertical line from (70,100) to (70, 250)
plt.annotate("",
xy=(70, 100), xycoords='data',
xytext=(70, 250), textcoords='data',
arrowprops=dict(arrowstyle="-",
edgecolor = "red",
linewidth=5,
alpha=0.65,
connectionstyle="arc3,rad=0."),
)
You can also add curve to the connecting line by adjusting the connectionstyle.