-
-
Notifications
You must be signed in to change notification settings - Fork 7.9k
Description
Bug summary
In a polar Axes with a log scale for the radial axis, the values reported on mouse hover are incorrect. This is due to a bug in the transData.transform stack.
The bug is located in/near:
matplotlib.projections.polar.InvertedPolarTransform.transform_non_affine
Code for reproduction
"""Example to show bug in Polar plots with Log radial scale."""
import sys
import matplotlib.pyplot as plt
import matplotlib as mpl
import pandas as pd
import numpy as np
import matplotlib
print(f"Matplotlib version: {matplotlib._version.version}")
print(f"Python version: {sys.version}")
_freq = np.arange(100, 10000, 200)
_angle = np.arange(-180, 180, 5) / 57.29
freqs, angles = np.meshgrid(_freq, _angle)
vals = np.random.random(freqs.shape)
fig = plt.figure(figsize=(10, 10))
ax = fig.add_subplot(111, projection="polar")
ax.set_rscale("log")
ax.set_rlim(200, 20000)
colormesh = ax.pcolormesh(angles, freqs, vals)
# Define a trivial format_coord
# without this function, we get an overflow when cursor hovers
# over axis.
def format_coord(x, y):
return f"{x}, {y}"
ax.format_coord = format_coord
# Demonstrate the bug. Transform from data coordinates to graphics
# coordinates and then back. The radial value ends up with an
# extra factor of 10^(minimum value of r axis).
# This bug is located in:
# matplotlib.projections.polar.InvertedPolarTransform.transform_non_affine
# see workaround at bottom of this file for more info.
# ang, rval
x, r = test_coords = (0.1, 1000)
round_trip_x, round_trip_r = ax.transData.inverted().transform(
ax.transData.transform(test_coords)
)
if np.isclose(round_trip_x, x):
print("X value made it through scaling round trip")
else:
print(f"X value did not make it through scaling was/is: {x}/{round_trip_x}")
if np.isclose(round_trip_r, r):
print("Y value made it through scaling round trip")
else:
print(f"Y value did not make it through scaling was/is: {r}/{round_trip_r}")
plt.show()
Actual outcome
Note the power of 10^200 in the lower right hand corner of the plot. This 200 comes from the minimum value of the r-axis.


Expected outcome
The reported value from the cursor output and from the transform round trip should match the plot data. (hovering over (1, 500) should report (1, 500) instead of (1, 1.2e200) etc.
Additional information
The problem lies with the stack of transforms that ends up being contained in ax.transData.inverted()
When there is a polar axes and a log axis, the inverted scaling doesn't happen in the correct order. See the workaround below. I'm using this fix, but I think it's possibly not the best spot to fix the bug, it could just be the order that the transforms are being applied. I didn't have time to completely figure out how/when transData gets built up. (I looked through the nested ._a and ._b of each scaling class to find the Polar transform bug fixed in the workaround.
# WORK AROUND
# This works, but isn't likely the most robust fix. self._axis.get_yscale() == 'log'
# doesn't look robust to me!
def my_transform_non_affine(self, values):
# docstring inherited
x, y = values.T
r = np.hypot(x, y)
theta = (np.arctan2(y, x) + 2 * np.pi) % (2 * np.pi)
# PolarAxes does not use the theta transforms here, but apply them for
# backwards-compatibility if not being used by it.
if self._apply_theta_transforms and self._axis is not None:
theta -= self._axis.get_theta_offset()
theta *= self._axis.get_theta_direction()
theta %= 2 * np.pi
if self._use_rmin and self._axis is not None:
------------ FIX FIX FIX FIX is here.
if self._axis.get_yscale() == "log":
base = self._axis.yaxis.get_transform().base
log_origin = np.log10(self._axis.get_rorigin()) / np.log10(base)
r += log_origin
else:
r += self._axis.get_rorigin()
-----------
r *= self._axis.get_rsign()
return np.column_stack([theta, r])
matplotlib.projections.polar.InvertedPolarTransform.transform_non_affine = (
my_transform_non_affine
)
Operating system
Mac OS
Matplotlib Version
3.7.2
Matplotlib Backend
MacOSX
Python version
3.8.17
Jupyter version
No response
Installation
conda