Display Matplotlib spines in Tkinter - python-3.x

I know how to display spines in Matplotlib. I know how to display a Matplotlib subplot in Tkinter too. But I would like to know how to put the spines in this subplot in Tkinter.
Here is the code to display a subplot in Tkinter :
import matplotlib
matplotlib.use('TkAgg')
from numpy import arange, sin, pi
from matplotlib.backends.backend_tkagg import FigureCanvasTkAgg, NavigationToolbar2TkAgg
from matplotlib.figure import Figure
import sys
if sys.version_info[0] < 3:
import Tkinter as Tk
else:
import tkinter as Tk
def destroy(e): sys.exit()
root = Tk.Tk()
root.wm_title("Embedding in TK")
f = Figure(figsize=(5,4), dpi=100)
a = f.add_subplot(111)
t = arange(0.0,3.0,0.01)
s = sin(2*pi*t)
a.plot(t,s)
a.set_title('Tk embedding')
a.set_xlabel('X axis label')
a.set_ylabel('Y label')
# a tk.DrawingArea
canvas = FigureCanvasTkAgg(f, master=root)
canvas.show()
canvas.get_tk_widget().pack(side=Tk.TOP, fill=Tk.BOTH, expand=1)
#toolbar = NavigationToolbar2TkAgg( canvas, root )
#toolbar.update()
canvas._tkcanvas.pack(side=Tk.TOP, fill=Tk.BOTH, expand=1)
button = Tk.Button(master=root, text='Quit', command=sys.exit)
button.pack(side=Tk.BOTTOM)
Tk.mainloop()`
Here is the code to display spines in Matplotlib :
import numpy as np
import matplotlib.pyplot as plt
fig, ax = plt.subplots()
image = np.random.uniform(size=(10, 10))
ax.imshow(image, cmap=plt.cm.gray, interpolation='nearest')
ax.set_title('dropped spines')
# Move left and bottom spines outward by 10 points
ax.spines['left'].set_position(('outward', 10))
ax.spines['bottom'].set_position(('outward', 10))
# Hide the right and top spines
ax.spines['right'].set_visible(False)
ax.spines['top'].set_visible(False)
# Only show ticks on the left and bottom spines
ax.yaxis.set_ticks_position('left')
ax.xaxis.set_ticks_position('bottom')
plt.show()

Where you use ax.set_title('...') in your second code block, you use a.set_title('...') in your first block. This pretty much gives away that the methods you can call on ax, you can also call on a.
Simply use the same code as in the second block, but replace ax with a, and it should work fine.
According to the docs, ax and a are not exactly the same objects. Figure.add_subplot() returns an Axes instance, and pyplot.subplots() returns an Axis object as second output parameter. However, since
The Axes contains most of the figure elements: Axis...
you can edit the spines in the same way from both.

Related

How to customize labels of a heatmap created using pyqtgraph?

I'm transitioning from tkinter with matplotlib to PyQt with pyqtgraph and wish to create a simple heatmap, with labels coming from a list of strings. I have no experience with PyQt nor pyqtgraph, and I'm having a hard time tracking down the means by which I can add the labels and adjust the fontsize. Here's what I have so far:
import numpy as np
import sys
from PyQt5.QtWidgets import QApplication, QWidget, QMainWindow, QHBoxLayout
import pyqtgraph as pg
pg.setConfigOption('background', 'lightgray')
from pyqtgraph import PlotWidget
class MainWindow(QMainWindow):
def __init__(self):
super().__init__()
self.setWindowTitle("Heatmap")
self.setStyleSheet("background-color: lightgray;")
self.resize(500,500)
layout = QHBoxLayout() # using since I will later add widgets to this row
graphWidget = pg.ImageView()
graphWidget.setImage(np.random.rand(5,5))
colors = [(0, 0, 0),(4, 5, 61),(84, 42, 55),(15, 87, 60),(208, 17, 141),(255,
255, 255)]
cmap = pg.ColorMap(pos=np.linspace(0.0, 1.0, 6), color=colors)
graphWidget.setColorMap(cmap)
layout.addWidget(graphWidget)
widget = QWidget()
widget.setLayout(layout)
self.setCentralWidget(widget)
app = QApplication(sys.argv)
window = MainWindow()
window.show()
app.exec()
The output image looks like this:
What I'd like to do is the following:
Starting with a list of strings, Labels=['a','b','c','d','e'], place the
labels along the horizontal and vertical axes, starting with 'a','a' in the lower
left and ending with 'e','e' in the top right. The labels should be centered with
respect to their corresponding squares.
Be able to adjust the font family and size of my labels.
Completely remove everything to the right of the heatmap itself and use a simple
static colorbar as I did using matplotlib.
I will later be adding a slider for animation, along with other features, later.

How do i add a text on matplotlib FuncAnimation

i am having difficulty adding a text to a static position using axes coordinate and not the data coordinate while running matplotlib funcanimation . Would you mind helping me out a bit?
import matplotlib.pyplot as plt
import random
from itertools import count
from matplotlib.animation import FuncAnimation
from datetime import datetime
import mplfinance as mpf
%matplotlib notebook
#creats subplots
fig = plt.figure(figsize=(8,4)) [enter image description here][1]
fig.patch.set_facecolor('#121416')
ax1=plt.subplot2grid((9,18), (0,0),colspan=12, rowspan=7 )
ax2=plt.subplot2grid((9,18), (0,12),colspan=6, rowspan=3)
ax3=plt.subplot2grid((9,18), (3,12),colspan=6, rowspan=3)
ax4=plt.subplot2grid((9,18), (6,12),colspan=6, rowspan=3)
ax5=plt.subplot2grid((9,18), (7,0),colspan=12, rowspan=3)
#values
x_vals=[]
y1_vals=[]
y2_vals=[]
y3_vals=[]
y4_vals=[]
y5_vals=[]
index = count()
def animate(i):
#generate and append data
x_vals.append(next(index))
y1_vals.append(random.randint(0,4))
y2_vals.append(random.randint(0,3))
y3_vals.append(random.randint(0,2))
y4_vals.append(random.randint(0,3))
y5_vals.append(random.randint(0,4))
#plot graph
ax1.plot(x_vals, y1_vals, color='green')
ax2.plot(x_vals, y2_vals, color='white')
ax3.plot(x_vals, y3_vals, color='green')
ax4.plot(x_vals, y4_vals, color='white')
ax5.plot(x_vals, y5_vals, color='green')
#add text and title
**ax1.text(y=3, x=0.05, s='position 1', transform=ax.transAxes, color='white' )**
ax1.set_title(label='graph 1', loc='center', fontsize=15, color='white' )
#funcanimation
anim = FuncAnimation(fig, animate, interval=1000)
plt.show()
desired image
I see a few things that may be wrong:
ax1.text(y=3, x=0.05, s='position 1', transform=ax.transAxes, color='white' )
ax.transAxes should be ax1.transAxes
ax1 has a white background, so you won't see color='white' text. Try black: color='k'
Axes coordinates go from 0.0 to 1.0 (fraction of the Axes object).
Therefore y=3 will not work. Try y=0.9
When I made the above changes, it worked for me.

Updating a plot in Python

My situation is this: I am developing a Jupyter-lab notebook to exemplify engineering topics. I find myself in the need of plotting something in an axes object within a figure, and then using a slider interact changing a value to update the plot.
Here is a MWE (or at least a shorter Working Example):
import ipywidgets as widgets
from ipywidgets import interact
import numpy as np
import matplotlib.pyplot as plt
global ax1
global fig
fig, (ax1) = plt.subplots(ncols=1, subplot_kw=dict(projection='polar'))
RAD = np.array([0.85, 0.85, 0.85])
ANG = np.array([np.pi/2, np.pi*(2/3+1/2), np.pi*(1/2-2/3)])
c = ax1.scatter(ANG, RAD)
ax1.set_ylim([0, 1])
ax1.set_yticklabels([])
def h(rh):
RADp = np.array([rh, rh, rh])
ANGp = np.array([-np.pi/2, np.pi*(2/3-1/2), np.pi*(-1/2-2/3)])
cp = ax1.scatter(ANGp, RADp)
ax1.add_artist(cp)
plt.show()
return (rh)
interact(h, rh = widgets.FloatSlider(min=0, max=1, step=0.001, value=1));
In this example I create the figure fig and its axes ax1 declared as global variables (so that they will be available within function h. Then using RAD and ANG I create a scatter plot c.
Afterwards using the interact widget I would like to have three crosses change position along the r axis by changing the value of rh with the slider.
I don't get any error, but neither get I any crosses at all.
In the actual code I use pcolormesh instead of scatter.
I hope I made myself clear. I had got ti working by creating the figure and ax1 each time the function is called, but then I added some more suff thath don't need to be plotted each time.
Thanks for taking the time to read!
A very limited answer is that you function should return fig not rh.
Also note that you don't need the lines with global, and plt.show()
import ipywidgets as widgets
from ipywidgets import interact
import numpy as np
import matplotlib.pyplot as plt
fig, (ax1) = plt.subplots(ncols=1, subplot_kw=dict(projection='polar'))
RAD = np.array([0.85, 0.85, 0.85])
ANG = np.array([np.pi/2, np.pi*(2/3+1/2), np.pi*(1/2-2/3)])
c = ax1.scatter(ANG, RAD)
ax1.set_ylim([0, 1])
ax1.set_yticklabels([])
def h(rh):
RADp = np.array([rh, rh, rh])
ANGp = np.array([-np.pi/2, np.pi*(2/3-1/2), np.pi*(-1/2-2/3)])
cp = ax1.scatter(ANGp, RADp)
ax1.add_artist(cp)
# plt.show()
return fig
interact(h, rh = widgets.FloatSlider(min=0, max=1, step=0.001, value=1));
I say limited because I think you want to update rather than add point?
A version which is hopefully more in line with what you want
the key point being the use of set_offsets method to update the positions.
import ipywidgets as widgets
from ipywidgets import interact
import numpy as np
import matplotlib.pyplot as plt
fig, (ax1) = plt.subplots(ncols=1, subplot_kw=dict(projection='polar'))
RAD = np.array([0.85, 0.85, 0.85])
ANG = np.array([np.pi/2, np.pi*(2/3+1/2), np.pi*(1/2-2/3)])
c = ax1.scatter(ANG, RAD)
ax1.set_ylim([0, 1])
ax1.set_yticklabels([])
def h(rh):
new = [
[-np.pi/2, rh],
[np.pi*(2/3-1/2), rh],
[np.pi*(-1/2-2/3), rh],
]
c.set_offsets(new)
return fig
interact(h, rh = widgets.FloatSlider(min=0, max=1, step=0.001, value=1));

Why is the grid turned on only on the last subplot?

I am using subplots in a function which is using a slider widget inputs to calculate some stuff and plotting results.
I want to turn on the grid for all subplots of ax1. But somehow jupternotebooks only turns it on only on the last plot...
import numpy as np
from matplotlib import pyplot as plt
import ipywidgets as widgets
from IPython.html.widgets import interact
%matplotlib inline
## Plot
fig, ax1 = plt.subplots(6,2)
plt.subplots_adjust(right = 2, top = 8 )
# Show the major grid lines with dark grey lines
plt.grid(b=True, which='major', color='#666666', linestyle='-')
# Show the minor grid lines with very faint and almost transparent grey lines
plt.minorticks_on()
plt.grid(b=True, which='minor', color='#999999', linestyle='-', alpha=0.2)
## Giergeschwindigkeit über v und ay
ax1[0,0].plot(v_ms, omega)
ax1[0,0].set_ylabel('Giergeschwindigkeit [rad/s]')
ax1[0,0].set_xlabel('Geschwindigkeit [m/s]')
ax1[0,0].set_title('Giergeschwindigkeit über Geschwindigkeit')
# ... more subplots
plt.show()
It looks like this:
And can you explain to me why in my case
ax1.grid()
throws an error?
AttributeError: 'numpy.ndarray' object has no attribute 'grid'
This is because plt will only operate on the last-created axes object.
And the reason you're getting that error is that ax1 is a numpy n-dimensional array, not an axes object.
You can do this to iterate over the numpy n-dimensional array to create the grids:
for row in axes:
for ax in row:
ax.grid(b=True, which='major', color='#666666', linestyle='-')
ax.minorticks_on()
ax.grid(b=True, which='minor', color='#999999', linestyle='-',alpha=0.2)
Result (without plt.subplots_adjust()):
You can set grid for every ax object, so in your case you should set like this:
ax1[0,0].grid()
ax1[0,1].grid()

Matplotlib setting `axes` object with `imshow` causes y-axis to become variable

Description
I have began refactoring some code based on the future warning of matplotlib, to re-use the initially defined axes object. However, I noticed that whenever I was re-using my axes object, the image size would be variable. Since, I have managed to isolate the problem to the axes.imshow method as after using imshow, the y-axis of any subsequent drawing on that axes has a y-axis that seems to rescale.
The feeling I have is that the y-axis scale is retained from the initial image that is plotted using imshow (I thought that axes.clear should reset this). Specifically in the below examples, shuffling plots some data spanning ~ 9.90 to 10.10 but because the original image spanned form 0 to 50 the y-axis is barely visible.
Below are first two screenshots of the expected and then 'bugged' behaviour, followed by an MVCE that has two sections that can be toggled to get the expected or 'bugged' behaviour:
Images
Splash without imshow:
Screen after 'Foo -> Shuffle' (Expected behaviour):
Splash with imshow:
Screen after 'Foo -> Shuffle' (unexpected behaviour):
MVCE
from matplotlib.backends.backend_tkagg import (
FigureCanvasTkAgg
)
import tkinter as tk
from matplotlib import image, figure
from numpy import random, linspace
from os import path, getcwd
from pylab import get_cmap
class Foo(object):
#classmethod
def run(cls):
root = tk.Tk()
Foo(root)
root.mainloop()
def __init__(self, master):
# Figure & canvas
self.fig = figure.Figure(figsize=(5,5))
self.axes = self.fig.add_subplot(111)
self.canvas = FigureCanvasTkAgg(self.fig, master=master)
self.canvas.get_tk_widget().pack(fill=tk.BOTH, expand=tk.YES)
# Splash image (This 'bugs')
Z = random.random((50,50))
self.axes.imshow(Z, cmap=get_cmap("Spectral"), interpolation='nearest')
self.canvas.draw()
# Dummy start data (This Works)
#self.axes.plot(random.normal(10,0.05,100))
#self.canvas.draw()
# MENU
menu = tk.Menu(master)
master.config(menu=menu)
test_menu = tk.Menu(menu, tearoff=0)
menu.add_cascade(label="Foo", menu=test_menu)
test_menu.add_command(label="Shuffle",
command=self.shuffle)
test_menu.add_command(label="Add",
command=self.add)
def add(self):
x_data = linspace(0,10, 1000)
y_data = random.normal(x_data)
self.axes.plot(x_data, y_data)
self.canvas.draw()
def shuffle(self):
self.axes.clear()
self.axes.plot(random.normal(10,0.05,100))
self.canvas.draw()
if __name__ == "__main__":
Foo.run()
Question
What is going on here, specifically what is causing the image to appear so differently and what can be done about it?
When no argument is given for aspect, it defaults to None. From the documentation:
If None, default to rc image.aspect value
Therefore if no argument is given to imshow, it will use whatever the rcParam for "image.aspect" is, which you can find by doing:
print (plt.rcParams["image.aspect"]) # default is "equal"
A fix to your problem would be to set it to "auto" in your shuffle function using axes.set_aspect():
def shuffle(self):
self.axes.clear()
self.axes.plot(random.normal(10,0.05,100))
self.axes.set_aspect("auto")
self.canvas.draw()
If you don't mind changing the aspect ratio of imshow, there is also an aspect= argument:
self.axes.imshow(Z, cmap=get_cmap("Spectral"), interpolation='nearest', aspect="auto")

Resources