|
| 1 | +# Chapter 6: Colors |
| 2 | + |
| 3 | +Methods like `plot` and `text` include a color parameter, which we've already made use of. While you can get pretty far simply using `color = 'blue'`, you might also make use of colormaps or set your own colors using hex strings or RGB(A) tuples. |
| 4 | + |
| 5 | +## 6.1 Colormaps |
| 6 | + |
| 7 | +According to the style sheet you are using, there will be some colormap and you will cycle through those colors by default when plotting (but not for text). The colors can be identified by the strings `'C0'`, `'C1'`, ... If, as in the default, your color map has only 10 distinct colors, then the eleventh color `'C10'` is valid, but simply refers to `'C0'` and the colors cycle from there. You'll notice that with successive plot calls on the same axes, the colors will automatically move through the colormap. This is not the case with text, as is demonstrated in the program below. |
| 8 | + |
| 9 | +```python |
| 10 | +fig, ax = plt.figure(), plt.axes() |
| 11 | +for i in range(12): |
| 12 | + # Plot color automatically cycles through color map |
| 13 | + ax.plot([0,1], np.ones(2)*i) |
| 14 | + |
| 15 | + # Text with default color on the left |
| 16 | + ax.text(0, i, 'C' + str(i), |
| 17 | + va = 'center', ha = 'right') |
| 18 | + |
| 19 | + # Text with variable color on the right |
| 20 | + ax.text(1, i, 'C' + str(i), |
| 21 | + va = 'center', ha = 'left', |
| 22 | + color = 'C'+str(i)) |
| 23 | +ax.axis('off') |
| 24 | +``` |
| 25 | + |
| 26 | + |
| 27 | + |
| 28 | +## 6.2 Red, Green, Blue, Alpha |
| 29 | + |
| 30 | +An RGB color is given by three values, specifying the amount of red, green, and blue. In matplotlib, these values are between zero and one (you might also see RGB values between zero and 255 elsewhere). These colors live inside a cube, as a particular color is a triple $(r,g,b) \in [0,1]^3$. |
| 31 | + |
| 32 | +  |
| 33 | + |
| 34 | +I like working with RGB tuples because they can be manipulated with mathematical operations. Two colors can easily be averaged or we can create a gradient between two. |
| 35 | + |
| 36 | +```python |
| 37 | +# Set Colors |
| 38 | +green = 76, 217, 100 |
| 39 | +green = np.array(green)/255 |
| 40 | +blue = 90, 200, 250 |
| 41 | +blue = np.array(blue)/255 |
| 42 | + |
| 43 | +# How many color changes |
| 44 | +segments = 100 |
| 45 | +interval_starts = np.linspace(0, 1, segments) |
| 46 | + |
| 47 | +fig, ax = plt.subplots(figsize = (8,8)) |
| 48 | + |
| 49 | +colors = dict() |
| 50 | +for i in range(3): |
| 51 | + colors[i] = np.linspace(blue[i], green[i], segments) |
| 52 | + |
| 53 | +for i in range(segments-1): |
| 54 | + rgb = colors[0][i], colors[1][i], colors[2][i] |
| 55 | + x = interval_starts[i], interval_starts[i+1] |
| 56 | + y = (0.5, 0.5) |
| 57 | + ax.plot(x, y, color = rgb, |
| 58 | + linewidth = 20, |
| 59 | + solid_capstyle = 'round') |
| 60 | + |
| 61 | +ax.set_aspect('equal') |
| 62 | +ax.axis('off') |
| 63 | +``` |
| 64 | + |
| 65 | + |
| 66 | + |
| 67 | +Any color can be made lighter by averaging it with white, $(1,1,1)$, or darker by averaging it with black $(0,0,0)$. We can also find the inverse of an RGB color by simply subtracting that triple from $(1,1,1)$. RGBA tuples are very similar, adding a fourth *a*lpha value for the opacity. |
| 68 | + |
| 69 | +With RGB and RGBA colors being so handy, you might want to convert strings like `'C0'` into RGB. `ColorConverter()` lets us do this, with the `to_rgb()` and `to_rgba()` methods. Below, we create another color gradient between the default `'C0'` blue, to `'C1'` orange, and on to light blue `'C9'`. |
| 70 | + |
| 71 | +```python |
| 72 | +# Set Colors |
| 73 | +blue = mpl.colors.ColorConverter().to_rgb('C0') |
| 74 | +orange = mpl.colors.ColorConverter().to_rgb('C1') |
| 75 | + |
| 76 | +n_colors = 10 |
| 77 | +color_strings = dict() |
| 78 | +for i in range(n_colors): |
| 79 | + color_strings[i] = 'C'+str(i) |
| 80 | +segments = 1000 # How many color changes |
| 81 | + |
| 82 | +fig, ax = plt.subplots(figsize = (14,8)) |
| 83 | + |
| 84 | +for c in range(n_colors - 1): |
| 85 | + color1 = mpl.colors.ColorConverter().to_rgb(color_strings[c]) |
| 86 | + color2 = mpl.colors.ColorConverter().to_rgb(color_strings[c+1]) |
| 87 | + |
| 88 | + interval_starts = np.linspace(c, c+1, segments) |
| 89 | + colors = dict() |
| 90 | + for i in range(3): |
| 91 | + colors[i] = np.linspace(color1[i], color2[i], segments) |
| 92 | + |
| 93 | + for i in range(segments-1): |
| 94 | + |
| 95 | + rgb = colors[0][i], colors[1][i], colors[2][i] |
| 96 | + |
| 97 | + x = interval_starts[i], interval_starts[i+1] |
| 98 | + y = [0.3,0.5] |
| 99 | + |
| 100 | + ax.plot(x, y, |
| 101 | + color = rgb, |
| 102 | + linewidth = 20, |
| 103 | + solid_capstyle = 'round') |
| 104 | + |
| 105 | + ax.text(c, .51, |
| 106 | + s = 'C'+str(c), |
| 107 | + va = 'bottom', |
| 108 | + size = 12, |
| 109 | + ha = 'center') |
| 110 | + |
| 111 | +ax.text(9, .51, |
| 112 | + s = 'C9', |
| 113 | + va = 'bottom', |
| 114 | + size = 12, |
| 115 | + ha = 'center') |
| 116 | + |
| 117 | +ax.set_aspect('equal') |
| 118 | +ax.axis('off') |
| 119 | +``` |
| 120 | + |
| 121 | + |
| 122 | + |
| 123 | +### Color Cube Code |
| 124 | + |
| 125 | +Here is the code for one of the RGB color cubes. |
| 126 | + |
| 127 | +```python |
| 128 | +from itertools import product |
| 129 | +from mpl_toolkits.mplot3d.art3d import Poly3DCollection |
| 130 | + |
| 131 | +light_gray = [.98]*3 |
| 132 | +fig = plt.figure(figsize = (6,6), |
| 133 | + facecolor = light_gray) |
| 134 | +ax = plt.axes(projection='3d', |
| 135 | + facecolor = light_gray) |
| 136 | + |
| 137 | +# control how many cubes/color changes |
| 138 | +pieces = 10 |
| 139 | +grid = np.linspace(0, 1, pieces)[:-1] |
| 140 | +width = grid[1] - grid[0] |
| 141 | + |
| 142 | +# Make smaller cube units |
| 143 | +for x in grid: |
| 144 | + for y in grid: |
| 145 | + for z in grid: |
| 146 | + vertices = list() |
| 147 | + for prod in product([x,x+width],[y,y+width], [z,z+width]): |
| 148 | + vertices.append(list(prod)) |
| 149 | + |
| 150 | + faces = list() |
| 151 | + for key, face in enumerate([x,y,z]): |
| 152 | + # face is 0 |
| 153 | + helper0 = [x for x in vertices if x[key] == face] |
| 154 | + helper1 = [x for x in vertices if x[key] == face + width] |
| 155 | + helper0.sort() |
| 156 | + helper0 = helper0[0:2] + helper0[::-1][0:2] |
| 157 | + helper1.sort() |
| 158 | + helper1 = helper1[0:2] + helper1[::-1][0:2] |
| 159 | + faces.append((helper0)) |
| 160 | + faces.append(helper1) |
| 161 | + |
| 162 | + facecolor = (x + width / 2, |
| 163 | + y + width / 2, |
| 164 | + z + width / 2) |
| 165 | + pc = Poly3DCollection(faces, |
| 166 | + facecolor = facecolor, |
| 167 | + edgecolor = 'black') |
| 168 | + ax.add_collection3d(pc) |
| 169 | + |
| 170 | +# Label Axes |
| 171 | +ax.set_xlabel("Red") |
| 172 | +ax.set_ylabel('Green') |
| 173 | +ax.set_zlabel("Blue") |
| 174 | + |
| 175 | +# Set Ticks |
| 176 | +ax.set_xticks([0,1]) |
| 177 | +ax.set_yticks([0,1]) |
| 178 | +ax.set_zticks([0,1]) |
| 179 | +# Change padding |
| 180 | +ax.xaxis.set_tick_params(pad = 0.1) |
| 181 | +ax.yaxis.set_tick_params(pad = 0.1) |
| 182 | +ax.zaxis.set_tick_params(pad = 0.1) |
| 183 | +# Change azimuth |
| 184 | +angle = 45 # + 180 # for second cube |
| 185 | +ax.view_init(elev = None, azim = angle) |
| 186 | +# Zoom out so labels are not cut off |
| 187 | +ax.set_box_aspect([1,1,1], zoom = 0.86) |
| 188 | +``` |
0 commit comments