matplotlib subplotting issue - python

I made a code for sub plotting
fig, axs = plt.subplots(2, 2, figsize = (20,10))
candlestick_ohlc(axs[0,0],df.values,width=0.6,colorup='green', colordown='red', alpha=0.5);
candlestick_ohlc(axs[0,0],df1.values,width=0.6,colorup='red', colordown='green', alpha=0.8);
date_format = mpl_dates.DateFormatter('%d %b %Y')
axs[0,0].xaxis.set_major_formatter(date_format)
axs[0,0].xaxis.set_major_formatter(date_format)
for level in levels:
axs[0,0].hlines(level[1],xmin=df['Date'][level[0]],\
xmax=max(df['Date']),colors='black')
fig.autofmt_xdate()
axs[1,1].plot(ichi['Close'],label = 'Close')
axs[1,0].scatter(df.index, df.Close, c = squeeze['signal'])
axs[1,1].fill_between(ichi.index, ichi['ISA_9'],ichi['ISB_26'], where = ichi['ISA_9']>ichi['ISB_26'], facecolor = 'green', alpha = 0.5)
axs[1,1].fill_between(ichi.index, ichi['ISA_9'],ichi['ISB_26'], where = ichi['ISA_9']<ichi['ISB_26'], facecolor = 'red', alpha = 0.5)
axs[1,1].legend()
And i am quite satisfied with this
My subplot
However, I wanted to add one more plot at axs[0,1] for which I used trendln package for plotting support and resistance
plt.figure(figsize = (20,10))
f= trendln.plot_support_resistance(hist[-100:].Close,accuracy = 10)
plt.show()
plt.clf() #clear figure
Support resistance plot
Is there any way such that I can incorporate support resistance plot into my initial plot axs[0,1]

Unfortunately, reading the source code of trendln, they directly use plt.plot for everything, so it's not easy to do this. You have to change the source code yourself. You can see where the source is located:
>>> import trendln
>>> trendln.__file__
'/home/username/.local/lib/python3.8/site-packages/trendln/__init__.py'
>>>
Then you can directly modify the plot_support_resistance function to the following. I basically make it take an axs and plot there instead of plt; there were also a few other changes to be made:
def plot_support_resistance(axs, hist, xformatter = None, numbest = 2, fromwindows = True,
pctbound=0.1, extmethod = METHOD_NUMDIFF, method=METHOD_NSQUREDLOGN,
window=125, errpct = 0.005, hough_scale=0.01, hough_prob_iter=10, sortError=False, accuracy=1):
import matplotlib.pyplot as plt
import matplotlib.ticker as ticker
ret = calc_support_resistance(hist, extmethod, method, window, errpct, hough_scale, hough_prob_iter, sortError, accuracy)
# plt.clf()
# plt.subplot(111)
if len(ret) == 2:
minimaIdxs, pmin, mintrend, minwindows = ret[0]
maximaIdxs, pmax, maxtrend, maxwindows = ret[1]
if type(hist) is tuple and len(hist) == 2 and check_num_alike(hist[0]) and check_num_alike(hist[1]):
len_h = len(hist[0])
min_h, max_h = min(min(hist[0]), min(hist[1])), max(max(hist[0]), max(hist[1]))
disp = [(hist[0], minimaIdxs, pmin, 'yo', 'Avg. Support', 'y--'), (hist[1], maximaIdxs, pmax, 'bo', 'Avg. Resistance', 'b--')]
dispwin = [(hist[0], minwindows, 'Support', 'g--'), (hist[1], maxwindows, 'Resistance', 'r--')]
disptrend = [(hist[0], mintrend, 'Support', 'g--'), (hist[1], maxtrend, 'Resistance', 'r--')]
axs.plot(range(len_h), hist[0], 'k--', label='Low Price')
axs.plot(range(len_h), hist[1], 'm--', label='High Price')
else:
len_h = len(hist)
min_h, max_h = min(hist), max(hist)
disp = [(hist, minimaIdxs, pmin, 'yo', 'Avg. Support', 'y--'), (hist, maximaIdxs, pmax, 'bo', 'Avg. Resistance', 'b--')]
dispwin = [(hist, minwindows, 'Support', 'g--'), (hist, maxwindows, 'Resistance', 'r--')]
disptrend = [(hist, mintrend, 'Support', 'g--'), (hist, maxtrend, 'Resistance', 'r--')]
axs.plot(range(len_h), hist, 'k--', label='Close Price')
else:
minimaIdxs, pmin, mintrend, minwindows = ([], [], [], []) if hist[0] is None else ret
maximaIdxs, pmax, maxtrend, maxwindows = ([], [], [], []) if hist[1] is None else ret
len_h = len(hist[1 if hist[0] is None else 0])
min_h, max_h = min(hist[1 if hist[0] is None else 0]), max(hist[1 if hist[0] is None else 0])
disp = [(hist[1], maximaIdxs, pmax, 'bo', 'Avg. Resistance', 'b--') if hist[0] is None else (hist[0], minimaIdxs, pmin, 'yo', 'Avg. Support', 'y--')]
dispwin = [(hist[1], maxwindows, 'Resistance', 'r--') if hist[0] is None else (hist[0], minwindows, 'Support', 'g--')]
disptrend = [(hist[1], maxtrend, 'Resistance', 'r--') if hist[0] is None else (hist[0], mintrend, 'Support', 'g--')]
axs.plot(range(len_h), hist[1 if hist[0] is None else 0], 'k--', label= ('High' if hist[0] is None else 'Low') + ' Price')
for h, idxs, pm, clrp, lbl, clrl in disp:
axs.plot(idxs, [h[x] for x in idxs], clrp)
axs.plot([0, len_h-1],[pm[1],pm[0] * (len_h-1) + pm[1]],clrl, label=lbl)
def add_trend(h, trend, lbl, clr, bFirst):
for ln in trend[:numbest]:
maxx = ln[0][-1]+1
while maxx < len_h:
ypred = ln[1][0] * maxx + ln[1][1]
if (h[maxx] > ypred and h[maxx-1] < ypred or h[maxx] < ypred and h[maxx-1] > ypred or
ypred > max_h + (max_h-min_h)*pctbound or ypred < min_h - (max_h-min_h)*pctbound): break
maxx += 1
x_vals = np.array((ln[0][0], maxx)) # plt.gca().get_xlim())
y_vals = ln[1][0] * x_vals + ln[1][1]
if bFirst:
axs.plot([ln[0][0], maxx], y_vals, clr, label=lbl)
bFirst = False
else: axs.plot([ln[0][0], maxx], y_vals, clr)
return bFirst
if fromwindows:
for h, windows, lbl, clr in dispwin:
bFirst = True
for trend in windows:
bFirst = add_trend(h, trend, lbl, clr, bFirst)
else:
for h, trend, lbl, clr in disptrend:
add_trend(h, trend, lbl, clr, True)
# axs.title('Prices with Support/Resistance Trend Lines')
#axs.xlabel('Date')
#axs.ylabel('Price')
axs.legend()
#plt.gca()
axs.xaxis.set_major_locator(ticker.MaxNLocator(6))
#plt.gca().xaxis.set_major_formatter(mdates.DateFormatter('%Y-%m'))
#if not xformatter is None: plt.gca().xaxis.set_major_formatter(xformatter)
#plt.setp(plt.gca().get_xticklabels(), rotation=30, ha='right')
#plt.gca().set_position([0, 0, 1, 1])
#plt.savefig(os.path.join(curdir, 'data', 'suppres.svg'), format='svg', bbox_inches = 'tight')
#plt.show()
return None#plt.gcf()
Now that that is taken care of, you can pass one of the axs to draw things:
import matplotlib.pyplot as plt
import trendln
import yfinance as yf
fig, axs = plt.subplots(2, 2, figsize = (20,10))
axs[0, 1].plot([0, 1], [3, 4])
tick = yf.Ticker('^GSPC') # S&P500
hist = tick.history(period="max", rounding=True)
f = trendln.plot_support_resistance(axs[0, 0], hist[-1000:].Close, accuracy = 2)
plt.show()
I get:
I hope this helps. You probably were looking for another option, but because of the hard coding they use, it's not easy. I also tried copying the axs that plt draws to instead of modifying the source code, but it didn't work.

Related

Python Matplotlib Funcanimation issue: struggle to animate a contour plot (contourf) within functions

I'm struggling with FuncAnimation in matplotlib.animation, and I could not find out any examples or post looking similar to my problem (I mean, yes there is post concerning contourf used in funcAnimation but in those posts they succeed to delete the PathCollection object but in my case something is not working).
Context:
In a school project concerning One-vs-All notion (multiple binary classifiers), I want to implement functions to animate a figure having 3 Axes and containing multiple Line2D objects, an PathCollection object from scatter method and a QuadContourSet from contourf method.
Here a screen of how it looks like (obtained when I plot the data at the end of the training of the One-vs-All):
Representation of the static graph
Legend:
Left: Boundary decision in (Herbology)-(Defense against Dark Arts) plane,
Top right: Loss function of each binary classifiers,
Bottom right: Precision and Recall metrics of each classifiers.
Methods:
I am trying to have a animated version of the plot using FuncAnimation from matplotlib.Animation module. Animated version of the plot is a bonus feature of my project, then the animation part/core is made in functions, you can see a simplification below (a bare bones representation) :
def anim_visu(models, data):
# initialization of the figure and object representing the data
...
def f_anim():
# Function which update the data at each frames
visu = FuncAnimation(fig, f_anim, ...)
return fig
[...]
if __name__ == "__main__":
[...]
if bool_dynamic: # activation of the dynamic visualization
anim_visu(models, data)
And here a minimal workish example:
# =========================================================================== #
# |Importation des lib/packages| #
# =========================================================================== #
import numpy as np
import matplotlib.pyplot as plt
import seaborn as sns
from matplotlib.animation import FuncAnimation
from matplotlib.gridspec import GridSpec
dct_palet = {"C1":"dodgerblue",
"C2":"red",
"C3":"green",
"C4":"goldenrod"}
fps = 15
# =========================================================================== #
# | Definition des fonctions | #
# =========================================================================== #
def one_vs_all_prediction(classifiers:list, X:np.array) -> np.array:
"""
... Docstring ...
"""
preds = np.zeros((X.shape[0],1))
for clf in classifiers:
tmp = clf.predict(X)
mask = preds == 0
preds[mask] = tmp[mask]
return preds
def one_vs_all_class_onehot(class_pred:np.array):
"""
... Docstring ...
"""
house = {"C1":1., "C2":2., "C3":3., "C4":4.}
onehot_pred = np.chararray((class_pred.shape[0],1), itemsize=2)
for key, item in house.items():
mask = class_pred == item
onehot_pred[mask] = key
return onehot_pred
def do_animation(clfs:list, data:np.ndarray):
""" Core function for the animated vizualisation.
The function defines all the x/y_labels, the titles.
"""
global idx, cost_clf1, cost_clf2, cost_clf3, cost_clf4, \
met1_clf1, met1_clf2, met1_clf3, met1_clf4, \
met2_clf1, met2_clf2, met2_clf3, met2_clf4, \
boundary, axes, \
l_cost_clf1, l_cost_clf2, l_cost_clf3, l_cost_clf4, \
l_met1_clf1, l_met1_clf2, l_met1_clf3, l_met1_clf4, \
l_met2_clf1, l_met2_clf2, l_met2_clf3, l_met2_clf4
plt.style.use('seaborn-pastel')
# -- Declaring the figure and the axes -- #
fig = plt.figure(figsize=(15,9.5))
gs = GridSpec(2, 2, figure=fig)
axes = [fig.add_subplot(gs[:, 0]), fig.add_subplot(gs[0, 1]), fig.add_subplot(gs[1, 1])]
# --formatting the different axes -- #
axes[0].set_xlabel("X_1")
axes[0].set_ylabel("X_2")
axes[0].set_title("Decision boundary")
axes[1].set_xlabel("i: iteration")
axes[1].set_xlim(-10, 1000)
axes[1].set_ylim(-10, 350)
axes[1].set_ylabel(r"$\mathcal{L}_{\theta_0,\theta_1}$")
axes[1].grid()
axes[2].set_xlabel("i: iteration")
axes[2].set_ylabel("Scores (metric_1 & metric_2)")
axes[2].set_xlim(-10, 1000)
axes[2].set_ylim(0.0,1.01)
axes[2].grid()
# -- Reading min and max values along X dimensions-- #
X = data[:,0:2]
X = X.astype(np.float64)
Y = data[:,2].reshape(-1,1)
idx = np.array([0])
X_min, X_max = X[:,:2].min(axis=0), X[:,:2].max(axis=0)
# -- Generate a grid of points with distance h between them -- #
h = 0.01
XX_1, XX_2 = np.meshgrid(np.arange(X_min[0], X_max[0], h),
np.arange(X_min[1], X_max[1], h))
zeros_arr = np.zeros((XX_1.shape[0] * XX_1.shape[1], 1))
XX = np.c_[XX_1.ravel(), XX_2.ravel(),
zeros_arr.ravel(), zeros_arr.ravel(), zeros_arr.ravel()]
# -- Predict the function value for the whole grid -- #
preds = one_vs_all_prediction(clfs, XX)
Z = preds.reshape(XX_1.shape)
## Initialisation of the PathCollection for the Axes[0] objects
boundary = axes[0].contourf(XX_1, XX_2, Z, 3,
colors=["red", "green", "goldenrod", "dodgerblue"], alpha=0.5)
lst_colors = np.array([dct_palet[house] for house in data[:,2]])
raw_data = axes[0].scatter(X[:,0], X[:,1], c=lst_colors, edgecolor="k")
## Initialisation of the Line2D object for the Axes[1] objects
cost_clf1 = clfs[0].cost()
cost_clf2 = clfs[1].cost()
cost_clf3 = clfs[2].cost()
cost_clf4 = clfs[3].cost()
l_cost_clf1, = axes[1].plot(idx, cost_clf1,
ls='-', marker='o', ms=2, lw=1.2, color=dct_palet[clfs[0].house])
l_cost_clf2, = axes[1].plot(idx, cost_clf2,
ls='-', marker='o', ms=2, lw=1.2, color=dct_palet[clfs[1].house])
l_cost_clf3, = axes[1].plot(idx, cost_clf3,
ls='-', marker='o', ms=2, lw=1.2, color=dct_palet[clfs[2].house])
l_cost_clf4, = axes[1].plot(idx, cost_clf4,
ls='-', marker='o', ms=2, lw=1.2, color=dct_palet[clfs[3].house])
## Initialisation of the Line2D object for the Axes[2] objects
met1_clf1 = clfs[0].dummy_metric1()
met1_clf2 = clfs[1].dummy_metric1()
met1_clf3 = clfs[2].dummy_metric1()
met1_clf4 = clfs[3].dummy_metric1()
met2_clf1 = clfs[0].dummy_metric2()
met2_clf2 = clfs[1].dummy_metric2()
met2_clf3 = clfs[2].dummy_metric2()
met2_clf4 = clfs[3].dummy_metric2()
l_met1_clf1, = axes[2].plot(idx, met1_clf1,
ls='-', marker='o', ms=2, lw=1.2, color=dct_palet[clfs[0].house])
l_met1_clf2, = axes[2].plot(idx, met1_clf2,
ls='-', marker='o', ms=2, lw=1.2, color=dct_palet[clfs[1].house])
l_met1_clf3, = axes[2].plot(idx, met1_clf3,
ls='-', marker='o', ms=2, lw=1.2, color=dct_palet[clfs[2].house])
l_met1_clf4, = axes[2].plot(idx, met1_clf4,
ls='-', marker='o', ms=2, lw=1.2, color=dct_palet[clfs[3].house])
l_met2_clf1, = axes[2].plot(idx, met2_clf1,
ls='--', marker='o', ms=2, lw=1.2, color=dct_palet[clfs[0].house])
l_met2_clf2, = axes[2].plot(idx, met2_clf2,
ls='--', marker='o', ms=2, lw=1.2, color=dct_palet[clfs[1].house])
l_met2_clf3, = axes[2].plot(idx, met2_clf3,
ls='--', marker='o', ms=2, lw=1.2, color=dct_palet[clfs[2].house])
l_met2_clf4, = axes[2].plot(idx, met2_clf4,
ls='--', marker='o', ms=2, lw=1.2, color=dct_palet[clfs[3].house])
fig.canvas.mpl_connect('close_event', f_close)
anim_fig = FuncAnimation(fig, f_animate, fargs=(XX_1, XX_2, XX,), frames=int(1000/fps), repeat=False, cache_frame_data = False, blit=False)
plt.waitforbuttonpress()
return fig
def f_animate(i, XX_1, XX_2, XX):
"""
... Docstring ...
"""
global clfs, idx, \
cost_clf1, cost_clf2, cost_clf3, cost_clf4, \
met1_clf1, met1_clf2, met1_clf3, met1_clf4, \
met2_clf1, met2_clf2, met2_clf3, met2_clf4, \
boundary, axes, l_cost_clf1, l_cost_clf2, l_cost_clf3, l_cost_clf4, \
l_met1_clf1, l_met1_clf2, l_met1_clf3, l_met1_clf4, \
l_met2_clf1, l_met2_clf2, l_met2_clf3, l_met2_clf4
n_cycle = 100
clfs[0].fit(n_cycle)
clfs[1].fit(n_cycle)
clfs[2].fit(n_cycle)
clfs[3].fit(n_cycle)
idx = np.concatenate((idx, np.array([i * n_cycle])))
preds = one_vs_all_prediction(clfs, XX)
Z = preds.reshape(XX_1.shape)
cost_clf1 = np.concatenate((cost_clf1, clfs[0].cost()))
cost_clf2 = np.concatenate((cost_clf2, clfs[1].cost()))
cost_clf3 = np.concatenate((cost_clf3, clfs[2].cost()))
cost_clf4 = np.concatenate((cost_clf4, clfs[3].cost()))
tmp_met1_clf1 = clfs[0].dummy_metric1()
tmp_met1_clf2 = clfs[1].dummy_metric1()
tmp_met1_clf3 = clfs[2].dummy_metric1()
tmp_met1_clf4 = clfs[3].dummy_metric1()
tmp_met2_clf1 = clfs[0].dummy_metric2()
tmp_met2_clf2 = clfs[1].dummy_metric2()
tmp_met2_clf3 = clfs[2].dummy_metric2()
tmp_met2_clf4 = clfs[3].dummy_metric2()
met1_clf1 = np.concatenate((met1_clf1, tmp_met1_clf1))
met1_clf2 = np.concatenate((met1_clf2, tmp_met1_clf2))
met1_clf3 = np.concatenate((met1_clf3, tmp_met1_clf3))
met1_clf4 = np.concatenate((met1_clf4, tmp_met1_clf4))
met2_clf1 = np.concatenate((met2_clf1, tmp_met2_clf1))
met2_clf2 = np.concatenate((met2_clf2, tmp_met2_clf2))
met2_clf3 = np.concatenate((met2_clf3, tmp_met2_clf3))
met2_clf4 = np.concatenate((met2_clf4, tmp_met2_clf4))
# -- Plot the contour and training examples -- #
# Update the plot objects: remove the previous collections to save memory.
#l = len(boundary.collections)
for coll in boundary.collections:
# Remove the existing contours
boundary.collections.remove(coll)
boundary = axes[0].contourf(XX_1, XX_2, Z, 3, colors=["red", "green", "goldenrod", "dodgerblue"], alpha=0.5)
l_cost_clf1.set_data(idx, cost_clf1)
l_cost_clf2.set_data(idx, cost_clf2)
l_cost_clf3.set_data(idx, cost_clf3)
l_cost_clf4.set_data(idx, cost_clf4)
l_met1_clf1.set_data(idx, met1_clf1)
l_met1_clf2.set_data(idx, met1_clf2)
l_met1_clf3.set_data(idx, met1_clf3)
l_met1_clf4.set_data(idx, met1_clf4)
l_met2_clf1.set_data(idx, met2_clf1)
l_met2_clf2.set_data(idx, met2_clf2)
l_met2_clf3.set_data(idx, met2_clf3)
l_met2_clf4.set_data(idx, met2_clf4)
return boundary.collections, l_cost_clf1, l_cost_clf2, l_cost_clf3, l_cost_clf4, \
l_met1_clf1, l_met1_clf2, l_met1_clf3, l_met1_clf4, \
l_met2_clf1, l_met2_clf2, l_met2_clf3, l_met2_clf4
def f_close(event):
""" Functions called when the graphical window is closed.
It prints the last value of the theta vector and the last value of the
cost function.
"""
plt.close()
class DummyBinary():
def __init__(self, house, theta0, theta1, alpha=1e-3):
self.house = house
self.theta0 = theta0
self.theta1 = theta1
self.alpha = alpha
if self.house == "C1":
self.border_x = 6
self.border_y = 6
if self.house == "C2":
self.border_x = 6
self.border_y = 13
if self.house == "C3":
self.border_x = 13
self.border_y = 6
if self.house == "C4":
self.border_x = 13
self.border_y = 13
def fit(self, n_cycle:int):
for _ in range(n_cycle):
self.theta0 = self.theta0 + self.alpha * (self.border_x - self.theta0)
self.theta1 = self.theta1 + self.alpha * (self.border_y - self.theta1)
def cost(self) -> float:
cost = (self.theta0 - self.border_x)**2 + (self.theta1 - self.border_y)**2
return cost
def predict(self, X:np.array) -> np.array:
if self.house == 'C1':
mask = (X[:,0] < self.theta0) & (X[:,1] < self.theta1)
if self.house == 'C2':
mask = (X[:,0] < self.theta0) & (X[:,1] > self.theta1)
if self.house == 'C3':
mask = (X[:,0] > self.theta0) & (X[:,1] < self.theta1)
if self.house == 'C4':
mask = (X[:,0] > self.theta0) & (X[:,1] > self.theta1)
pred =np.zeros((X.shape[0], 1))
pred[mask] = int(self.house[1])
return pred
def dummy_metric1(self):
return np.array([0.5 * (self.theta0 / self.border_x + self.theta1 / self.border_y)])
def dummy_metric2(self):
return np.array([0.5 * ((self.theta0 / self.border_x)**2 + (self.theta1 / self.border_y)**2)])
# =========================================================================== #
# _________________________________ MAIN __________________________________ #
# =========================================================================== #
if __name__ == "__main__":
# -- Dummy data -- #
x1 = np.random.randn(60,1) * 2.5 + 3.5
x2 = np.random.randn(60,1) * 2.5 + 3.5
x3 = np.random.randn(60,1) * 2.5 + 15.5
x4 = np.random.randn(60,1) * 2.5 + 15.5
stud_house = 60 * ['C1'] + 60 * ['C2'] + 60 * ['C3'] + 60 * ['C4']
c_house = [dct_palet[house] for house in stud_house]
y1 = np.random.randn(60,1) * 2.5 + 3.5
y2 = np.random.randn(60,1) * 2.5 + 15.5
y3 = np.random.randn(60,1) * 2.5 + 3.5
y4 = np.random.randn(60,1) * 2.5 + 15.5
X = np.concatenate((x1, x2, x3, x4)) # shape: (240,1)
Y = np.concatenate((y1, y2, y3, y4)) # shape: (240,1)
data = np.concatenate((X, Y, np.array(stud_house).reshape(-1,1)), axis=1) # shape: (240,3)
clf1 = DummyBinary("C1", np.random.rand(1), np.random.rand(1))
clf2 = DummyBinary("C2", np.random.rand(1), np.random.rand(1))
clf3 = DummyBinary("C3", np.random.rand(1), np.random.rand(1))
clf4 = DummyBinary("C4", np.random.rand(1), np.random.rand(1))
clfs = [clf1, clf2, clf3, clf4]
## Visualize the raw dummy data.
#plt.scatter(X, Y, c=c_house, s=5)
#plt.show()
do_animation(clfs, data)
The Class DummyBinary mimics in a simplified way, what my One-vs-All class can do.
You can see a bunch of global in anim_visu and f_anim, in this way the code "works", but I'm aware there is something very wrong.
Attempts:
No global variables, everything were passed to f_anim via fargs, but when returning from f_anim, all the modification of the variables in f_anim scope were lost (normal behavior obviously),
Moving the definition of the f_anim within the body of anim_visu, to make f_anim an inner_function. I'm not experienced enough, so I did not succeed to make it works this way, I noticed that It may appeared it is not possible to modify variable declared in the anim_visu scope in the inner function.
Declare all the variables I need as global, it work in a way, but as you can see by running the code (in the axes[0]), the PathCollections are not cleared/deleted (despite the loop with boundary.collections.remove(coll)) and the number of PathCollection in the axes[0] seems to increased, leading to a drop of the speed the frames are updated.
Looking forward for your advice (and solution+explanation I hope).
And thank you for your times and neurons.

Interactive annotations only show up for one data set in matplotlib

I wrote a script with annotations that get displayed upon hovering over data points based on some of the answers to similar questions by the user ImportanceOfBeingErnest. One of the changes I've made is that I only change the text and position of a single annotation and use it for more than one data set. This seems to cause the problem that the annotation only gets displayed for the last data set (or plotter, as I called them in my script) in the list of all data sets/ plotters.
How can I get the annotation to display for all data points of both scatter plots in my script? Do I have to make a new annotation for each data set and update them separately?
import numpy as np
import matplotlib
import matplotlib.pyplot as plt
from matplotlib.ticker import AutoMinorLocator, MultipleLocator
from scipy.stats import linregress
# All data in pA*s
gc_data = {
'KAL1':{'Toluol':400754.594,'1-Octen':53695.014,'Decan':6443.483,'1-Nonannitril':48984.504},
'KAL2':{'Toluol':417583.343,'1-Octen':29755.3,'Decan':16264.896,'1-Nonannitril':16264.896},
'KAL3':{'Toluol':442378.88,'1-Octen':18501.12,'Decan':19226.245,'1-Nonannitril':16200.611},
'KAL4':{'Toluol':389679.589,'1-Octen':13381.415,'Decan':68549.002,'1-Nonannitril':11642.123},
'KAL5':{'Toluol':423982.487,'1-Octen':6263.286,'Decan':53580.809,'1-Nonannitril':4946.271},
'KAL6':{'Toluol':351754.329,'1-Octen':8153.602,'Decan':105408.823,'1-Nonannitril':7066.718}
}
# All data in mg
mass_data = {
'KAL1':{'1-Octen':149.3,'Decan':17.8,'1-Nonannitril':154.7},
'KAL2':{'1-Octen':80.6,'Decan':43.7,'1-Nonannitril':82.8},
'KAL3':{'1-Octen':50.4,'Decan':51.8,'1-Nonannitril':51.5},
'KAL4':{'1-Octen':40.9,'Decan':206.9,'1-Nonannitril':40.8},
'KAL5':{'1-Octen':18.0,'Decan':155.2,'1-Nonannitril':16.4},
'KAL6':{'1-Octen':23.4,'Decan':301.4,'1-Nonannitril':23.6},
}
def update_annot(line, annot, ind):
if isinstance(line, matplotlib.collections.PathCollection):
x,y = line.get_offsets().transpose()
elif isinstance(line, matplotlib.lines.Line2D):
x,y = line.get_data()
else:
quit('No getter of x,y Data for this type of plotter.')
annot.xy = (x[ind["ind"][0]], y[ind["ind"][0]])
text = "x = {}\ny= {}".format(x[ind["ind"][0]], y[ind["ind"][0]])
annot.set_text(text)
def hover(event,fig,annot):
if event.inaxes in fig.axes:
plotters = fig.axes[0].collections
for plotter in plotters:
cont, ind = plotter.contains(event)
if cont:
update_annot(plotter, annot, ind)
annot.set_visible(True)
fig.canvas.draw_idle()
else:
if annot.get_visible():
annot.set_visible(False)
fig.canvas.draw_idle()
def get_data(substance,standard):
m = [mass_data[i][substance]/mass_data[i][standard] for i in mass_data]
A = [gc_data[i][substance]/gc_data[i][standard] for i in mass_data]
return A,m
def plot(substance,standard,save=None):
A,m = get_data(substance,standard)
A_baddata = A.pop(1)
m_baddata = m.pop(1)
# Linear regression
a,b,rval,pval,stdev = linregress(A,m)
# Plotting
fig, ax = plt.subplots(figsize=(6,6))
# Data inputs
ax.scatter(A,m,marker='o') # Measured data
ax.scatter(A_baddata,m_baddata,marker='o',c='r')
xmin,xmax = ax.get_xlim()
ymin,ymax = ax.get_ylim()
ax.plot(np.array([-2*max(A),2*max(A)]),np.array([-2*max(A),2*max(A)])*a + b) # graph from regression parameters
ax.set_ylim(ymin,ymax)
ax.set_xlim(xmin,xmax)
# General formatting
ax.tick_params(axis='both',which='both',labelsize=12,direction='in')
ax.xaxis.set_major_locator(MultipleLocator(1))
ax.yaxis.set_major_locator(MultipleLocator(1))
ax.xaxis.set_minor_locator(AutoMinorLocator())
ax.yaxis.set_minor_locator(AutoMinorLocator())
ax.set_ylabel(r'$m_{\mathrm{Substanz}}\quad/\quadm_{\mathrm{Standard}}$')
ax.set_xlabel(r'$A_{\mathrm{Substanz}}\quad/\quadA_{\mathrm{Standard}}$')
# Description Box
textstr='{}{}\n'.format('Substanz: ',substance)
textstr+='{}{}\n'.format('Standard: ',standard)
textstr+='{}{:.5f}\n'.format('a = ',a)
textstr+='{}{:.5f}\n'.format('b = ',b)
textstr+='{}{:.5f}\n'.format(r'$R^{2}$ = ',rval)
textstr+='{}{:.5f}\n'.format(r'$p$ = ',pval)
textstr+='{}{:.5f}'.format(r'$\bar X = $',stdev)
props = dict(boxstyle='round', fc='#96FBFF', ec='#3CF8FF', alpha=0.5)
ax.text(0.05, 0.95, textstr, transform=ax.transAxes, fontsize=10,
verticalalignment='top', bbox=props)
if save:
plt.savefig(substance+'.svg' ,bbox_inches='tight', transparent=True)
else:
# Hovering annotation
################################################################################################
# for i in range(len())
annot = ax.annotate("", xy=(0,0), xytext=(1,1),textcoords="offset points",
bbox=dict(boxstyle="round", fc="w", alpha=0.4),
arrowprops=dict(arrowstyle="->"))
annot.set_visible(False)
################################################################################################
fig.canvas.mpl_connect("motion_notify_event", lambda event: hover(event, fig, annot))
plt.show()
plot('1-Nonannitril','Decan',0)
The main problem is that the hover event gets triggered by the line instead of by the nearby scatter dots. So, this line should be excluded when connecting the motion_notify_event.
Since ImportanceOfBeingErnest's and others posts about how to create annotations, they developed the mplcursors library to strongly simplify the creation of this kind of annotations.
With mplcursors you can simply call mplcursors.cursor(ax.collections, hover=True) and automatically an annotation with x and y positions would be created. But easily can go much further. The example below also shows how to display the artist's label (here the 'artist' is one collection of scatter dots). Also, how to use the artist's color for the background of the annotation. Further, an extra attribute is added to the artist with a list of names. These names are then added to the annotation.
The code leaves out some of the elements that aren't relevant for the annotations, such as the large text.
import numpy as np
import matplotlib
import matplotlib.pyplot as plt
from matplotlib.ticker import AutoMinorLocator, MultipleLocator
from scipy.stats import linregress
import mplcursors
from matplotlib.colors import to_rgb
# All data in pA*s
gc_data = {
'KAL1': {'Toluol': 400754.594, '1-Octen': 53695.014, 'Decan': 6443.483, '1-Nonannitril': 48984.504},
'KAL2': {'Toluol': 417583.343, '1-Octen': 29755.3, 'Decan': 16264.896, '1-Nonannitril': 16264.896},
'KAL3': {'Toluol': 442378.88, '1-Octen': 18501.12, 'Decan': 19226.245, '1-Nonannitril': 16200.611},
'KAL4': {'Toluol': 389679.589, '1-Octen': 13381.415, 'Decan': 68549.002, '1-Nonannitril': 11642.123},
'KAL5': {'Toluol': 423982.487, '1-Octen': 6263.286, 'Decan': 53580.809, '1-Nonannitril': 4946.271},
'KAL6': {'Toluol': 351754.329, '1-Octen': 8153.602, 'Decan': 105408.823, '1-Nonannitril': 7066.718}
}
# All data in mg
mass_data = {
'KAL1': {'1-Octen': 149.3, 'Decan': 17.8, '1-Nonannitril': 154.7},
'KAL2': {'1-Octen': 80.6, 'Decan': 43.7, '1-Nonannitril': 82.8},
'KAL3': {'1-Octen': 50.4, 'Decan': 51.8, '1-Nonannitril': 51.5},
'KAL4': {'1-Octen': 40.9, 'Decan': 206.9, '1-Nonannitril': 40.8},
'KAL5': {'1-Octen': 18.0, 'Decan': 155.2, '1-Nonannitril': 16.4},
'KAL6': {'1-Octen': 23.4, 'Decan': 301.4, '1-Nonannitril': 23.6},
}
def update_annot(sel):
x, y = sel.target
label = sel.artist.get_label()
new_text = f'{label}\nx: {x:.2f}\ny: {y:.2f}'
# append the name
new_text += '\n' + sel.artist.data_names[sel.target.index]
sel.annotation.set_text(new_text)
# get the color of the scatter dots, make them whiter and use that as background color for the annotation
r, g, b = to_rgb(sel.artist.get_facecolor())
sel.annotation.get_bbox_patch().set(fc=((r + 2) / 3, (g + 2) / 3, (b + 2) / 3), alpha=0.7)
def get_data(substance, standard):
m = [mass_data[i][substance] / mass_data[i][standard] for i in mass_data]
A = [gc_data[i][substance] / gc_data[i][standard] for i in mass_data]
return A, m
def plot(substance, standard, save=None):
global measured_names, baddata_names
A, m = get_data(substance, standard)
measured_names = list(mass_data.keys())
A_baddata = A.pop(1)
m_baddata = m.pop(1)
baddata_names = [measured_names.pop(1)]
# Linear regression
a, b, rval, pval, stdev = linregress(A, m)
# Plotting
fig, ax = plt.subplots(figsize=(6, 6))
# Data inputs
scat1 = ax.scatter(A, m, marker='o', label='Measured data') # Measured data
scat1.data_names = measured_names
scat2 = ax.scatter(A_baddata, m_baddata, marker='o', c='r', label='Bad data')
scat2.data_names = baddata_names
xmin, xmax = ax.get_xlim()
ymin, ymax = ax.get_ylim()
ax.plot(np.array([-2 * max(A), 2 * max(A)]),
np.array([-2 * max(A), 2 * max(A)]) * a + b) # graph from regression parameters
ax.set_ylim(ymin, ymax)
ax.set_xlim(xmin, xmax)
ax.set_ylabel(r'$m_{\mathrm{Substanz}}\quad/\quadm_{\mathrm{Standard}}$')
ax.set_xlabel(r'$A_{\mathrm{Substanz}}\quad/\quadA_{\mathrm{Standard}}$')
# Hovering annotation
# cursor = mplcursors.cursor(ax.collections, hover=True)
cursor = mplcursors.cursor([scat1, scat2], hover=True)
cursor.connect("add", update_annot)
plt.show()
plot('1-Nonannitril', 'Decan', 0)

gridspec without using ":" in Parameter?

my Question is pretty simple: I cant do a ":" in the last parameter of
diagram(strecke, strecke2, "Strecke in Metern/s", "S(t) Diagramm", 0, :)
because it tells me than: '<' not supported between instances of 'str' and 'int'
but i want to have this diagramm over both columns, so what can i do that its over both columns?
Link for the file: https://filehorst.de/download.php?file=ceCnteJq
import codecs
import matplotlib.pyplot as plt
import matplotlib.patches as mpatches # wird nicht benutzt, test
import numpy as np
import matplotlib.gridspec as grid
zeit = []
strecke = []
geschwindigkeit = []
beschleunigung = []
strecke2 = []
geschwindigkeit2 = []
beschleunigung2 = []
gs = grid.GridSpec(2, 2)
fig = plt.figure('Diagramme', tight_layout=True)
daten = open("BewegungBeschleunigung.csv")
reiheÜberspringen = True
for i in daten:
if reiheÜberspringen == True:
reiheÜberspringen = False
continue
i = i.strip().split(",")
zeit.append(float(i[0]))
strecke.append(float(i[1]))
geschwindigkeit.append(float(i[2]))
beschleunigung.append(float(i[3]))
strecke2.append(float(i[4]))
geschwindigkeit2.append(float(i[5]))
beschleunigung2.append(float(i[6]))
def diagram(y, y2, yachse, titel, pos1, pos2):
ax = fig.add_subplot(gs[pos1, pos2])
ax.plot(zeit, y, "go", markersize = 2.5)
ax.plot(zeit, y2, "ro", markersize = 2.5)
ax.autoscale(enable = False, axis = "both", tight = None)
ax.grid(True)
ax.set_title(titel)
plt.gcf().canvas.set_window_title("Diagramme")
diagram(strecke, strecke2, "Strecke in Metern/s", "S(t) Diagramm", 0, :)
diagram(geschwindigkeit, geschwindigkeit2, "Geschwindigkeit in Metern/s", "V(t) Diagramm", 1, 0)
diagram(beschleunigung, beschleunigung2, "Beschleunigung in Metern/s^2", "a(t) Diagramm", 1, 1 )
plt.show()
daten.close
You can put an if condition inside your function. You can make the default value of pos2 as None. So if you want :, you don't pass any value while calling diagram. Then you check inside the function, if any value has been passed for pos2. If not, then your use : else you use the passed value.
Below is the modified function. I don't have your data but it will work.
def diagram(y, y2, yachse, titel, pos1, pos2=None):
if not pos2:
ax = fig.add_subplot(gs[pos1, :])
else:
ax = fig.add_subplot(gs[pos1, pos2])
ax.plot(zeit, y, "go", markersize = 2.5)
ax.plot(zeit, y2, "ro", markersize = 2.5)
ax.autoscale(enable = False, axis = "both", tight = None)
ax.grid(True)
ax.set_title(titel)
plt.gcf().canvas.set_window_title("Diagramme")
diagram(strecke, strecke2, "Strecke in Metern/s", "S(t) Diagramm", 0) # <-- No pos2
diagram(geschwindigkeit, geschwindigkeit2, "Geschwindigkeit in Metern/s", "V(t) Diagramm", 1, 0)
diagram(beschleunigung, beschleunigung2, "Beschleunigung in Metern/s^2", "a(t) Diagramm", 1, 1 )
plt.show()
Numpy provides a way to generate an indexing expression via numpy.s_. You may use it as
np.s_[:]
to pass the complete slice to a function.
import numpy as np
import matplotlib.pyplot as plt
import matplotlib.gridspec as gridspec
gs = gridspec.GridSpec(2, 2)
fig = plt.figure('Diagramme', tight_layout=True)
data = np.loadtxt("data/BewegungBeschleunigung.csv", skiprows=1, delimiter=",", unpack=True)
(zeit, strecke, geschwindigkeit, beschleunigung,
strecke2, geschwindigkeit2, beschleunigung2) = data
def diagram(y, y2, yachse, titel, pos1, pos2):
ax = fig.add_subplot(gs[pos1, pos2])
ax.plot(zeit, y, "go", markersize = 2.5)
ax.plot(zeit, y2, "ro", markersize = 2.5)
ax.autoscale(enable = False, axis = "both", tight = None)
ax.grid(True)
ax.set_title(titel)
plt.gcf().canvas.set_window_title("Diagramme")
diagram(strecke, strecke2, "Strecke", "S(t) Diagramm", 0, np.s_[:])
diagram(geschwindigkeit, geschwindigkeit2, "Geschwindigkeit", "V(t) Diagramm", 1, 0)
diagram(beschleunigung, beschleunigung2, "Beschleunigung", "a(t) Diagramm", 1, 1 )
plt.show()

Animate contour and scatter plot

I am trying to animate a scatter and bivariate gaussian distribution from a set of xy coordinates. I'll record the specific code that calls the scatter and distribution first and then how I measure the distribution afterwards.
import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
import scipy.stats as sts
import matplotlib.animation as animation
''' Below is a section of the script that generates the scatter and contour '''
fig, ax = plt.subplots(figsize = (10,4))
def plotmvs(df, xlim=None, ylim=None, fig=fig, ax=ax):
if xlim is None: xlim = datalimits(df['X'])
if ylim is None: ylim = datalimits(df['Y'])
PDFs = []
for (group,gdf),color in zip(df.groupby('group'), ('red', 'blue')):
ax.plot(*gdf[['X','Y']].values.T, '.', c=color, alpha = 0.5)
kwargs = {
'xlim': xlim,
'ylim': ylim
}
X, Y, PDF = mvpdfs(gdf['X'].values, gdf['Y'].values, **kwargs)
PDFs.append(PDF)
PDF = PDFs[0] - PDFs[1]
normPDF = PDF - PDF.min()
normPDF = normPDF/normPDF.max()
cfs = ax.contourf(X, Y, normPDF, levels=100, cmap='jet')
return fig, ax
n = 10
time = [1]
d = ({
'A1_Y' : [10,20,15,20,25,40,50,60,61,65],
'A1_X' : [15,10,15,20,25,25,30,40,60,61],
'A2_Y' : [10,13,17,10,20,24,29,30,33,40],
'A2_X' : [10,13,15,17,18,19,20,21,26,30],
'A3_Y' : [11,12,15,17,19,20,22,25,27,30],
'A3_X' : [15,18,20,21,22,28,30,32,35,40],
'A4_Y' : [15,20,15,20,25,40,50,60,61,65],
'A4_X' : [16,20,15,30,45,30,40,10,11,15],
'B1_Y' : [18,10,11,13,18,10,30,40,31,45],
'B1_X' : [17,20,15,10,25,20,10,12,14,25],
'B2_Y' : [13,10,14,20,21,12,30,20,11,35],
'B2_X' : [12,20,16,22,15,20,10,20,16,15],
'B3_Y' : [15,20,15,20,25,10,20,10,15,25],
'B3_X' : [18,15,13,20,21,10,20,10,11,15],
'B4_Y' : [19,12,15,18,14,19,13,12,11,18],
'B4_X' : [20,10,12,18,17,15,13,14,19,13],
})
tuples = [((t, k.split('_')[0][0], int(k.split('_')[0][1:]), k.split('_')[1]), v[i]) for k,v in d.items() for i,t in enumerate(time)]
df = pd.Series(dict(tuples)).unstack(-1)
df.index.names = ['time', 'group', 'id']
for time,tdf in df.groupby('time'):
plotmvs(tdf)
'''MY ATTEMPT AT ANIMATING THE PLOT '''
def animate(i) :
tdf.set_offsets([[tdf.iloc[0:,1][0+i][0], tdf.iloc[0:,0][0+i][0]], [tdf.iloc[0:,1][0+i][1], tdf.iloc[0:,0][0+i][1]], [tdf.iloc[0:,1][0+i][2], tdf.iloc[0:,0][0+i][2]], [tdf.iloc[0:,1][0+i][3], tdf.iloc[0:,0][0+i][3]], [tdf.iloc[0:,1][0+i][4], tdf.iloc[0:,0][0+i][4]]])
normPDF = n[i,:,0,:].T
cfs.set_data(X, Y, normPDF)
ani = animation.FuncAnimation(fig, animate, np.arange(0,10),# init_func = init,
interval = 10, blit = False)
A full working code on how the distribution is generated and plotted using a single frame
import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
import scipy.stats as sts
import matplotlib.animation as animation
def datalimits(*data, pad=.15):
dmin,dmax = min(d.min() for d in data), max(d.max() for d in data)
spad = pad*(dmax - dmin)
return dmin - spad, dmax + spad
def rot(theta):
theta = np.deg2rad(theta)
return np.array([
[np.cos(theta), -np.sin(theta)],
[np.sin(theta), np.cos(theta)]
])
def getcov(radius=1, scale=1, theta=0):
cov = np.array([
[radius*(scale + 1), 0],
[0, radius/(scale + 1)]
])
r = rot(theta)
return r # cov # r.T
def mvpdf(x, y, xlim, ylim, radius=1, velocity=0, scale=0, theta=0):
X,Y = np.meshgrid(np.linspace(*xlim), np.linspace(*ylim))
XY = np.stack([X, Y], 2)
x,y = rot(theta) # (velocity/2, 0) + (x, y)
cov = getcov(radius=radius, scale=scale, theta=theta)
PDF = sts.multivariate_normal([x, y], cov).pdf(XY)
return X, Y, PDF
def mvpdfs(xs, ys, xlim, ylim, radius=None, velocity=None, scale=None, theta=None):
PDFs = []
for i,(x,y) in enumerate(zip(xs,ys)):
kwargs = {
'xlim': xlim,
'ylim': ylim
}
X, Y, PDF = mvpdf(x, y,**kwargs)
PDFs.append(PDF)
return X, Y, np.sum(PDFs, axis=0)
fig, ax = plt.subplots(figsize = (10,4))
def plotmvs(df, xlim=None, ylim=None, fig=fig, ax=ax):
if xlim is None: xlim = datalimits(df['X'])
if ylim is None: ylim = datalimits(df['Y'])
PDFs = []
for (group,gdf),color in zip(df.groupby('group'), ('red', 'blue')):
#Animate this scatter
ax.plot(*gdf[['X','Y']].values.T, '.', c=color, alpha = 0.5)
kwargs = {
'xlim': xlim,
'ylim': ylim
}
X, Y, PDF = mvpdfs(gdf['X'].values, gdf['Y'].values, **kwargs)
PDFs.append(PDF)
PDF = PDFs[0] - PDFs[1]
normPDF = PDF - PDF.min()
normPDF = normPDF/normPDF.max()
#Animate this contour
cfs = ax.contourf(X, Y, normPDF, levels=100, cmap='jet')
return fig, ax
n = 10
time = [1]
d = ({
'A1_Y' : [10,20,15,20,25,40,50,60,61,65],
'A1_X' : [15,10,15,20,25,25,30,40,60,61],
'A2_Y' : [10,13,17,10,20,24,29,30,33,40],
'A2_X' : [10,13,15,17,18,19,20,21,26,30],
'A3_Y' : [11,12,15,17,19,20,22,25,27,30],
'A3_X' : [15,18,20,21,22,28,30,32,35,40],
'A4_Y' : [15,20,15,20,25,40,50,60,61,65],
'A4_X' : [16,20,15,30,45,30,40,10,11,15],
'B1_Y' : [18,10,11,13,18,10,30,40,31,45],
'B1_X' : [17,20,15,10,25,20,10,12,14,25],
'B2_Y' : [13,10,14,20,21,12,30,20,11,35],
'B2_X' : [12,20,16,22,15,20,10,20,16,15],
'B3_Y' : [15,20,15,20,25,10,20,10,15,25],
'B3_X' : [18,15,13,20,21,10,20,10,11,15],
'B4_Y' : [19,12,15,18,14,19,13,12,11,18],
'B4_X' : [20,10,12,18,17,15,13,14,19,13],
})
tuples = [((t, k.split('_')[0][0], int(k.split('_')[0][1:]), k.split('_')[1]), v[i]) for k,v in d.items() for i,t in enumerate(time)]
df = pd.Series(dict(tuples)).unstack(-1)
df.index.names = ['time', 'group', 'id']
for time,tdf in df.groupby('time'):
plotmvs(tdf)
I essentially want to animate this code by iterating over each row of xy coordinates.
Here's a very quick and dirty modification of the OP's code, fixing the scatter animation and adding (a form of) contour animation.
Basically, you start by creating the artists for your animation (in this case Line2D objects, as returned by plot()). Subsequently, you create an update function (and, optionally, an initialization function). In that function, you update the existing artists. I think the example in the matplotlib docs explains it all.
In this case, I modified the OP's plotmvs function to be used as the update function (instead of the OP's proposed animate function).
The QuadContourSet returned by contourf (i.e. your cfs) cannot be used as an artist in itself, but you can make it work using cfs.collections (props to this SO answer). However, you still need to create a new contour plot and remove the old one, instead of just updating the contour data. Personally I would prefer a lower level approach: try to get the contour-data without calling contourf, then initialize and update the contour lines just like you do for the scatter.
Nevertheless, the approach above is implemented in the OP's code below (just copy, paste, and run):
import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
import scipy.stats as sts
from matplotlib.animation import FuncAnimation
# quick and dirty override of datalimits(), to get a fixed contour-plot size
DATA_LIMITS = [0, 70]
def datalimits(*data, pad=.15):
# dmin,dmax = min(d.min() for d in data), max(d.max() for d in data)
# spad = pad*(dmax - dmin)
return DATA_LIMITS # dmin - spad, dmax + spad
def rot(theta):
theta = np.deg2rad(theta)
return np.array([
[np.cos(theta), -np.sin(theta)],
[np.sin(theta), np.cos(theta)]
])
def getcov(radius=1, scale=1, theta=0):
cov = np.array([
[radius*(scale + 1), 0],
[0, radius/(scale + 1)]
])
r = rot(theta)
return r # cov # r.T
def mvpdf(x, y, xlim, ylim, radius=1, velocity=0, scale=0, theta=0):
X,Y = np.meshgrid(np.linspace(*xlim), np.linspace(*ylim))
XY = np.stack([X, Y], 2)
x,y = rot(theta) # (velocity/2, 0) + (x, y)
cov = getcov(radius=radius, scale=scale, theta=theta)
PDF = sts.multivariate_normal([x, y], cov).pdf(XY)
return X, Y, PDF
def mvpdfs(xs, ys, xlim, ylim, radius=None, velocity=None, scale=None, theta=None):
PDFs = []
for i,(x,y) in enumerate(zip(xs,ys)):
kwargs = {
'xlim': xlim,
'ylim': ylim
}
X, Y, PDF = mvpdf(x, y,**kwargs)
PDFs.append(PDF)
return X, Y, np.sum(PDFs, axis=0)
fig, ax = plt.subplots(figsize = (10,4))
ax.set_xlim(DATA_LIMITS)
ax.set_ylim(DATA_LIMITS)
# Initialize empty lines for the scatter (increased marker size to make them more visible)
line_a, = ax.plot([], [], '.', c='red', alpha = 0.5, markersize=20, animated=True)
line_b, = ax.plot([], [], '.', c='blue', alpha = 0.5, markersize=20, animated=True)
cfs = None
# Modify the plotmvs function so it updates the lines
# (might as well rename the function to "update")
def plotmvs(tdf, xlim=None, ylim=None):
global cfs # as noted: quick and dirty...
if cfs:
for tp in cfs.collections:
# Remove the existing contours
tp.remove()
# Get the data frame for time t
df = tdf[1]
if xlim is None: xlim = datalimits(df['X'])
if ylim is None: ylim = datalimits(df['Y'])
PDFs = []
for (group, gdf), group_line in zip(df.groupby('group'), (line_a, line_b)):
#Animate this scatter
#ax.plot(*gdf[['X','Y']].values.T, '.', c=color, alpha = 0.5)
# Update the scatter line data
group_line.set_data(*gdf[['X','Y']].values.T)
kwargs = {
'xlim': xlim,
'ylim': ylim
}
X, Y, PDF = mvpdfs(gdf['X'].values, gdf['Y'].values, **kwargs)
PDFs.append(PDF)
PDF = PDFs[0] - PDFs[1]
normPDF = PDF - PDF.min()
normPDF = normPDF / normPDF.max()
# Plot a new contour
cfs = ax.contourf(X, Y, normPDF, levels=100, cmap='jet')
# Return the artists (the trick is to return cfs.collections instead of cfs)
return cfs.collections + [line_a, line_b]
n = 10
time = range(n) # assuming n represents the length of the time vector...
d = ({
'A1_Y' : [10,20,15,20,25,40,50,60,61,65],
'A1_X' : [15,10,15,20,25,25,30,40,60,61],
'A2_Y' : [10,13,17,10,20,24,29,30,33,40],
'A2_X' : [10,13,15,17,18,19,20,21,26,30],
'A3_Y' : [11,12,15,17,19,20,22,25,27,30],
'A3_X' : [15,18,20,21,22,28,30,32,35,40],
'A4_Y' : [15,20,15,20,25,40,50,60,61,65],
'A4_X' : [16,20,15,30,45,30,40,10,11,15],
'B1_Y' : [18,10,11,13,18,10,30,40,31,45],
'B1_X' : [17,20,15,10,25,20,10,12,14,25],
'B2_Y' : [13,10,14,20,21,12,30,20,11,35],
'B2_X' : [12,20,16,22,15,20,10,20,16,15],
'B3_Y' : [15,20,15,20,25,10,20,10,15,25],
'B3_X' : [18,15,13,20,21,10,20,10,11,15],
'B4_Y' : [19,12,15,18,14,19,13,12,11,18],
'B4_X' : [20,10,12,18,17,15,13,14,19,13],
})
tuples = [((t, k.split('_')[0][0], int(k.split('_')[0][1:]), k.split('_')[1]), v[i])
for k,v in d.items() for i,t in enumerate(time)]
df = pd.Series(dict(tuples)).unstack(-1)
df.index.names = ['time', 'group', 'id']
# Use the modified plotmvs as the update function, and supply the data frames
interval_ms = 200
delay_ms = 1000
ani = FuncAnimation(fig, plotmvs, frames=df.groupby('time'),
blit=True, interval=interval_ms, repeat_delay=delay_ms)
# Start the animation
plt.show()

Matplotlib tilted text on log scale?

Either I cannot figure it out, or there is a bug in matplotlib when drawing text() with rotation to loglog or xscale('log'); yscale('log'); plot.
my code looks like this:
from pylab import *
# =================== deltaV-vs-trust
figure( figsize=( 12, 8 ) )
times = array( [ 1.0, 60.0, 3600.0, 86400.0, 604800, 2592000, 31556926, 315569260, 3155692600 ] )
timeText = [ 'sec','min','hour', 'day', 'week', 'month', 'year', '10years', '100years' ]
dists = array( [ 1e+1, 1e+2, 1e+3, 1e+4, 1e+5, 1e+6, 6371e+3, 42164e+3, 384400e+3, 1e+9, 1e+10, 5.790918E+010, 1.082089E+011, 1.495979E+011, 2.279366E+011, 7.784120E+011, 1.426725E+012, 2.870972E+012, 4.498253E+012, 1.40621998e+13, 2.99195741e+14, 7.47989354e+15, 4.13425091e+16 ] )
distText = [ '10m','100m', '1km','10km', '100km', '1000km', 'LEO', 'GEO', 'Moon', r'10$^6$km',r'10$^7$km', 'Mercury', 'Venus', 'Earth', 'Mars', 'Jupiter', 'Satrun', 'Uranus', 'Neptune', 'Heliopause', 'Inner Oorth', 'Outer Oorth', 'Alpha Centauri' ]
vMin = 1e+0; vMax = 1e+8;
aMin = 1e-4; aMax = 1e+2;
As = linspace(aMin,aMax,2); print As
Vs = linspace(vMin,vMax,2); print Vs
As_ = As.copy(); Vs_ = Vs.copy()
for i in range(len(dists)):
dist = dists[i]
ts = sqrt( 2*dist / As )
As_[:] = As [:]
Vs_ = As * ts
if( Vs_[0] < Vs[0] ):
Vs_[0] = Vs[0]
As_[0] = Vs_[0]**2 / (2*dist)
plot( Vs_, As_, 'b-', alpha=0.5 )
plt.text( Vs_[0], As_[0], distText[i], rotation=60, color='b', horizontalalignment='center', verticalalignment='bottom') # this does not work properly
#plt.text( Vs_[0], As_[0], distText[i], rotation=60, color='b', horizontalalignment='center', verticalalignment='center') # this works but does not look nice
#plt.text( Vs_[0], 1.5*As_[0], distText[i], rotation=60, color='b', horizontalalignment='center', verticalalignment='center') # a bit better
for i in range(len(times)):
time = times[i]
As_[:] = As[:]
Vs_ = As * time
if( Vs_[1] > Vs[1] ):
Vs_[1] = Vs[1]
As_[1] = Vs_[1] / time
plot( Vs_, As_, 'r-', alpha=0.5 )
plt.text( Vs_[1], As_[1], timeText[i]+" ", rotation=40, color='r', horizontalalignment='right', verticalalignment='baseline')
ylabel( r" acceleration [m/s$^2$] " )
xlabel( r" delta-v [m/s ] " )
yscale('log')
xscale('log')
grid()
ylim( aMin, aMax )
xlim( vMin, vMax )
show()
the result looks like this ( you can see how text is not possitioned correctly on the corresponding line; very visible for Inner Oorth,Outer Oorth and Alpha Centauri ) :
I think this problem is visible only for large rotation angles. If I use horizontalalignment='center', verticalalignment='center' it works properly, however it does not looks nice ( because the line cross the text and the text cross the border of image )
just for context - what I'm trying to make is plot like this:
http://www.projectrho.com/public_html/rocket/images/enginelist/torchChart.jpg
The key is the 'rotation_mode' kwarg to text (doc) which I did not even know existed until tonight. What this controls is if the text is rotated, then aligned (the default) or aligned, then rotated.
In trying to understand your question I cleaned your code up to (more or less) conform to pep8 and simplified some of the computation.
import matplotlib.pyplot as plt
import numpy as np
# =================== deltaV-vs-trust
times = np.array([1.0, 60.0, 3600.0, 86400.0, 604800, 2592000, 31556926, 315569260, 3155692600])
timeText = ['sec','min','hour', 'day', 'week', 'month', 'year', '10years', '100years']
dists= np.array([1e+1, 1e+2, 1e+3, 1e+4, 1e+5, 1e+6, 6371e+3, 42164e+3,
384400e+3, 1e+9, 1e+10, 5.790918E+010, 1.082089E+011, 1.495979E+011,
2.279366E+011, 7.784120E+011, 1.426725E+012, 2.870972E+012,
4.498253E+012, 1.40621998e+13, 2.99195741e+14, 7.47989354e+15,
4.13425091e+16])
distText = ['10m','100m', '1km','10km', '100km', '1000km', 'LEO',
'GEO', 'Moon', r'10$^6$km',r'10$^7$km', 'Mercury', 'Venus', 'Earth',
'Mars', 'Jupiter', 'Satrun', 'Uranus', 'Neptune', 'Heliopause',
'Oorth', 'Outer Oorth', 'Alpha Centauri']
vMin, vMax = 1e+0, 1e+8
aMin, aMax = 1e-4, 1e+2
As = np.linspace(aMin, aMax, 2)
fig, ax = plt.subplots(figsize=(12, 8))
for dist, text in zip(dists, distText):
# compute the line
v = np.sqrt(2*dist * As)
ax.plot(v, As, 'b-', alpha=0.5)
# sort out where the label should be
txt_y = aMin
txt_x = v[0]
# clip to the edges
if (txt_x < vMin):
txt_x = vMin
txt_y = vMin**2 / (2*dist)
ax.text(txt_x, txt_y, text,
rotation=60, color='b', rotation_mode='anchor',
horizontalalignment='left',
verticalalignment='bottom')
for time, txt in zip(times, timeText):
# compute the line
x = As * time
ax.plot(x, As, 'r-', alpha=0.5)
# sort out where the label should be
txt_x = x[-1]
txt_y = aMax
# clip to the edges
if(txt_x > vMax):
txt_x = vMax
txt_y = vMax / time
ax.text(txt_x, txt_y, txt,
rotation=40, color='r',
horizontalalignment='right', rotation_mode='anchor',
verticalalignment='baseline')
ax.set_ylabel(r"acceleration [m/s$^2$]")
ax.set_xlabel(r"delta-v [m/s]")
ax.set_yscale('log')
ax.set_xscale('log')
ax.grid()
ax.set_ylim(aMin, aMax)
ax.set_xlim(vMin, vMax)

Categories

Resources