FazBrowse GitHub Viewer | Trending |
URL:
| Home
Tools: [Download Repo ZIP]   [Original HTTPS Page]

[ENH] Support for Multi-Color Line Legends in Plots · Issue #28304 · matplotlib/matplotlib · GitHub

Repository navigation

[ENH] Support for Multi-Color Line Legends in Plots #28304

Open

Description

Need

This enhancement proposes adding native support for multi-color lines in the legend of line (and other styles!) plots in Matplotlib. This feature will allow users to represent data series with gradients or multiple colors more intuitively within the plot legends.

Proposed solution

Hello, awesome matplotlib people!

Why This Enhancement is Needed

  • Improved Data Visualization: Multi-color lines in legends can significantly enhance the readability and interpretability of plots, especially in cases where data series represent a range of values or a transition over time.
  • Consistency: Currently, users need to implement custom handlers for multi-color lines, which can be cumbersome and inconsistent. Native support will standardize the implementation.
  • User Demand: As data visualizations become more complex and integral to analysis, the demand for more advanced and intuitive plotting features increases. This feature aligns with modern data visualization needs.

Benefits

  • Enhanced Clarity: Users can visually match plot lines with legend entries more easily when colors in the legend reflect the actual colors used in the plot.
  • Time-Saving: Eliminates the need for users to write and maintain custom legend handlers for multi-color lines.
  • Professional Presentation: Provides a more polished and professional look to data visualizations, which is crucial for presentations and publications.

Proposed Implementation

Add support in the Legend class for a new handler that can process and display multi-color lines.
This handler will generate a line segment with a gradient or series of colors, consistent with the corresponding plot line.

My current implementation:

import matplotlib.pyplot as plt
import numpy as np
from matplotlib.collections import LineCollection
from matplotlib.legend_handler import HandlerLineCollection
from matplotlib.colors import Normalize
import cmasher as cmr

class HandlerColorLineCollection(HandlerLineCollection):
    def __init__(self, cmap, **kwargs):
        self.cmap = cmap
        super().__init__(**kwargs)
    
    def create_artists(self, legend, artist, xdescent, ydescent, width, height, fontsize, trans):
        x = np.linspace(0, width, self.get_numpoints(legend) + 1)
        y = np.zeros(self.get_numpoints(legend) + 1) + height / 2. - ydescent
        points = np.array([x, y]).T.reshape(-1, 1, 2)
        segments = np.concatenate([points[:-1], points[1:]], axis=1)
        lc = LineCollection(segments, cmap=self.cmap, transform=trans)
        lc.set_array(x)
        lc.set_linewidth(artist.get_linewidth())
        return [lc]

def add_normalized_line_collection(ax, cmap, linewidth=3, linestyle='-'):
    norm = Normalize(vmin=0., vmax=1.)
    t = np.linspace(0, 1, 100)  # Smooth gradient
    lc = LineCollection([np.column_stack([t, t * 0])], cmap=cmap, norm=norm, linewidth=linewidth, linestyle=linestyle)
    lc.set_array(np.linspace(0., 1, len(t)))  # Ensure this spans 0 to 1 for correct normalization
    ax.add_collection(lc)  # Add the LineCollection to the axis
    return lc

# Sample data
x_data = np.logspace(1, 3, 50)
y_data = np.random.rand(3, len(x_data))

fig, ax = plt.subplots(figsize=(8, 6))

colors = cmr.take_cmap_colors('cmr.rainforest', 3, cmap_range=(0.15, 0.85), return_fmt='hex')
for i in range(y_data.shape[0]):
    ax.plot(x_data, y_data[i], color=colors[i], lw=3)
    
    ax.set_xlabel('$x$', fontsize=18)
    ax.set_ylabel('$y$', fontsize=18)

# Create color lines for legend
color_line = add_normalized_line_collection(ax, cmap="cmr.rainforest", linewidth=4)

# Existing legend handles and labels
handles, labels = ax.get_legend_handles_labels()
handles.append(color_line)
labels.append("Colormap-based Line")

ax.legend(handles, labels, handler_map={color_line: HandlerColorLineCollection(cmap="cmr.rainforest", numpoints=30)}, loc="upper right", frameon=True, fontsize=15)

plt.show()

Proposed usage

import matplotlib.pyplot as plt
import numpy as np
import cmasher as cmr

# Sample data
x_data = np.logspace(1, 3, 50)
y_data = np.random.rand(3, len(x_data))

fig, ax = plt.subplots(figsize=(8, 6))

colors = cmr.take_cmap_colors('cmr.rainforest', 3, cmap_range=(0.15, 0.85), return_fmt='hex')
for i in range(y_data.shape[0]):
    ax.plot(x_data, y_data[i], color=colors[i], lw=3)
    
    ax.set_xlabel('$x$', fontsize=18)
    ax.set_ylabel('$y$', fontsize=18)

# New API for colormap-based lines in legend
ax.legend(use_colormap=True, loc="upper right", frameon=True, fontsize=15, new_arg='cmr.rainforest', )

plt.show()

Please note that it would be beneficial to allow passing either a colormap or a list of colors (in HEX) to this new argument (new_arg) in the legend. Ideally, the legend object should be able to internally determine the colors used in the plot lines, making the process seamless and intuitive for the user.

I am also including a version from a colleague.

Many thanks!
Niko

Activity

  1. gpxxlt commented on Nov 24, 2024

    Contributor

    Hi Niko,

    We are a team of new contributors and would like to take on this issue. We plan to work on it for about two weeks, and will get back to you if we make significant progress.

  2. jklymak commented on Nov 24, 2024

    Member

    Since the color usually represents a value, what is the utility of putting it in the legend, versus having a proper colorbar with ticks showing those values?

  3. nikosarcevic commented on Nov 25, 2024

    Author

    Since the color usually represents a value, what is the utility of putting it in the legend, versus having a proper colorbar with ticks showing those values?

    Hi!
    That's a fair point, and a colorbar is often a great choice. However, it’s not always the simplest or most effective option. For example, in the plot I’ve shared, the data represents the binned PDF. In this context, the legend provides a clearer representation of the data compared to a colorbar.

  4. timhoffm commented on Aug 21, 2025

    Member

    the legend provides a clearer representation of the data compared to a colorbar.

    @nikosarcevic Hiw do I know what the different colors represent?

  5. nikosarcevic commented on Aug 21, 2025

    Author

    the legend provides a clearer representation of the data compared to a colorbar.

    @nikosarcevic Hiw do I know what the different colors represent?

    hi @timhoffm, thanks for the question.
    in my use case, the colors don’t map to a continuous physical value like temperature or density (where a colorbar would indeed be appropriate). instead, they indicate categories / binned ranges (e.g. a PDF split into intervals). the multicolor line in the legend makes it immediately clear which bins are combined into one curve, while the legend text itself specifies what the colors mean.
    so the colors in the legend are not standalone semantics — they’re reinforcing the label. the improvement I’m suggesting is just to make the visual correspondence between plotted line and legend entry more accurate without needing custom handlers.

  6. timhoffm commented on Aug 22, 2025

    Member

    Thanks for the explanation. So generally, this only makes sense to express segments of a whole.

    Overall, I don't think we can reasonably put this into legend() API or even make this a default behavior.

    There are cases where you don't want/need this: e.g. a scatter plot with two groups A, B using different markers - here the legend only encodes the marker type -> group mapping, but the color would still be encoded via the colorbar. Showing colored markers is rather a distraction in that case.

    Also, for markers, you need to decide how many / which colors should be used. That must be done explicitly by the user.

    I believe the reasonable approach here is still to opt-in via ax.legend(handles, labels, handler_map=...). I would be willing to accept a pull request with an appropriate Handler plus an example.

  7. tacaswell commented on Aug 22, 2025

    Member

    In the case of the N vs redshift figure am I to understand the markers and lines to be two different measures of the same thing (from the names atleast one of them a simulation)? In that case I would go with a 2 part legend that uses a black line and black marker to label "LSST SRD" and "SkySim5000" + 5 colored rectangles labeled with whatever the categorical is (or just drop the color labels in the legend and rely on the figure caption to do that work as the location of the peak also looks relevant). [sorry, you came asking for a feature and you are getting paper reviewer number 2 ;) ]

  8. nikosarcevic commented on Aug 22, 2025

    Author

    Thanks for the explanation. So generally, this only makes sense to express segments of a whole.

    Overall, I don't think we can reasonably put this into legend() API or even make this a default behavior.

    There are cases where you don't want/need this: e.g. a scatter plot with two groups A, B using different markers - here the legend only encodes the marker type -> group mapping, but the color would still be encoded via the colorbar. Showing colored markers is rather a distraction in that case.

    Also, for markers, you need to decide how many / which colors should be used. That must be done explicitly by the user.

    I believe the reasonable approach here is still to opt-in via ax.legend(handles, labels, handler_map=...). I would be willing to accept a pull request with an appropriate Handler plus an example.

    Thanks for clarifying. I see your point about not making this the default behavior. My suggestion was less about replacing colorbars or standard legends, and more about lowering the barrier for cases where multi-color lines are meaningful (segments of a whole, categorical bins, etc.). Right now, users need to write custom Handler code for this, which is a bit of a hurdle. Having an “official” handler and example in the library would go a long way toward consistency and discoverability, while still requiring explicit opt-in via handler_map.
    I’ll take this on and put together a proper PR or at least an example as soon as I can — I’m currently tied up finishing some code I’m preparing for publication. Thanks for the thoughtful feedback!

  9. nikosarcevic commented on Aug 22, 2025

    Author

    In the case of the N vs redshift figure am I to understand the markers and lines to be two different measures of the same thing (from the names atleast one of them a simulation)? In that case I would go with a 2 part legend that uses a black line and black marker to label "LSST SRD" and "SkySim5000" + 5 colored rectangles labeled with whatever the categorical is (or just drop the color labels in the legend and rely on the figure caption to do that work as the location of the peak also looks relevant). [sorry, you came asking for a feature and you are getting paper reviewer number 2 ;) ]

    Hi! That’s a fair “reviewer #2” perspective 🙂 (which reminds me I have 2 journal reviews to finish -- as a reviewer ahahahah). In my case, the lines and markers do indeed come from different sources (theoretical prediction as solid lines vs. simulation as circles), and the color segments represent redshift bins. A two-part legend with shared black line/marker plus colored rectangles could work, but it feels like a workaround compared to showing the actual multi-color line in the legend.

    Just to add some context: in the plot I shared, what we have is the overall redshift distribution of galaxies (a single PDF), which in cosmology we almost always split tomographically (see Figure 9 of this paper I co-authored ). There can also be different distributions for different galaxy samples, each binned tomographically. For example, I might start with sample 1 in pink and sample 2 in blue; after binning, the sub-bins of each sample become important since we look at correlations within and across bins (e.g. pink1–pink2, or pink1–blue3). Using gradients (all pink shades for sample 1, all blue shades for sample 2) makes this structure and “story” much clearer at a glance.
    Later on, when we compute correlations between bins — for instance calculating the power spectra for each bin combination — we can carry this through by coloring each spectrum with the same scheme (e.g. spectra involving pink1 get a pink-shaded curve). That consistency between the PDF plot and later correlation plots really helps readability.

    That’s why having the legend mirror the plotted objects directly would be helpful — it reinforces the visual correspondence without extra workarounds. I’ll put together a proper PR or at least a concrete example as soon as I can (currently tied up finishing some code for publication). Thanks for the thoughtful feedback and I hope I explained my reasoning!

  10. jklymak commented on Aug 22, 2025

    Member

    Having consistent colors between visualizations is great. However, I don't see any added value in the legend you show above with four different colored markers or lines - I just need to know what the symbols mean, and somewhere else I'd need to know what the colors mean; I can already see that there are four colors and I don't need that info duplicated in the legend unless there is a key to what the colors mean. If I were publishing this, I'd do as @tacaswell suggests, or I'd just have two markers in the legend for the first color, and then label the 4 distributions directly on the plot, perhaps with coloured text.

  11. story645 commented on Aug 22, 2025

    Member

    Using gradients (all pink shades for sample 1, all blue shades for sample 2) makes this structure and “story” much clearer at a glance.

    Sounds to me like you're doing a variant on math augmentation which is a visualization technique we should support folks using more easily. I appreciate you taking the visualization feedback in good sport, but I also think your initial ask for us to improve support for a (now somewhat common) visualization technique is also perfectly reasonable.

  12. jklymak commented on Aug 22, 2025

    Member

    which is a visualization technique we should support folks using more easily. I appreciate you taking the visualization feedback in good sport, but I also think your initial ask for us to improve support for a (now somewhat common) visualization technique is also perfectly reasonable.

    These plots are super-vanilla, so it's not clear why they need a different, non-vanilla, legend handler, or why that would "improve support".

    Of course if @nikosarcevic wants to write a legend handler they refer, that is great, and we have that flexibility. But I'd need stronger evidence that this conveys more information and is common in published works to be in favour of adding to the main library.

  13. story645 commented on Aug 24, 2025

    Member

    so it's not clear why they need a different, non-vanilla, legend handler,

    Because the vanilla legend handler associates a color (black) that is not used in the plot with the plot, while the goal is to use consistent semiotics (here the color set) to represent the same concept across the paper - including across multiple types of plots.

  14. jklymak commented on Aug 24, 2025

    Member

    My point is that the colors would ideally be labeled so the reader can know what they mean instead of just showing the colors; this is easy with the built-in handler; sure there is no legend entry for the data in the first three categories, but I don't think anyone would be confused:

  15. story645 commented on Aug 24, 2025

    Member

    My point is that the colors would ideally be labeled so the reader can know what they mean instead of just showing the colors

    The way this type of color coding generally works is that the bins/colors are defined somewhere early in the paper (something like notation) and then the color band is used as short hand. The visual form of defining math notation or a term and then using it throughout and is particularly helpful for communicating what's being shown across complex multiple views.

    If I'm understanding correctly that @nikosarcevic is trying to do something like this, I think it's up to the reviewers of the paper to decide if it's being used effectively. I think the question on the Matplotlib side (as an unopinionated visualization library) is if it's in scope to facilitate easier use of the technique, & going by #28304 (comment) this request is in scope.

  16. timhoffm commented on Aug 24, 2025

    Member

    As an overall judgement, this is not a terribly important feature, but there are some cases, in which it can be useful. I wouldn’t invest time into this myself, but I somebody provides a PR with reasonable handlers, I think accepting that is a good tradeoff between making this use case easier and maintainer effort.

  17. jklymak commented on Aug 24, 2025

    Member

    I'd be strongly against adding this to legend proper

    I'm also against us curating bespoke handlers. You could imagine a pretty Byzantine API for the proposal here to make it generalizable.

    I'd be fine with a new legend/handler example in the gallery, though it seems redundant with the ones already in the Legend Guide that already show how to do this.

    As for the case where the colors are defined elsewhere, that would be a risky technique given figures are often presented out of the context of the original paper. But even if that were deemed useful, just repeating the colors in the legend with no labels doesn't help the reader at all. If I wanted the more compact legend, I'd just choose one of the colors for each of "model" and "data" and have two normal legend entries. Again if someone wants to craft a bespoke handler to make that look cooler that's fine, but I don't think we should be encouraging PRs for everyone's bespoke handlers to become part of the supported code base.

  18. story645 commented on Aug 24, 2025

    Member

    But even if that were deemed useful, just repeating the colors in the legend with no labels doesn't help the reader at all.

    I don't think it's our place as maintainers to evaluate a visualization technique on whether we personally think it's useful b/c we come from different fields with different conventions. We are also not privy to the full paper - maybe the exact color values aren't important in this specific plot but the ordinal grouping (communicated by hue) is. Either way, color coding semantically linked information is a pretty standard pedogical and visualization technique.

    I don't think we should be encouraging PRs for everyone's bespoke handlers

    Me and @timhoffm are supporting adding a specific handler that adds better support of matching legend key to the colors of the plot it is a legend for, when using line collection in a way that folks have previously asked for. Even if @nikosarcevic added a separate colorbar, it would still be beneficial to have legend key elements that better match the element they are the key for.

  19. jklymak commented on Aug 25, 2025

    Member

    I don't think it's our place as maintainers to evaluate a visualization technique on whether we personally think it's useful b/c we come from different fields with different conventions.

    It is our place as maintainers to determine if new features should be added to the library. Typically we do so on the basis of a) whether or not the feature is easy to implement outside the library, b) whether the feature has a large community that has it as a standard, c) if the feature is demonstrably superior to existing methods, d) maintainability. My comments were only in respect to those criteria, not whether I think anyone should use the proposed legend handler in any given context.

    Of course if you and @timhoffm want to start shepherding more legend handlers, including the ones that this one inspires, thats fine - as @timhoffm says, they are off to the side, and probably don't affect most users very much. I would tend to be parsimonious like we are for colormaps, but that is just my vote.

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

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions


      Back | FazBrowse Home | New Git URL