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
Example #2
0
    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)
Example #3
0
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)
Example #5
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
Example #6
0
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)