第一章 需求分析
问题描述:
使用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上查阅了很多资料,找到了一些类似的案例,终于完成了这一部分。
这次开发经历让我学到了很多新的知识,包括新的开发环境的使用,全新函数的调用等等,希望这些知识能够在未来帮助到我。