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?
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.
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()
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.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With