#!/usr/bin/env python
# coding: utf-8
# ## Plotly interactive visualization of complex valued functions ##
# In this Jupyter Notebook we generate via [Plotly](https://plot.ly), an interactive plot of a complex valued function, $f:D\subset\mathbb{C}\to\mathbb{C}$. A complex function is visualized using a version of the
# [domain coloring method](http://nbviewer.jupyter.org/github/empet/Math/blob/master/DomainColoring.ipynb).
#
# Compared with other types of domain coloring, the Plotly interactive plot is much more informative. It displays for each point $z$ in a rectangular region of the complex plane,
# not only the hsv color associated to $\arg(f(z)$ (argument of $f(z)$), but also the values $\arg(f(z)$ and $\log(|f(z)|)$ (the log modulus).
# First we define a Plotly hsv (hue, saturation, value) colorscale, adapted to the range of the numpy functions, `np.angle`, respectively, `np.arctan2`.
# We plot a Heatmap over a rectangular region in the complex plane, colored via this colorscale, according to
# the values of the $\arg(f(z))$. Over the Heatmap are plotted a few contour lines of the log modulus $\log(|f(z)|)$.
#
# In[ ]:
import numpy as np
import numpy.ma as ma
from numpy import pi
import matplotlib.pyplot as plt
import matplotlib.colors
# In[ ]:
def hsv_colorscale(S=1, V=1):
if S < 0 or S > 1 or V < 0 or V > 1:
raise ValueError('Parameters S (saturation), V (value, brightness) must be in [0,1]')
argument = np.array([-pi, -5*pi/6, -2*pi/3, -3*pi/6, -pi/3, -pi/6, 0,
pi/6, pi/3, 3*pi/6, 2*pi/3, 5*pi/6, pi])
H = argument/(2*np.pi)+1
H = np.mod(H,1)
Sat = S*np.ones_like(H)
Val = V*np.ones_like(H)
HSV = np.dstack((H, Sat, Val))
RGB = matplotlib.colors.hsv_to_rgb(HSV)
colormap = (255* np.squeeze(RGB)).astype(int)
#Define and return the Plotly hsv colorscale adapted to polar coordinates for complex valued functions
step_size = 1 / (colormap.shape[0]-1)
return [[round(k*step_size, 4), f'rgb{tuple(c)}'] for k, c in enumerate(colormap)]
# In[ ]:
pl_hsv=hsv_colorscale()
pl_hsv
# The following two functions compute data needed for visualization:
# In[ ]:
def evaluate_function(func, re=(-1,1), im=(-1,1), N=100):
# func is the complex function to be ploted
#re, im are the interval ends on the real and imaginary axes, defining the rectangular region in the complex plane
#N gives the number of points in an interval of length 1
l = re[1]-re[0]
h = im[1]-im[0]
resL = int(N*l) #horizontal resolution
resH = int(N*h) #vertical resolution
X = np.linspace(re[0], re[1], resL)
Y = np.linspace(im[0], im[1], resH)
x, y = np.meshgrid(X,Y)
z = x+1j*y
w = func(z)
argument = np.angle(w)
modulus = np.absolute(w)
log_modulus = ma.log(modulus)
return X,Y, argument, log_modulus
# In[ ]:
def get_levels(fmodul, nr=10):
#define the levels for contour plot of the modulus |f(z)|
#fmodul is the log modulus of f(z) computed on meshgrid
#nr= the number of contour lines
mv = np.nanmin(fmodul)
Mv = np.nanmax(fmodul)
size = (Mv-mv)/float(nr)
return [mv+k*size for k in range(nr+1)]
# In[ ]:
import plotly.graph_objects as go
# We extract the contour lines from an attribute of the `matplotlib.contour.QuadContourSet` object returned by the `matplotlib.pyplot.contour`.
#
# The function defined in the next cell retrieves the points on the contour lines segments, and defines the corresponding Plotly traces:
# In[ ]:
def plotly_contour_lines(contour_data):
#contour_data is a matplotlib.contour.QuadContourSet object returned by plt.contour
contours=contour_data.allsegs #
if len(contours)==0:
raise ValueError('Something wrong hapend in computing contour lines')
#contours is a list of lists; if contour is a list in contours, its elements are arrays.
#Each array defines a segment of contour line at some level
xl = []# list of x coordinates of points on a contour line
yl = []# y
#lists of coordinates for contour lines consisting in one point:
xp = []#
yp = []
for k,contour in enumerate(contours):
L = len(contour)
if L!= 0: # sometimes the list of points at the level np.max(modulus) is empty
for ar in contour:
if ar.shape[0] == 1:
xp += [ar[0,0], None]
yp += [ar[0,1], None]
else:
xl += ar[:,0].tolist()
yl += ar[:,1].tolist()
xl.append(None)
yl.append(None)
lines = go.Scatter(x=xl,
y=yl,
mode='lines',
name='modulus',
line=dict(width=1,
color='#a5bab7',
shape='spline',
smoothing=1),
hoverinfo='skip'
)
if len(xp) == 0:
return lines
else:
points = go.Scatter(x=xp, y=yp, mode='markers',
marker=dict(size=4, color='#a5bab7'),
hoverinfo='none')
return lines, points
# Set the layout of the plot:
# In[ ]:
def set_plot_layout(title, width=500, height=500):
return go.Layout(title_text=title,
title_x=0.5,
width=width,
height=height,
showlegend=False,
xaxis_title='Re(z)',
yaxis_title='Im(z)')
# Define a function that associates to each point $z$ in a meshgrid, the strings containing the values to be displayed when hovering the mouse over that point:
# In[ ]:
def text_to_display(x, y, argum, modulus):
m, n = argum.shape
return [['z='+'{:.2f}'.format(x[j])+'+' + '{:.2f}'.format(y[i])+' j'+'
arg(f(z))='+'{:.2f}'.format(argum[i][j])+\
'
log(modulus(f(z)))='+'{:.2f}'.format(modulus[i][j]) for j in range(n)] for i in range(m)]
# Finally, the function `plotly_plot` calls all above defined functions in order to generate the phase plot
# of a given complex function:
# In[ ]:
def plotly_plot(f, re=(-1,1), im=(-1,1), N=50, nr=10, title='', width=500, height=500, **kwargs):
x, y, argument, log_modulus=evaluate_function(f, re=re, im=im, N=N)
levels = get_levels(log_modulus, nr=nr)
plt.figure(figsize=(0.05,0.05))
plt.axis('off')
cp = plt.contour(x,y, log_modulus, levels=levels)
cl = plotly_contour_lines(cp)
text = text_to_display(x,y, argument, log_modulus.data)
tickvals = [-np.pi, -2*np.pi/3, -np.pi/3, 0, np.pi/3, 2*np.pi/3, np.pi]
#define the above values as strings with pi-unicode
ticktext=['-\u03c0', '-2\u03c0/3', '-\u03c0/3', '0', '\u03c0/3', '2\u03c0/3', '\u03c0']
dom = go.Heatmap(x=x, y=y, z=argument, colorscale=pl_hsv,
text=text, hoverinfo='text',
colorbar=dict(thickness=20, tickvals=tickvals,
ticktext=ticktext,
title='arg(f(z))'))
if len(cl) == 2 and isinstance(cl[0], go.Scatter):
data = [dom, cl[0], cl[1]]
else:
data = [dom, cl]
layout = set_plot_layout(title=title)
fig=go.Figure(data=data, layout =layout)
return fig
# As an example we take the function $f(z)=\sin(z)/(1-cos(z^3))$:
# In[ ]:
fig = plotly_plot(lambda z: np.sin(z)/(1-np.cos(z**3)), re=(-2,2), im=(-2,2),
nr=22, title='$f(z)=\\sin(z)/(1-\\cos(z^3))$');
fig.update_layout(xaxis_range=[-2,2], yaxis_range=[-2,2])
fig.show()
# In[1]:
from IPython.display import IFrame
url = "https://chart-studio.plotly.com/~empet/13957"
IFrame(url, width=700, height=700)
# In[2]:
from IPython.core.display import HTML
def css_styling():
styles = open("./custom.css", "r").read()
return HTML(styles)
css_styling()
# In[ ]: