20

Windows高DPI系列控件(二) - 柱状图

 4 years ago
source link: http://www.cnblogs.com/swarmbees/p/13246989.html
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.
neoserver,ios ssh client

目录

原文链接: 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显示器

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

o_200703075747highDPI_bars2.gif

《来回切换显示器》

o_200703075740highDPI_bars.gif

《柱状图》

三、高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库

如果您觉得文章不错,不妨给个 打赏 ,写作不易,感谢各位的支持。您的支持是我最大的动力,谢谢!!!

o_weixin_reward.pngo_zhifubao_reward.png

很重要--转载声明

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

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


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK