背景: 之前发布过一个提问,用qchart绘制动态曲线,每200毫秒append一个点,这样确实是动态了,但是有一个问题,就是append多了,比如跑了1个小时,那我的series中就有18000个数据,这个时候,曲线的刷新会变得一顿一顿的,很不流畅。换个思路,当我的series超过50个点的时候,每次新加入一个点,就删除第一个点。现在就算跑一个小时两个小时,曲线都不会卡顿,但是新的问题来了,一旦我删除节点,也就是从51个点开始,我的曲线会飘动(能想象吗,就是曲线在前后移动的时候,还在上下飘动,像波浪一样,一浪一浪的,真的很搞笑,我测试效果的时候,差点没笑出声来)。终于,总算想起来还有这个事,直接把实现放上来了,需要的自取,以后就不用再发邮件了。
curvechart.h
#ifndef CURVECHART_H
#define CURVECHART_H
#include "exportHeader.h"
#include <QObject>
#include <QWidget>
#include <QMouseEvent>
class AATCOMMONUNIT_EXPORT CurveChart : public QWidget
{
Q_OBJECT
Q_PROPERTY(QColor BackgroundStartColor READ BackgroundStartColor WRITE setBackgroundStartColor)
Q_PROPERTY(QColor BackgroundEndColor READ BackgroundEndColor WRITE setBackgroundEndColor)
Q_PROPERTY(QColor TextColor READ TextColor WRITE setTextColor)
Q_PROPERTY(QColor PointColor READ PointColor WRITE setPointColor)
Q_PROPERTY(qreal StepHoriginal READ StepHoriginal WRITE setStepHoriginal)
Q_PROPERTY(qreal StepVertical READ StepVertical WRITE setStepVertical)
Q_PROPERTY(qreal ChartLeftMargin READ ChartLeftMargin WRITE setChartLeftMargin)
Q_PROPERTY(qreal ChartTopMargin READ ChartTopMargin WRITE setChartTopMargin)
Q_PROPERTY(qreal ChartRightMargin READ ChartRightMargin WRITE setChartRightMargin)
Q_PROPERTY(qreal ChartBottomMargin READ ChartBottomMargin WRITE setChartBottomMargin)
Q_PROPERTY(QString DataString READ DataString WRITE addDataStr)
public:
CurveChart(QWidget *parent = 0);
~CurveChart();
QColor BackgroundStartColor() const { return m_bgStartColor; }
void setBackgroundStartColor(QColor color);
QColor BackgroundEndColor() const { return m_bgEndColor; }
void setBackgroundEndColor(QColor color);
QColor TextColor() const { return m_textColor; }
void setTextColor(QColor color);
QColor PointColor() const { return m_pointColor; }
void setPointColor(QColor color);
void setMinY(qreal min);
void setMaxY(qreal max);
void setMinX(qreal min);
void setMaxX(qreal max);
qreal StepHoriginal() const { return m_stepH; }
void setStepHoriginal(qreal val);
qreal StepVertical() const { return m_stepV; }
void setStepVertical(qreal val);
qreal ChartLeftMargin() const { return m_chartLeftMargin; }
void setChartLeftMargin(qreal margin);
qreal ChartTopMargin() const { return m_chartTopMargin; }
void setChartTopMargin(qreal margin);
qreal ChartRightMargin() const { return m_chartRightMargin; }
void setChartRightMargin(qreal margin);
qreal ChartBottomMargin() const { return m_chartBottomMargin; }
void setChartBottomMargin(qreal margin);
QString DataString() const;
void addDataStr(QString str);
void addData(qreal val);
void setTipsKeys(const QString xstr, const QString ystr);
void setPaintFlag(bool stopPaint = true);
public slots:
void setTitle(QString str);
void setShowLine(bool show);
void setShowPoint(bool show);
void setShowPointBackground(bool show);
void clearData();
protected:
void paintEvent(QPaintEvent *);
void resizeEvent(QResizeEvent *event);
void mouseMoveEvent(QMouseEvent *event);
void drawBackground(QPainter* p);
void drawTitle(QPainter* p);
void drawBorder(QPainter* p);
void drawText(QPainter* p);
void drawPoints(QPainter* p);
void drawCallout(QPainter* p);
void timerEvent(QTimerEvent*);
QSize minimumSizeHint() const { return QSize(400, 250); }
private:
void setRangeY(qreal min, qreal max);
void setRangeX(qreal min, qreal max);
void preparePaintCallout();
bool findCurMouseDotValue();//查找鼠标当前点对应的曲线上的点的真实值
private:
QColor m_bgStartColor;//背景颜色
QColor m_bgEndColor;
QColor m_textColor;//标题/文本/边框颜色
QColor m_pointColor;//统计点颜色
QString m_title;//标题文本
QString m_xStr;//横坐标单位
QString m_yStr;//纵坐标单位
qreal m_minY;//最小值
qreal m_maxY;//最大值
qreal m_minX;//最小值
qreal m_maxX;//最大值
qreal m_xInterval;//初始m_maxX / m_stepValueH
qreal m_stepH;//水平像素间隔
qreal m_stepValueH;//水平值间隔
qreal m_stepV;//垂直间隔
qreal m_chartLeftMargin;//图表左边距
qreal m_chartTopMargin;
qreal m_chartRightMargin;
qreal m_chartBottomMargin;
int m_pointRadious;//数据点半径
int m_refreshTimes;
bool m_showLine;//显示刻度线
bool m_showPoint;//显示数据点
bool m_showPointBg;//显示数据点覆盖背景
bool m_paintCalloutFlag;//是否需要绘制callout的标志
bool m_stopPaint;
QVector<qreal> m_points;
QPointF m_curMousePos;
QPointF m_curMouseValuePos;//用来显示callout用的真实值
QPointF m_achor;//竖线与曲线相交点的像素值
QString m_text;
QRectF m_textRect;
QRectF m_rect;
QFont m_font;
};
#endif // CURVECHART_H
curvechart.cpp
#include "curvechart.h"
#include <QPainter>
#include <QtMath>
#include <QDebug>
#if _MSC_VER >= 1600
#pragma execution_character_set("utf-8")
#endif
CurveChart::CurveChart(QWidget *parent)
: QWidget(parent)
{
setMouseTracking(true);
m_bgStartColor = QColor(79, 79, 79);
m_bgEndColor = QColor(51, 51, 51);
m_textColor = QColor(Qt::white);
m_pointColor = QColor(38, 114, 179);
m_minY = 0;
m_maxY = 100;
m_minX = 0;
m_maxX = 12;
m_stepValueH = 2.0f;
m_xInterval = m_maxX / m_stepValueH;
m_stepH = 10;
m_stepV = 10;
m_chartLeftMargin = 50;
m_chartTopMargin = 50;
m_chartRightMargin = 60;
m_chartBottomMargin = 20;
m_pointRadious = 2;
m_refreshTimes = 0;
m_showLine = true;
m_showPoint = false;
m_showPointBg = true;
m_paintCalloutFlag = false;
m_stopPaint = false;
}
CurveChart::~CurveChart()
{
}
void CurveChart::setBackgroundStartColor(QColor color)
{
if(color != m_bgStartColor)
{
m_bgStartColor = color;
update();
}
}
void CurveChart::setBackgroundEndColor(QColor color)
{
if(color != m_bgEndColor)
{
m_bgEndColor = color;
update();
}
}
void CurveChart::setTextColor(QColor color)
{
if(color != m_textColor)
{
m_textColor = color;
update();
}
}
void CurveChart::setPointColor(QColor color)
{
if(color != m_pointColor)
{
m_pointColor = color;
update();
}
}
void CurveChart::setMinY(qreal min)
{
setRangeY(min, m_maxY);
}
void CurveChart::setMaxY(qreal max)
{
setRangeY(m_minY, max);
}
void CurveChart::setMinX(qreal min)
{
setRangeX(min, m_maxX);
}
void CurveChart::setMaxX(qreal max)
{
setRangeX(m_minX, max);
}
void CurveChart::setStepHoriginal(qreal val)
{
if(val != m_stepH)
{
m_stepH = val;
update();
}
}
void CurveChart::setStepVertical(qreal val)
{
if(val != m_stepV)
{
m_stepV = val;
update();
}
}
void CurveChart::setChartLeftMargin(qreal margin)
{
if(margin>=0 && margin != m_chartLeftMargin)
{
m_chartLeftMargin = margin;
update();
}
}
void CurveChart::setChartTopMargin(qreal margin)
{
if(margin>=0 && margin != m_chartTopMargin)
{
m_chartTopMargin = margin;
update();
}
}
void CurveChart::setChartRightMargin(qreal margin)
{
if(margin>=0 && margin != m_chartRightMargin)
{
m_chartRightMargin = margin;
update();
}
}
void CurveChart::setChartBottomMargin(qreal margin)
{
if(margin>=0 && margin != m_chartBottomMargin)
{
m_chartBottomMargin = margin;
update();
}
}
QString CurveChart::DataString() const
{
QStringList points;
for(int i=0; i<m_points.size(); ++i)
{
points << QString("%1").arg(m_points[i]);
}
return points.join("|");
}
void CurveChart::addDataStr(QString str)
{
clearData();
QStringList dataList = str.split("|");
foreach(QString valStr, dataList)
{
bool ok;
double val = valStr.toDouble(&ok);
if(ok)
{
addData(val);
}
}
}
void CurveChart::addData(qreal val)
{
int maxNum = qFloor((width()-m_chartLeftMargin-m_chartRightMargin)/m_stepH);
if(maxNum < 0) { return; }
if (val < 100)
{
int tY = val / 10;
tY = tY * 10 + 10;
m_maxY = m_maxY > tY ? m_maxY : tY;
m_stepV = m_maxY / 5;
}
else if (val < 1000)
{
int tY = val / 100;
tY = tY * 100 + 100;
m_maxY = m_maxY > tY ? m_maxY : tY;
m_stepV = m_maxY / 5;
}
else if (val < 10000)
{
int tY = val / 1000;
tY = tY * 1000+ 1000;
m_maxY = m_maxY > tY ? m_maxY : tY;
m_stepV = m_maxY / 5;
}
if(m_points.size()>maxNum)
{
m_refreshTimes++;
m_points.pop_front();
}
if (m_refreshTimes >=5)
{
m_maxX++;
m_minX = m_maxX - 12;
m_refreshTimes = 0;
}
m_points.push_back(val);
preparePaintCallout();
update();
}
void CurveChart::setTipsKeys(const QString xstr, const QString ystr)
{
m_xStr = xstr;
m_yStr = ystr;
}
void CurveChart::setPaintFlag(bool stopPaint /*= true*/)
{
m_stopPaint = stopPaint;
}
void CurveChart::setTitle(QString str)
{
if(str != m_title)
{
m_title = str;
update();
}
}
void CurveChart::setShowLine(bool show)
{
if(show != m_showLine)
{
m_showLine = show;
update();
}
}
void CurveChart::setShowPoint(bool show)
{
if(show != m_showPoint)
{
m_showPoint = show;
update();
}
}
void CurveChart::setShowPointBackground(bool show)
{
if(show != m_showPointBg)
{
m_showPointBg = show;
update();
}
}
void CurveChart::clearData()
{
if(m_points.size()>0)
{
m_points.clear();
m_minY = 0;
m_maxY = 100;
m_minX = 0;
m_maxX = 12;
m_stepValueH = 2.0f;
m_stepH = 10;
m_stepV = 10;
m_refreshTimes = 0;
m_paintCalloutFlag = false;
update();
}
}
void CurveChart::paintEvent(QPaintEvent *)
{
if (m_stopPaint)
{
return;
}
QPainter painter(this);
painter.setRenderHint(QPainter::Antialiasing);
drawBackground(&painter);
//drawTitle(&painter);
drawBorder(&painter);
drawText(&painter);
drawPoints(&painter);
drawCallout(&painter);
}
void CurveChart::resizeEvent(QResizeEvent *event)
{
m_stepH = (width() - m_chartLeftMargin - m_chartRightMargin) / ((m_xInterval) * 20);
preparePaintCallout();
update();
}
void CurveChart::mouseMoveEvent(QMouseEvent *event)
{
m_curMousePos = event->localPos();
preparePaintCallout();
update();
}
void CurveChart::drawBackground(QPainter *p)
{
p->save();
QLinearGradient linearGradient(QPointF(0,0), QPointF(0,height()));
linearGradient.setColorAt(0.0, m_bgStartColor);
linearGradient.setColorAt(1.0, m_bgEndColor);
p->setPen(Qt::NoPen);
p->setBrush(linearGradient);
p->drawRect(rect());
p->restore();
}
void CurveChart::drawTitle(QPainter *p)
{
p->save();
QPointF topLeft(0, 0);
QPointF bottomRight(width(), m_chartTopMargin);
QRectF titleRect(topLeft, bottomRight);
p->setPen(m_textColor);
p->drawText(titleRect, Qt::AlignCenter, m_title);
p->restore();
}
void CurveChart::drawBorder(QPainter *p)
{
p->save();
//画边框
QPointF topLeft(m_chartLeftMargin, m_chartTopMargin);
QPointF bottomRight(width()-m_chartRightMargin, height()-m_chartBottomMargin);
QRectF borderRect(topLeft, bottomRight);
p->setPen(m_textColor);
p->drawRect(borderRect);
//画线条
if(m_showLine)
{
QPen pen = p->pen();
pen.setStyle(Qt::CustomDashLine);
p->setPen(pen);
qreal count = (m_maxY-m_minY)/m_stepV;
qreal stepY = borderRect.height()/count;
qreal dy = m_chartTopMargin+stepY;
QPointF p1,p2;
while(dy<borderRect.bottom())
{
p1 = QPointF(borderRect.left(), dy);
p2 = QPointF(borderRect.left() + 5, dy);
p->drawLine(p1, p2);
dy += stepY;
}
qreal countX = (m_maxX-m_minX)/m_stepValueH;
qreal stepX = borderRect.width()/countX;
qreal dX = m_chartLeftMargin+stepX;
while(dX<borderRect.right())
{
p1 = QPointF(dX,borderRect.bottom());
p2 = QPointF(dX, borderRect.bottom() - 5);
p->drawLine(p1, p2);
dX += stepX;
}
}
p->restore();
}
void CurveChart::drawText(QPainter *p)
{
p->save();
qreal textMargin = 5;
QFontMetrics fm = fontMetrics();
int textHeight = fm.height();
QPointF topLeft(m_chartLeftMargin, m_chartTopMargin);
QPointF bottomRight(width()-m_chartRightMargin, height()-m_chartBottomMargin);
QRectF borderRect(topLeft, bottomRight);
qreal count = (m_maxY-m_minY)/m_stepV;
qreal stepY = borderRect.height()/count;
qreal dy = m_chartTopMargin;
QPointF pos;
qreal val = m_maxY;
p->setPen(m_textColor);
while(val >= m_minY)
{
QString text = QString("%1").arg(val);
int textWidth = fm.width(text);
pos = QPointF(m_chartLeftMargin-textMargin-textWidth, dy);
p->drawText(pos, text);
dy += stepY;
val -= m_stepV;
}
qreal countX = (m_maxX-m_minX)/m_stepValueH;
qreal stepX = borderRect.width()/countX;
qreal dx = m_chartLeftMargin;
val = m_minX;
while(val <= m_maxX)
{
QString text = QString("%1").arg(val);
int textWidth = fm.width(text);
pos = QPointF(dx-textWidth/2.0, borderRect.bottom()+textHeight);
p->drawText(pos, text);
dx += stepX;
val += m_stepValueH;
}
int textWidth = fm.width(m_yStr);
pos = QPointF(2, m_chartTopMargin - 20);
p->drawText(pos, m_yStr);
pos = QPointF(width() - m_chartRightMargin + textMargin, height() - m_chartBottomMargin);
p->drawText(pos, m_xStr);
p->restore();
}
void CurveChart::drawPoints(QPainter *p)
{
if(m_points.isEmpty())
return;
p->save();
QVector<QPointF> posVec;
qreal dx = width()-m_chartRightMargin;
qreal dy = height()-m_chartBottomMargin;
qreal percent = (height()-m_chartTopMargin-m_chartBottomMargin)/(m_maxY-m_minY);
posVec.append(QPointF(dx, dy));//结束点
QVector<qreal>::reverse_iterator it;
for(it = m_points.rbegin(); it!= m_points.rend(); ++it)
{
if(dx<m_chartLeftMargin)
{
break;
}
posVec.append(QPointF(dx, dy-(*it-m_minY)*percent));
dx -= m_stepH;
}
posVec.append(QPointF(dx<m_chartLeftMargin?m_chartLeftMargin:dx, dy));
if(!m_showPointBg)
{//不显示背景的话去除起始点和结束点
posVec.pop_front();
posVec.pop_back();
}
//画圆点
if(m_showPoint)
{
int radious = m_stepH/2;
p->save();
p->setPen(Qt::NoPen);
p->setBrush(m_pointColor);
for(int i=0; i<posVec.size(); ++i)
{
p->drawEllipse(posVec[i], radious, radious);
}
p->restore();
}
//画线
p->setPen(m_pointColor);
if(m_showPointBg)
{
p->setBrush(m_pointColor);
p->setOpacity(0.5);
p->drawConvexPolygon(QPolygonF(posVec));
}
else
{
p->drawPolyline(QPolygonF(posVec));
}
p->restore();
}
void CurveChart::drawCallout(QPainter *p)
{
if (m_paintCalloutFlag == false)
{
return;
}
p->save();
QPainterPath path;
path.addRoundedRect(m_rect, 5, 5);
QPointF anchor = m_achor;
if (!m_rect.contains(anchor))
{
QPointF point1, point2;
// establish the position of the anchor point in relation to m_rect
bool above = anchor.y() <= m_rect.top();
bool aboveCenter = anchor.y() > m_rect.top() && anchor.y() <= m_rect.center().y();
bool belowCenter = anchor.y() > m_rect.center().y() && anchor.y() <= m_rect.bottom();
bool below = anchor.y() > m_rect.bottom();
bool onLeft = anchor.x() <= m_rect.left();
bool leftOfCenter = anchor.x() > m_rect.left() && anchor.x() <= m_rect.center().x();
bool rightOfCenter = anchor.x() > m_rect.center().x() && anchor.x() <= m_rect.right();
bool onRight = anchor.x() > m_rect.right();
// get the nearest m_rect corner.
qreal x = (onRight + rightOfCenter) * m_rect.width() + m_rect.x();
qreal y = (below + belowCenter) * m_rect.height() + m_rect.y();
bool cornerCase = (above && onLeft) || (above && onRight) || (below && onLeft) || (below && onRight);
bool vertical = qAbs(anchor.x() - x) > qAbs(anchor.y() - y);
qreal x1 = x + leftOfCenter * 10 - rightOfCenter * 20 + cornerCase * !vertical * (onLeft * 10 - onRight * 20);
qreal y1 = y + aboveCenter * 10 - belowCenter * 20 + cornerCase * vertical * (above * 10 - below * 20);;
point1.setX(x1);
point1.setY(y1);
qreal x2 = x + leftOfCenter * 20 - rightOfCenter * 10 + cornerCase * !vertical * (onLeft * 20 - onRight * 10);;
qreal y2 = y + aboveCenter * 20 - belowCenter * 10 + cornerCase * vertical * (above * 20 - below * 10);;
point2.setX(x2);
point2.setY(y2);
path.moveTo(point1);
path.lineTo(anchor);
path.lineTo(point2);
path = path.simplified();
}
p->setBrush(QColor(200, 255, 200));
p->drawPath(path);
p->drawText(m_textRect, m_text);
p->setPen(QPen(QColor(200, 255, 255), 2));
p->drawEllipse(anchor, 1.5, 1.5);
p->drawLine(QPointF(m_curMousePos.x(),m_chartTopMargin + 2),QPointF(m_curMousePos.x(),height() - m_chartBottomMargin - 2));
p->restore();
}
void CurveChart::timerEvent(QTimerEvent*)
{
addData(m_points.last());
}
void CurveChart::setRangeY(qreal min, qreal max)
{
if(min>max)
qSwap(min, max);
m_minY = min;
m_maxY = max;
update();
}
void CurveChart::setRangeX(qreal min, qreal max)
{
if(min>max)
qSwap(min, max);
m_minX = min;
m_maxX = max;
update();
}
void CurveChart::preparePaintCallout()
{
bool re = findCurMouseDotValue();
QFontMetrics fm = fontMetrics();
QStringList co = m_text.split("\n");
int w1 = fm.width(co.first());
int w2 = fm.width(co.last());
int calloutWidth = w1 > w2 ? w1 : w2;
m_textRect = QRectF(width() - calloutWidth - 10, 5, calloutWidth, 30);
m_textRect.translate(5, 5);
m_rect = m_textRect.adjusted(-5, -5, 5, 5);
m_paintCalloutFlag = re;
}
bool CurveChart::findCurMouseDotValue()
{
float rectWidth = width() - m_chartLeftMargin - m_chartRightMargin;
float rectHeight = height() - m_chartBottomMargin - m_chartTopMargin;
float valueX = ((m_curMousePos.x() - m_chartLeftMargin) * (m_maxX - m_minX)) / rectWidth + m_minX + m_refreshTimes * m_stepH * m_stepValueH * m_xInterval / rectWidth;
float valueY = ((rectHeight - m_curMousePos.y() + m_chartTopMargin) * (m_maxY - m_minY)) / rectHeight;
if (m_points.size() < (rectWidth / m_stepH))
{
float txmin = width() - (m_points.size() * m_stepH) - m_chartRightMargin;
if ((m_curMousePos.x() <= txmin)
|| (m_curMousePos.x() >= (width() - m_chartRightMargin))
|| (valueY <= m_minY)
|| (valueY >= m_maxY))
{
m_paintCalloutFlag = false;
return false;
}
for (int i = 0; i < m_points.size(); i++)
{
float tx = m_chartLeftMargin + (rectWidth - m_points.size() * m_stepH) + i * m_stepH;
if (fabsf(tx - m_curMousePos.x()) <= (m_stepH / 2))
{
valueY = m_points.value(i);
break;
}
if (i == m_points.size() - 1)
{
m_paintCalloutFlag = false;
return false;
}
}
}
else
{
if (valueX <= (m_minX + m_refreshTimes * 0.2) || valueX >= m_maxX || valueY <= m_minY || valueY >= m_maxY)
{
m_paintCalloutFlag = false;
return false;
}
for (int i = 0; i < m_points.size(); i++)
{
float tx = m_chartLeftMargin + i * m_stepH;
if (fabsf(tx - m_curMousePos.x()) <= (m_stepH / 2))
{
valueY = m_points.value(i);
break;
}
if (i == m_points.size() - 1)
{
m_paintCalloutFlag = false;
return false;
}
}
}
float achorY = height() - (valueY * rectHeight / (m_maxY - m_minY)) - m_chartBottomMargin;
m_curMouseValuePos = QPointF(valueX,valueY);
m_text = QString("X:%1\nY:%2").arg(valueX).arg(valueY);
m_achor = QPointF(m_curMousePos.x(), achorY);
return true;
}
test_main.cpp
在qt设计师里面将某一个widget提升为我们自定义的curvechart类,然后随便命个名字,我这里是mMemChartViewer
以下是基本使用
int main()
{
int timerID;
mMemChartViewer->setTitle("Mem");//设置标题
mMemChartViewer->setTipsKeys(QString("时间(秒)"), QString("内存(MB)"));//设置横纵坐标单位
timerID = mMemChartViewer->startTimer(200);//每200毫秒自动刷新一次曲线图
}
槽函数:一旦收到手机内存信息就发送给curvechart()
{
mMemChartViewer->addData(纵坐标值);///因为横坐标是时间,而时间是在curvechart类内部自动刷新的,所以只需要发纵坐标进去就行了
}
我这个也是抄的网上的一个例子(收藏的链接太多了,一时半会找不到原来的网页链接给了),然后加了点自己需要的东西(比较low),里面还有一些其他设置,有些我都没用到的,可以自己去看看如何使用。
加了个timer自动刷新,因为我之前做那个是连接手机,实时获取手机的内存信息,数据输出的时间是不确定的,有时候1秒就获取到了,然后我就发给curvechart,
但有时候数据要隔几秒才会获取得到,那我不可能等几秒,曲线图都不动,所以就加了个timer,基本思想是,当外面有数据发进来的时候,就用新的数据刷新一下曲线图,
当外面没数据来的时候,我就每200毫秒(时间自己定一个传进去)用上一次的数据来刷新一次曲线图。