// // Created by Lenn on 2025/10/17. // #ifndef TOUCHSENSOR_HEATMAP_IMPL_HH #define TOUCHSENSOR_HEATMAP_IMPL_HH #include "heatmap.hh" #include "creeper-qt/utility/theme/theme.hh" #include "creeper-qt/widget/sliders.hh" #include "qcustomplot/qcustomplot.h" #include #include #include #include #include #include #include using namespace creeper::plot_widget::internal; struct BasicPlot::Impl { explicit Impl(BasicPlot& self) noexcept : matrix_size(QSize{3, 4}) , color_min(0.0) , color_max(800.0) , show_labels(false) , initialized(false) , self{self} { } public: std::optional scheme; auto set_xlabel_text(const QString& text) -> void { xlabel = text; if (initialized) { self.xAxis->setLabel(text); self.replot(); } } auto set_ylabel_text(const QString& text) -> void { ylabel = text; if (initialized) { self.yAxis->setLabel(text); self.replot(); } } auto set_matrix_size(const QSize& size) -> void { matrix_size = size; const int expected = std::max(0, matrix_size.width() * matrix_size.height()); last_values.assign(static_cast(expected), 0.0); if (initialized) { reset_plot(); if (!data_points.isEmpty()) { set_data(data_points); } else if (show_labels) { sync_labels(last_values); self.replot(); } } } auto load_theme_manager(ThemeManager& mgr) -> void { mgr.append_handler(&self, [this](const ThemeManager& mgr) { scheme = mgr.color_scheme(); apply_color_scheme(); if (initialized) { self.replot(); } }); } 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& data) -> void { data_points = data; if (initialized && color_map) { update_plot_data(); } } auto set_labels_visible(bool visible) -> void { show_labels = visible; if (initialized) { sync_labels(last_values); self.replot(); } } auto labels_visible() const -> bool { return show_labels; } 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 xticker(new QCPAxisTickerText); QSharedPointer yticker(new QCPAxisTickerText); xticker->setSubTickCount(1); yticker->setSubTickCount(1); self.xAxis->setVisible(false); self.yAxis->setVisible(false); self.xAxis->setTicker(xticker); self.yAxis->setTicker(yticker); self.xAxis->grid()->setPen(Qt::NoPen); self.yAxis->grid()->setPen(Qt::NoPen); self.xAxis->grid()->setSubGridVisible(true); self.yAxis->grid()->setSubGridVisible(true); self.xAxis->setSubTicks(true); self.yAxis->setSubTicks(true); self.xAxis->setTickLength(0); self.yAxis->setTickLength(0); self.xAxis->setSubTickLength(6); self.yAxis->setSubTickLength(6); self.xAxis->setRange(0, matrix_size.width()); self.yAxis->setRange(0, matrix_size.height()); if (!xlabel.isEmpty()) self.xAxis->setLabel(xlabel); if (!ylabel.isEmpty()) self.yAxis->setLabel(ylabel); 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(0, 176, 80)); gradient.setColorStopAt(0.5, QColor(255, 214, 10)); gradient.setColorStopAt(1.0, QColor(204, 0, 0)); 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); initialized = true; apply_color_scheme(); if (!data_points.isEmpty()) { update_plot_data(); } } auto reset_plot() -> void { // 清除所有绘图元素 clear_labels(); self.clearPlottables(); self.clearGraphs(); self.clearItems(); self.clearFocus(); color_map = nullptr; // 重新初始化 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 values(static_cast(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(item.y) * width + static_cast(item.x); if (idx >= 0 && idx < expected) { values[static_cast(idx)] = item.z; } } } last_values = values; sync_labels(last_values); // 重绘 self.replot(); } auto is_plot_initialized() const -> bool { return initialized; } auto get_matrix_size() const -> QSize { return matrix_size; } private: QString xlabel; QString ylabel; QSize matrix_size; QVector data_points; std::vector last_values; double color_min = 0.0; double color_max = 800.0; bool show_labels = false; bool initialized; BasicPlot& self; QCPColorScale* color_scale = nullptr; QCPColorMap* color_map = nullptr; QVector cell_labels; QColor label_text_color = QColor(0, 0, 0); void apply_color_scheme() { QColor text_color = QColor(30, 30, 30); if (scheme.has_value()) { if (scheme->on_surface.isValid()) { text_color = scheme->on_surface; } } label_text_color = text_color; const auto pen = QPen(text_color); self.xAxis->setTickLabelColor(text_color); self.yAxis->setTickLabelColor(text_color); self.xAxis->setLabelColor(text_color); self.yAxis->setLabelColor(text_color); self.xAxis->setBasePen(pen); self.yAxis->setBasePen(pen); self.xAxis->setTickPen(pen); self.yAxis->setTickPen(pen); if (color_scale && color_scale->axis()) { color_scale->axis()->setTickLabelColor(text_color); color_scale->axis()->setLabelColor(text_color); color_scale->axis()->setBasePen(pen); color_scale->axis()->setTickPen(pen); } // 已有标签更新 for (auto* label : cell_labels) { if (!label) continue; label->setColor(label_text_color); } } 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(label_text_color); label->setSelectable(false); cell_labels.push_back(label); } } } void update_label_values(const std::vector& values) { const int width = matrix_size.width(); const int height = matrix_size.height(); 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(idx) ? values[static_cast(idx)] : 0.0; label->setText(QString::number(value, 'f', 0)); const int x = idx % width; const int y = idx / width; label->position->setCoords(x + 0.5, y + 0.5); } } void sync_labels(const std::vector& values) { if (!initialized) { return; } if (show_labels) { ensure_labels(); update_label_values(values); } else if (!cell_labels.isEmpty()) { clear_labels(); } } }; #endif // TOUCHSENSOR_HEATMAP_IMPL_HH