跳转到内容
新建笔记

Qt Charts:轴关联、背景层次与对象所有权

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 顶层透明窗口

这个程序先创建视图,再配置图表;第一条系列预先装入数据,随后两条系列都显式连接到相同的横纵轴。程序最后才设置背景,展示这些操作不要求按原笔记的固定顺序排列。

main.cpp
#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();
}
CMakeLists.txt
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。也不要在图表仍持有对象时,让别的独立所有者按另一套生命周期释放它。