Qt/C++编写数据可视化大屏界面电子看板10-改造QCustomPlot

Qt/C++编写数据可视化大屏界面电子看板10-改造QCustomPlot

一、前言

为了抛弃对QChart的依赖,以及echart的依赖,(当然,后期也会做qchart的版本和echart的版本,尤其是echart的版本是肯定会做的,毕竟echart的效果牛逼的一塌糊涂,全宇宙最牛逼吧。)特意对QCustomPlot进行了大刀阔斧的改造,当然这个改造不是直接在源码上修改,这个就破坏了源码的完整性,说不定被QCustomPlot的作者知道了有种被QJ的感觉,我得改造是直接继承QCustomPlot中的部分类开始的,比如为了实现横向柱状图,特意继承自QCPItemRect类来实现的,包括了横向柱状图和横向柱状分组图。在这个横向柱状图的自动计算过程中,居然用到了十几年前学习的二元一次方程,自动计算数据和柱状图位置,给定两个数据点绘制矩形。

QCustomPlot类做的非常好,作者将曲线图和柱状图和其他几种形状的图,玩得神乎其神,本人直接跪了。尤其是现在的2.0版本,比以前的1.0版本更加上了一个档次,直接将各种功能拆分成一个个小类,分层绘制,相当牛逼,比如要做个游标,直接继承QCPLayerable类,然后在void draw(QCPPainter *painter);中绘制自己的东西即可,QCustomPlot提供了一个非常完美的鼠标拉动缩放的二维坐标系,还有对应的坐标与屏幕坐标转换的函数,继承自QCPItemRect这个东东,可以任意绘制任意图形,包括圆形矩形各种,本人有个大胆的想法就是,直接将那145个控件大全控件在QCustomPlot中绘制一遍,都是完全可行的,这样的话还支持滚轮任意缩放呢。

二、电子看板介绍

电子看板是目视化管理的一种表现形式,即对数据的状况一目了然地表现,主要是对于管理项目,它通过利用形象直观而又色彩适宜的各种视觉感知信息来组织现场生产活动,目视管理依据人类的生理特征,在生产现场充分利用信号灯、标识牌、符号颜色等方式来发出视觉信号,鲜明准确地刺激人的神经末梢,快速地传递信息,形象直观地将潜在的问题和浪费现象都显现出来。以便任何人都可以及时掌握管理现状和必要的情报,从而能够快速制定并实施应对措施。因此,管理看板是发现问题、解决问题的非常有效且直观的手段,是优秀的现场管理必不可少的工具之一。

三、功能特点

1. 整体总共分三级界面,一级界面是整体布局,二级界面是单个功能模块,三级界面是单个控件。

2. 子控件包括饼图+圆环图+曲线图+柱状图+柱状分组图+横向柱状图+横向柱状分组图+合格率控件+百分比控件+进度控件+设备状态面板+表格数据+地图控件(包括动态闪烁点+迁徙图等)+视频控件+其他控件等。

3. 二级界面可以自由拖动悬浮,支持最小化最大化关闭,响应双击自定义标题栏。

4. 数据源支持数据库采集(默认)、网络通信、网络请求等,可自由设定每个子界面的采集间隔即数据刷新频率。

5. 采用纯QWidget编写,支持Qt4.6到Qt5.12.3任何版本,支持嵌入式linux比如树莓派、香橙派、全志、imx6等。

6. 提供三个内核版本,自定义控件版本+qchart版本+echart版本。

7. 内置多套配色风格样式,默认紫色,支持任何分辨率。

8. 可设置标题+目标分辨率+布局方案,启动立即应用。

9. 可设置主背景颜色+面板颜色+十字线游标颜色。

10. 可设置多条曲线颜色,没有设置颜色的情况下内置15套精美颜色随机应用。

11. 可设置标题栏背景颜色+文字颜色。

12. 可设置曲线图表背景颜色+文字颜色+网格颜色。

13. 可设置正常颜色+警戒颜色+报警颜色+禁用颜色+百分比进度颜色。

14. 可分别设置各种字体大小,比如全局+软件名称+标题栏+子标题栏+加粗标签等。

15. 可设置标题栏高度+表头高度+行高度。

16. 曲线支持游标+悬停高亮数据点和显示值,柱状图支持顶部(可设置顶端+上部+中间+底部)显示数据,全部自适应计算位置。

17. 主界面直接鼠标右键切换布局+配色方案+关闭开启某个二级窗体。

18. 自动记忆所有子窗口的大小和位置,下次启动立即应用。

19. 动态加载布局方案菜单,可以动态新建布局、恢复布局、保存布局、另存布局等,用户可以制造任意布局。

20. 二级窗体,双击从主窗体分离出来浮动,可以自由调整大小。再次双击标题栏最大化,再次双击还原。

21. 每个模块都可以自定义采集速度,如果是数据库采集会自动排队处理。

五、特别说明

1. 可执行文件同级文件夹有layout+layout_1440+layout_1920,程序默认自动识别分辨率并加载对应的布局文件夹,比如1920分辨率则从layout_1920文件夹加载布局,并作为整体布局文件夹。

2. 程序默认是模拟数据,如果需要从数据库采集则修改配置文件WorkMode=db即可。

3. 如果发现布局拖动乱了,可以直接鼠标右键选择恢复布局即可,在保存布局以前。

4. 在中间地图模块鼠标右键可以弹出菜单,切换布局和配色方案等。

5. 在模块的标题栏上右键可以弹出默认的dock菜单,用来显示和隐藏各模块。

6. 软件关闭过程中会自动保存布局,下次启动以后自动应用。

7. 如果使用的默认的默认的配色方案比如紫色风格,则配置文件中的颜色全部无效,会自动应用代码中的颜色,如果需要启用自定义的颜色,则将配置文件的 Theme=\x81ea\x5b9a\x4e49\x98ce\x683c 即可。此时打开软件会应用配置文件中的颜色。

8. 右键菜单可以截图保存,默认命名为 配色方案名称_布局方案名称.png 保存在snap目录下。

9. 如果是XP系统请先执行fixff.cmd,用来修复ffmpeg在XP上不可用的BUG。

10. 在二级窗体的标题栏上右键弹出模块菜单,可以对单个模块打开关闭,其他地方右键全局菜单。

11. 可执行文件下载地址: pan.baidu.com/s/1o97IGv 提取码:r2bv。

12. 会不定期更新程序,欢迎各位提出批评和建议。

六、效果图

七、核心代码

void CustomPlot::mouseMove(QMouseEvent *event)
    if (tracer == 0 || textTip == 0) {
        return;
    //跟踪鼠标点击事件点击位置
    QCPGraph *graph = (QCPGraph *)customPlot->plottableAt(event->pos(), true);
    QRect rect(0, 0, 1, 1);
    QString labx, laby;
    int offset = 10;
    bool toolTip = false;
    if(graph != 0) {
        double posKey;
        double key = 0, value = 0;
        QPoint p;
        p.setX(event->pos().x());
        p.setY(event->pos().y());
#ifdef old
        foreach(QCPData data, graph->data()->values()) {
            key = data.key;
            value = data.value;
#else
        for (int i = 0; i < graph->dataCount(); i++) {
            key = graph->dataMainKey(i);
            value = graph->dataMainValue(i);
#endif
            //取出对应key处的label标签,如果标签为空则取key的字符串
            labx = customPlot->xAxis->tickVectorLabels().at(key);
            labx = labx.isEmpty() ? QString::number(key) : labx;
            //如果启用了百分比则需要后面显示百分比
            if (this->getPercentY()) {
                laby = QString("%1%").arg(value);
            } else {
                laby = QString("%1").arg(value);
            posKey = customPlot->xAxis->coordToPixel(key);
            if(qAbs(posKey - event->pos().x()) <= offset) {
                double posx = graph->keyAxis()->coordToPixel(key);
                double posy = graph->valueAxis()->coordToPixel(value);
                rect.setRect(posx - offset, posy - offset, offset * 2, offset * 2);
                if(!rect.contains(event->pos())) {
                    continue;
                } else {
                    break;
            if(posKey - event->pos().x() > offset) {
                break;
        if(!graph->realVisibility()) {
            toolTip = false;
            tracer->setVisible(false);
            customPlot->replot();
        } else if(rect.contains(event->pos())) {
            toolTip = true;
            tracer->setVisible(true);
            tracer->setGraph(graph);
            tracer->setGraphKey(key);
            customPlot->replot();
        } else if(tracer->visible()) {
            toolTip = false;
            tracer->setVisible(false);
            customPlot->replot();
    else if(tracer->visible()) {
        toolTip = false;
        tracer->setVisible(false);
        customPlot->replot();
    //处理提示信息
    if(toolTip) {
        //根据设定的提示信息位置调整
        double x = event->pos().x();
        double y = event->pos().y();
        if (toolTipPosition == 0) {
            //判断曲线控件的位置已经当前鼠标位置做出调整
            bool right = (customPlot->width() - x < 90);
            bool bottom = (customPlot->height() - y < 50);
            if (right) {
                if (bottom) {
                    x = x - 80;
                    y = y - 50;
                } else {
                    x = x - 80;
                    y = y - 20;
            } else {
                if (bottom) {
                    x = x + 10;
                    y = y - 50;
                } else {
                    x = x + 10;
                    y = y - 20;
        } else if (toolTipPosition == 1) {
            x = x - 30;
            y = y - 50;
        } else if (toolTipPosition == 2) {
            x = x + 10;
            y = y - 50;
        } else if (toolTipPosition == 3) {
            x = x + 10;
            y = y - 20;
        } else if (toolTipPosition == 4) {
            x = x + 10;
            y = y + 10;
        } else if (toolTipPosition == 5) {
            x = x - 30;
            y = y + 10;
        } else if (toolTipPosition == 6) {
            x = x - 75;
            y = y + 10;
        } else if (toolTipPosition == 7) {
            x = x - 80;
            y = y - 20;
        } else if (toolTipPosition == 8) {
            x = x - 80;
            y = y - 50;
        QString text = "横坐标: " + labx + "\n当前值: " + laby;
        textTip->position->setCoords(x, y);
        textTip->setText(text);
        textTip->setVisible(true);
        customPlot->replot();
    } else {
        if (textTip->visible()) {