Python可视化数据分析-柱状图/折线图

一、前言

使用python编写一个图表生成器,输入各公司的不良品数量,可以在一张图中同时展示数据的柱状图和折线图。

效果如下:

二、基础知识

绘制折线图和柱状图主要使用到了 pyecharts.charts 模块中的 LineBar 类。它们允许用户通过简单的调用方法创建和定制各种样式的折线图和柱状图,从而展示数据分布和趋势。以下是关于这两个类的详细解释:

1)Line类

Line 类用于绘制折线图,展示数据随时间或其他连续变量的变化趋势。以下是一些关键特征和方法 :

创建折线图

from pyecharts.charts import Line

from pyecharts import options as opts 

line_chart = Line()

添加 x 轴和 y 轴数据

line_chart.add_xaxis(["Jan", "Feb", "Mar", "Apr", "May"])
line_chart.add_yaxis("Sales", [150, 230, 224, 300, 290])

设置全局选项

# 设置全局选项,包括标题选项
line_chart.set_global_opts(
    title_opts=opts.TitleOpts(
        title="Sales Trend",  # 设置图表标题为 "Sales Trend"
        subtitle="Monthly Sales",  # 设置图表副标题为 "Monthly Sales"
        pos_left="left",  # 标题靠左显示
        pos_top="top",  # 标题距离顶部位置
        title_textstyle_opts=opts.TextStyleOpts(font_size=20, color="blue")  # 设置标题文本样式
    )
)

渲染和保存

line_chart.render("line_chart.html")

 效果图:

2) Bar类

Bar 类用于绘制柱状图,适用于展示不同类别或组的数据对比。以下是一些关键特征和方法:

创建柱状图: 

from pyecharts.charts import Bar

bar_chart = Bar()

添加 x 轴和 y 轴数据

bar_chart.add_xaxis(["A", "B", "C", "D", "E"])
bar_chart.add_yaxis("Category 1", [25, 40, 60, 55, 75])

设置柱状图特性

bar_chart.set_series_opts(itemstyle_opts=opts.ItemStyleOpts(color="skyblue")) 

设置全局选项:

bar_chart.set_global_opts(title_opts=opts.TitleOpts(title="Bar Chart")) 

渲染和保存

bar_chart.render("bar_chart.html") 

如何使用 LineBar 绘制并渲染折线图和柱状图: 

from pyecharts.charts import Line, Bar
from pyecharts import options as opts

# 创建折线图
line_chart = Line()
line_chart.add_xaxis(["Jan", "Feb", "Mar", "Apr", "May"])
line_chart.add_yaxis("Sales", [150, 230, 224, 300, 290])
line_chart.set_global_opts(title_opts=opts.TitleOpts(title="Sales Trend"))
line_chart.render("line_chart.html")

# 创建柱状图
bar_chart = Bar()
bar_chart.add_xaxis(["A", "B", "C", "D", "E"])
bar_chart.add_yaxis("Category 1", [25, 40, 60, 55, 75])
bar_chart.set_global_opts(title_opts=opts.TitleOpts(title="Bar Chart"))
bar_chart.render("bar_chart.html")

三、编写图表生成器
1)功能需求:

        1.输入各公司每月的不良品数据,可以生成柱状图和折线图。

         2.可以自由添加公司和月份。

        3.折线图和柱状图在同一个表中。

2)代码如下:
import sys
import os
from datetime import datetime
from PyQt5.QtWidgets import QApplication, QMainWindow, QVBoxLayout, QWidget, QPushButton, QLabel, QLineEdit, \
    QMessageBox, QInputDialog
from pyecharts.charts import Line, Bar
from pyecharts import options as opts
import weakref
import webbrowser


class PPMDataInputWidget(QWidget):
    instances = weakref.WeakSet()  # 使用 WeakSet 存储实例

    def __init__(self, company_name, parent=None):
        super().__init__(parent)
        self.company_name = company_name
        self.initUI()
        PPMDataInputWidget.instances.add(self)  # 添加实例到 WeakSet 中

    def initUI(self):
        self.layout = QVBoxLayout()

        # 添加公司名称标签
        company_label = QLabel(f"填写 {self.company_name} 的 不良品 数量:")
        self.layout.addWidget(company_label)

        self.input_fields = {}  # 存储输入框的字典

        # 添加输入框用于填写不定数量的月份 不良品 数量
        self.add_input_field("1月")
        self.add_input_field("2月")
        self.add_input_field("3月")

        # 添加按钮用于增加月份输入框
        add_month_button = QPushButton("添加月份", self)
        add_month_button.clicked.connect(self.add_month_input)
        self.layout.addWidget(add_month_button)

        # 添加删除按钮
        delete_button = QPushButton("删除公司", self)
        delete_button.clicked.connect(self.delete_company)
        self.layout.addWidget(delete_button)

        self.setLayout(self.layout)

    def add_input_field(self, month_name):
        edit = QLineEdit(self)
        edit.setPlaceholderText(f"{month_name}  不良品 数量")
        self.layout.addWidget(edit)
        self.input_fields[month_name] = edit

    def add_month_input(self):
        # 使用对话框获取新的月份名称
        new_month_name, ok = QInputDialog.getText(self, "添加新月份", "输入新月份名称:")
        if ok and new_month_name:
            self.add_input_field(new_month_name)

    def get_ppm_data(self):
        ppm_data = []
        for month_name, edit in self.input_fields.items():
            ppm_value = float(edit.text().strip())
            ppm_data.append(ppm_value)
        return ppm_data

    def delete_company(self):
        reply = QMessageBox.question(self, "确认删除", f"确定要删除 {self.company_name} 吗?",
                                     QMessageBox.Yes | QMessageBox.No, QMessageBox.No)
        if reply == QMessageBox.Yes:
            self.setParent(None)
            self.deleteLater()
            PPMDataInputWidget.instances.discard(self)  # 从 WeakSet 中移除实例


class PPMInputApp(QMainWindow):
    def __init__(self):
        super().__init__()
        self.setWindowTitle("填写 不良品 数量")
        self.setGeometry(100, 100, 600, 400)

        central_widget = QWidget()
        self.setCentralWidget(central_widget)

        layout = QVBoxLayout()
        central_widget.setLayout(layout)

        # 添加按钮用于添加新公司和月份
        add_company_button = QPushButton("添加公司", self)
        add_company_button.clicked.connect(self.add_company_input_dialog)
        layout.addWidget(add_company_button)

        # 添加按钮用于生成所有公司折线图和柱状图并保存为 HTML
        generate_button = QPushButton("生成所有公司折线图和柱状图并保存为 HTML", self)
        generate_button.clicked.connect(self.generate_all_charts)
        layout.addWidget(generate_button)

        # 添加退出按钮
        exit_button = QPushButton("退出", self)
        exit_button.clicked.connect(self.close)
        layout.addWidget(exit_button)

        self.widgets = []  # 存储 PPMDataInputWidget 的列表

    def add_company_widget(self, company_name):
        widget = PPMDataInputWidget(company_name)
        self.widgets.append(widget)
        layout = self.centralWidget().layout()
        layout.addWidget(widget)

    def add_company_input_dialog(self):
        # 使用对话框获取新公司名称
        new_company_name, ok = QInputDialog.getText(self, "添加新公司", "输入新公司名称:")
        if ok and new_company_name:
            self.add_company_widget(new_company_name)

    def generate_all_charts(self):
        try:
            print("Generating all charts...")
            line_chart = Line()
            bar_chart = Bar()

            # 遍历 WeakSet 中的实例,仅处理存在且有效的部件
            for widget in PPMDataInputWidget.instances:
                if widget.isVisible() and widget in self.widgets:
                    company_name = widget.company_name
                    ppm_data = widget.get_ppm_data()

                    print(f"Processing widget: {company_name}")

                    # 添加折线图数据
                    line_chart.add_xaxis(list(widget.input_fields.keys()))
                    line_chart.add_yaxis(company_name, ppm_data,
                                         markpoint_opts=opts.MarkPointOpts(data=[opts.MarkPointItem(type_="max")]),
                                         label_opts=opts.LabelOpts(formatter="{c}"))

                    # 添加柱状图数据
                    bar_chart.add_xaxis(list(widget.input_fields.keys()))
                    bar_chart.add_yaxis(company_name, ppm_data, gap="0%")  # 设置柱子之间的间距为0%并设置柱子宽度

            # 设置折线图和柱状图的全局选项
            line_chart.set_global_opts(
                # title_opts=opts.TitleOpts(title="各公司 不良品 折线图"),
                # yaxis_opts=opts.AxisOpts(name="数量")
                yaxis_opts=opts.AxisOpts(name="数量", axislabel_opts=opts.LabelOpts(font_weight="bold"))
            )
            bar_chart.set_global_opts(
                # title_opts=opts.TitleOpts(title="各公司 不良品 柱状图"),
                # yaxis_opts=opts.AxisOpts(name="数量")
                yaxis_opts=opts.AxisOpts(name="数量", axislabel_opts=opts.LabelOpts(font_weight="bold"))

            )

            # 叠加折线图和柱状图并保存为 HTML 文件
            overlap_chart = line_chart.overlap(bar_chart)
            # overlap_chart.set_global_opts(
            #     title_opts=opts.TitleOpts(title="折线图和柱状图")
            # )

            # 生成 HTML 文件路径
            timestamp = datetime.now().strftime("%Y%m%d%H%M%S")
            html_file_name = f"all_companies_PPM_{timestamp}.html"
            html_file_path = os.path.join(os.getcwd(), html_file_name)

            # 渲染并打开 HTML 文件
            overlap_chart.render(html_file_path)
            print(f"生成所有公司的 不良品 折线图和柱状图 HTML 文件:{html_file_path}")
            webbrowser.open(f"file://{html_file_path}", new=2)

        except Exception as e:
            print(f"Error occurred during chart generation: {e}")


if __name__ == '__main__':
    app = QApplication(sys.argv)
    window = PPMInputApp()
    window.show()
    sys.exit(app.exec_())
3)主要功能和结构解析:
  1. PPMDataInputWidget 类

    • 用于创建一个 QWidget 子类,用于输入和展示单个公司的产品不良品数量。
    • 每个实例对应一个公司的数据输入界面。
    • 使用 QVBoxLayout 进行布局管理,包括输入框、添加月份按钮和删除按钮。
    • add_input_field 方法用于添加新的月份输入框。
    • get_ppm_data 方法用于获取输入的不良品数量数据。
    • delete_company 方法用于删除当前公司的输入界面。
  2. PPMInputApp 类

    • 主窗口类,继承自 QMainWindow。
    • 包含添加新公司、生成图表和退出等功能按钮。
    • add_company_widget 方法用于在布局中添加新的 PPMDataInputWidget 实例。
    • generate_all_charts 方法用于生成所有公司的折线图和柱状图,并保存为 HTML 文件。
  3. 关键点解释

    • PPMInputApp 中,通过点击按钮添加新公司,每个公司对应一个 PPMDataInputWidget 实例,用于填写产品不良品数据。
    • 点击生成图表按钮时,遍历所有 PPMDataInputWidget 实例,获取数据并使用 Pyecharts 创建折线图和柱状图。
    • 折线图和柱状图数据使用 add_xaxisadd_yaxis 方法添加,同时设置全局选项,如标题和 Y 轴名称等。
    • 最后将折线图和柱状图叠加在一起,并保存为 HTML 文件,通过 webbrowser.open 打开该文件。
4)打包成exe

 在使用auto-py-to-exe或者Pyinstaller打包程序时,容易报错缺少map_filename.json或者直接缺失pyecharts包。我们在打包时,可以将文件夹一起打包:

pyinstaller --add-data="E:\anaconda3\envs\yolov5\Lib\site-packages\pyecharts;pyecharts" -F -w test.py

结果: 

5)运行

填好数据的效果如下:

 可以点击HTML查看,或者直接下载exe使用。

姐妹篇《Python可视化数据分析-饼状图》 ,欢迎大家一起学习!加电!

最近更新

  1. TCP协议是安全的吗?

    2024-04-21 07:20:07       18 阅读
  2. 阿里云服务器执行yum,一直下载docker-ce-stable失败

    2024-04-21 07:20:07       19 阅读
  3. 【Python教程】压缩PDF文件大小

    2024-04-21 07:20:07       19 阅读
  4. 通过文章id递归查询所有评论(xml)

    2024-04-21 07:20:07       20 阅读

热门阅读

  1. 开发语言漫谈-React

    2024-04-21 07:20:07       17 阅读
  2. 常用数据结构及设计

    2024-04-21 07:20:07       13 阅读
  3. 开发语言漫谈-脚本语言

    2024-04-21 07:20:07       13 阅读