一、前言

上次在写大屏数据可视化电子看板系统时候,提到过改造QCustomPlot来实现柱状分组图、横向柱状图、横向分组图、鼠标悬停提示等。这次单独列出来描述,有很多人疑问为啥不用QChart,或者echart等形式,其实这两种方式我都尝试过,比如Qt5.7以后新增的QChart模块,曲线这块,支持数据量很小,而且用法极其不适应,非常别扭,尤其是10W以上数据量的支持,简直是渣渣,优点也是有很多的,比如动画效果,我看过他的完整源码,动画这块处理的非常好,连坐标轴都可以有动画效果,而且支持很多种效果,而且内置了很多套theme皮肤,省去了很多渣渣审美的程序员自己来配色,这个倒是挺方便的。而对于echart,必须依赖浏览器控件,资源占用比较高,后面决定采用改造QCustomPlot来实现用户需要的各种图表效果。

在整个改造的过程中,全部封装成易用的函数,传入参数即可,同时还支持全局样式更改,支持样式表控制整体颜色更改,考虑了很多细节,比如弹出悬停信息的位置等,都自动计算显示在最佳最合理位置。考虑到很多人用的QCustomPlot1.0,特意还做了QCustomPlot1.0和2.0的完全兼容。

二、实现的功能

  • 1:可设置X轴Y轴范围值
  • 2:可设置背景颜色+文本颜色+网格颜色
  • 3:可设置三条曲线颜色+颜色集合
  • 4:可设置是否显示定位十字线,可分别设置横向和纵向
  • 5:可设置十字线的宽度和颜色
  • 6:可设置是否显示数据点以及数据点的大小
  • 7:可设置是否填充背景形成面积图
  • 8:可设置模式-拖动+缩放等
  • 9:可设置坐标轴间距+第二坐标系可见
  • 10:提供接口setDataLine直接设置曲线,支持多条
  • 11:提供接口setDataBar直接设置柱状图,支持多条形成堆积图
  • 12:提供接口setLabs设置文本标签替代key
  • 13:提供清空+重绘接口+外部获取QCustomPlot对象
  • 14:提供函数start+stop来模拟正弦曲线
  • 15:可设置柱状图的值的位置+精确度+颜色
  • 16:支持鼠标移动到数据点高亮显示数据点以及显示数据提示信息
  • 17:可设置提示信息位置 自动处理+顶部+右上角+右侧+右下角+底部+左下角+左侧+左上角
  • 18:可设置是否校验数据产生不同的背景颜色,比如柱状图的每根柱子都可以根据数据生成不同背景颜色
  • 19:可设置是否显示图例+图例位置+图例行数
  • 20:支持多条曲线+柱状图+柱状分组图+横向柱状图+横向柱状分组图+柱状堆积图
  • 21:内置15套精美颜色,自动取颜色集合的颜色,省去配色的烦恼
  • 22:同时支持 QCustomPlot 1.0 和 QCustomPlot 2.0

三、效果图

四、核心代码

void CustomPlot::setDataLine(int index, const QString &name, const QVector<double> &key, const QVector<double> &value)
{
if (customPlot->graphCount() > index) {
customPlot->graph(index)->setName(name);
customPlot->graph(index)->setData(key, value);
customPlot->xAxis->setRange(-offsetX, key.count() + offsetX, Qt::AlignLeft); //超过3条线条颜色设置颜色集合的颜色
if (index >= 3) {
setColor(index, colors.at(index));
} else {
setColor(0, colors.at(0));
setColor(1, colors.at(1));
setColor(2, colors.at(2));
}
}
} void CustomPlot::setDataBarv(const QStringList &rowNames,
const QStringList &columnNames,
const QList<QVector<double> > &values,
const QColor &borderColor,
int valuePosition,
int valuePrecision,
const QColor &valueColor,
bool checkData)
{
//只有1列的才能设置
if (columnNames.count() != 1) {
return;
} //可以直接用堆积图,因为只有一列的柱状图不会形成堆积
setDataBars(rowNames, columnNames, values, borderColor, valuePosition, valuePrecision, valueColor, checkData);
} void CustomPlot::setDataBarvs(const QStringList &rowNames,
const QStringList &columnNames,
const QList<QVector<double> > &values,
const QColor &borderColor,
int valuePosition,
int valuePrecision,
const QColor &valueColor,
bool checkData)
{
//过滤个数不一致数据,防止索引越界
int rowCount = rowNames.count();
int columnCount = columnNames.count();
int valueCount = values.count();
if (columnCount == 0 || valueCount == 0 || columnCount != valueCount) {
return;
} //设置网格线不显示,会更好看
customPlot->xAxis->grid()->setVisible(false);
//customPlot->yAxis->grid()->setVisible(false); //设置横坐标文字描述
QVector<double> ticks;
QVector<QString> labels;
int count = rowCount * columnCount;
for (int i = 0; i < rowCount; i++) {
ticks << 1.5 + (i * columnCount);
labels << rowNames.at(i);
} setLabX(ticks, labels);
customPlot->xAxis->setRange(0, count + 1); for (int i = 0; i < columnCount; i++) {
//同样也要先过滤个数是否符合要求
QVector<double> value = values.at(i);
if (rowCount != value.count()) {
continue;
} //创建柱状图
CustomBarv *bar = new CustomBarv(customPlot->xAxis, customPlot->yAxis);
bar->setCheckData(checkData); //设置宽度比例
bar->setWidth(0.9); //设置显示值的位置 0-不绘制 1-顶部上面 2-顶部居中 3-中间居中 4-底部居中
bar->setValuePostion(valuePosition);
bar->setValuePrecision(valuePrecision);
bar->setValueColor(valueColor); //设置名称
bar->setName(columnNames.at(i)); //设置颜色,取颜色集合
QColor color = QColor(51, 204, 255);
if (i < colors.count()) {
color = colors.at(i);
} //边缘高亮,如果传入了边框颜色则取边框颜色
bar->setPen(QPen(borderColor == Qt::transparent ? color.light(150) : borderColor));
bar->setBrush(color); //这个算法很巧妙,想了很久
QVector<double> ticks;
double offset = i * 0.9;
for (int j = 0; j < rowCount; j++) {
ticks << 1.0 + (j * columnCount) + offset;
} //设置数据
bar->setData(ticks, value);
}
} void CustomPlot::setDataBarh(const QStringList &rowNames,
const QStringList &columnNames,
const QList<QVector<double> > &values,
const QColor &borderColor,
int valuePosition,
int valuePrecision,
const QColor &valueColor,
bool checkData)
{
//只有1列的才能设置
if (columnNames.count() != 1) {
return;
} //过滤个数不一致数据,防止索引越界
int rowCount = rowNames.count();
int columnCount = columnNames.count();
int valueCount = values.count();
if (columnCount == 0 || valueCount == 0 || columnCount != valueCount) {
return;
} //设置网格线不显示,会更好看
customPlot->xAxis->grid()->setVisible(false);
customPlot->yAxis->grid()->setVisible(false);
customPlot->yAxis->setTickLength(0, 0); //设置横坐标文字描述
QVector<double> ticks;
QVector<QString> labels;
int count = rowCount * columnCount;
double padding = 1;
for (int i = 0; i < rowCount; i++) {
ticks << padding + (i * columnCount);
labels << rowNames.at(i);
} setLabY(ticks, labels);
customPlot->yAxis->setRange(0, count + 1); //先计算出每个柱子占用的高度
double barHeight = 0.7;
for (int i = 0; i < columnCount; i++) {
//同样也要先过滤个数是否符合要求
QVector<double> value = values.at(i);
if (rowCount != value.count()) {
continue;
} //先绘制系列1的数据,再绘制系列2,依次类推
for (int j = 0; j < rowCount; j++) {
//创建横向柱状图
double y = (0.67 + (j * columnCount));
CustomBarh *bar = new CustomBarh(customPlot);
bar->setCheckData(checkData);
bar->setRect(QPointF(0, y), QPointF(value.at(j), y + barHeight));
bar->setValue(value.at(j)); //设置显示值的位置 0-不绘制 1-顶部上面 2-顶部居中 3-中间居中 4-底部居中
bar->setValuePostion(valuePosition);
bar->setValuePrecision(valuePrecision);
bar->setValueColor(valueColor); //设置颜色,取颜色集合
QColor color = QColor(51, 204, 255);
if (i < colors.count()) {
color = colors.at(i);
} //边缘高亮,如果传入了边框颜色则取边框颜色
bar->setPen(QPen(borderColor == Qt::transparent ? color.light(150) : borderColor));
bar->setBrush(color);
}
}
} void CustomPlot::setDataBarhs(const QStringList &rowNames,
const QStringList &columnNames,
const QList<QVector<double> > &values,
const QColor &borderColor,
int valuePosition,
int valuePrecision,
const QColor &valueColor,
bool checkData)
{
//过滤个数不一致数据,防止索引越界
int rowCount = rowNames.count();
int columnCount = columnNames.count();
int valueCount = values.count();
if (columnCount == 0 || valueCount == 0 || columnCount != valueCount) {
return;
} //设置网格线不显示,会更好看
customPlot->xAxis->grid()->setVisible(false);
customPlot->yAxis->grid()->setVisible(false);
customPlot->yAxis->setTickLength(0, 0);
customPlot->xAxis->setVisible(false); //设置横坐标文字描述
QVector<double> ticks;
QVector<QString> labels;
int count = rowCount * columnCount;
//这个算法想了很久,很牛逼
double padding = 1.5 + (columnCount - 2) * 0.4;
for (int i = 0; i < rowCount; i++) {
ticks << padding + (i * columnCount);
labels << rowNames.at(i);
} setLabY(ticks, labels);
customPlot->yAxis->setRange(0, count + 1); //先计算出每个柱子占用的高度
double barHeight = 0.8;
for (int i = 0; i < columnCount; i++) {
//同样也要先过滤个数是否符合要求
QVector<double> value = values.at(i);
if (rowCount != value.count()) {
continue;
} //先绘制系列1的数据,再绘制系列2,依次类推
for (int j = 0; j < rowCount; j++) {
//创建横向柱状图
double y = (0.7 + i * barHeight + (j * columnCount));
CustomBarh *bar = new CustomBarh(customPlot);
bar->setCheckData(checkData);
bar->setRect(QPointF(0, y), QPointF(value.at(j), y + barHeight));
bar->setValue(value.at(j)); //设置显示值的位置 0-不绘制 1-顶部上面 2-顶部居中 3-中间居中 4-底部居中
bar->setValuePostion(valuePosition);
bar->setValuePrecision(valuePrecision);
bar->setValueColor(valueColor); //设置颜色,取颜色集合
QColor color = QColor(51, 204, 255);
if (j < colors.count()) {
color = colors.at(j);
} //边缘高亮,如果传入了边框颜色则取边框颜色
bar->setPen(QPen(borderColor == Qt::transparent ? color.light(150) : borderColor));
bar->setBrush(color);
}
}
} void CustomPlot::setDataBars(const QStringList &rowNames,
const QStringList &columnNames,
const QList<QVector<double> > &values,
const QColor &borderColor,
int valuePosition,
int valuePrecision,
const QColor &valueColor,
bool checkData)
{
//过滤个数不一致数据,防止索引越界
int rowCount = rowNames.count();
int columnCount = columnNames.count();
int valueCount = values.count();
if (columnCount == 0 || valueCount == 0 || columnCount != valueCount) {
return;
} //设置网格线不显示,会更好看
customPlot->xAxis->grid()->setVisible(false);
//customPlot->yAxis->grid()->setVisible(false); //先清空原有柱状图
bars.clear(); //设置横坐标文字描述
QVector<double> ticks;
QVector<QString> labels;
for (int i = 0; i < rowCount; i++) {
ticks << i + 1;
labels << rowNames.at(i);
} setLabX(ticks, labels);
customPlot->xAxis->setRange(0, rowCount + 1); for (int i = 0; i < columnCount; i++) {
//同样也要先过滤个数是否符合要求
QVector<double> value = values.at(i);
if (rowCount != value.count()) {
continue;
} //创建柱状堆积图
CustomBarv *bar = new CustomBarv(customPlot->xAxis, customPlot->yAxis);
bar->setCheckData(checkData); //设置宽度比例
bar->setWidth(0.6); //设置显示值的位置 0-不绘制 1-顶部上面 2-顶部居中 3-中间居中 4-底部居中
bar->setValuePostion(valuePosition);
bar->setValuePrecision(valuePrecision);
bar->setValueColor(valueColor); #ifndef old
//设置堆积间隙
if (borderColor != Qt::transparent) {
bar->setStackingGap(1);
}
#endif
//设置名称
bar->setName(columnNames.at(i)); //设置颜色,取颜色集合
QColor color = QColor(51, 204, 255);
if (i < colors.count()) {
color = colors.at(i);
} //边缘高亮,如果传入了边框颜色则取边框颜色
if (columnCount > 1 && borderColor == Qt::transparent) {
bar->setPen(Qt::NoPen);
} else {
bar->setPen(QPen(borderColor == Qt::transparent ? color.light(150) : borderColor));
} bar->setBrush(color); //设置堆积层叠顺序,后面那个移到前一个上面
bars << bar;
if (i > 0) {
bar->moveAbove(bars.at(i - 1));
} //设置数据
bar->setData(ticks, value);
}
}

五、控件介绍

  1. 超过146个精美控件,涵盖了各种仪表盘、进度条、进度球、指南针、曲线图、标尺、温度计、导航条、导航栏,flatui、高亮按钮、滑动选择器、农历等。远超qwt集成的控件数量。
  2. 每个类都可以独立成一个单独的控件,零耦合,每个控件一个头文件和一个实现文件,不依赖其他文件,方便单个控件以源码形式集成到项目中,较少代码量。qwt的控件类环环相扣,高度耦合,想要使用其中一个控件,必须包含所有的代码。
  3. 全部纯Qt编写,QWidget+QPainter绘制,支持Qt4.6到Qt5.12的任何Qt版本,支持mingw、msvc、gcc等编译器,支持任意操作系统比如windows+linux+mac+嵌入式linux等,不乱码,可直接集成到Qt Creator中,和自带的控件一样使用,大部分效果只要设置几个属性即可,极为方便。
  4. 每个控件都有一个对应的单独的包含该控件源码的DEMO,方便参考使用。同时还提供一个所有控件使用的集成的DEMO。
  5. 每个控件的源代码都有详细中文注释,都按照统一设计规范编写,方便学习自定义控件的编写。
  6. 每个控件默认配色和demo对应的配色都非常精美。
  7. 超过130个可见控件,6个不可见控件。
  8. 部分控件提供多种样式风格选择,多种指示器样式选择。
  9. 所有控件自适应窗体拉伸变化。
  10. 集成自定义控件属性设计器,支持拖曳设计,所见即所得,支持导入导出xml格式。
  11. 自带activex控件demo,所有控件可以直接运行在ie浏览器中。
  12. 集成fontawesome图形字体+阿里巴巴iconfont收藏的几百个图形字体,享受图形字体带来的乐趣。
  13. 所有控件最后生成一个dll动态库文件,可以直接集成到qtcreator中拖曳设计使用。
  14. 目前已经有qml版本,后期会考虑出pyqt版本,如果用户需求量很大的话。

六、SDK下载

  • SDK下载链接:https://pan.baidu.com/s/1tD9v1YPfE2fgYoK6lqUr1Q 提取码:lyhk
  • 自定义控件+属性设计器欣赏:https://pan.baidu.com/s/1l6L3rKSiLu_uYi7lnL3ibQ 提取码:tmvl
  • 下载链接中包含了各个版本的动态库文件,所有控件的头文件,使用demo。
  • 自定义控件插件开放动态库dll使用(永久免费),无任何后门和限制,请放心使用。
  • 目前已提供26个版本的dll,其中包括了qt5.12.3 msvc2017 32+64 mingw 32+64 的。
  • 不定期增加控件和完善控件,不定期更新SDK,欢迎各位提出建议,谢谢!
  • widget版本(QQ:517216493)qml版本(QQ:373955953)三峰驼(QQ:278969898)。
  • 涛哥的知乎专栏 Qt进阶之路 https://zhuanlan.zhihu.com/TaoQt
  • 欢迎关注微信公众号【高效程序员】,C++/Python、学习方法、写作技巧、热门技术、职场发展等内容,干货多多,福利多多!

Qt编写自定义控件25-自定义QCustomPlot的更多相关文章

  1. Qt编写自定义控件二动画按钮

    现在的web发展越来越快,很多流行的布局样式,都是从web开始的,写惯了Qt widgets 项目,很多时候想改进一下现有的人机交互,尤其是在现有的按钮上加一些动画的效果,例如鼠标移上去变大,移开还原 ...

  2. Qt编写自定义控件38-高亮按钮

    一.前言 高亮按钮控件,既可以作为类似于交通指示灯使用,也可以作为设备状态指示灯使用,控件内置多套颜色风格,还可以自己设置颜色风格,按钮可以增加文字显示,非常适合需要在状态设备上显示小量的文字展示,按 ...

  3. Qt编写自定义控件24-图片轮播控件

    一.前言 上一篇文章写的广告轮播控件,采用的传统widget堆积设置样式表做的,这次必须要用到更高级的QPainter来绘制了,这个才是最高效的办法,本控件参考雨田哥的轮播控件,经过大规模的改造而成, ...

  4. Qt编写自定义控件20-自定义饼图

    前言 上次在写可视化数据大屏电子看板项目的时候,为了逐步移除对QChart的依赖(主要是因为QChart真的太垃圾了,是所有Qt的模块中源码最烂的一个,看过源码的人没有一个不吐槽,不仅不支持10W级别 ...

  5. Qt编写自定义控件9-导航按钮控件

    前言 导航按钮控件,主要用于各种漂亮精美的导航条,我们经常在web中看到导航条都非常精美,都是html+css+js实现的,还自带动画过度效果,Qt提供的qss其实也是无敌的,支持基本上所有的CSS2 ...

  6. Qt编写自定义控件7-自定义可拖动多边形

    前言 自定义可拖动多边形控件,原创作者是赵彦博(QQ:408815041 zyb920@hotmail.com),创作之初主要是为了能够在视频区域内用户自定义可拖动的多个区域,即可用来作为警戒区域,也 ...

  7. Qt编写自定义控件12-进度仪表盘

    前言 进度仪表盘主要应用场景是标识一个任务进度完成的状况等,可以自由的设置范围值和当前值,为了美观还提供了四种指示器(圆形指示器/指针指示器/圆角指针指示器/三角形指示器),各种颜色都可以设置,其中的 ...

  8. Qt编写自定义控件72-提示进度条

    一.前言 我们在很多的安装包中,在安装过程中,经常可以在底部看到一个漂亮的进度条,上面悬浮着显示对应的进度,然后底部进度多种颜色渐变展示,Qt自带的进度条或者操作系统的进度条样式,不够炫,这次索性直接 ...

  9. Qt编写自定义控件71-圆弧进度条

    一.前言 现在web形式的图表框架非常流行,国产代表就是echart,本人用过几次,三个字屌爆了来形容,非常强大,而且易用性也非常棒,还是开源免费的,使用起来不要太爽,内置的各种图表和仪表盘等非常丰富 ...

随机推荐

  1. 2018 ACM 国际大学生程序设计竞赛上海大都会赛重现赛 J Beautiful Numbers (数位DP)

    2018 ACM 国际大学生程序设计竞赛上海大都会赛重现赛 J Beautiful Numbers (数位DP) 链接:https://ac.nowcoder.com/acm/contest/163/ ...

  2. Ubuntu死机解决方法汇总

    为什么不建议强制关机 如果长按电源按键强制关机,有可能损坏硬件或者丢失数据,甚至导致磁盘坏道! 其实, 大部分时候的死机是假死, 不是真死... 有时候鼠标还能动呢. 还有一个原因: 对于平时忠贞不二 ...

  3. mysql总复习

    目录 数据库操作 库操作 表操作 数据行操作 表关系操作 单表操作 外键创建 多表联查 pymysql模块 索引 主键索引 唯一索引 普通索引 数据库操作 库操作 create database 库名 ...

  4. idea 插件的使用

    idea 插件的使用 进阶篇(个人收集使用中的) 恭喜你,如果你已经看到这篇文章,证明在idear使用上已经初有小成!那么就要向着大神进发了! 下边就是大神之路! 插件的设置 在 IntelliJ I ...

  5. java web项目中后台控制层对参数进行自定义验证 类 Pattern

    Pattern pattern = Pattern.compile("/^([1-9]\d+元*|[0]{0,1})$/");//将给定的正则表达式编译到模式中 if(!" ...

  6. CCPC 2016 杭州 E. Master of Subgraph(点分治+bitset优化DP)

    题目链接:http://acm.hdu.edu.cn/downloads/CCPC2018-Hangzhou-ProblemSet.pdf 题意:给定一棵有 n 个结点的树和一个数 m,对于 i ∈ ...

  7. [2019HDU多校第二场][HDU 6591][A. Another Chess Problem]

    题目链接:http://acm.hdu.edu.cn/showproblem.php?pid=6591 题目大意:二维坐标系上,所有满足\(5|2x+y\)的点都被设为障碍物,无法通过.现给出一对点, ...

  8. 爬取淘宝商品数据并保存在excel中

    1.re实现 import requests from requests.exceptions import RequestException import re,json import xlwt,x ...

  9. Matlab step方法

    在Matlab中我们经常能看到Java的影子,即面向对象编程(Object Oriented Programming,OOP). 以Turbo编码为例: hTEnc = comm.TurboEncod ...

  10. fastJson与jackson性能对比

    转载:https://blog.csdn.net/u013433821/article/details/82905222最近项目用到fastJson和jackson,为了决定到底弃用哪个,随手写了个测 ...