Skip to content

Navigation Menu

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Appearance settings

Create InsetIndicator artist #27996

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 1 commit into from
Sep 18, 2024
Merged

Conversation

rcomer
Copy link
Member

@rcomer rcomer commented Mar 30, 2024

PR summary

#19768 (comment) suggested that we should have some kind of container for the output of indicate_inset[_zoom]. #23424 (comment) suggested that it should be drawn as a single path to avoid double-alpha where the box and connectors overlap. This PR is my attempt to implement that.

The new artist has convenience methods to update colour/linestyle/etc for the whole thing, but the user can also get hold of individual patches and update styles on those. The current doctrings of indicate_inset[_zoom] specifically mention updating the visibility of the connectors, but a user might also reasonably want a different colour or linestyle for the connectors than for the box.

The logic from indicate_inset_zoom that decided where the box should be is now method InsetIndicator._bounds_from_inset_ax so we can reuse it. Whenever the connectors attribute is accessed (including at draw) the box bounds and position of connectors (if any) get updated using the current inset axes x- and y-limits. This therefore closes #19768 and we can do things like

import matplotlib.pyplot as plt
import matplotlib.animation as animation
import numpy as np

x = np.arange(100)
y = x + np.sin(x) * 5

fig, ax = plt.subplots()
ax.plot(x, y)

ax_ins = ax.inset_axes([0.6, 0.1, 0.3, 0.3])
ax_ins.plot(x, y)
ax_ins.set_xlim([0, 10])
ax_ins.set_ylim([0, 10])
ind = ax.indicate_inset_zoom(ax_ins)
for conn in ind.connectors:
    # In this example all the connectors are invisible by default - I'm unclear why that is.
    conn.set_visible(True)

def animate(i):
    ax_ins.set_xlim([i, i+10])
    ax_ins.set_ylim([i-5, i+15])
    return ax_ins,

ani = animation.FuncAnimation(fig, animate, interval=100, frames=90)

plt.show()

inset

Styles for the connectors are now inherited from the parent artist when the connectors are created. This therefore closes #23424. Within the draw method, we check which connectors have the same style properties as the rectangle (currently four specific properties - maybe there is a better way). Those that do are drawn with the rectangle as a compound path.

This also inadvertently fixes #23425 since I haven't implemented get_window_extent or get_tight_bbox, so constrained layout will get a null tight bbox. I would like advice on whether I should implement one or both of these and what it should include. For the purposes of layout engines is does not seem useful to include the connectors, but maybe there are other uses for these methods?

PR checklist

@rcomer rcomer added this to the v3.10.0 milestone Mar 30, 2024
@rcomer
Copy link
Member Author

rcomer commented Mar 30, 2024

Added a __getitem__ method in an effort to provide a deprecation pathway. Although indicate_inset[_zoom] are still marked as experimental, I suspect the pattern box, connectors = ax.indicate_inset... is pretty common.

@rcomer rcomer changed the title POC: create IndicateInset patch POC: create InsetIndicator patch Apr 22, 2024
@rcomer rcomer force-pushed the indicate-inset-patch branch 2 times, most recently from fce57eb to 3d725ea Compare May 12, 2024 19:18
@rcomer
Copy link
Member Author

rcomer commented Jul 27, 2024

Perhaps instead of inheriting from Rectangle, this should be a container artist with both the rectangle and the connectors as children. Then users who want more control could get hold of the child artists and update colours, etc. on them individually 🤔

@story645
Copy link
Member

story645 commented Jul 28, 2024

Then users who want more control could get hold of the child artists and update colours, etc. on them individually

That sounds like a fairly common use case to me so I think that'd be awesome. Is there an advantage to the rectangle approach/downside to making it a container?

@rcomer
Copy link
Member Author

rcomer commented Jul 28, 2024

Is there an advantage to the rectangle approach/downside to making it a container?

Not that I can think of. I think I just had in mind from #23424 (comment) that the new object should be a Patch and inheriting from Rectangle seemed like the obvious way to do that. Having said that, I'm not sure what defines a Patch and maybe that fact that the connectors are already children that you can configure separately from the whole thing means it doesn't really fit the definition.

@story645
Copy link
Member

story645 commented Jul 28, 2024

I'm not sure what defines a Patch 

Technically artist w/ face color + edgecolor & for the most part I've always thought of it as roughly the MPL abstraction of an area mark b/c that's basically the space of visual encodings we allow/can apply to most of our patch artists:
Six-visual-variables-proposed-by-Bertin-and-their-capacity-in-representing-graphical.png

Where size and shape are controlled by the drawing of the shape, while for example the choice of marker determines shape and marker size controls the size.

So for what it's worth, I agree w/ your intuition that a container Artist is probably a better abstraction for what's actually going on here.

@rcomer rcomer force-pushed the indicate-inset-patch branch from 3d725ea to 745c737 Compare August 11, 2024 18:59
@github-actions github-actions bot added the Documentation: API files in lib/ and doc/api label Aug 11, 2024
@rcomer rcomer changed the title POC: create InsetIndicator patch POC: create InsetIndicator artist Aug 11, 2024
@rcomer rcomer force-pushed the indicate-inset-patch branch from 745c737 to b8d7469 Compare August 11, 2024 19:15
@rcomer
Copy link
Member Author

rcomer commented Aug 11, 2024

OK, the InsetIndicator is now a container artist instead of a patch. I couldn't see which module it would obviously fit in, so made a new one.

One side-effect of this is that we no longer get the axes limits updated around the artist, which is done in add_patch here:

self._update_patch_limits(p)

So my test image changed from
zoom_inset_connector_styles

to
zoom_inset_connector_styles

I guess it would be pretty easy to re-instate that but I'm not sure if it matters. Most of the time there will be a bunch of other artists that this one is indicating.

Still to do:

  • Add changenotes
  • Decide what, if anything, to do about get_window_extent/get_tightbbox

@rcomer rcomer force-pushed the indicate-inset-patch branch from b8d7469 to cbe1aa1 Compare August 12, 2024 10:56
@github-actions github-actions bot removed the Documentation: API files in lib/ and doc/api label Aug 12, 2024
@rcomer
Copy link
Member Author

rcomer commented Aug 12, 2024

Added changenotes and directives, though I'm not sure if the behaviour change documentation for indicate_inset[_zoom] is really needed since these methods were marked as experimental...?

@rcomer rcomer marked this pull request as ready for review August 12, 2024 10:57
@rcomer rcomer force-pushed the indicate-inset-patch branch from cbe1aa1 to 9deb35b Compare August 12, 2024 12:07
@pawjast

This comment was marked as resolved.

@rcomer

This comment was marked as resolved.

@story645

This comment was marked as resolved.

@pawjast

This comment was marked as resolved.

@rcomer

This comment was marked as resolved.

@story645

This comment was marked as resolved.

@pawjast

This comment was marked as resolved.

@rcomer
Copy link
Member Author

rcomer commented Aug 14, 2024

I hid the alpha discussion because that was addressed by #28710 and is not needed for the reviewers of this PR.

@story645 story645 self-assigned this Aug 15, 2024
@tacaswell
Copy link
Member

Decide what, if anything, to do about get_window_extent/get_tightbbox

It is also used by fig.savefig(..., bbox_inches='tight') so if we do not implement it then there is a high risk that people using the inline backend may be able to drive them selves to a state where we clip something we should not have, however as both the host and inset axes do implement those methods, I'm struggling to come up with an arangement of the two axes that would result in the connector getting clipped...

When you said "container" I was worried you were following what we do with e.g. error bar where we return a tuple subclass, but I think what you implemented is the right way to go.

@story645
Copy link
Member

why did the lower left and upper right segments disappear?

@jklymak
Copy link
Member

jklymak commented Aug 16, 2024

I don’t know about dragging but if you pan/zoom it can end up outside the axes.

yeah, that is all a bit tricky. The rectangle would ideally clip to the parent axes; probably easy. The connectors would ideally clip the parent axes as well, but only on the side that links to the rectangle. I'm not aware that we have a way to do that and I'm not sure it's worth the hassle to make it. Either you are doing this for data exploration, in which case a little mess is OK, or you are doing it for a final plot, in which case you can clean up the positions then.

@rcomer
Copy link
Member Author

rcomer commented Aug 16, 2024

why did the lower left and upper right segments disappear?

You mean the connectors? Only 2 of those are visible by default.
https://matplotlib.org/stable/gallery/subplots_axes_and_figures/zoom_inset_axes.html

@story645
Copy link
Member

why did the lower left and upper right segments disappear?

You mean the connectors? Only 2 of those are visible by default.
https://matplotlib.org/stable/gallery/subplots_axes_and_figures/zoom_inset_axes.html

Thanks! Sorry for not checking the docs 🤦‍♀️
Am clearly confused by that API choice but is out of scope here.

@rcomer rcomer force-pushed the indicate-inset-patch branch from 9deb35b to 9134a5c Compare August 16, 2024 19:31
@story645 story645 removed their assignment Aug 16, 2024
lib/matplotlib/axes/_axes.py Outdated Show resolved Hide resolved
lib/matplotlib/axes/_axes.py Outdated Show resolved Hide resolved
lib/matplotlib/axes/_axes.py Outdated Show resolved Hide resolved
lib/matplotlib/inset.py Show resolved Hide resolved
lib/matplotlib/inset.py Outdated Show resolved Hide resolved
for conn in self.connectors or []:
if conn.get_visible():
drawn = False
for s in _shared_properties:
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Isn't it also the case that if the non-shared properties differ, then you can't merge the paths? For example, zorder isn't listed as shared, but might make a difference.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah, I wasn't super-happy about doing this with a hard-coded list, but so far haven't thought of a better way. Very happy to take suggestions/pointers.

... oh I just realised I could use ArtistInspector.get_setters to make a complete list - maybe that's part of the solution, though we'd have to actively skip comparing some properties like transform 🤔

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I also just realised that I currently have the zorder going to Rectangle in the __init__ when it should be getting applied to InsetIndicator. So I need a better way to keep the properties straight at that end too.

lib/matplotlib/tests/test_inset.py Outdated Show resolved Hide resolved
lib/matplotlib/tests/test_inset.py Outdated Show resolved Hide resolved
lib/matplotlib/tests/test_inset.py Outdated Show resolved Hide resolved
lib/matplotlib/tests/test_inset.py Outdated Show resolved Hide resolved
@rcomer rcomer force-pushed the indicate-inset-patch branch 4 times, most recently from 6e106e9 to 143a228 Compare August 18, 2024 13:16
doc/users/next_whats_new/inset_indicator.rst Outdated Show resolved Hide resolved
Comment on lines +476 to +485
inset_indicator.rectangle : `.Rectangle`
The indicator frame.

inset_indicator.connectors : 4-tuple of `.patches.ConnectionPatch`
The four connector lines connecting to (lower_left, upper_left,
lower_right upper_right) corners of *inset_ax*. Two lines are
set with visibility to *False*, but the user can set the
visibility to True if the automatic choice is not deemed correct.

Copy link
Member

@story645 story645 Aug 22, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

would it make sense to make this an interpolated doc, even if it's only used in like two places? That way it can be defined in the module? Only thinking about this b/c of how it's being done for colorizer:

mpl._docstring.interpd.update(
colorizer_doc="""\
colorizer : `~matplotlib.colorizer.Colorizer` or None, default: None
The Colorizer object used to map color to data. If None, a Colorizer
object is created base on *norm* and *cmap*.""",
)

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Possibly can do something even more direct but I do not follow this example at all https://matplotlib.org/devdocs/devel/document.html#keyword-arguments

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do we have any general guidance about when it makes sense to do this rather than copy/paste? As you say it's only in two places and I am keenly aware that I have a lot more copy/paste going on in the artist's set_* docstrings.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Trying to figure that out w/ #28746 and probably a @timhoffm question?

lib/matplotlib/inset.py Outdated Show resolved Hide resolved
Comment on lines 105 to 109
if conn.get_visible():
# Make one visible connector a different style
conn.set_linestyle('dashed')
conn.set_color('blue')
break
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

shouldn't you know which connector this is b/c this is deterministic? basically why loop?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I was just too lazy to figure out which index it would be...

lib/matplotlib/inset.py Outdated Show resolved Hide resolved
@QuLogic
Copy link
Member

QuLogic commented Sep 12, 2024

Is this no longer PoC (as in title)?

@rcomer rcomer changed the title POC: create InsetIndicator artist Create InsetIndicator artist Sep 12, 2024
@rcomer rcomer force-pushed the indicate-inset-patch branch from d2cd137 to adb8a95 Compare September 12, 2024 20:30
@rcomer rcomer force-pushed the indicate-inset-patch branch from 4d944bb to b833055 Compare September 17, 2024 18:20
@rcomer
Copy link
Member Author

rcomer commented Sep 17, 2024

I just noticed I forgot to update the deprecation warning when the patch became an artist. Now fixed.

@tacaswell tacaswell merged commit ea78e25 into matplotlib:main Sep 18, 2024
43 checks passed
@rcomer rcomer deleted the indicate-inset-patch branch September 18, 2024 19:43
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
6 participants
Morty Proxy This is a proxified and sanitized view of the page, visit original site.