PyQT5 slot parameters not updating after first call - pyqt5

I have a signal that connects to a slot that I use to change an x-range span on a matplotlib plot. I am trying to update the span when a user changes the starting x value in a table that shows all the spans. However, after the first edit of the span, the proceeding edits all act as though they are editing the same span, rather than the newly selected span. When running the code:
#QtCore.pyqtSlot()
def xStartChanged(xStart, rowVal):
if self.inCounter == 0:
print( "row in start changed: ", rowVal)
print("xStart in start changed: ", xStart)
print(type(xStart))
tagName = self.highlights[xStart].tag
xEnd = self.highlights[xStart].xEnd
xStartNew = int(self.taggingTable.item(rowVal, 1).text())
self.highlights[xStart].highlightObj.remove() #remove old from the plot
del self.highlights[xStart] #remove old from directory
highlightObj = self.Axes.axvspan(xStartNew, xEnd, color = 'blue', alpha = 0.5) #add new to plot
self.highlights[xStartNew] = highlight(tagName, xStartNew, xEnd, highlightObj) #add new to directory
self.taggingTable.clearSelection() #deselect value from table
self.draw_idle()#update plot
self.inCounter = self.inCounter + 1
def onCellSelect():
index = self.taggingTable.selectedIndexes()
if len(index) != 0:
rowVal = index[0].row()
#get the starting adn ending indices so they can be edited.
##deal with empty cell click
if not (self.taggingTable.item(rowVal, 1) is None):
xStart = int(self.taggingTable.item(rowVal, 1).text())
print("row in cell select: ", rowVal)
print("xStart in cell select: ", xStart)
xEnd = int(self.taggingTable.item(rowVal, 2).text())
self.inCounter = 0
#self.taggingTable.itemChanged.connect(lambda state, item = None, xStartVal = xStart, rowValFxn = rowVal: xStartChanged(xStartVal, rowValFxn))
print(type(xStart))
self.taggingTable.itemChanged.connect(lambda item = None, xStart = xStart: xStartChanged(xStart, rowVal))
self.draw_idle()
#self.taggingTable.itemChanged.connect(xEndChanged)
#self.taggingTable.cellDoubleClicked.connect(onCellSelect)
self.taggingTable.selectionModel().selectionChanged.connect(onCellSelect)
self.draw_idle()
I get the following output: (NOTE: I have hidden the "path" because it is work related, sorry.
row in cell select: 0
xStart in cell select: 3517
<class 'int'>
row in start changed: 0
xStart in start changed: 3517
<class 'int'>
row in cell select: 1
xStart in cell select: 2801
<class 'int'>
row in start changed: 0
xStart in start changed: 3517
<class 'int'>
Traceback (most recent call last):
File "path", line 314, in <lambda>
self.taggingTable.itemChanged.connect(lambda item = None, xStart = xStart: xStartChanged(xStart, rowVal))
File "path", line 277, in xStartChanged
tagName = self.highlights[xStart].tag
KeyError: 3517
zsh: abort path
I understand that for some reason, the connection sends the row value and xStart value of 0 and 3517 and after the cell is changed and edited, the first slot function (onCellSelect) is reading the new cell and xStart values, but the nested signal-slot connection (xStartChanged) is still holding onto the older cell and xStart values. I'm assuming that I am calling or initializing it wrong in some way. Is there something i'm not realizing?
EDIT: minimal reproducible example.
import sys
from PyQt5.QtWidgets import *
from PyQt5 import QtCore
from PyQt5.QtCore import *
from PyQt5.QtGui import *
from matplotlib.backends.backend_qt5agg import FigureCanvasQTAgg as FigureCanvas
from matplotlib.figure import Figure
import matplotlib.widgets as mwidgets
class Window(QMainWindow):
def __init__(self, parent = None):
super(Window, self).__init__(parent)
self.resize(1600, 800)
self.MyUI()
def MyUI(self):
canvas = Canvas(self, width=14, height=12, dpi=100)
canvas.move(0,0)
class Canvas(FigureCanvas):
def __init__(self, parent, width = 14, height = 12, dpi = 100):
Plot = Figure(figsize=(width, height), dpi=dpi)
self.Axes = Plot.add_subplot(111)
self.Axes.set_position([0.05, 0.58, 0.66, 0.55])
self.rowCount = 0
super().__init__(Plot)
self.setParent(parent)
##add all relevant lines to plot
self.Axes.plot([0,1,2,3,4], [3, 4, 5, 6, 7])
self.Axes.set_xlabel('Frame', fontsize = 10)
self.Axes.grid()
self.Axes.set_aspect(1)
Plot.canvas.draw()
#use this object in the dictionary to hold onto all the spans.
class highlight:
def __init__(self, tag, xStart, xEnd, highlightObj):
self.tag = tag
self.xStart = xStart
self.xEnd = xEnd
self.highlightObj = highlightObj
self.highlights = {} #empty dictionary to store all the tags.
##define a table to hold the values postselection
self.taggingTable = QTableWidget(self)
self.taggingTable.setColumnCount(3)
self.taggingTable.setRowCount(100)
self.taggingTable.setGeometry(QRect(1005,85, 330, 310))
self.taggingTable.setHorizontalHeaderLabels(['Behavior','Start Frame', 'End Frame'])
##highlighting adds a highlight item to the directory.
def onHighlight(xStart, xEnd):
tagName = "No Tag"
self.taggingTable.setItem(self.rowCount, 0, QTableWidgetItem(tagName))
self.taggingTable.setItem(self.rowCount, 1, QTableWidgetItem(str(int(xStart))))
self.taggingTable.setItem(self.rowCount, 2, QTableWidgetItem(str(int(xEnd))))
self.rowCount = self.rowCount + 1
highlightObj = self.Axes.axvspan(xStart, xEnd, color = 'blue', alpha = 0.5)
self.highlights[int(xStart)] = highlight(tagName, xStart, xEnd, highlightObj)
self.draw_idle()
self.span = mwidgets.SpanSelector(self.Axes, onHighlight, "horizontal",
interactive = True, useblit=True, props=dict(alpha=0.5, facecolor="blue"),)
self.draw_idle()
##TODO: editing spans in table is reflected in plot --- use axvspan to track spans? and spanselector to draw them?
##define workflow for when a cell is selected
def xStartChanged(xStart, rowVal):
if self.inCounter == 0:
print( "row in start changed: ", rowVal)
print("xStart in start changed: ", xStart)
xEnd = self.highlights[xStart].xEnd
xStartNew = int(self.taggingTable.item(rowVal, 1).text())
self.highlights[xStart].highlightObj.remove() #remove old from the plot
del self.highlights[xStart] #remove old from directory
highlightObj = self.Axes.axvspan(xStartNew, xEnd, color = 'blue', alpha = 0.5) #add new to plot
self.highlights[xStartNew] = highlight("No tagName", xStartNew, xEnd, highlightObj) #add new to directory
self.taggingTable.clearSelection() #deselect value from table
self.draw_idle()#update plot
self.inCounter = self.inCounter + 1
def onCellSelect():
index = self.taggingTable.selectedIndexes()
if len(index) != 0:
rowVal = index[0].row()
if not (self.taggingTable.item(rowVal, 1) is None):
xStart = int(self.taggingTable.item(rowVal, 1).text())
print("row in cell select: ", rowVal)
print("xStart in cell select: ", xStart)
self.inCounter = 0
self.taggingTable.itemChanged.connect(lambda: xStartChanged(xStart, rowVal))
self.taggingTable.selectionModel().selectionChanged.connect(onCellSelect)
self.draw_idle()
app = QApplication(sys.argv)
window = Window()
window.show()
app.exec()
I receive the following error:
row in cell select: 0
xStart in cell select: 2
row in start changed: 0
xStart in start changed: 2
row in cell select: 1
xStart in cell select: 1
row in start changed: 0
xStart in start changed: 2
Traceback (most recent call last):
File "path", line 103, in <lambda>
self.taggingTable.itemChanged.connect(lambda: xStartChanged(xStart, rowVal))
File "path", line 84, in xStartChanged
xEnd = self.highlights[xStart].xEnd
KeyError: 2
zsh: abort "path"

Related

Remove default numbers from QHeaderView when using a custom label in the header

I am attempting to customize a QHeaderView in order to create custom labels that span two or more columns. In the code below, I have done this by placing a QLabel over the appropriate sections of the overall header. However, the default header numbering of the individual columns still appears as shown below. How can this be removed? I would prefer to do this without adding a background colour to each label.
import sys
from PySide6.QtCore import *
from PySide6.QtGui import *
from PySide6.QtWidgets import *
class CustomHeader(QHeaderView):
def __init__(self, orientation, labels, parent=None):
QHeaderView.__init__(self, orientation, parent=None)
self.sectionResized.connect(self.handleSectionResized)
self.label_text = labels
self.labels = list()
self.setFixedHeight(40)
def handleSectionResized(self, i):
start = self.visualIndex(i)
stop = self.count()
for i in range(start, stop):
self.labels[i].setGeometry(self.sectionViewportPosition(i), 0, self.sectionSize(i) - 5, self.height())
self.labels[i].show()
def showEvent(self, event):
for i in range(len(self.label_text)):
label = QLabel(self.label_text[i], self)
label.setAlignment(Qt.AlignCenter)
widget = QWidget(self)
vbox = QVBoxLayout()
vbox.setSpacing(0)
vbox.setContentsMargins(0,0,0,0)
vbox.addWidget(label)
widget.setLayout(vbox)
widget.show()
self.labels.append(widget)
self.adjustPositions()
return super().showEvent(event)
def adjustPositions(self):
for index, label in enumerate(self.labels):
if index == 1 or index == 3:
size = self.sectionSize(index)
print(size)
geom = QRect(
self.sectionViewportPosition(index),
0,
200,
self.height(),
)
else:
size = self.sectionSize(index)
print(size)
geom = QRect(
self.sectionViewportPosition(index),
0,
self.sectionSize(index),
self.height(),
)
geom.adjust(2, 0, -2, 0)
label.setGeometry(geom)
class MyTable(QTableWidget):
def __init__(self, nrow, ncol, parent=None):
super().__init__(nrow, ncol, parent=parent)
self.verticalHeader().hide()
self.setHorizontalHeaderLabels([])
labels = ['One', 'Two', '', 'Three'] #, 'Four', 'Five']
self.header = CustomHeader(Qt.Horizontal, labels, self)
self.setHorizontalHeader(self.header)
self.setSelectionBehavior(QAbstractItemView.SelectItems)
self.setEditTriggers(QAbstractItemView.NoEditTriggers)
data = [
[3, 20, 40, 25, 45],
[5, 22, 42, 27, 47],
]
for i in range(2):
for j in range(5):
item = QTableWidgetItem(str(data[i-2][j]))
self.setItem(i, j, item)
item.setBackground(Qt.white)
if __name__ == '__main__':
app = QApplication(sys.argv)
table = MyTable(7, 5)
table.show()
sys.exit(app.exec())
You could change them the same way you would with the standard header.
In your QTableWidget just call:
self.setHorizontalHeaderLabels([""] * self.columnCount())
For example:
class MyTable(QTableWidget):
def __init__(self, nrow, ncol, parent=None):
super().__init__(nrow, ncol, parent=parent)
self.verticalHeader().hide()
self.setHorizontalHeaderLabels([])
labels = ['One', 'Two', '', 'Three'] #, 'Four', 'Five']
self.header = CustomHeader(Qt.Horizontal, labels, self)
self.setHorizontalHeaderLabels([""] * self.columnCount()) # added this
self.setHorizontalHeader(self.header)
self.setSelectionBehavior(QAbstractItemView.SelectItems)
self.setEditTriggers(QAbstractItemView.NoEditTriggers)

override itemChanged signal so that I can access old and new data values [duplicate]

I am trying to tag x-spans of a data trace and populate a table with tagNames, starting x value, and the ending x value. I am using a dictionary of 'highlight' objects to keep track of the x-spans in case they need to be edited (increased or decreased) later. The dictionary maps the x Start value to the highlight object, as the x start values are expected to be unique (there is no overlap of x-spans for the tagging).
In order to do this, I am emitting a signal when the user beings to edit a cell on the table. The function that the first signal connects to emits another signal (ideally for whether the xStart is changed vs. the xEnd, but I have only implemented the xStart thus far), which actually changes the appearance of the span to match the edit.
I asked a similar question a few weeks ago but wasn't able to get an answer. The old question is here: PyQT5 slot parameters not updating after first call. In response to the tips given there, I wrote the following example:
import sys
from PyQt5.QtWidgets import *
from PyQt5 import QtCore
from PyQt5.QtCore import *
from PyQt5.QtGui import *
from matplotlib.backends.backend_qt5agg import FigureCanvasQTAgg as FigureCanvas
from matplotlib.figure import Figure
import matplotlib.widgets as mwidgets
from functools import partial
class Window(QMainWindow):
def __init__(self, parent = None):
super(Window, self).__init__(parent)
self.resize(1600, 800)
self.MyUI()
def MyUI(self):
canvas = Canvas(self, width=14, height=12, dpi=100)
canvas.move(0,0)
#use this object in the dictionary to hold onto all the spans.
class highlight:
def __init__(self, tag, xStart, xEnd, highlightObj):
self.tag = tag
self.xStart = xStart
self.xEnd = xEnd
self.highlightObj = highlightObj
class Canvas(FigureCanvas):
def __init__(self, parent, width = 14, height = 12, dpi = 100):
Plot = Figure(figsize=(width, height), dpi=dpi)
self.Axes = Plot.add_subplot(111)
self.Axes.set_position([0.05, 0.58, 0.66, 0.55])
self.rowCount = 0
super().__init__(Plot)
self.setParent(parent)
##add all relevant lines to plot
self.Axes.plot([0,1,2,3,4], [3, 4, 5, 6, 7])
self.Axes.set_xlabel('Frame', fontsize = 10)
self.Axes.grid()
self.Axes.set_aspect(1)
Plot.canvas.draw()
self.highlights = {} #empty dictionary to store all the tags.
##define a table to hold the values postselection
self.taggingTable = QTableWidget(self)
self.taggingTable.setColumnCount(3)
self.taggingTable.setRowCount(100)
self.taggingTable.setGeometry(QRect(1005,85, 330, 310))
self.taggingTable.setHorizontalHeaderLabels(['Behavior','Start Frame', 'End Frame'])
Canvas.span = mwidgets.SpanSelector(self.Axes, self.onHighlight, "horizontal",
interactive = True, useblit=True, props=dict(alpha=0.5, facecolor="blue"),)
self.draw_idle()
self.taggingTable.selectionModel().selectionChanged.connect(self.onCellSelect)
self.draw_idle()
##highlighting adds a highlight item to the directory.
def onHighlight(self, xStart, xEnd):
tagName = "No Tag"
self.taggingTable.setItem(self.rowCount, 0, QTableWidgetItem(tagName))
self.taggingTable.setItem(self.rowCount, 1, QTableWidgetItem(str(int(xStart))))
self.taggingTable.setItem(self.rowCount, 2, QTableWidgetItem(str(int(xEnd))))
self.rowCount = self.rowCount + 1
highlightObj = self.Axes.axvspan(xStart, xEnd, color = 'blue', alpha = 0.5)
self.highlights[int(xStart)] = highlight(tagName, xStart, xEnd, highlightObj)
self.draw_idle()
def xStartChanged(self, xStart, rowVal):
if self.inCounter == 0:
print("xStart in slot: ", xStart)
xEnd = self.highlights[xStart].xEnd
xStartNew = int(self.taggingTable.item(rowVal, 1).text())
self.highlights[xStart].highlightObj.remove() #remove old from the plot
del self.highlights[xStart] #remove old from directory
highlightObj = self.Axes.axvspan(xStartNew, xEnd, color = 'blue', alpha = 0.5) #add new to plot
self.highlights[xStartNew] = highlight("No tagName", xStartNew, xEnd, highlightObj) #add new to directory
self.taggingTable.clearSelection() #deselect value from table
self.draw_idle()
self.inCounter = self.inCounter + 1
def onCellSelect(self):
index = self.taggingTable.selectedIndexes()
if len(index) != 0:
rowVal = index[0].row()
if not (self.taggingTable.item(rowVal, 1) is None):
xStart = int(self.taggingTable.item(rowVal, 1).text())
print("--------------")
print("xStart in signal: ", xStart)
self.inCounter = 0
self.taggingTable.itemChanged.connect(lambda: self.xStartChanged(xStart, rowVal))
app = QApplication(sys.argv)
window = Window()
window.show()
app.exec()
A test I run is when I highlight two traces:
and then I successfully change a first trace:
However, when I attempt to edit the second trace, the program crashes:
To debug, I tried to check what the signals were emitting and receiving. it produces the following output:
--------------
xStart in signal: 0
xStart in slot: 0 ##First slot call gets correct signal
--------------
xStart in signal: 3
xStart in slot: 0 ## Second slot gets the first signal instead of the second
Traceback (most recent call last):
File "//Volumes/path/file.py", line 105, in <lambda>
self.taggingTable.itemChanged.connect(lambda: self.xStartChanged(xStart, rowVal))
File "//Volumes/path/file.py", line 86, in xStartChanged
xEnd = self.highlights[xStart].xEnd
KeyError: 0
zsh: abort python Volumes/path file.py
I tried to use information online on unique connections but I am not sure how to implement them. Thank you in advance for any help.
It seems that what you need is table-widget signal that emits an item and its old text value whenever a change is made to a specific column. Unfortunately, the itemChanged signal isn't really suitable, because it doesn't indicate what changed, and it doesn't supply the previous value. So, to work around this limitation, one solution would be to subclass QTableWidget / QTableWidgetItem and emit a custom signal with the required parameters. This will completely side-step the issue with multiple signal-slot connections.
The implementation of the subclasses is quite simple:
class TableWidgetItem(QTableWidgetItem):
def setData(self, role, value):
oldval = self.text()
super().setData(role, value)
if role == Qt.EditRole and self.text() != oldval:
table = self.tableWidget()
if table is not None:
table.itemTextChanged.emit(self, oldval)
class TableWidget(QTableWidget):
itemTextChanged = pyqtSignal(TableWidgetItem, str)
Below is basic a demo based on your example that shows how to use them. (Note that I have made no attempt to handle xEnd as well, as that would go beyond the scope of the immediate issue).
import sys
from PyQt5.QtWidgets import *
from PyQt5.QtCore import *
from PyQt5.QtGui import *
from matplotlib.backends.backend_qt5agg import FigureCanvasQTAgg as FigureCanvas
from matplotlib.figure import Figure
import matplotlib.widgets as mwidgets
class Window(QMainWindow):
def __init__(self, parent = None):
super(Window, self).__init__(parent)
self.resize(1600, 800)
self.MyUI()
def MyUI(self):
canvas = Canvas(self, width=14, height=12, dpi=100)
canvas.move(0,0)
# CUSTOM SUBCLASSES
class TableWidgetItem(QTableWidgetItem):
def setData(self, role, value):
oldval = self.text()
super().setData(role, value)
if role == Qt.EditRole and self.text() != oldval:
table = self.tableWidget()
if table is not None:
table.itemTextChanged.emit(self, oldval)
class TableWidget(QTableWidget):
itemTextChanged = pyqtSignal(TableWidgetItem, str)
#use this object in the dictionary to hold onto all the spans.
class highlight:
def __init__(self, tag, xStart, xEnd, highlightObj):
self.tag = tag
self.xStart = xStart
self.xEnd = xEnd
self.highlightObj = highlightObj
class Canvas(FigureCanvas):
def __init__(self, parent, width = 14, height = 12, dpi = 100):
Plot = Figure(figsize=(width, height), dpi=dpi)
self.Axes = Plot.add_subplot(111)
self.Axes.set_position([0.05, 0.58, 0.66, 0.55])
self.rowCount = 0
super().__init__(Plot)
self.setParent(parent)
##add all relevant lines to plot
self.Axes.plot([0,1,2,3,4], [3, 4, 5, 6, 7])
self.Axes.set_xlabel('Frame', fontsize = 10)
self.Axes.grid()
self.Axes.set_aspect(1)
Plot.canvas.draw()
self.highlights = {} #empty dictionary to store all the tags.
##define a table to hold the values postselection
# USE CUSTOM TABLE SUBCLASS
self.taggingTable = TableWidget(self)
self.taggingTable.setColumnCount(3)
self.taggingTable.setRowCount(100)
self.taggingTable.setGeometry(QRect(1005,85, 330, 310))
self.taggingTable.setHorizontalHeaderLabels(['Behavior','Start Frame', 'End Frame'])
# CONNECT TO CUSTOM SIGNAL
self.taggingTable.itemTextChanged.connect(self.xStartChanged)
Canvas.span = mwidgets.SpanSelector(self.Axes, self.onHighlight, "horizontal",
interactive = True, useblit=True, props=dict(alpha=0.5, facecolor="blue"),)
self.draw_idle()
##highlighting adds a highlight item to the directory.
def onHighlight(self, xStart, xEnd):
tagName = "No Tag"
self.taggingTable.setItem(self.rowCount, 0, QTableWidgetItem(tagName))
# USE CUSTOM ITEM SUBCLASS
self.taggingTable.setItem(self.rowCount, 1, TableWidgetItem(str(int(xStart))))
self.taggingTable.setItem(self.rowCount, 2, QTableWidgetItem(str(int(xEnd))))
self.rowCount = self.rowCount + 1
highlightObj = self.Axes.axvspan(xStart, xEnd, color = 'blue', alpha = 0.5)
self.highlights[int(xStart)] = highlight(tagName, xStart, xEnd, highlightObj)
self.draw_idle()
def xStartChanged(self, item, oldVal):
try:
# VALIDATE NEW VALUES
xStart = int(oldVal)
xStartNew = int(item.text())
except ValueError:
pass
else:
print("xStart in slot: ", xStart)
xEnd = self.highlights[xStart].xEnd
self.highlights[xStart].highlightObj.remove() #remove old from the plot
del self.highlights[xStart] #remove old from directory
highlightObj = self.Axes.axvspan(xStartNew, xEnd, color = 'blue', alpha = 0.5) #add new to plot
self.highlights[xStartNew] = highlight("No tagName", xStartNew, xEnd, highlightObj) #add new to directory
self.taggingTable.clearSelection() #deselect value from table
self.draw_idle()
app = QApplication(sys.argv)
window = Window()
window.show()
app.exec()

AttributeError: 'Axes' object has no attribute 'get_proj' in matplotlib

I am making the representation of a polynom function.
I have an error in a matplotlib code and cannot understand where it is coming from. any advice is welcome.
I tried already Gtk3agg but nothing changed.
Below is the failure code.
For any reason 'get_proj' dont work here for creating labels.
And: when I use ax.get_proj() instead,
a) all labels appear bottom left
b) not all labels appear at bottom left (all points are identified by the cursor bot the labels are not written at the bottom left).
The final project will be (few things still to be done):
- on button -> labelling with coordinate appear at each cursor movement (temporary)
- click right button, the labels will be persistent till button clear is clicked
- off button -> no labelling appear
My feeling: the 3x button creation is messing anything up.
# -*- coding: utf-8 -*-
import matplotlib as mpl
from mpl_toolkits.mplot3d.proj3d import proj_transform
import matplotlib.pyplot as plt
from matplotlib.widgets import Button
import numpy as np
mpl.use('tkagg')
def distance(point, event):
plt.sca(ax) # <------------------ introduce this one !!!!!!!!!!!!!!!!!!!!!!!!!!!
x2, y2, _ = proj_transform(point[0], point[1], point[2], plt.gca().get_proj())
x3, y3 = ax.transData.transform((x2, y2))
return np.sqrt ((x3 - event.x)**2 + (y3 - event.y)**2)
def calcClosestDatapoint(X, event):
distances = [distance(X[i, 0:3], event) for i in range(Sol)]
return np.argmin(distances)
#
def annotatePlot(X, index):
global last_mark, generated_labels
if activated_labelling:
x2, y2, _ = proj_transform(X[index, 0], X[index, 1], X[index, 2], ax.get_proj())
last_mark = plt.annotate(generated_labels[index],
xy = (x2, y2), xytext = (-20, 20), textcoords = 'offset points', ha = 'right', va = 'bottom',
bbox = dict(boxstyle = 'round,pad=0.5', fc = 'yellow', alpha = 0.5),
arrowprops = dict(arrowstyle = '->', connectionstyle = 'arc3,rad=0'))
fig.canvas.draw()
#
def onMouseMotion(event):
global Coord
if activated_labelling:
closestIndex = calcClosestDatapoint(Coord, event)
last_mark.remove()
annotatePlot(Coord, closestIndex)
def show_on(event):
global activated_labelling, last_mark,pid,mid
if activated_labelling == False:
activated_labelling = True
x2, y2, _ = proj_transform(Coord[0,0], Coord[0,1], Coord[0,2], ax.get_proj())
last_mark = plt.annotate("3D measurement on " + generated_labels[0],
xy = (x2, y2), xytext = (-20, 20), textcoords = 'offset points', ha = 'right', va = 'bottom',
bbox = dict(boxstyle = 'round,pad=0.5', fc = 'yellow', alpha = 0.5),
arrowprops = dict(arrowstyle = '->', connectionstyle = 'arc3,rad=0'))
mid = fig.canvas.mpl_connect('motion_notify_event', onMouseMotion)
#
def show_off(event):
global activated_labelling
'''
deactivate the persistent XYZ position labels at the grafic
'''
if activated_labelling:
activated_labelling = False
last_mark.remove()
fig.canvas.draw()
fig.canvas.mpl_disconnect(mid)
fig = plt.figure()
ax = fig.add_subplot(111, projection='3d')
#ax = fig.gca(projection='3d')
activated_labelling = False
Wide = 100
Minimum = -50
ScanLimit = 3 # searching between o and 3; 4 and 5 are no solutions
Search = 45
Coord=[]
values=[]
generated_labels = []
#
XMin = 0
XMax = 0
YMin = 0
YMax = 0
ZMin = 0
ZMax = 0
# count the solutions found in the scan area defined above
Sol=0
for i in range(Wide+1):
for j in range(Wide+1):
for k in range(Wide+1):
########################################################################
########################################################################
####
#### THIS IS THE POLYNOM TO BE REPRESENTED
####
param_dens = ((i+Minimum)**3)+((j+Minimum)**3)+((k+Minimum)**3) -Search
if abs(param_dens) <= abs(ScanLimit):
Coord.append([i+Minimum,j+Minimum,k+Minimum])
if ScanLimit !=0:
values.append([abs(param_dens)])
labelling = "value {}\nin X:{} Y:{} Z:{}".format(Search+param_dens,i+Minimum,j+Minimum,k+Minimum)
generated_labels.append(labelling)
print(labelling+"\n")
# increase the number indicating the solutions found
Sol +=1
# for centering the window
if XMin > i+Minimum:
XMin = i+Minimum
if YMin > j+Minimum:
YMin = j+Minimum
if ZMin > k+Minimum:
ZMin = k+Minimum
if XMax < i+Minimum:
XMax = i+Minimum
if YMax < j+Minimum:
YMax = j+Minimum
if ZMax < k+Minimum:
ZMax = k+Minimum
print('######################################################')
print('## statistics / move this to a parallel search engine?')
print('## search ')
print("## total solution %d for searching center %d" % (Sol,Search))
print("## from %d to %d" % (Search-ScanLimit,Search+ScanLimit))
print("## from %d to %d" % (Minimum,Wide+Minimum))
print('##')
print('#######################################################')
#
values = np.array(values, dtype='int64')
Coord = np.array(Coord, dtype='int64')
#
if ScanLimit !=0:
cmap = plt.cm.jet # define the colormap
# extract all colors from the .jet map
cmaplist = [cmap(i) for i in range(cmap.N)]
# force the first color entry to be black
cmaplist[0] = (0, 0, 0, 1.0)
# create the new map
cmap = mpl.colors.LinearSegmentedColormap.from_list('Custom cmap', cmaplist, cmap.N)
# define the bins and normalize
bounds = np.linspace(0, ScanLimit, ScanLimit+1)
norm = mpl.colors.BoundaryNorm(bounds, cmap.N)
# create a second axes for the colorbar
ax2 = fig.add_axes([0.95, 0.1, 0.03, 0.8])
cb = mpl.colorbar.ColorbarBase(ax2, cmap=cmap, norm=norm,
spacing='proportional', ticks=bounds, boundaries=bounds, format='%1i')
#
ax.set_xlim3d(XMin-5, XMax+5)
ax.set_ylim3d(YMin-5, YMax+5)
ax.set_zlim3d(ZMin-5, ZMax+5)
#
ax.set_xlabel('X X')
ax.set_ylabel('Y Y')
ax.set_zlabel('Z Z')
ax.set_aspect(aspect=1)
# extract the scatterplot drawing in a separate function so we ca re-use the code
def draw_scatterplot():
if ScanLimit !=0:
ax.scatter3D(Coord[:,0], Coord[:,1], Coord[:,2], s=20, c=values[:,0], cmap=cmap, norm=norm)
else:
ax.scatter3D(Coord[:,0], Coord[:,1], Coord[:,2], s=20, c='green')
# draw the initial scatterplot
draw_scatterplot()
# create the "on" button, and place it somewhere on the screen
ax_on = plt.axes([0.0, 0.0, 0.1, 0.05])
button_on = Button(ax_on, 'on')
#
ax_off = plt.axes([0.12, 0.0, 0.1, 0.05])
button_off = Button(ax_off, 'off')
#
#ax_off = plt.axes([0.24, 0.0, 0.1, 0.05])
#button_off = Button(ax_off, 'off')
# link the event handler function to the click event on the button
button_on.on_clicked(show_on)
button_off.on_clicked(show_off)
#fig.colorbar(img)
plt.show()
Traceback (most recent call last):
File "C:\Program Files\Anaconda3\lib\site-packages\matplotlib\cbook\__init__.py", line 388, in process
proxy(*args, **kwargs)
File "C:\Program Files\Anaconda3\lib\site-packages\matplotlib\cbook\__init__.py", line 228, in __call__
return mtd(*args, **kwargs)
File "C:/Users/../Desktop/heat.py", line 137, in onClick
closestIndex,LowestDistance = calcClosestDatapoint(Coord, event)
File "C:/Users/../Desktop/heat.py", line 50, in calcClosestDatapoint
distances = [distance(X[i, 0:3], event) for i in range(Sol)]
File "C:/Users/../Desktop/heat.py", line 50, in <listcomp>
distances = [distance(X[i, 0:3], event) for i in range(Sol)]
File "C:/Users/../Desktop/heat.py", line 35, in distance
x2, y2, _ = proj_transform(point[0], point[1], point[2], plt.gca().get_proj())
AttributeError: 'Axes' object has no attribute 'get_proj'

In a matplotlib plot consisting of histogram subplots, how can the height and bar edges of one histogram be changed?

I've got a little function that generates a plot of two subplots. One subplot is two histograms overlaid and the other subplot is the results of dividing one histogram by the other.
For the second subplot, I don't know how to remove the edges between histogram bars (like the one above it) and I don't know how to reduce its height (such that it is, say, half the height of the one above it). I'm also not sure how to set the title to the very top of the plot.
How could these things be done?
My code is as follows:
import numpy
import matplotlib.pyplot
import datavision # sudo pip install datavision
import shijian # sudo pip install shijian
def main():
a = numpy.random.normal(2, 2, size = 120)
b = numpy.random.normal(2, 2, size = 120)
save_histogram_comparison_matplotlib(
values_1 = a,
values_2 = b,
label_1 = "a",
label_2 = "b",
normalize = True,
label_ratio_x = "frequency",
label_y = "",
title = "comparison of a and b",
filename = "test.png"
)
def save_histogram_comparison_matplotlib(
values_1 = None,
values_2 = None,
filename = None,
number_of_bins = None,
normalize = True,
label_x = "",
label_y = None,
label_ratio_x = "frequency",
label_ratio_y = "ratio",
title = None,
label_1 = "1",
label_2 = "2",
overwrite = True,
LaTeX = False
):
matplotlib.pyplot.ioff()
if LaTeX is True:
matplotlib.pyplot.rc("text", usetex = True)
matplotlib.pyplot.rc("font", family = "serif")
if number_of_bins is None:
number_of_bins_1 = datavision.propose_number_of_bins(values_1)
number_of_bins_2 = datavision.propose_number_of_bins(values_2)
number_of_bins = int((number_of_bins_1 + number_of_bins_2) / 2)
if filename is None:
filename = shijian.propose_filename(
filename = title.replace(" ", "_") + ".png",
overwrite = overwrite
)
values = []
values.append(values_1)
values.append(values_2)
bar_width = 0.8
figure, (axis_1, axis_2) = matplotlib.pyplot.subplots(nrows = 2)
ns, bins, patches = axis_1.hist(
values,
normed = normalize,
histtype = "stepfilled",
bins = number_of_bins,
alpha = 0.5,
label = [label_1, label_2],
rwidth = bar_width,
linewidth = 0
)
axis_1.legend()
axis_2.bar(
bins[:-1],
ns[0] / ns[1],
edgecolor = "#ffffff", # "none"
alpha = 1,
width = bins[1] - bins[0]
)
axis_1.set_xlabel(label_x)
axis_1.set_ylabel(label_y)
axis_2.set_xlabel(label_ratio_x)
axis_2.set_ylabel(label_ratio_y)
matplotlib.pyplot.title(title)
matplotlib.pyplot.savefig(filename)
matplotlib.pyplot.close()
if __name__ == "__main__":
main()
You have 3 questions:
1. How to remove the edges between histogram bars
Here, you can set the linewidth to 0 for the call to bar:
axis_2.bar(
bins[:-1],
ns[0] / ns[1],
linewidth=0,
alpha = 1,
width = bins[1] - bins[0]
)
2. How to reduce the height of the second subplot
Here, we can send kwargs to gridspec when we create the subplots. The relevant option is height_ratios. We send them using the gridspec_kw option to subplots. If we set it to (2,1), that makes the first subplot twice the height of the second one.
figure, (axis_1, axis_2) = matplotlib.pyplot.subplots(
nrows = 2,
gridspec_kw={'height_ratios':(2,1)}
)
3. How to set the title to the very top of the plot
When you call matplotlib.pyplot.title(title), that is actually setting the title of the currently active subplot axes, which in this case is axis_2. To set the title of the overall figure, you can set the suptitle:
matplotlib.pyplot.suptitle(title)
Or alternatively, since you already named your figure, you can use:
figure.suptitle(title)
And likewise, you could use:
figure.savefig(filename)
to save a few keystrokes.
Putting it all together:

Quickly and Efficiently update Matplotlib Axes (Plot)

I've run into some performance issues with a GUI that I'm working on. Specificially I'm using wxPython as the backend for several matplotlib figures which I've embedded in the canvas. I've gotten the basic functionality working with a simple self.axes.clear() and self.axes.plot() command, but I can't seem to get any sort of reasonable frame rates using this method. After performing a search it seems that if I were using a plot object that I I could reset the xdata and ydata then redraw the figure to obtain a faster refresh rate. Unfortunately, the layout that I'm using precludes the use of the plot object, so I've implemented the code using the axes() object. As far as I can tell the axes() object does not have any equivalent methods for setting the xdata and ydata (see this post: How to update a plot in matplotlib?). Here's the code I'm using:
import sys,os,csv
import numpy as np
import wx
import matplotlib
import pylab
from matplotlib.figure import Figure
from matplotlib.backends.backend_wxagg import \
FigureCanvasWxAgg as FigCanvas, \
NavigationToolbar2WxAgg as NavigationToolbar
class UltrasoundDemoGUI(wx.Frame):
title = ' Ultrasound Demo '
def __init__(self):
wx.Frame.__init__(self, None, -1, self.title)
self.create_menu()
self.statusbar = self.CreateStatusBar()
self.create_main_panel()
self.dataMon = pylab.randn(100,1);
self.dataBi = pylab.randn(100,1);
self.dataLoc = pylab.randn(100,1);
self.redraw_timer = wx.Timer(self)
self.Bind(wx.EVT_TIMER, self.on_redraw_timer, self.redraw_timer)
self.redraw_timer.Start(300)
def create_menu(self):
self.menubar = wx.MenuBar()
menu_file = wx.Menu()
m_expt = menu_file.Append(-1, "&Save plot\tCtrl-S", "Save plot to file")
self.Bind(wx.EVT_MENU, self.on_save_plot, m_expt)
menu_file.AppendSeparator()
m_exit = menu_file.Append(-1, "E&xit\tCtrl-X", "Exit")
self.Bind(wx.EVT_MENU, self.on_exit, m_exit)
self.menubar.Append(menu_file, "&File")
self.SetMenuBar(self.menubar)
def create_main_panel(self):
self.panel = wx.Panel(self)
# self.init_plot()
self.dpi = 100
self.figSignals = Figure((12, 5.0), dpi=self.dpi)
self.canvas = FigCanvas(self.panel, -1, self.figSignals)
rectSubplotMono = .1, .55, .4, .4
self.axesMono = self.figSignals.add_axes(rectSubplotMono)
rectSubplotBi = .1, .05, .4, .4
self.axesBi = self.figSignals.add_axes(rectSubplotBi)
rectSubplotLoc = .55, .05, .4, .9
self.axesLoc = self.figSignals.add_axes(rectSubplotLoc)
self.axesMono.set_axis_bgcolor('white')
self.axesBi.set_axis_bgcolor('white')
self.axesLoc.set_axis_bgcolor('white')
self.axesMono.set_title('Raw Ultrasound Signal', size=12)
pylab.setp(self.axesMono.get_xticklabels(), fontsize=8)
pylab.setp(self.axesMono.get_yticklabels(), fontsize=8)
pylab.setp(self.axesBi.get_xticklabels(), fontsize=8)
pylab.setp(self.axesBi.get_yticklabels(), fontsize=8)
pylab.setp(self.axesLoc.get_xticklabels(), fontsize=8)
pylab.setp(self.axesLoc.get_yticklabels(), fontsize=8)
# plot the data as a line series, and save the reference
# to the plotted line series
#
self.dataMono = pylab.randn(100,1)
self.dataBi = pylab.randn(100,1)
self.dataLoc = pylab.randn(100,1)
self.plot_dataMono = self.axesMono.plot(
self.dataMono,
linewidth=1,
color=(1, 1, 0),
)[0]
self.plot_dataBi = self.axesBi.plot(
self.dataBi,
linewidth=1,
color=(1, 1, 0),
)[0]
self.plot_dataLoc = self.axesLoc.plot(
self.dataLoc,
linewidth=1,
color=(1, 1, 0),
)[0]
self.toolbar = NavigationToolbar(self.canvas)
self.vbox = wx.BoxSizer(wx.VERTICAL)
self.vbox.Add(self.canvas, 1, wx.LEFT | wx.TOP | wx.GROW)
self.vbox.AddSpacer(10)
self.hbox = wx.BoxSizer(wx.HORIZONTAL)
flags = wx.ALIGN_LEFT | wx.ALL | wx.ALIGN_CENTER_VERTICAL
self.panel.SetSizer(self.vbox)
self.vbox.Fit(self)
def init_plot(self):
rectSubplotMono = .1, .55, .4, .4
self.axesMono = self.figSignals.add_axes(rectSubplotMono)
rectSubplotBi = .1, .05, .4, .4
self.axesBi = self.figSignals.add_axes(rectSubplotBi)
rectSubplotLoc = .55, .05, .4, .9
self.axesLoc = self.figSignals.add_axes(rectSubplotLoc)
self.axesMono.set_axis_bgcolor('white')
self.axesBi.set_axis_bgcolor('white')
self.axesLoc.set_axis_bgcolor('white')
self.axesMono.set_title('Raw Ultrasound Signal', size=12)
pylab.setp(self.axesMono.get_xticklabels(), fontsize=8)
pylab.setp(self.axesMono.get_yticklabels(), fontsize=8)
pylab.setp(self.axesBi.get_xticklabels(), fontsize=8)
pylab.setp(self.axesBi.get_yticklabels(), fontsize=8)
pylab.setp(self.axesLoc.get_xticklabels(), fontsize=8)
pylab.setp(self.axesLoc.get_yticklabels(), fontsize=8)
def on_redraw_timer(self, event):
self.draw_plot()
def draw_plot(self):
self.axesMono.clear()
self.axesBi.clear()
self.axesLoc.clear()
i = np.arange(1,100)
w = i;
x = pylab.randn(100,1);
y = pylab.randn(100, 1);
z = pylab.randn(100, 1);
# self.axesMono.set_xdata(np.arange(len(x)))
# self.axesMono.set_ydata(np.array(x))
self.axesMono.plot(x, 'red')
self.axesBi.plot(x,'yellow')
self.axesLoc.plot(x, z, 'black')
self.canvas.draw()
def on_save_plot(self, event):
file_choices = "PNG (*.png)|*.png"
dlg = wx.FileDialog(
self,
message="Save plot as...",
defaultDir=os.getcwd(),
defaultFile="plot.png",
wildcard=file_choices,
style=wx.SAVE)
if dlg.ShowModal() == wx.ID_OK:
path = dlg.GetPath()
self.canvas.print_figure(path, dpi=self.dpi)
self.flash_status_message("Saved to %s" % path)
def on_exit(self, event):
self.Destroy()
if __name__ == '__main__':
app = wx.PySimpleApp()
app.frame = UltrasoundDemoGUI()
app.frame.Show()
app.frame.draw_plot()
app.MainLoop()
del app
I appear to be limited to a refresh rate of approximate 3Hz. Ideally I'd like to visualize the data at a frame rate of 10Hz or higher. Does anyone have any idea how I can efficiently (quickly) update the plots using the axes object?
Thanks for your help,
-B