mirror of
https://github.com/Serial-Studio/Serial-Studio.git
synced 2025-01-31 17:42:55 +08:00
parent
cabe4e8c77
commit
e333783784
@ -66,9 +66,10 @@ Widgets.Pane {
|
|||||||
Rectangle {
|
Rectangle {
|
||||||
z: 2
|
z: 2
|
||||||
id: header
|
id: header
|
||||||
height: actionsLayout.implicitHeight + 12
|
|
||||||
visible: Cpp_UI_Dashboard.actionCount > 0
|
|
||||||
color: Cpp_ThemeManager.colors["groupbox_background"]
|
color: Cpp_ThemeManager.colors["groupbox_background"]
|
||||||
|
height: visible ? actionsLayout.implicitHeight + 12 : 0
|
||||||
|
visible: Cpp_UI_Dashboard.actionCount > 0 && !Cpp_CSV_Player.isOpen
|
||||||
|
|
||||||
anchors {
|
anchors {
|
||||||
top: parent.top
|
top: parent.top
|
||||||
left: parent.left
|
left: parent.left
|
||||||
@ -122,8 +123,8 @@ Widgets.Pane {
|
|||||||
contentWidth: width
|
contentWidth: width
|
||||||
anchors.leftMargin: 8
|
anchors.leftMargin: 8
|
||||||
anchors.bottomMargin: 8
|
anchors.bottomMargin: 8
|
||||||
anchors.topMargin: header.height + 8
|
|
||||||
contentHeight: grid.height
|
contentHeight: grid.height
|
||||||
|
anchors.topMargin: header.height + 8
|
||||||
|
|
||||||
ScrollBar.vertical: ScrollBar {
|
ScrollBar.vertical: ScrollBar {
|
||||||
id: scroll
|
id: scroll
|
||||||
|
@ -56,9 +56,9 @@ Item {
|
|||||||
xMax: root.model.maxX
|
xMax: root.model.maxX
|
||||||
yMin: root.model.minY
|
yMin: root.model.minY
|
||||||
yMax: root.model.maxY
|
yMax: root.model.maxY
|
||||||
xLabel: qsTr("Samples")
|
|
||||||
curveColors: [root.color]
|
curveColors: [root.color]
|
||||||
yLabel: root.model.yLabel
|
yLabel: root.model.yLabel
|
||||||
|
xLabel: root.model.xLabel
|
||||||
xAxis.tickInterval: root.model.xTickInterval
|
xAxis.tickInterval: root.model.xTickInterval
|
||||||
yAxis.tickInterval: root.model.yTickInterval
|
yAxis.tickInterval: root.model.yTickInterval
|
||||||
Component.onCompleted: graph.addSeries(lineSeries)
|
Component.onCompleted: graph.addSeries(lineSeries)
|
||||||
|
@ -22,6 +22,30 @@
|
|||||||
|
|
||||||
#include "JSON/Action.h"
|
#include "JSON/Action.h"
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Reads a value from a QJsonObject based on a key, returning a default
|
||||||
|
* value if the key does not exist.
|
||||||
|
*
|
||||||
|
* This function checks if the given key exists in the provided QJsonObject.
|
||||||
|
* If the key is found, it returns the associated value. Otherwise, it returns
|
||||||
|
* the specified default value.
|
||||||
|
*
|
||||||
|
* @param object The QJsonObject to read the data from.
|
||||||
|
* @param key The key to look for in the QJsonObject.
|
||||||
|
* @param defaultValue The value to return if the key is not found in the
|
||||||
|
* QJsonObject.
|
||||||
|
* @return The value associated with the key, or the defaultValue if the key is
|
||||||
|
* not present.
|
||||||
|
*/
|
||||||
|
static QVariant SAFE_READ(const QJsonObject &object, const QString &key,
|
||||||
|
const QVariant &defaultValue)
|
||||||
|
{
|
||||||
|
if (object.contains(key))
|
||||||
|
return object.value(key);
|
||||||
|
|
||||||
|
return defaultValue;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief Constructs an Action object with a specified action ID.
|
* @brief Constructs an Action object with a specified action ID.
|
||||||
*
|
*
|
||||||
@ -124,11 +148,10 @@ bool JSON::Action::read(const QJsonObject &object)
|
|||||||
{
|
{
|
||||||
if (!object.isEmpty())
|
if (!object.isEmpty())
|
||||||
{
|
{
|
||||||
m_icon = object.value(QStringLiteral("icon")).toString();
|
m_txData = SAFE_READ(object, "txData", "").toString();
|
||||||
m_txData = object.value(QStringLiteral("txData")).toString();
|
m_eolSequence = SAFE_READ(object, "eol", "").toString();
|
||||||
m_eolSequence = object.value(QStringLiteral("eol")).toString();
|
m_icon = SAFE_READ(object, "icon", "").toString().simplified();
|
||||||
m_title = object.value(QStringLiteral("title")).toString().simplified();
|
m_title = SAFE_READ(object, "title", "").toString().simplified();
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -22,6 +22,33 @@
|
|||||||
|
|
||||||
#include "JSON/Dataset.h"
|
#include "JSON/Dataset.h"
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Reads a value from a QJsonObject based on a key, returning a default
|
||||||
|
* value if the key does not exist.
|
||||||
|
*
|
||||||
|
* This function checks if the given key exists in the provided QJsonObject.
|
||||||
|
* If the key is found, it returns the associated value. Otherwise, it returns
|
||||||
|
* the specified default value.
|
||||||
|
*
|
||||||
|
* @param object The QJsonObject to read the data from.
|
||||||
|
* @param key The key to look for in the QJsonObject.
|
||||||
|
* @param defaultValue The value to return if the key is not found in the
|
||||||
|
* QJsonObject.
|
||||||
|
* @return The value associated with the key, or the defaultValue if the key is
|
||||||
|
* not present.
|
||||||
|
*/
|
||||||
|
static QVariant SAFE_READ(const QJsonObject &object, const QString &key,
|
||||||
|
const QVariant &defaultValue)
|
||||||
|
{
|
||||||
|
if (object.contains(key))
|
||||||
|
return object.value(key);
|
||||||
|
|
||||||
|
return defaultValue;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Constructor function, initializes default values
|
||||||
|
*/
|
||||||
JSON::Dataset::Dataset(const int groupId, const int datasetId)
|
JSON::Dataset::Dataset(const int groupId, const int datasetId)
|
||||||
: m_fft(false)
|
: m_fft(false)
|
||||||
, m_led(false)
|
, m_led(false)
|
||||||
@ -36,6 +63,7 @@ JSON::Dataset::Dataset(const int groupId, const int datasetId)
|
|||||||
, m_min(0)
|
, m_min(0)
|
||||||
, m_alarm(0)
|
, m_alarm(0)
|
||||||
, m_ledHigh(1)
|
, m_ledHigh(1)
|
||||||
|
, m_xAxisId(-1)
|
||||||
, m_fftSamples(256)
|
, m_fftSamples(256)
|
||||||
, m_fftSamplingRate(100)
|
, m_fftSamplingRate(100)
|
||||||
, m_groupId(groupId)
|
, m_groupId(groupId)
|
||||||
@ -147,6 +175,15 @@ const QString &JSON::Dataset::widget() const
|
|||||||
return m_widget;
|
return m_widget;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return The frame index for the data source for the x-axis, -1 when the
|
||||||
|
* x axis data should be automatically generated by Serial Studio.
|
||||||
|
*/
|
||||||
|
int JSON::Dataset::xAxisId() const
|
||||||
|
{
|
||||||
|
return m_xAxisId;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns the maximum freq. for the FFT transform
|
* Returns the maximum freq. for the FFT transform
|
||||||
*/
|
*/
|
||||||
@ -210,6 +247,7 @@ QJsonObject JSON::Dataset::serialize() const
|
|||||||
object.insert(QStringLiteral("index"), m_index);
|
object.insert(QStringLiteral("index"), m_index);
|
||||||
object.insert(QStringLiteral("alarm"), m_alarm);
|
object.insert(QStringLiteral("alarm"), m_alarm);
|
||||||
object.insert(QStringLiteral("graph"), m_graph);
|
object.insert(QStringLiteral("graph"), m_graph);
|
||||||
|
object.insert(QStringLiteral("xAxis"), m_xAxisId);
|
||||||
object.insert(QStringLiteral("ledHigh"), m_ledHigh);
|
object.insert(QStringLiteral("ledHigh"), m_ledHigh);
|
||||||
object.insert(QStringLiteral("fftSamples"), m_fftSamples);
|
object.insert(QStringLiteral("fftSamples"), m_fftSamples);
|
||||||
object.insert(QStringLiteral("value"), m_value.simplified());
|
object.insert(QStringLiteral("value"), m_value.simplified());
|
||||||
@ -229,21 +267,22 @@ bool JSON::Dataset::read(const QJsonObject &object)
|
|||||||
{
|
{
|
||||||
if (!object.isEmpty())
|
if (!object.isEmpty())
|
||||||
{
|
{
|
||||||
m_fft = object.value(QStringLiteral("fft")).toBool();
|
m_min = SAFE_READ(object, "min", 0).toDouble();
|
||||||
m_led = object.value(QStringLiteral("led")).toBool();
|
m_max = SAFE_READ(object, "max", 0).toDouble();
|
||||||
m_log = object.value(QStringLiteral("log")).toBool();
|
m_index = SAFE_READ(object, "index", 0).toInt();
|
||||||
m_min = object.value(QStringLiteral("min")).toDouble();
|
m_fft = SAFE_READ(object, "fft", false).toBool();
|
||||||
m_max = object.value(QStringLiteral("max")).toDouble();
|
m_led = SAFE_READ(object, "led", false).toBool();
|
||||||
m_index = object.value(QStringLiteral("index")).toInt();
|
m_log = SAFE_READ(object, "log", false).toBool();
|
||||||
m_alarm = object.value(QStringLiteral("alarm")).toDouble();
|
m_xAxisId = SAFE_READ(object, "xAxis", 0).toInt();
|
||||||
m_graph = object.value(QStringLiteral("graph")).toBool();
|
m_alarm = SAFE_READ(object, "alarm", 0).toDouble();
|
||||||
m_ledHigh = object.value(QStringLiteral("ledHigh")).toDouble();
|
m_graph = SAFE_READ(object, "graph", false).toBool();
|
||||||
m_fftSamples = object.value(QStringLiteral("fftSamples")).toInt();
|
m_ledHigh = SAFE_READ(object, "ledHigh", 0).toDouble();
|
||||||
m_title = object.value(QStringLiteral("title")).toString().simplified();
|
m_fftSamples = SAFE_READ(object, "fftSamples", 256).toInt();
|
||||||
m_value = object.value(QStringLiteral("value")).toString().simplified();
|
m_title = SAFE_READ(object, "title", "").toString().simplified();
|
||||||
m_units = object.value(QStringLiteral("units")).toString().simplified();
|
m_value = SAFE_READ(object, "value", "").toString().simplified();
|
||||||
m_widget = object.value(QStringLiteral("widget")).toString().simplified();
|
m_units = SAFE_READ(object, "units", "").toString().simplified();
|
||||||
m_fftSamplingRate = object.value(QStringLiteral("fftSamplingRate")).toInt();
|
m_widget = SAFE_READ(object, "widget", "").toString().simplified();
|
||||||
|
m_fftSamplingRate = SAFE_READ(object, "fftSamplingRate", 100).toInt();
|
||||||
if (m_value.isEmpty())
|
if (m_value.isEmpty())
|
||||||
m_value = QStringLiteral("--.--");
|
m_value = QStringLiteral("--.--");
|
||||||
|
|
||||||
|
@ -83,6 +83,8 @@ public:
|
|||||||
[[nodiscard]] double max() const;
|
[[nodiscard]] double max() const;
|
||||||
[[nodiscard]] double alarm() const;
|
[[nodiscard]] double alarm() const;
|
||||||
[[nodiscard]] double ledHigh() const;
|
[[nodiscard]] double ledHigh() const;
|
||||||
|
|
||||||
|
[[nodiscard]] int xAxisId() const;
|
||||||
[[nodiscard]] int fftSamples() const;
|
[[nodiscard]] int fftSamples() const;
|
||||||
[[nodiscard]] int fftSamplingRate() const;
|
[[nodiscard]] int fftSamplingRate() const;
|
||||||
|
|
||||||
@ -121,6 +123,7 @@ private:
|
|||||||
int m_fftSamplingRate;
|
int m_fftSamplingRate;
|
||||||
|
|
||||||
int m_groupId;
|
int m_groupId;
|
||||||
|
int m_xAxisId;
|
||||||
int m_datasetId;
|
int m_datasetId;
|
||||||
|
|
||||||
friend class JSON::ProjectModel;
|
friend class JSON::ProjectModel;
|
||||||
|
@ -22,6 +22,30 @@
|
|||||||
|
|
||||||
#include "JSON/Frame.h"
|
#include "JSON/Frame.h"
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Reads a value from a QJsonObject based on a key, returning a default
|
||||||
|
* value if the key does not exist.
|
||||||
|
*
|
||||||
|
* This function checks if the given key exists in the provided QJsonObject.
|
||||||
|
* If the key is found, it returns the associated value. Otherwise, it returns
|
||||||
|
* the specified default value.
|
||||||
|
*
|
||||||
|
* @param object The QJsonObject to read the data from.
|
||||||
|
* @param key The key to look for in the QJsonObject.
|
||||||
|
* @param defaultValue The value to return if the key is not found in the
|
||||||
|
* QJsonObject.
|
||||||
|
* @return The value associated with the key, or the defaultValue if the key is
|
||||||
|
* not present.
|
||||||
|
*/
|
||||||
|
static QVariant SAFE_READ(const QJsonObject &object, const QString &key,
|
||||||
|
const QVariant &defaultValue)
|
||||||
|
{
|
||||||
|
if (object.contains(key))
|
||||||
|
return object.value(key);
|
||||||
|
|
||||||
|
return defaultValue;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Destructor function, free memory used by the @c Group objects before
|
* Destructor function, free memory used by the @c Group objects before
|
||||||
* destroying an instance of this class.
|
* destroying an instance of this class.
|
||||||
@ -96,16 +120,15 @@ bool JSON::Frame::read(const QJsonObject &object)
|
|||||||
// Get title & groups array
|
// Get title & groups array
|
||||||
const auto groups = object.value(QStringLiteral("groups")).toArray();
|
const auto groups = object.value(QStringLiteral("groups")).toArray();
|
||||||
const auto actions = object.value(QStringLiteral("actions")).toArray();
|
const auto actions = object.value(QStringLiteral("actions")).toArray();
|
||||||
const auto title
|
const auto title = SAFE_READ(object, "title", "").toString().simplified();
|
||||||
= object.value(QStringLiteral("title")).toString().simplified();
|
|
||||||
|
|
||||||
// We need to have a project title and at least one group
|
// We need to have a project title and at least one group
|
||||||
if (!title.isEmpty() && !groups.isEmpty())
|
if (!title.isEmpty() && !groups.isEmpty())
|
||||||
{
|
{
|
||||||
// Update title
|
// Update title
|
||||||
m_title = title;
|
m_title = title;
|
||||||
m_frameEnd = object.value(QStringLiteral("frameEnd")).toString();
|
m_frameEnd = SAFE_READ(object, "frameEnd", "").toString();
|
||||||
m_frameStart = object.value(QStringLiteral("frameStart")).toString();
|
m_frameStart = SAFE_READ(object, "frameStart", "").toString();
|
||||||
|
|
||||||
// Generate groups & datasets from data frame
|
// Generate groups & datasets from data frame
|
||||||
for (auto i = 0; i < groups.count(); ++i)
|
for (auto i = 0; i < groups.count(); ++i)
|
||||||
|
@ -23,6 +23,30 @@
|
|||||||
#include <QJsonArray>
|
#include <QJsonArray>
|
||||||
#include "JSON/Group.h"
|
#include "JSON/Group.h"
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Reads a value from a QJsonObject based on a key, returning a default
|
||||||
|
* value if the key does not exist.
|
||||||
|
*
|
||||||
|
* This function checks if the given key exists in the provided QJsonObject.
|
||||||
|
* If the key is found, it returns the associated value. Otherwise, it returns
|
||||||
|
* the specified default value.
|
||||||
|
*
|
||||||
|
* @param object The QJsonObject to read the data from.
|
||||||
|
* @param key The key to look for in the QJsonObject.
|
||||||
|
* @param defaultValue The value to return if the key is not found in the
|
||||||
|
* QJsonObject.
|
||||||
|
* @return The value associated with the key, or the defaultValue if the key is
|
||||||
|
* not present.
|
||||||
|
*/
|
||||||
|
static QVariant SAFE_READ(const QJsonObject &object, const QString &key,
|
||||||
|
const QVariant &defaultValue)
|
||||||
|
{
|
||||||
|
if (object.contains(key))
|
||||||
|
return object.value(key);
|
||||||
|
|
||||||
|
return defaultValue;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief Constructor function
|
* @brief Constructor function
|
||||||
*/
|
*/
|
||||||
@ -78,8 +102,8 @@ bool JSON::Group::read(const QJsonObject &object)
|
|||||||
{
|
{
|
||||||
// clang-format off
|
// clang-format off
|
||||||
const auto array = object.value(QStringLiteral("datasets")).toArray();
|
const auto array = object.value(QStringLiteral("datasets")).toArray();
|
||||||
const auto title = object.value(QStringLiteral("title")).toString().simplified();
|
const auto title = SAFE_READ(object, "title", "").toString().simplified();
|
||||||
const auto widget = object.value(QStringLiteral("widget")).toString().simplified();
|
const auto widget = SAFE_READ(object, "widget", "").toString().simplified();
|
||||||
// clang-format on
|
// clang-format on
|
||||||
|
|
||||||
if (!title.isEmpty() && !array.isEmpty())
|
if (!title.isEmpty() && !array.isEmpty())
|
||||||
|
@ -86,6 +86,7 @@ typedef enum
|
|||||||
kDatasetView_Alarm, /**< Represents the dataset alarm value item. */
|
kDatasetView_Alarm, /**< Represents the dataset alarm value item. */
|
||||||
kDatasetView_FFT_Samples, /**< Represents the FFT window size item. */
|
kDatasetView_FFT_Samples, /**< Represents the FFT window size item. */
|
||||||
kDatasetView_FFT_SamplingRate, /**< Represents the FFT sampling rate item. */
|
kDatasetView_FFT_SamplingRate, /**< Represents the FFT sampling rate item. */
|
||||||
|
kDatasetView_xAxis /**< Represents the plot X axis item. */
|
||||||
} DatasetItem;
|
} DatasetItem;
|
||||||
// clang-format on
|
// clang-format on
|
||||||
|
|
||||||
@ -347,6 +348,39 @@ QString JSON::ProjectModel::selectedIcon() const
|
|||||||
return data.toString();
|
return data.toString();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Retrieves a list of available X-axis data sources.
|
||||||
|
*
|
||||||
|
* This function returns a list of X-axis data source names. It includes a
|
||||||
|
* default entry ("Samples") and all registered datasets from the project model.
|
||||||
|
* Each dataset is identified by its title and the title of the group it belongs
|
||||||
|
* to.
|
||||||
|
*
|
||||||
|
* @return A `QStringList` containing the names of X-axis data sources.
|
||||||
|
*/
|
||||||
|
QStringList JSON::ProjectModel::xDataSources() const
|
||||||
|
{
|
||||||
|
QStringList list;
|
||||||
|
list.append(tr("Samples"));
|
||||||
|
|
||||||
|
QMap<int, QString> registeredDatasets;
|
||||||
|
for (const auto &group : m_groups)
|
||||||
|
{
|
||||||
|
for (const auto &dataset : group.datasets())
|
||||||
|
{
|
||||||
|
const auto index = dataset.index();
|
||||||
|
if (!registeredDatasets.contains(index))
|
||||||
|
{
|
||||||
|
const auto t = QString("%1 (%2)").arg(dataset.title(), group.title());
|
||||||
|
registeredDatasets.insert(index, t);
|
||||||
|
list.append(t);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return list;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief Retrieves the project title.
|
* @brief Retrieves the project title.
|
||||||
*
|
*
|
||||||
@ -2315,6 +2349,7 @@ void JSON::ProjectModel::buildDatasetModel(const JSON::Dataset &dataset)
|
|||||||
const bool showWidget = currentDatasetIsEditable();
|
const bool showWidget = currentDatasetIsEditable();
|
||||||
const bool showFFTOptions = dataset.fft();
|
const bool showFFTOptions = dataset.fft();
|
||||||
const bool showLedOptions = dataset.led();
|
const bool showLedOptions = dataset.led();
|
||||||
|
const bool showPlotOptions = dataset.graph();
|
||||||
const bool showMinMax = dataset.graph() || dataset.widget() == "gauge"
|
const bool showMinMax = dataset.graph() || dataset.widget() == "gauge"
|
||||||
|| dataset.widget() == "bar"
|
|| dataset.widget() == "bar"
|
||||||
|| m_selectedGroup.widget() == "multiplot";
|
|| m_selectedGroup.widget() == "multiplot";
|
||||||
@ -2388,6 +2423,90 @@ void JSON::ProjectModel::buildDatasetModel(const JSON::Dataset &dataset)
|
|||||||
m_datasetModel->appendRow(widget);
|
m_datasetModel->appendRow(widget);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Get appropiate plotting mode index for current dataset
|
||||||
|
int plotIndex = 0;
|
||||||
|
bool found = false;
|
||||||
|
const auto currentPair = qMakePair(dataset.graph(), dataset.log());
|
||||||
|
for (auto it = m_plotOptions.begin(); it != m_plotOptions.end();
|
||||||
|
++it, ++plotIndex)
|
||||||
|
{
|
||||||
|
if (it.key() == currentPair)
|
||||||
|
{
|
||||||
|
found = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// If not found, reset the index to 0
|
||||||
|
if (!found)
|
||||||
|
plotIndex = 0;
|
||||||
|
|
||||||
|
// Add plotting mode
|
||||||
|
auto plot = new QStandardItem();
|
||||||
|
plot->setEditable(true);
|
||||||
|
plot->setData(ComboBox, WidgetType);
|
||||||
|
plot->setData(m_plotOptions.values(), ComboBoxData);
|
||||||
|
plot->setData(plotIndex, EditableValue);
|
||||||
|
plot->setData(tr("Oscilloscope Plot"), ParameterName);
|
||||||
|
plot->setData(kDatasetView_Plot, ParameterType);
|
||||||
|
plot->setData(tr("Plot data in real-time"), ParameterDescription);
|
||||||
|
m_datasetModel->appendRow(plot);
|
||||||
|
|
||||||
|
// Add FFT checkbox
|
||||||
|
auto fft = new QStandardItem();
|
||||||
|
fft->setEditable(true);
|
||||||
|
fft->setData(CheckBox, WidgetType);
|
||||||
|
fft->setData(dataset.fft(), EditableValue);
|
||||||
|
fft->setData(tr("FFT Plot"), ParameterName);
|
||||||
|
fft->setData(kDatasetView_FFT, ParameterType);
|
||||||
|
fft->setData(0, PlaceholderValue);
|
||||||
|
fft->setData(tr("Plot frequency-domain data"), ParameterDescription);
|
||||||
|
m_datasetModel->appendRow(fft);
|
||||||
|
|
||||||
|
// Add LED panel checkbox
|
||||||
|
auto led = new QStandardItem();
|
||||||
|
led->setEditable(true);
|
||||||
|
led->setData(CheckBox, WidgetType);
|
||||||
|
led->setData(dataset.led(), EditableValue);
|
||||||
|
led->setData(tr("Show in LED Panel"), ParameterName);
|
||||||
|
led->setData(kDatasetView_LED, ParameterType);
|
||||||
|
led->setData(0, PlaceholderValue);
|
||||||
|
led->setData(tr("Quick status monitoring"), ParameterDescription);
|
||||||
|
m_datasetModel->appendRow(led);
|
||||||
|
|
||||||
|
// Add X-axis selector
|
||||||
|
if (showPlotOptions)
|
||||||
|
{
|
||||||
|
// Ensure X-axis ID is reset to "Samples" when an invalid index is set
|
||||||
|
int xAxisIdx = 0;
|
||||||
|
for (const auto &group : m_groups)
|
||||||
|
{
|
||||||
|
for (const auto &dataset : group.datasets())
|
||||||
|
{
|
||||||
|
const auto index = dataset.index();
|
||||||
|
if (index == m_selectedDataset.xAxisId())
|
||||||
|
{
|
||||||
|
xAxisIdx = index;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (xAxisIdx != 0)
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Construct item
|
||||||
|
auto xAxis = new QStandardItem();
|
||||||
|
xAxis->setEditable(true);
|
||||||
|
xAxis->setData(ComboBox, WidgetType);
|
||||||
|
xAxis->setData(xAxisIdx, EditableValue);
|
||||||
|
xAxis->setData(xDataSources(), ComboBoxData);
|
||||||
|
xAxis->setData(kDatasetView_xAxis, ParameterType);
|
||||||
|
xAxis->setData(tr("X-Axis Source"), ParameterName);
|
||||||
|
xAxis->setData(tr("Data series for the X-Axis"), ParameterDescription);
|
||||||
|
m_datasetModel->appendRow(xAxis);
|
||||||
|
}
|
||||||
|
|
||||||
// Add minimum/maximum values
|
// Add minimum/maximum values
|
||||||
if (showMinMax)
|
if (showMinMax)
|
||||||
{
|
{
|
||||||
@ -2431,46 +2550,6 @@ void JSON::ProjectModel::buildDatasetModel(const JSON::Dataset &dataset)
|
|||||||
m_datasetModel->appendRow(alarm);
|
m_datasetModel->appendRow(alarm);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Get appropiate plotting mode index for current dataset
|
|
||||||
int plotIndex = 0;
|
|
||||||
bool found = false;
|
|
||||||
const auto currentPair = qMakePair(dataset.graph(), dataset.log());
|
|
||||||
for (auto it = m_plotOptions.begin(); it != m_plotOptions.end();
|
|
||||||
++it, ++plotIndex)
|
|
||||||
{
|
|
||||||
if (it.key() == currentPair)
|
|
||||||
{
|
|
||||||
found = true;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// If not found, reset the index to 0
|
|
||||||
if (!found)
|
|
||||||
plotIndex = 0;
|
|
||||||
|
|
||||||
// Add plotting mode
|
|
||||||
auto plot = new QStandardItem();
|
|
||||||
plot->setEditable(true);
|
|
||||||
plot->setData(ComboBox, WidgetType);
|
|
||||||
plot->setData(m_plotOptions.values(), ComboBoxData);
|
|
||||||
plot->setData(plotIndex, EditableValue);
|
|
||||||
plot->setData(tr("Oscilloscope Plot"), ParameterName);
|
|
||||||
plot->setData(kDatasetView_Plot, ParameterType);
|
|
||||||
plot->setData(tr("Plot data in real-time"), ParameterDescription);
|
|
||||||
m_datasetModel->appendRow(plot);
|
|
||||||
|
|
||||||
// Add FFT checkbox
|
|
||||||
auto fft = new QStandardItem();
|
|
||||||
fft->setEditable(true);
|
|
||||||
fft->setData(CheckBox, WidgetType);
|
|
||||||
fft->setData(dataset.fft(), EditableValue);
|
|
||||||
fft->setData(tr("FFT Plot"), ParameterName);
|
|
||||||
fft->setData(kDatasetView_FFT, ParameterType);
|
|
||||||
fft->setData(0, PlaceholderValue);
|
|
||||||
fft->setData(tr("Plot frequency-domain data"), ParameterDescription);
|
|
||||||
m_datasetModel->appendRow(fft);
|
|
||||||
|
|
||||||
// FFT-specific options
|
// FFT-specific options
|
||||||
if (showFFTOptions)
|
if (showFFTOptions)
|
||||||
{
|
{
|
||||||
@ -2504,17 +2583,6 @@ void JSON::ProjectModel::buildDatasetModel(const JSON::Dataset &dataset)
|
|||||||
m_datasetModel->appendRow(fftSamplingRate);
|
m_datasetModel->appendRow(fftSamplingRate);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Add LED panel checkbox
|
|
||||||
auto led = new QStandardItem();
|
|
||||||
led->setEditable(true);
|
|
||||||
led->setData(CheckBox, WidgetType);
|
|
||||||
led->setData(dataset.led(), EditableValue);
|
|
||||||
led->setData(tr("Show in LED Panel"), ParameterName);
|
|
||||||
led->setData(kDatasetView_LED, ParameterType);
|
|
||||||
led->setData(0, PlaceholderValue);
|
|
||||||
led->setData(tr("Quick status monitoring"), ParameterDescription);
|
|
||||||
m_datasetModel->appendRow(led);
|
|
||||||
|
|
||||||
// Add LED High value
|
// Add LED High value
|
||||||
if (showLedOptions)
|
if (showLedOptions)
|
||||||
{
|
{
|
||||||
@ -2982,6 +3050,9 @@ void JSON::ProjectModel::onDatasetItemChanged(QStandardItem *item)
|
|||||||
m_selectedDataset.m_log = plotOptions.at(value.toInt()).second;
|
m_selectedDataset.m_log = plotOptions.at(value.toInt()).second;
|
||||||
buildDatasetModel(m_selectedDataset);
|
buildDatasetModel(m_selectedDataset);
|
||||||
break;
|
break;
|
||||||
|
case kDatasetView_xAxis:
|
||||||
|
m_selectedDataset.m_xAxisId = value.toInt();
|
||||||
|
break;
|
||||||
case kDatasetView_Min:
|
case kDatasetView_Min:
|
||||||
m_selectedDataset.m_min = value.toFloat();
|
m_selectedDataset.m_min = value.toFloat();
|
||||||
break;
|
break;
|
||||||
|
@ -224,6 +224,8 @@ public:
|
|||||||
[[nodiscard]] QString selectedText() const;
|
[[nodiscard]] QString selectedText() const;
|
||||||
[[nodiscard]] QString selectedIcon() const;
|
[[nodiscard]] QString selectedIcon() const;
|
||||||
|
|
||||||
|
[[nodiscard]] QStringList xDataSources() const;
|
||||||
|
|
||||||
[[nodiscard]] const QString actionIcon() const;
|
[[nodiscard]] const QString actionIcon() const;
|
||||||
[[nodiscard]] const QStringList &availableActionIcons() const;
|
[[nodiscard]] const QStringList &availableActionIcons() const;
|
||||||
|
|
||||||
|
@ -29,16 +29,60 @@
|
|||||||
#include "JSON/Dataset.h"
|
#include "JSON/Dataset.h"
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @typedef Curve
|
* @typedef PlotDataX
|
||||||
* @brief Defines a plot series or curve as a vector of real (qreal) values.
|
* @brief Represents the unique X-axis data points for a plot.
|
||||||
|
*
|
||||||
|
* The X-axis data points are stored as a set of unique `qreal` values.
|
||||||
|
* This ensures that each X value is distinct, which is essential for correct
|
||||||
|
* rendering of the plot. The set is inherently ordered in ascending order.
|
||||||
*/
|
*/
|
||||||
typedef QVector<qreal> Curve;
|
typedef QVector<qreal> PlotDataX;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @typedef MultipleCurves
|
* @typedef PlotDataY
|
||||||
* @brief Defines a collection of curves, used for representing multiple plots.
|
* @brief Represents the Y-axis data points for a single curve.
|
||||||
|
*
|
||||||
|
* The Y-axis data points are stored as a vector of `qreal` values.
|
||||||
|
* Unlike X-axis data, Y values can have duplicates and are directly
|
||||||
|
* mapped to the corresponding X values during plotting.
|
||||||
*/
|
*/
|
||||||
typedef QVector<Curve> MultipleCurves;
|
typedef QVector<qreal> PlotDataY;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @typedef MultiPlotDataY
|
||||||
|
* @brief Represents Y-axis data for multiple curves in a multiplot.
|
||||||
|
*
|
||||||
|
* A vector of `PlotDataY` structures, where each element represents
|
||||||
|
* the Y-axis data for one curve in a multiplot widget. This allows
|
||||||
|
* managing multiple curves independently.
|
||||||
|
*/
|
||||||
|
typedef QVector<PlotDataY> MultiPlotDataY;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @typedef LineSeries
|
||||||
|
* @brief Represents a paired series of X-axis and Y-axis data for a plot.
|
||||||
|
*
|
||||||
|
* The `LineSeries` type is defined as a `QPair` containing:
|
||||||
|
* - A pointer to `PlotDataX`, which holds the unique X-axis values.
|
||||||
|
* - A pointer to `PlotDataY`, which holds the Y-axis values.
|
||||||
|
*
|
||||||
|
* This type simplifies data processing by tightly coupling the related X and Y
|
||||||
|
* data for a plot, ensuring that they are always accessed and managed together.
|
||||||
|
*/
|
||||||
|
typedef struct
|
||||||
|
{
|
||||||
|
PlotDataX *x;
|
||||||
|
PlotDataY *y;
|
||||||
|
} LineSeries;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @typedef MultiLineSeries
|
||||||
|
*/
|
||||||
|
typedef struct
|
||||||
|
{
|
||||||
|
PlotDataX *x;
|
||||||
|
QList<PlotDataY> y;
|
||||||
|
} MultiLineSeries;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @class SerialStudio
|
* @class SerialStudio
|
||||||
|
@ -448,6 +448,22 @@ QStringList UI::Dashboard::actionTitles() const
|
|||||||
return titles;
|
return titles;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Provides access to the map of dataset objects.
|
||||||
|
*
|
||||||
|
* This function returns a constant reference to the map that associates dataset
|
||||||
|
* indexes with their corresponding `JSON::Dataset` objects.
|
||||||
|
*
|
||||||
|
* @return A constant reference to the `QMap` mapping dataset indexes (`int`)
|
||||||
|
* to their respective `JSON::Dataset` objects.
|
||||||
|
*
|
||||||
|
* @note The map can be used to retrieve datasets by their index.
|
||||||
|
*/
|
||||||
|
const QMap<int, JSON::Dataset> &UI::Dashboard::datasets() const
|
||||||
|
{
|
||||||
|
return m_datasets;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief Provides access to a specific group widget based on widget type and
|
* @brief Provides access to a specific group widget based on widget type and
|
||||||
* relative index.
|
* relative index.
|
||||||
@ -493,48 +509,52 @@ const JSON::Frame &UI::Dashboard::currentFrame()
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief Provides the FFT plot values currently displayed on the dashboard.
|
* @brief Provides the FFT plot values currently displayed on the dashboard.
|
||||||
* @return A reference to a QVector containing the FFT Curve data.
|
* @return A reference to a QVector containing the FFT PlotDataY data.
|
||||||
*/
|
*/
|
||||||
const QVector<Curve> &UI::Dashboard::fftPlotValues()
|
const PlotDataY &UI::Dashboard::fftData(const int index) const
|
||||||
{
|
{
|
||||||
return m_fftPlotValues;
|
return m_fftValues[index];
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief Provides the linear plot values currently displayed on the dashboard.
|
* @brief Provides the linear plot values currently displayed on the dashboard.
|
||||||
* @return A reference to a QVector containing the linear Curve data.
|
* @return A reference to a QVector containing the linear PlotDataY data.
|
||||||
*/
|
*/
|
||||||
const QVector<Curve> &UI::Dashboard::linearPlotValues()
|
const LineSeries &UI::Dashboard::plotData(const int index) const
|
||||||
{
|
{
|
||||||
return m_linearPlotValues;
|
return m_pltValues[index];
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief Provides the values for multiplot visuals on the dashboard.
|
* @brief Provides the values for multiplot visuals on the dashboard.
|
||||||
* @return A reference to a QVector containing MultipleCurves data.
|
* @return A reference to a QVector containing MultiPlotDataY data.
|
||||||
*/
|
*/
|
||||||
const QVector<MultipleCurves> &UI::Dashboard::multiplotValues()
|
const MultiLineSeries &UI::Dashboard::multiplotData(const int index) const
|
||||||
{
|
{
|
||||||
return m_multiplotValues;
|
return m_multipltValues[index];
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief Sets the number of data points displayed in the dashboard plots.
|
* @brief Sets the number of data points for the dashboard plots.
|
||||||
* Clears existing multiplot and linear plot values and emits the
|
|
||||||
* @c pointsChanged signal.
|
|
||||||
*
|
*
|
||||||
* @param points The new point/sample count.
|
* This function updates the total number of points (samples) used in the plots
|
||||||
|
* and reconfigures the data structures for linear and multi-line series to
|
||||||
|
* reflect the new point count.
|
||||||
|
*
|
||||||
|
* @param points The new number of data points (samples).
|
||||||
*/
|
*/
|
||||||
void UI::Dashboard::setPoints(const int points)
|
void UI::Dashboard::setPoints(const int points)
|
||||||
{
|
{
|
||||||
if (m_points != points)
|
if (m_points != points)
|
||||||
{
|
{
|
||||||
|
// Update number of points
|
||||||
m_points = points;
|
m_points = points;
|
||||||
m_multiplotValues.clear();
|
|
||||||
m_linearPlotValues.clear();
|
|
||||||
m_multiplotValues.squeeze();
|
|
||||||
m_linearPlotValues.squeeze();
|
|
||||||
|
|
||||||
|
// Update plot data structures
|
||||||
|
configureLineSeries();
|
||||||
|
configureMultiLineSeries();
|
||||||
|
|
||||||
|
// Update the UI
|
||||||
Q_EMIT pointsChanged();
|
Q_EMIT pointsChanged();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -592,12 +612,18 @@ void UI::Dashboard::setShowLegends(const bool enabled)
|
|||||||
void UI::Dashboard::resetData(const bool notify)
|
void UI::Dashboard::resetData(const bool notify)
|
||||||
{
|
{
|
||||||
// Clear plotting data
|
// Clear plotting data
|
||||||
m_fftPlotValues.clear();
|
m_fftValues.clear();
|
||||||
m_multiplotValues.clear();
|
m_pltValues.clear();
|
||||||
m_linearPlotValues.clear();
|
m_multipltValues.clear();
|
||||||
m_fftPlotValues.squeeze();
|
|
||||||
m_multiplotValues.squeeze();
|
// Free memory associated with the containers of the plotting data
|
||||||
m_linearPlotValues.squeeze();
|
m_fftValues.squeeze();
|
||||||
|
m_pltValues.squeeze();
|
||||||
|
m_multipltValues.squeeze();
|
||||||
|
|
||||||
|
// Clear X/Y axis arrays
|
||||||
|
m_xAxisData.clear();
|
||||||
|
m_yAxisData.clear();
|
||||||
|
|
||||||
// Clear widget & action structures
|
// Clear widget & action structures
|
||||||
m_widgetCount = 0;
|
m_widgetCount = 0;
|
||||||
@ -662,81 +688,62 @@ void UI::Dashboard::setWidgetVisible(const SerialStudio::DashboardWidget widget,
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief Updates plot data for linear, FFT, and multiplot widgets on the
|
* @brief Updates the plot data for all dashboard widgets.
|
||||||
* dashboard.
|
|
||||||
*
|
*
|
||||||
* This function checks and initializes the data structures for each plot type
|
* This function ensures that the data structures for FFT plots, linear plots,
|
||||||
* (linear plots, FFT plots, and multiplots) if needed.
|
* and multiplots are correctly initialized and updated with the latest values
|
||||||
|
* from the datasets. It handles reinitialization if the widget count changes
|
||||||
|
* and shifts data to accommodate new samples.
|
||||||
*
|
*
|
||||||
* It then appends the latest values from the data sources to these plots by
|
* @note This function is typically called in real-time to keep plots
|
||||||
* shifting older data back and adding new data to the end.
|
* synchronized with incoming data.
|
||||||
*/
|
*/
|
||||||
void UI::Dashboard::updatePlots()
|
void UI::Dashboard::updatePlots()
|
||||||
{
|
{
|
||||||
// Check if we need to re-initialize linear plots data
|
|
||||||
if (m_linearPlotValues.count() != widgetCount(SerialStudio::DashboardPlot))
|
|
||||||
{
|
|
||||||
m_linearPlotValues.clear();
|
|
||||||
m_linearPlotValues.squeeze();
|
|
||||||
for (int i = 0; i < widgetCount(SerialStudio::DashboardPlot); ++i)
|
|
||||||
{
|
|
||||||
m_linearPlotValues.append(Curve());
|
|
||||||
m_linearPlotValues.last().resize(points() + 1);
|
|
||||||
SIMD::fill<qreal>(m_linearPlotValues.last().data(), points() + 1, 0);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Check if we need to re-initialize FFT plots data
|
// Check if we need to re-initialize FFT plots data
|
||||||
if (m_fftPlotValues.count() != widgetCount(SerialStudio::DashboardFFT))
|
if (m_fftValues.count() != widgetCount(SerialStudio::DashboardFFT))
|
||||||
{
|
configureFftSeries();
|
||||||
m_fftPlotValues.clear();
|
|
||||||
m_fftPlotValues.squeeze();
|
// Check if we need to re-initialize linear plots data
|
||||||
for (int i = 0; i < widgetCount(SerialStudio::DashboardFFT); ++i)
|
if (m_pltValues.count() != widgetCount(SerialStudio::DashboardPlot))
|
||||||
{
|
configureLineSeries();
|
||||||
const auto &dataset = getDatasetWidget(SerialStudio::DashboardFFT, i);
|
|
||||||
m_fftPlotValues.append(Curve());
|
|
||||||
m_fftPlotValues.last().resize(dataset.fftSamples());
|
|
||||||
SIMD::fill<qreal>(m_fftPlotValues.last().data(), dataset.fftSamples(), 0);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Check if we need to re-initialize multiplot data
|
// Check if we need to re-initialize multiplot data
|
||||||
if (m_multiplotValues.count()
|
if (m_multipltValues.count() != widgetCount(SerialStudio::DashboardMultiPlot))
|
||||||
!= widgetCount(SerialStudio::DashboardMultiPlot))
|
configureMultiLineSeries();
|
||||||
{
|
|
||||||
m_multiplotValues.clear();
|
|
||||||
m_multiplotValues.squeeze();
|
|
||||||
for (int i = 0; i < widgetCount(SerialStudio::DashboardMultiPlot); ++i)
|
|
||||||
{
|
|
||||||
const auto &group = getGroupWidget(SerialStudio::DashboardMultiPlot, i);
|
|
||||||
m_multiplotValues.append(MultipleCurves());
|
|
||||||
m_multiplotValues.last().resize(group.datasetCount());
|
|
||||||
for (int j = 0; j < group.datasetCount(); ++j)
|
|
||||||
{
|
|
||||||
m_multiplotValues[i][j].resize(points() + 1);
|
|
||||||
SIMD::fill<qreal>(m_multiplotValues[i][j].data(), points() + 1, 0);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Append latest values to linear plots data
|
|
||||||
for (int i = 0; i < widgetCount(SerialStudio::DashboardPlot); ++i)
|
|
||||||
{
|
|
||||||
const auto &dataset = getDatasetWidget(SerialStudio::DashboardPlot, i);
|
|
||||||
auto *data = m_linearPlotValues[i].data();
|
|
||||||
auto count = m_linearPlotValues[i].count();
|
|
||||||
SIMD::shift<qreal>(data, count, dataset.value().toFloat());
|
|
||||||
}
|
|
||||||
|
|
||||||
// Append latest values to FFT plots data
|
// Append latest values to FFT plots data
|
||||||
for (int i = 0; i < widgetCount(SerialStudio::DashboardFFT); ++i)
|
for (int i = 0; i < widgetCount(SerialStudio::DashboardFFT); ++i)
|
||||||
{
|
{
|
||||||
const auto &dataset = getDatasetWidget(SerialStudio::DashboardFFT, i);
|
const auto &dataset = getDatasetWidget(SerialStudio::DashboardFFT, i);
|
||||||
auto *data = m_fftPlotValues[i].data();
|
auto *data = m_fftValues[i].data();
|
||||||
auto count = m_fftPlotValues[i].count();
|
auto count = m_fftValues[i].count();
|
||||||
SIMD::shift<qreal>(data, count, dataset.value().toFloat());
|
SIMD::shift<qreal>(data, count, dataset.value().toFloat());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Append latest values to linear plots data
|
||||||
|
for (int i = 0; i < widgetCount(SerialStudio::DashboardPlot); ++i)
|
||||||
|
{
|
||||||
|
const auto &yDataset = getDatasetWidget(SerialStudio::DashboardPlot, i);
|
||||||
|
if (m_datasets.contains(yDataset.xAxisId()))
|
||||||
|
{
|
||||||
|
const auto &xDataset = m_datasets[yDataset.xAxisId()];
|
||||||
|
auto *xData = m_xAxisData[xDataset.index()].data();
|
||||||
|
auto *yData = m_yAxisData[yDataset.index()].data();
|
||||||
|
auto xCount = m_xAxisData[xDataset.index()].count();
|
||||||
|
auto yCount = m_yAxisData[yDataset.index()].count();
|
||||||
|
SIMD::shift<qreal>(xData, xCount, xDataset.value().toFloat());
|
||||||
|
SIMD::shift<qreal>(yData, yCount, yDataset.value().toFloat());
|
||||||
|
}
|
||||||
|
|
||||||
|
else
|
||||||
|
{
|
||||||
|
auto *data = m_yAxisData[yDataset.index()].data();
|
||||||
|
auto count = m_yAxisData[yDataset.index()].count();
|
||||||
|
SIMD::shift<qreal>(data, count, yDataset.value().toFloat());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Append latest values to multiplots data
|
// Append latest values to multiplots data
|
||||||
for (int i = 0; i < widgetCount(SerialStudio::DashboardMultiPlot); ++i)
|
for (int i = 0; i < widgetCount(SerialStudio::DashboardMultiPlot); ++i)
|
||||||
{
|
{
|
||||||
@ -744,13 +751,164 @@ void UI::Dashboard::updatePlots()
|
|||||||
for (int j = 0; j < group.datasetCount(); ++j)
|
for (int j = 0; j < group.datasetCount(); ++j)
|
||||||
{
|
{
|
||||||
const auto &dataset = group.datasets()[j];
|
const auto &dataset = group.datasets()[j];
|
||||||
auto *data = m_multiplotValues[i][j].data();
|
auto *data = m_multipltValues[i].y[j].data();
|
||||||
auto count = m_multiplotValues[i][j].count();
|
auto count = m_multipltValues[i].y[j].count();
|
||||||
SIMD::shift<qreal>(data, count, dataset.value().toFloat());
|
SIMD::shift<qreal>(data, count, dataset.value().toFloat());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Configures the FFT series data structure for the dashboard.
|
||||||
|
*
|
||||||
|
* This function clears existing FFT values and initializes the data structure
|
||||||
|
* for each FFT plot widget with a predefined number of samples, filling it with
|
||||||
|
* zeros.
|
||||||
|
*
|
||||||
|
* @note Typically called during dashboard setup or reset to prepare FFT plot
|
||||||
|
* widgets for rendering.
|
||||||
|
*/
|
||||||
|
void UI::Dashboard::configureFftSeries()
|
||||||
|
{
|
||||||
|
// Clear memory
|
||||||
|
m_fftValues.clear();
|
||||||
|
m_fftValues.squeeze();
|
||||||
|
|
||||||
|
// Construct FFT plot data structure
|
||||||
|
for (int i = 0; i < widgetCount(SerialStudio::DashboardFFT); ++i)
|
||||||
|
{
|
||||||
|
const auto &dataset = getDatasetWidget(SerialStudio::DashboardFFT, i);
|
||||||
|
m_fftValues.append(PlotDataY());
|
||||||
|
m_fftValues.last().resize(dataset.fftSamples());
|
||||||
|
SIMD::fill<qreal>(m_fftValues.last().data(), dataset.fftSamples(), 0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Configures the line series data structure for the dashboard.
|
||||||
|
*
|
||||||
|
* This function clears and reinitializes the X-axis and Y-axis data arrays,
|
||||||
|
* as well as the plot values structure (`m_pltValues`). It associates each
|
||||||
|
* dataset with its respective X and Y data, creating `LineSeries` objects
|
||||||
|
* for plotting.
|
||||||
|
*
|
||||||
|
* - If a dataset specifies an X-axis source, the corresponding data is used.
|
||||||
|
* - Otherwise, the default X-axis (based on sample points) is used.
|
||||||
|
*
|
||||||
|
* @note Typically called during dashboard setup or reset to prepare plot
|
||||||
|
* widgets for rendering.
|
||||||
|
*/
|
||||||
|
void UI::Dashboard::configureLineSeries()
|
||||||
|
{
|
||||||
|
// Clear memory
|
||||||
|
m_xAxisData.clear();
|
||||||
|
m_yAxisData.clear();
|
||||||
|
m_pltValues.clear();
|
||||||
|
m_pltValues.squeeze();
|
||||||
|
|
||||||
|
// Reset default X-axis data
|
||||||
|
m_defaultXAxis.clear();
|
||||||
|
m_defaultXAxis.squeeze();
|
||||||
|
m_defaultXAxis.reserve(points() + 1);
|
||||||
|
for (int i = 0; i < points() + 1; ++i)
|
||||||
|
m_defaultXAxis.append(i);
|
||||||
|
|
||||||
|
// Construct X/Y axis data arrays
|
||||||
|
for (auto i = m_widgetDatasets.begin(); i != m_widgetDatasets.end(); ++i)
|
||||||
|
{
|
||||||
|
// Obtain list of datasets for a widget type
|
||||||
|
const auto &datasets = i.value();
|
||||||
|
|
||||||
|
// Iterate over all the datasets
|
||||||
|
for (auto d = datasets.begin(); d != datasets.end(); ++d)
|
||||||
|
{
|
||||||
|
if (d->graph())
|
||||||
|
{
|
||||||
|
// Register X-axis
|
||||||
|
PlotDataY yAxis;
|
||||||
|
m_yAxisData.insert(d->index(), yAxis);
|
||||||
|
|
||||||
|
// Register X-axis
|
||||||
|
int xSource = d->xAxisId();
|
||||||
|
if (!m_xAxisData.contains(xSource))
|
||||||
|
{
|
||||||
|
PlotDataX xAxis;
|
||||||
|
if (m_datasets.contains(xSource))
|
||||||
|
m_xAxisData.insert(xSource, xAxis);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Construct plot values structure
|
||||||
|
for (int i = 0; i < widgetCount(SerialStudio::DashboardPlot); ++i)
|
||||||
|
{
|
||||||
|
// Obtain Y-axis data
|
||||||
|
const auto &yDataset = getDatasetWidget(SerialStudio::DashboardPlot, i);
|
||||||
|
|
||||||
|
// Add X-axis data & generate a line series with X/Y data
|
||||||
|
if (m_datasets.contains(yDataset.xAxisId()))
|
||||||
|
{
|
||||||
|
const auto &xDataset = m_datasets[yDataset.xAxisId()];
|
||||||
|
m_xAxisData[xDataset.index()].resize(points() + 1);
|
||||||
|
m_yAxisData[yDataset.index()].resize(points() + 1);
|
||||||
|
SIMD::fill<qreal>(m_xAxisData[xDataset.index()].data(), points() + 1, 0);
|
||||||
|
SIMD::fill<qreal>(m_yAxisData[yDataset.index()].data(), points() + 1, 0);
|
||||||
|
|
||||||
|
LineSeries series;
|
||||||
|
series.x = &m_xAxisData[xDataset.index()];
|
||||||
|
series.y = &m_yAxisData[yDataset.index()];
|
||||||
|
m_pltValues.append(series);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Only use Y-axis data, use samples/points as X-axis
|
||||||
|
else
|
||||||
|
{
|
||||||
|
m_yAxisData[yDataset.index()].resize(points() + 1);
|
||||||
|
SIMD::fill<qreal>(m_yAxisData[yDataset.index()].data(), points() + 1, 0);
|
||||||
|
|
||||||
|
LineSeries series;
|
||||||
|
series.x = &m_defaultXAxis;
|
||||||
|
series.y = &m_yAxisData[yDataset.index()];
|
||||||
|
m_pltValues.append(series);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Configures the multi-line series data structure for the dashboard.
|
||||||
|
*
|
||||||
|
* This function initializes the data structure used for multi-plot widgets.
|
||||||
|
* It assigns the default X-axis to all multi-line series and creates a
|
||||||
|
* `PlotDataY` vector for each dataset in the group, initializing it with zeros.
|
||||||
|
*
|
||||||
|
* @note Typically called during dashboard setup or reset to prepare multi-plot
|
||||||
|
* widgets for rendering.
|
||||||
|
*/
|
||||||
|
void UI::Dashboard::configureMultiLineSeries()
|
||||||
|
{
|
||||||
|
// Clear data
|
||||||
|
m_multipltValues.clear();
|
||||||
|
m_multipltValues.squeeze();
|
||||||
|
|
||||||
|
// Construct multi-plot values structure
|
||||||
|
for (int i = 0; i < widgetCount(SerialStudio::DashboardMultiPlot); ++i)
|
||||||
|
{
|
||||||
|
const auto &group = getGroupWidget(SerialStudio::DashboardMultiPlot, i);
|
||||||
|
|
||||||
|
MultiLineSeries series;
|
||||||
|
series.x = &m_defaultXAxis;
|
||||||
|
for (int j = 0; j < group.datasetCount(); ++j)
|
||||||
|
{
|
||||||
|
series.y.append(PlotDataY());
|
||||||
|
series.y.last().resize(points() + 1);
|
||||||
|
SIMD::fill<qreal>(series.y.last().data(), points() + 1, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
m_multipltValues.append(series);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief Processes and updates the dashboard data based on a new frame.
|
* @brief Processes and updates the dashboard data based on a new frame.
|
||||||
*
|
*
|
||||||
@ -803,6 +961,7 @@ void UI::Dashboard::processFrame(const JSON::Frame &frame)
|
|||||||
Q_EMIT actionCountChanged();
|
Q_EMIT actionCountChanged();
|
||||||
|
|
||||||
// Update widget data structures
|
// Update widget data structures
|
||||||
|
m_datasets.clear();
|
||||||
JSON::Group ledPanel;
|
JSON::Group ledPanel;
|
||||||
for (const auto &group : frame.groups())
|
for (const auto &group : frame.groups())
|
||||||
{
|
{
|
||||||
@ -816,6 +975,7 @@ void UI::Dashboard::processFrame(const JSON::Frame &frame)
|
|||||||
|
|
||||||
for (const auto &dataset : group.datasets())
|
for (const auto &dataset : group.datasets())
|
||||||
{
|
{
|
||||||
|
m_datasets.insert(dataset.index(), dataset);
|
||||||
auto keys = SerialStudio::getDashboardWidgets(dataset);
|
auto keys = SerialStudio::getDashboardWidgets(dataset);
|
||||||
for (const auto &key : keys)
|
for (const auto &key : keys)
|
||||||
{
|
{
|
||||||
@ -904,6 +1064,11 @@ void UI::Dashboard::processFrame(const JSON::Frame &frame)
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Clear plot data setup
|
||||||
|
m_fftValues.clear();
|
||||||
|
m_pltValues.clear();
|
||||||
|
m_multipltValues.clear();
|
||||||
|
|
||||||
// Update user interface
|
// Update user interface
|
||||||
Q_EMIT widgetCountChanged();
|
Q_EMIT widgetCountChanged();
|
||||||
Q_EMIT widgetVisibilityChanged();
|
Q_EMIT widgetVisibilityChanged();
|
||||||
|
@ -134,14 +134,16 @@ public:
|
|||||||
[[nodiscard]] QStringList actionTitles() const;
|
[[nodiscard]] QStringList actionTitles() const;
|
||||||
|
|
||||||
// clang-format off
|
// clang-format off
|
||||||
|
[[nodiscard]] const QMap<int, JSON::Dataset> &datasets() const;
|
||||||
[[nodiscard]] const JSON::Group &getGroupWidget(const SerialStudio::DashboardWidget widget, const int index) const;
|
[[nodiscard]] const JSON::Group &getGroupWidget(const SerialStudio::DashboardWidget widget, const int index) const;
|
||||||
[[nodiscard]] const JSON::Dataset &getDatasetWidget(const SerialStudio::DashboardWidget widget, const int index) const;
|
[[nodiscard]] const JSON::Dataset &getDatasetWidget(const SerialStudio::DashboardWidget widget, const int index) const;
|
||||||
// clang-format on
|
// clang-format on
|
||||||
|
|
||||||
[[nodiscard]] const JSON::Frame ¤tFrame();
|
[[nodiscard]] const JSON::Frame ¤tFrame();
|
||||||
[[nodiscard]] const QVector<Curve> &fftPlotValues();
|
|
||||||
[[nodiscard]] const QVector<Curve> &linearPlotValues();
|
[[nodiscard]] const PlotDataY &fftData(const int index) const;
|
||||||
[[nodiscard]] const QVector<MultipleCurves> &multiplotValues();
|
[[nodiscard]] const LineSeries &plotData(const int index) const;
|
||||||
|
[[nodiscard]] const MultiLineSeries &multiplotData(const int index) const;
|
||||||
|
|
||||||
public slots:
|
public slots:
|
||||||
void setPoints(const int points);
|
void setPoints(const int points);
|
||||||
@ -155,6 +157,9 @@ public slots:
|
|||||||
|
|
||||||
private slots:
|
private slots:
|
||||||
void updatePlots();
|
void updatePlots();
|
||||||
|
void configureFftSeries();
|
||||||
|
void configureLineSeries();
|
||||||
|
void configureMultiLineSeries();
|
||||||
void processFrame(const JSON::Frame &frame);
|
void processFrame(const JSON::Frame &frame);
|
||||||
|
|
||||||
private:
|
private:
|
||||||
@ -165,11 +170,16 @@ private:
|
|||||||
bool m_updateRequired;
|
bool m_updateRequired;
|
||||||
SerialStudio::AxisVisibility m_axisVisibility;
|
SerialStudio::AxisVisibility m_axisVisibility;
|
||||||
|
|
||||||
QVector<Curve> m_fftPlotValues;
|
PlotDataX m_defaultXAxis;
|
||||||
QVector<Curve> m_linearPlotValues;
|
QMap<int, PlotDataX> m_xAxisData;
|
||||||
QVector<MultipleCurves> m_multiplotValues;
|
QMap<int, PlotDataY> m_yAxisData;
|
||||||
|
|
||||||
|
QVector<PlotDataY> m_fftValues;
|
||||||
|
QVector<LineSeries> m_pltValues;
|
||||||
|
QVector<MultiLineSeries> m_multipltValues;
|
||||||
|
|
||||||
QVector<JSON::Action> m_actions;
|
QVector<JSON::Action> m_actions;
|
||||||
|
QMap<int, JSON::Dataset> m_datasets;
|
||||||
QList<SerialStudio::DashboardWidget> m_availableWidgets;
|
QList<SerialStudio::DashboardWidget> m_availableWidgets;
|
||||||
QMap<int, QPair<SerialStudio::DashboardWidget, int>> m_widgetMap;
|
QMap<int, QPair<SerialStudio::DashboardWidget, int>> m_widgetMap;
|
||||||
QMap<SerialStudio::DashboardWidget, QVector<bool>> m_widgetVisibility;
|
QMap<SerialStudio::DashboardWidget, QVector<bool>> m_widgetVisibility;
|
||||||
|
@ -146,15 +146,12 @@ void Widgets::FFTPlot::updateData()
|
|||||||
if (!isEnabled())
|
if (!isEnabled())
|
||||||
return;
|
return;
|
||||||
|
|
||||||
// Get the plot data
|
if (VALIDATE_WIDGET(SerialStudio::DashboardFFT, m_index))
|
||||||
auto dash = &UI::Dashboard::instance();
|
|
||||||
auto plotData = dash->fftPlotValues();
|
|
||||||
|
|
||||||
// If the plot data is valid, update the data
|
|
||||||
if (plotData.count() > m_index)
|
|
||||||
{
|
{
|
||||||
|
// Get the plot data
|
||||||
|
const auto &data = UI::Dashboard::instance().fftData(m_index);
|
||||||
|
|
||||||
// Obtain samples from data
|
// Obtain samples from data
|
||||||
const auto &data = plotData.at(m_index);
|
|
||||||
for (int i = 0; i < m_size; ++i)
|
for (int i = 0; i < m_size; ++i)
|
||||||
m_samples[i] = static_cast<float>(data[i]);
|
m_samples[i] = static_cast<float>(data[i]);
|
||||||
|
|
||||||
|
@ -194,19 +194,15 @@ void Widgets::MultiPlot::updateData()
|
|||||||
|
|
||||||
if (VALIDATE_WIDGET(SerialStudio::DashboardMultiPlot, m_index))
|
if (VALIDATE_WIDGET(SerialStudio::DashboardMultiPlot, m_index))
|
||||||
{
|
{
|
||||||
const auto &plotData = UI::Dashboard::instance().multiplotValues();
|
const auto &data = UI::Dashboard::instance().multiplotData(m_index);
|
||||||
if (m_index >= 0 && plotData.count() > m_index)
|
for (int i = 0; i < data.y.count(); ++i)
|
||||||
{
|
{
|
||||||
const auto &curves = plotData[m_index];
|
const auto &series = data.y[i];
|
||||||
for (int i = 0; i < curves.count(); ++i)
|
if (m_data[i].count() != series.count())
|
||||||
{
|
m_data[i].resize(series.count());
|
||||||
const auto &values = curves[i];
|
|
||||||
if (m_data[i].count() != values.count())
|
|
||||||
m_data[i].resize(values.count());
|
|
||||||
|
|
||||||
for (int j = 0; j < values.count(); ++j)
|
for (int j = 0; j < series.count(); ++j)
|
||||||
m_data[i][j] = QPointF(j, values[j]);
|
m_data[i][j] = QPointF(data.x->at(j), series[j]);
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -39,14 +39,26 @@ Widgets::Plot::Plot(const int index, QQuickItem *parent)
|
|||||||
{
|
{
|
||||||
if (VALIDATE_WIDGET(SerialStudio::DashboardPlot, m_index))
|
if (VALIDATE_WIDGET(SerialStudio::DashboardPlot, m_index))
|
||||||
{
|
{
|
||||||
const auto &dataset = GET_DATASET(SerialStudio::DashboardPlot, m_index);
|
const auto &yDataset = GET_DATASET(SerialStudio::DashboardPlot, m_index);
|
||||||
|
|
||||||
m_yLabel = dataset.title();
|
m_minY = qMin(yDataset.min(), yDataset.max());
|
||||||
m_minY = qMin(dataset.min(), dataset.max());
|
m_maxY = qMax(yDataset.min(), yDataset.max());
|
||||||
m_maxY = qMax(dataset.min(), dataset.max());
|
|
||||||
|
|
||||||
if (!dataset.units().isEmpty())
|
const auto xAxisId = yDataset.xAxisId();
|
||||||
m_yLabel += " (" + dataset.units() + ")";
|
if (UI::Dashboard::instance().datasets().contains(xAxisId))
|
||||||
|
{
|
||||||
|
const auto &xDataset = UI::Dashboard::instance().datasets()[xAxisId];
|
||||||
|
m_xLabel = xDataset.title();
|
||||||
|
if (!xDataset.units().isEmpty())
|
||||||
|
m_xLabel += " (" + xDataset.units() + ")";
|
||||||
|
}
|
||||||
|
|
||||||
|
else
|
||||||
|
m_xLabel = tr("Samples");
|
||||||
|
|
||||||
|
m_yLabel = yDataset.title();
|
||||||
|
if (!yDataset.units().isEmpty())
|
||||||
|
m_yLabel += " (" + yDataset.units() + ")";
|
||||||
|
|
||||||
connect(&UI::Dashboard::instance(), &UI::Dashboard::updated, this,
|
connect(&UI::Dashboard::instance(), &UI::Dashboard::updated, this,
|
||||||
&Plot::updateData);
|
&Plot::updateData);
|
||||||
@ -121,6 +133,15 @@ const QString &Widgets::Plot::yLabel() const
|
|||||||
return m_yLabel;
|
return m_yLabel;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Returns the X-axis label.
|
||||||
|
* @return The X-axis label.
|
||||||
|
*/
|
||||||
|
const QString &Widgets::Plot::xLabel() const
|
||||||
|
{
|
||||||
|
return m_xLabel;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief Draws the data on the given QLineSeries.
|
* @brief Draws the data on the given QLineSeries.
|
||||||
* @param series The QLineSeries to draw the data on.
|
* @param series The QLineSeries to draw the data on.
|
||||||
@ -145,16 +166,27 @@ void Widgets::Plot::updateData()
|
|||||||
|
|
||||||
if (VALIDATE_WIDGET(SerialStudio::DashboardPlot, m_index))
|
if (VALIDATE_WIDGET(SerialStudio::DashboardPlot, m_index))
|
||||||
{
|
{
|
||||||
const auto &plotData = UI::Dashboard::instance().linearPlotValues();
|
// Get plotting data
|
||||||
|
const auto &plotData = UI::Dashboard::instance().plotData(m_index);
|
||||||
|
const auto X = plotData.x;
|
||||||
|
const auto Y = plotData.y;
|
||||||
|
|
||||||
if (m_index >= 0 && plotData.count() > m_index)
|
// Resize series array if required
|
||||||
|
if (m_data.count() != X->count())
|
||||||
|
m_data.resize(X->count());
|
||||||
|
|
||||||
|
// Convert data to a list of points
|
||||||
|
int i = 0;
|
||||||
|
for (auto x = X->begin(); x != X->end(); ++x)
|
||||||
{
|
{
|
||||||
const auto &values = plotData[m_index];
|
if (Y->count() > i)
|
||||||
if (m_data.count() != values.count())
|
{
|
||||||
m_data.resize(UI::Dashboard::instance().points());
|
m_data[i] = QPointF(*x, Y->at(i));
|
||||||
|
++i;
|
||||||
|
}
|
||||||
|
|
||||||
for (int i = 0; i < values.count(); ++i)
|
else
|
||||||
m_data[i] = QPointF(i, values[i]);
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -164,45 +196,137 @@ void Widgets::Plot::updateData()
|
|||||||
*/
|
*/
|
||||||
void Widgets::Plot::updateRange()
|
void Widgets::Plot::updateRange()
|
||||||
{
|
{
|
||||||
// Reserve the number of points in the dashboard
|
// Clear memory
|
||||||
m_data.clear();
|
m_data.clear();
|
||||||
m_data.squeeze();
|
m_data.squeeze();
|
||||||
m_data.resize(UI::Dashboard::instance().points() + 1);
|
m_data.resize(UI::Dashboard::instance().points() + 1);
|
||||||
|
|
||||||
// Update x-axis
|
// Obtain dataset information
|
||||||
|
if (VALIDATE_WIDGET(SerialStudio::DashboardPlot, m_index))
|
||||||
|
{
|
||||||
|
const auto &yD = GET_DATASET(SerialStudio::DashboardPlot, m_index);
|
||||||
|
if (yD.xAxisId() > 0)
|
||||||
|
{
|
||||||
|
const auto &xD = UI::Dashboard::instance().datasets()[yD.xAxisId()];
|
||||||
|
m_minX = xD.min();
|
||||||
|
m_maxX = xD.max();
|
||||||
|
}
|
||||||
|
|
||||||
|
else
|
||||||
|
{
|
||||||
m_minX = 0;
|
m_minX = 0;
|
||||||
m_maxX = UI::Dashboard::instance().points();
|
m_maxX = UI::Dashboard::instance().points();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Update the plot
|
// Update the plot
|
||||||
Q_EMIT rangeChanged();
|
Q_EMIT rangeChanged();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief Calculates the auto-scale range for the Y-axis.
|
* @brief Calculates the auto-scale range for both X and Y axes of the plot.
|
||||||
|
*
|
||||||
|
* This function determines the minimum and maximum values for the X and Y axes
|
||||||
|
* of the plot based on the associated dataset. If the X-axis data source is set
|
||||||
|
* to a specific dataset, its range is computed; otherwise, the range defaults
|
||||||
|
* to `[0, points]`. For the Y-axis, the range is always determined from the
|
||||||
|
* dataset values.
|
||||||
|
*
|
||||||
|
* @note The function emits the `rangeChanged()` signal if either the X or Y
|
||||||
|
* range is updated.
|
||||||
*/
|
*/
|
||||||
void Widgets::Plot::calculateAutoScaleRange()
|
void Widgets::Plot::calculateAutoScaleRange()
|
||||||
|
{
|
||||||
|
// Validate that the dataset exists
|
||||||
|
if (!VALIDATE_WIDGET(SerialStudio::DashboardPlot, m_index))
|
||||||
|
return;
|
||||||
|
|
||||||
|
// Initialize parameters
|
||||||
|
bool xChanged = false;
|
||||||
|
bool yChanged = false;
|
||||||
|
|
||||||
|
// Obtain scale range for Y-axis
|
||||||
|
// clang-format off
|
||||||
|
const auto &yDataset = GET_DATASET(SerialStudio::DashboardPlot, m_index);
|
||||||
|
yChanged = computeMinMaxValues(m_minY, m_maxY, yDataset, true, [](const QPointF &p) { return p.y(); });
|
||||||
|
// clang-format on
|
||||||
|
|
||||||
|
// Obtain range scale for X-axis
|
||||||
|
// clang-format off
|
||||||
|
if (UI::Dashboard::instance().datasets().contains(yDataset.xAxisId()))
|
||||||
|
{
|
||||||
|
const auto &xDataset = UI::Dashboard::instance().datasets()[yDataset.xAxisId()];
|
||||||
|
xChanged = computeMinMaxValues(m_minX, m_maxX, xDataset, false, [](const QPointF &p) { return p.x(); });
|
||||||
|
}
|
||||||
|
// clang-format on
|
||||||
|
|
||||||
|
// X-axis data source set to samples, use [0, points] as range
|
||||||
|
else
|
||||||
|
{
|
||||||
|
const auto points = UI::Dashboard::instance().points();
|
||||||
|
|
||||||
|
if (m_minX != 0 || m_maxX != points)
|
||||||
|
{
|
||||||
|
m_minX = 0;
|
||||||
|
m_maxX = points;
|
||||||
|
xChanged = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Update user interface
|
||||||
|
if (xChanged || yChanged)
|
||||||
|
Q_EMIT rangeChanged();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Computes the minimum and maximum values for a given axis of the plot.
|
||||||
|
*
|
||||||
|
* This templated function calculates the minimum and maximum values for a plot
|
||||||
|
* axis (either X or Y) using the provided dataset and an extractor function. If
|
||||||
|
* the dataset has no valid range or is empty, a fallback range `[0, 1]` or an
|
||||||
|
* adjusted range is applied.
|
||||||
|
*
|
||||||
|
* @tparam Extractor A callable object (e.g., lambda) used to extract values
|
||||||
|
* from data points.
|
||||||
|
*
|
||||||
|
* @param min Reference to the variable storing the minimum value.
|
||||||
|
* @param max Reference to the variable storing the maximum value.
|
||||||
|
* @param dataset The dataset to compute the range from.
|
||||||
|
* @param extractor A function used to extract axis-specific values (e.g.,
|
||||||
|
* `p.y()` or `p.x()`).
|
||||||
|
*
|
||||||
|
* @return `true` if the computed range differs from the previous range, `false`
|
||||||
|
* otherwise.
|
||||||
|
*
|
||||||
|
* @note If the dataset has the same minimum and maximum values, the range is
|
||||||
|
* adjusted to provide a better display.
|
||||||
|
*/
|
||||||
|
template<typename Extractor>
|
||||||
|
bool Widgets::Plot::computeMinMaxValues(qreal &min, qreal &max,
|
||||||
|
const JSON::Dataset &dataset,
|
||||||
|
const bool addPadding,
|
||||||
|
Extractor extractor)
|
||||||
{
|
{
|
||||||
// Store previous values
|
// Store previous values
|
||||||
bool ok = true;
|
bool ok = true;
|
||||||
const auto prevMinY = m_minY;
|
const auto prevMinY = min;
|
||||||
const auto prevMaxY = m_maxY;
|
const auto prevMaxY = max;
|
||||||
|
|
||||||
// If the data is empty, set the range to 0-1
|
// If the data is empty, set the range to 0-1
|
||||||
if (m_data.isEmpty())
|
if (m_data.isEmpty())
|
||||||
{
|
{
|
||||||
m_minY = 0;
|
min = 0;
|
||||||
m_maxY = 1;
|
max = 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Obtain min/max values from datasets
|
// Obtain min/max values from datasets
|
||||||
else if (VALIDATE_WIDGET(SerialStudio::DashboardPlot, m_index))
|
else
|
||||||
{
|
{
|
||||||
const auto &dataset = GET_DATASET(SerialStudio::DashboardPlot, m_index);
|
|
||||||
ok &= !qFuzzyCompare(dataset.min(), dataset.max());
|
ok &= !qFuzzyCompare(dataset.min(), dataset.max());
|
||||||
if (ok)
|
if (ok)
|
||||||
{
|
{
|
||||||
m_minY = qMin(dataset.min(), dataset.max());
|
min = qMin(dataset.min(), dataset.max());
|
||||||
m_maxY = qMax(dataset.min(), dataset.max());
|
max = qMax(dataset.min(), dataset.max());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -210,49 +334,52 @@ void Widgets::Plot::calculateAutoScaleRange()
|
|||||||
if (!ok)
|
if (!ok)
|
||||||
{
|
{
|
||||||
// Initialize values to ensure that min/max are set
|
// Initialize values to ensure that min/max are set
|
||||||
m_minY = std::numeric_limits<qreal>::max();
|
min = std::numeric_limits<qreal>::max();
|
||||||
m_maxY = std::numeric_limits<qreal>::lowest();
|
max = std::numeric_limits<qreal>::lowest();
|
||||||
|
|
||||||
// Loop through the plot data and update the min and max
|
// Loop through the plot data and update the min and max
|
||||||
m_minY = SIMD::findMin(m_data, [](const QPointF &p) { return p.y(); });
|
min = SIMD::findMin(m_data, extractor);
|
||||||
m_maxY = SIMD::findMax(m_data, [](const QPointF &p) { return p.y(); });
|
max = SIMD::findMax(m_data, extractor);
|
||||||
|
|
||||||
// If min and max are the same, adjust the range
|
// If min and max are the same, adjust the range
|
||||||
if (qFuzzyCompare(m_minY, m_maxY))
|
if (qFuzzyCompare(min, max))
|
||||||
{
|
{
|
||||||
if (qFuzzyIsNull(m_minY))
|
if (qFuzzyIsNull(min))
|
||||||
{
|
{
|
||||||
m_minY = -1;
|
min = -1;
|
||||||
m_maxY = 1;
|
max = 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
double absValue = qAbs(m_minY);
|
double absValue = qAbs(min);
|
||||||
m_minY = m_minY - absValue * 0.1;
|
min = min - absValue * 0.1;
|
||||||
m_maxY = m_maxY + absValue * 0.1;
|
min = max + absValue * 0.1;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// If the min and max are not the same, set the range to 10% more
|
// If the min and max are not the same, set the range to 10% more
|
||||||
else
|
else if (addPadding)
|
||||||
{
|
{
|
||||||
double range = m_maxY - m_minY;
|
double range = max - min;
|
||||||
m_minY -= range * 0.1;
|
min -= range * 0.1;
|
||||||
m_maxY += range * 0.1;
|
max += range * 0.1;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Round to integer numbers
|
// Round to integer numbers
|
||||||
m_maxY = std::ceil(m_maxY);
|
max = std::ceil(max);
|
||||||
m_minY = std::floor(m_minY);
|
min = std::floor(min);
|
||||||
if (qFuzzyCompare(m_maxY, m_minY))
|
if (qFuzzyCompare(max, min) && addPadding)
|
||||||
{
|
{
|
||||||
m_minY -= 1;
|
min -= 1;
|
||||||
m_maxY += 1;
|
max += 1;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Update user interface if required
|
// Update user interface if required
|
||||||
if (qFuzzyCompare(prevMinY, m_minY) || qFuzzyCompare(prevMaxY, m_maxY))
|
if (qFuzzyCompare(prevMinY, min) || qFuzzyCompare(prevMaxY, max))
|
||||||
Q_EMIT rangeChanged();
|
return true;
|
||||||
|
|
||||||
|
// Data not changed
|
||||||
|
return false;
|
||||||
}
|
}
|
||||||
|
@ -26,6 +26,8 @@
|
|||||||
#include <QVector>
|
#include <QVector>
|
||||||
#include <QLineSeries>
|
#include <QLineSeries>
|
||||||
|
|
||||||
|
#include "JSON/Dataset.h"
|
||||||
|
|
||||||
namespace Widgets
|
namespace Widgets
|
||||||
{
|
{
|
||||||
/**
|
/**
|
||||||
@ -35,6 +37,7 @@ class Plot : public QQuickItem
|
|||||||
{
|
{
|
||||||
Q_OBJECT
|
Q_OBJECT
|
||||||
Q_PROPERTY(QString yLabel READ yLabel CONSTANT)
|
Q_PROPERTY(QString yLabel READ yLabel CONSTANT)
|
||||||
|
Q_PROPERTY(QString xLabel READ xLabel CONSTANT)
|
||||||
Q_PROPERTY(qreal minX READ minX NOTIFY rangeChanged)
|
Q_PROPERTY(qreal minX READ minX NOTIFY rangeChanged)
|
||||||
Q_PROPERTY(qreal maxX READ maxX NOTIFY rangeChanged)
|
Q_PROPERTY(qreal maxX READ maxX NOTIFY rangeChanged)
|
||||||
Q_PROPERTY(qreal minY READ minY NOTIFY rangeChanged)
|
Q_PROPERTY(qreal minY READ minY NOTIFY rangeChanged)
|
||||||
@ -60,6 +63,7 @@ public:
|
|||||||
[[nodiscard]] qreal xTickInterval() const;
|
[[nodiscard]] qreal xTickInterval() const;
|
||||||
[[nodiscard]] qreal yTickInterval() const;
|
[[nodiscard]] qreal yTickInterval() const;
|
||||||
[[nodiscard]] const QString &yLabel() const;
|
[[nodiscard]] const QString &yLabel() const;
|
||||||
|
[[nodiscard]] const QString &xLabel() const;
|
||||||
|
|
||||||
public slots:
|
public slots:
|
||||||
void draw(QLineSeries *series);
|
void draw(QLineSeries *series);
|
||||||
@ -69,6 +73,11 @@ private slots:
|
|||||||
void updateRange();
|
void updateRange();
|
||||||
void calculateAutoScaleRange();
|
void calculateAutoScaleRange();
|
||||||
|
|
||||||
|
private:
|
||||||
|
template<typename Extractor>
|
||||||
|
bool computeMinMaxValues(qreal &min, qreal &max, const JSON::Dataset &dataset,
|
||||||
|
const bool addPadding, Extractor extractor);
|
||||||
|
|
||||||
private:
|
private:
|
||||||
int m_index;
|
int m_index;
|
||||||
qreal m_minX;
|
qreal m_minX;
|
||||||
@ -76,6 +85,7 @@ private:
|
|||||||
qreal m_minY;
|
qreal m_minY;
|
||||||
qreal m_maxY;
|
qreal m_maxY;
|
||||||
QString m_yLabel;
|
QString m_yLabel;
|
||||||
|
QString m_xLabel;
|
||||||
QVector<QPointF> m_data;
|
QVector<QPointF> m_data;
|
||||||
};
|
};
|
||||||
} // namespace Widgets
|
} // namespace Widgets
|
||||||
|
Loading…
x
Reference in New Issue
Block a user