# [Matplotlib-devel] IndexLocator vs. MultipleLocator

Sat Mar 18 07:29:21 EDT 2017

```Hello folks,

A few weeks ago, I tried to use an `IndexLocator` instance for the y-axis of an
`eventplot`, and I encoutered several weird behaviors. Maybe I was just misusing
`IndexLocator` but I was not alone to think these might actually be bugs. Here
is the relevant [Gitter
discussion](https://gitter.im/matplotlib/matplotlib?at=58b7f75000c00c3d4fabe3b6).

In a nutshell, the following example
```python
import numpy as np
import matplotlib.pyplot as plt
from matplotlib.ticker import IndexLocator

# Event sequence is "10" at 0.1 s, "21" at 0.5 s, and "10" at 0.9 s.
events = [[0.1, 0.9], [0.5]]
indices = [10, 21]
event_height = 0.9  # not 1 to better visualize rows of events

fig, axs = plt.subplots(ncols=2, num='eventplot_and_IndexLocator')
for ax in axs:
ax.eventplot(events, lineoffsets=indices, linelengths=event_height)

# One wants the ticks to be all the indices: a possible thought may
# be to use an IndexLocator with base = 1 and offset = 0:
axs[0].yaxis.set_major_locator(IndexLocator(1, 0))
axs[0].set_title("base=1, offset=0")
# Issue 1?: the tick values are not integers...

# Case that is even worse: let's assume that for any good reason, we
# want the ticks to start at 12 (offset = 12), and go 5 by 5 (base = 5).
axs[1].yaxis.set_major_locator(IndexLocator(5, 12))
axs[1].set_title("base=5, offset=12")
# Issue 2?: one would expect the ticks to be 12, 17 and 21...

plt.tight_layout()  # for eyes' pleasure
plt.show()
```
produces the attached file named “issue_with_eventplot_and_IndexLocator.pdf”. My
analysis is that there are (at least) two problems:

1. `IndexLocator` uses the “data limits”, which does not return integer values
in the case of an eventplot with linelengths != 1 (yes I like when there is a
small space between successive rows of events). Besides, why rely on the data
limits and not the view ones?

2. The way the offset is handled does not take into account the case where
`offset >= base`, which seemed rather natural to me though if for example one
have indices that start to 10.

An easy workaround to 1. is to use a `MultipleLocator` instance instead of an
`IndexLocator` one. However, doig so, one looses the possibility to have a
offset. So I would have several question:

A. Should we consider the previous behaviors of `IndexLocator` as bugs? Or am I
just not using the right tool?

B. `IndexLocator` seems to be a really old piece of the code base, while
`MultipleLocator` is more recent and elaborated (from what I understand, it
internally handles some floating point errors and other tricks). From my
viewpoint, if `MultipleLocator` had a `offset` capability, then `IndexLocator`
would be more or less¹ a subset of `MultipleLocator`.

My genuine suggestion would be to add an “offset” keyword argument to
`MultipleLocator`, and possibly deprecate `IndexLocator` in the long term as it
would then become slightly redundant (and seems quite broken from my point of
view). But again, maybe I am missing the point with `IndexLocator`.

Here is a quick demo of what I have in mind (which produces the attached file
“MultipleLocatorBis_with_an_offset.pdf”):

```python
import numpy as np
import matplotlib.pyplot as plt

from matplotlib.ticker import IndexLocator, MultipleLocator, Base

class MultipleLocatorBis(MultipleLocator):
def __init__(self, base=1.0, offset=0.0):
"""Yes, I have never really understand how to use `super`..."""
self._base = Base(base)
self._offset = offset  # <= added

def set_params(self, base, offset=0.0):
self._offset = offset  # <= added
if base is not None:
self._base = base

def tick_values(self, vmin, vmax):
if vmax < vmin:
vmin, vmax = vmax, vmin
vmin = self._base.ge(vmin)
base = self._base.get_base()
n = (vmax - vmin + 0.001 * base) // base
# locs = vmin - base + np.arange(n + 3) * base  # <= vanilla case
locs = (vmin - base + (self._offset % base) + np.arange(n + 3) * base)
return self.raise_if_exceeds(locs)

def compare_locators(axs_row, base, offset):
if offset is None:  # testing the default offset if it is possible
locators = (IndexLocator(base, 0), MultipleLocator(base),
MultipleLocatorBis(base))
else:
locators = (IndexLocator(base, offset), MultipleLocator(base),
MultipleLocatorBis(base, offset=offset))

for ax, loc in zip(axs_row, locators):
ax.yaxis.set_major_locator(loc)

axs_row[0].set_ylabel("base={b}, offset={o}".format(b=base, o=offset))

if __name__ == '__main__':

plt.close('all')

# Event sequence is "10" at 0.1 s, "21" at 0.5 s, and "10" at 0.9 s.
events = [[0.1, 0.9], [0.5]]
indices = [10, 21]
event_height = 0.9  # not 1 to better visualize rows of events

fig, axs = plt.subplots(nrows=3, ncols=3, figsize=(9.6, 6.4), sharex=True)
for ax in axs.flat:
ax.eventplot(events, lineoffsets=indices, linelengths=event_height)

for axs_row, base, offset in zip(axs, (2, 5.0, 2.5), (None, 12, -6.0)):
compare_locators(axs_row, base, offset)

# Cosmeticks
axs[0, 0].set_title("IndexLocator")
axs[0, 1].set_title("MultipleLocator (no offset)")
axs[0, 2].set_title("MultipleLocatorBis")
plt.tight_layout()

plt.show()
```

to know if I am the only person who had this use case of `IndexLocator`…). I am
pretty sure too that there are some matplotlib.ticker gurus and experts around
here, who will be able to shed some light on IndexLocator!

Best,

¹: one remaining subtlety would be the “data limits” approach vs. the “view
limits” one.

-------------- next part --------------
A non-text attachment was scrubbed...
Name: issue_with_eventplot_and_IndexLocator.pdf
Type: application/pdf
Size: 10311 bytes
Desc: not available
URL: <http://mail.python.org/pipermail/matplotlib-devel/attachments/20170318/b8bba8a3/attachment-0002.pdf>
-------------- next part --------------
A non-text attachment was scrubbed...
Name: MultipleLocatorBis_with_an_offset.pdf
Type: application/pdf
Size: 16327 bytes
Desc: not available
URL: <http://mail.python.org/pipermail/matplotlib-devel/attachments/20170318/b8bba8a3/attachment-0003.pdf>
```