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

paulmthompson / WhiskerToolbox / 17451722900

04 Sep 2025 02:43AM UTC coverage: 70.97% (+0.7%) from 70.31%
17451722900

push

github

paulmthompson
fix bug where dataviewer jumps to different position when gain or color is adjusted

43 of 45 new or added lines in 3 files covered. (95.56%)

1 existing line in 1 file now uncovered.

34135 of 48098 relevant lines covered (70.97%)

1302.39 hits per line

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

44.88
/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,
13✔
36
                                     TimeScrollBar * time_scrollbar,
37
                                     MainWindow * main_window,
38
                                     QWidget * parent)
13✔
39
    : QWidget(parent),
40
      _data_manager{std::move(data_manager)},
13✔
41
      _time_scrollbar{time_scrollbar},
13✔
42
      _main_window{main_window},
13✔
43
      ui(new Ui::DataViewer_Widget) {
26✔
44

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

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

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

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

57
    // Set up observer to automatically clean up data when it's deleted from DataManager
58
    _data_manager->addObserver([this]() {
13✔
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});
39✔
64
    ui->feature_tree_widget->setDataManager(_data_manager);
13✔
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) {
13✔
68
        _handleFeatureSelected(QString::fromStdString(feature));
×
69
    });
×
70
    
71
    connect(ui->feature_tree_widget, &Feature_Tree_Widget::addFeature, this, [this](std::string const & feature) {
13✔
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) {
13✔
77
        std::cout << "Removing single feature: " << feature << std::endl;
50✔
78
        _addFeatureToModel(QString::fromStdString(feature), false);
50✔
79
    });
50✔
80

81
    connect(ui->feature_tree_widget, &Feature_Tree_Widget::addFeatures, this, [this](std::vector<std::string> const & features) {
13✔
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;
×
NEW
98
            ui->openGLWidget->updateCanvas();
×
99
        }
100
    });
×
101

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

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

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

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

123
    // Connect color change signals from the model
124
    connect(_feature_tree_model.get(), &Feature_Tree_Model::featureColorChanged, this, &DataViewer_Widget::_handleColorChanged);
13✔
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) {
13✔
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);
13✔
134

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

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

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

141
    //We should alwasy get the master clock because we plot
142
    // Check for master clock
143
    auto time_keys = _data_manager->getTimeFrameKeys();
13✔
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()) {
13✔
146
        std::cout << "No master clock found in DataManager" << std::endl;
13✔
147
        _time_frame = _data_manager->getTime(TimeKey("time"));
13✔
148
    } else {
149
        _time_frame = _data_manager->getTime(TimeKey("master"));
×
150
    }
151

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

155
    // Set the master time frame for proper coordinate conversion
156
    ui->openGLWidget->setMasterTimeFrame(_time_frame);
13✔
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());
13✔
160
    std::cout << "Setting x_axis_samples maximum to " << data_range << std::endl;
13✔
161
    ui->x_axis_samples->setMaximum(data_range);
13✔
162

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

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

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

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

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

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

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

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

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

202
DataViewer_Widget::~DataViewer_Widget() {
25✔
203
    delete ui;
13✔
204
}
25✔
205

206
void DataViewer_Widget::openWidget() {
11✔
207
    std::cout << "DataViewer Widget Opened" << std::endl;
11✔
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();
11✔
212

213
    this->show();
11✔
214
    _updateLabels();
11✔
215
}
11✔
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) {
11✔
224
    QWidget::resizeEvent(event);
11✔
225

226
    // Update plotting manager dimensions when widget is resized
227
    _updatePlottingManagerDimensions();
11✔
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) {
11✔
232
        ui->openGLWidget->update();
11✔
233
    }
234
}
11✔
235

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

242
    _updateLabels();
1✔
243
}
1✔
244

245

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

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

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

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

264
    if (!_data_manager) {
6✔
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);
6✔
271
    std::cout << "Using color: " << color << " for series: " << key << std::endl;
6✔
272

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

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

281
    if (data_type == DM_DataType::Analog) {
6✔
282

283
        std::cout << "Adding << " << key << " to PlottingManager and OpenGLWidget" << std::endl;
6✔
284
        auto series = _data_manager->getData<AnalogTimeSeries>(key);
6✔
285
        if (!series) {
6✔
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);
6✔
292
        std::cout << "Time frame key: " << time_key << std::endl;
6✔
293
        auto time_frame = _data_manager->getTime(time_key);
6✔
294
        if (!time_frame) {
6✔
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;
6✔
300

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

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

307
    } else if (data_type == DM_DataType::DigitalEvent) {
6✔
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) {
6✔
356
        _applyPlottingManagerAllocation(key);
6✔
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;
6✔
361
    autoArrangeVerticalSpacing();// This now includes auto-fill functionality
6✔
362

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

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

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

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

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

385
    // Remove from plotting manager first
386
    if (_plotting_manager) {
50✔
387
        bool removed = false;
50✔
388
        if (data_type == DM_DataType::Analog) {
50✔
389
            removed = _plotting_manager->removeAnalogSeries(key);
18✔
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) {
50✔
396
            std::cout << "Unregistered '" << key << "' from plotting manager" << std::endl;
×
397
        }
398
    }
399

400
    if (data_type == DM_DataType::Analog) {
50✔
401
        ui->openGLWidget->removeAnalogTimeSeries(key);
18✔
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;
50✔
413
    autoArrangeVerticalSpacing();// This now includes auto-fill functionality
50✔
414

415
    std::cout << "Series removal and auto-arrangement completed" << std::endl;
50✔
416
    // Trigger canvas update to reflect the removal
417
    std::cout << "Triggering canvas update after removal" << std::endl;
50✔
418
    ui->openGLWidget->updateCanvas();
50✔
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) {
14✔
482
    // Use setRangeWidth for spinbox changes (absolute value)
483
    std::cout << "Spinbox requested range width: " << value << std::endl;
14✔
484
    int64_t const actual_range = ui->openGLWidget->setRangeWidth(static_cast<int64_t>(value));
14✔
485
    std::cout << "Actual range width achieved: " << actual_range << std::endl;
14✔
486

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

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

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

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

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

521
        // Trigger canvas update
522
        ui->openGLWidget->updateCanvas();
7✔
523
    }
7✔
524
}
7✔
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() {
12✔
568
    auto x_axis = ui->openGLWidget->getXAxis();
12✔
569
    ui->neg_x_label->setText(QString::number(x_axis.getStart()));
12✔
570
    ui->pos_x_label->setText(QString::number(x_axis.getEnd()));
12✔
571
}
12✔
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
NEW
598
    ui->openGLWidget->updateCanvas();
×
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 {
20✔
632
    return ui->openGLWidget->getAnalogConfig(key);
20✔
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) {
6✔
666
    ui->openGLWidget->setVerticalSpacing(static_cast<float>(spacing));
6✔
667

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

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

688
        // Trigger canvas update
689
        ui->openGLWidget->updateCanvas();
6✔
690
    }
6✔
691
}
6✔
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) {
60✔
771
    std::cout << "Attempting to remove feature (batch): " << key << std::endl;
60✔
772

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

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

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

785
    if (data_type == DM_DataType::Analog) {
60✔
786
        ui->openGLWidget->removeAnalogTimeSeries(key);
28✔
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;
60✔
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() {
108✔
981
    std::cout << "DataViewer_Widget: Auto-arranging with plotting manager..." << std::endl;
108✔
982

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

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

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

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

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

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

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

1014
void DataViewer_Widget::_updateViewBounds() {
108✔
1015
    if (!_plotting_manager) {
108✔
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;
108✔
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() {
119✔
1042
    if (!_plotting_manager) {
119✔
1043
        return;
×
1044
    }
1045

1046
    // Get current canvas dimensions from OpenGL widget
1047
    auto [canvas_width, canvas_height] = ui->openGLWidget->getCanvasSize();
119✔
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: "
119✔
1053
              << canvas_width << "x" << canvas_height << " pixels" << std::endl;
119✔
1054
}
1055

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

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

1063
    std::cout << "DataViewer_Widget: Applying plotting manager allocation for '" << series_key << "'" << std::endl;
55✔
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) {
55✔
1070
        auto config = ui->openGLWidget->getAnalogConfig(series_key);
55✔
1071
        if (config.has_value()) {
55✔
1072
            // Basic allocation - will be properly implemented when OpenGL widget is updated
1073
            std::cout << "  Applied basic allocation to analog '" << series_key << "'" << std::endl;
55✔
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() {
108✔
1093
    std::cout << "DataViewer_Widget: Auto-filling canvas with PlottingManager..." << std::endl;
108✔
1094

1095
    if (!_plotting_manager) {
108✔
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();
108✔
1102
    std::cout << "Canvas size: " << canvas_width << "x" << canvas_height << " pixels" << std::endl;
108✔
1103

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

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

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

1118
    if (total_visible == 0) {
108✔
1119
        std::cout << "No visible series to auto-scale" << std::endl;
102✔
1120
        return;
102✔
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;
6✔
1126
    float const optimal_spacing_pixels = usable_height / static_cast<float>(total_visible);
6✔
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;
6✔
1130

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

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

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

1142
    // Calculate and apply optimal event heights for digital event series
1143
    if (visible_event_count > 0) {
6✔
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) {
6✔
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) {
6✔
1183
        // Sample a few analog series to estimate appropriate scaling
1184
        std::vector<float> sample_std_devs;
6✔
1185
        sample_std_devs.reserve(std::min(5, visible_analog_count));// Sample up to 5 series
6✔
1186

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

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

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

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

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

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

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

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

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

1235
void DataViewer_Widget::cleanupDeletedData() {
14✔
1236
    if (!_data_manager) {
14✔
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✔
1246
            if (!_data_manager->getData<AnalogTimeSeries>(key)) {
×
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✔
1252
            if (!_data_manager->getData<DigitalEventSeries>(key)) {
×
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✔
1258
            if (!_data_manager->getData<DigitalIntervalSeries>(key)) {
×
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
1269
    std::sort(keys_to_cleanup.begin(), keys_to_cleanup.end());
×
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
1273
    QPointer<OpenGLWidget> glw = ui ? ui->openGLWidget : nullptr;
×
1274
    if (glw) {
×
1275
        QMetaObject::invokeMethod(glw, [glw, keys = keys_to_cleanup]() {
×
1276
            if (!glw) return;
×
1277
            for (auto const & key : keys) {
×
1278
                glw->removeAnalogTimeSeries(key);
×
1279
                glw->removeDigitalEventSeries(key);
×
1280
                glw->removeDigitalIntervalSeries(key);
×
1281
            }
1282
        }, Qt::QueuedConnection);
1283
    }
1284

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

1294
    // Re-arrange remaining data
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