Qt之图表Chart绘制
功能
- 读写CSV文件,加载数据和导出数据
- 支持同时加载多个图表数据
- 支持可拉伸图表比例
- 支持鼠标悬浮指定刻度位置,显示坐标信息
- 支持清屏
- 支持显示峰值坐标
效果图
核心代码
void ChartWidget::addChartData(const QList<qreal>& pointy, const QColor& color)
m_pointylst.clear();
m_pointylst = pointy;
m_color = color;
void ChartWidget::mapCoordinate()
if (0 == m_pointylst.size())
return;
//曲线区域高度
int h = m_height - yoffset - 40;
qreal max = 0;
qreal min = 0;
//最大最小值
QList<qreal> tmplst = m_pointylst;
qSort(tmplst.begin(), tmplst.end());
max = tmplst.last();
min = tmplst.first();
//映射每个点坐标
int distance = max - min;
int size = m_pointylst.size();
qreal xscale = interval * 1.0 / size;
m_pointMaplst.clear();
for (int i = 0; i < size; i++)
QPointF point;
point.setX((i + 1) * m_intervalWidth * xscale + xoffset);
point.setY(h * (max - m_pointylst[i])* 1.0 / distance + 16);
m_pointMaplst << point;
void ChartWidget::resizeWidthforHeight(int width, int height)
m_width = width;
m_height = height;
m_intervalWidth = (width - xoffset * 2) / interval;
mapCoordinate();
QPointF ChartWidget::paintChart(QPainter* painter, QRectF linerect, bool bshowAxis)
painter->save();
QPen pen;
pen.setColor(QColor(m_color));
pen.setStyle(Qt::SolidLine);
pen.setWidth(2);
painter->setPen(pen);
painter->setRenderHint(QPainter::Antialiasing, true);
QPainterPath path;
int size = m_pointMaplst.size();
path.moveTo(m_pointMaplst[0]);
for (int i = 1; i < size - 1; i++)
QPointF sp = m_pointMaplst[i];
QPointF ep = m_pointMaplst[i + 1];
QPointF c1 = QPointF((sp.x() + ep.x()) / 2, sp.y());
QPointF c2 = QPointF((sp.x() + ep.x()) / 2, ep.y());
path.cubicTo(c1, c2, ep);
painter->drawPath(path);
painter->restore();
if (bshowAxis)
painter->save();
QPen penAxis;
penAxis.setColor(QColor(m_color));
penAxis.setStyle(Qt::SolidLine);
penAxis.setWidth(1);
painter->setPen(penAxis);
painter->setRenderHint(QPainter::Antialiasing, true);
auto pairPoint = CommonUtils::findPeaks(m_pointylst);
//极大值
const QList<int> maxlst = pairPoint.first;
for (auto iterMax : maxlst)
QPointF point;
point = m_pointMaplst.at(iterMax);
const QString&& num = QString::number(m_pointylst.at(iterMax));
int numWidth = painter->fontMetrics().width(num);
point.setX(point.x() - numWidth / 2);
point.setY(point.y() - 4);
painter->drawText(point, num);
//极小值
const QList<int> minlst = pairPoint.second;
for (auto iterMin : minlst)
QPointF point;
point = m_pointMaplst.at(iterMin);
const QString&& num = QString::number(m_pointylst.at(iterMin));
int numWidth = painter->fontMetrics().width(num);
point.setX(point.x() - numWidth / 2);
point.setY(point.y() + 14);
painter->drawText(point, num);
painter->restore();
QPointF linePoint;
// Check if the rectangle surounds any subpath...
for (int i = 0; i < path.elementCount(); ++i) {
const QPainterPath::Element &e = path.elementAt(i);
if (linerect.contains(e))
painter->save();
painter->setPen(Qt::NoPen);
painter->setBrush(QColor(m_color));
painter->drawEllipse(e.x - 4, e.y - 4, 8, 8);
linePoint.setX(e.x);
linePoint.setY(e.y);
painter->restore();
return linePoint;
void ChartManagerWidget::paintEvent(QPaintEvent *event)
QPainter painter(this);
QStyleOption opt;
opt.init(this);
style()->drawPrimitive(QStyle::PE_Widget, &opt, &painter, this);
painter.setRenderHint(QPainter::Antialiasing, true);
//绘制坐标
QPen linpen;
linpen.setColor(QColor(102, 102, 102));
linpen.setStyle(Qt::SolidLine);
linpen.setWidth(1);
painter.setPen(linpen);
//纵坐标
painter.drawLine(xoffset, height() - yoffset, width() - xoffset, height() - yoffset);
//横坐标
int intervalWidth = (width() - xoffset * 2) / 80;
for (int i = 0; i <= 80; i++){
if (i == 0){
painter.drawText(i * intervalWidth + xoffset, height() - 5, QString("0"));
else if (i % 5 == 0){
const QString&& num = QString("%1").arg(i);
int numWidth = painter.fontMetrics().width(num);
painter.drawText(i * intervalWidth + xoffset - numWidth / 2, height() - 5, num);
painter.drawLine(i * intervalWidth + xoffset, height() - 21, i * intervalWidth + xoffset, height() - 26);
//绘制线轴
qreal lineNum = -1;
QRectF linerect;
int chartWidgetSize = m_chartWidgetlst.size();
int linesize = m_lineRect.size();
if (chartWidgetSize > 0)
for (int index = 0; index < linesize; index++)
if (m_lineRect[index].contains(m_mousepoint))
linerect.setX(m_linePosx[index]);
linerect.setY(10);
linerect.setWidth(1);
linerect.setHeight(height() - 40);
painter.drawLine(m_linePosx[index], 10, m_linePosx[index], height() - 30);
lineNum = (m_linePosx[index] - xoffset) * 1.0 / intervalWidth;
int curH = 16;
int maxWidth = 80;
QList<QString> linepointLst;
for (int index = 0; index < m_chartWidgetlst.size(); index++){
painter.save();
linpen.setColor(m_chartWidgetlst.at(index)->getColor());
linpen.setStyle(Qt::SolidLine);
linpen.setWidth(3);
painter.setPen(linpen);
QFont font = painter.font();
font.setFamily(QString::fromLocal8Bit("微软雅黑"));
painter.setFont(font);
painter.drawText(4, curH, QString::fromLocal8Bit("曲线%1:").arg(index + 1));
painter.drawLine(48, curH - 4, 88, curH - 4);
painter.restore();
QPointF linePoint = m_chartWidgetlst.at(index)->paintChart(&painter, linerect, m_bShowAxis);
QString text = QString::fromLocal8Bit("曲线%1:(%2, %3)").arg(index + 1).arg(lineNum).arg(linePoint.y());
int numWidth = painter.fontMetrics().width(text);
maxWidth = qMax(maxWidth, numWidth);
linepointLst << text;
curH += 16;
//绘制气泡
if (lineNum != -1)
painter.save();
QPointF rectpoint = m_mousepoint + QPoint(20, 0);
if (rectpoint.x() + maxWidth + 12 >= this->width()){
rectpoint.setX(m_mousepoint.x() - 32 - maxWidth);
painter.setOpacity(0.95);
painter.setBrush(Qt::gray);
painter.setPen(Qt::NoPen);
painter.drawRoundedRect(rectpoint.x(), rectpoint.y(), maxWidth + 12, linepointLst.size() * 20 + 12, 2, 2);
for (int index = 0; index < linepointLst.size();index++)
painter.setPen(Qt::white);
QFont font = painter.font();
font.setFamily(QString::fromLocal8Bit("微软雅黑"));
painter.setFont(font);
painter.setOpacity(1);
painter.drawText(QRect(rectpoint.x() + 6, rectpoint.y() + index * 20 + 6, maxWidth, 20), Qt::AlignLeft | Qt::AlignVCenter, linepointLst[index]);
painter.restore();
void ChartManagerWidget::mouseMoveEvent(QMouseEvent *event)
m_mousepoint = this->mapFromGlobal(QCursor::pos());
update();
QWidget::mouseMoveEvent(event);
void ChartManagerWidget::resizeEvent(QResizeEvent *event)
m_linePosx.clear();
m_lineRect.clear();
int intervalWidth = (width() - xoffset * 2) / 80;
for (int i = 0; i <= 80; i++){
if (i % 5 == 0){
QRect rect(i * intervalWidth + xoffset - 10, 20, 20, height() - 40);
m_linePosx << i * intervalWidth + xoffset;
m_lineRect << rect;