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

paulmthompson / WhiskerToolbox / 17468365695

04 Sep 2025 03:09PM UTC coverage: 70.849% (+0.02%) from 70.828%
17468365695

push

github

paulmthompson
faster loading for multi channel analog

0 of 3 new or added lines in 1 file covered. (0.0%)

770 existing lines in 6 files now uncovered.

34341 of 48471 relevant lines covered (70.85%)

1293.61 hits per line

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

48.18
/src/WhiskerToolbox/DataViewer/PlottingManager/PlottingManager.cpp
1
#include "PlottingManager.hpp"
2

3
#include <algorithm>
4
#include <cmath>
5
#include <iostream>
6
#include <sstream>
7
#include <iomanip>
8

9
void PlottingManager::calculateAnalogSeriesAllocation(int series_index,
21✔
10
                                                      float & allocated_center,
11
                                                      float & allocated_height) const {
12
    if (total_analog_series <= 0) {
21✔
13
        allocated_center = 0.0f;
1✔
14
        allocated_height = 2.0f;// Full canvas height
1✔
15
        return;
1✔
16
    }
17

18
    // Calculate allocated height for this series
19
    allocated_height = (viewport_y_max - viewport_y_min) / static_cast<float>(total_analog_series);
20✔
20

21
    // Calculate center Y coordinate
22
    // Series are stacked from top to bottom starting at viewport_y_min
23
    allocated_center = viewport_y_min + allocated_height * (static_cast<float>(series_index) + 0.5f);
20✔
24
}
25

26
void PlottingManager::setVisibleDataRange(int start_index, int end_index) {
33✔
27
    visible_start_index = start_index;
33✔
28
    visible_end_index = end_index;
33✔
29
    total_data_points = end_index - start_index;
33✔
30
}
33✔
31

32
int PlottingManager::addAnalogSeries() {
22✔
33
    int series_index = total_analog_series;
22✔
34
    total_analog_series++;
22✔
35
    return series_index;
22✔
36
}
37

38
int PlottingManager::addDigitalIntervalSeries() {
8✔
39
    int series_index = total_digital_series;
8✔
40
    total_digital_series++;
8✔
41
    return series_index;
8✔
42
}
43

44
int PlottingManager::addDigitalEventSeries() {
16✔
45
    int series_index = total_event_series;
16✔
46
    total_event_series++;
16✔
47
    return series_index;
16✔
48
}
49

50
int PlottingManager::addAnalogSeries(std::string const & key,
13✔
51
                                     std::shared_ptr<AnalogTimeSeries> series,
52
                                     std::shared_ptr<TimeFrame> time_frame,
53
                                     std::string const & color) {
54
    int series_index = total_analog_series;
13✔
55
    
56
    AnalogSeriesInfo info;
13✔
57
    info.series = series;
13✔
58
    info.time_frame = time_frame;
13✔
59
    info.key = key;
13✔
60
    info.color = color.empty() ? generateDefaultColor(series_index) : color;
13✔
61
    info.visible = true;
13✔
62
    // Extract group and channel id from key pattern name_idx
63
    _extractGroupAndChannel(key, info.group_name, info.channel_id);
13✔
64
    
65
    analog_series_map[key] = std::move(info);
13✔
66
    total_analog_series++;
13✔
67
    
68
    return series_index;
13✔
69
}
13✔
70

UNCOV
71
int PlottingManager::addDigitalEventSeries(std::string const & key,
×
72
                                           std::shared_ptr<DigitalEventSeries> series,
73
                                           std::shared_ptr<TimeFrame> time_frame,
74
                                           std::string const & color) {
75
    int series_index = total_event_series;
×
76
    
77
    DigitalEventSeriesInfo info;
×
78
    info.series = series;
×
79
    info.time_frame = time_frame;
×
80
    info.key = key;
×
UNCOV
81
    info.color = color.empty() ? generateDefaultColor(total_analog_series + series_index) : color;
×
82
    info.visible = true;
×
83
    
UNCOV
84
    digital_event_series_map[key] = std::move(info);
×
85
    total_event_series++;
×
86
    
UNCOV
87
    return series_index;
×
88
}
×
89

UNCOV
90
int PlottingManager::addDigitalIntervalSeries(std::string const & key,
×
91
                                              std::shared_ptr<DigitalIntervalSeries> series,
92
                                              std::shared_ptr<TimeFrame> time_frame,
93
                                              std::string const & color) {
94
    int series_index = total_digital_series;
×
95
    
96
    DigitalIntervalSeriesInfo info;
×
97
    info.series = series;
×
98
    info.time_frame = time_frame;
×
99
    info.key = key;
×
UNCOV
100
    info.color = color.empty() ? generateDefaultColor(total_analog_series + total_event_series + series_index) : color;
×
101
    info.visible = true;
×
102
    
UNCOV
103
    digital_interval_series_map[key] = std::move(info);
×
104
    total_digital_series++;
×
105
    
UNCOV
106
    return series_index;
×
107
}
×
108

109
bool PlottingManager::removeAnalogSeries(std::string const & key) {
×
110
    auto it = analog_series_map.find(key);
×
111
    if (it != analog_series_map.end()) {
×
112
        analog_series_map.erase(it);
×
UNCOV
113
        updateSeriesCounts();
×
114
        return true;
×
115
    }
UNCOV
116
    return false;
×
117
}
118

119
bool PlottingManager::removeDigitalEventSeries(std::string const & key) {
×
120
    auto it = digital_event_series_map.find(key);
×
121
    if (it != digital_event_series_map.end()) {
×
122
        digital_event_series_map.erase(it);
×
UNCOV
123
        updateSeriesCounts();
×
124
        return true;
×
125
    }
UNCOV
126
    return false;
×
127
}
128

129
bool PlottingManager::removeDigitalIntervalSeries(std::string const & key) {
×
130
    auto it = digital_interval_series_map.find(key);
×
131
    if (it != digital_interval_series_map.end()) {
×
132
        digital_interval_series_map.erase(it);
×
UNCOV
133
        updateSeriesCounts();
×
134
        return true;
×
135
    }
UNCOV
136
    return false;
×
137
}
138

139
void PlottingManager::clearAllSeries() {
×
140
    analog_series_map.clear();
×
141
    digital_event_series_map.clear();
×
142
    digital_interval_series_map.clear();
×
143
    total_analog_series = 0;
×
144
    total_event_series = 0;
×
UNCOV
145
    total_digital_series = 0;
×
146
}
×
147

148
PlottingManager::AnalogSeriesInfo * PlottingManager::getAnalogSeriesInfo(std::string const & key) {
×
UNCOV
149
    auto it = analog_series_map.find(key);
×
UNCOV
150
    return (it != analog_series_map.end()) ? &it->second : nullptr;
×
151
}
152

153
PlottingManager::DigitalEventSeriesInfo * PlottingManager::getDigitalEventSeriesInfo(std::string const & key) {
×
UNCOV
154
    auto it = digital_event_series_map.find(key);
×
UNCOV
155
    return (it != digital_event_series_map.end()) ? &it->second : nullptr;
×
156
}
157

158
PlottingManager::DigitalIntervalSeriesInfo * PlottingManager::getDigitalIntervalSeriesInfo(std::string const & key) {
×
UNCOV
159
    auto it = digital_interval_series_map.find(key);
×
UNCOV
160
    return (it != digital_interval_series_map.end()) ? &it->second : nullptr;
×
161
}
162

163
void PlottingManager::setSeriesVisibility(std::string const & key, bool visible) {
×
164
    // Check all series types and update visibility
165
    auto analog_it = analog_series_map.find(key);
×
166
    if (analog_it != analog_series_map.end()) {
×
UNCOV
167
        analog_it->second.visible = visible;
×
UNCOV
168
        return;
×
169
    }
170
    
171
    auto event_it = digital_event_series_map.find(key);
×
172
    if (event_it != digital_event_series_map.end()) {
×
UNCOV
173
        event_it->second.visible = visible;
×
UNCOV
174
        return;
×
175
    }
176
    
177
    auto interval_it = digital_interval_series_map.find(key);
×
178
    if (interval_it != digital_interval_series_map.end()) {
×
UNCOV
179
        interval_it->second.visible = visible;
×
UNCOV
180
        return;
×
181
    }
182
}
183

184
std::vector<std::string> PlottingManager::getVisibleAnalogSeriesKeys() const {
69✔
185
    std::vector<std::string> visible_keys;
69✔
186
    for (auto const & [key, info] : analog_series_map) {
205✔
187
        if (info.visible) {
136✔
188
            visible_keys.push_back(key);
136✔
189
        }
190
    }
191
    return visible_keys;
69✔
UNCOV
192
}
×
193

194
std::vector<std::string> PlottingManager::getVisibleDigitalEventSeriesKeys() const {
68✔
195
    std::vector<std::string> visible_keys;
68✔
196
    for (auto const & [key, info] : digital_event_series_map) {
68✔
UNCOV
197
        if (info.visible) {
×
UNCOV
198
            visible_keys.push_back(key);
×
199
        }
200
    }
201
    return visible_keys;
68✔
UNCOV
202
}
×
203

204
std::vector<std::string> PlottingManager::getVisibleDigitalIntervalSeriesKeys() const {
68✔
205
    std::vector<std::string> visible_keys;
68✔
206
    for (auto const & [key, info] : digital_interval_series_map) {
68✔
UNCOV
207
        if (info.visible) {
×
UNCOV
208
            visible_keys.push_back(key);
×
209
        }
210
    }
211
    return visible_keys;
68✔
UNCOV
212
}
×
213

214
void PlottingManager::setGlobalZoom(float zoom) {
14✔
215
    global_zoom = std::max(0.01f, zoom); // Prevent negative or zero zoom
14✔
216
}
14✔
217

UNCOV
218
float PlottingManager::getGlobalZoom() const {
×
UNCOV
219
    return global_zoom;
×
220
}
221

222
void PlottingManager::setGlobalVerticalScale(float scale) {
13✔
223
    global_vertical_scale = std::max(0.01f, scale); // Prevent negative or zero scale
13✔
224
}
13✔
225

UNCOV
226
float PlottingManager::getGlobalVerticalScale() const {
×
UNCOV
227
    return global_vertical_scale;
×
228
}
229

230
void PlottingManager::calculateDigitalIntervalSeriesAllocation(int series_index,
8✔
231
                                                               float & allocated_center,
232
                                                               float & allocated_height) const {
233

234
    static_cast<void>(series_index);
235

236
    // Digital intervals use the full canvas height by default
237
    allocated_center = (viewport_y_min + viewport_y_max) * 0.5f;// Center of viewport
8✔
238
    allocated_height = viewport_y_max - viewport_y_min;         // Full viewport height
8✔
239
}
8✔
240

241
void PlottingManager::calculateDigitalEventSeriesAllocation(int series_index,
12✔
242
                                                            float & allocated_center,
243
                                                            float & allocated_height) const {
244
    // For now, assume stacked mode allocation (like analog series)
245
    // This can be extended to support different plotting modes
246
    if (total_event_series <= 0) {
12✔
247
        allocated_center = 0.0f;
×
UNCOV
248
        allocated_height = 2.0f;// Full canvas height
×
UNCOV
249
        return;
×
250
    }
251

252
    // Calculate allocated height for this series
253
    allocated_height = (viewport_y_max - viewport_y_min) / static_cast<float>(total_event_series);
12✔
254

255
    // Calculate center Y coordinate
256
    // Series are stacked from top to bottom starting at viewport_y_min
257
    allocated_center = viewport_y_min + allocated_height * (static_cast<float>(series_index) + 0.5f);
12✔
258
}
259

260
void PlottingManager::calculateGlobalStackedAllocation(int analog_series_index,
4✔
261
                                                       int event_series_index,
262
                                                       int total_stackable_series,
263
                                                       float & allocated_center,
264
                                                       float & allocated_height) const {
265
    if (total_stackable_series <= 0) {
4✔
266
        allocated_center = 0.0f;
×
UNCOV
267
        allocated_height = 2.0f;// Full canvas height
×
UNCOV
268
        return;
×
269
    }
270

271
    // Calculate allocated height for each series (equal division)
272
    allocated_height = (viewport_y_max - viewport_y_min) / static_cast<float>(total_stackable_series);
4✔
273

274
    // Determine the global index for this series
275
    int global_series_index;
276
    if (analog_series_index >= 0) {
4✔
277
        // This is an analog series
278
        global_series_index = analog_series_index;
2✔
279
    } else {
280
        // This is a digital event series, comes after all analog series
281
        global_series_index = total_analog_series + event_series_index;
2✔
282
    }
283

284
    // Calculate center Y coordinate based on global stacking order
285
    allocated_center = viewport_y_min + allocated_height * (static_cast<float>(global_series_index) + 0.5f);
4✔
286
}
287

288
void PlottingManager::setPanOffset(float pan_offset) {
57✔
289
    vertical_pan_offset = pan_offset;
57✔
290
}
57✔
291

292
void PlottingManager::applyPanDelta(float pan_delta) {
4✔
293
    vertical_pan_offset += pan_delta;
4✔
294
}
4✔
295

296
float PlottingManager::getPanOffset() const {
13✔
297
    return vertical_pan_offset;
13✔
298
}
299

300
void PlottingManager::resetPan() {
8✔
301
    vertical_pan_offset = 0.0f;
8✔
302
}
8✔
303

304
void PlottingManager::updateSeriesCounts() {
×
305
    // Count only visible series
306
    total_analog_series = static_cast<int>(std::count_if(
×
307
        analog_series_map.begin(), analog_series_map.end(),
308
        [](auto const & pair) { return pair.second.visible; }));
×
309
    
310
    total_event_series = static_cast<int>(std::count_if(
×
311
        digital_event_series_map.begin(), digital_event_series_map.end(),
312
        [](auto const & pair) { return pair.second.visible; }));
×
313
    
314
    total_digital_series = static_cast<int>(std::count_if(
×
315
        digital_interval_series_map.begin(), digital_interval_series_map.end(),
UNCOV
316
        [](auto const & pair) { return pair.second.visible; }));
×
317
}
×
318

319
bool PlottingManager::_extractGroupAndChannel(std::string const & key, std::string & group, int & channel_id) {
13✔
320
    channel_id = -1;
13✔
321
    group.clear();
13✔
322
    auto const pos = key.rfind('_');
13✔
323
    if (pos == std::string::npos || pos + 1 >= key.size()) {
13✔
324
        return false;
×
325
    }
326
    group = key.substr(0, pos);
13✔
327
    try {
328
        int const parsed = std::stoi(key.substr(pos + 1));
13✔
329
        channel_id = parsed > 0 ? parsed - 1 : parsed;
13✔
UNCOV
330
    } catch (...) {
×
UNCOV
331
        channel_id = -1;
×
332
        return false;
×
333
    }
×
334
    return true;
13✔
335
}
336

337
void PlottingManager::loadAnalogSpikeSorterConfiguration(std::string const & group_name,
1✔
338
                                                        std::vector<AnalogGroupChannelPosition> const & positions) {
339
    _analog_group_configs[group_name] = positions;
1✔
340
}
1✔
341

UNCOV
342
void PlottingManager::clearAnalogGroupConfiguration(std::string const & group_name) {
×
343
    _analog_group_configs.erase(group_name);
×
UNCOV
344
}
×
345

346
std::vector<std::string> PlottingManager::_orderedVisibleAnalogKeysByConfig() const {
152✔
347
    // Group visible analog series by group_name
348
    struct Item { std::string key; std::string group; int channel; };
349
    std::vector<Item> items;
152✔
350
    items.reserve(analog_series_map.size());
152✔
351
    for (auto const & [key, info] : analog_series_map) {
619✔
352
        if (!info.visible) continue;
467✔
353
        Item it{key, info.group_name, info.channel_id};
467✔
354
        items.push_back(std::move(it));
467✔
355
    }
467✔
356

357
    // Sort with configuration: by group; within group, if config present, by descending y; else by channel id
358
    std::stable_sort(items.begin(), items.end(), [&](Item const & a, Item const & b) {
152✔
359
        if (a.group != b.group) return a.group < b.group;
454✔
360
        auto cfg_it = _analog_group_configs.find(a.group);
454✔
361
        if (cfg_it == _analog_group_configs.end()) {
454✔
362
            return a.channel < b.channel;
406✔
363
        }
364
        auto const & cfg = cfg_it->second;
48✔
365
        auto find_y = [&](int ch) {
48✔
366
            for (auto const & p : cfg) if (p.channel_id == ch) return p.y;
216✔
UNCOV
367
            return 0.0f; };
×
368
        float ya = find_y(a.channel);
48✔
369
        float yb = find_y(b.channel);
48✔
370
        if (ya == yb) return a.channel < b.channel;
48✔
371
        return ya < yb; // ascending by y so larger y get larger index (top)
48✔
372
    });
373

374
    std::vector<std::string> keys;
152✔
375
    keys.reserve(items.size());
152✔
376
    for (auto const & it : items) keys.push_back(it.key);
619✔
377
    return keys;
152✔
378
}
152✔
379

380
bool PlottingManager::getAnalogSeriesAllocationForKey(std::string const & key,
152✔
381
                                                     float & allocated_center,
382
                                                     float & allocated_height) const {
383
    // Build ordered visible list considering configuration
384
    auto ordered = _orderedVisibleAnalogKeysByConfig();
152✔
385
    if (ordered.empty()) {
152✔
UNCOV
386
        allocated_center = 0.0f;
×
UNCOV
387
        allocated_height = viewport_y_max - viewport_y_min;
×
UNCOV
388
        return false;
×
389
    }
390
    // Find index of key among visible
391
    int index = -1;
152✔
392
    for (size_t i = 0; i < ordered.size(); ++i) {
319✔
393
        if (ordered[i] == key) { index = static_cast<int>(i); break; }
319✔
394
    }
395
    if (index < 0) {
152✔
UNCOV
396
        return false;
×
397
    }
398
    // Use same formula as calculateAnalogSeriesAllocation but with index in ordered list and count = ordered.size()
399
    float const total = static_cast<float>(ordered.size());
152✔
400
    allocated_height = (viewport_y_max - viewport_y_min) / total;
152✔
401
    allocated_center = viewport_y_min + allocated_height * (static_cast<float>(index) + 0.5f);
152✔
402
    return true;
152✔
403
}
152✔
404

UNCOV
405
std::string PlottingManager::generateDefaultColor(int series_index) const {
×
406
    // Generate a nice color palette for series
407
    // Use HSV color space with fixed saturation and value, varying hue
UNCOV
408
    constexpr int num_colors = 12;
×
UNCOV
409
    float hue = (static_cast<float>(series_index % num_colors) / static_cast<float>(num_colors)) * 360.0f;
×
410
    
411
    // Convert HSV to RGB
UNCOV
412
    constexpr float saturation = 0.8f;
×
UNCOV
413
    constexpr float value = 0.9f;
×
414
    
UNCOV
415
    float c = value * saturation;
×
UNCOV
416
    float x = c * (1.0f - std::abs(std::fmod(hue / 60.0f, 2.0f) - 1.0f));
×
UNCOV
417
    float m = value - c;
×
418
    
419
    float r, g, b;
UNCOV
420
    if (hue >= 0 && hue < 60) {
×
UNCOV
421
        r = c; g = x; b = 0;
×
UNCOV
422
    } else if (hue >= 60 && hue < 120) {
×
UNCOV
423
        r = x; g = c; b = 0;
×
UNCOV
424
    } else if (hue >= 120 && hue < 180) {
×
UNCOV
425
        r = 0; g = c; b = x;
×
UNCOV
426
    } else if (hue >= 180 && hue < 240) {
×
UNCOV
427
        r = 0; g = x; b = c;
×
UNCOV
428
    } else if (hue >= 240 && hue < 300) {
×
UNCOV
429
        r = x; g = 0; b = c;
×
430
    } else {
UNCOV
431
        r = c; g = 0; b = x;
×
432
    }
433
    
434
    // Convert to 0-255 range and create hex string
UNCOV
435
    int red = static_cast<int>((r + m) * 255);
×
UNCOV
436
    int green = static_cast<int>((g + m) * 255);
×
UNCOV
437
    int blue = static_cast<int>((b + m) * 255);
×
438
    
UNCOV
439
    std::ostringstream ss;
×
UNCOV
440
    ss << "#" << std::hex << std::setfill('0') 
×
UNCOV
441
       << std::setw(2) << red 
×
UNCOV
442
       << std::setw(2) << green 
×
UNCOV
443
       << std::setw(2) << blue;
×
444
    
UNCOV
445
    return ss.str();
×
UNCOV
446
}
×
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