Matplotlib Navigation Toolbar in wxPython Panel - python-3.x

I am working on a GUI (developed with wxPython) where you can plot graphs on different panels. At the moment I have this:
from matplotlib.backends.backend_wxagg import FigureCanvasWxAgg as FigureCanvas
from matplotlib.backends.backend_wx import NavigationToolbar2Wx
from matplotlib import pyplot as plt
import numpy as np
import wx
class Frame(wx.Frame):
def __init__(self):
wx.Frame.__init__(self,None,-1,'Plot',size=(1000,800))
# Main Panel (It will contain other elements besides the plotting panel)
self.mainPanel = wx.Panel(self,-1,size=(1000,800))
self.mainPanel.SetBackgroundColour('gray')
# Plotting panel
self.plottingPanel = wx.Panel(self,-1,pos=(50,20),size=(500,400))
self.plottingPanel.SetBackgroundColour('white')
# Plot example
figure = plt.figure()
axes = figure.add_subplot(111)
t = np.arange(0.0, 3.0, 0.01); s = np.cos(2 * np.pi * t);axes.plot(t,s)
plt.title('Cosine')
plt.xlabel('x');plt.ylabel('y')
# Canvas
canvas = FigureCanvas(self.plottingPanel,-1,figure)
# Navegation toolbar
navToolbar = NavigationToolbar2Wx(canvas)
navToolbar.DeleteToolByPos(6);navToolbar.DeleteToolByPos(2);navToolbar.DeleteToolByPos(1)
sizer = wx.BoxSizer(wx.VERTICAL)
sizer.Add(canvas)
sizer.Add(navToolbar)
class App(wx.App):
def OnInit(self):
self.Frame = Frame()
self.Frame.Show()
self.SetTopWindow(self.Frame)
return True
def main():
app = App()
app.MainLoop()
if __name__ == '__main__':
main()
When I run the script get this:
I have colored the plotting panel white to highlight it. How can the plot size be adapted to the panel size?
I want to get something like this (this is a montage):
On the other hand, I managed to eliminate from the bar some buttons that are unnecessary for what I need but the bar does not work, that is, when pressing the buttons nothing happens :(
Thanks for your help

It is possible to set parameters when the container for the plot elements (matplotlib.figure.Figure) is created.
e.g. figsize sets the figure dimension in inches and tight_layout adjust the sub plots in tight layout.
figure = plt.figure(figsize = (4, 3), tight_layout=True)
Alternatively you can set the position of the matplotlib.axes.Axes object by .set_position:
figure = plt.figure()
axes = figure.add_subplot(111)
axes.set_position(pos = [0.15,0.3,0.55,0.55], which='both')

Related

I am getting unwanted loading of previous plot axis points to the next plot in PyQt5 matplotlib

I am trying to update the plot after a new file is selected, but the new plot that is generated it has the points that are of previous plot on both X and Y axis, I don't want to those previous points, please anyone explain why this happens so and how to get rid of this. Images are shown here, previous plot is
after this I choose to select second file with different data to plot it, next plot is this image
The code I am trying to build is
import matplotlib.pyplot as plt
from matplotlib.backends.backend_qt5agg import FigureCanvasQTAgg as FigureCanvas
from PyQt5.QtWidgets import (QApplication, QWidget, QFileDialog, QPushButton, QLabel, QGridLayout, QVBoxLayout, QLineEdit)
from Bio import SeqIO
from collections import Counter
from Bio.SeqUtils import molecular_weight
from Bio.SeqUtils import GC
class MainWindow(QWidget):
def __init__(self):
super().__init__()
self.setWindowTitle("DNA Sequence Analysis - Prashik Lokhande")
self.setLayout(QVBoxLayout())
my_label = QLabel("DNA Sequence Analysis from the FASTA Database, (FASTA databse can be found on NCBI website). Build by Prashik Lokhande")
self.layout().addWidget(my_label)
self.visualize()
self.show()
def visualize(self):
container = QWidget()
container.setLayout(QGridLayout())
label_1 = QLabel("PLease Select FASTA file")
button_1 = QPushButton("Select file", clicked = lambda: self.get_plot())
gc_count_label = QLabel("GC Count = ")
self.gc_count_field = QLabel("0")
self.canvas = FigureCanvas(plt.Figure(figsize=(10, 4)))
container.layout().addWidget(label_1, 0,0)
container.layout().addWidget(button_1, 1,0)
container.layout().addWidget(gc_count_label, 2, 1)
container.layout().addWidget(self.gc_count_field, 3, 1)
container.layout().addWidget(self.canvas, 2, 0, 3, 1)
self.layout().addWidget(container)
def get_plot(self):
filepath, _ = QFileDialog.getOpenFileName(self, 'select FASTA file')
record = SeqIO.read(filepath,"fasta")
dna = record.seq
mrna = dna.transcribe()
protein = mrna.translate()
self.mol_weight = molecular_weight(dna)
gc = GC(dna)
self.gc_count_field.setText(str(gc))
pr_freq = Counter(protein)
self.ax = self.canvas.figure.subplots()
self.ax.bar(pr_freq.keys(), pr_freq.values())
self.ax.set_title("Amino Acid Contents in the sequence (X-axis Amino acids, Y-axis frequency)")
app = QApplication([])
mw = MainWindow()
app.exec_()
Every time you press the button, self.ax = self.canvas.figure.subplots() will create a new set of axes and add it at the (0,0) position in the grid of previously created subplots. Since all subplots are placed at the same position in the grid they all overlap. To get around this, you could just create one set of axes in MainWindow.__init__, and reuse this one in MainWidon.get_plot, i.e.
class MainWindow(QWidget):
def __init__(self):
....
self.ax = self.canvas.figure.subplots()
def get_plot(self):
....
# clear previous plot
self.ax.clear()
self.ax.bar(pr_freq.keys(), pr_freq.values())
....

No example available for `vispy.visuals.GraphVisual`. My code just shows blank screen

I tried googling and piecing together an example from many sources. Here is what I've got:
import numpy as np
from vispy import app
from vispy import visuals
from vispy.visuals.transforms import STTransform
import networkx as nx
class Canvas(app.Canvas):
def __init__(self, **kwargs):
super().__init__(title="Simple NetworkX Graph", keys="interactive", size=(600, 600))
graph = nx.path_graph(8)
#graph = nx.adjacency_matrix(
#nx.fast_gnp_random_graph(500, 0.005, directed=True))
layout = nx.layout.circular_layout
self.matrix = nx.adjacency_matrix(graph)
self.visual = visuals.GraphVisual(
nx.adjacency_matrix(graph),
layout=layout,
line_color='white', arrow_type="angle_30",
arrow_size=30, node_symbol="disc", node_size=20,
face_color=(1, 0, 0, 0.5), border_width=0.0, animate=True,
directed=True)
self.visual.transform = STTransform((1, 1), (20, 20))
self.show()
#def on_resize(self, event):
#set_viewport(0, 0, *event.physical_size)
def on_draw(self, event):
clear(color=True, depth=True)
if __name__ == '__main__':
c = Canvas(title="Graph")
app.run()
The output is a blank (black) screen, with nothing else displayed.
My goal is to display a simple network graph using VisPy. Have you gotten this to work and could you post your code / relevant code?
It looks like your on_draw method is missing the most important part which is to call the draw method of the visual. See the graph example from the vispy repository:
https://github.com/vispy/vispy/blob/master/examples/basics/visuals/graph.py
Here are the methods from that example:
#property
def visual_size(self):
return self.physical_size[0] - 40, self.physical_size[1] - 40
def on_resize(self, event):
self.visual.transform.scale = self.visual_size
vp = (0, 0, self.physical_size[0], self.physical_size[1])
self.context.set_viewport(*vp)
self.visual.transforms.configure(canvas=self, viewport=vp)
def on_draw(self, event):
self.context.clear('white')
self.visual.draw()
if not self.visual.animate_layout():
self.update()
One option is to not use VisPy, but instead use matplotlib:
import matplotlib.pyplot as plt
import networkx as nx
G = nx.path_graph(8)
nx.draw(G)
plt.show()
The above displays the graph in a window very quickly.
Another alternative lib is grave:
import networkx as nx
import matplotlib.pyplot as plt
from grave import plot_network
# Generate a networkx graph
graph = nx.powerlaw_cluster_graph(50, 1, .2)
# Plot it
plot_network(graph)
which seems to allow some forms of graph interaction (with the mouse).

Embedded Plot in Tkinter: disconnect y-axis & limit ticks and label to subplot

I have an application set up with an interactive plot window embedded. I would like to put a couple of subplots on top of eachother, with a shared x-axis and an independent y-axis. The whole thing is meant for data analysis.
I got the subplots set up, but for some reason the y-axis of the first subplot is somehow connected to the other subplots, but not the other way around.
Additionally, the ticks and labels overlap instead of staying at their respective subplots.
I tried using the pyplot.subplots function, which takes care of the problem of the independent axes and ticks/labels, but this opens up another window when I call it instead of embedding it.
working example code that showcases the problem (Python 3.7):
# -*- coding: utf-8 -*-
import tkinter as tk
from matplotlib.backends.backend_tkagg import FigureCanvasTkAgg, NavigationToolbar2Tk
from matplotlib.figure import Figure
from tkinter import ttk
import numpy as np
import pandas as pd
class window(tk.Tk):
def __init__(self,*args,**kwargs):
tk.Tk.__init__(self,*args,**kwargs)
self.generate_data()
self.plotPane = self.plot_pane(self)
def generate_data(self):
self.data = pd.DataFrame()
x = [1,2,3,4,5]
y = np.zeros((len(x)))
for i,yy in enumerate(y):
y[i] = yy + 10.*float(np.random.random())
self.data['x'] = x
self.data['y'] = y
class plot_pane():
def __init__(self,parent):
self.parent = parent
self.labelframe = ttk.Labelframe(self.parent,text="Plot")
replotButton = ttk.Button(self.parent,text="replot",command=self.update_plot)
self.labelframe.grid(row=0, column=0)
replotButton.grid(row=1,column=0)
self.figure = Figure(figsize=(6,3),dpi=100)
self.figure.subplots_adjust(left=0.11,bottom=0.09,right=0.77,top=0.92)# BaMa:. subplot margins, so labels are visible
self.sub = self.figure.add_subplot(111)
# toolbar and canvas for live plot
canvas = FigureCanvasTkAgg(self.figure,self.labelframe)
canvas.get_tk_widget().grid(row=1,column=1,rowspan=2,sticky="NSEW",pady=20)
canvas._tkcanvas.grid(row=1,column=1,rowspan=2, sticky="NSEW")
toolbar_frame = tk.Frame(self.labelframe)
toolbar_frame.grid(row=0,column=1,sticky="SEW")
toolbar = NavigationToolbar2Tk(canvas,toolbar_frame)
toolbar.update()
self.update_plot()
def update_plot(self,*args):
# clear figure
self.figure.clf()
stackAx = []
for i in range(0,2):
# generate new random data
self.parent.generate_data()
testX = self.parent.data['x']
testY = self.parent.data['y']
# add subplots
if i == 0:
stackAx.append(self.figure.add_subplot(i+1,1,1))# y-axis of this subplot is somehow connected to the other
else:
stackAx.append(self.figure.add_subplot(i+1,1,1,sharex=stackAx[0]))
# plot, add labels, ticks and grid
stackAx[i].plot(testX,testY)
stackAx[i].set_ylabel("ax-"+str(i))
stackAx[i].tick_params('y')
stackAx[i].grid()
self.figure.canvas.draw()
window = window()
window.mainloop()
So, when you move the subplot at the top, the bottom y-axis moves as well and the "ax-0" label + tickmarks breach the upper subplot. When you move the lower plot, the y-axis of the upper plot does not move (as it should be)
I figured it out. Apparently add_subplot and pyplot.subplots function somewhat differently and I didn't understand it correctly.
The following update function works:
def update_plot(self,*args):
# clear figure
self.figure.clf()
stackAx = []
numberOfPlots = 2
for i in range(0,numberOfPlots):
# generate new random data
self.parent.generate_data()
testX = self.parent.data['x']
testY = self.parent.data['y']
# add subplots
if i == 0:
stackAx.append(self.figure.add_subplot(numberOfPlots,1,i+1))
else:
stackAx.append(self.figure.add_subplot(numberOfPlots,1,i+1,sharex=stackAx[0]))
# plot, add labels, ticks and grid
stackAx[i].plot(testX,testY)
stackAx[i].set_ylabel("ax-"+str(i))
stackAx[i].tick_params('y')
stackAx[i].grid()
self.figure.canvas.draw()
I got it from here: https://pythonprogramming.net/subplot2grid-add_subplot-matplotlib-tutorial/

Control imbedded figure size on Tkinter canvas?

I dont seem to be able to fully control the figure size on my embedded figure on a Tkinter canvas.
Heres what i want to do. Maybe you have another suggestion, than using the embedded figure.
Im trying to make a simple script to make some visual content. Right now its just a pixel mapping of falling squares in random colors.
My problem is that i need it to be fullscreen, and i can for my life not figure out how.
It is mainly about this piece of code, i think:
fig = plt.figure(figsize=(40,40))
im = plt.imshow(top.img) # later use a.set_data(new_data)
plt.tick_params(
axis='both', # changes apply to the x-axis
which='both', # both major and minor ticks are affected
bottom='off', # ticks along the bottom edge are off
top='off', # ticks along the top edge are off
left='off',
right='off',
labelleft='off',
labelbottom='off') # labels along the bottom edge are off
# a tk.DrawingArea
canvas = FigureCanvasTkAgg(fig, master=top)
canvas.show()
canvas.get_tk_widget().pack(side=gui.TOP , fill=gui.BOTH, expand=1)
It seems that figsize has a limit to how big it goes.
Heres all the code:
import matplotlib
import numpy as np
matplotlib.use('TkAgg')
from matplotlib.backends.backend_tkagg import FigureCanvasTkAgg
from matplotlib import figure
import matplotlib.pyplot as plt
import tkinter as gui
from math import floor
import time
class FullScreenApp(object):
def __init__(self, master, **kwargs):
self.master=master
pad=3
self._geom='200x200+0+0'
master.geometry("{0}x{1}+0+0".format(
master.winfo_screenwidth()-pad, master.winfo_screenheight()-pad))
master.bind('<Escape>',self.toggle_geom)
def toggle_geom(self,event):
geom=self.master.winfo_geometry()
print(geom,self._geom)
self.master.geometry(self._geom)
self._geom=geom
def flashBox(color,oy,ox):
global j1, j2
top.img[0+oy:j2+oy,0+ox:j1+ox] = color
im.set_data(top.img)
canvas.draw();
time.sleep(t)
top.img[0+oy:j2+oy,0+ox:j1+ox] = [0,0,0]
im.set_data(top.img)
canvas.draw();
return top.img
def drawBox(color,oy,ox):
global j1, j2
top.img[0+oy:j2+oy,0+ox:j1+ox] = color
im.set_data(top.img)
canvas.draw();
time.sleep(t)
return top.img
def resetBox(oy,ox):
global j1, j2
top.img[0+oy:j2+oy,0+ox:j1+ox] = [0,0,0]
im.set_data(top.img)
canvas.draw();
return top.img
def drawColumn(color,u):
global gridsize, j1, j2
for l in range(gridsize):
im.set_data(flashBox(color,j2*l,j1*u))
time.sleep(t2)
top = gui.Tk()
t = 0.1
t2 = 0.00001
x = 40
y = 40
gridsize = 10
j1 = floor(x // gridsize)
j2 = floor(y // gridsize)
top.img = np.zeros([y,x,3],dtype=np.uint8)
top.img.fill(0) # or img[:] = 255
fig = plt.figure(figsize=(40,40))
im = plt.imshow(top.img) # later use a.set_data(new_data)
plt.tick_params(
axis='both', # changes apply to the x-axis
which='both', # both major and minor ticks are affected
bottom='off', # ticks along the bottom edge are off
top='off', # ticks along the top edge are off
left='off',
right='off',
labelleft='off',
labelbottom='off') # labels along the bottom edge are off
# a tk.DrawingArea
canvas = FigureCanvasTkAgg(fig, master=top)
canvas.show()
canvas.get_tk_widget().pack(side=gui.TOP , fill=gui.BOTH, expand=1)
#app=FullScreenApp(top)
while True:
for n in range(gridsize):
top.update()
p = np.random.randint(0,99)
#drawColumn([np.random.random_integers(0,255),np.random.random_integers(0,255),np.random.random_integers(0,255)],np.random.random_integers(0,gridsize-1))
if p > 10:
flashBox([np.random.random_integers(0,255),np.random.random_integers(0,255),np.random.random_integers(0,255)],j1*np.random.random_integers(0,gridsize-1),j2*np.random.random_integers(0,gridsize-1))
else:
flashBox([0,0,0],0,0)

Plot mouse clicks over an image

I writing a code in Python 3 to plot some markers over a DICOM image. for this, I wrote a very short program:
In the main program, I read the DICOM filename from the terminal and plot the image.
main_prog.py:
import sys
import dicom as dcm
import numpy as np
from matplotlib import pyplot as plt
from dicomplot import dicomplot as dcmplot
filename = sys.argv[1]
dicomfile = dcm.read_file(filename)
dicomimg = dicomfile.pixel_array
fig = plt.figure(dpi = 300)
ax = fig.add_subplot(1, 1, 1)
plt.set_cmap(plt.gray())
plt.pcolormesh(np.flipud(dicomimg))
dcm = dcmplot(ax)
plt.show()
Then, I define a class to store the coordinates clicked by the user and plot each of them at a time over the image:
dicomplot.py
from matplotlib import pyplot as plt
class dicomplot():
def __init__(self, img):
self.img = img
self.fig = plt.figure(dpi = 300)
self.xcoord = list()
self.ycoord = list()
self.cid = img.figure.canvas.mpl_connect('button_press_event', self)
def __call__(self, event):
if event.button == 1:
self.xcoord.append(event.x)
self.ycoord.append(event.y)
self.img.plot(self.ycoord, self.xcoord, 'r*')
self.img.figure.canvas.draw()
elif event.button == 2:
self.img.figure.canvas.mpl_disconnect(self.cid)
elif event.button == 3:
self.xcoord.append(-1)
self.ycoord.append(-1)
The problem is that when I click over the image, the markers appear in a different scale, and not over the image as they are supposed to.
How can I modify my code so when I click on the image, all the mouse clicks are stored and ploted in the desired position?
The MouseEvent objects carry both a x/y andxdata/ydata attributes (docs). The first set is in screen coordinates (ex pixels from the lower left) and the second set (*data) are in the data coordinates.
You might also be interested in mpldatacursor.

Resources