Right now, each Axis has a two lists of Tick objects (one major, one minor), each of which has 3 Line objects (for left tick, right tick and gridline) and 2 Text objects (for main and secondary label). Constructing Line objects is rather heavy and expensive. Refactoring this code so that each Axis had 3 LineCollection objects instead where only the points of the ticks needs to be updated, sharing all of the line style information, should be much faster.
On the following simple benchmark:
plt.subplots(8, 8)
plt.savefig('test.png')
25% of the time is spent constructing the Line objects. The suggested change above should reduce the number of Line objects from 3*N (where N is the number of ticks, usually a dozen or so) to 3.
I think i have a prototype doing that, if no one works on this before i will have a look after christmas. The whole axis code seems quite convoluted for what it does.
This looks like an interesting topic with some performance gain and not too difficult (once we've figured out the right structure). 😄
I may embark on the topic. Some first thoughts:
Current implementation:
Axis
- major: Ticker
- minor: Ticker
- majorticks: List[Tick]
- minorticks: List[Tick]
Ticker
- locator: Locator
- formatter: Formatter
Tick
- tick1line: Line
- tick2line: Line
- gridline: Line
- label1: Text
- label2: Text
This feels significantly better and should be doable with reasonable backward compatibility (full compatibility may be difficult or at least require some extra work, if users really descend to the Artists of individual ticks). I'm not sure if and how much incompatibility we want to allow here.
Structuring by major/minor and then by primary/secondary may not be the best option. One could swap the order so that
This would potentially allow different ticks on the secondary axis. Also it could make it simpler to handle interactions between minor and major ticks (e.g. leave out minor ticks at the position of major ticks).
@timhoffm I had started giving a try to the idea (not pursuing it actively now), per https://github.com/anntzer/matplotlib/tree/_wip/newaxis (taking an approach of "delete everything and reimplement stuff on top of it"). Feel free to scavenge stuff from that.
A few random notes:
LineCollections don't support markers, so if you use them for ticks each tick has to become a line itself, rather than being represented by a marker. Conversely, using a single line object to represent all ticks (what I've done right now) makes controlling properties of individual ticks close to impossible.
I think that it'll be very difficult to maintain backcompat on a project like that; however, a possibility (noted in this week's call) would be to have a new, separate Axes class that uses these new Axises (while also maintaining the user-facing plotting methods); one could then choose to use the new Axes class by (ab)using the projection system.
This was discussed a bit during the call this week.
In working on #10841, I found it pretty strange that the formatter only operated on one tick at a time, and the formatter ended up looping through all the ticks on the first tick and then caching the labels for all the ticks. So, a reorganization like this makes a lot of sense to me - I think there are more use cases where you want to treat the ticks as a list rather than being able to do something (?) to individual ticks.
One case (not that I use it, but doesn't sounds too crazy) is https://matplotlib.org/examples/api/skewt.html, where you can see that for some of the slanted gridlines, the associated tick is not visible.
@anntzer Thanks for the example. It helps to have an idea what could all be needed. This needed a special implementation for XTick. The same solution for a future TickCollection (or whatever it's called) will probably need a special subclass of that.
@ all: Generally, can we agree that in the default implementation all ticks of a kind (major/minor) will have the same properties? It's still always possible to tell the locator to leave out certain positions and draw something custom there yourself.
I just realized that that specific example could in fact be made to work by clipping the x-ticks to a bounding box that bounds the horizontal direction but not the vertical one -- perhaps a better idea than manipulating tick visibility...).
I suspect we will need to aggressively shim the back compatibility on anything that is public API (and maybe even some private stuff). Users are extremely clever and find all the corner cases.
separating the primary/secondary ticks is probably a good idea as that makes in easy(ier) to put different locators and tickers on each of them.
It is probably worth going through the axis_grid implementation of ticks and axis objects carefully to understand what it does differently, what of it we want to pull up to mainline, and what lessons we should learn.
... actually playing with the clipbox won't work in the example above as labels will not be cropped out properly (they may only be half-clipped).
@tacaswell If we move stuff to a separate "projection" (as proposed above) the new implementation would be strictly opt-in. It doesn't mean we should gratuitiously break things but given that the current proposals don't even really include the notion of "individual" ticks (everything goes into a Collection object), any shimming is going to be tricky too...
I responded not having read the rest of the discussion (because I did not reload my browser), sorry.
Putting in it a separate projection is a good way to make progress and side-step back compatibility issues, but I worry that we will end up with a third full independent implementation of ticks and friends in the code base.
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!
Another datapoint from #29435 that even with plotted data, ticks operations are taking up ~half the draw time on basic 3D plots (profiled using py-spy):
I just did some profiling today, because I was annoyed by the duration of the test suite. For
fig, ax = plt.subplots()
ax.plot([1, 3, 2])
Basically all time goes into Axes.__init__, more specifically Axes.__clear. We more or less know that already from earlier, but to state this explicitly: Axes initialization is the limiting factor.
Activity
Tillsten commented on Dec 11, 2015
I think i have a prototype doing that, if no one works on this before i will have a look after christmas. The whole axis code seems quite convoluted for what it does.
timhoffm commented on Mar 28, 2018
This looks like an interesting topic with some performance gain and not too difficult (once we've figured out the right structure). 😄
I may embark on the topic. Some first thoughts:
Current implementation:
Possible alternative implementation (first try):
This feels significantly better and should be doable with reasonable backward compatibility (full compatibility may be difficult or at least require some extra work, if users really descend to the Artists of individual ticks). I'm not sure if and how much incompatibility we want to allow here.
Structuring by major/minor and then by primary/secondary may not be the best option. One could swap the order so that
This would potentially allow different ticks on the secondary axis. Also it could make it simpler to handle interactions between minor and major ticks (e.g. leave out minor ticks at the position of major ticks).
anntzer commented on Mar 28, 2018
@timhoffm I had started giving a try to the idea (not pursuing it actively now), per https://github.com/anntzer/matplotlib/tree/_wip/newaxis (taking an approach of "delete everything and reimplement stuff on top of it"). Feel free to scavenge stuff from that.
A few random notes:
jklymak commented on Mar 28, 2018
This was discussed a bit during the call this week.
In working on #10841, I found it pretty strange that the formatter only operated on one tick at a time, and the formatter ended up looping through all the ticks on the first tick and then caching the labels for all the ticks. So, a reorganization like this makes a lot of sense to me - I think there are more use cases where you want to treat the ticks as a list rather than being able to do something (?) to individual ticks.
anntzer commented on Mar 28, 2018
#5804 is possibly relevant re: formatter work too.
efiring commented on Mar 28, 2018
I suspect the need to manipulate individual ticks is extremely rare. It's possible that the tick handling pre-dated the introduction of collections.
anntzer commented on Mar 28, 2018
One case (not that I use it, but doesn't sounds too crazy) is https://matplotlib.org/examples/api/skewt.html, where you can see that for some of the slanted gridlines, the associated tick is not visible.
timhoffm commented on Mar 28, 2018
@anntzer Thanks for the example. It helps to have an idea what could all be needed. This needed a special implementation for XTick. The same solution for a future TickCollection (or whatever it's called) will probably need a special subclass of that.
@ all: Generally, can we agree that in the default implementation all ticks of a kind (major/minor) will have the same properties? It's still always possible to tell the locator to leave out certain positions and draw something custom there yourself.
anntzer commented on Mar 28, 2018
I just realized that that specific example could in fact be made to work by clipping the x-ticks to a bounding box that bounds the horizontal direction but not the vertical one -- perhaps a better idea than manipulating tick visibility...).
tacaswell commented on Mar 28, 2018
I suspect we will need to aggressively shim the back compatibility on anything that is public API (and maybe even some private stuff). Users are extremely clever and find all the corner cases.
separating the primary/secondary ticks is probably a good idea as that makes in easy(ier) to put different locators and tickers on each of them.
It is probably worth going through the axis_grid implementation of ticks and axis objects carefully to understand what it does differently, what of it we want to pull up to mainline, and what lessons we should learn.
anntzer commented on Mar 28, 2018
... actually playing with the clipbox won't work in the example above as labels will not be cropped out properly (they may only be half-clipped).
@tacaswell If we move stuff to a separate "projection" (as proposed above) the new implementation would be strictly opt-in. It doesn't mean we should gratuitiously break things but given that the current proposals don't even really include the notion of "individual" ticks (everything goes into a Collection object), any shimming is going to be tricky too...
tacaswell commented on Mar 28, 2018
I responded not having read the rest of the discussion (because I did not reload my browser), sorry.
Putting in it a separate projection is a good way to make progress and side-step back compatibility issues, but I worry that we will end up with a third full independent implementation of ticks and friends in the code base.
22 remaining items
github-actions commented on Jan 29, 2024
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!
timhoffm commented on Jan 29, 2024
#5665 (comment) Still holds
scottshambaugh commented on Jan 8, 2025
Another datapoint from #29435 that even with plotted data, ticks operations are taking up ~half the draw time on basic 3D plots (profiled using py-spy):
tacaswell commented on Jan 9, 2025
This is project is included in https://github.com/matplotlib/2024-NASA-ROSES-F7
timhoffm commented on Sep 21, 2025
I just did some profiling today, because I was annoyed by the duration of the test suite. For
Basically all time goes into Axes.__init__, more specifically Axes.__clear. We more or less know that already from earlier, but to state this explicitly: Axes initialization is the limiting factor.
Within Axes.__clear(), 30% goes into this line
matplotlib/lib/matplotlib/axes/_base.py
Line 1355 in f8400db
which in the end calls Axis.set_tick_params; and I suspect that this triggers tick creation.
QuLogic commented on Sep 22, 2025
These loops are likely triggers:
matplotlib/lib/matplotlib/axis.py
Lines 977 to 984 in f8400db
but also probably this one:
matplotlib/lib/matplotlib/axis.py
Lines 1122 to 1123 in f8400db
I'm uncertain how to check if the descriptor is realized before accessing it. Actually, it looks like #16067 might lazify these two cases?