121 lines
4.6 KiB
C++
121 lines
4.6 KiB
C++
#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
|