Qt5绘制仪表盘dashboard

说明

  • 本文演示Qt版本: Qt5.14.
  • 本文将使用QPainter一步一步绘制仪表盘:刻度、指针、刻度值
  • 注意: 绘制顺序,如果先绘制,则后来绘制的将会覆盖住先前绘制的。
  • 如果需要绘制半透明, 请设置QColor的第四个参数 alpha , 范围: 0~255, 0 - 全透明, 255-不透明

样式

  • 比较粗糙, 后面在优化
  • 这是工作上遇到的需要绘制的图形。
  • 蓝色的是指针indicator

下面一步一步开始绘制

开始前之前

  • 重写函数paintEvent( QPaintEvent *event )QtGuiApplication1继承自QWidget.
  • 设置反走样(抗锯齿)
	QPainter painter( this );
	painter.setRenderHint( QPainter::Antialiasing, true);

绘制圆

代码

void QtGuiApplication1::draw_ellipse_( QPainter& painter )
{
	painter.save();
	/// -----------------------------------------------------------------

	/// 设置画笔颜色
	painter.setPen(QPen(QColor(100, 200, 100), 2));

	/// 直接绘制圆
	painter.drawEllipse( circle_config_.start_x_, circle_config_.start_y_,
						 circle_config_.radius_ * 2, circle_config_.radius_ * 2 );
	/// -----------------------------------------------------------------
	painter.restore();
}

效果

绘制刻度

每10度绘制一个刻度线,每30度绘制一个较长的刻度线

代码

void QtGuiApplication1::draw_dial_( QPainter& painter )
{
	painter.save();

	painter.setPen(QPen(QColor(200, 100, 200), 2));

	/// 旋转坐标
	/// 第一个参数: 坐标起点X
	/// 第二个参数: 坐标起点Y
	/// 第三个参数: 相对当前坐标系 向目标坐标系需要旋转多少度
	/// 第四个参数: 是否绘制较长的刻度
	auto draw_dial = [&]( const double& xxx, const double& yyy, const double& angle, const bool& is_longer )
	{
		int dial_len = 8;

		if (true == is_longer)
			dial_len = 30;

		/// 重置坐标系
		painter.resetTransform();
		/// 将其移动到目标点
		painter.translate(xxx, yyy );
		/// 再旋转指定角度
		painter.rotate( angle );
		/// 绘制刻度
		painter.drawLine( QLine(QPoint(0, 0), QPoint(-dial_len, 0)) );

	};

	
	/// 圆。 360度, 每10度绘制一个刻度线
	for (int angle = 0; angle <= 360; angle += 10)
	{
		double xxx = 0;
		double yyy = 0;
		/// 根据圆心得到园周边的点坐标
		get_dial_config_(circle_config_.center_x_, circle_config_.center_y_, circle_config_.radius_, xxx, yyy, angle);

		/// 每30°绘制一个长一点的刻度
		bool is_longer = ( 0 == ( angle % 30 ) ? true : false );
		/// 绘制刻度
		draw_dial( xxx, yyy, angle, is_longer);
	}

	painter.restore();

}

效果

绘制指针

  • 这里是用扇形标识指针
  • 注意: X轴正方向是3点钟反向

代码

/// --------------------------------------------------------------------------------------------------------
///	@brief: 	   绘制指针
/// --------------------------------------------------------------------------------------------------------
void QtGuiApplication1::draw_indicator_( QPainter& painter )
{
	painter.save();
	/// -----------------------------------------------------------------

	painter.setPen(QPen(QColor(0, 0, 239)));			 
	QBrush brush( QBrush( QColor( 0, 0, 239, 100) ) );
	painter.setBrush(brush);

	/// 在矩形内切椭圆上画,3点钟为0度,逆时针为正,角度要乘以16(参考文档)
	/// 起点, 
	int startAngle	= -3 * 16;
	/// 圆弧的角度大小
	int spanAngle	= 6 * 16;	
	painter.drawPie(circle_config_.start_x_, circle_config_.start_y_, circle_config_.radius_ * 2, circle_config_.radius_ * 2, 
					 startAngle, spanAngle);

	/// -----------------------------------------------------------------
	painter.restore();
}

效果

绘制刻度数字

  • 刻度数字其实也是绘制在圆周边上的,比如,上图的中的半径为R,而刻度文字所在圆的半径则是 0.8倍R,这样就可以将文字绘制圈圈内了。

代码

void QtGuiApplication1::draw_scale_number_( QPainter& painter )
{
	painter.save();
	/// -----------------------------------------------------------------


	double rrr		= circle_config_.radius_ * 0.8 ;
	painter.setFont( QFont( "Microsoft Yahei", 10 ) );

	QFontMetrics fm( painter.font() );


	/// for (int index = -150; index <= 150; index += 30)
	for (int index = 0; index < 360; index += 30)
	{
		double xxx = 0;
		double yyy = 0;
		/// 根据圆心得到园周边的点坐标
		get_dial_config_( circle_config_.center_x_, circle_config_.center_y_, rrr, xxx, yyy, index );

		int dial_num = index - 270;

		/// 3点为X轴, 故这里要特殊显示为90 ~ 150
		if (90 > index)
			dial_num = index + 90;

		/// 180不显示
		if (-180 == dial_num)
			continue;

		/// 下面这一步是为了得到绘制文字的宽和高, 从而决定文字的起点坐标
		/// -----------------------------------------------------------------
		/// 字体的高度
		int fontw			= fm.width(QString::number( dial_num));
		/// 字体的宽度
		int fonth			= fm.height();
		
		xxx -= fontw / 2.0;
		yyy += fonth / 4.0;
		/// -----------------------------------------------------------------

		painter.drawText( xxx, yyy,  QString::number( dial_num));
	}

	painter.restore();
}

效果

下面我们让指针动起来

开始之前

  • UI增加控件 QSlider,设置其范围是0 ~360
  • 关联滑块的信号,当值发生变化,则触发重绘
void QtGuiApplication1::slot_slider_value_changed_( int value )
{
	/// 指针当前角度
	circle_config_.cur_angle_ = ( double ) value;
	QWidget::update();
}

绘制指针

这里只需要将 起始角度 -3 改为 当前滑块的值即可。 完整代码如下

代码

void QtGuiApplication1::draw_indicator_( QPainter& painter )
{
	painter.save();
	/// -----------------------------------------------------------------

	painter.setPen(QPen(QColor(0, 0, 239)));			 
	QBrush brush( QBrush( QColor( 0, 0, 239, 100) ) );
	painter.setBrush(brush);

	/// 在矩形内切椭圆上画,3点钟为0度,逆时针为正,角度要乘以16(参考文档)
	/// 起点, 
	int startAngle	= (circle_config_.cur_angle_ - 3.0) * 16;
	/// 圆弧的角度大小
	int spanAngle	= 6 * 16;	
	painter.drawPie(circle_config_.start_x_, circle_config_.start_y_, circle_config_.radius_ * 2, circle_config_.radius_ * 2, 
					 startAngle, spanAngle);

	/// -----------------------------------------------------------------
	painter.restore();
}

效果

补充

  • 实战中,发现使用 扇形绘制的指针,指针填充渐变色的时候 不太理想, 因为按照公式计算的半径得到的坐标总是不对。 于是 换了中绘制指针的方法: 画两根线, 两根线中间用画刷刷成指定的渐变色。
  • 为了见刻度显示在指针的上面, 因此,先与刻度绘制指针。

效果图

代码

这里是先绘制的指针,再绘制的周围的刻度



	draw_indicator2_(painter);
	/// 绘制指针
	//draw_indicator_(painter);

	/// 绘制圆
	draw_ellipse_(painter);
	/// 绘制刻度
	draw_dial_(painter);


	/// 绘制刻度值
	draw_scale_number_(painter);

绘制指针

/// --------------------------------------------------------------------------------------------------------
///	@brief: 绘制指针
/// --------------------------------------------------------------------------------------------------------
void QtGuiApplication1::draw_indicator2_(QPainter& painter)
{
	painter.save();
	/// -----------------------------------------------------------------

	/// 计算指针两条线的坐标, 方法: 以圆心为起点,旋转指定角度绘制两条只想, 夹角暂定为6度
	/// 起始角度为当前角度 - 3(3 = 6 / 2.0)
	auto draw_line = [&](const double& startx, const double& starty, const double& radius, const double& angle)
	{
		painter.resetTransform();
		painter.translate(startx, starty);
		painter.rotate(angle);

		painter.drawLine(0, 0, radius, 0);
	};
	
	/// 因为是逆时针绘制的夹角,
	draw_line(circle_config_.center_x_, circle_config_.center_y_, circle_config_.radius_, circle_config_.cur_angle_ - 3);
	draw_line(circle_config_.center_x_, circle_config_.center_y_, circle_config_.radius_, circle_config_.cur_angle_ + 3);

	/// -----------------------------------------------------------------
	painter.restore();
}

加填充色填充

  • 这里以填充渐变色, 还能学学 渐变色 QLinearGradient类的用法
  • 老规矩, 上效果:

代码

/// --------------------------------------------------------------------------------------------------------
///	@brief: 绘制指针
/// --------------------------------------------------------------------------------------------------------
void QtGuiApplication1::draw_indicator2_(QPainter& painter)
{
	painter.save();
	/// -----------------------------------------------------------------

	/// 计算指针两条线的坐标, 方法: 以圆心为起点,旋转指定角度绘制两条只想, 夹角暂定为6度
	/// 起始角度为当前角度 - 3(3 = 6 / 2.0)
	auto draw_line = [&](const double& startx, const double& starty, const double& radius, const double& angle)
	{
		painter.save();
		
		painter.resetTransform();
		painter.translate(startx, starty);
		painter.rotate(angle);

		painter.drawLine(0, 0, radius, 0);
		
		painter.restore();
	};
	
	/// 因为是逆时针绘制的夹角,
	draw_line(circle_config_.center_x_, circle_config_.center_y_, circle_config_.radius_, circle_config_.cur_angle_ - 3);
	draw_line(circle_config_.center_x_, circle_config_.center_y_, circle_config_.radius_, circle_config_.cur_angle_ + 3);


	/// 计算渐变色的坐标
	/// 渐变色的坐标起点是圆心 终点 就是 指针与圆周边的交点
	/// 这里的指针是用一个三角形代替指针的,当周围的刻度绘制在指针的上面,哈哈遮住了 圆弧那段没有填充色的部分

	/// 指针的两条直线之一A与圆周边的焦点A
	double lg_a_end_x = 0;
	double lg_a_end_y = 0;
	get_dial_config_(circle_config_.center_x_, circle_config_.center_y_, circle_config_.radius_, lg_a_end_x, lg_a_end_y, circle_config_.cur_angle_ - 3);
	
	/// 指针的两条直线之一B与圆周边的焦点A
	double lg_b_end_x = 0;
	double lg_b_end_y = 0;
	get_dial_config_(circle_config_.center_x_, circle_config_.center_y_, circle_config_.radius_, lg_b_end_x, lg_b_end_y, circle_config_.cur_angle_ + 3);

	/// 三角区域
	const QPointF triganle_points[3] = 
	{
		QPointF(circle_config_.center_x_, circle_config_.center_y_), 
		QPointF(lg_a_end_x, lg_a_end_y),
		QPointF(lg_b_end_x, lg_b_end_y)
	};

	/// 当前角度与圆的交点坐标,用于确定填充区域
	double lg_end_x = 0;
	double lg_end_y = 0;
	get_dial_config_(circle_config_.center_x_, circle_config_.center_y_, circle_config_.radius_, lg_end_x, lg_end_y, circle_config_.cur_angle_);


	QLinearGradient lg(circle_config_.center_x_, circle_config_.center_y_, lg_end_x, lg_end_y);

	/// 0 ~ 0.3的区域填充 红色
	lg.setColorAt(0.3, QColor(230, 0, 0));
	/// 0.3 ~ 0.6的区域填充绿色
	lg.setColorAt(0.6, QColor(0, 230, 0));
	/// 0.6~1之间的区域填充蓝色
	lg.setColorAt(1.0, QColor(0, 0, 230));

	/// 设置划线的颜色
	painter.setPen(QPen(QColor(30, 30, 30), 2));

	/// 设置画刷颜色
	painter.setBrush(QBrush(lg));

	painter.drawPolygon(triganle_points, 3, Qt::OddEvenFill);

	/// -----------------------------------------------------------------
	painter.restore();
}

gif效果图

posted @ 2021-05-19 20:04  mohist  阅读(2028)  评论(0编辑  收藏  举报