Skip to content

Allow more than 2 colormaps again #493

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 5 commits into from
Jun 3, 2016
Merged
Show file tree
Hide file tree
Changes from 3 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
80 changes: 80 additions & 0 deletions plotly/graph_reference/default-schema.json
Original file line number Diff line number Diff line change
Expand Up @@ -11230,20 +11230,31 @@
},
"lighting": {
"ambient": {
"description": "Ambient light increases overall color visibility but can wash out the image.",
"dflt": 0.8,
"max": 1,
"min": 0,
"role": "style",
"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,
Expand All @@ -11252,18 +11263,55 @@
},
"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,
"role": "style",
"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": {
Expand Down Expand Up @@ -17937,20 +17985,23 @@
},
"lighting": {
"ambient": {
"description": "Ambient light increases overall color visibility but can wash out the image.",
"dflt": 0.8,
"max": 1,
"min": 0,
"role": "style",
"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"
},
"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,
Expand All @@ -17959,20 +18010,49 @@
},
"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,
"role": "style",
"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"
}
},
"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",
Expand Down
3 changes: 2 additions & 1 deletion plotly/tests/test_optional/test_figure_factory.py
Original file line number Diff line number Diff line change
Expand Up @@ -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))

Expand Down
94 changes: 62 additions & 32 deletions plotly/tools.py
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What is the benefit gained by making it a list and looping through? IME using arrays instead of lists almost always makes things much faster.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What is the benefit gained by making it a list and looping through? IME using arrays instead of lists almost always makes things much faster.

I don't necessarily think it's faster or anything like that. I actually just switched it back to this because I wanted it to work with my FF converting code

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm wondering if there's a benefit other than speed. E.g., if you thought the user might pass in tuples of variable shapes then it's probably better for the input to be a list or tuple or something (since arrays require consistency across dimensions). If you like, maybe I can take a pass through and make it work w/ arrays similar to the last PR. I don't think the syntax would be any different, and it'll likely be an order of magnitude faster to use arrays.

lowcolor[1] + intermed * diff_1,
lowcolor[2] + intermed * diff_2)
return inter_colors

@staticmethod
Expand Down Expand Up @@ -1503,35 +1503,53 @@ 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):
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is support for multiple colormaps mentioned in the docstring for trisurf anywhere? (or is it supported in a different function that calls this function?) I didn't see any mention of multiple colormaps. What's the usecase for including multiple colormaps in the call to trisurf, e.g., how would people specify which colormap goes with which datapoint?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@Kully and @choldgraf did you guys resolve this one?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think I see now what @Kully was getting at, though it took a fair bit of digging for me to figure it out. I wanted to ask - in terms of interpolating between points of a list of multiple colormaps, why not use colorlover? You could just do cl.interp(list_of_colors, 10000)[int(t * 10000)] right?

"""
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:
raise exceptions.PlotlyError("Incorrect relation between vmin "
"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
# find the normalized distance t of a triangle face between vmin and
#vmax where the distance is normalized between 0 and 1
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎎 missing a in this comment.

t = (face - vmin) / float((vmax - vmin))
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🐄 Can you move this check below the if len(colormap) <= 1? You're trying to say "if there is no colormap or the colormap only has one value, just return the same color, always", right? It might make it easier to understand what's going on in this function.


if len(colormap) <= 1:
t_color = colormap[0]
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If len(colormap) == 0 here, you'll get an IndexError. Does this condition need to be updated to len(colormap) == 1? with another case for len(colormap) == 0?

color = FigureFactory._convert_to_RGB_255(t_color)
color = FigureFactory._label_rgb(color)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🐄 Use a different variable name here for readability.

else:
# account for colormaps with more than one color
incr = 1./(len(colormap) - 1)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🐄 incr?

low_color_index = int(t/incr)

if t == 1:
t_color = colormap[low_color_index]
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Similar to https://github.com/plotly/plotly.py/pull/493/files#r65412272. Why not just do colormap[-1] here? It's much easier to understand. You're just doing colormap[1 / (1 / (len(colormap) - 1))].

image

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

so, colormap[np.exp(np.pi * np.sqrt(-1))]? ;)

On Wed, Jun 1, 2016 at 11:19 AM, Andrew [email protected] wrote:

In plotly/tools.py
#493 (comment):

  •    return labelled_colors
    
  •    # find the normalized distance t of a triangle face between vmin and
    
  •    #vmax where the distance is normalized between 0 and 1
    
  •    t = (face - vmin) / float((vmax - vmin))
    
  •    if len(colormap) <= 1:
    
  •        t_color = colormap[0]
    
  •        color = FigureFactory._convert_to_RGB_255(t_color)
    
  •        color = FigureFactory._label_rgb(color)
    
  •    else:
    
  •        # account for colormaps with more than one color
    
  •        incr = 1./(len(colormap) - 1)
    
  •        low_color_index = int(t/incr)
    
  •        if t == 1:
    
  •            t_color = colormap[low_color_index]
    

Similar to https://github.com/plotly/plotly.py/pull/493/files#r65412272.
Why not just do colormap[-1] here? It's much easier to understand. You're
just doing colormap[1 / (1 / (len(colormap) - 1))].

[image: image]
https://camo.githubusercontent.com/4815359abf15ffcb436e4b27c7bdcda27bbc93da/687474703a2f2f696d67732e786b63642e636f6d2f636f6d6963732f655f746f5f7468655f70695f74696d65735f692e706e67


You are receiving this because you were mentioned.
Reply to this email directly, view it on GitHub
https://github.com/plotly/plotly.py/pull/493/files/c5339c1b9e8c3b896c6a70f1c3241630df67aba9#r65414977,
or mute the thread
https://github.com/notifications/unsubscribe/ABwSHaS4pYXxTQM_gb0rE1D9z0zvh-yRks5qHc08gaJpZM4Ip-KO
.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why not just do colormap[-1]

That gets me the last element in the list. How is that helpful here?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

here's where I thought it might be easy to just use interp. I think it'd be a clearer way of doing the same thing. so t_color = cl.interp(colormap, 255)[int(t * 255)].

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

here's where I thought it might be easy to just use interp. I think it'd be a clearer way of doing the same thing. so t_color = cl.interp(colormap, 255)[int(t * 255)].

We're still doing that in another PR right?

I just got Andrew's easier way of computing t_colors working.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ah sure - just lemme know when this is merged. Might be a little bit of time though, because I'm heading out of town for a few days tomorrow

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ah sure - just lemme know when this is merged. Might be a little bit of time though, because I'm heading out of town for a few days tomorrow

Okay no problem.

color = FigureFactory._convert_to_RGB_255(t_color)
color = FigureFactory._label_rgb(color)

else:
t_color = FigureFactory._find_intermediate_color(
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ah, so by "multiple colormaps" do you just mean "will allow 2 colormaps and assume these are the min/max ends of a spectrum, interpolating colors in between"?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ah, so by "multiple colormaps" do you just mean "will allow 2 colormaps and assume these are the min/max ends of a spectrum, interpolating colors in between"?

I mean more than 2 colors and yes, will interpolate between all the colors linearly, first deciding which two colors it is 'in between'. I should make all this more clear in the doc, you're right. I'll do that now after the test hopefully passes

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ah I see - I guess I'm having a hard time following the function. I don't see any for loops here, so how will it iterate through all of the colors if you pass a colormap with, e.g., 4 values?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ah, so by "multiple colormaps" do you just mean "will allow 2 colormaps and assume these are the min/max ends of a spectrum, interpolating colors in between"?

I thought I responded to this message.

Oh well, I said that I want a user to be able to enter a sequence of n colors and this can create a divergent type of colormap, where each facecolor is placed between its closest colors in colormap based on its relative position between vmin and vmax. So you'll get a flow from one color to the next in the plot. I already updated the string docs locally and will update once the tests pass.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Also, just my intuition here, I think it's better to have this handled by the colormap itself, rather than internally in a function like this. E.g., a matplotlib colormap will accept any value from 0 to 1, and will linearly interpolate according to the colormap's parameters. Is there some kind of functionality like this in plotly? (this PR is implementing something like this, but IMO it should be a more general function that is used elsewhere in the codebase and not just for trisurf)

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Mk, I'll wait until this is pushed and then I can iterate off of that. Just a thought - this kind of functionality might be useful in the colorlover package. It'd be useful if there was a ColorMapper class that would do the kinds of interpolation that you're talking about here. That might be a more appropriate place to handle interpolating within or between an arbitrary list of colors. So you could initialize it with a list of colors (like we have here) and an arbitrary number of bins (e.g., mymap = cl.ColorMapper(color_list, n_bins=1000)) and then you could pass a number between 0 and 1 to return the color for that bin (e.g., mymap(.2) and it'd return the color indexed according to int(n_bins * .2)).

This sounds like a good idea. Much more structured.

@theengineear Can you have a quick look at this PR? It looks finished to me and I'd like to push it to master.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sure, looking now.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sure, looking now.

Thanks. I'll fix this all up.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If you guys are curious, I threw together a quick proof of concept here:

https://github.com/choldgraf/ecogtools/blob/master/ecogtools/colors.py

Right now it depends on matplotlib, seaborn, and colorlover, because seaborn does interpolation between colors more effectively than colorlover does (and because I think it's more natural to represent colors as matplotlib maps than lists of rgb strings).

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actually, it's in its own repository now, plus here's an example:

https://github.com/choldgraf/colorbabel/blob/master/examples/demo.ipynb

colormap[low_color_index],
colormap[low_color_index + 1],
(t - low_color_index * incr) / incr)
color = FigureFactory._convert_to_RGB_255(t_color)
color = FigureFactory._label_rgb(color)

return 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
"""
Expand All @@ -1556,8 +1574,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)
Expand All @@ -1581,10 +1601,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
Expand Down Expand Up @@ -1653,10 +1679,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
Expand Down Expand Up @@ -2931,11 +2959,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
Expand Down