Repository: matplotlib/matplotlib

Issue:
Add `fmt_r` and `fmt_theta` methods to polar axes
The example here works in Cartesian coordinates:

http://matplotlib.org/examples/pylab_examples/coords_report.html

but if you change 

`subplots()`

to

`subplots(subplot_kw={'polar':True})`

Then the `millions()` function is never even called.

( no response on mailing list: http://matplotlib.1069221.n5.nabble.com/fmt-xdata-fmt-ydata-on-polar-plot-td45838.html )



Discussion:
Because the polar plot does not use those functions.  The message shown in the the gui are generated by calls to `axes.format_coords`, which is the case the the basic Cartesian coordinates the function is: https://github.com/matplotlib/matplotlib/blob/master/lib/matplotlib/axes/_base.py#L3056 which looks at `self.fmt_xdata` and `self.fmt_ydata` (via `format_xdata` and `format_ydata`).  In contrast the same function on the `Axes` sub-class for polar axes (https://github.com/matplotlib/matplotlib/blob/master/lib/matplotlib/projections/polar.py#L589) does not.  

If you want to change the message for polar coordinates, directly assign to `ax.format_coords` as shown in http://matplotlib.org/examples/api/image_zcoord.html.

It would be sensible to have a `fmt_r` and `fmt_theta` functions attached to polar axes.

Thanks for the solution!

As far as naming convention goes (if you do implement the new functions), I don't know if `fmt_r` and `fmt_theta` would make things more or less consistent... there are other cases in polar where we still use functions with 'x' and 'y' in the name... e.g. `set_yticklabels`.

The problem I have always had with PolarAxes is remembering whether x maps to r or theta. We need to use the x and y notations for subclassing purposes (that way the core matplotlib code can continue to work properly by treating PolarAxes like any other 2D axes object). Aliases to x and y for r and theta would be very useful to end-users in general, like we have now for some things in PolarAxes.

However, I don't know how valuable it would be for this particular case. The way we advertise these formatting functions, we expect users to either monkey patch the particular method they want to modify, or to subclass and override the relevant method. If we had aliases, they won't get updated to the new methods, nor would they get used anyway by the internal methods.

No, I think the correct solution here is to make PolarAxes have a `fmt_xdata` and `fmt_ydata` method like most other classes do. I think the code we see in `format_coords` is intended to be compatibility code to help transition Axes objects that had not yet been updated to provide such methods.

This issue has been marked "inactive" because it has been 365 days since the last comment. If this issue is still present in recent Matplotlib releases, or the feature request is still wanted, please leave a comment and this label will be removed. If there are no updates in another 30 days, this issue will be automatically closed, but you are free to re-open or create a new issue if needed. We value issue reports, and this procedure is meant to help us resurface and prioritize issues that have not been addressed yet, not make them disappear.  Thanks for your help!
hi there, @atpage @tacaswell is this issue still open?
Hi @antara-gandhi , unless the issue is closed (as seen on the GitHub interface) you are free to propose a PR or ask for guidance 😄 In this case, it would be nice to check if the issue is still present in recent versions and then propose a solution. Cheers!
Is the goal to change the example code to use fmt_r and fmt_theta and be displayed as a polar graph or a way to standardize using fmt_r and fmt_theta to change coordinate plots to polar plots?

My second question is more if there is code I can modify, or do I need to come up with a way to standardize this?

I changed and added to the example code is this correct?

`def fmt_r(r):
    
    return f"R={r:.2f}"

def fmt_theta(theta, pos=None):
    
    return f"Theta={np.degrees(theta):.2f}°"

def millions(x, pos=None):
    return '$%1.1fM' % (x*1e-6)

x = np.random.rand(20)
y = 1e7 * np.random.rand(20)

fig, ax = plt.subplots(subplot_kw={'polar': True})

ax.set_yticklabels([fmt_r(label) for label in ax.get_yticks()])

ax.set_xticklabels([fmt_theta(label) for label in ax.get_xticks()])

ax.yaxis.set_major_formatter(plt.FuncFormatter(millions))

ax.plot(x, y, 'o')

plt.show()`



Patch:
diff --git a/lib/matplotlib/projections/polar.py b/lib/matplotlib/projections/polar.py
index 8d3e03f64e7c..025155351f88 100644
--- a/lib/matplotlib/projections/polar.py
+++ b/lib/matplotlib/projections/polar.py
@@ -1447,12 +1447,25 @@ def format_sig(value, delta, opt, fmt):
                     cbook._g_sig_digits(value, delta))
             return f"{value:-{opt}.{prec}{fmt}}"
 
-        return ('\N{GREEK SMALL LETTER THETA}={}\N{GREEK SMALL LETTER PI} '
-                '({}\N{DEGREE SIGN}), r={}').format(
+        # In case fmt_xdata was not specified, resort to default
+
+        if self.fmt_ydata is None:
+            r_label = format_sig(r, delta_r, "#", "g")
+        else:
+            r_label = self.format_ydata(r)
+
+        if self.fmt_xdata is None:
+            return ('\N{GREEK SMALL LETTER THETA}={}\N{GREEK SMALL LETTER PI} '
+                    '({}\N{DEGREE SIGN}), r={}').format(
                     format_sig(theta_halfturns, delta_t_halfturns, "", "f"),
                     format_sig(theta_degrees, delta_t_degrees, "", "f"),
-                    format_sig(r, delta_r, "#", "g"),
+                    r_label
                 )
+        else:
+            return '\N{GREEK SMALL LETTER THETA}={}, r={}'.format(
+                        self.format_xdata(theta),
+                        r_label
+                        )
 
     def get_data_ratio(self):
         """


Test patch:
diff --git a/lib/matplotlib/tests/test_polar.py b/lib/matplotlib/tests/test_polar.py
index 6b3c08d2eb3f..0bb41a50b2d4 100644
--- a/lib/matplotlib/tests/test_polar.py
+++ b/lib/matplotlib/tests/test_polar.py
@@ -436,6 +436,33 @@ def test_cursor_precision():
     assert ax.format_coord(2, 1) == "θ=0.637π (114.6°), r=1.000"
 
 
+def test_custom_fmt_data():
+    ax = plt.subplot(projection="polar")
+    def millions(x):
+        return '$%1.1fM' % (x*1e-6)
+
+    # Test only x formatter
+    ax.fmt_xdata = None
+    ax.fmt_ydata = millions
+    assert ax.format_coord(12, 2e7) == "θ=3.8197186342π (687.54935416°), r=$20.0M"
+    assert ax.format_coord(1234, 2e6) == "θ=392.794399551π (70702.9919191°), r=$2.0M"
+    assert ax.format_coord(3, 100) == "θ=0.95493π (171.887°), r=$0.0M"
+
+    # Test only y formatter
+    ax.fmt_xdata = millions
+    ax.fmt_ydata = None
+    assert ax.format_coord(2e5, 1) == "θ=$0.2M, r=1.000"
+    assert ax.format_coord(1, .1) == "θ=$0.0M, r=0.100"
+    assert ax.format_coord(1e6, 0.005) == "θ=$1.0M, r=0.005"
+
+    # Test both x and y formatters
+    ax.fmt_xdata = millions
+    ax.fmt_ydata = millions
+    assert ax.format_coord(2e6, 2e4*3e5) == "θ=$2.0M, r=$6000.0M"
+    assert ax.format_coord(1e18, 12891328123) == "θ=$1000000000000.0M, r=$12891.3M"
+    assert ax.format_coord(63**7, 1081968*1024) == "θ=$3938980.6M, r=$1107.9M"
+
+
 @image_comparison(['polar_log.png'], style='default')
 def test_polar_log():
     fig = plt.figure()