DynExp
Highly flexible laboratory automation for dynamically changing experiments.
Loading...
Searching...
No Matches
GraphUtil.cpp
Go to the documentation of this file.
1// This file is part of DynExp.
2
3#include "GraphUtil.h"
4
6{
8 {
9 switch (Multiplier)
10 {
11 case 0: return "";
12 case 3: return "m";
13 case 6: return "u";
14 case 9: return "n";
15 case 12: return "p";
16 default: return "?";
17 }
18 }
19
21 {
22 Multiplier = 0;
26 MinValues = {
27 std::numeric_limits<QPointFValueType>::max(),
28 std::numeric_limits<QPointFValueType>::max()
29 };
30 MaxValues = {
31 std::numeric_limits<QPointFValueType>::lowest(),
32 std::numeric_limits<QPointFValueType>::lowest()
33 };
34
36 }
37
38 void LineGraphPlotInfo::GenerateSampleTimingInfo(std::vector<DynExpInstr::DataStreamBase::BasicSampleListType>& BasicSamplesSeries)
39 {
40 bool TimingInfoFound = false;
41 Multiplier = std::numeric_limits<decltype(Multiplier)>::max();
42
44 {
45 for (auto& Samples : BasicSamplesSeries)
46 {
47 if (Samples.empty())
48 continue;
49
50 // Use stable_sort() to not affect the order of samples with equal time, in case the data
51 // stream supports sample timing but the user of the stream ignores it.
52 std::stable_sort(Samples.begin(), Samples.end(), [](const auto& a, const auto& b) {
53 return a.Time < b.Time;
54 });
55
56 TimingInfoFound = true;
57
58 // Switch back to use sample indices as x values if all Time values are equal.
59 if (Samples.front().Time == Samples.back().Time && Samples.size() > 1)
60 {
62 Multiplier = 0;
63
64 break;
65 }
66
68 {
69 // Determine best order of magnitude to display the time with if time is given in seconds.
70 if (std::abs(Samples.front().Time) < 1e-9 && std::abs(Samples.back().Time) < 1e-9)
71 Multiplier = std::min(Multiplier, 12u);
72 else if (std::abs(Samples.front().Time) < 1e-6 && std::abs(Samples.back().Time) < 1e-6)
73 Multiplier = std::min(Multiplier, 9u);
74 else if (std::abs(Samples.front().Time) < 1e-3 && std::abs(Samples.back().Time) < 1e-3)
75 Multiplier = std::min(Multiplier, 6u);
76 else if (std::abs(Samples.front().Time) < 1.0 && std::abs(Samples.back().Time) < 1.0)
77 Multiplier = std::min(Multiplier, 3u);
78 else
79 Multiplier = 0;
80 }
81 else
82 Multiplier = 0;
83 }
84 }
85
86 if (!TimingInfoFound)
87 Multiplier = 0;
88 }
89
90 bool LineGraphPlotInfo::ProcessSamples(QList<QPointF> RawSamples, QList<QPointF>& Samples, const size_t SeriesIndex)
91 {
92 if (RawSamples.empty())
93 return false;
94
95 auto YMin{ std::numeric_limits<QPointFValueType>::max() };
96 auto YMax{ std::numeric_limits<QPointFValueType>::lowest() };
97
98 for (qsizetype i = 0; i < RawSamples.size(); ++i)
99 {
100 const auto X = ApplyXLog(!DynExp::Units::IsIndexUnit(XUnit) ? RawSamples.at(i).x() * std::pow(10.0, Multiplier) : i);
101 const auto Y = ApplyYLog(RawSamples.at(i).y());
102 Samples.append({ X, Y });
103
104 if (std::isfinite(Y))
105 {
106 YMin = std::min(YMin, Y);
107 YMax = std::max(YMax, Y);
108 }
109
110 // To avoid a second loop, do the calculation with axes limits from the previous run.
111 // Find hovered point for series with more than a single sample.
112 if (i)
113 CheckSampleHovered(X, Y, SeriesIndex);
114 }
115
116 MaxSampleCountPerSeries = std::max(MaxSampleCountPerSeries, Util::NumToT<size_t>(RawSamples.size()));
117 MinValues = { std::min(MinValues.x(), Samples.first().x()), std::min(MinValues.y(), YMin) };
118 MaxValues = { std::max(MaxValues.x(), Samples.last().x()), std::max(MaxValues.y(), YMax) };
119
120 return true;
121 }
122
124 QList<QPointF>& Samples, const size_t SeriesIndex)
125 {
126 if (BasicSamples.empty())
127 return false;
128
129 auto YMin{ std::numeric_limits<QPointFValueType>::max() };
130 auto YMax{ std::numeric_limits<QPointFValueType>::lowest() };
131
132 for (size_t i = 0; i < BasicSamples.size(); ++i)
133 {
134 const auto X = ApplyXLog(!DynExp::Units::IsIndexUnit(XUnit) ? BasicSamples[i].Time * std::pow(10.0, Multiplier) : i);
135 const auto Y = ApplyYLog(BasicSamples[i].Value);
136 Samples.append({ X, Y });
137
138 if (std::isfinite(Y))
139 {
140 YMin = std::min(YMin, Y);
141 YMax = std::max(YMax, Y);
142 }
143
144 // To avoid a second loop, do the calculation with axes limits from the previous run.
145 // Find hovered point for series with more than a single sample.
146 if (i)
147 CheckSampleHovered(X, Y, SeriesIndex);
148 }
149
150 MaxSampleCountPerSeries = std::max(MaxSampleCountPerSeries, BasicSamples.size());
151 MinValues = { std::min(MinValues.x(), Samples.first().x()), std::min(MinValues.y(), YMin) };
152 MaxValues = { std::max(MaxValues.x(), Samples.last().x()), std::max(MaxValues.y(), YMax) };
153
154 return true;
155 }
156
158 QList<QPointF>& Samples, const size_t SeriesIndex)
159 {
160 if (!Spectrum.HasSpectrum())
161 return false;
162
163 auto YMin{ std::numeric_limits<QPointFValueType>::max() };
164 auto YMax{ std::numeric_limits<QPointFValueType>::lowest() };
165
166 size_t i = 0;
167 for (const auto& Sample : Spectrum.GetSpectrum())
168 {
169 const auto X = ApplyXLog(!DynExp::Units::IsIndexUnit(XUnit) ? Sample.first : i);
170 const auto Y = ApplyYLog(Sample.second);
171 Samples.append({ X, Y });
172
173 if (std::isfinite(Y))
174 {
175 YMin = std::min(YMin, Y);
176 YMax = std::max(YMax, Y);
177 }
178
179 // To avoid a second loop, do the calculation with axes limits from the previous run.
180 // Find hovered point for series with more than a single sample.
181 if (i)
182 CheckSampleHovered(X, Y, SeriesIndex);
183
184 ++i;
185 }
186
187 MaxSampleCountPerSeries = std::max(MaxSampleCountPerSeries, Spectrum.GetSpectrum().size());
188 MinValues = { std::min(MinValues.x(), Samples.first().x()), std::min(MinValues.y(), YMin) };
189 MaxValues = { std::max(MaxValues.x(), Samples.last().x()), std::max(MaxValues.y(), YMax) };
190
191 return true;
192 }
193
195 {
197 {
198 MinValues = { 0., 0. };
199 MaxValues = { 1., 1. };
200 }
201 else
202 {
204 {
205 MaxValues.setY(std::max(std::abs(MinValues.y()), std::abs(MaxValues.y())));
206 MinValues.setY(0.);
207 }
208 else if (MinValues.y() == MaxValues.y())
209 {
210 auto YRangeDelta = std::abs(MinValues.y()) * .01;
211 YRangeDelta = YRangeDelta == 0. ? 2. : YRangeDelta;
212 MinValues.setY(MinValues.y() - YRangeDelta);
213 MaxValues.setY(MaxValues.y() + YRangeDelta);
214 }
215 }
216 }
217
218 void LineGraphPlotInfo::ReprocessSamples(const QList<QPointF>& Samples, const size_t SeriesIndex)
219 {
222
223 for (size_t i = 0; i < Util::NumToT<size_t>(Samples.size()); ++i)
224 {
225 // Find hovered point for series with more than a single sample.
226 if (i)
227 CheckSampleHovered(Samples.at(i).x(), Samples.at(i).y(), SeriesIndex);
228 }
229 }
230
231 void LineGraphPlotInfo::CheckSampleHovered(const QPointFValueType X, const QPointFValueType Y, const size_t SeriesIndex)
232 {
233 if (!(LastMinValues.isNull() && LastMaxValues.isNull()) && !CursorPosition.isNull())
234 {
235 const auto XNormalized = (X - LastMinValues.x()) / (LastMaxValues.x() - LastMinValues.x());
236 const auto YNormalized = (Y - LastMinValues.y()) / (LastMaxValues.y() - LastMinValues.y());
237 const auto XDist = XNormalized - CursorPosition.x();
238 const auto YDist = YNormalized - CursorPosition.y();
239 const auto Dist = XDist * XDist + YDist * YDist;
240
241 if (Dist < 0.001 && Dist < HoveredDistance)
242 {
243 HoveredDistance = Dist;
244 HoveredPoint = { XNormalized, YNormalized };
245 HoveredSample = { X, Y };
246 HoveredSeries = SeriesIndex;
247 }
248 }
249 }
250
252 {
253 HoveredPoint = {};
254 HoveredSample = {};
255 HoveredDistance = std::numeric_limits<QPointFValueType>::max();
256 }
257}
Defines helper classes for QML graphs.
std::vector< BasicSample > BasicSampleListType
Type of a list containing data stream samples of type BasicSample.
Type describing a spectrum as acquired by the Spectrometer instrument.
bool HasSpectrum() const noexcept
Indicates whether the spectrum is empty.
auto & GetSpectrum() const noexcept
Getter for Samples.
bool IsIndexUnit(UnitType Unit)
Checks whether Unit is a sample index unit (UnitType::Index).
Definition Units.cpp:53
@ Index
Sample index.
bool IsTimeUnitStrict(UnitType Unit)
Checks whether Unit is a time unit (excluding UnitType::Index).
Definition Units.cpp:48
QPointF LastMinValues
Lower axes limits of x and y axes from previous graph update.
Definition GraphUtil.h:155
size_t MaxSampleCountPerSeries
Indicates the number of samples contained in the longest data series to plot. A value of 0 indicates ...
Definition GraphUtil.h:140
decltype(std::declval< QPointF >().x()) QPointFValueType
Data type of sample values for plotting.
Definition GraphUtil.h:21
DynExp::Units::UnitType XUnit
Joint unit of the plot's x axis.
Definition GraphUtil.h:104
QPointF CursorPosition
Relative position of the mouse cursor inside the coordinate system (between 0. and 1....
Definition GraphUtil.h:165
void AdjustAxesLimits()
Adjusts MinValues and MaxValues to best display all data series.
QPointF HoveredPoint
Hovered point belonging to any series in normalized coordinate system coordinates (between 0....
Definition GraphUtil.h:170
size_t HoveredSeries
Index of the data series the hovered point belongs to.
Definition GraphUtil.h:180
void CheckSampleHovered(const QPointFValueType X, const QPointFValueType Y, const size_t SeriesIndex)
Checks whether the given sample is close to the mouse cursor (hovered).
bool ProcessSpectrum(DynExpInstr::SpectrometerData::SpectrumType Spectrum, QList< QPointF > &Samples, const size_t SeriesIndex)
Converts Spectrum to displayable format and stores its minimal and maximal values in this DynExpLineG...
QPointF MinValues
Current lower axes limits of x and y axes.
Definition GraphUtil.h:145
void ReprocessSamples(const QList< QPointF > &Samples, const size_t SeriesIndex)
If data plotting is not enabled (running), the available and already plotted samples have to be repro...
double ApplyXLog(double Value) const
Definition GraphUtil.h:188
QPointF LastMaxValues
Upper axes limits of x and y axes from previous graph update.
Definition GraphUtil.h:160
QString GetMultiplierLabel() const
Returns the multiplier prefix associated with Multiplier.
Definition GraphUtil.cpp:7
QPointFValueType HoveredDistance
Distance between the mouse cursor and the hovered point.
Definition GraphUtil.h:185
void ResetHoveredSample()
Removes the stored hovered sample.
void Reset()
Resets the instance to generate information from new sample series.
Definition GraphUtil.cpp:20
unsigned int Multiplier
Best order of magnitude to scale the plot's joint time axis with.
Definition GraphUtil.h:134
bool ProcessBasicSamples(DynExpInstr::DataStreamBase::BasicSampleListType BasicSamples, QList< QPointF > &Samples, const size_t SeriesIndex)
Converts BasicSamples to displayable format and stores their minimal and maximal values in this DynEx...
void GenerateSampleTimingInfo(std::vector< DynExpInstr::DataStreamBase::BasicSampleListType > &BasicSamplesSeries)
Extracts sample timing information for x axis scaling from BasicSamples and stores the results in thi...
Definition GraphUtil.cpp:38
double ApplyYLog(double Value) const
Definition GraphUtil.h:189
QPointF HoveredSample
Hovered sample belonging to any series in the graphs's real coordinate system.
Definition GraphUtil.h:175
bool ProcessSamples(QList< QPointF > RawSamples, QList< QPointF > &Samples, const size_t SeriesIndex)
Converts RawSamples to displayable format and stores their minimal and maximal values in this DynExpL...
Definition GraphUtil.cpp:90
QPointF MaxValues
Current upper axes limits of x and y axes.
Definition GraphUtil.h:150