// // Created by Codex on 2025/12/05. // #include "vector_field.hh" #include #include #include #include #include #include #include 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& data) { data_points_ = data; if (initialized_) { update_vectors(); } } void VectorPlot::paintEvent(QPaintEvent* event) { if (!initialized_) { initialize_plot(); } QCustomPlot::paintEvent(event); // Custom glossy arrow rendering const auto rect = axisRect(); if (!rect) { return; } QPainter painter(this); painter.setRenderHint(QPainter::Antialiasing, true); const QRectF plot_rect = rect->rect(); const double side_pad = plot_rect.width() * 0.24; QRectF area = plot_rect.adjusted(side_pad, 8.0, -side_pad, -8.0); const double cx_canvas = area.center().x(); const double cy_canvas = area.center().y(); const double scale = 0.72; // overall shrink to make it slimmer area.setWidth(area.width() * scale); area.setHeight(area.height() * scale); area.moveCenter(QPointF(cx_canvas, cy_canvas)); if (area.width() <= 0.0 || area.height() <= 0.0) { return; } const double w = area.width(); const double h = area.height(); const double cx = area.center().x(); const double shaft_w = w * 0.18; const double shaft_r = shaft_w * 0.25; const double head_h = h * 0.36; const double head_w = w * 0.52; const double shaft_h = h - head_h; const double shaft_x = cx - shaft_w * 0.5; const double shaft_y = area.top(); const double head_base_y = shaft_y + shaft_h - shaft_r * 0.4; // overlap slightly to avoid seam QPainterPath shaft; shaft.addRoundedRect(QRectF(shaft_x, shaft_y, shaft_w, shaft_h), shaft_r, shaft_r); QPainterPath head; head.moveTo(cx - head_w * 0.5, head_base_y); head.lineTo(cx + head_w * 0.5, head_base_y); head.lineTo(cx, area.bottom()); head.closeSubpath(); QPainterPath arrow = shaft.united(head); // Vibrant orange palette (fixed) QColor base_color(255, 140, 0); QColor highlight = base_color.lighter(165); QColor mid = base_color; QColor shadow = base_color.darker(180); QLinearGradient body_grad(area.topLeft(), QPointF(area.left(), area.bottom())); body_grad.setColorAt(0.0, highlight); body_grad.setColorAt(0.28, mid); body_grad.setColorAt(0.72, base_color.darker(110)); body_grad.setColorAt(1.0, shadow); const double angle_deg = std::atan2(arrow_dir_.y(), arrow_dir_.x()) * 180.0 / 3.14159265358979323846 - 90.0; QTransform transform; transform.translate(cx, (shaft_y + head_base_y + area.bottom()) / 3.0); // approximate center transform.rotate(angle_deg); transform.translate(-cx, -(shaft_y + head_base_y + area.bottom()) / 3.0); auto draw_with_transform = [&](const QPainterPath& path, const QBrush& brush, const QPen* pen = nullptr) { const QPainterPath rotated = transform.map(path); painter.setBrush(brush); if (pen) { painter.setPen(*pen); } else { painter.setPen(Qt::NoPen); } painter.drawPath(rotated); }; draw_with_transform(arrow, QBrush(body_grad)); // Gloss highlight QPainterPath gloss; const double gloss_w = shaft_w * 0.42; QRectF gloss_rect(cx - gloss_w * 0.5, shaft_y + h * 0.04, gloss_w, shaft_h * 0.5); gloss.addRoundedRect(gloss_rect, gloss_w * 0.4, gloss_w * 0.4); QLinearGradient gloss_grad(gloss_rect.topLeft(), gloss_rect.bottomLeft()); QColor gloss_hi = Qt::white; gloss_hi.setAlpha(190); QColor gloss_lo = Qt::white; gloss_lo.setAlpha(40); gloss_grad.setColorAt(0.0, gloss_hi); gloss_grad.setColorAt(1.0, gloss_lo); draw_with_transform(gloss, QBrush(gloss_grad)); // Head specular highlights const double spec_w = head_w * 0.18; QRectF spec_left(cx - head_w * 0.32, head_base_y + head_h * 0.08, spec_w, head_h * 0.2); QRectF spec_right(cx + head_w * 0.14, head_base_y + head_h * 0.1, spec_w * 0.8, head_h * 0.2); auto paint_spec = [&](const QRectF& r) { QPainterPath p; p.addRoundedRect(r, r.width() * 0.4, r.height() * 0.6); QLinearGradient g(r.topLeft(), r.bottomLeft()); QColor hi = Qt::white; hi.setAlpha(180); QColor lo = Qt::white; lo.setAlpha(30); g.setColorAt(0.0, hi); g.setColorAt(1.0, lo); draw_with_transform(p, QBrush(g)); }; paint_spec(spec_left); paint_spec(spec_right); } 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); primary_arrow_->setVisible(false); // 使用自绘 3D 箭头 } } 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 values(static_cast(expected), 0.0); for (const auto& item: data_points_) { const int x = static_cast(item.x); const int y = static_cast(item.y); if (x >= 0 && x < width && y >= 0 && y < height) { const int idx = y * width + x; values[static_cast(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(y * width + x)]; }; std::vector> grads(static_cast(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(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(width) * 0.5; const double mid_y = static_cast(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; arrow_dir_ = QPointF(dir_x, dir_y); 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(width) * 0.5; const double cy = static_cast(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(false); // 位置保留以备需要,但当前使用自绘 3D 箭头 } replot(); } using namespace creeper; void VectorFieldPlot::paintEvent(QPaintEvent* event) { vector_widget::internal::VectorPlot::paintEvent(event); }