Skip to content

twinx not respecting log-scale #18385

Closed

Description

@MarkusSchwarz1980

Bug report

Bug summary

When a new Axes is created with twinx() from an Axes that has a logarithmic scale, the resulting Axes does not have the logarithmic scale.

Code for reproduction

This code is based on the matplotlib example Plots with different scales

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)

fig1, ax1 = plt.subplots(num='1')

color = 'tab:red'
ax1.set_xlabel('time (s)')
ax1.set_ylabel('exp', color=color)
ax1.set_xscale('log')  # set x axis to log scale
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)

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

Actual outcome

The second axis does not have logarithmic scaling:
twinx1

Expected outcome
By using ax1.set_xscale('log') I expected that the 'twinned' Axes also has a logarithmic scale:
twinx2

By setting the logarithmic scale individually after the second axis has been created gives the desired result

fig2, ax3 = plt.subplots(num='2')

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

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

# set log scale for both axis
for ax in [ax3, ax4]:
    ax.set_xscale('log')

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

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

Matplotlib version

  • Operating system: macOS 10.15.6
  • Matplotlib version: 3.3.1 (from channel conda-forge)
  • Matplotlib backend (print(matplotlib.get_backend())): Qt5Agg
  • Python version: 3.7.8
  • Jupyter version (if applicable):
  • Other libraries:

Activity

  1. QuLogic commented on Sep 2, 2020

    @QuLogic
    Member

    This appears to be a regression since 3.2.2.

  2. added this to the v3.3.2 milestone on Sep 2, 2020
  3. QuLogic commented on Sep 2, 2020

    @QuLogic
    Member

    Bisect says 83c2d7b is the first bad commit.

    commit 83c2d7b6b567b8d4006671ce862bf804f3b50426
    Author: Antony Lee <anntzer.lee@gmail.com>
    Date:   Wed Sep 18 12:39:11 2019 +0200
    
        Allow sharex/y after axes creation.
        
        This intentionally does not allow unsharing or changing the shared axes,
        as there are bigger questions on the API there.
        
        The API is named `Axes.sharex()` to allow for a later `Axes.unsharex()`,
        though (`set_sharex()` would be fine, but `set_unsharex()`? or perhaps
        `set_sharex(None)`, though).
        
        An example use case is creating a grid with `subplots()`, but with
        custom sharing relationships between the subplots -- e.g., sharex/sharey
        across all, except the first row of axes which only shares x with their
        column and the first column which only shares y with their lines.
    
  4. QuLogic commented on Sep 2, 2020

    @QuLogic
    Member

    A simple workaround is moving sharex before cla, and then a small tweak to ensure it doesn't break then:

    diff --git a/lib/matplotlib/axes/_base.py b/lib/matplotlib/axes/_base.py
    index 23222d885..b6960a3db 100644
    --- a/lib/matplotlib/axes/_base.py
    +++ b/lib/matplotlib/axes/_base.py
    @@ -488,7 +488,11 @@ class _AxesBase(martist.Artist):
             self._stale_viewlim_x = False
             self._stale_viewlim_y = False
             self._sharex = None
    +        if sharex is not None:
    +            self.sharex(sharex)
             self._sharey = None
    +        if sharey is not None:
    +            self.sharey(sharey)
             self.set_label(label)
             self.set_figure(fig)
             self.set_box_aspect(box_aspect)
    @@ -507,11 +511,6 @@ class _AxesBase(martist.Artist):
             self._rasterization_zorder = None
             self.cla()
     
    -        if sharex is not None:
    -            self.sharex(sharex)
    -        if sharey is not None:
    -            self.sharey(sharey)
    -
             # funcs used to format x and y - fall back on major formatters
             self.fmt_xdata = None
             self.fmt_ydata = None
    @@ -1018,6 +1017,9 @@ class _AxesBase(martist.Artist):
                 raise ValueError("x-axis is already shared")
             self._shared_x_axes.join(self, other)
             self._sharex = other
    +        if not hasattr(self, 'xaxis'):
    +            # This happens during __init__.
    +            return
             self.xaxis.major = other.xaxis.major  # Ticker instances holding
             self.xaxis.minor = other.xaxis.minor  # locator and formatter.
             x0, x1 = other.get_xlim()
    @@ -1037,6 +1039,9 @@ class _AxesBase(martist.Artist):
                 raise ValueError("y-axis is already shared")
             self._shared_y_axes.join(self, other)
             self._sharey = other
    +        if not hasattr(self, 'xaxis'):
    +            # This happens during __init__.
    +            return
             self.yaxis.major = other.yaxis.major  # Ticker instances holding
             self.yaxis.minor = other.yaxis.minor  # locator and formatter.
             y0, y1 = other.get_ylim()

    But I'm not sure that's the best fix.

  5. QuLogic commented on Sep 2, 2020

    @QuLogic
    Member

    Or also, can just assign directly to _sharex and let the sharex call in cla handle everything:

    diff --git a/lib/matplotlib/axes/_base.py b/lib/matplotlib/axes/_base.py
    index 23222d885..6c6b20289 100644
    --- a/lib/matplotlib/axes/_base.py
    +++ b/lib/matplotlib/axes/_base.py
    @@ -487,8 +487,8 @@ class _AxesBase(martist.Artist):
             self._anchor = 'C'
             self._stale_viewlim_x = False
             self._stale_viewlim_y = False
    -        self._sharex = None
    -        self._sharey = None
    +        self._sharex = sharex
    +        self._sharey = sharey
             self.set_label(label)
             self.set_figure(fig)
             self.set_box_aspect(box_aspect)
    @@ -507,11 +507,6 @@ class _AxesBase(martist.Artist):
             self._rasterization_zorder = None
             self.cla()
     
    -        if sharex is not None:
    -            self.sharex(sharex)
    -        if sharey is not None:
    -            self.sharey(sharey)
    -
             # funcs used to format x and y - fall back on major formatters
             self.fmt_xdata = None
             self.fmt_ydata = None
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

      Milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions