10

Is there a proper way to draw a border to outline a matplotlib plot?

The best I've found so far is this answer[1] and a matplotlib tutorial[2] that use matplotlib.patheffects to draw a slightly thicker stroke for the outline.

My issue though is that it breaks semitransparent plots, if you set alpha < 1.0 you will see the full stroke behind the main one while I'd like a true border. Is there a way to draw a real outline?

import numpy as np
import matplotlib.pyplot as plt
import matplotlib.patheffects as mpe

outline=mpe.withStroke(linewidth=8, foreground='black')

x = np.linspace(0, 2*np.pi, 1000)

plt.plot(x, np.sin(x), lw=5, color='red', path_effects=[outline],
         label="stroke no alpha")

plt.plot(x, np.sin(x-np.pi/3.), lw=5, alpha=0.5, color='red', path_effects=[outline],
         label="stroke with alpha")

plt.plot(x, np.sin(x-2*np.pi/3.), lw=5, alpha=0.5, color='red',
         label="no stroke with alpha")

plt.legend()
plt.show()

enter image description here

filippo
  • 5,197
  • 2
  • 21
  • 44
  • The main issue is with the "stroke with alpha" and "no stroke with alpha" having different colors? Or is seeing the lines underneath also relevant? – OriolAbril May 18 '18 at 23:28
  • @xg.plt.py I'd like the line with the black border to behave like a normal matplotlib line, so if you set alpha you should definitely see what's behind – filippo May 19 '18 at 01:49
  • Doesn't this already happen in your example? – OriolAbril May 19 '18 at 01:53
  • @xg.plt.py nope, in my example if I set alpha I see the border is not a border but a full black thicker line. I'd like a real border – filippo May 19 '18 at 02:07
  • 1
    "Stroke" is not really a stroke; it's a copy of the path which is a little thicker. Hence I doubt one can achieve the desired with the available patheffects. One may create a new effect though. Possibly relevant here: [In matplotlib, how can I plot a multi-colored line, like a rainbow](https://stackoverflow.com/questions/42165631/in-matplotlib-how-can-i-plot-a-multi-colored-line-like-a-rainbow). – ImportanceOfBeingErnest May 21 '18 at 21:40
  • @ImportanceOfBeingErnest yep, used *stroke* as that's what matplotlib calls it. Great pointers there, let's see if anyone wants to turn them into answer, do you? – filippo May 22 '18 at 05:49
  • I think it depends on if you need to stroke the line caps as well, whether or not you can directly use the concept of the linked question. Given that the motivation here seems to be drawn from [this answer](https://stackoverflow.com/a/50407082/4124317), a solution for the caps would be needed, right? – ImportanceOfBeingErnest May 22 '18 at 10:55
  • You are right, it started with that answer indeed. I actually looked into this a couple of times in the past too but always gave up. Ideally the solution should support everything a normal line does, so linecaps too. I don't think it would be that hard for *projecting* and *butt* capstyles, maybe *round* is a bit trickier – filippo May 22 '18 at 15:27

1 Answers1

4

There is one way of drawing a true border using alpha, in the sense that the thicker black line won't be seen beneath the red line. The trick is to plot a white line covering the unwanted part of the black line, in order to leave only the border.

Thus, the "stroke with alpha" would instead be like:

pe1 = [mpe.Stroke(linewidth=8, foreground='black'),
       mpe.Stroke(foreground='white',alpha=1),
       mpe.Normal()]

plt.plot(x, np.sin(x-np.pi/3.), color='red', label="stroke with alpha", lw=5, 
alpha=0.5, path_effects=pe1)

Which yields the following plot:

stroke

As it can be seen, this solves the color difference problem between having a border and not having one, but prevents seeing the lines below.

Depending on the application, the alpha parameter of the white layer could be set to a semitransparent value too, in order to achieve a trade-off between masking the black line to plot the border and allowing to see other lines that may be below. For instance, the following path_effect:

pe1 = [mpe.Stroke(linewidth=8, foreground='black'),
       mpe.Stroke(foreground='white',alpha=0.6),
       mpe.Normal()]

yields:

strokes alpha

which is half way between the pinkish color resulting from combining red and alpha 0.5, and seeing completely the black line beneath; while still allowing to see the other lines previously plotted.

OriolAbril
  • 7,315
  • 4
  • 29
  • 40
  • Hi, thanks for the answer, but I'm not after the pink color, I'd like to see what's behind just like `alpha` usually does. The only way for this to work would be to draw a real outline, but I don't know how... maybe some kind of clipping – filippo May 19 '18 at 02:45
  • the point, beside being a useful thing on its own, is that I need to use this in a lot more crowded plot where the amount of blending between overlapping lines is informative and this way it can be misleading – filippo May 19 '18 at 07:58