feat:heapmap with value;fix:qcustomplot warning

This commit is contained in:
2025-11-04 10:47:41 +08:00
parent f411ab21cb
commit a07ff7d6b7
9 changed files with 852 additions and 389 deletions

View File

@@ -8,13 +8,17 @@
#include "heatmap.hh"
#include "modern-qt/utility/theme/theme.hh"
#include "modern-qt/widget/sliders.hh"
#include <memory>
#include <qcolor.h>
#include <qdebug.h>
using namespace creeper::plot_widget::internal;
struct BasicPlot::Impl {
explicit Impl(BasicPlot& self) noexcept : self{self}, initialized(false), matrix_size(QSize{3, 4}) {}
#include "qcustomplot/qcustomplot.h"
#include <algorithm>
#include <memory>
#include <qcolor.h>
#include <qdebug.h>
#include <qfont.h>
#include <vector>
using namespace creeper::plot_widget::internal;
struct BasicPlot::Impl {
explicit Impl(BasicPlot& self) noexcept : self{self}, initialized(false), matrix_size(QSize{3, 4}) {}
public:
auto set_xlabel_text(const QString& text) -> void {
@@ -51,31 +55,31 @@ public:
});
}
auto set_color_gradient_range(const double& min, const double& max) -> void {
if (initialized && self.plottableCount() > 0) {
auto* cpmp = static_cast<QCPColorMap*>(self.plottable(0));
cpmp->setDataRange(QCPRange(min, max));
self.replot();
}
color_min = min;
color_max = max;
auto set_color_gradient_range(const double& min, const double& max) -> void {
if (initialized && color_map) {
color_map->setDataRange(QCPRange(min, max));
self.replot();
}
color_min = min;
color_max = max;
}
auto set_data(const QVector<PointData>& data) -> void {
data_points = data;
if (initialized) {
update_plot_data();
}
}
auto initialize_plot() -> void {
if (initialized) return;
QCPColorMap* cpmp = new QCPColorMap(self.xAxis, self.yAxis);
cpmp->data()->setSize(matrix_size.width(), matrix_size.height());
cpmp->data()->setRange(QCPRange(0.5, matrix_size.width() - 0.5),
QCPRange(0.5, matrix_size.height() - 0.5));
auto set_data(const QVector<PointData>& data) -> void {
data_points = data;
if (initialized && color_map) {
update_plot_data();
}
}
auto initialize_plot() -> void {
if (initialized) return;
color_map = new QCPColorMap(self.xAxis, self.yAxis);
auto* cpmp = color_map;
cpmp->data()->setSize(matrix_size.width(), matrix_size.height());
cpmp->data()->setRange(QCPRange(0.5, matrix_size.width() - 0.5),
QCPRange(0.5, matrix_size.height() - 0.5));
QSharedPointer<QCPAxisTickerText> xticker(new QCPAxisTickerText);
QSharedPointer<QCPAxisTickerText> yticker(new QCPAxisTickerText);
xticker->setSubTickCount(1);
@@ -103,23 +107,35 @@ QCPColorMap* cpmp = new QCPColorMap(self.xAxis, self.yAxis);
if (!xlabel.isEmpty()) self.xAxis->setLabel(xlabel);
if (!ylabel.isEmpty()) self.yAxis->setLabel(ylabel);
QCPColorScale* color_scale = new QCPColorScale(&self);
color_scale->setType(QCPAxis::atBottom);
self.plotLayout()->addElement(1, 0, color_scale);
cpmp->setColorScale(color_scale);
QCPColorGradient gradient;
gradient.setColorStopAt(0.0, QColor(246, 239, 166)); // F6EFA6
gradient.setColorStopAt(1.0, QColor(191, 68, 76)); // BF444C
if (!color_scale) {
color_scale = new QCPColorScale(&self);
color_scale->setType(QCPAxis::atBottom);
}
if (self.plotLayout()) {
auto existing = self.plotLayout()->element(1, 0);
if (existing && existing != color_scale) {
self.plotLayout()->take(existing);
}
if (!existing || existing != color_scale) {
self.plotLayout()->addElement(1, 0, color_scale);
}
}
cpmp->setColorScale(color_scale);
QCPColorGradient gradient;
gradient.setColorStopAt(0.0, QColor(240, 246, 255)); // 低值淡色
gradient.setColorStopAt(0.35, QColor(142, 197, 252));
gradient.setColorStopAt(0.7, QColor(56, 128, 199));
gradient.setColorStopAt(1.0, QColor(8, 36, 95)); // 高值深色
cpmp->setGradient(gradient);
cpmp->setDataRange(QCPRange(color_min, color_max));
cpmp->setInterpolate(false);
QCPMarginGroup *margin_group = new QCPMarginGroup(&self);
self.axisRect()->setMarginGroup(QCP::msLeft | QCP::msRight, margin_group);
color_scale->setMarginGroup(QCP::msLeft | QCP::msRight, margin_group);
QCPMarginGroup *margin_group = new QCPMarginGroup(&self);
self.axisRect()->setMarginGroup(QCP::msLeft | QCP::msRight, margin_group);
color_scale->setMarginGroup(QCP::msLeft | QCP::msRight, margin_group);
initialized = true;
if (!data_points.isEmpty()) {
@@ -127,33 +143,46 @@ QCPColorMap* cpmp = new QCPColorMap(self.xAxis, self.yAxis);
}
}
auto reset_plot() -> void {
// 清除所有绘图元素
self.clearPlottables();
self.clearGraphs();
self.clearItems();
self.clearFocus();
// 重新初始化
initialized = false;
initialize_plot();
}
auto update_plot_data() -> void {
if (!initialized || self.plottableCount() == 0) return;
auto* cpmp = static_cast<QCPColorMap*>(self.plottable(0));
// 设置新数据
for (const auto& item : data_points) {
if (item.x >= 0 && item.x < matrix_size.width() &&
item.y >= 0 && item.y < matrix_size.height()) {
cpmp->data()->setCell(item.x, item.y, item.z);
}
}
// 重绘
self.replot();
}
auto reset_plot() -> void {
// 清除所有绘图元素
self.clearPlottables();
self.clearGraphs();
self.clearItems();
self.clearFocus();
color_map = nullptr;
cell_labels.clear();
// 重新初始化
initialized = false;
initialize_plot();
}
auto update_plot_data() -> void {
if (!initialized || !color_map) return;
ensure_labels();
const int width = matrix_size.width();
const int height = matrix_size.height();
const int expected = width * height;
std::vector<double> values(static_cast<std::size_t>(expected), 0.0);
// 设置新数据
for (const auto& item : data_points) {
if (item.x >= 0 && item.x < matrix_size.width() &&
item.y >= 0 && item.y < matrix_size.height()) {
color_map->data()->setCell(item.x, item.y, item.z);
const int idx = static_cast<int>(item.y) * width + static_cast<int>(item.x);
if (idx >= 0 && idx < expected) {
values[static_cast<std::size_t>(idx)] = item.z;
}
}
}
update_label_values(values);
// 重绘
self.replot();
}
auto is_plot_initialized() const -> bool {
return initialized;
@@ -167,11 +196,87 @@ private:
QString xlabel;
QString ylabel;
QSize matrix_size;
QVector<PointData> data_points;
double color_min = 0.0;
double color_max = 800.0;
bool initialized;
BasicPlot& self;
};
#endif // TOUCHSENSOR_HEATMAP_IMPL_HH
QVector<PointData> data_points;
double color_min = 0.0;
double color_max = 800.0;
bool initialized;
BasicPlot& self;
QCPColorScale* color_scale = nullptr;
QCPColorMap* color_map = nullptr;
QVector<QCPItemText*> cell_labels;
void clear_labels() {
for (auto* label : cell_labels) {
if (label) {
self.removeItem(label);
}
}
cell_labels.clear();
}
void ensure_labels() {
const int width = matrix_size.width();
const int height = matrix_size.height();
const int expected = width * height;
if (expected <= 0) {
clear_labels();
return;
}
if (cell_labels.size() == expected) {
return;
}
clear_labels();
cell_labels.reserve(expected);
for (int y = 0; y < height; ++y) {
for (int x = 0; x < width; ++x) {
auto* label = new QCPItemText(&self);
label->position->setType(QCPItemPosition::ptPlotCoords);
label->setClipToAxisRect(true);
label->setClipAxisRect(self.axisRect());
label->setPositionAlignment(Qt::AlignCenter);
label->position->setCoords(x + 0.5, y + 0.5);
label->setBrush(Qt::NoBrush);
label->setPen(Qt::NoPen);
QFont font = label->font();
if (font.pointSize() > 0) {
font.setPointSize(std::max(font.pointSize() - 1, 6));
} else {
font.setPointSize(8);
}
label->setFont(font);
label->setColor(Qt::black);
label->setSelectable(false);
cell_labels.push_back(label);
}
}
}
void update_label_values(const std::vector<double>& values) {
const int width = matrix_size.width();
const int height = matrix_size.height();
const double range = std::max(color_max - color_min, 1.0);
const int expected = width * height;
for (int idx = 0; idx < expected && idx < cell_labels.size(); ++idx) {
auto* label = cell_labels[idx];
if (!label) {
continue;
}
const double value = values.size() > static_cast<std::size_t>(idx)
? values[static_cast<std::size_t>(idx)]
: 0.0;
label->setText(QString::number(value, 'f', 0));
const double normalized = std::clamp((value - color_min) / range, 0.0, 1.0);
if (normalized > 0.55) {
label->setColor(Qt::white);
} else {
label->setColor(QColor(25, 25, 25));
}
const int x = idx % width;
const int y = idx / width;
label->position->setCoords(x + 0.5, y + 0.5);
}
}
};
#endif // TOUCHSENSOR_HEATMAP_IMPL_HH