matplotlib中的自定义绘图线型 [英] Custom plot linestyle in matplotlib

查看:237
本文介绍了matplotlib中的自定义绘图线型的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我正在尝试使用matplotlib来实现图形,该图形的点附近有空格,像这样:


(来源: simplystatistics.org ) sub>

我了解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()

解决方案

好的,我提出了一个令人满意的解决方案.它是罗and的,仍然有点破烂,但它的工作原理!它在每个点周围提供一个固定的显示偏移量,它与交互式内容(缩放,平移等)相对立,并且无论您执行什么操作,都保持相同的显示偏移量.

通过为图中的每个线段创建自定义的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()

I'm trying to achieve graph using matplotlib with lines with whitespaces near points like in this one:


(source: simplystatistics.org)

I know about set_dashes function, but it sets periodic dashes from start-point without control over end-point dash.

EDIT: I made a workaround, but the resulting plot is just a bunch of usual lines, it is not a single object. Also it uses another library pandas and, strangely, works not exactly as I expected - I want equal offsets, but somehow they are clearly relative to the length.

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()

解决方案

Ok, I've made a somewhat satisfactory solution. It is wordy and still a bit hackish, but it works! It provides a fixed display offset around each point, it stands against interactive stuff - zooming, panning etc - and maintains the same display offset whatever you do.

It works by creating a custom matplotlib.transforms.Transform object for each line patch in a plot. It is certainly a slow solution, but plots of this kind are not intended to be used with hundreds or thousands of points, so I guess performance is not such a big deal.

Ideally, all those patches are needed to be combined into one single "plot-line", but it suits me as it is.

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()

这篇关于matplotlib中的自定义绘图线型的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆