feat: graphs now properly sized + port static graphs to c++
This commit is contained in:
@@ -0,0 +1,910 @@
|
||||
#include "linegraph.hpp"
|
||||
|
||||
#include "internal/linegeometry.hpp"
|
||||
|
||||
#include <QCanvasPainterItemRenderer>
|
||||
#include <QFont>
|
||||
#include <QQuickWindow>
|
||||
#include <qcanvaspainter.h>
|
||||
|
||||
#include <algorithm>
|
||||
#include <cmath>
|
||||
#include <limits>
|
||||
|
||||
namespace ZShell::components {
|
||||
|
||||
namespace {
|
||||
constexpr qreal kNaN = std::numeric_limits<qreal>::quiet_NaN();
|
||||
|
||||
bool sameReal(qreal a, qreal b) {
|
||||
if (std::isnan(a) || std::isnan(b))
|
||||
return std::isnan(a) && std::isnan(b);
|
||||
return qFuzzyCompare(a, b);
|
||||
}
|
||||
|
||||
// A value is plottable only if it is a real number. Everything else - null,
|
||||
// undefined, NaN, an unparsable string from the API - is NaN, so the series splits
|
||||
// there. Non-finite coordinates must never reach the path.
|
||||
qreal parseValue(const QVariant& v) {
|
||||
bool ok = false;
|
||||
const double d = v.toDouble(&ok);
|
||||
return ok && std::isfinite(d) ? qreal(d) : kNaN;
|
||||
}
|
||||
|
||||
// Index of the segment whose right endpoint is at or past targetX.
|
||||
int indexAt(const QVector<QPointF>& points, qreal targetX) {
|
||||
for (int i = 0; i < points.size() - 1; ++i) {
|
||||
if (targetX <= points[i + 1].x())
|
||||
return i;
|
||||
}
|
||||
const int last = static_cast<int>(points.size()) - 2;
|
||||
return last > 0 ? last : 0;
|
||||
}
|
||||
|
||||
// The part of the polyline left/right of targetX, ending or beginning exactly at
|
||||
// the curve point there. Used to carve a gap around the marker dot.
|
||||
QVector<QPointF> cutLeft(
|
||||
const QVector<QPointF>& points,
|
||||
qreal targetX,
|
||||
bool curveSmoothing) {
|
||||
if (targetX <= points.first().x())
|
||||
return {};
|
||||
if (targetX >= points.last().x())
|
||||
return points;
|
||||
|
||||
const int j = indexAt(points, targetX);
|
||||
QVector<QPointF> left = points.first(j + 1);
|
||||
if (targetX > points[j].x())
|
||||
left.append(internal::pointAtX(points, targetX, curveSmoothing));
|
||||
return left;
|
||||
}
|
||||
|
||||
QVector<QPointF> cutRight(
|
||||
const QVector<QPointF>& points,
|
||||
qreal targetX,
|
||||
bool curveSmoothing) {
|
||||
if (targetX >= points.last().x())
|
||||
return {};
|
||||
if (targetX <= points.first().x())
|
||||
return points;
|
||||
|
||||
const int j = indexAt(points, targetX);
|
||||
QVector<QPointF> right = points.mid(j + 1);
|
||||
if (targetX < points[j + 1].x())
|
||||
right.prepend(internal::pointAtX(points, targetX, curveSmoothing));
|
||||
return right;
|
||||
}
|
||||
|
||||
QString formatXLabel(LineGraph::XLabelMode mode, int i, bool use12) {
|
||||
if (mode == LineGraph::XLabelMode::None)
|
||||
return {};
|
||||
if (mode == LineGraph::XLabelMode::Number)
|
||||
return QString::number(i);
|
||||
// Hour: an hour of day 0..24, on a 12- or 24-hour clock.
|
||||
if (use12) {
|
||||
const int hr = (i % 12 == 0) ? 12 : (i % 12);
|
||||
return QString::number(hr)
|
||||
+ (i < 12 ? QStringLiteral(" AM")
|
||||
: QStringLiteral(" PM"));
|
||||
}
|
||||
return QString::number(i);
|
||||
}
|
||||
|
||||
QString formatYLabel(
|
||||
LineGraph::YLabelMode mode,
|
||||
qreal v,
|
||||
int decimals,
|
||||
const QString& suffix) {
|
||||
if (mode == LineGraph::YLabelMode::Off)
|
||||
return {};
|
||||
return QString::number(v, 'f', qMax(0, decimals)) + suffix;
|
||||
}
|
||||
} // namespace
|
||||
|
||||
// Everything paint needs, computed once on the GUI thread in synchronizeData so
|
||||
// paint() (render thread) only reads it. Curve vertices already carry their
|
||||
// device-pixel column quantization from internal::linegeometry.
|
||||
class LineGraphRenderer final : public QCanvasPainterItemRenderer {
|
||||
public:
|
||||
void synchronizeData(QCanvasPainterItem* item) override;
|
||||
void paint(QCanvasPainter* painter) override;
|
||||
|
||||
private:
|
||||
struct Plot {
|
||||
qreal x = 0.0;
|
||||
qreal y = 0.0;
|
||||
qreal w = 0.0;
|
||||
qreal h = 0.0;
|
||||
};
|
||||
|
||||
struct Label {
|
||||
QString text;
|
||||
qreal x = 0.0;
|
||||
qreal y = 0.0;
|
||||
};
|
||||
|
||||
struct Segment {
|
||||
QVector<QPointF> fill;
|
||||
QVector<QVector<QPointF>> strokes;
|
||||
};
|
||||
|
||||
struct Marker {
|
||||
bool hasLine = false;
|
||||
qreal lineX = 0.0;
|
||||
QVector<QPair<qreal, qreal>> dash;
|
||||
bool hasDot = false;
|
||||
QPointF dot;
|
||||
qreal dotRadius = 0.0;
|
||||
};
|
||||
|
||||
struct Model {
|
||||
bool valid = false;
|
||||
qreal height = 0.0;
|
||||
Plot plot;
|
||||
qreal plotBottom = 0.0;
|
||||
qreal gridWidth = 0.0;
|
||||
qreal lineWidth = 0.0;
|
||||
QVector<qreal> vGrid;
|
||||
QVector<qreal> hGrid;
|
||||
QVector<Label> xLabels;
|
||||
QVector<Label> yLabels;
|
||||
QVector<Segment> segments;
|
||||
Marker marker;
|
||||
qreal labelFontSize = 0.0;
|
||||
};
|
||||
|
||||
void buildModel(const LineGraph* graph);
|
||||
|
||||
Model m_model;
|
||||
QColor m_lineColor;
|
||||
QColor m_gridColor;
|
||||
QColor m_markerColor;
|
||||
QColor m_labelColor;
|
||||
qreal m_gridAlpha = 0.12;
|
||||
qreal m_labelAlpha = 0.6;
|
||||
qreal m_fillAlpha = 0.15;
|
||||
qreal m_dpr = 1.0;
|
||||
};
|
||||
|
||||
void LineGraphRenderer::synchronizeData(QCanvasPainterItem* item) {
|
||||
auto* graph = static_cast<LineGraph*>(item);
|
||||
m_lineColor = graph->m_lineColor;
|
||||
m_gridColor = graph->m_gridColor;
|
||||
m_markerColor = graph->m_markerColor;
|
||||
m_labelColor = graph->m_labelColor;
|
||||
m_gridAlpha = graph->m_gridAlpha;
|
||||
m_labelAlpha = graph->m_labelAlpha;
|
||||
m_fillAlpha = graph->m_fillAlpha;
|
||||
m_dpr = graph->m_dpr > 0.0 ? graph->m_dpr : 1.0;
|
||||
buildModel(graph);
|
||||
}
|
||||
|
||||
void LineGraphRenderer::buildModel(const LineGraph* graph) {
|
||||
Model model;
|
||||
const qreal W = width();
|
||||
const qreal H = height();
|
||||
const qreal dpr = m_dpr;
|
||||
model.height = H;
|
||||
model.labelFontSize = graph->m_labelFontSize;
|
||||
|
||||
if (W < 2 || H < 2 || graph->m_xSteps < 1) {
|
||||
m_model = model;
|
||||
return;
|
||||
}
|
||||
|
||||
const qreal padLeft = graph->m_labelFontSize * 5.0;
|
||||
const qreal padBottom = graph->m_labelFontSize + 14.0;
|
||||
model.plot = Plot {padLeft, 0.0, W - padLeft, H - padBottom};
|
||||
if (model.plot.w < 2 || model.plot.h < 2) {
|
||||
m_model = model;
|
||||
return;
|
||||
}
|
||||
model.plotBottom = model.plot.y + model.plot.h;
|
||||
|
||||
// Raw series, holes as NaN.
|
||||
QVector<qreal> raw;
|
||||
raw.reserve(graph->m_values.size());
|
||||
for (const QVariant& v : graph->m_values)
|
||||
raw.append(parseValue(v));
|
||||
|
||||
// Moving average over the non-hole neighbours, the same window the sparkline
|
||||
// applies at the same radius.
|
||||
QVector<qreal> series = raw;
|
||||
const int radius = qRound(graph->m_smoothing);
|
||||
if (radius > 0 && !raw.isEmpty()) {
|
||||
series.resize(raw.size());
|
||||
for (int i = 0; i < raw.size(); ++i) {
|
||||
if (std::isnan(raw[i])) {
|
||||
series[i] = kNaN;
|
||||
continue;
|
||||
}
|
||||
qreal sum = 0.0;
|
||||
int n = 0;
|
||||
for (int j = -radius; j <= radius; ++j) {
|
||||
const int u = i + j;
|
||||
if (u < 0 || u >= raw.size())
|
||||
continue;
|
||||
if (!std::isnan(raw[u])) {
|
||||
sum += raw[u];
|
||||
++n;
|
||||
}
|
||||
}
|
||||
series[i] = n > 0 ? sum / qreal(n) : kNaN;
|
||||
}
|
||||
}
|
||||
|
||||
// y range: explicit yMin/yMax, or auto from the data snapped to the grid step.
|
||||
qreal lo = graph->m_yMin;
|
||||
qreal hi = graph->m_yMax;
|
||||
const bool loAuto = std::isnan(lo);
|
||||
const bool hiAuto = std::isnan(hi);
|
||||
if (loAuto || hiAuto) {
|
||||
qreal dmin = std::numeric_limits<qreal>::infinity();
|
||||
qreal dmax = -std::numeric_limits<qreal>::infinity();
|
||||
for (const qreal v : series) {
|
||||
if (std::isnan(v))
|
||||
continue;
|
||||
dmin = std::min(dmin, v);
|
||||
dmax = std::max(dmax, v);
|
||||
}
|
||||
if (std::isinf(dmin)) {
|
||||
dmin = 0.0;
|
||||
dmax = 1.0;
|
||||
}
|
||||
|
||||
const int step = graph->m_horizontalLineStep;
|
||||
const int minSteps = graph->m_minHorizontalLines > 1
|
||||
? graph->m_minHorizontalLines - 1
|
||||
: 0;
|
||||
if (step > 0) {
|
||||
if (hiAuto) {
|
||||
const qreal baseLo =
|
||||
loAuto ? std::floor(dmin / step) * step : lo;
|
||||
const int span = std::max(
|
||||
{static_cast<int>(std::ceil((dmax - baseLo) / step)),
|
||||
minSteps,
|
||||
1});
|
||||
lo = baseLo;
|
||||
hi = baseLo + qreal(span) * step;
|
||||
} else {
|
||||
const int span = std::max(
|
||||
{static_cast<int>(std::ceil((hi - dmin) / step)),
|
||||
minSteps,
|
||||
1});
|
||||
lo = hi - qreal(span) * step;
|
||||
}
|
||||
} else {
|
||||
if (loAuto)
|
||||
lo = dmin;
|
||||
if (hiAuto)
|
||||
hi = dmax;
|
||||
if (hi <= lo)
|
||||
hi = lo + 1.0;
|
||||
}
|
||||
}
|
||||
|
||||
const qreal scaleBottom = lo - graph->m_yPaddingBottom;
|
||||
const qreal scaleTop = hi + graph->m_yPaddingTop;
|
||||
const qreal range = scaleTop - scaleBottom;
|
||||
if (range <= 0.0) {
|
||||
m_model = model;
|
||||
return;
|
||||
}
|
||||
|
||||
const auto xAt = [&](qreal i) {
|
||||
return model.plot.x + (i / qreal(graph->m_xSteps)) * model.plot.w;
|
||||
};
|
||||
const auto yAt = [&](qreal v) {
|
||||
return model.plot.y
|
||||
+ (1.0 - (v - scaleBottom) / range) * model.plot.h;
|
||||
};
|
||||
|
||||
const auto devW = [&](qreal w) {
|
||||
return std::max<qint64>(1, internal::toDev(w, dpr));
|
||||
};
|
||||
const qint64 gridWDev = devW(1.0);
|
||||
const qint64 lineWDev = devW(graph->m_lineWidth);
|
||||
model.gridWidth = internal::fromDev(gridWDev, dpr);
|
||||
model.lineWidth = internal::fromDev(lineWDev, dpr);
|
||||
|
||||
const int vStep = graph->m_verticalLineStep;
|
||||
const int hStep = graph->m_horizontalLineStep;
|
||||
const auto tickVisible = [&](int i) {
|
||||
if (i == 0 && !graph->m_firstXLine)
|
||||
return false;
|
||||
if (i == graph->m_xSteps && !graph->m_lastXLine)
|
||||
return false;
|
||||
return true;
|
||||
};
|
||||
|
||||
if (vStep > 0) {
|
||||
for (int i = 0; i <= graph->m_xSteps; i += vStep) {
|
||||
if (!tickVisible(i))
|
||||
continue;
|
||||
model.vGrid.append(
|
||||
internal::snapLine(xAt(i), gridWDev, dpr));
|
||||
}
|
||||
}
|
||||
|
||||
const qreal vStart = lo
|
||||
- (graph->m_yPaddingBottom > 0.0
|
||||
? std::floor(graph->m_yPaddingBottom / hStep)
|
||||
* hStep
|
||||
: 0.0);
|
||||
if (hStep > 0) {
|
||||
for (qreal v = vStart; v <= hi + 0.001; v += hStep)
|
||||
model.hGrid.append(
|
||||
internal::snapLine(yAt(v), gridWDev, dpr));
|
||||
}
|
||||
|
||||
if (vStep > 0 && graph->m_xLabelMode != LineGraph::XLabelMode::None) {
|
||||
for (int i = 0; i <= graph->m_xSteps; i += vStep) {
|
||||
if (!tickVisible(i))
|
||||
continue;
|
||||
const QString text = formatXLabel(
|
||||
graph->m_xLabelMode, i, graph->m_useTwelveHourClock);
|
||||
if (!text.isEmpty())
|
||||
model.xLabels.append(Label {
|
||||
text, xAt(i) + 4.0,
|
||||
model.plot.y + model.plot.h + 8.0});
|
||||
}
|
||||
}
|
||||
|
||||
if (hStep > 0 && graph->m_yLabelMode != LineGraph::YLabelMode::Off) {
|
||||
for (qreal v = vStart; v <= hi + 0.001; v += hStep) {
|
||||
const QString text = formatYLabel(
|
||||
graph->m_yLabelMode, v, graph->m_yLabelDecimals,
|
||||
graph->m_yLabelSuffix);
|
||||
if (!text.isEmpty())
|
||||
model.yLabels.append(Label {text, 2.0, yAt(v) - 2.0});
|
||||
}
|
||||
}
|
||||
|
||||
// Split the series into contiguous runs; each run becomes one curve. Points
|
||||
// land on device-pixel columns so a flat run stays on whole columns.
|
||||
const auto snapX = [&](qreal logical) {
|
||||
return internal::fromDev(internal::toDev(logical, dpr), dpr);
|
||||
};
|
||||
|
||||
QVector<QVector<QPointF>> runs;
|
||||
QVector<QPointF> current;
|
||||
for (int i = 0; i < series.size(); ++i) {
|
||||
if (std::isnan(series[i])) {
|
||||
if (!current.isEmpty()) {
|
||||
runs.append(current);
|
||||
current.clear();
|
||||
}
|
||||
continue;
|
||||
}
|
||||
current.append(QPointF(snapX(xAt(i)), yAt(series[i])));
|
||||
}
|
||||
if (!current.isEmpty())
|
||||
runs.append(current);
|
||||
|
||||
const bool curve = graph->m_curveSmoothing;
|
||||
const bool hasMarker =
|
||||
graph->m_markerX >= 0.0 && graph->m_markerX <= graph->m_xSteps;
|
||||
qreal markerPx = 0.0;
|
||||
qreal gapPx = 0.0;
|
||||
if (hasMarker) {
|
||||
markerPx = xAt(graph->m_markerX);
|
||||
qreal slope = 0.0;
|
||||
for (const auto& run : runs) {
|
||||
if (run.size() > 2 && run.first().x() <= markerPx
|
||||
&& markerPx <= run.last().x()
|
||||
&& markerPx - 1.0 >= run.first().x()
|
||||
&& markerPx + 1.0 <= run.last().x()) {
|
||||
slope = (internal::pointAtX(run, markerPx + 1.0, curve).y()
|
||||
- internal::pointAtX(run, markerPx - 1.0, curve)
|
||||
.y())
|
||||
/ 2.0;
|
||||
break;
|
||||
}
|
||||
}
|
||||
gapPx = (graph->m_markerDotRadius + graph->m_markerGap)
|
||||
/ std::sqrt(1.0 + slope * slope);
|
||||
}
|
||||
|
||||
for (const auto& run : runs) {
|
||||
if (run.size() < 2)
|
||||
continue;
|
||||
|
||||
Segment seg;
|
||||
seg.fill = internal::curveVertices(run, dpr, curve);
|
||||
seg.fill.append(QPointF(run.last().x(), model.plotBottom));
|
||||
seg.fill.append(QPointF(run.first().x(), model.plotBottom));
|
||||
|
||||
if (hasMarker) {
|
||||
const QVector<QPointF> left =
|
||||
cutLeft(run, markerPx - gapPx, curve);
|
||||
const QVector<QPointF> right =
|
||||
cutRight(run, markerPx + gapPx, curve);
|
||||
if (left.size() >= 2)
|
||||
seg.strokes.append(
|
||||
internal::curveVertices(left, dpr, curve));
|
||||
if (right.size() >= 2)
|
||||
seg.strokes.append(
|
||||
internal::curveVertices(right, dpr, curve));
|
||||
} else {
|
||||
seg.strokes.append(internal::curveVertices(run, dpr, curve));
|
||||
}
|
||||
model.segments.append(seg);
|
||||
}
|
||||
|
||||
if (hasMarker) {
|
||||
model.marker.hasLine = true;
|
||||
model.marker.lineX =
|
||||
internal::snapLine(markerPx, gridWDev, dpr);
|
||||
|
||||
// Dashes are drawn as solid segments: QCanvasPainter strokes are always
|
||||
// solid, so a dashed line is a run of short strokes, as LineGraph.qml did.
|
||||
qreal dashY = model.plot.y;
|
||||
bool on = true;
|
||||
while (dashY < H) {
|
||||
const qreal dashEnd = std::min(dashY + 4.0, H);
|
||||
if (on)
|
||||
model.marker.dash.append(
|
||||
{snapX(dashY), snapX(dashEnd)});
|
||||
dashY = dashEnd;
|
||||
on = !on;
|
||||
}
|
||||
|
||||
const int i0 = static_cast<int>(std::floor(graph->m_markerX));
|
||||
const qreal v0 = (i0 >= 0 && i0 < series.size()) ? series[i0]
|
||||
: kNaN;
|
||||
const qreal v1 = (i0 + 1 >= 0 && i0 + 1 < series.size())
|
||||
? series[i0 + 1]
|
||||
: kNaN;
|
||||
qreal y = kNaN;
|
||||
if (!std::isnan(v0) && !std::isnan(v1))
|
||||
y = yAt(v0 + (v1 - v0) * (graph->m_markerX - qreal(i0)));
|
||||
else if (!std::isnan(v0))
|
||||
y = yAt(v0);
|
||||
if (!std::isnan(y)) {
|
||||
model.marker.hasDot = true;
|
||||
model.marker.dot = QPointF(snapX(markerPx), y);
|
||||
model.marker.dotRadius = graph->m_markerDotRadius;
|
||||
}
|
||||
}
|
||||
|
||||
// The plot is well-formed: grid, labels and marker can draw even with no curve
|
||||
// segments, matching the QML which painted the grid regardless of data.
|
||||
model.valid = true;
|
||||
m_model = model;
|
||||
}
|
||||
|
||||
void LineGraphRenderer::paint(QCanvasPainter* painter) {
|
||||
painter->clearRect(0, 0, width(), height());
|
||||
if (!m_model.valid)
|
||||
return;
|
||||
|
||||
const Model& m = m_model;
|
||||
|
||||
// Grid: every line in one path, one stroke.
|
||||
QColor grid = m_gridColor;
|
||||
grid.setAlphaF(static_cast<float>(m_gridAlpha));
|
||||
painter->setStrokeStyle(grid);
|
||||
painter->setLineWidth(m.gridWidth);
|
||||
painter->beginPath();
|
||||
for (const qreal x : m.vGrid) {
|
||||
painter->moveTo(x, m.plot.y);
|
||||
painter->lineTo(x, m.height);
|
||||
}
|
||||
for (const qreal y : m.hGrid) {
|
||||
painter->moveTo(0, y);
|
||||
painter->lineTo(m.plot.x + m.plot.w, y);
|
||||
}
|
||||
painter->stroke();
|
||||
|
||||
// Axis labels.
|
||||
QColor label = m_labelColor;
|
||||
label.setAlphaF(static_cast<float>(m_labelAlpha));
|
||||
painter->setFillStyle(label);
|
||||
QFont font(QStringLiteral("sans-serif"));
|
||||
font.setPointSizeF(m.labelFontSize);
|
||||
painter->setFont(font);
|
||||
painter->setTextAlign(QCanvasPainter::TextAlign::Left);
|
||||
painter->setTextBaseline(QCanvasPainter::TextBaseline::Top);
|
||||
for (const Label& l : m.xLabels)
|
||||
painter->fillText(l.text, l.x, l.y);
|
||||
painter->setTextBaseline(QCanvasPainter::TextBaseline::Bottom);
|
||||
for (const Label& l : m.yLabels)
|
||||
painter->fillText(l.text, l.x, l.y);
|
||||
|
||||
// Area fill, then the stroked curve pieces on top.
|
||||
QColor fill = m_lineColor;
|
||||
fill.setAlphaF(static_cast<float>(m_fillAlpha));
|
||||
painter->setFillStyle(fill);
|
||||
for (const Segment& seg : m.segments) {
|
||||
painter->beginPath();
|
||||
painter->moveTo(seg.fill.first());
|
||||
for (int i = 1; i < seg.fill.size(); ++i)
|
||||
painter->lineTo(seg.fill[i]);
|
||||
painter->fill();
|
||||
}
|
||||
|
||||
painter->setStrokeStyle(m_lineColor);
|
||||
painter->setLineWidth(m.lineWidth);
|
||||
painter->setLineCap(QCanvasPainter::LineCap::Round);
|
||||
painter->setLineJoin(QCanvasPainter::LineJoin::Round);
|
||||
for (const Segment& seg : m.segments) {
|
||||
for (const auto& poly : seg.strokes) {
|
||||
painter->beginPath();
|
||||
painter->moveTo(poly.first());
|
||||
for (int i = 1; i < poly.size(); ++i)
|
||||
painter->lineTo(poly[i]);
|
||||
painter->stroke();
|
||||
}
|
||||
}
|
||||
|
||||
if (m.marker.hasLine) {
|
||||
QColor markerLine = m_markerColor;
|
||||
markerLine.setAlphaF(0.6f);
|
||||
painter->setStrokeStyle(markerLine);
|
||||
painter->setLineWidth(m.gridWidth);
|
||||
painter->beginPath();
|
||||
for (const auto& d : m.marker.dash) {
|
||||
painter->moveTo(m.marker.lineX, d.first);
|
||||
painter->lineTo(m.marker.lineX, d.second);
|
||||
}
|
||||
painter->stroke();
|
||||
}
|
||||
|
||||
if (m.marker.hasDot) {
|
||||
painter->setFillStyle(m_markerColor);
|
||||
painter->beginPath();
|
||||
painter->circle(
|
||||
m.marker.dot.x(), m.marker.dot.y(), m.marker.dotRadius);
|
||||
painter->fill();
|
||||
}
|
||||
}
|
||||
|
||||
LineGraph::LineGraph(QQuickItem* parent) : QCanvasPainterItem(parent) {
|
||||
setFillColor(Qt::transparent);
|
||||
setAlphaBlending(true);
|
||||
}
|
||||
|
||||
QCanvasPainterItemRenderer* LineGraph::createItemRenderer() const {
|
||||
return new LineGraphRenderer;
|
||||
}
|
||||
|
||||
void LineGraph::itemChange(ItemChange change, const ItemChangeData& value) {
|
||||
QCanvasPainterItem::itemChange(change, value);
|
||||
if (change == ItemSceneChange && value.window)
|
||||
m_dpr = value.window->effectiveDevicePixelRatio();
|
||||
else if (change == ItemDevicePixelRatioHasChanged && window())
|
||||
m_dpr = window()->effectiveDevicePixelRatio();
|
||||
}
|
||||
|
||||
QVariantList LineGraph::values() const {
|
||||
return m_values;
|
||||
}
|
||||
|
||||
void LineGraph::setValues(const QVariantList& values) {
|
||||
if (m_values == values) return;
|
||||
m_values = values;
|
||||
emit valuesChanged();
|
||||
update();
|
||||
}
|
||||
|
||||
int LineGraph::xSteps() const {
|
||||
return m_xSteps;
|
||||
}
|
||||
|
||||
void LineGraph::setXSteps(int xSteps) {
|
||||
if (m_xSteps == xSteps) return;
|
||||
m_xSteps = xSteps;
|
||||
emit xStepsChanged();
|
||||
update();
|
||||
}
|
||||
|
||||
qreal LineGraph::yMin() const {
|
||||
return m_yMin;
|
||||
}
|
||||
|
||||
void LineGraph::setYMin(qreal value) {
|
||||
if (sameReal(m_yMin, value)) return;
|
||||
m_yMin = value;
|
||||
emit yMinChanged();
|
||||
update();
|
||||
}
|
||||
|
||||
qreal LineGraph::yMax() const {
|
||||
return m_yMax;
|
||||
}
|
||||
|
||||
void LineGraph::setYMax(qreal value) {
|
||||
if (sameReal(m_yMax, value)) return;
|
||||
m_yMax = value;
|
||||
emit yMaxChanged();
|
||||
update();
|
||||
}
|
||||
|
||||
qreal LineGraph::yPaddingTop() const {
|
||||
return m_yPaddingTop;
|
||||
}
|
||||
|
||||
void LineGraph::setYPaddingTop(qreal value) {
|
||||
if (sameReal(m_yPaddingTop, value)) return;
|
||||
m_yPaddingTop = value;
|
||||
emit yPaddingTopChanged();
|
||||
update();
|
||||
}
|
||||
|
||||
qreal LineGraph::yPaddingBottom() const {
|
||||
return m_yPaddingBottom;
|
||||
}
|
||||
|
||||
void LineGraph::setYPaddingBottom(qreal value) {
|
||||
if (sameReal(m_yPaddingBottom, value)) return;
|
||||
m_yPaddingBottom = value;
|
||||
emit yPaddingBottomChanged();
|
||||
update();
|
||||
}
|
||||
|
||||
int LineGraph::verticalLineStep() const {
|
||||
return m_verticalLineStep;
|
||||
}
|
||||
|
||||
void LineGraph::setVerticalLineStep(int step) {
|
||||
if (m_verticalLineStep == step) return;
|
||||
m_verticalLineStep = step;
|
||||
emit verticalLineStepChanged();
|
||||
update();
|
||||
}
|
||||
|
||||
int LineGraph::horizontalLineStep() const {
|
||||
return m_horizontalLineStep;
|
||||
}
|
||||
|
||||
void LineGraph::setHorizontalLineStep(int step) {
|
||||
if (m_horizontalLineStep == step) return;
|
||||
m_horizontalLineStep = step;
|
||||
emit horizontalLineStepChanged();
|
||||
update();
|
||||
}
|
||||
|
||||
bool LineGraph::firstXLine() const {
|
||||
return m_firstXLine;
|
||||
}
|
||||
|
||||
void LineGraph::setFirstXLine(bool show) {
|
||||
if (m_firstXLine == show) return;
|
||||
m_firstXLine = show;
|
||||
emit firstXLineChanged();
|
||||
update();
|
||||
}
|
||||
|
||||
bool LineGraph::lastXLine() const {
|
||||
return m_lastXLine;
|
||||
}
|
||||
|
||||
void LineGraph::setLastXLine(bool show) {
|
||||
if (m_lastXLine == show) return;
|
||||
m_lastXLine = show;
|
||||
emit lastXLineChanged();
|
||||
update();
|
||||
}
|
||||
|
||||
int LineGraph::minHorizontalLines() const {
|
||||
return m_minHorizontalLines;
|
||||
}
|
||||
|
||||
void LineGraph::setMinHorizontalLines(int count) {
|
||||
if (m_minHorizontalLines == count) return;
|
||||
m_minHorizontalLines = count;
|
||||
emit minHorizontalLinesChanged();
|
||||
update();
|
||||
}
|
||||
|
||||
LineGraph::XLabelMode LineGraph::xLabelMode() const {
|
||||
return m_xLabelMode;
|
||||
}
|
||||
|
||||
void LineGraph::setXLabelMode(XLabelMode mode) {
|
||||
if (m_xLabelMode == mode) return;
|
||||
m_xLabelMode = mode;
|
||||
emit xLabelModeChanged();
|
||||
update();
|
||||
}
|
||||
|
||||
bool LineGraph::useTwelveHourClock() const {
|
||||
return m_useTwelveHourClock;
|
||||
}
|
||||
|
||||
void LineGraph::setUseTwelveHourClock(bool use) {
|
||||
if (m_useTwelveHourClock == use) return;
|
||||
m_useTwelveHourClock = use;
|
||||
emit useTwelveHourClockChanged();
|
||||
update();
|
||||
}
|
||||
|
||||
LineGraph::YLabelMode LineGraph::yLabelMode() const {
|
||||
return m_yLabelMode;
|
||||
}
|
||||
|
||||
void LineGraph::setYLabelMode(YLabelMode mode) {
|
||||
if (m_yLabelMode == mode) return;
|
||||
m_yLabelMode = mode;
|
||||
emit yLabelModeChanged();
|
||||
update();
|
||||
}
|
||||
|
||||
QString LineGraph::yLabelSuffix() const {
|
||||
return m_yLabelSuffix;
|
||||
}
|
||||
|
||||
void LineGraph::setYLabelSuffix(const QString& suffix) {
|
||||
if (m_yLabelSuffix == suffix) return;
|
||||
m_yLabelSuffix = suffix;
|
||||
emit yLabelSuffixChanged();
|
||||
update();
|
||||
}
|
||||
|
||||
int LineGraph::yLabelDecimals() const {
|
||||
return m_yLabelDecimals;
|
||||
}
|
||||
|
||||
void LineGraph::setYLabelDecimals(int decimals) {
|
||||
if (m_yLabelDecimals == decimals) return;
|
||||
m_yLabelDecimals = decimals;
|
||||
emit yLabelDecimalsChanged();
|
||||
update();
|
||||
}
|
||||
|
||||
qreal LineGraph::smoothing() const {
|
||||
return m_smoothing;
|
||||
}
|
||||
|
||||
void LineGraph::setSmoothing(qreal smoothing) {
|
||||
smoothing = qMax<qreal>(0.0, smoothing);
|
||||
if (sameReal(m_smoothing, smoothing)) return;
|
||||
m_smoothing = smoothing;
|
||||
emit smoothingChanged();
|
||||
update();
|
||||
}
|
||||
|
||||
bool LineGraph::curveSmoothing() const {
|
||||
return m_curveSmoothing;
|
||||
}
|
||||
|
||||
void LineGraph::setCurveSmoothing(bool enabled) {
|
||||
if (m_curveSmoothing == enabled) return;
|
||||
m_curveSmoothing = enabled;
|
||||
emit curveSmoothingChanged();
|
||||
update();
|
||||
}
|
||||
|
||||
qreal LineGraph::markerX() const {
|
||||
return m_markerX;
|
||||
}
|
||||
|
||||
void LineGraph::setMarkerX(qreal markerX) {
|
||||
if (sameReal(m_markerX, markerX)) return;
|
||||
m_markerX = markerX;
|
||||
emit markerXChanged();
|
||||
update();
|
||||
}
|
||||
|
||||
qreal LineGraph::markerGap() const {
|
||||
return m_markerGap;
|
||||
}
|
||||
|
||||
void LineGraph::setMarkerGap(qreal gap) {
|
||||
if (sameReal(m_markerGap, gap)) return;
|
||||
m_markerGap = gap;
|
||||
emit markerGapChanged();
|
||||
update();
|
||||
}
|
||||
|
||||
QColor LineGraph::lineColor() const {
|
||||
return m_lineColor;
|
||||
}
|
||||
|
||||
void LineGraph::setLineColor(const QColor& color) {
|
||||
if (m_lineColor == color) return;
|
||||
m_lineColor = color;
|
||||
emit lineColorChanged();
|
||||
update();
|
||||
}
|
||||
|
||||
QColor LineGraph::gridColor() const {
|
||||
return m_gridColor;
|
||||
}
|
||||
|
||||
void LineGraph::setGridColor(const QColor& color) {
|
||||
if (m_gridColor == color) return;
|
||||
m_gridColor = color;
|
||||
emit gridColorChanged();
|
||||
update();
|
||||
}
|
||||
|
||||
QColor LineGraph::markerColor() const {
|
||||
return m_markerColor;
|
||||
}
|
||||
|
||||
void LineGraph::setMarkerColor(const QColor& color) {
|
||||
if (m_markerColor == color) return;
|
||||
m_markerColor = color;
|
||||
emit markerColorChanged();
|
||||
update();
|
||||
}
|
||||
|
||||
QColor LineGraph::labelColor() const {
|
||||
return m_labelColor;
|
||||
}
|
||||
|
||||
void LineGraph::setLabelColor(const QColor& color) {
|
||||
if (m_labelColor == color) return;
|
||||
m_labelColor = color;
|
||||
emit labelColorChanged();
|
||||
update();
|
||||
}
|
||||
|
||||
qreal LineGraph::gridAlpha() const {
|
||||
return m_gridAlpha;
|
||||
}
|
||||
|
||||
void LineGraph::setGridAlpha(qreal alpha) {
|
||||
if (sameReal(m_gridAlpha, alpha)) return;
|
||||
m_gridAlpha = alpha;
|
||||
emit gridAlphaChanged();
|
||||
update();
|
||||
}
|
||||
|
||||
qreal LineGraph::labelAlpha() const {
|
||||
return m_labelAlpha;
|
||||
}
|
||||
|
||||
void LineGraph::setLabelAlpha(qreal alpha) {
|
||||
if (sameReal(m_labelAlpha, alpha)) return;
|
||||
m_labelAlpha = alpha;
|
||||
emit labelAlphaChanged();
|
||||
update();
|
||||
}
|
||||
|
||||
qreal LineGraph::fillAlpha() const {
|
||||
return m_fillAlpha;
|
||||
}
|
||||
|
||||
void LineGraph::setFillAlpha(qreal alpha) {
|
||||
if (sameReal(m_fillAlpha, alpha)) return;
|
||||
m_fillAlpha = alpha;
|
||||
emit fillAlphaChanged();
|
||||
update();
|
||||
}
|
||||
|
||||
qreal LineGraph::lineWidth() const {
|
||||
return m_lineWidth;
|
||||
}
|
||||
|
||||
void LineGraph::setLineWidth(qreal width) {
|
||||
if (sameReal(m_lineWidth, width)) return;
|
||||
m_lineWidth = width;
|
||||
emit lineWidthChanged();
|
||||
update();
|
||||
}
|
||||
|
||||
qreal LineGraph::markerDotRadius() const {
|
||||
return m_markerDotRadius;
|
||||
}
|
||||
|
||||
void LineGraph::setMarkerDotRadius(qreal radius) {
|
||||
if (sameReal(m_markerDotRadius, radius)) return;
|
||||
m_markerDotRadius = radius;
|
||||
emit markerDotRadiusChanged();
|
||||
update();
|
||||
}
|
||||
|
||||
qreal LineGraph::labelFontSize() const {
|
||||
return m_labelFontSize;
|
||||
}
|
||||
|
||||
void LineGraph::setLabelFontSize(qreal size) {
|
||||
if (sameReal(m_labelFontSize, size)) return;
|
||||
m_labelFontSize = size;
|
||||
emit labelFontSizeChanged();
|
||||
update();
|
||||
}
|
||||
|
||||
} // namespace ZShell::components
|
||||
Reference in New Issue
Block a user