<SYSTEM_TASK:>
Given the following text description, write Python code to implement the functionality described below step by step
<END_TASK>
<USER_TASK:>
Description:
 Plotly interactive spherical Voronoi diagram
Starting with version 0.18.0, scipy provides the class scipy.spatial.SphericalVoronoi, that defines a spherical Voronoi diagram associated to a set of points on a sphere. 
The example posted at the above link suggests to visualize the Voronoi diagram  as a mpl_toolkits.mplot3d.art3d.Poly3DCollection. The collection of polygons has only the vertices on the sphere and  it represents an approximation of the spherical Voronoi diagram.
In this Jupyter Notebook we project the sides of the polygons onto the sphere, in order to get the true boundaries of the spherical Voronoi regions. The spherical Voronoi diagram is visualized as an interactive Plotly plot.
A second Plotly  plot displays a colored polyhedral approximation of a spherical Voronoi diagram.
Step1:  We are working with the unit sphere S(center=O, radius=1).
Step2:  Define the SphericalVoronoi object associated to a set of points
Step3:  sv.vertices is the numpy array of all Voronoi vertices (3d points on the unit sphere).
sv.regions is a list of lists. An inner list stores the indices of vertices associated to a region
Step4:  In order to get the spherical arcs as boundaries of the Voronoi regions we proceed as follows
Step5:  The planar approximations of the spherical Voronoi regions are polygons in the 3d space. The union of their sides is returned by the function
Step6:  The collection of all polygons that represent the  Voronoi regions defines a polyhedral approximation of the spherical Voronoi diagram. This approximation can be visualized as a Plotly Mesh3d object. 
Namely, since each Voronoi region is  a convex polygon, we can triangulate it adding diagonals from the vertex 0  to all non-adjacent vertices.
The triangles of a region are colored with the same color chosen randomly from a list colors.
The following function triangulates each region and assigns to its triangles (simplices) a color
Step7:  Depending on the type of plot, spherical arcs on sphere or polyhedral approximation of the Voronoi diagram,
the function get_data defines the Plotly objects involved in each plot (sphere, points, arcs, respectively,
points, line segments, colored Voronoi regions)
Step8:  Set the plot layout (with axes or not)
Step9:  The Plotly plot of spherical Voronoi regions
Step10:  Define a custom Plotly colorscale to plot the sphere
Step11:  Polyhedral approximation of a spherical Voronoi diagram
List of colors for planar Voronoi regions
<ASSISTANT_TASK:>
Python Code:
import scipy
scipy. __version__
from scipy.spatial import SphericalVoronoi
from scipy.interpolate import splev,  splprep
import numpy as np
import plotly.plotly as py
from plotly.graph_objs import *
Explanation: Plotly interactive spherical Voronoi diagram
Starting with version 0.18.0, scipy provides the class scipy.spatial.SphericalVoronoi, that defines a spherical Voronoi diagram associated to a set of points on a sphere. 
The example posted at the above link suggests to visualize the Voronoi diagram  as a mpl_toolkits.mplot3d.art3d.Poly3DCollection. The collection of polygons has only the vertices on the sphere and  it represents an approximation of the spherical Voronoi diagram.
In this Jupyter Notebook we project the sides of the polygons onto the sphere, in order to get the true boundaries of the spherical Voronoi regions. The spherical Voronoi diagram is visualized as an interactive Plotly plot.
A second Plotly  plot displays a colored polyhedral approximation of a spherical Voronoi diagram.
End of explanation
def sphere():
    theta=np.linspace(0, 2*np.pi, 200)
    phi=np.linspace(0, np.pi, 100)
    theta, phi=np.meshgrid(theta, phi)
    x=np.cos(theta)*np.sin(phi)
    y=np.sin(theta)*np.sin(phi)
    z=np.cos(phi)
    return x, y ,z
Explanation: We are working with the unit sphere S(center=O, radius=1).
End of explanation
def s_Voronoi(points):
    if not isinstance(points, np.ndarray):
        raise ValueError('points must be a numpy array of shape (n,3)')
        
    center = np.zeros(3)
    radius = 1.0
    sv = SphericalVoronoi(points, radius, center)
    sv.sort_vertices_of_regions()# sort vertices
    return sv
Explanation: Define the SphericalVoronoi object associated to a set of points
End of explanation
def points_sphere(N=80):
    points=np.random.randn(N,3)
    return points/np.linalg.norm(points, axis=1)[:, None]
Explanation: sv.vertices is the numpy array of all Voronoi vertices (3d points on the unit sphere).
sv.regions is a list of lists. An inner list stores the indices of vertices associated to a region:
N points uniformly distributed on the unit sphere are defined as unit vectors associated to N 3D-vectors,  of independent coordinates, generated from the standard normal distribution:
End of explanation
def spheric_arcs(sv, R=1.005):
    
    #sv is an instance of SphericalVoronoi class
    #R is a radius slightly greater than 1; the arcs are plotted on the shere of radius R
    #to avoid to be covered by the sphere surface
    
    t=np.array([0, 0.25, 0.5, 0.75, 1.0])# five parameters for convex combination of points
    p=(1-t)[:, None]
    q=t[:, None]
    #Xa, Ya, Za  are the lists of point coordinates to be plotted as spherical arcs
    Xa=[]
    Ya=[]
    Za=[]
    for region in sv.regions:
        P=sv.vertices[region]#P is an array whose rows are the vertices of the Voronoi points on the sphere
        L=P.shape[0]
        for k in range(L):
            B=np.array([P[k,:]]*5)
            C=np.array([P[(k+1)%L, :]]*5)
            A=B*p+C*q#A is an array of 5 points on the segment of ends P[k,:], P[(k+1)%L, :]
       
            A=R*A/np.linalg.norm(A, axis=1)[:, None]#central projection of the points in A onto the sphere 
            tck,u=splprep([A[:,0],A[:,1],A[:,2]],s=0)
            xi,yi, zi= splev(np.linspace(0,1,20),tck)#spline interpolation of the five points on sphere
            Xa+=xi.tolist()
            Ya+=yi.tolist()
            Za+=zi.tolist()
        Xa+=[None]  #after processing a region insert None in each list to avoid 
        Ya+=[None]  #unwanted lines from one region to another
        Za+=[None]
    
    return Xa, Ya, Za
Explanation: In order to get the spherical arcs as boundaries of the Voronoi regions we proceed as follows:
For each pair of consecutive points, $P_k, P_{k+1}$, defining a Voronoi region, compute 5 points on the segment having these points as ends.
The five points are projected (via a central projection of center O) onto the sphere of radius $R$, and the  corresponding points  are spline interpolated to get a spherical arc:
End of explanation
def polygons(sv):
    Xp=[]
    Yp=[]
    Zp=[]
    for region in sv.regions:
        V=sv.vertices[region]
        Xp+=V[:,0].tolist()+[V[0,0], None]
        Yp+=V[:,1].tolist()+[V[0,1], None]
        Zp+=V[:,2].tolist()+[V[0,2], None]
    return Xp, Yp, Zp    
Explanation: The planar approximations of the spherical Voronoi regions are polygons in the 3d space. The union of their sides is returned by the function:
End of explanation
def triangulate_regions(sv, colors):
    simplices=[]# a list of 3-lists of integers giving the vertex indices of a triangle
    facecolor=[]# the list of colors associated to each simplex in the triangulation
   
    for k, region  in enumerate(sv.regions):
        color=colors[np.random.randint(0, len(colors))]# choose the color for the region region
        #triangulate the region 
        simplices+=[[region[0], region[j], region[j+1]] for j in range(1, len(region)-1)]
        facecolor+=[color for _  in range(1, len(region)-1)]
    return simplices, facecolor
Explanation: The collection of all polygons that represent the  Voronoi regions defines a polyhedral approximation of the spherical Voronoi diagram. This approximation can be visualized as a Plotly Mesh3d object. 
Namely, since each Voronoi region is  a convex polygon, we can triangulate it adding diagonals from the vertex 0  to all non-adjacent vertices.
The triangles of a region are colored with the same color chosen randomly from a list colors.
The following function triangulates each region and assigns to its triangles (simplices) a color:
End of explanation
def get_data(points,  R=1.005, arcs=True, colorscale=[], colors=[]):
    
    #for arcs=True, and colorscale of length at least 2, the function set up data for plotting the sphere, data points and
    #spherical arcs 
    # for arcs=False, and length of colors,  non zero -> data for the polyhedral approximation: points, sides of polygons, 
    # colored planar Voronoi regions
    
    sv=s_Voronoi(points)
    
    if arcs:
        if  len(colorscale)<2:
            raise ValueError('the colorscale must have at least length=2')
        x, y, z=sphere()
        sphere_surf=Surface(x=x, y=y, z=z, colorscale=colorscale, showscale=False, name='sphere')
        data_pts=Scatter3d(x=R*points[:,0], y=R*points[:,1], z=R*points[:,2], name='points',
                   mode='markers', marker=dict(color='black', size=3))
        Xa, Ya, Za=spheric_arcs(sv,R=R)
        lines=Scatter3d(x=Xa, y=Ya, z=Za,  name='spheric arc',  mode='lines',
                line=dict(width=2, color='rgb(10,10,10)')) 
        
        return Data([sphere_surf, data_pts, lines ])
    else:
        if len(colors)==0:
            raise ValueError('the list of colors is empty')
        simplices, facecolor=triangulate_regions(sv, colors=colors)    
        I, J, K=np.array(simplices).T
        x,y,z=sv.vertices.T
        triangles=Mesh3d(x=x,
                     y=y,
                     z=z,
                     facecolor=facecolor, 
                     i=I,
                     j=J,
                     k=K,
                     name='',
                    )
        data_pts=Scatter3d(x=points[:,0], y=points[:,1], z=points[:,2], name='points',
                   mode='markers', marker=dict(color='black', size=2))
        Xp, Yp, Zp=polygons(sv)
        lines=Scatter3d(x=Xp, y=Yp, z=Zp,  name='spheric arc',  mode='lines',
                line=dict(width=2, color='rgb(10,10,10)')) 
        
        return Data([triangles, data_pts, lines])
Explanation: Depending on the type of plot, spherical arcs on sphere or polyhedral approximation of the Voronoi diagram,
the function get_data defines the Plotly objects involved in each plot (sphere, points, arcs, respectively,
points, line segments, colored Voronoi regions):
End of explanation
axis = dict(
            showbackground=True, 
            backgroundcolor='rgb(40,40,40)', #"rgb(230, 230,230)",
            gridcolor='rgb(255, 255, 255)',      
            zerolinecolor='rgb(255, 255, 255)',  
            )
noaxis=dict(showbackground=False,
            showgrid=False,
            showline=False,
            showticklabels=False,
            ticks='',
            title='',
            zeroline=False)
def plot_layout(ax=noaxis):
    return Layout(title='Spherical Voronoi Diagram',
                  font=dict(family='Balto', size=16),
                  width=700,
                  height=700,
                  showlegend=False,
                  scene=Scene(xaxis=XAxis(ax),
                              yaxis=YAxis(ax), 
                              zaxis=ZAxis(ax), 
                              aspectratio=dict(x=1,
                                               y=1,
                                               z=1
                                              ),
                                )
                    )
  
Explanation: Set the plot layout (with axes or not):
End of explanation
points=points_sphere(N=80)
Explanation: The Plotly plot of spherical Voronoi regions
End of explanation
pl_col=[[0.0, 'rgb(230,230,230)'], [1.0, 'rgb(230,230,230)']]
data1=get_data(points,  R=1.005, arcs=True, colorscale=pl_col, colors=[])
fig1 = Figure(data=data1, layout=plot_layout(ax=axis))
py.sign_in('empet', 'jhog40sb94')
py.iplot(fig1, filename='sph-voronoi-axes')
Explanation: Define a custom Plotly colorscale to plot the sphere:
End of explanation
colors=['rgb(53,195,176)',
'rgb(168,201,121)',
'rgb(255,210,181)',
'rgb(255,169,164)',
'rgb(255,140,148)']
points=points_sphere(N=100)
data2=get_data(points,  R=1.005, arcs=False, colorscale=[], colors=colors)
fig2 = Figure(data=data2, layout=plot_layout(ax=noaxis))
fig2['layout'].update(title='Polyhedral approximation of a spherical Voronoi diagram')
py.iplot(fig2, filename='polyhedral-voronoi')
from IPython.core.display import HTML
def  css_styling():
    styles = open("./custom.css", "r").read()
    return HTML(styles)
css_styling()
Explanation: Polyhedral approximation of a spherical Voronoi diagram
List of colors for planar Voronoi regions:
End of explanation
<END_TASK>