原文链接:Windows高DPI系列控件(二) - 柱状图

一、QCP

QCP全称QCustomPlot,是一个基于Qt的图表库,同时支持Qt4和Qt5,使用起来还是很方便的,不管是编译成dll还是直接嵌入到我们自己的程序都是极其容易,毕竟只有两个文件。之前写过几篇简单的关于QCP的文章,从使用的角度分析了QCP的一些简单用法,包括:QCustomplot使用分享(一) 能做什么事QCustomplot使用分享(二) 源码解读QCustomplot使用分享(三) 图QCustomplot使用分享(四) QCPAbstractItemQCustomplot使用分享(五) 布局QCustomplot使用分享(六) 坐标轴和网格线QCustomplot使用分享(七) 层(完结),感兴趣的同学可以抽时间阅读一遍。高DPI系列控件大多数也都是基于QCP库进行的定制,首先是对QCP的包装,其次其次也对QCP源码进行了高DPI适配,主要还是针对我自己的DPI框架进行适配。

此高DPI适配框架,在DPI为96整数倍下是非常完美的,无失真情况,其他非整数比缩放情况下会有一些小瑕疵,比如非整数缩放比下图片进行过拉伸,某些拉伸会导致失真的图片会发生模糊的情况;其次当缩放比不是0.5的整数倍时,字号放大有可能不是按比例的,会导致界面字体不协调,但是字体不会发虚。对于大多数用户来说,目前的高DPI框架都是可以满足的,比如我个人的显示就是一个1080P显示器和一个4K显示器,两个显示器的缩放比分别是100%和200%,没有任何失真的情况。

上一篇文章Windos高DPI系列控件(一) - 饼图中讲过怎么在高DPI显示器下绘制饼图,并且坐到没有任何的失真,毕竟都是自己绘制的。饼图控件是很早以前就完成的功能,这一篇文章主要是对他进行了高DPI的适配,让饼图控件在我的4K显示器上也可以很友好的展示,并且可以在两个显示器直接无缝切换。

本篇文章是继Windos高DPI系列控件(一) - 饼图文章后的第二篇关于高DPI控件的实例,包括后续会定制的一些列高DPI控件,大多数基于QCP来实现。文章前面也说过,QCP是一个非常强大的绘图库,比较遗憾的就是没有饼图这个控件,上一篇Windos高DPI系列控件(一) - 饼图控件是我参考QCP的代码自己实现的绘制,效率也是杠杠滴,除此之外QCP也是非常容易扩展的,比如可以自己添加新的层,去绘制一些自己需要的东西,多说一句QCP2.0版本区别于1.0版本又一个很大的优势就是多层绘制的,这样效率比较高,并且您也可以指定某个层进行单独刷新,本篇文章讲到的柱状图上的tooltips提示实现方式就是新增了一个tooltips绘制层,这样实现的好处就是跟现有框架的其他层代码是解耦的,比如后期您不要这个层了,可以直接删掉,或者隐藏都是可以的,这样对整个代码运行效率几乎没有任何损失。

二、效果展示

如下图所示,柱状图和折线图适配高DPI后展示效果。

左右两侧的显示物理尺寸一致,也就是视觉上大小一样大,不同的是左侧是1080P显示器,右侧是4K显示器

因为是视频录制原因,可能会有视觉误差,实际看的话,左右两个窗体给人的视觉感受大小是一样的。

《来回切换显示器》

《柱状图》

三、高DPI适配

1、自定义柱状图

QCP库中的柱状图支持多种模式,上一小节中的效果图是普通的柱状图展示效果,除此之外QCP中的柱状图可以做到多组同时展示,并且两组柱状图之间可以堆叠,由于适配需要花更多的实际,因此更多复杂的展示效果会在后续的文章中会陆续提供。

如《柱状图》展示图中的效果,我们定制了柱状图一个新的柱状图类,主要是基于该类我们支持了展示tooltips效果,下面主要介绍下比较重要的几个实现点

tips实现

文章开头也提到过,QCP2.0是多层绘制的,这样可以提高绘制效率,并且提升更强的扩展能力,如QCustomplot使用分享(七) 层(完结)这篇文章所分析的那样。本篇文章中的提示框就是继承可绘制对象QCPLayerable,界面上几乎所有的绘制元素包括布局元素都是继承自这个类,该类有一个draw函数,实现该接口就可以实现我们自己想要绘制的东西,界面绘制时,绘制区域取决于自己所在的布局区域。

#ifndef CHARTTIP_H
#define CHARTTIP_H #include "qcp/QCustomplot.h" class CBaseToolTip : public QCPLayerable
{
Q_OBJECT public:
CBaseToolTip(QCustomPlot * plot);
~CBaseToolTip(); public:
QString LayerName() const;
void SetVisible(bool visible);//ÉèÖòãÊÇ·ñ»æÖÆ void SetTipLabel(const QVector<QString> & labels);
void SetTopLeft(const QPoint & pos); protected:
virtual void applyDefaultAntialiasingHint(QCPPainter * painter) const override{};
virtual void draw(QCPPainter * painter) override; virtual void DrawTip(QCPPainter * painter) = 0; protected:
QPoint m_AnchorPos;
QVector<QString> m_Labels; private: }; class CSideToolTip : public CBaseToolTip
{
public:
CSideToolTip(QCustomPlot * plot);
~CSideToolTip(){} protected:
virtual void DrawTip(QCPPainter * painter) override;
}; class CTopToolTip : public CBaseToolTip
{
public:
CTopToolTip(QCustomPlot * plot);
~CTopToolTip(){} protected:
virtual void DrawTip(QCPPainter * painter) override; private:
int radius = 3;
int height = 46;
int width = 125;
}; #endif // CHARTTIP_H

上述头文件中,包括三个类,其中CBaseToolTip是tip提示的基类,他负责完成了tip对象的一些公有属性,比如构造该绘制对象时自动创建tip绘制层,并把层加到QCP窗口对象上;除此之外还可以指定绘制的位置等。

本篇文章中的tip是CSideToolTip类来实现的,下面是该类具体的绘制逻辑,仔细一看下面的diamante,发现这里又出现了我们熟悉的XXX_SCALE_NUMBER宏,不错这个宏就是用来实现高DPI逻辑的,比如下面绘制矩形框的逻辑,矩形的长和宽都被我们用QCP_PAINTER_SCLAE_NUMBER宏给包裹起来,这样绘制时绘制的矩形大小就是我们需要的大小。

#define QCP_PAINTER_SCLAE_NUMBER(a) (a) * painter->dpi_scale

这里简单说下这个宏的作用,QCPPainter源码本身是没有dpi_scale这个变量的,为了更好的适配高DPI显示器,这个变量是我自己加上的,表示当前绘制painter需要缩放的比例,比如我们绘制tip矩形区域时,就需要对长和宽进行必要的缩放。

除过QCPPainter我添加了dpi_scale缩放系数成员变量以外,QCPLayerable基类我也添加了该成员变量,并且在合适的实际都会进行变量更新,后续我会专门写一篇关于QCP适配高DPI的文章,专门讲解针对高DPI适配我都做了哪些工作。

void CSideToolTip::DrawTip(QCPPainter * painter)
{
//绘制圆圈
painter->setPen(Qt::transparent);
// painter->setBrush(QColor(255, 204, 51, 80));
// painter->drawEllipse(m_AnchorPos, 5, 5);
painter->setBrush(QColor(255, 181, 26));
painter->drawEllipse(m_AnchorPos, int(QCP_PAINTER_SCLAE_NUMBER(3)), int(QCP_PAINTER_SCLAE_NUMBER(3))); //绘制矩形 135 * 65
QRect rect(0, 0, QCP_PAINTER_SCLAE_NUMBER(100), QCP_PAINTER_SCLAE_NUMBER(30));
rect.moveBottomRight(m_AnchorPos - QPoint(QCP_PAINTER_SCLAE_NUMBER(8), 0)); if (rect.left() < mParentPlot->axisRect()->outerRect().left())
{
rect.moveBottomLeft(m_AnchorPos + QPoint(QCP_PAINTER_SCLAE_NUMBER(8), 0));
} painter->setPen(QColor(255, 204, 51));
painter->setBrush(QColor(0, 0, 0, 255 * 0.7));
painter->drawRect(rect); //绘制矩形框文字
QFont font(QStringLiteral("微软雅黑"));
font.setPixelSize(QCP_PAINTER_SCLAE_NUMBER(10));
painter->setFont(font); painter->drawText(QPoint(QCP_PAINTER_SCLAE_NUMBER(8), QCP_PAINTER_SCLAE_NUMBER(13))
+ rect.topLeft(), QStringLiteral("两融余额:%1").arg(m_Labels[0]));
painter->drawText(QPoint(QCP_PAINTER_SCLAE_NUMBER(8), QCP_PAINTER_SCLAE_NUMBER(25))
+ rect.topLeft(), QStringLiteral("上证指数:%1").arg(m_Labels[1]));
}

2、新的柱状图

新的柱状图被我命名为CTooltipBars,该类没有什么比较牛逼的实现,代码量也不大,其中有一个比较重要的接口OnCheckHover和一个关键信号HoverIndex;

#ifndef TIPBAR_H
#define TIPBAR_H #include "../rluilib/dpi_macro.h" #include "qcp/QCustomplot.h" class QMouseEvent;
class CTooltipBars : public QCPBars
{
Q_OBJECT signals :
void HoverIndex(double index); public:
CTooltipBars(float scale, QCPAxis * keyAxis, QCPAxis * valueAxis);
~CTooltipBars(); public:
void SetValueVisible(bool visible); public slots:
void OnCheckHover(QMouseEvent * pos); protected:
virtual void draw(QCPPainter * painter) override; private:
bool m_bValueVisible = false;
int m_iLabelHeight = 0;
}; #endif // TIPBAR_H

OnCheckHover:hover行为检测接口,当我们移动鼠标时,该接口会重复被触发去检测是否hover到了某个柱子上,如果hover成功那么就会触发HoverIndex信号,参数表示hover的柱子序号。

检测鼠标hover事件的代码如下图所示,整个代码的核心思想就是获取所有可见的柱子数据,然后循环去判断鼠标当前的坐标是否在哪个柱子的区域内,并触发HoverIndex信号,参数为-1时表示没有hover到任何柱子,那么此时可能需要隐藏已经展示的tip提示。

void CTooltipBars::OnCheckHover(QMouseEvent * event)
{
QCPBarsDataContainer::const_iterator visibleBegin, visibleEnd;
getVisibleDataBounds(visibleBegin, visibleEnd); QList<QCPDataRange> selectedSegments, unselectedSegments, allSegments;
getDataSegments(selectedSegments, unselectedSegments);
allSegments << unselectedSegments << selectedSegments; double success = -1; for (int i = 0; i < allSegments.size(); ++i)
{
bool isSelectedSegment = i >= unselectedSegments.size();
QCPBarsDataContainer::const_iterator begin = visibleBegin;
QCPBarsDataContainer::const_iterator end = visibleEnd;
mDataContainer->limitIteratorsToDataRange(begin, end, allSegments.at(i));
if (begin == end)
{
continue;
} for (QCPBarsDataContainer::const_iterator it = begin; it != end; ++it)
{
QRectF rect = getBarRect(it->key, it->value);
rect = rect.adjusted(0, -m_iLabelHeight, 0, 0);
bool contain = rect.contains(event->pos());
if (contain)
{
success = it->key;
break;
}
}
if (success != -1)
{
break;
}
} if (mParentPlot->axisRect()->rect().contains(event->pos()) == false)
{
success = -1;
} emit HoverIndex(success);
}

HoverIndex:柱状图hover时触发信号,表示鼠标在参数指定的柱子内

3、测试代码

如下代码是效果图中的测试代码,我们构造了一个CBarChart对象,然后添加了一些简单的额测试数据,用起来是不是很爽呢!

QWidget * CreateBar(float scale)
{
CBarChart * barChart = new CBarChart(scale);
BarDataList datas;
datas.push_back({ 1585816691, 200, });
datas.push_back({ 1588408691, 150, });
datas.push_back({ 1591087091, 220, });
datas.push_back({ 1593679092, 100, });
barChart->SetDatas(datas);
barChart->SetYRange(QCPRange(0, 250), 5);
return barChart; QStringLiteral("柱状图"));
}

CBarChart类是一个对外的柱状图使用类,主要是把支持tip的柱状图、tip类和legend类进行了组装,这里就不在详细讲解了,该类的成员变量如下代码所示,其中还有一些辅助性的成员,比如说折线图QCPGraph类,QCP唯一窗口类QCustomPlot,该类也是我们绘制图表时不可或缺的对象,所以的绘制调用逻辑都是从该窗口的paintEvent函数开始出发的,并且该类做了各种绘制优化操作,并且支持3种绘制方式,感兴趣的同学可以搜索下QCPAbstractPaintBuffer这个类,这个是绘制buffer基类,其他绘制的实现都是基于该类实现。

struct BarChartPrivate
{
QVector<double> m_TickKey;
QVector<QString> m_TickNames;
QLabel * m_pBarLabel = nullptr;
QLabel * m_pGraphLabel = nullptr;
CLegendWidget * m_pLegend = nullptr;
CBaseToolTip * m_pToolTip = nullptr;
QCPGraph * m_pGraph = nullptr;
QList<CTooltipBars *> m_pBars;
QSharedPointer<QCPAxisTickerText> m_pXAxisTicker;
QCustomPlot * m_pWidget = nullptr;
QCPMarginGroup * m_pMarginGroup = nullptr;
};

四、相关文章

  1. Qt之高DPI显示器(一) - 解决方案整理
  2. Qt之高DPI显示器(二) - 自适配解决方案分析
  3. Qt之自绘制饼图
  4. QCustomPlot之布局系统
  5. QCustomplot使用分享(一) 能做什么事
  6. QCustomplot使用分享(二) 源码解读
  7. QCustomplot使用分享(三) 图
  8. QCustomplot使用分享(四) QCPAbstractItem
  9. QCustomplot使用分享(五) 布局
  10. QCustomplot使用分享(六) 坐标轴和网格线
  11. QCustomplot使用分享(七) 层(完结)
  12. Windos高DPI系列控件(一) - 饼图

值得一看的优秀文章:

  1. 财联社-产品展示
  2. 广联达-产品展示
  3. Qt定制控件列表
  4. 牛逼哄哄的Qt库
如果您觉得文章不错,不妨给个打赏,写作不易,感谢各位的支持。您的支持是我最大的动力,谢谢!!!

很重要--转载声明

  1. 本站文章无特别说明,皆为原创,版权所有,转载时请用链接的方式,给出原文出处。同时写上原作者:朝十晚八 or Twowords

  2. 如要转载,请原文转载,如在转载时修改本文,请事先告知,谢绝在转载时通过修改本文达到有利于转载者的目的。


Windows高DPI系列控件(二) - 柱状图的更多相关文章

  1. Windows高DPI系列控件(一) - 饼图

    目录 一.醉一醉 二.效果展示 三.高DPI适配 1.高DPI框架运作 2.适配高DPI 3.适配饼图 四.相关文章 原文链接:Windos高DPI系列控件(一) - 饼图 一.醉一醉 眨眼功夫,20 ...

  2. Windows UWP开发系列 – 控件默认样式

    今天用一个Pivot控件的时候,想修改一下它的Header样式,却发现用Blend和VS无法导出它的默认样式了,导致无法下手,不知道是不是Blend的bug. 在网上搜了一下,在MSDN上还是找到了它 ...

  3. 【Windows编程】系列第二篇:Windows SDK创建基本控件

    在Win32 SDK环境下,怎么来创建常用的那些基本控件呢?我们知道如果用MFC,简单的拖放即可完成大多数控件的创建,但是我们既然是用Windows SDK API编程,当然是从根上解决这个问题,实际 ...

  4. 【WPF】DPI对控件定位产生的影响

    原文:[WPF]DPI对控件定位产生的影响 需求 程序界面上是一个Window,当用户点击桌面上除此Window之外的任何地方,都要把这个window隐藏掉.程序有个托盘图标,点击托盘图标不能隐藏wi ...

  5. Dev系列控件的AJAX (转)

    介绍Dev系列控件在前台也就是客户端的一些常用方法介绍以及前后台异步通信的方法. 一.Dev Data Edit控件通用属性以及方法: 属性 1.GetEnabled():返回控件是否为可操作状态 2 ...

  6. asp.net2.0安全性(4)--Login系列控件--转载来自车老师

    前面主要说了与安全相关的一系列的类,现在我们使用这些类就可以做出我们自己的安全系统了.其实微软的目的远不至于此,下面我们就来看一下微软为我们提供的Login系列控件. Login系列控件是微软为了简化 ...

  7. QRowTable表格控件(二)-红涨绿跌

    目录 一.开心一刻 二.概述 三.效果展示 四.任务需求 五.指定列排序 六.排序 七.列对其方式 八.相关文章 原文链接:QRowTable表格控件(二)-红涨绿跌 一.开心一刻 一天,五娃和六娃去 ...

  8. Dev系列控件的AJAX使用Demo

    一.Dev Data Edit控件通用属性以及方法: 属性 1.GetEnabled():返回控件是否为可操作状态 2.GetText():返回控件的Text的值 3.SetEnabled():设置控 ...

  9. windows下注册ocx控件

    OCX 是对象类别扩充组件(Object Linking and Embedding (OLE) Control Extension):是可执行的文件的一种,但不可直接被执行: 是 ocx 控件的扩展 ...

随机推荐

  1. Java Review (一、Java开发环境)

    @ 目录 Java程序运行机制 高级语言运行机制 编译型语言 解释型语言 Java运行机制和JVM 编写 编译 运行 Java开发工具包 JDK JRE JDK.JRE与JVM HelloWord 编 ...

  2. opencl(7) 内核执行命令入队]工作组、工作项

    1:将内核执行命令入队(该命令可被多个工作项执行) cl_int clEnqueueNDRangeKernel( cl_command_queue command_queue, cl_kernel k ...

  3. python自学Day07(自学书籍python编程从入门到实践)

    第8章 函数 函数是带名字的代码块,用于完成具体的工作. 学习定义函数,向函数传递信息. 学习如何编写主要任务是显示信息的函数,还有用于处理数据并返回一个或一组值得函数. 学习如何将函数存储在被称为模 ...

  4. Laravel中Homestead添加多站点时遇到问题

    Homestead做Laravel的开发还是很方便的,但是在添加多站点的时候,发现几个坑,来做个记录. 首先,官方文档给了修改homestead.yaml文件的方式,只需要在sites字节下添加一个新 ...

  5. mail邮件操作

    目录 1. 概念 1.1. 常见的类型 1.2. 相关协议 1.3. SMTP协议 2. python::smtplib 1. 概念 1.1. 常见的类型 Mail User Agent 收发邮件用的 ...

  6. c++ vector基本函数、排序、查找用法

    vector用法目录: 1.基本用法 2.vector的删除操作 3.vector的sort排序 4.翻转vector中的所有元素 5.find()函数的用法 6.vector实战(这里写的是我在最开 ...

  7. 这一次搞懂Spring事务注解的解析

    前言 事务我们都知道是什么,而Spring事务就是在数据库之上利用AOP提供声明式事务和编程式事务帮助我们简化开发,解耦业务逻辑和系统逻辑.但是Spring事务原理是怎样?事务在方法间是如何传播的?为 ...

  8. Python中用OpenPyXL处理Excel表格 - 单元格格式设置

    官方文档: http://openpyxl.readthedocs.io/en/default/ OpenPyXL库 --单元格样式设置 单元格样式的控制,依赖openpyxl.style包,其中定义 ...

  9. GridView绑定数据与隐藏指定控件(模板列)

    1.1.    GridView绑定数据 1)       可以配置SqlDataSource数据源,修改select语句生成框架(不想手动绑定) 2)       删除DataSourceID属性和 ...

  10. swiper 实现滑动解锁

    最近项目中有这样一个需求,研究了两种写法一个原生,一个使用框架 原生写法: <!DOCTYPE html> <html> <head> <meta chars ...