How to move exponent label with spine in matplotlib twin_x plot? - python

I am using twin_x() to create a plot with shared twin axes. To create a MWE, I used an example matplotlib provides in their documentation also provided below in the code block.
If I make the y values of the twin axes that has the offset large, then the tick labels will have an exponential factor as desired. However, this value does not move with the spine.
How can I get the exponent factor label to move with the spline?
import matplotlib.pyplot as plt
import numpy as np
fig, ax = plt.subplots()
fig.subplots_adjust(right=0.75)
twin1 = ax.twinx()
twin2 = ax.twinx()
# Offset the right spine of twin2. The ticks and label have already been
# placed on the right by twinx above.
twin2.spines.right.set_position(("axes", 1.2))
y = 1e6*np.linspace(1,2,3)
p1, = ax.plot([0, 1, 2], [0, 1, 2], "b-", label="Density")
p2, = twin1.plot([0, 1, 2], [0, 3, 2], "r-", label="Temperature")
p3, = twin2.plot([0, 1, 2], y, "g-", label="Velocity")
ax.set_xlabel("Distance")
ax.set_ylabel("Density")
twin1.set_ylabel("Temperature")
twin2.set_ylabel("Velocity")
ax.yaxis.label.set_color(p1.get_color())
twin1.yaxis.label.set_color(p2.get_color())
twin2.yaxis.label.set_color(p3.get_color())
tkw = dict(size=4, width=1.5)
ax.tick_params(axis='y', colors=p1.get_color(), **tkw)
twin1.tick_params(axis='y', colors=p2.get_color(), **tkw)
twin2.tick_params(axis='y', colors=p3.get_color(), **tkw)
ax.tick_params(axis='x', **tkw)
ax.legend(handles=[p1, p2, p3])
plt.show()
I have included a picture to help demonstrate. I want to move the 1e6 to be near the axis spine for velocity since it represents the velocity value.

You can do this by getting the text of the second axis and setting it to the position of the second axis. .set_position(1.2,1.1) can be set arbitrarily, you can adjust it yourself. This answer is based on this answer.
ax.legend(handles=[p1, p2, p3])
# update
twin2.get_yaxis().get_offset_text().set_position((1.2,1.1))
plt.show()

Related

How can I add text to the same position in multiple matplotlib plots with different axis scales?

I have ~20 plots with different axes, ranging from scales of 0-1 to 0-300. I want to use plt.text(x,y) to add text to the top left corner in my automated plotting function, but the changing axis size does not allow for this to be automated and completely consistent.
Here are two example plots:
import matplotlib.pyplot as plt
plt.plot([1, 2, 3, 4])
plt.ylabel('some numbers')
plt.show()
#Plot 2
plt.plot([2, 4, 6, 8])
plt.ylabel('some numbers')
plt.show()
I want to use something like plt.text(x, y, 'text', fontsize=8) in both plots, but without specifying the x and y for each plot by hand, instead just saying that the text should go in the top left. Is this possible?
Have you tried ax.text with transform=ax.transAxes?
import matplotlib.pyplot as plt
fig, ax = plt.subplots()
ax.plot([1, 2, 3, 4])
ax.set_ylabel('XXX')
ax.text(0.05, 0.95, 'text', transform=ax.transAxes, fontsize=8, va='top', ha='left')
plt.show()
Explanation:
The ax.text takes first the x and y coordinates of the text, then the transform argument which specifies the coordinate system to use, and the va and ha arguments which specify the vertical and horizontal alignment of the text.
Use ax.annotate with axes fraction coordinates:
fig, axs = plt.subplots(1, 2)
axs[0].plot([0, 0.8, 1, 0.5])
axs[1].plot([10, 300, 200])
for ax in axs:
ax.annotate('text', (0.05, 0.9), xycoords='axes fraction')
# ------------------------
Here (0.05, 0.9) refers to 5% and 90% of the axes lengths, regardless of the data:

Position label of colorbar

I have this function:
def scatter_diagdistance(x, y) :
z = abs(y-x)
fig, ax = plt.subplots(dpi=200)
sc = ax.scatter(x, y, c=z, s=50, edgecolor='none')
x_diag = np.arange(min(x*100), max(x*100))/100
ax.plot(x_diag, x_diag, '-', c="red")
cbar = fig.colorbar(sc)
cbar.set_label('Distance from diagonal')
return(fig)
Which gives me this sort of image:
How can I position the "Distance from diagonal" to the left of the colorbar?
(Also, is there a cleaner way to plot the diagonal over a scatter plot like this?)
one way to do it is to use the text as the label for the secondary y-axis. That will keep the text before the colorbar. Also, you can draw a line for the diagonal. The code (without your data) is shown below. If you use transform=ax.transAxes details, the coordinates are interpreted as axes coordinates
fig, ax = plt.subplots(dpi=200)
ax2 = ax.twinx() ##Create secondary axis
ax2.set_yticks([]) ##No ticks for the secondary axis
sc = ax.scatter(0.5, 0.5, c=1, s=50, edgecolor='none')
ax2.set_ylabel('Distance from diagonal') ##Label for secondary axis
ax.plot([0, 1], [0, 1], '-', c="red", transform=ax.transAxes) #Line from 0 to 1
cbar = fig.colorbar(sc)
Plot

how to show an interval in logarithmic scale and other linear in matplotlib

I am using matplotlib and I want to plot a graph in which the negative part of y axis has large numbers while the positive part has smaller values and the positive part is more valuable to show. So I prefer to draw the negative part of y axis in logarithmic scale and positive part in linear(normal) scale. Here I show whole the plot with log scale and this is my code:
plt.plot(time_stamps,objs,'-rD', markevery=markers_on )
markers_on= list(i for i in range(len(upper_bound)))
plt.plot(time_stamps,upper_bound,'-bD', markevery=markers_on )
markers_on= list(i for i in range(len(best_P_list)))
plt.plot(exce_time_list,best_P_list,'-gD', markevery=markers_on)
plt.xlabel("x", fontsize=10)
plt.ylabel("y ", fontsize=10)
plt.yscale('symlog')
plt.show()
How can I show positive part of y axis in linear scale?
I completely reworked my answer because I found this SO answer which explains how to divide axes to behave differently.
Now, we still need to separate the different values and steps but it's much clearer.
import matplotlib.pyplot as plt
from mpl_toolkits.axes_grid1 import make_axes_locatable
import numpy as np
vals = np.array([-1e10, -1e7, 1e3, 1e2, -1e8, -1e1, 1e3, 500])
steps = np.array([0, 5, 1, 4, 9, 20, 7, 15])
def plot_diff_scales(vals, steps):
fig, ax = plt.subplots()
ax.plot(steps[vals >= 0], vals[vals >= 0], "-gD") # only positive values
ax.set_yscale("linear")
ax.spines["bottom"].set_visible(False) # hide bottom of box
ax.get_xaxis().set_visible(False) # hide x-axis entirely
ax.set_ylabel("Linear axis")
# Dividing the axes
divider = make_axes_locatable(ax)
ax_log = divider.append_axes("bottom", size=2, pad=0, sharex=ax)
# Acting on the log axis
ax_log.set_yscale("symlog")
ax_log.spines["top"].set_visible(False) # hide top of box
ax_log.plot(steps[vals < 0], vals[vals < 0], "-bD") # only negative values
ax_log.set_xlabel("X axis")
ax_log.set_ylabel("Symlog axis")
# Show delimiter
ax.axhline(y=0, color="black", linestyle="dashed", linewidth=1)
# Plotting proper
fig.tight_layout()
plt.show()
plot_diff_scales(vals, steps)

Transformation from axis to absolute figure coordinates using display coordinates does not work in matplotlib

I need to plot point markers at the edge along the y-axes in Matplotlib. I want the location of the markers in x-direction to be constant (independent of the size of the axes) while y-direction should be in data coordinates. I've tried to create blended transformation according to Transformations tutorial but it did not work according to my expectations. Can anyone explain where I'm making a mistake? And how to fix it?
import matplotlib
print(matplotlib.__version__) # 3.3.1
import matplotlib.transforms as transforms
fig, ax = plt.subplots(figsize=(2, 3.5))
ax.set_xlim((0,2))
ax.set_ylim((0,2))
ax.plot([0,2], [0,2])
ax.grid(True)
fig.set_facecolor('lightgray')
point = [1,0]
# Ok, let's use the y-axis transform to plot in axis coordinates in the x-direction
t_yaxis = ax.get_yaxis_transform()
ax.scatter([1, 1.1], [2, 2], transform=t_yaxis, clip_on=False, label='p1')
# This works but the distance between points depends on the size of the axes.
# We want to set it in absolute units.
# Here we create blended transformation which actually works exactly the same as the previous one.
t_own_yaxis = transforms.blended_transform_factory(ax.transAxes, ax.transData)
ax.scatter([1, 1.1], [1.5, 1.5], transform=t_own_yaxis, clip_on=False, label='p2')
# Let's try to blend physical coordinates of the figure in x-direction
# with data coordinates in y-direction.
t_blended = transforms.blended_transform_factory(fig.dpi_scale_trans, ax.transData)
ax.scatter([1, 1.1], [1, 1], transform=t_blended, clip_on=False, label='p3')
# This works for distance between points but we want to offset the points at the edge of the axes
# asi in previous cases.
# Let's calculate the location of the edge and plot using that.
t_yaxis2blended = t_yaxis + t_blended.inverted()
p4 = t_yaxis2blended.transform([1, 0.5])
print(p4)
ax.scatter([p4[0], p4[0]+0.1], [0.5, 0.5], transform=t_blended, clip_on=False, label='p4')
# Why the left 'p4' point is not at the edge?
plt.legend()
[Edit] The blue, orange and green dots are where I expected them. I expected the left red dot at [2.0, 0.5].
Interestingly, if I use interactive backend %matplotlib notebook for rendering of the plots the result is as expected:
[Edit] The reason I need this is to annotate dendrograms like this:
It is possible to use axes scale and calculate the absolute distance between points from axes size:
import matplotlib.pyplot as plt
import matplotlib.transforms as transforms
fig, ax = plt.subplots(figsize=(2, 3.5))
ax.set_xlim((0,2))
ax.set_ylim((0,2))
ax.plot([0,2], [0,2])
ax.grid(True)
fig.set_facecolor('lightgray')
t_yaxis = ax.get_yaxis_transform()
ax.scatter([1, 1.1], [2, 2], transform=t_yaxis, clip_on=False, label='p1')
bbox = ax.get_window_extent().transformed(fig.dpi_scale_trans.inverted())
shift = 0.1 / bbox.width
ax.scatter([1, 1 + shift], [0, 0], transform=t_yaxis, clip_on=False, label='p5')
plt.legend()
If you want to offset something, you can use a ScaledTransform:
import matplotlib.pyplot as plt
import matplotlib.transforms as transforms
fig, ax = plt.subplots(figsize=(2, 3.5))
ax.set_xlim((0,2))
ax.set_ylim((0,2))
ax.plot([0,2], [0,2])
t_yaxis = ax.get_yaxis_transform()
ax.scatter([1], [2], transform=t_yaxis, clip_on=False, label='p1')
# offset -0.25 inch horizontally
trans = t_yaxis + transforms.ScaledTranslation(-0.25, 0, fig.dpi_scale_trans)
ax.scatter([1], [2], transform=trans, clip_on=False, label='p1')
plt.show()
This will make the second (orange) point always offset from the blue by -0.25 inches, regardless of window resizing (note the figure above has been resized manually)
More details at https://matplotlib.org/tutorials/advanced/transforms_tutorial.html#plotting-in-physical-coordinates

How can I have a stacked plot with a shared X axis and multiple Y axis on one of the plots?

I am trying to modify this example so also have a second plot on top sharing the same X-axis. Unfortunately, it is not clear how to add subplots to this structure. I have tried to directly combine a different example, but it just creates two figures.
How do I put another plot separated from the two-y-axis plot with the same X-axis?
"""
Parasite axis demo
The following code is an example of a parasite axis. It aims to show a user how
to plot multiple different values onto one single plot. Notice how in this
example, par1 and par2 are both calling twinx meaning both are tied directly to
the x-axis. From there, each of those two axis can behave separately from the
each other, meaning they can take on separate values from themselves as well as
the x-axis.
"""
from mpl_toolkits.axes_grid1 import host_subplot
import mpl_toolkits.axisartist as AA
import matplotlib.pyplot as plt
host = host_subplot(111, axes_class=AA.Axes)
plt.subplots_adjust(right=0.75)
par1 = host.twinx()
par2 = host.twinx()
offset = 60
new_fixed_axis = par2.get_grid_helper().new_fixed_axis
par2.axis["right"] = new_fixed_axis(loc="right",
axes=par2,
offset=(offset, 0))
par2.axis["right"].toggle(all=True)
host.set_xlim(0, 2)
host.set_ylim(0, 2)
host.set_xlabel("Distance")
host.set_ylabel("Density")
par1.set_ylabel("Temperature")
par2.set_ylabel("Velocity")
p1, = host.plot([0, 1, 2], [0, 1, 2], label="Density")
p2, = par1.plot([0, 1, 2], [0, 3, 2], label="Temperature")
p3, = par2.plot([0, 1, 2], [50, 30, 15], label="Velocity")
par1.set_ylim(0, 4)
par2.set_ylim(1, 65)
host.legend()
host.axis["left"].label.set_color(p1.get_color())
par1.axis["right"].label.set_color(p2.get_color())
par2.axis["right"].label.set_color(p3.get_color())
## Attempts to add stacked plot on top
f, axarr = plt.subplots(2, sharex=True)
axarr[1].plot([0,1,2], [5,6,7])
axarr[1].set_title('Sharing X axis')
plt.draw()
plt.show()
You can create a new host subplot and share the xaxis.
replace host = host_subplot(111, axes_class=AA.Axes)
with host = host_subplot(211, axes_class=AA.Axes)
replace the lines below your line ## Attempts to add stacked plot on top with
host2 = host_subplot(212, axes_class=AA.Axes, sharex=host)
host2.plot([0,1,2], [5,6,7])
host2.set_title('Sharing X axis')
plt.tight_layout()
plt.draw()
plt.show()
this results in

Categories