203 lines
6.2 KiB
C++
203 lines
6.2 KiB
C++
//
|
|
// Created by Codex on 2025/12/05.
|
|
//
|
|
|
|
#include "vector_field.hh"
|
|
#include <algorithm>
|
|
#include <cmath>
|
|
#include <vector>
|
|
|
|
using creeper::vector_widget::internal::VectorPlot;
|
|
|
|
VectorPlot::VectorPlot() {
|
|
setBackground(Qt::transparent);
|
|
}
|
|
|
|
VectorPlot::~VectorPlot() = default;
|
|
|
|
void VectorPlot::load_theme_manager(creeper::ThemeManager& mgr) {
|
|
mgr.append_handler(this, [this](const creeper::ThemeManager& manager) {
|
|
scheme_ = manager.color_scheme();
|
|
apply_color_scheme();
|
|
if (initialized_) {
|
|
replot();
|
|
}
|
|
});
|
|
}
|
|
|
|
void VectorPlot::set_matrix_size(const QSize& size) {
|
|
if (size == matrix_size_) {
|
|
return;
|
|
}
|
|
matrix_size_ = size;
|
|
reset_plot();
|
|
}
|
|
|
|
void VectorPlot::set_data(const QVector<PointData>& data) {
|
|
data_points_ = data;
|
|
if (initialized_) {
|
|
update_vectors();
|
|
}
|
|
}
|
|
|
|
void VectorPlot::paintEvent(QPaintEvent* event) {
|
|
if (!initialized_) {
|
|
initialize_plot();
|
|
}
|
|
QCustomPlot::paintEvent(event);
|
|
}
|
|
|
|
void VectorPlot::initialize_plot() {
|
|
if (initialized_) {
|
|
return;
|
|
}
|
|
|
|
xAxis->setVisible(false);
|
|
yAxis->setVisible(false);
|
|
xAxis->grid()->setPen(Qt::NoPen);
|
|
yAxis->grid()->setPen(Qt::NoPen);
|
|
xAxis->setRange(0.0, std::max(1, matrix_size_.width()));
|
|
yAxis->setRange(0.0, std::max(1, matrix_size_.height()));
|
|
|
|
xAxis->setSubTicks(true);
|
|
yAxis->setSubTicks(true);
|
|
xAxis->setTickLength(0);
|
|
yAxis->setTickLength(0);
|
|
xAxis->setSubTickLength(4);
|
|
yAxis->setSubTickLength(4);
|
|
|
|
initialized_ = true;
|
|
apply_color_scheme();
|
|
ensure_arrows();
|
|
update_vectors();
|
|
}
|
|
|
|
void VectorPlot::reset_plot() {
|
|
clearItems();
|
|
primary_arrow_ = nullptr;
|
|
initialized_ = false;
|
|
initialize_plot();
|
|
}
|
|
|
|
void VectorPlot::ensure_arrows() {
|
|
// no-op: legacy multi-arrow support removed
|
|
}
|
|
|
|
void VectorPlot::ensure_primary_arrow() {
|
|
if (primary_arrow_) {
|
|
return;
|
|
}
|
|
primary_arrow_ = new QCPItemLine(this);
|
|
primary_arrow_->start->setType(QCPItemPosition::ptPlotCoords);
|
|
primary_arrow_->end->setType(QCPItemPosition::ptPlotCoords);
|
|
primary_arrow_->setClipToAxisRect(true);
|
|
primary_arrow_->setClipAxisRect(axisRect());
|
|
primary_arrow_->setHead(QCPLineEnding(QCPLineEnding::esSpikeArrow, 14, 8));
|
|
primary_arrow_->setTail(QCPLineEnding(QCPLineEnding::esNone));
|
|
apply_color_scheme();
|
|
}
|
|
|
|
void VectorPlot::apply_color_scheme() {
|
|
QColor pen_color = arrow_color_;
|
|
if (scheme_.has_value() && scheme_->primary.isValid()) {
|
|
pen_color = scheme_->primary;
|
|
}
|
|
|
|
QPen strong_pen(pen_color);
|
|
strong_pen.setWidthF(8.0); // 稍细一点但仍然饱满
|
|
strong_pen.setCapStyle(Qt::FlatCap);
|
|
strong_pen.setJoinStyle(Qt::MiterJoin);
|
|
if (primary_arrow_) {
|
|
primary_arrow_->setPen(strong_pen);
|
|
}
|
|
}
|
|
|
|
void VectorPlot::update_vectors() {
|
|
if (!initialized_) {
|
|
return;
|
|
}
|
|
ensure_primary_arrow();
|
|
|
|
const int width = std::max(1, matrix_size_.width());
|
|
const int height = std::max(1, matrix_size_.height());
|
|
const int expected = width * height;
|
|
if (expected <= 0 || !primary_arrow_) {
|
|
replot();
|
|
return;
|
|
}
|
|
|
|
std::vector<double> values(static_cast<std::size_t>(expected), 0.0);
|
|
for (const auto& item: data_points_) {
|
|
const int x = static_cast<int>(item.x);
|
|
const int y = static_cast<int>(item.y);
|
|
if (x >= 0 && x < width && y >= 0 && y < height) {
|
|
const int idx = y * width + x;
|
|
values[static_cast<std::size_t>(idx)] = item.z;
|
|
}
|
|
}
|
|
|
|
auto value_at = [&](int x, int y) -> double {
|
|
if (x < 0 || x >= width || y < 0 || y >= height) {
|
|
return 0.0;
|
|
}
|
|
return values[static_cast<std::size_t>(y * width + x)];
|
|
};
|
|
|
|
std::vector<std::pair<double, double>> grads(static_cast<std::size_t>(expected), { 0.0, 0.0 });
|
|
double max_mag = 0.0;
|
|
double sum_gx = 0.0;
|
|
double sum_gy = 0.0;
|
|
for (int y = 0; y < height; ++y) {
|
|
for (int x = 0; x < width; ++x) {
|
|
const double gx = 0.5 * (value_at(x + 1, y) - value_at(x - 1, y));
|
|
const double gy = 0.5 * (value_at(x, y + 1) - value_at(x, y - 1));
|
|
const double mag = std::sqrt(gx * gx + gy * gy);
|
|
grads[static_cast<std::size_t>(y * width + x)] = { gx, gy };
|
|
max_mag = std::max(max_mag, mag);
|
|
sum_gx += gx;
|
|
sum_gy += gy;
|
|
}
|
|
}
|
|
|
|
const double scale = (max_mag > 1e-6) ? (0.35 / max_mag) : 0.0;
|
|
const bool fallback = max_mag <= 1e-6;
|
|
const double mid_x = static_cast<double>(width) * 0.5;
|
|
const double mid_y = static_cast<double>(height) * 0.5;
|
|
|
|
double dir_x = sum_gx;
|
|
double dir_y = sum_gy;
|
|
if (fallback || std::abs(dir_x) + std::abs(dir_y) < 1e-6) {
|
|
dir_x = 0.0;
|
|
dir_y = -1.0; // 默认向上指,保证可见
|
|
}
|
|
double dir_len = std::sqrt(dir_x * dir_x + dir_y * dir_y);
|
|
if (dir_len < 1e-6) {
|
|
dir_x = 0.0;
|
|
dir_y = -1.0;
|
|
dir_len = 1.0;
|
|
}
|
|
dir_x /= dir_len;
|
|
dir_y /= dir_len;
|
|
const double arrow_len = 0.48 * std::min(width, height); // 稍长的指针
|
|
const double tail_ratio = 0.25; // 穿过中心的尾巴略长
|
|
const double tail_len = arrow_len * tail_ratio;
|
|
const double head_len = arrow_len - tail_len;
|
|
const double backoff = std::max(0.5, head_len * 0.12); // 轻微回缩,避免方头顶出
|
|
const double head_base = std::max(0.0, head_len - backoff);
|
|
const double cx = static_cast<double>(width) * 0.5;
|
|
const double cy = static_cast<double>(height) * 0.5;
|
|
if (primary_arrow_) {
|
|
primary_arrow_->start->setCoords(cx - dir_x * tail_len, cy - dir_y * tail_len);
|
|
primary_arrow_->end->setCoords(cx + dir_x * head_base, cy + dir_y * head_base);
|
|
primary_arrow_->setVisible(true);
|
|
}
|
|
|
|
replot();
|
|
}
|
|
|
|
using namespace creeper;
|
|
|
|
void VectorFieldPlot::paintEvent(QPaintEvent* event) {
|
|
vector_widget::internal::VectorPlot::paintEvent(event);
|
|
}
|