• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

paulmthompson / WhiskerToolbox / 17465586740

04 Sep 2025 01:21PM UTC coverage: 70.828% (-0.1%) from 70.97%
17465586740

push

github

paulmthompson
feature tree widget shouldn't emit signals during rebuild

121 of 131 new or added lines in 4 files covered. (92.37%)

108 existing lines in 7 files now uncovered.

34146 of 48210 relevant lines covered (70.83%)

1299.48 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

37.93
/src/WhiskerToolbox/DataViewer_Widget/DataViewer_Widget.cpp
1
#include "DataViewer_Widget.hpp"
2
#include "ui_DataViewer_Widget.h"
3

4
#include "AnalogTimeSeries/Analog_Time_Series.hpp"
5
#include "AnalogTimeSeries/utils/statistics.hpp"
6
#include "DataManager.hpp"
7
#include "DigitalTimeSeries/Digital_Event_Series.hpp"
8
#include "DigitalTimeSeries/Digital_Interval_Series.hpp"
9

10
#include "DataViewer/AnalogTimeSeries/AnalogTimeSeriesDisplayOptions.hpp"
11
#include "DataViewer/AnalogTimeSeries/MVP_AnalogTimeSeries.hpp"
12
#include "DataViewer/DigitalEvent/DigitalEventSeriesDisplayOptions.hpp"
13
#include "DataViewer/DigitalEvent/MVP_DigitalEvent.hpp"
14
#include "DataViewer/DigitalInterval/DigitalIntervalSeriesDisplayOptions.hpp"
15
#include "DataViewer/DigitalInterval/MVP_DigitalInterval.hpp"
16
#include "Feature_Tree_Model.hpp"
17
#include "Feature_Tree_Widget/Feature_Tree_Widget.hpp"
18
#include "OpenGLWidget.hpp"
19
#include "TimeFrame/TimeFrame.hpp"
20
#include "TimeScrollBar/TimeScrollBar.hpp"
21

22
#include "AnalogTimeSeries/AnalogViewer_Widget.hpp"
23
#include "DigitalEvent/EventViewer_Widget.hpp"
24
#include "DigitalInterval/IntervalViewer_Widget.hpp"
25

26
#include <QMetaObject>
27
#include <QPointer>
28
#include <QTableWidget>
29
#include <QWheelEvent>
30

31
#include <algorithm>
32
#include <cmath>
33
#include <iostream>
34

35
DataViewer_Widget::DataViewer_Widget(std::shared_ptr<DataManager> data_manager,
14✔
36
                                     TimeScrollBar * time_scrollbar,
37
                                     MainWindow * main_window,
38
                                     QWidget * parent)
14✔
39
    : QWidget(parent),
40
      _data_manager{std::move(data_manager)},
14✔
41
      _time_scrollbar{time_scrollbar},
14✔
42
      _main_window{main_window},
14✔
43
      ui(new Ui::DataViewer_Widget) {
28✔
44

45
    ui->setupUi(this);
14✔
46

47
    // Initialize plotting manager with default viewport
48
    _plotting_manager = std::make_unique<PlottingManager>();
14✔
49

50
    // Provide PlottingManager reference to OpenGL widget
51
    ui->openGLWidget->setPlottingManager(_plotting_manager.get());
14✔
52

53
    // Initialize feature tree model
54
    _feature_tree_model = std::make_unique<Feature_Tree_Model>(this);
14✔
55
    _feature_tree_model->setDataManager(_data_manager);
14✔
56

57
    // Set up observer to automatically clean up data when it's deleted from DataManager
58
    // Queue the cleanup to the Qt event loop to avoid running during mid-update mutations
59
    _data_manager->addObserver([this]() {
14✔
60
        QPointer<DataViewer_Widget> self = this;
15✔
61
        QMetaObject::invokeMethod(self, [self]() {
15✔
62
            if (!self) return;
15✔
63
            self->cleanupDeletedData(); }, Qt::QueuedConnection);
15✔
64
    });
30✔
65

66
    // Configure Feature_Tree_Widget
67
    ui->feature_tree_widget->setTypeFilters({DM_DataType::Analog, DM_DataType::DigitalEvent, DM_DataType::DigitalInterval});
42✔
68
    ui->feature_tree_widget->setDataManager(_data_manager);
14✔
69

70
    // Connect Feature_Tree_Widget signals using the new interface
71
    connect(ui->feature_tree_widget, &Feature_Tree_Widget::featureSelected, this, [this](std::string const & feature) {
14✔
72
        _handleFeatureSelected(QString::fromStdString(feature));
×
73
    });
×
74

75
    connect(ui->feature_tree_widget, &Feature_Tree_Widget::addFeature, this, [this](std::string const & feature) {
14✔
76
        std::cout << "Adding single feature: " << feature << std::endl;
×
77
        _addFeatureToModel(QString::fromStdString(feature), true);
×
78
    });
×
79

80
    connect(ui->feature_tree_widget, &Feature_Tree_Widget::removeFeature, this, [this](std::string const & feature) {
14✔
UNCOV
81
        std::cout << "Removing single feature: " << feature << std::endl;
×
UNCOV
82
        _addFeatureToModel(QString::fromStdString(feature), false);
×
UNCOV
83
    });
×
84

85
    connect(ui->feature_tree_widget, &Feature_Tree_Widget::addFeatures, this, [this](std::vector<std::string> const & features) {
14✔
86
        std::cout << "Adding " << features.size() << " features as group" << std::endl;
×
87

88
        // Process all features in the group without triggering individual canvas updates
89
        for (auto const & key: features) {
×
90
            _plotSelectedFeatureWithoutUpdate(key);
×
91
        }
92

93
        // Auto-arrange and auto-fill when toggling a group to optimize space usage
94
        if (!features.empty()) {
×
95
            std::cout << "Auto-arranging and filling canvas for group toggle" << std::endl;
×
96
            autoArrangeVerticalSpacing();// This now includes auto-fill functionality
×
97
        }
98

99
        // Trigger a single canvas update at the end
100
        if (!features.empty()) {
×
101
            std::cout << "Triggering single canvas update for group toggle" << std::endl;
×
102
            ui->openGLWidget->updateCanvas();
×
103
        }
104
    });
×
105

106
    connect(ui->feature_tree_widget, &Feature_Tree_Widget::removeFeatures, this, [this](std::vector<std::string> const & features) {
14✔
UNCOV
107
        std::cout << "Removing " << features.size() << " features as group" << std::endl;
×
108

109
        // Process all features in the group without triggering individual canvas updates
UNCOV
110
        for (auto const & key: features) {
×
UNCOV
111
            _removeSelectedFeatureWithoutUpdate(key);
×
112
        }
113

114
        // Auto-arrange and auto-fill when toggling a group to optimize space usage
UNCOV
115
        if (!features.empty()) {
×
UNCOV
116
            std::cout << "Auto-arranging and filling canvas for group toggle" << std::endl;
×
UNCOV
117
            autoArrangeVerticalSpacing();// This now includes auto-fill functionality
×
118
        }
119

120
        // Trigger a single canvas update at the end
UNCOV
121
        if (!features.empty()) {
×
UNCOV
122
            std::cout << "Triggering single canvas update for group toggle" << std::endl;
×
UNCOV
123
            ui->openGLWidget->updateCanvas();
×
124
        }
UNCOV
125
    });
×
126

127
    // Connect color change signals from the model
128
    connect(_feature_tree_model.get(), &Feature_Tree_Model::featureColorChanged, this, &DataViewer_Widget::_handleColorChanged);
14✔
129

130
    // Connect color change signals from the tree widget to the model
131
    connect(ui->feature_tree_widget, &Feature_Tree_Widget::colorChangeFeatures, this, [this](std::vector<std::string> const & features, std::string const & hex_color) {
14✔
NEW
132
        for (auto const & feature: features) {
×
133
            _feature_tree_model->setFeatureColor(feature, hex_color);
×
134
        }
135
    });
×
136

137
    connect(ui->x_axis_samples, QOverload<int>::of(&QSpinBox::valueChanged), this, &DataViewer_Widget::_handleXAxisSamplesChanged);
14✔
138

139
    connect(ui->global_zoom, &QDoubleSpinBox::valueChanged, this, &DataViewer_Widget::_updateGlobalScale);
14✔
140

141
    connect(ui->theme_combo, QOverload<int>::of(&QComboBox::currentIndexChanged), this, &DataViewer_Widget::_handleThemeChanged);
14✔
142

143
    connect(time_scrollbar, &TimeScrollBar::timeChanged, this, &DataViewer_Widget::_updatePlot);
14✔
144

145
    //We should alwasy get the master clock because we plot
146
    // Check for master clock
147
    auto time_keys = _data_manager->getTimeFrameKeys();
14✔
148
    // if timekeys doesn't have master, we should throw an error
149
    if (std::find(time_keys.begin(), time_keys.end(), TimeKey("master")) == time_keys.end()) {
14✔
150
        std::cout << "No master clock found in DataManager" << std::endl;
14✔
151
        _time_frame = _data_manager->getTime(TimeKey("time"));
14✔
152
    } else {
153
        _time_frame = _data_manager->getTime(TimeKey("master"));
×
154
    }
155

156
    std::cout << "Setting GL limit to " << _time_frame->getTotalFrameCount() << std::endl;
14✔
157
    ui->openGLWidget->setXLimit(_time_frame->getTotalFrameCount());
14✔
158

159
    // Set the master time frame for proper coordinate conversion
160
    ui->openGLWidget->setMasterTimeFrame(_time_frame);
14✔
161

162
    // Set spinbox maximum to the actual data range (not the hardcoded UI limit)
163
    int const data_range = static_cast<int>(_time_frame->getTotalFrameCount());
14✔
164
    std::cout << "Setting x_axis_samples maximum to " << data_range << std::endl;
14✔
165
    ui->x_axis_samples->setMaximum(data_range);
14✔
166

167
    // Setup stacked widget with data-type specific viewers
168
    auto analog_widget = new AnalogViewer_Widget(_data_manager, ui->openGLWidget);
14✔
169
    auto interval_widget = new IntervalViewer_Widget(_data_manager, ui->openGLWidget);
14✔
170
    auto event_widget = new EventViewer_Widget(_data_manager, ui->openGLWidget);
14✔
171

172
    ui->stackedWidget->addWidget(analog_widget);
14✔
173
    ui->stackedWidget->addWidget(interval_widget);
14✔
174
    ui->stackedWidget->addWidget(event_widget);
14✔
175

176
    // Connect color change signals from sub-widgets
177
    connect(analog_widget, &AnalogViewer_Widget::colorChanged,
42✔
178
            this, &DataViewer_Widget::_handleColorChanged);
28✔
179
    connect(interval_widget, &IntervalViewer_Widget::colorChanged,
42✔
180
            this, &DataViewer_Widget::_handleColorChanged);
28✔
181
    connect(event_widget, &EventViewer_Widget::colorChanged,
42✔
182
            this, &DataViewer_Widget::_handleColorChanged);
28✔
183

184
    // Connect mouse hover signal from OpenGL widget
185
    connect(ui->openGLWidget, &OpenGLWidget::mouseHover,
42✔
186
            this, &DataViewer_Widget::_updateCoordinateDisplay);
28✔
187

188
    // Grid line connections
189
    connect(ui->grid_lines_enabled, &QCheckBox::toggled, this, &DataViewer_Widget::_handleGridLinesToggled);
14✔
190
    connect(ui->grid_spacing, QOverload<int>::of(&QSpinBox::valueChanged), this, &DataViewer_Widget::_handleGridSpacingChanged);
14✔
191

192
    // Vertical spacing connection
193
    connect(ui->vertical_spacing, QOverload<double>::of(&QDoubleSpinBox::valueChanged), this, &DataViewer_Widget::_handleVerticalSpacingChanged);
14✔
194

195
    // Auto-arrange button connection
196
    connect(ui->auto_arrange_button, &QPushButton::clicked, this, &DataViewer_Widget::autoArrangeVerticalSpacing);
14✔
197

198
    // Initialize grid line UI to match OpenGLWidget defaults
199
    ui->grid_lines_enabled->setChecked(ui->openGLWidget->getGridLinesEnabled());
14✔
200
    ui->grid_spacing->setValue(ui->openGLWidget->getGridSpacing());
14✔
201

202
    // Initialize vertical spacing UI to match OpenGLWidget defaults
203
    ui->vertical_spacing->setValue(static_cast<double>(ui->openGLWidget->getVerticalSpacing()));
14✔
204
}
28✔
205

206
DataViewer_Widget::~DataViewer_Widget() {
27✔
207
    delete ui;
14✔
208
}
27✔
209

210
void DataViewer_Widget::openWidget() {
12✔
211
    std::cout << "DataViewer Widget Opened" << std::endl;
12✔
212

213
    // Tree is already populated by observer pattern in setDataManager()
214
    // Trigger refresh in case of manual opening
215
    ui->feature_tree_widget->refreshTree();
12✔
216

217
    this->show();
12✔
218
    _updateLabels();
12✔
219
}
12✔
220

221
void DataViewer_Widget::closeEvent(QCloseEvent * event) {
×
222
    static_cast<void>(event);
223

224
    std::cout << "Close event detected" << std::endl;
×
225
}
×
226

227
void DataViewer_Widget::resizeEvent(QResizeEvent * event) {
12✔
228
    QWidget::resizeEvent(event);
12✔
229

230
    // Update plotting manager dimensions when widget is resized
231
    _updatePlottingManagerDimensions();
12✔
232

233
    // The OpenGL widget will automatically get its resizeGL called by Qt
234
    // but we can trigger an additional update if needed
235
    if (ui->openGLWidget) {
12✔
236
        ui->openGLWidget->update();
12✔
237
    }
238
}
12✔
239

240
void DataViewer_Widget::_updatePlot(int time) {
1✔
241
    //std::cout << "Time is " << time;
242
    time = _data_manager->getTime(TimeKey("time"))->getTimeAtIndex(TimeFrameIndex(time));
1✔
243
    //std::cout << ""
244
    ui->openGLWidget->updateCanvas(time);
1✔
245

246
    _updateLabels();
1✔
247
}
1✔
248

249

250
void DataViewer_Widget::_addFeatureToModel(QString const & feature, bool enabled) {
9✔
251
    std::cout << "Feature toggle signal received: " << feature.toStdString() << " enabled: " << enabled << std::endl;
9✔
252

253
    if (enabled) {
9✔
254
        _plotSelectedFeature(feature.toStdString());
9✔
255
    } else {
UNCOV
256
        _removeSelectedFeature(feature.toStdString());
×
257
    }
258
}
9✔
259

260
void DataViewer_Widget::_plotSelectedFeature(std::string const & key) {
9✔
261
    std::cout << "Attempting to plot feature: " << key << std::endl;
9✔
262

263
    if (key.empty()) {
9✔
264
        std::cerr << "Error: empty key in _plotSelectedFeature" << std::endl;
×
265
        return;
×
266
    }
267

268
    if (!_data_manager) {
9✔
269
        std::cerr << "Error: null data manager in _plotSelectedFeature" << std::endl;
×
270
        return;
×
271
    }
272

273
    // Get color from model
274
    std::string color = _feature_tree_model->getFeatureColor(key);
9✔
275
    std::cout << "Using color: " << color << " for series: " << key << std::endl;
9✔
276

277
    auto data_type = _data_manager->getType(key);
9✔
278
    std::cout << "Feature type: " << convert_data_type_to_string(data_type) << std::endl;
9✔
279

280
    // Register with plotting manager for coordinated positioning
281
    if (_plotting_manager) {
9✔
282
        std::cout << "Registering series with plotting manager: " << key << std::endl;
9✔
283
    }
284

285
    if (data_type == DM_DataType::Analog) {
9✔
286

287
        std::cout << "Adding << " << key << " to PlottingManager and OpenGLWidget" << std::endl;
9✔
288
        auto series = _data_manager->getData<AnalogTimeSeries>(key);
9✔
289
        if (!series) {
9✔
290
            std::cerr << "Error: failed to get AnalogTimeSeries for key: " << key << std::endl;
×
291
            return;
×
292
        }
293

294

295
        auto time_key = _data_manager->getTimeKey(key);
9✔
296
        std::cout << "Time frame key: " << time_key << std::endl;
9✔
297
        auto time_frame = _data_manager->getTime(time_key);
9✔
298
        if (!time_frame) {
9✔
299
            std::cerr << "Error: failed to get TimeFrame for key: " << key << std::endl;
×
300
            return;
×
301
        }
302

303
        std::cout << "Time frame has " << time_frame->getTotalFrameCount() << " frames" << std::endl;
9✔
304

305
        // Add to plotting manager first
306
        _plotting_manager->addAnalogSeries(key, series, time_frame, color);
9✔
307

308
        ui->openGLWidget->addAnalogTimeSeries(key, series, time_frame, color);
9✔
309
        std::cout << "Successfully added analog series to PlottingManager and OpenGL widget" << std::endl;
9✔
310

311
    } else if (data_type == DM_DataType::DigitalEvent) {
9✔
312

313
        std::cout << "Adding << " << key << " to PlottingManager and OpenGLWidget" << std::endl;
×
314
        auto series = _data_manager->getData<DigitalEventSeries>(key);
×
315
        if (!series) {
×
316
            std::cerr << "Error: failed to get DigitalEventSeries for key: " << key << std::endl;
×
317
            return;
×
318
        }
319

320
        auto time_key = _data_manager->getTimeKey(key);
×
321
        auto time_frame = _data_manager->getTime(time_key);
×
322
        if (!time_frame) {
×
323
            std::cerr << "Error: failed to get TimeFrame for key: " << key << std::endl;
×
324
            return;
×
325
        }
326

327
        // Add to plotting manager first
328
        _plotting_manager->addDigitalEventSeries(key, series, time_frame, color);
×
329

330
        ui->openGLWidget->addDigitalEventSeries(key, series, time_frame, color);
×
331

332
    } else if (data_type == DM_DataType::DigitalInterval) {
×
333

334
        std::cout << "Adding << " << key << " to PlottingManager and OpenGLWidget" << std::endl;
×
335
        auto series = _data_manager->getData<DigitalIntervalSeries>(key);
×
336
        if (!series) {
×
337
            std::cerr << "Error: failed to get DigitalIntervalSeries for key: " << key << std::endl;
×
338
            return;
×
339
        }
340

341
        auto time_key = _data_manager->getTimeKey(key);
×
342
        auto time_frame = _data_manager->getTime(time_key);
×
343
        if (!time_frame) {
×
344
            std::cerr << "Error: failed to get TimeFrame for key: " << key << std::endl;
×
345
            return;
×
346
        }
347

348
        // Add to plotting manager first
349
        _plotting_manager->addDigitalIntervalSeries(key, series, time_frame, color);
×
350

351
        ui->openGLWidget->addDigitalIntervalSeries(key, series, time_frame, color);
×
352

353
    } else {
×
354
        std::cout << "Feature type not supported: " << convert_data_type_to_string(data_type) << std::endl;
×
355
        return;
×
356
    }
357

358
    // Apply coordinated plotting manager allocation after adding to OpenGL widget
359
    if (_plotting_manager) {
9✔
360
        _applyPlottingManagerAllocation(key);
9✔
361
    }
362

363
    // Auto-arrange and auto-fill canvas to make optimal use of space
364
    std::cout << "Auto-arranging and filling canvas after adding series" << std::endl;
9✔
365
    autoArrangeVerticalSpacing();// This now includes auto-fill functionality
9✔
366

367
    std::cout << "Series addition and auto-arrangement completed" << std::endl;
9✔
368
    // Trigger canvas update to show the new series
369
    std::cout << "Triggering canvas update" << std::endl;
9✔
370
    ui->openGLWidget->updateCanvas();
9✔
371
    std::cout << "Canvas update completed" << std::endl;
9✔
372
}
9✔
373

UNCOV
374
void DataViewer_Widget::_removeSelectedFeature(std::string const & key) {
×
UNCOV
375
    std::cout << "Attempting to remove feature: " << key << std::endl;
×
376

UNCOV
377
    if (key.empty()) {
×
378
        std::cerr << "Error: empty key in _removeSelectedFeature" << std::endl;
×
379
        return;
×
380
    }
381

UNCOV
382
    if (!_data_manager) {
×
383
        std::cerr << "Error: null data manager in _removeSelectedFeature" << std::endl;
×
384
        return;
×
385
    }
386

UNCOV
387
    auto data_type = _data_manager->getType(key);
×
388

389
    // Remove from plotting manager first
UNCOV
390
    if (_plotting_manager) {
×
UNCOV
391
        bool removed = false;
×
UNCOV
392
        if (data_type == DM_DataType::Analog) {
×
UNCOV
393
            removed = _plotting_manager->removeAnalogSeries(key);
×
UNCOV
394
        } else if (data_type == DM_DataType::DigitalEvent) {
×
UNCOV
395
            removed = _plotting_manager->removeDigitalEventSeries(key);
×
UNCOV
396
        } else if (data_type == DM_DataType::DigitalInterval) {
×
UNCOV
397
            removed = _plotting_manager->removeDigitalIntervalSeries(key);
×
398
        }
UNCOV
399
        if (removed) {
×
400
            std::cout << "Unregistered '" << key << "' from plotting manager" << std::endl;
×
401
        }
402
    }
403

UNCOV
404
    if (data_type == DM_DataType::Analog) {
×
UNCOV
405
        ui->openGLWidget->removeAnalogTimeSeries(key);
×
UNCOV
406
    } else if (data_type == DM_DataType::DigitalEvent) {
×
UNCOV
407
        ui->openGLWidget->removeDigitalEventSeries(key);
×
UNCOV
408
    } else if (data_type == DM_DataType::DigitalInterval) {
×
UNCOV
409
        ui->openGLWidget->removeDigitalIntervalSeries(key);
×
410
    } else {
411
        std::cout << "Feature type not supported for removal: " << convert_data_type_to_string(data_type) << std::endl;
×
412
        return;
×
413
    }
414

415
    // Auto-arrange and auto-fill canvas to rescale remaining elements
UNCOV
416
    std::cout << "Auto-arranging and filling canvas after removing series" << std::endl;
×
UNCOV
417
    autoArrangeVerticalSpacing();// This now includes auto-fill functionality
×
418

UNCOV
419
    std::cout << "Series removal and auto-arrangement completed" << std::endl;
×
420
    // Trigger canvas update to reflect the removal
UNCOV
421
    std::cout << "Triggering canvas update after removal" << std::endl;
×
UNCOV
422
    ui->openGLWidget->updateCanvas();
×
423
}
424

425
void DataViewer_Widget::_handleFeatureSelected(QString const & feature) {
×
426
    std::cout << "Feature selected signal received: " << feature.toStdString() << std::endl;
×
427

428
    if (feature.isEmpty()) {
×
429
        std::cerr << "Error: empty feature name in _handleFeatureSelected" << std::endl;
×
430
        return;
×
431
    }
432

433
    if (!_data_manager) {
×
434
        std::cerr << "Error: null data manager in _handleFeatureSelected" << std::endl;
×
435
        return;
×
436
    }
437

438
    _highlighted_available_feature = feature;
×
439

440
    // Switch stacked widget based on data type
441
    auto const type = _data_manager->getType(feature.toStdString());
×
442
    auto key = feature.toStdString();
×
443

444
    std::cout << "Feature type for selection: " << convert_data_type_to_string(type) << std::endl;
×
445

446
    if (type == DM_DataType::Analog) {
×
447
        int const stacked_widget_index = 1;// Analog widget is at index 1 (after empty page)
×
448
        ui->stackedWidget->setCurrentIndex(stacked_widget_index);
×
449
        auto analog_widget = dynamic_cast<AnalogViewer_Widget *>(ui->stackedWidget->widget(stacked_widget_index));
×
450
        if (analog_widget) {
×
451
            analog_widget->setActiveKey(key);
×
452
            std::cout << "Selected Analog Time Series: " << key << std::endl;
×
453
        } else {
454
            std::cerr << "Error: failed to cast to AnalogViewer_Widget" << std::endl;
×
455
        }
456

457
    } else if (type == DM_DataType::DigitalInterval) {
×
458
        int const stacked_widget_index = 2;// Interval widget is at index 2
×
459
        ui->stackedWidget->setCurrentIndex(stacked_widget_index);
×
460
        auto interval_widget = dynamic_cast<IntervalViewer_Widget *>(ui->stackedWidget->widget(stacked_widget_index));
×
461
        if (interval_widget) {
×
462
            interval_widget->setActiveKey(key);
×
463
            std::cout << "Selected Digital Interval Series: " << key << std::endl;
×
464
        } else {
465
            std::cerr << "Error: failed to cast to IntervalViewer_Widget" << std::endl;
×
466
        }
467

468
    } else if (type == DM_DataType::DigitalEvent) {
×
469
        int const stacked_widget_index = 3;// Event widget is at index 3
×
470
        ui->stackedWidget->setCurrentIndex(stacked_widget_index);
×
471
        auto event_widget = dynamic_cast<EventViewer_Widget *>(ui->stackedWidget->widget(stacked_widget_index));
×
472
        if (event_widget) {
×
473
            event_widget->setActiveKey(key);
×
474
            std::cout << "Selected Digital Event Series: " << key << std::endl;
×
475
        } else {
476
            std::cerr << "Error: failed to cast to EventViewer_Widget" << std::endl;
×
477
        }
478

479
    } else {
480
        // No specific widget for this type, don't change the current index
481
        std::cout << "Unsupported feature type for detailed view: " << convert_data_type_to_string(type) << std::endl;
×
482
    }
483
}
×
484

485
void DataViewer_Widget::_handleXAxisSamplesChanged(int value) {
15✔
486
    // Use setRangeWidth for spinbox changes (absolute value)
487
    std::cout << "Spinbox requested range width: " << value << std::endl;
15✔
488
    int64_t const actual_range = ui->openGLWidget->setRangeWidth(static_cast<int64_t>(value));
15✔
489
    std::cout << "Actual range width achieved: " << actual_range << std::endl;
15✔
490

491
    // Update the spinbox with the actual range width achieved (in case it was clamped)
492
    if (actual_range != value) {
15✔
493
        std::cout << "Range was clamped, updating spinbox to: " << actual_range << std::endl;
15✔
494
        updateXAxisSamples(static_cast<int>(actual_range));
15✔
495
    }
496
}
15✔
497

498
void DataViewer_Widget::updateXAxisSamples(int value) {
15✔
499
    ui->x_axis_samples->blockSignals(true);
15✔
500
    ui->x_axis_samples->setValue(value);
15✔
501
    ui->x_axis_samples->blockSignals(false);
15✔
502
}
15✔
503

504
void DataViewer_Widget::_updateGlobalScale(double scale) {
10✔
505
    ui->openGLWidget->setGlobalScale(static_cast<float>(scale));
10✔
506

507
    // Also update PlottingManager zoom factor
508
    if (_plotting_manager) {
10✔
509
        _plotting_manager->setGlobalZoom(static_cast<float>(scale));
10✔
510

511
        // Apply updated positions to all registered series
512
        auto analog_keys = _plotting_manager->getVisibleAnalogSeriesKeys();
10✔
513
        for (auto const & key: analog_keys) {
33✔
514
            _applyPlottingManagerAllocation(key);
23✔
515
        }
516
        auto event_keys = _plotting_manager->getVisibleDigitalEventSeriesKeys();
10✔
517
        for (auto const & key: event_keys) {
10✔
518
            _applyPlottingManagerAllocation(key);
×
519
        }
520
        auto interval_keys = _plotting_manager->getVisibleDigitalIntervalSeriesKeys();
10✔
521
        for (auto const & key: interval_keys) {
10✔
522
            _applyPlottingManagerAllocation(key);
×
523
        }
524

525
        // Trigger canvas update
526
        ui->openGLWidget->updateCanvas();
10✔
527
    }
10✔
528
}
10✔
529

530
void DataViewer_Widget::wheelEvent(QWheelEvent * event) {
×
531
    // Disable zooming while dragging intervals
532
    if (ui->openGLWidget->isDraggingInterval()) {
×
533
        return;
×
534
    }
535

536
    auto const numDegrees = static_cast<float>(event->angleDelta().y()) / 8.0f;
×
537
    auto const numSteps = numDegrees / 15.0f;
×
538

539
    auto const current_range = ui->x_axis_samples->value();
×
540

541
    float rangeFactor;
×
542
    if (_zoom_scaling_mode == ZoomScalingMode::Adaptive) {
×
543
        // Adaptive scaling: range factor is proportional to current range width
544
        // This makes adjustments more sensitive when zoomed in (small range), less sensitive when zoomed out (large range)
545
        rangeFactor = static_cast<float>(current_range) * 0.1f;// 10% of current range width
×
546

547
        // Clamp range factor to reasonable bounds
548
        rangeFactor = std::max(1.0f, std::min(rangeFactor, static_cast<float>(_time_frame->getTotalFrameCount()) / 100.0f));
×
549
    } else {
550
        // Fixed scaling (original behavior)
551
        rangeFactor = static_cast<float>(_time_frame->getTotalFrameCount()) / 10000.0f;
×
552
    }
553

554
    // Calculate range delta
555
    // Wheel up (positive numSteps) should zoom IN (decrease range width)
556
    // Wheel down (negative numSteps) should zoom OUT (increase range width)
557
    auto const range_delta = static_cast<int64_t>(-numSteps * rangeFactor);
×
558

559
    // Apply range delta and get the actual achieved range
560
    ui->openGLWidget->changeRangeWidth(range_delta);
×
561

562
    // Get the actual range that was achieved (may be different due to clamping)
563
    auto x_axis = ui->openGLWidget->getXAxis();
×
564
    auto const actual_range = static_cast<int>(x_axis.getEnd() - x_axis.getStart());
×
565

566
    // Update spinbox with the actual achieved range (not the requested range)
567
    updateXAxisSamples(actual_range);
×
568
    _updateLabels();
×
569
}
570

571
void DataViewer_Widget::_updateLabels() {
13✔
572
    auto x_axis = ui->openGLWidget->getXAxis();
13✔
573
    ui->neg_x_label->setText(QString::number(x_axis.getStart()));
13✔
574
    ui->pos_x_label->setText(QString::number(x_axis.getEnd()));
13✔
575
}
13✔
576

577
void DataViewer_Widget::_handleColorChanged(std::string const & feature_key, std::string const & hex_color) {
×
578
    // Update the color in the OpenGL widget display options (tree widget color management will be added later)
579

580
    auto const type = _data_manager->getType(feature_key);
×
581

582
    if (type == DM_DataType::Analog) {
×
583
        auto config = ui->openGLWidget->getAnalogConfig(feature_key);
×
584
        if (config.has_value()) {
×
585
            config.value()->hex_color = hex_color;
×
586
        }
587

588
    } else if (type == DM_DataType::DigitalEvent) {
×
589
        auto config = ui->openGLWidget->getDigitalEventConfig(feature_key);
×
590
        if (config.has_value()) {
×
591
            config.value()->hex_color = hex_color;
×
592
        }
593

594
    } else if (type == DM_DataType::DigitalInterval) {
×
595
        auto config = ui->openGLWidget->getDigitalIntervalConfig(feature_key);
×
596
        if (config.has_value()) {
×
597
            config.value()->hex_color = hex_color;
×
598
        }
599
    }
600

601
    // Trigger a redraw
602
    ui->openGLWidget->updateCanvas();
×
603

604
    std::cout << "Color changed for " << feature_key << " to " << hex_color << std::endl;
×
605
}
×
606

607
void DataViewer_Widget::_updateCoordinateDisplay(float time_coordinate, float canvas_y, QString const & series_info) {
×
608
    // Convert time coordinate to actual time using the time frame
609
    int const time_index = static_cast<int>(std::round(time_coordinate));
×
610
    int const actual_time = _time_frame->getTimeAtIndex(TimeFrameIndex(time_index));
×
611

612
    // Get canvas size for debugging
613
    auto [canvas_width, canvas_height] = ui->openGLWidget->getCanvasSize();
×
614

615
    QString coordinate_text;
×
616
    if (series_info.isEmpty()) {
×
617
        coordinate_text = QString("Coordinates: Time: %1 (index: %2), Canvas Y: %3 | Canvas: %4x%5")
×
618
                                  .arg(actual_time)
×
619
                                  .arg(time_index)
×
620
                                  .arg(canvas_y, 0, 'f', 1)
×
621
                                  .arg(canvas_width)
×
622
                                  .arg(canvas_height);
×
623
    } else {
624
        coordinate_text = QString("Coordinates: Time: %1 (index: %2), %3 | Canvas: %4x%5")
×
625
                                  .arg(actual_time)
×
626
                                  .arg(time_index)
×
627
                                  .arg(series_info)
×
628
                                  .arg(canvas_width)
×
629
                                  .arg(canvas_height);
×
630
    }
631

632
    ui->coordinate_label->setText(coordinate_text);
×
633
}
×
634

635
std::optional<NewAnalogTimeSeriesDisplayOptions *> DataViewer_Widget::getAnalogConfig(std::string const & key) const {
30✔
636
    return ui->openGLWidget->getAnalogConfig(key);
30✔
637
}
638

639
std::optional<NewDigitalEventSeriesDisplayOptions *> DataViewer_Widget::getDigitalEventConfig(std::string const & key) const {
×
640
    return ui->openGLWidget->getDigitalEventConfig(key);
×
641
}
642

643
std::optional<NewDigitalIntervalSeriesDisplayOptions *> DataViewer_Widget::getDigitalIntervalConfig(std::string const & key) const {
×
644
    return ui->openGLWidget->getDigitalIntervalConfig(key);
×
645
}
646

647
void DataViewer_Widget::_handleThemeChanged(int theme_index) {
×
648
    PlotTheme theme = (theme_index == 0) ? PlotTheme::Dark : PlotTheme::Light;
×
649
    ui->openGLWidget->setPlotTheme(theme);
×
650

651
    // Update coordinate label styling based on theme
652
    if (theme == PlotTheme::Dark) {
×
653
        ui->coordinate_label->setStyleSheet("background-color: rgba(0, 0, 0, 50); color: white; padding: 2px;");
×
654
    } else {
655
        ui->coordinate_label->setStyleSheet("background-color: rgba(255, 255, 255, 50); color: black; padding: 2px;");
×
656
    }
657

658
    std::cout << "Theme changed to: " << (theme_index == 0 ? "Dark" : "Light") << std::endl;
×
659
}
×
660

661
void DataViewer_Widget::_handleGridLinesToggled(bool enabled) {
×
662
    ui->openGLWidget->setGridLinesEnabled(enabled);
×
663
}
×
664

665
void DataViewer_Widget::_handleGridSpacingChanged(int spacing) {
×
666
    ui->openGLWidget->setGridSpacing(spacing);
×
667
}
×
668

669
void DataViewer_Widget::_handleVerticalSpacingChanged(double spacing) {
9✔
670
    ui->openGLWidget->setVerticalSpacing(static_cast<float>(spacing));
9✔
671

672
    // Also update PlottingManager vertical scale
673
    if (_plotting_manager) {
9✔
674
        // Convert spacing to a scale factor relative to default (0.1f)
675
        float const scale_factor = static_cast<float>(spacing) / 0.1f;
9✔
676
        _plotting_manager->setGlobalVerticalScale(scale_factor);
9✔
677

678
        // Apply updated positions to all registered series
679
        auto analog_keys = _plotting_manager->getVisibleAnalogSeriesKeys();
9✔
680
        for (auto const & key: analog_keys) {
31✔
681
            _applyPlottingManagerAllocation(key);
22✔
682
        }
683
        auto event_keys = _plotting_manager->getVisibleDigitalEventSeriesKeys();
9✔
684
        for (auto const & key: event_keys) {
9✔
685
            _applyPlottingManagerAllocation(key);
×
686
        }
687
        auto interval_keys = _plotting_manager->getVisibleDigitalIntervalSeriesKeys();
9✔
688
        for (auto const & key: interval_keys) {
9✔
689
            _applyPlottingManagerAllocation(key);
×
690
        }
691

692
        // Trigger canvas update
693
        ui->openGLWidget->updateCanvas();
9✔
694
    }
9✔
695
}
9✔
696

697
void DataViewer_Widget::_plotSelectedFeatureWithoutUpdate(std::string const & key) {
×
698
    std::cout << "Attempting to plot feature (batch): " << key << std::endl;
×
699

700
    if (key.empty()) {
×
701
        std::cerr << "Error: empty key in _plotSelectedFeatureWithoutUpdate" << std::endl;
×
702
        return;
×
703
    }
704

705
    if (!_data_manager) {
×
706
        std::cerr << "Error: null data manager in _plotSelectedFeatureWithoutUpdate" << std::endl;
×
707
        return;
×
708
    }
709

710
    // Get color from model
711
    std::string color = _feature_tree_model->getFeatureColor(key);
×
712
    std::cout << "Using color: " << color << " for series: " << key << std::endl;
×
713

714
    auto data_type = _data_manager->getType(key);
×
715
    std::cout << "Feature type: " << convert_data_type_to_string(data_type) << std::endl;
×
716

717
    if (data_type == DM_DataType::Analog) {
×
718
        auto series = _data_manager->getData<AnalogTimeSeries>(key);
×
719
        if (!series) {
×
720
            std::cerr << "Error: failed to get AnalogTimeSeries for key: " << key << std::endl;
×
721
            return;
×
722
        }
723

724
        auto time_key = _data_manager->getTimeKey(key);
×
725
        auto time_frame = _data_manager->getTime(time_key);
×
726
        if (!time_frame) {
×
727
            std::cerr << "Error: failed to get TimeFrame for key: " << key << std::endl;
×
728
            return;
×
729
        }
730

731
        ui->openGLWidget->addAnalogTimeSeries(key, series, time_frame, color);
×
732

733
    } else if (data_type == DM_DataType::DigitalEvent) {
×
734
        auto series = _data_manager->getData<DigitalEventSeries>(key);
×
735
        if (!series) {
×
736
            std::cerr << "Error: failed to get DigitalEventSeries for key: " << key << std::endl;
×
737
            return;
×
738
        }
739

740
        auto time_key = _data_manager->getTimeKey(key);
×
741
        auto time_frame = _data_manager->getTime(time_key);
×
742
        if (!time_frame) {
×
743
            std::cerr << "Error: failed to get TimeFrame for key: " << key << std::endl;
×
744
            return;
×
745
        }
746

747
        ui->openGLWidget->addDigitalEventSeries(key, series, time_frame, color);
×
748

749
    } else if (data_type == DM_DataType::DigitalInterval) {
×
750
        auto series = _data_manager->getData<DigitalIntervalSeries>(key);
×
751
        if (!series) {
×
752
            std::cerr << "Error: failed to get DigitalIntervalSeries for key: " << key << std::endl;
×
753
            return;
×
754
        }
755

756
        auto time_key = _data_manager->getTimeKey(key);
×
757
        auto time_frame = _data_manager->getTime(time_key);
×
758
        if (!time_frame) {
×
759
            std::cerr << "Error: failed to get TimeFrame for key: " << key << std::endl;
×
760
            return;
×
761
        }
762

763
        ui->openGLWidget->addDigitalIntervalSeries(key, series, time_frame, color);
×
764

765
    } else {
×
766
        std::cout << "Feature type not supported: " << convert_data_type_to_string(data_type) << std::endl;
×
767
        return;
×
768
    }
769

770
    // Note: No canvas update triggered - this is for batch operations
771
    std::cout << "Successfully added series to OpenGL widget (batch mode)" << std::endl;
×
772
}
×
773

UNCOV
774
void DataViewer_Widget::_removeSelectedFeatureWithoutUpdate(std::string const & key) {
×
UNCOV
775
    std::cout << "Attempting to remove feature (batch): " << key << std::endl;
×
776

UNCOV
777
    if (key.empty()) {
×
778
        std::cerr << "Error: empty key in _removeSelectedFeatureWithoutUpdate" << std::endl;
×
779
        return;
×
780
    }
781

UNCOV
782
    if (!_data_manager) {
×
783
        std::cerr << "Error: null data manager in _removeSelectedFeatureWithoutUpdate" << std::endl;
×
784
        return;
×
785
    }
786

UNCOV
787
    auto data_type = _data_manager->getType(key);
×
788

UNCOV
789
    if (data_type == DM_DataType::Analog) {
×
UNCOV
790
        ui->openGLWidget->removeAnalogTimeSeries(key);
×
UNCOV
791
    } else if (data_type == DM_DataType::DigitalEvent) {
×
UNCOV
792
        ui->openGLWidget->removeDigitalEventSeries(key);
×
UNCOV
793
    } else if (data_type == DM_DataType::DigitalInterval) {
×
UNCOV
794
        ui->openGLWidget->removeDigitalIntervalSeries(key);
×
795
    } else {
796
        std::cout << "Feature type not supported for removal: " << convert_data_type_to_string(data_type) << std::endl;
×
797
        return;
×
798
    }
799

800
    // Note: No canvas update triggered - this is for batch operations
UNCOV
801
    std::cout << "Successfully removed series from OpenGL widget (batch mode)" << std::endl;
×
802
}
803

804
void DataViewer_Widget::_calculateOptimalScaling(std::vector<std::string> const & group_keys) {
×
805
    if (group_keys.empty()) {
×
806
        return;
×
807
    }
808

809
    std::cout << "Calculating optimal scaling for " << group_keys.size() << " analog channels..." << std::endl;
×
810

811
    // Get current canvas dimensions
812
    auto [canvas_width, canvas_height] = ui->openGLWidget->getCanvasSize();
×
813
    std::cout << "Canvas size: " << canvas_width << "x" << canvas_height << " pixels" << std::endl;
×
814

815
    // Count total number of currently visible analog series (including the new group)
816
    int total_visible_analog_series = static_cast<int>(group_keys.size());
×
817

818
    // Add any other already visible analog series
819
    auto all_keys = _data_manager->getAllKeys();
×
820
    for (auto const & key: all_keys) {
×
821
        if (_data_manager->getType(key) == DM_DataType::Analog) {
×
822
            // Check if this key is already in our group (avoid double counting)
823
            bool in_group = std::find(group_keys.begin(), group_keys.end(), key) != group_keys.end();
×
824
            if (!in_group) {
×
825
                // Check if this series is currently visible
826
                auto config = ui->openGLWidget->getAnalogConfig(key);
×
827
                if (config.has_value() && config.value()->is_visible) {
×
828
                    total_visible_analog_series++;
×
829
                }
830
            }
831
        }
832
    }
833

834
    std::cout << "Total visible analog series (including new group): " << total_visible_analog_series << std::endl;
×
835

836
    if (total_visible_analog_series <= 0) {
×
837
        return;// No series to scale
×
838
    }
839

840
    // Calculate optimal vertical spacing
841
    // Leave some margin at top and bottom (10% each = 20% total)
842
    float const effective_height = static_cast<float>(canvas_height) * 0.8f;
×
843
    float const optimal_spacing = effective_height / static_cast<float>(total_visible_analog_series);
×
844

845
    // Convert to normalized coordinates (OpenGL widget uses normalized spacing)
846
    // Assuming the widget's view height is typically around 2.0 units in normalized coordinates
847
    float const normalized_spacing = (optimal_spacing / static_cast<float>(canvas_height)) * 2.0f;
×
848

849
    // Clamp to reasonable bounds
850
    float const min_spacing = 0.01f;
×
851
    float const max_spacing = 1.0f;
×
852
    float const final_spacing = std::clamp(normalized_spacing, min_spacing, max_spacing);
×
853

854
    std::cout << "Calculated spacing: " << optimal_spacing << " pixels -> "
×
855
              << final_spacing << " normalized units" << std::endl;
×
856

857
    // Calculate optimal global gain based on standard deviations
858
    std::vector<float> std_devs;
×
859
    std_devs.reserve(group_keys.size());
×
860

861
    for (auto const & key: group_keys) {
×
862
        auto series = _data_manager->getData<AnalogTimeSeries>(key);
×
863
        if (series) {
×
864
            float const std_dev = calculate_std_dev_approximate(*series);
×
865
            std_devs.push_back(std_dev);
×
866
            std::cout << "Series " << key << " std dev: " << std_dev << std::endl;
×
867
        }
868
    }
×
869

870
    if (!std_devs.empty()) {
×
871
        // Use the median standard deviation as reference for scaling
872
        std::sort(std_devs.begin(), std_devs.end());
×
873
        float const median_std_dev = std_devs[std_devs.size() / 2];
×
874

875
        // Calculate optimal global scale
876
        // Target: each series should use about 60% of its allocated vertical space
877
        float const target_amplitude_fraction = 0.6f;
×
878
        float const target_amplitude_in_pixels = optimal_spacing * target_amplitude_fraction;
×
879

880
        // Convert to normalized coordinates (3 standard deviations should fit in target amplitude)
881
        float const target_amplitude_normalized = (target_amplitude_in_pixels / static_cast<float>(canvas_height)) * 2.0f;
×
882
        float const three_sigma_target = target_amplitude_normalized;
×
883

884
        // Calculate scale factor needed
885
        float const optimal_global_scale = three_sigma_target / (3.0f * median_std_dev);
×
886

887
        // Clamp to reasonable bounds
888
        float const min_scale = 0.1f;
×
889
        float const max_scale = 100.0f;
×
890
        float const final_scale = std::clamp(optimal_global_scale, min_scale, max_scale);
×
891

892
        std::cout << "Median std dev: " << median_std_dev
×
893
                  << ", target amplitude: " << target_amplitude_in_pixels << " pixels"
×
894
                  << ", optimal global scale: " << final_scale << std::endl;
×
895

896
        // Apply the calculated settings
897
        ui->vertical_spacing->setValue(static_cast<double>(final_spacing));
×
898
        ui->global_zoom->setValue(static_cast<double>(final_scale));
×
899

900
        std::cout << "Applied auto-scaling: vertical spacing = " << final_spacing
×
901
                  << ", global scale = " << final_scale << std::endl;
×
902

903
    } else {
904
        // If we can't calculate standard deviations, just apply spacing
905
        ui->vertical_spacing->setValue(static_cast<double>(final_spacing));
×
906
        std::cout << "Applied auto-spacing only: vertical spacing = " << final_spacing << std::endl;
×
907
    }
908
}
×
909

910
void DataViewer_Widget::_calculateOptimalEventSpacing(std::vector<std::string> const & group_keys) {
×
911
    if (group_keys.empty()) {
×
912
        return;
×
913
    }
914

915
    std::cout << "Calculating optimal event spacing for " << group_keys.size() << " digital event series..." << std::endl;
×
916

917
    // Get current canvas dimensions
918
    auto [canvas_width, canvas_height] = ui->openGLWidget->getCanvasSize();
×
919
    std::cout << "Canvas size: " << canvas_width << "x" << canvas_height << " pixels" << std::endl;
×
920

921
    // Count total number of currently visible digital event series (including the new group)
922
    int total_visible_event_series = static_cast<int>(group_keys.size());
×
923

924
    // Add any other already visible digital event series
925
    auto all_keys = _data_manager->getAllKeys();
×
926
    for (auto const & key: all_keys) {
×
927
        if (_data_manager->getType(key) == DM_DataType::DigitalEvent) {
×
928
            // Check if this key is already in our group (avoid double counting)
929
            bool const in_group = std::find(group_keys.begin(), group_keys.end(), key) != group_keys.end();
×
930
            if (!in_group) {
×
931
                // Check if this series is currently visible
932
                auto config = ui->openGLWidget->getDigitalEventConfig(key);
×
933
                if (config.has_value() && config.value()->is_visible) {
×
934
                    total_visible_event_series++;
×
935
                }
936
            }
937
        }
938
    }
939

940
    std::cout << "Total visible digital event series (including new group): " << total_visible_event_series << std::endl;
×
941

942
    if (total_visible_event_series <= 0) {
×
943
        return;// No series to scale
×
944
    }
945

946
    // Calculate optimal vertical spacing
947
    // Leave some margin at top and bottom (10% each = 20% total)
948
    float const effective_height = static_cast<float>(canvas_height) * 0.8f;
×
949
    float const optimal_spacing = effective_height / static_cast<float>(total_visible_event_series);
×
950

951
    // Convert to normalized coordinates (OpenGL widget uses normalized spacing)
952
    // Assuming the widget's view height is typically around 2.0 units in normalized coordinates
953
    float const normalized_spacing = (optimal_spacing / static_cast<float>(canvas_height)) * 2.0f;
×
954

955
    // Clamp to reasonable bounds
956
    float const min_spacing = 0.01f;
×
957
    float const max_spacing = 1.0f;
×
958
    float const final_spacing = std::clamp(normalized_spacing, min_spacing, max_spacing);
×
959

960
    // Calculate optimal event height (should be smaller than spacing to avoid overlap)
961
    float const optimal_event_height = final_spacing * 0.6f;// 60% of spacing for visible separation
×
962
    float const min_height = 0.01f;
×
963
    float const max_height = 0.5f;
×
964
    float const final_height = std::clamp(optimal_event_height, min_height, max_height);
×
965

966
    std::cout << "Calculated spacing: " << optimal_spacing << " pixels -> "
×
967
              << final_spacing << " normalized units" << std::endl;
×
968
    std::cout << "Calculated event height: " << final_height << " normalized units" << std::endl;
×
969

970
    // Apply the calculated settings to all event series in the group
971
    for (auto const & key: group_keys) {
×
972
        auto config = ui->openGLWidget->getDigitalEventConfig(key);
×
973
        if (config.has_value()) {
×
974
            config.value()->vertical_spacing = final_spacing;
×
975
            config.value()->event_height = final_height;
×
976
            config.value()->display_mode = EventDisplayMode::Stacked;// Ensure stacked mode
×
977
        }
978
    }
979

980
    std::cout << "Applied auto-calculated event spacing: spacing = " << final_spacing
×
981
              << ", height = " << final_height << std::endl;
×
982
}
×
983

984
void DataViewer_Widget::autoArrangeVerticalSpacing() {
9✔
985
    std::cout << "DataViewer_Widget: Auto-arranging with plotting manager..." << std::endl;
9✔
986

987
    // Update dimensions first
988
    _updatePlottingManagerDimensions();
9✔
989

990
    // Apply new allocations to all registered series
991
    auto analog_keys = _plotting_manager->getVisibleAnalogSeriesKeys();
9✔
992
    auto event_keys = _plotting_manager->getVisibleDigitalEventSeriesKeys();
9✔
993
    auto interval_keys = _plotting_manager->getVisibleDigitalIntervalSeriesKeys();
9✔
994

995
    for (auto const & key: analog_keys) {
31✔
996
        _applyPlottingManagerAllocation(key);
22✔
997
    }
998
    for (auto const & key: event_keys) {
9✔
999
        _applyPlottingManagerAllocation(key);
×
1000
    }
1001
    for (auto const & key: interval_keys) {
9✔
1002
        _applyPlottingManagerAllocation(key);
×
1003
    }
1004

1005
    // Calculate and apply optimal scaling to fill the canvas
1006
    _autoFillCanvas();
9✔
1007

1008
    // Update OpenGL widget view bounds based on content height
1009
    _updateViewBounds();
9✔
1010

1011
    // Trigger canvas update to show new positions
1012
    ui->openGLWidget->updateCanvas();
9✔
1013

1014
    auto total_keys = analog_keys.size() + event_keys.size() + interval_keys.size();
9✔
1015
    std::cout << "DataViewer_Widget: Auto-arrange completed for " << total_keys << " series" << std::endl;
9✔
1016
}
18✔
1017

1018
void DataViewer_Widget::_updateViewBounds() {
9✔
1019
    if (!_plotting_manager) {
9✔
1020
        return;
×
1021
    }
1022

1023
    // PlottingManager uses normalized coordinates, so view bounds are typically -1 to +1
1024
    // For now, use standard bounds but this enables future enhancement
1025
    std::cout << "DataViewer_Widget: Using standard view bounds with PlottingManager" << std::endl;
9✔
1026
}
1027

1028
std::string DataViewer_Widget::_convertDataType(DM_DataType dm_type) const {
×
1029
    switch (dm_type) {
×
1030
        case DM_DataType::Analog:
×
1031
            return "Analog";
×
1032
        case DM_DataType::DigitalEvent:
×
1033
            return "DigitalEvent";
×
1034
        case DM_DataType::DigitalInterval:
×
1035
            return "DigitalInterval";
×
1036
        default:
×
1037
            // For unsupported types, default to Analog
1038
            // This should be rare in practice given our type filters
1039
            std::cerr << "Warning: Unsupported data type " << convert_data_type_to_string(dm_type)
×
1040
                      << " defaulting to Analog for plotting manager" << std::endl;
×
1041
            return "Analog";
×
1042
    }
1043
}
1044

1045
void DataViewer_Widget::_updatePlottingManagerDimensions() {
21✔
1046
    if (!_plotting_manager) {
21✔
1047
        return;
×
1048
    }
1049

1050
    // Get current canvas dimensions from OpenGL widget
1051
    auto [canvas_width, canvas_height] = ui->openGLWidget->getCanvasSize();
21✔
1052

1053
    // PlottingManager works in normalized device coordinates, so no specific dimension update needed
1054
    // But we could update viewport bounds if needed in the future
1055

1056
    std::cout << "DataViewer_Widget: Updated plotting manager dimensions: "
21✔
1057
              << canvas_width << "x" << canvas_height << " pixels" << std::endl;
21✔
1058
}
1059

1060
void DataViewer_Widget::_applyPlottingManagerAllocation(std::string const & series_key) {
76✔
1061
    if (!_plotting_manager) {
76✔
1062
        return;
×
1063
    }
1064

1065
    auto data_type = _data_manager->getType(series_key);
76✔
1066

1067
    std::cout << "DataViewer_Widget: Applying plotting manager allocation for '" << series_key << "'" << std::endl;
76✔
1068

1069
    // For now, use a basic implementation that will be enhanced when we update OpenGLWidget
1070
    // The main goal is to get the compilation working first
1071

1072
    // Apply positioning based on data type
1073
    if (data_type == DM_DataType::Analog) {
76✔
1074
        auto config = ui->openGLWidget->getAnalogConfig(series_key);
76✔
1075
        if (config.has_value()) {
76✔
1076
            // Basic allocation - will be properly implemented when OpenGL widget is updated
1077
            std::cout << "  Applied basic allocation to analog '" << series_key << "'" << std::endl;
76✔
1078
        }
1079

1080
    } else if (data_type == DM_DataType::DigitalEvent) {
×
1081
        auto config = ui->openGLWidget->getDigitalEventConfig(series_key);
×
1082
        if (config.has_value()) {
×
1083
            // Basic allocation - will be properly implemented when OpenGL widget is updated
1084
            std::cout << "  Applied basic allocation to event '" << series_key << "'" << std::endl;
×
1085
        }
1086

1087
    } else if (data_type == DM_DataType::DigitalInterval) {
×
1088
        auto config = ui->openGLWidget->getDigitalIntervalConfig(series_key);
×
1089
        if (config.has_value()) {
×
1090
            // Basic allocation - will be properly implemented when OpenGL widget is updated
1091
            std::cout << "  Applied basic allocation to interval '" << series_key << "'" << std::endl;
×
1092
        }
1093
    }
1094
}
1095

1096
void DataViewer_Widget::_autoFillCanvas() {
9✔
1097
    std::cout << "DataViewer_Widget: Auto-filling canvas with PlottingManager..." << std::endl;
9✔
1098

1099
    if (!_plotting_manager) {
9✔
1100
        std::cout << "No plotting manager available" << std::endl;
×
1101
        return;
×
1102
    }
1103

1104
    // Get current canvas dimensions
1105
    auto [canvas_width, canvas_height] = ui->openGLWidget->getCanvasSize();
9✔
1106
    std::cout << "Canvas size: " << canvas_width << "x" << canvas_height << " pixels" << std::endl;
9✔
1107

1108
    // Count visible series using PlottingManager
1109
    auto analog_keys = _plotting_manager->getVisibleAnalogSeriesKeys();
9✔
1110
    auto event_keys = _plotting_manager->getVisibleDigitalEventSeriesKeys();
9✔
1111
    auto interval_keys = _plotting_manager->getVisibleDigitalIntervalSeriesKeys();
9✔
1112

1113
    int visible_analog_count = static_cast<int>(analog_keys.size());
9✔
1114
    int visible_event_count = static_cast<int>(event_keys.size());
9✔
1115
    int visible_interval_count = static_cast<int>(interval_keys.size());
9✔
1116

1117
    int total_visible = visible_analog_count + visible_event_count + visible_interval_count;
9✔
1118
    std::cout << "Visible series: " << visible_analog_count << " analog, "
9✔
1119
              << visible_event_count << " events, " << visible_interval_count
9✔
1120
              << " intervals (total: " << total_visible << ")" << std::endl;
9✔
1121

1122
    if (total_visible == 0) {
9✔
UNCOV
1123
        std::cout << "No visible series to auto-scale" << std::endl;
×
UNCOV
1124
        return;
×
1125
    }
1126

1127
    // Calculate optimal vertical spacing to fill canvas
1128
    // Use 90% of canvas height, leaving 5% margin at top and bottom
1129
    float const usable_height = static_cast<float>(canvas_height) * 0.9f;
9✔
1130
    float const optimal_spacing_pixels = usable_height / static_cast<float>(total_visible);
9✔
1131

1132
    // Convert to normalized coordinates (assuming 2.0 total normalized height)
1133
    float const optimal_spacing_normalized = (optimal_spacing_pixels / static_cast<float>(canvas_height)) * 2.0f;
9✔
1134

1135
    // Clamp to reasonable bounds
1136
    float const min_spacing = 0.02f;
9✔
1137
    float const max_spacing = 1.5f;
9✔
1138
    float const final_spacing = std::clamp(optimal_spacing_normalized, min_spacing, max_spacing);
9✔
1139

1140
    std::cout << "Calculated optimal spacing: " << optimal_spacing_pixels << " pixels -> "
9✔
1141
              << final_spacing << " normalized units" << std::endl;
9✔
1142

1143
    // Apply the calculated vertical spacing
1144
    ui->vertical_spacing->setValue(static_cast<double>(final_spacing));
9✔
1145

1146
    // Calculate and apply optimal event heights for digital event series
1147
    if (visible_event_count > 0) {
9✔
1148
        // Calculate optimal event height to fill most of the allocated space
1149
        // Use 80% of the spacing to leave some visual separation between events
1150
        float const optimal_event_height = final_spacing * 0.8f;
×
1151

1152
        std::cout << "Calculated optimal event height: " << optimal_event_height << " normalized units" << std::endl;
×
1153

1154
        // Apply optimal height to all visible digital event series
1155
        for (auto const & key: event_keys) {
×
1156
            auto config = ui->openGLWidget->getDigitalEventConfig(key);
×
1157
            if (config.has_value() && config.value()->is_visible) {
×
1158
                config.value()->event_height = optimal_event_height;
×
1159
                config.value()->display_mode = EventDisplayMode::Stacked;// Ensure stacked mode
×
1160
                std::cout << "  Applied event height " << optimal_event_height
×
1161
                          << " to series '" << key << "'" << std::endl;
×
1162
            }
1163
        }
1164
    }
1165

1166
    // Calculate and apply optimal interval heights for digital interval series
1167
    if (visible_interval_count > 0) {
9✔
1168
        // Calculate optimal interval height to fill most of the allocated space
1169
        // Use 80% of the spacing to leave some visual separation between intervals
1170
        float const optimal_interval_height = final_spacing * 0.8f;
×
1171

1172
        std::cout << "Calculated optimal interval height: " << optimal_interval_height << " normalized units" << std::endl;
×
1173

1174
        // Apply optimal height to all visible digital interval series
1175
        for (auto const & key: interval_keys) {
×
1176
            auto config = ui->openGLWidget->getDigitalIntervalConfig(key);
×
1177
            if (config.has_value() && config.value()->is_visible) {
×
1178
                config.value()->interval_height = optimal_interval_height;
×
1179
                std::cout << "  Applied interval height " << optimal_interval_height
×
1180
                          << " to series '" << key << "'" << std::endl;
×
1181
            }
1182
        }
1183
    }
1184

1185
    // Calculate optimal global scale for analog series to use their allocated space effectively
1186
    if (visible_analog_count > 0) {
9✔
1187
        // Sample a few analog series to estimate appropriate scaling
1188
        std::vector<float> sample_std_devs;
9✔
1189
        sample_std_devs.reserve(std::min(5, visible_analog_count));// Sample up to 5 series
9✔
1190

1191
        int sampled = 0;
9✔
1192
        for (auto const & key: analog_keys) {
31✔
1193
            if (sampled >= 5) break;
22✔
1194

1195
            auto config = ui->openGLWidget->getAnalogConfig(key);
22✔
1196
            if (config.has_value() && config.value()->is_visible) {
22✔
1197
                auto series = _data_manager->getData<AnalogTimeSeries>(key);
22✔
1198
                if (series) {
22✔
1199
                    float std_dev = calculate_std_dev_approximate(*series);
22✔
1200
                    if (std_dev > 0.0f) {
22✔
1201
                        sample_std_devs.push_back(std_dev);
22✔
1202
                        sampled++;
22✔
1203
                    }
1204
                }
1205
            }
22✔
1206
        }
1207

1208
        if (!sample_std_devs.empty()) {
9✔
1209
            // Use median standard deviation for scaling calculation
1210
            std::sort(sample_std_devs.begin(), sample_std_devs.end());
9✔
1211
            float median_std_dev = sample_std_devs[sample_std_devs.size() / 2];
9✔
1212

1213
            // Calculate scale so that ±3 standard deviations use ~60% of allocated space
1214
            float const target_amplitude_fraction = 0.6f;
9✔
1215
            float const target_amplitude_pixels = optimal_spacing_pixels * target_amplitude_fraction;
9✔
1216
            float const target_amplitude_normalized = (target_amplitude_pixels / static_cast<float>(canvas_height)) * 2.0f;
9✔
1217

1218
            // For ±3σ coverage
1219
            float const three_sigma_coverage = target_amplitude_normalized;
9✔
1220
            float const optimal_global_scale = three_sigma_coverage / (6.0f * median_std_dev);
9✔
1221

1222
            // Clamp to reasonable bounds
1223
            float const min_scale = 0.01f;
9✔
1224
            float const max_scale = 100.0f;
9✔
1225
            float const final_scale = std::clamp(optimal_global_scale, min_scale, max_scale);
9✔
1226

1227
            std::cout << "Calculated optimal global scale: median_std_dev=" << median_std_dev
9✔
1228
                      << ", target_amplitude=" << target_amplitude_pixels << " pixels"
9✔
1229
                      << ", final_scale=" << final_scale << std::endl;
9✔
1230

1231
            // Apply the calculated global scale
1232
            ui->global_zoom->setValue(static_cast<double>(final_scale));
9✔
1233
        }
1234
    }
9✔
1235

1236
    std::cout << "Auto-fill canvas completed" << std::endl;
9✔
1237
}
9✔
1238

1239
void DataViewer_Widget::cleanupDeletedData() {
15✔
1240
    if (!_data_manager) {
15✔
1241
        return;
×
1242
    }
1243

1244
    // Collect keys that no longer exist in DataManager
1245
    std::vector<std::string> keys_to_cleanup;
15✔
1246

1247
    if (_plotting_manager) {
15✔
1248
        auto analog_keys = _plotting_manager->getVisibleAnalogSeriesKeys();
15✔
1249
        for (auto const & key: analog_keys) {
18✔
1250
            if (!_data_manager->getData<AnalogTimeSeries>(key)) {
3✔
1251
                keys_to_cleanup.push_back(key);
×
1252
            }
1253
        }
1254
        auto event_keys = _plotting_manager->getVisibleDigitalEventSeriesKeys();
15✔
1255
        for (auto const & key: event_keys) {
15✔
1256
            if (!_data_manager->getData<DigitalEventSeries>(key)) {
×
1257
                keys_to_cleanup.push_back(key);
×
1258
            }
1259
        }
1260
        auto interval_keys = _plotting_manager->getVisibleDigitalIntervalSeriesKeys();
15✔
1261
        for (auto const & key: interval_keys) {
15✔
1262
            if (!_data_manager->getData<DigitalIntervalSeries>(key)) {
×
1263
                keys_to_cleanup.push_back(key);
×
1264
            }
1265
        }
1266
    }
15✔
1267

1268
    if (keys_to_cleanup.empty()) {
15✔
1269
        return;
15✔
1270
    }
1271

1272
    // De-duplicate keys in case the same key appears in multiple lists
1273
    std::sort(keys_to_cleanup.begin(), keys_to_cleanup.end());
×
1274
    keys_to_cleanup.erase(std::unique(keys_to_cleanup.begin(), keys_to_cleanup.end()), keys_to_cleanup.end());
×
1275

1276
    // Post cleanup to OpenGLWidget's thread safely
1277
    QPointer<OpenGLWidget> glw = ui ? ui->openGLWidget : nullptr;
×
1278
    if (glw) {
×
1279
        QMetaObject::invokeMethod(glw, [glw, keys = keys_to_cleanup]() {
×
1280
            if (!glw) return;
×
1281
            for (auto const & key : keys) {
×
1282
                glw->removeAnalogTimeSeries(key);
×
1283
                glw->removeDigitalEventSeries(key);
×
1284
                glw->removeDigitalIntervalSeries(key);
×
1285
            } }, Qt::QueuedConnection);
1286
    }
1287

1288
    // Remove from PlottingManager defensively (all types) on our thread
1289
    if (_plotting_manager) {
×
NEW
1290
        for (auto const & key: keys_to_cleanup) {
×
1291
            (void) _plotting_manager->removeAnalogSeries(key);
×
1292
            (void) _plotting_manager->removeDigitalEventSeries(key);
×
1293
            (void) _plotting_manager->removeDigitalIntervalSeries(key);
×
1294
        }
1295
    }
1296

1297
    // Re-arrange remaining data
1298
    autoArrangeVerticalSpacing();
×
1299
}
15✔
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc