def createTurnTableVoltageControlGroup(self): self.motorVoltageSlider = DoubleSlider(orientation=Qt.Orientation.Horizontal) self.motorVoltageSlider.setTickPosition(QSlider.TickPosition.TicksBothSides) self.motorVoltageSlider.setTickInterval(200) self.motorVoltageSlider.setEnabled(False) self.motorVoltageSliderValueSpinBox = createDoubleSpinBox() self.motorVoltageSliderValueSpinBox.setEnabled(False) self.motorVoltageSliderValueSpinBox.valueChanged.connect(self.motorVoltageSlider.setValue) self.motorVoltageSlider.doubleValueChanged.connect(self.motorVoltageSliderValueSpinBox.setValue) self.resetVoltageButton = QPushButton("Reset") self.resetVoltageButton.setEnabled(False) self.resetVoltageButton.setShortcut(QKeySequence(Qt.CTRL + Qt.Key_R)) self.resetVoltageButton.pressed.connect(lambda: self.motorVoltageSlider.setValue(0.000)) sliderGridLayout = QGridLayout() sliderGridLayout.addWidget(QLabel("Min Voltage"), 0, 0, 1, 1) sliderGridLayout.addWidget(QLabel("Max Voltage"), 0, 4, 1, 1) sliderGridLayout.addWidget(self.motorVoltageSlider, 1, 0, 1, 5) sliderGridLayout.addWidget(self.motorVoltageSliderValueSpinBox, 2, 1, 1, 1) sliderGridLayout.addWidget(self.resetVoltageButton, 2, 4, 1, 1) voltageControlGroupBox = QGroupBox() voltageControlGroupBox.setLayout(sliderGridLayout) return voltageControlGroupBox
def __init__(self, parent): ''' Constructor ''' qt.QToolBar.__init__(self, parent) self.setIconSize(qt.QSize(25, 25)) self.zoomAutoAction = qt.QAction(qt.QIcon('Icones/autozoom.png'), '&Zoom', self) self.zoomAutoAction.setStatusTip('Fit window') self.zoomAutoAction.setCheckable(False) self.zoomAutoAction.setChecked(False) self.zoomActive = False qt.QObject.connect(self.zoomAutoAction, qt.SIGNAL("triggered()"), self.zoomAutoPushed) self.zone1Action = qt.QAction(qt.QIcon('Icones/zone.png'), '&ZoneSelection', self) self.zone1Action.setStatusTip('Select Zone') self.zone1Action.setCheckable(True) self.zone1Action.setChecked(False) qt.QObject.connect(self.zone1Action, qt.SIGNAL("triggered()"), self.zone1Selected) self.pointerAction = qt.QAction(qt.QIcon('Icones/cursor.png'), '&PointerSelection', self) self.pointerAction.setStatusTip('Select Pointer') self.pointerAction.setCheckable(True) self.pointerAction.setChecked(True) qt.QObject.connect(self.pointerAction, qt.SIGNAL("triggered()"), self.pointerSelected) self.drawingAction = qt.QAction(qt.QIcon('Icones/circle18.png'), '&DrawingSelection', self) self.drawingAction.setStatusTip('Select Drawing') self.drawingAction.setCheckable(True) self.drawingAction.setChecked(False) qt.QObject.connect(self.drawingAction, qt.SIGNAL("triggered()"), self.drawingSelected) self.polygonAction = qt.QAction(qt.QIcon('Icones/polygon.png'), '&PolygonSelection', self) self.polygonAction.setStatusTip('Select Polygone') self.polygonAction.setCheckable(True) self.polygonAction.setChecked(False) qt.QObject.connect(self.polygonAction, qt.SIGNAL("triggered()"), self.polygonSelected) self.pointRemoveAction = qt.QAction(qt.QIcon('Icones/remove.png'), '&DeletePoint', self) self.pointRemoveAction.setStatusTip('remove Point') self.pointRemoveAction.setCheckable(False) self.pointRemoveAction.setChecked(False) self.radius = LabelEditAndButton(True, "", True, str(100), False) self.doubleSlider = DoubleSlider(self) self.setMinAndMaxToolBar(0, 0) self.doubleSlider.setMaximumWidth(800) self.colorChoice = qt.QComboBox() self.colormapList = [] colorMapDefault = "GrayLevel", range(256), range(256), range(256) self.colormapList.append(colorMapDefault) self.colorChoice.addItems([self.colormapList[0][0]]) self.addColorMap('Jet', './jet_color.txt') self.addAction(self.zoomAutoAction) self.addSeparator() self.addAction(self.pointerAction) self.addAction(self.zone1Action) self.addAction(self.drawingAction) self.ActionRadius = self.addWidget(self.radius) self.ActionRadius.setVisible(False) self.addAction(self.polygonAction) self.addAction(self.pointRemoveAction) self.addWidget(self.doubleSlider) self.addWidget(self.colorChoice)
class CustomToolBar(qt.QToolBar): def __init__(self, parent): ''' Constructor ''' qt.QToolBar.__init__(self, parent) self.setIconSize(qt.QSize(25, 25)) self.zoomAutoAction = qt.QAction(qt.QIcon('Icones/autozoom.png'), '&Zoom', self) self.zoomAutoAction.setStatusTip('Fit window') self.zoomAutoAction.setCheckable(False) self.zoomAutoAction.setChecked(False) self.zoomActive = False qt.QObject.connect(self.zoomAutoAction, qt.SIGNAL("triggered()"), self.zoomAutoPushed) self.zone1Action = qt.QAction(qt.QIcon('Icones/zone.png'), '&ZoneSelection', self) self.zone1Action.setStatusTip('Select Zone') self.zone1Action.setCheckable(True) self.zone1Action.setChecked(False) qt.QObject.connect(self.zone1Action, qt.SIGNAL("triggered()"), self.zone1Selected) self.pointerAction = qt.QAction(qt.QIcon('Icones/cursor.png'), '&PointerSelection', self) self.pointerAction.setStatusTip('Select Pointer') self.pointerAction.setCheckable(True) self.pointerAction.setChecked(True) qt.QObject.connect(self.pointerAction, qt.SIGNAL("triggered()"), self.pointerSelected) self.drawingAction = qt.QAction(qt.QIcon('Icones/circle18.png'), '&DrawingSelection', self) self.drawingAction.setStatusTip('Select Drawing') self.drawingAction.setCheckable(True) self.drawingAction.setChecked(False) qt.QObject.connect(self.drawingAction, qt.SIGNAL("triggered()"), self.drawingSelected) self.polygonAction = qt.QAction(qt.QIcon('Icones/polygon.png'), '&PolygonSelection', self) self.polygonAction.setStatusTip('Select Polygone') self.polygonAction.setCheckable(True) self.polygonAction.setChecked(False) qt.QObject.connect(self.polygonAction, qt.SIGNAL("triggered()"), self.polygonSelected) self.pointRemoveAction = qt.QAction(qt.QIcon('Icones/remove.png'), '&DeletePoint', self) self.pointRemoveAction.setStatusTip('remove Point') self.pointRemoveAction.setCheckable(False) self.pointRemoveAction.setChecked(False) self.radius = LabelEditAndButton(True, "", True, str(100), False) self.doubleSlider = DoubleSlider(self) self.setMinAndMaxToolBar(0, 0) self.doubleSlider.setMaximumWidth(800) self.colorChoice = qt.QComboBox() self.colormapList = [] colorMapDefault = "GrayLevel", range(256), range(256), range(256) self.colormapList.append(colorMapDefault) self.colorChoice.addItems([self.colormapList[0][0]]) self.addColorMap('Jet', './jet_color.txt') self.addAction(self.zoomAutoAction) self.addSeparator() self.addAction(self.pointerAction) self.addAction(self.zone1Action) self.addAction(self.drawingAction) self.ActionRadius = self.addWidget(self.radius) self.ActionRadius.setVisible(False) self.addAction(self.polygonAction) self.addAction(self.pointRemoveAction) self.addWidget(self.doubleSlider) self.addWidget(self.colorChoice) def setMinAndMaxToolBar(self, min, max): self.doubleSlider.minSlider.setRange(min, max) self.doubleSlider.minSlider.setValue(min) self.doubleSlider.maxSlider.setRange(min, max) self.doubleSlider.maxSlider.setValue(max) self.doubleSlider.setMinMax(min, max) def zoomPushed(self): if (self.zoomAutoAction.isChecked() == True): self.zoomAutoAction.setChecked(False) self.zoomActive = False else: self.zoomAction.setChecked(True) self.zoomActive = True def zoomAutoPushed(self): self.zone1Action.setChecked(False) self.pointerAction.setChecked(False) def zone1Selected(self): self.ActionRadius.setVisible(0) if (self.pointerAction.isChecked() == True): self.pointerAction.setChecked(False) if (self.drawingAction.isChecked() == True): self.drawingAction.setChecked(False) if self.polygonAction.isChecked() == True: self.polygonAction.setChecked(False) def pointerSelected(self): self.ActionRadius.setVisible(0) if (self.zone1Action.isChecked() == True): self.zone1Action.setChecked(False) if (self.drawingAction.isChecked() == True): self.drawingAction.setChecked(False) if self.polygonAction.isChecked() == True: self.polygonAction.setChecked(False) def drawingSelected(self): self.ActionRadius.setVisible(1) if (self.pointerAction.isChecked() == True): self.pointerAction.setChecked(False) if (self.zone1Action.isChecked() == True): self.zone1Action.setChecked(False) if self.polygonAction.isChecked() == True: self.polygonAction.setChecked(False) def polygonSelected(self): self.ActionRadius.setVisible(0) if (self.pointerAction.isChecked() == True): self.pointerAction.setChecked(False) if (self.zone1Action.isChecked() == True): self.zone1Action.setChecked(False) if (self.drawingAction.isChecked() == True): self.drawingAction.setChecked(False) def addColorMap(self, name, path): f = open(path) R = [] G = [] B = [] for line in f: listColor = line.split(' ') R.append((float(listColor[0]) * 255)) G.append((float(listColor[1]) * 255)) B.append((float(listColor[2]) * 255)) NewcolorMap = name, R, G, B self.colormapList.append(NewcolorMap) self.colorChoice.addItem(name)
class MainView(QMainWindow): applicationClosed = Signal() def __init__(self): super().__init__() menuBar = self.menuBar() menuBar.addMenu(self.createFileMenu()) menuBar.addMenu(self.createSettingsMenu()) menuBar.addMenu(self.createHelpMenu()) self.statusBar().showMessage("Ready") connectionStatusGroupBox = self.createConnectionStatusGroup() positionIndicationGroupBox = self.createPositionIndicationGroup() turnTableControlGroupBox = self.createTurnTableControlGroup() mainWidgetLayout = QVBoxLayout() mainWidgetLayout.addWidget(connectionStatusGroupBox) mainWidgetLayout.addWidget(positionIndicationGroupBox) mainWidgetLayout.addWidget(turnTableControlGroupBox) mainWidgetGroup = QGroupBox() mainWidgetGroup.setLayout(mainWidgetLayout) self.setCentralWidget(mainWidgetGroup) #------------------------------------------------------------------------------ # Interface Creation Helper Functions #------------------------------------------------------------------------------ def createFileMenu(self): self.loadZeroPositionDataAction = QAction("Load Zero Position", self) self.saveZeroPositionDataAction = QAction("Save Zero Position", self) exitAction = QAction( "E&xit", self, shortcut="Ctrl+Q", statusTip="Exit the application", triggered=self.close ) fileMenu = QMenu("File") fileMenu.addAction(self.loadZeroPositionDataAction) fileMenu.addAction(self.saveZeroPositionDataAction) fileMenu.addSeparator() fileMenu.addAction(exitAction) return fileMenu def createSettingsMenu(self): self.applicationSettingsAction = QAction("Settings", self) settingsManagerMenu = QMenu("Settings") settingsManagerMenu.addAction(self.applicationSettingsAction) return settingsManagerMenu def createHelpMenu(self): helpViewAction = QAction("Help", self) aboutViewAction = QAction("About", self) helpMenu = QMenu("Help") helpMenu.addAction(helpViewAction) helpMenu.addAction(aboutViewAction) return helpMenu def createPositionIndicationGroup(self): self.currentPositionLineEdit = createReadOnlyLineEdit("+0.000") self.targetPositionLineEdit = createReadOnlyLineEdit("+0.000") self.positionErrorLineEdit = createReadOnlyLineEdit("+0.000") self.setCurrentPositionAsZeroButton = QPushButton("Set As Zero") self.resetZeroPositionButton = QPushButton("Reset Zero") positionInformationLayout = QGridLayout() positionInformationLayout.addWidget(createPositionLabel("Current"), 0, 0) positionInformationLayout.addWidget(createPositionLabel("Target"), 0, 1) positionInformationLayout.addWidget(createPositionLabel("Error"), 0, 2) positionInformationLayout.addWidget(self.currentPositionLineEdit, 1, 0) positionInformationLayout.addWidget(self.targetPositionLineEdit, 1, 1) positionInformationLayout.addWidget(self.positionErrorLineEdit, 1, 2) positionInformationLayout.addWidget(self.setCurrentPositionAsZeroButton, 2, 0) positionInformationLayout.addWidget(self.resetZeroPositionButton, 3, 0) positionGroupBox = QGroupBox("Azimuth") positionGroupBox.setLayout(positionInformationLayout) return positionGroupBox def createTurnTableControlGroup(self): self.gotoPositionSpinBox = createDoubleSpinBox() self.stepSizeSpinBox = createDoubleSpinBox() self.goPushButton = QPushButton("Go") self.goPushButton.setEnabled(False) self.goPushButton.setShortcut(QKeySequence(Qt.CTRL + Qt.Key_G)) self.stepPushButton = QPushButton("Step") self.stepPushButton.setEnabled(False) self.stepPushButton.setShortcut(QKeySequence(Qt.CTRL + Qt.Key_S)) self.enableSteppingCheckBox = QCheckBox("Enable Stepping") self.enableSteppingCheckBox.setEnabled(False) self.enableSteppingCheckBox.clicked.connect(self.stepPushButton.setEnabled) self.stopPushButton = QPushButton("STOP") self.stopPushButton.setEnabled(False) self.stopPushButton.pressed.connect(lambda: self.motorVoltageSlider.setValue(0.000)) self.stopPushButton.setShortcut(QKeySequence(Qt.CTRL + Qt.Key_T)) turnTableVoltageControlGroup = self.createTurnTableVoltageControlGroup() turnTableControlGridLayout = QGridLayout() turnTableControlGridLayout.addWidget(QLabel("Go To Position:"), 0, 0) turnTableControlGridLayout.addWidget(self.gotoPositionSpinBox, 0, 1) turnTableControlGridLayout.addWidget(self.goPushButton, 0, 2) turnTableControlGridLayout.addWidget(QLabel("Step Size:"), 1, 0) turnTableControlGridLayout.addWidget(self.stepSizeSpinBox, 1, 1) turnTableControlGridLayout.addWidget(self.stepPushButton, 1, 2) turnTableControlGridLayout.addWidget(self.enableSteppingCheckBox, 1, 3) turnTableControlGridLayout.addWidget(self.stopPushButton, 2, 3) turnTableControlGridLayout.addWidget(turnTableVoltageControlGroup, 3, 0, 3, 4) turnTableGroupBox = QGroupBox("Turn Table Controls") turnTableGroupBox.setLayout(turnTableControlGridLayout) return turnTableGroupBox def createTurnTableVoltageControlGroup(self): self.motorVoltageSlider = DoubleSlider(orientation=Qt.Orientation.Horizontal) self.motorVoltageSlider.setTickPosition(QSlider.TickPosition.TicksBothSides) self.motorVoltageSlider.setTickInterval(200) self.motorVoltageSlider.setEnabled(False) self.motorVoltageSliderValueSpinBox = createDoubleSpinBox() self.motorVoltageSliderValueSpinBox.setEnabled(False) self.motorVoltageSliderValueSpinBox.valueChanged.connect(self.motorVoltageSlider.setValue) self.motorVoltageSlider.doubleValueChanged.connect(self.motorVoltageSliderValueSpinBox.setValue) self.resetVoltageButton = QPushButton("Reset") self.resetVoltageButton.setEnabled(False) self.resetVoltageButton.setShortcut(QKeySequence(Qt.CTRL + Qt.Key_R)) self.resetVoltageButton.pressed.connect(lambda: self.motorVoltageSlider.setValue(0.000)) sliderGridLayout = QGridLayout() sliderGridLayout.addWidget(QLabel("Min Voltage"), 0, 0, 1, 1) sliderGridLayout.addWidget(QLabel("Max Voltage"), 0, 4, 1, 1) sliderGridLayout.addWidget(self.motorVoltageSlider, 1, 0, 1, 5) sliderGridLayout.addWidget(self.motorVoltageSliderValueSpinBox, 2, 1, 1, 1) sliderGridLayout.addWidget(self.resetVoltageButton, 2, 4, 1, 1) voltageControlGroupBox = QGroupBox() voltageControlGroupBox.setLayout(sliderGridLayout) return voltageControlGroupBox def createConnectionStatusGroup(self): connectionStatusGroup = QGroupBox("Connection Status") self.shaftEncoderConnectionStatusLineEdit = createReadOnlyLineEdit("Disconnected") self.motorControllerConnectionStatusLineEdit = createReadOnlyLineEdit("Disconnected") self.watchDogConnectionStatusLineEdit = createReadOnlyLineEdit("Disconnected") self.tcpServerConnectionStatusLineEdit = createReadOnlyLineEdit("Disconnected") self.connectButton = QPushButton("Connect") self.disconnectButton = QPushButton("Disconnect") self.disconnectButton.setEnabled(False) connectionButtonLayout = QHBoxLayout() connectionButtonLayout.addWidget(self.connectButton) connectionButtonLayout.addWidget(self.disconnectButton) connectionStatusLayout = QFormLayout() connectionStatusLayout.addRow(QLabel("Shaft Encoder: "), self.shaftEncoderConnectionStatusLineEdit) connectionStatusLayout.addRow(QLabel("Motor Controller: "), self.motorControllerConnectionStatusLineEdit) connectionStatusLayout.addRow(QLabel("Watchdog: "), self.watchDogConnectionStatusLineEdit) connectionStatusLayout.addRow(QLabel("TCP Server: "), self.tcpServerConnectionStatusLineEdit) connectionStatusLayout.addRow(connectionButtonLayout) connectionStatusGroup.setLayout(connectionStatusLayout) return connectionStatusGroup def closeEvent(self, event): messageBox = QMessageBox() messageBox.setText("The Turn Table is currently still running.") messageBox.setInformativeText("Are you sure you want to exit?") messageBox.setStandardButtons(QMessageBox.Ok | QMessageBox.Cancel) messageBox.setDefaultButton(QMessageBox.Cancel) response = messageBox.exec_() if response == QMessageBox.Ok: self.applicationClosed.emit() event.accept() elif response == QMessageBox.Cancel: event.ignore() #------------------------------------------------------------------------------ # Callback functions for handling GUI events #------------------------------------------------------------------------------ def updatePositionLineEdits(self, newPosition): updatePositionLineEdit(self.currentPositionLineEdit, newPosition.current) updatePositionLineEdit(self.targetPositionLineEdit, newPosition.target) updatePositionLineEdit(self.positionErrorLineEdit, newPosition.error) def updateConnectionStatusLineEdits(self, shaftEncoder: bool, motorController: bool, watchdog: bool, tcpServer: bool): updateConnectionStatusLineEdit(self.shaftEncoderConnectionStatusLineEdit, shaftEncoder) updateConnectionStatusLineEdit(self.motorControllerConnectionStatusLineEdit, motorController) updateConnectionStatusLineEdit(self.watchDogConnectionStatusLineEdit, watchdog) updateConnectionStatusLineEdit(self.tcpServerConnectionStatusLineEdit, tcpServer) def toggleControls(self): toggleControlEnable(self.enableSteppingCheckBox) toggleControlEnable(self.stopPushButton) toggleControlEnable(self.goPushButton) toggleControlEnable(self.connectButton) toggleControlEnable(self.disconnectButton) toggleControlEnable(self.motorVoltageSlider) toggleControlEnable(self.motorVoltageSliderValueSpinBox) toggleControlEnable(self.resetVoltageButton)
def __init__(self, width, height): self.path = config.find_options("general.cfg")['database'] self.descriptors = config.find_graphs("graphs.cfg") self.data = {} self.collections = {} self.nav_collections = {} self.color_map = {} self.connection = sql.connect(self.path, detect_types=(sql.PARSE_COLNAMES|sql.PARSE_DECLTYPES)) self.root = Tk.Tk() self.root.wm_title("Historical Data Viewer") self.intervals = self.connection.execute("SELECT * FROM intervals ORDER BY start;").fetchall() self.intervals = list(reversed(self.intervals)) #Grid layout: # Col 0 Col 1 # +------------+------+ # Row 0 | | ITVL | # | ZOOM | SLCT | # | - -GRAPH- -+------+ # Row 1 | | DATA | # | | SLCT | # +------------+------+ # Row 2 | NAV GRAPH | ZOOM | # | | SLCT | # +------------+------+ #Setup for the Tk/Agg plotting backend self.figure = Figure(figsize = (width+1, height+2), dpi = 100) self.figure.subplots_adjust(bottom=0.15) self.axes = self.figure.add_subplot(111, autoscale_on=True) #self.axes.xaxis.labelpad *= 3 self.canvas = FigureCanvasTkAgg(self.figure, master=self.root) self.canvas.get_tk_widget().grid(row=0, column=0, rowspan=2) #Setup for the mini-navigation figure self.nav_figure = Figure(figsize = ((width+1)*1.05, 1.8), dpi = 100) #self.nav_figure.subplots_adjust(bottom=0.3) self.nav_axes = self.nav_figure.add_subplot(111, autoscale_on=False) #self.nav_axes.xaxis.labelpad *= 2 self.nav_canvas = FigureCanvasTkAgg(self.nav_figure, master=self.root) self.nav_canvas.get_tk_widget().grid(row=2, column=0) self.nav_axes.yaxis.set_major_locator(ticker.NullLocator()) #Setup for the slider def scale_to_data(left, right, x): return from_ordinal(to_ordinal(left) + x * (to_ordinal(right) - to_ordinal(left))) def data_to_scale(left, right, x): return (to_ordinal(x) - to_ordinal(left)) / (to_ordinal(right) - to_ordinal(left)) self.slider = DoubleSlider(self.root, left_bound=datetime(1999,1,1), right_bound=datetime(1999,1,28), data_to_scale=data_to_scale, scale_to_data=scale_to_data, set_on_drag=False, on_change=self.update_bounds, on_drag=lambda left, right: self.update_bounds(left, right, True)) self.slider.grid(row=3, column=0, sticky=W+E) #Setup for the choosing the interval we want to examine self.interval_frame = Tk.Frame(self.root) self.interval_frame.grid(row=0, column=1, sticky=N+E) picker_label = Tk.Label(self.interval_frame, text="Data Intervals") picker_label.grid(row=0, column=0, sticky=N) self.interval_picker = Tk.Listbox(self.interval_frame, selectmode=Tk.BROWSE, exportselection=0) self.interval_picker.grid(row=1, column=0, sticky=N+W+E) width = 20 for (name, start, end) in self.intervals: string = format_span(start, end) width = max(width, len(string)) self.interval_picker.insert(Tk.END, string) self.interval_picker.config(width=width) self.interval_picker.bind("<Double-Button-1>", self.select_interval) self.interval_scrollbar = Tk.Scrollbar(self.interval_frame, command=self.interval_picker.yview) self.interval_picker.config(yscrollcommand=self.interval_scrollbar.set) self.interval_scrollbar.grid(row=1, column=1, sticky=N+S+W) picker_button = Tk.Button(self.interval_frame, text="Select interval", command=self.select_interval) picker_button.grid(row=2, column=0, sticky=N+W) #----Variables for picking the table from the database-------------- self.source_frame = Tk.Frame(self.root) self.source_frame.config(relief=Tk.GROOVE, borderwidth=2) self.source_frame.grid(row=1, column=1, sticky=N+W+E+S) source_label = Tk.Label(self.source_frame, text="Show/Hide plots") source_label.grid(column=0, columnspan=2, sticky=N) def set_var_callback(cb, data, checkbutton, var=None): if var is None: var = Tk.IntVar() def wrapper_callback(): var.set(var.get()) return cb(data, var.get()) checkbutton.config(variable=var, command=wrapper_callback) self.checkbuttons = [] for i,desc in enumerate(self.descriptors): checkbutton = Tk.Checkbutton(self.source_frame, text=desc.title) toggle = Tk.Checkbutton(self.source_frame, text=" ") color = self.colors[i % len(self.colors)] checkbutton.config(relief=Tk.SUNKEN, indicatoron=False) toggle.config(indicatoron=False, background=tk_color(lighten(color)), highlightbackground=tk_color(color), activebackground=tk_color(color), selectcolor=tk_color(color), state=ACTIVE) var = Tk.IntVar() set_var_callback(self.toggle_source, desc, checkbutton, var) set_var_callback(self.toggle_source, desc, toggle, var) self.color_map[desc.table_name] = color toggle.grid(column=0, row=i+1, sticky=N+W) checkbutton.grid(column=1, row=i+1, sticky=N+W) self.checkbuttons.append(checkbutton) self.style_count = 0 self.min_y = self.descriptors[0].min_y self.max_y = self.descriptors[0].max_y
class HistoricalViewerApp: line_styles = ["solid"] colors = [ (0.0, 0.0, 1.0, 1.0), #Blue (1.0, 0.0, 0.0, 1.0), #Red (0.0, 1.0, 0.0, 1.0), #Green (1.0, 1.0, 0.0, 1.0) ] def __init__(self, width, height): self.path = config.find_options("general.cfg")['database'] self.descriptors = config.find_graphs("graphs.cfg") self.data = {} self.collections = {} self.nav_collections = {} self.color_map = {} self.connection = sql.connect(self.path, detect_types=(sql.PARSE_COLNAMES|sql.PARSE_DECLTYPES)) self.root = Tk.Tk() self.root.wm_title("Historical Data Viewer") self.intervals = self.connection.execute("SELECT * FROM intervals ORDER BY start;").fetchall() self.intervals = list(reversed(self.intervals)) #Grid layout: # Col 0 Col 1 # +------------+------+ # Row 0 | | ITVL | # | ZOOM | SLCT | # | - -GRAPH- -+------+ # Row 1 | | DATA | # | | SLCT | # +------------+------+ # Row 2 | NAV GRAPH | ZOOM | # | | SLCT | # +------------+------+ #Setup for the Tk/Agg plotting backend self.figure = Figure(figsize = (width+1, height+2), dpi = 100) self.figure.subplots_adjust(bottom=0.15) self.axes = self.figure.add_subplot(111, autoscale_on=True) #self.axes.xaxis.labelpad *= 3 self.canvas = FigureCanvasTkAgg(self.figure, master=self.root) self.canvas.get_tk_widget().grid(row=0, column=0, rowspan=2) #Setup for the mini-navigation figure self.nav_figure = Figure(figsize = ((width+1)*1.05, 1.8), dpi = 100) #self.nav_figure.subplots_adjust(bottom=0.3) self.nav_axes = self.nav_figure.add_subplot(111, autoscale_on=False) #self.nav_axes.xaxis.labelpad *= 2 self.nav_canvas = FigureCanvasTkAgg(self.nav_figure, master=self.root) self.nav_canvas.get_tk_widget().grid(row=2, column=0) self.nav_axes.yaxis.set_major_locator(ticker.NullLocator()) #Setup for the slider def scale_to_data(left, right, x): return from_ordinal(to_ordinal(left) + x * (to_ordinal(right) - to_ordinal(left))) def data_to_scale(left, right, x): return (to_ordinal(x) - to_ordinal(left)) / (to_ordinal(right) - to_ordinal(left)) self.slider = DoubleSlider(self.root, left_bound=datetime(1999,1,1), right_bound=datetime(1999,1,28), data_to_scale=data_to_scale, scale_to_data=scale_to_data, set_on_drag=False, on_change=self.update_bounds, on_drag=lambda left, right: self.update_bounds(left, right, True)) self.slider.grid(row=3, column=0, sticky=W+E) #Setup for the choosing the interval we want to examine self.interval_frame = Tk.Frame(self.root) self.interval_frame.grid(row=0, column=1, sticky=N+E) picker_label = Tk.Label(self.interval_frame, text="Data Intervals") picker_label.grid(row=0, column=0, sticky=N) self.interval_picker = Tk.Listbox(self.interval_frame, selectmode=Tk.BROWSE, exportselection=0) self.interval_picker.grid(row=1, column=0, sticky=N+W+E) width = 20 for (name, start, end) in self.intervals: string = format_span(start, end) width = max(width, len(string)) self.interval_picker.insert(Tk.END, string) self.interval_picker.config(width=width) self.interval_picker.bind("<Double-Button-1>", self.select_interval) self.interval_scrollbar = Tk.Scrollbar(self.interval_frame, command=self.interval_picker.yview) self.interval_picker.config(yscrollcommand=self.interval_scrollbar.set) self.interval_scrollbar.grid(row=1, column=1, sticky=N+S+W) picker_button = Tk.Button(self.interval_frame, text="Select interval", command=self.select_interval) picker_button.grid(row=2, column=0, sticky=N+W) #----Variables for picking the table from the database-------------- self.source_frame = Tk.Frame(self.root) self.source_frame.config(relief=Tk.GROOVE, borderwidth=2) self.source_frame.grid(row=1, column=1, sticky=N+W+E+S) source_label = Tk.Label(self.source_frame, text="Show/Hide plots") source_label.grid(column=0, columnspan=2, sticky=N) def set_var_callback(cb, data, checkbutton, var=None): if var is None: var = Tk.IntVar() def wrapper_callback(): var.set(var.get()) return cb(data, var.get()) checkbutton.config(variable=var, command=wrapper_callback) self.checkbuttons = [] for i,desc in enumerate(self.descriptors): checkbutton = Tk.Checkbutton(self.source_frame, text=desc.title) toggle = Tk.Checkbutton(self.source_frame, text=" ") color = self.colors[i % len(self.colors)] checkbutton.config(relief=Tk.SUNKEN, indicatoron=False) toggle.config(indicatoron=False, background=tk_color(lighten(color)), highlightbackground=tk_color(color), activebackground=tk_color(color), selectcolor=tk_color(color), state=ACTIVE) var = Tk.IntVar() set_var_callback(self.toggle_source, desc, checkbutton, var) set_var_callback(self.toggle_source, desc, toggle, var) self.color_map[desc.table_name] = color toggle.grid(column=0, row=i+1, sticky=N+W) checkbutton.grid(column=1, row=i+1, sticky=N+W) self.checkbuttons.append(checkbutton) self.style_count = 0 self.min_y = self.descriptors[0].min_y self.max_y = self.descriptors[0].max_y def run(self): left_pad = self.nav_figure.subplotpars.left * self.nav_figure.get_figwidth() * self.nav_figure.dpi right_pad = (1-self.nav_figure.subplotpars.right) * self.nav_figure.get_figwidth() * self.nav_figure.dpi self.slider.config(left_padding=left_pad, right_padding=right_pad) self.figure.canvas.draw() self.nav_figure.canvas.draw() self.slider.init() self.select_interval(index=0) self.interval_picker.selection_set(first=0) for desc, checkbutton in zip(self.descriptors, self.checkbuttons): checkbutton.select() self.toggle_source(desc, True) locator = KenLocator(7.8) self.nav_axes.xaxis.set_major_locator(locator) self.nav_axes.xaxis.set_major_formatter(KenFormatter(locator)) locator = KenLocator(7.8) self.axes.xaxis.set_major_locator(locator) self.axes.xaxis.set_major_formatter(KenFormatter(locator)) self.root.mainloop() def update_bounds(self, left, right, dragging=False): if not dragging: self.axes.set_xbound(to_ordinal(left), to_ordinal(right)) self.axes.set_xlabel(format_span(left, right) + " (" + format_timedelta(right - left) + ")") agg_canvas = self.nav_canvas.get_tk_widget() agg_canvas.delete("OVERLAY") # c1 c3 # +---+---------+---+ # |...| _/\ _ |...| # |...|/ \_/ \|...| # +---+---------+---+ # c2 c4 cx2, cy2 = self.nav_axes.transData.transform_point((to_ordinal(left), self.max_y)).tolist() cx3, cy3 = self.nav_axes.transData.transform_point((to_ordinal(right), self.min_y)).tolist() agg_canvas.create_rectangle([cx2, cy2, cx3, cy3], tags="OVERLAY", stipple = "gray50", fill = "green") ## cx1, cy1 = self.nav_axes.transData.transform_point((to_ordinal(self.slider.left_bound), self.min_y)).tolist() ## cx2, cy2 = self.nav_axes.transData.transform_point((to_ordinal(left), self.max_y)).tolist() ## agg_canvas.create_rectangle([(cx1+1, cy1+2), (cx2, cy2)], ## tags="OVERLAY", ## stipple="gray50", ## fill="red", outline="") ## ## cx3, cy3 = self.nav_axes.transData.transform_point((to_ordinal(right), self.min_y)).tolist() ## cx4, cy4 = self.nav_axes.transData.transform_point((to_ordinal(self.slider.right_bound), self.max_y)).tolist() ## agg_canvas.create_rectangle([(cx3, cy3+2), (cx4, cy4)], ## tags="OVERLAY", ## stipple="gray50", ## fill="red", outline="") if not dragging: self.figure.canvas.draw() self.nav_figure.canvas.draw() def select_interval(self, index=None): #Support direct invocation, invocation by event, and as a command if not isinstance(index, int): if not self.interval_picker.curselection(): return index = int(self.interval_picker.curselection()[0]) name, start, end = self.intervals[index] for identifier in self.data: view = self.data[identifier] view.load(start, end) self.collections[identifier].set_segments([view.export()]) self.collections[identifier].set_antialiased(False) self.nav_collections[identifier].set_segments([view.export()]) self.slider.config(left_bound=start, right_bound=end) self.slider.reset() self.nav_axes.set_xbound(to_ordinal(start), to_ordinal(end)) self.nav_axes.set_xlabel("%s (%s)" % (format_span(start, end), format_timedelta(end - start))) self.redraw() def toggle_source(self, desc, enabled): if desc.table_name in self.data: self.nav_collections[desc.table_name].set_visible(enabled) self.collections[desc.table_name].set_visible(enabled) self.redraw() elif enabled: self.axes.set_ylabel(desc.units) self.add_source(desc.table_name, desc.title) self.min_y = min(self.min_y, desc.min_y) self.max_y = max(self.max_y, desc.max_y) self.axes.set_ybound(self.min_y,self.max_y) self.nav_axes.set_ybound(self.min_y, self.max_y) ## self.axes.legend(loc=3) self.redraw() def redraw(self): self.figure.canvas.draw() self.nav_figure.canvas.draw() def add_source(self, identifier, title): left, right = sorted([self.slider.left_bound, self.slider.right_bound]) view = SQLIntervalView(self.connection, identifier, left, right) self.data[identifier] = view colors = [self.color_map.get(identifier, self.colors[0])] col = DatetimeCollection([view.export()], colors=colors) col.set_label(title) self.collections[identifier] = col col2 = DatetimeCollection([view.export()], colors=colors) self.nav_collections[identifier] = col2 self.axes.add_collection(col) self.nav_axes.add_collection(col2)