Exemple #1
0
class DataTicksCalculatorFmtWidget(QWidget):
    """Fmt widget for :class:`psy_simple.plotters.DataTicksCalculator`

    This widget contains a combo box with the different options from the
    :attr:`psy_simple.plotters.DataTicksCalculator.calc_funcs`, a spin box
    for the number of increments and two text widgets for minimum and maximum
    percentile"""

    def __init__(self, parent, fmto, what=None, N=None, pctl_min=None,
                 pctl_max=None):
        QWidget.__init__(self, parent)

        hbox = QHBoxLayout()

        self.combo = QComboBox()
        self.combo.addItems(sorted(fmto.calc_funcs))
        hbox.addWidget(self.combo)

        self.sb_N = QSpinBox()
        hbox.addWidget(self.sb_N)

        self.txt_min_pctl = QLineEdit()
        self.txt_min_pctl.setValidator(QDoubleValidator(0., 100., 10))
        hbox.addWidget(QLabel('Percentiles:'))
        hbox.addWidget(QLabel('Min.:'))
        hbox.addWidget(self.txt_min_pctl)

        self.txt_max_pctl = QLineEdit()
        self.txt_max_pctl.setValidator(QDoubleValidator(0., 100., 10))
        hbox.addWidget(QLabel('Max.:'))
        hbox.addWidget(self.txt_max_pctl)

        if what is not None:
            self.combo.setCurrentText(what)
        if N is not None:
            self.sb_N.setValue(N)
        if pctl_min is not None:
            self.txt_min_pctl.setText('%1.6g' % pctl_min)
        if pctl_max is not None:
            self.txt_max_pctl.setText('%1.6g' % pctl_max)

        self.combo.currentTextChanged.connect(self.set_obj)
        self.sb_N.valueChanged.connect(self.set_obj)
        self.txt_min_pctl.textChanged.connect(self.set_obj)
        self.txt_max_pctl.textChanged.connect(self.set_obj)

        self.setLayout(hbox)

    def set_obj(self):
        obj = [self.combo.currentText(),
               self.sb_N.value()]
        if (self.txt_min_pctl.text().strip() or
                self.txt_max_pctl.text().strip()):
            obj.append(float(self.txt_min_pctl.text().strip() or 0))
            if self.txt_max_pctl.text().strip():
                obj.append(float(self.txt_max_pctl.text().strip()))
        self.parent().set_obj(obj)
Exemple #2
0
class MarkerControl(StraditizerControlBase, QWidget):
    """Widget to control the appearance of the marks

    This widget controls the appearance of the
    :class:`straditize.cross_mark.CrossMarks` instances in the
    :attr:`~straditize.straditizer.Straditizer.marks` attribute of the
    :attr:`~straditize.widgets.StraditizerControlBase.straditizer`"""

    _toolbar = None

    @property
    def marks(self):
        """The :class:`~straditize.cross_mark.CrossMarks` of the straditizer
        """
        return chain(self.straditizer.marks, self.straditizer.magni_marks)

    @docstrings.dedent
    def __init__(self, straditizer_widgets, item, *args, **kwargs):
        """
        Parameters
        ----------
        %(StraditizerControlBase.init_straditizercontrol.parameters)s
        """
        super(MarkerControl, self).__init__(*args, **kwargs)
        self.init_straditizercontrol(straditizer_widgets, item)
        vbox = QVBoxLayout()

        # auto hide button
        box_hide = QGridLayout()
        self.cb_auto_hide = QCheckBox('Auto-hide')
        self.cb_show_connected = QCheckBox('Show additionals')
        self.cb_drag_hline = QCheckBox('Drag in y-direction')
        self.cb_drag_vline = QCheckBox('Drag in x-direction')
        self.cb_selectable_hline = QCheckBox('Horizontal lines selectable')
        self.cb_selectable_vline = QCheckBox('Vertical lines selectable')
        self.cb_show_hlines = QCheckBox('Show horizontal lines')
        self.cb_show_vlines = QCheckBox('Show vertical lines')
        box_hide.addWidget(self.cb_auto_hide, 0, 0)
        box_hide.addWidget(self.cb_show_connected, 0, 1)
        box_hide.addWidget(self.cb_show_vlines, 1, 0)
        box_hide.addWidget(self.cb_show_hlines, 1, 1)
        box_hide.addWidget(self.cb_drag_hline, 2, 0)
        box_hide.addWidget(self.cb_drag_vline, 2, 1)
        box_hide.addWidget(self.cb_selectable_hline, 3, 0)
        box_hide.addWidget(self.cb_selectable_vline, 3, 1)
        vbox.addLayout(box_hide)

        style_box = QGridLayout()
        style_box.addWidget(QLabel('Unselected:'), 0, 1)
        selected_label = QLabel('Selected:')
        style_box.addWidget(selected_label, 0, 2)
        max_width = selected_label.sizeHint().width()

        # line color
        self.lbl_color_select = ColorLabel()
        self.lbl_color_unselect = ColorLabel()
        self.lbl_color_select.setMaximumWidth(max_width)
        self.lbl_color_unselect.setMaximumWidth(max_width)
        style_box.addWidget(QLabel('Line color:'), 1, 0)
        style_box.addWidget(self.lbl_color_unselect, 1, 1)
        style_box.addWidget(self.lbl_color_select, 1, 2)

        # line width
        self.txt_line_width = QLineEdit()
        self.txt_line_width_select = QLineEdit()

        validator = QDoubleValidator()
        validator.setBottom(0)
        self.txt_line_width.setValidator(validator)
        self.txt_line_width_select.setValidator(validator)

        style_box.addWidget(QLabel('Line width:'), 2, 0)
        style_box.addWidget(self.txt_line_width, 2, 1)
        style_box.addWidget(self.txt_line_width_select, 2, 2)

        vbox.addLayout(style_box)

        # line style
        hbox_line_style = QHBoxLayout()
        hbox_line_style.addWidget(QLabel('Line style'))
        self.combo_line_style = QComboBox()
        hbox_line_style.addWidget(self.combo_line_style)
        vbox.addLayout(hbox_line_style)
        self.fill_linestyles()
        self.combo_line_style.setSizeAdjustPolicy(QComboBox.AdjustToContents)

        # marker style
        hbox_marker_style = QHBoxLayout()
        hbox_marker_style.addWidget(QLabel('Marker size'))
        self.txt_marker_size = QLineEdit()
        self.txt_marker_size.setMinimumWidth(40)
        self.txt_marker_size.setText(str(mpl.rcParams['lines.markersize']))
        validator = QDoubleValidator()
        validator.setBottom(0)
        self.txt_marker_size.setValidator(validator)
        hbox_marker_style.addWidget(self.txt_marker_size)
        hbox_marker_style.addWidget(QLabel('Marker style'))
        self.combo_marker_style = QComboBox()
        hbox_marker_style.addWidget(self.combo_marker_style)
        vbox.addLayout(hbox_marker_style)

        self.setLayout(vbox)

        self.widgets2disable = [
            self.lbl_color_select, self.lbl_color_unselect, self.cb_auto_hide,
            self.txt_line_width, self.txt_line_width_select,
            self.combo_line_style, self.cb_show_connected,
            self.txt_marker_size, self.combo_marker_style, self.cb_drag_vline,
            self.cb_drag_hline, self.cb_selectable_vline,
            self.cb_selectable_hline, self.cb_show_hlines, self.cb_show_vlines
        ]

        self.fill_markerstyles()

        self.tb_actions = []

        # ---------------------------------------------------------------------
        # ---------------------------- connections ----------------------------
        # ---------------------------------------------------------------------

        self.lbl_color_select.color_changed.connect(self.change_select_colors)
        self.lbl_color_unselect.color_changed.connect(
            self.change_unselect_colors)
        self.txt_line_width.textChanged.connect(self.change_line_widths)
        self.txt_line_width_select.textChanged.connect(
            self.change_selection_line_widths)
        self.cb_auto_hide.stateChanged.connect(self.change_auto_hide)
        self.combo_marker_style.currentIndexChanged.connect(
            self.change_marker_style)
        self.combo_line_style.currentIndexChanged.connect(
            self.change_line_style)
        self.txt_marker_size.textChanged.connect(self.change_marker_size)
        self.cb_show_connected.stateChanged.connect(
            self.change_show_connected_artists)
        self.cb_drag_hline.stateChanged.connect(self.change_hline_draggable)
        self.cb_drag_vline.stateChanged.connect(self.change_vline_draggable)
        self.cb_selectable_hline.stateChanged.connect(
            self.change_hline_selectable)
        self.cb_selectable_vline.stateChanged.connect(
            self.change_vline_selectable)
        self.cb_show_vlines.stateChanged.connect(self.change_show_vlines)
        self.cb_show_hlines.stateChanged.connect(self.change_show_hlines)

    def draw_figs(self):
        """Draw the figures of the :attr:`marks`"""
        for canvas in {m.fig.canvas for m in self.marks}:
            canvas.draw_idle()

    def fill_linestyles(self):
        """Fill the :attr:`combo_line_style` combobox"""
        self.line_styles = [('-', 'solid'), ('--', 'dashed'),
                            ('-.', 'dashdot'), (':', 'dotted'),
                            ('None', None, '', ' ')]
        self.combo_line_style.addItems([t[0] for t in self.line_styles])

    def fill_markerstyles(self):
        """Fill the :attr:`combo_marker_style` combobox"""
        self.marker_styles = [('point', (".", )), ('pixel', (",", )),
                              ('circle', ("o", )),
                              ('triangle down', ("v", "1")),
                              ('triangle up', ("^", "2")),
                              ('triangle left', ("<", "3")),
                              ('triangle right', (">", "4")),
                              ('octagon', ("8", )), ('square', ("s", )),
                              ('pentagon', ("p", )),
                              ('plus (filled)', ("P", )), ('star', ("*", )),
                              ('hexagon1', ("h", )), ('hexagon2', ("H", )),
                              ('plus', ("+", )), ('x', ("x", )),
                              ('x (filled)', ("X", )), ('diamond', ("D", )),
                              ('thin diamond', ("d", )), ('vline', ("|", )),
                              ('hline', ("_", )),
                              ('no marker', ('None', None, '', ' '))]
        self.combo_marker_style.addItems([
            '%s (%s)' % (key.capitalize(), ','.join(map(str, filter(None, t))))
            for key, t in self.marker_styles
        ])

    def set_marker_item(self, marker):
        """Switch the :attr:`combo_marker_style` to the given `marker`

        Parameters
        ----------
        marker: str
            A matplotlib marker string"""
        for i, (key, t) in enumerate(self.marker_styles):
            if marker in t:
                block = self.combo_marker_style.blockSignals(True)
                self.combo_marker_style.setCurrentIndex(i)
                self.combo_marker_style.blockSignals(block)
                break

    def set_line_style_item(self, ls):
        """Switch the :attr:`combo_line_style` to the given linestyle

        Parameters
        ----------
        ls: str
            The matplotlib linestyle string
        """
        for i, t in enumerate(self.line_styles):
            if ls in t:
                block = self.combo_line_style.blockSignals(True)
                self.combo_line_style.setCurrentIndex(i)
                self.combo_line_style.blockSignals(block)
                break

    def should_be_enabled(self, w):
        """Check if a widget `w` should be enabled or disabled

        Parameters
        ----------
        w: PyQt5.QtWidgets.QWidget
            The widget to (potentially) enable

        Returns
        -------
        bool
            True if the :attr:`straditizer` of this instance has marks.
            Otherwise False"""
        return self.straditizer is not None and bool(self.straditizer.marks)

    def change_select_colors(self, color):
        """Change the selection color of the marks

        Change the selection color of the marks to the given color.

        Parameters
        ----------
        color: PyQt5.QtGui.QColor or a matplotlib color
            The color to use
        """
        if isinstance(color, QtGui.QColor):
            color = np.array(color.getRgb()) / 255.
        for mark in self.marks:
            key = 'color' if 'color' in mark._select_props else 'c'
            mark._select_props[key] = color
            mark._unselect_props[key] = mark.hline.get_c()
        self.draw_figs()

    def change_unselect_colors(self, color):
        """Change the :attr:`straditize.cross_mark.CrossMark.cunselect` color

        Change the unselection color of the marks to the given color.

        Parameters
        ----------
        color: PyQt5.QtGui.QColor or a matplotlib color
            The color to use
        """
        if isinstance(color, QtGui.QColor):
            color = np.array(color.getRgb()) / 255.
        for mark in self.marks:
            key = 'color' if 'color' in mark._unselect_props else 'c'
            mark._unselect_props[key] = color
            for l in chain(mark.hlines, mark.vlines, mark.line_connections):
                l.set_color(color)
        self.draw_figs()

    def change_line_widths(self, lw):
        """Change the linewidth of the marks

        Parameters
        ----------
        lw: float
            The line width to use
        """
        lw = float(lw or 0)
        for mark in self.marks:
            key = 'lw' if 'lw' in mark._unselect_props else 'linewidth'
            mark._unselect_props[key] = lw
            if not mark.auto_hide:
                for l in chain(mark.hlines, mark.vlines,
                               mark.line_connections):
                    l.set_lw(lw)
        self.draw_figs()

    def change_selection_line_widths(self, lw):
        """Change the linewidth for selected marks

        Parameters
        ----------
        lw: float
            The linewidth for selected marks"""
        lw = float(lw or 0)
        for mark in self.marks:
            key = 'lw' if 'lw' in mark._select_props else 'linewidth'
            mark._select_props[key] = lw

    def change_auto_hide(self, auto_hide):
        """Toggle the :attr:`~straditize.cross_mark.CrossMark.auto_hide`

        This method disables or enables the
        :attr:`~straditize.cross_mark.CrossMark.auto_hide` of the marks

        Parameters
        ----------
        auto_hide: bool or PyQt5.QtGui.Qt.Checked or PyQt5.QtGui.Qt.Unchecked
            The value to use for the auto_hide. :data:`PyQt5.QtGui.Qt.Checked`
            is equivalent to ``True``
        """
        if auto_hide is Qt.Checked:
            auto_hide = True
        elif auto_hide is Qt.Unchecked:
            auto_hide = False
        for mark in self.marks:
            mark.auto_hide = auto_hide
            if auto_hide:
                for l in chain(mark.hlines, mark.vlines,
                               mark.line_connections):
                    l.set_lw(0)
            else:
                lw = mark._unselect_props.get(
                    'lw', mark._unselect_props.get('linewidth'))
                for l in chain(mark.hlines, mark.vlines,
                               mark.line_connections):
                    l.set_lw(lw)
        self.draw_figs()

    def change_show_connected_artists(self, show):
        """Change the visibility of connected artists

        Parameters
        ----------
        show: bool
            The visibility for the
            :meth:`straditize.cross_mark.CrossMarks.set_connected_artists_visible`
            method"""
        if show is Qt.Checked:
            show = True
        elif show is Qt.Unchecked:
            show = False
        for mark in self.marks:
            mark.set_connected_artists_visible(show)
        self.draw_figs()

    def change_line_style(self, i):
        """Change the line style of the marks

        Parameters
        ----------
        i: int
            The index of the line style in the :attr:`line_styles` attribute
            to use
        """
        ls = self.line_styles[i][0]
        for mark in self.marks:
            for l in chain(mark.hlines, mark.vlines, mark.line_connections):
                l.set_ls(ls)
        self.draw_figs()

    def change_marker_style(self, i):
        """Change the marker style of the marks

        Parameters
        ----------
        i: int
            The index of the marker style in the :attr:`marker_styles`
            attribute to use
        """
        marker = self.marker_styles[i][1][0]
        for mark in self.marks:
            for l in chain(mark.hlines, mark.vlines, mark.line_connections):
                l.set_marker(marker)
        self.draw_figs()

    def change_marker_size(self, markersize):
        """Change the size of the markers

        Parameters
        ----------
        markersize: float
            The size of the marker to use"""
        markersize = float(markersize or 0)
        for mark in self.marks:
            for l in chain(mark.hlines, mark.vlines, mark.line_connections):
                l.set_markersize(markersize)
        self.draw_figs()

    def fill_from_mark(self, mark):
        """Set the widgets of this :class:`MarkerControl` from a mark

        This method sets the color labels, combo boxes, check boxes and
        text edits to match the properties of the given `mark`

        Parameters
        ----------
        mark: straditize.cross_mark.CrossMark
            The mark to use the attributes from
        """
        line = mark.hline
        try:
            cselect = mark._select_props['c']
        except KeyError:
            try:
                cselect = mark._select_props['color']
            except KeyError:
                cselect = mark.hline.get_c()
        lw_key = 'lw' if 'lw' in mark._unselect_props else 'linewidth'
        self.set_line_style_item(line.get_linestyle())
        self.set_marker_item(line.get_marker())
        self.txt_line_width.setText(str(mark._unselect_props.get(lw_key, 0)))
        self.txt_line_width_select.setText(
            str(mark._select_props.get(lw_key, 0)))
        self.txt_marker_size.setText(str(line.get_markersize()))
        self.lbl_color_select._set_color(cselect)
        self.lbl_color_unselect._set_color(mark.hline.get_c())
        self.cb_auto_hide.setChecked(mark.auto_hide)
        self.cb_show_connected.setChecked(mark.show_connected_artists)
        try:
            mark.x
        except NotImplementedError:
            self.cb_drag_vline.setEnabled(False)
            self.cb_show_vlines.setEnabled(False)
            self.cb_selectable_vline.setEnabled(False)
        else:
            self.cb_drag_vline.setEnabled(True)
            self.cb_show_vlines.setEnabled(True)
            self.cb_selectable_vline.setEnabled(True)
        try:
            mark.y
        except NotImplementedError:
            self.cb_drag_hline.setEnabled(False)
            self.cb_show_hlines.setEnabled(False)
            self.cb_selectable_hline.setEnabled(False)
        else:
            self.cb_drag_hline.setEnabled(True)
            self.cb_show_hlines.setEnabled(True)
            self.cb_selectable_hline.setEnabled(True)
        self.cb_drag_hline.setChecked('h' in mark.draggable)
        self.cb_drag_vline.setChecked('v' in mark.draggable)
        self.cb_show_hlines.setChecked(not mark.hide_horizontal)
        self.cb_show_vlines.setChecked(not mark.hide_vertical)
        self.cb_selectable_hline.setChecked('h' in mark.selectable)
        self.cb_selectable_vline.setChecked('v' in mark.selectable)

    @property
    def line_props(self):
        """The properties of the lines as a :class:`dict`"""
        return {
            'ls':
            self.combo_line_style.currentText(),
            'marker':
            self.marker_styles[self.combo_marker_style.currentIndex()][1][0],
            'lw':
            float(self.txt_line_width.text().strip() or 0),
            'markersize':
            float(self.txt_marker_size.text().strip() or 0),
            'c':
            self.lbl_color_unselect.color.getRgbF(),
        }

    @property
    def select_props(self):
        """The properties of selected marks as a :class:`dict`"""
        return {
            'c': self.lbl_color_select.color.getRgbF(),
            'lw': float(self.txt_line_width_select.text().strip() or 0)
        }

    def update_mark(self, mark):
        """Update the properties of a mark to match the settings

        Parameters
        ----------
        mark: straditize.cross_mark.CrossMarks
            The mark to update
        """
        if len(self.straditizer.marks) < 2:
            return
        # line properties
        props = self.line_props
        mark._unselect_props.update(props)
        mark._select_props.update(self.select_props)

        # auto_hide
        auto_hide = self.cb_auto_hide.isChecked()
        mark.auto_hide = auto_hide
        if auto_hide:
            props['lw'] = 0

        # show_connected
        show_connected = self.cb_show_connected.isChecked()
        mark.set_connected_artists_visible(show_connected)

        # drag hline
        drag_hline = self.cb_drag_hline.isChecked()
        if drag_hline and 'h' not in mark.draggable:
            mark.draggable.append('h')
        elif not drag_hline and 'h' in mark.draggable:
            mark.draggable.remove('h')

        # drag vline
        drag_vline = self.cb_drag_vline.isChecked()
        if drag_vline and 'v' not in mark.draggable:
            mark.draggable.append('v')
        elif not drag_vline and 'v' in mark.draggable:
            mark.draggable.remove('v')

        # select hline
        select_hline = self.cb_selectable_hline.isChecked()
        if select_hline and 'h' not in mark.selectable:
            mark.selectable.append('h')
        elif not select_hline and 'h' in mark.selectable:
            mark.selectable.remove('h')

        # select hline
        select_vline = self.cb_selectable_vline.isChecked()
        if select_vline and 'v' not in mark.selectable:
            mark.selectable.append('v')
        elif not select_vline and 'v' in mark.selectable:
            mark.selectable.remove('v')

        show_horizontal = self.cb_show_hlines.isChecked()
        mark.hide_horizontal = ~show_horizontal
        show_vertical = self.cb_show_vlines.isChecked()
        mark.hide_vertical = ~show_vertical

        for l in chain(mark.hlines, mark.vlines):
            l.update(props)
        for l in mark.hlines:
            l.set_visible(show_horizontal)
        for l in mark.vlines:
            l.set_visible(show_vertical)

    def change_hline_draggable(self, state):
        """Enable or disable the dragging of horizontal lines

        Parameters
        ----------
        state: Qt.Checked or Qt.Unchecked
            If Qt.Checked, the horizontal lines can be dragged and dropped"""
        if state == Qt.Checked:
            for mark in self.marks:
                mark.draggable = np.unique(np.r_[['h'], mark.draggable])
        else:
            for mark in self.marks:
                mark.draggable = np.array(list(set(mark.draggable) - {'h'}))

    def change_hline_selectable(self, state):
        """Enable or disable the selection of horizontal lines

        Parameters
        ----------
        state: Qt.Checked or Qt.Unchecked
            If Qt.Checked, the horizontal lines can be selected"""
        if state == Qt.Checked:
            for mark in self.marks:
                mark.selectable = np.unique(np.r_[['h'], mark.selectable])
        else:
            for mark in self.marks:
                mark.selectable = np.array(list(set(mark.selectable) - {'h'}))

    def change_vline_draggable(self, state):
        """Enable or disable the dragging of vertical lines

        Parameters
        ----------
        state: Qt.Checked or Qt.Unchecked
            If Qt.Checked, the vertical lines can be dragged and dropped"""
        if state == Qt.Checked:
            for mark in self.marks:
                mark.draggable = np.unique(np.r_[['v'], mark.draggable])
        else:
            for mark in self.marks:
                mark.draggable = np.array(list(set(mark.draggable) - {'v'}))

    def change_vline_selectable(self, state):
        """Enable or disable the selection of vertical lines

        Parameters
        ----------
        state: Qt.Checked or Qt.Unchecked
            If Qt.Checked, the vertical lines can be selected"""
        if state == Qt.Checked:
            for mark in self.marks:
                mark.selectable = np.unique(np.r_[['v'], mark.selectable])
        else:
            for mark in self.marks:
                mark.selectable = np.array(list(set(mark.selectable) - {'v'}))

    def change_show_hlines(self, state):
        """Enable of disable the visibility of horizontal lines

        Parameters
        ----------
        state: Qt.Checked or Qt.Unchecked
            If Qt.Checked, all horizontal lines are hidden"""
        if state == Qt.Checked:
            for mark in self.marks:
                mark.hide_horizontal = False
                mark.set_visible(True)
        else:
            for mark in self.marks:
                mark.hide_horizontal = True
                mark.set_visible(True)
        self.draw_figs()

    def change_show_vlines(self, state):
        """Enable of disable the visibility of vertical lines

        Parameters
        ----------
        state: Qt.Checked or Qt.Unchecked
            If Qt.Checked, all vertical lines are hidden"""
        if state == Qt.Checked:
            for mark in self.marks:
                mark.hide_vertical = False
                mark.set_visible(True)
        else:
            for mark in self.marks:
                mark.hide_vertical = True
                mark.set_visible(True)
        self.draw_figs()

    def enable_or_disable_widgets(self, b):
        """Renabled to use the :meth:`refresh` method
        """
        self.refresh()

    def fill_after_adding(self, mark):
        if not self._filled:
            self.refresh()

    def go_to_right_mark(self):
        """Move the plot to the next right cross mark"""
        ax = self.straditizer.marks[0].ax
        if ax.xaxis_inverted():
            return self.go_to_smaller_x_mark(min(ax.get_xlim()))
        else:
            return self.go_to_greater_x_mark(max(ax.get_xlim()))

    def go_to_left_mark(self):
        """Move the plot to the previous left cross mark"""
        ax = self.straditizer.marks[0].ax
        if ax.xaxis_inverted():
            return self.go_to_greater_x_mark(max(ax.get_xlim()))
        else:
            return self.go_to_smaller_x_mark(min(ax.get_xlim()))

    def go_to_greater_x_mark(self, x):
        """Move the plot to the next mark with a x-position greater than `x`

        Parameters
        ----------
        x: float
            The reference x-position that shall be smaller than the new
            centered mark"""
        def is_visible(mark):
            return (np.searchsorted(np.sort(xlim),
                                    mark.xa) == 1).any() and (np.searchsorted(
                                        np.sort(ylim), mark.ya) == 1).any()

        ax = next(self.marks).ax
        xlim = np.asarray(ax.get_xlim())
        ylim = np.asarray(ax.get_ylim())
        marks = self.straditizer.marks
        if len(marks[0].xa) > 1:
            # get the mark in the center
            yc = ylim.mean()
            try:
                mark = min(filter(is_visible, marks),
                           key=lambda m: np.abs(m.ya - yc).min())
            except ValueError:  # empty sequence
                mark = min(marks, key=lambda m: np.abs(m.ya - yc).min())
            # if all edges are visible already, we return
            if (np.searchsorted(xlim, mark.xa) == 1).all():
                return
            mask = mark.xa > x
            if not mask.any():  # already on the right side
                return
            i = mark.xa[mask].argmin()
            x = mark.xa[mask][i]
            dx = np.diff(xlim) / 2.
            ax.set_xlim(x - dx, x + dx)
            ax.set_ylim(*ax.get_ylim())
        else:
            distances = ((mark.xa > x).any()
                         and (mark.xa[mark.xa > x] - x).min()
                         for mark in marks)
            try:
                dist, mark = min((t for t in zip(distances, marks) if t[0]),
                                 key=lambda t: t[0])
            except ValueError:  # empty sequence
                return
            mask = mark.xa > x
            i = mark.xa[mask].argmin()
            x = mark.xa[mask][i]
            j = np.abs(mark.ya - ylim.mean()).argmin()
            y = mark.ya[j]
            dx = np.diff(xlim) / 2.
            ax.set_xlim(x - dx, x + dx)
            dy = np.diff(ylim) / 2.
            ax.set_ylim(y - dy, y + dy)
        self.straditizer.draw_figure()

    def go_to_smaller_x_mark(self, x):
        """Move the plot to the next mark with a x-position smaller than `x`

        Parameters
        ----------
        x: float
            The reference x-position that shall be greater than the new
            centered mark"""
        def is_visible(mark):
            return (np.searchsorted(np.sort(xlim),
                                    mark.xa) == 1).any() and (np.searchsorted(
                                        np.sort(ylim), mark.ya) == 1).any()

        ax = next(self.marks).ax
        xlim = np.asarray(ax.get_xlim())
        ylim = np.asarray(ax.get_ylim())
        marks = self.straditizer.marks
        if len(marks[0].xa) > 1:
            # get the mark in the center
            yc = ylim.mean()
            try:
                mark = min(filter(is_visible, marks),
                           key=lambda m: np.abs(m.ya - yc).min())
            except ValueError:  # empty sequence
                mark = min(marks, key=lambda m: np.abs(m.ya - yc).min())
            # if all edges are visible already, we return
            if (np.searchsorted(xlim, mark.xa) == 1).all():
                return
            mask = mark.xa < x
            if not mask.any():  # already on the right side
                return
            i = mark.xa[mask].argmin()
            x = mark.xa[mask][i]
            dx = np.diff(xlim) / 2.
            ax.set_xlim(x - dx, x + dx)
            ax.set_ylim(*ax.get_ylim())
        else:
            distances = ((mark.xa < x).any()
                         and (mark.xa[mark.xa < x] - x).min()
                         for mark in marks)
            try:
                dist, mark = min((t for t in zip(distances, marks) if t[0]),
                                 key=lambda t: t[0])
            except ValueError:  # empty sequence
                return
            mask = mark.xa < x
            i = mark.xa[mask].argmin()
            x = mark.xa[mask][i]
            j = np.abs(mark.ya - ylim.mean()).argmin()
            y = mark.ya[j]
            dx = np.diff(xlim) / 2.
            ax.set_xlim(x - dx, x + dx)
            dy = np.diff(ylim) / 2.
            ax.set_ylim(y - dy, y + dy)
        self.straditizer.draw_figure()

    def go_to_upper_mark(self):
        """Go to the next mark above the current y-limits"""
        ax = self.straditizer.marks[0].ax
        if ax.xaxis_inverted():
            return self.go_to_greater_y_mark(max(ax.get_ylim()))
        else:
            return self.go_to_smaller_y_mark(min(ax.get_ylim()))

    def go_to_lower_mark(self):
        """Go to the next mark below the current y-limits"""
        ax = self.straditizer.marks[0].ax
        if ax.xaxis_inverted():
            return self.go_to_smaller_y_mark(min(ax.get_ylim()))
        else:
            return self.go_to_greater_y_mark(max(ax.get_ylim()))

    def go_to_greater_y_mark(self, y):
        """Move the plot to the next mark with a y-position greater than `y`

        Parameters
        ----------
        y: float
            The reference y-position that shall be smaller than the new
            centered mark"""
        def is_visible(mark):
            return (np.searchsorted(np.sort(xlim),
                                    mark.xa) == 1).any() and (np.searchsorted(
                                        np.sort(ylim), mark.ya) == 1).any()

        ax = next(self.marks).ax
        xlim = np.asarray(ax.get_xlim())
        ylim = np.asarray(ax.get_ylim())
        marks = self.straditizer.marks
        if len(marks[0].ya) > 1:
            # get the mark in the center
            xc = xlim.mean()
            try:
                mark = min(filter(is_visible, marks),
                           key=lambda m: np.abs(m.xa - xc).min())
            except ValueError:  # empty sequence
                mark = min(marks, key=lambda m: np.abs(m.xa - xc).min())
            # if all edges are visible already, we return
            if (np.searchsorted(ylim, mark.ya) == 1).all():
                return
            mask = mark.ya > y
            if not mask.any():  # already on the right side
                return
            i = mark.ya[mask].argmin()
            y = mark.ya[mask][i]
            dy = np.diff(ylim) / 2.
            ax.set_ylim(y - dy, y + dy)
        else:
            distances = ((mark.ya > y).any()
                         and (mark.xa[mark.ya > y] - y).min()
                         for mark in marks)
            try:
                dist, mark = min((t for t in zip(distances, marks) if t[0]),
                                 key=lambda t: t[0])
            except ValueError:  # empty sequence
                return
            mask = mark.ya > y
            i = mark.ya[mask].argmin()
            y = mark.ya[mask][i]
            j = np.abs(mark.xa - xlim.mean()).argmin()
            x = mark.xa[j]
            dx = np.diff(xlim) / 2.
            ax.set_xlim(x - dx, x + dx)
            dy = np.diff(ylim) / 2.
            ax.set_ylim(y - dy, y + dy)
        self.straditizer.draw_figure()

    def go_to_smaller_y_mark(self, y):
        """Move the plot to the next mark with a y-position smaller than `x`

        Parameters
        ----------
        y: float
            The reference y-position that shall be smaller than the new
            centered mark"""
        def is_visible(mark):
            return (np.searchsorted(np.sort(xlim),
                                    mark.xa) == 1).any() and (np.searchsorted(
                                        np.sort(ylim), mark.ya) == 1).any()

        ax = next(self.marks).ax
        xlim = np.asarray(ax.get_xlim())
        ylim = np.asarray(ax.get_ylim())
        marks = self.straditizer.marks
        if len(marks[0].ya) > 1:
            # get the mark in the center
            xc = xlim.mean()
            try:
                mark = min(filter(is_visible, marks),
                           key=lambda m: np.abs(m.xa - xc).min())
            except ValueError:  # empty sequence
                mark = min(marks, key=lambda m: np.abs(m.xa - xc).min())
            # if all edges are visible already, we return
            if (np.searchsorted(ylim, mark.ya) == 1).all():
                return
            mask = mark.ya < y
            if not mask.any():  # already on the right side
                return
            i = mark.ya[mask].argmin()
            y = mark.ya[mask][i]
            dy = np.diff(ylim) / 2.
            ax.set_ylim(y - dy, y + dy)
        else:
            distances = ((mark.ya < y).any()
                         and (mark.ya[mark.ya < y] - y).min()
                         for mark in marks)
            try:
                dist, mark = min((t for t in zip(distances, marks) if t[0]),
                                 key=lambda t: t[0])
            except ValueError:  # empty sequence
                return
            mask = mark.ya < y
            i = mark.ya[mask].argmin()
            y = mark.ya[mask][i]
            j = np.abs(mark.xa - xlim.mean()).argmin()
            x = mark.xa[j]
            dx = np.diff(xlim) / 2.
            ax.set_xlim(x - dx, x + dx)
            dy = np.diff(ylim) / 2.
            ax.set_ylim(y - dy, y + dy)
        self.straditizer.draw_figure()

    def add_toolbar_widgets(self, mark):
        """Add the navigation actions to the toolbar"""
        tb = self.straditizer.marks[0].ax.figure.canvas.toolbar
        if not isinstance(tb, QToolBar):
            return
        if self.tb_actions:
            self.remove_actions()

        self.tb_actions.append(tb.addSeparator())
        try:
            mark.x
        except NotImplementedError:
            add_right = False
        else:
            a = tb.addAction(QIcon(get_icon('left_mark.png')), 'left mark',
                             self.go_to_left_mark)
            a.setToolTip('Move to the next cross mark on the left')
            self.tb_actions.append(a)
            add_right = True

        try:
            mark.y
        except NotImplementedError:
            pass
        else:
            a = tb.addAction(QIcon(get_icon('upper_mark.png')), 'upper mark',
                             self.go_to_upper_mark)
            a.setToolTip('Move to the next cross mark above')
            self.tb_actions.append(a)

            a = tb.addAction(QIcon(get_icon('lower_mark.png')), 'lower mark',
                             self.go_to_lower_mark)
            a.setToolTip('Move to the next cross mark below')
            self.tb_actions.append(a)

        if add_right:
            a = tb.addAction(QIcon(get_icon('right_mark.png')), 'right mark',
                             self.go_to_right_mark)
            a.setToolTip('Move to the next cross mark on the right')
            self.tb_actions.append(a)
        self._toolbar = tb

    def remove_actions(self):
        """Remove the navigation actions from the toolbar"""
        if self._toolbar is None:
            return
        tb = self._toolbar
        for a in self.tb_actions:
            tb.removeAction(a)
        self.tb_actions.clear()

    def refresh(self):
        """Reimplemented to also set the properties of this widget
        """
        super(MarkerControl, self).refresh()
        if self.straditizer is not None and self.straditizer.marks:
            self._filled = True
            mark = self.straditizer.marks[0]
            self.fill_from_mark(mark)
            self.add_toolbar_widgets(mark)
        else:
            self.remove_actions()
            self._filled = False
        if self.straditizer is not None:
            self.straditizer.mark_added.connect(self.fill_after_adding)
            self.straditizer.mark_added.connect(self.update_mark)
Exemple #3
0
class NormalizationWidget(QWidget):
    """A simple widget representing a boundary norm"""

    def __init__(self, parent, norm):
        QWidget.__init__(self, parent)
        self.norm = norm

        validator = QDoubleValidator()
        self.txt_min = QLineEdit()
        self.txt_min.setValidator(validator)
        self.txt_max = QLineEdit()
        self.txt_max.setValidator(validator)

        self.lbl_linthresh = QLabel('linthresh:')
        self.txt_linthresh = QLineEdit()  # linthresh for SymLogNorm
        self.txt_linthresh.setValidator(validator)
        self.txt_linthresh.setToolTip(
            'The threshold for linear scaling. Within this distance from 0, '
            'the scaling will be linear, not logarithmic.')

        self.lbl_gamma = QLabel('gamma:')
        self.txt_gamma = QLineEdit()  # gamma for PowerNorm
        self.txt_gamma.setValidator(validator)
        self.txt_gamma.setToolTip('The power value for the PowerNorm')

        self.fill_from_norm()

        hbox = QHBoxLayout()
        hbox.addWidget(QLabel('Min.:'))
        hbox.addWidget(self.txt_min)
        hbox.addWidget(QLabel('Max.:'))
        hbox.addWidget(self.txt_max)
        hbox.addWidget(self.lbl_linthresh)
        hbox.addWidget(self.txt_linthresh)
        hbox.addWidget(self.lbl_gamma)
        hbox.addWidget(self.txt_gamma)
        self.setLayout(hbox)

        self.txt_min.textChanged.connect(self.set_obj)
        self.txt_max.textChanged.connect(self.set_obj)
        self.txt_linthresh.textChanged.connect(self.set_obj)
        self.txt_gamma.textChanged.connect(self.set_obj)

    def fill_from_norm(self):
        norm = self.norm
        if norm.vmin is not None:
            self.txt_min.setText('%1.6g' % norm.vmin)
        if norm.vmax is not None:
            self.txt_max.setText('%1.6g' % norm.vmax)
        if isinstance(self.norm, mcol.SymLogNorm):
            self.txt_linthresh.setVisible(True)
            self.txt_linthresh.setEnabled(True)
            self.lbl_linthresh.setVisible(True)
            self.txt_linthresh.setText('%1.6g' % norm.linthresh)
        else:
            self.txt_linthresh.setVisible(False)
            self.txt_linthresh.setEnabled(False)
            self.lbl_linthresh.setVisible(False)
        if isinstance(norm, mcol.PowerNorm):
            self.txt_gamma.setVisible(True)
            self.txt_gamma.setEnabled(True)
            self.lbl_gamma.setVisible(True)
            self.txt_gamma.setText('%1.6g' % norm.gamma)
        else:
            self.txt_gamma.setVisible(False)
            self.txt_gamma.setEnabled(False)
            self.lbl_gamma.setVisible(False)

    def set_obj(self):
        cls = self.norm.__class__
        if issubclass(cls, mcol.PowerNorm):
            args = [float(self.txt_gamma.text().strip() or 1.0)]
        elif issubclass(cls, mcol.SymLogNorm):
            args = [float(self.txt_linthresh.text().strip() or 1e-3)]
        else:
            args = []
        vmin = vmax = None
        if self.txt_min.text().strip():
            vmin = float(self.txt_min.text().strip())
        if self.txt_max.text().strip():
            vmax = float(self.txt_max.text().strip())
        try:
            norm = cls(*args, vmin=vmin, vmax=vmax)
        except Exception:
            pass
        else:
            self.parent().set_obj(norm)
Exemple #4
0
class ArrayFmtWidget(QWidget):
    """Fmt widget for :class:`psy_simple.plotters.DataTicksCalculator`

    This formatoption widgets contains 3 line edits, one for the minimum, one
    for the maximum and one for the step size. And a spin box for the number
    of increments"""

    def __init__(self, parent, array=None):
        QWidget.__init__(self, parent)

        self.txt_min = QLineEdit()
        self.txt_min.setValidator(QDoubleValidator())
        self.txt_max = QLineEdit()
        self.txt_max.setValidator(QDoubleValidator())
        self.txt_step = QLineEdit()
        self.txt_step.setValidator(QDoubleValidator(1e-10, 1e10, 10))
        self.sb_nsteps = QSpinBox()
        self.step_inc_combo = combo = QComboBox()
        combo.addItems(['Step', '# Steps'])

        if array is not None:
            self.txt_min.setText('%1.4g' % array.min())
            self.txt_max.setText('%1.4g' % array.max())
            steps = np.diff(array)
            if len(steps) == 1 or np.diff(steps).max() < 1e-5:
                self.txt_step.setText('%1.4g' % steps[0])
                combo.setCurrentIndex(0)
            else:
                combo.setCurrentIndex(1)
            self.sb_nsteps.setValue(len(array))

        self.toggle_txt_step(combo.currentText())

        hbox = QHBoxLayout()
        hbox.addWidget(QLabel('Min.'))
        hbox.addWidget(self.txt_min)
        hbox.addWidget(QLabel('Max.'))
        hbox.addWidget(self.txt_max)
        hbox.addWidget(combo)
        hbox.addWidget(self.txt_step)
        hbox.addWidget(self.sb_nsteps)
        self.setLayout(hbox)

        for w in [self.txt_min, self.txt_max, self.txt_step]:
            w.textChanged.connect(self.set_array)
        self.sb_nsteps.valueChanged.connect(self.set_array)

        combo.currentTextChanged.connect(self.toggle_txt_step)

    def toggle_txt_step(self, s):
        show_step = s == 'Step'
        self.txt_step.setVisible(show_step)
        self.sb_nsteps.setVisible(not show_step)
        self.txt_step.setEnabled(show_step)
        self.sb_nsteps.setEnabled(not show_step)

    def set_array(self, *args, **kwargs):
        try:
            vmin = float(self.txt_min.text())
        except (ValueError, TypeError):
            return
        try:
            vmax = float(self.txt_max.text())
        except (ValueError, TypeError):
            return
        if self.txt_step.isEnabled():
            try:
                step = float(self.txt_step.text().strip())
            except (ValueError, TypeError):
                return
            arr = np.arange(vmin, vmax + 0.05 * step, step)
        else:
            arr = np.linspace(vmin, vmax, self.sb_nsteps.value())
        self.parent().set_obj(np.round(arr, 4).tolist())
class ImageRotator(StraditizerControlBase, QWidget):
    """Widget to rotate the image

    This control mainly adds a QLineEdit :attr:`txt_rotate` to the
    :class:`straditize.widgets.StraditizerWidgets` to rotate the image.
    It also enables the user to specify the rotation angle using two
    connected :class:`~straditize.cross_mark.CrossMarks`. Here the user can
    decide between a horizontal alignment (:attr:`btn_rotate_horizontal`) or
    a vertical alignment (:attr:`btn_rotate_vertical`)"""

    _rotating = False
    _ha = False
    _va = False

    #: A QPushButton for horizontal alignment
    btn_rotate_horizontal = None

    #: A QPushButton for vertical alignment
    btn_rotate_vertical = None

    #: A QLineEdit to display the rotation angle
    txt_rotate = None

    @docstrings.dedent
    def __init__(self, straditizer_widgets, item=None, *args, **kwargs):
        """
        Parameters
        ----------
        %(StraditizerControlBase.init_straditizercontrol.parameters)s"""
        super(ImageRotator, self).__init__(*args, **kwargs)
        self.txt_rotate = QLineEdit()
        self.txt_rotate.setValidator(QDoubleValidator())

        self.btn_rotate_horizontal = QPushButton('Horizontal alignment')
        self.btn_rotate_horizontal.setToolTip(
            'Mark two points that should be on the same horizontal level '
            'and rotate the picture to achieve this.')

        self.btn_rotate_vertical = QPushButton('Vertical alignment')
        self.btn_rotate_vertical.setToolTip(
            'Mark two points that should be on the same vertical level '
            'and rotate the picture to achieve this.')

        self.init_straditizercontrol(straditizer_widgets, item)

        # ---------------------------------------------------------------------
        # --------------------------- Layouts ---------------------------------
        # ---------------------------------------------------------------------

        layout = QVBoxLayout()

        hbox = QHBoxLayout()
        hbox.addWidget(QLabel('Rotate:'))
        hbox.addWidget(self.txt_rotate)
        layout.addLayout(hbox)

        layout.addWidget(self.btn_rotate_horizontal)
        layout.addWidget(self.btn_rotate_vertical)

        self.setLayout(layout)

        # ---------------------------------------------------------------------
        # --------------------------- Connections -----------------------------
        # ---------------------------------------------------------------------

        self.txt_rotate.textChanged.connect(self.start_rotation)
        self.btn_rotate_horizontal.clicked.connect(
            self.start_horizontal_alignment)
        self.btn_rotate_vertical.clicked.connect(
            self.start_vertical_alignment)

        self.widgets2disable = [self.txt_rotate, self.btn_rotate_horizontal,
                                self.btn_rotate_vertical]

    def should_be_enabled(self, w):
        if not self._rotating:
            return self.straditizer is not None
        elif w is self.txt_rotate:
            return True
        return False

    def start_rotation(self):
        """Start the rotation (if not already started)"""
        if not self._rotating:
            self._rotating = True
            self.connect2apply(self.rotate_image)
            self.connect2cancel(self.remove_marks,
                                self.straditizer.draw_figure)
            self.straditizer_widgets.apply_button.setText('Rotate')

    def draw_figure(self):
        self.straditizer.draw_figure()

    def start_horizontal_alignment(self):
        """Start the horizontal alignment"""
        self.start_rotation()
        self._ha = True
        self._start_alignment()

    def start_vertical_alignment(self):
        """Start the vertical alignment"""
        self.start_rotation()
        self._va = True
        self._start_alignment()

    def _start_alignment(self):
        def new_mark(pos):
            if len(self.straditizer.marks) == 2:
                return
            mark = cm.CrossMarks(pos, ax=stradi.ax, c='b')
            if self.straditizer.marks:
                marks = [mark] + self.straditizer.marks
                mark.connect_marks(marks, visible=True)
                self.update_txt_rotate(marks=marks)
            mark.moved.connect(self.update_txt_rotate)
            return [mark]

        stradi = self.straditizer
        stradi.marks = []

        stradi.mark_cids.add(stradi.fig.canvas.mpl_connect(
            'button_press_event', stradi._add_mark_event(new_mark)))
        stradi.mark_cids.add(stradi.fig.canvas.mpl_connect(
            'button_press_event', stradi._remove_mark_event))

    def update_txt_rotate(self, *args, marks=[], **kwargs):
        """Update the :attr:`txt_rotate` from the displayed cross marks"""
        stradi = self.straditizer
        marks = marks or stradi.marks
        if len(marks) != 2:
            return
        x0, y0 = marks[0].pos
        x1, y1 = marks[1].pos
        dx = x0 - x1
        dy = y0 - y1

        if self._ha:
            angle = np.arctan(dy / dx) if dx else np.sign(dy) * np.pi / 2.
            self.txt_rotate.setText('%1.3g' % np.rad2deg(angle))
        elif self._va:
            angle = np.arctan(dx / dy) if dy else np.sign(dx) * np.pi / 2.
            self.txt_rotate.setText('%1.3g' % -np.rad2deg(angle))

    def enable_or_disable_widgets(self, b):
        super(ImageRotator, self).enable_or_disable_widgets(b)
        if self._rotating:
            self.txt_rotate.setEnabled(self.should_be_enabled(self.txt_rotate))

    @property
    def angle(self):
        """The rotation angle from :attr:`txt_rotate` as a float"""
        angle = self.txt_rotate.text()
        if not angle.strip():
            return
        return float(angle.strip())

    def rotate_image(self):
        """Rotate the image based on the specified :attr:`angle`"""
        angle = self.angle
        if angle is None:
            return
        sw = self.straditizer_widgets
        answer = QMessageBox.Yes if sw.always_yes else QMessageBox.question(
            self, 'Restart project?',
            'This will close the straditizer and create new figures. '
            'Are you sure, you want to continue?')
        if answer == QMessageBox.Yes:
            image = self.straditizer.image.rotate(angle, expand=True)
            attrs = self.straditizer.attrs
            self.straditizer_widgets.close_straditizer()
            self.straditizer_widgets.menu_actions.open_straditizer(
                image, attrs=attrs)

        self._rotating = self._ha = self._va = False

    def remove_marks(self):
        """Remove the cross marks used for the rotation angle"""
        self._rotating = self._ha = self._va = False
        self.straditizer.remove_marks()
        self.straditizer_widgets.apply_button.setText('Apply')