GraphUI.clear()   A
last analyzed

Complexity

Conditions 1

Size

Total Lines 2

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 1
Metric Value
cc 1
c 1
b 0
f 1
dl 0
loc 2
rs 10
1
#! /usr/bin/env python3
2
3
4
5
import logging
6
from PyQt5.QtCore import QTimer, Qt
7
from PyQt5.QtWidgets import QLabel
8
9
from opcua import ua
10
from opcua import Node
11
12
from uawidgets.utils import trycatchslot
13
14
use_graph = True
15
try:
16
    import pyqtgraph as pg
17
    import numpy as np
18
except ImportError:
19
    print("pyqtgraph or numpy are not installed, use of graph feature disabled")
20
    use_graph = False
21
22
23
24
if use_graph:
25
    pg.setConfigOptions(antialias=True)
26
    pg.setConfigOption('background', 'w')
27
    pg.setConfigOption('foreground', 'k')
28
29
logger = logging.getLogger(__name__)
30
31
32
class GraphUI(object):
33
34
    # use tango color schema (public domain)
35
    colorCycle = ['#4e9a06ff', '#ce5c00ff', '#3465a4ff', '#75507bff', '#cc0000ff', '#edd400ff']
36
    acceptedDatatypes = ['Decimal128', 'Double', 'Float', 'Integer', 'UInteger']
37
38
    def __init__(self, window, uaclient):
39
        self.window = window
40
        self.uaclient = uaclient
41
42
        # exit if the modules are not present
43
        if not use_graph:
44
            self.window.ui.graphLayout.addWidget(QLabel("pyqtgraph or numpy not installed"))
45
            return
46
        self._node_list = [] # holds the nodes to poll
47
        self._channels = [] # holds the actual data
48
        self._curves = [] # holds the curve objects
49
        self.pw = pg.PlotWidget(name='Plot1')
50
        self.pw.showGrid(x = True, y = True, alpha = 0.3)
51
        self.legend = self.pw.addLegend()
52
        self.window.ui.graphLayout.addWidget(self.pw)
53
54
        self.window.ui.actionAddToGraph.triggered.connect(self._add_node_to_channel)
55
        self.window.ui.actionRemoveFromGraph.triggered.connect(self._remove_node_from_channel)
56
57
        # populate contextual menu
58
        self.window.ui.treeView.addAction(self.window.ui.actionAddToGraph)
59
        self.window.ui.treeView.addAction(self.window.ui.actionRemoveFromGraph)
60
61
        # connect Apply button
62
        self.window.ui.buttonApply.clicked.connect(self.restartTimer)
63
        self.restartTimer()
64
65
66
    def restartTimer(self):
67
        # stop current timer, if it exists
68
        if hasattr(self ,'timer') and self.timer.isActive():
69
            self.timer.stop()
70
71
        # define the number of polls displayed in graph
72
        self.N = self.window.ui.spinBoxNumberOfPoints.value()
73
        self.ts = np.arange(self.N)
74
        # define the poll intervall
75
        self.intervall = self.window.ui.spinBoxIntervall.value( ) *1000
76
77
        # overwrite current channel buffers with zeros of current length and add to curves again
78
        for i ,channel in enumerate(self._channels):
79
            self._channels[i] = np.zeros(self.N)
80
            self._curves[i].setData(self._channels[i])
81
82
        # starting new timer
83
        self.timer = QTimer()
84
        self.timer.setInterval(self.intervall)
85
        self.timer.timeout.connect(self.pushtoGraph)
86
        self.timer.start()
87
88
    @trycatchslot
89
    def _add_node_to_channel(self ,node=None):
90
        if not isinstance(node, Node):
91
            node = self.window.get_current_node()
92
            if node is None:
93
                return
94
        if node not in self._node_list:
95
            dtype = node.get_attribute(ua.AttributeIds.DataType)
96
97
            dtypeStr = ua.ObjectIdNames[dtype.Value.Value.Identifier]
98
99
100
101
            if dtypeStr in self.acceptedDatatypes and not isinstance(node.get_value() ,list):
102
                self._node_list.append(node)
103
                displayName = node.get_display_name().Text
104
                colorIndex = len(self._node_list) % len(self.colorCycle)
105
                self._curves.append \
106
                    (self.pw.plot(pen=pg.mkPen(color=self.colorCycle[colorIndex] ,width=3 ,style=Qt.SolidLine), name=displayName))
107
                # set initial data to zero
108
                self._channels.append(np.zeros(self.N)) # init data sequence with zeros
109
                # add the new channel data to the new curve
110
                self._curves[-1].setData(self._channels[-1])
111
                logger.info("Variable %s added to graph", displayName)
112
113
            else:
114
                logger.info("Variable cannot be added to graph because it is of type %s or an array", dtypeStr)
115
116
117
    @trycatchslot
118
    def _remove_node_from_channel(self ,node=None):
119
        if not isinstance(node, Node):
120
            node = self.window.get_current_node()
121
            if node is None:
122
                return
123
        if node in self._node_list:
124
            idx = self._node_list.index(node)
125
            self._node_list.pop(idx)
126
            displayName = node.get_display_name().Text
127
            self.legend.removeItem(displayName)
128
            self.pw.removeItem(self._curves[idx])
129
            self._curves.pop(idx)
130
            self._channels.pop(idx)
131
132
133
    def pushtoGraph(self):
134
        # ringbuffer: shift and replace last
135
        for i ,node in enumerate(self._node_list):
136
            self._channels[i] = np.roll(self._channels[i] ,-1) # shift elements to the left by one
137
            self._channels[i][-1] = float(node.get_value())
138
            self._curves[i].setData(self.ts ,self._channels[i])
139
140
141
    def clear(self):
142
        pass
143
144
145
    def show_error(self, *args):
146
        self.window.show_error(*args)
147