2
2
colors
3
3
=====
4
4
5
- Functions that manipulate colors and arrays of colors
5
+ Functions that manipulate colors and arrays of colors.
6
6
7
7
There are three basic types of color types: rgb, hex and tuple:
8
8
9
9
rgb - An rgb color is a string of the form 'rgb(a,b,c)' where a, b and c are
10
- floats between 0 and 255 inclusive.
10
+ integers between 0 and 255 inclusive.
11
11
12
12
hex - A hex color is a string of the form '#xxxxxx' where each x is a
13
13
character that belongs to the set [0,1,2,3,4,5,6,7,8,9,a,b,c,d,e,f]. This is
14
- just the list of characters used in the hexadecimal numeric system.
14
+ just the set of characters used in the hexadecimal numeric system.
15
15
16
16
tuple - A tuple color is a 3-tuple of the form (a,b,c) where a, b and c are
17
17
floats between 0 and 1 inclusive.
18
18
19
19
"""
20
20
from __future__ import absolute_import
21
- from plotly import exceptions
21
+
22
+ import decimal
22
23
from numbers import Number
23
24
25
+ from plotly import exceptions
26
+
24
27
DEFAULT_PLOTLY_COLORS = ['rgb(31, 119, 180)' , 'rgb(255, 127, 14)' ,
25
28
'rgb(44, 160, 44)' , 'rgb(214, 39, 40)' ,
26
29
'rgb(148, 103, 189)' , 'rgb(140, 86, 75)' ,
@@ -174,17 +177,18 @@ def color_parser(colors, function):
174
177
return new_color_list
175
178
176
179
177
- def validate_colors (colors ):
180
+ def validate_colors (colors , colors_list = None ):
178
181
"""
179
- Validates color(s) and returns an error for invalid colors
182
+ Validates color(s) and returns an error for invalid color(s)
180
183
"""
181
- colors_list = []
184
+ if colors_list is None :
185
+ colors_list = []
182
186
183
187
if isinstance (colors , str ):
184
188
if colors in PLOTLY_SCALES :
185
189
return
186
190
elif 'rgb' in colors or '#' in colors :
187
- colors_list = [ colors ]
191
+ colors_list . append ( colors )
188
192
else :
189
193
raise exceptions .PlotlyError (
190
194
"If your colors variable is a string, it must be a "
@@ -215,42 +219,43 @@ def validate_colors(colors):
215
219
"Whoops! The elements in your rgb colors "
216
220
"tuples cannot exceed 255.0."
217
221
)
218
-
219
222
elif '#' in each_color :
220
223
each_color = color_parser (
221
224
each_color , hex_to_rgb
222
225
)
223
-
224
226
elif isinstance (each_color , tuple ):
225
227
for value in each_color :
226
228
if value > 1.0 :
227
229
raise exceptions .PlotlyError (
228
230
"Whoops! The elements in your colors tuples "
229
231
"cannot exceed 1.0."
230
232
)
231
- return colors
233
+ return
232
234
233
235
234
- def convert_colors_to_same_type (colors , colortype = 'rgb' ):
236
+ def convert_colors_to_same_type (colors , colortype = 'rgb' ,
237
+ scale = None , colors_list = None ):
235
238
"""
236
239
Converts color(s) to the specified color type
237
240
238
- Takes a single color or an iterable of colors and outputs a list of the
239
- color(s) converted all to an rgb or tuple color type. If colors is a
240
- Plotly Scale name then the cooresponding colorscale will be outputted and
241
- colortype will not be applicable
241
+ Takes a single color or an iterable of colors, as well as a list of scale
242
+ values, and outputs a 2-pair of the list of color(s) converted all to an
243
+ rgb or tuple color type, aswell as the scale as the second element. If
244
+ colors is a Plotly Scale name, then 'scale' will be forced to the scale
245
+ from the respective colorscale and the colors in that colorscale will also
246
+ be coverted to the selected colortype.
242
247
"""
243
- colors_list = []
248
+ if colors_list is None :
249
+ colors_list = []
244
250
245
251
if isinstance (colors , str ):
246
252
if colors in PLOTLY_SCALES :
247
- return PLOTLY_SCALES [colors ]
253
+ colors_list = colorscale_to_colors (PLOTLY_SCALES [colors ])
254
+ if scale is None :
255
+ scale = colorscale_to_scale (PLOTLY_SCALES [colors ])
256
+
248
257
elif 'rgb' in colors or '#' in colors :
249
258
colors_list = [colors ]
250
- else :
251
- raise exceptions .PlotlyError (
252
- "If your colors variable is a string, it must be a Plotly "
253
- "scale, an rgb color or a hex color." )
254
259
255
260
elif isinstance (colors , tuple ):
256
261
if isinstance (colors [0 ], Number ):
@@ -261,6 +266,16 @@ def convert_colors_to_same_type(colors, colortype='rgb'):
261
266
elif isinstance (colors , list ):
262
267
colors_list = colors
263
268
269
+ # validate scale
270
+ if scale is not None :
271
+ validate_scale_values (scale )
272
+
273
+ if len (colors_list ) != len (scale ):
274
+ raise exceptions .PlotlyError (
275
+ "Make sure that the length of your scale matches the length "
276
+ "of your list of colors which is {}." .format (len (colors_list ))
277
+ )
278
+
264
279
# convert all colors to rgb
265
280
for j , each_color in enumerate (colors_list ):
266
281
if '#' in each_color :
@@ -282,7 +297,7 @@ def convert_colors_to_same_type(colors, colortype='rgb'):
282
297
colors_list [j ] = each_color
283
298
284
299
if colortype == 'rgb' :
285
- return colors_list
300
+ return ( colors_list , scale )
286
301
elif colortype == 'tuple' :
287
302
for j , each_color in enumerate (colors_list ):
288
303
each_color = color_parser (
@@ -292,7 +307,7 @@ def convert_colors_to_same_type(colors, colortype='rgb'):
292
307
each_color , unconvert_from_RGB_255
293
308
)
294
309
colors_list [j ] = each_color
295
- return colors_list
310
+ return ( colors_list , scale )
296
311
else :
297
312
raise exceptions .PlotlyError ("You must select either rgb or tuple "
298
313
"for your colortype variable." )
@@ -339,7 +354,30 @@ def convert_dict_colors_to_same_type(colors, colortype='rgb'):
339
354
"for your colortype variable." )
340
355
341
356
342
- def make_colorscale (colors , scale = None ):
357
+ def validate_scale_values (scale ):
358
+ """
359
+ Validates scale values from a colorscale
360
+ """
361
+ if len (scale ) < 2 :
362
+ raise exceptions .PlotlyError ("You must input a list of scale values "
363
+ "that has at least two values." )
364
+
365
+ if (scale [0 ] != 0 ) or (scale [- 1 ] != 1 ):
366
+ raise exceptions .PlotlyError (
367
+ "The first and last number in your scale must be 0.0 and 1.0 "
368
+ "respectively."
369
+ )
370
+
371
+ for j in range (1 , len (scale )):
372
+ if scale [j ] <= scale [j - 1 ]:
373
+ raise exceptions .PlotlyError (
374
+ "'scale' must be a list that contains an increasing "
375
+ "sequence of numbers."
376
+ )
377
+ return
378
+
379
+
380
+ def make_colorscale (colors , scale = None , colorscale = None ):
343
381
"""
344
382
Makes a colorscale from a list of colors and a scale
345
383
@@ -350,36 +388,24 @@ def make_colorscale(colors, scale=None):
350
388
For documentation regarding to the form of the output, see
351
389
https://plot.ly/python/reference/#mesh3d-colorscale
352
390
"""
353
- colorscale = []
391
+ if colorscale is None :
392
+ colorscale = []
354
393
355
394
# validate minimum colors length of 2
356
395
if len (colors ) < 2 :
357
396
raise exceptions .PlotlyError ("You must input a list of colors that "
358
397
"has at least two colors." )
359
398
360
- if not scale :
399
+ if scale is None :
361
400
scale_incr = 1. / (len (colors ) - 1 )
362
401
return [[i * scale_incr , color ] for i , color in enumerate (colors )]
363
402
364
403
else :
365
- # validate scale
366
404
if len (colors ) != len (scale ):
367
405
raise exceptions .PlotlyError ("The length of colors and scale "
368
406
"must be the same." )
369
407
370
- if (scale [0 ] != 0 ) or (scale [- 1 ] != 1 ):
371
- raise exceptions .PlotlyError (
372
- "The first and last number in scale must be 0.0 and 1.0 "
373
- "respectively."
374
- )
375
-
376
- for j in range (1 , len (scale )):
377
- if scale [j ] <= scale [j - 1 ]:
378
- raise exceptions .PlotlyError (
379
- "'scale' must be a list that contains an increasing "
380
- "sequence of numbers where the first and last number are"
381
- "0.0 and 1.0 respectively."
382
- )
408
+ validate_scale_values (scale )
383
409
384
410
colorscale = [list (tup ) for tup in zip (scale , colors )]
385
411
return colorscale
@@ -398,10 +424,9 @@ def find_intermediate_color(lowcolor, highcolor, intermed):
398
424
diff_1 = float (highcolor [1 ] - lowcolor [1 ])
399
425
diff_2 = float (highcolor [2 ] - lowcolor [2 ])
400
426
401
- inter_colors = (lowcolor [0 ] + intermed * diff_0 ,
402
- lowcolor [1 ] + intermed * diff_1 ,
403
- lowcolor [2 ] + intermed * diff_2 )
404
- return inter_colors
427
+ return (lowcolor [0 ] + intermed * diff_0 ,
428
+ lowcolor [1 ] + intermed * diff_1 ,
429
+ lowcolor [2 ] + intermed * diff_2 )
405
430
406
431
407
432
def unconvert_from_RGB_255 (colors ):
@@ -413,18 +438,33 @@ def unconvert_from_RGB_255(colors):
413
438
a value between 0 and 1
414
439
415
440
"""
416
- un_rgb_color = (colors [0 ]/ (255.0 ),
417
- colors [1 ]/ (255.0 ),
418
- colors [2 ]/ (255.0 ))
419
-
420
- return un_rgb_color
441
+ return (colors [0 ]/ (255.0 ),
442
+ colors [1 ]/ (255.0 ),
443
+ colors [2 ]/ (255.0 ))
421
444
422
445
423
- def convert_to_RGB_255 (colors ):
446
+ def convert_to_RGB_255 (colors , rgb_components = None ):
424
447
"""
425
448
Multiplies each element of a triplet by 255
449
+
450
+ Each coordinate of the color tuple is rounded to the nearest float and
451
+ then is turned into an integer. If a number is of the form x.5, then
452
+ if x is odd, the number rounds up to (x+1). Otherwise, it rounds down
453
+ to just x. This is the way rounding works in Python 3 and in current
454
+ statistical analysis to avoid rounding bias
426
455
"""
427
- return (colors [0 ]* 255.0 , colors [1 ]* 255.0 , colors [2 ]* 255.0 )
456
+ if rgb_components is None :
457
+ rgb_components = []
458
+
459
+ for component in colors :
460
+ rounded_num = decimal .Decimal (str (component * 255.0 )).quantize (
461
+ decimal .Decimal ('1' ), rounding = decimal .ROUND_HALF_EVEN
462
+ )
463
+ # convert rounded number to an integer from 'Decimal' form
464
+ rounded_num = int (rounded_num )
465
+ rgb_components .append (rounded_num )
466
+
467
+ return (rgb_components [0 ], rgb_components [1 ], rgb_components [2 ])
428
468
429
469
430
470
def n_colors (lowcolor , highcolor , n_colors ):
@@ -504,11 +544,40 @@ def hex_to_rgb(value):
504
544
for i in range (0 , hex_total_length , rgb_section_length ))
505
545
506
546
507
- def colorscale_to_colors (colorscale ):
547
+ def colorscale_to_colors (colorscale , color_list = None ):
508
548
"""
509
- Converts a colorscale into a list of colors
549
+ Extracts the colors from colorscale as a list
510
550
"""
511
- color_list = []
512
- for color in colorscale :
513
- color_list .append (color [1 ])
551
+ if color_list is None :
552
+ color_list = []
553
+ for item in colorscale :
554
+ color_list .append (item [1 ])
514
555
return color_list
556
+
557
+
558
+ def colorscale_to_scale (colorscale , scale_list = None ):
559
+ """
560
+ Extracts the interpolation scale values from colorscale as a list
561
+ """
562
+ if scale_list is None :
563
+ scale_list = []
564
+ for item in colorscale :
565
+ scale_list .append (item [0 ])
566
+ return scale_list
567
+
568
+
569
+ def convert_colorscale_to_rgb (colorscale ):
570
+ """
571
+ Converts the colors in a colorscale to rgb colors
572
+
573
+ A colorscale is an array of arrays, each with a numeric value as the
574
+ first item and a color as the second. This function specifically is
575
+ converting a colorscale with tuple colors (each coordinate between 0
576
+ and 1) into a colorscale with the colors transformed into rgb colors
577
+ """
578
+ for color in colorscale :
579
+ color [1 ] = convert_to_RGB_255 (color [1 ])
580
+
581
+ for color in colorscale :
582
+ color [1 ] = label_rgb (color [1 ])
583
+ return colorscale
0 commit comments