Skip to content

Commit 866ea46

Browse files
committed
First Push for Trisurf Plots
1 parent 7a25cee commit 866ea46

File tree

1 file changed

+333
-0
lines changed

1 file changed

+333
-0
lines changed

plotly/tools.py

Lines changed: 333 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1425,6 +1425,12 @@ def return_figure_from_figure_or_data(figure_or_data, validate_figure):
14251425
_DEFAULT_INCREASING_COLOR = '#3D9970' # http://clrs.cc
14261426
_DEFAULT_DECREASING_COLOR = '#FF4136'
14271427

1428+
DEFAULT_PLOTLY_COLORS = ['rgb(31, 119, 180)', 'rgb(255, 127, 14)',
1429+
'rgb(44, 160, 44)', 'rgb(214, 39, 40)',
1430+
'rgb(148, 103, 189)', 'rgb(140, 86, 75)',
1431+
'rgb(227, 119, 194)', 'rgb(127, 127, 127)',
1432+
'rgb(188, 189, 34)', 'rgb(23, 190, 207)']
1433+
14281434

14291435
class FigureFactory(object):
14301436
"""
@@ -1442,6 +1448,333 @@ class FigureFactory(object):
14421448
more information and examples of a specific chart type.
14431449
"""
14441450

1451+
@staticmethod
1452+
def _unlabel_rgb(colors):
1453+
unlabelled_colors = []
1454+
for color in colors:
1455+
str_vals = ''
1456+
for index in range(len(color)):
1457+
try:
1458+
float(color[index])
1459+
str_vals = str_vals + color[index]
1460+
except ValueError:
1461+
if (color[index] == ',') or (color[index] == '.'):
1462+
str_vals = str_vals + color[index]
1463+
1464+
str_vals = str_vals + ','
1465+
numbers = []
1466+
str_num = ''
1467+
for char in str_vals:
1468+
if char != ',':
1469+
str_num = str_num + char
1470+
else:
1471+
numbers.append(float(str_num))
1472+
str_num = ''
1473+
unlabelled_tuple = (numbers[0], numbers[1], numbers[2])
1474+
unlabelled_colors.append(unlabelled_tuple)
1475+
1476+
return unlabelled_colors
1477+
1478+
@staticmethod
1479+
def _find_intermediate_color(tuple1, tuple2, t):
1480+
# Given two color tuples (in normalized RGB space)
1481+
# and a value 0 < t < 1, spit out a color that is
1482+
# t percent from tuple1 to tuple2.
1483+
diff_0 = float(tuple2[0] - tuple1[0])
1484+
diff_1 = float(tuple2[1] - tuple1[1])
1485+
diff_2 = float(tuple2[2] - tuple1[2])
1486+
1487+
new_tuple = (tuple1[0] + t*diff_0,
1488+
tuple1[1] + t*diff_1,
1489+
tuple1[2] + t*diff_2)
1490+
return new_tuple
1491+
1492+
@staticmethod
1493+
def _unconvert_from_RGB_255(colors):
1494+
un_rgb_colors = []
1495+
for color in colors:
1496+
un_rgb_color = (color[0]/(255.0),
1497+
color[1]/(255.0),
1498+
color[2]/(255.0))
1499+
1500+
un_rgb_colors.append(un_rgb_color)
1501+
return un_rgb_colors
1502+
1503+
@staticmethod
1504+
def _map_z2color(zval, colormap, vmin, vmax):
1505+
# Map the normalized value zval to a
1506+
#corresponding color in the colormap
1507+
if vmin > vmax:
1508+
raise ValueError("Incorrect relation between vmin and vmax."
1509+
" The vmin cannot be bigger than vmax.")
1510+
t = (zval - vmin)/float((vmax - vmin)) # normalize val
1511+
t_color = FigureFactory._find_intermediate_color(colormap[0],
1512+
colormap[1],
1513+
t)
1514+
t_color = (t_color[0]*255.0, t_color[1]*255.0, t_color[2]*255.0)
1515+
labelled_color = 'rgb{}'.format(t_color)
1516+
1517+
return labelled_color
1518+
1519+
@staticmethod
1520+
def _tri_indices(simplices):
1521+
return ([triplet[c] for triplet in simplices] for c in range(3))
1522+
1523+
@staticmethod
1524+
def _plotly_trisurf(x, y, z, simplices, colormap=None,
1525+
plot_edges=None):
1526+
import numpy as np
1527+
from plotly.graph_objs import graph_objs
1528+
points3D = np.vstack((x, y, z)).T
1529+
1530+
# vertices of the surface triangles
1531+
tri_vertices = map(lambda index: points3D[index], simplices)
1532+
# mean values of z-coordinates of triangle vertices
1533+
zmean = [np.mean(tri[:, 2]) for tri in tri_vertices]
1534+
min_zmean = np.min(zmean)
1535+
max_zmean = np.max(zmean)
1536+
facecolor = ([FigureFactory._map_z2color(zz, colormap, min_zmean,
1537+
max_zmean) for zz in zmean])
1538+
I, J, K = FigureFactory._tri_indices(simplices)
1539+
1540+
triangles = graph_objs.Mesh3d(x=x, y=y, z=z, facecolor=facecolor,
1541+
i=I, j=J, k=K, name='')
1542+
1543+
if plot_edges is None: # the triangle sides are not plotted
1544+
return graph_objs.Data([triangles])
1545+
1546+
else:
1547+
# define the lists Xe, Ye, Ze, of x, y, resp z coordinates of
1548+
# edge end points for each triangle
1549+
# None separates data corresponding to two consecutive triangles
1550+
lists_coord = ([[[T[k % 3][c] for k in range(4)]+[None]
1551+
for T in tri_vertices] for c in range(3)])
1552+
Xe, Ye, Ze = ([reduce(lambda x, y: x+y, lists_coord[k])
1553+
for k in range(3)])
1554+
1555+
# define the lines to be plotted
1556+
lines = graph_objs.Scatter3d(
1557+
x=Xe, y=Ye, z=Ze, mode='lines',
1558+
line=graph_objs.Line(color='rgb(50,50,50)',
1559+
width=1.5)
1560+
)
1561+
1562+
return graph_objs.Data([triangles, lines])
1563+
1564+
@staticmethod
1565+
def create_trisurf(x, y, z, colormap=None, simplices=None,
1566+
title='Trisurf Plot',
1567+
showbackground=True,
1568+
backgroundcolor='rgb(230, 230, 230)',
1569+
gridcolor='rgb(255, 255, 255)',
1570+
zerolinecolor='rgb(255, 255, 255)',
1571+
height=800, width=800,
1572+
aspectratio=dict(x=1, y=1, z=1)):
1573+
"""
1574+
Returns data for a triangulated surface plot.
1575+
1576+
:param (array) x: data values of x in a 1D array
1577+
:param (array) y: data values of y in a 1D array
1578+
:param (array) z: data values of z in a 1D array
1579+
:param (str|list) colormap: either a plotly scale name, or a list
1580+
containing 2 triplets. These triplets must be of the form (a,b,c)
1581+
or 'rgb(x,y,z)' where a,b,c belong to the interval [0,1] and x,y,z
1582+
belong to [0,255]
1583+
:param (array) simplices: an array of shape (ntri, 3) where ntri is
1584+
the number of triangles in the triangularization. Each row of the
1585+
array contains the indicies of the verticies of each triangle.
1586+
:param (str) title: title of the plot
1587+
:param (bool) showbackground: makes background in plot visible
1588+
:param (str) backgroundcolor: color of background. Takes a string of
1589+
the form 'rgb(x,y,z)' x,y,z are between 0 and 255 inclusive.
1590+
:param (str) gridcolor: color of the gridlines besides the axes. Takes
1591+
a string of the form 'rgb(x,y,z)' x,y,z are between 0 and 255
1592+
inclusive.
1593+
:param (str) zerolinecolor: color of the axes. Takes a string of the
1594+
form 'rgb(x,y,z)' x,y,z are between 0 and 255 inclusive.
1595+
:param (int|float) height: the height of the plot (in pixels)
1596+
:param (int|float) width: the width of the plot (in pixels)
1597+
:param (dict) aspectratio: a dictionary of the aspect ratio values for
1598+
the x, y and z axes. 'x', 'y' and 'z' take (int|float) values.
1599+
1600+
Example 1: Sphere
1601+
```
1602+
# Necessary Imports for Trisurf
1603+
import numpy as np
1604+
from scipy.spatial import Delaunay
1605+
1606+
import plotly.plotly as py
1607+
from plotly.tools import FigureFactory as FF
1608+
from plotly.graph_objs import graph_objs
1609+
1610+
# Make data for plot
1611+
u=np.linspace(0, 2*np.pi, 20)
1612+
v=np.linspace(0, np.pi, 20)
1613+
u,v=np.meshgrid(u,v)
1614+
u=u.flatten()
1615+
v=v.flatten()
1616+
1617+
x = np.sin(v)*np.cos(u)
1618+
y = np.sin(v)*np.sin(u)
1619+
z = np.cos(v)
1620+
1621+
points2D = np.vstack([u,v]).T
1622+
tri = Delaunay(points2D)
1623+
simplices = tri.simplices
1624+
1625+
# Create a figure
1626+
fig1 = FF.create_trisurf(x=x, y=y, z=z,
1627+
colormap="Blues",
1628+
simplices=simplices)
1629+
# Plot the data
1630+
py.iplot(fig1, filename='Trisurf Plot')
1631+
```
1632+
1633+
Example 2: Torus
1634+
```
1635+
# Necessary Imports for Trisurf
1636+
import numpy as np
1637+
from scipy.spatial import Delaunay
1638+
1639+
import plotly.plotly as py
1640+
from plotly.tools import FigureFactory as FF
1641+
from plotly.graph_objs import graph_objs
1642+
1643+
# Make data for plot
1644+
u=np.linspace(0, 2*np.pi, 20)
1645+
v=np.linspace(0, 2*np.pi, 20)
1646+
u,v=np.meshgrid(u,v)
1647+
u=u.flatten()
1648+
v=v.flatten()
1649+
1650+
x = (3 + (np.cos(v)))*np.cos(u)
1651+
y = (3 + (np.cos(v)))*np.sin(u)
1652+
z = np.sin(v)
1653+
1654+
points2D = np.vstack([u,v]).T
1655+
tri = Delaunay(points2D)
1656+
simplices = tri.simplices
1657+
1658+
# Create a figure
1659+
fig1 = FF.create_trisurf(x=x, y=y, z=z,
1660+
colormap="Portland",
1661+
simplices=simplices)
1662+
# Plot the data
1663+
py.iplot(fig1, filename='Trisurf Plot')
1664+
```
1665+
1666+
Example 3: Mobius Band
1667+
```
1668+
# Necessary Imports for Trisurf
1669+
import numpy as np
1670+
from scipy.spatial import Delaunay
1671+
1672+
import plotly.plotly as py
1673+
from plotly.tools import FigureFactory as FF
1674+
from plotly.graph_objs import graph_objs
1675+
1676+
# Make data for plot
1677+
u=np.linspace(0, 2*np.pi, 24)
1678+
v=np.linspace(-1, 1, 8)
1679+
u,v=np.meshgrid(u,v)
1680+
u=u.flatten()
1681+
v=v.flatten()
1682+
1683+
tp = 1 + 0.5*v*np.cos(u/2.)
1684+
x=tp*np.cos(u)
1685+
y=tp*np.sin(u)
1686+
z=0.5*v*np.sin(u/2.)
1687+
1688+
points2D = np.vstack([u,v]).T
1689+
tri = Delaunay(points2D)
1690+
simplices = tri.simplices
1691+
1692+
# Create a figure
1693+
fig1 = FF.create_trisurf(x=x, y=y, z=z,
1694+
colormap=[(0.2, 0.4, 0.6),(1, 1, 1)],
1695+
simplices=simplices)
1696+
# Plot the data
1697+
py.iplot(fig1, filename='Trisurf Plot')
1698+
```
1699+
"""
1700+
from plotly.graph_objs import graph_objs
1701+
plotly_scales = {'Greys': ['rgb(0,0,0)', 'rgb(255,255,255)'],
1702+
'YlGnBu': ['rgb(8,29,88)', 'rgb(255,255,217)'],
1703+
'Greens': ['rgb(0,68,27)', 'rgb(247,252,245)'],
1704+
'YlOrRd': ['rgb(128,0,38)', 'rgb(255,255,204)'],
1705+
'Bluered': ['rgb(0,0,255)', 'rgb(255,0,0)'],
1706+
'RdBu': ['rgb(5,10,172)', 'rgb(178,10,28)'],
1707+
'Reds': ['rgb(220,220,220)', 'rgb(178,10,28)'],
1708+
'Blues': ['rgb(5,10,172)', 'rgb(220,220,220)'],
1709+
'Picnic': ['rgb(0,0,255)', 'rgb(255,0,0)'],
1710+
'Rainbow': ['rgb(150,0,90)', 'rgb(255,0,0)'],
1711+
'Portland': ['rgb(12,51,131)', 'rgb(217,30,30)'],
1712+
'Jet': ['rgb(0,0,131)', 'rgb(128,0,0)'],
1713+
'Hot': ['rgb(0,0,0)', 'rgb(255,255,255)'],
1714+
'Blackbody': ['rgb(0,0,0)', 'rgb(160,200,255)'],
1715+
'Earth': ['rgb(0,0,130)', 'rgb(255,255,255)'],
1716+
'Electric': ['rgb(0,0,0)', 'rgb(255,250,220)'],
1717+
'Viridis': ['rgb(68,1,84)', 'rgb(253,231,37)']}
1718+
1719+
if simplices is None:
1720+
raise exceptions.PlotlyError("Make sure you enter 'simplices' "
1721+
"in the trisurf function.")
1722+
1723+
# Validate colormap
1724+
if colormap is None:
1725+
colormap = [DEFAULT_PLOTLY_COLORS[0],
1726+
DEFAULT_PLOTLY_COLORS[1]]
1727+
colormap = FigureFactory._unlabel_rgb(colormap)
1728+
colormap = FigureFactory._unconvert_from_RGB_255(colormap)
1729+
1730+
if isinstance(colormap, str):
1731+
if colormap not in plotly_scales:
1732+
raise exceptions.PlotlyError("You must pick a valid "
1733+
"plotly colorscale name.")
1734+
colormap = [plotly_scales[colormap][0],
1735+
plotly_scales[colormap][1]]
1736+
colormap = FigureFactory._unlabel_rgb(colormap)
1737+
colormap = FigureFactory._unconvert_from_RGB_255(colormap)
1738+
1739+
else:
1740+
if not isinstance(colormap, list):
1741+
raise exceptions.PlotlyError("If 'colormap' is a list, then "
1742+
"its items must be tripets of "
1743+
"the form a,b,c or 'rgbx,y,z' "
1744+
"where a,b,c are between 0 and "
1745+
"1 inclusive and x,y,z are "
1746+
"between 0 and 255 inclusive.")
1747+
if 'rgb' in colormap[0]:
1748+
colormap = FigureFactory._unlabel_rgb(colormap)
1749+
colormap = FigureFactory._unconvert_from_RGB_255(colormap)
1750+
1751+
data1 = FigureFactory._plotly_trisurf(x, y, z, simplices,
1752+
colormap=colormap,
1753+
plot_edges=True)
1754+
axis = dict(
1755+
showbackground=showbackground,
1756+
backgroundcolor=backgroundcolor,
1757+
gridcolor=gridcolor,
1758+
zerolinecolor=zerolinecolor,
1759+
)
1760+
layout = graph_objs.Layout(
1761+
title=title,
1762+
width=width,
1763+
height=height,
1764+
scene=graph_objs.Scene(
1765+
xaxis=graph_objs.XAxis(axis),
1766+
yaxis=graph_objs.YAxis(axis),
1767+
zaxis=graph_objs.ZAxis(axis),
1768+
aspectratio=dict(
1769+
x=aspectratio['x'],
1770+
y=aspectratio['y'],
1771+
z=aspectratio['z']),
1772+
)
1773+
)
1774+
fig1 = graph_objs.Figure(data=data1, layout=layout)
1775+
1776+
return fig1
1777+
14451778
@staticmethod
14461779
def _validate_equal_length(*args):
14471780
"""

0 commit comments

Comments
 (0)