22colors
33=====
44
5- Functions that manipulate colors and arrays of colors
5+ Functions that manipulate colors and arrays of colors.
66
77There are three basic types of color types: rgb, hex and tuple:
88
99rgb - 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.
1111
1212hex - A hex color is a string of the form '#xxxxxx' where each x is a
1313character 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.
1515
1616tuple - A tuple color is a 3-tuple of the form (a,b,c) where a, b and c are
1717floats between 0 and 1 inclusive.
1818
1919"""
2020from __future__ import absolute_import
21- from plotly import exceptions
21+
22+ import decimal
2223from numbers import Number
2324
25+ from plotly import exceptions
26+
2427DEFAULT_PLOTLY_COLORS = ['rgb(31, 119, 180)' , 'rgb(255, 127, 14)' ,
2528 'rgb(44, 160, 44)' , 'rgb(214, 39, 40)' ,
2629 'rgb(148, 103, 189)' , 'rgb(140, 86, 75)' ,
@@ -174,17 +177,18 @@ def color_parser(colors, function):
174177 return new_color_list
175178
176179
177- def validate_colors (colors ):
180+ def validate_colors (colors , colors_list = None ):
178181 """
179- Validates color(s) and returns an error for invalid colors
182+ Validates color(s) and returns an error for invalid color(s)
180183 """
181- colors_list = []
184+ if colors_list is None :
185+ colors_list = []
182186
183187 if isinstance (colors , str ):
184188 if colors in PLOTLY_SCALES :
185189 return
186190 elif 'rgb' in colors or '#' in colors :
187- colors_list = [ colors ]
191+ colors_list . append ( colors )
188192 else :
189193 raise exceptions .PlotlyError (
190194 "If your colors variable is a string, it must be a "
@@ -215,42 +219,43 @@ def validate_colors(colors):
215219 "Whoops! The elements in your rgb colors "
216220 "tuples cannot exceed 255.0."
217221 )
218-
219222 elif '#' in each_color :
220223 each_color = color_parser (
221224 each_color , hex_to_rgb
222225 )
223-
224226 elif isinstance (each_color , tuple ):
225227 for value in each_color :
226228 if value > 1.0 :
227229 raise exceptions .PlotlyError (
228230 "Whoops! The elements in your colors tuples "
229231 "cannot exceed 1.0."
230232 )
231- return colors
233+ return
232234
233235
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 ):
235238 """
236239 Converts color(s) to the specified color type
237240
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.
242247 """
243- colors_list = []
248+ if colors_list is None :
249+ colors_list = []
244250
245251 if isinstance (colors , str ):
246252 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+
248257 elif 'rgb' in colors or '#' in colors :
249258 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." )
254259
255260 elif isinstance (colors , tuple ):
256261 if isinstance (colors [0 ], Number ):
@@ -261,6 +266,16 @@ def convert_colors_to_same_type(colors, colortype='rgb'):
261266 elif isinstance (colors , list ):
262267 colors_list = colors
263268
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+
264279 # convert all colors to rgb
265280 for j , each_color in enumerate (colors_list ):
266281 if '#' in each_color :
@@ -282,7 +297,7 @@ def convert_colors_to_same_type(colors, colortype='rgb'):
282297 colors_list [j ] = each_color
283298
284299 if colortype == 'rgb' :
285- return colors_list
300+ return ( colors_list , scale )
286301 elif colortype == 'tuple' :
287302 for j , each_color in enumerate (colors_list ):
288303 each_color = color_parser (
@@ -292,7 +307,7 @@ def convert_colors_to_same_type(colors, colortype='rgb'):
292307 each_color , unconvert_from_RGB_255
293308 )
294309 colors_list [j ] = each_color
295- return colors_list
310+ return ( colors_list , scale )
296311 else :
297312 raise exceptions .PlotlyError ("You must select either rgb or tuple "
298313 "for your colortype variable." )
@@ -339,7 +354,30 @@ def convert_dict_colors_to_same_type(colors, colortype='rgb'):
339354 "for your colortype variable." )
340355
341356
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 ):
343381 """
344382 Makes a colorscale from a list of colors and a scale
345383
@@ -350,36 +388,24 @@ def make_colorscale(colors, scale=None):
350388 For documentation regarding to the form of the output, see
351389 https://plot.ly/python/reference/#mesh3d-colorscale
352390 """
353- colorscale = []
391+ if colorscale is None :
392+ colorscale = []
354393
355394 # validate minimum colors length of 2
356395 if len (colors ) < 2 :
357396 raise exceptions .PlotlyError ("You must input a list of colors that "
358397 "has at least two colors." )
359398
360- if not scale :
399+ if scale is None :
361400 scale_incr = 1. / (len (colors ) - 1 )
362401 return [[i * scale_incr , color ] for i , color in enumerate (colors )]
363402
364403 else :
365- # validate scale
366404 if len (colors ) != len (scale ):
367405 raise exceptions .PlotlyError ("The length of colors and scale "
368406 "must be the same." )
369407
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 )
383409
384410 colorscale = [list (tup ) for tup in zip (scale , colors )]
385411 return colorscale
@@ -398,10 +424,9 @@ def find_intermediate_color(lowcolor, highcolor, intermed):
398424 diff_1 = float (highcolor [1 ] - lowcolor [1 ])
399425 diff_2 = float (highcolor [2 ] - lowcolor [2 ])
400426
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 )
405430
406431
407432def unconvert_from_RGB_255 (colors ):
@@ -413,18 +438,33 @@ def unconvert_from_RGB_255(colors):
413438 a value between 0 and 1
414439
415440 """
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 ))
421444
422445
423- def convert_to_RGB_255 (colors ):
446+ def convert_to_RGB_255 (colors , rgb_components = None ):
424447 """
425448 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
426455 """
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 ])
428468
429469
430470def n_colors (lowcolor , highcolor , n_colors ):
@@ -504,11 +544,40 @@ def hex_to_rgb(value):
504544 for i in range (0 , hex_total_length , rgb_section_length ))
505545
506546
507- def colorscale_to_colors (colorscale ):
547+ def colorscale_to_colors (colorscale , color_list = None ):
508548 """
509- Converts a colorscale into a list of colors
549+ Extracts the colors from colorscale as a list
510550 """
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 ])
514555 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