217 Star 1K Fork 245

GVPpyminer / pyminer

 / 详情

关于构建统一绘图api的探索,以matplotlib和pyqtgraph两种库为例

待办的
需求 成员
创建于  
2020-11-21 18:53

在编辑器中,使用backend='graph'或者backend='mpl'切换后端。
输入图片说明

效果图
输入图片说明
输入图片说明

from PyQt5.QtWidgets import QWidget
from pyminer2.extensions.packages.pmagg.PMAgg import Window
from pyminer2.extensions.packages.graph_agg.graph_agg import GraphAgg
import matplotlib.pyplot as plt


class Graph:
    def __init__(self, app=None, widget: QWidget = None, subplots: tuple = (1, 1), backend: str = 'mpl'):
        """
        :param widget: 将图绘制在哪个widget上,如果不指定,则默认使用pmagg的tabWidget,或其它绘图后端UI
        :param figsize: 如绘制3*3个子图
        :param backend: 选择mpl或者graph作为绘图后端
        """
        self.axes = [[None]*subplots[1]]*subplots[0]  # 二维数组,用于存储所有的子图对象
        self.backend = backend
        self.subplots = subplots
        self.widget = widget
        self.app = app
        if (app and widget) is None and backend == 'mpl':
            fig = plt.figure()
            self.axes = fig.subplots(nrows=subplots[0], ncols=subplots[1])
            if subplots == (1, 1):
                self.axes = [[self.axes]]
            else:
                self.axes=self.axes.tolist()
            self.app = Window(bg=self.backend)
            self.app.get_canvas(fig)
            self.widget = self.app.tabWidget.widget(self.app.tab_page_title_index)  # 指定widget为pmagg的tab
        if (app and widget) is None and backend == 'graph':
            self.app=GraphAgg()
            self.widget=self.app.widget
            for row in range(subplots[0]):
                for col in range(subplots[1]):
                    ax=self.widget.addPlot(row=row,col=col)
                    self.axes[row][col]=ax
        # 在初始化过程中,需要提供app,widget,axes

    def plot(self, x, y, position: tuple):
        """根据不同的绘图库,进行封装"""
        row,col=position
        if self.backend == 'mpl':
            self.axes[row][col].plot(x, y)
        if self.backend == 'graph':
            self.widget.getItem(row,col).plot(x,y)

    def show(self):
        self.app.show()


if __name__ == '__main__':
    graph = Graph(subplots=(3, 3),backend='graph')
    graph.plot([1, 2, 3], [4, 5, 6], position=(0, 0))
    graph.plot([1, 2, 3], [6, 5, 4], position=(0, 1))
    graph.plot([1, 2, 3], [3, 5, 3], position=(0, 1))
    graph.plot([1, 2, 3], [6, 3, 6], position=(0, 2))
    graph.show()

在插件文件夹下创建了graph_agg文件夹,用来做将来的pyqtgraph的UI界面(名称可以后面再改)
输入图片说明

在算法文件夹下创建了graph文件,用作绘图api的构建
输入图片说明
统一的绘图api的好处,一是用户使用起来简单,提供多种绘图引擎,二是可以根据需要定制某些功能。

我这也只是搭了个简单的架子,对封装绘图函数有兴趣的朋友可以在graph.py文件下,写plot,scatter,三维图的方法封装。有人对pyqtgraph的UI界面包装感兴趣,可以在graph_agg文件夹下开展工作。

评论 (4)

nihk 创建了需求
nihk 负责人设置为westat
nihk 优先级设置为次要
nihk 关联仓库设置为py2cn/pyminer
nihk 添加了
 
PyQt
标签
nihk 添加了
 
讨论
标签
nihk 添加了
 
需求讨论
标签
nihk 添加了
 
意见征求稿
标签
nihk 添加协作者nihk
nihk 添加协作者hzy15610046011
nihk 添加协作者心随风
展开全部操作日志

引入pyqtgraph绘图包的优点有哪些?

我的关注点之一是引入pyqtgraph可以解决哪些问题,pyqtgraph可以解决哪些matplotlib无法解决的问题?
首先是一些我关于绘图方面比较关注的问题。

  1. 三维绘图能力
    • 目前matplotlib对于平面制图功能强大,易于使用,基本与matlab的平面制图功能相当。
    • 不过在三维绘图方面,matplotlib似乎是短板。
    • 关于真三维绘图,matplotlib难以实现。
  2. 高性能绘图
    • mpl的绘图更多基于面向对象的实现,在绘图元素较多时,难以实现快速出图。
  3. 界面绘图调整及持久化
    • mpl默认的界面基本是没法用的,这一方面我觉得是我们可以发力的地方,包括pmagg我觉得一定是项目的亮点。

接口的定义风格

我的关注点之二在于接口的顶层设计。
接口是面向用户的终端,而相对于界面可以进行多次迭代调整,接口一旦定义,只能增加功能,不可删减功能。
在进行接口的定义时,matlab与mpl的接口模式可以认为是目前较好的接口,可以沿用。

在引入pyqtgraph的过程中,有三种方案:

第一种,将pyqtgraph嵌入matplotlib的接口中,即将pyqtgraph作为一个matplotlib的Agg,
不过matplotlib似乎是有qt5agg的,这样的意义何在?

第二种,将pyqtgraph作为matplotlib并行的同样强大的接口而存在,定义一个pyminer_plot_backend之类的配置项,然后对所有matplotlib的函数进行封装,通过分支判断采用何种接口。这需要实现所有的matplotlib函数,而这与matplotlib相比的优势在哪里?

第三种,脱离matplotlib,定义一套全新的接口,采用qt_*作为函数的前缀,比如qt_plot,qt_scatter等。
此时,可以将pyqtgraph作为一个专用的绘图包,专门解决一些matplotlib无法解决的问题,比如真三维绘图等。

具体的代码实现

这一部分我非常不关心,作为程序员,解决问题是家常便饭。明确目标与需求,写出代码并不需要很多时间。

关于开发新库的个人看法

关于开发新库,我的观点是一向是,先基于开源世界已有的内容进行整合,形成核心,打造python世界最好用的数据分析平台,然后再基于python社区的已有成果进行扩展。

近期我在用pyqtgraph制作一款系统监视的软件,经过尝试,我对图形的设置选项有一点点建议。

建议的设置选项

初始化时就要确定的选项

  • 画布颜色paper_color(用canvas_color更严谨一些,但是paper_color记忆难度较小)
  • 画布颜色不透明度paper_opacity
  • 文字(表头、轴标签、刻度、图例等)颜色text_color
  • 图例背景颜色legend_bg
  • 图例背景颜色不透明度legend_opacity

可以动态改变的选项

线条颜色、散点颜色等。

  • 线条颜色line_color
  • 线条宽度line_width
  • 线条类型line_type
  • 散点边缘颜色border_color(对于饼图、条形图则指图元的边缘线颜色)
  • 散点边缘宽度border_width(对于饼图、条形图则指图元的边缘线宽度)
  • 散点填充颜色fill_color(对于饼图、条形图指图元的填充颜色)
  • 散点尺寸symbol_size
  • 散点类型symbol_type
    命名原则是尽可能用简单的单词,方便记忆与统一。

风格建议

统一形状和颜色的名称,以matlab-matplotlib风格为主,为pyqtgraph提供一套转换。
具体如下:

  • 颜色使用#xxxxxx的十六进制字符串表示,pyqtgraph一侧进行转换。同时兼容matplotlib的颜色简称,比如'b'代表蓝色等。
  • 散点标记在pyqtgraph一侧比较乱,有时输入不合法的符号,会导致图形无法显示。我尝试给出自己的映射方案,支持用matplotlib风格、pyqtgraph原生风格甚至中文创造符号,并且对于不存在的符号,抛出异常:
symbols_dic = {'o': 'o', 's': 's', 't': 't', 't1': 't1', 't2': 't2', 't3': 't3',
               'd': 'd', '+': '+', 'x': 'x', 'p': 'p', 'h': 'h', 'star': 'star',
               '圆': 'o', '方': 's', '正三角': 't1', '倒三角': 't', '五边形': 'p', '六边形': 'h', '星': 'star',
               '五星': 'star', '菱形': 'd'}
  • 对于所有背景颜色都要留出透明度设置的接口。
  • 对于折线图,线型在pyqtgraph中无法设置。因此也以matplotlib为主,pyqtgraph一侧忽略这个属性。
  • 统一以下对颜色和形状的英文命名,我想可以如上所示。

代码建议

我写过一个类,在这里贴上来。绘图对象直接多继承这个类即可(类的属性不完全)。

from typing import Union


class PMGPlotCustomizer():
    def __init__(self):
        self._line_color = '#000000'
        self._line_style = '--'
        self._line_width = 2
        self._border_color = '#000000'
        self._border_width = 2
        self._symbol = 't'
        self._item_color = '#000000'
        self._face_color = '#ffffff'
        self._legend_face_color = '#ffffff'
        self._symbols_dic = {}# 这里存储自定义的名称到线型映射。

    @property
    def border_color(self):
        return self._border_color

    @border_color.setter
    def border_color(self, color: str):
        self._border_color = color

    @property
    def border_width(self):
        return self._border_width

    @border_width.setter
    def border_width(self, border_width: Union[int, float]):
        self._border_width = border_width

    @property
    def face_color(self):
        return self._face_color

    @face_color.setter
    def face_color(self, color: str):
        self._face_color = color

    @property
    def item_color(self):
        return self._item_color

    @item_color.setter
    def item_color(self, color: str):
        self._item_color = color

    @property
    def line_color(self):
        return self._line_color

    @line_color.setter
    def line_color(self, color: str):
        self._line_color = color

    @property
    def line_style(self):
        return self._line_style

    @line_style.setter
    def line_style(self, style: str):
        self._line_style = style

    @property
    def line_width(self):
        return self._line_width

    @line_width.setter
    def line_width(self, width: Union[int, float]):
        assert isinstance(width, (int, float))
        self._line_width = width

    @property
    def symbol(self):
        return self._symbol

    @symbol.setter
    def symbol(self, symbol: str):
        if symbol in self._symbols_dic.keys():
            self._symbol = self._symbols_dic[symbol]
        else:
            raise ValueError(
                'Symbol \'%s\' is not allowed.\nAll allowed symbols are:%s' % (symbol,
                                                                               list(self._symbols_dic.keys())))

    @property
    def legend_face_color(self):
        return self._legend_face_color

    @legend_face_color.setter
    def legend_face_color(self, color):
        self._legend_face_color = color

当使用pyminer的内置编辑器书写绘图函数的时候,编辑器一端可以针对性的,对绘图设置项字符串进行补全。

我想我们是想要统一封装一个库给用户使用起来方便呢,还是为用户提供一个良好的图形化的界面来进行操作呢?
毕竟我们基于python的生态,我们需要再创建一套接口吗?
我觉得我们应该更加注重于常见图形的使用上,类似于业内有很多专业的作图工具,各种图形的基本轮廓由,由用户来进行数据填充以及少的设定就能得到其想要的图片,而不是让用户来学pyminer的统一作图接口来绘图

登录 后才可以发表评论

状态
负责人
项目
里程碑
Pull Requests
关联的 Pull Requests 被合并后可能会关闭此 issue
分支
开始日期   -   截止日期
-
置顶选项
优先级
预计工期 (小时)
参与者(4)
Python
1
https://gitee.com/py2cn/pyminer.git
git@gitee.com:py2cn/pyminer.git
py2cn
pyminer
pyminer

搜索帮助