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

paulmthompson / WhiskerToolbox / 17446119178

03 Sep 2025 09:06PM UTC coverage: 70.31% (-1.9%) from 72.184%
17446119178

push

github

paulmthompson
fix life cycle issues with data viewer widget. It now cleans up references it holds if something is deleted from data manager that it is plotting

243 of 273 new or added lines in 3 files covered. (89.01%)

130 existing lines in 3 files now uncovered.

33739 of 47986 relevant lines covered (70.31%)

1302.4 hits per line

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

28.12
/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_Widget/Feature_Tree_Widget.hpp"
17
#include "Feature_Tree_Model.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 <QTableWidget>
27
#include <QWheelEvent>
28
#include <QMetaObject>
29
#include <QPointer>
30

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

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

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

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

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

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

57
    // Set up observer to automatically clean up data when it's deleted from DataManager
58
    _data_manager->addObserver([this]() {
11✔
59
        cleanupDeletedData();
14✔
60
    });
14✔
61

62
    // Configure Feature_Tree_Widget
63
    ui->feature_tree_widget->setTypeFilters({DM_DataType::Analog, DM_DataType::DigitalEvent, DM_DataType::DigitalInterval});
33✔
64
    ui->feature_tree_widget->setDataManager(_data_manager);
11✔
65

66
    // Connect Feature_Tree_Widget signals using the new interface
67
    connect(ui->feature_tree_widget, &Feature_Tree_Widget::featureSelected, this, [this](std::string const & feature) {
11✔
68
        _handleFeatureSelected(QString::fromStdString(feature));
×
69
    });
×
70
    
71
    connect(ui->feature_tree_widget, &Feature_Tree_Widget::addFeature, this, [this](std::string const & feature) {
11✔
72
        std::cout << "Adding single feature: " << feature << std::endl;
×
73
        _addFeatureToModel(QString::fromStdString(feature), true);
×
74
    });
×
75
    
76
    connect(ui->feature_tree_widget, &Feature_Tree_Widget::removeFeature, this, [this](std::string const & feature) {
11✔
77
        std::cout << "Removing single feature: " << feature << std::endl;
40✔
78
        _addFeatureToModel(QString::fromStdString(feature), false);
40✔
79
    });
40✔
80

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

84
        // Process all features in the group without triggering individual canvas updates
85
        for (auto const & key: features) {
×
86
            _plotSelectedFeatureWithoutUpdate(key);
×
87
        }
88

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

95
        // Trigger a single canvas update at the end
96
        if (!features.empty()) {
×
97
            std::cout << "Triggering single canvas update for group toggle" << std::endl;
×
98
            ui->openGLWidget->updateCanvas(_data_manager->getCurrentTime());
×
99
        }
100
    });
×
101

102
    connect(ui->feature_tree_widget, &Feature_Tree_Widget::removeFeatures, this, [this](std::vector<std::string> const & features) {
11✔
103
        std::cout << "Removing " << features.size() << " features as group" << std::endl;
80✔
104

105
        // Process all features in the group without triggering individual canvas updates
106
        for (auto const & key: features) {
120✔
107
            _removeSelectedFeatureWithoutUpdate(key);
40✔
108
        }
109

110
        // Auto-arrange and auto-fill when toggling a group to optimize space usage
111
        if (!features.empty()) {
80✔
112
            std::cout << "Auto-arranging and filling canvas for group toggle" << std::endl;
40✔
113
            autoArrangeVerticalSpacing();// This now includes auto-fill functionality
40✔
114
        }
115

116
        // Trigger a single canvas update at the end
117
        if (!features.empty()) {
80✔
118
            std::cout << "Triggering single canvas update for group toggle" << std::endl;
40✔
119
            ui->openGLWidget->updateCanvas(_data_manager->getCurrentTime());
40✔
120
        }
121
    });
80✔
122

123
    // Connect color change signals from the model
124
    connect(_feature_tree_model.get(), &Feature_Tree_Model::featureColorChanged, this, &DataViewer_Widget::_handleColorChanged);
11✔
125
    
126
    // Connect color change signals from the tree widget to the model
127
    connect(ui->feature_tree_widget, &Feature_Tree_Widget::colorChangeFeatures, this, [this](std::vector<std::string> const & features, std::string const & hex_color) {
11✔
128
        for (auto const & feature : features) {
×
129
            _feature_tree_model->setFeatureColor(feature, hex_color);
×
130
        }
131
    });
×
132

133
    connect(ui->x_axis_samples, QOverload<int>::of(&QSpinBox::valueChanged), this, &DataViewer_Widget::_handleXAxisSamplesChanged);
11✔
134

135
    connect(ui->global_zoom, &QDoubleSpinBox::valueChanged, this, &DataViewer_Widget::_updateGlobalScale);
11✔
136

137
    connect(ui->theme_combo, QOverload<int>::of(&QComboBox::currentIndexChanged), this, &DataViewer_Widget::_handleThemeChanged);
11✔
138

139
    connect(time_scrollbar, &TimeScrollBar::timeChanged, this, &DataViewer_Widget::_updatePlot);
11✔
140

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

152
    std::cout << "Setting GL limit to " << _time_frame->getTotalFrameCount() << std::endl;
11✔
153
    ui->openGLWidget->setXLimit(_time_frame->getTotalFrameCount());
11✔
154

155
    // Set the master time frame for proper coordinate conversion
156
    ui->openGLWidget->setMasterTimeFrame(_time_frame);
11✔
157

158
    // Set spinbox maximum to the actual data range (not the hardcoded UI limit)
159
    int const data_range = static_cast<int>(_time_frame->getTotalFrameCount());
11✔
160
    std::cout << "Setting x_axis_samples maximum to " << data_range << std::endl;
11✔
161
    ui->x_axis_samples->setMaximum(data_range);
11✔
162

163
    // Setup stacked widget with data-type specific viewers
164
    auto analog_widget = new AnalogViewer_Widget(_data_manager, ui->openGLWidget);
11✔
165
    auto interval_widget = new IntervalViewer_Widget(_data_manager, ui->openGLWidget);
11✔
166
    auto event_widget = new EventViewer_Widget(_data_manager, ui->openGLWidget);
11✔
167

168
    ui->stackedWidget->addWidget(analog_widget);
11✔
169
    ui->stackedWidget->addWidget(interval_widget);
11✔
170
    ui->stackedWidget->addWidget(event_widget);
11✔
171

172
    // Connect color change signals from sub-widgets
173
    connect(analog_widget, &AnalogViewer_Widget::colorChanged,
33✔
174
            this, &DataViewer_Widget::_handleColorChanged);
22✔
175
    connect(interval_widget, &IntervalViewer_Widget::colorChanged,
33✔
176
            this, &DataViewer_Widget::_handleColorChanged);
22✔
177
    connect(event_widget, &EventViewer_Widget::colorChanged,
33✔
178
            this, &DataViewer_Widget::_handleColorChanged);
22✔
179

180
    // Connect mouse hover signal from OpenGL widget
181
    connect(ui->openGLWidget, &OpenGLWidget::mouseHover,
33✔
182
            this, &DataViewer_Widget::_updateCoordinateDisplay);
22✔
183

184
    // Grid line connections
185
    connect(ui->grid_lines_enabled, &QCheckBox::toggled, this, &DataViewer_Widget::_handleGridLinesToggled);
11✔
186
    connect(ui->grid_spacing, QOverload<int>::of(&QSpinBox::valueChanged), this, &DataViewer_Widget::_handleGridSpacingChanged);
11✔
187

188
    // Vertical spacing connection
189
    connect(ui->vertical_spacing, QOverload<double>::of(&QDoubleSpinBox::valueChanged), this, &DataViewer_Widget::_handleVerticalSpacingChanged);
11✔
190

191
    // Auto-arrange button connection
192
    connect(ui->auto_arrange_button, &QPushButton::clicked, this, &DataViewer_Widget::autoArrangeVerticalSpacing);
11✔
193

194
    // Initialize grid line UI to match OpenGLWidget defaults
195
    ui->grid_lines_enabled->setChecked(ui->openGLWidget->getGridLinesEnabled());
11✔
196
    ui->grid_spacing->setValue(ui->openGLWidget->getGridSpacing());
11✔
197

198
    // Initialize vertical spacing UI to match OpenGLWidget defaults
199
    ui->vertical_spacing->setValue(static_cast<double>(ui->openGLWidget->getVerticalSpacing()));
11✔
200
}
22✔
201

202
DataViewer_Widget::~DataViewer_Widget() {
21✔
203
    delete ui;
11✔
204
}
21✔
205

206
void DataViewer_Widget::openWidget() {
9✔
207
    std::cout << "DataViewer Widget Opened" << std::endl;
9✔
208

209
    // Tree is already populated by observer pattern in setDataManager()
210
    // Trigger refresh in case of manual opening
211
    ui->feature_tree_widget->refreshTree();
9✔
212

213
    this->show();
9✔
214
    _updateLabels();
9✔
215
}
9✔
216

217
void DataViewer_Widget::closeEvent(QCloseEvent * event) {
×
218
    static_cast<void>(event);
219

220
    std::cout << "Close event detected" << std::endl;
×
221
}
×
222

223
void DataViewer_Widget::resizeEvent(QResizeEvent * event) {
9✔
224
    QWidget::resizeEvent(event);
9✔
225

226
    // Update plotting manager dimensions when widget is resized
227
    _updatePlottingManagerDimensions();
9✔
228

229
    // The OpenGL widget will automatically get its resizeGL called by Qt
230
    // but we can trigger an additional update if needed
231
    if (ui->openGLWidget) {
9✔
232
        ui->openGLWidget->update();
9✔
233
    }
234
}
9✔
235

236
void DataViewer_Widget::_updatePlot(int time) {
×
237
    //std::cout << "Time is " << time;
238
    time = _data_manager->getTime(TimeKey("time"))->getTimeAtIndex(TimeFrameIndex(time));
×
239
    //std::cout << ""
240
    ui->openGLWidget->updateCanvas(time);
×
241

242
    _updateLabels();
×
243
}
×
244

245

246
void DataViewer_Widget::_addFeatureToModel(QString const & feature, bool enabled) {
40✔
247
    std::cout << "Feature toggle signal received: " << feature.toStdString() << " enabled: " << enabled << std::endl;
40✔
248

249
    if (enabled) {
40✔
250
        _plotSelectedFeature(feature.toStdString());
×
251
    } else {
252
        _removeSelectedFeature(feature.toStdString());
40✔
253
    }
254
}
40✔
255

256
void DataViewer_Widget::_plotSelectedFeature(std::string const & key) {
×
257
    std::cout << "Attempting to plot feature: " << key << std::endl;
×
258

259
    if (key.empty()) {
×
260
        std::cerr << "Error: empty key in _plotSelectedFeature" << std::endl;
×
261
        return;
×
262
    }
263

264
    if (!_data_manager) {
×
265
        std::cerr << "Error: null data manager in _plotSelectedFeature" << std::endl;
×
266
        return;
×
267
    }
268

269
    // Get color from model
270
    std::string color = _feature_tree_model->getFeatureColor(key);
×
271
    std::cout << "Using color: " << color << " for series: " << key << std::endl;
×
272

273
    auto data_type = _data_manager->getType(key);
×
274
    std::cout << "Feature type: " << convert_data_type_to_string(data_type) << std::endl;
×
275

276
    // Register with plotting manager for coordinated positioning
277
    if (_plotting_manager) {
×
278
        std::cout << "Registering series with plotting manager: " << key << std::endl;
×
279
    }
280

281
    if (data_type == DM_DataType::Analog) {
×
282

283
        std::cout << "Adding << " << key << " to PlottingManager and OpenGLWidget" << std::endl;
×
284
        auto series = _data_manager->getData<AnalogTimeSeries>(key);
×
285
        if (!series) {
×
286
            std::cerr << "Error: failed to get AnalogTimeSeries for key: " << key << std::endl;
×
287
            return;
×
288
        }
289

290

291
        auto time_key = _data_manager->getTimeKey(key);
×
292
        std::cout << "Time frame key: " << time_key << std::endl;
×
293
        auto time_frame = _data_manager->getTime(time_key);
×
294
        if (!time_frame) {
×
295
            std::cerr << "Error: failed to get TimeFrame for key: " << key << std::endl;
×
296
            return;
×
297
        }
298

299
        std::cout << "Time frame has " << time_frame->getTotalFrameCount() << " frames" << std::endl;
×
300

301
        // Add to plotting manager first
302
        _plotting_manager->addAnalogSeries(key, series, time_frame, color);
×
303

304
        ui->openGLWidget->addAnalogTimeSeries(key, series, time_frame, color);
×
305
        std::cout << "Successfully added analog series to PlottingManager and OpenGL widget" << std::endl;
×
306

307
    } else if (data_type == DM_DataType::DigitalEvent) {
×
308

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

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

323
        // Add to plotting manager first
324
        _plotting_manager->addDigitalEventSeries(key, series, time_frame, color);
×
325

326
        ui->openGLWidget->addDigitalEventSeries(key, series, time_frame, color);
×
327

328
    } else if (data_type == DM_DataType::DigitalInterval) {
×
329

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

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

344
        // Add to plotting manager first
345
        _plotting_manager->addDigitalIntervalSeries(key, series, time_frame, color);
×
346

347
        ui->openGLWidget->addDigitalIntervalSeries(key, series, time_frame, color);
×
348

349
    } else {
×
350
        std::cout << "Feature type not supported: " << convert_data_type_to_string(data_type) << std::endl;
×
351
        return;
×
352
    }
353

354
    // Apply coordinated plotting manager allocation after adding to OpenGL widget
355
    if (_plotting_manager) {
×
356
        _applyPlottingManagerAllocation(key);
×
357
    }
358

359
    // Auto-arrange and auto-fill canvas to make optimal use of space
360
    std::cout << "Auto-arranging and filling canvas after adding series" << std::endl;
×
361
    autoArrangeVerticalSpacing();// This now includes auto-fill functionality
×
362

363
    std::cout << "Series addition and auto-arrangement completed" << std::endl;
×
364
    // Trigger canvas update to show the new series
365
    std::cout << "Triggering canvas update" << std::endl;
×
366
    ui->openGLWidget->updateCanvas(_data_manager->getCurrentTime());
×
367
    std::cout << "Canvas update completed" << std::endl;
×
368
}
×
369

370
void DataViewer_Widget::_removeSelectedFeature(std::string const & key) {
40✔
371
    std::cout << "Attempting to remove feature: " << key << std::endl;
40✔
372

373
    if (key.empty()) {
40✔
374
        std::cerr << "Error: empty key in _removeSelectedFeature" << std::endl;
×
375
        return;
×
376
    }
377

378
    if (!_data_manager) {
40✔
379
        std::cerr << "Error: null data manager in _removeSelectedFeature" << std::endl;
×
380
        return;
×
381
    }
382

383
    auto data_type = _data_manager->getType(key);
40✔
384

385
    // Remove from plotting manager first
386
    if (_plotting_manager) {
40✔
387
        bool removed = false;
40✔
388
        if (data_type == DM_DataType::Analog) {
40✔
389
            removed = _plotting_manager->removeAnalogSeries(key);
8✔
390
        } else if (data_type == DM_DataType::DigitalEvent) {
32✔
391
            removed = _plotting_manager->removeDigitalEventSeries(key);
14✔
392
        } else if (data_type == DM_DataType::DigitalInterval) {
18✔
393
            removed = _plotting_manager->removeDigitalIntervalSeries(key);
18✔
394
        }
395
        if (removed) {
40✔
396
            std::cout << "Unregistered '" << key << "' from plotting manager" << std::endl;
×
397
        }
398
    }
399

400
    if (data_type == DM_DataType::Analog) {
40✔
401
        ui->openGLWidget->removeAnalogTimeSeries(key);
8✔
402
    } else if (data_type == DM_DataType::DigitalEvent) {
32✔
403
        ui->openGLWidget->removeDigitalEventSeries(key);
14✔
404
    } else if (data_type == DM_DataType::DigitalInterval) {
18✔
405
        ui->openGLWidget->removeDigitalIntervalSeries(key);
18✔
406
    } else {
407
        std::cout << "Feature type not supported for removal: " << convert_data_type_to_string(data_type) << std::endl;
×
408
        return;
×
409
    }
410

411
    // Auto-arrange and auto-fill canvas to rescale remaining elements
412
    std::cout << "Auto-arranging and filling canvas after removing series" << std::endl;
40✔
413
    autoArrangeVerticalSpacing();// This now includes auto-fill functionality
40✔
414

415
    std::cout << "Series removal and auto-arrangement completed" << std::endl;
40✔
416
    // Trigger canvas update to reflect the removal
417
    std::cout << "Triggering canvas update after removal" << std::endl;
40✔
418
    ui->openGLWidget->updateCanvas(_data_manager->getCurrentTime());
40✔
419
}
420

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

424
    if (feature.isEmpty()) {
×
425
        std::cerr << "Error: empty feature name in _handleFeatureSelected" << std::endl;
×
426
        return;
×
427
    }
428

429
    if (!_data_manager) {
×
430
        std::cerr << "Error: null data manager in _handleFeatureSelected" << std::endl;
×
431
        return;
×
432
    }
433

434
    _highlighted_available_feature = feature;
×
435

436
    // Switch stacked widget based on data type
437
    auto const type = _data_manager->getType(feature.toStdString());
×
438
    auto key = feature.toStdString();
×
439

440
    std::cout << "Feature type for selection: " << convert_data_type_to_string(type) << std::endl;
×
441

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

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

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

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

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

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

494
void DataViewer_Widget::updateXAxisSamples(int value) {
11✔
495
    ui->x_axis_samples->blockSignals(true);
11✔
496
    ui->x_axis_samples->setValue(value);
11✔
497
    ui->x_axis_samples->blockSignals(false);
11✔
498
}
11✔
499

500
void DataViewer_Widget::_updateGlobalScale(double scale) {
×
501
    ui->openGLWidget->setGlobalScale(static_cast<float>(scale));
×
502

503
    // Also update PlottingManager zoom factor
504
    if (_plotting_manager) {
×
505
        _plotting_manager->setGlobalZoom(static_cast<float>(scale));
×
506

507
        // Apply updated positions to all registered series
508
        auto analog_keys = _plotting_manager->getVisibleAnalogSeriesKeys();
×
509
        for (auto const & key: analog_keys) {
×
510
            _applyPlottingManagerAllocation(key);
×
511
        }
512
        auto event_keys = _plotting_manager->getVisibleDigitalEventSeriesKeys();
×
513
        for (auto const & key: event_keys) {
×
514
            _applyPlottingManagerAllocation(key);
×
515
        }
516
        auto interval_keys = _plotting_manager->getVisibleDigitalIntervalSeriesKeys();
×
517
        for (auto const & key: interval_keys) {
×
518
            _applyPlottingManagerAllocation(key);
×
519
        }
520

521
        // Trigger canvas update
522
        ui->openGLWidget->updateCanvas(_data_manager->getCurrentTime());
×
523
    }
×
524
}
×
525

526
void DataViewer_Widget::wheelEvent(QWheelEvent * event) {
×
527
    // Disable zooming while dragging intervals
528
    if (ui->openGLWidget->isDraggingInterval()) {
×
529
        return;
×
530
    }
531

532
    auto const numDegrees = static_cast<float>(event->angleDelta().y()) / 8.0f;
×
533
    auto const numSteps = numDegrees / 15.0f;
×
534

535
    auto const current_range = ui->x_axis_samples->value();
×
536

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

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

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

555
    // Apply range delta and get the actual achieved range
556
    ui->openGLWidget->changeRangeWidth(range_delta);
×
557

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

562
    // Update spinbox with the actual achieved range (not the requested range)
563
    updateXAxisSamples(actual_range);
×
564
    _updateLabels();
×
565
}
566

567
void DataViewer_Widget::_updateLabels() {
9✔
568
    auto x_axis = ui->openGLWidget->getXAxis();
9✔
569
    ui->neg_x_label->setText(QString::number(x_axis.getStart()));
9✔
570
    ui->pos_x_label->setText(QString::number(x_axis.getEnd()));
9✔
571
}
9✔
572

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

576
    auto const type = _data_manager->getType(feature_key);
×
577

578
    if (type == DM_DataType::Analog) {
×
579
        auto config = ui->openGLWidget->getAnalogConfig(feature_key);
×
580
        if (config.has_value()) {
×
581
            config.value()->hex_color = hex_color;
×
582
        }
583

584
    } else if (type == DM_DataType::DigitalEvent) {
×
585
        auto config = ui->openGLWidget->getDigitalEventConfig(feature_key);
×
586
        if (config.has_value()) {
×
587
            config.value()->hex_color = hex_color;
×
588
        }
589

590
    } else if (type == DM_DataType::DigitalInterval) {
×
591
        auto config = ui->openGLWidget->getDigitalIntervalConfig(feature_key);
×
592
        if (config.has_value()) {
×
593
            config.value()->hex_color = hex_color;
×
594
        }
595
    }
596

597
    // Trigger a redraw
598
    ui->openGLWidget->updateCanvas(_data_manager->getCurrentTime());
×
599

600
    std::cout << "Color changed for " << feature_key << " to " << hex_color << std::endl;
×
601
}
×
602

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

608
    // Get canvas size for debugging
609
    auto [canvas_width, canvas_height] = ui->openGLWidget->getCanvasSize();
×
610

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

628
    ui->coordinate_label->setText(coordinate_text);
×
629
}
×
630

631
std::optional<NewAnalogTimeSeriesDisplayOptions *> DataViewer_Widget::getAnalogConfig(std::string const & key) const {
×
632
    return ui->openGLWidget->getAnalogConfig(key);
×
633
}
634

635
std::optional<NewDigitalEventSeriesDisplayOptions *> DataViewer_Widget::getDigitalEventConfig(std::string const & key) const {
×
636
    return ui->openGLWidget->getDigitalEventConfig(key);
×
637
}
638

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

643
void DataViewer_Widget::_handleThemeChanged(int theme_index) {
×
644
    PlotTheme theme = (theme_index == 0) ? PlotTheme::Dark : PlotTheme::Light;
×
645
    ui->openGLWidget->setPlotTheme(theme);
×
646

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

654
    std::cout << "Theme changed to: " << (theme_index == 0 ? "Dark" : "Light") << std::endl;
×
655
}
×
656

657
void DataViewer_Widget::_handleGridLinesToggled(bool enabled) {
×
658
    ui->openGLWidget->setGridLinesEnabled(enabled);
×
659
}
×
660

661
void DataViewer_Widget::_handleGridSpacingChanged(int spacing) {
×
662
    ui->openGLWidget->setGridSpacing(spacing);
×
663
}
×
664

665
void DataViewer_Widget::_handleVerticalSpacingChanged(double spacing) {
×
666
    ui->openGLWidget->setVerticalSpacing(static_cast<float>(spacing));
×
667

668
    // Also update PlottingManager vertical scale
669
    if (_plotting_manager) {
×
670
        // Convert spacing to a scale factor relative to default (0.1f)
671
        float const scale_factor = static_cast<float>(spacing) / 0.1f;
×
672
        _plotting_manager->setGlobalVerticalScale(scale_factor);
×
673

674
        // Apply updated positions to all registered series
675
        auto analog_keys = _plotting_manager->getVisibleAnalogSeriesKeys();
×
676
        for (auto const & key: analog_keys) {
×
677
            _applyPlottingManagerAllocation(key);
×
678
        }
679
        auto event_keys = _plotting_manager->getVisibleDigitalEventSeriesKeys();
×
680
        for (auto const & key: event_keys) {
×
681
            _applyPlottingManagerAllocation(key);
×
682
        }
683
        auto interval_keys = _plotting_manager->getVisibleDigitalIntervalSeriesKeys();
×
684
        for (auto const & key: interval_keys) {
×
685
            _applyPlottingManagerAllocation(key);
×
686
        }
687

688
        // Trigger canvas update
689
        ui->openGLWidget->updateCanvas(_data_manager->getCurrentTime());
×
690
    }
×
691
}
×
692

693
void DataViewer_Widget::_plotSelectedFeatureWithoutUpdate(std::string const & key) {
×
694
    std::cout << "Attempting to plot feature (batch): " << key << std::endl;
×
695

696
    if (key.empty()) {
×
697
        std::cerr << "Error: empty key in _plotSelectedFeatureWithoutUpdate" << std::endl;
×
698
        return;
×
699
    }
700

701
    if (!_data_manager) {
×
702
        std::cerr << "Error: null data manager in _plotSelectedFeatureWithoutUpdate" << std::endl;
×
703
        return;
×
704
    }
705

706
    // Get color from model
707
    std::string color = _feature_tree_model->getFeatureColor(key);
×
708
    std::cout << "Using color: " << color << " for series: " << key << std::endl;
×
709

710
    auto data_type = _data_manager->getType(key);
×
711
    std::cout << "Feature type: " << convert_data_type_to_string(data_type) << std::endl;
×
712

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

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

727
        ui->openGLWidget->addAnalogTimeSeries(key, series, time_frame, color);
×
728

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

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

743
        ui->openGLWidget->addDigitalEventSeries(key, series, time_frame, color);
×
744

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

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

759
        ui->openGLWidget->addDigitalIntervalSeries(key, series, time_frame, color);
×
760

761
    } else {
×
762
        std::cout << "Feature type not supported: " << convert_data_type_to_string(data_type) << std::endl;
×
763
        return;
×
764
    }
765

766
    // Note: No canvas update triggered - this is for batch operations
767
    std::cout << "Successfully added series to OpenGL widget (batch mode)" << std::endl;
×
768
}
×
769

770
void DataViewer_Widget::_removeSelectedFeatureWithoutUpdate(std::string const & key) {
40✔
771
    std::cout << "Attempting to remove feature (batch): " << key << std::endl;
40✔
772

773
    if (key.empty()) {
40✔
774
        std::cerr << "Error: empty key in _removeSelectedFeatureWithoutUpdate" << std::endl;
×
775
        return;
×
776
    }
777

778
    if (!_data_manager) {
40✔
779
        std::cerr << "Error: null data manager in _removeSelectedFeatureWithoutUpdate" << std::endl;
×
780
        return;
×
781
    }
782

783
    auto data_type = _data_manager->getType(key);
40✔
784

785
    if (data_type == DM_DataType::Analog) {
40✔
786
        ui->openGLWidget->removeAnalogTimeSeries(key);
8✔
787
    } else if (data_type == DM_DataType::DigitalEvent) {
32✔
788
        ui->openGLWidget->removeDigitalEventSeries(key);
14✔
789
    } else if (data_type == DM_DataType::DigitalInterval) {
18✔
790
        ui->openGLWidget->removeDigitalIntervalSeries(key);
18✔
791
    } else {
792
        std::cout << "Feature type not supported for removal: " << convert_data_type_to_string(data_type) << std::endl;
×
793
        return;
×
794
    }
795

796
    // Note: No canvas update triggered - this is for batch operations
797
    std::cout << "Successfully removed series from OpenGL widget (batch mode)" << std::endl;
40✔
798
}
799

800
void DataViewer_Widget::_calculateOptimalScaling(std::vector<std::string> const & group_keys) {
×
801
    if (group_keys.empty()) {
×
802
        return;
×
803
    }
804

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

807
    // Get current canvas dimensions
808
    auto [canvas_width, canvas_height] = ui->openGLWidget->getCanvasSize();
×
809
    std::cout << "Canvas size: " << canvas_width << "x" << canvas_height << " pixels" << std::endl;
×
810

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

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

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

832
    if (total_visible_analog_series <= 0) {
×
833
        return;// No series to scale
×
834
    }
835

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

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

845
    // Clamp to reasonable bounds
846
    float const min_spacing = 0.01f;
×
847
    float const max_spacing = 1.0f;
×
848
    float const final_spacing = std::clamp(normalized_spacing, min_spacing, max_spacing);
×
849

850
    std::cout << "Calculated spacing: " << optimal_spacing << " pixels -> "
×
851
              << final_spacing << " normalized units" << std::endl;
×
852

853
    // Calculate optimal global gain based on standard deviations
854
    std::vector<float> std_devs;
×
855
    std_devs.reserve(group_keys.size());
×
856

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

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

871
        // Calculate optimal global scale
872
        // Target: each series should use about 60% of its allocated vertical space
873
        float const target_amplitude_fraction = 0.6f;
×
874
        float const target_amplitude_in_pixels = optimal_spacing * target_amplitude_fraction;
×
875

876
        // Convert to normalized coordinates (3 standard deviations should fit in target amplitude)
877
        float const target_amplitude_normalized = (target_amplitude_in_pixels / static_cast<float>(canvas_height)) * 2.0f;
×
878
        float const three_sigma_target = target_amplitude_normalized;
×
879

880
        // Calculate scale factor needed
881
        float const optimal_global_scale = three_sigma_target / (3.0f * median_std_dev);
×
882

883
        // Clamp to reasonable bounds
884
        float const min_scale = 0.1f;
×
885
        float const max_scale = 100.0f;
×
886
        float const final_scale = std::clamp(optimal_global_scale, min_scale, max_scale);
×
887

888
        std::cout << "Median std dev: " << median_std_dev
×
889
                  << ", target amplitude: " << target_amplitude_in_pixels << " pixels"
×
890
                  << ", optimal global scale: " << final_scale << std::endl;
×
891

892
        // Apply the calculated settings
893
        ui->vertical_spacing->setValue(static_cast<double>(final_spacing));
×
894
        ui->global_zoom->setValue(static_cast<double>(final_scale));
×
895

896
        std::cout << "Applied auto-scaling: vertical spacing = " << final_spacing
×
897
                  << ", global scale = " << final_scale << std::endl;
×
898

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

906
void DataViewer_Widget::_calculateOptimalEventSpacing(std::vector<std::string> const & group_keys) {
×
907
    if (group_keys.empty()) {
×
908
        return;
×
909
    }
910

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

913
    // Get current canvas dimensions
914
    auto [canvas_width, canvas_height] = ui->openGLWidget->getCanvasSize();
×
915
    std::cout << "Canvas size: " << canvas_width << "x" << canvas_height << " pixels" << std::endl;
×
916

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

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

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

938
    if (total_visible_event_series <= 0) {
×
939
        return;// No series to scale
×
940
    }
941

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

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

951
    // Clamp to reasonable bounds
952
    float const min_spacing = 0.01f;
×
953
    float const max_spacing = 1.0f;
×
954
    float const final_spacing = std::clamp(normalized_spacing, min_spacing, max_spacing);
×
955

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

962
    std::cout << "Calculated spacing: " << optimal_spacing << " pixels -> "
×
963
              << final_spacing << " normalized units" << std::endl;
×
964
    std::cout << "Calculated event height: " << final_height << " normalized units" << std::endl;
×
965

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

976
    std::cout << "Applied auto-calculated event spacing: spacing = " << final_spacing
×
977
              << ", height = " << final_height << std::endl;
×
978
}
×
979

980
void DataViewer_Widget::autoArrangeVerticalSpacing() {
80✔
981
    std::cout << "DataViewer_Widget: Auto-arranging with plotting manager..." << std::endl;
80✔
982

983
    // Update dimensions first
984
    _updatePlottingManagerDimensions();
80✔
985

986
    // Apply new allocations to all registered series
987
    auto analog_keys = _plotting_manager->getVisibleAnalogSeriesKeys();
80✔
988
    auto event_keys = _plotting_manager->getVisibleDigitalEventSeriesKeys();
80✔
989
    auto interval_keys = _plotting_manager->getVisibleDigitalIntervalSeriesKeys();
80✔
990

991
    for (auto const & key: analog_keys) {
80✔
992
        _applyPlottingManagerAllocation(key);
×
993
    }
994
    for (auto const & key: event_keys) {
80✔
995
        _applyPlottingManagerAllocation(key);
×
996
    }
997
    for (auto const & key: interval_keys) {
80✔
998
        _applyPlottingManagerAllocation(key);
×
999
    }
1000

1001
    // Calculate and apply optimal scaling to fill the canvas
1002
    _autoFillCanvas();
80✔
1003

1004
    // Update OpenGL widget view bounds based on content height
1005
    _updateViewBounds();
80✔
1006

1007
    // Trigger canvas update to show new positions
1008
    ui->openGLWidget->updateCanvas(_data_manager->getCurrentTime());
80✔
1009

1010
    auto total_keys = analog_keys.size() + event_keys.size() + interval_keys.size();
80✔
1011
    std::cout << "DataViewer_Widget: Auto-arrange completed for " << total_keys << " series" << std::endl;
80✔
1012
}
160✔
1013

1014
void DataViewer_Widget::_updateViewBounds() {
80✔
1015
    if (!_plotting_manager) {
80✔
1016
        return;
×
1017
    }
1018

1019
    // PlottingManager uses normalized coordinates, so view bounds are typically -1 to +1
1020
    // For now, use standard bounds but this enables future enhancement
1021
    std::cout << "DataViewer_Widget: Using standard view bounds with PlottingManager" << std::endl;
80✔
1022
}
1023

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

1041
void DataViewer_Widget::_updatePlottingManagerDimensions() {
89✔
1042
    if (!_plotting_manager) {
89✔
1043
        return;
×
1044
    }
1045

1046
    // Get current canvas dimensions from OpenGL widget
1047
    auto [canvas_width, canvas_height] = ui->openGLWidget->getCanvasSize();
89✔
1048

1049
    // PlottingManager works in normalized device coordinates, so no specific dimension update needed
1050
    // But we could update viewport bounds if needed in the future
1051

1052
    std::cout << "DataViewer_Widget: Updated plotting manager dimensions: "
89✔
1053
              << canvas_width << "x" << canvas_height << " pixels" << std::endl;
89✔
1054
}
1055

1056
void DataViewer_Widget::_applyPlottingManagerAllocation(std::string const & series_key) {
×
1057
    if (!_plotting_manager) {
×
1058
        return;
×
1059
    }
1060

1061
    auto data_type = _data_manager->getType(series_key);
×
1062

1063
    std::cout << "DataViewer_Widget: Applying plotting manager allocation for '" << series_key << "'" << std::endl;
×
1064

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

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

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

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

1092
void DataViewer_Widget::_autoFillCanvas() {
80✔
1093
    std::cout << "DataViewer_Widget: Auto-filling canvas with PlottingManager..." << std::endl;
80✔
1094

1095
    if (!_plotting_manager) {
80✔
1096
        std::cout << "No plotting manager available" << std::endl;
×
1097
        return;
×
1098
    }
1099

1100
    // Get current canvas dimensions
1101
    auto [canvas_width, canvas_height] = ui->openGLWidget->getCanvasSize();
80✔
1102
    std::cout << "Canvas size: " << canvas_width << "x" << canvas_height << " pixels" << std::endl;
80✔
1103

1104
    // Count visible series using PlottingManager
1105
    auto analog_keys = _plotting_manager->getVisibleAnalogSeriesKeys();
80✔
1106
    auto event_keys = _plotting_manager->getVisibleDigitalEventSeriesKeys();
80✔
1107
    auto interval_keys = _plotting_manager->getVisibleDigitalIntervalSeriesKeys();
80✔
1108

1109
    int visible_analog_count = static_cast<int>(analog_keys.size());
80✔
1110
    int visible_event_count = static_cast<int>(event_keys.size());
80✔
1111
    int visible_interval_count = static_cast<int>(interval_keys.size());
80✔
1112

1113
    int total_visible = visible_analog_count + visible_event_count + visible_interval_count;
80✔
1114
    std::cout << "Visible series: " << visible_analog_count << " analog, "
80✔
1115
              << visible_event_count << " events, " << visible_interval_count
80✔
1116
              << " intervals (total: " << total_visible << ")" << std::endl;
80✔
1117

1118
    if (total_visible == 0) {
80✔
1119
        std::cout << "No visible series to auto-scale" << std::endl;
80✔
1120
        return;
80✔
1121
    }
1122

1123
    // Calculate optimal vertical spacing to fill canvas
1124
    // Use 90% of canvas height, leaving 5% margin at top and bottom
1125
    float const usable_height = static_cast<float>(canvas_height) * 0.9f;
×
1126
    float const optimal_spacing_pixels = usable_height / static_cast<float>(total_visible);
×
1127

1128
    // Convert to normalized coordinates (assuming 2.0 total normalized height)
1129
    float const optimal_spacing_normalized = (optimal_spacing_pixels / static_cast<float>(canvas_height)) * 2.0f;
×
1130

1131
    // Clamp to reasonable bounds
1132
    float const min_spacing = 0.02f;
×
1133
    float const max_spacing = 1.5f;
×
1134
    float const final_spacing = std::clamp(optimal_spacing_normalized, min_spacing, max_spacing);
×
1135

1136
    std::cout << "Calculated optimal spacing: " << optimal_spacing_pixels << " pixels -> "
×
1137
              << final_spacing << " normalized units" << std::endl;
×
1138

1139
    // Apply the calculated vertical spacing
1140
    ui->vertical_spacing->setValue(static_cast<double>(final_spacing));
×
1141

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

1148
        std::cout << "Calculated optimal event height: " << optimal_event_height << " normalized units" << std::endl;
×
1149

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

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

1168
        std::cout << "Calculated optimal interval height: " << optimal_interval_height << " normalized units" << std::endl;
×
1169

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

1181
    // Calculate optimal global scale for analog series to use their allocated space effectively
1182
    if (visible_analog_count > 0) {
×
1183
        // Sample a few analog series to estimate appropriate scaling
1184
        std::vector<float> sample_std_devs;
×
1185
        sample_std_devs.reserve(std::min(5, visible_analog_count));// Sample up to 5 series
×
1186

1187
        int sampled = 0;
×
1188
        for (auto const & key: analog_keys) {
×
1189
            if (sampled >= 5) break;
×
1190

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

1204
        if (!sample_std_devs.empty()) {
×
1205
            // Use median standard deviation for scaling calculation
1206
            std::sort(sample_std_devs.begin(), sample_std_devs.end());
×
1207
            float median_std_dev = sample_std_devs[sample_std_devs.size() / 2];
×
1208

1209
            // Calculate scale so that ±3 standard deviations use ~60% of allocated space
1210
            float const target_amplitude_fraction = 0.6f;
×
1211
            float const target_amplitude_pixels = optimal_spacing_pixels * target_amplitude_fraction;
×
1212
            float const target_amplitude_normalized = (target_amplitude_pixels / static_cast<float>(canvas_height)) * 2.0f;
×
1213

1214
            // For ±3σ coverage
1215
            float const three_sigma_coverage = target_amplitude_normalized;
×
1216
            float const optimal_global_scale = three_sigma_coverage / (6.0f * median_std_dev);
×
1217

1218
            // Clamp to reasonable bounds
1219
            float const min_scale = 0.01f;
×
1220
            float const max_scale = 100.0f;
×
1221
            float const final_scale = std::clamp(optimal_global_scale, min_scale, max_scale);
×
1222

1223
            std::cout << "Calculated optimal global scale: median_std_dev=" << median_std_dev
×
1224
                      << ", target_amplitude=" << target_amplitude_pixels << " pixels"
×
1225
                      << ", final_scale=" << final_scale << std::endl;
×
1226

1227
            // Apply the calculated global scale
1228
            ui->global_zoom->setValue(static_cast<double>(final_scale));
×
1229
        }
1230
    }
×
1231

1232
    std::cout << "Auto-fill canvas completed" << std::endl;
×
1233
}
240✔
1234

1235
void DataViewer_Widget::cleanupDeletedData() {
14✔
1236
    if (!_data_manager) {
14✔
NEW
1237
        return;
×
1238
    }
1239

1240
    // Collect keys that no longer exist in DataManager
1241
    std::vector<std::string> keys_to_cleanup;
14✔
1242

1243
    if (_plotting_manager) {
14✔
1244
        auto analog_keys = _plotting_manager->getVisibleAnalogSeriesKeys();
14✔
1245
        for (auto const & key : analog_keys) {
14✔
NEW
1246
            if (!_data_manager->getData<AnalogTimeSeries>(key)) {
×
NEW
1247
                keys_to_cleanup.push_back(key);
×
1248
            }
1249
        }
1250
        auto event_keys = _plotting_manager->getVisibleDigitalEventSeriesKeys();
14✔
1251
        for (auto const & key : event_keys) {
14✔
NEW
1252
            if (!_data_manager->getData<DigitalEventSeries>(key)) {
×
NEW
1253
                keys_to_cleanup.push_back(key);
×
1254
            }
1255
        }
1256
        auto interval_keys = _plotting_manager->getVisibleDigitalIntervalSeriesKeys();
14✔
1257
        for (auto const & key : interval_keys) {
14✔
NEW
1258
            if (!_data_manager->getData<DigitalIntervalSeries>(key)) {
×
NEW
1259
                keys_to_cleanup.push_back(key);
×
1260
            }
1261
        }
1262
    }
14✔
1263

1264
    if (keys_to_cleanup.empty()) {
14✔
1265
        return;
14✔
1266
    }
1267

1268
    // De-duplicate keys in case the same key appears in multiple lists
NEW
1269
    std::sort(keys_to_cleanup.begin(), keys_to_cleanup.end());
×
NEW
1270
    keys_to_cleanup.erase(std::unique(keys_to_cleanup.begin(), keys_to_cleanup.end()), keys_to_cleanup.end());
×
1271

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

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

1294
    // Re-arrange remaining data
NEW
1295
    autoArrangeVerticalSpacing();
×
1296
}
14✔
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