geopro/src/app/panels/chart/ScatterHistogram.cpp

121 lines
4.6 KiB
C++
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

#include "panels/chart/ScatterHistogram.hpp"
#include <algorithm>
#include <cmath>
#include <QPaintEvent>
#include <QPainter>
#include "panels/chart/ScatterDataOps.hpp" // buildScatterHistogram
namespace geopro::app {
namespace {
constexpr int kBinCount = 20; // 分箱数(对照原版 D3 stepRange=20
const QColor kBarIn(64, 128, 255); // 选区内柱:蓝(对照原版高亮)
const QColor kBarOut(200, 205, 215); // 选区外柱:灰
const QColor kIndicator(64, 128, 255, 51); // 选区指示矩形rgba(64,128,255,0.2)
const QColor kAxis(150, 150, 150); // 轴线/刻度
constexpr int kPadL = 8; // 左右内边距
constexpr int kPadR = 8;
constexpr int kPadTop = 8; // 顶部内边距
constexpr int kAxisH = 18; // 底部刻度区高度
constexpr int kBarGap = 1; // 柱间距(像素)
} // namespace
ScatterHistogramView::ScatterHistogramView(QWidget* parent) : QWidget(parent) {
setMinimumHeight(160);
setMinimumWidth(280);
}
void ScatterHistogramView::setValues(const std::vector<double>& values) {
values_.clear();
values_.reserve(values.size());
for (double x : values)
if (std::isfinite(x)) values_.push_back(x);
if (values_.empty()) {
dataMin_ = dataMax_ = 0.0;
} else {
dataMin_ = *std::min_element(values_.begin(), values_.end());
dataMax_ = *std::max_element(values_.begin(), values_.end());
}
update();
}
void ScatterHistogramView::setSelection(double min, double max) {
selMin_ = min;
selMax_ = max;
hasSel_ = (min <= max);
update();
}
void ScatterHistogramView::paintEvent(QPaintEvent*) {
QPainter p(this);
p.setRenderHint(QPainter::Antialiasing, false);
const QRect r = rect();
const int plotL = r.left() + kPadL;
const int plotR = r.right() - kPadR;
const int plotTop = r.top() + kPadTop;
const int plotBottom = r.bottom() - kAxisH;
const int plotW = plotR - plotL;
const int plotH = plotBottom - plotTop;
if (plotW <= 0 || plotH <= 0) return;
// 数据域无效(无值/退化区间)→ 仅画基线,空态。
if (values_.empty() || !(dataMax_ > dataMin_)) {
p.setPen(kAxis);
p.drawLine(plotL, plotBottom, plotR, plotBottom);
return;
}
// 在全量数据域上分箱(每柱代表一个等宽区间)。
const auto h = buildScatterHistogram(values_, dataMin_, dataMax_, kBinCount);
const int n = static_cast<int>(h.counts.size());
if (n <= 0) return;
const int maxCount = *std::max_element(h.counts.begin(), h.counts.end());
if (maxCount <= 0) {
p.setPen(kAxis);
p.drawLine(plotL, plotBottom, plotR, plotBottom);
return;
}
// 值 → 像素 x 的映射(数据域 [dataMin,dataMax] → [plotL,plotR])。
auto xPix = [&](double val) {
return plotL + (val - dataMin_) / (dataMax_ - dataMin_) * plotW;
};
// 选区指示矩形(先画,柱叠其上)。
if (hasSel_ && selMax_ > selMin_) {
const double lo = std::clamp(selMin_, dataMin_, dataMax_);
const double hi = std::clamp(selMax_, dataMin_, dataMax_);
const QRectF ind(xPix(lo), plotTop, xPix(hi) - xPix(lo), plotH);
p.fillRect(ind, kIndicator);
}
// 画柱:高度按计数归一;选区内蓝、选区外灰。
const double binW = static_cast<double>(plotW) / n;
for (int i = 0; i < n; ++i) {
const double binLo = dataMin_ + i * h.step;
const double binHi = binLo + h.step;
const int barH = static_cast<int>(static_cast<double>(h.counts[static_cast<std::size_t>(i)]) /
maxCount * plotH);
const int bx = static_cast<int>(plotL + i * binW) + kBarGap;
const int bw = std::max(1, static_cast<int>(binW) - 2 * kBarGap);
// 柱中心落在选区内 → 高亮(与原版“区间内点高亮”观感一致)。
const double binCenter = (binLo + binHi) / 2.0;
const bool inSel = hasSel_ && binCenter >= selMin_ && binCenter <= selMax_;
p.fillRect(bx, plotBottom - barH, bw, barH, inSel ? kBarIn : kBarOut);
}
// 底部基线 + 两端数值刻度min/max
p.setPen(kAxis);
p.drawLine(plotL, plotBottom, plotR, plotBottom);
p.drawText(QRect(plotL, plotBottom, plotW / 2, kAxisH), Qt::AlignLeft | Qt::AlignVCenter,
QString::number(dataMin_, 'g', 4));
p.drawText(QRect(plotL + plotW / 2, plotBottom, plotW / 2, kAxisH),
Qt::AlignRight | Qt::AlignVCenter, QString::number(dataMax_, 'g', 4));
}
} // namespace geopro::app