Ejemplo n.º 1
0
class OWFreeViz(OWAnchorProjectionWidget, ConcurrentWidgetMixin):
    MAX_INSTANCES = 10000

    name = "FreeViz"
    description = "Displays FreeViz projection"
    icon = "icons/Freeviz.svg"
    priority = 240
    keywords = ["viz"]

    settings_version = 3
    initialization = settings.Setting(InitType.Circular)
    GRAPH_CLASS = OWFreeVizGraph
    graph = settings.SettingProvider(OWFreeVizGraph)

    class Error(OWAnchorProjectionWidget.Error):
        no_class_var = widget.Msg("Data must have a target variable.")
        multiple_class_vars = widget.Msg(
            "Data must have a single target variable.")
        not_enough_class_vars = widget.Msg(
            "Target variable must have at least two unique values.")
        features_exceeds_instances = widget.Msg(
            "Number of features exceeds the number of instances.")
        too_many_data_instances = widget.Msg("Data is too large.")
        constant_data = widget.Msg("All data columns are constant.")
        not_enough_features = widget.Msg("At least two features are required.")

    class Warning(OWAnchorProjectionWidget.Warning):
        removed_features = widget.Msg("Categorical features with more than"
                                      " two values are not shown.")

    def __init__(self):
        OWAnchorProjectionWidget.__init__(self)
        ConcurrentWidgetMixin.__init__(self)

    def _add_controls(self):
        self.__add_controls_start_box()
        super()._add_controls()
        self.gui.add_control(self._effects_box,
                             gui.hSlider,
                             "Hide radius:",
                             master=self.graph,
                             value="hide_radius",
                             minValue=0,
                             maxValue=100,
                             step=10,
                             createLabel=False,
                             callback=self.__radius_slider_changed)

    def __add_controls_start_box(self):
        box = gui.vBox(self.controlArea, box="Optimize", spacing=0)
        gui.comboBox(box,
                     self,
                     "initialization",
                     label="Initialization:",
                     items=InitType.items(),
                     orientation=Qt.Horizontal,
                     labelWidth=90,
                     callback=self.__init_combo_changed)
        self.run_button = gui.button(box, self, "Start", self._toggle_run)

    @property
    def effective_variables(self):
        return [
            a for a in self.data.domain.attributes
            if a.is_continuous or a.is_discrete and len(a.values) == 2
        ]

    def __radius_slider_changed(self):
        self.graph.update_radius()

    def __init_combo_changed(self):
        self.Error.proj_error.clear()
        self.init_projection()
        self.setup_plot()
        self.commit.deferred()
        if self.task is not None:
            self._run()

    def _toggle_run(self):
        if self.task is not None:
            self.cancel()
            self.graph.set_sample_size(None)
            self.run_button.setText("Resume")
            self.commit.deferred()
        else:
            self._run()

    def _run(self):
        if self.data is None:
            return
        self.graph.set_sample_size(self.SAMPLE_SIZE)
        self.run_button.setText("Stop")
        self.start(run_freeviz, self.effective_data, self.projector)

    # ConcurrentWidgetMixin
    def on_partial_result(self, result: Result):
        assert isinstance(result.projector, FreeViz)
        assert isinstance(result.projection, FreeVizModel)
        self.projector = result.projector
        self.projection = result.projection
        self.graph.update_coordinates()
        self.graph.update_density()

    def on_done(self, result: Result):
        assert isinstance(result.projector, FreeViz)
        assert isinstance(result.projection, FreeVizModel)
        self.projector = result.projector
        self.projection = result.projection
        self.graph.set_sample_size(None)
        self.run_button.setText("Start")
        self.commit.deferred()

    def on_exception(self, ex: Exception):
        self.Error.proj_error(ex)
        self.graph.set_sample_size(None)
        self.run_button.setText("Start")

    # OWAnchorProjectionWidget
    def set_data(self, data):
        super().set_data(data)
        self.graph.set_sample_size(None)
        if self._invalidated:
            self.init_projection()

    def init_projection(self):
        if self.data is None:
            return
        anchors = FreeViz.init_radial(len(self.effective_variables)) \
            if self.initialization == InitType.Circular \
            else FreeViz.init_random(len(self.effective_variables), 2)
        self.projector = FreeViz(scale=False,
                                 center=False,
                                 initial=anchors,
                                 maxiter=10)
        data = self.projector.preprocess(self.effective_data)
        self.projector.domain = data.domain
        self.projector.components_ = anchors.T
        self.projection = FreeVizModel(self.projector, self.projector.domain,
                                       2)
        self.projection.pre_domain = data.domain
        self.projection.name = self.projector.name

    def check_data(self):
        def error(err):
            err()
            self.data = None

        super().check_data()
        if self.data is not None:
            class_vars, domain = self.data.domain.class_vars, self.data.domain
            if not class_vars:
                error(self.Error.no_class_var)
            elif len(class_vars) > 1:
                error(self.Error.multiple_class_vars)
            elif class_vars[0].is_discrete and len(np.unique(self.data.Y)) < 2:
                error(self.Error.not_enough_class_vars)
            elif len(self.data.domain.attributes) < 2:
                error(self.Error.not_enough_features)
            elif len(self.data.domain.attributes) > self.data.X.shape[0]:
                error(self.Error.features_exceeds_instances)
            elif not np.sum(np.std(self.data.X, axis=0)):
                error(self.Error.constant_data)
            elif np.sum(np.all(np.isfinite(self.data.X),
                               axis=1)) > self.MAX_INSTANCES:
                error(self.Error.too_many_data_instances)
            else:
                if len(self.effective_variables) < len(domain.attributes):
                    self.Warning.removed_features()

    def enable_controls(self):
        super().enable_controls()
        self.run_button.setEnabled(self.data is not None)
        self.run_button.setText("Start")

    def get_coordinates_data(self):
        embedding = self.get_embedding()
        if embedding is None:
            return None, None
        valid_emb = embedding[self.valid_data]
        return valid_emb.T / (np.max(np.linalg.norm(valid_emb, axis=1)) or 1)

    def _manual_move(self, anchor_idx, x, y):
        self.projector.initial[anchor_idx] = [x, y]
        super()._manual_move(anchor_idx, x, y)

    def clear(self):
        super().clear()
        self.cancel()

    def onDeleteWidget(self):
        self.shutdown()
        super().onDeleteWidget()

    @classmethod
    def migrate_settings(cls, _settings, version):
        if version < 3:
            if "radius" in _settings:
                _settings["graph"]["hide_radius"] = _settings["radius"]

    @classmethod
    def migrate_context(cls, context, version):
        if version < 3:
            values = context.values
            values["attr_color"] = values["graph"]["attr_color"]
            values["attr_size"] = values["graph"]["attr_size"]
            values["attr_shape"] = values["graph"]["attr_shape"]
            values["attr_label"] = values["graph"]["attr_label"]
Ejemplo n.º 2
0
class OWFreeViz(OWAnchorProjectionWidget):
    MAX_ITERATIONS = 1000
    MAX_INSTANCES = 10000

    name = "FreeViz"
    description = "Displays FreeViz projection"
    icon = "icons/Freeviz.svg"
    priority = 240
    keywords = ["viz"]

    settings_version = 3
    initialization = settings.Setting(InitType.Circular)
    GRAPH_CLASS = OWFreeVizGraph
    graph = settings.SettingProvider(OWFreeVizGraph)

    class Error(OWAnchorProjectionWidget.Error):
        no_class_var = widget.Msg("Data has no target variable")
        not_enough_class_vars = widget.Msg(
            "Target variable is not at least binary")
        features_exceeds_instances = widget.Msg(
            "Number of features exceeds the number of instances.")
        too_many_data_instances = widget.Msg("Data is too large.")
        constant_data = widget.Msg("All data columns are constant.")
        not_enough_features = widget.Msg("At least two features are required")

    class Warning(OWAnchorProjectionWidget.Warning):
        removed_features = widget.Msg("Categorical features with more than"
                                      " two values are not shown.")

    def __init__(self):
        super().__init__()
        self._loop = AsyncUpdateLoop(parent=self)
        self._loop.yielded.connect(self.__set_projection)
        self._loop.finished.connect(self.__freeviz_finished)
        self._loop.raised.connect(self.__on_error)

    def _add_controls(self):
        self.__add_controls_start_box()
        super()._add_controls()
        self.gui.add_control(
            self._effects_box, gui.hSlider, "Hide radius:", master=self.graph,
            value="hide_radius", minValue=0, maxValue=100, step=10,
            createLabel=False, callback=self.__radius_slider_changed
        )

    def __add_controls_start_box(self):
        box = gui.vBox(self.controlArea, box=True)
        gui.comboBox(
            box, self, "initialization", label="Initialization:",
            items=InitType.items(), orientation=Qt.Horizontal,
            labelWidth=90, callback=self.__init_combo_changed)
        self.btn_start = gui.button(
            box, self, "Optimize", self.__toggle_start, enabled=False)

    @property
    def effective_variables(self):
        return [a for a in self.data.domain.attributes
                if a.is_continuous or a.is_discrete and len(a.values) == 2]

    def __radius_slider_changed(self):
        self.graph.update_radius()

    def __toggle_start(self):
        if self._loop.isRunning():
            self._loop.cancel()
            self.btn_start.setText("Optimize")
            self.progressBarFinished(processEvents=False)
        else:
            self._start()

    def __init_combo_changed(self):
        if self.data is None:
            return
        running = self._loop.isRunning()
        if running:
            self._loop.cancel()
        self.init_projection()
        self.graph.update_coordinates()
        self.commit()
        if running:
            self._start()

    def _start(self):
        def update_freeviz(anchors):
            while True:
                self.projection = self.projector(self.effective_data)
                _anchors = self.projector.components_.T
                self.projector.initial = _anchors
                yield _anchors
                if np.allclose(anchors, _anchors, rtol=1e-5, atol=1e-4):
                    return
                anchors = _anchors

        self.graph.set_sample_size(self.SAMPLE_SIZE)
        self._loop.setCoroutine(update_freeviz(self.projector.components_.T))
        self.btn_start.setText("Stop")
        self.progressBarInit()
        self.setBlocking(True)
        self.setStatusMessage("Optimizing")

    def __set_projection(self, _):
        # Set/update the projection matrix and coordinate embeddings
        self.progressBarAdvance(100. / self.MAX_ITERATIONS)
        self.graph.update_coordinates()

    def __freeviz_finished(self):
        self.graph.set_sample_size(None)
        self.btn_start.setText("Optimize")
        self.setStatusMessage("")
        self.setBlocking(False)
        self.progressBarFinished()
        self.commit()

    def __on_error(self, err):
        sys.excepthook(type(err), err, getattr(err, "__traceback__"))

    def check_data(self):
        def error(err):
            err()
            self.data = None

        super().check_data()
        if self.data is not None:
            class_var, domain = self.data.domain.class_var, self.data.domain
            if class_var is None:
                error(self.Error.no_class_var)
            elif class_var.is_discrete and len(np.unique(self.data.Y)) < 2:
                error(self.Error.not_enough_class_vars)
            elif len(self.data.domain.attributes) < 2:
                error(self.Error.not_enough_features)
            elif len(self.data.domain.attributes) > self.data.X.shape[0]:
                error(self.Error.features_exceeds_instances)
            elif not np.sum(np.std(self.data.X, axis=0)):
                error(self.Error.constant_data)
            elif np.sum(self.valid_data) > self.MAX_INSTANCES:
                error(self.Error.too_many_data_instances)
            else:
                if len(self.effective_variables) < len(domain.attributes):
                    self.Warning.removed_features()
        self.btn_start.setEnabled(self.data is not None)

    def set_data(self, data):
        super().set_data(data)
        if self.data is not None:
            self.init_projection()

    def init_projection(self):
        anchors = FreeViz.init_radial(len(self.effective_variables)) \
            if self.initialization == InitType.Circular \
            else FreeViz.init_random(len(self.effective_variables), 2)
        self.projector = FreeViz(scale=False, center=False,
                                 initial=anchors, maxiter=10)
        data = self.projector.preprocess(self.effective_data)
        self.projector.domain = data.domain
        self.projector.components_ = anchors.T
        self.projection = FreeVizModel(self.projector, self.projector.domain, 2)
        self.projection.pre_domain = data.domain
        self.projection.name = self.projector.name

    def get_coordinates_data(self):
        embedding = self.get_embedding()
        if embedding is None:
            return None, None
        valid_emb = embedding[self.valid_data]
        return valid_emb.T / (np.max(np.linalg.norm(valid_emb, axis=1)) or 1)

    def _manual_move(self, anchor_idx, x, y):
        self.projector.initial[anchor_idx] = [x, y]
        super()._manual_move(anchor_idx, x, y)

    def clear(self):
        super().clear()
        self._loop.cancel()

    @classmethod
    def migrate_settings(cls, _settings, version):
        if version < 3:
            if "radius" in _settings:
                _settings["graph"]["hide_radius"] = _settings["radius"]

    @classmethod
    def migrate_context(cls, context, version):
        if version < 3:
            values = context.values
            values["attr_color"] = values["graph"]["attr_color"]
            values["attr_size"] = values["graph"]["attr_size"]
            values["attr_shape"] = values["graph"]["attr_shape"]
            values["attr_label"] = values["graph"]["attr_label"]
Ejemplo n.º 3
0
class OWFreeViz(OWAnchorProjectionWidget):
    MAX_ITERATIONS = 1000
    MAX_INSTANCES = 10000

    name = "FreeViz"
    description = "Displays FreeViz projection"
    icon = "icons/Freeviz.svg"
    priority = 240
    keywords = ["viz"]

    settings_version = 3
    initialization = settings.Setting(InitType.Circular)
    GRAPH_CLASS = OWFreeVizGraph
    graph = settings.SettingProvider(OWFreeVizGraph)

    class Error(OWAnchorProjectionWidget.Error):
        no_class_var = widget.Msg("Data has no target variable")
        not_enough_class_vars = widget.Msg(
            "Target variable is not at least binary")
        features_exceeds_instances = widget.Msg(
            "Number of features exceeds the number of instances.")
        too_many_data_instances = widget.Msg("Data is too large.")
        constant_data = widget.Msg("All data columns are constant.")
        not_enough_features = widget.Msg("At least two features are required")

    class Warning(OWAnchorProjectionWidget.Warning):
        removed_features = widget.Msg("Categorical features with more than"
                                      " two values are not shown.")

    def __init__(self):
        super().__init__()
        self._loop = AsyncUpdateLoop(parent=self)
        self._loop.yielded.connect(self.__set_projection)
        self._loop.finished.connect(self.__freeviz_finished)
        self._loop.raised.connect(self.__on_error)

    def _add_controls(self):
        self.__add_controls_start_box()
        super()._add_controls()
        self.graph.gui.add_control(self._effects_box,
                                   gui.hSlider,
                                   "Hide radius:",
                                   master=self.graph,
                                   value="hide_radius",
                                   minValue=0,
                                   maxValue=100,
                                   step=10,
                                   createLabel=False,
                                   callback=self.__radius_slider_changed)

    def __add_controls_start_box(self):
        box = gui.vBox(self.controlArea, box=True)
        gui.comboBox(box,
                     self,
                     "initialization",
                     label="Initialization:",
                     items=InitType.items(),
                     orientation=Qt.Horizontal,
                     labelWidth=90,
                     callback=self.__init_combo_changed)
        self.btn_start = gui.button(box,
                                    self,
                                    "Optimize",
                                    self.__toggle_start,
                                    enabled=False)

    @property
    def effective_variables(self):
        return [
            a for a in self.data.domain.attributes
            if a.is_continuous or a.is_discrete and len(a.values) == 2
        ]

    def __radius_slider_changed(self):
        self.graph.update_radius()

    def __toggle_start(self):
        if self._loop.isRunning():
            self._loop.cancel()
            self.btn_start.setText("Optimize")
            self.progressBarFinished(processEvents=False)
        else:
            self._start()

    def __init_combo_changed(self):
        if self.data is None:
            return
        running = self._loop.isRunning()
        if running:
            self._loop.cancel()
        self.init_projection()
        self.graph.update_coordinates()
        self.commit()
        if running:
            self._start()

    def _start(self):
        def update_freeviz(anchors):
            while True:
                self.projection = self.projector(self.effective_data)
                _anchors = self.projector.components_.T
                self.projector.initial = _anchors
                yield _anchors
                if np.allclose(anchors, _anchors, rtol=1e-5, atol=1e-4):
                    return
                anchors = _anchors

        self.graph.set_sample_size(self.SAMPLE_SIZE)
        self._loop.setCoroutine(update_freeviz(self.projector.components_.T))
        self.btn_start.setText("Stop")
        self.progressBarInit()
        self.setBlocking(True)
        self.setStatusMessage("Optimizing")

    def __set_projection(self, _):
        # Set/update the projection matrix and coordinate embeddings
        self.progressBarAdvance(100. / self.MAX_ITERATIONS)
        self.graph.update_coordinates()

    def __freeviz_finished(self):
        self.graph.set_sample_size(None)
        self.btn_start.setText("Optimize")
        self.setStatusMessage("")
        self.setBlocking(False)
        self.progressBarFinished()
        self.commit()

    def __on_error(self, err):
        sys.excepthook(type(err), err, getattr(err, "__traceback__"))

    def check_data(self):
        def error(err):
            err()
            self.data = None

        super().check_data()
        if self.data is not None:
            class_var, domain = self.data.domain.class_var, self.data.domain
            if class_var is None:
                error(self.Error.no_class_var)
            elif class_var.is_discrete and len(np.unique(self.data.Y)) < 2:
                error(self.Error.not_enough_class_vars)
            elif len(self.data.domain.attributes) < 2:
                error(self.Error.not_enough_features)
            elif len(self.data.domain.attributes) > self.data.X.shape[0]:
                error(self.Error.features_exceeds_instances)
            elif not np.sum(np.std(self.data.X, axis=0)):
                error(self.Error.constant_data)
            elif np.sum(self.valid_data) > self.MAX_INSTANCES:
                error(self.Error.too_many_data_instances)
            else:
                if len(self.effective_variables) < len(domain.attributes):
                    self.Warning.removed_features()
        self.btn_start.setEnabled(self.data is not None)

    def set_data(self, data):
        super().set_data(data)
        if self.data is not None:
            self.init_projection()

    def init_projection(self):
        anchors = FreeViz.init_radial(len(self.effective_variables)) \
            if self.initialization == InitType.Circular \
            else FreeViz.init_random(len(self.effective_variables), 2)
        self.projector = FreeViz(scale=False,
                                 center=False,
                                 initial=anchors,
                                 maxiter=10)
        data = self.projector.preprocess(self.effective_data)
        self.projector.domain = data.domain
        self.projector.components_ = anchors.T
        self.projection = FreeVizModel(self.projector, self.projector.domain)
        self.projection.pre_domain = data.domain
        self.projection.name = self.projector.name

    def get_coordinates_data(self):
        embedding = self.get_embedding()
        if embedding is None:
            return None, None
        valid_emb = embedding[self.valid_data]
        return valid_emb.T / (np.max(np.linalg.norm(valid_emb, axis=1)) or 1)

    def _manual_move(self, anchor_idx, x, y):
        self.projector.initial[anchor_idx] = [x, y]
        super()._manual_move(anchor_idx, x, y)

    def clear(self):
        super().clear()
        self._loop.cancel()

    @classmethod
    def migrate_settings(cls, _settings, version):
        if version < 3:
            if "radius" in _settings:
                _settings["graph"]["hide_radius"] = _settings["radius"]

    @classmethod
    def migrate_context(cls, context, version):
        if version < 3:
            values = context.values
            values["attr_color"] = values["graph"]["attr_color"]
            values["attr_size"] = values["graph"]["attr_size"]
            values["attr_shape"] = values["graph"]["attr_shape"]
            values["attr_label"] = values["graph"]["attr_label"]
Ejemplo n.º 4
0
class OWFreeViz(OWAnchorProjectionWidget, ConcurrentWidgetMixin):
    MAX_INSTANCES = 10000

    name = "FreeViz"
    description = "Displays FreeViz projection"
    icon = "icons/Freeviz.svg"
    priority = 240
    keywords = ["viz"]

    settings_version = 3
    initialization = settings.Setting(InitType.Circular)
    GRAPH_CLASS = OWFreeVizGraph
    graph = settings.SettingProvider(OWFreeVizGraph)

    left_side_scrolling = True

    class Error(OWAnchorProjectionWidget.Error):
        no_class_var = widget.Msg("Data has no target variable")
        not_enough_class_vars = widget.Msg(
            "Target variable is not at least binary")
        features_exceeds_instances = widget.Msg(
            "Number of features exceeds the number of instances.")
        too_many_data_instances = widget.Msg("Data is too large.")
        constant_data = widget.Msg("All data columns are constant.")
        not_enough_features = widget.Msg("At least two features are required")

    class Warning(OWAnchorProjectionWidget.Warning):
        removed_features = widget.Msg("Categorical features with more than"
                                      " two values are not shown.")

    def __init__(self):
        OWAnchorProjectionWidget.__init__(self)
        ConcurrentWidgetMixin.__init__(self)

    def _add_controls(self):
        self.__add_controls_start_box()
        super()._add_controls()
        self.gui.add_control(
            self._effects_box, gui.hSlider, "Hide radius:", master=self.graph,
            value="hide_radius", minValue=0, maxValue=100, step=10,
            createLabel=False, callback=self.__radius_slider_changed
        )

    def __add_controls_start_box(self):
        box = gui.vBox(self.controlArea, box=True)
        gui.comboBox(
            box, self, "initialization", label="Initialization:",
            items=InitType.items(), orientation=Qt.Horizontal,
            labelWidth=90, callback=self.__init_combo_changed)
        self.run_button = gui.button(box, self, "Start", self._toggle_run)

    @property
    def effective_variables(self):
        return [a for a in self.data.domain.attributes
                if a.is_continuous or a.is_discrete and len(a.values) == 2]

    def __radius_slider_changed(self):
        self.graph.update_radius()

    def __init_combo_changed(self):
        self.Error.proj_error.clear()
        self.init_projection()
        self.setup_plot()
        self.commit()
        if self.task is not None:
            self._run()

    def _toggle_run(self):
        if self.task is not None:
            self.cancel()
            self.graph.set_sample_size(None)
            self.run_button.setText("Resume")
            self.commit()
        else:
            self._run()

    def _run(self):
        if self.data is None:
            return
        self.graph.set_sample_size(self.SAMPLE_SIZE)
        self.run_button.setText("Stop")
        self.start(run_freeviz, self.effective_data, self.projector)

    # ConcurrentWidgetMixin
    def on_partial_result(self, result: Result):
        assert isinstance(result.projector, FreeViz)
        assert isinstance(result.projection, FreeVizModel)
        self.projector = result.projector
        self.projection = result.projection
        self.graph.update_coordinates()
        self.graph.update_density()

    def on_done(self, result: Result):
        assert isinstance(result.projector, FreeViz)
        assert isinstance(result.projection, FreeVizModel)
        self.projector = result.projector
        self.projection = result.projection
        self.graph.set_sample_size(None)
        self.run_button.setText("Start")
        self.commit()

    def on_exception(self, ex: Exception):
        self.Error.proj_error(ex)
        self.graph.set_sample_size(None)
        self.run_button.setText("Start")

    # OWAnchorProjectionWidget
    def set_data(self, data):
        super().set_data(data)
        if self._invalidated:
            self.init_projection()

    def init_projection(self):
        if self.data is None:
            return
        anchors = FreeViz.init_radial(len(self.effective_variables)) \
            if self.initialization == InitType.Circular \
            else FreeViz.init_random(len(self.effective_variables), 2)
        self.projector = FreeViz(scale=False, center=False,
                                 initial=anchors, maxiter=10)
        data = self.projector.preprocess(self.effective_data)
        self.projector.domain = data.domain
        self.projector.components_ = anchors.T
        self.projection = FreeVizModel(self.projector, self.projector.domain, 2)
        self.projection.pre_domain = data.domain
        self.projection.name = self.projector.name

    def check_data(self):
        def error(err):
            err()
            self.data = None

        super().check_data()
        if self.data is not None:
            class_var, domain = self.data.domain.class_var, self.data.domain
            if class_var is None:
                error(self.Error.no_class_var)
            elif class_var.is_discrete and len(np.unique(self.data.Y)) < 2:
                error(self.Error.not_enough_class_vars)
            elif len(self.data.domain.attributes) < 2:
                error(self.Error.not_enough_features)
            elif len(self.data.domain.attributes) > self.data.X.shape[0]:
                error(self.Error.features_exceeds_instances)
            elif not np.sum(np.std(self.data.X, axis=0)):
                error(self.Error.constant_data)
            elif np.sum(np.all(np.isfinite(self.data.X), axis=1)) > self.MAX_INSTANCES:
                error(self.Error.too_many_data_instances)
            else:
                if len(self.effective_variables) < len(domain.attributes):
                    self.Warning.removed_features()

    def enable_controls(self):
        super().enable_controls()
        self.run_button.setEnabled(self.data is not None)
        self.run_button.setText("Start")

    def get_coordinates_data(self):
        embedding = self.get_embedding()
        if embedding is None:
            return None, None
        valid_emb = embedding[self.valid_data]
        return valid_emb.T / (np.max(np.linalg.norm(valid_emb, axis=1)) or 1)

    def _manual_move(self, anchor_idx, x, y):
        self.projector.initial[anchor_idx] = [x, y]
        super()._manual_move(anchor_idx, x, y)

    def clear(self):
        super().clear()
        self.cancel()

    def onDeleteWidget(self):
        self.shutdown()
        super().onDeleteWidget()

    @classmethod
    def migrate_settings(cls, _settings, version):
        if version < 3:
            if "radius" in _settings:
                _settings["graph"]["hide_radius"] = _settings["radius"]

    @classmethod
    def migrate_context(cls, context, version):
        if version < 3:
            values = context.values
            values["attr_color"] = values["graph"]["attr_color"]
            values["attr_size"] = values["graph"]["attr_size"]
            values["attr_shape"] = values["graph"]["attr_shape"]
            values["attr_label"] = values["graph"]["attr_label"]