コード例 #1
0
ファイル: PMAgg.py プロジェクト: jangocheng/pyminer
class Window(QtWidgets.QMainWindow, Ui_MainWindow):
    """
    重写修改该类即可实现自定义后端界面,相加什么按钮可以随便加,目前还只是个demo

    self.canvas.draw() 每执行该函数,图形重绘
    """
    def __init__(self, figure):
        super(Window, self).__init__()
        self.setupUi(self)  # 先执行父类方法,以产生成员变量
        self.figure = figure
        self.canvas = FigureCanvas(self.figure)  # 这里的canvas就是曲线图
        self.toolbar = NavigationToolbar(self.canvas, self)
        self.toolbar.hide()  # 隐藏QT原来的工具栏
        # self.graphicsView.addWidget(self.canvas)  # 将canvas渲染到布局中
        self.scene = QGraphicsScene()
        self.scene.addWidget(self.canvas)
        self.graphicsView.setScene(self.scene)
        self.graphicsView.show()
        self.actionX_X.triggered.connect(self.axes_control_slot)
        # 初始化当前界面
        self.init_gui()
        # 槽函数连接
        # 当前子图对象切换
        self.current_path = os.path.dirname(__file__)
        self.saveAction = QAction(
            QIcon(os.path.join(self.current_path, 'icons/save.png')), 'save',
            self)
        self.saveAction.setShortcut('Ctrl+S')
        self.saveAction.triggered.connect(self.save_slot)
        self.toolBar.addAction(self.saveAction)

        self.settingAction = QAction(
            QIcon(os.path.join(self.current_path, 'icons/setting.png')),
            'setting', self)
        self.settingAction.triggered.connect(self.axes_control_slot)
        self.toolBar.addAction(self.settingAction)

        self.homeAction = QAction(
            QIcon(os.path.join(self.current_path, 'icons/home.png')), 'home',
            self)
        self.homeAction.setShortcut('Ctrl+H')
        self.homeAction.triggered.connect(self.home_slot)
        self.toolBar.addAction(self.homeAction)

        self.backAction = QAction(
            QIcon(os.path.join(self.current_path, 'icons/back.png')), 'back',
            self)
        self.backAction.triggered.connect(self.back_slot)
        self.toolBar.addAction(self.backAction)

        self.frontAction = QAction(
            QIcon(os.path.join(self.current_path, 'icons/front.png')), 'front',
            self)
        self.frontAction.triggered.connect(self.front_slot)
        self.toolBar.addAction(self.frontAction)

        self.zoomAction = QAction(
            QIcon(os.path.join(self.current_path, 'icons/zoom.png')), 'zoom',
            self)
        self.zoomAction.triggered.connect(self.zoom_slot)
        self.toolBar.addAction(self.zoomAction)

        self.panAction = QAction(
            QIcon(os.path.join(self.current_path, 'icons/pan.png')), '平移',
            self)
        self.panAction.triggered.connect(self.pan_slot)
        self.toolBar.addAction(self.panAction)

        self.rotateAction = QAction(
            QIcon(os.path.join(self.current_path, 'icons/rotate.png')),
            'rotate', self)
        self.rotateAction.triggered.connect(self.rotate_slot)
        self.toolBar.addAction(self.rotateAction)

        self.textAction = QAction(
            QIcon(os.path.join(self.current_path, 'icons/text.png')), 'text',
            self)
        self.textAction.triggered.connect(self.add_text_slot)
        self.toolBar.addAction(self.textAction)

        self.rectAction = QAction(
            QIcon(os.path.join(self.current_path, 'icons/rect.png')), 'rect',
            self)
        self.rectAction.triggered.connect(self.add_rect_slot)
        self.toolBar.addAction(self.rectAction)

        self.ovalAction = QAction(
            QIcon(os.path.join(self.current_path, 'icons/oval.png')), 'oval',
            self)
        self.ovalAction.triggered.connect(self.add_oval_slot)
        self.toolBar.addAction(self.ovalAction)

        self.arrowAction = QAction(
            QIcon(os.path.join(self.current_path, 'icons/arrow.png')), 'arrow',
            self)
        self.arrowAction.triggered.connect(self.add_arrow_slot)
        self.toolBar.addAction(self.arrowAction)

        self.pointAction = QAction(
            QIcon(os.path.join(self.current_path, 'icons/point.png')), 'point',
            self)
        self.pointAction.triggered.connect(self.add_point_slot)
        self.toolBar.addAction(self.pointAction)

        self.styleAction = QAction(
            QIcon(os.path.join(self.current_path, 'icons/style.png')), 'style',
            self)
        self.styleAction.triggered.connect(self.add_style_slot)
        self.toolBar.addAction(self.styleAction)

        # 将下拉菜单放在最右边
        self.toolBar.addSeparator()
        spacer = QWidget()
        spacer.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Expanding)
        self.toolBar.addWidget(spacer)
        self.toolBar.addWidget(self.comboBox)

        # 以上为工具栏1

        self.gridAction = QAction(
            QIcon(os.path.join(self.current_path, 'icons/grid.png')),
            '显示/隐藏网格', self)
        self.gridAction.triggered.connect(self.show_grid_slot)
        self.toolBar_2.addAction(self.gridAction)

        self.legendAction = QAction(
            QIcon(os.path.join(self.current_path, 'icons/legend.png')),
            '显示/隐藏图例', self)
        self.legendAction.triggered.connect(self.show_legend_slot)
        self.toolBar_2.addAction(self.legendAction)

        self.colorbarAction = QAction(
            QIcon(os.path.join(self.current_path, 'icons/colorbar.png')),
            '显示/隐藏colorbar', self)
        self.colorbarAction.triggered.connect(self.show_colorbar_slot)
        self.toolBar_2.addAction(self.colorbarAction)

        self.layoutAction = QAction(
            QIcon(os.path.join(self.current_path, 'icons/layout.png')), '改变布局',
            self)
        # self.layoutAction.triggered.connect(self.show_layout_slot)
        self.toolBar_2.addAction(self.layoutAction)

        self.mainViewAction = QAction(
            QIcon(os.path.join(self.current_path, 'icons/mainView.png')),
            'mainView', self)
        self.mainViewAction.triggered.connect(self.mainView_slot)
        self.toolBar_2.addAction(self.mainViewAction)

        self.leftViewAction = QAction(
            QIcon(os.path.join(self.current_path, 'icons/leftView.png')),
            'leftView', self)
        self.leftViewAction.triggered.connect(self.leftView_slot)
        self.toolBar_2.addAction(self.leftViewAction)

        self.rightViewAction = QAction(
            QIcon(os.path.join(self.current_path, 'icons/rightView.png')),
            'rightView', self)
        self.rightViewAction.triggered.connect(self.rightView_slot)
        self.toolBar_2.addAction(self.rightViewAction)

        self.topViewAction = QAction(
            QIcon(os.path.join(self.current_path, 'icons/topView.png')),
            'topView', self)
        self.topViewAction.triggered.connect(self.topView_slot)
        self.toolBar_2.addAction(self.topViewAction)

        self.bottomViewAction = QAction(
            QIcon(os.path.join(self.current_path, 'icons/bottomView.png')),
            'bottomView', self)
        self.bottomViewAction.triggered.connect(self.bottomView_slot)
        self.toolBar_2.addAction(self.bottomViewAction)

        self.backViewAction = QAction(
            QIcon(os.path.join(self.current_path, 'icons/backView.png')),
            'backView', self)
        self.backViewAction.triggered.connect(self.backView_slot)
        self.toolBar_2.addAction(self.backViewAction)

        # 样式右键菜单功能集
        self.setContextMenuPolicy(QtCore.Qt.CustomContextMenu)
        self.customContextMenuRequested.connect(self.rightMenuShow)
        self.rightMenuShow()  # 创建上下文菜单

        self.comboBox.currentIndexChanged.connect(self.combobox_slot)
        # 获取子图对象
        self.axes = self.canvas.figure.get_axes()
        if not self.axes:
            QtWidgets.QMessageBox.warning(self.canvas.parent(), "Error",
                                          "There are no axes to edit.")
            return
        elif len(self.axes) == 1:
            self.current_subplot, = self.axes
            titles = ['图1']
        else:
            titles = ['图' + str(i + 1) for i in range(len(self.axes))]
        # 将三维图的初始视角保存下来,便于旋转之后可以复原
        self.init_views = [(index, item.azim, item.elev)
                           for index, item in enumerate(self.axes)
                           if hasattr(item, 'azim')]
        self.comboBox.addItems(titles)

        # 鼠标拖拽,实现三维图形旋转功能
        self.canvas.mpl_connect('motion_notify_event', self.on_rotate)
        # 鼠标拖拽,实现画矩形功能
        self.canvas.mpl_connect('motion_notify_event', self.add_rect)
        self.mouse_pressed = False
        # 鼠标拖拽,实现画椭圆和按住shift画圆的功能实现
        self.canvas.mpl_connect('motion_notify_event', self.add_oval)
        # 画箭头
        self.canvas.mpl_connect('motion_notify_event', self.add_arrow)
        # 画点,并且按住点能够移动
        self.canvas.mpl_connect('button_press_event', self.add_point)
        self.canvas.mpl_connect('motion_notify_event', self.add_point)
        self.canvas.mpl_connect('button_release_event', self.add_point)
        self.canvas.mpl_connect('pick_event', self.add_point)
        self.press_time = 0
        # 记录鼠标运动的位置
        self.rotate_mouse_point = None
        self.canvas.mpl_connect('button_press_event', self.add_text)
        # 为曲线添加样式的功能实现
        self.canvas.mpl_connect('button_press_event', self.add_style)
        self.canvas.mpl_connect('pick_event', self.add_style)
        # 为图例绑定监听事件
        self.canvas.mpl_connect('button_press_event', self.change_legend)
        self.canvas.mpl_connect('pick_event', self.change_legend)
        # 所有的按钮标志
        self.make_flag_invalid()
        self.artist = None
        for ax in self.axes:
            for line in ax.lines:
                line.set_picker(True)
                line.set_pickradius(5)

    def make_flag_invalid(self):
        self.add_rect_flag = False
        self.add_oval_flag = False
        self.add_text_flag = False
        self.rotate_flag = False
        self.home_flag = False
        self.pan_flag = False
        self.zoom_flag = False
        self.add_arrow_flag = False
        self.add_point_flag = False
        self.add_style_flag = False
        self.show_grid_flag = False
        self.show_legend_flag = False
        # 禁用移动和缩放
        self.toolbar.mode = None

    def home_slot(self):
        self.make_flag_invalid()
        self.home_flag = not self.home_flag
        self.home()

    def home(self):
        """
        matplotlib lines里面放曲线,patches可以放图形,artists也可以放东西,设为空则可以删除对应的对象
        """
        if not self.home_flag:
            return
        self.toolbar.home()
        # 将三维图视角还原
        for item in self.init_views:
            self.axes[item[0]].view_init(azim=item[1], elev=item[2])
        # 将所有添加的形状去除
        for item in self.axes:
            item.patches = []
            item.artists = []  # 去除画在图中的点,是否需要去掉有待考究
        self.canvas.draw()

    def zoom_slot(self):
        self.make_flag_invalid()
        self.zoom_flag = not self.zoom_flag
        self.zoom()

    def zoom(self):
        if not self.zoom_flag:
            return
        self.toolbar.zoom()

    def pan_slot(self):
        self.make_flag_invalid()
        self.pan_flag = not self.pan_flag
        self.pan()

    def pan(self):
        if not self.pan_flag:
            return
        self.toolbar.pan()

    def save_slot(self):
        self.toolbar.save_figure()

    def front_slot(self):
        self.toolbar._nav_stack.forward()
        self.toolbar._update_view()

    def back_slot(self):
        self.toolbar._nav_stack.back()
        self.toolbar._update_view()

    def add_text_slot(self):
        self.make_flag_invalid()
        self.add_text_flag = not self.add_text_flag

    def add_text(self, event):
        if not self.add_text_flag:
            return
        if self.add_text_flag and event.xdata and event.ydata and not hasattr(
                event.inaxes, 'azim'):
            text, ok = QtWidgets.QInputDialog.getText(self.canvas.parent(),
                                                      '输入文字', '添加注释')
            if ok and text:
                event.inaxes.text(event.xdata, event.ydata, text)
                # plt.text(event.xdata, event.ydata, text)
                self.canvas.draw()

    def rotate_slot(self):
        self.make_flag_invalid()
        self.rotate_flag = not self.rotate_flag

    def on_rotate(self, event):
        """
        通过观察发现,旋转时产生的xdata,ydata是以图像中心为原点,正负0.1范围内的数值
        """
        if not self.rotate_flag:
            return
        # 如果鼠标移动过程有按下,视为拖拽,判断当前子图是否有azim属性来判断当前是否3D
        if event.button and hasattr(event.inaxes, 'azim'):
            for item in self.init_views:
                if self.axes[item[0]] == event.inaxes:
                    delta_azim = 180 * event.xdata * -1 / 0.1
                    delta_elev = 180 * event.ydata / 0.1
                    azim = delta_azim + item[1]
                    elev = delta_elev + item[2]
                    event.inaxes.view_init(azim=azim, elev=elev)
                    self.canvas.draw()

    def add_rect_slot(self):
        # 除了本标记,其余全置False
        self.make_flag_invalid()
        self.add_rect_flag = not self.add_rect_flag

    def add_rect(self, event):
        if not self.add_rect_flag:
            return
        if not event.button and event.inaxes:
            self.ax_init = event
            if self.mouse_pressed and event.inaxes.patches:
                event.inaxes.add_patch(event.inaxes.patches[0])
            self.mouse_pressed = False

        # 仅能在二维图形中画矩形,鼠标按下,且当前子图和初始点的子图相同
        try:
            if event.button and event.inaxes and event.inaxes == self.ax_init.inaxes and not hasattr(
                    event.inaxes, 'azim'):
                self.mouse_pressed = True
                if event.inaxes.patches:
                    event.inaxes.patches.pop()
                rect = plt.Rectangle((self.ax_init.xdata, self.ax_init.ydata),
                                     event.xdata - self.ax_init.xdata,
                                     event.ydata - self.ax_init.ydata,
                                     fill=False,
                                     edgecolor='red',
                                     linewidth=1)
                event.inaxes.add_patch(rect)
                self.canvas.draw()
                self.canvas.flush_events()
        except Exception:
            pass

    def add_oval_slot(self):
        self.make_flag_invalid()
        self.add_oval_flag = not self.add_oval_flag

    def add_oval(self, event):
        if not self.add_oval_flag:
            return
        if not event.button and event.inaxes:
            self.ax_init = event
            if self.mouse_pressed and event.inaxes.patches:
                event.inaxes.add_patch(event.inaxes.patches[0])
            self.mouse_pressed = False
        try:
            if event.button and event.inaxes and event.inaxes == self.ax_init.inaxes and not hasattr(
                    event.inaxes, 'azim'):
                self.mouse_pressed = True
                if event.inaxes.patches:
                    event.inaxes.patches.pop()
                oval = Ellipse(xy=(self.ax_init.xdata, self.ax_init.ydata),
                               width=abs(event.xdata - self.ax_init.xdata) * 2,
                               height=abs(event.ydata - self.ax_init.ydata) *
                               2,
                               angle=0,
                               fill=False,
                               edgecolor='red',
                               linewidth=1)
                event.inaxes.add_patch(oval)
                self.canvas.draw()
                self.canvas.flush_events()
        except Exception:
            pass

    def add_arrow_slot(self):
        self.make_flag_invalid()
        self.add_arrow_flag = not self.add_arrow_flag

    def add_arrow(self, event):
        if not self.add_arrow_flag:
            return
        if not event.button and event.inaxes:
            self.ax_init = event
            if self.mouse_pressed and event.inaxes.patches:
                event.inaxes.add_patch(event.inaxes.patches[0])
            self.mouse_pressed = False
        try:
            if event.button and event.inaxes and event.inaxes == self.ax_init.inaxes and not hasattr(
                    event.inaxes, 'azim'):
                self.mouse_pressed = True
                if event.inaxes.patches:
                    event.inaxes.patches.pop()
                arrow = event.inaxes.arrow(self.ax_init.xdata,
                                           self.ax_init.ydata,
                                           event.xdata - self.ax_init.xdata,
                                           event.ydata - self.ax_init.ydata,
                                           width=0.01,
                                           length_includes_head=True,
                                           head_width=0.05,
                                           head_length=0.1,
                                           fc='r',
                                           ec='r')
                event.inaxes.add_patch(arrow)
                # 请恕我无知,我也不懂这里为什么还要pop一次,我不想思考,但的确这样是正确的。
                if event.inaxes.patches:
                    event.inaxes.patches.pop()
                self.canvas.draw()
                self.canvas.flush_events()
        except Exception:
            pass

    def add_point_slot(self):
        self.make_flag_invalid()
        self.add_point_flag = not self.add_point_flag

    # def add_point(self, event):
    #     if not self.add_point_flag:
    #         return
    #     if event.inaxes and event.button and not hasattr(event.inaxes, 'azim'):
    #         x_range = np.array(event.inaxes.get_xlim())
    #         y_range = np.array(event.inaxes.get_ylim())
    #         self.offset = np.sqrt(np.sum((x_range - y_range) ** 2)) / 20 # 将坐标轴范围的1/50视为误差
    #         self.nearest_point = None
    #         d_min = 10 * self.offset
    #         for point in event.inaxes.artists:
    #             xt, yt = point.get_data()
    #             d = ((xt - event.xdata) ** 2 + (yt - event.ydata) ** 2) ** 0.5
    #             if d <= self.offset and d < d_min:  # 如果在误差范围内,移动该点
    #                 d_min = d
    #                 self.nearest_point = point
    #         if self.nearest_point:
    #             new_point = Line2D([event.xdata], [event.ydata], ls="",
    #                                marker='o', markerfacecolor='r',
    #                                animated=False)
    #             event.inaxes.add_artist(new_point)
    #             event.inaxes.artists.remove(self.nearest_point)
    #             self.canvas.restore_region(self.bg)
    #             event.inaxes.draw_artist(new_point)
    #             self.canvas.blit(event.inaxes.bbox)
    #             self.bg = self.canvas.copy_from_bbox(event.inaxes.bbox)
    def add_point(self, event):
        if not self.add_point_flag:
            return
        if event.name == 'pick_event':
            self.artist = event.artist
            return
        if event.name == 'button_press_event' and not self.artist and hasattr(
                event, 'inaxes') and not hasattr(event.inaxes, 'azim'):
            point = Line2D([event.xdata], [event.ydata],
                           ls="",
                           marker='o',
                           markerfacecolor='r',
                           animated=False,
                           pickradius=5,
                           picker=True)
            event.inaxes.add_artist(point)
            self.canvas.draw()
            return
        if event.name == 'motion_notify_event' and self.artist and hasattr(
                event, 'inaxes') and event.button and not hasattr(
                    event.inaxes, 'azim'):
            xy = self.artist.get_data()
            if len(xy[0]) == 1:  # 判断该对象是否是一个点。
                self.artist.set_data(([event.xdata], [event.ydata]))
                self.canvas.draw()
        if event.name == 'button_release_event':
            self.artist = None

    def add_style_slot(self):
        self.make_flag_invalid()
        self.add_style_flag = not self.add_style_flag

    def add_style(self, event):
        if not self.add_style_flag:
            return
        if event.name == 'pick_event':
            self.artist = event.artist
        if self.artist and event.name == 'button_press_event':
            for line in event.inaxes.lines:
                if self.artist != line:
                    line.set_alpha(0.5)
                else:
                    line.set_alpha(1)
            self.canvas.draw_idle()
            if event.button == 3:
                self.contextMenu.popup(QCursor.pos())  # 2菜单显示的位置
                self.contextMenu.show()
                return
            elif event.button == 1:
                self.artist = None
                return
        if not self.artist and event.name == 'button_press_event' and event.button == 1:
            for line in event.inaxes.lines:
                line.set_alpha(1)
            self.canvas.draw_idle()

    def show_legend_slot(self):
        legend_titles = []
        for index in range(len(self.current_subplot.lines)):
            legend_titles.append('curve ' + str(index + 1))  # 从1开始算
        if self.current_subplot.lines:  # 如果存在曲线才允许画图例
            leg = self.current_subplot.legend(self.current_subplot.lines,
                                              legend_titles)
            leg.set_draggable(True)  # 设置legend可拖拽
            for legline in leg.get_lines():
                legline.set_pickradius(10)
                legline.set_picker(True)  # 给每个legend设置可点击
            self.canvas.draw()

    def change_legend(self, event):
        if event.name == 'pick_event':
            self.artist = event.artist
        if self.artist and event.name == 'button_press_event' and event.button == 3:
            self.contextMenu.popup(QCursor.pos())  # 2菜单显示的位置
            self.contextMenu.show()

    def show_colorbar_slot(self):
        # print(self.current_subplot.curves)
        pass
        # self.canvas.figure.colorbar(self.canvas,self.current_subplot)

    def rightMenuShow(self):
        self.contextMenu = QMenu()
        self.actionStyle = self.contextMenu.addAction('修改曲线样式')
        self.actionLegend = self.contextMenu.addAction('修改图例样式')
        self.actionCurve = self.contextMenu.addAction('修改曲线类型')
        self.actionStyle.triggered.connect(self.styleHandler)
        self.actionLegend.triggered.connect(self.legendHandler)
        self.actionLegend.triggered.connect(self.curveHandler)

    def styleHandler(self):
        print(self.artist)

    def legendHandler(self):
        print(self.artist)

    def curveHandler(self):
        print(self.artist)

    def mainView_slot(self):
        if hasattr(self.current_subplot, 'azim'):
            self.current_subplot.view_init(azim=0, elev=0)
            self.canvas.draw()

    def leftView_slot(self):
        if hasattr(self.current_subplot, 'azim'):
            self.current_subplot.view_init(azim=90, elev=0)
            self.canvas.draw()

    def rightView_slot(self):
        if hasattr(self.current_subplot, 'azim'):
            self.current_subplot.view_init(azim=-90, elev=0)
            self.canvas.draw()

    def topView_slot(self):
        if hasattr(self.current_subplot, 'azim'):
            self.current_subplot.view_init(azim=0, elev=90)
            self.canvas.draw()

    def bottomView_slot(self):
        if hasattr(self.current_subplot, 'azim'):
            self.current_subplot.view_init(azim=0, elev=-90)
            self.canvas.draw()

    def backView_slot(self):
        if hasattr(self.current_subplot, 'azim'):
            self.current_subplot.view_init(azim=180, elev=0)
            self.canvas.draw()

    def show_grid_slot(self):
        self.show_grid_flag = not self.show_grid_flag
        self.current_subplot.grid(self.show_grid_flag)
        self.canvas.draw_idle()

    def init_gui(self):
        self.toolbar._update_view()

    def combobox_slot(self):
        self.current_subplot = self.axes[
            self.comboBox.currentIndex()]  # 将当前选择付给子图对象

    def axes_control_slot(self):
        if not self.current_subplot:
            QtWidgets.QMessageBox.warning(self.canvas.parent(), "错误",
                                          "没有可选的子图!")
            return
        Ui_Form_Manager(self.current_subplot, self.canvas)

    def show(self):
        super().show()