v2.0.0
Loading...
Searching...
No Matches
spline.cpp
Go to the documentation of this file.
1//=============================================================================================================
16
17//=============================================================================================================
18// INCLUDES
19//=============================================================================================================
20
21#include "spline.h"
22
23#include "helpers/colormap.h"
24
25//=============================================================================================================
26// QT INCLUDES
27//=============================================================================================================
28
29#include <QPainter>
30#include <QPainterPath>
31#include <QPaintEvent>
32#include <QMouseEvent>
33#include <QLinearGradient>
34#include <QDebug>
35
36//=============================================================================================================
37// EIGEN INCLUDES
38//=============================================================================================================
39
40//=============================================================================================================
41// USED NAMESPACES
42//=============================================================================================================
43
44using namespace DISPLIB;
45
46//=============================================================================================================
47// DEFINE MEMBER METHODS
48//=============================================================================================================
49
50Spline::Spline(QWidget* parent, const QString& title)
51: QWidget(parent)
52, m_dMinAxisX(0)
53, m_dMaxAxisX(0)
57, m_bHasData(false)
58, m_bHasThresholds(false)
60{
61 Q_UNUSED(title)
62 setMinimumSize(200, 150);
63 setBackgroundRole(QPalette::Base);
64 setAutoFillBackground(true);
65}
66
67//=============================================================================================================
68
69double Spline::dataToPixelX(double dataX) const
70{
71 const int plotW = width() - leftMargin() - rightMargin();
73 return leftMargin();
74 return leftMargin() + (dataX - m_dMinAxisX) / (m_dMaxAxisX - m_dMinAxisX) * plotW;
75}
76
77//=============================================================================================================
78
79double Spline::dataToPixelY(double dataY) const
80{
81 const int plotH = height() - topMargin() - bottomMargin();
82 const double maxY = (m_iMaximumFrequency > 0) ? static_cast<double>(m_iMaximumFrequency) : 1.0;
83 return topMargin() + plotH - (dataY / maxY) * plotH;
84}
85
86//=============================================================================================================
87
88double Spline::pixelToDataX(double pixelX) const
89{
90 const int plotW = width() - leftMargin() - rightMargin();
91 if (plotW <= 0)
92 return m_dMinAxisX;
93 return m_dMinAxisX + (pixelX - leftMargin()) / plotW * (m_dMaxAxisX - m_dMinAxisX);
94}
95
96//=============================================================================================================
97
98void Spline::paintEvent(QPaintEvent* /*event*/)
99{
100 QPainter painter(this);
101 painter.setRenderHint(QPainter::Antialiasing);
102
103 const int w = width();
104 const int h = height();
105 const int mLeft = leftMargin();
106 const int mRight = rightMargin();
107 const int mTop = topMargin();
108 const int mBottom = bottomMargin();
109 const int plotW = w - mLeft - mRight;
110 const int plotH = h - mTop - mBottom;
111
112 Q_UNUSED(h)
113
114 if (plotW <= 0 || plotH <= 0)
115 return;
116
117 // Draw color map background if thresholds are set
118 if (m_bHasThresholds && !m_colorMap.isEmpty() && m_dMaxAxisX > m_dMinAxisX) {
119 double leftNorm = (m_dLeftThreshold - m_dMinAxisX) / (m_dMaxAxisX - m_dMinAxisX);
120 double middleNorm = (m_dMiddleThreshold - m_dMinAxisX) / (m_dMaxAxisX - m_dMinAxisX);
121 double rightNorm = (m_dRightThreshold - m_dMinAxisX) / (m_dMaxAxisX - m_dMinAxisX);
122
123 QLinearGradient gradient(mLeft, 0, mLeft + plotW, 0);
124 const int steps = 25;
125 double stepLeftMiddle = (middleNorm - leftNorm) / steps;
126 double stepMiddleRight = (rightNorm - middleNorm) / steps;
127
128 gradient.setColorAt(qBound(0.0, leftNorm, 1.0), ColorMap::valueToColor(0.0, m_colorMap));
129 for (int i = 1; i < steps; ++i) {
130 double pos1 = leftNorm + stepLeftMiddle * i;
131 double val1 = static_cast<double>(i) * (0.5 / static_cast<double>(steps));
132 gradient.setColorAt(qBound(0.0, pos1, 1.0), ColorMap::valueToColor(val1, m_colorMap));
133
134 double pos2 = middleNorm + stepMiddleRight * i;
135 double val2 = 0.5 + static_cast<double>(i) * (0.5 / static_cast<double>(steps));
136 gradient.setColorAt(qBound(0.0, pos2, 1.0), ColorMap::valueToColor(val2, m_colorMap));
137 }
138 gradient.setColorAt(qBound(0.0, rightNorm, 1.0), ColorMap::valueToColor(1.0, m_colorMap));
139
140 painter.fillRect(QRect(mLeft, mTop, plotW, plotH), gradient);
141 }
142
143 // Draw axes
144 painter.setPen(QPen(Qt::black, 1));
145 painter.drawLine(mLeft, mTop, mLeft, mTop + plotH);
146 painter.drawLine(mLeft, mTop + plotH, mLeft + plotW, mTop + plotH);
147
148 // Y-axis ticks
149 const int maxFreq = (m_iMaximumFrequency > 0) ? m_iMaximumFrequency : 1;
150 const int nYTicks = 5;
151 for (int i = 0; i <= nYTicks; ++i) {
152 int yVal = maxFreq * i / nYTicks;
153 int y = mTop + plotH - (plotH * i / nYTicks);
154 painter.drawLine(mLeft - 5, y, mLeft, y);
155 painter.drawText(QRect(0, y - 10, mLeft - 8, 20), Qt::AlignRight | Qt::AlignVCenter, QString::number(yVal));
156 }
157
158 // X-axis ticks
159 const int nXTicks = 5;
160 for (int i = 0; i <= nXTicks; ++i) {
161 double dataVal = m_dMinAxisX + (m_dMaxAxisX - m_dMinAxisX) * i / nXTicks;
162 int x = mLeft + plotW * i / nXTicks;
163 painter.drawLine(x, mTop + plotH, x, mTop + plotH + 5);
164 painter.drawText(QRect(x - 30, mTop + plotH + 6, 60, 20), Qt::AlignCenter, QString::number(dataVal, 'g', 3));
165 }
166
167 if (!m_bHasData || m_seriesData.isEmpty())
168 return;
169
170 // Draw spline curve using Catmull-Rom to cubic Bezier conversion
171 QPainterPath path;
172 const int n = m_seriesData.size();
173
174 if (n == 1) {
175 // Single point: draw a dot
176 double px = dataToPixelX(m_seriesData[0].x());
177 double py = dataToPixelY(m_seriesData[0].y());
178 painter.setBrush(QColor(70, 130, 180));
179 painter.drawEllipse(QPointF(px, py), 3, 3);
180 } else {
181 // Catmull-Rom spline
182 auto getPoint = [this](int idx) -> QPointF {
183 const int count = m_seriesData.size();
184 if (idx < 0)
185 idx = 0;
186 if (idx >= count)
187 idx = count - 1;
188 return QPointF(dataToPixelX(m_seriesData[idx].x()),
189 dataToPixelY(m_seriesData[idx].y()));
190 };
191
192 path.moveTo(getPoint(0));
193
194 for (int i = 0; i < n - 1; ++i) {
195 QPointF p0 = getPoint(i - 1);
196 QPointF p1 = getPoint(i);
197 QPointF p2 = getPoint(i + 1);
198 QPointF p3 = getPoint(i + 2);
199
200 // Catmull-Rom to cubic Bezier control points
201 QPointF cp1(p1.x() + (p2.x() - p0.x()) / 6.0,
202 p1.y() + (p2.y() - p0.y()) / 6.0);
203 QPointF cp2(p2.x() - (p3.x() - p1.x()) / 6.0,
204 p2.y() - (p3.y() - p1.y()) / 6.0);
205
206 path.cubicTo(cp1, cp2, p2);
207 }
208
209 painter.setPen(QPen(QColor(70, 130, 180), 2));
210 painter.setBrush(Qt::NoBrush);
211 painter.drawPath(path);
212 }
213
214 // Draw threshold lines
215 if (m_bHasThresholds) {
216 auto drawThresholdLine = [&](double dataX, const QColor& color) {
217 if (dataX >= m_dMinAxisX && dataX <= m_dMaxAxisX) {
218 double px = dataToPixelX(dataX);
219 painter.setPen(QPen(color, 2));
220 painter.drawLine(QPointF(px, mTop), QPointF(px, mTop + plotH));
221 }
222 };
223
224 drawThresholdLine(m_dLeftThreshold, Qt::red);
225 drawThresholdLine(m_dMiddleThreshold, Qt::green);
226 drawThresholdLine(m_dRightThreshold, Qt::blue);
227 }
228}
229
230//=============================================================================================================
231
232void Spline::mousePressEvent(QMouseEvent* event)
233{
234 if (!m_bHasData || m_seriesData.isEmpty()) {
235 qDebug() << "Data set not found.";
236 return;
237 }
238
239 double dataX = pixelToDataX(event->position().x());
240
241 // Check bounds
242 if (dataX < m_dMinAxisX || dataX > m_dMaxAxisX)
243 return;
244
245 if (event->buttons() == Qt::LeftButton) {
246 if (dataX < m_dMiddleThreshold && dataX < m_dRightThreshold) {
247 m_dLeftThreshold = dataX;
248 m_bHasThresholds = true;
249 }
250 } else if (event->buttons() == Qt::MiddleButton) {
251 if (dataX > m_dLeftThreshold && dataX < m_dRightThreshold) {
252 m_dMiddleThreshold = dataX;
253 m_bHasThresholds = true;
254 }
255 } else if (event->buttons() == Qt::RightButton) {
256 if (dataX > m_dLeftThreshold && dataX > m_dMiddleThreshold) {
257 m_dRightThreshold = dataX;
258 m_bHasThresholds = true;
259 }
260 }
261
262 double emitLeft = m_dLeftThreshold * pow(10, m_vecResultExponentValues[0]);
263 double emitMiddle = m_dMiddleThreshold * pow(10, m_vecResultExponentValues[0]);
264 double emitRight = m_dRightThreshold * pow(10, m_vecResultExponentValues[0]);
265 emit borderChanged(emitLeft, emitMiddle, emitRight);
266
268 QWidget::update();
269}
270
271//=============================================================================================================
272
273void Spline::setThreshold(const QVector3D& vecThresholdValues)
274{
275 if (!m_bHasData || m_seriesData.isEmpty()) {
276 qDebug() << "Data set not found.";
277 return;
278 }
279
280 QVector3D correctedVectorThreshold = correctionDisplayTrueValue(vecThresholdValues, "up");
281
282 // Check if all values are within range
283 if (correctedVectorThreshold.x() < m_dMinAxisX || correctedVectorThreshold.y() < m_dMinAxisX || correctedVectorThreshold.z() < m_dMinAxisX ||
284 correctedVectorThreshold.x() > m_dMaxAxisX || correctedVectorThreshold.y() > m_dMaxAxisX || correctedVectorThreshold.z() > m_dMaxAxisX) {
285 qDebug() << "One or more of the values given are out of the minimum and maximum range. Changed to default thresholds.";
286 // 1 % inside each end of the span; scaling the limits themselves leaves the axis for negative values
287 const double span = m_dMaxAxisX - m_dMinAxisX;
288 m_dLeftThreshold = m_dMinAxisX + 0.01 * span;
289 m_dMiddleThreshold = m_dMinAxisX + 0.5 * span;
290 m_dRightThreshold = m_dMaxAxisX - 0.01 * span;
291 } else {
292 // Sort the three values into left < middle < right
293 double vals[3] = {static_cast<double>(correctedVectorThreshold.x()),
294 static_cast<double>(correctedVectorThreshold.y()),
295 static_cast<double>(correctedVectorThreshold.z())};
296 std::sort(vals, vals + 3);
297 m_dLeftThreshold = vals[0];
298 m_dMiddleThreshold = vals[1];
299 m_dRightThreshold = vals[2];
300 }
301
302 m_bHasThresholds = true;
304 QWidget::update();
305}
306
307//=============================================================================================================
308
309void Spline::setColorMap(const QString& colorMap)
310{
311 m_colorMap = colorMap;
312 QWidget::update();
313}
314
315//=============================================================================================================
316
317const QVector3D& Spline::getThreshold()
318{
319 QVector3D originalVector;
320 originalVector.setX(static_cast<float>(m_dLeftThreshold));
321 originalVector.setY(static_cast<float>(m_dMiddleThreshold));
322 originalVector.setZ(static_cast<float>(m_dRightThreshold));
323 m_vecReturnVector = correctionDisplayTrueValue(originalVector, "down");
324 return m_vecReturnVector;
325}
326
327//=============================================================================================================
328
329QVector3D Spline::correctionDisplayTrueValue(QVector3D vecOriginalValues, QString upOrDown)
330{
331 QVector3D returnCorrectedVector;
332
333 if (m_vecResultExponentValues.rows() > 0) {
334 int exponent = 0;
335 if (upOrDown == "up") {
336 if (m_vecResultExponentValues[0] < 0) {
337 exponent = std::abs(m_vecResultExponentValues[0]);
338 } else if (m_vecResultExponentValues[0] > 0) {
339 exponent = -(std::abs(m_vecResultExponentValues[0]));
340 }
341 } else if (upOrDown == "down") {
342 if (m_vecResultExponentValues[0] < 0) {
343 exponent = -(std::abs(m_vecResultExponentValues[0]));
344 } else if (m_vecResultExponentValues[0] > 0) {
345 exponent = std::abs(m_vecResultExponentValues[0]);
346 }
347 } else {
348 qDebug() << "Spline::correctionDisplayTrueValue error.";
349 }
350
351 returnCorrectedVector.setX(vecOriginalValues.x() * static_cast<float>(pow(10, exponent)));
352 returnCorrectedVector.setY(vecOriginalValues.y() * static_cast<float>(pow(10, exponent)));
353 returnCorrectedVector.setZ(vecOriginalValues.z() * static_cast<float>(pow(10, exponent)));
354 }
355
356 return returnCorrectedVector;
357}
Histogram widget with a cubic spline overlay and click-driven threshold markers.
Static scalar-to-colour lookup helpers (Jet, Hot, Bone, Viridis, Cool, RedBlue, MNE) used by every pl...
2-D display widgets and visualisation helpers (charts, topography, colour maps).
static QRgb valueToColor(double v, const QString &sMap)
Definition colormap.h:683
double m_dLeftThreshold
Definition spline.h:262
QString m_colorMap
Definition spline.h:268
double dataToPixelX(double dataX) const
Definition spline.cpp:69
QVector3D correctionDisplayTrueValue(QVector3D vecOriginalValues, QString upOrDown)
Definition spline.cpp:329
void mousePressEvent(QMouseEvent *event) override
Definition spline.cpp:232
void setThreshold(const QVector3D &vecThresholdValues)
Definition spline.cpp:273
QList< QPointF > m_seriesData
Definition spline.h:261
bool m_bHasData
Definition spline.h:265
int m_iMaximumFrequency
Definition spline.h:267
double m_dMiddleThreshold
Definition spline.h:263
double pixelToDataX(double pixelX) const
Definition spline.cpp:88
int topMargin() const
Definition spline.h:245
QVector3D m_vecReturnVector
Definition spline.h:269
Eigen::VectorXi m_vecResultExponentValues
Definition spline.h:174
Spline(QWidget *parent=nullptr, const QString &title="Spline Histogram")
Definition spline.cpp:50
int bottomMargin() const
Definition spline.h:256
double m_dRightThreshold
Definition spline.h:264
void borderChanged(double leftThreshold, double middleThreshold, double rightThreshold)
void setColorMap(const QString &colorMap)
Definition spline.cpp:309
double dataToPixelY(double dataY) const
Definition spline.cpp:79
double m_dMaxAxisX
Definition spline.h:176
const QVector3D & getThreshold()
Definition spline.cpp:317
double m_dMinAxisX
Definition spline.h:175
void paintEvent(QPaintEvent *event) override
Definition spline.cpp:98
int rightMargin() const
Definition spline.h:234
bool m_bHasThresholds
Definition spline.h:266
int leftMargin() const
Definition spline.h:223