Skip to content

Commit 920d304

Browse files
committed
added colorscale_to_rgb in colors.py//replaced all trisurf color methods and added custom scale value functionality
1 parent 22cb9d9 commit 920d304

File tree

2 files changed

+182
-81
lines changed

2 files changed

+182
-81
lines changed

plotly/colors.py

Lines changed: 126 additions & 57 deletions
Original file line numberDiff line numberDiff line change
@@ -2,25 +2,28 @@
22
colors
33
=====
44
5-
Functions that manipulate colors and arrays of colors
5+
Functions that manipulate colors and arrays of colors.
66
77
There are three basic types of color types: rgb, hex and tuple:
88
99
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.
1111
1212
hex - A hex color is a string of the form '#xxxxxx' where each x is a
1313
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.
1515
1616
tuple - A tuple color is a 3-tuple of the form (a,b,c) where a, b and c are
1717
floats between 0 and 1 inclusive.
1818
1919
"""
2020
from __future__ import absolute_import
21-
from plotly import exceptions
21+
22+
import decimal
2223
from numbers import Number
2324

25+
from plotly import exceptions
26+
2427
DEFAULT_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

407432
def 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

430470
def 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

Comments
 (0)