一、前言
负荷分布模块包括工序计划负荷、当日负荷、负荷百分比三个子模块,工序计划负荷用表格的形式展示不同工序在不同日期的负荷工作时长,比如组装工序在 2022-02-10 运行了88小时,一般表格显示最近的一星期的信息;当日负荷用柱状图的形式显示不同工序当天的负荷运行时长;负荷百分比用折线图展示,其中包括了定位线默认放在100的值位置。
子模块表名对应表:
子模块标题 | 子模块表名 |
---|---|
工序计划负荷 | t_5_1_work_load |
当日负荷 | t_5_2_work_load_today |
负荷百分比 | t_5_3_work_load_percent |
1 工序计划负荷
表名:t_5_1_work_load
字段名 | 中文名 | 类型 | 长度 | 说明 |
---|---|---|---|---|
internal_id | 序号 | INTEGER | 11 | 主键自增 |
process_name | 工序名称 | VARCHAR | 255 | |
work_load_1 | 第1天负荷 | VARCHAR | 255 | |
work_load_* | 第*天负荷 | VARCHAR | 255 | |
work_load_7 | 第7天负荷 | VARCHAR | 255 |
默认数据:
internal_id | process_name | work_load_1 | work_load_2 | work_load_3 | work_load_4 | work_load_5 | work_load_6 | work_load_7 |
---|---|---|---|---|---|---|---|---|
1 | CNC粗 | 101H | 81H | 90H | 120H | 30H | 60H | 120H |
2 | CNC精 | 102H | 102H | 120H | 81H | 45H | 102H | 81H |
3 | EDM | 77H | 102H | 90H | 102H | 45H | 90H | 120H |
4 | WEDM | 87H | 102H | 120H | 45H | 102H | 102H | 90H |
5 | 抛光 | 45H | 102H | 102H | 90H | 81H | 81H | 81H |
6 | 钳工 | 89H | 90H | 45H | 120H | 120H | 120H | 102H |
7 | 组装 | 77H | 90H | 81H | 102H | 45H | 102H | 81H |
表名:t_5_1_work_load_table_head
这个设计很巧妙,相当于动态的日期和数据,日期作为标题。
字段名 | 中文名 | 类型 | 长度 | 说明 |
---|---|---|---|---|
internal_id | 序号 | INTEGER | 11 | 主键自增 |
date_1 | 第1天日期 | VARCHAR | 255 | |
date_2 | 第2天日期 | VARCHAR | 255 | |
date_3 | 第3天日期 | VARCHAR | 255 | |
date_4 | 第4天日期 | VARCHAR | 255 | |
date_5 | 第5天日期 | VARCHAR | 255 | |
date_6 | 第6天日期 | VARCHAR | 255 | |
date_7 | 第7天日期 | VARCHAR | 255 |
默认数据:
internal_id | date_1 | date_2 | date_3 | date_4 | date_5 | date_6 | date_7 |
---|---|---|---|---|---|---|---|
1 | 2019-04-30 | 2019-05-01 | 2019-05-02 | 2019-05-03 | 2019-05-04 | 2019-05-05 | 2019-05-06 |
2 当日负荷
表名:t_5_2_work_load_today
字段名 | 中文名 | 类型 | 长度 | 说明 |
---|---|---|---|---|
internal_id | 序号 | INTEGER | 11 | 主键自增 |
group_name | 分组名称 | REAL | 11 | 小数位精度1 |
green | 绿色值 | REAL | 11 | 小数位精度1 |
red | 红色值 | REAL | 11 | 小数位精度1 |
默认数据:
internal_id | group_name | green | red |
---|---|---|---|
1 | CNC粗 | 20 | 20 |
2 | CNC精 | 40 | 20 |
3 | EDM | 60 | 0 |
4 | WEDM | 30.2 | 2 |
5 | 铣床 | 40 | 0 |
6 | 磨床 | 30 | 50 |
3 负荷百分比
表名:t_5_3_work_load_percent
字段名 | 中文名 | 类型 | 长度 | 说明 |
---|---|---|---|---|
internal_id | 序号 | INTEGER | 11 | 主键自增 |
group_name | 分组名称 | VARCHAR | 255 | |
day_1 | 第1天 | INTEGER | 3 | |
day_* | 第*天 | INTEGER | 3 | |
day_7 | 第7天 | INTEGER | 3 |
默认数据:
internal_id | group_name | day_1 | day_2 | day_3 | day_4 | day_5 | day_6 | day_7 |
---|---|---|---|---|---|---|---|---|
1 | CNC粗 | 70 | 80 | 90 | 70 | 50 | 99 | 80 |
2 | CNC精 | 120 | 100 | 130 | 140 | 90 | 100 | 85 |
3 | EDM | 120 | 100 | 120 | 80 | 90 | 40 | 50 |
4 | WEDM | 100 | 120 | 120 | 100 | 100 | 80 | 70 |
5 | 铣床 | 90 | 80 | 75 | 40 | 12 | 30 | 10 |
6 | 磨床 | 80 | 70 | 50 | 60 | 40 | 50 | 30 |
二、功能特点
- 采用分层设计,整体总共分三级界面,一级界面是整体布局,二级界面是单个功能模块,三级界面是单个控件。
- 子控件包括饼图、圆环图、曲线图、柱状图、柱状分组图、横向柱状图、横向柱状分组图、合格率控件、百分比控件、进度控件、设备状态面板、表格数据、地图控件、视频控件等。
- 二级界面可以自由拖动悬浮,支持最小化隐藏、最大化关闭、响应双击自定义标题栏。
- 数据源支持模拟数据(默认)、数据库采集、串口通信(需定制)、网络通信(需定制)、网络请求等,可自由设定每个子界面的采集间隔即数据刷新频率。
- 采用纯QWidget编写,亲测Qt4.6到Qt6.2任意版本,理论上支持后续其他Qt版本。
- 超强跨平台,亲测windows、linux、mac、国产uos、国产银河麒麟kylin等系统,效果完美,同时还支持嵌入式linux比如树莓派、香橙派、全志、imx6等。
- 同时集成了自定义控件、qchart饼图、echart地图等功能。
- 内置多套配色风格样式(紫色、蓝色、深蓝、黑色),默认紫色,自适应任意分辨率。
- 可设置系统标题、目标分辨率、布局方案,启动立即应用。
- 可设置主背景颜色、面板颜色、十字线游标颜色等各种颜色。
- 可设置多条曲线不同颜色,没有设置颜色的情况下内置多套精美颜色随机应用。
- 可设置标题栏背景颜色、文字颜色。
- 可设置曲线图表背景颜色、文字颜色、网格颜色。
- 可设置正常颜色、警戒颜色、报警颜色、禁用颜色、百分比进度颜色。
- 可分别设置各种字体大小,比如全局字体、软件名称、标题栏、子标题栏、加粗标签等。
- 可设置标题栏高度、表头高度、行高度。
- 曲线支持游标、定位线、悬停高亮数据点、悬停显示值。
- 柱状图支持顶部(可设置顶端、上部、中间、底部)显示数据,全部自适应计算位置。
- 支持平滑曲线,内置多种平滑曲线算法,还支持面积图平滑。
- 面积图填充颜色可选多种规则比如单色透明度填充、透明度渐变填充等。
- 数据库支持sqlite、mysql、postgresql、oracle、国产人大金仓等数据库。
- 主界面直接鼠标右键切换布局、配色方案、关闭开启某个二级窗体。
- 自动记忆所有子窗口的大小和位置,下次启动立即应用。
- 动态加载布局方案菜单,可以动态新建布局、恢复布局、保存布局、另存布局等,用户可以制造任意布局。
- 二级窗体,双击从主窗体分离出来浮动,可以自由调整大小。再次双击标题栏最大化,再次双击还原。
- 子模块也可以全屏显示作为一个大屏,这样就可以一个大屏拓展出多个子大屏,放大查看子模块的数据详情,适用多屏展示。
- 每个模块都可以自定义采集速度,如果是数据库采集会自动排队处理,后期还可以拓展每个子模块都独立的数据库采集。
- 提供系统设置模块进行整体的配置参数设置,效果立即应用。
- 提供精美炫酷的大屏地图模块,包括静态图片、闪烁效果、迁徙效果、世界地图、区域地图等,可指定点的经纬度坐标,识别单击响应,可以做地图跳转等,每个点都可以不同的颜色和提示信息。
- 除了提供大屏系统外,还将每个模块都做了独立的模块示例界面,每个模块都可以独立学习使用,里面用到的控件也单独做了控件示例界面,方便学习每个控件如何使用。
- 非常详细的开发和使用手册,其中包括数据库说明、模块对照图、控件对照图、项目结构、代码说明(精确到每个类)、演示demo、使用方法等。
三、体验地址
- 体验地址:https://pan.baidu.com/s/1d7TH_GEYl5nOecuNlWJJ7g 提取码:01jf 文件名:bin_bigscreen.zip。
- 国内站点:https://gitee.com/feiyangqingyun
- 国际站点:https://github.com/feiyangqingyun
- 个人主页:https://blog.csdn.net/feiyangqingyun
- 知乎主页:https://www.zhihu.com/people/feiyangqingyun/
- 在线文档:https://feiyangqingyun.gitee.io/qwidgetdemo/bigscreen/
四、效果图
五、核心代码
#include "frmmodule5.h"
#include "ui_frmmodule5.h"
#include "quihelper.h"
#include "progressthree.h"
frmModule5::frmModule5(QWidget *parent) : QWidget(parent), ui(new Ui::frmModule5)
{
ui->setupUi(this);
this->initForm();
this->initTable();
this->initPlot1();
this->initPlot2();
}
frmModule5::~frmModule5()
{
delete ui;
}
void frmModule5::initForm()
{
//设置对应的属性应用特定样式
ui->labTitle1->setProperty("flag", "title");
ui->labTitle2->setProperty("flag", "title");
ui->labTitle3->setProperty("flag", "title");
ui->widgetSub1->setProperty("flag", "sub");
ui->widgetSub2->setProperty("flag", "sub");
ui->widgetSub3->setProperty("flag", "sub");
//定时器模拟数据
timer = new QTimer(this);
connect(timer, SIGNAL(timeout()), this, SLOT(loadTable()));
connect(timer, SIGNAL(timeout()), this, SLOT(loadPlot1()));
connect(timer, SIGNAL(timeout()), this, SLOT(loadPlot2()));
//绑定信号槽处理接收的数据+发送执查询语句
connect(DbData::DbLocal, SIGNAL(receiveData(QString, QStringList, int)),
this, SLOT(receiveData(QString, QStringList, int)));
connect(DbData::DbHttp, SIGNAL(receiveData(QString, QStringList, int)),
this, SLOT(receiveData(QString, QStringList, int)));
//绑定样式改变信号重新加载数据
connect(AppEvent::Instance(), SIGNAL(changeStyle()), this, SLOT(start()));
}
void frmModule5::receiveData(const QString &tag, const QStringList &data, int mesc)
{
int count = data.count();
if (tag == "t_5_1_work_load") {
if (count % 8 == 0) {
lstringl values;
ui->tableWidget->setRowCount(count / 8);
for (int i = 0; i < count; i = i + 8) {
QStringList value;
value << data.at(i);
value << data.at(i + 1);
value << data.at(i + 2);
value << data.at(i + 3);
value << data.at(i + 4);
value << data.at(i + 5);
value << data.at(i + 6);
value << data.at(i + 7);
value << QString("100|0|0");
values << value;
}
loadTable(values);
}
} else if (tag == "t_5_2_work_load_today") {
if (count % 3 == 0) {
rowNames1.clear();
vdouble value1, value2;
for (int i = 0; i < count; i = i + 3) {
rowNames1 << data.at(i);
value1 << data.at(i + 1).toDouble();
value2 << data.at(i + 2).toDouble();
}
loadPlot1(lvdouble() << value1 << value2);
}
} else if (tag == "t_5_3_work_load_percent") {
if (count % 8 == 0) {
lineNames.clear();
vstring lab;
lab << "1" << "2" << "3" << "4" << "5" << "6" << "7";
vdouble key;
key << 0 << 1 << 2 << 3 << 4 << 5 << 6;
//取出总共行数
int row = count / 8;
lvdouble values;
for (int i = 0; i < row; i++) {
for (int j = (8 * i); j < (8 * i) + 1; j++) {
lineNames << data.at(j);
}
double max = 8 * (i + 1);
vdouble value;
for (int j = (8 * i) + 1; j < max; j++) {
value << data.at(j).toDouble();
}
values << value;
}
loadPlot2(lab, key, values);
}
} else if (tag == "t_5_1_work_load_table_head") {
if (count == 7) {
QStringList headText;
headText << "工序";
for (int i = 0; i < count; i++) {
headText << data.at(i);
}
ui->tableWidget->setHorizontalHeaderLabels(headText);
}
}
}
void frmModule5::initTable()
{
QStringList headText;
headText << "工序" << "4月1日" << "4月2日" << "4月3日" << "4月4日" << "4月5日" << "4月6日" << "4月7日" << "进度" << "";
int columnCount = headText.count();
ui->tableWidget->setColumnCount(columnCount);
ui->tableWidget->setHorizontalHeaderLabels(headText);
ui->tableWidget->horizontalHeader()->setMinimumHeight(AppConfig::HeadHeight);
ui->tableWidget->verticalHeader()->setDefaultSectionSize(AppConfig::RowHeight);
//计算值的宽度
int width = 100;
if (QUIHelper::deskWidth() <= 1440) {
width = 80;
}
if (AppConfig::WorkMode != "timer") {
width = 110;
}
ui->tableWidget->setColumnWidth(0, 80);
ui->tableWidget->setColumnWidth(columnCount - 2, 120);
ui->tableWidget->setColumnHidden(columnCount - 2, true);
for (int i = 1; i < columnCount - 1; i++) {
ui->tableWidget->setColumnWidth(i, width);
}
ui->tableWidget->setFocusPolicy(Qt::NoFocus);
ui->tableWidget->setSelectionBehavior(QAbstractItemView::SelectRows);
ui->tableWidget->setEditTriggers(QAbstractItemView::NoEditTriggers);
ui->tableWidget->setSelectionMode(QAbstractItemView::SingleSelection);
ui->tableWidget->setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOff);
ui->tableWidget->setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff);
//ui->tableWidget->setAlternatingRowColors(true);
ui->tableWidget->verticalHeader()->setVisible(false);
ui->tableWidget->horizontalHeader()->setStretchLastSection(true);
ui->tableWidget->horizontalHeader()->setHighlightSections(false);
}
void frmModule5::loadTable()
{
QStringList list;
list << "process_name";
for (int i = 1; i <= 7; i++) {
list << QString("work_load_%1").arg(i);
}
QString tableName = "t_5_1_work_load";
QString columnName = list.join(",");
//读取表头
list.clear();
for (int i = 1; i <= 7; i++) {
list << QString("date_%1").arg(i);
}
QString tableName2 = "t_5_1_work_load_table_head";
QString columnName2 = list.join(",");
if (AppConfig::WorkMode == "timer") {
lstringl values;
int rowCount = 9;
ui->tableWidget->setRowCount(rowCount);
for (int i = 0; i < rowCount; i++) {
QStringList value;
value << QString(QUIHelper::getRandValue(1, 3) == 1 ? "CNC粗" : "CNC精");
value << QString("%1H").arg(QUIHelper::getRandValue(50, 150));
value << QString("%1H").arg(QUIHelper::getRandValue(100, 150));
value << QString("%1H").arg(QUIHelper::getRandValue(50, 150));
value << QString("%1H").arg(QUIHelper::getRandValue(100, 150));
value << QString("%1H").arg(QUIHelper::getRandValue(80, 150));
value << QString("%1H").arg(QUIHelper::getRandValue(100, 150));
value << QString("%1H").arg(QUIHelper::getRandValue(100, 150));
value << QString("%1|%2|0").arg(QUIHelper::getRandValue(10, 50)).arg(QUIHelper::getRandValue(10, 50));
values << value;
}
loadTable(values);
} else if (AppConfig::WorkMode == "db") {
DbData::DbLocal->select(tableName, columnName, true);
DbData::DbLocal->select(tableName2, columnName2, true);
} else if (AppConfig::WorkMode == "http") {
DbData::DbHttp->select(tableName, columnName, true);
DbData::DbHttp->select(tableName2, columnName2, true);
}
}
void frmModule5::loadTable(const lstringl &values)
{
int rowCount = values.count();
for (int i = 0; i < rowCount; i++) {
QStringList value = values.at(i);
int columnCount = value.count();
for (int j = 0; j < columnCount; j++) {
//末尾数据为进度条
if (j == columnCount - 1) {
QString data = value.at(j);
QStringList list = data.split("|");
if (list.count() != 3) {
continue;
}
//自定义多态进度条
ProgressThree *progress = new ProgressThree;
progress->setValue1(list.at(0).toInt());
progress->setValue2(list.at(1).toInt());
progress->setValue3(list.at(2).toInt());
progress->setFixedHeight(15);
//增加widget+布局巧妙实现居中
QWidget *widget = new QWidget;
QHBoxLayout *layout = new QHBoxLayout;
layout->setSpacing(0);
layout->setContentsMargins(0, 0, 0, 0);
layout->addWidget(progress);
widget->setLayout(layout);
ui->tableWidget->setCellWidget(i, j, widget);
} else {
QTableWidgetItem *item = new QTableWidgetItem(value.at(j));
item->setTextAlignment(Qt::AlignCenter);
ui->tableWidget->setItem(i, j, item);
}
}
}
}
void frmModule5::initPlot1()
{
rowNames1 << "CNC粗" << "CNC精" << "EDM" << "WEDM" << "铣床" << "磨床";
columnNames1 << "系列1" << "系列2";
}
void frmModule5::loadPlot1()
{
QString tableName = "t_5_2_work_load_today";
QString columnName = "group_name,green,red";
if (AppConfig::WorkMode == "timer") {
vdouble value1, value2;
for (int i = 0; i < 6; i++) {
value1 << QUIHelper::getRandValue(20, 50);
value2 << QUIHelper::getRandValue(20, 50);
}
loadPlot1(lvdouble() << value1 << value2);
} else if (AppConfig::WorkMode == "db") {
DbData::DbLocal->select(tableName, columnName, true);
} else if (AppConfig::WorkMode == "http") {
DbData::DbHttp->select(tableName, columnName, true);
}
}
void frmModule5::loadPlot1(const lvdouble &values)
{
ui->customPlot1->clear();
double maxY = CustomPlotHelper::getMaxValue2(values) * 1.2;
ui->customPlot1->setRangeX(0.5, rowNames1.count() + 0.5);
ui->customPlot1->setRangeY(0, maxY, AppConfig::StepY);
//设置柱状数据结构体
BarData data;
data.rowNames = rowNames1;
data.columnNames = columnNames1;
data.values = values;
data.borderColor = AppConfig::ColorChartText;
data.valuePosition = 0;
data.valuePrecision = 0;
data.valueColor = AppConfig::ColorChartText;
ui->customPlot1->setDataBars(data);
ui->customPlot1->replot();
}
void frmModule5::initPlot2()
{
lineNames << "CNC粗" << "CNC精" << "EDM" << "WEDM" << "铣床" << "磨床";
ui->customPlot2->setLegend(true, 7, 3);
}
void frmModule5::loadPlot2()
{
QStringList list;
list << "group_name";
for (int i = 1; i <= 7; i++) {
list << QString("day_%1").arg(i);
}
QString tableName = "t_5_3_work_load_percent";
QString columnName = list.join(",");
if (AppConfig::WorkMode == "timer") {
vstring lab;
lab << "1" << "2" << "3" << "4" << "5" << "6" << "7";
vdouble key, value1, value2, value3, value4, value5, value6;
key << 0 << 1 << 2 << 3 << 4 << 5 << 6;
int dataCount = lab.count();
for (int i = 0; i < dataCount; i++) {
value1 << QUIHelper::getRandValue(10, 150);
value2 << QUIHelper::getRandValue(10, 150);
value3 << QUIHelper::getRandValue(10, 150);
value4 << QUIHelper::getRandValue(10, 150);
value5 << QUIHelper::getRandValue(10, 150);
value6 << QUIHelper::getRandValue(10, 150);
}
loadPlot2(lab, key, lvdouble() << value1 << value2 << value3 << value4 << value5 << value6);
} else if (AppConfig::WorkMode == "db") {
DbData::DbLocal->select(tableName, columnName, true);
} else if (AppConfig::WorkMode == "http") {
DbData::DbHttp->select(tableName, columnName, true);
}
}
void frmModule5::loadPlot2(const vstring &lab, const vdouble &key, const lvdouble &values)
{
if (AppConfig::StaticLine) {
ui->customPlot2->drawStaticLineh(-0.5, 100, AppConfig::ColorLine);
}
int count = values.count();
ui->customPlot2->getPlot()->clearGraphs();
ui->customPlot2->addGraph(count);
LineData data;
data.key = key;
for (int i = 0; i < count; i++) {
data.index = i;
data.name = lineNames.at(i);
data.value = values.at(i);
//设置线条颜色,可以指定颜色也可以随机颜色
data.lineColor = ui->customPlot2->getColors().at(i);
ui->customPlot2->setDataLine(data);
}
//设置坐标轴范围值+X坐标对应显示文字
double maxY = CustomPlotHelper::getMaxValue(values) * 1.5;
ui->customPlot2->setLabX(key, lab);
ui->customPlot2->setRangeX(-0.5, key.count() - 0.5);
ui->customPlot2->setRangeY(0, maxY, AppConfig::StepY, AppConfig::ShowPercent);
ui->customPlot2->replot();
}
void frmModule5::start(int interval)
{
this->loadPlot1();
this->loadPlot2();
this->loadTable();
//如果间隔太短表示不需要刷新,执行一次即可
if (interval > 2000) {
timer->start(interval);
}
}
void frmModule5::stop()
{
if (timer->isActive()) {
timer->stop();
}
}