How to remove a matplotlib collection - python

The Python program below plots a random set of 3 points and circles around them according to the eps slider value.
When changing the slider value, the circles change.
Also, if two circles touch each other, a segment connecting their centers is drawn.
My problem is: how to remove such segment if the eps decreases so there is no intersection anymore?
In resume, how to remove the line collection from the plot?
MWE
import numpy as np
import matplotlib.pyplot as plt
from matplotlib.widgets import Slider, Button, RadioButtons
from matplotlib import collections as mc
# create circles
circles = []
def create_circles(N):
for i in range(N):
circles.append(plt.Circle(pts[i], min_distance/10, ec="b", fill=False))
ax.add_patch(circles[i])
# create edges
def create_edges(eps):
edges = []
tmp = np.where(distance_matrix < 2 * eps)
edges_ij = zip(tmp[0], tmp[1])
for e in edges_ij:
if e[0] < e[1]:
edges.append([pts[e[0]], pts[e[1]]])
lc = mc.LineCollection(edges, colors='r', linewidths=1)
ax.add_collection(lc)
# create points
def create_points(N):
ptsx = np.random.random(N)
ptsy = np.random.random(N)
pts = zip(ptsx, ptsy)
return pts
# create distance matrix
def create_distance_matrix(pts):
N = len(pts)
distance_matrix = np.zeros((N, N))
for i in range(N-1):
for j in range(i+1,N):
P = pts[i]
Q = pts[j]
distance_matrix[i, j] = np.sqrt((P[0]-Q[0])**2 + (P[1]-Q[1])**2)
distance_matrix[j, i] = distance_matrix[i, j]
max_distance = np.max(distance_matrix)
min_distance = np.min(distance_matrix[distance_matrix > 0])
return min_distance, max_distance, distance_matrix
# when epsilon slider changes
def update_eps(val):
eps1 = eps_slider.val
create_edges(eps1)
for i in range(len(circles)):
circles[i].set_radius(eps1)
fig.canvas.draw_idle()
axis_color = 'lightgoldenrodyellow'
bullet_size = 4
xmin, xmax = 0, 1
ymin, ymax = 0, 1
delta = .2
xlim = [xmin-delta, xmax+delta]
ylim = [ymin-delta, ymax+delta]
fig, ax = plt.subplots()
plt.subplots_adjust(bottom=0.25)
ax.axis([xlim[0], xlim[1], ylim[0], ylim[1]])
ax.set_aspect("equal")
N_0 = 3
N_max = 10
pts = create_points(N_0)
min_distance, max_distance, distance_matrix = create_distance_matrix(pts)
create_circles(N_0)
eps_0 = min_distance / 3
# create sliders
eps_slider_ax = fig.add_axes([0.25, 0.15, .65, 0.03], facecolor=axis_color)
N_slider_ax = fig.add_axes([0.25, 0.1, 0.65, 0.03], facecolor=axis_color)
eps_slider = Slider(eps_slider_ax, 'eps', min_distance/3, max_distance, valinit=eps_0)
N_slider = Slider(N_slider_ax, 'Num pts', 1, N_max, valinit=2, valfmt="%i")
eps_slider.on_changed(update_eps)
# Draw the initial plot
desenho = ax.scatter([x[0] for x in pts], [x[1] for x in pts], alpha=1, s=bullet_size)
plt.show()

Related

Visual defects in matplotlib graph

Current matplotlib graph
How it should look like
I want to change the graph color and gradient direction in the parts where graph goes below zero. Alternative image for illustration:
I have tried it using this code
def add_gradient_fill(ax: Optional[plt.Axes] = None, alpha_gradientglow: float = 1.0):
"""Add a gradient fill under each line,
i.e. faintly color the area below the line."""
if not ax:
ax = plt.gca()
lines = ax.get_lines()
for line in lines:
# don't add gradient fill for glow effect lines:
if hasattr(line, 'is_glow_line') and line.is_glow_line:
continue
fill_color = line.get_color()
zorder = line.get_zorder()
alpha = line.get_alpha()
alpha = 1.0 if alpha is None else alpha
rgb = mcolors.colorConverter.to_rgb(fill_color)
z = np.empty((100, 1, 4), dtype=float)
z[:, :, :3] = rgb
z[:, :, -1] = np.linspace(0, alpha, 100)[:, None]
x, y = line.get_data(orig=False)
x, y = np.array(x), np.array(y) # enforce x,y as numpy arrays
xmin, xmax = x.min(), x.max()
ymin, ymax = y.min(), y.max()
im = ax.imshow(z, aspect='auto',
extent=[xmin, xmax, ymin, ymax],
alpha=alpha_gradientglow,
origin='lower', zorder=zorder)
xy = np.column_stack([x, y])
xy = np.vstack([[xmin, ymin], xy, [xmax, ymin], [xmin, ymin]])
clip_path = Polygon(xy, facecolor='none', edgecolor='none', closed=True)
ax.add_patch(clip_path)
im.set_clip_path(clip_path)
ax.autoscale(True)
This code is also a part of a matplotlib theming library called mplcyberpunk.
This provides great looks to the plot, but as mentioned earlier, I want that the sub-zero parts of the graphs be in different color with gradient direction reversed.
How can this be possibly achieved?
PS: Sincerely, my question is different from other graph gradient questions, please don't close this.
Edit
Minimal reproducible code
import matplotlib.pyplot as plt
import mplcyberpunk as mplcp
x = range(-10, 11)
y = [(i ** 2) - 50 for i in x]
plt.style.use('cyberpunk')
###### just for setting the theme, ignore these lines #########
for param in ['figure.facecolor', 'axes.facecolor', 'savefig.facecolor']:
plt.rcParams[param] = '#303030'
for param in ['text.color', 'axes.labelcolor', 'xtick.color', 'ytick.color']:
plt.rcParams[param] = '#ffffff'
plt.subplots()[1].grid(color='#404040')
##################################################################
plt.plot(x, y)
mplcp.make_lines_glow()
mplcp.add_gradient_fill()
plt.show()
Update:
Well I somehow figured it out, but there are some visual defects that need focus. Here are the functions and output:
from itertools import groupby
import numpy as np
from matplotlib.lines import Line2D
import matplotlib.pyplot as plt
import matplotlib.colors as mcolors
from matplotlib.patches import Polygon
def add_glow_effects(n_glow_lines: int = 10,
diff_linewidth: float = 1.05,
alpha_line: float = 0.3,
change_line_color: bool = True,
color_positive: str = '#0000ff',
color_negative: str = '#ff0000',
alpha_gradientglow: float = 1.0, ):
make_lines_glow(n_glow_lines, diff_linewidth, alpha_line, change_line_color, color_positive, color_negative)
add_gradient_fill(alpha_gradientglow, color_positive, color_negative, )
def make_lines_glow(n_glow_lines: int = 10,
diff_linewidth: float = 1.05,
alpha_line: float = 0.3,
change_line_color: bool = True,
color_positive: str = '#0000ff',
color_negative: str = '#ff0000'):
ax = plt.gca()
lines = ax.get_lines()
alpha_value = alpha_line / n_glow_lines
for line_element in lines:
if not isinstance(line_element, Line2D):
continue
x, y = line_element.get_data(orig=False)
x, y = optimize_lines(list(x), list(y))
lines_list = list_form(x, y)
for line in lines_list:
if change_line_color:
y_avg = sum(line[1]) / len(line[1])
if y_avg >= 0:
color = color_positive
else:
color = color_negative
else:
color = line_element.get_color()
line = Line2D(line[0], line[1], linewidth=line_element.get_linewidth(), color=color)
data = list(line.get_data(orig=False))
linewidth = line.get_linewidth()
ax.plot(data[0], data[1], color=color, linewidth=linewidth)
for n in range(1, n_glow_lines + 1):
glow_line, = ax.plot(*data)
glow_line.update_from(line)
# line properties are copied as seen in this solution: https://stackoverflow.com/a/54688412/3240855
glow_line.set_alpha(alpha_value)
glow_line.set_linewidth(linewidth + (diff_linewidth * n))
# mark the glow lines, to disregard them in the underglow function.
glow_line.is_glow_line = True
# noinspection PyArgumentList
def add_gradient_fill(alpha_gradientglow: float = 1.0,
color_positive: str = '#00ff00',
color_negative: str = '#ff0000'):
"""Add a gradient fill under each line,
i.e. faintly color the area below the line."""
ax = plt.gca()
lines = ax.get_lines()
for line_element in lines:
if not isinstance(line_element, Line2D):
continue
x, y = line_element.get_data(orig=False)
x, y = optimize_lines(list(x), list(y))
lines_list = list_form(x, y)
for line in lines_list:
y_avg = sum(line[1]) / len(line[1])
# don't add gradient fill for glow effect lines:
if hasattr(line, 'is_glow_line') and line.is_glow_line:
continue
line = Line2D(line[0], line[1], linewidth=line_element.get_linewidth())
zorder = line.get_zorder()
alpha = line_element.get_alpha()
alpha = 1.0 if alpha is None else alpha
x, y = line.get_data(orig=False)
x, y = np.array(x), np.array(y) # enforce x,y as numpy arrays
xmin, xmax = x.min(), x.max()
ymin, ymax = y.min(), y.max()
xy = np.column_stack([x, y])
if y_avg >= 0:
fill_color = color_positive
linspace = np.linspace(0, alpha, 100)[:, None]
xy = np.vstack([[xmin, ymin], xy, [xmax, ymin], [xmin, ymin]])
else:
fill_color = color_negative
linspace = np.linspace(alpha, 0, 100)[:, None]
xy = np.vstack([[xmin, ymax], xy, [xmax, ymax], [xmin, ymax]])
rgb = mcolors.colorConverter.to_rgb(fill_color)
z = np.empty((100, 1, 4), dtype=float)
z[:, :, :3] = rgb
z[:, :, -1] = linspace
im = ax.imshow(z, aspect='auto',
extent=[xmin, xmax, ymin, ymax],
alpha=alpha_gradientglow,
origin='lower', zorder=zorder)
clip_path = Polygon(xy, facecolor='none', edgecolor='none', closed=True)
ax.add_patch(clip_path)
im.set_clip_path(clip_path)
ax.autoscale(True)
def optimize_lines(x: list, y: list):
y = [list(element) for index, element in groupby(y, lambda a: a >= 0)]
indexes = [0]
for i in y:
indexes.append(len(i) + indexes[-1])
# from https://www.geeksforgeeks.org/python-group-consecutive-elements-by-sign/
x = [x[indexes[i]:indexes[i + 1]] for i, _ in enumerate(indexes) if i != len(indexes) - 1]
for i in range(len(y) - 1):
if y[i][-1] == 0 and y[i + 1][0] == 0:
continue
a = y[i][-1]
b = y[i + 1][0]
diff = abs(a) + abs(b)
a_ = (abs(0 - a)) / diff
b_ = abs(0 - b) / diff
x[i].append(x[i][-1] + a_)
x[i + 1].insert(0, x[i + 1][0] - b_)
y[i].append(0)
y[i + 1].insert(0, 0)
x = [list(i) for i in x]
y = [list(i) for i in y]
# input: x=[1,2,3,4,5], y=[1,2,-5,0,2]
# output: x=[[1, 2, 2.2857142857142856], [2.2857142857142856, 3, 4.0], [4.0, 4, 5]],
# y=[[1, 2, 0], [0, -5, 0], [0, 0, 2]]
return list(x), list(y)
def list_form(x: list[list], y: list[list]):
lst = []
for i in range(len(x)):
lst.append([x[i], y[i]])
return lst
The output is now this:
Notice how the glow from function is collected at the left side of graph. also, at the end of the graph these is a tiny purple triangle that is offset by one corner.
The title of this post has been changed to "Visual defects in matplotlib graph" from "Matplotlib graph gradient away from the x axis" for the purpose of relevance, keeping in mind the latest update to the post.
Interesting question. I have several ideas to help you there. I think the easiest solution will be to find an elegant way to "split" the data conditionally when zero-crossing occurs (but you need to detect the zero-crossings accurately for clean clipping masks).
The solution below is not yet finished, but it solves the first issue of having a two-color gradient and a compound path to get a positive/negative clipping mask. Now there is the line color that needs to be also split into + and - parts. So far, I just overlayed the line below zero on top of the existing line, and the glow of this line clearly mixes with the one of the first line.
I'll be back to it later; maybe this will help meanwhile.
import matplotlib.pyplot as plt
import mplcyberpunk as mplcp
import matplotlib.colors as mcolors
from matplotlib.path import Path
import numpy as np
from matplotlib.lines import Line2D
from matplotlib.patches import Polygon, PathPatch
def add_gradient_fill(ax=None, alpha_gradientglow=1.0, negative_color="C1"):
"""Add a gradient fill under each line,
i.e. faintly color the area below the line."""
if not ax:
ax = plt.gca()
lines = ax.get_lines()
for line in lines:
# don't add gradient fill for glow effect lines:
if hasattr(line, 'is_glow_line') and line.is_glow_line:
continue
fill_color = line.get_color()
zorder = line.get_zorder()
alpha = line.get_alpha()
alpha = 1.0 if alpha is None else alpha
rgb = mcolors.colorConverter.to_rgb(fill_color)
x, y = line.get_data(orig=False)
x, y = np.array(x), np.array(y) # enforce x,y as numpy arrays
xmin, xmax = np.nanmin(x), np.nanmax(x)
ymin, ymax = np.nanmin(y), np.nanmax(y)
z = np.empty((100, 1, 4), dtype=float)
z[:, :, :3] = rgb
# z[:, :, -1] = np.linspace(0, alpha, 100)[:, None]
ynorm = max(np.abs(ymin), np.abs(ymax))
ymin_norm = ymin / ynorm
ymax_norm = ymax / ynorm
ynorm = np.linspace(ymin_norm, ymax_norm, 100)
z[:, :, -1] = alpha * np.abs(ynorm[:, None])
rgb_neg = mcolors.colorConverter.to_rgb(negative_color)
z[ynorm < 0, :, :3] = rgb_neg
im = ax.imshow(z, aspect='auto',
extent=[xmin, xmax, ymin, ymax],
alpha=alpha_gradientglow,
origin='lower', zorder=zorder)
# Detect zero crossings
y_copy = y.copy()
y = y.clip(0, None)
xy = np.column_stack([x, y])
xy = np.vstack([[xmin, 0], xy, [xmax, 0], [xmin, 0]])
clip_path_1 = Polygon(xy, facecolor='none', edgecolor='none', closed=True)
y = y_copy.copy()
y = y.clip(None, 0)
xy = np.column_stack([x, y])
xy = np.vstack([[xmin, 0], xy, [xmax, 0], [xmin, 0]])
clip_path_2 = Polygon(xy, facecolor='none', edgecolor='none', closed=True)
ax.add_patch(clip_path_1)
ax.add_patch(clip_path_2)
clip_paths = clip_path_2, clip_path_1
vertices = np.concatenate([i.get_path().vertices for i in clip_paths])
codes = np.concatenate([i.get_path().codes for i in clip_paths])
clip_path = PathPatch(Path(vertices, codes), transform=ax.transData)
im.set_clip_path(clip_path)
ax.autoscale(True)
y = y_copy.copy()
y[y > 0] = np.nan
ax.plot(x, y)
def make_lines_glow(
ax=None,
n_glow_lines: int = 10,
diff_linewidth: float = 1.05,
alpha_line: float = 0.3,
lines=None,
) -> None:
"""Add a glow effect to the lines in an axis object.
Each existing line is redrawn several times with increasing width and low alpha to create the glow effect.
"""
if not ax:
ax = plt.gca()
lines = ax.get_lines() if lines is None else lines
lines = [lines] if isinstance(lines, Line2D) else lines
alpha_value = alpha_line / n_glow_lines
for line in lines:
data = line.get_data(orig=False)
linewidth = line.get_linewidth()
try:
step_type = line.get_drawstyle().split('-')[1]
except:
step_type = None
for n in range(1, n_glow_lines + 1):
if step_type:
glow_line, = ax.step(*data)
else:
glow_line, = ax.plot(*data)
glow_line.update_from(line) # line properties are copied as seen in this solution: https://stackoverflow.com/a/54688412/3240855
glow_line.set_alpha(alpha_value)
glow_line.set_linewidth(linewidth + (diff_linewidth * n))
glow_line.is_glow_line = True # mark the glow lines, to disregard them in the underglow function.
x = np.arange(-10, 11)
y = np.array([(i ** 2) - 50 for i in x])
plt.style.use('cyberpunk')
for param in ['figure.facecolor', 'axes.facecolor', 'savefig.facecolor']:
plt.rcParams[param] = '#303030'
for param in ['text.color', 'axes.labelcolor', 'xtick.color', 'ytick.color']:
plt.rcParams[param] = '#ffffff'
plt.subplots()[1].grid(color='#404040')
plt.plot(x, y)
add_gradient_fill(negative_color="C1")
make_lines_glow()
plt.show()

I just get a ball going directly upwards here, what can i change to make it work

import numpy as np
import matplotlib.pyplot as plot
from IPython.display import HTML
from matplotlib import animation
#setup fig with axis
fig, ax = plot.subplots(figsize=(8,8))
#set axis limits
ax.set(xlim=(-2,2), ylim=(0,600), xlabel="position, metres", ylabel="height, metres", title="falling apple")
#initial params
T = 100.
m = 3
g = 9.81
v0x = 10
H = 553.
#setting calc interval
dt = 0.1
N = int(T/dt)
#arrays
v = np.zeros((N+1 , 2))
x = np.zeros((N+1 , 2))
f = np.zeros((N+1 , 2))
#array start [x ,y] format
v[0] = np.array([0. , H])
x[0] = np.array([v0x , 0.])
# the only force is gravity
f[:] = np.array([0., m * g])
#running the dynamics sim
for n in range(N):
v[n+1] = v[n] + ((f[n]/m) * dt)
x[n+1] = x[n] + (v[n+1] * dt)
#scatter plot
scat_plt = ax.scatter(x[0,0], x[0,1], marker='o', c='#1f77b4', s=200)
## animating
def animate(i):
scat_plt.set_offsets(x[i])
ani = animation.FuncAnimation(fig, func=animate, frames=N)
ani.save('ball.html', writer=animation.HTMLWriter(fps= 1//dt))
plot.close()
ani.save('ball.mp4', fps= 1//dt)
HTML('ball.html')
The out put is just a circle going straight up where as this is supposed to simulate a ball being thrown horizontally off a tower
It would be highly appreciated if someone could suggest any changes to be made to the logic/physics or the code.
Thank you!!
I think you mixed x with v at some point. Also the force should be negative in y. I tried this and it seems to work:
import numpy as np
import matplotlib.pyplot as plot
from IPython.display import HTML
from matplotlib import animation
#setup fig with axis
fig, ax = plot.subplots(figsize=(8,8))
#set axis limits
ax.set(xlim=(-200,200), ylim=(0,600), xlabel="position, metres", ylabel="height, metres", title="falling apple")
#initial params
T = 100.
m = 3
g = 9.81
v0x = 10
H = 553.
#setting calc interval
dt = 0.1
N = int(T/dt)
#arrays
v = np.zeros((N+1 , 2))
x = np.zeros((N+1 , 2))
f = np.zeros((N+1 , 2))
#array start [x ,y] format
x[0] = np.array([0. , H])
v[0] = np.array([v0x , 0.])
# the only force is gravity
f[:] = np.array([0., -m * g])
#running the dynamics sim
for n in range(N):
v[n+1] = v[n] + ((f[n]/m) * dt)
x[n+1] = x[n] + (v[n+1] * dt)
#scatter plot
scat_plt = ax.scatter(x[0,0], x[0,1], marker='o', c='#1f77b4', s=200)
## animating
def animate(i):
scat_plt.set_offsets(x[i])
ani = animation.FuncAnimation(fig, func=animate, frames=N)
ani.save('ball.html', writer=animation.HTMLWriter(fps= 1//dt))
plot.close()
ani.save('ball.gif', fps= 1//dt)
HTML('ball.html')

Embed subplot in cartopy map

I want to embed subplots canvas inside a cartopy projected map. I wrote this code to show the expected result by using rectangles:
#%%
import numpy as np
import cartopy as cr
import cartopy.crs as ccrs
import matplotlib.pyplot as plt
import matplotlib.patches as mpatches
from cartopy.io import shapereader
import geopandas
resolution = '10m'
category = 'cultural'
name = 'admin_0_countries'
shpfilename = shapereader.natural_earth(resolution, category, name)
# read the shapefile using geopandas
df = geopandas.read_file(shpfilename)
# read the country borders
usa = df.loc[df['ADMIN'] == 'United States of America']['geometry'].values[0]
can = df.loc[df['ADMIN'] == 'Canada']['geometry'].values[0]
central_lon, central_lat = -80, 60
extent = [-85, -55, 40, 62]
# ax = plt.axes(projection=ccrs.Orthographic(central_lon, central_lat))
#Golden ratio
phi = 1.618033987
h = 7
w = phi*h
fig = plt.figure(figsize=(w,h))
ax = fig.add_subplot(1, 1, 1, projection=ccrs.PlateCarree())
#Set map extent
ax.set_extent(extent)
ax.set_xticks(np.linspace(extent[0],extent[1],11))
ax.set_yticks(np.linspace(extent[2],extent[3],6))
ax.add_geometries(usa, crs=ccrs.PlateCarree(), facecolor='none',
edgecolor='k')
# ax.gridlines()
ax.coastlines(resolution='50m')
nx, ny = 7,6
#Begin firts rectangle
xi = extent[0] + 0.5
yi = extent[2] + 0.5
x, y = xi, yi
#Loop for create the plots grid
for i in range(nx):
for j in range(ny):
#Inner rect height
in_h = 2.8
#Draw the rect
rect = ax.add_patch(mpatches.Rectangle(xy=[x, y], width=phi*in_h, height=in_h,
facecolor='blue',
alpha=0.2,
transform=ccrs.PlateCarree()))
#Get vertex of the drawn rectangle
verts = rect.get_path().vertices
trans = rect.get_patch_transform()
points = trans.transform(verts)
#Refresh rectangle coordinates
x += (points[1,0]-points[0,0]) + 0.2
if j == ny-1:
x = xi
y += (points[2,1]-points[1,1]) + 0.2
# print(points)
fig.tight_layout()
fig.savefig('Figure.pdf',format='pdf',dpi=90)
plt.show()
This routine prints this figure
What I am looking for is a way to embed plots that match every single rectangle in the figure. I tried with fig.add_axes, but I couldn't get that mini-canvas match with the actual rectangles.
Since you want to embed the axes inside the parent axes is recommend using inset_axes, see the documentation here.
I wrote simple code to demonstrate how it works. Clearly there will be some tweaking of the inset_axes positions and sizes necessary for your desired output, but I think my trivial implementation already does decent.
All created axes instances are stored in a list so that they can be accessed later.
import matplotlib.pyplot as plt
import numpy as np
fig, ax = plt.subplots()
axis = []
x = np.linspace(-85, -55)
y = np.linspace(40, 62)
ax.plot(x, y)
offset_l = 0.05
offset_h = 0.12
num_x = 6
num_y = 7
xs = np.linspace(offset_l, 1-offset_h, num_x)
ys = np.linspace(offset_l, 1-offset_h, num_y)
for k in range(num_x):
for j in range(num_y):
ax_ins = ax.inset_axes([xs[k], ys[j], 0.1, 0.1])
ax_ins.axhspan(0, 1, color='tab:blue', alpha=0.2)
axis.append(ax_ins)
Alternatively, you can also specify the inset_axes positions using data coordinates, for this you have to set the kwarg transform in the method to transform=ax.transData, see also my code below.
import matplotlib.pyplot as plt
import numpy as np
#Golden ratio
phi = 1.618033987
h = 7
w = phi*h
fig, ax = plt.subplots(figsize=(w, h))
axis = []
x = np.linspace(-85, -55)
y = np.linspace(40, 62)
ax.plot(x, y)
offset_l = 0.05
offset_h = 0.12
num_x = 6
num_y = 7
fig.tight_layout()
extent = [-85, -55, 40, 62]
xi = extent[0] + 0.5
yi = extent[2] + 0.5
in_h = 2.8
in_w = phi * 2.8
spacing = 0.4
for k in range(num_x):
for j in range(num_y):
ax_ins = ax.inset_axes([xi+k*(in_w + phi*spacing), yi+j*(in_h + spacing),
in_w, in_h], transform=ax.transData)
ax_ins.axhspan(0, 1, color='tab:blue', alpha=0.2)
axis.append(ax_ins)

How to put the scaling on the ticks in ternary plot instead of x and y axis

I am try to work out with my atomic composition with ternary phase diagram, here is my picture
I wish to put my scale to the ticks on the ternary phase diagram (i.e. those triangular axis) instead of x and y axis. Is there a ways to put the scale on the tick at triangular axis instead of axis x and y? How to remove the x-axis and y-axis while still maintain its labels?
from __future__ import division
import numpy as np
import matplotlib.pyplot as plt
import matplotlib.tri as tri
def plot_ticks(start, stop, tick, n):
r = np.linspace(0, 1, n+1)
x = start[0] * (1 - r) + stop[0] * r
x = np.vstack((x, x + tick[0]))
y = start[1] * (1 - r) + stop[1] * r
y = np.vstack((y, y + tick[1]))
plt.plot(x, y, 'k', lw=1)
n = 5
tick_size = 0.1
margin = 0.05
# define corners of triangle
left = np.r_[0, 0]
right = np.r_[1, 0]
top = np.r_[0.5, np.sqrt(3)*0.576]
triangle = np.c_[left, right, top, left]
# define corners of triangle
left = np.r_[0, 0]
right = np.r_[1, 0]
top = np.r_[0.5, np.sqrt(3)*0.576]
triangle = np.c_[left, right, top, left]
# define vectors for ticks
bottom_tick = 0.8264*tick_size * (right - top) / n
right_tick = 0.8264*tick_size * (top - left) / n
left_tick = 0.8264*tick_size * (left - right) / n
# first load some data: format x1,x2,x3,value
test_data = np.array([[4,0,0,2.238],
[0,4,0,2.315],
[0,0,4,2.147],
[3,1,0,2.494],
[2,2,0,2.190],
[2,2,0,2.632],
[3,0,1,2.173],
[2,0,2,2.329],
[1,0,3,2.526],
[0,3,1,2.365],
[0,2,2,2.220],
[0,1,3,2.080],
[2,1,1,2.231],
[1,2,1,2.291],
[1,1,2,2.088]])
#Define twin axis
#ax = plt.gca()
fig, ax = plt.subplots()
plot_ticks(left, right, bottom_tick, n)
plot_ticks(right, top, right_tick, n)
plot_ticks(left, top, left_tick, n)
#ax2 = ax.twinx()
# barycentric coords: (a,b,c)
a=test_data[:,0]
b=test_data[:,1]
c=test_data[:,2]
# values is stored in the last column
v = test_data[:,-1]
# translate the data to cartesian corrds
x = 0.5 * ( 2.*b+c ) / ( a+b+c )
y = 0.576*np.sqrt(3) * c / (a+b+c)
# create a triangulation out of these points
T = tri.Triangulation(x,y)
# plot the contour
plt.tricontourf(x,y,T.triangles,v,cmap='jet')
# create the grid
corners = np.array([[0, 0], [1, 0], [0.5, np.sqrt(3)*0.576]])
triangle = tri.Triangulation(corners[:, 0], corners[:, 1])
# creating the grid
refiner = tri.UniformTriRefiner(triangle)
trimesh = refiner.refine_triangulation(subdiv=4)
#plotting the mesh and caliberate the axis
plt.triplot(trimesh,'k--')
#plt.title('Binding energy peratom of Al-Ti-Ni clusters')
ax.set_xlabel('Al-Ti',fontsize=12,color='black')
ax.set_ylabel('Ti-Ni',fontsize=12,color='black')
ax2 = ax.twinx()
ax2.set_ylabel('Al-Ni',fontsize=12,color='black')
plt.gcf().text(0.07, 0.05, 'Ti', fontsize=12,color='black')
plt.gcf().text(0.93, 0.05, 'Al', fontsize=12,color='black')
plt.gcf().text(0.5, 0.9, 'Ni', fontsize=12,color='black')
#set scale for axis
ax.set_xlim(1, 0)
ax.set_ylim(0, 1)
ax2.set_ylim(1, 0)
cax = plt.axes([0.75, 0.55, 0.055, 0.3])
plt.colorbar(cax=cax,format='%.3f')
plt.savefig("AID.png", dpi=1000)
plt.show()
As was mentioned in the comments you can make your own axis just by adding a text to the ticks you generate. Most of the time you need a little tweaking
to get the offsets right...
def plot_ticks(start, stop, tick, n, offset=(.0, .0)):
r = np.linspace(0, 1, n+1)
x = start[0] * (1 - r) + stop[0] * r
x = np.vstack((x, x + tick[0]))
y = start[1] * (1 - r) + stop[1] * r
y = np.vstack((y, y + tick[1]))
plt.plot(x, y, 'k', lw=1)
# add tick labels
for xx, yy, rr in zip(x[1], y[1], r):
plt.text(xx+offset[0], yy+offset[1], "{:.2}".format(rr))
# Note that the ordering from start to stop is important for the tick labels
plot_ticks(right, left, bottom_tick, n, offset=(0, -0.04))
plot_ticks(left, top, left_tick, n, offset=(-0.06, -0.0))
plot_ticks(top, right, right_tick, n)
In addition I switched the axis off via ax.set_axis_off() and I also deleted the twin axis, as you used these only to display the ticks and labels for the connections. These labels can also easily be placed via fig.text() as you did with the corners:
# Corners
fig.text(0.07, 0.05, 'Ti', fontsize=12, color='black')
fig.text(0.93, 0.05, 'Al', fontsize=12, color='black')
fig.text(0.50, 0.90, 'Ni', fontsize=12, color='black')
# Connections
fig.text(0.47, 0.05, 'Ti-Al', fontsize=12, color='black') # Note: not sure about
fig.text(0.72, 0.50, 'Al-Ni', fontsize=12, color='black') # the nomenclature;
fig.text(0.25, 0.50, 'Ti-Ni', fontsize=12, color='black') # might be switched

how to remove plot elements in python

I'm trying to a 3d figure without any plot elements in python. Sort of a 3d version of this.
When I run the code I have added below, I get regular plot.
I want to remove the axes, axes labels, ticks, and background (and remain only with the surface).
How can I remove them.
Also, is there way to add arrows to the plot?
Here is my code:
import random
import math
from mpl_toolkits.mplot3d import Axes3D
import matplotlib.pyplot as plt
import numpy as np
pi = 3.14159
bignum = 3
amp = 0.1
mat = []
X = []
Y = []
class mode:
i=0
j=0
phase=0
amp=0
modes = dict()
for i in range(0,3):
submodes = dict()
for j in range (0,5):
if not (i==0 and j==2):
m = mode()
m.i = i
m.j = j
m.amp = amp*random.random()/(pow(i,2) + pow(j-2,2))
m.phase = random.random()*2*pi
submodes[j] = m
modes[i] = submodes
for x in range (0,bignum):
mat.append([])
for y in range (0,bignum):
dz = 0
for i in range (0,3):
for j in range (0,5):
if not (i == 0 and j == 2):
dz += math.cos(i*x*2*pi/bignum + j *y*2/bignum + modes[i][j].phase)*modes[i][j].amp
mat[x].append(dz)
X = np.mgrid[:bignum,:bignum]
print (len(X[0]))
print (len(mat))
fig = plt.figure(figsize=plt.figaspect(2.))
fig.frameon=True
ax = fig.add_subplot(1,1,1, projection='3d')
ax.frameon=False
ax.xticks=[]
ax.yticks=[]
ax.zticks=[]
surf = ax.plot_surface(X[0],X[1],mat,rstride=1, cstride=1,
linewidth=0, antialiased=False)
ax.set_zlim3d(0, 1)
plt.show()
To eliminate the 3d frame from the figure, use:
ax.set_axis_off()
This doesn't answer much of your question. But you can start turning stuff off with
plt.setp(ax.get_xticklabels(), visible=False)
I did some of it below. Also, the plt.annotate() function is how to add arrows in 2d plots...not sure how it upscales.
import random
import math
from mpl_toolkits.mplot3d import Axes3D
import matplotlib.pyplot as plt
import numpy as np
pi = 3.14159
bignum = 3
amp = 0.1
mat = []
X = []
Y = []
class mode:
i=0
j=0
phase=0
amp=0
modes = dict()
for i in range(0,3):
submodes = dict()
for j in range (0,5):
if not (i==0 and j==2):
m = mode()
m.i = i
m.j = j
m.amp = amp*random.random()/(pow(i,2) + pow(j-2,2))
m.phase = random.random()*2*pi
submodes[j] = m
modes[i] = submodes
for x in range (0,bignum):
mat.append([])
for y in range (0,bignum):
dz = 0
for i in range (0,3):
for j in range (0,5):
if not (i == 0 and j == 2):
dz += math.cos(i*x*2*pi/bignum + j *y*2/bignum + modes[i][j].phase)*modes[i][j].amp
mat[x].append(dz)
X = np.mgrid[:bignum,:bignum]
print (len(X[0]))
print (len(mat))
fig = plt.figure(figsize=plt.figaspect(2.))
fig.frameon=True
ax = fig.add_subplot(1,1,1, projection='3d')
ax.frameon=False
surf = ax.plot_surface(X[0],X[1],mat,rstride=1, cstride=1,
linewidth=0, antialiased=False)
ax.set_zlim3d(0, 1)
plt.setp(ax.get_xticklabels(), visible=False)
plt.setp(ax.get_yticklabels(), visible=False)
plt.setp(ax.get_zticklabels(), visible=False)
plt.setp(ax.get_xticklines(), visible=False)
plt.setp(ax.get_yticklines(), visible=False)
plt.setp(ax.get_zticklines(), visible=False)
plt.setp(ax.get_frame(), visible = False)
#plt.annotate(r'Hello', xy = (.5, .5),
# xytext = (10,10),
# textcoords='offset points', arrowprops=dict(arrowstyle='->',
# connectionstyle='arc3,rad=0'))
plt.show()
You didn't ask this...but you should vectorize this code. Most/(all?) of the for loops could be avoided.

Categories