Example #1
0
def loadLibrary(reloadLibs=False, libPath=None):
    """Import all Node subclasses found within files in the library module."""

    global NODE_LIST, NODE_TREE
    if libPath is None:
        libPath = os.path.dirname(os.path.abspath(__file__))

    if reloadLibs:
        reload.reloadAll(libPath)

    for f in os.listdir(libPath):
        pathName, ext = os.path.splitext(f)
        if ext != '.py' or '__init__' in pathName:
            continue
        try:
            #print "importing from", f
            mod = __import__(pathName, globals(), locals())
        except:
            printExc("Error loading flowchart library %s:" % pathName)
            continue

        nodes = []
        for n in dir(mod):
            o = getattr(mod, n)
            if isNodeClass(o):
                #print "  ", str(o)
                registerNodeType(o, [(pathName, )], override=reloadLibs)
Example #2
0
 def sendClickEvent(self, ev):
     ## if we are in mid-drag, click events may only go to the dragged item.
     if self.dragItem is not None and hasattr(self.dragItem, 'mouseClickEvent'):
         ev.currentItem = self.dragItem
         self.dragItem.mouseClickEvent(ev)
         
     ## otherwise, search near the cursor
     else:
         if self.lastHoverEvent is not None:
             acceptedItem = self.lastHoverEvent.clickItems().get(ev.button(), None)
         else:
             acceptedItem = None
             
         if acceptedItem is not None:
             ev.currentItem = acceptedItem
             try:
                 acceptedItem.mouseClickEvent(ev)
             except:
                 debug.printExc("Error sending click event:")
         else:
             for item in self.itemsNearEvent(ev):
                 if hasattr(item, 'mouseClickEvent'):
                     ev.currentItem = item
                     try:
                         item.mouseClickEvent(ev)
                     except:
                         debug.printExc("Error sending click event:")
                         
                     if ev.isAccepted():
                         break
             if not ev.isAccepted() and ev.button() is QtCore.Qt.RightButton:
                 #print "GraphicsScene emitting sigSceneContextMenu"
                 self.sigMouseClicked.emit(ev)
                 ev.accept()
     return ev.isAccepted()
Example #3
0
def loadLibrary(reloadLibs=False, libPath=None):
    """Import all Node subclasses found within files in the library module."""

    global NODE_LIST, NODE_TREE
    if libPath is None:
        libPath = os.path.dirname(os.path.abspath(__file__))
    
    if reloadLibs:
        reload.reloadAll(libPath)
    
    for f in os.listdir(libPath):
        pathName, ext = os.path.splitext(f)
        if ext != '.py' or '__init__' in pathName:
            continue
        try:
            #print "importing from", f
            mod = __import__(pathName, globals(), locals())
        except:
            printExc("Error loading flowchart library %s:" % pathName)
            continue
        
        nodes = []
        for n in dir(mod):
            o = getattr(mod, n)
            if isNodeClass(o):
                #print "  ", str(o)
                registerNodeType(o, [(pathName,)], override=reloadLibs)
Example #4
0
 def restoreTransform(self, tr):
     try:
         #self.userTranslate = pg.Point(tr['trans'])
         #self.userRotate = tr['rot']
         self.userTransform = pg.Transform(tr)
         self.updateTransform()
         
         self.selectBoxFromUser() ## move select box to match
         self.sigTransformChanged.emit(self)
         self.sigTransformChangeFinished.emit(self)
     except:
         #self.userTranslate = pg.Point([0,0])
         #self.userRotate = 0
         self.userTransform = pg.Transform()
         debug.printExc("Failed to load transform:")
Example #5
0
 def restoreTransform(self, tr):
     try:
         #self.userTranslate = pg.Point(tr['trans'])
         #self.userRotate = tr['rot']
         self.userTransform = pg.SRTTransform(tr)
         self.updateTransform()
         
         self.selectBoxFromUser() ## move select box to match
         self.sigTransformChanged.emit(self)
         self.sigTransformChangeFinished.emit(self)
     except:
         #self.userTranslate = pg.Point([0,0])
         #self.userRotate = 0
         self.userTransform = pg.SRTTransform()
         debug.printExc("Failed to load transform:")
Example #6
0
        def __call__(self, y, *args, **kwargs):
            if self.func is None:
                self.func = self.set_func()

            x = np.arange(0, y.size, 1)
            try:
                best_vals, covar = optimize.curve_fit(self.func,
                                                      x,
                                                      y,
                                                      p0=self.p0)
                return self.func(x, *best_vals)
            except RuntimeError:
                printExc()

            return np.array([])
Example #7
0
 def restoreTerminals(self, state):
     for name in list(self.terminals.keys()):
         if name not in state:
             self.removeTerminal(name)
     for name, opts in state.items():
         if name in self.terminals:
             term = self[name]
             term.setOpts(**opts)
             continue
         try:
             # opts = strDict(opts)
             self.addTerminal(name, **opts)
         except Exception:
             printExc("Error restoring terminal %s (%s):" %
                      (str(name), str(opts)))
Example #8
0
    def sendHoverEvents(self, ev, exitOnly=False):
        ## if exitOnly, then just inform all previously hovered items that the mouse has left.

        if exitOnly:
            acceptable = False
            items = []
            event = HoverEvent(None, acceptable)
        else:
            acceptable = int(
                ev.buttons()
            ) == 0  ## if we are in mid-drag, do not allow items to accept the hover event.
            event = HoverEvent(ev, acceptable)
            items = self.itemsNearEvent(event, hoverable=True)
            self.sigMouseHover.emit(items)

        prevItems = list(self.hoverItems.keys())

        for item in items:
            if hasattr(item, 'hoverEvent'):
                event.currentItem = item
                if item not in self.hoverItems:
                    self.hoverItems[item] = None
                    event.enter = True
                else:
                    prevItems.remove(item)
                    event.enter = False

                try:
                    item.hoverEvent(event)
                except:
                    debug.printExc("Error sending hover event:")

        event.enter = False
        event.exit = True
        for item in prevItems:
            event.currentItem = item
            try:
                item.hoverEvent(event)
            except:
                debug.printExc("Error sending hover exit event:")
            finally:
                del self.hoverItems[item]

        if hasattr(ev, 'buttons') and int(ev.buttons()) == 0:
            self.lastHoverEvent = event  ## save this so we can ask about accepted events later.
Example #9
0
    def applyClicked(self):
        loaded = False

        for mod, nodes in self.modules.items():
            for node in nodes:
                try:
                    self.library.addNodeType(node, [(mod.__name__, )])
                    loaded = True
                except Exception as e:
                    printExc(e)

        if not loaded:
            return

        self.ctrl.ui.clear_model(self.ctrl.ui.node_tree)
        self.ctrl.ui.create_model(self.ctrl.ui.node_tree, self.library.getLabelTree(rebuild=True))

        self.sigApplyClicked.emit()
Example #10
0
 def sendHoverEvents(self, ev, exitOnly=False):
     ## if exitOnly, then just inform all previously hovered items that the mouse has left.
     
     if exitOnly:
         acceptable=False
         items = []
         event = HoverEvent(None, acceptable)
     else:
         acceptable = int(ev.buttons()) == 0  ## if we are in mid-drag, do not allow items to accept the hover event.
         event = HoverEvent(ev, acceptable)
         items = self.itemsNearEvent(event, hoverable=True)
         self.sigMouseHover.emit(items)
         
     prevItems = list(self.hoverItems.keys())
         
     for item in items:
         if hasattr(item, 'hoverEvent'):
             event.currentItem = item
             if item not in self.hoverItems:
                 self.hoverItems[item] = None
                 event.enter = True
             else:
                 prevItems.remove(item)
                 event.enter = False
                 
             try:
                 item.hoverEvent(event)
             except:
                 debug.printExc("Error sending hover event:")
                 
     event.enter = False
     event.exit = True
     for item in prevItems:
         event.currentItem = item
         try:
             item.hoverEvent(event)
         except:
             debug.printExc("Error sending hover exit event:")
         finally:
             del self.hoverItems[item]
     
     if hasattr(ev, 'buttons') and int(ev.buttons()) == 0:
         self.lastHoverEvent = event  ## save this so we can ask about accepted events later.
Example #11
0
 def drawItemTree(self, item=None, useItemNames=False):
     if item is None:
         items = [x for x in self.items if x.parentItem() is None]
     else:
         items = item.childItems()
         items.append(item)
     items.sort(key=lambda a: a.depthValue())
     for i in items:
         if not i.visible():
             continue
         if i is item:
             try:
                 glPushAttrib(GL_ALL_ATTRIB_BITS)
                 if useItemNames:
                     glLoadName(i._id)
                     self._itemNames[i._id] = i
                 i.paint()
             except:
                 from pyqtgraph import debug
                 debug.printExc()
                 msg = "Error while drawing item %s." % str(item)
                 ver = glGetString(GL_VERSION)
                 if ver is not None:
                     ver = ver.split()[0]
                     if int(ver.split(b'.')[0]) < 2:
                         print(msg + " The original exception is printed above; however, pyqtgraph requires OpenGL version 2.0 or greater for many of its 3D features and your OpenGL version is %s. Installing updated display drivers may resolve this issue." % ver)
                     else:
                         print(msg)
                 
             finally:
                 glPopAttrib()
         else:
             glMatrixMode(GL_MODELVIEW)
             glPushMatrix()
             try:
                 tr = i.transform()
                 a = np.array(tr.copyDataTo()).reshape((4,4))
                 glMultMatrixf(a.transpose())
                 self.drawItemTree(i, useItemNames=useItemNames)
             finally:
                 glMatrixMode(GL_MODELVIEW)
                 glPopMatrix()
Example #12
0
    def sendClickEvent(self, ev):
        ## if we are in mid-drag, click events may only go to the dragged item.
        if self.dragItem is not None and hasattr(self.dragItem,
                                                 'mouseClickEvent'):
            ev.currentItem = self.dragItem
            self.dragItem.mouseClickEvent(ev)

        ## otherwise, search near the cursor
        else:
            if self.lastHoverEvent is not None:
                acceptedItem = self.lastHoverEvent.clickItems().get(
                    ev.button(), None)
            else:
                acceptedItem = None

            if acceptedItem is not None:
                ev.currentItem = acceptedItem
                try:
                    acceptedItem.mouseClickEvent(ev)
                except:
                    debug.printExc("Error sending click event:")
            else:
                for item in self.itemsNearEvent(ev):
                    if not item.isVisible() or not item.isEnabled():
                        continue
                    if hasattr(item, 'mouseClickEvent'):
                        ev.currentItem = item
                        try:
                            item.mouseClickEvent(ev)
                        except:
                            debug.printExc("Error sending click event:")

                        if ev.isAccepted():
                            if int(item.flags() & item.ItemIsFocusable) > 0:
                                item.setFocus(QtCore.Qt.MouseFocusReason)
                            break
                #if not ev.isAccepted() and ev.button() is QtCore.Qt.RightButton:
                #print "GraphicsScene emitting sigSceneContextMenu"
                #self.sigMouseClicked.emit(ev)
                #ev.accept()
        self.sigMouseClicked.emit(ev)
        return ev.isAccepted()
    def restoreState(self, state, clear=False):
        self.blockSignals(True)
        try:
            if clear:
                self.clear()
            Node.restoreState(self, state)
            nodes = state['nodes']
            #nodes.sort(lambda a, b: cmp(a['pos'][0], b['pos'][0]))
            nodes.sort(key=lambda a: a['pos'][0])
            for n in nodes:
                if n['name'] in self._nodes:
                    #self._nodes[n['name']].graphicsItem().moveBy(*n['pos'])
                    self._nodes[n['name']].restoreState(n['state'])
                    continue
                try:
                    node = self.createNode(n['class'], name=n['name'])
                    node.restoreState(n['state'])
                except:
                    printExc("Error creating node %s: (continuing anyway)" %
                             n['name'])
                #node.graphicsItem().moveBy(*n['pos'])

            self.inputNode.restoreState(state.get('inputNode', {}))
            self.outputNode.restoreState(state.get('outputNode', {}))

            #self.restoreTerminals(state['terminals'])
            for n1, t1, n2, t2 in state['connects']:
                try:
                    self.connectTerminals(self._nodes[n1][t1],
                                          self._nodes[n2][t2])
                except:
                    print(self._nodes[n1].terminals)
                    print(self._nodes[n2].terminals)
                    printExc("Error connecting terminals %s.%s - %s.%s:" %
                             (n1, t1, n2, t2))

        finally:
            self.blockSignals(False)

        self.sigChartLoaded.emit()
        self.outputChanged()
        self.sigStateChanged.emit()
Example #14
0
    def restoreState(self, state, clear=False):
        self.blockSignals(True)
        try:
            if clear:
                self.clear()
            Node.restoreState(self, state)
            nodes = state['nodes']
            nodes.sort(lambda a, b: cmp(a['pos'][0], b['pos'][0]))
            for n in nodes:
                if n['name'] in self._nodes:
                    #self._nodes[n['name']].graphicsItem().moveBy(*n['pos'])
                    self._nodes[n['name']].restoreState(n['state'])
                    continue
                try:
                    node = self.createNode(n['class'], name=n['name'])
                    node.restoreState(n['state'])
                except:
                    printExc("Error creating node %s: (continuing anyway)" %
                             n['name'])
                #node.graphicsItem().moveBy(*n['pos'])

            self.inputNode.restoreState(state.get('inputNode', {}))
            self.outputNode.restoreState(state.get('outputNode', {}))

            #self.restoreTerminals(state['terminals'])
            for n1, t1, n2, t2 in state['connects']:
                try:
                    self.connectTerminals(self._nodes[n1][t1],
                                          self._nodes[n2][t2])
                except:
                    print(self._nodes[n1].terminals)
                    print(self._nodes[n2].terminals)
                    printExc("Error connecting terminals %s.%s - %s.%s:" %
                             (n1, t1, n2, t2))

        finally:
            self.blockSignals(False)

        self.sigChartLoaded.emit()
        self.outputChanged()
        self.sigStateChanged.emit()
Example #15
0
    def sendClickEvent(self, ev):
        ## if we are in mid-drag, click events may only go to the dragged item.
        if self.dragItem is not None and hasattr(self.dragItem, "mouseClickEvent"):
            ev.currentItem = self.dragItem
            self.dragItem.mouseClickEvent(ev)

        ## otherwise, search near the cursor
        else:
            if self.lastHoverEvent is not None:
                acceptedItem = self.lastHoverEvent.clickItems().get(ev.button(), None)
            else:
                acceptedItem = None

            if acceptedItem is not None:
                ev.currentItem = acceptedItem
                try:
                    acceptedItem.mouseClickEvent(ev)
                except:
                    debug.printExc("Error sending click event:")
            else:
                for item in self.itemsNearEvent(ev):
                    if not item.isVisible() or not item.isEnabled():
                        continue
                    if hasattr(item, "mouseClickEvent"):
                        ev.currentItem = item
                        try:
                            item.mouseClickEvent(ev)
                        except:
                            debug.printExc("Error sending click event:")

                        if ev.isAccepted():
                            if int(item.flags() & item.ItemIsFocusable) > 0:
                                item.setFocus(QtCore.Qt.MouseFocusReason)
                            break
                # if not ev.isAccepted() and ev.button() is QtCore.Qt.RightButton:
                # print "GraphicsScene emitting sigSceneContextMenu"
                # self.sigMouseClicked.emit(ev)
                # ev.accept()
        self.sigMouseClicked.emit(ev)
        return ev.isAccepted()
Example #16
0
    def sendDragEvent(self, ev, init=False, final=False):
        ## Send a MouseDragEvent to the current dragItem or to
        ## items near the beginning of the drag
        event = MouseDragEvent(ev,
                               self.clickEvents[0],
                               self.lastDrag,
                               start=init,
                               finish=final)
        #print "dragEvent: init=", init, 'final=', final, 'self.dragItem=', self.dragItem
        if init and self.dragItem is None:
            if self.lastHoverEvent is not None:
                acceptedItem = self.lastHoverEvent.dragItems().get(
                    event.button(), None)
            else:
                acceptedItem = None

            if acceptedItem is not None:
                #print "Drag -> pre-selected item:", acceptedItem
                self.dragItem = acceptedItem
                event.currentItem = self.dragItem
                try:
                    self.dragItem.mouseDragEvent(event)
                except:
                    debug.printExc("Error sending drag event:")

            else:
                #print "drag -> new item"
                for item in self.itemsNearEvent(event):
                    #print "check item:", item
                    if not item.isVisible() or not item.isEnabled():
                        continue
                    if hasattr(item, 'mouseDragEvent'):
                        event.currentItem = item
                        try:
                            item.mouseDragEvent(event)
                        except:
                            debug.printExc("Error sending drag event:")
                        if event.isAccepted():
                            #print "   --> accepted"
                            self.dragItem = item
                            if int(item.flags() & item.ItemIsFocusable) > 0:
                                item.setFocus(QtCore.Qt.MouseFocusReason)
                            break
        elif self.dragItem is not None:
            event.currentItem = self.dragItem
            try:
                self.dragItem.mouseDragEvent(event)
            except:
                debug.printExc("Error sending hover exit event:")

        self.lastDrag = event

        return event.isAccepted()
Example #17
0
 def sendDragEvent(self, ev, init=False, final=False):
     ## Send a MouseDragEvent to the current dragItem or to 
     ## items near the beginning of the drag
     event = MouseDragEvent(ev, self.clickEvents[0], self.lastDrag, start=init, finish=final)
     #print "dragEvent: init=", init, 'final=', final, 'self.dragItem=', self.dragItem
     if init and self.dragItem is None:
         if self.lastHoverEvent is not None:
             acceptedItem = self.lastHoverEvent.dragItems().get(event.button(), None)
         else:
             acceptedItem = None
             
         if acceptedItem is not None:
             #print "Drag -> pre-selected item:", acceptedItem
             self.dragItem = acceptedItem
             event.currentItem = self.dragItem
             try:
                 self.dragItem.mouseDragEvent(event)
             except:
                 debug.printExc("Error sending drag event:")
                 
         else:
             #print "drag -> new item"
             for item in self.itemsNearEvent(event):
                 #print "check item:", item
                 if not item.isVisible() or not item.isEnabled():
                     continue
                 if hasattr(item, 'mouseDragEvent'):
                     event.currentItem = item
                     try:
                         item.mouseDragEvent(event)
                     except:
                         debug.printExc("Error sending drag event:")
                     if event.isAccepted():
                         #print "   --> accepted"
                         self.dragItem = item
                         if int(item.flags() & item.ItemIsFocusable) > 0:
                             item.setFocus(QtCore.Qt.MouseFocusReason)
                         break
     elif self.dragItem is not None:
         event.currentItem = self.dragItem
         try:
             self.dragItem.mouseDragEvent(event)
         except:
             debug.printExc("Error sending hover exit event:")
         
     self.lastDrag = event
     
     return event.isAccepted()
Example #18
0
    def restoreState(self, state):
        """
        Restore the state of this flowchart from a previous call to `saveState()`.
        """
        self.blockSignals(True)
        try:
            if 'source_configuration' in state:
                src_cfg = state['source_configuration']
                self.widget().sourceConfigure.restoreState(src_cfg)
                if src_cfg['files']:
                    self.widget().sourceConfigure.applyClicked()

            if 'library' in state:
                lib_cfg = state['library']
                self.widget().libraryEditor.restoreState(lib_cfg)
                self.widget().libraryEditor.applyClicked()

            if 'viewbox' in state:
                self.viewBox.restoreState(state['viewbox'])

            nodes = state['nodes']
            nodes.sort(key=lambda a: a['state']['pos'][0])
            for n in nodes:
                if n['class'] == 'SourceNode':
                    try:
                        ttype = eval(n['state']['terminals']['Out']['ttype'])
                        n['state']['terminals']['Out']['ttype'] = ttype
                        node = SourceNode(name=n['name'],
                                          terminals=n['state']['terminals'])
                        self.addNode(node=node)
                    except Exception:
                        printExc(
                            "Error creating node %s: (continuing anyway)" %
                            n['name'])
                else:
                    try:
                        node = self.createNode(n['class'], name=n['name'])
                    except Exception:
                        printExc(
                            "Error creating node %s: (continuing anyway)" %
                            n['name'])

                node.restoreState(n['state'])
                if hasattr(node, "display"):
                    node.display(topics=None, terms=None, addr=None, win=None)
                    if hasattr(node.widget,
                               'restoreState') and 'widget' in n['state']:
                        node.widget.restoreState(n['state']['widget'])

            connections = {}
            with tempfile.NamedTemporaryFile(mode='w') as type_file:
                type_file.write("from mypy_extensions import TypedDict\n")
                type_file.write("from typing import *\n")
                type_file.write("import numbers\n")
                type_file.write("import amitypes\n")
                type_file.write("T = TypeVar('T')\n\n")

                nodes = self.nodes(data='node')

                for n1, t1, n2, t2 in state['connects']:
                    try:
                        node1 = nodes[n1]
                        term1 = node1[t1]
                        node2 = nodes[n2]
                        term2 = node2[t2]

                        self.connectTerminals(term1, term2, type_file)
                        if term1.isInput() or term1.isCondition:
                            in_name = node1.name() + '_' + term1.name()
                            in_name = in_name.replace('.', '_')
                            out_name = node2.name() + '_' + term2.name()
                            out_name = out_name.replace('.', '_')
                        else:
                            in_name = node2.name() + '_' + term2.name()
                            in_name = in_name.replace('.', '_')
                            out_name = node1.name() + '_' + term1.name()
                            out_name = out_name.replace('.', '_')

                        connections[(in_name, out_name)] = (term1, term2)
                    except Exception:
                        print(node1.terminals)
                        print(node2.terminals)
                        printExc("Error connecting terminals %s.%s - %s.%s:" %
                                 (n1, t1, n2, t2))

                type_file.flush()
                status = subprocess.run(
                    ["mypy", "--follow-imports", "silent", type_file.name],
                    capture_output=True,
                    text=True)
                if status.returncode != 0:
                    lines = status.stdout.split('\n')[:-1]
                    for line in lines:
                        m = re.search(r"\"+(\w+)\"+", line)
                        if m:
                            m = m.group().replace('"', '')
                            for i in connections:
                                if i[0] == m:
                                    term1, term2 = connections[i]
                                    term1.disconnectFrom(term2)
                                    break
                                elif i[1] == m:
                                    term1, term2 = connections[i]
                                    term1.disconnectFrom(term2)
                                    break

        finally:
            self.blockSignals(False)

        for name, node in self.nodes(data='node'):
            self.sigNodeChanged.emit(node)
Example #19
0
    async def applyClicked(self, build_views=True):
        graph_nodes = []
        disconnectedNodes = []
        displays = set()

        msg = QtWidgets.QMessageBox(parent=self)
        msg.setText("Failed to submit graph! See status.")

        if self.chart.deleted_nodes:
            await self.graphCommHandler.remove(self.chart.deleted_nodes)
            self.chart.deleted_nodes = []

        # detect if the manager has no graph (e.g. from a purge on failure)
        if await self.graphCommHandler.graphVersion == 0:
            # mark all the nodes as changed to force a resubmit of the whole graph
            for name, gnode in self.chart._graph.nodes().items():
                gnode = gnode['node']
                gnode.changed = True
            # reset reference counting on views
            await self.features.reset()

        outputs = [n for n, d in self.chart._graph.out_degree() if d == 0]
        changed_nodes = set()
        failed_nodes = set()
        seen = set()

        for name, gnode in self.chart._graph.nodes().items():
            gnode = gnode['node']
            if not gnode.enabled():
                continue

            if not gnode.hasInput():
                disconnectedNodes.append(gnode)
                continue
            elif gnode.exception:
                gnode.clearException()
                gnode.recolor()

            if gnode.changed and gnode not in changed_nodes:
                changed_nodes.add(gnode)

                if not hasattr(gnode, 'to_operation'):
                    if gnode.isSource() and gnode.viewable() and gnode.viewed:
                        displays.add(gnode)
                    elif gnode.exportable():
                        displays.add(gnode)
                    continue

                for output in outputs:
                    paths = list(
                        nx.algorithms.all_simple_paths(self.chart._graph, name,
                                                       output))

                    if not paths:
                        paths = [[output]]

                    for path in paths:
                        for gnode in path:
                            gnode = self.chart._graph.nodes[gnode]
                            node = gnode['node']

                            if hasattr(node,
                                       'to_operation') and node not in seen:
                                try:
                                    nodes = node.to_operation(
                                        inputs=node.input_vars(),
                                        conditions=node.condition_vars())
                                except Exception:
                                    self.chartWidget.updateStatus(
                                        f"{node.name()} error!", color='red')
                                    printExc(
                                        f"{node.name()} raised exception! See console for stacktrace."
                                    )
                                    node.setException(True)
                                    failed_nodes.add(node)
                                    continue

                                seen.add(node)

                                if type(nodes) is list:
                                    graph_nodes.extend(nodes)
                                else:
                                    graph_nodes.append(nodes)

                            if (node.viewable()
                                    or node.buffered()) and node.viewed:
                                displays.add(node)

        if disconnectedNodes:
            for node in disconnectedNodes:
                self.chartWidget.updateStatus(f"{node.name()} disconnected!",
                                              color='red')
                node.setException(True)
            msg.exec()
            return

        if failed_nodes:
            self.chartWidget.updateStatus("failed to submit graph",
                                          color='red')
            msg.exec()
            return

        if graph_nodes:
            await self.graphCommHandler.add(graph_nodes)

            node_names = ', '.join(
                set(map(lambda node: node.parent, graph_nodes)))
            self.chartWidget.updateStatus(f"Submitted {node_names}")

        node_names = ', '.join(set(map(lambda node: node.name(), displays)))
        if displays and build_views:
            self.chartWidget.updateStatus(f"Redisplaying {node_names}")
            await self.chartWidget.build_views(displays,
                                               export=True,
                                               redisplay=True)

        for node in changed_nodes:
            node.changed = False

        self.metadata = await self.graphCommHandler.metadata
        self.ui.setPendingClear()

        version = str(await self.graphCommHandler.graphVersion)
        state = self.chart.saveState()
        state = json.dumps(state,
                           indent=2,
                           separators=(',', ': '),
                           sort_keys=True,
                           cls=amitypes.TypeEncoder)
        self.graph_info.labels(self.chart.hutch, self.graph_name).info({
            'graph':
            state,
            'version':
            version
        })