Skip to content

DOC: New color line by value example #28307

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

Merged
merged 2 commits into from
Jun 5, 2024

Conversation

eytanadler
Copy link
Contributor

PR summary

I opened issue #28242, which is asking for better ways to plot a line with a color at each point assigned by a third value. @timhoffm suggested a nice way to do this. This PR adds a new example that implements his improved method.

@timhoffm said this should replace this example. @rcomer also thought this example could be replaced by it. I haven't removed either of the examples since I want to be clear on the expectations before I start deleting previous work.

The approach in previous methods produce results that look something like this:
image

The new approach in the provided example applied to the same plot produces this:
image

PR checklist

@github-actions github-actions bot added the Documentation: examples files in galleries/examples label May 26, 2024
Copy link

@github-actions github-actions bot 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 us on gitter for real-time discussion.

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

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

@jklymak
Copy link
Member

jklymak commented May 28, 2024

https://matplotlib.org/stable/gallery/lines_bars_and_markers/multicolored_line.html is the correct example to change. If it were me I'd add a second plot to the simpler example. I agree that this strategy is superior to what is in the current example, but it is more convoluted to follow, so I think there is value in both.

@timhoffm
Copy link
Member

https://matplotlib.org/stable/gallery/lines_bars_and_markers/multicolored_line.html is the correct example to change. If it were me I'd add a second plot to the simpler example. I agree that this strategy is superior to what is in the current example, but it is more convoluted to follow, so I think there is value in both.

Indeed, it makes sense to collect the content under the multicolored_line example. We could keep parts of the original example. Some suggestions on that:

  • Drop the second part of the existing example. Using a BoundaryNorm for discrete coloring is an orthogonal topic and additionally likely more of a niche case for line coloring. So let's not clutter the example with that.
  • The existing example nicely conceals the topic that the color information is between the points instead of at the points by choosing the derivative as a quantity that lives between the points.
    This would be a good way of distinguishing the two approaches. If the to-be-colored information is between the points, use the simple segments. If you have information at the points, use the more advanced segmentation.

Comment on lines 18 to 24
def colored_line(x, y, c, ax, **lc_kwargs):
"""
Plot a line with a color specified along the line by a third value. It does this
by creating a collection of line segments. Each line segment is made up of two
straight lines each connecting the current (x, y) point to the midpoints of the
lines connecting the current point with its two neighbors. This creates a smooth
line with no gaps between the line segments.
Copy link
Contributor

Choose a reason for hiding this comment

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

This is very minor, but I suggest that you add a new line to reflect standard docstring conventions.

Suggested change
def colored_line(x, y, c, ax, **lc_kwargs):
"""
Plot a line with a color specified along the line by a third value. It does this
by creating a collection of line segments. Each line segment is made up of two
straight lines each connecting the current (x, y) point to the midpoints of the
lines connecting the current point with its two neighbors. This creates a smooth
line with no gaps between the line segments.
def colored_line(x, y, c, ax, **lc_kwargs):
"""
Plot a line with a color specified along the line by a third value.
It does this by creating a collection of line segments. Each line segment
is made up of two straight lines each connecting the current (x, y) point
to the midpoints of the lines connecting the current point with its two
neighbors. This creates a smooth line with no gaps between the line
segments.

@eytanadler
Copy link
Contributor Author

Based on feedback, I merged the new example into the multicolored line example. It's now a bit verbose, but my goal was to put the code into well-documented functions so that using them is easy. That way users can copy these functions and use them for their own purposes without having to do too much parsing of the example.

Copy link
Member

@timhoffm timhoffm left a comment

Choose a reason for hiding this comment

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

I suggest you make two independent code blocks for the two plots. It's simpler to follow only half of the code that is relevant for the respective plot. You may also consider giving the two variants separate sections.

Docstring structure:

"""
==================
Multicolored lines
==================

...

Color values at points
----------------------

"""
<first code block>
####################################################################
# Color values between points
# ---------------------------
# ...
<second code block>

Copy link
Member

@timhoffm timhoffm left a comment

Choose a reason for hiding this comment

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

Minor style suggestions.

matplotlib.collections.LineCollection
The generated line collection representing the colored line.
"""
# Give a warning if the user specified an array keyword argument
Copy link
Member

@timhoffm timhoffm Jun 1, 2024

Choose a reason for hiding this comment

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

Suggested change
# Give a warning if the user specified an array keyword argument

The comment does not add information that is not already obvious from the code.

Comment on lines 74 to 76
# Create the line collection and set the colors of each segment
lc = LineCollection(segments, **default_kwargs)
lc.set_array(c)
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
# Create the line collection and set the colors of each segment
lc = LineCollection(segments, **default_kwargs)
lc.set_array(c)
lc = LineCollection(segments, **default_kwargs)
lc.set_array(c) # set the colors of each segment

a bit more compact

lc = LineCollection(segments, **default_kwargs)
lc.set_array(c)

# Add the collection to the axis and return the line collection object
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
# Add the collection to the axis and return the line collection object
# Add the collection to the Axes and return the line collection object

But IMHO the comment is on the border of not-needed.

matplotlib.collections.LineCollection
The generated line collection representing the colored line.
"""
# Give a warning if the user specified an array keyword argument
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
# Give a warning if the user specified an array keyword argument

as above

Comment on lines 145 to 146
lines = colored_line(x, y, color, ax1, linewidth=10, cmap="plasma")
cax1 = fig1.colorbar(lines) # add a color legend
Copy link
Member

@timhoffm timhoffm Jun 1, 2024

Choose a reason for hiding this comment

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

Suggested change
lines = colored_line(x, y, color, ax1, linewidth=10, cmap="plasma")
cax1 = fig1.colorbar(lines) # add a color legend
lines = colored_line(x, y, color, ax1, linewidth=10, cmap="plasma")
fig1.colorbar(lines) # add a color legend

If return values are not used, don't assign them to variables. This keeps the code more readable.

Comment on lines 163 to 164
line = colored_line_between_pts(x, y, dydx, ax2, linewidth=2, cmap="viridis")
cax2 = fig2.colorbar(line, ax=ax2)
Copy link
Member

@timhoffm timhoffm Jun 1, 2024

Choose a reason for hiding this comment

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

Suggested change
line = colored_line_between_pts(x, y, dydx, ax2, linewidth=2, cmap="viridis")
cax2 = fig2.colorbar(line, ax=ax2)
line = colored_line_between_pts(x, y, dydx, ax2, linewidth=2, cmap="viridis")
fig2.colorbar(line, ax=ax2, label="dy/dx")

Comment on lines 168 to 171

# Labels
ax2.set_title("Color between points")
cax2.set_label("dy/dx")
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
# Labels
ax2.set_title("Color between points")
cax2.set_label("dy/dx")
ax2.set_title("Color between points")

@eytanadler eytanadler requested a review from timhoffm June 4, 2024 15:16
@QuLogic QuLogic linked an issue Jun 4, 2024 that may be closed by this pull request
Copy link
Member

@timhoffm timhoffm left a comment

Choose a reason for hiding this comment

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

Sorry, I still found some nits, but let's try to be as precise and helpful to users as possible.

Also please squash your commits. They currently add and remove a file, which CI flags as unnecessary overhead.

Comment on lines 71 to 73
coord_start = np.column_stack((x_midpts[:-1], y_midpts[:-1])).reshape(x.size, 1, 2)
coord_mid = np.column_stack((x, y)).reshape(x.size, 1, 2)
coord_end = np.column_stack((x_midpts[1:], y_midpts[1:])).reshape(x.size, 1, 2)
Copy link
Member

Choose a reason for hiding this comment

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

Optional, but you could use a[:, np.newaxis, :] instead of a.reshape(x.size, 1, 2), wich is slightly more telling that you only introduce an extra dimension.

Suggested change
coord_start = np.column_stack((x_midpts[:-1], y_midpts[:-1])).reshape(x.size, 1, 2)
coord_mid = np.column_stack((x, y)).reshape(x.size, 1, 2)
coord_end = np.column_stack((x_midpts[1:], y_midpts[1:])).reshape(x.size, 1, 2)
coord_start = np.column_stack((x_midpts[:-1], y_midpts[:-1]))[:, np.newaxis, :]
coord_mid = np.column_stack((x, y))[:, np.newaxis, :]
coord_end = np.column_stack((x_midpts[1:], y_midpts[1:]))[:, np.newaxis, :]


# Compute the midpoints of the line segments. Include the first and last points
# twice so we don't need any special syntax later to handle them.
x_midpts = np.hstack((x[0], 0.5 * (x[1:] + x[:-1]), x[-1]))
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
x_midpts = np.hstack((x[0], 0.5 * (x[1:] + x[:-1]), x[-1]))
x = np.asarray(x)
y = np.asarray(y)
x_midpts = np.hstack((x[0], 0.5 * (x[1:] + x[:-1]), x[-1]))

Otherwise, we'd have to claim "array" instead of "array-like" in the docstring.

plt.show()


####################################################################
Copy link
Member

Choose a reason for hiding this comment

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

Do we want an additional plot to illustrate the mechanism?

Suggested change
####################################################################
####################################################################
# This method is designed to give a smooth impression when distances and color
# differences between adjacent points are not too large. The following example
# does not meet this criteria and by that serves to illustrate the segmentation
# and coloring mechanism.
x = [0, 1, 2, 3, 4]
y = [0, 1, 2, 1, 1]
c = [1, 2, 3, 4, 5]
fig, ax = plt.subplots()
ax.scatter(x, y, c=c, cmap='rainbow')
colored_line(x, y, c=c, ax=ax, cmap='rainbow')
####################################################################


Parameters
----------
x, y : array-like or scalar
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
x, y : array-like or scalar
x, y : array-like

scalars do not work because you index below. But that's ok, let's just not bother with them.

----------
x, y : array-like or scalar
The horizontal and vertical coordinates of the data points.
c : array-like or scalar
Copy link
Member

Choose a reason for hiding this comment

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

As above

Suggested change
c : array-like or scalar
c : array-like

@eytanadler
Copy link
Contributor Author

Sorry, I still found some nits, but let's try to be as precise and helpful to users as possible.

Also please squash your commits. They currently add and remove a file, which CI flags as unnecessary overhead.

These are useful fixes, thanks! I tried fixing the git history, but I think I may have made it worse. What git commands do I need to properly squash the previous commits?

@rcomer
Copy link
Member

rcomer commented Jun 5, 2024

@eytanadler to squash, you can do an interactive rebase. I strongly recommend saving a backup copy of your branch first if you have not done this (much) before.

@eytanadler eytanadler force-pushed the colorline_example branch from bad8da4 to 19bfcf5 Compare June 5, 2024 18:16
@timhoffm timhoffm merged commit 7ccfd38 into matplotlib:main Jun 5, 2024
20 checks passed
@timhoffm
Copy link
Member

timhoffm commented Jun 5, 2024

Thanks @eytanadler and congratulations on your first contribution to Matplotlib 🎉. We hope to hear from you again.

@timhoffm timhoffm added this to the v3.9.1 milestone Jun 5, 2024
@timhoffm
Copy link
Member

timhoffm commented Jun 5, 2024

@meeseeksdev backport to v3.9.x

@eytanadler
Copy link
Contributor Author

Thank you @timhoffm! I’ve gotten so much use out of matplotlib that contributing an example is the least I can do.

@eytanadler eytanadler deleted the colorline_example branch June 5, 2024 21:40
timhoffm added a commit that referenced this pull request Jun 5, 2024
…307-on-v3.9.x

Backport PR #28307 on branch v3.9.x (DOC: New color line by value example)
muchojp pushed a commit to muchojp/matplotlib that referenced this pull request Jun 6, 2024
* New color line example with multiple coloring methods
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Documentation: examples files in galleries/examples
Projects
Status: Waiting for author
Development

Successfully merging this pull request may close these issues.

[ENH]: Color along line by a value
5 participants