I have a nested layout 4-levels of nesting as in the attached photo
- top layout is MainHorLayout |
-
|--QVBoxLayout_______________ Hosts CheckBoxes |--AllfigVlayout_____________ Hosts figures |-TopHlayoutfig_0_1_______Hosts profile_0,Profile_1 charts, downside labels | | | |-- TopVlayoutFig_0_Lbl (loaded with chart[0], chart[0].belowlabel) | | | |-- TopVlayoutFig_1_Lbl (loaded with chart[1], chart[1].belowlabel) | |-BotHlayoutfig_2_3_______same as TopHlayoutfig_0_1 but for bottom fig 2,3 | |-- TopVlayoutFig_2_Lbl (loaded with chart[2], chart[2].belowlabel) | |-- TopVlayoutFig_3_Lbl (loaded with chart[2], chart[2].belowlabel)
I'm using check box to toggle the charts[i] visibale/ unvisible start up behaviour is shown in phot 1 No problem with Charts[0] and charts2 as in photo 2
The problem when i'm trying to hid charts1 it hides charts[0] and 1 also trying to hide charts3 it hides both 2 and 3 please check photo 3
when i trying to show them again the dimensions of charts is not equall and non symetrical please check photo 4 as it shows when program starts
from functools import partial
from scipy.interpolate import make_interp_spline, BSpline
import sys
import matplotlib.pyplot as plt
import numpy as np
#from PyQt5.QtWidgets import QWidget
import matplotlib
matplotlib.use('QtAgg')
from PyQt6 import QtCore
from PyQt6.QtWidgets import QWidget,QApplication , QLabel, QSizePolicy, QFrame, QCheckBox, QVBoxLayout, QHBoxLayout
from matplotlib.backends.backend_qt5agg import FigureCanvasQTAgg as FigureCanvas
from crosshairplt import CrossHairCursor
import mouse
import csv
NUMBERofProfiles = 4
class BelowFigLabel(QLabel):
def __init__(self):
super().__init__()
sizePolicy = QSizePolicy(QSizePolicy.Policy.Preferred, QSizePolicy.Policy.Preferred)
#sizePolicy.setHorizontalStretch(0)
sizePolicy.setVerticalStretch(0)
self.setMaximumHeight(60)
#self.setMaximumHeight(self.height()/2)
#sizePolicy.setHeightForWidth(self.sizePolicy().hasHeightForWidth())
self.setSizePolicy(sizePolicy)
#self.setTextFormat(QtCore.Qt.TextFormat.RichText)
self.setScaledContents(True)
#self.label_1.setStyleSheet("font-family: Verdana,sans-serif;font: 87 10pt \"Arial Black\";\n""background-color: rgb(30,231,235);")#;font-size: 12px;height: 28px;width: 80px
self.setStyleSheet("font-family: Verdana,sans-serif; font: 87 12pt ;font-weight:Bold;background-color: #007EA7;color:#E9D2F4")
self.setFrameShape(QFrame.Shape.Box)
self.setFrameShadow(QFrame.Shadow.Sunken)
self.setLineWidth(2)
###############################
class Canvas(FigureCanvas):
def __init__(self, parent,Horizontal,Vertical,figname,subscribt):
self.parentx = parent
self.t = Horizontal
self.s = Vertical
self.figname = figname +f"_{subscribt}"
self.fig, self.ax = plt.subplots()
super().__init__(self.fig)
self.setParent(parent)# parent must be a QWidget
self.geo = self.parentx.geometry() # used to calculate the fig location
self.x = (self.t[-1] - self.t[0])/2 #initial zome distance axis rang/2
self.belowfiglbl = BelowFigLabel()
self.colSize = self.s.shape[0] # get column size
self.slic = [self.s[i][0] for i in range(self.colSize)]
#self.cursor = CrossHairCursor(self.ax,self.t,self.slic, self.belowfiglbl)
self.ax.plot(self.t, self.s)
self.ax.set(xlabel='time (s)', ylabel='Current (A))', title=self.figname)
self.ax.grid()
self.ax.grid(True,'both','x',color='r', linestyle='-' , linewidth=1)
self.ax.grid(True,'both','y',color='b', linestyle='--', linewidth=1)
self.ax.grid()
ticks = []
self.fig.canvas.mpl_connect('scroll_event', self.on_mouse_scroll)
#self.fig.canvas.mpl_connect('motion_notify_event', self.cursor.on_mouse_move)
#self.fig.canvas.mpl_connect('button_press_event', self.cursor.on_mouse_click)
#self.fig.canvas.mpl_connect('button_release_event', self.cursor.on_mouse_click)
"""
Matplotlib Script
"""
def on_mouse_scroll(self,event):
#self.geo = self.parentx.geometry()
#print(self.geo)
self.event = event
if self.event.inaxes:
if self.event.button == 'up':
max_x = self.event.xdata + self.x
min_x = self.event.xdata - self.x
#print("P= %0.3f min = %0.3f max = %0.3f x= %0.3f" %(self.event.xdata, min_x,max_x, self.x))
if max_x > (min_x+.002): # ADD .002 MARGIN COZ when both limits get equall the widget behalves in WRONG WAY
if min_x > self.t[0] and max_x < self.t[-1]:
self.ax.set_xlim(min_x,max_x)
self.x /=2
#print("in bound")
elif min_x <= self.t[0]:
#print(" -------bound")
self.ax.set_xlim(self.t[0],max_x)
self.x /=2
else:
#print(" ++++++++++bound")
self.ax.set_xlim(min_x,self.t[-1])
self.x /=2
elif self.event.button == 'down':
x_0 = self.ax.get_xlim()
#y_0 = self.ax.get_ylim()
min_x = x_0[0]-self.x
max_x = x_0[1]+self.x
if min_x >= self.t[0] and max_x <= self.t[-1]:
self.ax.set_xlim(min_x,max_x)
self.x *=2
elif min_x < self.t[0] and max_x <= self.t[-1]:
self.ax.set_xlim(self.t[0],max_x)
self.x *=2
elif min_x>= self.t[0] and max_x > self.t[-1]:
self.ax.set_xlim(min_x,self.t[-1])
self.x *=2
else:
self.ax.set_xlim(self.t[0],self.t[-1])
#self.fig.canvas.draw()
"""
Get new Transformed co-ordinates after Canvas redraw to have updated xdata and ydata transformed
"""
DataTransformedXtoCanvas,DataTransformedYtoCanvas = self.ax.transData.transform((self.event.xdata,self.event.ydata))
"""
- The mouse object co-ordinates are the physical display
- The event object Uses the Data Co-ordinates for event.xdata,event.ydata
- When we use the transData it tranform the data Co-ordinates into fig Co-ordinates
- The Fig's are hosted in GridLayout so each figure has it on position relative to parent widget window
- The parent window has its own relative position to physical dispaly
---- so to keep the mouse pointer pointing to the DATApOINT under zoom in/out
1- we first Transform Data Coordinates into fig coordinates using .transData.transform
2- add to the coordinates in (1) the canvas.geometry()'s topleft corner position relative to parent
self.geometry().left(), self.geometry().top()
3- add the parent TopLeft corner position relative to PHYSICAL DISPLAY
"""
CavasTopCornertoParent = self.geometry().top()
CavasLefCornertoParent = self.geometry().left()
ParentTopCornertoScreen = self.parentx.geometry().top()
ParentLeftCornertoScreen = self.parentx.geometry().left()
PointerPositionX = (DataTransformedXtoCanvas + CavasLefCornertoParent + ParentLeftCornertoScreen)
PointerPositionY = (DataTransformedYtoCanvas + CavasTopCornertoParent + ParentTopCornertoScreen)
mouse.move(PointerPositionX , PointerPositionY)
def on_mouse_click(self,event):
print(event.xdata,event.ydata)
print("geo = ", self.parentx.geometry())#fig.canvas.geometry())
inverted = self.ax.transData.inverted().transform((event.x,event.y))
print(f"inverted ={inverted}, screen xy ={(event.x,event.y)}")
class AppDemo(QWidget):
def __init__(self):
super().__init__()
self.resize(1600, 800)
#self.setStyleSheet("font: 75 10pt \"Monospac821 BT\";""background-color: rgb(120,190,220);")
self.setStyleSheet("font: 75 10pt Monospac821 BT;background-color: #003459")#rgb(120,190,220)")
self.setWindowTitle("Current Oscilography")
self.s=[]
self.Ismoth=[]
for i in range(NUMBERofProfiles):
with open(f"E:/OneDrive/011_APROJECTS/digital relay/code/GitHub/Python Application/SourceCode/MODBUSfolder/loadprofiles/tstdata/LP_{i}.csv", mode='r', newline='') as profile:
a = csv.reader(profile)
x = 0
CurrentTimeSamples = [] # list holding 5 col 4 for current and 1st is for time series
for row in a:
try:
xx =[float(i) for i in row ]
CurrentTimeSamples.append(xx)
except:
CurrentTimeSamples.append(row)
#CurrentTimeSamples is 5x801 list
CurrentTimeSamples = CurrentTimeSamples[1:] # Remove Header Line
CurrentOnly = [CurrentTimeSamples[i][1:] for i in range(len(CurrentTimeSamples))] # remove 1st Col which is time series
#Isolate the time series
timex = np.array([CurrentTimeSamples[i][0] for i in range(len(CurrentTimeSamples))])
#self.s.append(CurrentOnly)
#self.s[i]=np.array(self.s[i])
# convert to Numpy
CurrentOnly = np.array(CurrentOnly)
# evenly spaced 3200 points between Xmin and X
self.time = np.linspace(timex.min(), timex.max(), 3200)
# order 3 " Quadraic" Interpolation Equation
spl = make_interp_spline(timex, CurrentOnly, k=3)
# Application of the quadratic interpolation f
self.Ismoth.append(spl(self.time))
self.charts = []
for i in range(NUMBERofProfiles):
self.charts.append(Canvas(self,self.time,self.Ismoth[i],"Profile", i))
self.MainHorLayout = QHBoxLayout(self)
#self.MainHorLayout.setSpacing(0)
self.TopHlayoutfig_0_1 = QHBoxLayout()
#self.TopHlayoutfig_0_1.setSpacing(0)
self.TopHlayoutfig_0_1.setAlignment(QtCore.Qt.AlignmentFlag.AlignTop)#QtCore.Qt.AlignmentFlag.AlignLeft|
self.TopVlayoutFig_0_Lbl =QVBoxLayout()
self.TopVlayoutFig_0_Lbl.addWidget(self.charts[0])
self.TopVlayoutFig_0_Lbl.addWidget(self.charts[0].belowfiglbl)
self.TopVlayoutFig_1_Lbl =QVBoxLayout()
self.TopVlayoutFig_1_Lbl.addWidget(self.charts[1])
self.TopVlayoutFig_1_Lbl.addWidget(self.charts[1].belowfiglbl)
self.TopHlayoutfig_0_1.addLayout(self.TopVlayoutFig_0_Lbl)
self.TopHlayoutfig_0_1.addLayout(self.TopVlayoutFig_1_Lbl)
self.BotHlayoutfig_2_3 = QHBoxLayout()
self.BotHlayoutfig_2_3.setAlignment(QtCore.Qt.AlignmentFlag.AlignTop)
self.BotVlayoutFig_2_Lbl =QVBoxLayout()
self.BotVlayoutFig_2_Lbl.addWidget(self.charts[2])
self.BotVlayoutFig_2_Lbl.addWidget(self.charts[2].belowfiglbl)
self.BotVlayoutFig_3_Lbl =QVBoxLayout()
self.BotVlayoutFig_3_Lbl.addWidget(self.charts[3])
self.BotVlayoutFig_3_Lbl.addWidget(self.charts[3].belowfiglbl)
self.BotHlayoutfig_2_3.addLayout(self.BotVlayoutFig_2_Lbl)
self.BotHlayoutfig_2_3.addLayout(self.BotVlayoutFig_3_Lbl)
self.AllfigVlayout = QVBoxLayout()
self.AllfigVlayout.addLayout(self.TopHlayoutfig_0_1)
self.AllfigVlayout.addLayout(self.BotHlayoutfig_2_3)
#self.AllfigVlayout.setSpacing(0)
self.MainHorLayout.addLayout(self.AllfigVlayout)
SideBarVLayout_main = QVBoxLayout()
LeftSidbarLabel = QLabel()
ChBoxesFrame = QFrame()
ChBoxesFrame.setStyleSheet("font-family: Verdana,sans-serif; font: 40 12pt ;font-weight:Bold;background-color: #30638E;color:#ffd166")
ChBoxesFrame.setContentsMargins(5,1,5,5)
ChBoxesFrame.setFrameShape(QFrame.Shape.Box)
ChBoxesFrame.setFrameShadow(QFrame.Shadow.Sunken)
ChBoxesFrame.setLineWidth(3)
ChBoxesFrame.setMaximumHeight(220)
ChBoxesFrame_Vlaouy = QVBoxLayout(ChBoxesFrame)
SelectActvProfilLabel = QLabel("SeLect Profiles", ChBoxesFrame)
SelectActvProfilLabel.setAlignment(QtCore.Qt.AlignmentFlag.AlignTop|QtCore.Qt.AlignmentFlag.AlignLeft)
SelectActvProfilLabel.setMaximumHeight(20)
ChBoxesFrame_Vlaouy.addWidget(SelectActvProfilLabel)
cc = QCheckBox()
self.ChkBox =[]
for i in range(NUMBERofProfiles):
self.ChkBox.append(QCheckBox(f"Profile_{i}",ChBoxesFrame))
self.ChkBox[i].setChecked(True)
self.ChkBox[i].clicked.connect(partial(self.chkBox_cliked,i))
ChBoxesFrame_Vlaouy.addWidget(self.ChkBox[i])
SideBarVLayout_main.addWidget(ChBoxesFrame)
LeftSidbarLabel.setMaximumWidth(180)
LeftSidbarLabel.setStyleSheet("font-family: Verdana,sans-serif; font: 40 12pt ;font-weight:Bold;background-color: #30638E;color:#EDAE49")
LeftSidbarLabel.setContentsMargins(10,5,5,5)
LeftSidbarLabel.setAlignment(QtCore.Qt.AlignmentFlag.AlignTop|QtCore.Qt.AlignmentFlag.AlignLeft)
LeftSidbarLabel.setFrameShape(QFrame.Shape.Box)
LeftSidbarLabel.setFrameShadow(QFrame.Shadow.Sunken)
LeftSidbarLabel.setLineWidth(3)
SideBarVLayout_main.addWidget(LeftSidbarLabel)
self.MainHorLayout.addLayout(SideBarVLayout_main, )
def chkBox_cliked(self,ckboxNum):
#for i in range(NUMBERofProfiles):
if self.ChkBox[ckboxNum].isChecked() == False:
self.charts[ckboxNum].belowfiglbl.setVisible(False)
#deleteLater()
self.charts[ckboxNum].setVisible(False)
else:
self.charts[ckboxNum].setVisible(True)# = Canvas(self,self.time,self.Ismoth[ckboxNum],"Profile",ckboxNum)
self.charts[ckboxNum].belowfiglbl.setVisible(True)
#self.MainHorLayout.update()
if __name__ == "__main__":
app = QApplication(sys.argv)
demo = AppDemo()
demo.show()#Maximized()
sys.exit(app.exec())



