c++ 实现绘图功能

运行结果


#include "plotter.h"
#include <qtoolbutton.h>
#include "PlotSettings.h"
#include <QIcon>
#include <qstylepainter.h>
#include <qstyleoption.h>
#include <qstyle.h>
#include <qevent.h>
#include <algorithm>
#include <qpolygon.h>

const QColor plotter::s_colorForIds[COLOR_FOR_IDS_SIZE] = {
	Qt::red,Qt::green,Qt::blue,Qt::cyan,Qt::magenta,Qt::yellow
};

plotter::plotter(QWidget *parent)
	: QWidget(parent){

	//告诉调色板用暗分量作为重绘窗口部件的角色
	setBackgroundRole(QPalette::Dark);

	//子窗口部件从父窗口继承相应的背景色
	setAutoFillBackground(true);

	//大小的策略
	setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding);

	//通过单击或者tab输入焦点
	setFocusPolicy(Qt::StrongFocus);

	//设置橡皮筋窗口不可见
	m_rubberBandIsShown = false;

	//放大按钮
	m_zoomInButton = new QToolButton(this);
	m_zoomInButton->setIcon(QIcon("src/in.png"));
	m_zoomInButton->adjustSize();
	connect(m_zoomInButton, SIGNAL(clicked()), this, SLOT(zoomIn()));

	//缩小按钮
	m_zoomOutButton = new QToolButton(this);
	m_zoomOutButton->setIcon(QIcon("src/out.png"));
	m_zoomOutButton->adjustSize();
	connect(m_zoomOutButton, SIGNAL(clicked()), this, SLOT(zoomOut()));

	setPlotSettings(PlotSettings());//默认设置
}

void plotter::setPlotSettings(const PlotSettings& settings){
	
	//使得堆栈只存放一项
	m_zoomStack.clear();
	m_zoomStack.push_back(settings);

	//当前的plotsetting 
	m_curZoom = 0u;
	
	//设置按钮隐藏
	m_zoomInButton->hide();
	m_zoomOutButton->hide();

	//刷新映射
	refreshPixmap();
}


void plotter::zoomOut(){

	//在头部
	if (m_curZoom <= 0) return;

	//缩小槽的设置
	--m_curZoom;
	m_zoomOutButton->setEnabled(m_curZoom > 0);
	m_zoomInButton->setEnabled(true);
	m_zoomInButton->show();

	//刷新映射
	refreshPixmap();
}

void plotter::zoomIn(){

	//如果在末端就不继续
	if (m_curZoom >= static_cast<int>(m_zoomStack.size()) - 1) return;
	
	//放大槽的设置
	++m_curZoom;
	m_zoomInButton->setEnabled(m_curZoom < static_cast<int>(m_zoomStack.size()) - 1);
	m_zoomOutButton->setEnabled(true);
	m_zoomOutButton->show();

	//刷新映射
	refreshPixmap();
}

void plotter::setCurveData(const int key, const QPointFVec& data){

	//添加或替换新的曲线
	m_curvesMap[key] = data;

	refreshPixmap();
}

void plotter::clearCurve(const int key){

	//删除曲线
	QPointFVecMap::iterator it = m_curvesMap.find(key);
	
	//如果没有找到
	if (it == m_curvesMap.end()) return;
	
	m_curvesMap.erase(it);

	refreshPixmap();
}

void plotter::paintEvent(QPaintEvent* /** event */){
	//画笔 
	QStylePainter painter(this);

	//绘制pixmap
	painter.drawPixmap(0, 0, m_pixmap);

	if (m_rubberBandIsShown){
		//获得画板中颜色,以形成反差
	//	painter.setPen(palette().light().color());
		painter.setPen(palette().dark().color());

		//大小减去一 以允许有一个一像素的轮廓
		painter.drawRect(m_rubberBandRect.normalized().adjusted(0,0,-1,-1));
	}

	//如果获得了焦点
	if (hasFocus()){
		//焦点选择框
		QStyleOptionFocusRect option;
		option.initFrom(this);

		//背景的颜色
		option.backgroundColor = palette().dark().color();

		//绘制原来的图
		painter.drawPrimitive(QStyle::PE_FrameFocusRect, option);
	}
}

void plotter::resizeEvent(QResizeEvent* /* event*/){
	//这个函数会在窗口部件改变大小 以及创建的时候调用
	int x = width() - (
		m_zoomInButton->width() + m_zoomOutButton->width() + BUTTON_BLANK * 2
		);

	m_zoomInButton->move(x, BUTTON_BLANK);
	m_zoomOutButton->move(x + BUTTON_BLANK + m_zoomInButton->width(), BUTTON_BLANK);

	refreshPixmap();
}

void plotter::mousePressEvent(QMouseEvent* event){
	if (event->button() != Qt::LeftButton) return;

	//如果按下了左键
	//获得有效区域
	QRect rect(MARGIN, MARGIN, width() - 2 * MARGIN, height() - 2 * MARGIN);

	//如果落在有效区域
	if (rect.contains(event->pos())){
		//橡皮筋区可以显示了
		m_rubberBandIsShown = true;

		//设置左上角坐标
		m_rubberBandRect.setTopLeft(event->pos());
		//设置右下角坐标
		m_rubberBandRect.setBottomRight(event->pos());

		//更新橡皮筋区域
		updateRubberBandRegion();

		//设置当前的鼠标形状
		setCursor(Qt::CrossCursor);
	}
}

void plotter::mouseMoveEvent(QMouseEvent* event){

	//如果并没有使用橡皮筋就直接返回
	if (!m_rubberBandIsShown) return;

	//更新橡皮筋
	updateRubberBandRegion();

	//设置右下角的坐标
	m_rubberBandRect.setBottomRight(event->pos());

	//更新橡皮筋
	updateRubberBandRegion();
}

void plotter::mouseReleaseEvent(QMouseEvent* event){

	//如果按下的是左键 并且使用了橡皮筋功能
	if ((event->button() == Qt::LeftButton) &&
		m_rubberBandIsShown){

		//现在可以取消了
		m_rubberBandIsShown = false;

		//更新
		updateRubberBandRegion();

		//恢复光标
		unsetCursor();

		//获得一般化的矩形
		QRect rect = m_rubberBandRect.normalized();

		//可能是用户误点了,或者只是为了让窗口聚焦
		if ((rect.width() < MIN_WIDTH_HEIGHT) || (rect.height() < MIN_WIDTH_HEIGHT))
			return;

		//因为其坐标是相对于整个坐标的,因此要减去
		rect.translate(-MARGIN, -MARGIN);

		//之前的坐标设置
		PlotSettings prevSetting = m_zoomStack[m_curZoom];

		//最新的坐标设置
		PlotSettings setting;

		//重置坐标
		double x = prevSetting.spanX() / (width() - 2 * MARGIN);
		double y = prevSetting.spanY() / (height() - 2 * MARGIN);

		setting.setMinX(prevSetting.minX() + x * rect.left());
		setting.setMaxX(prevSetting.minX() + x * rect.right());

		setting.setMinY(prevSetting.maxY() - y * rect.bottom());
		setting.setMaxY(prevSetting.maxY() - y * rect.top());

		//调整
		setting.adjust();

		//压入栈
		m_zoomStack.push_back(setting);

		//放大
		zoomIn();
	}
}

void plotter::keyPressEvent(QKeyEvent* event){

	//键盘事件
	switch (event->key()){
	case Qt::Key_Plus: // + 为放大
		zoomIn();
		break;
	case Qt::Key_Minus: //- 为缩小
		zoomOut();
		break;
	case Qt::Key_Left: // 上下左右为旋转
		m_zoomStack[m_curZoom].scroll(-1, 0);
		refreshPixmap();
		break;
	case Qt::Key_Right:
		m_zoomStack[m_curZoom].scroll(1, 0);
		refreshPixmap();
		break;
	case Qt::Key_Down:
		m_zoomStack[m_curZoom].scroll(0, -1);
		refreshPixmap();
		break;
	case Qt::Key_Up:
		m_zoomStack[m_curZoom].scroll(0, 1);
		refreshPixmap();
		break;
	default: // 其他时间仍然按原来的进行处理
		QWidget::keyPressEvent(event);
		break;
	}
}


void plotter::wheelEvent(QWheelEvent* event){

	//qt 以角度的八倍
	int degrees = event->delta() / MOUSE_MAGNIFICATION;
	//15度为一个步长
	int ticks = degrees / MOUSE_STEP;

	//如果是垂直滚轮
	if (event->orientation() == Qt::Horizontal)
		m_zoomStack[m_curZoom].scroll(ticks, 0);
	else m_zoomStack[m_curZoom].scroll(0, ticks);

	//刷新
	refreshPixmap();
}

void plotter::updateRubberBandRegion(){
	//获得橡皮筋区域
	QRect rect	  = m_rubberBandRect.normalized();

	//得到橡皮筋的宽高
	int width	  = rect.width();
	int heigth	  = rect.height();

	//获得橡皮筋的相关坐标
	int leftTopX  = rect.left();
	int rightTopX = rect.right();

	int topY	  = rect.top();
	int bottomY   = rect.bottom();

	//更新橡皮筋边框
	update(leftTopX, topY, width, 1);
	update(leftTopX, topY, 1, heigth);
	update(leftTopX, bottomY, width, 1);
	update(rightTopX, topY, 1, heigth);
}

void plotter::refreshPixmap(){
	//调整大小
	m_pixmap = QPixmap(size());

	//如果画笔是非实心的,就要调整偏移量
	m_pixmap.fill(this, 0, 0);

	//画笔
	QPainter painter(&m_pixmap);

	//设置画笔使用的画笔背景色和字体
	painter.initFrom(this);

	//开始绘制
	drawGrid(&painter);
	drawCurves(&painter);

	//预约一个绘制事件
	update();
}

void plotter::drawGrid(QPainter* painter){
	
	//获得绘图区
	QRect rect(MARGIN, MARGIN, width() - 2 * MARGIN, height() - 2 * MARGIN);

	//如果窗口不够大 不能容下图形
	if (!rect.isValid()) return;

	//获得当前的缩放大小
	PlotSettings settings = m_zoomStack[m_curZoom];

	//获得笔
	QPen quiteDarkPen	  = palette().dark().color().light();
	QPen lightPen		  = palette().light().color();

	//标记x y 轴刻度
	double label		  = 0;
	int x				  = 0;
	int y				  = 0;

	for (int i = 0; i <= settings.numXTicks(); ++i){

		//获得 x 坐标
		x = rect.left() + (i * (rect.width() - 1) / settings.numXTicks());

		//设置x轴上的标签
		label = settings.minX() + (i * settings.spanX() / settings.numXTicks());

		//设置画竖线的笔
		painter->setPen(quiteDarkPen);

		//竖线
		painter->drawLine(x, rect.top(), x, rect.bottom());

		//设置画x轴上面的小短线的笔
		painter->setPen(lightPen);

		//画小短线
		painter->drawLine(x, rect.bottom(), x, rect.bottom() + SHORT_LENGTH);

		//画出标记
		painter->drawText(x - MARGIN, rect.bottom() + SHORT_LENGTH, X_LABLE_WIDTH, X_LABLE_HEIGHT,
			Qt::AlignHCenter | Qt::AlignTop, QString::number(label));
	}

	for (int i = 0; i <= settings.numYTicks(); ++i){

		//获得Y坐标
		y = rect.bottom() - (i * (rect.height() - 1) / settings.numYTicks());

		//计算刻度
		label = settings.minY() + (i * settings.spanY() / settings.numYTicks());

		//设置画笔
		painter->setPen(quiteDarkPen);

		//画出横线
		painter->drawLine(rect.left(), y, rect.right(), y);

		//设置画笔 画小短线
		painter->setPen(lightPen);

		//长度为5的小短线
		painter->drawLine(rect.left() - SHORT_LENGTH, y, rect.left(), y);

		//画上刻度
		painter->drawText(rect.left() - MARGIN, y - 2 * SHORT_LENGTH, Y_LABLE_WIDTH, Y_LABLE_HEIGHT,
			Qt::AlignRight | Qt::AlignVCenter, QString::number(label));
	}

	//画上矩形
	painter->drawRect(rect.adjusted(0, 0, -1, -1));
}

void plotter::drawCurves(QPainter* painter){

	QRect rect(MARGIN, MARGIN, width() - 2 * MARGIN, height() - 2 * MARGIN);
	if (!rect.isValid()) return;

	//之前的设置
	PlotSettings settings = m_zoomStack[m_curZoom];

	//获得中间的绘图区
	painter->setClipRect(rect.adjusted(+1, +1, -1, -1));

	std::for_each(m_curvesMap.begin(), m_curvesMap.end(), [&](QPointFVecMap::value_type& value) {
		
		QPointFVec& vec = value.second;

		QPolygonF polyline;
		
		std::for_each(vec.begin(), vec.end(), [&](QPointF& point){
			double dx = point.x() - settings.minX();
			double dy = point.y() - settings.minY();

			double x = rect.left() + (dx * (rect.width() - 1) / settings.spanX());
			double y = rect.bottom() - (dy * (rect.height() - 1) / settings.spanY());

			polyline.push_back(QPointF(x, y));
		});

		painter->setPen(s_colorForIds[vec.size() % COLOR_FOR_IDS_SIZE]);
		painter->drawPolyline(polyline);
	});
}

#ifndef PLOTTER_H
#define PLOTTER_H

#include <QtWidgets/QWidget>
#include <unordered_map>
#include <vector>
#include <qpixmap.h>
#include "PlotSettings.h"

class QToolButton;

class plotter : public QWidget
{
	Q_OBJECT

private:
	typedef std::vector<QPointF>					QPointFVec;
	typedef std::vector<PlotSettings>				PlotSettiongsVec;
	typedef std::unordered_map<int, QPointFVec>		QPointFVecMap;
	const static size_t MARGIN						= 0x00000032u;
	const static size_t MIN_WIDTH					= 0x00000006u * MARGIN;
	const static size_t MIN_HEIGHT					= 0x00000004u * MARGIN;
	const static size_t HINT_WIDTH					= 0x0000000Cu * MARGIN;
	const static size_t HINT_HEIGHT					= 0x00000008u * MARGIN;
	const static size_t BUTTON_BLANK				= 0x00000005u;//放大缩小按钮之间的空白
	const static size_t MIN_WIDTH_HEIGHT			= 0x00000004u;//橡皮筋最小的范围 防止用户无心使用橡皮筋功能
	const static size_t MOUSE_STEP					= 0x0000000Fu;//鼠标滚动度数的步长
	const static size_t MOUSE_MAGNIFICATION			= 0x00000008u;//鼠标滚轮的角度放大倍数
	const static size_t SHORT_LENGTH				= 0x00000005u;//x y 轴上小短线的长度
	const static size_t X_LABLE_WIDTH				= 0x00000064u;//x 轴上标签的宽
	const static size_t X_LABLE_HEIGHT				= 0x00000014u;//x 轴上标签的高
	const static size_t Y_LABLE_WIDTH				= MARGIN - 0x00000005u;
	const static size_t Y_LABLE_HEIGHT				= X_LABLE_HEIGHT;
	const static size_t COLOR_FOR_IDS_SIZE			= 0X00000006u;
	const static QColor s_colorForIds[COLOR_FOR_IDS_SIZE];

public:
	plotter(QWidget *parent = NULL);
	~plotter() = default;

public:
	void setPlotSettings(const PlotSettings&);
	void setCurveData(const int, const QPointFVec&);
	void clearCurve(int);

	QSize minimumSizeHint()const { return QSize(MIN_WIDTH, MIN_HEIGHT); }
	QSize sizeHint()const { return QSize(HINT_WIDTH, HINT_HEIGHT); }

public slots:
	void zoomIn();//放大
	void zoomOut();//缩小

protected:
	void paintEvent(QPaintEvent*);
	void resizeEvent(QResizeEvent*);
	void mousePressEvent(QMouseEvent*);
	void mouseMoveEvent(QMouseEvent*);
	void mouseReleaseEvent(QMouseEvent*);
	void keyPressEvent(QKeyEvent*);
	void wheelEvent(QWheelEvent*);

private:
	void updateRubberBandRegion();//更新橡皮筋选择框
	void refreshPixmap();//刷新像素映射
	void drawGrid(QPainter*);
	void drawCurves(QPainter*);

private:
	int				 m_curZoom;//当前缩放
	bool			 m_rubberBandIsShown;
	QRect			 m_rubberBandRect;
	QPixmap			 m_pixmap;
	QPointFVecMap	 m_curvesMap;
	QToolButton*	 m_zoomInButton;
	QToolButton*	 m_zoomOutButton;
	PlotSettiongsVec m_zoomStack;//缩放堆栈

};

#endif // PLOTTER_H

#ifndef __PLOT__SETTINGS__H__
#define __PLOT__SETTINGS__H__

#if defined _MSC_VER && _MSC_VER > 1000
	#pragma once
#endif

class PlotSettings{
public:
	PlotSettings();

	void scroll(int, int);
	void adjust();

	double spanX()const { return m_maxX - m_minX; }
	double spanY()const { return m_maxY - m_minY; }

	double minX()const { return m_minX; }
	double minY()const { return m_minY; }
	double maxX()const { return m_maxX; }
	double maxY()const	{ return m_maxY; }

	void setMinX(const int x) { m_minX = x; }
	void setMaxX(const int x) { m_maxX = x; }

	void setMinY(const int y) { m_minY = y; }
	void setMaxY(const int y) { m_maxY = y; }

	int numXTicks()const { return m_numXTicks; }
	int numYTicks()const { return m_numYTicks; }

private:
	double m_minX;
	double m_maxX;
	int	   m_numXTicks;

	double m_minY;
	double m_maxY;
	int	   m_numYTicks;
private:
	static void adjustAxis(double&, double&, int&);
};



#endif


#include "PlotSettings.h"
#include <cmath>

PlotSettings::PlotSettings() :
m_minX(0),
m_minY(0),
m_maxX(10),
m_maxY(10),
m_numXTicks(5),
m_numYTicks(5){
}

void PlotSettings::scroll(int x, int y){

	double stepX = spanX() / numXTicks();
	m_minX += x * stepX;
	m_maxX += x * stepX;

	double stepY = spanY() / numYTicks();
	m_minY += y * stepY;
	m_maxY += y * stepY;
}

void PlotSettings::adjust(){
	adjustAxis(m_minX, m_maxX, m_numXTicks);
	adjustAxis(m_minY, m_maxY, m_numYTicks);
}

void PlotSettings::adjustAxis(double& min, double& max, int& numTicks){
	static const int MinTicks = 4;

	double grossStep = (max - min) / MinTicks;

	double step = std::pow(10, std::floor(std::log10(grossStep)));

	if (5 * step < grossStep)
		step *= 5;
	else if (2 * step < grossStep)
		step *= 2;

	numTicks = int(std::ceil(max / step) - std::floor(min / step));

	if (numTicks < MinTicks) numTicks = MinTicks;

	min = std::floor(min / step) * step;
	max = std::ceil(max / step) * step;
}


#include "plotter.h"
#include <QtWidgets/QApplication>
#include <cstdlib>

int main(int argc, char *argv[])
{
	QApplication a(argc, argv);

	std::vector<QPointF> vec;

	for (size_t i = 0; i != 10; ++i){
		vec.push_back(QPointF(rand() % 10, rand()% 10));
	}

	plotter w;
	w.setCurveData(1, vec);

	w.show();
	return a.exec();
}


  • 39
    点赞
  • 151
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值