I have a small program that plots a figure with 4 subplots. I make a little GUI and call that plotting function from a thread, so each time I click the button, it should recollect data and redraw that figure (with 4 subplot), without closing the script. The script can plot for the first time, however at the second time (click button without closing the script), it stops at initialize the subplots. I have tried plt.close('all'), plt.close(fig), plt.clf(),... but it doesnt help. I really run out of idea why it stops at the second time.
Here is my full little script. Much appreciate for any inputs
import sys, os
import time
import wx
import traceback
from textwrap import wrap
import shutil
import itertools
import numpy as np
import matplotlib
import matplotlib.pyplot as plt
import threading
def test():
Do = Data()
Do.PlotGraph()
class Data(object):
def __init__(self):
self.SavePath = "C:\\Plots\\"
def f(self, t):
return np.exp(-t) * np.cos(2*np.pi*t)
def PlotGraph(self):
#Plotting
print "***** Generating plot"
t1 = np.arange(0.0, 5.0, 0.1)
t2 = np.arange(0.0, 5.0, 0.02)
print "Initialize subplots"
fig, ((ax1, ax2), (ax3, ax4)) = plt.subplots(nrows = 2, ncols = 2, frameon=False, figsize=(20, 10))
fontSize = 13
print "Creating ax1"
ax1title = "ax1"
ax1.set_title(ax1title, fontsize = fontSize)
ax1.plot(t1, self.f(t1), 'bo', t2, self.f(t2), 'k')
print "Creating ax2"
ax2title = "ax2"
ax2.set_title(ax2title, fontsize = fontSize)
ax2.plot(t1, self.f(t1), 'bo', t2, self.f(t2), 'k')
print "Creating ax3"
ax3title = "ax3"
ax3.set_title(ax3title, fontsize = fontSize)
ax3.plot(t1, self.f(t1), 'bo', t2, self.f(t2), 'k')
print "Creating ax4"
ax4title = "ax4"
ax4.set_title(ax4title, fontsize = fontSize)
ax4.plot(t1, self.f(t1), 'bo', t2, self.f(t2), 'k')
fig.subplots_adjust(hspace = 0.35) #make room for axes title and x-axis label
fig.subplots_adjust(bottom = 0.07) #make room for axes title and x-axis label
fig.subplots_adjust(wspace = 0.30)
fig.subplots_adjust(top = .86)
filename= "Test"
if not os.path.exists(self.SavePath):
os.makedirs(self.SavePath)
savefilename = self.unique_file(self.SavePath, filename, "png")
print "***** Saving plot to: " + self.SavePath + savefilename
fig.savefig(self.SavePath + savefilename, dpi = 200)
plt.close(fig)
def unique_file(self, path, basename, ext):
actualname = "%s.%s" % (basename, ext)
c = itertools.count()
while os.path.exists(path + actualname):
actualname = "%s_[%d].%s" % (basename, next(c), ext)
#print "actualname: " + actualname
return actualname
################## THREAD UPDATE GUI ######################
#1. Create new custom event to update the display
DisplayEventType = wx.NewEventType();
EVT_DISPLAY = wx.PyEventBinder(DisplayEventType, 1);
def GetDataThreadStart(window):
GetDataThread(window)
class GetDataThread(threading.Thread):
def __init__(self, output_window):
threading.Thread.__init__(self)
self.output_window = output_window
print "Thread started"
self.start()
def run(self):
test()
print "Test Done\n\n"
self.UpdateFunction("Enable Go Button")
def UpdateFunction(self, msg):
evt = UpdateDisplayEvent(DisplayEventType, -1) #initialize update display event
evt.UpdateText(str(msg)); #update display event
wx.PostEvent(self.output_window, evt)
#Define event
class UpdateDisplayEvent(wx.PyCommandEvent):
def __init__(self, evtType, id):
wx.PyCommandEvent.__init__(self, evtType, id)
self.msg = ""
def UpdateText(self,text):
self.msg = text
def GetText(self):
return self.msg
######## Define GUI ###########
class MyPanel(wx.Panel):
def __init__(self, parent):
wx.Panel.__init__(self, parent, -1)
class MyFrame(wx.Frame):
def __init__(self, parent):
self.title = 'Testing'
wx.Frame.__init__(self, parent, -1, self.title, size = (300,350))
pnl = MyPanel(self)
self.Go_Button = wx.Button(pnl, -1, "Goooooo!")
self.Bind(wx.EVT_BUTTON, self.ClickGo, self.Go_Button)
BoxSizerMain = wx.BoxSizer(wx.VERTICAL)
BoxSizerMain.Add(self.Go_Button, 0, wx.ALIGN_CENTER_HORIZONTAL)
self.Bind(EVT_DISPLAY, self.OnThreadUpdate)
def ClickGo(self, event):
self.Go_Button.Disable()
GetDataThreadStart(self)
def OnThreadUpdate(self, event):
msg = event.GetText()
if msg == "Enable Go Button":
self.Go_Button.Enable()
def invokeGUI():
app = wx.PySimpleApp()
frame = MyFrame(None)
frame.Show()
frame.Iconize(True)
frame.Iconize(False)
frame.CenterOnScreen()
app.MainLoop()
if __name__ == '__main__':
invokeGUI()
The result
I believe you need to change the matplotlib backend to wx via the matplotlib.use() function in order to embed matplotlib plots within wx graphics.
Here's what I changed in your script:
import matplotlib
matplotlib.use('WX')
import matplotlib.pyplot as plt
This produced the following image after testing:
Console output:
Initialize subplots
Creating ax1
Creating ax2
Creating ax3
Creating ax4
***** Saving plot to: /Users/luccary/Downloads/foo/Test_[0].png
/Users/luccary/.virtualenvs/numpy/lib/python2.7/site-packages/matplotlib/cbook.py:136: MatplotlibDeprecationWarning: The WX backend is deprecated. It's untested and will be removed in Matplotlib 2.2. Use the WXAgg backend instead. See Matplotlib usage FAQ for more info on backends.
warnings.warn(message, mplDeprecation, stacklevel=1)
Test Done
Please note the deprecation warning, since wx will be removed as a backend in Matplotlib 2.2, and you should likely use wxagg instead.
References: See this matplotlib tutorial which I found after reading a Stack Overflow answer to a similar question here.
Hope that answers your question. Good luck!
Related
I am new to PyQt and Im developing a utility where a user can import data from an excel file and plot its X and Y in a 2d scatter plot using below code:
def plot_2d_scatter(graphWidget,x,z,color=(66, 245, 72)):
graphWidget.clear()
brush = pg.mkBrush(color)
scatter = pg.ScatterPlotItem(size=5, brush=brush)
scatter.addPoints(x,z)
graphWidget.addItem(scatter)
Now I want a functionality which will allow the user to move his mouse over the scatter plot points using a cross hair / pointer / etc and select points on the scatter plot.
Whenever the user does a left click on the crosshair / marker on the scatter plot, I want its x,y coordinates to be saved for further use.
I have already tried the below snippet from somewhere on internet for using mouse events and getting my scatter points , but this didnt give me a cross hair that falls on my scatter points
def mouseMoved(self, evt):
pos = evt
if self.plotWidget.sceneBoundingRect().contains(pos):
mousePoint = self.plotWidget.plotItem.vb.mapSceneToView(pos)
mx = np.array([abs(float(i) - float(mousePoint.x())) for i in self.plotx])
index = mx.argmin()
if index >= 0 and index < len(self.plotx):
self.cursorlabel.setHtml(
"<span style='font-size: 12pt'>x={:0.1f}, \
<span style='color: red'>y={:0.1f}</span>".format(
self.plotx[index], self.ploty[index])
)
self.vLine.setPos(self.plotx[index])
self.hLine.setPos(self.ploty[index])
Any guidance is thankfully appreciated
my best fast effort, never used pg untill today:
import sys
from PyQt5.QtWidgets import QApplication, QMainWindow, QDesktopWidget, QWidget
from PyQt5.QtCore import Qt
from PyQt5 import QtGui, QtCore, QtWidgets
import pyqtgraph as pg
import numpy as np
class MyApp(QMainWindow):
def __init__(self, parent=None):
super(MyApp, self).__init__(parent)
self.resize(781, 523)
self.graphWidget = pg.PlotWidget()
self.setCentralWidget(self.graphWidget)
self.show()
self.x = [1,2,3,4,5,6,7,8,9,5,6,7,8]
self.y = [1,2,3,4,5,6,7,8,9,5,6,7,8]
# self.y.reverse()
self.plot_2d_scatter(self.graphWidget, self.x, self.y)
self.cursor = Qt.CrossCursor
# self.cursor = Qt.BlankCursor
self.graphWidget.setCursor(self.cursor)
# Add crosshair lines.
self.crosshair_v = pg.InfiniteLine(angle=90, movable=False)
self.crosshair_h = pg.InfiniteLine(angle=0, movable=False)
self.graphWidget.addItem(self.crosshair_v, ignoreBounds=True)
self.graphWidget.addItem(self.crosshair_h, ignoreBounds=True)
self.cursorlabel = pg.TextItem()
self.graphWidget.addItem(self.cursorlabel)
self.proxy = pg.SignalProxy(self.graphWidget.scene().sigMouseMoved, rateLimit=60, slot=self.update_crosshair)
self.mouse_x = None
self.mouse_y = None
def plot_2d_scatter(self,graphWidget,x,z,color=(66, 245, 72)):
# graphWidget.clear()
brush = pg.mkBrush(color)
scatter = pg.ScatterPlotItem(size=5, brush=brush)
scatter.addPoints(x,z)
graphWidget.addItem(scatter)
def update_crosshair(self, e):
pos = e[0]
if self.graphWidget.sceneBoundingRect().contains(pos):
mousePoint = self.graphWidget.plotItem.vb.mapSceneToView(pos)
mx = np.array([abs(float(i) - float(mousePoint.x())) for i in self.x])
index = mx.argmin()
if index >= 0 and index < len(self.x):
self.cursorlabel.setText(
str((self.x[index], self.y[index])))
self.crosshair_v.setPos(self.x[index])
self.crosshair_h.setPos(self.y[index])
self.mouse_x = self.crosshair_v.setPos(self.x[index])
self.mouse_y = self.crosshair_h.setPos(self.y[index])
self.mouse_x = (self.x[index])
self.mouse_y = (self.y[index])
def mousePressEvent(self, e):
if e.buttons() & QtCore.Qt.LeftButton:
print('pressed')
# if self.mouse_x in self.x and self.mouse_y in self.y:
print(self.mouse_x, self.mouse_y)
if __name__ == '__main__':
app = QApplication(sys.argv)
myapp = MyApp()
# myapp.show()
try:
sys.exit(app.exec_())
except SystemExit:
print('Closing Window...')
it just prints out the coordinate of pressed point in graph
copied from https://www.pythonguis.com/faq/pyqt-show-custom-cursor-pyqtgraph/ and your piece of code result looks like:
there are other examples on SO like Trying to get cursor with coordinate display within a pyqtgraph plotwidget in PyQt5 and others
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())
....
I am trying to add new lines to an existing and open Plot.
I wrote a watchdog watching a folder with measurement data. Every few secs there will be a new data file. The Application I try to generate should read the file when triggered by the watchdog and add the data to the plot.
Dynamic plots using QTimer and stuff is easy when updating existing data, but I don't get a hook for new lines.
Also, do I have to use multithreading when I want to run a script while having a plot on _exec()?
from pyqtgraph.Qt import QtGui, QtCore
import pyqtgraph as pq
import sys
import numpy as np
import time
class Plot2d(object):
def __init__(self):
self.traces = dict()
self.num = 0
pq.setConfigOptions(antialias=True)
self.app = QtGui.QApplication(sys.argv)
self.win = pq.GraphicsWindow(title='examples')
self.win.resize(1000, 600)
self.win.setWindowTitle('Windowtitle')
self.canvas = self.win.addPlot(title='Plot')
def starter(self):
if (sys.flags.interactive != 1) or not hasattr(QtCore, 'PYQT_VERSION'):
QtGui.QApplication.instance().exec_()
def trace(self, name, dataset_x, dataset_y):
if name in self.traces:
self.traces[name].setData(dataset_x, dataset_y)
else:
self.traces[name] = self.canvas.plot(pen='y')
def update(self, i):
x_data = np.arange(0, 3.0, 0.01)
y_data = x_data*i
self.trace(str(i), x_data, y_data)
self.trace(str(i), x_data, y_data)
if __name__ == '__main__':
p = Plot2d()
p.update(1)
p.starter()
time.sleep(1)
p.update(2)
This is what I tried. The update function should be called by the watchdog when new data is available in the dir.
import time as time
from watchdog.observers import Observer
from watchdog.events import PatternMatchingEventHandler
if __name__ == "__main__":
patterns = "*"
ignore_patterns = ""
ignore_directories = False
case_sensitive = True
go_recursively = False
my_event_handler = PatternMatchingEventHandler(patterns, ignore_patterns, ignore_directories, case_sensitive)
def on_created(event):
print(f" {event.src_path} has been created!") #this function should call the plot update
my_event_handler.on_created = on_created
path = (r'C:\Users\...') #path from GUI at some point
my_observer = Observer()
my_observer.schedule(my_event_handler, path, recursive=go_recursively)
my_observer.start()
try:
while True:
time.sleep(1)
except KeyboardInterrupt:
my_observer.stop()
my_observer.join()
The exec_() method is blocking, so starter() will be too, and will be unlocked when the window is closed, which implies that all the code after starter will only be executed after closing the window. On the other hand you should not use time.sleep in the GUI thread as it blocks the execution of the GUI event loop.
Depending on the technology used, ways of updating elements are offered, in the case of Qt the appropriate way is to use signals so that all the logic of the watchdog will be encapsulated in a QObject that will transmit the information to the GUI through a signal, Then the GUI takes that information to plot.
import sys
from watchdog.observers import Observer
from watchdog.events import PatternMatchingEventHandler
from pyqtgraph.Qt import QtGui, QtCore
import pyqtgraph as pq
import numpy as np
class QObserver(QtCore.QObject):
dataChanged = QtCore.pyqtSignal(object)
def __init__(self, parent=None):
super(QObserver, self).__init__(parent)
patterns = "*"
ignore_patterns = ""
ignore_directories = False
case_sensitive = True
go_recursively = False
path = r"C:\Users\..." # path from GUI at some point
event_handler = PatternMatchingEventHandler(
patterns, ignore_patterns, ignore_directories, case_sensitive
)
event_handler.on_created = self.on_created
self.observer = Observer()
self.observer.schedule(event_handler, path, recursive=go_recursively)
self.observer.start()
def on_created(self, event):
print(
f" {event.src_path} has been created!"
) # this function should call the plot update
self.dataChanged.emit(np.random.randint(1, 5))
class Plot2d(QtCore.QObject):
def __init__(self, parent=None):
super().__init__(parent)
self.traces = dict()
self.num = 0
pq.setConfigOptions(antialias=True)
self.app = QtGui.QApplication(sys.argv)
self.win = pq.GraphicsWindow(title="examples")
self.win.resize(1000, 600)
self.win.setWindowTitle("Windowtitle")
self.canvas = self.win.addPlot(title="Plot")
def starter(self):
if (sys.flags.interactive != 1) or not hasattr(QtCore, "PYQT_VERSION"):
QtGui.QApplication.instance().exec_()
def trace(self, name, dataset_x, dataset_y):
if name in self.traces:
self.traces[name].setData(dataset_x, dataset_y)
else:
self.traces[name] = self.canvas.plot(pen="y")
#QtCore.pyqtSlot(object)
def update(self, i):
x_data = np.arange(0, 3.0, 0.01)
y_data = x_data * i
self.trace(str(i), x_data, y_data)
self.trace(str(i), x_data, y_data)
if __name__ == "__main__":
p = Plot2d()
qobserver = QObserver()
qobserver.dataChanged.connect(p.update)
p.starter()
I apologize for the title but I couldn't think of a better description of the problem. I have a Tkinter based program that has an option for the user to generate a PDF report that consists of an overview, followed by some detail plots. I knew that for some reason the entire program would shut down after it finalized the PDF report but I only recently sat down to truly identify what caused it.
I found that the plt.close line in the initial overview plot, causes the entire program to close once the pdf report has been written (which is the first part that I don't understand as surely, if the plot.close is to blame why does the entire module run til completion)? Secondly, why does this even happen?
The minimum example that I was able to produce (with nonsense data for the plots) is listed below where if the line that is preceded by # THE CULPRIT is commented, the Tk() instance stays alive but if it's left as is, the Tk() instance is closed.
import tkinter as tk
import matplotlib.pyplot as plt
import numpy as np
from matplotlib.backends.backend_pdf import PdfPages
from pathlib import Path
class Pdf(object):
def __init__(self, master):
self.master = master
pdf = PdfPages(Path.cwd() / 'demo.pdf')
self.pdf = pdf
def plot_initial(self):
fig = plt.figure(figsize=(8,6))
fig.add_subplot(111)
mu, sigma = 0, 0.1
s = np.random.normal(mu, sigma, 1000)
count, bins, ignored = plt.hist(s, 30, density=True)
plt.plot(bins, 1/(sigma * np.sqrt(2 * np.pi)) *
np.exp( - (bins - mu)**2 / (2 * sigma**2) ),
linewidth=2, color='r')
plt.title('Overview')
plt.xlabel('X')
plt.ylabel('Y')
self.pdf.savefig(fig)
# THE CULPRIT
plt.close(fig)
def plot_extra(self):
fig = plt.figure(figsize=(8,6))
fig.add_subplot(111)
mu, sigma = 0, 0.1
s = np.random.normal(mu, sigma, 1000)
count, bins, ignored = plt.hist(s, 30, density=True)
plt.plot(bins, 1/(sigma * np.sqrt(2 * np.pi)) *
np.exp( - (bins - mu)**2 / (2 * sigma**2) ),
linewidth=2, color='r')
plt.title('Extra')
plt.xlabel('X')
plt.ylabel('Y')
self.pdf.savefig(fig)
plt.close(fig)
def close(self):
self.pdf.close()
class MVE(object):
#classmethod
def run(cls):
root = tk.Tk()
MVE(root)
root.mainloop()
def __init__(self, master):
self.root = master
tk.Frame(master)
menu = tk.Menu(master)
master.config(menu=menu)
test_menu = tk.Menu(menu, tearoff=0)
menu.add_cascade(label='Bug', menu=test_menu)
test_menu.add_command(label='PDF', command=
self.generate_pdf)
def generate_pdf(self):
pdf = Pdf(self)
pdf.plot_initial()
for i in range(0,3):
pdf.plot_extra()
pdf.close()
if __name__ == "__main__":
MVE.run()
Versions of installed packages/python base:
Python 3.7.0
Tkinter 8.6
Matplotlib 2.2.3
Numpy 1.15.1
Edit
I have upgraded to Matplotlib 3.0.2 as per #ImportanceOfBeingErnest suggestion, however the problem still remains.
Seems like that the default backend used is TkAgg, change it to non-interactive backend, like agg, before importing matplotlib.pyplot:
import tkinter as tk
import matplotlib as mpl
mpl.use('agg')
import matplotlib.pyplot as plt
...
The problem arises because: "tension in Matplotlib between providing a low-level library to be used by application developers and being a front-line user interface. In this case, the niceties we need to provide for the end-user (ex, exiting the GUI main loop when the last figure is closed) is conflicting with how #Tarskin wants to use Matplot as a low-level library.". For the full comment see here.
The problem is apparantly caused by matplotlib/lib/matplotlib/backends/_backend_tk.py:
def destroy(self, *args):
if self.window is not None:
#self.toolbar.destroy()
if self.canvas._idle_callback:
self.canvas._tkcanvas.after_cancel(self.canvas._idle_callback)
self.window.destroy()
if Gcf.get_num_fig_managers() == 0:
if self.window is not None:
self.window.quit()
self.window = None
which quits the application when all of the figures are closed. This is essential for plt.show(block=True) to work (so when you close all of the plots we return control to the terminal). For the source of this go here.
The suggestion was made to use Agg and while this indeed fixes it for now, the suggestion was made to ignore pyplot completely, when one is integrating matplotlib into a stand-alone package. Therefore, I have now fixed this by using just the matplotlib.figure.Figure class, as listed below (with a bit more nonsensical data, to avoid the use of pyplot completely).
import tkinter as tk
from matplotlib.figure import Figure
import numpy as np
from matplotlib.backends.backend_pdf import PdfPages
from pathlib import Path
class Pdf(object):
def __init__(self, master):
self.master = master
pdf = PdfPages(Path.cwd() / 'demo.pdf')
fig = Figure(figsize=(8,6))
axes = fig.add_subplot(111)
axes.set_xlabel('X')
axes.set_ylabel('Y')
self.fig = fig
self.axes = axes
self.pdf = pdf
def plot_initial(self):
mu, sigma = 0, 0.1
s = np.random.normal(mu, sigma, 1000)
self.axes.clear()
self.axes.plot(s)
self.axes.set_title('Overview')
self.pdf.savefig(self.fig)
def plot_extra(self):
mu, sigma = 0, 0.1
s = np.random.normal(mu, sigma, 1000)
self.axes.clear()
self.axes.plot(s)
self.axes.set_title('Extra')
self.pdf.savefig(self.fig)
def close(self):
self.pdf.close()
class MVE(object):
#classmethod
def run(cls):
root = tk.Tk()
MVE(root)
root.mainloop()
def __init__(self, master):
self.root = master
tk.Frame(master)
menu = tk.Menu(master)
master.config(menu=menu)
test_menu = tk.Menu(menu, tearoff=0)
menu.add_cascade(label='Fixed', menu=test_menu)
test_menu.add_command(label='PDF', command=
self.generate_pdf)
def generate_pdf(self):
pdf = Pdf(self)
pdf.plot_initial()
for i in range(0,3):
pdf.plot_extra()
pdf.close()
if __name__ == "__main__":
MVE.run()
The full discussion can be found at this github issue thread.
I have an application that displays a GUI using GTK3 for python 3. It has a plot rendered by matplotlib. When the plot is included in the code the UI gets zoomed in (see pictures). This happens on a mac with retina display. On Ubuntu it works perfectly. Here is the code that draws the GUI:
import gi
gi.require_version('Gtk', '3.0')
from gi.repository import Gtk
from matplotlib.figure import Figure
import matplotlib.cm as cm
from matplotlib.backends.backend_gtk3cairo import FigureCanvasGTK3Cairo as FigureCanvas
from threading import Thread
import os
from instruction import *
from instructionwriter import *
class UI:
def __init__(self):
self.instructions = None
self.builder = Gtk.Builder()
self.builder.add_from_file(os.path.dirname(os.path.realpath(__file__)) + os.path.sep + "UI.glade")
self.builder.connect_signals(self)
scale = self.builder.get_object("scale")
scale.set_label("Välj skalfaktor")
scale.set_relief(Gtk.ReliefStyle.NORMAL)
# This part has to be commented out to make the GUI display correctly but is needed for the application.
#self.fig = Figure()
#self.fig.suptitle("Förhandsvisning")
#self.ax = self.fig.add_subplot(1, 1, 1)
#self.canvas = FigureCanvas(self.fig)
#self.builder.get_object("scrolledwindow1").add_with_viewport(self.canvas)
#self._preview()
self.buttons = [
self.builder.get_object("filechooser"),
self.builder.get_object("centerbutton"),
self.builder.get_object("scale"),
self.builder.get_object("autobutton"),
self.builder.get_object("preview"),
self.builder.get_object("save")
]
self.lastScale = 0.5
statusBar = self.builder.get_object("statusBar")
self.context_id = statusBar.get_context_id("id")
def show(self):
self.builder.get_object("window1").show_all()
def on_exit(self, *args):
Gtk.main_quit()
def on_file_select(self, *args):
filename = self.builder.get_object("filechooser").get_filename()
self.builder.get_object("snurrare").start()
self._disable_ui()
Thread(target=self._load, args=(filename,)).start()
def on_preview(self, *args):
self._preview()
self._display_time()
def on_center(self, *args):
self.instructions.center()
def on_fit(self, *args):
self.instructions.fit()
def on_save(self, *args):
dialog = Gtk.FileChooserDialog("Välj mapp", self.builder.get_object("window1"),
Gtk.FileChooserAction.SELECT_FOLDER,
(Gtk.STOCK_CANCEL, Gtk.ResponseType.CANCEL,
"Välj", Gtk.ResponseType.OK))
dialog.set_default_size(800, 400)
response = dialog.run()
if response == Gtk.ResponseType.OK:
self.builder.get_object("snurrare").start()
self._disable_ui()
Thread(target=self._save, args=(dialog.get_filename(),)).start()
dialog.destroy()
def _load(self, filename):
self.lastScale = 0.5
self.builder.get_object("scale").set_value(0.5)
ext = os.path.splitext(filename)[1]
if ext == ".svg":
self.instructions = InstructionList.from_svg(filename)
elif ext == ".py":
self.instructions = InstructionList.from_code(filename)
self.builder.get_object("snurrare").stop()
self._display_time()
self._enable_ui()
def _save(self, folder):
writer = InstructionWriter(self.instructions, 0)
writer.write(folder)
self.builder.get_object("snurrare").stop()
self._enable_ui()
def _disable_ui(self):
for button in self.buttons:
button.set_sensitive(False)
def _enable_ui(self):
for button in self.buttons:
button.set_sensitive(True)
def _preview(self):
if not self.instructions is None:
if self.builder.get_object("scale").get_value() != self.lastScale:
self.lastScale = self.builder.get_object("scale").get_value()
self.instructions.scale(2*self.lastScale)
self.ax.clear()
x = []
y = []
for inst in self.instructions.instructions:
if inst.type == Instruction.MOVE:
self.ax.plot(x, y, 'b')
x = [inst.dest.real]
y = [inst.dest.imag]
elif inst.type == Instruction.LINE:
x.append(inst.dest.real)
y.append(inst.dest.imag)
self.ax.plot(x, y, 'b')
self.ax.plot([-XLIM, XLIM, XLIM, -XLIM, -XLIM], [-YLIM, -YLIM, YLIM, YLIM, -YLIM], 'r')
self.ax.plot([-585, 585, 585, -585, -585], [-413.5, -413.5, 413.5, 413.5, -413.5], 'g:')
self.ax.axis("equal")
self.fig.canvas.draw()
def _display_time(self):
t = self.instructions.time()
self.builder.get_object("statusBar").push(self.context_id, "Uppskattad tid: %dmin %ds" % (t//60, t%60))
ui = UI()
ui.show()
Gtk.main()
GUI when plot is included
GUI when plot is excluded