Opened 11 months ago

Closed 5 months ago

Last modified 5 months ago

#27866 closed enhancement (fixed)

Introduce graphics insets

Reported by: egourgoulhon Owned by:
Priority: major Milestone: sage-9.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) Commit:
Dependencies: #27865 Stopgaps:

Description (last modified by egourgoulhon)

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 multi-graphics (e.g. a GraphicsArray); see this preview example
  • the function multi_graphics() , which creates a figure from a list of Graphics objects at user-specified locations; see this preview example

Attachments (4)

g2.png (18.0 KB) - added by gh-mwageringel 8 months ago.
g2_new.png (18.9 KB) - added by egourgoulhon 7 months ago.
g4.png (35.8 KB) - added by gh-mwageringel 6 months ago.
g4_aspect_ratio1.png (37.5 KB) - added by egourgoulhon 6 months ago.

Download all attachments as: .zip

Change History (30)

comment:1 Changed 11 months ago by egourgoulhon

  • Authors set to Eric Gourgoulhon
  • Branch set to public/graphics/graphics_insets
  • Commit set to e1776462164bf01ed857eccb06513c276986e633
  • Dependencies set to #27865

Last 10 new commits:

1bf8ad1Small cleaning in MultiGraphics
ccff60cFix some doctests after the improved computation of nrows and ncols in graphics_array
26180abSome cleaning in MultiGraphics.matplotlib
3bd6a56Minor cleaning in GraphicsArray
3a5ef55Change index range in GraphicsArray._add_subplot()
60ede56First draft of multi_graphics
e58adf3Add method inset() to Graphics and MultiGraphics
26a8eb5Add method position in MultiGraphics and GraphicsArray
8ce0db3Add transparency of individual graphics in MultiGraphics + more doctests
e177646Add documentation to MultiGraphics

comment:2 Changed 11 months ago by egourgoulhon

  • Cc kcrisman chapoton dkrenn jdemeyer added
  • Status changed from new to needs_review

comment:3 Changed 11 months ago by egourgoulhon

  • Cc embray added

comment:4 Changed 11 months ago by egourgoulhon

  • Description modified (diff)

comment:5 Changed 9 months ago by embray

  • Milestone changed from sage-8.8 to sage-8.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 8 months ago by git

  • Commit changed from e1776462164bf01ed857eccb06513c276986e633 to f504d7a7b5e155a419cc590445a54342ac9b439b

Branch pushed to git repo; I updated commit sha1. New commits:

cfb4c35Merge branch 'public/graphics/graphics_insets' of git://trac.sagemath.org/sage into Sage 8.9.beta5
f504d7aCorrect a typo in plot.py

comment:7 Changed 8 months ago by egourgoulhon

The two pyflakes errors reported by the patchbot are spurious ones: these imports are really necessary.

comment:8 Changed 8 months ago by git

  • Commit changed from f504d7a7b5e155a419cc590445a54342ac9b439b to 3099f48c2560a6d2131def1b830d032e1941419b

Branch pushed to git repo; I updated commit sha1. New commits:

3099f4827866: fix typos

comment:9 follow-up: Changed 8 months ago by gh-mwageringel

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.

Last edited 8 months ago by gh-mwageringel (previous) (diff)

Changed 8 months ago by gh-mwageringel

comment:10 in reply to: ↑ 9 ; follow-up: Changed 8 months ago by egourgoulhon

Replying to gh-mwageringel:

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") 4-tuple "(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 in reply to: ↑ 10 ; follow-up: Changed 8 months ago by gh-mwageringel

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 7 months ago by git

  • Commit changed from 3099f48c2560a6d2131def1b830d032e1941419b to 71dc5c8501a8129816d0ee5ec4768bf3f60b8fc6

Branch pushed to git repo; I updated commit sha1. New commits:

3d68cedMerge branch 'public/graphics/graphics_insets' of git://trac.sagemath.org/sage into Sage 8.9.beta8
71dc5c8Improve size adjustment in graphics insets

Changed 7 months ago by egourgoulhon

comment:13 in reply to: ↑ 11 Changed 7 months ago by egourgoulhon

Replying to gh-mwageringel:

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.

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 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?

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 7 months ago by git

  • Commit changed from 71dc5c8501a8129816d0ee5ec4768bf3f60b8fc6 to d34b69fafa50c3b879b189fd413a4867321f919c

Branch pushed to git repo; I updated commit sha1. New commits:

d34b69fRestore file permissions on build/pkgs/flint/spkg-check

comment:15 Changed 7 months ago by egourgoulhon

For some reason, yesterday's commit (comment:12) contained a spurious change of permissions on the file build/pkgs/flint/spkg-check. The above commit (comment:14) corrects this.

Changed 6 months ago by gh-mwageringel

comment:16 follow-up: Changed 6 months ago by gh-mwageringel

  • Reviewers set to Markus Wageringel
  • Status changed from needs_review to 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 6 months ago by git

  • Commit changed from d34b69fafa50c3b879b189fd413a4867321f919c to 2600bd730cca8d0f61bbe063a0b6562149e0100a

Branch pushed to git repo; I updated commit sha1. New commits:

2600bd7Fix merge conflict of graphics insets branch with Sage 9.0.beta2

comment:18 Changed 6 months ago by git

  • Commit changed from 2600bd730cca8d0f61bbe063a0b6562149e0100a to ed586d3135daa95881bcac5c55766c77fc5bddc8

Branch pushed to git repo; I updated commit sha1. New commits:

ed586d3Remove restrictive type check in MultiGraphics

Changed 6 months ago by egourgoulhon

comment:19 in reply to: ↑ 16 Changed 6 months ago by egourgoulhon

Sorry for the delay in replying...

Replying to gh-mwageringel:

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 are Graphics and GraphicsArray the only types that support tight_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 6 months ago by egourgoulhon

  • Status changed from needs_work to needs_review

comment:21 follow-up: Changed 6 months ago by gh-mwageringel

  • Status changed from needs_review to positive_review

Ok, thanks for the explanations. I am setting this to positive then.

comment:22 Changed 6 months ago by gh-mwageringel

  • Milestone changed from sage-8.9 to sage-9.0

comment:23 in reply to: ↑ 21 Changed 5 months ago by egourgoulhon

Replying to gh-mwageringel:

Ok, thanks for the explanations. I am setting this to positive then.

Thanks for the review!

comment:24 Changed 5 months ago by vbraun

  • Branch changed from public/graphics/graphics_insets to ed586d3135daa95881bcac5c55766c77fc5bddc8
  • Resolution set to fixed
  • Status changed from positive_review to closed

comment:25 Changed 5 months ago by arojas

  • Commit ed586d3135daa95881bcac5c55766c77fc5bddc8 deleted

This is causing two test failures on Arch when compiling against system libraries (haven't tried on sage-the-distro)

**********************************************************************
File "/usr/lib/python3.7/site-packages/sage/plot/multigraphics.py", line 1297, in sage.plot.multigraphics.GraphicsArray.position
Failed example:
    G.position(0)  # tol 1.0e-13
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 2e-1 > 1e-13
    0.038541666666666696 vs 0.03415488992713045, tolerance 2e-1 > 1e-13
    0.45390624999999996 vs 0.4627803348994754, tolerance 2e-2 > 1e-13
    0.9229166666666667 vs 0.9345951100728696, tolerance 2e-2 > 1e-13
**********************************************************************
File "/usr/lib/python3.7/site-packages/sage/plot/multigraphics.py", line 1302, in sage.plot.multigraphics.GraphicsArray.position
Failed example:
    G.position(1)  # tol 1.0e-13
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 7e-3 > 1e-13
    0.038541666666666696 vs 0.19293222169724827, tolerance 5e0 > 1e-13
    0.45390624999999996 vs 0.46278033489947534, tolerance 2e-2 > 1e-13
    0.9229166666666667 vs 0.617040446532634, tolerance 4e-1 > 1e-13
**********************************************************************
1 item had failures:
   2 of   6 in sage.plot.multigraphics.GraphicsArray.position
    [192 tests, 2 failures, 15.63 s]

comment:26 Changed 5 months ago by vbraun

New ticket please!

Note: See TracTickets for help on using tickets.