1

Usually two y-axes are kept apart with different colors, as shown in the example below.

For publications it's often necessary to keep it distinguishable, even when it's printed in black and white.

This is usually done by plotting circles around a line, which have an arrow in the direction of the corresponding axis attached.

How can this be achieved with matplotlib? Or is there a better way to accomplish black and white readability without those circles?

Code from matplotlib.org:

import numpy as np
import matplotlib.pyplot as plt

# Create some mock data
t = np.arange(0.01, 10.0, 0.01)
data1 = np.exp(t)
data2 = np.sin(2 * np.pi * t)

fig, ax1 = plt.subplots()

color = 'tab:red'
ax1.set_xlabel('time (s)')
ax1.set_ylabel('exp', color=color)
ax1.plot(t, data1, color=color)
ax1.tick_params(axis='y', labelcolor=color)

ax2 = ax1.twinx()  # instantiate a second axes that shares the same x-axis

color = 'tab:blue'
ax2.set_ylabel('sin', color=color)  # we already handled the x-label with ax1
ax2.plot(t, data2, color=color)
ax2.tick_params(axis='y', labelcolor=color)

fig.tight_layout()  # otherwise the right y-label is slightly clipped
plt.show()

Marcel
  • 285
  • 2
  • 10

2 Answers2

4

This approach is based on this answer. It uses arc, which can be configured as follows:

enter image description here

import matplotlib.pyplot as plt
from matplotlib.patches import Arc

# Generate example graph
fig = plt.figure(figsize=(5, 5))
ax = fig.add_subplot(1, 1, 1)
ax.plot([1,2,3,4,5,6], [2,4,6,8,10,12])

# Configure arc
center_x = 2            # x coordinate
center_y = 3.8          # y coordinate
radius_1 = 0.25         # radius 1
radius_2 = 1            # radius 2 >> for cicle: radius_2 = 2 x radius_1
angle = 180             # orientation
theta_1 = 70            # arc starts at this angle
theta_2 = 290           # arc finishes at this angle
arc = Arc([center_x, center_y],
          radius_1,
          radius_2,
          angle = angle,
          theta1 = theta_1,
          theta2=theta_2,
          capstyle = 'round',
          linestyle='-',
          lw=1,
          color = 'black')

# Add arc
ax.add_patch(arc)

# Add arrow
x1 = 1.9            # x coordinate
y1 = 4              # y coordinate    
length_x = -0.5     # length on the x axis (negative so the arrow points to the left)
length_y = 0        # length on the y axis
ax.arrow(x1,
         y1,
         length_x,
         length_y,
         head_width=0.1,
         head_length=0.05,
         fc='k',
         ec='k',
         linewidth = 0.6)

The result is shown below:

enter image description here

  • I need at least 10 reputation to post images. Sorry about that... please run the code to see the results for yourself. Cheers. – wagnojunior Jun 24 '20 at 23:38
1

You can use matplotlib's axes annotate to draw arrows to the y-axes. You will need to find the points in the plot where the arrows should start. However, this does not plot circles around lines. If you really want to plot a circle, you could use plt.scatter or plt.Circle to plot an appropriate circle covering the relevant area.

import numpy as np
import matplotlib.pyplot as plt

# Create some mock data
t = np.arange(0.01, 10.0, 0.01)
data1 = np.exp(t)
data2 = np.sin(2 * np.pi * t)

fig, ax1 = plt.subplots()

color = 'tab:red'
ax1.set_xlabel('time (s)')
ax1.set_ylabel('exp', color=color)
ax1.plot(t, data1, color=color)
ax1.tick_params(axis='y', labelcolor=color)

ax1.annotate('', xy=(7, 1096), xytext=(-0.5, 1096), # start the arrow from x=7 and draw towards primary y-axis
            arrowprops=dict(arrowstyle="<-", color=color))

ax2 = ax1.twinx()  # instantiate a second axes that shares the same x-axis

color = 'tab:blue'
ax2.set_ylabel('sin', color=color)  # we already handled the x-label with ax1
ax2.plot(t, data2, color=color)
ax2.tick_params(axis='y', labelcolor=color)

# plt.arrow()
ax2.annotate('', xy=(6,0),  xytext=(10.4, 0), # start the arrow from x=6 and draw towards secondary y-axis
            arrowprops=dict(arrowstyle="<-", color=color))

fig.tight_layout()  # otherwise the right y-label is slightly clipped
plt.show()

Following is the sample output figure. enter image description here

EDIT: Following is the snippet with the circles you've requested. I have used plt.scatter.

import numpy as np
import matplotlib.pyplot as plt
from matplotlib.patches import Circle
# Create some mock data
t = np.arange(0.01, 10.0, 0.01)
data1 = np.exp(t)
data2 = np.sin(2 * np.pi * t)

fig, ax1 = plt.subplots()

color = 'tab:red'
ax1.set_xlabel('time (s)')
ax1.set_ylabel('exp', color=color)
ax1.plot(t, data1, color=color)
ax1.tick_params(axis='y', labelcolor=color)

ax1.annotate('', xy=(7, 1096), xytext=(-0.5, 1096), # start the arrow from x=7 and draw towards primary y-axis
            arrowprops=dict(arrowstyle="<-", color=color))

# circle1 = Circle((5, 3000), color='r')
# ax1.add_artist(circle1)
plt.scatter(7, 1096, s=100, facecolors='none', edgecolors='r')

ax2 = ax1.twinx()  # instantiate a second axes that shares the same x-axis

color = 'tab:blue'
ax2.set_ylabel('sin', color=color)  # we already handled the x-label with ax1
ax2.plot(t, data2, color=color)
ax2.tick_params(axis='y', labelcolor=color)

# plt.arrow()
ax2.annotate('', xy=(6.7,0),  xytext=(10.5, 0), # start the arrow from x=6.7 and draw towards secondary y-axis
            arrowprops=dict(arrowstyle="<-", color=color))
plt.scatter(6,0, s=2000, facecolors='none', edgecolors=color)


fig.tight_layout()  # otherwise the right y-label is slightly clipped
plt.savefig('fig')
plt.show()

Here is the sample output.

enter image description here

Achintha Ihalage
  • 2,310
  • 4
  • 20
  • 33