matplotlib FigureCanvasQTAgg:QWidget 上的慢速旋转和网格重叠表面

Posted

技术标签:

【中文标题】matplotlib FigureCanvasQTAgg:QWidget 上的慢速旋转和网格重叠表面【英文标题】:matplotlib FigureCanvasQTAgg: slow rotation and grid overlaping surface on QWidget 【发布时间】:2012-11-23 07:24:58 【问题描述】:

我有 qtdesigner 生成的小部件 - optim_plotting_frame.py(下面的代码)


    from PyQt4 import QtCore, QtGui

    try:
        _fromUtf8 = QtCore.QString.fromUtf8
    except AttributeError:
        _fromUtf8 = lambda s: s

    class Ui_optim_plotting_frame(object):
        def setupUi(self, optim_plotting_frame):
            optim_plotting_frame.setObjectName(_fromUtf8("optim_plotting_frame"))
            optim_plotting_frame.setWindowModality(QtCore.Qt.ApplicationModal)
            optim_plotting_frame.resize(700, 580)
            optim_plotting_frame.setWindowTitle(QtGui.QApplication.translate("optim_plotting_frame", "Plotting", None, QtGui.QApplication.UnicodeUTF8))
            self.verticalLayout_2 = QtGui.QVBoxLayout(optim_plotting_frame)
            self.verticalLayout_2.setObjectName(_fromUtf8("verticalLayout_2"))
            self.horizontalLayout = QtGui.QHBoxLayout()
            self.horizontalLayout.setObjectName(_fromUtf8("horizontalLayout"))
            self.verticalLayout = QtGui.QVBoxLayout()
            self.verticalLayout.setSizeConstraint(QtGui.QLayout.SetMinimumSize)
            self.verticalLayout.setObjectName(_fromUtf8("verticalLayout"))
            self.label_function = QtGui.QLabel(optim_plotting_frame)
            self.label_function.setMinimumSize(QtCore.QSize(111, 16))
            self.label_function.setMaximumSize(QtCore.QSize(16777215, 16777215))
            self.label_function.setToolTip(QtGui.QApplication.translate("optim_plotting_frame", "defines radial basis function for interpolation", None, QtGui.QApplication.UnicodeUTF8))
            self.label_function.setText(QtGui.QApplication.translate("optim_plotting_frame", "\n"
    "\n"
    "p, li  white-space: pre-wrap; \n"
    "\n"
    "Interpolation function:", None, QtGui.QApplication.UnicodeUTF8))
            self.label_function.setObjectName(_fromUtf8("label_function"))
            self.verticalLayout.addWidget(self.label_function)
            self.cmb_function = QtGui.QComboBox(optim_plotting_frame)
            self.cmb_function.setMinimumSize(QtCore.QSize(111, 22))
            self.cmb_function.setMaximumSize(QtCore.QSize(16777215, 16777215))
            self.cmb_function.setToolTip(QtGui.QApplication.translate("optim_plotting_frame", "defines radial basis function for interpolation", None, QtGui.QApplication.UnicodeUTF8))
            self.cmb_function.setObjectName(_fromUtf8("cmb_function"))
            self.cmb_function.addItem(_fromUtf8(""))
            self.cmb_function.setItemText(0, QtGui.QApplication.translate("optim_plotting_frame", "multiquadric", None, QtGui.QApplication.UnicodeUTF8))
            self.cmb_function.addItem(_fromUtf8(""))
            self.cmb_function.setItemText(1, QtGui.QApplication.translate("optim_plotting_frame", "inverse", None, QtGui.QApplication.UnicodeUTF8))
            self.cmb_function.addItem(_fromUtf8(""))
            self.cmb_function.setItemText(2, QtGui.QApplication.translate("optim_plotting_frame", "gaussian", None, QtGui.QApplication.UnicodeUTF8))
            self.cmb_function.addItem(_fromUtf8(""))
            self.cmb_function.setItemText(3, QtGui.QApplication.translate("optim_plotting_frame", "linear", None, QtGui.QApplication.UnicodeUTF8))
            self.cmb_function.addItem(_fromUtf8(""))
            self.cmb_function.setItemText(4, QtGui.QApplication.translate("optim_plotting_frame", "cubic", None, QtGui.QApplication.UnicodeUTF8))
            self.cmb_function.addItem(_fromUtf8(""))
            self.cmb_function.setItemText(5, QtGui.QApplication.translate("optim_plotting_frame", "quintic", None, QtGui.QApplication.UnicodeUTF8))
            self.cmb_function.addItem(_fromUtf8(""))
            self.cmb_function.setItemText(6, QtGui.QApplication.translate("optim_plotting_frame", "thin_plate", None, QtGui.QApplication.UnicodeUTF8))
            self.verticalLayout.addWidget(self.cmb_function)
            self.label_alpha = QtGui.QLabel(optim_plotting_frame)
            self.label_alpha.setMinimumSize(QtCore.QSize(111, 16))
            self.label_alpha.setMaximumSize(QtCore.QSize(16777215, 16777215))
            self.label_alpha.setToolTip(QtGui.QApplication.translate("optim_plotting_frame", "Defines transparency: 0 - transparent, 1 - not transparent", None, QtGui.QApplication.UnicodeUTF8))
            self.label_alpha.setText(QtGui.QApplication.translate("optim_plotting_frame", "Alpha:", None, QtGui.QApplication.UnicodeUTF8))
            self.label_alpha.setObjectName(_fromUtf8("label_alpha"))
            self.verticalLayout.addWidget(self.label_alpha)
            self.dspb_alpha = QtGui.QDoubleSpinBox(optim_plotting_frame)
            self.dspb_alpha.setMinimumSize(QtCore.QSize(111, 0))
            self.dspb_alpha.setMaximumSize(QtCore.QSize(16777215, 16777215))
            self.dspb_alpha.setToolTip(QtGui.QApplication.translate("optim_plotting_frame", "Defines transparency: 0 - transparent, 1 - not transparent", None, QtGui.QApplication.UnicodeUTF8))
            self.dspb_alpha.setMaximum(1.0)
            self.dspb_alpha.setSingleStep(0.1)
            self.dspb_alpha.setProperty("value", 0.7)
            self.dspb_alpha.setObjectName(_fromUtf8("dspb_alpha"))
            self.verticalLayout.addWidget(self.dspb_alpha)
            self.label_smooth = QtGui.QLabel(optim_plotting_frame)
            self.label_smooth.setMinimumSize(QtCore.QSize(111, 16))
            self.label_smooth.setMaximumSize(QtCore.QSize(16777215, 16777215))
            self.label_smooth.setToolTip(QtGui.QApplication.translate("optim_plotting_frame", "Smoothness of the approximation", None, QtGui.QApplication.UnicodeUTF8))
            self.label_smooth.setText(QtGui.QApplication.translate("optim_plotting_frame", "Smoothness:", None, QtGui.QApplication.UnicodeUTF8))
            self.label_smooth.setObjectName(_fromUtf8("label_smooth"))
            self.verticalLayout.addWidget(self.label_smooth)
            self.dspb_smooth = QtGui.QDoubleSpinBox(optim_plotting_frame)
            self.dspb_smooth.setMinimumSize(QtCore.QSize(111, 0))
            self.dspb_smooth.setMaximumSize(QtCore.QSize(16777215, 16777215))
            self.dspb_smooth.setToolTip(QtGui.QApplication.translate("optim_plotting_frame", "Smoothness of the approximation", None, QtGui.QApplication.UnicodeUTF8))
            self.dspb_smooth.setSingleStep(0.1)
            self.dspb_smooth.setObjectName(_fromUtf8("dspb_smooth"))
            self.verticalLayout.addWidget(self.dspb_smooth)
            self.chb_normxy = QtGui.QCheckBox(optim_plotting_frame)
            self.chb_normxy.setText(QtGui.QApplication.translate("optim_plotting_frame", "normalized x,y ticks", None, QtGui.QApplication.UnicodeUTF8))
            self.chb_normxy.setChecked(True)
            self.chb_normxy.setObjectName(_fromUtf8("chb_normxy"))
            self.verticalLayout.addWidget(self.chb_normxy)
            spacerItem = QtGui.QSpacerItem(20, 40, QtGui.QSizePolicy.Minimum, QtGui.QSizePolicy.Expanding)
            self.verticalLayout.addItem(spacerItem)
            self.horizontalLayout.addLayout(self.verticalLayout)
            self.widget = QtGui.QWidget(optim_plotting_frame)
            self.widget.setFocusPolicy(QtCore.Qt.StrongFocus)
            self.widget.setObjectName(_fromUtf8("widget"))
            self.horizontalLayout.addWidget(self.widget)
            self.horizontalLayout.setStretch(1, 10)
            self.verticalLayout_2.addLayout(self.horizontalLayout)

            self.retranslateUi(optim_plotting_frame)
            QtCore.QMetaObject.connectSlotsByName(optim_plotting_frame)

        def retranslateUi(self, optim_plotting_frame):
            pass

和 main.py 中的类(下面的代码)继承生成的小部件并在其上绘制表面(将带有绘图表面的 FigureCanvasQTAgg 添加到小部件)


    import sys
    from PyQt4 import QtGui, QtCore
    from PyQt4.QtGui import QApplication, QDialog

    import numpy as np
    from scipy.interpolate import Rbf

    from optim_plotting_frame import Ui_optim_plotting_frame

    from matplotlib.backends.backend_qt4agg import FigureCanvasQTAgg as FigureCanvas
    from matplotlib.backends.backend_qt4agg import NavigationToolbar2QTAgg as NavigationToolbar
    from mpl_toolkits.mplot3d import Axes3D
    from matplotlib.figure import Figure
    from matplotlib import cm


    class optim_plotting_frame(QtGui.QWidget):
        "Plotting settings widget"
        def __init__(self, x, y, z, minx=None, maxx=None, miny=None, maxy=None, xname=None, yname=None, zname=None):
            super(optim_plotting_frame, self).__init__()

            self.ui = Ui_optim_plotting_frame()
            self.ui.setupUi(self)

            """
            Parameters:
                x: numpy.ndarray
                    set of first parameter data (first coordinate)

                y: numpy.ndarray
                    set of second parameter data (second coordinate)

                z: numpy.ndarray
                    set of data corresponding to x and y (result)

                alpha: float, optional
                    Number between 0 and 1 that defines transparency: 0 - transparent,
                    1 - not transparent.

                function: string, optional
                    defines radial basis function for interpolation, by default - 'multiquadric'

                    'multiquadric': sqrt((r/self.epsilon)**2 + 1)
                    'inverse':      1.0/sqrt((r/self.epsilon)**2 + 1)
                    'gaussian':     exp(-(r/self.epsilon)**2)
                    'linear':       r
                    'cubic':        r**3
                    'quintic':      r**5
                    'thin_plate':   r**2 * log(r)

                smooth: float, optional
                    Values greater than zero increase the smoothness of the approximation.
                    0 is for interpolation (default), the function will always go through
                    the nodal points in this case.

                minx: float, opyional
                    minimum possible value of x

                maxx: float, opyional
                    maximum possible value of x

                miny: float, opyional
                    minimum possible value of y

                maxy: float, opyional
                    maximum possible value of y

                xname: string, optional
                    name of x parameter

                yname: string, optional
                    name of y parameter

                zname: string, optional
                    name of z parameter (objective function)
            """
            #==========================Data validation=============================
            if any((not isinstance(x, np.ndarray),
                    not isinstance(y, np.ndarray),
                    not isinstance(z, np.ndarray))):
                print "Error: x, y, z must be of numpy.ndarray type."
                return None

            if x.size != y.size != z.size:
                print "Error: x, y, z must be of equal size."
                return None

            if not isinstance(minx, (float, int)):
                minx = x.min()

            if not isinstance(maxx, (float, int)):
                maxx = x.max()

            if not isinstance(miny, (float, int)):
                miny = y.min()

            if not isinstance(maxy, (float, int)):
                maxy = y.max()

            if minx > maxx:
                tmp = minx
                minx = maxx
                maxx = tmp

            if miny > maxy:
                tmp = miny
                miny = maxy
                maxy = tmp

            if not isinstance(xname, str):
                xname = ""

            if not isinstance(yname, str):
                yname = ""

            if not isinstance(zname, str):
                zname = ""
            #======================================================================

            self.initialized = False

            self.x = x
            self.y = y
            self.z = z
            self.minx = minx
            self.maxx = maxx
            self.miny = miny
            self.maxy = maxy
            self.xname = xname
            self.yname = yname
            self.zname = zname

            # map to [0,1] range
            self.x = (x - minx) / maxx
            self.y = (y - miny) / maxy

            # update alpha, function, smooth values from widget
            self.alpha = self.ui.dspb_alpha.value()
            self.function = self.ui.cmb_function.currentText()
            self.smooth = self.ui.dspb_smooth.value()

            self.create_main_frame()

            self.initialized = True
            self.plot()

        def updateVals(self):
            """
            update alpha, function, smooth values from widget and parameters
            """
            self.alpha = self.ui.dspb_alpha.value()
            self.function = self.ui.cmb_function.currentText()
            self.smooth = self.ui.dspb_smooth.value()

        def plot(self):
            self.updateVals()
            self.axes.clear()

            try:
                # getting coordinate matrices from two coordinate vectors.
                tx = np.linspace(self.x.min(), self.x.max(), 100)
                ty = np.linspace(self.y.min(), self.y.max(), 100)
                XI, YI = np.meshgrid(tx, ty)
                # interpolating by radial basis function
                rbf = Rbf(self.x, self.y, self.z, function=str(self.function), smooth=self.smooth)
                # getting interpolation function results corresponding to (XI, YI)
                ZI = rbf(XI, YI)
                # plotting interpolaed surface
                self.axes.plot_surface(XI, YI, ZI, cmap=cm.jet, alpha=self.alpha)
            except Exception as e:
                print "Error occured! original message: " + e.message

            # plotting initial points
            self.axes.scatter(self.x, self.y, self.z)

            self.axes.set_xlim(self.x.min(), self.x.max())
            self.axes.set_ylim(self.y.min(), self.y.max())

            self.axes.set_title('RBF interpolation ' + self.function)
            if not self.ui.chb_normxy.isChecked():
                # setting ticks labels on the x line
                self.axes.set_xticklabels((self.axes.get_xticks() * self.maxx + self.minx).round(1))
                # setting ticks labels on the y line
                self.axes.set_yticklabels((self.axes.get_yticks() * self.maxy + self.miny).round(1))
            self.axes.set_xlabel(self.xname)
            self.axes.set_ylabel(self.yname)
            self.axes.set_zlabel(self.zname)

            ## adding colorbar
            #m = cm.ScalarMappable(cmap=cm.jet)
            #m.set_array(ZI)
            #self.axes.figure.colorbar(m)

            self.canvas.draw()

        def create_main_frame(self):
            # Create the mpl Figure and FigCanvas objects. 5x4 inches, 100 dots-per-inch
            self.dpi = 100
            self.fig = Figure((8.0, 4.0), dpi=self.dpi)
            # setting diagram background
            self.fig.patch.set_facecolor('white')
            self.canvas = FigureCanvas(self.fig)
            self.canvas.setParent(self.ui.widget)

            self.axes = Axes3D(self.fig)

            # Create the navigation toolbar, tied to the canvas
            self.mpl_toolbar = NavigationToolbar(self.canvas, self.ui.widget, coordinates=False)

            # connecting signals
            self.ui.cmb_function.currentIndexChanged.connect(self.plot)
            self.ui.dspb_alpha.valueChanged.connect(self.plot)
            self.ui.dspb_smooth.valueChanged.connect(self.plot)
            self.ui.chb_normxy.stateChanged.connect(self.plot)

            # Vertical layout for canvas and toolbar
            vbox = QtGui.QVBoxLayout()
            vbox.addWidget(self.canvas)
            vbox.addWidget(self.mpl_toolbar)

            self.ui.widget.setLayout(vbox)
            self.ui.widget.setFocus(QtCore.Qt.MouseFocusReason)

    # Create a Qt application
    app = QApplication(sys.argv)
    window = QDialog()

    opf = optim_plotting_frame(x = np.array([  100.,   300.,   500.,   700.,   900.,  1000.,   100.,   300., 500.,   700.,   900.,  1000.,   100.,   300.,   500.,   700., 900.,  1000.,   100.,   300.,   500.,   700.,   900.,  1000., 100.,   300.,   500.,   700.,   900.,  1000.,   100.,   300., 500.,   700.,   900.,  1000.]),
                               y = np.array([  100.,   100.,   100.,   100.,   100.,   100.,   300.,   300., 300.,   300.,   300.,   300.,   500.,   500.,   500.,   500., 500.,   500.,   700.,   700.,   700.,   700.,   700.,   700., 900.,   900.,   900.,   900.,   900.,   900.,  1000.,  1000., 1000.,  1000.,  1000.,  1000.]),
                               z = np.array([374712.60107421875, 526249.09765625, 500842.119140625, 391724.2041015625, 329192.123046875, 298277.92041015625, 526249.259765625, 601555.873046875, 598078.173828125, 529956.01953125, 502884.986328125, 485526.5244140625, 500841.181640625, 598078.400390625, 587555.86328125, 530815.837890625, 495623.544921875, 474902.572265625, 391725.0869140625, 529956.8408203125, 530815.6259765625, 447601.33081054688, 402540.9443359375, 385187.92944335938, 329192.2392578125, 502885.27734375, 495623.6396484375, 402541.17431640625, 365774.16870117188, 343962.6298828125, 298277.88305664062, 485526.775390625, 474903.0673828125, 385187.75439453125, 343962.728515625, 326735.05065917969]),
                               minx = 100,
                               maxx = 1000,
                               miny = 100,
                               maxy = 1000,
                               xname = 'width',
                               yname = 'height',
                               zname = 'WOPT')
    opf.show()
    sys.exit(app.exec_())

还有两个问题: 1)轴网格与我的表面重叠。 2)sirface的旋转和缩放速度很慢。

当我不使用 QWidget 时,一切正常(下面的示例),但我应该在我的应用程序中绘制这个表面并且我需要在 QWidget 上进行。因此,任何解决此问题的建议将不胜感激。


    import numpy as np
    from scipy.interpolate import Rbf
    import matplotlib.pyplot as plt
    import matplotlib.colors as colors
    from matplotlib import cm
    from mpl_toolkits.mplot3d import Axes3D

    def plotSurface(x, y, z, alpha=None, function=None, smooth=None,
                    minx=None, maxx=None, miny=None, maxy=None, xname=None, yname=None, zname=None):
        """
        This function will create new window with plotted surface on it.

        Parameters:
            x: numpy.ndarray
                set of first parameter data (first coordinate)

            y: numpy.ndarray
                set of second parameter data (second coordinate)

            z: numpy.ndarray
                set of data corresponding to x and y (result)

            alpha: float, optional
                Number between 0 and 1 that defines transparency: 0 - transparent,
                1 - not transparent.

            function: string, optional
                defines radial basis function for interpolation, by default - 'multiquadric'

                'multiquadric': sqrt((r/self.epsilon)**2 + 1)
                'inverse':      1.0/sqrt((r/self.epsilon)**2 + 1)
                'gaussian':     exp(-(r/self.epsilon)**2)
                'linear':       r
                'cubic':        r**3
                'quintic':      r**5
                'thin_plate':   r**2 * log(r)

            smooth: float, optional
                Values greater than zero increase the smoothness of the approximation.
                0 is for interpolation (default), the function will always go through
                the nodal points in this case.

            minx: float, opyional
                minimum possible value of x

            maxx: float, opyional
                maximum possible value of x

            miny: float, opyional
                minimum possible value of y

            maxy: float, opyional
                maximum possible value of y

            xname: string, optional
                name of x parameter

            yname: string, optional
                name of y parameter

            zname: string, optional
                name of z parameter (objective function)
        """
        #==========================Data validation=================================
        if any((not isinstance(x, np.ndarray),
                not isinstance(y, np.ndarray),
                not isinstance(z, np.ndarray))):
            print "Error: x, y, z must be of numpy.ndarray type."
            return None

        if x.size != y.size != z.size:
            print "Error: x, y, z must be of equal size."
            return None

        if not isinstance(alpha, (float, int)):
            alpha = 0.7

        if not isinstance(function, str):
            try:
                function = str(function)
            except:
                function = 'multiquadric'

        if not any((function == 'multiquadric',
                    function == 'inverse',
                    function == 'gaussian',
                    function == 'linear',
                    function == 'cubic',
                    function == 'quintic',
                    function == 'thin_plate')):
            function = 'multiquadric'

        if smooth is None:
            smooth = 0

        if not isinstance(minx, (float, int)):
            minx = x.min()

        if not isinstance(maxx, (float, int)):
            maxx = x.max()

        if not isinstance(miny, (float, int)):
            miny = y.min()

        if not isinstance(maxy, (float, int)):
            maxy = y.max()

        if minx > maxx:
            tmp = minx
            minx = maxx
            maxx = tmp

        if miny > maxy:
            tmp = miny
            miny = maxy
            maxy = tmp

        if not isinstance(xname, str):
            xname = ""

        if not isinstance(yname, str):
            yname = ""

        if not isinstance(zname, str):
            zname = ""
        #==========================================================================

        fig = plt.figure()
        # setting diagram background
        fig.patch.set_facecolor('white')
        ax = Axes3D(fig)

        # map to [0,1] range
        x = (x - minx) / maxx
        y = (y - miny) / maxy

        try:
            # getting coordinate matrices from two coordinate vectors.
            tx = np.linspace(x.min(), x.max(), 100)
            ty = np.linspace(y.min(), y.max(), 100)
            XI, YI = np.meshgrid(tx, ty)
            # interpolating by radial basis function
            rbf = Rbf(x, y, z, function=function, smooth=smooth)
            # getting interpolation function results corresponding to (XI, YI)
            ZI = rbf(XI, YI)
            # plotting interpolaed surface
            ax.plot_surface(XI, YI, ZI, cmap=cm.jet, alpha=alpha)
        except Exception as e:
            print "Error occured! original message: " + e.message

        # plotting initial points
        ax.scatter(x, y, z)

        ax.set_xlim(x.min(), x.max())
        ax.set_ylim(y.min(), y.max())

        ax.set_title('RBF interpolation ' + function)
        # setting ticks positions on the x line
        ax.set_xticklabels((ax.get_xticks() * maxx + minx).round(1))
        # setting ticks labelson the y line
        ax.set_yticklabels((ax.get_yticks() * maxy + miny).round(1))
        ax.set_xlabel(xname)
        ax.set_ylabel(yname)
        ax.set_zlabel(zname)

        # adding colorbar
        m = cm.ScalarMappable(cmap=cm.jet)
        m.set_array(ZI)
        ax.figure.colorbar(m)

        # showing window with diagram
        plt.show()

    plotSurface(x = np.array([  100.,   300.,   500.,   700.,   900.,  1000.,   100.,   300., 500.,   700.,   900.,  1000.,   100.,   300.,   500.,   700., 900.,  1000.,   100.,   300.,   500.,   700.,   900.,  1000., 100.,   300.,   500.,   700.,   900.,  1000.,   100.,   300., 500.,   700.,   900.,  1000.]),
                y = np.array([  100.,   100.,   100.,   100.,   100.,   100.,   300.,   300., 300.,   300.,   300.,   300.,   500.,   500.,   500.,   500., 500.,   500.,   700.,   700.,   700.,   700.,   700.,   700., 900.,   900.,   900.,   900.,   900.,   900.,  1000.,  1000., 1000.,  1000.,  1000.,  1000.]),
                z = np.array([374712.60107421875, 526249.09765625, 500842.119140625, 391724.2041015625, 329192.123046875, 298277.92041015625, 526249.259765625, 601555.873046875, 598078.173828125, 529956.01953125, 502884.986328125, 485526.5244140625, 500841.181640625, 598078.400390625, 587555.86328125, 530815.837890625, 495623.544921875, 474902.572265625, 391725.0869140625, 529956.8408203125, 530815.6259765625, 447601.33081054688, 402540.9443359375, 385187.92944335938, 329192.2392578125, 502885.27734375, 495623.6396484375, 402541.17431640625, 365774.16870117188, 343962.6298828125, 298277.88305664062, 485526.775390625, 474903.0673828125, 385187.75439453125, 343962.728515625, 326735.05065917969]),
                alpha = 0.7,
                function = 'multiquadric',
                smooth = 0.0,
                minx = 100,
                maxx = 1000,
                miny = 100,
                maxy = 1000,
                xname = 'width',
                yname = 'height',
                zname = 'WOPT')

【问题讨论】:

【参考方案1】:

使用 matplotlib 旋转和缩放总是一个相当慢的操作,因为它需要再次渲染整个图形。

【讨论】:

以上是关于matplotlib FigureCanvasQTAgg:QWidget 上的慢速旋转和网格重叠表面的主要内容,如果未能解决你的问题,请参考以下文章

Matplotlib 基本概念

Matplotlib快速入门

matplotlib绘图入门详解

matplotlib 绘图常用函数

Matplotlib 教程

安装matplotlib?