第一章 需求分析
问题描述:
使用c++ 或者 java语言设计一个数据图形化系统。利用数据图形化的描述语言,显示数据。要求所开发的系统具备图形操作界面。
可以在https://gitee.com/czyt1988/data-workbench的基础上进行 定制图形界面不能调用任何第三方库或系统自身提供的绘图库API。
支持缩放、保存、鼠标悬停时提示信息。
功能需求:
柱状图
描述语言例子:
option = {
xAxis: {
data: ['Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat', 'Sun']
},
yAxis: {},
series: [
{
type: 'bar',
data: [23, 24, 18, 25, 27, 28, 25]
}
]
};
对应的柱状图:

堆叠柱状图
堆叠柱状图就是一个系列的数值“堆叠”在另一个系列上,因而从他们的高度总和就能表达总量的变化。
option = {
xAxis: {
data: ['A', 'B', 'C', 'D', 'E']
},
yAxis: {},
series: [
{
data: [10, 22, 28, 43, 49],
type: 'bar',
stack: 'x'
},
{
data: [5, 4, 3, 5, 10],
type: 'bar',
stack: 'x'
}
]
};
对应的堆叠柱状图:

折线图
option = {
xAxis: {
type: 'category',
data: ['A', 'B', 'C']
},
yAxis: {
type: 'value'
},
series: [
{
data: [120, 200, 150],
type: 'line'
}
]
};
对应的折线图:

在这个例子中,我们通过 xAxis 将横坐标设为类目型,并指定了对应的值;通过 type 将 yAxis 的类型设定为数值型。在 series 中,我们将系列类型设为 line,并且通过 data 指定了折线图三个点的取值。这样,就能得到一个最简单的折线图了。
平滑曲线图
option = {
xAxis: {
data: ['A', 'B', 'C', 'D', 'E']
},
yAxis: {},
series: [
{
data: [10, 22, 28, 23, 19],
type: 'line',
smooth: true
}
]
};
对应的平滑曲线图:

使用合适的平滑算法。
开发环境:
Qt Creator 4.5.1(Community)
开发过程:
- 阅读实验需求,理解实验原理。
- 设计程序框架。
- 编写代码。
第二章 概要设计
总体设计:
该系统通过 MainWidget 类提供了一个简单的用户界面,使用户能够输入数据、生成不同类型的图表并进行保存。核心功能由 ChartWidget 类实现,包括图表的绘制、缩放和交互功能。整体架构利用 Qt 的信号槽机制实现了用户操作与图表显示的关联,同时提供了良好的用户体验和操作界面。
这种设计使得用户可以方便地输入数据、生成定制化的图表,并将其保存为常见的图像格式,适用于需要展示和分析数据的应用场景。
类的定义:
MainWidget 类
构造函数 MainWidget(QWidget *parent = nullptr):
初始化主窗口部件,并设置窗口初始大小为 800×600 像素。
创建 inputEdit,用于用户输入 JSON 格式的数据。
创建 generateButton 和 saveButton,分别用于生成图表和保存图表。
创建 chartWidget 对象,用于显示图表,这是 ChartWidget 类的实例。
设置各个控件的位置和大小。
析构函数 ~MainWidget():清理资源。
私有槽函数 onGenerateChart():
处理生成图表按钮 (generateButton) 的点击信号。
获取 inputEdit 中的 JSON 字符串,将其解析为 QJsonDocument 对象。
如果解析成功,调用 chartWidget 的 setChartOptions() 方法,将解析后的 QJsonObject 设置为图表的选项,从而更新图表显示。
私有槽函数 onSaveChart():
处理保存图表按钮 (saveButton) 的点击信号。
弹出文件对话框让用户选择保存路径和文件类型(PNG、JPEG、BMP)。
如果用户选择了文件路径,调用 chartWidget 的 saveImage() 方法将当前显示的图表保存为用户指定的图像文件。
ChartWidget 类
继承自 QWidget:用于显示图表的自定义窗口部件。
成员函数:
构造函数 ChartWidget(QWidget *parent = nullptr):初始化图表部件,设置鼠标跟踪。
setChartOptions(const QJsonObject &options):设置图表的选项,更新并重绘图表。
saveImage(const QString &filePath):将当前图表保存为指定路径的图像文件。
缩放相关函数:zoomIn()、zoomOut()、resetZoom(),用于放大、缩小和重置图表的缩放比例。
事件处理函数:包括 paintEvent(绘制图表)、mouseMoveEvent(处理鼠标移动事件以显示工具提示)、wheelEvent(处理鼠标滚轮事件以实现缩放)。
成员变量:
chartOptions:存储图表的选项(例如 X 轴数据、系列数据等)。
mousePos:存储当前鼠标位置,用于显示工具提示。
zoomFactor:当前的缩放因子,用于实现图表的缩放功能。
其他私有变量和方法用于具体的绘制功能和数据处理。
接口设计:
输入和操作:
inputEdit:用于用户输入 JSON 数据,支持任意格式的数据输入。
generateButton:生成图表按钮,点击后触发 onGenerateChart() 槽函数,根据用户输入的 JSON 数据生成并显示图表。
saveButton:保存图表按钮,点击后触发 onSaveChart() 槽函数,允许用户选择文件路径和类型保存当前显示的图表。
图表显示:
chartWidget:由 ChartWidget 类负责实际的图表绘制和显示,通过调用 setChartOptions() 更新图表数据,并通过 saveImage() 方法保存图表为图像文件。
setChartOptions(const QJsonObject &options):通过传入 JSON 对象设置图表的各种选项,如 X 轴数据、系列数据等。
saveImage(const QString &filePath):将当前显示的图表保存为指定路径的图像文件。
缩放相关函数:zoomIn()、zoomOut()、resetZoom(),提供用户交互接口,支持图表的放大和缩小。
运行界面设计:
图表绘制:使用 QPainter 绘制 X 轴、Y 轴、数据点、折线或柱状图。
交互功能:支持鼠标悬停显示数据点信息,支持滚轮缩放图表。
用户反馈:通过工具提示显示鼠标悬停位置的数据信息,提高用户体验。
第三章 详细设计
1. ChartWidget 类
头文件 chartwidget.h
#ifndef CHARTWIDGET_H#define CHARTWIDGET_H
#include <QWidget>
#include <QJsonObject>
#include <QMap>
class ChartWidget : public QWidget
{
Q_OBJECT
public:
explicit ChartWidget(QWidget *parent = nullptr);
void setChartOptions(const QJsonObject &options);
void saveImage(const QString &filePath);
void zoomIn();
void zoomOut();
void resetZoom();
protected:
void paintEvent(QPaintEvent *event) override;
void mouseMoveEvent(QMouseEvent *event) override;
void wheelEvent(QWheelEvent *event) override;
private:
QJsonObject chartOptions;
QString chartTitle;
QPoint mousePos;
qreal zoomFactor = 1.0;
 
void drawBarChart(QPainter &painter, const QJsonArray &data, const QString &stack, qreal xInterval, int height);
void drawLineChart(QPainter &painter, const QJsonArray &xAxisData, const QJsonArray &data, qreal xInterval, int height);
void drawSmoothLineChart(QPainter &painter, const QJsonArray &xAxisData, const QJsonArray &data, qreal xInterval, int height);
};
#endif // CHARTWIDGET_H
成员变量:
- chartOptions:存储图表选项的 JSON 对象。
- chartTitle:存储图表标题。
- mousePos:存储当前鼠标位置。
- zoomFactor:当前的缩放因子,用于控制图表的缩放。
公共函数:
- ChartWidget(QWidget *parent = nullptr):构造函数,初始化图表部件。
- void setChartOptions(const QJsonObject &options):设置图表的选项,更新并重绘图表。
- void saveImage(const QString &filePath):将当前图表保存为指定路径的图像文件。
- void zoomIn()、void zoomOut()、void resetZoom():分别实现放大、缩小和重置图表的缩放功能。
事件处理函数:
- void paintEvent(QPaintEvent *event):重绘事件处理函数,绘制图表内容。
- void mouseMoveEvent(QMouseEvent *event):鼠标移动事件处理函数,用于显示数据提示。
- void wheelEvent(QWheelEvent *event):鼠标滚轮事件处理函数,实现图表的放大和缩小。
私有函数:
- void drawBarChart(QPainter &painter, const QJsonArray &data, const QString &stack, qreal xInterval, int height):绘制柱状图。
- void drawLineChart(QPainter &painter, const QJsonArray &xAxisData, const QJsonArray &data, qreal xInterval, int height):绘制折线图。
- void drawSmoothLineChart(QPainter &painter, const QJsonArray &xAxisData, const QJsonArray &data, qreal xInterval, int height):绘制平滑曲线图。
实现文件 chartwidget.cpp
#include "chartwidget.h"
#include <QPainter>
#include <QPainterPath>
#include <QMouseEvent>
#include <QToolTip>
#include <QJsonArray>
#include <QtCore/qmath.h>
 
ChartWidget::ChartWidget(QWidget *parent)
: QWidget(parent)
{
setMouseTracking(true); // 开启鼠标跟踪,以便实时获取鼠标位置
}
void ChartWidget::setChartOptions(const QJsonObject &options){
chartOptions = options;
update(); // 更新图表内容
}
void ChartWidget::saveImage(const QString &filePath){
QPixmap pixmap(size());
render(&pixmap);
pixmap.save(filePath);
}
void ChartWidget::zoomIn(){
zoomFactor *= 1.1; // 每次放大10%
update(); // 更新图表内容
}
void ChartWidget::zoomOut(){
zoomFactor /= 1.1; // 每次缩小10%
update(); // 更新图表内容
}
void ChartWidget::resetZoom(){
zoomFactor = 1.0; // 重置缩放因子
update(); // 更新图表内容
}
void ChartWidget::wheelEvent(QWheelEvent *event){
if (event->angleDelta().y() > 0)
{
zoomIn(); // 向上滚动放大
}
else
{
zoomOut(); // 向下滚动缩小
}
}
void ChartWidget::paintEvent(QPaintEvent *event){
// 绘制图表的具体实现,根据 chartOptions 中的数据绘制不同类型的图表
}
void ChartWidget::mouseMoveEvent(QMouseEvent *event){
// 处理鼠标移动事件,显示数据提示信息
}
void ChartWidget::drawBarChart(QPainter &painter, const QJsonArray &data, const QString &stack, qreal xInterval, int height){
// 绘制柱状图的具体实现
}
void ChartWidget::drawLineChart(QPainter &painter, const QJsonArray &xAxisData, const QJsonArray &data, qreal xInterval, int height){
// 绘制折线图的具体实现
}
void ChartWidget::drawSmoothLineChart(QPainter &painter, const QJsonArray &xAxisData, const QJsonArray &data, qreal xInterval, int height){
// 绘制平滑折线图的具体实现
}
2. MainWidget 类
头文件 mainwidget.h
#ifndef MAINWIDGET_H#define MAINWIDGET_H
#include <QWidget>#include <QJsonObject>#include <QJsonDocument>#include <QJsonArray>#include <QVBoxLayout>#include <QLineEdit>#include <QPushButton>#include <QLabel>
class ChartWidget;
class MainWidget : public QWidget
{
Q_OBJECT
public:
MainWidget(QWidget *parent = nullptr);
~MainWidget();
private slots:
void onGenerateChart();
void onSaveChart();
private:
QLineEdit *inputEdit;
QPushButton *generateButton;
QPushButton *saveButton;
ChartWidget *chartWidget;
};
#endif // MAINWIDGET_H
成员变量:
- inputEdit:用户输入框,用于输入 JSON 数据。
- generateButton、saveButton:生成图表和保存图表的按钮。
- chartWidget:指向 ChartWidget类的对象指针,用于显示图表。
公共函数:
- MainWidget(QWidget *parent = nullptr):构造函数,初始化界面布局和控件。
- ~MainWidget():析构函数,清理资源。
私有槽函数:
- onGenerateChart():处理生成图表按钮点击事件,解析用户输入的 JSON 数据,并更新 chartWidget显示对应的图表。
- onSaveChart():处理保存图表按钮点击事件,弹出文件保存对话框,并将当前显示的图表保存为用户指定的图像文件。
实现文件 mainwidget.cpp
#include "mainwidget.h"
#include "chartwidget.h"
#include <QFileDialog>
 
MainWidget::MainWidget(QWidget *parent)
: QWidget(parent),
inputEdit(new QLineEdit(this)),
generateButton(new QPushButton("生成图表", this)),
saveButton(new QPushButton("保存图片", this)),
chartWidget(new ChartWidget(this))
{
resize(800, 600); // 设置主窗口初始大小
 
// 设置控件位置和大小
inputEdit->setGeometry(0, 0, 800, 100);
generateButton->setGeometry(0, 100, 400, 20);
saveButton->setGeometry(400, 100, 400, 20);
chartWidget->setGeometry(0, 200, 800, 400);
 
// 连接信号和槽
connect(generateButton, &QPushButton::clicked, this, &MainWidget::onGenerateChart);
connect(saveButton, &QPushButton::clicked, this, &MainWidget::onSaveChart);
}
 
MainWidget::~MainWidget()
{
// 清理资源
}
void MainWidget::onGenerateChart(){
// 处理生成图表按钮点击事件
QString jsonString = inputEdit->text(); // 获取用户输入的 JSON 字符串
QJsonDocument jsonDoc = QJsonDocument::fromJson(jsonString.toUtf8()); // 解析为 JSON 文档
if (!jsonDoc.isNull())
{
chartWidget->setChartOptions(jsonDoc.object()); // 将解析后的 JSON 对象传递给 chartWidget 显示图表
}
}
void MainWidget::onSaveChart(){
// 处理保存图表按钮点击事件
QString filePath = QFileDialog::getSaveFileName(this, "Save Chart", "", "PNG Files (*.png);;JPEG Files (*.jpg *.jpeg);;BMP Files (*.bmp)"); // 弹出文件保存对话框
if (!filePath.isEmpty())
{
chartWidget->saveImage(filePath); // 将当前图表保存为指定格式的图像文件
}
}
3. 主程序入口 main.cpp
文件 main.cpp
#include <QApplication>
#include "mainwidget.h"
int main(int argc, char *argv[]){
QApplication a(argc, argv); // 创建 Qt 应用程序对象
 
MainWidget w; // 创建主窗口对象
w.show(); // 显示主窗口
 
return a.exec(); // 进入 Qt 事件循环,等待事件发生并处理
}
函数 main():
- int main(int argc, char *argv[]):主函数入口,负责启动应用程序。
- QApplication a(argc, argv);:创建 QApplication对象 a,管理应用程序的整体资源和事件处理。
- MainWidget w;:创建 MainWidget类的实例 w,即主窗口对象。
- show();:调用 show()方法显示主窗口,使用户可以看到和操作界面。
- return a.exec();:进入 Qt 的事件循环,等待用户操作和系统事件,保持应用程序的响应性和交互性。
第四章 测试分析
柱状图
测试用例:
{
"xAxis": {
"data": ["A", "B", "C", "D", "E"]
},
"series": [
{
"type": "bar",
"data": [30, 50, 80, 40, 70]
}
]
}
运行结果:

堆叠柱状图
测试用例:
{
"xAxis": {
"data": ["A", "B", "C", "D", "E"]
},
"series": [
{
"type": "bar",
"stack": "stack1",
"data": [30, 50, 80, 40, 70]
},
{
"type": "bar",
"stack": "stack1",
"data": [20, 40, 60, 30, 50]
}
]
}
运行结果:

折线图
测试用例:
{
"xAxis": {
"data": ["Jan", "Feb", "Mar", "Apr", "May"]
},
"series": [
{
"type": "line",
"smooth": false,
"data": [100, 120, 90, 150, 80]
}
]
}
运行结果:

平滑曲线图
测试用例:
{
"xAxis": {
"data": ["Jan", "Feb", "Mar", "Apr", "May"]
},
"series": [
{
"type": "line",
"smooth": true,
"data": [100, 120, 90, 150, 80]
}
]
}
运行结果:

第五章 总结和体会
这是我第一次独自完成一个完整项目的开发,内容涉及多个方面,包括窗口界面的设计,用户的输入,绘制图像,保存图片,鼠标悬停显示信息,滚轮放大缩小等内容。开发过程中遇到了很多困难。刚开始我对全新的开发环境(Qt Creator)不够熟悉,编译器始终找不到文件所在的位置。查找了很多资料,才知道文件的存储路径不能存在中文。QT提供了很多全新的函数,需要花费很多的精力去学习。在窗口界面的设计上遇到了很多不懂的问题,在这部分我请教了周围的同学,终于得到了解答。在图像的绘制部分,我刚开始把Y轴的高度固定为100,但在老师的提醒下才发现这样设计不合理。于是我修改了y轴的生成过程,通过读取数据的最大值和最小值,动态地改变Y轴的标记,使生成的图像更加合理。关于鼠标悬停显示信息的部分,我在CSDN上查阅了很多资料,找到了一些类似的案例,终于完成了这一部分。
这次开发经历让我学到了很多新的知识,包括新的开发环境的使用,全新函数的调用等等,希望这些知识能够在未来帮助到我。

