5

I want to start the curve with one color and progressively blend into another color until the end. The following function in my MCVE works, but surely, there has to be a better way I haven't found out about, yet?!

import numpy as np
import matplotlib.pyplot as plt

def colorlist(color1, color2, num):
    """Generate list of num colors blending from color1 to color2"""
    result = [np.array(color1), np.array(color2)]
    while len(result) < num:
        temp = [result[0]]
        for i in range(len(result)-1):
            temp.append(np.sqrt((result[i]**2+result[i+1]**2)/2))
            temp.append(result[i+1])
        result = temp
    indices = np.linspace(0, len(result)-1, num).round().astype(int)
    return [result[i] for i in indices]

x = np.linspace(0, 2*np.pi, 100)
y = np.sin(x)
colors = colorlist((1, 0, 0), (0, 0, 1), len(x))

for i in range(len(x)-1):
    xi = x[i:i+1+1]
    yi = y[i:i+1+1]
    ci = colors[i]
    plt.plot(xi, yi, color=ci, linestyle='solid', linewidth='10')

plt.show()

1

2 Answers 2

11

Not sure what "better way" refers to. A solution with less code, which would draw faster is the use of a LineCollection together with a colormap.

A colormap can be defined by two colors and any colors in between are automatically interpolated.

cmap = matplotlib.colors.LinearSegmentedColormap.from_list("", [(1, 0, 0), (0, 0, 1)])

A LineCollection can be used to plot a lot of lines at once. Being a ScalarMappable it can use a colormap to colorize each line differently according to some array - in this case one may just use the x values for that purpose.

import numpy as np
import matplotlib.pyplot as plt
from matplotlib.collections import LineCollection
from matplotlib.colors import LinearSegmentedColormap

x = np.linspace(0, 2*np.pi, 100)
y = np.sin(x)

cmap = LinearSegmentedColormap.from_list("", [(1, 0, 0), (0, 0, 1)])

points = np.array([x, y]).T.reshape(-1,1,2)
segments = np.concatenate([points[:-1],points[1:]], axis=1)

lc = LineCollection(segments, cmap=cmap, linewidth=10)
lc.set_array(x)
plt.gca().add_collection(lc)
plt.gca().autoscale()
plt.show()

enter image description here

The drawback of this solution as can be see in the picture is that the individual lines are not well connected.

So to circumvent this, one may plot those points overlapping, using

segments = np.concatenate([points[:-2],points[1:-1], points[2:]], axis=1)

enter image description here


In the above the color is linearly interpolated between the two given colors. The plot therefore looks different than the one from the question using some custom interpolation.

enter image description here

To obtain the same colors as in the question, you may use the same function to create the colors used in the colormap for the LineCollection. If the aim is to simplify this function you may directly calculate the values as the square root of the color difference in the channels.

import numpy as np
import matplotlib.pyplot as plt
from matplotlib.collections import LineCollection
from matplotlib.colors import LinearSegmentedColormap

x = np.linspace(0, 2*np.pi, 100)
y = np.sin(x)

def colorlist2(c1, c2, num):
    l = np.linspace(0,1,num)
    a = np.abs(np.array(c1)-np.array(c2))
    m = np.min([c1,c2], axis=0)
    s  = np.sign(np.array(c2)-np.array(c1)).astype(int)
    s[s==0] =1
    r = np.sqrt(np.c_[(l*a[0]+m[0])[::s[0]],(l*a[1]+m[1])[::s[1]],(l*a[2]+m[2])[::s[2]]])
    return r

cmap = LinearSegmentedColormap.from_list("", colorlist2((1, 0, 0), (0, 0, 1),100))

points = np.array([x, y]).T.reshape(-1,1,2)
segments = np.concatenate([points[:-2],points[1:-1], points[2:]], axis=1)

lc = LineCollection(segments, cmap=cmap, linewidth=10)
lc.set_array(x)
plt.gca().add_collection(lc)
plt.gca().autoscale()
plt.show()

enter image description here

Sign up to request clarification or add additional context in comments.

6 Comments

How does one change this to work on Y-axis values instead?
@TimStack You could use something like colors = [cmap(k) for k in y[:-1]] and lc = LineCollection(segments, colors=colors, linewidth=10)
@Not_a_programmer I am afraid that only changes the colour hues
@TimStack Apparently, I'm missing the point then. I thought your question was about changing the line's color depending on its y value. That's what my code intends to do. However, I forgot to mention that y values must be normalized, as shown in this post.
@Not_a_programmer That's correct. However, with the code above and your amendments, the hues of the last figure in this post are changed, and are still dependent on the X-axis
|
0

In response to a comment above: If you want to change the color depending on the y value, you can use the following code:

import numpy as np
import matplotlib.pyplot as plt
from matplotlib.collections import LineCollection
from matplotlib.colors import LinearSegmentedColormap

x = np.linspace(0, 2 * np.pi, 100)
y = np.sin(x)
ynorm = (y - y.min()) / (y.max() - y.min())


def colorlist2(c1, c2, num):
    l = np.linspace(0, 1, num)
    a = np.abs(np.array(c1) - np.array(c2))
    m = np.min([c1, c2], axis=0)
    s = np.sign(np.array(c2) - np.array(c1)).astype(int)
    s[s == 0] = 1
    r = np.sqrt(np.c_[(l * a[0] + m[0])[::s[0]],
                      (l * a[1] + m[1])[::s[1]], (l * a[2] + m[2])[::s[2]]])
    return r


cmap = LinearSegmentedColormap.from_list(
    "", colorlist2((1, 0, 0), (0, 0, 1), 100))
colors = [cmap(k) for k in ynorm[:-1]]

points = np.array([x, y]).T.reshape(-1, 1, 2)
segments = np.concatenate([points[:-2], points[1:-1], points[2:]], axis=1)

lc = LineCollection(segments, colors=colors, linewidth=10)
lc.set_array(x)
plt.gca().add_collection(lc)
plt.gca().autoscale()
plt.show()

This will output this graph:

Graph with color depending on y value

4 Comments

I ran the code in your post, but the resulting figure still has colours dependant on the x-axis.. odd!
Are you sure that you used the y values as inputs for the colors instead of the x values? That's the only thing I can think about
I have simply copied your code without make any alterations
This is very strange as the colormap of your graph is also different. I'm afraid I'm out of ideas about what could've gone wrong. Sorry, I can't help you.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.