/
bent-cool-warm.ipynb
554 lines (554 loc) · 43 KB
/
bent-cool-warm.ipynb
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
{
"cells": [
{
"cell_type": "markdown",
"metadata": {},
"source": [
"# Bent Diverging Color Maps\n",
"\n",
"This notebook describes the creation of some diverging color maps. The maps are based on those from \"[Diverging Color Maps for Scientific Visualization](http://www.kennethmoreland.com/color-maps/)\" by Kenneth Moreland except that the maps here have a linear interpolation of luminance with a bend in the middle. This can create an artifact at the midpoint."
]
},
{
"cell_type": "code",
"execution_count": 1,
"metadata": {},
"outputs": [],
"source": [
"from __future__ import print_function"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"## Diverging Color Map Generation\n",
"\n",
"Here we create a class named `BentDivergingColorMap` designed to create diverging color maps based on the definition of the endpoints."
]
},
{
"cell_type": "code",
"execution_count": 2,
"metadata": {},
"outputs": [],
"source": [
"from colormath.color_objects import *\n",
"from colormath.color_conversions import convert_color, color_conversion_function\n",
"from colormath import color_diff\n",
"\n",
"import numpy"
]
},
{
"cell_type": "code",
"execution_count": 3,
"metadata": {},
"outputs": [],
"source": [
"class BentDivergingColorMap:\n",
" def __init__(self,\n",
" low_color=sRGBColor(0.230, 0.299, 0.754),\n",
" high_color=sRGBColor(0.706, 0.016, 0.150),\n",
" mid_color=LCHabColor(88.0, 0.0, 0.0),\n",
" low_hue_spin=-32,\n",
" high_hue_spin=32):\n",
" \"\"\"\n",
" :param color low_color: The color at the low end of the map.\n",
" :param color high_color: The color at the high end of the map.\n",
" :param color mid_color: The color at the middle of the map. Should be unsaturated.\n",
" :param float low_hue_spin: The amount of spin to put on the low side of the map (in degrees)\n",
" :param float high_hue_spin: The amount of spin to put on the high side of the map (in degrees)\n",
" \"\"\"\n",
" self.low_lch = convert_color(low_color, LCHabColor)\n",
" self.high_lch = convert_color(high_color, LCHabColor)\n",
" \n",
" # Record lighting conditions (which makes a difference for color conversions)\n",
" self.observer = self.low_lch.observer\n",
" self.illuminant = self.low_lch.illuminant\n",
" \n",
" # If the points are saturated and distinct, then we place a white point\n",
" # in the middle. Otherwise we ignore it.\n",
" if self.low_lch.lch_c > 5:\n",
" if self.high_lch.lch_c > 5:\n",
" if (abs(self.low_lch.lch_h - self.high_lch.lch_h) > 60.0) \\\n",
" and mid_color:\n",
" # Both endpoints are saturated and unique and a midpoint was\n",
" # given. Interpolate through this midpoint and compute an\n",
" # appropriate hue spin.\n",
" mid_lch = convert_color(mid_color, LCHabColor,\n",
" target_illuminant=self.illuminant)\n",
" self.midpoint_luminance = mid_lch.lch_l\n",
" self.midpoint_low_hue = self.low_lch.lch_h + low_hue_spin\n",
" self.midpoint_high_hue = self.high_lch.lch_h + high_hue_spin\n",
" else:\n",
" # Both endpoints are distinct colors, but they are either very close\n",
" # in hue or no middle point was given. In this case, interpolate\n",
" # directly between them.\n",
" self.midpoint_luminance = None\n",
" else:\n",
" # The low color is saturated but the high color is unsaturated.\n",
" # Interpolate directly between them, but adjust the hue of the unsaturated\n",
" # high color.\n",
" self.midpoint_luminance = None\n",
" self.high_lch.lch_h = self.low_lch.lch_h + low_hue_spin\n",
" else:\n",
" # The low color is unsaturated. Assume the high color is saturated. (If not,\n",
" # then this is a boring map no matter what we do.) Interpolate directly\n",
" # between them, but adjust the hue of the unsaturated low color.\n",
" self.midpoint_luminance = None\n",
" self.low_lch.lch_h = self.high_lch.lch_h + high_hue_spin\n",
"\n",
" def print_self(self):\n",
" print('Low Color:')\n",
" print('\\t', self.low_lch)\n",
" print('\\t', convert_color(self.low_lch, LabColor))\n",
" print('\\t', convert_color(self.low_lch, sRGBColor))\n",
" \n",
" print('Middle Color:')\n",
" if (self.midpoint_luminance):\n",
" print('\\t Luminance', self.midpoint_luminance)\n",
" print('\\t Low Hue', self.midpoint_low_hue)\n",
" print('\\t High Hue', self.midpoint_high_hue)\n",
" else:\n",
" print('\\t No Midpoint')\n",
"\n",
" print('High Color:')\n",
" print('\\t', self.high_lch)\n",
" print('\\t', convert_color(self.high_lch, LabColor))\n",
" print('\\t', convert_color(self.high_lch, sRGBColor))\n",
" \n",
" def map_scalar(self, scalar, space=LCHabColor):\n",
" '''\n",
" Given a scalar value between 0 and 1, map to a color. The color is\n",
" returned as a sRGBColor object.\n",
" \n",
" :param float scalar: The value to map to a color.\n",
" :param color_object space: The colormath color object to do interpolation in.\n",
" '''\n",
" if scalar < 0:\n",
" return convert_color(self.low_lch, sRGBColor)\n",
" if scalar > 1:\n",
" return convert_color(self.high_lch, sRGBColor)\n",
" \n",
" interp = scalar\n",
" low_color = convert_color(self.low_lch, space)\n",
" high_color = convert_color(self.high_lch, space)\n",
" if self.midpoint_luminance:\n",
" # Adjust the interpolation around the midpoint\n",
" if scalar < 0.5:\n",
" interp = 2*scalar\n",
" high_lch = LCHabColor(self.midpoint_luminance, 0, self.midpoint_low_hue,\n",
" observer=self.observer, illuminant=self.illuminant)\n",
" high_color = convert_color(high_lch, space)\n",
" else:\n",
" interp = 2*scalar - 1\n",
" low_lch = LCHabColor(self.midpoint_luminance, 0, self.midpoint_high_hue,\n",
" observer=self.observer, illuminant=self.illuminant)\n",
" low_color = convert_color(low_lch, space)\n",
" low_color = numpy.array(low_color.get_value_tuple())\n",
" high_color = numpy.array(high_color.get_value_tuple())\n",
" \n",
" mid_color = interp*(high_color-low_color) + low_color\n",
" rgb = convert_color(space(mid_color[0], mid_color[1], mid_color[2],\n",
" observer=self.observer, illuminant=self.illuminant),\n",
" sRGBColor)\n",
" \n",
" if ((rgb.rgb_r < -0.0019) or (rgb.rgb_r > 1.0019) or\n",
" (rgb.rgb_g < -0.0019) or (rgb.rgb_g > 1.0019) or\n",
" (rgb.rgb_b < -0.0019) or (rgb.rgb_b > 1.0019)):\n",
" print('WARNING: Value at scalar %1.4f is out of range' % scalar,\n",
" rgb.get_value_tuple())\n",
" \n",
" return rgb\n",
" \n",
" def map_scalar_array(self, scalar_array, space=LCHabColor):\n",
" '''\n",
" Given an array of scalar values between 0 and 1, map them to colors.\n",
" The color is returned as a sRGBColor object.\n",
" \n",
" :param float scalar_array: Array of values to map to colors.\n",
" :param color_object space: The colormath color object to do interpolation in.\n",
" '''\n",
" f = numpy.vectorize(lambda x: self.map_scalar(x, space))\n",
" return f(scalar_array)\n"
]
},
{
"cell_type": "markdown",
"metadata": {
"collapsed": true
},
"source": [
"## Color Plots\n",
"\n",
"Now that we have a class that helps us create color maps, create some plots on its colors and properties. We are plugging in the default colors here, but you can easily change them to experiment with other colors."
]
},
{
"cell_type": "code",
"execution_count": 4,
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"Low Color:\n",
"\t LCHabColor (lch_l:37.7323 lch_c:70.5602 lch_h:296.9621)\n",
"\t LabColor (lab_l:37.7323 lab_a:31.9920 lab_b:-62.8908)\n",
"\t sRGBColor (rgb_r:0.2300 rgb_g:0.2990 rgb_b:0.7540)\n",
"Middle Color:\n",
"\t Luminance 95.58063098914221\n",
"\t Low Hue 264.9620756859777\n",
"\t High Hue 60.694304003123264\n",
"High Color:\n",
"\t LCHabColor (lch_l:37.7337 lch_c:70.5778 lch_h:28.6943)\n",
"\t LabColor (lab_l:37.7337 lab_a:61.9104 lab_b:33.8870)\n",
"\t sRGBColor (rgb_r:0.7060 rgb_g:0.0160 rgb_b:0.1500)\n"
]
}
],
"source": [
"diverging_color_map = BentDivergingColorMap(\n",
" low_color=sRGBColor(0.230, 0.299, 0.754),\n",
" high_color=sRGBColor(0.706, 0.016, 0.150),\n",
" mid_color=sRGBColor(0.95, 0.95, 0.95))\n",
"diverging_color_map.print_self()"
]
},
{
"cell_type": "code",
"execution_count": 5,
"metadata": {},
"outputs": [],
"source": [
"import toyplot\n",
"import toyplot.svg\n",
"\n",
"import pandas"
]
},
{
"cell_type": "code",
"execution_count": 6,
"metadata": {},
"outputs": [
{
"data": {
"image/png": "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\n",
"text/html": [
"<div class=\"toyplot\" id=\"t3dfdd8c25631409490e2fc60573ccce3\" style=\"text-align:center\"><svg class=\"toyplot-canvas-Canvas\" xmlns:toyplot=\"http://www.sandia.gov/toyplot\" xmlns:xlink=\"http://www.w3.org/1999/xlink\" xmlns=\"http://www.w3.org/2000/svg\" width=\"130.0px\" height=\"300.0px\" viewBox=\"0 0 130.0 300.0\" preserveAspectRatio=\"xMidYMid meet\" style=\"background-color:transparent;border-color:#292724;border-style:none;border-width:1.0;fill:rgb(16.1%,15.3%,14.1%);fill-opacity:1.0;font-family:Helvetica;font-size:12px;opacity:1.0;stroke:rgb(16.1%,15.3%,14.1%);stroke-opacity:1.0;stroke-width:1.0\" id=\"t2d00d1d8cb7145c9abd3c6024ea715eb\"><g class=\"toyplot-coordinates-Numberline\" id=\"td0739b7ecd874742b6ffec28195f9e26\"><clipPath id=\"t2caf6b40b9734f47a26c48a869d0e575\"><rect x=\"0\" y=\"-5.0\" width=\"286.0\" height=\"10.0\"></rect></clipPath><g clip-path=\"url(#t2caf6b40b9734f47a26c48a869d0e575)\" transform=\"translate(16.0,293.0)rotate(-90.0)\"><g class=\"toyplot-color-Map\" id=\"t4300920f68d345faaa364201a24a1b4b\"><defs><linearGradient id=\"t3630d259e38e4f238e8ef728708dc82a\" x1=\"0.0\" x2=\"286.0\" y1=\"0\" y2=\"0\" gradientUnits=\"userSpaceOnUse\"><stop stop-color=\"rgb(23%,29.9%,75.4%)\" stop-opacity=\"1.0\" offset=\"0.0\"></stop><stop stop-color=\"rgb(24.5%,32.1%,76.4%)\" stop-opacity=\"1.0\" offset=\"0.015873015873015872\"></stop><stop stop-color=\"rgb(26.1%,34.3%,77.4%)\" stop-opacity=\"1.0\" offset=\"0.031746031746031744\"></stop><stop stop-color=\"rgb(27.8%,36.5%,78.4%)\" stop-opacity=\"1.0\" offset=\"0.047619047619047616\"></stop><stop stop-color=\"rgb(29.5%,38.6%,79.3%)\" stop-opacity=\"1.0\" offset=\"0.06349206349206349\"></stop><stop stop-color=\"rgb(31.3%,40.7%,80.2%)\" stop-opacity=\"1.0\" offset=\"0.07936507936507936\"></stop><stop stop-color=\"rgb(33.1%,42.9%,81.1%)\" stop-opacity=\"1.0\" offset=\"0.09523809523809523\"></stop><stop stop-color=\"rgb(34.9%,45%,81.9%)\" stop-opacity=\"1.0\" offset=\"0.1111111111111111\"></stop><stop stop-color=\"rgb(36.8%,47.1%,82.7%)\" stop-opacity=\"1.0\" offset=\"0.12698412698412698\"></stop><stop stop-color=\"rgb(38.8%,49.2%,83.5%)\" stop-opacity=\"1.0\" offset=\"0.14285714285714285\"></stop><stop stop-color=\"rgb(40.8%,51.3%,84.2%)\" stop-opacity=\"1.0\" offset=\"0.15873015873015872\"></stop><stop stop-color=\"rgb(42.9%,53.4%,84.9%)\" stop-opacity=\"1.0\" offset=\"0.1746031746031746\"></stop><stop stop-color=\"rgb(45%,55.5%,85.6%)\" stop-opacity=\"1.0\" offset=\"0.19047619047619047\"></stop><stop stop-color=\"rgb(47.1%,57.5%,86.2%)\" stop-opacity=\"1.0\" offset=\"0.20634920634920634\"></stop><stop stop-color=\"rgb(49.3%,59.6%,86.9%)\" stop-opacity=\"1.0\" offset=\"0.2222222222222222\"></stop><stop stop-color=\"rgb(51.5%,61.7%,87.5%)\" stop-opacity=\"1.0\" offset=\"0.23809523809523808\"></stop><stop stop-color=\"rgb(53.8%,63.8%,88%)\" stop-opacity=\"1.0\" offset=\"0.25396825396825395\"></stop><stop stop-color=\"rgb(56.1%,65.8%,88.6%)\" stop-opacity=\"1.0\" offset=\"0.2698412698412698\"></stop><stop stop-color=\"rgb(58.5%,67.9%,89.1%)\" stop-opacity=\"1.0\" offset=\"0.2857142857142857\"></stop><stop stop-color=\"rgb(60.9%,69.9%,89.6%)\" stop-opacity=\"1.0\" offset=\"0.30158730158730157\"></stop><stop stop-color=\"rgb(63.4%,72%,90.1%)\" stop-opacity=\"1.0\" offset=\"0.31746031746031744\"></stop><stop stop-color=\"rgb(65.9%,74%,90.6%)\" stop-opacity=\"1.0\" offset=\"0.3333333333333333\"></stop><stop stop-color=\"rgb(68.4%,76%,91.1%)\" stop-opacity=\"1.0\" offset=\"0.3492063492063492\"></stop><stop stop-color=\"rgb(71%,78.1%,91.5%)\" stop-opacity=\"1.0\" offset=\"0.36507936507936506\"></stop><stop stop-color=\"rgb(73.7%,80.1%,92%)\" stop-opacity=\"1.0\" offset=\"0.38095238095238093\"></stop><stop stop-color=\"rgb(76.4%,82.1%,92.4%)\" stop-opacity=\"1.0\" offset=\"0.3968253968253968\"></stop><stop stop-color=\"rgb(79.1%,84.1%,92.8%)\" stop-opacity=\"1.0\" offset=\"0.4126984126984127\"></stop><stop stop-color=\"rgb(81.9%,86.1%,93.2%)\" stop-opacity=\"1.0\" offset=\"0.42857142857142855\"></stop><stop stop-color=\"rgb(84.7%,88.1%,93.6%)\" stop-opacity=\"1.0\" offset=\"0.4444444444444444\"></stop><stop stop-color=\"rgb(87.6%,90.1%,94%)\" stop-opacity=\"1.0\" offset=\"0.4603174603174603\"></stop><stop stop-color=\"rgb(90.5%,92.1%,94.4%)\" stop-opacity=\"1.0\" offset=\"0.47619047619047616\"></stop><stop stop-color=\"rgb(93.5%,94%,94.8%)\" stop-opacity=\"1.0\" offset=\"0.4920634920634921\"></stop><stop stop-color=\"rgb(94.8%,93.8%,93.2%)\" stop-opacity=\"1.0\" offset=\"0.5079365079365079\"></stop><stop stop-color=\"rgb(94.3%,91.4%,89.8%)\" stop-opacity=\"1.0\" offset=\"0.5238095238095237\"></stop><stop stop-color=\"rgb(93.7%,89%,86.4%)\" stop-opacity=\"1.0\" offset=\"0.5396825396825397\"></stop><stop stop-color=\"rgb(93.2%,86.6%,83%)\" stop-opacity=\"1.0\" offset=\"0.5555555555555556\"></stop><stop stop-color=\"rgb(92.7%,84.2%,79.7%)\" stop-opacity=\"1.0\" offset=\"0.5714285714285714\"></stop><stop stop-color=\"rgb(92.1%,81.8%,76.4%)\" stop-opacity=\"1.0\" offset=\"0.5873015873015872\"></stop><stop stop-color=\"rgb(91.6%,79.3%,73.2%)\" stop-opacity=\"1.0\" offset=\"0.6031746031746031\"></stop><stop stop-color=\"rgb(91%,76.9%,70.1%)\" stop-opacity=\"1.0\" offset=\"0.6190476190476191\"></stop><stop stop-color=\"rgb(90.4%,74.5%,67%)\" stop-opacity=\"1.0\" offset=\"0.6349206349206349\"></stop><stop stop-color=\"rgb(89.8%,72%,64%)\" stop-opacity=\"1.0\" offset=\"0.6507936507936507\"></stop><stop stop-color=\"rgb(89.1%,69.6%,61.1%)\" stop-opacity=\"1.0\" offset=\"0.6666666666666666\"></stop><stop stop-color=\"rgb(88.5%,67.1%,58.2%)\" stop-opacity=\"1.0\" offset=\"0.6825396825396826\"></stop><stop stop-color=\"rgb(87.8%,64.6%,55.4%)\" stop-opacity=\"1.0\" offset=\"0.6984126984126984\"></stop><stop stop-color=\"rgb(87.1%,62.1%,52.6%)\" stop-opacity=\"1.0\" offset=\"0.7142857142857142\"></stop><stop stop-color=\"rgb(86.4%,59.6%,49.9%)\" stop-opacity=\"1.0\" offset=\"0.7301587301587301\"></stop><stop stop-color=\"rgb(85.6%,57.1%,47.3%)\" stop-opacity=\"1.0\" offset=\"0.746031746031746\"></stop><stop stop-color=\"rgb(84.9%,54.6%,44.7%)\" stop-opacity=\"1.0\" offset=\"0.7619047619047619\"></stop><stop stop-color=\"rgb(84.1%,52%,42.2%)\" stop-opacity=\"1.0\" offset=\"0.7777777777777777\"></stop><stop stop-color=\"rgb(83.3%,49.5%,39.8%)\" stop-opacity=\"1.0\" offset=\"0.7936507936507936\"></stop><stop stop-color=\"rgb(82.4%,46.9%,37.4%)\" stop-opacity=\"1.0\" offset=\"0.8095238095238095\"></stop><stop stop-color=\"rgb(81.6%,44.2%,35.2%)\" stop-opacity=\"1.0\" offset=\"0.8253968253968254\"></stop><stop stop-color=\"rgb(80.7%,41.5%,33%)\" stop-opacity=\"1.0\" offset=\"0.8412698412698412\"></stop><stop stop-color=\"rgb(79.8%,38.8%,30.8%)\" stop-opacity=\"1.0\" offset=\"0.8571428571428571\"></stop><stop stop-color=\"rgb(78.8%,36%,28.7%)\" stop-opacity=\"1.0\" offset=\"0.873015873015873\"></stop><stop stop-color=\"rgb(77.9%,33.1%,26.8%)\" stop-opacity=\"1.0\" offset=\"0.8888888888888888\"></stop><stop stop-color=\"rgb(76.9%,30.1%,24.8%)\" stop-opacity=\"1.0\" offset=\"0.9047619047619048\"></stop><stop stop-color=\"rgb(75.9%,26.9%,23%)\" stop-opacity=\"1.0\" offset=\"0.9206349206349206\"></stop><stop stop-color=\"rgb(74.9%,23.6%,21.2%)\" stop-opacity=\"1.0\" offset=\"0.9365079365079364\"></stop><stop stop-color=\"rgb(73.9%,19.9%,19.6%)\" stop-opacity=\"1.0\" offset=\"0.9523809523809523\"></stop><stop stop-color=\"rgb(72.8%,15.7%,18%)\" stop-opacity=\"1.0\" offset=\"0.9682539682539681\"></stop><stop stop-color=\"rgb(71.7%,10.5%,16.4%)\" stop-opacity=\"1.0\" offset=\"0.9841269841269842\"></stop><stop stop-color=\"rgb(70.6%,1.6%,15%)\" stop-opacity=\"1.0\" offset=\"1.0\"></stop></linearGradient></defs><rect x=\"0.0\" y=\"-15.0\" width=\"286.0\" height=\"30\" style=\"fill:url(#t3630d259e38e4f238e8ef728708dc82a);stroke:rgb(82.7%,82.7%,82.7%);stroke-opacity:1.0;stroke-width:1.0\"></rect></g></g><g class=\"toyplot-coordinates-Axis\" id=\"tc7fd0278c38146ef9a34165bb332b4ff\" transform=\"translate(16.0,293.0)rotate(-90.0)translate(0,5.0)\"><g><g transform=\"translate(0.0,6)rotate(90)\"><text x=\"7.0\" y=\"2.5549999999999997\" style=\"fill:rgb(16.1%,15.3%,14.1%);fill-opacity:1.0;font-family:helvetica;font-size:10.0px;font-weight:normal;stroke:none;vertical-align:baseline;white-space:pre\">0.0, (59, 76, 192)</text></g><g transform=\"translate(143.0,6)rotate(90)\"><text x=\"7.0\" y=\"2.5549999999999997\" style=\"fill:rgb(16.1%,15.3%,14.1%);fill-opacity:1.0;font-family:helvetica;font-size:10.0px;font-weight:normal;stroke:none;vertical-align:baseline;white-space:pre\">0.5, (242, 242, 242)</text></g><g transform=\"translate(286.0,6)rotate(90)\"><text x=\"7.0\" y=\"2.5549999999999997\" style=\"fill:rgb(16.1%,15.3%,14.1%);fill-opacity:1.0;font-family:helvetica;font-size:10.0px;font-weight:normal;stroke:none;vertical-align:baseline;white-space:pre\">1.0, (180, 4, 38)</text></g></g><g class=\"toyplot-coordinates-Axis-coordinates\" style=\"visibility:hidden\" transform=\"\"><line x1=\"0\" x2=\"0\" y1=\"-3.0\" y2=\"4.5\" style=\"stroke:rgb(43.9%,50.2%,56.5%);stroke-opacity:1.0;stroke-width:1.0\"></line><text x=\"0\" y=\"-6\" style=\"alignment-baseline:alphabetic;fill:rgb(43.9%,50.2%,56.5%);fill-opacity:1.0;font-size:10px;font-weight:normal;stroke:none;text-anchor:middle\"></text></g></g></g></svg><div class=\"toyplot-behavior\"><script>(function()\n",
"{\n",
"var modules={};\n",
"modules[\"toyplot/canvas/id\"] = \"t2d00d1d8cb7145c9abd3c6024ea715eb\";\n",
"modules[\"toyplot/canvas\"] = (function(canvas_id)\n",
" {\n",
" return document.querySelector(\"#\" + canvas_id);\n",
" })(modules[\"toyplot/canvas/id\"]);\n",
"modules[\"toyplot.coordinates.Axis\"] = (\n",
" function(canvas)\n",
" {\n",
" function sign(x)\n",
" {\n",
" return x < 0 ? -1 : x > 0 ? 1 : 0;\n",
" }\n",
"\n",
" function mix(a, b, amount)\n",
" {\n",
" return ((1.0 - amount) * a) + (amount * b);\n",
" }\n",
"\n",
" function log(x, base)\n",
" {\n",
" return Math.log(Math.abs(x)) / Math.log(base);\n",
" }\n",
"\n",
" function in_range(a, x, b)\n",
" {\n",
" var left = Math.min(a, b);\n",
" var right = Math.max(a, b);\n",
" return left <= x && x <= right;\n",
" }\n",
"\n",
" function inside(range, projection)\n",
" {\n",
" for(var i = 0; i != projection.length; ++i)\n",
" {\n",
" var segment = projection[i];\n",
" if(in_range(segment.range.min, range, segment.range.max))\n",
" return true;\n",
" }\n",
" return false;\n",
" }\n",
"\n",
" function to_domain(range, projection)\n",
" {\n",
" for(var i = 0; i != projection.length; ++i)\n",
" {\n",
" var segment = projection[i];\n",
" if(in_range(segment.range.bounds.min, range, segment.range.bounds.max))\n",
" {\n",
" if(segment.scale == \"linear\")\n",
" {\n",
" var amount = (range - segment.range.min) / (segment.range.max - segment.range.min);\n",
" return mix(segment.domain.min, segment.domain.max, amount)\n",
" }\n",
" else if(segment.scale[0] == \"log\")\n",
" {\n",
" var amount = (range - segment.range.min) / (segment.range.max - segment.range.min);\n",
" var base = segment.scale[1];\n",
" return sign(segment.domain.min) * Math.pow(base, mix(log(segment.domain.min, base), log(segment.domain.max, base), amount));\n",
" }\n",
" }\n",
" }\n",
" }\n",
"\n",
" var axes = {};\n",
"\n",
" function display_coordinates(e)\n",
" {\n",
" var current = canvas.createSVGPoint();\n",
" current.x = e.clientX;\n",
" current.y = e.clientY;\n",
"\n",
" for(var axis_id in axes)\n",
" {\n",
" var axis = document.querySelector(\"#\" + axis_id);\n",
" var coordinates = axis.querySelector(\".toyplot-coordinates-Axis-coordinates\");\n",
" if(coordinates)\n",
" {\n",
" var projection = axes[axis_id];\n",
" var local = current.matrixTransform(axis.getScreenCTM().inverse());\n",
" if(inside(local.x, projection))\n",
" {\n",
" var domain = to_domain(local.x, projection);\n",
" coordinates.style.visibility = \"visible\";\n",
" coordinates.setAttribute(\"transform\", \"translate(\" + local.x + \")\");\n",
" var text = coordinates.querySelector(\"text\");\n",
" text.textContent = domain.toFixed(2);\n",
" }\n",
" else\n",
" {\n",
" coordinates.style.visibility= \"hidden\";\n",
" }\n",
" }\n",
" }\n",
" }\n",
"\n",
" canvas.addEventListener(\"click\", display_coordinates);\n",
"\n",
" var module = {};\n",
" module.show_coordinates = function(axis_id, projection)\n",
" {\n",
" axes[axis_id] = projection;\n",
" }\n",
"\n",
" return module;\n",
" })(modules[\"toyplot/canvas\"]);\n",
"(function(axis, axis_id, projection)\n",
" {\n",
" axis.show_coordinates(axis_id, projection);\n",
" })(modules[\"toyplot.coordinates.Axis\"],\"tc7fd0278c38146ef9a34165bb332b4ff\",[{\"domain\": {\"bounds\": {\"max\": Infinity, \"min\": -Infinity}, \"max\": 1.0, \"min\": 0.0}, \"range\": {\"bounds\": {\"max\": Infinity, \"min\": -Infinity}, \"max\": 286.0, \"min\": 0.0}, \"scale\": \"linear\"}]);\n",
"})();</script></div></div>"
],
"text/plain": [
"<toyplot.canvas.Canvas at 0x118761250>"
]
},
"execution_count": 6,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"# Build arrays of scalars and colors\n",
"scalar_array = pandas.Series(numpy.linspace(0, 1, 1024))\n",
"sRGB_array = pandas.Series(diverging_color_map.map_scalar_array(scalar_array))\n",
"rgb_array = sRGB_array.apply(lambda color: color.get_value_tuple())\n",
"\n",
"# Create toyplot colormap object\n",
"palette = toyplot.color.Palette(colors=rgb_array.values)\n",
"colormap = toyplot.color.LinearMap(palette=palette,\n",
" domain_min=0, domain_max=1)\n",
"\n",
"# Create toyplot display of colors.\n",
"canvas = toyplot.Canvas(width=130, height=300)\n",
"numberline = canvas.numberline(x1=16, x2=16, y1=-7, y2=7)\n",
"numberline.padding = 5\n",
"numberline.axis.spine.show = False\n",
"numberline.colormap(colormap,\n",
" width=30,\n",
" style={'stroke':'lightgrey'})\n",
"\n",
"control_point_scalars = [0, 0.5, 1]\n",
"control_point_labels = []\n",
"for scalar in control_point_scalars:\n",
" control_point_labels.append(\n",
" '{:1.1f}, {}'.format(\n",
" scalar,\n",
" diverging_color_map.map_scalar(scalar).get_upscaled_value_tuple()))\n",
"\n",
"numberline.axis.ticks.locator = \\\n",
" toyplot.locator.Explicit(locations=control_point_scalars,\n",
" labels=control_point_labels)\n",
"numberline.axis.ticks.labels.angle = -90\n",
"numberline.axis.ticks.labels.style = {'text-anchor':'start',\n",
" 'baseline-shift':'0%',\n",
" '-toyplot-anchor-shift':'7px'\n",
" }\n",
"\n",
"toyplot.svg.render(canvas, 'bent-cool-warm.svg')\n",
"\n",
"canvas"
]
},
{
"cell_type": "markdown",
"metadata": {
"collapsed": true
},
"source": [
"## Color Table Files"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"A convenience function that takes a column of RGB triples in a pandas dataframe, unzips it, and adds three columns to the data frame with the red, green, and blue values."
]
},
{
"cell_type": "code",
"execution_count": 7,
"metadata": {},
"outputs": [],
"source": [
"def unzip_rgb_triple(dataframe, column='RGB'):\n",
" '''Given a dataframe and the name of a column holding an RGB triplet,\n",
" this function creates new separate columns for the R, G, and B values\n",
" with the same name as the original with '_r', '_g', and '_b' appended.'''\n",
" # Creates a data frame with separate columns for the triples in the given column\n",
" unzipped_rgb = pandas.DataFrame(dataframe[column].values.tolist(),\n",
" columns=['r', 'g', 'b'])\n",
" # Add the columns to the original data frame\n",
" dataframe[column + '_r'] = unzipped_rgb['r']\n",
" dataframe[column + '_g'] = unzipped_rgb['g']\n",
" dataframe[column + '_b'] = unzipped_rgb['b']"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Create several csv files containing color tables for this color map. We will create color tables of many different sizes from 8 rows to 1024. We also write out one set of csv files for \"upscaled\" color bytes (values 0-255) and another for floating point numbers (0-1)."
]
},
{
"cell_type": "code",
"execution_count": 8,
"metadata": {},
"outputs": [],
"source": [
"for num_bits in range(3, 11):\n",
" table_length = 2 ** num_bits\n",
" color_table = pandas.DataFrame()\n",
" color_table['scalar'] = numpy.linspace(0, 1, table_length)\n",
" color_table['sRGBColor'] = \\\n",
" diverging_color_map.map_scalar_array(color_table['scalar'])\n",
" color_table['RGB'] = \\\n",
" color_table['sRGBColor'].apply(lambda rgb: rgb.get_upscaled_value_tuple())\n",
" color_table['sRGB'] = \\\n",
" color_table['sRGBColor'].apply(lambda rgb: rgb.get_value_tuple())\n",
" unzip_rgb_triple(color_table, 'RGB')\n",
" color_table.to_csv('bent-cool-warm-table-byte-{:04}.csv'.format(table_length),\n",
" index=False,\n",
" columns=['scalar', 'RGB_r', 'RGB_g', 'RGB_b'])\n",
" unzip_rgb_triple(color_table, 'sRGB')\n",
" color_table.to_csv('bent-cool-warm-table-float-{:04}.csv'.format(table_length),\n",
" index=False,\n",
" columns=['scalar', 'sRGB_r', 'sRGB_g', 'sRGB_b'],\n",
" header=['scalar', 'RGB_r', 'RGB_g', 'RGB_b'])"
]
},
{
"cell_type": "markdown",
"metadata": {
"collapsed": true
},
"source": [
"Create a color preset file for ParaView. Since ParaView 4.4, JSON files are supported, which makes it easy to export.\n",
"\n",
"ParaView can interpolate in Lab space, but not LCH space. To approximate the curve we add several control points."
]
},
{
"cell_type": "code",
"execution_count": 9,
"metadata": {},
"outputs": [],
"source": [
"color_table = pandas.DataFrame()\n",
"color_table['scalar'] = numpy.linspace(0, 1, 15)\n",
"color_table['sRGBColor'] = \\\n",
" diverging_color_map.map_scalar_array(color_table['scalar'])\n",
"color_table['sRGB'] = \\\n",
" color_table['sRGBColor'].apply(lambda rgb: rgb.get_value_tuple())\n",
"\n",
"RGBPoints = []\n",
"for index in range(0, color_table.index.size):\n",
" RGBPoints.append(color_table['scalar'][index])\n",
" RGBPoints.extend(color_table['sRGB'][index])\n",
" \n",
"#RGBPoints"
]
},
{
"cell_type": "code",
"execution_count": 10,
"metadata": {},
"outputs": [],
"source": [
"import json\n",
"\n",
"file_descriptor = open('bent-cool-warm-paraview-colors.json', 'w')\n",
"json.dump([{'ColorSpace':'Lab',\n",
" 'Name':'Bent Cool Warm',\n",
" 'NanColor':[1.0,1.0,0.0],\n",
" 'RGBPoints':RGBPoints}],\n",
" file_descriptor,\n",
" indent=2)\n",
"file_descriptor.close()"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": []
}
],
"metadata": {
"kernelspec": {
"display_name": "Python 3",
"language": "python",
"name": "python3"
},
"language_info": {
"codemirror_mode": {
"name": "ipython",
"version": 3
},
"file_extension": ".py",
"mimetype": "text/x-python",
"name": "python",
"nbconvert_exporter": "python",
"pygments_lexer": "ipython3",
"version": "3.8.8"
}
},
"nbformat": 4,
"nbformat_minor": 1
}