Movatterモバイル変換


[0]ホーム

URL:


Skip to content

Navigation Menu

Sign in
Appearance settings

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

Sign up
Appearance settings

Support individual styling of major and minor grid through rcParams#29481

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 ourterms of service andprivacy statement. We’ll occasionally send you account related emails.

Already on GitHub?Sign in to your account

Open
konmenel wants to merge18 commits intomatplotlib:main
base:main
Choose a base branch
Loading
fromkonmenel:fix-issue-13919

Conversation

konmenel
Copy link

PR summary

Possiblycloses#13919 issue. Additionally, PR#26121 also tried to solve the same issue but I understand that it is not backwards compatible and there haven't been any updates since 2023. My implementation should be backwards compatible.

I have tested the code with pytest. Some tests failed on my machine, but all were latex tests. The problem is probably my latex installation.

I also tested to code with a small python script that:

  1. Modifies the rcParams at runtime for 3 different scenarios and uses
  2. Uses mstyle files for the same 3 scenario

The results were identical and as expected.

Test

My test script:

# test_grid_rcparams.pyimportmatplotlibasmplimportmatplotlib.pyplotaspltdpi=150#---------------------------------------------# RUNTIME#---------------------------------------------mpl.rcdefaults()mpl.rcParams["axes.grid"]=Truempl.rcParams["ytick.minor.visible"]=Truempl.rcParams["xtick.minor.visible"]=Truempl.rcParams["axes.grid.which"]="both"mpl.rcParams["grid.color"]="red"mpl.rcParams["grid.linewidth"]=1mpl.rcParams["grid.linestyle"]="-"mpl.rcParams["grid.alpha"]=1plt.figure()plt.plot([0,1], [0,1])plt.gcf().savefig("runtime1.png",dpi=dpi)mpl.rcdefaults()mpl.rcParams["axes.grid"]=Truempl.rcParams["ytick.minor.visible"]=Truempl.rcParams["xtick.minor.visible"]=Truempl.rcParams["axes.grid.which"]="both"mpl.rcParams["grid.color"]="red"mpl.rcParams["grid.linewidth"]=1mpl.rcParams["grid.linestyle"]="-"mpl.rcParams["grid.alpha"]=1mpl.rcParams["grid.major.color"]="black"mpl.rcParams["grid.major.linewidth"]=2mpl.rcParams["grid.major.linestyle"]=":"plt.figure()plt.plot([0,1], [0,1])plt.gcf().savefig("runtime2.png",dpi=dpi)mpl.rcdefaults()mpl.rcParams["axes.grid"]=Truempl.rcParams["ytick.minor.visible"]=Truempl.rcParams["xtick.minor.visible"]=Truempl.rcParams["axes.grid.which"]="both"mpl.rcParams["grid.color"]="red"mpl.rcParams["grid.linewidth"]=1mpl.rcParams["grid.linestyle"]="-"mpl.rcParams["grid.alpha"]=1mpl.rcParams["grid.minor.color"]="red"mpl.rcParams["grid.minor.linewidth"]=0.5mpl.rcParams["grid.minor.linestyle"]=":"mpl.rcParams["grid.minor.alpha"]=0.5plt.figure()plt.plot([0,1], [0,1])plt.gcf().savefig("runtime3.png",dpi=dpi)#---------------------------------------------# MSTYLE FILE#---------------------------------------------mpl.rcdefaults()plt.style.use("./style1.mstyle")plt.figure()plt.plot([0,1], [0,1])plt.gcf().savefig("mstyle1.png",dpi=dpi)mpl.rcdefaults()plt.style.use("./style2.mstyle")plt.figure()plt.plot([0,1], [0,1])plt.gcf().savefig("mstyle2.png",dpi=dpi)mpl.rcdefaults()plt.style.use("./style3.mstyle")plt.figure()plt.plot([0,1], [0,1])plt.gcf().savefig("mstyle3.png",dpi=dpi)
# style1.mstyleaxes.grid: Trueytick.minor.visible: Truextick.minor.visible: Trueaxes.grid.which: bothgrid.color: redgrid.linewidth: 1grid.linestyle: -grid.alpha: 1
# style2.mstyleaxes.grid: Trueytick.minor.visible: Truextick.minor.visible: Trueaxes.grid.which: bothgrid.color: redgrid.linewidth: 1grid.linestyle: -grid.alpha: 1grid.major.color: blackgrid.major.linewidth: 2grid.major.linestyle: :
# style3.mstyleaxes.grid: Trueytick.minor.visible: Truextick.minor.visible: Trueaxes.grid.which: bothgrid.color: redgrid.linewidth: 1grid.linestyle: -grid.alpha: 1grid.minor.color: redgrid.minor.linewidth: 0.5grid.minor.linestyle: :grid.minor.alpha: 0.5

Results

mstyle1
mstyle2
mstyle3
runtime1
runtime2
runtime3

PR checklist

Copy link

@github-actionsgithub-actionsbot left a comment

Choose a reason for hiding this comment

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

Thank you for opening your first PR into Matplotlib!

If you have not heard from us in a week or so, please leave a new comment below and that should bring it to our attention. Most of our reviewers are volunteers and sometimes things fall through the cracks.

You can also join uson gitter for real-time discussion.

For details on testing, writing docs, and our review process, please seethe developer guide

We strive to be a welcoming and open project. Please follow ourCode of Conduct.

@timhoffmtimhoffm changed the titleFIX: issue #13919Support individual styling of major and minor grid through rcParamsJan 27, 2025
Copy link
Member

@timhoffmtimhoffm left a comment

Choose a reason for hiding this comment

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

Thanks for the PR. The solution is surprisingly simple. I would have thought that there are more complications because of the concurrent settings and the need for backward compatibility.

The only improvement needed is the clearer distinction on the color and linewidth parameter values between "none" andNone.

Comment on lines 1240 to 1241
"grid.major.color": validate_color, # grid color
"grid.major.linestyle": _validate_linestyle, # solid
Copy link
Member

Choose a reason for hiding this comment

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

We have to be a bit more precise with these. The string "none" is a valid value for both. We should not repurpose it here with the meaning not defined. In the parsedrcParams dict, we need to be able to have both valuesNone and"none".
Sincematplotlibrc is purely text based, we have to distinguish by capitalization. 'None' must be parsed toNone, and 'none' must be parsed to"none". Please create respectivevalidate_color_or_None andvalidate_linestyle_or_None functions for that.

Copy link
Author

Choose a reason for hiding this comment

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

Sounds good. I was thinking of doing something like that but I saw that the functions parse the "None" and "none", so I didn't know if I should introduce a new function or not. Nevertheless, this should be easy.

Copy link
Member

Choose a reason for hiding this comment

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

Yes, it’s a bit unfortunate that the existing parameters are permissive wrt. to capitalization. But the new ones have to distinguish. We may deprecate accepting None for the other variants. But that’d be for a separate PR.

grid_linewidth = mpl._val_or_rc(grid_linewidth, "grid.linewidth")
grid_color = (
mpl._val_or_rc(grid_color, "grid.color")
if mpl.rcParams[f"grid.{major_minor}.color"] == "none"
Copy link
Member

Choose a reason for hiding this comment

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

Suggested change
ifmpl.rcParams[f"grid.{major_minor}.color"]=="none"
ifmpl.rcParams[f"grid.{major_minor}.color"]isNone

see comment on validation

)
grid_linestyle = (
mpl._val_or_rc(grid_linestyle, "grid.linestyle")
if mpl.rcParams[f"grid.{major_minor}.linestyle"] == "none"
Copy link
Member

Choose a reason for hiding this comment

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

Suggested change
ifmpl.rcParams[f"grid.{major_minor}.linestyle"]=="none"
ifmpl.rcParams[f"grid.{major_minor}.linestyle"]isNone

see comment on validation

grid_alpha = mpl.rcParams["grid.alpha"]
grid_alpha = (
mpl.rcParams["grid.alpha"]
if f"grid.{major_minor}.alpha" not in mpl.rcParams
Copy link
Member

Choose a reason for hiding this comment

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

Why is the logic here different (hasattr) compared to the other parameters (is None)?

Copy link
Author

@konmenelkonmenelJan 27, 2025
edited
Loading

Choose a reason for hiding this comment

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

Honestly, I have no idea so the only explanation is stupidity. Furthermore, I think this is a bug because grid.minor/major.alpha is always inrcParams. I will fix this so that is the same with the rest.

timhoffm reacted with thumbs up emoji
@timhoffm
Copy link
Member

One related aspect to think about: Do we anticipate that we'll need a distinction between x and y axis as well? Because if so, we should directly introduce these.

timhoffm pushed a commit to timhoffm/matplotlib that referenced this pull requestJan 27, 2025
The `*_or_None` validators have accepted any capitalization of thestring "none" as input. This is overly permissive andwill likely lead to conflicts in the future because we cannotdistinguish between resolving to `None` and `"none"` which may be needfor some parameters in the future.Inspired bymatplotlib#29481.
@konmenel
Copy link
Author

konmenel commentedJan 27, 2025
edited
Loading

I made the suggested changes.

One related aspect to think about: Do we anticipate that we'll need a distinction between x and y axis as well? Because if so, we should directly introduce these.

Can you clarify what you meant by "a distinction between x and y axis"? Do you mean something like

rcParams["grid.major.xaxis.linestyle"]=":"rcParams["grid.major.yaxis.linestyle"]="-"

timhoffm pushed a commit to timhoffm/matplotlib that referenced this pull requestJan 27, 2025
The `*_or_None` validators have accepted any capitalization of thestring "none" as input. This is overly permissive andwill likely lead to conflicts in the future because we cannotdistinguish between resolving to `None` and `"none"` which may be needfor some parameters in the future.Inspired bymatplotlib#29481.
@konmenel
Copy link
Author

konmenel commentedJan 27, 2025
edited
Loading

Sorry I forgot to update the ".pyi" file

@timhoffm
Copy link
Member

between x and y axis"? Do you mean something like

rcParams["grid.major.xaxis.linestyle"]=":"rcParams["grid.major.yaxis.linestyle"]="-"

Basically yes. Exact spelling t.b.d.

@@ -140,6 +141,7 @@ def validate_fillstylelist(
) -> list[Literal["full", "left", "right", "bottom", "top", "none"]]: ...
def validate_markevery(s: Any) -> MarkEveryType: ...
def _validate_linestyle(s: Any) -> LineStyleType: ...
def _validate_linestyle_or_None(s: Any) -> LineStyleType | None: ...
Copy link
Author

Choose a reason for hiding this comment

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

Not sure why mypy fails for this function but it doesn't for the_validate_linestyle. The stub and runtime arguments are different for both.

Copy link
Member

Choose a reason for hiding this comment

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

No idea. Just make the arguments the same.

@konmenel
Copy link
Author

konmenel commentedJan 27, 2025
edited
Loading

Basically yes. Exact spelling t.b.d.

I think this is possible however it might look "ugly". The change will require theTick class to know which child (XTick,YTick) was instantiated since the lines are created in the parent class.

One possible way is by accessing theself.__name__ attribute which should either be"xtick" or"ytick" since the base class is never instantiated. However, this might cause problems if this attribute ever changes which might be hard to debug. It is also possible to add a new attribute to theTick class which will default toNone and will be set by the child class to be used explicitly for this purpose so that we don't use the__name__ attribute.

Another way is to add an extra required argument to the constructor ofTick, probably an enum or a bool. For instance, an argumentaxis that will be either 1 or 2 ifXTick orYTick has called theTick.__init__ method, respectively.

Any thoughts on those?

@timhoffm
Copy link
Member

We already rely onTick.__name__ see

name=self.__name__
major_minor="major"ifmajorelse"minor"
self._size=mpl._val_or_rc(size,f"{name}.{major_minor}.size")
self._width=mpl._val_or_rc(width,f"{name}.{major_minor}.width")
self._base_pad=mpl._val_or_rc(pad,f"{name}.{major_minor}.pad")
color=mpl._val_or_rc(color,f"{name}.color")
labelcolor=mpl._val_or_rc(labelcolor,f"{name}.labelcolor")
ifcbook._str_equal(labelcolor,'inherit'):
# inherit from tick color
labelcolor=mpl.rcParams[f"{name}.color"]
labelsize=mpl._val_or_rc(labelsize,f"{name}.labelsize")

It's ok to use that.

Spelling: We have "xaxis", "xticks", "xscale" as axis-dependent names (but they are all also used in the python interface). We do not have any xgrid or similar names. Options:

  • "xgrid.major.linestyle" - in analogy to "xticks"
  • "grid.xaxis.major.linestyle" / "grid.x.major.linestyle" - still put it under the "grid" top level group
  • "xtick.major.grid.linestyle" - put everything under the corresponding tick
  • "xaxis.grid.major.linestyle" - put everything under the corresponding axis

None of them look like the obvious right choice to me.

@konmenel
Copy link
Author

Out of the four, I prefer two:

  • "xtick.major.grid.linestyle" - many options already exist under "xtick.major.*" so it is logical.
  • "grid.xaxis.major.linestyle" - configures similar to both axis. Also, it will be in a similar place in the matplotlibrc making easier to find if you are looking for it.

I will try to implement the second one for now. I can change the key later.

timhoffm reacted with thumbs up emoji

timhoffm added a commit to timhoffm/matplotlib that referenced this pull requestJan 27, 2025
The `*_or_None` validators have accepted any capitalization of thestring "none" as input. This is overly permissive andwill likely lead to conflicts in the future because we cannotdistinguish between resolving to `None` and `"none"` which may be needfor some parameters in the future.Inspired bymatplotlib#29481.
@konmenel
Copy link
Author

I implemented the x and y axis distinction however I came across something that needs further clarification. Right now "grid.xaxis.major.linestyle" is a valid key but "grid.xaxis.linestyle" is not. Should it be a valid option as well? If so it is unclear which of "grid.major.linestyle"and "grid.xaxis.linestyle" should take precedence.

@timhoffm
Copy link
Member

No, we don't need "grid.xaxis.linestyle". If starting from scratch, I'd just have the granular settings "grid.xaxis.major.linestyle" etc. After all, these are advanced style configuration options, and if somebody cares to adapt them, it's ok to write out the details. We keep the high-level ones "grid.linestyle" for backward compatibility (and it is a nice convenience) but we don't need the middle-ground.

Copy link
Member

@timhoffmtimhoffm left a comment

Choose a reason for hiding this comment

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

Looks good. Please add a What's New note according tohttps://matplotlib.org/devdocs/devel/api_changes.html#what-s-new-notes

`_val_or_rc` now accept multiple rc names and return val or the first non-None value in rcParams. Returns last rc name if all other are None. Also, simplified code in `Tick` for grid lines creatation
@konmenel
Copy link
Author

I added the What's New note. I am not if the syntax is correct for the rc parameters. I have never used rst before so let me know if it needs correcting. Also, I tried to resolve the stubtest unsuccessfully.

@timhoffm
Copy link
Member

Also, I tried to resolve the stubtest unsuccessfully.

I suppose you've tricked yourself. You've made the_validate_linestyle parameter names consistent, which renders the allowlist entry unnecessary (and mypy complains on that). Then, you've repeated the same for_validate_linestyle_or_None.

Try removing both allowlist entries.

Copy link
Member

@timhoffmtimhoffm left a comment

Choose a reason for hiding this comment

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

Please also add a test. Basically, create a plot with the rcParams set and investigate the grid line properties, e.g. usingax.xaxis.get_gridlines()[0].get_color(). You might needsame_color() for comparison if the returned color format does not match the input format.

Using :rc:`grid.major.*` or :rc:`grid.minor.*` will overwrite the value in
:rc:`grid.*` for the major and minor gridlines, respectively. Similarly,
specifying :rc:`grid.xaxis.major.*` and :rc:`grid.yaxis.major.*` will overwrite
`grid.major.*` for x and y axis major gridlines respectively.
Copy link
Member

Choose a reason for hiding this comment

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

Suggested change
`grid.major.*` for x and y axis major gridlines respectively.
:rc:`grid.major.*` for x and y axis major gridlines respectively.

Should fix sphinx.

specifying :rc:`grid.xaxis.major.*` and :rc:`grid.yaxis.major.*` will overwrite
`grid.major.*` for x and y axis major gridlines respectively.

.. plot::
Copy link
Member

Choose a reason for hiding this comment

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

The example is a bit heavy. This should be illustrative easy to grap. It should give an idea what you can do and how. It's not an exhaustive documentation of the feature. The rendered entry should fit on a (large) screen. Please boil down to one figure (you can have two Axes side by side if really needed) and take a subset of rcParams only.

Typically, one figure should be enough. Select a few parameters.

Comment on lines 1263 to 1282
"grid.xaxis.major.color": validate_color_or_None, # grid color
"grid.xaxis.major.linestyle": _validate_linestyle_or_None, # solid
"grid.xaxis.major.linewidth": validate_float_or_None, # in points
"grid.xaxis.major.alpha": validate_float_or_None,

"grid.xaxis.minor.color": validate_color_or_None, # grid color
"grid.xaxis.minor.linestyle": _validate_linestyle_or_None, # solid
"grid.xaxis.minor.linewidth": validate_float_or_None, # in points
"grid.xaxis.minor.alpha": validate_float_or_None,

"grid.yaxis.major.color": validate_color_or_None, # grid color
"grid.yaxis.major.linestyle": _validate_linestyle_or_None, # solid
"grid.yaxis.major.linewidth": validate_float_or_None, # in points
"grid.yaxis.major.alpha": validate_float_or_None,

"grid.yaxis.minor.color": validate_color_or_None, # grid color
"grid.yaxis.minor.linestyle": _validate_linestyle_or_None, # solid
"grid.yaxis.minor.linewidth": validate_float_or_None, # in points
"grid.yaxis.minor.alpha": validate_float_or_None,

Copy link
Member

Choose a reason for hiding this comment

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

Seeing this in code, it feels a bit too much. I've browsed though a lot of plots and didn't find relevant usage. If the grids are different on x and y, it's typically that only one of them is activated. I've not seen the case that people use different styles for x and y.

Therefore I revert my previous proposal and think we should leave this out (YAGNI). Sorry for the back-and-forth, but sometimes one has to go the step to see it was wrong. On the plus side, this interplay lead to the expansion of_val_or_rc() which I think is a real win.

Copy link
Author

Choose a reason for hiding this comment

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

That is far. I will revert the changes. It's simple anyway with the new_val_or_rc() to go back-and-forth.

@konmenel
Copy link
Author

Do you want me to create the test in the file "test_rcparams.py" or create a separate file?

@timhoffm
Copy link
Member

Please put it intest_axis.py. This is where the semantic code lives and where it logically belongs.test_rcparams.py is only for the rcparams infrastructure not testind individual values.

@konmenel
Copy link
Author

I was working on the test and I realised that theget_gridlines() only returns the major gridlines. If it's okay I will introduce two new functionsget_major_gridlines() andget_minor_gridlines() to be explicit. That way I will able to easily extract the minor gridlines as well. Also, the original function will now call theget_major_gridlines() to be backwards compatible

@timhoffm
Copy link
Member

Then useax.xaxis.get_major_ticks()[0].gridline. There are various ways to drill down, and not all are complete or consistent. Sorting/consolidating that is for another time. Let's not create more functions as part of this PR.

@konmenel
Copy link
Author

I fix the spelling so all tests should pass now. If there is anything else please let me know.

@carlosgmartin
Copy link

@timhoffm Any idea when this might be merged?

@timhoffm
Copy link
Member

This needs a second review by a core developer, and review time is scarce. However, this is flagged for 3.11 and I'm pretty sure it will get the attention in time for the next release.

Sign up for freeto join this conversation on GitHub. Already have an account?Sign in to comment
Reviewers

@github-actionsgithub-actions[bot]github-actions[bot] left review comments

@timhoffmtimhoffmtimhoffm approved these changes

Assignees
No one assigned
Projects
Status: Needs decision
Milestone
v3.11.0
Development

Successfully merging this pull request may close these issues.

Impossible to configure minor/major grid line style independently in rcParams
3 participants
@konmenel@timhoffm@carlosgmartin

[8]ページ先頭

©2009-2025 Movatter.jp