Skip to content

plot_date() after axhline() doesn't rescale axes #7742

Closed

Description

@dstansby

If I plot an axhline first, and then two points with plot_date, the x-axis is not rescaled to the two new data points:

import matplotlib.pyplot as plt
from datetime import datetime

fig, axs = plt.subplots(2, 1)

axs[0].axhline(1.5)
axs[0].plot([datetime(2016, 1, 1, 0, 0, 0), datetime(2016, 1, 2, 0, 0, 0)], [1, 2])

axs[1].plot([datetime(2016, 1, 1, 0, 0, 0), datetime(2016, 1, 2, 0, 0, 0)], [1, 2])
axs[1].axhline(1.5)

plt.show()

figure_1-1

I would expect the two plots to be the same, and the plotting order not to matter.

Present on current master (2.0.0rc2.post2912+g015d8d5) and on 1.5

Activity

  1. Yasaman-Mah commented on Feb 28, 2017

    @Yasaman-Mah

    I think I have a fix for this, after making the fix, the script above results in:
    screenshot from 2017-02-28 17 12 32
    But I'm not sure how to write tests for it. Are they needed?

  2. phobson commented on Feb 28, 2017

    @phobson
    Member

    @Yasaman-Mah
    In this is case, I would write a test confirming that both axes have the expected limits:

    import matplotlib.pyplot as plt
    from datetime import datetime
    
    dates = [datetime(2016, 1, 1, 0, 0, 0), datetime(2016, 1, 2, 0, 0, 0)]
    values = [1, 2]
    expected_limits = (xmin, xmax) # fill this in
    
    fig, (ax1, ax2) = plt.subplots(2, 1)
    ax1.axhline(1.5)
    ax1.plot(dates, values)
    
    ax2.plot(dates, values)
    ax2.axhline(1.5)
    
    assert ax1.get_xlim() == expected_limits
    assert ax1.get_xlim() == ax2.get_xlim()

    I think it'd be fine to place this in matplotlib/lib/tests/test_axes.py

  3. added this to the milestone on Mar 1, 2017
  4. tacaswell commented on Mar 1, 2017

    @tacaswell
    Member

    @Yasaman-Mah Looking forward to seeing your PR!

  5. added a commit that references this issue on Mar 4, 2017
    7eceed5
  6. Shang-Jia commented on Mar 6, 2017

    @Shang-Jia

    @Yasaman-Mah
    Plotting again after axhline causes the x-axis to go only up to 2016-01-10 instead of expected 2016-01-30

    axs[1].plot([datetime(2016, 1, 1, 0, 0, 0), datetime(2016, 1, 30, 0, 0, 0)], [1, 2])
    axs[1].axhline(1.5)
    axs[1].plot([datetime(2016, 1, 1, 0, 0, 0), datetime(2016, 1, 10, 0, 0, 0)], [0, 5])
    
  7. added a commit that references this issue on Mar 7, 2017
    7cf097d
  8. sughandj commented on Mar 7, 2017

    @sughandj

    I also encountered what @Shang-Jia said and I have submitted a PR #8210 that fixes all issues discussed.

  9. Yasaman-Mah commented on Mar 8, 2017

    @Yasaman-Mah

    @Shang-Jia
    I understand that you are in UTSC, I just wanted to remind you that we are all doing this as a part of course work, and it would be against course (and possibly university's) policies to look at another student's code before submission deadline. Your comment above indicates that this is what you did.

    To matplotlib developers: Thank you for all the feedback. Unfortunately, to me it seems that some of my classmates are trying to take credit for most of my fix by making minor improvements to it. In fact, I do have a better solution in mind, which I will start working on once the situation above is resolved.

  10. added a commit that references this issue on Mar 14, 2017
    f6151e7
  11. added a commit that references this issue on Mar 29, 2017
    9cb0b6c
  12. modified the milestones: , on May 3, 2017
  13. 9 remaining items

  14. added this to the v3.2.0 milestone on Mar 18, 2019
  15. efiring commented on Jul 14, 2020

    @efiring
    Member
    In [3]: matplotlib.__version__
    Out[3]: '3.3.0rc1.post273+gb803891a4b'
    

    It looks like we have code in master that is designed to handle this (autoscaling with the mixed transform should be leaving x limits scaling alone), but the original example now yields a traceback:

    TypeError                                 Traceback (most recent call last)
    <ipython-input-1-01cdab9ed12b> in <module>
          5
          6 axs[0].axhline(1.5)
    ----> 7 axs[0].plot([datetime(2016, 1, 1, 0, 0, 0), datetime(2016, 1, 2, 0, 0, 0)], [1, 2])
          8
          9 axs[1].plot([datetime(2016, 1, 1, 0, 0, 0), datetime(2016, 1, 2, 0, 0, 0)], [1, 2])
    
    ~/work/programs/py/mpl/matplotlib/lib/matplotlib/axes/_axes.py in plot(self, scalex, scaley, data, *args, **kwargs)
       1743         lines = [*self._get_lines(*args, data=data, **kwargs)]
       1744         for line in lines:
    -> 1745             self.add_line(line)
       1746         self._request_autoscale_view(scalex=scalex, scaley=scaley)
       1747         return lines
    
    ~/work/programs/py/mpl/matplotlib/lib/matplotlib/axes/_base.py in add_line(self, line)
       1975             line.set_clip_path(self.patch)
       1976
    -> 1977         self._update_line_limits(line)
       1978         if not line.get_label():
       1979             line.set_label('_line%d' % len(self.lines))
    
    ~/work/programs/py/mpl/matplotlib/lib/matplotlib/axes/_base.py in _update_line_limits(self, line)
       1997         Figures out the data limit of the given line, updating self.dataLim.
       1998         """
    -> 1999         path = line.get_path()
       2000         if path.vertices.size == 0:
       2001             return
    
    ~/work/programs/py/mpl/matplotlib/lib/matplotlib/lines.py in get_path(self)
       1009         """
       1010         if self._invalidy or self._invalidx:
    -> 1011             self.recache()
       1012         return self._path
       1013
    
    ~/work/programs/py/mpl/matplotlib/lib/matplotlib/lines.py in recache(self, always)
        651         if always or self._invalidx:
        652             xconv = self.convert_xunits(self._xorig)
    --> 653             x = _to_unmasked_float_array(xconv).ravel()
        654         else:
        655             x = self._x
    
    ~/work/programs/py/mpl/matplotlib/lib/matplotlib/cbook/__init__.py in _to_unmasked_float_array(x)
       1296         return np.ma.asarray(x, float).filled(np.nan)
       1297     else:
    -> 1298         return np.asarray(x, float)
       1299
       1300
    ~/miniconda3/envs/mpl1/lib/python3.7/site-packages/numpy/core/_asarray.py in asarray(a, dtype, order)
         83
         84     """
    ---> 85     return array(a, dtype, copy=False, order=order)
         86
         87
    
    TypeError: float() argument must be a string or a number, not 'datetime.datetime'
    
  16. jklymak commented on Jul 14, 2020

    @jklymak
    Member

    @efiring, that's because on master the date unit converter is not being registered. If you merge #17869 that will be fixed 😉

  17. jklymak commented on Jul 14, 2020

    @jklymak
    Member

    (ahem, or if you register the date converter manually)

  18. efiring commented on Jul 15, 2020

    @efiring
    Member

    Merged and verified: the bug reported here still exists.

  19. modified the milestones: needs sorting, v3.4.0 on Jul 15, 2020
  20. modified the milestones: v3.4.0, v3.5.0 on Jan 27, 2021
  21. jklymak commented on May 6, 2021

    @jklymak
    Member

    This isn't just dates, this is all units. Basically if the units change on an axes (as they do here from no units to date units) axis.update_info is called, before the new plot artists are added. This calls axis.set_default_interval, and the dataLim is set to the default for the converter, which for dates is 2000-2010.

    This doesn't happen if the new plot has no units. the xlims correctly remain -np.Inf, +np.Inf and the relimit works as expected.

    I don't know why axis.set_default_intervals is called. And this is the only place in the code where it is called. So I think we should just not call it here either.

  22. dstansby commented on Jun 1, 2021

    @dstansby
    MemberAuthor

    Thanks to everyone who worked on fixing this! I remember opening this in the early days of my contributions to Matplotlib, so great to finally see it fixed!

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