From 920d304c20049f226dd8e0463d556c5abbec347d Mon Sep 17 00:00:00 2001 From: Adam Kulidjian Date: Tue, 13 Sep 2016 18:13:33 -0400 Subject: [PATCH 1/6] added colorscale_to_rgb in colors.py//replaced all trisurf color methods and added custom scale value functionality --- plotly/colors.py | 183 ++++++++++++++++++++++++++++++++--------------- plotly/tools.py | 80 ++++++++++++++------- 2 files changed, 182 insertions(+), 81 deletions(-) diff --git a/plotly/colors.py b/plotly/colors.py index 11e3f9c5f47..05284dc095d 100644 --- a/plotly/colors.py +++ b/plotly/colors.py @@ -2,25 +2,28 @@ colors ===== -Functions that manipulate colors and arrays of colors +Functions that manipulate colors and arrays of colors. There are three basic types of color types: rgb, hex and tuple: rgb - An rgb color is a string of the form 'rgb(a,b,c)' where a, b and c are -floats between 0 and 255 inclusive. +integers between 0 and 255 inclusive. hex - A hex color is a string of the form '#xxxxxx' where each x is a character that belongs to the set [0,1,2,3,4,5,6,7,8,9,a,b,c,d,e,f]. This is -just the list of characters used in the hexadecimal numeric system. +just the set of characters used in the hexadecimal numeric system. tuple - A tuple color is a 3-tuple of the form (a,b,c) where a, b and c are floats between 0 and 1 inclusive. """ from __future__ import absolute_import -from plotly import exceptions + +import decimal from numbers import Number +from plotly import exceptions + DEFAULT_PLOTLY_COLORS = ['rgb(31, 119, 180)', 'rgb(255, 127, 14)', 'rgb(44, 160, 44)', 'rgb(214, 39, 40)', 'rgb(148, 103, 189)', 'rgb(140, 86, 75)', @@ -174,17 +177,18 @@ def color_parser(colors, function): return new_color_list -def validate_colors(colors): +def validate_colors(colors, colors_list=None): """ - Validates color(s) and returns an error for invalid colors + Validates color(s) and returns an error for invalid color(s) """ - colors_list = [] + if colors_list is None: + colors_list = [] if isinstance(colors, str): if colors in PLOTLY_SCALES: return elif 'rgb' in colors or '#' in colors: - colors_list = [colors] + colors_list.append(colors) else: raise exceptions.PlotlyError( "If your colors variable is a string, it must be a " @@ -215,12 +219,10 @@ def validate_colors(colors): "Whoops! The elements in your rgb colors " "tuples cannot exceed 255.0." ) - elif '#' in each_color: each_color = color_parser( each_color, hex_to_rgb ) - elif isinstance(each_color, tuple): for value in each_color: if value > 1.0: @@ -228,29 +230,32 @@ def validate_colors(colors): "Whoops! The elements in your colors tuples " "cannot exceed 1.0." ) - return colors + return -def convert_colors_to_same_type(colors, colortype='rgb'): +def convert_colors_to_same_type(colors, colortype='rgb', + scale=None, colors_list=None): """ Converts color(s) to the specified color type - Takes a single color or an iterable of colors and outputs a list of the - color(s) converted all to an rgb or tuple color type. If colors is a - Plotly Scale name then the cooresponding colorscale will be outputted and - colortype will not be applicable + Takes a single color or an iterable of colors, as well as a list of scale + values, and outputs a 2-pair of the list of color(s) converted all to an + rgb or tuple color type, aswell as the scale as the second element. If + colors is a Plotly Scale name, then 'scale' will be forced to the scale + from the respective colorscale and the colors in that colorscale will also + be coverted to the selected colortype. """ - colors_list = [] + if colors_list is None: + colors_list = [] if isinstance(colors, str): if colors in PLOTLY_SCALES: - return PLOTLY_SCALES[colors] + colors_list = colorscale_to_colors(PLOTLY_SCALES[colors]) + if scale is None: + scale = colorscale_to_scale(PLOTLY_SCALES[colors]) + elif 'rgb' in colors or '#' in colors: colors_list = [colors] - else: - raise exceptions.PlotlyError( - "If your colors variable is a string, it must be a Plotly " - "scale, an rgb color or a hex color.") elif isinstance(colors, tuple): if isinstance(colors[0], Number): @@ -261,6 +266,16 @@ def convert_colors_to_same_type(colors, colortype='rgb'): elif isinstance(colors, list): colors_list = colors + # validate scale + if scale is not None: + validate_scale_values(scale) + + if len(colors_list) != len(scale): + raise exceptions.PlotlyError( + "Make sure that the length of your scale matches the length " + "of your list of colors which is {}.".format(len(colors_list)) + ) + # convert all colors to rgb for j, each_color in enumerate(colors_list): if '#' in each_color: @@ -282,7 +297,7 @@ def convert_colors_to_same_type(colors, colortype='rgb'): colors_list[j] = each_color if colortype == 'rgb': - return colors_list + return (colors_list, scale) elif colortype == 'tuple': for j, each_color in enumerate(colors_list): each_color = color_parser( @@ -292,7 +307,7 @@ def convert_colors_to_same_type(colors, colortype='rgb'): each_color, unconvert_from_RGB_255 ) colors_list[j] = each_color - return colors_list + return (colors_list, scale) else: raise exceptions.PlotlyError("You must select either rgb or tuple " "for your colortype variable.") @@ -339,7 +354,30 @@ def convert_dict_colors_to_same_type(colors, colortype='rgb'): "for your colortype variable.") -def make_colorscale(colors, scale=None): +def validate_scale_values(scale): + """ + Validates scale values from a colorscale + """ + if len(scale) < 2: + raise exceptions.PlotlyError("You must input a list of scale values " + "that has at least two values.") + + if (scale[0] != 0) or (scale[-1] != 1): + raise exceptions.PlotlyError( + "The first and last number in your scale must be 0.0 and 1.0 " + "respectively." + ) + + for j in range(1, len(scale)): + if scale[j] <= scale[j-1]: + raise exceptions.PlotlyError( + "'scale' must be a list that contains an increasing " + "sequence of numbers." + ) + return + + +def make_colorscale(colors, scale=None, colorscale=None): """ Makes a colorscale from a list of colors and a scale @@ -350,36 +388,24 @@ def make_colorscale(colors, scale=None): For documentation regarding to the form of the output, see https://plot.ly/python/reference/#mesh3d-colorscale """ - colorscale = [] + if colorscale is None: + colorscale = [] # validate minimum colors length of 2 if len(colors) < 2: raise exceptions.PlotlyError("You must input a list of colors that " "has at least two colors.") - if not scale: + if scale is None: scale_incr = 1./(len(colors) - 1) return [[i * scale_incr, color] for i, color in enumerate(colors)] else: - # validate scale if len(colors) != len(scale): raise exceptions.PlotlyError("The length of colors and scale " "must be the same.") - if (scale[0] != 0) or (scale[-1] != 1): - raise exceptions.PlotlyError( - "The first and last number in scale must be 0.0 and 1.0 " - "respectively." - ) - - for j in range(1, len(scale)): - if scale[j] <= scale[j-1]: - raise exceptions.PlotlyError( - "'scale' must be a list that contains an increasing " - "sequence of numbers where the first and last number are" - "0.0 and 1.0 respectively." - ) + validate_scale_values(scale) colorscale = [list(tup) for tup in zip(scale, colors)] return colorscale @@ -398,10 +424,9 @@ def find_intermediate_color(lowcolor, highcolor, intermed): diff_1 = float(highcolor[1] - lowcolor[1]) diff_2 = float(highcolor[2] - lowcolor[2]) - inter_colors = (lowcolor[0] + intermed * diff_0, - lowcolor[1] + intermed * diff_1, - lowcolor[2] + intermed * diff_2) - return inter_colors + return (lowcolor[0] + intermed * diff_0, + lowcolor[1] + intermed * diff_1, + lowcolor[2] + intermed * diff_2) def unconvert_from_RGB_255(colors): @@ -413,18 +438,33 @@ def unconvert_from_RGB_255(colors): a value between 0 and 1 """ - un_rgb_color = (colors[0]/(255.0), - colors[1]/(255.0), - colors[2]/(255.0)) - - return un_rgb_color + return (colors[0]/(255.0), + colors[1]/(255.0), + colors[2]/(255.0)) -def convert_to_RGB_255(colors): +def convert_to_RGB_255(colors, rgb_components=None): """ Multiplies each element of a triplet by 255 + + Each coordinate of the color tuple is rounded to the nearest float and + then is turned into an integer. If a number is of the form x.5, then + if x is odd, the number rounds up to (x+1). Otherwise, it rounds down + to just x. This is the way rounding works in Python 3 and in current + statistical analysis to avoid rounding bias """ - return (colors[0]*255.0, colors[1]*255.0, colors[2]*255.0) + if rgb_components is None: + rgb_components = [] + + for component in colors: + rounded_num = decimal.Decimal(str(component*255.0)).quantize( + decimal.Decimal('1'), rounding=decimal.ROUND_HALF_EVEN + ) + # convert rounded number to an integer from 'Decimal' form + rounded_num = int(rounded_num) + rgb_components.append(rounded_num) + + return (rgb_components[0], rgb_components[1], rgb_components[2]) def n_colors(lowcolor, highcolor, n_colors): @@ -504,11 +544,40 @@ def hex_to_rgb(value): for i in range(0, hex_total_length, rgb_section_length)) -def colorscale_to_colors(colorscale): +def colorscale_to_colors(colorscale, color_list=None): """ - Converts a colorscale into a list of colors + Extracts the colors from colorscale as a list """ - color_list = [] - for color in colorscale: - color_list.append(color[1]) + if color_list is None: + color_list = [] + for item in colorscale: + color_list.append(item[1]) return color_list + + +def colorscale_to_scale(colorscale, scale_list=None): + """ + Extracts the interpolation scale values from colorscale as a list + """ + if scale_list is None: + scale_list = [] + for item in colorscale: + scale_list.append(item[0]) + return scale_list + + +def convert_colorscale_to_rgb(colorscale): + """ + Converts the colors in a colorscale to rgb colors + + A colorscale is an array of arrays, each with a numeric value as the + first item and a color as the second. This function specifically is + converting a colorscale with tuple colors (each coordinate between 0 + and 1) into a colorscale with the colors transformed into rgb colors + """ + for color in colorscale: + color[1] = convert_to_RGB_255(color[1]) + + for color in colorscale: + color[1] = label_rgb(color[1]) + return colorscale diff --git a/plotly/tools.py b/plotly/tools.py index 3f529068103..31146b50bf3 100644 --- a/plotly/tools.py +++ b/plotly/tools.py @@ -16,6 +16,7 @@ import math import decimal +from plotly import colors from plotly import utils from plotly import exceptions from plotly import graph_reference @@ -3226,7 +3227,7 @@ def _unconvert_from_RGB_255(colors): colors[2]/(255.0)) @staticmethod - def _map_face2color(face, colormap, vmin, vmax): + def _map_face2color(face, colormap, scale, vmin, vmax): """ Normalize facecolor values by vmin/vmax and return rgb-color strings @@ -3241,36 +3242,58 @@ def _map_face2color(face, colormap, vmin, vmax): "and vmax. The vmin value cannot be " "bigger than or equal to the value " "of vmax.") - if len(colormap) == 1: # color each triangle face with the same color in colormap face_color = colormap[0] - face_color = FigureFactory._convert_to_RGB_255(face_color) - face_color = FigureFactory._label_rgb(face_color) + face_color = colors.convert_to_RGB_255(face_color) + face_color = colors.label_rgb(face_color) + return face_color + if face == vmax: + # pick last color in colormap + face_color = colormap[-1] + face_color = colors.convert_to_RGB_255(face_color) + face_color = colors.label_rgb(face_color) + return face_color else: - if face == vmax: - # pick last color in colormap - face_color = colormap[-1] - face_color = FigureFactory._convert_to_RGB_255(face_color) - face_color = FigureFactory._label_rgb(face_color) - else: + if scale is None: # find the normalized distance t of a triangle face between # vmin and vmax where the distance is between 0 and 1 t = (face - vmin) / float((vmax - vmin)) low_color_index = int(t / (1./(len(colormap) - 1))) - face_color = FigureFactory._find_intermediate_color( + face_color = colors.find_intermediate_color( colormap[low_color_index], colormap[low_color_index + 1], t * (len(colormap) - 1) - low_color_index ) - face_color = FigureFactory._convert_to_RGB_255(face_color) - face_color = FigureFactory._label_rgb(face_color) - return face_color + face_color = colors.convert_to_RGB_255(face_color) + face_color = colors.label_rgb(face_color) + else: + # find the face color for a non-linearly interpolated scale + t = (face - vmin) / float((vmax - vmin)) + + low_color_index = 0 + for k in range(len(scale) - 1): + if scale[k] <= t < scale[k+1]: + break + low_color_index += 1 + + low_scale_val = scale[low_color_index] + high_scale_val = scale[low_color_index + 1] + + face_color = colors.find_intermediate_color( + colormap[low_color_index], + colormap[low_color_index + 1], + (t - low_scale_val)/(high_scale_val - low_scale_val) + ) + + face_color = colors.convert_to_RGB_255(face_color) + face_color = colors.label_rgb(face_color) + return face_color @staticmethod - def _trisurf(x, y, z, simplices, show_colorbar, edges_color, + def _trisurf(x, y, z, simplices, show_colorbar, edges_color, scale, colormap=None, color_func=None, plot_edges=False, x_edge=None, y_edge=None, z_edge=None, facecolor=None): """ @@ -3302,12 +3325,12 @@ def _trisurf(x, y, z, simplices, show_colorbar, edges_color, for index in range(len(color_func)): if isinstance(color_func[index], str): if '#' in color_func[index]: - foo = FigureFactory._hex_to_rgb(color_func[index]) - color_func[index] = FigureFactory._label_rgb(foo) + foo = colors.hex_to_rgb(color_func[index]) + color_func[index] = colors.label_rgb(foo) if isinstance(color_func[index], tuple): - foo = FigureFactory._convert_to_RGB_255(color_func[index]) - color_func[index] = FigureFactory._label_rgb(foo) + foo = colors.convert_to_RGB_255(color_func[index]) + color_func[index] = colors.label_rgb(foo) mean_dists = np.asarray(color_func) else: @@ -3334,6 +3357,7 @@ def _trisurf(x, y, z, simplices, show_colorbar, edges_color, for index in range(len(mean_dists)): color = FigureFactory._map_face2color(mean_dists[index], colormap, + scale, min_mean_dists, max_mean_dists) facecolor.append(color) @@ -3349,8 +3373,8 @@ def _trisurf(x, y, z, simplices, show_colorbar, edges_color, if mean_dists_are_numbers and show_colorbar is True: # make a colorscale from the colors - colorscale = FigureFactory._make_colorscale(colormap) - colorscale = FigureFactory._convert_colorscale_to_rgb(colorscale) + colorscale = colors.make_colorscale(colormap, scale) + colorscale = colors.convert_colorscale_to_rgb(colorscale) colorbar = graph_objs.Scatter3d( x=x[:1], @@ -3422,8 +3446,8 @@ def _trisurf(x, y, z, simplices, show_colorbar, edges_color, @staticmethod def create_trisurf(x, y, z, simplices, colormap=None, show_colorbar=True, - color_func=None, title='Trisurf Plot', plot_edges=True, - showbackground=True, + scale=None, color_func=None, title='Trisurf Plot', + plot_edges=True, showbackground=True, backgroundcolor='rgb(230, 230, 230)', gridcolor='rgb(255, 255, 255)', zerolinecolor='rgb(255, 255, 255)', @@ -3446,6 +3470,9 @@ def create_trisurf(x, y, z, simplices, colormap=None, show_colorbar=True, a, b and c belong to [0, 1]. If colormap is a list, it must contain the valid color types aforementioned as its members :param (bool) show_colorbar: determines if colorbar is visible + :param (list|array) scale: sets the scale values to be used if a non- + linearly interpolated colormap is desired. If left as None, a + linear interpolation between the colors will be excecuted :param (function|list) color_func: The parameter that determines the coloring of the surface. Takes either a function with 3 arguments x, y, z or a list/array of color values the same length as @@ -3654,14 +3681,19 @@ def dist_origin(x, y, z): from plotly.graph_objs import graph_objs # Validate colormap - colormap = FigureFactory._validate_colors(colormap, 'tuple') + colors.validate_colors(colormap) + colormap, scale = colors.convert_colors_to_same_type( + colormap, colortype='tuple', scale=scale + ) data1 = FigureFactory._trisurf(x, y, z, simplices, show_colorbar=show_colorbar, color_func=color_func, colormap=colormap, + scale=scale, edges_color=edges_color, plot_edges=plot_edges) + axis = dict( showbackground=showbackground, backgroundcolor=backgroundcolor, From f9f3bdc7cf49655aec6348ecf8fb1bcbe8864955 Mon Sep 17 00:00:00 2001 From: Adam Kulidjian Date: Wed, 14 Sep 2016 12:44:22 -0400 Subject: [PATCH 2/6] updated tests in colors.py and replace proper trisurf_all_args in test_optional --- plotly/colors.py | 12 ++- .../test_core/test_colors/test_colors.py | 65 ++++++++------- .../test_optional/test_figure_factory.py | 80 +++++++------------ plotly/tools.py | 3 +- 4 files changed, 78 insertions(+), 82 deletions(-) diff --git a/plotly/colors.py b/plotly/colors.py index 05284dc095d..8f0b301c0a7 100644 --- a/plotly/colors.py +++ b/plotly/colors.py @@ -233,8 +233,10 @@ def validate_colors(colors, colors_list=None): return -def convert_colors_to_same_type(colors, colortype='rgb', - scale=None, colors_list=None): +def convert_colors_to_same_type(colors, colortype='rgb', scale=None, + return_default_colors=False, + num_of_defualt_colors=2, + colors_list=None): """ Converts color(s) to the specified color type @@ -243,11 +245,15 @@ def convert_colors_to_same_type(colors, colortype='rgb', rgb or tuple color type, aswell as the scale as the second element. If colors is a Plotly Scale name, then 'scale' will be forced to the scale from the respective colorscale and the colors in that colorscale will also - be coverted to the selected colortype. + be coverted to the selected colortype. If colors is None, then there is an + option to return portion of the DEFAULT_PLOTLY_COLORS """ if colors_list is None: colors_list = [] + if colors is None and return_default_colors is True: + colors_list = DEFAULT_PLOTLY_COLORS[0:num_of_defualt_colors] + if isinstance(colors, str): if colors in PLOTLY_SCALES: colors_list = colorscale_to_colors(PLOTLY_SCALES[colors]) diff --git a/plotly/tests/test_core/test_colors/test_colors.py b/plotly/tests/test_core/test_colors/test_colors.py index 864ac7a3f18..d6884034fb0 100644 --- a/plotly/tests/test_core/test_colors/test_colors.py +++ b/plotly/tests/test_core/test_colors/test_colors.py @@ -39,15 +39,12 @@ def test_validate_colors(self): def test_convert_colors_to_same_type(self): - # test string input - color_string = 'foo' + # test colortype + color_tuple = ['#aaaaaa', '#bbbbbb', '#cccccc'] + scale = [0, 1] - pattern = ("If your colors variable is a string, it must be a " - "Plotly scale, an rgb color or a hex color.") - - self.assertRaisesRegexp(PlotlyError, pattern, - colors.convert_colors_to_same_type, - color_string) + self.assertRaises(PlotlyError, colors.convert_colors_to_same_type, + color_tuple, scale=scale) # test colortype color_tuple = (1, 1, 1) @@ -73,6 +70,38 @@ def test_convert_dict_colors_to_same_type(self): colors.convert_dict_colors_to_same_type, color_dict, colortype) + def test_validate_scale_values(self): + + # test that scale length is at least 2 + scale = [0] + + pattern = ("You must input a list of scale values that has at least " + "two values.") + + self.assertRaisesRegexp(PlotlyError, pattern, + colors.validate_scale_values, + scale) + + # test if first and last number is 0 and 1 respectively + scale = [0, 1.1] + + pattern = ("The first and last number in your scale must be 0.0 and " + "1.0 respectively.") + + self.assertRaisesRegexp(PlotlyError, pattern, + colors.validate_scale_values, + scale) + + # test numbers increase + scale = [0, 2, 1] + + pattern = ("'scale' must be a list that contains an increasing " + "sequence of numbers.") + + self.assertRaisesRegexp(PlotlyError, pattern, + colors.validate_scale_values, + scale) + def test_make_colorscale(self): # test minimum colors length @@ -93,23 +122,3 @@ def test_make_colorscale(self): self.assertRaisesRegexp(PlotlyError, pattern2, colors.make_colorscale, color_list2, scale) - - # test first and last number of scale - scale2 = [0, 2] - - pattern3 = ("The first and last number in scale must be 0.0 and 1.0 " - "respectively.") - - self.assertRaisesRegexp(PlotlyError, pattern3, colors.make_colorscale, - color_list2, scale2) - - # test for strictly increasing scale - color_list3 = [(0, 0, 0), (0.5, 0.5, 0.5), (1, 1, 1)] - scale3 = [0, 1, 1] - - pattern4 = ("'scale' must be a list that contains an increasing " - "sequence of numbers where the first and last number are" - "0.0 and 1.0 respectively.") - - self.assertRaisesRegexp(PlotlyError, pattern4, colors.make_colorscale, - color_list3, scale3) diff --git a/plotly/tests/test_optional/test_figure_factory.py b/plotly/tests/test_optional/test_figure_factory.py index 907628238ef..571c6eb97ac 100644 --- a/plotly/tests/test_optional/test_figure_factory.py +++ b/plotly/tests/test_optional/test_figure_factory.py @@ -842,70 +842,50 @@ def test_trisurf_all_args(self): ) exp_trisurf_plot = { - 'data': [{'facecolor': ['rgb(144, 94, 132)', - 'rgb(23, 190, 207)', - 'rgb(144, 94, 132)', - 'rgb(31, 119, 180)', - 'rgb(144, 94, 132)', - 'rgb(31, 119, 180)', - 'rgb(144, 94, 132)', - 'rgb(23, 190, 207)'], - 'i': np.array([3, 1, 1, 5, 7, 3, 5, 7]), - 'j': np.array([1, 3, 5, 1, 3, 7, 7, 5]), - 'k': np.array([4, 0, 4, 2, 4, 6, 4, 8]), + 'data': [{'facecolor': ['rgb(143, 123, 97)', 'rgb(255, 127, 14)', + 'rgb(143, 123, 97)', 'rgb(31, 119, 180)', + 'rgb(143, 123, 97)', 'rgb(31, 119, 180)', + 'rgb(143, 123, 97)', 'rgb(255, 127, 14)'], + 'i': [3, 1, 1, 5, 7, 3, 5, 7], + 'j': [1, 3, 5, 1, 3, 7, 7, 5], + 'k': [4, 0, 4, 2, 4, 6, 4, 8], 'name': '', 'type': 'mesh3d', - 'x': np.array([-1., 0., 1., -1., 0., - 1., -1., 0., 1.]), - 'y': np.array([-1., -1., -1., 0., 0., - 0., 1., 1., 1.]), - 'z': np.array([1., -0., -1., -0., 0., - 0., -1., 0., 1.])}, + 'x': [-1., 0., 1., -1., 0., 1., -1., 0., 1.], + 'y': [-1., -1., -1., 0., 0., 0., 1., 1., 1.], + 'z': [1., -0., -1., -0., 0., 0., -1., 0., 1.]}, {'line': {'color': 'rgb(50, 50, 50)', 'width': 1.5}, 'mode': 'lines', 'showlegend': False, 'type': 'scatter3d', - 'x': np.array([-1.0, 0.0, 0.0, -1.0, None, 0.0, -1.0, - -1.0, 0.0, None, 0.0, 1.0, 0.0, 0.0, - None, 1.0, 0.0, 1.0, 1.0, None, 0.0, - -1.0, 0.0, 0.0, None, -1.0, 0.0, -1.0, - -1.0, None, 1.0, 0.0, 0.0, 1.0, None, - 0.0, 1.0, 1.0, 0.0, None]), - 'y': np.array([0.0, -1.0, 0.0, 0.0, None, -1.0, 0.0, - -1.0, -1.0, None, -1.0, 0.0, 0.0, -1.0, - None, 0.0, -1.0, -1.0, 0.0, None, 1.0, - 0.0, 0.0, 1.0, None, 0.0, 1.0, 1.0, 0.0, - None, 0.0, 1.0, 0.0, 0.0, None, 1.0, 0.0, - 1.0, 1.0, None]), - 'z': np.array([-0.0, -0.0, 0.0, -0.0, None, -0.0, -0.0, - 1.0, -0.0, None, -0.0, 0.0, 0.0, -0.0, - None, 0.0, -0.0, -1.0, 0.0, None, 0.0, - -0.0, 0.0, 0.0, None, -0.0, 0.0, -1.0, - -0.0, None, 0.0, 0.0, 0.0, 0.0, None, - 0.0, 0.0, 1.0, 0.0, None])}, + 'x': [-1.0, 0.0, 0.0, -1.0, None, 0.0, -1.0, -1.0, 0.0, + None, 0.0, 1.0, 0.0, 0.0, None, 1.0, 0.0, 1.0, + 1.0, None, 0.0, -1.0, 0.0, 0.0, None, -1.0, 0.0, + -1.0, -1.0, None, 1.0, 0.0, 0.0, 1.0, None, 0.0, + 1.0, 1.0, 0.0, None], + 'y': [0.0, -1.0, 0.0, 0.0, None, -1.0, 0.0, -1.0, -1.0, + None, -1.0, 0.0, 0.0, -1.0, None, 0.0, -1.0, -1.0, + 0.0, None, 1.0, 0.0, 0.0, 1.0, None, 0.0, 1.0, + 1.0, 0.0, None, 0.0, 1.0, 0.0, 0.0, None, 1.0, + 0.0, 1.0, 1.0, None], + 'z': [-0.0, -0.0, 0.0, -0.0, None, -0.0, -0.0, 1.0, + -0.0, None, -0.0, 0.0, 0.0, -0.0, None, 0.0, -0.0, + -1.0, 0.0, None, 0.0, -0.0, 0.0, 0.0, None, -0.0, + 0.0, -1.0, -0.0, None, 0.0, 0.0, 0.0, 0.0, None, + 0.0, 0.0, 1.0, 0.0, None]}, {'hoverinfo': 'None', 'marker': {'color': [-0.33333333333333331, 0.33333333333333331], - 'colorscale': [ - [0.0, 'rgb(31, 119, 180)'], - [0.1111111111111111, 'rgb(255, 127, 14)'], - [0.2222222222222222, 'rgb(44, 160, 44)'], - [0.3333333333333333, 'rgb(214, 39, 40)'], - [0.4444444444444444, 'rgb(148, 103, 189)'], - [0.5555555555555556, 'rgb(140, 86, 75)'], - [0.6666666666666666, 'rgb(227, 119, 194)'], - [0.7777777777777778, 'rgb(127, 127, 127)'], - [0.8888888888888888, 'rgb(188, 189, 34)'], - [1.0, 'rgb(23, 190, 207)'] - ], + 'colorscale': [[0.0, 'rgb(31, 119, 180)'], + [1.0, 'rgb(255, 127, 14)']], 'showscale': True, 'size': 0.1}, 'mode': 'markers', 'showlegend': False, 'type': 'scatter3d', - 'x': [-1.0], - 'y': [-1.0], - 'z': [1.0]}], + 'x': [-1.], + 'y': [-1.], + 'z': [1.]}], 'layout': {'height': 800, 'scene': {'aspectratio': {'x': 1, 'y': 1, 'z': 1}, 'xaxis': {'backgroundcolor': 'rgb(230, 230, 230)', diff --git a/plotly/tools.py b/plotly/tools.py index 31146b50bf3..f99dec9cd42 100644 --- a/plotly/tools.py +++ b/plotly/tools.py @@ -3683,7 +3683,8 @@ def dist_origin(x, y, z): # Validate colormap colors.validate_colors(colormap) colormap, scale = colors.convert_colors_to_same_type( - colormap, colortype='tuple', scale=scale + colormap, colortype='tuple', + return_default_colors=True, scale=scale ) data1 = FigureFactory._trisurf(x, y, z, simplices, From 9a701a2bd10fa0236fdf825990c8dbd7fa52c9b7 Mon Sep 17 00:00:00 2001 From: Adam Kulidjian Date: Wed, 14 Sep 2016 17:19:46 -0400 Subject: [PATCH 3/6] Andrew's docs comments - adding docs on various params --- plotly/colors.py | 72 +++++++++++-------- .../test_core/test_colors/test_colors.py | 4 +- 2 files changed, 44 insertions(+), 32 deletions(-) diff --git a/plotly/colors.py b/plotly/colors.py index 8f0b301c0a7..01b1a2d546b 100644 --- a/plotly/colors.py +++ b/plotly/colors.py @@ -159,7 +159,6 @@ def color_parser(colors, function): the color or iterable of colors. If given an iterable, it will only be able to work with it if all items in the iterable are of the same type - rgb string, hex string or tuple - """ if isinstance(colors, str): return function(colors) @@ -180,6 +179,10 @@ def color_parser(colors, function): def validate_colors(colors, colors_list=None): """ Validates color(s) and returns an error for invalid color(s) + + :param (list) colors_list: whether a singleton color or a list/tuple of + colors is inputted, all the color types are appended to colors_list + so they can be easily iterated through for validation """ if colors_list is None: colors_list = [] @@ -191,8 +194,8 @@ def validate_colors(colors, colors_list=None): colors_list.append(colors) else: raise exceptions.PlotlyError( - "If your colors variable is a string, it must be a " - "Plotly scale, an rgb color or a hex color." + 'If your colors variable is a string, it must be a ' + 'Plotly scale, an rgb color or a hex color.' ) elif isinstance(colors, tuple): @@ -216,8 +219,8 @@ def validate_colors(colors, colors_list=None): for value in each_color: if value > 255.0: raise exceptions.PlotlyError( - "Whoops! The elements in your rgb colors " - "tuples cannot exceed 255.0." + 'Whoops! The elements in your rgb colors ' + 'tuples cannot exceed 255.0.' ) elif '#' in each_color: each_color = color_parser( @@ -227,10 +230,9 @@ def validate_colors(colors, colors_list=None): for value in each_color: if value > 1.0: raise exceptions.PlotlyError( - "Whoops! The elements in your colors tuples " - "cannot exceed 1.0." + 'Whoops! The elements in your colors tuples ' + 'cannot exceed 1.0.' ) - return def convert_colors_to_same_type(colors, colortype='rgb', scale=None, @@ -247,6 +249,12 @@ def convert_colors_to_same_type(colors, colortype='rgb', scale=None, from the respective colorscale and the colors in that colorscale will also be coverted to the selected colortype. If colors is None, then there is an option to return portion of the DEFAULT_PLOTLY_COLORS + + :param (list) colors_list: see docs for validate_colors() + :param (list) scale: see docs for validate_scale_values() + + :rtype (tuple) (colors_list, scale) if scale is None in the function call, + then scale will remain None in the returned tuple """ if colors_list is None: colors_list = [] @@ -278,8 +286,8 @@ def convert_colors_to_same_type(colors, colortype='rgb', scale=None, if len(colors_list) != len(scale): raise exceptions.PlotlyError( - "Make sure that the length of your scale matches the length " - "of your list of colors which is {}.".format(len(colors_list)) + 'Make sure that the length of your scale matches the length ' + 'of your list of colors which is {}.'.format(len(colors_list)) ) # convert all colors to rgb @@ -315,8 +323,8 @@ def convert_colors_to_same_type(colors, colortype='rgb', scale=None, colors_list[j] = each_color return (colors_list, scale) else: - raise exceptions.PlotlyError("You must select either rgb or tuple " - "for your colortype variable.") + raise exceptions.PlotlyError('You must select either rgb or tuple ' + 'for your colortype variable.') def convert_dict_colors_to_same_type(colors, colortype='rgb'): @@ -356,31 +364,36 @@ def convert_dict_colors_to_same_type(colors, colortype='rgb'): ) return colors else: - raise exceptions.PlotlyError("You must select either rgb or tuple " - "for your colortype variable.") + raise exceptions.PlotlyError('You must select either rgb or tuple ' + 'for your colortype variable.') def validate_scale_values(scale): """ Validates scale values from a colorscale + + :param (list) scale: a strictly increasing list of floats that begins + with 0 and ends with 1. Its usage derives from a colorscale which is + a list of two-lists (a list with two elements) of the form + [value, color] which are used to determine how interpolation weighting + works between the colors in the colorscale. Therefore scale is just + the extraction of these values from the two-lists in order """ if len(scale) < 2: - raise exceptions.PlotlyError("You must input a list of scale values " - "that has at least two values.") + raise exceptions.PlotlyError('You must input a list of scale values ' + 'that has at least two values.') if (scale[0] != 0) or (scale[-1] != 1): raise exceptions.PlotlyError( - "The first and last number in your scale must be 0.0 and 1.0 " - "respectively." + 'The first and last number in your scale must be 0.0 and 1.0 ' + 'respectively.' ) - for j in range(1, len(scale)): - if scale[j] <= scale[j-1]: + if not all(x < y for x, y in zip(scale, scale[1:])): raise exceptions.PlotlyError( - "'scale' must be a list that contains an increasing " + "'scale' must be a list that contains a strictly increasing " "sequence of numbers." ) - return def make_colorscale(colors, scale=None, colorscale=None): @@ -399,8 +412,8 @@ def make_colorscale(colors, scale=None, colorscale=None): # validate minimum colors length of 2 if len(colors) < 2: - raise exceptions.PlotlyError("You must input a list of colors that " - "has at least two colors.") + raise exceptions.PlotlyError('You must input a list of colors that ' + 'has at least two colors.') if scale is None: scale_incr = 1./(len(colors) - 1) @@ -408,8 +421,8 @@ def make_colorscale(colors, scale=None, colorscale=None): else: if len(colors) != len(scale): - raise exceptions.PlotlyError("The length of colors and scale " - "must be the same.") + raise exceptions.PlotlyError('The length of colors and scale ' + 'must be the same.') validate_scale_values(scale) @@ -424,7 +437,6 @@ def find_intermediate_color(lowcolor, highcolor, intermed): This function takes two color tuples, where each element is between 0 and 1, along with a value 0 < intermed < 1 and returns a color that is intermed-percent from lowcolor to highcolor - """ diff_0 = float(highcolor[0] - lowcolor[0]) diff_1 = float(highcolor[1] - lowcolor[1]) @@ -442,7 +454,6 @@ def unconvert_from_RGB_255(colors): Takes a (list of) color tuple(s) where each element is between 0 and 255. Returns the same tuples where each tuple element is normalized to a value between 0 and 1 - """ return (colors[0]/(255.0), colors[1]/(255.0), @@ -458,6 +469,9 @@ def convert_to_RGB_255(colors, rgb_components=None): if x is odd, the number rounds up to (x+1). Otherwise, it rounds down to just x. This is the way rounding works in Python 3 and in current statistical analysis to avoid rounding bias + + :param (list) rgb_components: grabs the three R, G and B values to be + returned as computed in the function """ if rgb_components is None: rgb_components = [] @@ -480,7 +494,6 @@ def n_colors(lowcolor, highcolor, n_colors): Accepts two color tuples and returns a list of n_colors colors which form the intermediate colors between lowcolor and highcolor from linearly interpolating through RGB space - """ diff_0 = float(highcolor[0] - lowcolor[0]) incr_0 = diff_0/(n_colors - 1) @@ -512,7 +525,6 @@ def unlabel_rgb(colors): This function takes either an 'rgb(a, b, c)' color or a list of such colors and returns the color tuples in tuple(s) (a, b, c) - """ str_vals = '' for index in range(len(colors)): diff --git a/plotly/tests/test_core/test_colors/test_colors.py b/plotly/tests/test_core/test_colors/test_colors.py index d6884034fb0..36da8c6cc59 100644 --- a/plotly/tests/test_core/test_colors/test_colors.py +++ b/plotly/tests/test_core/test_colors/test_colors.py @@ -95,8 +95,8 @@ def test_validate_scale_values(self): # test numbers increase scale = [0, 2, 1] - pattern = ("'scale' must be a list that contains an increasing " - "sequence of numbers.") + pattern = ("'scale' must be a list that contains a strictly " + "increasing sequence of numbers.") self.assertRaisesRegexp(PlotlyError, pattern, colors.validate_scale_values, From 53f44beafc8c8ed4912dde87991b691b01b34986 Mon Sep 17 00:00:00 2001 From: Adam Kulidjian Date: Thu, 15 Sep 2016 10:59:08 -0400 Subject: [PATCH 4/6] made more documentation on colors param in colors.py and redid some examples in string docs (tools.py) --- plotly/colors.py | 53 ++++++++++++++++++++++++++---------------------- plotly/tools.py | 11 +++++----- 2 files changed, 35 insertions(+), 29 deletions(-) diff --git a/plotly/colors.py b/plotly/colors.py index 01b1a2d546b..7b77f4b8c60 100644 --- a/plotly/colors.py +++ b/plotly/colors.py @@ -180,13 +180,16 @@ def validate_colors(colors, colors_list=None): """ Validates color(s) and returns an error for invalid color(s) - :param (list) colors_list: whether a singleton color or a list/tuple of + :param (str|tuple|list) colors: either a plotly scale name, an rgb or hex + color, a color tuple or a list/tuple of colors + :param (list) colors_list: whether a single color or a list/tuple of colors is inputted, all the color types are appended to colors_list so they can be easily iterated through for validation """ if colors_list is None: colors_list = [] + # if colors is a single color, put into colors_list if isinstance(colors, str): if colors in PLOTLY_SCALES: return @@ -250,6 +253,8 @@ def convert_colors_to_same_type(colors, colortype='rgb', scale=None, be coverted to the selected colortype. If colors is None, then there is an option to return portion of the DEFAULT_PLOTLY_COLORS + :param (str|tuple|list) colors: either a plotly scale name, an rgb or hex + color, a color tuple or a list/tuple of colors :param (list) colors_list: see docs for validate_colors() :param (list) scale: see docs for validate_scale_values() @@ -327,42 +332,40 @@ def convert_colors_to_same_type(colors, colortype='rgb', scale=None, 'for your colortype variable.') -def convert_dict_colors_to_same_type(colors, colortype='rgb'): +def convert_dict_colors_to_same_type(colors_dict, colortype='rgb'): """ - Converts color(s) to the specified color type + Converts a colors in a dictioanry of colors to the specified color type - Takes a single color or an iterable of colors and outputs a list of the - color(s) converted all to an rgb or tuple color type. If colors is a - Plotly Scale name then the cooresponding colorscale will be outputted + :param (dict) colors_dict: a dictioanry whose values are single colors """ - for key in colors: - if '#' in colors[key]: - colors[key] = color_parser( - colors[key], hex_to_rgb + for key in colors_dict: + if '#' in colors_dict[key]: + colors_dict[key] = color_parser( + colors_dict[key], hex_to_rgb ) - colors[key] = color_parser( - colors[key], label_rgb + colors_dict[key] = color_parser( + colors_dict[key], label_rgb ) - elif isinstance(colors[key], tuple): - colors[key] = color_parser( - colors[key], convert_to_RGB_255 + elif isinstance(colors_dict[key], tuple): + colors_dict[key] = color_parser( + colors_dict[key], convert_to_RGB_255 ) - colors[key] = color_parser( - colors[key], label_rgb + colors_dict[key] = color_parser( + colors_dict[key], label_rgb ) if colortype == 'rgb': - return colors + return colors_dict elif colortype == 'tuple': - for key in colors: - colors[key] = color_parser( - colors[key], unlabel_rgb + for key in colors_dict: + colors_dict[key] = color_parser( + colors_dict[key], unlabel_rgb ) - colors[key] = color_parser( - colors[key], unconvert_from_RGB_255 + colors_dict[key] = color_parser( + colors_dict[key], unconvert_from_RGB_255 ) - return colors + return colors_dict else: raise exceptions.PlotlyError('You must select either rgb or tuple ' 'for your colortype variable.') @@ -406,6 +409,8 @@ def make_colorscale(colors, scale=None, colorscale=None): list, it must be the same legnth as colors and must contain all floats For documentation regarding to the form of the output, see https://plot.ly/python/reference/#mesh3d-colorscale + + :param (list) colors: a list of single colors """ if colorscale is None: colorscale = [] diff --git a/plotly/tools.py b/plotly/tools.py index f99dec9cd42..ebb9a5fbdd8 100644 --- a/plotly/tools.py +++ b/plotly/tools.py @@ -3521,7 +3521,7 @@ def create_trisurf(x, y, z, simplices, colormap=None, show_colorbar=True, # Create a figure fig1 = FF.create_trisurf(x=x, y=y, z=z, - colormap="Blues", + colormap="Rainbow", simplices=simplices) # Plot the data py.iplot(fig1, filename='trisurf-plot-sphere') @@ -3554,7 +3554,7 @@ def create_trisurf(x, y, z, simplices, colormap=None, show_colorbar=True, # Create a figure fig1 = FF.create_trisurf(x=x, y=y, z=z, - colormap="Greys", + colormap="Viridis", simplices=simplices) # Plot the data py.iplot(fig1, filename='trisurf-plot-torus') @@ -3625,9 +3625,10 @@ def dist_origin(x, y, z): # Create a figure fig1 = FF.create_trisurf(x=x, y=y, z=z, - colormap=['#604d9e', - 'rgb(50, 150, 255)', - (0.2, 0.2, 0.8)], + colormap=['#FFFFFF', '#E4FFFE', + '#A4F6F9', '#FF99FE', + '#BA52ED'], + scale=[0, 0.6, 0.71, 0.89, 1], simplices=simplices, color_func=dist_origin) # Plot the data From aa89eb9319ac84e1a40a43e234e84e2454f55416 Mon Sep 17 00:00:00 2001 From: Adam Kulidjian Date: Fri, 16 Sep 2016 13:42:47 -0400 Subject: [PATCH 5/6] Removed colors_list from call sig --- plotly/colors.py | 16 +++++----------- 1 file changed, 5 insertions(+), 11 deletions(-) diff --git a/plotly/colors.py b/plotly/colors.py index 7b77f4b8c60..4ec13037b93 100644 --- a/plotly/colors.py +++ b/plotly/colors.py @@ -176,18 +176,14 @@ def color_parser(colors, function): return new_color_list -def validate_colors(colors, colors_list=None): +def validate_colors(colors): """ Validates color(s) and returns an error for invalid color(s) :param (str|tuple|list) colors: either a plotly scale name, an rgb or hex color, a color tuple or a list/tuple of colors - :param (list) colors_list: whether a single color or a list/tuple of - colors is inputted, all the color types are appended to colors_list - so they can be easily iterated through for validation """ - if colors_list is None: - colors_list = [] + colors_list = [] # if colors is a single color, put into colors_list if isinstance(colors, str): @@ -240,8 +236,7 @@ def validate_colors(colors, colors_list=None): def convert_colors_to_same_type(colors, colortype='rgb', scale=None, return_default_colors=False, - num_of_defualt_colors=2, - colors_list=None): + num_of_defualt_colors=2): """ Converts color(s) to the specified color type @@ -255,14 +250,13 @@ def convert_colors_to_same_type(colors, colortype='rgb', scale=None, :param (str|tuple|list) colors: either a plotly scale name, an rgb or hex color, a color tuple or a list/tuple of colors - :param (list) colors_list: see docs for validate_colors() :param (list) scale: see docs for validate_scale_values() :rtype (tuple) (colors_list, scale) if scale is None in the function call, then scale will remain None in the returned tuple """ - if colors_list is None: - colors_list = [] + #if colors_list is None: + colors_list = [] if colors is None and return_default_colors is True: colors_list = DEFAULT_PLOTLY_COLORS[0:num_of_defualt_colors] From 69ed5d08f5e27cfdd965169da63a3cef40c074b8 Mon Sep 17 00:00:00 2001 From: Adam Kulidjian Date: Fri, 16 Sep 2016 14:26:12 -0400 Subject: [PATCH 6/6] removed all extraneous param=None in call signatures --- plotly/colors.py | 20 ++++++++------------ 1 file changed, 8 insertions(+), 12 deletions(-) diff --git a/plotly/colors.py b/plotly/colors.py index 4ec13037b93..ea87648ee7e 100644 --- a/plotly/colors.py +++ b/plotly/colors.py @@ -393,7 +393,7 @@ def validate_scale_values(scale): ) -def make_colorscale(colors, scale=None, colorscale=None): +def make_colorscale(colors, scale=None): """ Makes a colorscale from a list of colors and a scale @@ -406,8 +406,7 @@ def make_colorscale(colors, scale=None, colorscale=None): :param (list) colors: a list of single colors """ - if colorscale is None: - colorscale = [] + colorscale = [] # validate minimum colors length of 2 if len(colors) < 2: @@ -459,7 +458,7 @@ def unconvert_from_RGB_255(colors): colors[2]/(255.0)) -def convert_to_RGB_255(colors, rgb_components=None): +def convert_to_RGB_255(colors): """ Multiplies each element of a triplet by 255 @@ -472,8 +471,7 @@ def convert_to_RGB_255(colors, rgb_components=None): :param (list) rgb_components: grabs the three R, G and B values to be returned as computed in the function """ - if rgb_components is None: - rgb_components = [] + rgb_components = [] for component in colors: rounded_num = decimal.Decimal(str(component*255.0)).quantize( @@ -561,23 +559,21 @@ def hex_to_rgb(value): for i in range(0, hex_total_length, rgb_section_length)) -def colorscale_to_colors(colorscale, color_list=None): +def colorscale_to_colors(colorscale): """ Extracts the colors from colorscale as a list """ - if color_list is None: - color_list = [] + color_list = [] for item in colorscale: color_list.append(item[1]) return color_list -def colorscale_to_scale(colorscale, scale_list=None): +def colorscale_to_scale(colorscale): """ Extracts the interpolation scale values from colorscale as a list """ - if scale_list is None: - scale_list = [] + scale_list = [] for item in colorscale: scale_list.append(item[0]) return scale_list