一、前言
使用python编写一个图表生成器,输入各公司的不良品数量,可以在一张图中同时展示数据的柱状图和折线图。
效果如下:
二、基础知识
绘制折线图和柱状图主要使用到了 pyecharts.charts
模块中的 Line
和 Bar
类。它们允许用户通过简单的调用方法创建和定制各种样式的折线图和柱状图,从而展示数据分布和趋势。以下是关于这两个类的详细解释:
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")
如何使用 Line
和 Bar
绘制并渲染折线图和柱状图:
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)主要功能和结构解析:
- PPMDataInputWidget 类:
- 用于创建一个 QWidget 子类,用于输入和展示单个公司的产品不良品数量。
- 每个实例对应一个公司的数据输入界面。
- 使用
QVBoxLayout
进行布局管理,包括输入框、添加月份按钮和删除按钮。 -
add_input_field
方法用于添加新的月份输入框。 -
get_ppm_data
方法用于获取输入的不良品数量数据。 -
delete_company
方法用于删除当前公司的输入界面。
- PPMInputApp 类:
- 主窗口类,继承自 QMainWindow。
- 包含添加新公司、生成图表和退出等功能按钮。
-
add_company_widget
方法用于在布局中添加新的 PPMDataInputWidget 实例。 -
generate_all_charts
方法用于生成所有公司的折线图和柱状图,并保存为 HTML 文件。
- 关键点解释:
- 在
PPMInputApp
中,通过点击按钮添加新公司,每个公司对应一个PPMDataInputWidget
实例,用于填写产品不良品数据。 - 点击生成图表按钮时,遍历所有
PPMDataInputWidget
实例,获取数据并使用 Pyecharts 创建折线图和柱状图。 - 折线图和柱状图数据使用
add_xaxis
和add_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可视化数据分析-饼状图》 ,欢迎大家一起学习!加电!