Skip to content
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 our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[ENH]: provide automatic linestyle cycling by default #26931

Open
drew-parsons opened this issue Sep 27, 2023 · 14 comments
Open

[ENH]: provide automatic linestyle cycling by default #26931

drew-parsons opened this issue Sep 27, 2023 · 14 comments

Comments

@drew-parsons
Copy link

drew-parsons commented Sep 27, 2023

Problem

matplotlib already provides automatic cycling of line colours, for instance using ax.plot(x,y) multiple times to show multiple curves on the same axes generates those curves with colours blue, orange, green, red, etc.

It would be generally useful to also provide or allow for automatic cycling of the linestyle (solid, dotted, dashed etc). A particular use-case is when a document might be printed on a black&white printer. Colours may not be easily discernible in grey shading, so different linestyles are important for distinguishing which curve is which.

Proposed solution

The existing colour cycler could be extended to also cycle over linestyles. (I haven't been able to locate the definition of the default colour cycling in the code, I'd be grateful if someone could point it out).

If consensus is that by default the linestyle should remain solid for all lines, then as an alternative it would be helpful to provide an alternative cycler definition which adds linestyle cycling to the existing colour cycling. For completeness it could also then be appropriate to provide a third cycler object which provides linestyle cycling with the same colour. This would save having to create custom cyclers manually each time (or reconfigure the local rc config file for each user).

@jklymak
Copy link
Member

jklymak commented Sep 27, 2023

You can cycle over line styles: https://matplotlib.org/stable/users/explain/artists/color_cycle.html

@drew-parsons
Copy link
Author

That link describes how to set up cycling manually. This issue requests cycling as a default configuration.

Another point is that that page only cycles over 3 or 4 linestyles. A larger set of linestyles would be desirable, in line with the number colours that are cycled over in the default configuration.

@jklymak
Copy link
Member

jklymak commented Sep 27, 2023

I doubt that we plan to change the default cycle. You can create your own style sheet (https://matplotlib.org/stable/users/explain/customizing.html#defining-your-own-style) or just change your .matplotlibrc to change this all the time.

A larger set of linestyles would be desirable

Our Linestyle API is here: https://matplotlib.org/stable/gallery/lines_bars_and_markers/linestyles.html. You can use this to specify your cycler to cycle through those line styles.

@drew-parsons
Copy link
Author

For reference, the ISO 128-2:2022 standard defines 15 specific line types, https://www.iso.org/standard/83355.html
I'm not sure if there's a public version of the document though.

@jklymak
Copy link
Member

jklymak commented Sep 27, 2023

I think we would consider expanding the Linestyles API to make creation of ISO line types easier. I'm not sure if we would expand our string designation of the line styles, but a simple proposal might work.

This is somewhat orthogonal to the original request for a lifestyle cycle to be default.

@timhoffm
Copy link
Member

timhoffm commented Sep 27, 2023

I‘m -1 on line style cycling by default. Apart from the backwards compatibility aspect of changing a default, I claim that black-white print is not the most common representation of plots anymore, and that solid colored lines are better readable on colored devices than dashed or dotted lines. I‘m also not aware of any other current visualization software that cycles line styles by default.

@drew-parsons
Copy link
Author

Not entirely orthogonal: enabling easy creation of linestyle cyclers would implement this request via the option in the last paragraph: providing standardised cyclers that can be easily accessed rather than manually generating linestyle cyclers each time. I can imagine the three cases

  1. standard_cycler_color (used by default)
  2. standard_cycler_color_linestyle
  3. standard_cycler_linestyle

Perhaps ISO128_2 could be embedded in the cycler names. Incidently ISO128-2 also declares standard colours, but the preview copy I have at hand doesn't include that section. So I don't know if they're the same as matplotlib's existing set or not. For my own work I've been using the colour set designed for colour blindness recommended by https://www.nature.com/articles/nmeth.1618 (see also https://jfly.uni-koeln.de/html/manuals/pdf/color_blind.pdf)

Using the default cycler and the linestyle examples at https://matplotlib.org/stable/gallery/lines_bars_and_markers/linestyles.html, one approach could be something like

import matplotlib
from cycler import cycler

standard_linestyle_str = [
     ('solid', 'solid'),      # Same as (0, ()) or '-'
     ('dotted', 'dotted'),    # Same as (0, (1, 1)) or ':'
     ('dashed', 'dashed'),    # Same as '--'
     ('dashdot', 'dashdot')]  # Same as '-.'

standard_linestyle_tuple = [
     ('loosely dotted',        (0, (1, 10))),
     ('dotted',                (0, (1, 1))),
     ('densely dotted',        (0, (1, 1))),
     ('long dash with offset', (5, (10, 3))),
     ('loosely dashed',        (0, (5, 10))),
     ('dashed',                (0, (5, 5))),
     ('densely dashed',        (0, (5, 1))),

     ('loosely dashdotted',    (0, (3, 10, 1, 10))),
     ('dashdotted',            (0, (3, 5, 1, 5))),
     ('densely dashdotted',    (0, (3, 1, 1, 1))),

     ('dashdotdotted',         (0, (3, 5, 1, 5, 1, 5))),
     ('loosely dashdotdotted', (0, (3, 10, 1, 10, 1, 10))),
     ('densely dashdotdotted', (0, (3, 1, 1, 1, 1, 1)))]

standard_linestyles_known = standard_linestyle_str + standard_linestyle_tuple
standard_colors_known = matplotlib.rcParams['axes.prop_cycle'].by_key()['color']  # i.e. access "standard_cycler_color"
combined_style_number = min(len(standard_linestyles_known), len(standard_colors_known))

standard_cycler_color_linestyle = (cycler(color=standard_colors_known[:combined_style_number]) +
                     cycler(linestyle=[ ls[1] for ls in standard_linestyles_known[:combined_style_number]]))

standard_cycler_linestyle = cycler(linestyle=[ ls[1] for ls in standard_linestyles_known])

Then the colour/line cycler can be set with

plt.rc('axes', prop_cycle=standard_cycler_color_linestyle)

Could you point me where in the code the current default (colour) cycler is defined? The idea would be to have these standard linestyle definitions alongside the standard colour definitions.

@jklymak
Copy link
Member

jklymak commented Sep 27, 2023

The defaults are defined in our matplotlibrc:

#axes.prop_cycle: cycler('color', ['1f77b4', 'ff7f0e', '2ca02c', 'd62728', '9467bd', '8c564b', 'e377c2', '7f7f7f', 'bcbd22', '17becf'])

Other styles are defined in https://github.com/matplotlib/matplotlib/tree/main/lib/matplotlib/mpl-data/stylelib

Linestyles are defined at

def set_linestyle(self, ls):
At some point we had the idea of a Linestyle class but it looks like that didn't make it through.

Given the lack of a Linestyles class I think what you are discussing is more possible strings to set_linestyle. eg maybe 'iso15' or somesuch. Maybe long names like above would be fine too.

Conversely, you may just want to define a style which has the line cycler include the codes you want to use.

The code you cribbed from above is just an example to show how to use the (offset, onoffseq) pairs in set_linestyle.

@QuLogic
Copy link
Member

QuLogic commented Sep 29, 2023

I think changing the default cycle is not likely something to happen outside of a larger defaults change.

However, we looked at ISO 128-2 yesterday; it appears quite possible to define all their line styles based on the preview I had available:
iso128
The question does remain as to which name these should be added, or if perhaps they belong in a third-party package.

@drew-parsons
Copy link
Author

Fair enough, there's consensus to keep the default cycle is it is.

For providing ISO 128-2 definitions, would the lines submodule be the right place to keep them?

Does your ISO 128-2 preview show the ISO colours? They could be provided in the colors submodule. Alternatively an ISO submodule could be created containing both (the ISO standard also defines standard line widths)

@QuLogic
Copy link
Member

QuLogic commented Sep 30, 2023

The preview is only the first 15 or so pages; I have no idea what the colours are.

@timhoffm
Copy link
Member

The question does remain as to which name these should be added, or if perhaps they belong in a third-party package.

The third-party package doesn’t make sense. Nobody would install an additional package just for the names and the maintenance cost within Matplotlib for having an extended mapping of names to styles is low.

Linestyle names could be one or both of

  • numbers: “ISO 128-2 01”, “ISO 128-2 02”… While they are not understandable, they might be helpful for “I need N different line styles” so that doesn’t have to bother with stating individual names (but that could also be solved by an accessible list).
  • The description column in table 1, without the “line” suffix, e.g. “continuous”, “dashes spaced”.

@drew-parsons
Copy link
Author

The preview is only the first 15 or so pages; I have no idea what the colours are.

Same as me. If we do agree to go ahead implementing an ISO set, it'd probably be good manners to buy the official standard (and then also get the colours and line widths).

Might be an idea to check with ISO about copyright. I don't think it will be a problem, they want their standards used. The copyright is on the standards document, not the implementation of the standards. It fits with "Copy parts of a standard for your book or software" on p.5 of their copyright user guide. So I think it will be fine but should check with them.

Does matplotlib have funds to buy the standard? If not, might be possible to ask NumFOCUS for support.

@jklymak
Copy link
Member

jklymak commented Sep 30, 2023

My loose understanding of ISO standards is that you can't claim something is "iso" unless it's been audited by someone who can approve it as meeting the ISO spec? I'm fine with something that is "iso-esque". I'm not sure we want to claim perfect ISO compliance.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

4 participants