Opened 3 years ago

Last modified 3 years ago

#25165 new defect

Showing a plot overwrites matplotlib global options

Reported by: mmezzarobba Owned by:
Priority: major Milestone: sage-8.2
Component: graphics Keywords:
Cc: strogdon Merged in:
Authors: Reviewers:
Report Upstream: N/A Work issues:
Branch: Commit:
Dependencies: Stopgaps:

Description

sage: from matplotlib import rcParams
sage: rcParams['font.family'] = 'serif'
sage: p = plot(sin, 0, 1)
sage: rcParams['font.family']
[u'serif']
sage: p.show() # or save(), or matplotlib()
Launched png viewer for Graphics object consisting of 1 graphics primitive
sage: rcParams['font.family']
[u'sans-serif']

I think this is because Graphics.matplotlib() always loads a matplotlib stylesheet—usually 'classic'—, but I'm not sure what the right thing to do would be.

Change History (8)

comment:1 Changed 3 years ago by strogdon

  • Cc strogdon added

comment:2 Changed 3 years ago by strogdon

Probably any of the defaults in matplotlib/mpl-data/stylelib/classic.mplstyle if changed through rcParams[] will be overwritten if matplotlib is called. I don't know what would have happened prior to the matplotlib upgrade.

comment:3 Changed 3 years ago by strogdon

Yes, prior to commit d109b7593e user changes via rcParams[] were not altered after calls to matplotlib.

comment:4 Changed 3 years ago by fbissey

I had not tested that particular scenario I must say. I thought it would work, there may be an ordering issue. One of the issue that lead to the inclusion of that style in matplotlib calls is that the default style changed between 1.5.x and 2.1.x. I got comments that plots after the upgrade to 2.1.x were not looking very good.

Including the style restored the default of 1.5.x and restored the general quality of the plots.

In effect I have been torn between importing a style sheet before every graphics call and documenting it somewhere that you needed it for nice plots (how many graphics calls for the documentation? A few hundreds probably). Or just applying it blindly.

Anyway, I'll have to dig that code again to see what is the right thing to do to over-ride style sheet options. I added the option to change the style sheet but you want something more fine grained than that.

comment:5 Changed 3 years ago by mmezzarobba

I really like the option to specify a style sheet, it is a great way to give more control to the user on the appearance of the plots at a low development effort and without duplicating the functionality of matplotlib. And it could be enough to document prominently that this is the recommended way to set matplotlib options in sage (at the moment, there are several answers on ask.sage that suggest using matplotlib.rc or similar).

comment:6 Changed 3 years ago by strogdon

We could keep track of user changes. Something like

  • src/sage/plot/graphics.py

    diff --git a/src/sage/plot/graphics.py b/src/sage/plot/graphics.py
    index 58ece044f6..8d68b0a681 100644
    a b class Graphics(WithEqualityById, SageObject): 
    25272527        if not isinstance(ticks, (list, tuple)):
    25282528            ticks = (ticks, None)
    25292529
     2530        from matplotlib import rcParams, style
     2531
     2532        #  save rcParams changed by the user
     2533        userdefaults = rcParams.items()
     2534        style.use('default')
     2535        l = []
     2536        for i in range(0, len(userdefaults)):
     2537            if userdefaults[i] != rcParams.items()[i]:
     2538                l.append(userdefaults[i])
     2539
    25302540        import matplotlib.pyplot as plt
    25312541        if stylesheet not in plt.style.available:
    25322542            stylesheet = 'classic'
    25332543        plt.style.use(stylesheet)
     2544        #  add user changed rcParams to current stylesheet
     2545        for i in range(0, len(l)):
     2546            rcParams[l[i][0]] = l[i][1]
    25342547
    25352548        from sage.symbolic.ring import SR
    25362549        if not isinstance(tick_formatter, (list, tuple)):  # make sure both formatters typeset or both don't
    class Graphics(WithEqualityById, SageObject): 
    30053018        for g in self._objects:
    30063019            g.set_options(old_opts[g])
    30073020
     3021        #  switch to the 'default' stylesheet and add back user changed rcParams
     3022        style.use('default')
     3023        for i in range(0, len(l)):
     3024            rcParams[l[i][0]] = l[i][1]
     3025
    30083026        return figure
    30093027
    30103028    def save_image(self, filename=None, *args, **kwds):

I'm not sure it is robust enough, particularly with how the user changes are added in. And it will increase overhead if there are a lot of user changes. Before the patch

sage: from matplotlib import rcParams
sage: rcParams['font.size']
10.0
sage: p = plot(sin, 0, 1)
sage: p.matplotlib()
<matplotlib.figure.Figure object at 0x7f76037d2490>
sage: rcParams['font.size']
12.0

and after the patch

sage: from matplotlib import rcParams
sage: rcParams['font.size']
10.0
sage: p = plot(sin, 0, 1)
sage: p.matplotlib()
<matplotlib.figure.Figure object at 0x7f1928dff410>
sage: rcParams['font.size']
10.0

comment:7 Changed 3 years ago by strogdon

This

rcParams[l[i][0]] = l[i][1]

is not correct in all cases, in particular the l[i][1]. But at the least we should restore the defaults.

comment:8 Changed 3 years ago by strogdon

Well, may be better than I thought

sage: from matplotlib import rcParams, rc
sage: rc('font', size=9.0, family='serif')
sage: rcParams['font.family']
[u'serif']
sage: rcParams['font.size']
9.0
sage: p = plot(sin, 0, 1)
sage: p.matplotlib()
<matplotlib.figure.Figure object at 0x7f7ff24a2350>
sage: rcParams['font.family']
[u'serif']
sage: rcParams['font.size']
9.0
Last edited 3 years ago by strogdon (previous) (diff)
Note: See TracTickets for help on using tickets.