0

For my report, I'm creating a special color plot in jupyter notebook. There are two parameters, x and y.

import numpy as np

x = np.arange(-1,1,0.1)
y = np.arange(1,11,1)

with which I compute a third quantity. Here is an example to demonstrate the concept:

values = []
for i in range(len(y)) :
    z = y[i] * x**3
    # in my case the value z represents phases of oscillators
    # so I will transform the computed values to the intervall [0,2pi)
    values.append(z)
values = np.array(values) % 2*np.pi 

I'm plotting y vs x. For each y = 1,2,3,4... there will be a horizontal line with total length two. For example: The coordinate (0.5,8) stands for a single point on line 8 at position x = 0.5 and z(0.5,8) is its associated value.

Now I want to represent each point on all ten lines with a unique color that is determined by z(x,y). Since z(x,y) takes only values in [0,2pi) I need a color scheme that starts at zero (for example z=0 corresponds to blue). For increasing z the color continuously changes and in the end at 2pi it takes the same color again (so at z ~ 2pi it becomes blue again).

Does someone know how this can be done in python?

JohanC
  • 71,591
  • 8
  • 33
  • 66
  • Does this answer your question? [Matplotlib scatterplot; colour as a function of a third variable](https://stackoverflow.com/questions/8202605/matplotlib-scatterplot-colour-as-a-function-of-a-third-variable) – wwii Jan 13 '20 at 19:34
  • 2
    Matplotlib comes with [three cyclic colormaps](https://matplotlib.org/3.1.0/_images/sphx_glr_colormap_reference_005.png) inbuild. If there is any problem using them please provide a [mcve], – ImportanceOfBeingErnest Jan 13 '20 at 19:36
  • https://matplotlib.org/gallery/index.html#color , https://matplotlib.org/tutorials/index.html#colors – wwii Jan 13 '20 at 19:36
  • Thx for the links. I dont understand how create a "symmetric" color scheme. By symmetric i mean: it has unique colors in [0,pi] and this colors are reversed in [pi,2pi] so that for example pi/2 and 3pi/2 or 0 and 2pi have the same color –  Jan 13 '20 at 20:02

2 Answers2

0

The kind of structure for x, y and z you need, is easier using a meshgrid. Also, to have a lot of x-values between -1 and 1, np.linspace(-1,1,N) divides the range in N even intervals.

Using meshgrid, z can be calculated in one line using numpy's vectorization. This runs much faster.

To set a repeating color, a cyclic colormap such as hsv can be used. There the last color is the same as the starting color.

import numpy as np
from matplotlib import pyplot as plt

x, y = np.meshgrid(np.linspace(-1,1,100), np.arange(1,11,1))
z = (y * x**3) % 2*np.pi
plt.scatter(x, y, c=z, s=6, cmap='hsv')
plt.yticks(range(1,11))
plt.show()

resulting plot

Alternatively, a symmetric colormap could be built taken the colors from and existing map and combining them with the same colors in reverse order.

import numpy as np
from matplotlib import pyplot as plt
import matplotlib.colors as mcolors

colors_orig = plt.cm.viridis_r(np.linspace(0, 1, 128))
# combine the colors with the reversed array and build a new colormap
colors = np.vstack((colors_orig, colors_orig[::-1]))
symcmap = mcolors.LinearSegmentedColormap.from_list('symcmap', colors)

x, y = np.meshgrid(np.linspace(-1,1,100), np.arange(1,11,1))
z = (y * x**3) % 2*np.pi
plt.scatter(x, y, c=z, s=6, cmap=symcmap)
plt.yticks(range(1,11))
plt.show()
JohanC
  • 71,591
  • 8
  • 33
  • 66
  • That is exactly what I am looking for. Thank you! Is there a way to substitute plt.scatter(x, y, c=z, s=6, cmap=symcmap) with another command to get lines - instead of dots. I tried plt.plot(x, y, c=z, s=6, cmap=symcmap) but it does not work... –  Jan 14 '20 at 11:42
0

Multicolored lines are somewhat more complicated than just scatter plots. The docs have an example using LineCollections. Here is the adapted code. Note that the line segments are colored using their start point, so make sure there are enough x values. Also, the x and y limits aren't set automatically any more.

The code also adds a colorbar to illustrate how the colors map to the z values. Some interesting code from Jake VanderPlas shows how to create ticks for multiples of π.

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

# code from Jake VanderPlas
def format_func(value, tick_number):
    # find number of multiples of pi/2
    N = int(np.round(2 * value / np.pi))
    if N == 0:
        return "0"
    elif N == 1:
        return r"$\pi/2$"
    elif N == 2:
        return r"$\pi$"
    elif N % 2 > 0:
        return r"${0}\pi/2$".format(N)
    else:
        return r"${0}\pi$".format(N // 2)

x = np.linspace(-1, 1, 500)
y_max = 10
# Create a continuous norm to map from data points to colors
norm = plt.Normalize(0, 2 * np.pi)

for y in range(1, y_max + 1):
    z = (y * x ** 3) % 2 * np.pi
    y_array = y * np.ones_like(x)
    points = np.array([x, y_array]).T.reshape(-1, 1, 2)
    segments = np.concatenate([points[:-1], points[1:]], axis=1)
    lc = LineCollection(segments, cmap='hsv', norm=norm)
    lc.set_array(z)  # Set the values used for colormapping
    lc.set_linewidth(2)
    line = plt.gca().add_collection(lc)
    # plt.scatter(x, y_array, c=z, s=10, norm=norm, cmap='hsv')

cbar = plt.colorbar(line)  # , ticks=[k*np.pi for k in np.arange(0, 2.001, 0.25)])
cbar.locator = plt.MultipleLocator(np.pi / 2)
cbar.minor_locator = plt.MultipleLocator(np.pi / 4)
cbar.formatter = plt.FuncFormatter(format_func)
cbar.ax.minorticks_on()
cbar.update_ticks()

plt.yticks(range(1, y_max + 1))  # one tick for every y
plt.xlim(x.min(), x.max())  # the LineCollection doesn't force the limits
plt.ylim(0.5, y_max + 0.5)
plt.show()

resulting image

JohanC
  • 71,591
  • 8
  • 33
  • 66