diff --git a/plotly/graph_reference/default-schema.json b/plotly/graph_reference/default-schema.json index cc0ae722eec..27cdf82abf6 100644 --- a/plotly/graph_reference/default-schema.json +++ b/plotly/graph_reference/default-schema.json @@ -11230,6 +11230,7 @@ }, "lighting": { "ambient": { + "description": "Ambient light increases overall color visibility but can wash out the image.", "dflt": 0.8, "max": 1, "min": 0, @@ -11237,13 +11238,23 @@ "valType": "number" }, "diffuse": { + "description": "Represents the extent that incident rays are reflected in a range of angles.", "dflt": 0.8, "max": 1, "min": 0, "role": "style", "valType": "number" }, + "facenormalsepsilon": { + "description": "Epsilon for face normals calculation avoids math issues arising from degenerate geometry.", + "dflt": 1e-06, + "max": 1, + "min": 0, + "role": "style", + "valType": "number" + }, "fresnel": { + "description": "Represents the reflectance as a dependency of the viewing angle; e.g. paper is reflective when viewing it from the edge of the paper (almost 90 degrees), causing shine.", "dflt": 0.2, "max": 5, "min": 0, @@ -11252,6 +11263,7 @@ }, "role": "object", "roughness": { + "description": "Alters specular reflection; the rougher the surface, the wider and less contrasty the shine.", "dflt": 0.5, "max": 1, "min": 0, @@ -11259,11 +11271,47 @@ "valType": "number" }, "specular": { + "description": "Represents the level that incident rays are reflected in a single direction, causing shine.", "dflt": 0.05, "max": 2, "min": 0, "role": "style", "valType": "number" + }, + "vertexnormalsepsilon": { + "description": "Epsilon for vertex normals calculation avoids math issues arising from degenerate geometry.", + "dflt": 1e-12, + "max": 1, + "min": 0, + "role": "style", + "valType": "number" + } + }, + "lightposition": { + "role": "object", + "x": { + "description": "Numeric vector, representing the X coordinate for each vertex.", + "dflt": 100000, + "max": 100000, + "min": -100000, + "role": "style", + "valType": "number" + }, + "y": { + "description": "Numeric vector, representing the Y coordinate for each vertex.", + "dflt": 100000, + "max": 100000, + "min": -100000, + "role": "style", + "valType": "number" + }, + "z": { + "description": "Numeric vector, representing the Z coordinate for each vertex.", + "dflt": 0, + "max": 100000, + "min": -100000, + "role": "style", + "valType": "number" } }, "name": { @@ -17937,6 +17985,7 @@ }, "lighting": { "ambient": { + "description": "Ambient light increases overall color visibility but can wash out the image.", "dflt": 0.8, "max": 1, "min": 0, @@ -17944,6 +17993,7 @@ "valType": "number" }, "diffuse": { + "description": "Represents the extent that incident rays are reflected in a range of angles.", "dflt": 0.8, "max": 1, "min": 0, @@ -17951,6 +18001,7 @@ "valType": "number" }, "fresnel": { + "description": "Represents the reflectance as a dependency of the viewing angle; e.g. paper is reflective when viewing it from the edge of the paper (almost 90 degrees), causing shine.", "dflt": 0.2, "max": 5, "min": 0, @@ -17959,6 +18010,7 @@ }, "role": "object", "roughness": { + "description": "Alters specular reflection; the rougher the surface, the wider and less contrasty the shine.", "dflt": 0.5, "max": 1, "min": 0, @@ -17966,6 +18018,7 @@ "valType": "number" }, "specular": { + "description": "Represents the level that incident rays are reflected in a single direction, causing shine.", "dflt": 0.05, "max": 2, "min": 0, @@ -17973,6 +18026,33 @@ "valType": "number" } }, + "lightposition": { + "role": "object", + "x": { + "description": "Numeric vector, representing the X coordinate for each vertex.", + "dflt": 10, + "max": 100000, + "min": -100000, + "role": "style", + "valType": "number" + }, + "y": { + "description": "Numeric vector, representing the Y coordinate for each vertex.", + "dflt": 100000, + "max": 100000, + "min": -100000, + "role": "style", + "valType": "number" + }, + "z": { + "description": "Numeric vector, representing the Z coordinate for each vertex.", + "dflt": 0, + "max": 100000, + "min": -100000, + "role": "style", + "valType": "number" + } + }, "name": { "description": "Sets the trace name. The trace name appear as the legend item and on hover.", "role": "info", diff --git a/plotly/tests/test_optional/test_figure_factory.py b/plotly/tests/test_optional/test_figure_factory.py index 8604595c987..608320338e6 100644 --- a/plotly/tests/test_optional/test_figure_factory.py +++ b/plotly/tests/test_optional/test_figure_factory.py @@ -929,7 +929,8 @@ def test_trisurf_all_args(self): z, simplices, color_func=colors_bad) # Check converting custom colors to strings test_colors_plot = tls.FigureFactory.create_trisurf( - x, y, z, simplices, color_func=colors_raw) + x, y, z, simplices, color_func=colors_raw + ) self.assertTrue(isinstance(test_colors_plot['data'][0]['facecolor'][0], str)) diff --git a/plotly/tools.py b/plotly/tools.py index 5ffe5eb6f95..c2cf7a394e1 100644 --- a/plotly/tools.py +++ b/plotly/tools.py @@ -1468,9 +1468,9 @@ def _find_intermediate_color(lowcolor, highcolor, intermed): diff_1 = float(highcolor[1] - lowcolor[1]) diff_2 = float(highcolor[2] - lowcolor[2]) - inter_colors = np.array([lowcolor[0] + intermed * diff_0, - lowcolor[1] + intermed * diff_1, - lowcolor[2] + intermed * diff_2]) + inter_colors = (lowcolor[0] + intermed * diff_0, + lowcolor[1] + intermed * diff_1, + lowcolor[2] + intermed * diff_2) return inter_colors @staticmethod @@ -1503,14 +1503,14 @@ def _unconvert_from_RGB_255(colors): return un_rgb_colors @staticmethod - def _map_array2color(array, colormap, vmin, vmax): + def _map_face2color(face, colormap, vmin, vmax): """ - Normalize values in array by vmin/vmax and return plotly color strings. + Normalize facecolor values by vmin/vmax and return rgb-color strings - This function takes an array of values along with a colormap and a - minimum (vmin) and maximum (vmax) range of possible z values for the - given parametrized surface. It returns an rgb color based on the - relative position of zval between vmin and vmax + This function takes a tuple color along with a colormap and a minimum + (vmin) and maximum (vmax) range of possible mean distances for the + given parametrized surface. It returns an rgb color based on the mean + distance between vmin and vmax """ if vmin >= vmax: @@ -1518,20 +1518,37 @@ def _map_array2color(array, colormap, vmin, vmax): "and vmax. The vmin value cannot be " "bigger than or equal to the value " "of vmax.") - # find distance t of zval from vmin to vmax where the distance - # is normalized to be between 0 and 1 - t = (array - vmin) / float((vmax - vmin)) - t_colors = FigureFactory._find_intermediate_color(colormap[0], - colormap[1], - t) - t_colors = t_colors * 255. - labelled_colors = ['rgb(%s, %s, %s)' % (i, j, k) - for i, j, k in t_colors.T] - return labelled_colors + + 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) + 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: + # 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( + 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 @staticmethod def _trisurf(x, y, z, simplices, colormap=None, color_func=None, - plot_edges=False, x_edge=None, y_edge=None, z_edge=None): + plot_edges=False, x_edge=None, y_edge=None, z_edge=None, + facecolor=None): """ Refer to FigureFactory.create_trisurf() for docstring """ @@ -1556,8 +1573,10 @@ def _trisurf(x, y, z, simplices, colormap=None, color_func=None, if len(color_func) != len(simplices): raise ValueError("If color_func is a list/array, it must " "be the same length as simplices.") - # convert all colors to rgb - for index in range(len(color_func)): + + # convert all colors to rgb + 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) @@ -1581,10 +1600,16 @@ def _trisurf(x, y, z, simplices, colormap=None, color_func=None, else: min_mean_dists = np.min(mean_dists) max_mean_dists = np.max(mean_dists) - facecolor = FigureFactory._map_array2color(mean_dists, - colormap, - min_mean_dists, - max_mean_dists) + + if facecolor is None: + facecolor = [] + for index in range(len(mean_dists)): + color = FigureFactory._map_face2color(mean_dists[index], + colormap, + min_mean_dists, + max_mean_dists) + facecolor.append(color) + # Make sure we have arrays to speed up plotting facecolor = np.asarray(facecolor) ii, jj, kk = simplices.T @@ -1653,10 +1678,12 @@ def create_trisurf(x, y, z, simplices, colormap=None, color_func=None, :param (array) simplices: an array of shape (ntri, 3) where ntri is the number of triangles in the triangularization. Each row of the array contains the indicies of the verticies of each triangle - :param (str|list) colormap: either a plotly scale name, or a list - containing 2 triplets. These triplets must be of the form (a,b,c) - or 'rgb(x,y,z)' where a,b,c belong to the interval [0,1] and x,y,z - belong to [0,255] + :param (str|tuple|list) colormap: either a plotly scale name, an rgb + or hex color, a color tuple or a list of colors. An rgb color is + of the form 'rgb(x, y, z)' where x, y, z belong to the interval + [0, 255] and a color tuple is a tuple of the form (a, b, c) where + 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 (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 @@ -2931,11 +2958,13 @@ def _label_rgb(colors): """ if isinstance(colors, tuple): - return 'rgb{}'.format(colors) + return ('rgb(%s, %s, %s)' % (colors[0], colors[1], colors[2])) else: colors_label = [] for color in colors: - color_label = 'rgb{}'.format(color) + color_label = ('rgb(%s, %s, %s)' % (color[0], + color[1], + color[2])) colors_label.append(color_label) return colors_label