2011-12-14 3 views
24

Чтобы указать его в общем виде, я ищу способ присоединиться к нескольким точкам с помощью линии градиента цвета с использованием matplotlib, и я не нахожу его нигде. Чтобы быть более конкретным, я планирую 2D случайное блуждание с одной цветной линией. Но, поскольку точки имеют соответствующую последовательность, я хотел бы посмотреть на график и посмотреть, куда переместились данные. Градиентная цветная линия сделала бы трюк. Или линия с постепенно меняющейся прозрачностью.Как построить линию цвета градиента в matplotlib?

Я просто пытаюсь улучшить визуализацию своих данных. Посмотрите это красивое изображение, созданное пакетом ggplot2 R. Я ищу то же самое в matplotlib. Благодарю.

enter image description here

+1

Я не уверен, что вы имеете в виду под «градиент цвета линии»: Вы имеете в виду, что (например) прогулка начинается с синей линии и постепенно меняется на красный в конце концов? Можете ли вы предоставить минимальный пример вашего текущего кода, который отображает прогулку с помощью одноцветной линии? –

+0

Я не знаю ни одного способа построения градиентов в линиях с matplotlib, хотя было бы неплохо. Я могу предложить вам использовать pycairo вместо этого, там вы можете использовать градиенты точно и получить намного больше контроля над сюжетом. Хотя вы потеряете некоторое удобство от matplotlib, например оси и автоматического диапазона данных :-( – dsign

+0

вы уверены? :(знаете ли вы о каких-либо планах по включению этой функции? Я никогда не слышал о pycairo. Можете ли вы дать мне несколько советов ? @ Mathematical.coffee: да, это то, что я имею в виду. Код может быть простым набором данных (некоторые моменты и все) – PDRX

ответ

18

Я недавно ответил на вопрос, с подобной просьбой (creating over 20 unique legend colors using matplotlib). Там я показал, что вы можете отобразить цикл цветов, который вам нужен, чтобы выстроить линии на цветную карту. Вы можете использовать ту же процедуру для получения определенного цвета для каждой пары точек.

Вы должны выбрать цветовую карту тщательно, потому что цветовые переходы вдоль вашей линии могут оказаться резкими, если цветная карта является красочной.

В качестве альтернативы, вы можете изменить альфа каждого сегмента линии, в диапазоне от 0 до 1.

В примере кода является обычным (highResPoints) расширить количество точек ваша случайная прогулка, потому что если у вас слишком мало очков, переходы могут показаться резкими. Этот бит кода был вдохновлен другой недавний ответ я представил: https://stackoverflow.com/a/8253729/717357

import numpy as np 
import matplotlib.pyplot as plt 

def highResPoints(x,y,factor=10): 
    ''' 
    Take points listed in two vectors and return them at a higher 
    resultion. Create at least factor*len(x) new points that include the 
    original points and those spaced in between. 

    Returns new x and y arrays as a tuple (x,y). 
    ''' 

    # r is the distance spanned between pairs of points 
    r = [0] 
    for i in range(1,len(x)): 
     dx = x[i]-x[i-1] 
     dy = y[i]-y[i-1] 
     r.append(np.sqrt(dx*dx+dy*dy)) 
    r = np.array(r) 

    # rtot is a cumulative sum of r, it's used to save time 
    rtot = [] 
    for i in range(len(r)): 
     rtot.append(r[0:i].sum()) 
    rtot.append(r.sum()) 

    dr = rtot[-1]/(NPOINTS*RESFACT-1) 
    xmod=[x[0]] 
    ymod=[y[0]] 
    rPos = 0 # current point on walk along data 
    rcount = 1 
    while rPos < r.sum(): 
     x1,x2 = x[rcount-1],x[rcount] 
     y1,y2 = y[rcount-1],y[rcount] 
     dpos = rPos-rtot[rcount] 
     theta = np.arctan2((x2-x1),(y2-y1)) 
     rx = np.sin(theta)*dpos+x1 
     ry = np.cos(theta)*dpos+y1 
     xmod.append(rx) 
     ymod.append(ry) 
     rPos+=dr 
     while rPos > rtot[rcount+1]: 
      rPos = rtot[rcount+1] 
      rcount+=1 
      if rcount>rtot[-1]: 
       break 

    return xmod,ymod 


#CONSTANTS 
NPOINTS = 10 
COLOR='blue' 
RESFACT=10 
MAP='winter' # choose carefully, or color transitions will not appear smoooth 

# create random data 
np.random.seed(101) 
x = np.random.rand(NPOINTS) 
y = np.random.rand(NPOINTS) 

fig = plt.figure() 
ax1 = fig.add_subplot(221) # regular resolution color map 
ax2 = fig.add_subplot(222) # regular resolution alpha 
ax3 = fig.add_subplot(223) # high resolution color map 
ax4 = fig.add_subplot(224) # high resolution alpha 

# Choose a color map, loop through the colors, and assign them to the color 
# cycle. You need NPOINTS-1 colors, because you'll plot that many lines 
# between pairs. In other words, your line is not cyclic, so there's 
# no line from end to beginning 
cm = plt.get_cmap(MAP) 
ax1.set_color_cycle([cm(1.*i/(NPOINTS-1)) for i in range(NPOINTS-1)]) 
for i in range(NPOINTS-1): 
    ax1.plot(x[i:i+2],y[i:i+2]) 


ax1.text(.05,1.05,'Reg. Res - Color Map') 
ax1.set_ylim(0,1.2) 

# same approach, but fixed color and 
# alpha is scale from 0 to 1 in NPOINTS steps 
for i in range(NPOINTS-1): 
    ax2.plot(x[i:i+2],y[i:i+2],alpha=float(i)/(NPOINTS-1),color=COLOR) 

ax2.text(.05,1.05,'Reg. Res - alpha') 
ax2.set_ylim(0,1.2) 

# get higher resolution data 
xHiRes,yHiRes = highResPoints(x,y,RESFACT) 
npointsHiRes = len(xHiRes) 

cm = plt.get_cmap(MAP) 

ax3.set_color_cycle([cm(1.*i/(npointsHiRes-1)) 
        for i in range(npointsHiRes-1)]) 


for i in range(npointsHiRes-1): 
    ax3.plot(xHiRes[i:i+2],yHiRes[i:i+2]) 

ax3.text(.05,1.05,'Hi Res - Color Map') 
ax3.set_ylim(0,1.2) 

for i in range(npointsHiRes-1): 
    ax4.plot(xHiRes[i:i+2],yHiRes[i:i+2], 
      alpha=float(i)/(npointsHiRes-1), 
      color=COLOR) 
ax4.text(.05,1.05,'High Res - alpha') 
ax4.set_ylim(0,1.2) 



fig.savefig('gradColorLine.png') 
plt.show() 

На этом рисунке показаны четыре случаи:

enter image description here

+0

приятно! Я добавлю это в свою коллекцию фрагментов кода. –

+0

@Yann: Это хорошая особенность Янн! Я не знал о цветовой карте. Но если вы удалите строку «ax.plot (x, y)», она станет лучше (меньше шума). Тем не менее, я вижу два перекрывающих цвета в каждом сегменте. Ты можешь починить это? Например, такая визуализация может помочь, если я использую 2-х цветовую карту. Однако то, что я искал, было плавным переходом цвета даже между точками. Еще лучше (но, возможно, слишком много), будет только одним цветом, но с альфа-фактором для прозрачности, который также будет меняться. Я только что редактировал мой вопрос. Проверьте изображение. – PDRX

+1

@Yann Спасибо! Ваш ответ был большой помощью. Можно видеть, что вам удобно с matplotlib. Подход «highres» умный. Когда я увидел ваш первый ответ, я сразу же решил создать дополнительные точки, чтобы сделать прогрессивную оценку, но вы сделали это через секунду! Я прыгал, там была какая-то функция buit-in в matplotlib, которая могла бы справиться с проблемой и избавить меня от написания кода и его всегда вокруг! Думаю, мне придется жить с этим. – PDRX

13

Обратите внимание, что если у вас есть много точек, вызывая plt.plot для каждой строки сегмент может быть довольно медленным. Более эффективно использовать объект LineCollection.

Использование colorline recipe вы можете сделать следующее:

import matplotlib.pyplot as plt 
import numpy as np 
import matplotlib.collections as mcoll 
import matplotlib.path as mpath 

def colorline(
    x, y, z=None, cmap=plt.get_cmap('copper'), norm=plt.Normalize(0.0, 1.0), 
     linewidth=3, alpha=1.0): 
    """ 
    http://nbviewer.ipython.org/github/dpsanders/matplotlib-examples/blob/master/colorline.ipynb 
    http://matplotlib.org/examples/pylab_examples/multicolored_line.html 
    Plot a colored line with coordinates x and y 
    Optionally specify colors in the array z 
    Optionally specify a colormap, a norm function and a line width 
    """ 

    # Default colors equally spaced on [0,1]: 
    if z is None: 
     z = np.linspace(0.0, 1.0, len(x)) 

    # Special case if a single number: 
    if not hasattr(z, "__iter__"): # to check for numerical input -- this is a hack 
     z = np.array([z]) 

    z = np.asarray(z) 

    segments = make_segments(x, y) 
    lc = mcoll.LineCollection(segments, array=z, cmap=cmap, norm=norm, 
           linewidth=linewidth, alpha=alpha) 

    ax = plt.gca() 
    ax.add_collection(lc) 

    return lc 


def make_segments(x, y): 
    """ 
    Create list of line segments from x and y coordinates, in the correct format 
    for LineCollection: an array of the form numlines x (points per line) x 2 (x 
    and y) array 
    """ 

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

N = 10 
np.random.seed(101) 
x = np.random.rand(N) 
y = np.random.rand(N) 
fig, ax = plt.subplots() 

path = mpath.Path(np.column_stack([x, y])) 
verts = path.interpolated(steps=3).vertices 
x, y = verts[:, 0], verts[:, 1] 
z = np.linspace(0, 1, len(x)) 
colorline(x, y, z, cmap=plt.get_cmap('jet'), linewidth=2) 

plt.show() 

enter image description here

3

Слишком долго для комментариев, так что просто хотел, чтобы подтвердить, что LineCollection это намного быстрее, чем для петли по линии подсегментов.

Метод LineCollection намного быстрее в моих руках.

# Setup 
x = np.linspace(0,4*np.pi,1000) 
y = np.sin(x) 
MAP = 'cubehelix' 
NPOINTS = len(x) 

Мы проверим итеративный чертеж по методу LineCollection выше.

%%timeit -n1 -r1 
# Using IPython notebook timing magics 
fig = plt.figure() 
ax1 = fig.add_subplot(111) # regular resolution color map 
cm = plt.get_cmap(MAP) 
for i in range(10): 
    ax1.set_color_cycle([cm(1.*i/(NPOINTS-1)) for i in range(NPOINTS-1)]) 
    for i in range(NPOINTS-1): 
     plt.plot(x[i:i+2],y[i:i+2]) 

1 loops, best of 1: 13.4 s per loop

%%timeit -n1 -r1 
fig = plt.figure() 
ax1 = fig.add_subplot(111) # regular resolution color map 
for i in range(10): 
    colorline(x,y,cmap='cubehelix', linewidth=1) 

1 loops, best of 1: 532 ms per loop

UPSAMPLING вашу линию для лучшего цветового градиента, так как выбранный ответ дает, по-прежнему хорошая идея, если вы хотите плавный градиент, и только вы есть несколько моментов.

1

Я добавил свое решение, используя pcolormesh Каждый отрезок линии рисуется с использованием прямоугольника, который интерполирует между цветами на каждом конце. Таким образом, это действительно интерполирует цвет, но мы должны пройти толщину линии.

import numpy as np 
import matplotlib.pyplot as plt 

def colored_line(x, y, z=None, linewidth=1, MAP='jet'): 
    # this uses pcolormesh to make interpolated rectangles 
    xl = len(x) 
    [xs, ys, zs] = [np.zeros((xl,2)), np.zeros((xl,2)), np.zeros((xl,2))] 

    # z is the line length drawn or a list of vals to be plotted 
    if z == None: 
     z = [0] 

    for i in range(xl-1): 
     # make a vector to thicken our line points 
     dx = x[i+1]-x[i] 
     dy = y[i+1]-y[i] 
     perp = np.array([-dy, dx]) 
     unit_perp = (perp/np.linalg.norm(perp))*linewidth 

     # need to make 4 points for quadrilateral 
     xs[i] = [x[i], x[i] + unit_perp[0] ] 
     ys[i] = [y[i], y[i] + unit_perp[1] ] 
     xs[i+1] = [x[i+1], x[i+1] + unit_perp[0] ] 
     ys[i+1] = [y[i+1], y[i+1] + unit_perp[1] ] 

     if len(z) == i+1: 
      z.append(z[-1] + (dx**2+dy**2)**0.5)  
     # set z values 
     zs[i] = [z[i], z[i] ] 
     zs[i+1] = [z[i+1], z[i+1] ] 

    fig, ax = plt.subplots() 
    cm = plt.get_cmap(MAP) 
    ax.pcolormesh(xs, ys, zs, shading='gouraud', cmap=cm) 
    plt.axis('scaled') 
    plt.show() 

# create random data 
N = 10 
np.random.seed(101) 
x = np.random.rand(N) 
y = np.random.rand(N) 
colored_line(x, y, linewidth = .01) 

enter image description here

Смежные вопросы