#27866 closed enhancement (fixed)
Introduce graphics insets
Reported by:  egourgoulhon  Owned by:  

Priority:  major  Milestone:  sage9.0 
Component:  graphics  Keywords:  graphics inset 
Cc:  kcrisman, chapoton, dkrenn, jdemeyer, embray  Merged in:  
Authors:  Eric Gourgoulhon  Reviewers:  Markus Wageringel 
Report Upstream:  N/A  Work issues:  
Branch:  ed586d3 (Commits, GitHub, GitLab)  Commit:  
Dependencies:  #27865  Stopgaps: 
Description (last modified by )
This ticket introduces graphics insets, i.e. it allows to have various Graphics
objects at arbitrary locations and sizes on a single figure. Previously, only graphics arrays, i.e. Graphics
objects arranged in a regular array, were possible.
This is made possible thanks to the generic class MultiGraphics
introduced in #27865.
The user interface is constituted of three new functions:
 the method
Graphics.inset()
, which adds an inset to a given graphics; see this preview example  the method
MultiGraphics.inset()
, which adds an inset to a multigraphics (e.g. aGraphicsArray
); see this preview example  the function
multi_graphics()
, which creates a figure from a list ofGraphics
objects at userspecified locations; see this preview example
Attachments (4)
Change History (31)
comment:1 Changed 4 years ago by
Authors:  → Eric Gourgoulhon 

Branch:  → public/graphics/graphics_insets 
Commit:  → e1776462164bf01ed857eccb06513c276986e633 
Dependencies:  → #27865 
comment:2 Changed 4 years ago by
Cc:  kcrisman chapoton dkrenn jdemeyer added 

Status:  new → needs_review 
comment:3 Changed 4 years ago by
Cc:  embray added 

comment:4 Changed 4 years ago by
Description:  modified (diff) 

comment:5 Changed 4 years ago by
Milestone:  sage8.8 → sage8.9 

Moving tickets from the Sage 8.8 milestone that have been actively worked on in the last six months to the next release milestone (optimistically).
comment:6 Changed 3 years ago by
Commit:  e1776462164bf01ed857eccb06513c276986e633 → f504d7a7b5e155a419cc590445a54342ac9b439b 

comment:7 Changed 3 years ago by
The two pyflakes errors reported by the patchbot are spurious ones: these imports are really necessary.
comment:8 Changed 3 years ago by
Commit:  f504d7a7b5e155a419cc590445a54342ac9b439b → 3099f48c2560a6d2131def1b830d032e1941419b 

Branch pushed to git repo; I updated commit sha1. New commits:
3099f48  27866: fix typos

comment:9 followup: 10 Changed 3 years ago by
I was wondering what happens if the graphics inset requires a particular aspect ratio, and apparently the aspect ratio is preserved, which is good. However, in the cases I tested I obtained an inset that is much larger than expected. Could you explain the output for g2
and g3
?
sage: g1 = plot(x^3, x, 1, 1, aspect_ratio=1) sage: g2 = g1.inset(circle((0,1000), 1, frame=True, aspect_ratio=1), (.5,.5,.5,.5)) sage: g3 = g1.inset(circle((0,1000), 1, frame=True, aspect_ratio='automatic'), (.5,.5,.5,.5))
Edit: To be more precise, the inset has the correct size relative to the final image, but not in relation to the main graphics.
Changed 3 years ago by
comment:10 followup: 11 Changed 3 years ago by
Replying to ghmwageringel:
Edit: To be more precise, the inset has the correct size relative to the final image, but not in relation to the main graphics.
Actually, this is a feature, not a bug. From the documentation returned by g1.inset?
:
Signature: g1.inset(graphics, pos=None, fontsize=None) Docstring: Add a graphics object as an inset. INPUT: * "graphics"  the graphics object (instance of "Graphics") to be added as an inset to the current graphics * "pos"  (default: "None") 4tuple "(left, bottom, width, height)" specifying the location and size of the inset on the figure, all quantities being in fractions of the figure width and height; if "None", the value "(0.70, 0.68, 0.2, 0.2)" is used * "fontsize"  (default: "None") integer, font size (in points) for the inset; if "None", the value of 6 points is used, unless "fontsize" has been explicitely set in the construction of "graphics" (in this case, it is not overwritten here) OUTPUT: * instance of "MultiGraphics"
As you can see, the argument pos
specifies the position and size of the inset relative to the figure size. So, with pos=(0.5, 0.5, 0.5, 0.5)
, as in your example g2
, one obtains an inset that has half the figure width and half the figure height. Do you think that the documentation should be improved about this?
comment:11 followup: 13 Changed 3 years ago by
Should it be half the width and height of the figure g1
or the figure g2
? It seems to be half the size of g2
, but I would have expected it to be half the size of g1
as well. In other words, g1
appears too small in g2
– I would have expected g1
to fill out the figure g2
.
As far as I understand, the position is computed in terms of the figure size, not the plotting area, so I should not expect the inset to cover the top right quadrant of the plot precisely, but in g2
the positioning of the inset seems a bit too far off. In particular, I would like to understand whether the tick labels count towards the width and height given in pos
or whether these parameters just determine the size of the frame of the inset. From the looks of it, horizontal labels do not count towards the height, while vertical ones seem to add to the width?
comment:12 Changed 3 years ago by
Commit:  3099f48c2560a6d2131def1b830d032e1941419b → 71dc5c8501a8129816d0ee5ec4768bf3f60b8fc6 

Changed 3 years ago by
Attachment:  g2_new.png added 

comment:13 Changed 3 years ago by
Replying to ghmwageringel:
Should it be half the width and height of the figure
g1
or the figureg2
? It seems to be half the size ofg2
, but I would have expected it to be half the size ofg1
as well. In other words,g1
appears too small ing2
– I would have expectedg1
to fill out the figureg2
.
I've changed the rescaling of the base plot when generating the MultiGraphics
object, the previous values were indeed shrinking g1
too much. The new output is in the attached figure g2_new.png
.
As far as I understand, the position is computed in terms of the figure size, not the plotting area, so I should not expect the inset to cover the top right quadrant of the plot precisely, but in
g2
the positioning of the inset seems a bit too far off. In particular, I would like to understand whether the tick labels count towards the width and height given inpos
or whether these parameters just determine the size of the frame of the inset. From the looks of it, horizontal labels do not count towards the height, while vertical ones seem to add to the width?
The position/size given in the argument pos
regard the position/size of the frame box of the inset with respect to the final plotting area (canvas), not with respect to g1
. This is a feature we should keep IMHO, since it matches with Matplotlib conventions, cf. the argument rect
in the method Figure.add_axes
at https://matplotlib.org/api/_as_gen/matplotlib.figure.Figure.html.
comment:14 Changed 3 years ago by
Commit:  71dc5c8501a8129816d0ee5ec4768bf3f60b8fc6 → d34b69fafa50c3b879b189fd413a4867321f919c 

Branch pushed to git repo; I updated commit sha1. New commits:
d34b69f  Restore file permissions on build/pkgs/flint/spkgcheck

comment:15 Changed 3 years ago by
For some reason, yesterday's commit (comment:12) contained a spurious change of permissions on the file build/pkgs/flint/spkgcheck
. The above commit (comment:14) corrects this.
Changed 3 years ago by
comment:16 followup: 19 Changed 3 years ago by
Reviewers:  → Markus Wageringel 

Status:  needs_review → needs_work 
There still seems to be an inconsistency between vertical and horizontal positioning. For example, in g4
the blue squares touch each other in one direction, but not the other. I am not sure what the preferred behaviour is in this case, but it should be the same in both directions.
g1 = plot(x^3, x, 1, 1, aspect_ratio=1, color='red', frame=True) g1.axes_color('red') c = circle((0,1000), 1, frame=True, aspect_ratio=1, transparent=True) c.axes_color('blue') g4 = g1.inset(c, (0,0,.5,.5)).inset(c, (0,.5,.5,.5)).inset(c, (.5,0,.5,.5)).inset(c, (.5,.5,.5,.5))
Moreover, some of the type checks testing for lists and tuples seem a bit restrictive. Especially this one is not necessary, as the implementation will work for any iterable and iterator, so the type check should just be removed:
if not isinstance(graphics_list, (list, tuple)): raise TypeError("graphics_list must be a list or a tuple, " "not {}".format(graphics_list))
I am not sure why the following change is needed. Would it be better to test for the converse, i.e. if not isinstance...
of the types you want to exclude? Or are Graphics
and GraphicsArray
the only types that support tight_layout
? Also, there is a similar change in multigraphics.py that only tests for isinstance(self, GraphicsArray)
. If you think it is preferable to keep this as is, that would also be fine be me.
 # tight_layout adjusts the *subplot* parameters so ticks aren't  # cut off, etc.  figure.tight_layout() + if isinstance(graphics, (sage.plot.graphics.Graphics, + sage.plot.multigraphics.GraphicsArray)): + # for Graphics and GraphicsArray, tight_layout adjusts the + # *subplot* parameters so ticks aren't cut off, etc. + figure.tight_layout()
Other than that, everything looks good to me.
comment:17 Changed 3 years ago by
Commit:  d34b69fafa50c3b879b189fd413a4867321f919c → 2600bd730cca8d0f61bbe063a0b6562149e0100a 

Branch pushed to git repo; I updated commit sha1. New commits:
2600bd7  Fix merge conflict of graphics insets branch with Sage 9.0.beta2

comment:18 Changed 3 years ago by
Commit:  2600bd730cca8d0f61bbe063a0b6562149e0100a → ed586d3135daa95881bcac5c55766c77fc5bddc8 

Branch pushed to git repo; I updated commit sha1. New commits:
ed586d3  Remove restrictive type check in MultiGraphics

Changed 3 years ago by
Attachment:  g4_aspect_ratio1.png added 

comment:19 Changed 3 years ago by
Sorry for the delay in replying...
Replying to ghmwageringel:
There still seems to be an inconsistency between vertical and horizontal positioning. For example, in
g4
the blue squares touch each other in one direction, but not the other. I am not sure what the preferred behaviour is in this case, but it should be the same in both directions.
Actually this is not a bug but results from a clash between the default aspect ratio of the whole figure, which is 4/3, and the aspect ratio 1 of the subplots. If you ask for an aspect ratio of 1 for the whole figure, then everything is OK:
show(g4, figsize=(5, 5))
leads to
Moreover, some of the type checks testing for lists and tuples seem a bit restrictive. Especially this one is not necessary, as the implementation will work for any iterable and iterator, so the type check should just be removed:
Indeed! Thanks for pointing this. The type check has been removed in the above commit.
I am not sure why the following change is needed. Would it be better to test for the converse, i.e.
if not isinstance...
of the types you want to exclude? Or areGraphics
andGraphicsArray
the only types that supporttight_layout
?
Yes, I think so.
Also, there is a similar change in multigraphics.py that only tests for
isinstance(self, GraphicsArray)
. If you think it is preferable to keep this as is, that would also be fine be me.
Yes I think it is preferable to keep the tests as they are, since only GraphicsArray
requires the tight layout.
comment:20 Changed 3 years ago by
Status:  needs_work → needs_review 

comment:21 followup: 23 Changed 3 years ago by
Status:  needs_review → positive_review 

Ok, thanks for the explanations. I am setting this to positive then.
comment:22 Changed 3 years ago by
Milestone:  sage8.9 → sage9.0 

comment:23 Changed 3 years ago by
Replying to ghmwageringel:
Ok, thanks for the explanations. I am setting this to positive then.
Thanks for the review!
comment:24 Changed 3 years ago by
Branch:  public/graphics/graphics_insets → ed586d3135daa95881bcac5c55766c77fc5bddc8 

Resolution:  → fixed 
Status:  positive_review → closed 
comment:25 Changed 3 years ago by
Commit:  ed586d3135daa95881bcac5c55766c77fc5bddc8 

This is causing two test failures on Arch when compiling against system libraries (haven't tried on sagethedistro)
********************************************************************** File "/usr/lib/python3.7/sitepackages/sage/plot/multigraphics.py", line 1297, in sage.plot.multigraphics.GraphicsArray.position Failed example: G.position(0) # tol 1.0e13 Expected: (0.028906249999999998, 0.038541666666666696, 0.45390624999999996, 0.9229166666666667) Got: (0.023437500000000003, 0.03415488992713045, 0.4627803348994754, 0.9345951100728696) Tolerance exceeded in 4 of 4: 0.028906249999999998 vs 0.023437500000000003, tolerance 2e1 > 1e13 0.038541666666666696 vs 0.03415488992713045, tolerance 2e1 > 1e13 0.45390624999999996 vs 0.4627803348994754, tolerance 2e2 > 1e13 0.9229166666666667 vs 0.9345951100728696, tolerance 2e2 > 1e13 ********************************************************************** File "/usr/lib/python3.7/sitepackages/sage/plot/multigraphics.py", line 1302, in sage.plot.multigraphics.GraphicsArray.position Failed example: G.position(1) # tol 1.0e13 Expected: (0.5171874999999999, 0.038541666666666696, 0.45390624999999996, 0.9229166666666667) Got: (0.5136230468749999, 0.19293222169724827, 0.46278033489947534, 0.617040446532634) Tolerance exceeded in 4 of 4: 0.5171874999999999 vs 0.5136230468749999, tolerance 7e3 > 1e13 0.038541666666666696 vs 0.19293222169724827, tolerance 5e0 > 1e13 0.45390624999999996 vs 0.46278033489947534, tolerance 2e2 > 1e13 0.9229166666666667 vs 0.617040446532634, tolerance 4e1 > 1e13 ********************************************************************** 1 item had failures: 2 of 6 in sage.plot.multigraphics.GraphicsArray.position [192 tests, 2 failures, 15.63 s]
Last 10 new commits:
Small cleaning in MultiGraphics
Fix some doctests after the improved computation of nrows and ncols in graphics_array
Some cleaning in MultiGraphics.matplotlib
Minor cleaning in GraphicsArray
Change index range in GraphicsArray._add_subplot()
First draft of multi_graphics
Add method inset() to Graphics and MultiGraphics
Add method position in MultiGraphics and GraphicsArray
Add transparency of individual graphics in MultiGraphics + more doctests
Add documentation to MultiGraphics