Qt Charts 的初始化不必遵守一条固定流水线。真正需要保证的是对象已经存在、轴与系列属于同一个图表、系列正确关联到所需的轴,以及销毁时遵守所有权。图例、背景、视图和数据的设置通常可以穿插进行。
本文面向 C++17、Qt 6.10 的 Widgets / Qt Charts。它使用 Qt6::Charts 和 QChartView;同名的 Qt Graphs 系列类是另一套 API,不能混用文档。版本与极坐标接口见Qt Charts 版本与极坐标。
区分依赖关系和普通设置顺序
跳转到“区分依赖关系和普通设置顺序”下表对应原笔记的十种场景。前两列说明需要维护的关系,最后一列指出哪些设置没有原先声称的先后限制。
| 场景 | 应保证的关系 | 不应写成硬性规则 |
|---|---|---|
| 轴与系列关联 | 先让轴和系列加入同一个图表,再调用并检查 attachAxis() | addAxis() 和 addSeries() 两者之间没有统一先后要求 |
| 系列加入图表 | 系列加入图表后才有图表上下文 | addSeries() 不会自动把既有轴附加到新系列 |
| 图例 | 图表拥有图例,系列决定其标记内容 | 图例可见性和对齐方式可以在添加系列前设置 |
| 绘图区背景 | 用画刷和可见性属性控制 | 不要求早于添加轴和系列 |
| 图表视图 | QChartView 管理当前图表 | 可以先把空图表交给视图,再填充图表 |
| 添加数据 | 数据保存在系列中,轴关联决定坐标映射 | 可以先给系列填数据,再加入图表和关联轴 |
| 清除数据 | series->clear() 清空系列点集 | 通常不必再手动 chart->update() 才能让标准图表获知变化 |
| 动态数据 | 在 GUI 线程修改已显示图表的系列 | append()、replace() 等已有通知机制;高频数据宜考虑批量更新 |
| 多系列与多轴 | 每个系列显式关联其所需的轴 | 不能因为第二个系列刚添加,就假定它会继承第一个系列的轴 |
| 销毁 | 由视图释放当前图表,图表释放仍拥有的系列和轴 | 不需要先手动逐个删除系列和轴,再删除图表 |
attachAxis() 返回 bool,失败不能只靠界面“看起来有曲线”判断。没有关联轴的系列也可能被按自身范围铺满绘图区,和其他系列产生不同坐标尺度。createDefaultAxes() 可以在添加系列后生成并关联默认轴,但会删除已有轴,且不会自动服务后来新增的系列。QChart 关联与默认轴、attachAxis
背景分为图表、绘图区和视图层
跳转到“背景分为图表、绘图区和视图层”| 原调用 | 实际作用 | 不能据此推出 |
|---|---|---|
fontChart->setBackgroundRoundness(0) | 设置图表外层背景圆角的直径为零 | 不会因此变透明 |
fontChart->setBackgroundVisible(false) | 隐藏图表外层背景 | 不会同时关闭独立的绘图区背景、坐标轴或曲线 |
fontChartView->setStyleSheet("background: transparent;") | 设置 Widgets 视图一侧的样式背景 | 不会直接改变 QChart 内部图元的画刷 |
fontChartView->setAttribute(Qt::WA_TranslucentBackground) | 涉及窗口的透明背景能力 | 不是普通子控件显示父背景的通用必需步骤 |
绘图区另外由 setPlotAreaBackgroundBrush() 和 setPlotAreaBackgroundVisible() 控制。视图还有 QGraphicsView 背景画刷及 viewport 的背景填充。要让图表融入父窗口,应逐层检查哪一层仍在画不透明颜色;不能把“透明颜色”解释为一定仍能看见某个背景轮廓。
WA_TranslucentBackground 主要用于支持合成的窗口系统中的顶层窗口透明。官方文档还列出 Windows、macOS 的无边框要求和 X11 合成条件。普通的子 QChartView 显示父窗口背景,通常不需要给它加这个属性。QWidget 顶层透明窗口
可运行的双系列示例
跳转到“可运行的双系列示例”这个程序先创建视图,再配置图表;第一条系列预先装入数据,随后两条系列都显式连接到相同的横纵轴。程序最后才设置背景,展示这些操作不要求按原笔记的固定顺序排列。
#include <QApplication>#include <QBrush>#include <QHBoxLayout>#include <QPainter>#include <QPalette>#include <QPushButton>#include <QVBoxLayout>#include <QWidget>#include <QtCharts/QChart>#include <QtCharts/QChartView>#include <QtCharts/QLegend>#include <QtCharts/QLineSeries>#include <QtCharts/QValueAxis>
#include <stdexcept>
class ChartWindow : public QWidget {public: explicit ChartWindow(QWidget* parent = nullptr) : QWidget(parent), fontChart(new QChart), fontChartView(new QChartView(fontChart, this)), series1(new QLineSeries), series2(new QLineSeries), axisX(new QValueAxis), axisY(new QValueAxis) { auto* layout = new QVBoxLayout(this); layout->addWidget(fontChartView); auto* controls = new QHBoxLayout; layout->addLayout(controls); auto* appendButton = new QPushButton("Append", this); auto* clearButton = new QPushButton("Clear", this); auto* backgroundButton = new QPushButton("Toggle plot background", this); controls->addWidget(appendButton); controls->addWidget(clearButton); controls->addWidget(backgroundButton);
fontChart->setAnimationOptions(QChart::NoAnimation); fontChart->legend()->setVisible(true); fontChart->legend()->setAlignment(Qt::AlignBottom); series1->setName("Preloaded data"); series2->setName("Data after attachment"); series1->append(0, 0); series1->append(1, 1); axisX->setRange(0, 100); axisY->setRange(0, 100);
fontChart->addAxis(axisX, Qt::AlignBottom); fontChart->addSeries(series1); fontChart->addAxis(axisY, Qt::AlignLeft); fontChart->addSeries(series2); for (auto* series : {series1, series2}) { if (!series->attachAxis(axisX) || !series->attachAxis(axisY)) { throw std::runtime_error("axis attachment failed"); } } series2->append(0, 20); series2->append(1, 21);
QPalette colors = palette(); colors.setColor(QPalette::Window, QColor(215, 230, 245)); setPalette(colors); setAutoFillBackground(true); fontChart->setBackgroundRoundness(0); fontChart->setBackgroundVisible(false); fontChart->setDropShadowEnabled(false); fontChart->setPlotAreaBackgroundBrush(QBrush(Qt::lightGray)); fontChart->setPlotAreaBackgroundVisible(true); fontChartView->setStyleSheet("background: transparent; border: none;"); fontChartView->setBackgroundBrush(QBrush(Qt::NoBrush)); fontChartView->viewport()->setAutoFillBackground(false); fontChartView->setRenderHint(QPainter::Antialiasing);
connect(appendButton, &QPushButton::clicked, this, [this] { appendPoint(); }); connect(clearButton, &QPushButton::clicked, this, [this] { clearPoints(); }); connect(backgroundButton, &QPushButton::clicked, this, [this] { fontChart->setPlotAreaBackgroundVisible( !fontChart->isPlotAreaBackgroundVisible()); }); }
void appendPoint() { if (nextX > 100) { clearPoints(); } series1->append(nextX, nextX); series2->append(nextX, 100 - nextX); ++nextX; } void clearPoints() { series1->clear(); series2->clear(); nextX = 0; }
QChart* fontChart; QChartView* fontChartView; QLineSeries* series1; QLineSeries* series2; QValueAxis* axisX; QValueAxis* axisY;private: int nextX = 2;};
int main(int argc, char* argv[]) { QApplication app(argc, argv); ChartWindow window; window.setWindowTitle("Chart associations and backgrounds"); window.resize(800, 520); window.show(); return app.exec();}cmake_minimum_required(VERSION 3.16)project(chart_associations LANGUAGES CXX)set(CMAKE_CXX_STANDARD 17)set(CMAKE_CXX_STANDARD_REQUIRED ON)set(CMAKE_CXX_EXTENSIONS OFF)find_package(Qt6 REQUIRED COMPONENTS Widgets Charts)add_executable(chart-example main.cpp)target_link_libraries(chart-example PRIVATE Qt6::Widgets Qt6::Charts)点击 Append 增加点,Clear 清空两条系列;这两个操作都没有额外调用 chart->update()。Toggle plot background 只切换灰色绘图区背景,曲线、坐标轴和图例仍然存在。样式设置之后若调用 setTheme(),主题可能覆盖原有系列样式或改变阴影设置,应重新应用需要保留的自定义外观。
释放与替换遵守所有权
跳转到“释放与替换遵守所有权”示例中的指针成员用于访问已由 Qt 管理的对象,不是独立释放责任。窗口销毁时,子视图销毁当前 QChart,该图表再销毁仍归它所有的系列、轴和图例。
removeSeries()、removeAxis() 是移除并释放所有权,调用方要安排后续释放或转移;removeAllSeries() 则会同时删除这些系列。对一个视图调用 setChart(newChart) 时,视图接管新图表,释放旧图表的所有权,旧图表不会因此自动删除。QChart 所有权、QChartView 替换图表
若使用 QPolarChart,关联和所有权原则仍然适用,但添加轴时应使用 PolarOrientationAngular、PolarOrientationRadial,不能直接照搬笛卡尔图的 Qt::AlignBottom、Qt::AlignLeft。也不要在图表仍持有对象时,让别的独立所有者按另一套生命周期释放它。