1

I almost wrote a code which plots vectors:

a = [2,-1]
b = [1,2]
d = [5,2]

def plot_vectors(**kwargs):
  M = []
  for key, value in kwargs.items():
    M.append(list([key]) + list(value))

  ax = plt.axes()
  ax.grid(b=True, which='major')

  for i in range(len(M)):
    l = 0

    for j in range(1, len(M[i])):
      l += M[i][j]**2

    l = l**0.5
    ax.text(M[i][1]/2, M[i][2]/2, f"{M[i][0]}={l:.2f}", size=14)
    ax.plot([0,M[i][1]], [0,M[i][2]])

  ax.set_aspect('equal', 'box')

plot_vectors(a=a, b=b, d=d)

enter image description here

The main idea is not to set up ax.set_xlim directly but do this automatically with ax.set_aspect('equal', 'box'). I didn't find how to do that with ax.quiver and ax.arrow. Can anybody suggest how to draw arrows here and modify y axis values to look like this: enter image description here

I modified the code to support 2D numpy arrays:

a = [2,-1]
b = [1,2]
d = [5,2]

def plot_vectors(**kwargs):
  M = []
  for key, value in kwargs.items():
    if isinstance(value, np.ndarray):
      value = value.tolist()

    for i, v in enumerate(value):
      if isinstance(v, np.ndarray):
        value[i] = value[i].tolist()

    if not isinstance(value[0], list):
      value = [[0,0], value]

    M.append([key] + value)

  ax = plt.axes()
  ax.grid(b=True, which='major')

  for i in range(len(M)):
    l = 0; pos = []
    for j in range(0, len(M[i][1])):
      pos.append(M[i][2][j] - M[i][1][j])
      l += (pos[j])**2
      pos[j] = pos[j] / 2 + M[i][1][j]

    l = l**0.5
    ax.plot([M[i][1][0], M[i][2][0]], [M[i][1][1], M[i][2][1]])
    ax.text(pos[0], pos[1], f"{M[i][0]}={l:.2f}", size=14)

  ax.set_aspect('equal', 'box')

plot_vectors(a=np.array(a), b=b, d=d, e=[d,np.array(b)])

enter image description here

My attempt with quiver:

a = [2,-1]
b = [1,2]
d = [5,2]

def plot_vectors(**kwargs):
  M = []
  for key, value in kwargs.items():
    if isinstance(value, np.ndarray):
      value = value.tolist()

    for i, v in enumerate(value):
      if isinstance(v, np.ndarray):
        value[i] = value[i].tolist()

    if not isinstance(value[0], list):
      value = [[0,0], value]
    M.append([key] + value)

  ax = plt.axes()
  ax.grid(b=True, which='major')
  print(M)
  for i in range(len(M)):
    l = 0; pos = []
    for j in range(0, len(M[i][1])):
      pos.append(M[i][2][j] - M[i][1][j])
      l += (pos[j])**2
      pos[j] = pos[j] / 2 + M[i][1][j]
    l = l**0.5
    ax.text(pos[0], pos[1], f"{M[i][0]}={l:.2f}", size=14)

  x, y, u, v = zip(*[(i[1][0], i[1][1], i[2][0], i[2][1]) for i in M])
  print(x, y, u, v)
  ax.quiver(x, y, u, v, scale=1)
  ax.set_aspect('equal', 'box')

plot_vectors(a=np.array(a), b=b, d=d, e=[d,np.array(b)])

returns:

[['a', [0, 0], [2, -1]], ['b', [0, 0], [1, 2]], ['d', [0, 0], [5, 2]],
['e', [5, 2], [1, 2]]]
(0, 0, 0, 5) (0, 0, 0, 2) (2, 1, 5, 1) (-1, 2, 2, 2)

enter image description here

Problems:

  1. Vector e was not drawn.
  2. How to assign visually distinct random color for every new vector?
  3. Part of the vector a was not drawn.
  4. How to remove decimal values from y axis?

Finally I did what I wanted with great help of @ImportanceOfBeingErnest:

import matplotlib.pyplot as plt
from matplotlib.ticker import MaxNLocator
import numpy as np

a = [2,-1]
b = [1,2]
d = [5,2]

def plot_vectors(**kwargs):
  M = []
  for key, value in kwargs.items():
    if isinstance(value, np.ndarray):
      value = value.tolist()

    for i, v in enumerate(value):
      if isinstance(v, np.ndarray):
        value[i] = value[i].tolist()

    if not isinstance(value[0], list):
      value = [[0,0], value]

    M.append([key] + value)

  plt.figure(figsize=(12,12))
  ax = plt.axes()
  ax.grid(b=True, which='major')
  ax.xaxis.set_major_locator(MaxNLocator(integer=True)); ax.yaxis.set_major_locator(MaxNLocator(integer=True))
  ax.set_aspect('equal', 'box')
  cmap = plt.get_cmap('nipy_spectral')
  lc = np.linspace(0.03, 0.99, 20)
  colors = cmap(np.insert(lc[::2], range(10), lc[::-2]))

  for i in range(len(M)):
    l = 0; pos = []

    for j in range(0, len(M[i][1])):
      pos.append(M[i][2][j] - M[i][1][j])
      l += (pos[j])**2
      pos[j] = pos[j] / 2 + M[i][1][j]

    l = l**0.5
    ax.text(pos[0], pos[1], f'{M[i][0]}={l:.2f}', size=18)

  x, y, u, v = zip(*[(i[1][0], i[1][1], i[2][0] - i[1][0], i[2][1] - i[1][1]) for i in M])
  ax.quiver(x, y, u, v, angles='xy', scale_units='xy', scale=1., color=colors[:len(M)])
  ax.plot(np.array(x)+np.array(u), np.array(y)+np.array(v), np.array(x), np.array(y), visible=False)

plot_vectors(a=np.array(a), b=b, d=d, e=np.array([d,np.array(b)]), ab=[a,b])

enter image description here

dereks
  • 544
  • 1
  • 8
  • 25

1 Answers1

2

I think you will find solutions to 2. and 4. by searching stackoverflow a bit harder. The real problem (1./3.) is that the arrow ends do not take part in the autoscaling mechanism. This is in general expected when they are not in data coordinates, but in case they are, one could expect them to change the data limits of the plot.

In any case a workaround would be to plot an invisible plot in addition to the quiver with the points from the start and end of the vectors:

ax.quiver(x, y, u, v, angles='xy', scale_units='xy', scale=1.)
ax.plot(np.array(x)+np.array(u), np.array(y)+np.array(v), np.array(x), np.array(y), visible=False)

enter image description here

ImportanceOfBeingErnest
  • 321,279
  • 53
  • 665
  • 712
  • Thank you, but it is drawing wrong: vector `e` should be from `[5, 2]` to `[1, 2]`. – dereks Aug 25 '19 at 16:28
  • No, vectors always point from (x,y) to (x+u, y+v), so the plot is correct. – ImportanceOfBeingErnest Aug 25 '19 at 16:34
  • That is why I wrote in the post that I didn't find how to do that with `ax.quiver`. The question was how to draw any vector without fancy restrictions. – dereks Aug 25 '19 at 16:39
  • There is no restrictions. You just need to supply the correct data. – ImportanceOfBeingErnest Aug 25 '19 at 16:42
  • You're right, no restrictions, but a lot of workaround for simply draw 2d vectors like in usual elementary school. Do you know any better way to do that? – dereks Aug 25 '19 at 16:57
  • u and v are the vectors you know from school. Their offset is given by x and y. So the confusing bit is just the need to use `angles='xy', scale_units='xy', scale=1.`. If instead you have start and end point of a vector you need to perform the trivial substraction `end-start` to get the vector. – ImportanceOfBeingErnest Aug 25 '19 at 17:10
  • I already did that. I'm talking about whole usability. About colors I can find what I need somewhere else, but I have no idea how to deal with decimal ticks. There are a lot of questions where they are set manually to a range, but nothing about the step size. – dereks Aug 25 '19 at 17:19
  • 1
    For the format of the ticklabels, see [this](https://stackoverflow.com/questions/29188757/matplotlib-specify-format-of-floats-for-tick-lables). If you only want ticks on integer numbers, see [this](https://stackoverflow.com/questions/30914462/matplotlib-how-to-force-integer-tick-labels) – ImportanceOfBeingErnest Aug 25 '19 at 17:25