6

I am trying to represent two variables x and y on a map using colours.

So far the best I can acheive is using color for one variable and transparancy for another.

plt.Polygon(shape.points, fill=False, color=cmap(y), alpha=x)

Is it possible to create a 2D colormap in matplotlib that takes two variables?

hangc
  • 4,730
  • 10
  • 33
  • 66
  • Related (definitely not dupe) http://stackoverflow.com/q/10958835/2988730 – Mad Physicist Jan 31 '17 at 20:30
  • Specifically the second answer http://stackoverflow.com/a/10992313/2988730 – Mad Physicist Jan 31 '17 at 20:33
  • You could also try implementing your own colormap that takes x and y encoded as a single parameter, e.g. a complex number, and have a function that returns that encoded value. – Mad Physicist Jan 31 '17 at 20:38
  • For a nice implementation of a 2D colormap as polar plot, see [this answer](https://stackoverflow.com/a/45630020/4124317). – ImportanceOfBeingErnest Aug 11 '17 at 11:07
  • Does this answer your question? [Is there any way to use bivariate colormaps in matplotlib?](https://stackoverflow.com/questions/15207255/is-there-any-way-to-use-bivariate-colormaps-in-matplotlib) – Neinstein Aug 30 '21 at 09:09

2 Answers2

6

In general colormaps in matplotlib are 1D, i.e. they map a scalar to a color. In order to obtain a 2D colormap one would need to somehow invent a mapping of two scalars to a color. While this is in principle possible, it's not as convenient as the usual colormaps.

An example is below, where we map two parameters to the red and blue RGB color channel respectively and thereby create a 2D colormap.

import matplotlib.pyplot as plt
import numpy as np

fig, ax = plt.subplots()
ax.set_aspect("equal")

x = [1,1,2,2,3,3] # xdata
y = [1,2,3,1,2,3] # ydata
p1 = [0.2,0.4,.6,.8,.6,.1] # parameter 1
p2 = [0.4,0.5,.7,.1,.3,.7] # parameter 2

# create a very simple colormap, 
#  mapping parameter 1 to the red channel and 
#          parameter 2 to the blue channel
cmap = lambda p1,p2 : (p1, 0, p2)

# put shapes at positions (x[i], y[i]) and colorize them with our
# cmap according to their respective parameters
for i in range(len(x)):
    circle = plt.Circle((x[i], y[i]), 0.5, color=cmap(p1[i],p2[i]))
    ax.add_artist(circle)
    tx="p1: {}\np2: {}".format(p1[i],p2[i]) # create a label
    ax.text(x[i], y[i], tx, ha="center", color="w", va="center")

ax.set_xlim(0,4)
ax.set_ylim(0,4)
ax.set_xlabel("x")
ax.set_ylabel("y")

# create the legend:

plt.subplots_adjust(left=0.1, right=0.65, top=0.85)
cax = fig.add_axes([0.7,0.55,0.3,0.3])
cp1 = np.linspace(0,1)
cp2 = np.linspace(0,1)
Cp1, Cp2 = np.meshgrid(cp1,cp2)
C0 = np.zeros_like(Cp1)
# make RGB image, p1 to red channel, p2 to blue channel
Legend = np.dstack((Cp1, C0, Cp2))
# parameters range between 0 and 1
cax.imshow(Legend, origin="lower", extent=[0,1,0,1])
cax.set_xlabel("p1")
cax.set_ylabel("p2")
cax.set_title("2D cmap legend", fontsize=10)

plt.show()

enter image description here

This scheme can of course be extended to other (more complicated) colormappings and also to imshow or pcolormesh plots. Also, the use of the HSV colorscheme may be beneficial compared to the RGB, so matplotlib.colors.hsv_to_rgb(hsv) might be helpful.

Community
  • 1
  • 1
ImportanceOfBeingErnest
  • 321,279
  • 53
  • 665
  • 712
2

There is a Javascript plugin for 2D colormaps available here.

A quick translation of this to Python could be something like this:

# Based on: https://github.com/dominikjaeckle/Color2D

import matplotlib.pyplot as plt
import numpy as np

COLORMAP_FILE = "./bremm.png"


class ColorMap2D:
    def __init__(self, filename=None):
        self._colormap_file = filename or COLORMAP_FILE
        self._img = plt.imread(self._colormap_file)

        self._width = len(self._img[0])
        self._height = len(self._img)

        self._range_x = (0, 1)
        self._range_y = (0, 1)

    def _scale(self, u: float, u_min: float, u_max: float) -> float:
        return ((u + 1) - (u_min + 1)) / ((u_max + 1) - (u_min + 1))

    def _scale_x(self, x: float) -> int:
        val = self._scale(x, self._range_x[0], self._range_x[1])
        return int(val * (self._width - 1))

    def _scale_y(self, y: float) -> int:
        val = self._scale(y, self._range_y[0], self._range_y[1])
        return int(val * (self._height - 1))

    def __call__(self, X):
        assert len(X.shape) == 2

        self._range_x = (X[:, 0].min(), X[:, 0].max())
        self._range_y = (X[:, 1].min(), X[:, 1].max())

        output = np.zeros((X.shape[0], 3))
        for i in range(X.shape[0]):
            x, y = X[i, :]
            xp = self._scale_x(x)
            yp = self._scale_y(y)
            output[i, :] = self._img[xp, yp]
        return output

which expects the bremm.png file in the same directory.

This can be used in the same way as any other Matplotlib colormap:

cmap = ColorMap2D()
colors = cmap(array)

where array is an N x 2 matrix of (x, y) coordinates.

GjjvdBurg
  • 478
  • 3
  • 14
  • Some simplification remarks, if you don't mind: 1) Why do you add 1 to both side of the subtractions in `_scale`? It could be simply `(u - u_min) / (u_max - u_min)`. 2) `len(X.shape)` is simply `X.ndim` (and `X.shape[0]` is `len(X)`). – Neinstein Aug 27 '21 at 09:46
  • I posted an updated version here: https://stackoverflow.com/a/68981516/5099168 – Neinstein Aug 30 '21 at 09:59