marimo-team / marimo

A reactive notebook for Python — run reproducible experiments, execute as a script, deploy as an app, and version with git.
https://marimo.io
Apache License 2.0
7.26k stars 252 forks source link

matplotlib: rc file not work #2404

Closed YDX-2147483647 closed 1 month ago

YDX-2147483647 commented 1 month ago

Describe the bug

Matplotlib uses matplotlibrc configuration files to customize properties. For example, you can add mathtext.fontset: cm into the file matplotlibrc, and all math texts in plots will now in Computer Modern.

However, if you matplotlib in a marimo cell, the rc file takes no effect.

Environment

{
  "marimo": "0.8.19",
  "OS": "Windows",
  "OS Version": "10",
  "Processor": "Intel64 Family 6 Model 166 Stepping 0, GenuineIntel",
  "Python Version": "3.12.5",
  "Binaries": {
    "Browser": "--",
    "Node": "v20.9.0"
  },
  "Dependencies": {
    "click": "8.1.7",
    "importlib-resources": "missing",
    "jedi": "0.19.1",
    "markdown": "3.7",
    "pygments": "2.18.0",
    "pymdown-extensions": "10.10.1",
    "ruff": "0.6.7",
    "starlette": "0.39.0",
    "tomlkit": "0.13.2",
    "typing-extensions": "missing",
    "uvicorn": "0.30.6",
    "websockets": "12.0"
  },
  "Optional Dependencies": {}
}

Matplotlib version: 3.9.2

Code to reproduce

matplotlibrc:

mathtext.fontset: cm

This is an invalid line on purpose.

test.py:

def foo():
    from matplotlib import rcParams, matplotlib_fname

    print("Outside:", rcParams["mathtext.fontset"], matplotlib_fname())

foo()

import marimo

app = marimo.App()

@app.cell
def __():
    from matplotlib import rcParams, matplotlib_fname

    print("In app:", rcParams["mathtext.fontset"], matplotlib_fname())

app.run()

foo()

python test.py:

Missing colon in file 'matplotlibrc', line 3 ('This is an invalid line on purpose.')   ✅ expected
Outside cm matplotlibrc                ✅ expected
In app: dejavusans matplotlibrc     😖 it should be `cm`
Outside: dejavusans matplotlibrc   ❓ side effect?

Question

Is it by design for reproducibility? I have found nothing in the doc or discord…

Workaround

Set rcParams["mathtext.fontset"] = 'cm' dynamically in the cell.

Runtime rc settings — Customizing Matplotlib with style sheets and rcParams — Matplotlib 3.9.2 documentation

akshayka commented 1 month ago

Thanks for reporting. It's not by design. We can look into this. If you're familiar with matplotlib and think you can help (that would be appreciated!) you can look at our matplotlib formatting code here: https://github.com/marimo-team/marimo/blob/3f6fcb4cc3571680a6de8e432c87362c5fbf97d5/marimo/_output/formatters/matplotlib_formatters.py. If not, no worries

YDX-2147483647 commented 1 month ago

Wow, thanks for showing the way.

I add if key == 'mathtext.fontset': print(f"{key} = {val}") in RcParams._set(self, key, val) https://github.com/matplotlib/matplotlib/blob/a254b687df97cda8c6affa37a1dfcf213f8e6c3a/lib/matplotlib/__init__.py#L692, and find something weird: rcParams is set three times! And marimo somehow changes the order…

def foo():
    print("Outside:")
    from matplotlib import rcParams, matplotlib_fname

foo()

import marimo

app = marimo.App()

@app.cell
def __():
    print("Before import?")
    from matplotlib import rcParams, matplotlib_fname

print("In app:")
app.run()
Outside:
mathtext.fontset = dejavusans
mathtext.fontset = cm
mathtext.fontset = cm
In app:
mathtext.fontset = cm
mathtext.fontset = cm
mathtext.fontset = dejavusans
Before import?

Update: The first two are set in register(self) https://github.com/marimo-team/marimo/blob/3f6fcb4cc3571680a6de8e432c87362c5fbf97d5/marimo/_output/formatters/matplotlib_formatters.py#L14 and the third in apply_theme(self, theme: Theme). https://github.com/marimo-team/marimo/blob/3f6fcb4cc3571680a6de8e432c87362c5fbf97d5/marimo/_output/formatters/matplotlib_formatters.py#L63-L65

akshayka commented 1 month ago

Thank you for investigating! So it looks like apply_theme, specifically matplotlib.style.use, is causing the rcParams to be reset?

YDX-2147483647 commented 1 month ago

matplotlib.style.use("dark_background") is OK, but matplotlib.style.use("default") overrides the rc file. https://github.com/matplotlib/matplotlib/blob/a254b687df97cda8c6affa37a1dfcf213f8e6c3a/lib/matplotlib/style/core.py#L112-L120

Change apply_theme(self, theme: Theme) to the following would fix the issue.

if theme == "dark":
        matplotlib.style.use("dark_background")

But if apply_theme multiple times, first dark then light, it won't change.

akshayka commented 1 month ago

Awesome, thank you so much for looking into this! I will make a PR with a fix.

YDX-2147483647 commented 1 month ago

Wait! If apply_theme is called multiple times, the “fix” might break things.


if theme == "dark":
    matplotlib.style.use("dark_background")
else:
    matplotlib.style.use("default")
    matplotlib.style.use(matplotlib_fname())  # Apply rc file given by the user

This might work, but not tested yet. It's really late in my timezone…

akshayka commented 1 month ago

Thanks! I can test. Or, if you'd like to make the PR yourself (you're more than welcome to!), I can hold off and leave this for you. Either works 🙂

YDX-2147483647 commented 1 month ago

It’s nice of you, please go ahead and get it fixed. It looks like that you are more familiar with the code base, and therefore more capable of it.