コード例 #1
0
 def __init__(self):
     super().__init__()
     self.n_object_types = 0
     self.n_relations = 0
     self.relations = {}  # id-->relation map
     self.graph = FusionGraph(fusion.FusionGraph())
     self.graphview = FusionGraphView(self)
     self.graphview.selectionChanged.connect(self.on_graph_element_selected)
     self._create_layout()
コード例 #2
0
 def __init__(self):
     super().__init__()
     self.n_object_types = 0
     self.n_relations = 0
     self.relations = {}  # id-->relation map
     self.graph_element_selected.connect(self.on_graph_element_selected)
     self.graph = FusionGraph(fusion.FusionGraph())
     self.webview = WebviewWidget(self.mainArea)
     self._create_layout()
コード例 #3
0
    def on_relation_change(self, relation, id):
        def _on_remove_relation(id):
            try:
                relation = self.relations.pop(id)
            except KeyError:
                return
            self.graph.remove_relation(relation)

        def _on_add_relation(relation, id):
            _on_remove_relation(id)
            self.relations[id] = relation
            self.graph.add_relation(relation)

        if relation:
            _on_add_relation(relation.relation, id)
        else:
            _on_remove_relation(id)

        self.graphview.clear()
        for relation in self.graph.relations:
            self.graphview.addRelation(relation)
        self.graphview.hideSquares()

        self._populate_table()
        LIMIT_RANK_THRESHOLD = 1000  # If so many objects or more, limit maximum rank
        self.slider_rank.setMaximum(30 if any(
            max(rel.data.shape) > LIMIT_RANK_THRESHOLD
            for rel in self.graph.relations) else 100)
        self.send(Output.FUSION_GRAPH, FusionGraph(self.graph))
        # this ensures gui.label-s get updated
        self.n_object_types = self.graph.n_object_types
        self.n_relations = self.graph.n_relations
コード例 #4
0
    def on_relation_change(self, relation, id):
        def _on_remove_relation(id):
            try: relation = self.relations.pop(id)
            except KeyError: return
            self.graph.remove_relation(relation)

        def _on_add_relation(relation, id):
            _on_remove_relation(id)
            self.relations[id] = relation
            self.graph.add_relation(relation)

        if relation:
            _on_add_relation(relation.relation, id)
        else:
            _on_remove_relation(id)
        self._populate_table()
        self.slider_rank.setMaximum(30
                                    if any(max(rel.data.shape) > LIMIT_RANK_THRESHOLD
                                           for rel in self.graph.relations)
                                    else
                                    100)
        self.webview.repaint(self.graph, self)
        self.send(Output.FUSION_GRAPH, FusionGraph(self.graph))
        # this ensures gui.label-s get updated
        self.n_object_types = self.graph.n_object_types
        self.n_relations = self.graph.n_relations
コード例 #5
0
 def __init__(self):
     super().__init__()
     self.n_object_types = 0
     self.n_relations = 0
     self.relations = {}  # id-->relation map
     self.graph = FusionGraph(fusion.FusionGraph())
     self.graphview = FusionGraphView(self)
     self.graphview.selectionChanged.connect(self.on_graph_element_selected)
     self._create_layout()
コード例 #6
0
ファイル: owfusiongraph.py プロジェクト: r0b1n1983liu/o3env
 def __init__(self):
     super().__init__()
     self.n_object_types = 0
     self.n_relations = 0
     self.relations = {}  # id-->relation map
     self.graph_element_selected.connect(self.on_graph_element_selected)
     self.graph = FusionGraph(fusion.FusionGraph())
     self.webview = gui.WebviewWidget(self.mainArea, self)
     self._create_layout()
コード例 #7
0
class OWFusionGraph(widget.OWWidget):
    name = "Fusion Graph"
    description = "Construct data fusion graph and run " \
                  "collective matrix factorization."
    priority = 10000
    icon = "icons/FusionGraph.svg"
    inputs = [("Relation", Relation, "on_relation_change", widget.Multiple)]
    outputs = [
        (Output.RELATION, Relation),
        (Output.FUSER, FittedFusionGraph, widget.Default),
        (Output.FUSION_GRAPH, FusionGraph),
    ]

    pref_algo_name = settings.Setting('')
    pref_algorithm = settings.Setting(0)
    pref_initialization = settings.Setting(0)
    pref_n_iterations = settings.Setting(10)
    pref_rank = settings.Setting(10)
    autorun = settings.Setting(False)

    def __init__(self):
        super().__init__()
        self.n_object_types = 0
        self.n_relations = 0
        self.relations = {}  # id-->relation map
        self.graph = FusionGraph(fusion.FusionGraph())
        self.graphview = FusionGraphView(self)
        self.graphview.selectionChanged.connect(self.on_graph_element_selected)
        self._create_layout()

    def on_graph_element_selected(self, selected):
        if not selected:
            return self._populate_table()
        selected_is_edge = isinstance(selected[0], Edge)
        # Update the control table table
        if selected_is_edge:
            edge = next(i for i in selected if isinstance(i, Edge))
            nodes = self.graph.get_selected_nodes([edge.source.name, edge.dest.name])
            relations = self.graph.get_relations(*nodes)
        else:
            node = next(i for i in selected if isinstance(i, Node))
            nodes = self.graph.get_selected_nodes([node.name])
            relations = (set(i for i in self.graph.in_relations(nodes[0])) |
                         set(i for i in self.graph.out_relations(nodes[0])))
        self._populate_table(relations)

    def _create_layout(self):
        self.mainArea.layout().addWidget(self.graphview)
        info = gui.widgetBox(self.controlArea, 'Info')
        gui.label(info, self, '%(n_object_types)d object types')
        gui.label(info, self, '%(n_relations)d relations')
        # Table view of relation details
        info = gui.widgetBox(self.controlArea, 'Relations')

        class TableView(gui.TableView):
            def __init__(self, parent=None, **kwargs):
                super().__init__(parent, **kwargs)
                self._parent = parent
                self.bold_font = self.BoldFontDelegate(self)   # member because PyQt sometimes unrefs too early
                self.setItemDelegateForColumn(2, self.bold_font)
                self.setItemDelegateForColumn(4, self.bold_font)
                self.horizontalHeader().setVisible(False)

            def selectionChanged(self, selected, deselected):
                super().selectionChanged(selected, deselected)
                if not selected:
                    assert len(deselected) > 0
                    relation = None
                else:
                    assert len(selected) == 1
                    data = self._parent.tablemodel[selected[0].top()][0]
                    relation = Relation(data)
                self._parent.send(Output.RELATION, relation)

        model = self.tablemodel = PyTableModel(parent=self)
        table = self.table = TableView(self,
                                       selectionMode=TableView.SingleSelection)
        table.setModel(model)
        info.layout().addWidget(self.table)

        gui.lineEdit(self.controlArea,
                     self, 'pref_algo_name', 'Fuser name:',
                     orientation='horizontal',
                     callback=self.checkcommit, enterPlaceholder=True)
        gui.radioButtons(self.controlArea,
                         self, 'pref_algorithm', [i[0] for i in DECOMPOSITION_ALGO],
                         box='Decomposition algorithm',
                         callback=self.checkcommit)
        gui.radioButtons(self.controlArea,
                         self, 'pref_initialization', INITIALIZATION_ALGO,
                         box='Initialization algorithm',
                         callback=self.checkcommit)
        slider = gui.hSlider(
            self.controlArea, self, 'pref_n_iterations',
            'Maximum number of iterations',
            minValue=10, maxValue=500, createLabel=True,
            callback=self.checkcommit)
        slider.setTracking(False)
        self.slider_rank = gui.hSlider(self.controlArea, self, 'pref_rank',
                                       'Factorization rank',
                                       minValue=1, maxValue=100, createLabel=True,
                                       labelFormat=" %d%%",
                                       callback=self.checkcommit)
        self.slider_rank.setTracking(False)
        gui.auto_commit(self.controlArea, self, "autorun", "Run",
                        checkbox_label="Run after any change  ")

    def checkcommit(self):
        return self.commit()

    def commit(self):
        self.progressbar = gui.ProgressBar(self, self.pref_n_iterations)
        Algo = DECOMPOSITION_ALGO[self.pref_algorithm][1]
        init_type = INITIALIZATION_ALGO[self.pref_initialization].lower().replace(' ', '_')
        # Update rank on object-types
        maxrank = defaultdict(int)
        for rel in self.graph.relations:
            rows, cols = rel.data.shape
            row_type, col_type = rel.row_type, rel.col_type
            if rows > maxrank[row_type]:
                maxrank[row_type] = row_type.rank = max(5, int(rows * (self.pref_rank / 100)))
            if cols > maxrank[col_type]:
                maxrank[col_type] = col_type.rank = max(5, int(cols * (self.pref_rank / 100)))
        # Run the algo ...
        try:
            self.fuser = Algo(init_type=init_type,
                              max_iter=self.pref_n_iterations,
                              random_state=0,
                              callback=lambda *args: self.progressbar.advance()).fuse(self.graph)
        finally:
            self.progressbar.finish()
        self.fuser.name = self.pref_algo_name
        self.send(Output.FUSER, FittedFusionGraph(self.fuser))

    def _populate_table(self, relations=None):
        self.tablemodel.wrap([[rel, rel_shape(rel.data)] + rel_cols(rel)
                              for rel in relations or self.graph.relations])
        self.table.hideColumn(0)
        self.table.selectRow(0)

    def on_relation_change(self, relation, id):
        def _on_remove_relation(id):
            try: relation = self.relations.pop(id)
            except KeyError: return
            self.graph.remove_relation(relation)

        def _on_add_relation(relation, id):
            _on_remove_relation(id)
            self.relations[id] = relation
            self.graph.add_relation(relation)

        if relation:
            _on_add_relation(relation.relation, id)
        else:
            _on_remove_relation(id)

        self.graphview.clear()
        for relation in self.graph.relations:
            self.graphview.addRelation(relation)
        self.graphview.hideSquares()

        self._populate_table()
        LIMIT_RANK_THRESHOLD = 1000  # If so many objects or more, limit maximum rank
        self.slider_rank.setMaximum(30
                                    if any(max(rel.data.shape) > LIMIT_RANK_THRESHOLD
                                           for rel in self.graph.relations)
                                    else
                                    100)
        self.send(Output.FUSION_GRAPH, FusionGraph(self.graph))
        # this ensures gui.label-s get updated
        self.n_object_types = self.graph.n_object_types
        self.n_relations = self.graph.n_relations

    # called when all signals are received, so the graph is updated only once
    def handleNewSignals(self):
        self.commit()
コード例 #8
0
ファイル: owfusiongraph.py プロジェクト: r0b1n1983liu/o3env
class OWFusionGraph(widget.OWWidget):
    name = "Fusion Graph"
    description = "Construct data fusion graph and run " \
                  "collective matrix factorization."
    priority = 10000
    icon = "icons/FusionGraph.svg"
    inputs = [("Relation", Relation, "on_relation_change", widget.Multiple)]
    outputs = [
        (Output.RELATION, Relation),
        (Output.FUSER, FittedFusionGraph, widget.Default),
        (Output.FUSION_GRAPH, FusionGraph),
    ]

    # Signal emitted when a node in the SVG is selected, carrying its name
    graph_element_selected = QtCore.pyqtSignal(str)

    pref_algo_name = settings.Setting('')
    pref_algorithm = settings.Setting(0)
    pref_initialization = settings.Setting(0)
    pref_n_iterations = settings.Setting(10)
    pref_rank = settings.Setting(10)
    autorun = settings.Setting(False)

    def __init__(self):
        super().__init__()
        self.n_object_types = 0
        self.n_relations = 0
        self.relations = {}  # id-->relation map
        self.graph_element_selected.connect(self.on_graph_element_selected)
        self.graph = FusionGraph(fusion.FusionGraph())
        self.webview = gui.WebviewWidget(self.mainArea, self)
        self._create_layout()

    @QtCore.pyqtSlot(str)
    def on_graph_element_selected(self, element_id):
        """Handle self.graph_element_selected signal, and highlight also:
           * if edge was selected, the two related nodes,
           * if node was selected, all its edges.
           Additionally, update the info box.
        """
        if not element_id:
            return self._populate_table()
        selected_is_edge = element_id.startswith('edge ')
        nodes = self.graph.get_selected_nodes(element_id)
        # CSS selector query for selection-relevant nodes
        selector = ','.join('[id^="node "][id*="`%s`"]' % n.name for n in nodes)
        # If a node was selected, include the edges that connect to it
        if not selected_is_edge:
            selector += ',[id^="edge "][id*="`%s`"]' % nodes[0].name
        # Highlight these additional elements
        self.webview.evalJS("highlight('%s');" % selector)
        # Update the control table table
        if selected_is_edge:
            relations = self.graph.get_relations(*nodes)
        else:
            relations = (set(i for i in self.graph.in_relations(nodes[0])) |
                         set(i for i in self.graph.out_relations(nodes[0])))
        self._populate_table(relations)

    def _create_layout(self):
        info = gui.widgetBox(self.controlArea, 'Info')
        gui.label(info, self, '%(n_object_types)d object types')
        gui.label(info, self, '%(n_relations)d relations')
        # Table view of relation details
        info = gui.widgetBox(self.controlArea, 'Relations')

        def send_relation(selected, deselected):
            if not selected:
                assert len(deselected) > 0
                relation = None
            else:
                assert len(selected) == 1
                data = self.table.rowData(selected[0].top())
                relation = Relation(data)
            self.send(Output.RELATION, relation)

        self.table = gui.TableWidget(info, select_rows=True)
        self.table.selectionChanged = send_relation
        self.table.setColumnFilter(bold_item, (1, 3))

        self.controlArea.layout().addStretch(1)
        gui.lineEdit(self.controlArea,
                     self, 'pref_algo_name', 'Fuser name',
                     callback=self.checkcommit, enterPlaceholder=True)
        gui.radioButtons(self.controlArea,
                         self, 'pref_algorithm', [i[0] for i in DECOMPOSITION_ALGO],
                         box='Decomposition algorithm',
                         callback=self.checkcommit)
        gui.radioButtons(self.controlArea,
                         self, 'pref_initialization', INITIALIZATION_ALGO,
                         box='Initialization algorithm',
                         callback=self.checkcommit)
        gui.hSlider(self.controlArea, self, 'pref_n_iterations',
                    'Maximum number of iterations',
                    minValue=10, maxValue=500, createLabel=True,
                    callback=self.checkcommit)
        self.slider_rank = gui.hSlider(self.controlArea, self, 'pref_rank',
                                       'Factorization rank',
                                       minValue=1, maxValue=100, createLabel=True,
                                       labelFormat=" %d%%",
                                       callback=self.checkcommit)
        gui.auto_commit(self.controlArea, self, "autorun", "Run",
                        checkbox_label="Run after any change  ")

    def checkcommit(self):
        return self.commit()

    def commit(self):
        self.progressbar = gui.ProgressBar(self, self.pref_n_iterations)
        Algo = DECOMPOSITION_ALGO[self.pref_algorithm][1]
        init_type = INITIALIZATION_ALGO[self.pref_initialization].lower().replace(' ', '_')
        # Update rank on object-types
        maxrank = defaultdict(int)
        for rel in self.graph.relations:
            rows, cols = rel.data.shape
            row_type, col_type = rel.row_type, rel.col_type
            if rows > maxrank[row_type]:
                maxrank[row_type] = row_type.rank = max(5, int(rows * (self.pref_rank / 100)))
            if cols > maxrank[col_type]:
                maxrank[col_type] = col_type.rank = max(5, int(cols * (self.pref_rank / 100)))
        # Run the algo ...
        self.fuser = Algo(init_type=init_type,
                          max_iter=self.pref_n_iterations,
                          random_state=0,
                          callback=lambda *args: self.progressbar.advance()).fuse(self.graph)
        self.progressbar.finish()
        self.fuser.name = self.pref_algo_name
        self.send(Output.FUSER, FittedFusionGraph(self.fuser))

    def _populate_table(self, relations=None):
        self.table.clear()
        for rel in relations or self.graph.relations:
            self.table.addRow([rel_shape(rel.data)] + rel_cols(rel), data=rel)
        self.table.selectFirstRow()

    def on_relation_change(self, relation, id):
        def _on_remove_relation(id):
            try: relation = self.relations.pop(id)
            except KeyError: return
            self.graph.remove_relation(relation)

        def _on_add_relation(relation, id):
            _on_remove_relation(id)
            self.relations[id] = relation
            self.graph.add_relation(relation)

        if relation:
            _on_add_relation(relation.relation, id)
        else:
            _on_remove_relation(id)
        self._populate_table()
        LIMIT_RANK_THRESHOLD = 1000  # If so many objects or more, limit maximum rank
        self.slider_rank.setMaximum(30
                                    if any(max(rel.data.shape) > LIMIT_RANK_THRESHOLD
                                           for rel in self.graph.relations)
                                    else
                                    100)
        redraw_graph(self.webview, self.graph)
        self.send(Output.FUSION_GRAPH, FusionGraph(self.graph))
        # this ensures gui.label-s get updated
        self.n_object_types = self.graph.n_object_types
        self.n_relations = self.graph.n_relations

    # called when all signals are received, so the graph is updated only once
    def handleNewSignals(self):
        self.commit()
コード例 #9
0
class OWFusionGraph(widget.OWWidget):
    name = "Fusion Graph"
    description = "Construct data fusion graph and run " \
                  "collective matrix factorization."
    priority = 10000
    icon = "icons/FusionGraph.svg"
    inputs = [("Relation", Relation, "on_relation_change", widget.Multiple)]
    outputs = [
        (Output.RELATION, Relation),
        (Output.FUSER, FittedFusionGraph, widget.Default),
        (Output.FUSION_GRAPH, FusionGraph),
    ]

    pref_algo_name = settings.Setting('')
    pref_algorithm = settings.Setting(0)
    pref_initialization = settings.Setting(0)
    pref_n_iterations = settings.Setting(10)
    pref_rank = settings.Setting(10)
    autorun = settings.Setting(False)

    def __init__(self):
        super().__init__()
        self.n_object_types = 0
        self.n_relations = 0
        self.relations = {}  # id-->relation map
        self.graph = FusionGraph(fusion.FusionGraph())
        self.graphview = FusionGraphView(self)
        self.graphview.selectionChanged.connect(self.on_graph_element_selected)
        self._create_layout()

    def on_graph_element_selected(self, selected):
        if not selected:
            return self._populate_table()
        selected_is_edge = isinstance(selected[0], Edge)
        # Update the control table table
        if selected_is_edge:
            edge = next(i for i in selected if isinstance(i, Edge))
            nodes = self.graph.get_selected_nodes(
                [edge.source.name, edge.dest.name])
            relations = self.graph.get_relations(*nodes)
        else:
            node = next(i for i in selected if isinstance(i, Node))
            nodes = self.graph.get_selected_nodes([node.name])
            relations = (set(i for i in self.graph.in_relations(nodes[0]))
                         | set(i for i in self.graph.out_relations(nodes[0])))
        self._populate_table(relations)

    def _create_layout(self):
        self.mainArea.layout().addWidget(self.graphview)
        info = gui.widgetBox(self.controlArea, 'Info')
        gui.label(info, self, '%(n_object_types)d object types')
        gui.label(info, self, '%(n_relations)d relations')
        # Table view of relation details
        info = gui.widgetBox(self.controlArea, 'Relations')

        class TableView(gui.TableView):
            def __init__(self, parent=None, **kwargs):
                super().__init__(parent, **kwargs)
                self._parent = parent
                self.bold_font = self.BoldFontDelegate(
                    self)  # member because PyQt sometimes unrefs too early
                self.setItemDelegateForColumn(2, self.bold_font)
                self.setItemDelegateForColumn(4, self.bold_font)
                self.horizontalHeader().setVisible(False)

            def selectionChanged(self, selected, deselected):
                super().selectionChanged(selected, deselected)
                if not selected:
                    assert len(deselected) > 0
                    relation = None
                else:
                    assert len(selected) == 1
                    data = self._parent.tablemodel[selected[0].top()][0]
                    relation = Relation(data)
                self._parent.send(Output.RELATION, relation)

        model = self.tablemodel = PyTableModel(parent=self)
        table = self.table = TableView(self,
                                       selectionMode=TableView.SingleSelection)
        table.setModel(model)
        info.layout().addWidget(self.table)

        gui.lineEdit(self.controlArea,
                     self,
                     'pref_algo_name',
                     'Fuser name:',
                     orientation='horizontal',
                     callback=self.checkcommit,
                     enterPlaceholder=True)
        gui.radioButtons(self.controlArea,
                         self,
                         'pref_algorithm', [i[0] for i in DECOMPOSITION_ALGO],
                         box='Decomposition algorithm',
                         callback=self.checkcommit)
        gui.radioButtons(self.controlArea,
                         self,
                         'pref_initialization',
                         INITIALIZATION_ALGO,
                         box='Initialization algorithm',
                         callback=self.checkcommit)
        slider = gui.hSlider(self.controlArea,
                             self,
                             'pref_n_iterations',
                             'Maximum number of iterations',
                             minValue=10,
                             maxValue=500,
                             createLabel=True,
                             callback=self.checkcommit)
        slider.setTracking(False)
        self.slider_rank = gui.hSlider(self.controlArea,
                                       self,
                                       'pref_rank',
                                       'Factorization rank',
                                       minValue=1,
                                       maxValue=100,
                                       createLabel=True,
                                       labelFormat=" %d%%",
                                       callback=self.checkcommit)
        self.slider_rank.setTracking(False)
        gui.auto_commit(self.controlArea,
                        self,
                        "autorun",
                        "Run",
                        checkbox_label="Run after any change  ")

    def checkcommit(self):
        return self.commit()

    def commit(self):
        self.progressbar = gui.ProgressBar(self, self.pref_n_iterations)
        Algo = DECOMPOSITION_ALGO[self.pref_algorithm][1]
        init_type = INITIALIZATION_ALGO[
            self.pref_initialization].lower().replace(' ', '_')
        # Update rank on object-types
        maxrank = defaultdict(int)
        for rel in self.graph.relations:
            rows, cols = rel.data.shape
            row_type, col_type = rel.row_type, rel.col_type
            if rows > maxrank[row_type]:
                maxrank[row_type] = row_type.rank = max(
                    5, int(rows * (self.pref_rank / 100)))
            if cols > maxrank[col_type]:
                maxrank[col_type] = col_type.rank = max(
                    5, int(cols * (self.pref_rank / 100)))
        # Run the algo ...
        try:
            self.fuser = Algo(
                init_type=init_type,
                max_iter=self.pref_n_iterations,
                random_state=0,
                callback=lambda *args: self.progressbar.advance()).fuse(
                    self.graph)
        finally:
            self.progressbar.finish()
        self.fuser.name = self.pref_algo_name
        self.send(Output.FUSER, FittedFusionGraph(self.fuser))

    def _populate_table(self, relations=None):
        self.tablemodel.wrap([[rel, rel_shape(rel.data)] + rel_cols(rel)
                              for rel in relations or self.graph.relations])
        self.table.hideColumn(0)
        self.table.selectRow(0)

    def on_relation_change(self, relation, id):
        def _on_remove_relation(id):
            try:
                relation = self.relations.pop(id)
            except KeyError:
                return
            self.graph.remove_relation(relation)

        def _on_add_relation(relation, id):
            _on_remove_relation(id)
            self.relations[id] = relation
            self.graph.add_relation(relation)

        if relation:
            _on_add_relation(relation.relation, id)
        else:
            _on_remove_relation(id)

        self.graphview.clear()
        for relation in self.graph.relations:
            self.graphview.addRelation(relation)
        self.graphview.hideSquares()

        self._populate_table()
        LIMIT_RANK_THRESHOLD = 1000  # If so many objects or more, limit maximum rank
        self.slider_rank.setMaximum(30 if any(
            max(rel.data.shape) > LIMIT_RANK_THRESHOLD
            for rel in self.graph.relations) else 100)
        self.send(Output.FUSION_GRAPH, FusionGraph(self.graph))
        # this ensures gui.label-s get updated
        self.n_object_types = self.graph.n_object_types
        self.n_relations = self.graph.n_relations

    # called when all signals are received, so the graph is updated only once
    def handleNewSignals(self):
        self.commit()
コード例 #10
0
class OWFusionGraph(widget.OWWidget):
    name = "Fusion Graph"
    description = "Construct data fusion graph and run " \
                  "collective matrix factorization."
    priority = 10000
    icon = "icons/FusionGraph.svg"
    inputs = [("Relation", Relation, "on_relation_change", widget.Multiple)]
    outputs = [
        (Output.RELATION, Relation),
        (Output.FUSER, FittedFusionGraph, widget.Default),
        (Output.FUSION_GRAPH, FusionGraph),
    ]

    # Signal emitted when a node in the SVG is selected, carrying its name
    graph_element_selected = QtCore.pyqtSignal(str)

    pref_algo_name = settings.Setting('')
    pref_algorithm = settings.Setting(0)
    pref_initialization = settings.Setting(0)
    pref_n_iterations = settings.Setting(10)
    pref_rank = settings.Setting(10)
    autorun = settings.Setting(False)

    def __init__(self):
        super().__init__()
        self.n_object_types = 0
        self.n_relations = 0
        self.relations = {}  # id-->relation map
        self.graph_element_selected.connect(self.on_graph_element_selected)
        self.graph = FusionGraph(fusion.FusionGraph())
        self.webview = WebviewWidget(self.mainArea)
        self._create_layout()

    @QtCore.pyqtSlot(str)
    def on_graph_element_selected(self, element_id):
        """Handle self.graph_element_selected signal, and highlight also:
           * if edge was selected, the two related nodes,
           * if node was selected, all its edges.
           Additionally, update the info box.
        """
        if not element_id:
            return self._populate_table()
        selected_is_edge = element_id.startswith('edge ')
        nodes = self.graph.get_selected_nodes(element_id)
        # CSS selector query for selection-relevant nodes
        selector = ','.join('[id^="node "][id*="`%s`"]' % n.name for n in nodes)
        # If a node was selected, include the edges that connect to it
        if not selected_is_edge:
            selector += ',[id^="edge "][id*="`%s`"]' % nodes[0].name
        # Highlight these additional elements
        self.webview.evalJS("highlight('%s');" % selector)
        # Update the control table table
        if selected_is_edge:
            relations = self.graph.get_relations(*nodes)
        else:
            relations = (set(i for i in self.graph.in_relations(nodes[0])) |
                         set(i for i in self.graph.out_relations(nodes[0])))
        self._populate_table(relations)

    def _create_layout(self):
        info = gui.widgetBox(self.controlArea, 'Info')
        gui.label(info, self, '%(n_object_types)d object types')
        gui.label(info, self, '%(n_relations)d relations')
        # Table view of relation details
        info = gui.widgetBox(self.controlArea, 'Relations')

        def send_relation(item):
            data = item.data(QtCore.Qt.UserRole)
            self.send(Output.RELATION, Relation(data))

        self.table = SimpleTableWidget(info, callback=send_relation)
        self.controlArea.layout().addStretch(1)
        gui.lineEdit(self.controlArea,
                     self, 'pref_algo_name', 'Fuser name',
                     callback=self.checkcommit, enterPlaceholder=True)
        gui.radioButtons(self.controlArea,
                         self, 'pref_algorithm', [i[0] for i in DECOMPOSITION_ALGO],
                         box='Decomposition algorithm',
                         callback=self.checkcommit)
        gui.radioButtons(self.controlArea,
                         self, 'pref_initialization', INITIALIZATION_ALGO,
                         box='Initialization algorithm',
                         callback=self.checkcommit)
        gui.hSlider(self.controlArea, self, 'pref_n_iterations',
                    'Maximum number of iterations',
                    minValue=10, maxValue=500, createLabel=True,
                    callback=self.checkcommit)
        self.slider_rank = gui.hSlider(self.controlArea, self, 'pref_rank',
                                       'Factorization rank',
                                       minValue=1, maxValue=100, createLabel=True,
                                       labelFormat=" %d%%",
                                       callback=self.checkcommit)
        gui.auto_commit(self.controlArea, self, "autorun", "Run",
                        checkbox_label="Run after any change  ")

    def checkcommit(self):
        return self.commit()

    def commit(self):
        Algo = DECOMPOSITION_ALGO[self.pref_algorithm][1]
        init_type = INITIALIZATION_ALGO[self.pref_initialization].lower().replace(' ', '_')
        # Update rank on object-types
        maxrank = defaultdict(int)
        for rel in self.graph.relations:
            rows, cols = rel.data.shape
            row_type, col_type = rel.row_type, rel.col_type
            if rows > maxrank[row_type]:
                maxrank[row_type] = row_type.rank = max(5, int(rows * (self.pref_rank / 100)))
            if cols > maxrank[col_type]:
                maxrank[col_type] = col_type.rank = max(5, int(cols * (self.pref_rank / 100)))
        # Run the algo ...
        self.fuser = Algo(init_type=init_type,
                          max_iter=self.pref_n_iterations, random_state=0).fuse(self.graph)
        self.fuser.name = self.pref_algo_name
        self.send(Output.FUSER, FittedFusionGraph(self.fuser))

    def _populate_table(self, relations=None):
        self.table.clear()
        for i in relations or self.graph.relations:
            self.table.add([(rel_shape(i.data), i)] + rel_cols(i), bold=(1, 3))
        self.table.select_first()

    def on_relation_change(self, relation, id):
        def _on_remove_relation(id):
            try: relation = self.relations.pop(id)
            except KeyError: return
            self.graph.remove_relation(relation)

        def _on_add_relation(relation, id):
            _on_remove_relation(id)
            self.relations[id] = relation
            self.graph.add_relation(relation)

        if relation:
            _on_add_relation(relation.relation, id)
        else:
            _on_remove_relation(id)
        self._populate_table()
        self.slider_rank.setMaximum(30
                                    if any(max(rel.data.shape) > LIMIT_RANK_THRESHOLD
                                           for rel in self.graph.relations)
                                    else
                                    100)
        self.webview.repaint(self.graph, self)
        self.send(Output.FUSION_GRAPH, FusionGraph(self.graph))
        # this ensures gui.label-s get updated
        self.n_object_types = self.graph.n_object_types
        self.n_relations = self.graph.n_relations

    # called when all signals are received, so the graph is updated only once
    def handleNewSignals(self):
        self.unconditional_commit()