Files
z-bar-qt/Plugins/ZShell/Internal/sparklineitem.cpp
T

323 lines
7.6 KiB
C++

#include "sparklineitem.hpp"
#include <qmath.h>
#include <qnumeric.h>
#include <qpainter.h>
#include <qpainterpath.h>
#include <qpen.h>
#include <qtypes.h>
namespace ZShell::internal {
SparklineItem::SparklineItem(QQuickItem* parent) : QQuickPaintedItem(parent) {
setAntialiasing(true);
}
void SparklineItem::paint(QPainter* painter) {
const bool has1 = m_line1 && m_line1->count() >= 2;
const bool has2 = m_line2 && m_line2->count() >= 2;
if (!has1 && !has2) return;
painter->setRenderHint(QPainter::Antialiasing, true);
// Draw line1 first (behind), then line2 (in front)
if (has1) drawLine(painter, m_line1, m_line1Color, m_line1FillAlpha);
if (has2) drawLine(painter, m_line2, m_line2Color, m_line2FillAlpha);
}
void SparklineItem::drawLine(
QPainter* painter,
CircularBuffer* buffer,
const QColor& color,
qreal fillAlpha) {
if (m_historyLength < 2) return;
const qreal w = width();
const qreal h = height();
const int len = buffer->count();
if (len < 2 || m_maxValue <= 0.0) return;
qreal stepX = 0.0;
if (m_sliding)
stepX = w / static_cast<qreal>(m_historyLength - 1);
else
stepX = w / static_cast<qreal>(len - 1);
qreal startX = 0.0;
if (m_sliding)
startX = w - (len - 1) * stepX - stepX * m_slideProgress + stepX;
else
startX = 0;
const qreal strokePad = qCeil(m_lineWidth / 2);
const qreal curvePad = 3.0;
const qreal topPad = strokePad + curvePad;
const qreal bottomPad = strokePad;
const qreal plotTop = topPad;
const qreal plotBottom = h - bottomPad;
const qreal fillBottom = h;
const qreal plotH = qMax<qreal>(1.0, plotBottom - plotTop);
QVector<QPointF> points;
points.reserve(len);
for (int i = 0; i < len; ++i) {
qreal value = 0.0;
if (m_smoothing > 0.0) {
const int radius = qRound(m_smoothing);
qreal sum = 0.0;
int count = 0;
for (int j = -radius; j <= radius; ++j) {
const int index = i + j;
if (index < 0 || index >= len) continue;
sum += buffer->at(index);
count++;
}
value = sum / static_cast<qreal>(count);
} else {
value = buffer->at(i);
}
value = qBound<qreal>(0.0, value, m_maxValue);
const qreal x = startX + i * stepX;
const qreal y = plotTop + (1.0 - (value / m_maxValue)) * plotH;
points.append(QPointF(x, y));
}
QPainterPath linePath;
linePath.moveTo(points[0]);
if (m_curveSmoothing && points.size() > 2) {
// 1.0 = standard uniform Catmull-Rom. Lower (e.g. 0.5) = tighter, less overshoot.
const qreal tension = 1.0;
const qreal k =
tension / 6.0; // Catmull-Rom -> Bezier factor (1/6 for uniform)
const int n = points.size();
for (int i = 0; i < n - 1; ++i) {
// Duplicate endpoints so the first/last segments are well-defined
const QPointF& p0 = points[qMax(i - 1, 0)];
const QPointF& p1 = points[i];
const QPointF& p2 = points[i + 1];
const QPointF& p3 = points[qMin(i + 2, n - 1)];
QPointF c1 = p1 + (p2 - p0) * k;
QPointF c2 = p2 - (p3 - p1) * k;
// Keep control points inside the vertical range of the segment's
// endpoints so we never overshoot (no dips below zero, no
// bulges above a peak). Remember: smaller y = higher value.
const qreal segMinY = qMin(p1.y(), p2.y());
const qreal segMaxY = qMax(p1.y(), p2.y());
c1.setY(qBound(segMinY, c1.y(), segMaxY));
c2.setY(qBound(segMinY, c2.y(), segMaxY));
linePath.cubicTo(c1, c2, p2);
}
} else {
// Existing horizontal-tangent (smoothstep-style) curve
for (int i = 0; i < points.size() - 1; ++i) {
const QPointF& p0 = points[i];
const QPointF& p1 = points[i + 1];
const qreal ctrlX = (p0.x() + p1.x()) * 0.5;
linePath.cubicTo(QPointF(ctrlX, p0.y()), QPointF(ctrlX, p1.y()), p1);
}
}
QPen pen(color, m_lineWidth);
pen.setCapStyle(Qt::RoundCap);
pen.setJoinStyle(Qt::RoundJoin);
painter->setPen(pen);
painter->setBrush(Qt::NoBrush);
painter->drawPath(linePath);
QPainterPath fillPath = linePath;
fillPath.lineTo(startX + (len - 1) * stepX, fillBottom);
fillPath.lineTo(startX, fillBottom);
fillPath.closeSubpath();
QColor fillColor = color;
fillColor.setAlphaF(static_cast<float>(fillAlpha));
painter->setPen(Qt::NoPen);
painter->setBrush(fillColor);
painter->drawPath(fillPath);
}
void SparklineItem::connectBuffer(CircularBuffer* buffer) {
if (!buffer) return;
connect(buffer, &CircularBuffer::valuesChanged, this, [this]() {
update();
});
connect(buffer, &QObject::destroyed, this, [this, buffer]() {
if (m_line1 == buffer) {
m_line1 = nullptr;
emit line1Changed();
}
if (m_line2 == buffer) {
m_line2 = nullptr;
emit line2Changed();
}
update();
});
}
CircularBuffer* SparklineItem::line1() const {
return m_line1;
}
void SparklineItem::setLine1(CircularBuffer* buffer) {
if (m_line1 == buffer) return;
if (m_line1) disconnect(m_line1, nullptr, this, nullptr);
m_line1 = buffer;
connectBuffer(buffer);
emit line1Changed();
update();
}
CircularBuffer* SparklineItem::line2() const {
return m_line2;
}
void SparklineItem::setLine2(CircularBuffer* buffer) {
if (m_line2 == buffer) return;
if (m_line2) disconnect(m_line2, nullptr, this, nullptr);
m_line2 = buffer;
connectBuffer(buffer);
emit line2Changed();
update();
}
QColor SparklineItem::line1Color() const {
return m_line1Color;
}
void SparklineItem::setLine1Color(const QColor& color) {
if (m_line1Color == color) return;
m_line1Color = color;
emit line1ColorChanged();
update();
}
QColor SparklineItem::line2Color() const {
return m_line2Color;
}
void SparklineItem::setLine2Color(const QColor& color) {
if (m_line2Color == color) return;
m_line2Color = color;
emit line2ColorChanged();
update();
}
qreal SparklineItem::line1FillAlpha() const {
return m_line1FillAlpha;
}
void SparklineItem::setLine1FillAlpha(qreal alpha) {
if (qFuzzyCompare(m_line1FillAlpha, alpha)) return;
m_line1FillAlpha = alpha;
emit line1FillAlphaChanged();
update();
}
qreal SparklineItem::line2FillAlpha() const {
return m_line2FillAlpha;
}
void SparklineItem::setLine2FillAlpha(qreal alpha) {
if (qFuzzyCompare(m_line2FillAlpha, alpha)) return;
m_line2FillAlpha = alpha;
emit line2FillAlphaChanged();
update();
}
qreal SparklineItem::maxValue() const {
return m_maxValue;
}
void SparklineItem::setMaxValue(qreal value) {
if (qFuzzyCompare(m_maxValue, value)) return;
m_maxValue = value;
emit maxValueChanged();
update();
}
qreal SparklineItem::slideProgress() const {
return m_slideProgress;
}
void SparklineItem::setSlideProgress(qreal progress) {
if (qFuzzyCompare(m_slideProgress, progress)) return;
m_slideProgress = progress;
emit slideProgressChanged();
update();
}
int SparklineItem::historyLength() const {
return m_historyLength;
}
void SparklineItem::setHistoryLength(int length) {
if (m_historyLength == length) return;
m_historyLength = length;
emit historyLengthChanged();
update();
}
qreal SparklineItem::lineWidth() const {
return m_lineWidth;
}
void SparklineItem::setLineWidth(qreal width) {
if (qFuzzyCompare(m_lineWidth, width)) return;
m_lineWidth = width;
emit lineWidthChanged();
update();
}
void SparklineItem::setSliding(bool sliding) {
if (m_sliding == sliding) return;
m_sliding = sliding;
emit slidingChanged();
}
bool SparklineItem::sliding() const {
return m_sliding;
};
void SparklineItem::setSmoothing(qreal smoothing) {
smoothing = qMax<qreal>(0.0, smoothing);
if (qFuzzyCompare(m_smoothing, smoothing)) return;
m_smoothing = smoothing;
emit smoothingChanged();
}
qreal SparklineItem::smoothing() const {
return m_smoothing;
};
void SparklineItem::setCurveSmoothing(bool sliding) {
if (m_curveSmoothing == sliding) return;
m_curveSmoothing = sliding;
emit curveSmoothingChanged();
}
bool SparklineItem::curveSmoothing() const {
return m_curveSmoothing;
};
} // namespace ZShell::internal