Пользовательский стиль линии сюжета в matplotlib
Я пытаюсь добиться графа с помощью matplotlib
с линиями с пробелами рядом с точками, как в этом:
http://simplystatistics.org/wp-content/uploads/2013/01/Beijingair.png
Я знаю о set_dashes
функция, но она устанавливает периодические тире от начальной точки без контроля над тире конечной точки.
РЕДАКТИРОВАТЬ: я сделал обходной путь, но в результате сюжет просто куча обычных линий, это не один объект. Также он использует другую библиотеку pandas
и, как ни странно, работает не совсем так, как я ожидал - я хочу равных смещений, но как-то они явно относятся к длине.
import numpy as np
import matplotlib
import matplotlib.pyplot as plt
import pandas as pd
def my_plot(X,Y):
df = pd.DataFrame({
'x': X,
'y': Y,
})
roffset = 0.1
df['x_diff'] = df['x'].diff()
df['y_diff'] = df['y'].diff()
df['length'] = np.sqrt(df['x_diff']**2 + df['y_diff']**2)
aoffset = df['length'].mean()*roffset
# this is to drop values with negative magnitude
df['length_'] = df['length'][df['length']>2*aoffset]-2*aoffset
df['x_start'] = df['x'] -aoffset*(df['x_diff']/df['length'])
df['x_end'] = df['x']-df['x_diff']+aoffset*(df['x_diff']/df['length'])
df['y_start'] = df['y'] -aoffset*(df['y_diff']/df['length'])
df['y_end'] = df['y']-df['y_diff']+aoffset*(df['y_diff']/df['length'])
ax = plt.gca()
d = {}
idf = df.dropna().index
for i in idf:
line, = ax.plot(
[df['x_start'][i], df['x_end'][i]],
[df['y_start'][i], df['y_end'][i]],
linestyle='-', **d)
d['color'] = line.get_color()
ax.plot(df['x'], df['y'], marker='o', linestyle='', **d)
fig = plt.figure(figsize=(8,6))
axes = plt.subplot(111)
X = np.linspace(0,2*np.pi, 8)
Y = np.sin(X)
my_plot(X,Y)
plt.show()
2 ответа
Хорошо, я сделал несколько удовлетворительное решение. Это многословно и все еще немного хакерски, но это работает! Он обеспечивает фиксированное смещение дисплея вокруг каждой точки, противостоит интерактивным элементам - масштабированию, панорамированию и т. Д. - и поддерживает то же смещение дисплея, что бы вы ни делали.
Это работает путем создания пользовательских matplotlib.transforms.Transform
объект для каждого патча линии в сюжете. Это, конечно, медленное решение, но графики такого типа не предназначены для использования с сотнями или тысячами точек, поэтому я думаю, что производительность не такая уж большая проблема.
В идеале все эти патчи нужно объединить в одну "сюжетную линию", но она мне подходит как есть.
import numpy as np
import matplotlib as mpl
import matplotlib.pyplot as plt
class MyTransform(mpl.transforms.Transform):
input_dims = 2
output_dims = 2
def __init__(self, base_point, base_transform, offset, *kargs, **kwargs):
self.base_point = base_point
self.base_transform = base_transform
self.offset = offset
super(mpl.transforms.Transform, self).__init__(*kargs, **kwargs)
def transform_non_affine(self, values):
new_base_point = self.base_transform.transform(self.base_point)
t = mpl.transforms.Affine2D().translate(-new_base_point[0], -new_base_point[1])
values = t.transform(values)
x = values[:, 0:1]
y = values[:, 1:2]
r = np.sqrt(x**2+y**2)
new_r = r-self.offset
new_r[new_r<0] = 0.0
new_x = new_r/r*x
new_y = new_r/r*y
return t.inverted().transform(np.concatenate((new_x, new_y), axis=1))
def my_plot(X,Y):
ax = plt.gca()
line, = ax.plot(X, Y, marker='o', linestyle='')
color = line.get_color()
size = X.size
for i in range(1,size):
mid_x = (X[i]+X[i-1])/2
mid_y = (Y[i]+Y[i-1])/2
# this transform takes data coords and returns display coords
t = ax.transData
# this transform takes display coords and
# returns them shifted by `offset' towards `base_point'
my_t = MyTransform(base_point=(mid_x, mid_y), base_transform=t, offset=10)
# resulting combination of transforms
t_end = t + my_t
line, = ax.plot(
[X[i-1], X[i]],
[Y[i-1], Y[i]],
linestyle='-', color=color)
line.set_transform(t_end)
fig = plt.figure(figsize=(8,6))
axes = plt.subplot(111)
X = np.linspace(0,2*np.pi, 8)
Y = np.sin(X)
my_plot(X,Y)
plt.show()
Можно ли просто сделать толстую белую рамку вокруг маркеров? Это не пользовательский стиль линии, а простой способ получить подобный эффект:
y = np.random.randint(1,9,15)
plt.plot(y,'o-', color='black', ms=10, mew=5, mec='white')
plt.ylim(0,10)
Здесь ключом являются аргументы
mec='white'
, цвет белого маркераms=10
, размер маркера 10 баллов (это довольно большой),mew=5
, ширина края маркера 5 точек, так что фактически точки имеют размер 10-5=5 точек.