这一篇我们详细介绍图表各个部分的设置和操作,包括图表的标题、图例、边距等属性设置,QLineSeries序列的属性设置,QValueAxis坐标轴的属性设置,以及图标的缩放。(这些应该都是在实际的Qt开发中比较常用的图表操作)先看运行时的界面:

界面设计

  • 工具栏:创建几个Action,并创建工具栏,实现图表数据刷新和缩放功能。
  • 主工作区图标视图:从组件面板放置一个QGraphics View组件作为视图组件,并用Promote方法升级为QChartView组件,命名为chartView
  • 图表属性设置面板:左侧是一个QToolBox组件,分为3个操作面板,用于进行图表设置、曲线设置和坐标轴设置。

主窗口类的定义和初始化

下面是主窗口类MainWindow的类定义(省略了Action和界面组件的槽函数定义)。在mainwindow.h文件重需要包含QtChart,并使用宏QT_CHARTS_USE_NAMESPACE导入命名空间。

#ifndef MAINWINDOW_H
#define MAINWINDOW_H

#include <QMainWindow>
#include <QtCharts>
QT_CHARTS_USE_NAMESPACE
QT_BEGIN_NAMESPACE
namespace Ui { class MainWindow; }
QT_END_NAMESPACE

class MainWindow : public QMainWindow
{
    Q_OBJECT

public:
    MainWindow(QWidget *parent = nullptr);
    ~MainWindow();

private:

    QLineSeries* curSeries; // 当前序列
    QValueAxis* curAxis; // 当前坐标轴
    void createChart(); // 创建图表
    void prepareData(); // 更新数据
    void updateFromChart(); // 从图表更新到界面

private:
    Ui::MainWindow *ui;
};
#endif // MAINWINDOW_H

MainWindow类中定义了两个私有的变量,curSeries用于指向当前的QLineSeries序列,界面上对序列的设置操作都是针对当前选择的序列;curAxis用于指向当前的QValueAxis坐标轴,对坐标轴进行设置时就是针对当前坐标轴进行设置。

createChart()函数用于创建图表的各个基本部件,在构造函数里调用,prepareData()用于更新序列的数据,updateFormChart()用于读取图表的一些属性,并刷新界面显示。下面是主窗口构造函数,以及这3个函数的代码。

MainWindow::MainWindow(QWidget *parent)
    : QMainWindow(parent)
    , ui(new Ui::MainWindow)
{
    ui->setupUi(this);
    createChart(); // 创建图表
    prepareData(); // 生成数据
    updateFromChart(); // 从图表获取属性值,刷新到界面显示
}

void MainWindow::createChart()
{
    QChart* chart = new QChart();
    chart->setTitle("简单函数曲线");
    ui->chartView->setChart(chart);
    ui->chartView->setRenderHint(QPainter::Antialiasing); // 抗锯齿,平滑

    QLineSeries* series0 = new QLineSeries();
    QLineSeries* series1 = new QLineSeries();
    series0->setName("Sin 曲线");
    series1->setName("Cos 曲线");
    curSeries = series0;

    QPen pen;
    pen.setStyle(Qt::DotLine);
    pen.setWidth(2);
    pen.setColor(Qt::red);
    series0->setPen(pen);
    pen.setStyle(Qt::SolidLine);
    pen.setColor(Qt::blue);
    series1->setPen(pen);
    chart->addSeries(series0);
    chart->addSeries(series1);

    QValueAxis* axisX = new QValueAxis();
    curAxis = axisX;
    axisX->setRange(0, 10);
    axisX->setLabelFormat("%.1f"); // 标签格式
    axisX->setTickCount(11); // 主分隔个数
    axisX->setMinorTickCount(9); // 次分隔个数
    axisX->setTitleText("time(secs)"); // 标题

    QValueAxis* axisY = new QValueAxis();
    axisY->setRange(-2, 2);
    axisY->setTitleText("value");
    axisY->setTickCount(5);
    axisY->setLabelFormat("%.2f");
    axisY->setMinorTickCount(4);

    chart->setAxisX(axisX, series0);
    chart->setAxisY(axisY, series0);
    chart->setAxisX(axisX, series1);
    chart->setAxisY(axisY, series1);
}

void MainWindow::prepareData()
{
    QLineSeries* series0 = (QLineSeries*)ui->chartView->chart()->series().at(0);
    QLineSeries* series1 = (QLineSeries*)ui->chartView->chart()->series().at(1);

    series0->clear();
    series1->clear();

    qsrand(QTime::currentTime().second()); // 初始化随机数
    qreal t = 0;
    qreal y1, y2;
    qreal intv = 0.1;
    qreal rd;
    int cnt = 100;

    for (int i = 0; i < cnt; i++) {

        rd = (qrand() % 10) - 5;
        y1 = qSin(t) + rd / 50;
        series0->append(t, y1);
        rd = (qrand() % 10) - 5;
        y2 = qCos(t) + rd / 50;
        series1->append(t, y2);
        t += intv;
    }
}

void MainWindow::updateFromChart()
{
    QChart* chart = ui->chartView->chart();
    ui->editTitle->setText(chart->title());
    QMargins mg = chart->margins(); // 边距
    ui->spinMarginTop->setValue(mg.top());
    ui->spinMarginLeft->setValue(mg.left());
    ui->spinMarginRight->setValue(mg.right());
    ui->spinMarginBottom->setValue(mg.bottom());
}
  • MainWindow类个构造函数调用单个私有函数进行图表和界面的初始化。
  • createChart()函数用于创建QChart对象,创建数据序列和坐标轴,并将这些部件组合成一个完整的图表。
  • prepareDate()函数用于为图表重的两个序列生成数据,其中使用随机数,以便使得每次生成的数据稍有不同。
  • updateFromChart()函数用于将图表重的标题和边距信息显示到窗口界面上。

笔画设置对话框QWDialogPen

在这个demo中,需要设置一些对象的pen属性,比如折线序列的pen属性,网络先的pen属性等。pen属性其实就是一个QPen对象,设置内容主要包括线型,线宽和颜色。为了使用方便,设计一个自定义对话框QWDialogPen,专门用于QPen对象的属性设置。

QWDialogPen是一个可视化设计的对话框,其类型定义如下:

class QWDialogPen : public QDialog
{
    Q_OBJECT

public:
    explicit QWDialogPen(QWidget *parent = nullptr);
    ~QWDialogPen();

    void setPen(QPen pen); // 设置Qpen,用于对话框界面显示
    QPen getPen(); // 获取对话框设置的QPen属性
    static QPen getPen(QPen iniPen, bool& ok); //静态函数

private slots:
    void on_btnColor_clicked();

private:
    Ui::QWDialogPen *ui;
    QPen m_pen;
};

QWDialogPen类的getPen()以及相关函数的实现代码如下:

void QWDialogPen::setPen(QPen pen)
{
    m_pen = pen;
    ui->spinWidth->setValue(pen.width());
    int i = static_cast<int>(pen.style());
    ui->comboPenStyle->setCurrentIndex(i);
    QColor color = pen.color();
    ui->btnColor->setAutoFillBackground(true);
    QString str = QString::asprintf("background-color: rgb(%d,%d,%d);", color.red(), color.green(), color.blue());
    ui->btnColor->setStyleSheet(str);
}

QPen QWDialogPen::getPen()
{
    m_pen.setStyle(Qt::PenStyle(ui->comboPenStyle->currentIndex()));
    m_pen.setWidth(ui->spinWidth->value());
    QColor color = ui->btnColor->palette().color(QPalette::Button);
    m_pen.setColor(color);
    return m_pen;
}

QPen QWDialogPen::getPen(QPen iniPen, bool &ok)
{
    QWDialogPen* dlg = new QWDialogPen();
    dlg->setPen(iniPen);
    QPen pen;
    int ret = dlg->exec(); // 弹出对话框
    if (ret == QDialog::Accepted) {

        pen = dlg->getPen(); // 获取
        ok = true;
    }
    else {

        pen = iniPen;
        ok = false;
    }

    delete dlg; // 删除对话框对象
    return pen; //返回设置的QPen对象
}

静态函数getPen()里创建了一个QWDialogPen类的实例dlg,然后调用dlg→setPen(iniPen)进行初始化,运行对话框并获取返回值,若返回值为QDialog::Accepted,就调用dlg→getPen()获取设置属性后的QPen对象,最后删除对话框对象并返回设置的Qpen对象。所以,静态函数getPen()就是集成了普通方法调用对话框时创建对话框,设置初始值、获取对话框返回状态、获取返回删除对话框的过程,简化了调用代码。

QChart设置

QChart类的主要函数

| 分组 | 函数名 | 功能描述 |
| :—: | — | — |
| 图表外观 | void setTitle() | 设置图表标题,支持HTML |
| | void setTitleFont() | 设置图表标题字体 |
| | void setTitleBrush() | 设置比图表标题画刷 |
| | void setTheme() | 设置主题,定义了图表的配色 |
| | void setMargins() | 设置绘图区与图表边界的四个边距 |
| | QLegend* legend() | 返回图表的图例,是一个QLegend类对象 |
| | void setAnimationOptions() | 设置序列或坐标轴的动画效果 |
| 数据序列 | void addSeries() | 添加序列 |
| | QList>series() | 返回图表拥有的序列的列表 |
| | void removeSeries() | 移除一个序列,但并不删除序列对象 |
| | void removeAllSeries() | 移除并删除图表的所有序列 |
| 坐标轴 | void addAxis() | 为图表的某个方向添加坐标轴 |
| | QList> axes() | 返回某个方向的坐标轴列表 |
| | void setAxisX() | 设置某个序列的水平方向的坐标轴 |
| | void setAxisY() | 设置某个序列的垂直方向的坐标轴 |
| | void removeAxis() | 移除一个坐标轴 |
| | void createDefaultAxes() | 更具已添加的序列类型,创建缺省的坐标轴,前面已有的坐标轴会被删除 |

通过图表的设置界面可以设置标题的内容和字体,可以设置图例的位置,是否显示,字体颜色和边距;可以设置动画效果和主题。

setAnimationOptions(AnimationOptions options)函数设置图表的动画效果,输入参数是QChart::AnimationOption枚举类型,有以下取值:

  • QChart::NoAnimation:无动画效果;
  • QChart::GridAxisAnimations:背景网格有动画;
  • QChart::SeriesAnimations:序列有动画效果;
  • QChart::AllAnimations:都有动画效果。

主题是预定义的图表配色样式,是QChart::ChartTheme枚举类型,有多种取值。

图例是一个QLegend类对象,通过QChart::legend()可以获取图表的图例。图例是根据添加的序列自动生成的,但是可以修改一些属性,如:位置,文字和字体等。例如:

ui->chartView->chart()->legend()->setAlignment(Qt::AlignBottom);

void MainWindows::on_btnLegendFont_clicked() {

    QFont font = ui->chartView->chart()->legend()->font();
    bool ok = false;
    font = QFontDialog::getFont(&ok, font);
    if (ok) {
      ui->chartView->chart()->legend()->setFont(font);
    }
}

QLineSeries序列设置

在这个项目中,我们使用的是 QLineSeries,他是 QXYSeries 的子类,用于绘制二维数据的折线图。QLineSeries 的主要函数(包括从父类集成的函数,省略参数)如下:

| 分组 | 函数 | 功能描述 |
| :—: | — | — |
| 序列名称 | void setName () | 设置序列名称,支持 HTML 格式 |
| 图标 | QChart* chart () | 返回序列所属的图表对象 |
| 序列外观 | void setVisible () | 设置序列可见性 |
| | void show () | 显示序列,可见 |
| | void hide () | 隐藏序列,不可见 |
| | void setColor () | 设置序列的线条颜色 |
| | void setPen () | 设置绘制线条的笔画 |
| | void setBrush () | 设置绘制数据点的画刷 |
| | void setOpacity () | 设置序列的透明度,0 表示完全透明,1 表示不透明 |
| 数据点 | void setPointsVisible () | 设置数据点可见性 |
| | void append () | 添加一个数据点到序列 |
| | void insert () | 在某个位置插入数据点 |
| | void replace () | 替换某个数据点 |
| | void clear () | 清除所有数据点 |
| | void remove () | 删除某个数据点 |
| | void removePoints () | 从某个位置开始,删除指定数量的数据点 |
| | int count () | 数据点个数 |
| 数据点 | QPointF& at () | 返回某个位置的数据点 |
| | QList points () | 返回数据点列表 |
| | QVector pointsVector () | 返回数据点的向量,效率高 |
| 数据点标签 | void setPointLabelsVisible () | 设置数据点标签可见性 |
| | void setPointLabelsColor () | 设置数据点标签颜色 |
| | void setPointLabelsFont () | 设置数据点标签的字体 |
| | void setPointLabelsFormat () | 设置数据点标签的格式 |
| | void setPointLabelsClipping () | 设置标签的裁剪属性,缺省为 True,即绘图区外的标签被裁剪 |
| 坐标轴 | bool attachAxis () | 为序列附加一个坐标轴 |
| | bool detachAxis () | 接触一个附加的坐标轴 |
| | QListattachedAxes () | 返回附加的坐标轴列表 |
| 在我们的 demo 中队曲线序列进行属性设置的界面选择操作的 RedioButton 按钮选择操作序列,两个代码相同: | | |

void MainWindow::on_radioSeries0_clicked()

{
    if (ui->radioSeries0->isChecked()) {
        curSeries = (QLineSeries*)ui->chartView->chart()->series().at(0);
    }
    else {
        curSeries = (QLineSeries*)ui->chartView->chart()->series().at(1);

    }
    ui->editSeriesName->setText(curSeries->name()); // 序列名称
    ui->chkSeriesVisible->setChecked(curSeries->isVisible()); // 序列可见性
    ui->chkPointVisible->setChecked(curSeries->pointsVisible()); // 数据点可见性
    ui->sliderSeriesOpacity->setValue(curSeries->opacity() * 10); // 透明度
    ui->chkPointLableVisible->setChecked(curSeries->pointLabelsVisible()); // 数据点标签可见性

}

curSeries 是在 MainWindow 类里定义的私有变量,用于指向当前操作的序列。选择序列后,会将当前序列的一些属性显示到界面上,如序列名称、序列可见性、序列数据点可见性等。
数据点标签的格式设置使用函数 setPointLabelsFormat() ,有两种数据库在数据点标签重显示,有固定标签:

  • @xPoint,数据点的 X 值;
  • @yPoint,数据点的 Y 值。
    例如,使数据点标签只显示 Y 值,设置语句为:
curSeries->setPointLabelsFormat("@xPoint")

如果使数据点标签显示 (X,Y) 的值,设置语句为:

curSeries->setPointLabelsFormat("@xPoint,@yPoint")

为一个序列添加数据点,可以使用 append() 函数们也可以使用流操作符 <<,如 prepareData() 函数中添加一个数据点的不封可以改写为:

*series0 << QPointF(t, y1); // 序列添加数据点

为序列指定坐标轴,前面在 createChart() 函数中,使用 QChart 的函数为序列设置坐标轴:

chart->addAxis(axisX, Qt::AlignBottom);
chart->addAxis(axisY, Qt::AlignRight);
series0->attachAxis(axisX);
series0->attachAxis(axisY);
series1->attachAxis(axisX);
series1->attachAxis(axisY);

addAxis() 添加坐标轴到图表中,并且指定了方向。attachAxis() 函数为序列附加坐标轴。(需要注意的是,这是 Qt 5.12.1 之后的写法)

QValueAxis 坐标轴的设置

在这个 demo 中,我们使用 QValeAxis 类的坐标轴,也就是数值类型坐标轴,与 QLineSeries 正好配合使用。Qt 提供了好几种坐标轴类都是从 QAbstractAxis 类继承而来,这个我们前面说过了。QValueAxis 类的主要函数如下:(缺省了输入参数)

| 分组 | 函数 | 功能描述 |
| — | — | — |
| 坐标轴整体 | void setVisible () | 设置坐标轴可见性 |
| | Qt::Orientation orientation () | 返回坐标轴方向 |
| | void setMin () | 设置坐标轴最小值 |
| | void setMax () | 设置坐标轴最大值 |
| | void setRange () | 设置坐标轴范围 |
| 轴标题 | void setTitleVisible () | 设置标题可见性 |
| | void setTitleTest () | 设置标题文字 |
| | void setTitleFont () | 设置标题字体 |
| | void setTitleBrush () | 设置标题画刷 |
| 轴标签 | void setLabelFormat () | 设置轴标签格式 |
| | void setLabelsAngle () | 设置标签的角度(度) |
| | void setLabelsBrush () | 设置轴标签画刷 |
| | void setLabelsColor () | 设置轴标签颜色 |
| | void setLabelsFont () | 这种轴标签字体 |
| | void setLabelsVisible () | 设置轴标签文字是否可见 |
| 轴线和刻度线 | void setTickCount () | 设置坐标轴主刻度的个数 |
| | void setLineVisible () | 设置轴线和刻度线的可见性 |
| | void setLinePen () | 设置轴线和刻度线的画笔 |
| | void setLinePenColor () | 设置轴线和刻度线的颜色 |
| 主网格线 | void setGridLineColor () | 设置网格线的颜色 |
| | void setFridLinePen () | 设置网格线的画笔 |
| | void setGridLineVisible () | 设置网格线的可见性 |
| 次刻度和次网格线 | void setMinorTickCount () | 设置两个主刻度之间的次刻度的个数 |
| | void setMinorGridLineColor () | 设置次网格线的颜色 |
| | void setMinorGridLinePen () | 设置次网格线的画笔 |
| | void setMinorGridLineVisible () | 设置次网格线的可见性 |

QValueAxis 坐标轴有以下几个组成部分:

  • 坐标轴标题:坐标轴下方显示的文字,表示坐标轴名称,可以设置内容、字体、画刷和可见性。
  • 轴线和刻度线:轴线是从左到右表示轴的直线,刻度线是垂直于垂直于轴线的短线,包括主刻度线和次刻度线,主刻格式是 tickCount(),每两个主刻度之间的次刻度的个数是 minorTickCount()
  • 轴标签:在主刻度处显示数值标签文字,可以控制其数值格式、文字颜色和字体等。
  • 主网格线:在绘图区与主刻度相对应的网格线,可以设置颜色、线条的 Pen 属性和可见性等。
  • 次网格线:在绘图区与次刻度相对应的网格线,可以设置颜色、线条的 Pen 属性和可见性等。

坐标轴设置界面的 UI 如下:

在界面上我们可以选择需要操作的坐标轴的对象,两个 RadioButton 按钮的响应代码相同。为 radioX 按钮编写事件槽函数,另一个按钮的槽函数里只要调用这个槽函数即可,代码如下:

void MainWindow::on_radioX_clicked()
{
    QList<QAbstractAxis*> axes;
    if (ui->radioX->isChecked()) {
        axes = ui->chartView->chart()->axes(Qt::Horizontal);
    }
    else {
        axes = ui->chartView->chart()->axes(Qt::Vertical);
    }

    curAxis = (QValueAxis*)axes[0];

    ui->spinAxisMin->setValue(curAxis->min());
    ui->spinAxisMax->setValue(curAxis->max());

    ui->editAxisTitle->setText(curAxis->titleText());
    ui->chkBoxAxisTitle->setChecked(curAxis->isTitleVisible());

    ui->editAxisLabelFormat->setText(curAxis->labelFormat());
    ui->chkBoxLabelsVisible->setChecked(curAxis->labelsVisible());

    ui->chkGridLineVisible->setChecked(curAxis->isGridLineVisible());
    ui->chkAxisLineVisible->setChecked(curAxis->isLineVisible());

    ui->spinMinorTickCount->setValue(curAxis->minorTickCount());
    ui->chkMinorTickVisible->setChecked(curAxis->isMinorGridLineVisible());
}

void MainWindow::on_radioY_clicked()
{
    on_radioX_clicked();
}

程序中 curAxis 是在 MainWindow 类中定义的私有变量,用于表示当前操作的坐标轴对象。获取对象后,首先获得轴对象的各种属性并显示在界面上。
在编辑框 editAxisLabelFormat 里设置格式字符串,例如“%. 2f”,作为 QValueAxis::setLableFormat() 的输入参数。格式字符串的定义与 printf() 函数的格式字符串定义一样。