jfjoch_viewer: Minor adjustments

This commit is contained in:
2025-11-18 08:39:05 +01:00
parent 7e6fa5aadb
commit 21824c24e3
7 changed files with 136 additions and 163 deletions
+121 -20
View File
@@ -7,9 +7,10 @@
#include <QClipboard>
#include <QApplication>
#include <QCategoryAxis>
#include <QRegularExpression>
JFJochSimpleChartView::JFJochSimpleChartView(QWidget *parent)
: QChartView(new QChart(), parent) {
: QChartView(new QChart(), parent) {
chart()->legend()->hide();
setFixedHeight(300);
setRenderHint(QPainter::Antialiasing);
@@ -17,8 +18,11 @@ JFJochSimpleChartView::JFJochSimpleChartView(QWidget *parent)
//setRubberBand(QChartView::RubberBand::HorizontalRubberBand);
}
#include <iostream>
void JFJochSimpleChartView::UpdateData(const std::vector<float> &in_x, const std::vector<float> &in_y,
QString legend_x, QString legend_y) {
QString legend_x, QString legend_y, bool in_one_over_d) {
one_over_d = in_one_over_d;
x = in_x;
y = in_y;
@@ -41,23 +45,114 @@ void JFJochSimpleChartView::UpdateData(const std::vector<float> &in_x, const std
chart()->addSeries(series);
m_series = series;
auto *axY = new QValueAxis();
axY->setTitleText(legend_y);
chart()->addAxis(axY, Qt::AlignLeft);
series->attachAxis(axY);
// Compute Y range
double ymin = 0.0, ymax = 0.0;
{
auto [minYIt, maxYIt] = std::minmax_element(y.begin(), y.end());
ymin = static_cast<double>(*minYIt);
ymax = static_cast<double>(*maxYIt);
if (ymin == ymax) {
const double eps = (std::abs(ymax) > 0.0) ? std::abs(ymax) * 1e-6 : 1.0;
ymin -= eps;
ymax += eps;
}
}
// Hidden value axis (left): range + grid
auto *axYvalue = new QValueAxis();
axYvalue->setTitleText(legend_y);
axYvalue->setRange(ymin, ymax);
axYvalue->setTickCount(5); // ensure ticks exist now (avoid 0 before layout)
axYvalue->setLabelsVisible(false); // hide numeric labels (grid only)
chart()->addAxis(axYvalue, Qt::AlignRight);
series->attachAxis(axYvalue);
// SI formatter (Plotly/D3-like ".3~s")
int precision = 3;
const auto formatSI = [precision](double v) -> QString {
if (v == 0.0 || std::isnan(v)) return QStringLiteral("0");
static const struct { int exp; const char* pre; } si[] = {
{24,"Y"},{21,"Z"},{18,"E"},{15,"P"},{12,"T"},{9,"G"},{6,"M"},{3,"k"},
{0,""},{-3,"m"},{-6,"µ"},{-9,"n"},{-12,"p"},{-15,"f"},{-18,"a"},{-21,"z"},{-24,"y"}
};
double av = std::fabs(v);
int chosenExp = 0; const char* chosenPre = "";
if (av > 0.0) {
int exp10 = static_cast<int>(std::floor(std::log10(av)));
int exp3 = (exp10 >= 0) ? (exp10 / 3) * 3 : -(((-exp10 + 2) / 3) * 3);
if (exp3 > 24) exp3 = 24;
if (exp3 < -24) exp3 = -24;
for (auto &e : si) if (e.exp == exp3) { chosenExp = e.exp; chosenPre = e.pre; break; }
}
const double scaled = v / std::pow(10.0, chosenExp);
QString num = QString::number(scaled, 'g', std::max(1, precision)); // ~ trimming effect
std::cout << v << " " << num.toStdString() << std::endl;
return (chosenPre && *chosenPre) ? num + QStringLiteral(" ") + QString::fromUtf8(chosenPre) : num;
};
// Visible category axis (right) with formatted labels
auto *axYcat = new QCategoryAxis();
axYcat->setLabelsPosition(QCategoryAxis::AxisLabelsPositionOnValue);
axYcat->setGridLineVisible(false);
axYcat->setMinorGridLineVisible(false);
axYcat->setTitleText(legend_y);
axYcat->setLabelsVisible(true); // make sure labels are actually shown
// Build ticks from hidden axis tick count immediately (stable even before layout)
const int tickCountY = std::max(2, axYvalue->tickCount());
const double ystep = (ymax - ymin) / (tickCountY - 1);
for (int i = 0; i < tickCountY; ++i) {
const double yv = (i == tickCountY - 1) ? ymax : (ymin + i * ystep);
axYcat->append(formatSI(yv), yv);
}
chart()->addAxis(axYcat, Qt::AlignLeft);
series->attachAxis(axYcat);
// Give a bit more room on the right so labels are not clipped
QMargins m = chart()->margins();
if (m.right() < 12) {
m.setRight(12);
chart()->setMargins(m);
}
// Build X range
auto [minXIt, maxXIt] = std::minmax_element(x.begin(), x.end());
const double xmin = *minXIt, xmax = *maxXIt;
const float xmin = x[0], xmax = x[x.size() - 1];
// Normal value axis at bottom
auto *axX = new QValueAxis();
axX->setRange(xmin, xmax);
axX->setTitleText(legend_x);
chart()->addAxis(axX, Qt::AlignBottom);
series->attachAxis(axX);
if (one_over_d) {
auto *axXTop = new QValueAxis();
axXTop->setRange(xmin, xmax);
axXTop->setTickCount(5);
axXTop->setLabelsVisible(false);
chart()->addAxis(axXTop, Qt::AlignTop);
series->attachAxis(axXTop);
auto *axXcat = new QCategoryAxis();
axXcat->setLabelsPosition(QCategoryAxis::AxisLabelsPositionOnValue);
axXcat->setGridLineVisible(false);
axXcat->setMinorGridLineVisible(false);
axXcat->setTitleText(legend_x);
const int tickCount = axXTop->tickCount();
const double step = (tickCount > 1) ? (xmax - xmin) / (tickCount - 1) : 0.0;
for (int i = 0; i < tickCount; ++i) {
const double xv = xmin + i * step;
const QString lab = (std::abs(xv) < 1e-12)
? QStringLiteral("")
: QString::number(1.0 / sqrtf(xv), 'f', 2);
axXcat->append(lab, xv);
}
chart()->addAxis(axXcat, Qt::AlignBottom);
series->attachAxis(axXcat);
} else {
auto *axX = new QValueAxis();
axX->setRange(xmin, xmax);
axX->setTitleText(legend_x);
chart()->addAxis(axX, Qt::AlignBottom);
series->attachAxis(axX);
}
}
void JFJochSimpleChartView::ClearData() {
x.clear();
y.clear();
@@ -97,7 +192,7 @@ void JFJochSimpleChartView::mouseMoveEvent(QMouseEvent *event) {
// Map mouse position to chart coordinates
const QPointF chartPos = chart()->mapToValue(event->pos(), m_series);
const double xVal = chartPos.x();
const double xVal = chartPos.x();
// Find closest index in x[]
auto it = std::lower_bound(x.begin(), x.end(), static_cast<float>(xVal));
@@ -118,7 +213,7 @@ void JFJochSimpleChartView::mouseMoveEvent(QMouseEvent *event) {
// Map that data point to scene coords to get the x position
const QPointF ptOnSeries = chart()->mapToPosition(QPointF(xNearest, yNearest), m_series);
const QRectF plotArea = chart()->plotArea();
const QRectF plotArea = chart()->plotArea();
if (!m_hoverLine) {
m_hoverLine = new QGraphicsLineItem;
@@ -130,15 +225,21 @@ void JFJochSimpleChartView::mouseMoveEvent(QMouseEvent *event) {
ptOnSeries.x(), plotArea.bottom()));
// Send to status bar
emit writeStatusBar(getText(idx),6000);
emit writeStatusBar(getText(idx), 6000);
}
QString JFJochSimpleChartView::getText(size_t idx) {
if (idx < x.size())
if (idx > x.size())
return {};
if (one_over_d)
return QString("d = %1 Å, y = %2")
.arg(1 / sqrt(x[idx]), 0, 'g', 4)
.arg(y[idx], 0, 'g', 6);
else
return QString("x = %1, y = %2")
.arg(x[idx], 0, 'g', 6)
.arg(y[idx], 0, 'g', 6);
return QString();
}
void JFJochSimpleChartView::leaveEvent(QEvent *event) {
@@ -148,5 +249,5 @@ void JFJochSimpleChartView::leaveEvent(QEvent *event) {
delete m_hoverLine;
m_hoverLine = nullptr;
}
emit writeStatusBar(QString(), 0); // clear status bar when leaving
emit writeStatusBar(QString(), 6000); // clear status bar when leaving
}