I'm trying to plot a Chord diagram using Matplotlib. I am aware that already existing libraries, such as Plotly give me that functionality but I would really like to do it in matplotlib.
The code I have so far looks like this:
import itertools
import matplotlib.patches as patches
import matplotlib.pyplot as plt
import numpy as np
%matplotlib inline
fig, ax = plt.subplots()
ax.axhline(0, color='black', linestyle='--')
ax.axvline(0, color='black', linestyle='--')
npoints = 3
# Calculate the xy coords for each point on the circle
s = 2 * np.pi / npoints
verts = np.zeros((npoints, 2))
for i in np.arange(npoints):
angle = s * i
x = npoints * np.cos(angle)
y = npoints * np.sin(angle)
verts[i] = [x, y]
# Plot the arcs
numbers = [i for i in xrange(npoints)]
for i, j in itertools.product(numbers, repeat=2):
if i == j:
continue
x1y1 = x1, y1 = verts[i]
x2y2 = x2, y2 = verts[j]
# Calculate the centre of the Arc
mxmy = mx, my = [(x1 + x2) / 2, (y1 + y2) / 2]
r = np.sqrt((x1 - mx)**2 + (y1 - my)**2)
xy = [mx - r, my - r]
width = 2 * r
height = 2 * r
start_angle = np.arctan2(y1 - my, x1 - mx) * 180 / np.pi
end_angle = np.arctan2(y2 - my, x2 - mx) * 180 / np.pi
arc = patches.Arc(mxmy, width, height, start_angle, end_angle)
ax.add_patch(arc)
# Plot the points
x, y = verts.T
ax.scatter(x, y, marker='o', s=50, c='r')
ax.annotate("1", (x[0], y[0]), xytext=(x[0] + .5, y[0] + .5))
ax.annotate("2", (x[1], y[1]), xytext=(x[1] - 1, y[1] + .5))
ax.annotate("3", (x[2], y[2]), xytext=(x[2] - 1, y[2] - 1))
ax.set_xlim(-npoints - 5, npoints + 6)
ax.set_ylim(-npoints - 5, npoints + 6)
ax.set(aspect=1)
Is anyone able to tell me why my plot looks like this?
I'm expecting something more like the following (image taken from Plotly)
Edit 1
I would like to draw arcs between the following points:
1 and 2
1 and 3
2 and 3
These arcs should ideally be on the inside.
Edit 2
After some further investigation I figured that the end_angle seems to be the root of the problem.
After #f5r5e5d pointing out the Bézier curve used in plotly, I've decided to give this one a go. It looks like this is the way to go in my case, too.
import itertools
import matplotlib.patches as patches
import matplotlib.pyplot as plt
import numpy as np
import sys
%matplotlib inline
fig, ax = plt.subplots()
npoints = 5
# Calculate the xy coords for each point on the circle
s = 2 * np.pi / npoints
verts = np.zeros((npoints, 2))
for i in np.arange(npoints):
angle = s * i
x = npoints * np.cos(angle)
y = npoints * np.sin(angle)
verts[i] = [x, y]
# Plot the Bezier curves
numbers = [i for i in xrange(npoints)]
bezier_path = np.arange(0, 1.01, 0.01)
for a, b in itertools.product(numbers, repeat=2):
if a == b:
continue
x1y1 = x1, y1 = verts[a]
x2y2 = x2, y2 = verts[b]
xbyb = xb, yb = [0, 0]
# Compute and store the Bezier curve points
x = (1 - bezier_path)** 2 * x1 + 2 * (1 - bezier_path) * bezier_path * xb + bezier_path** 2 * x2
y = (1 - bezier_path)** 2 * y1 + 2 * (1 - bezier_path) * bezier_path * yb + bezier_path** 2 * y2
ax.plot(x, y, 'k-')
x, y = verts.T
ax.scatter(x, y, marker='o', s=50, c='r')
ax.set_xlim(-npoints - 5, npoints + 6)
ax.set_ylim(-npoints - 5, npoints + 6)
ax.set(aspect=1)
The code above plots what I wanted it do to. Some modifications on the style and it should be good to go.
Since the underlying problem was "how can I draw a chord diagram in matplotlib", I just want to let you know that there is now a python library to do that: mpl-chord-diagram.
You can just do pip install mpl-chord-diagram.
[disclaimer] I am the current maintainer [/disclaimer]
Related
After reading Calculus book I think for function that has no trouble to calculate its inverse it is easy to calculate the surface area about x-axis and y-axis. But, if the function is like this: y = (x^6 + 2)/(8x^2)
I can calculate the surface area revolve around x-axis. But not so easy about y-axis. Since I think calculating the inverse of such function is not easy.
This is the code / MWE:
import matplotlib.pyplot as plt
import numpy as np
import sympy as sy
x = sy.Symbol("x")
def f(x):
return ((x**6) + 2)/ (8*x ** 2)
def fd(x):
return sy.simplify(sy.diff(f(x), x))
def f2(x):
return sy.sqrt((1 + (fd(x)**2)))
def vx(x):
return 2*sy.pi*(f(x)*sy.sqrt(1 + (fd(x) ** 2)))
vxi = sy.Integral(vx(x), (x, 1, 3))
vxf = vxi.simplify().doit()
vxn = vxf.evalf()
n = 100
fig = plt.figure(figsize=(14, 7))
ax1 = fig.add_subplot(221)
ax2 = fig.add_subplot(222, projection='3d')
ax3 = fig.add_subplot(223)
ax4 = fig.add_subplot(224, projection='3d')
x = np.linspace(1, 3, 3)
y = ((x ** 6) + 2) / (8 * x ** 2)
t = np.linspace(0, np.pi * 2, n)
xn = np.outer(x, np.cos(t))
yn = np.outer(x, np.sin(t))
zn = np.zeros_like(xn)
for i in range(len(x)):
zn[i:i + 1, :] = np.full_like(zn[0, :], y[i])
ax1.plot(x, y)
ax1.set_title("$f(x)$")
ax2.plot_surface(xn, yn, zn)
ax2.set_title("$f(x)$: Revolution around $y$")
# find the inverse of the function
y_inverse = x
x_inverse = ((y_inverse ** 6) + 2) / ( 8 * x ** 2)
xn_inverse = np.outer(x_inverse, np.cos(t))
yn_inverse = np.outer(x_inverse, np.sin(t))
zn_inverse = np.zeros_like(xn_inverse)
for i in range(len(x_inverse)):
zn_inverse[i:i + 1, :] = np.full_like(zn_inverse[0, :], y_inverse[i])
ax3.plot(x_inverse, y_inverse)
ax3.set_title("Inverse of $f(x)$")
ax4.plot_surface(xn_inverse, yn_inverse, zn_inverse)
ax4.set_title("$f(x)$: Revolution around $x$ \n Surface Area = {}".format(vxn))
plt.tight_layout()
plt.show()
The question is, can anyone help to calculate the inverse and then continuing with sympy to integrate it and calculate the surface area for this function that is revolved about y-axis?
I have add the assumption of nonnegative for variables x and r so why I can't plot this?
this is my code:
# Calculate the surface area of y = sqrt(r^2 - x^2)
# revolved about the x-axis
import matplotlib.pyplot as plt
import numpy as np
import sympy as sy
x = sy.Symbol("x", nonnegative=True)
r = sy.Symbol("r", nonnegative=True)
def f(x):
return sy.sqrt(r**2 - x**2)
def fd(x):
return sy.simplify(sy.diff(f(x), x))
def f2(x):
return sy.sqrt((1 + (fd(x)**2)))
def vx(x):
return 2*sy.pi*(f(x)*sy.sqrt(1 + (fd(x) ** 2)))
vxi = sy.Integral(vx(x), (x, -r, r))
vxf = vxi.simplify().doit()
vxn = vxf.evalf()
n = 100
fig = plt.figure(figsize=(14, 7))
ax1 = fig.add_subplot(221)
ax2 = fig.add_subplot(222, projection='3d')
ax3 = fig.add_subplot(223)
ax4 = fig.add_subplot(224, projection='3d')
x = np.linspace(1, 3, 3)
# Plot the circle
y = np.sqrt(r ** 2 - x ** 2)
t = np.linspace(0, np.pi * 2, n)
xn = np.outer(x, np.cos(t))
yn = np.outer(x, np.sin(t))
zn = np.zeros_like(xn)
for i in range(len(x)):
zn[i:i + 1, :] = np.full_like(zn[0, :], y[i])
ax1.plot(x, y)
ax1.set_title("$f(x)$")
ax2.plot_surface(xn, yn, zn)
ax2.set_title("$f(x)$: Revolution around $y$")
# find the inverse of the function
y_inverse = x
x_inverse = np.sqrt(r ** 2 - y_inverse ** 2)
xn_inverse = np.outer(x_inverse, np.cos(t))
yn_inverse = np.outer(x_inverse, np.sin(t))
zn_inverse = np.zeros_like(xn_inverse)
for i in range(len(x_inverse)):
zn_inverse[i:i + 1, :] = np.full_like(zn_inverse[0, :], y_inverse[i])
ax3.plot(x_inverse, y_inverse)
ax3.set_title("Inverse of $f(x)$")
ax4.plot_surface(xn_inverse, yn_inverse, zn_inverse)
ax4.set_title("$f(x)$: Revolution around $x$ \n Surface Area = {}".format(vxn))
plt.tight_layout()
plt.show()
That's because at this line of code:
y = np.sqrt(r ** 2 - x ** 2)
r is still a Sympy's symbol. You need to assign a number to r.
I have created this code by modification from previous topics.
I put the calculated volume on the volume plot. My questions are:
My plots are correct right?
My volume calculations are correct too right?
Why there will be negative volume? If I put the formula for vx(x) as r1 - r2 it will be negative. Should I put abs (absolute value) instead in the future? So I could careless If I put r1 - r2 or r2 - r1, the numbers is the same, only one has negative sign. What is the significant meaning of negative sign for volume? Do we need a careful thought when calculating volume through integration?
I do not use sympy is sympy better in calculating integral than numpy/scipy?
Thanks.. this is my code / MWE:
# Compare the plot at xy axis with the solid of revolution toward x and y axis
# For region bounded by the line x - 2y = 0 and y^2 = 4x
# Plotting the revolution of the bounded region
# can be done by limiting the np.linspace of the y, u, and x_inverse values
# You can determine the limits by finding the intersection points of the two functions.
import matplotlib.pyplot as plt
import numpy as np
import sympy as sy
def r1(x):
return x/2
def r2(x):
return 2*(x**(1/2))
def r3(x):
return 2*x
def r4(x):
return (x/2)**(2)
def vx(x):
return np.pi*(r2(x)**2 - r1(x)**2)
def vy(x):
return np.pi*(r3(x)**2 - r4(x)**2)
x = sy.Symbol("x")
vx = sy.integrate(vx(x), (x, 0, 16))
vy = sy.integrate(vy(x), (x, 0, 8))
n = 200
fig = plt.figure(figsize=(14, 7))
ax1 = fig.add_subplot(221)
ax2 = fig.add_subplot(222, projection='3d')
ax3 = fig.add_subplot(223)
ax4 = fig.add_subplot(224, projection='3d')
y = np.linspace(0, 8, n)
x1 = (2*y)
x2 = (y / 2) ** (2)
t = np.linspace(0, np.pi * 2, n)
u = np.linspace(0, 16, n)
v = np.linspace(0, 2 * np.pi, n)
U, V = np.meshgrid(u, v)
X = U
Y1 = (2 * U ** (1/2)) * np.cos(V)
Z1 = (2 * U ** (1/2)) * np.sin(V)
Y2 = (U / 2) * np.cos(V)
Z2 = (U / 2) * np.sin(V)
Y3 = ((U / 2) ** (2)) * np.cos(V)
Z3 = ((U / 2) ** (2)) * np.sin(V)
Y4 = (2*U) * np.cos(V)
Z4 = (2*U) * np.sin(V)
ax1.plot(x1, y, label='$y=x/2$')
ax1.plot(x2, y, label='$y=2 \sqrt{x}$')
ax1.legend()
ax1.set_title('$f(x)$')
ax2.plot_surface(X, Y3, Z3, alpha=0.3, color='red', rstride=6, cstride=12)
ax2.plot_surface(X, Y4, Z4, alpha=0.3, color='blue', rstride=6, cstride=12)
ax2.set_title("$f(x)$: Revolution around $y$ \n Volume = {}".format(vy))
# find the inverse of the function
x_inverse = np.linspace(0, 8, n)
y1_inverse = np.power(2*x_inverse, 1)
y2_inverse = np.power(x_inverse / 2, 2)
ax3.plot(x_inverse, y1_inverse, label='Inverse of $y=x/2$')
ax3.plot(x_inverse, y2_inverse, label='Inverse of $y=2 \sqrt{x}$')
ax3.set_title('Inverse of $f(x)$')
ax3.legend()
ax4.plot_surface(X, Y1, Z1, alpha=0.3, color='red', rstride=6, cstride=12)
ax4.plot_surface(X, Y2, Z2, alpha=0.3, color='blue', rstride=6, cstride=12)
ax4.set_title("$f(x)$: Revolution around $x$ \n Volume = {}".format(vx))
plt.tight_layout()
plt.show()
Your plots are correct except for the plot at the upper right. The boundary is a little bit off. I change the np.linspace for u to u = np.linspace(0, 8, n). However, the np.linspace of u for bottom right plot is correct, so it remains u = np.linspace(0, 16, n). You can create different variable names for them, but I just simply reassign u again to u itself, and create an X2. I attached the complete code below.
Your volume calculations are correct.
Upper right plot:
Bottom right plot:
It is impossible to have negative volume. You can solve the integrate by hand first and compare it to the numerical results. You can check:
https://math.stackexchange.com/questions/261244/is-there-a-fundamental-reason-that-int-ba-int-ab?rq=1
SymPy is for symbolic computation, but it can also do numerical integration, so does SciPy. My guess is that they both have dependencies on NumPy. I think you are fine as long as you implement them correctly.
# Compare the plot at xy axis with the solid of revolution toward x and y axis
# For region bounded by the line x - 2y = 0 and y^2 = 4x
# Plotting the revolution of the bounded region
# can be done by limiting the np.linspace of the y, u, and x_inverse values
# You can determine the limits by finding the intersection points of the two functions.
import matplotlib.pyplot as plt
import numpy as np
import sympy as sy
def r1(x):
return x / 2
def r2(x):
return 2 * (x ** (1 / 2))
def r3(x):
return 2 * x
def r4(x):
return (x / 2) ** (2)
def vx(x):
return np.pi * (r2(x) ** 2 - r1(x) ** 2)
def vy(x):
return np.pi * (r3(x) ** 2 - r4(x) ** 2)
x = sy.Symbol("x")
vx = sy.integrate(vx(x), (x, 0, 16))
vy = sy.integrate(vy(x), (x, 0, 8))
n = 200
fig = plt.figure(figsize=(14, 7))
ax1 = fig.add_subplot(221)
ax2 = fig.add_subplot(222, projection='3d')
ax3 = fig.add_subplot(223)
ax4 = fig.add_subplot(224, projection='3d')
y = np.linspace(0, 8, n)
x1 = (2 * y)
x2 = (y / 2) ** (2)
t = np.linspace(0, np.pi * 2, n)
u = np.linspace(0, 16, n)
v = np.linspace(0, 2 * np.pi, n)
U, V = np.meshgrid(u, v)
X = U
Y1 = (2 * U ** (1 / 2)) * np.cos(V)
Z1 = (2 * U ** (1 / 2)) * np.sin(V)
Y2 = (U / 2) * np.cos(V)
Z2 = (U / 2) * np.sin(V)
#######################################
u = np.linspace(0, 8, n) # linspace u for the upper right figure should be from 0 to 8 instead of 0 to 16
v = np.linspace(0, 2 * np.pi, n)
U, V = np.meshgrid(u, v)
X2 = U # created X2 here
Y3 = ((U / 2) ** (2)) * np.cos(V)
Z3 = ((U / 2) ** (2)) * np.sin(V)
Y4 = (2 * U) * np.cos(V)
Z4 = (2 * U) * np.sin(V)
ax1.plot(x1, y, label='$y=x/2$')
ax1.plot(x2, y, label='$y=2 \sqrt{x}$')
ax1.legend()
ax1.set_title('$f(x)$')
ax2.plot_surface(X2, Y3, Z3, alpha=0.3, color='red', rstride=6, cstride=12)
ax2.plot_surface(X2, Y4, Z4, alpha=0.3, color='blue', rstride=6, cstride=12)
ax2.set_title("$f(x)$: Revolution around $y$ \n Volume = {}".format(vy))
# find the inverse of the function
x_inverse = np.linspace(0, 8, n)
y1_inverse = np.power(2 * x_inverse, 1)
y2_inverse = np.power(x_inverse / 2, 2)
ax3.plot(x_inverse, y1_inverse, label='Inverse of $y=x/2$')
ax3.plot(x_inverse, y2_inverse, label='Inverse of $y=2 \sqrt{x}$')
ax3.set_title('Inverse of $f(x)$')
ax3.legend()
ax4.plot_surface(X, Y1, Z1, alpha=0.3, color='red', rstride=6, cstride=12)
ax4.plot_surface(X, Y2, Z2, alpha=0.3, color='blue', rstride=6, cstride=12)
ax4.set_title("$f(x)$: Revolution around $x$ \n Volume = {}".format(vx))
plt.tight_layout()
plt.show()
This snippet of code is going to generate the following two pictures, which represents a complex function. Is it possible to apply the colors of the first image to the surface? If so, how?
import numpy as np
import matplotlib.pyplot as plt
from matplotlib.colors import hsv_to_rgb
def saw_func(x, dx, a, b):
x = x / dx - np.floor(x / dx)
return a + (b - a) * x
def domain_coloring(mag, arg, phaseres=20):
arg[arg < 0] += 2 * np.pi
arg /= (2 * np.pi)
blackp = saw_func(arg, 1 / phaseres, 0.75, 1)
blackm = saw_func(np.log(mag), 2 * np.pi / phaseres, 0.75, 1)
black = blackp * blackm
H = arg
S, V = np.ones_like(H), black
return (hsv_to_rgb(np.dstack([H, S, V])) * 255).astype(np.uint8)
x = y = np.linspace(-2, 2, 500)
x, y = np.meshgrid(x, y)
z = x + 1j * y
f = (z - 1) / (z**2 + z + 1)
mag, arg = np.absolute(f), np.angle(f)
img = domain_coloring(mag, arg)
fig1, ax1 = plt.subplots()
ax1.imshow(
img,
extent = [np.amin(x), np.amax(x), np.amin(y), np.amax(y)],
interpolation = "nearest",
origin = "lower",
)
plt.show()
fig2 = plt.figure()
ax2 = fig2.add_subplot(1, 1, 1, projection="3d")
ax2.plot_surface(x, y, mag)
ax2.set_zlim([0, 10])
plt.show()
This tutorial example uses a parameter facecolors=. The colors need to be rgb values between 0 and 1. The example code uses a 200x200 grid, as 500x500 is rather slow (and also has more problems with artifacts at the asymptotes). rstride and cstride are set to 1 as default plot_surface skips points.
import numpy as np
import matplotlib.pyplot as plt
from matplotlib.colors import hsv_to_rgb
def saw_func(x, dx, a, b):
x = x / dx - np.floor(x / dx)
return a + (b - a) * x
def domain_coloring(mag, arg, phaseres=20):
arg[arg < 0] += 2 * np.pi
arg /= (2 * np.pi)
blackp = saw_func(arg, 1 / phaseres, 0.75, 1)
blackm = saw_func(np.log(mag), 2 * np.pi / phaseres, 0.75, 1)
black = blackp * blackm
H = arg
S, V = np.ones_like(H), black
return hsv_to_rgb(np.dstack([H, S, V]))
x = y = np.linspace(-2, 2, 200)
x, y = np.meshgrid(x, y)
z = x + 1j * y
f = (z - 1) / (z**2 + z + 1)
mag, arg = np.absolute(f), np.angle(f)
img = domain_coloring(mag, arg)
fig2 = plt.figure()
ax2 = fig2.add_subplot(1, 1, 1, projection="3d")
ax2.plot_surface(x, y, mag, facecolors=img)
ax2.set_zlim([0, 10])
plt.show()
I have been trying to get a plot of vector lines going using the matplotlib library and I keep getting something like this:
Not sure what is happening since the code I'm running seems to follow the syntax for how to make a basic quiver plot. I've tried messing with the array type to see if that's the issue but no luck. Some points on the plot just don't seem to be getting any vector data.
import matplotlib.pyplot as plt
import numpy as np
X = np.arange(-2,2,.1)
Y = np.arange(-2,2,.1)
x,y = np.meshgrid(X,Y)
m1 =1
m2 =2
x1 =4/3
x2 =2/3
omega = 3/8
u = -(m1/(abs(x-x1))**3)*(x-x1)-(m2/(abs(x-x2))**3)*(x-x2)+ x*omega
v = -(m1/(abs(y))**3)*(y)-(m2/(abs(y))**3)*(y)+ y*omega
fig, ax = plt.subplots()
ax.quiver(x,y,u,v)
plt.show()
A nice way, I find, to have a look at your data is to normalise the vector field and colour it by intensity. You can always mask glyphs for which the intensity is too low by using a Numpy MaskedArray. Have a look below.
import matplotlib.colors as cl
import matplotlib.pyplot as plt
import matplotlib.ticker as tck
import numpy as np
x, y = np.meshgrid(np.linspace(-2, 2, 41), np.linspace(-2, 2, 41))
m1, m2, x1, x2, omega = (1, 2, 4 / 3, 2 / 3, 3 / 8)
u = -(m1 / abs(x - x1) ** 3 * (x - x1) - m2 / abs(x - x2) ** 3 * (x - x2)
+ x * omega)
v = y * (omega - (m1 + m2) / abs(y) ** 3)
fig, (ax, bx) = plt.subplots(ncols=2, figsize=(20, 10))
ax.quiver(x, y, u, v, antialiased=True, scale=1e4, width=6e-3, headwidth=3,
headlength=4, headaxislength=3.5, pivot='tail',
edgecolors='xkcd:white', linewidths=1)
ax.set_aspect('equal')
w = np.sqrt(u ** 2 + v ** 2)
quiv = bx.quiver(x, y, u / w, v / w, w, antialiased=True, scale=3e1,
width=6e-3, headwidth=3, headlength=4, headaxislength=3.5,
pivot='tail', edgecolors='xkcd:white', linewidths=1,
norm=cl.LogNorm(vmin=1e-1, vmax=1e3))
bx.set_aspect('equal')
fig.colorbar(quiv, cax=fig.add_axes([0.93, 0.1, 0.02, 0.8]),
extend='both', ticks=tck.LogLocator(),
format=tck.LogFormatterSciNotation())
Some of the y values are close to 0 so that you get crazily large v values. I would check the equation because the plot is actually correct (the arrows are infinitely large when y ~= 0).