INSTINCT Code Coverage Report


Directory: src/
File: util/Plot/Colormap.cpp
Date: 2025-02-07 16:54:41
Exec Total Coverage
Lines: 17 179 9.5%
Functions: 5 16 31.2%
Branches: 12 276 4.3%

Line Branch Exec Source
1 // This file is part of INSTINCT, the INS Toolkit for Integrated
2 // Navigation Concepts and Training by the Institute of Navigation of
3 // the University of Stuttgart, Germany.
4 //
5 // This Source Code Form is subject to the terms of the Mozilla Public
6 // License, v. 2.0. If a copy of the MPL was not distributed with this
7 // file, You can obtain one at https://mozilla.org/MPL/2.0/.
8
9 /// @file Colormap.cpp
10 /// @brief Colormap
11 /// @author T. Topp (topp@ins.uni-stuttgart.de)
12 /// @date 2023-09-22
13
14 #include "Colormap.hpp"
15
16 #include <algorithm>
17 #include <chrono>
18
19 #include "internal/gui/widgets/imgui_ex.hpp"
20 #include "util/Logger.hpp"
21 #include "util/ImGui.hpp"
22
23 namespace NAV
24 {
25
26 std::vector<Colormap> ColormapsGlobal;
27 std::vector<Colormap> ColormapsFlow;
28
29
2/4
✓ Branch 1 taken 1 times.
✗ Branch 2 not taken.
✓ Branch 4 taken 1 times.
✗ Branch 5 not taken.
4 Colormap::Colormap()
30 {
31
1/2
✓ Branch 1 taken 1 times.
✗ Branch 2 not taken.
1 id = std::chrono::duration_cast<std::chrono::microseconds>(
32 1 std::chrono::system_clock::now().time_since_epoch())
33 1 .count();
34 1 }
35
36 3 void Colormap::addColor(double value, ImColor color)
37 {
38
2/4
✓ Branch 3 taken 3 times.
✗ Branch 4 not taken.
✓ Branch 7 taken 3 times.
✗ Branch 8 not taken.
3 colormap.insert(std::upper_bound(colormap.begin(), colormap.end(), value, // NOLINT(boost-use-ranges,modernize-use-ranges) // ranges::upper_bound with lambda is not supported yet
39 4 [](double value, const std::pair<double, ImColor>& item) { return value < item.first; }),
40 3 std::make_pair(value, color));
41 3 version++;
42 3 }
43
44 5 void Colormap::removeColor(size_t idx)
45 {
46
6/6
✓ Branch 1 taken 4 times.
✓ Branch 2 taken 1 times.
✓ Branch 4 taken 1 times.
✓ Branch 5 taken 3 times.
✓ Branch 6 taken 2 times.
✓ Branch 7 taken 3 times.
5 if (idx >= colormap.size() || colormap.size() == 1) { return; }
47
1/2
✓ Branch 4 taken 3 times.
✗ Branch 5 not taken.
3 colormap.erase(colormap.begin() + static_cast<std::ptrdiff_t>(idx));
48 3 version++;
49 }
50
51 ImColor Colormap::getColor(double value, const ImColor& defaultColor) const
52 {
53 for (size_t i = colormap.size() - 1;; i--)
54 {
55 const auto& item = colormap.at(i);
56 if (value >= item.first)
57 {
58 if (discrete || i == colormap.size() - 1) { return item.second; }
59
60 const auto& other = colormap.at(i + 1);
61
62 double t = (value - item.first) / (other.first - item.first);
63
64 return item.second.Value + static_cast<float>(t) * (other.second.Value - item.second.Value);
65 }
66
67 if (i == 0) { break; }
68 }
69
70 return defaultColor;
71 }
72
73 int64_t Colormap::getId() const
74 {
75 return id;
76 }
77
78 18 const std::vector<std::pair<double, ImColor>>& Colormap::getColormap() const
79 {
80 18 return colormap;
81 }
82
83 void Colormap::render() const
84 {
85 const ImVec2 pos = ImGui::GetCursorScreenPos();
86 const float w = ImGui::CalcItemWidth();
87 const float h = ImGui::GetFrameHeight();
88 const ImRect bounds = ImRect(pos.x, pos.y, pos.x + w, pos.y + h);
89
90 render(bounds);
91 }
92
93 void Colormap::render(const ImRect& bounds) const
94 {
95 if (auto* drawList = ImGui::GetWindowDrawList())
96 {
97 if (colormap.empty())
98 {
99 drawList->AddRectFilled(bounds.Min, bounds.Max, IM_COL32(25, 25, 25, 255));
100 return;
101 }
102
103 const size_t n = colormap.size() >= 2 ? (discrete ? colormap.size() : colormap.size() - 1) : colormap.size();
104
105 const float step = bounds.GetWidth() / static_cast<float>(n);
106 ImRect rect(bounds.Min.x, bounds.Min.y, bounds.Min.x + step, bounds.Max.y);
107 for (size_t i = 0; i < n; ++i)
108 {
109 ImU32 col1 = colormap.at(i).second;
110 ImU32 col2 = discrete || colormap.size() == 1 ? col1 : ImU32(colormap.at(i + 1).second);
111
112 drawList->AddRectFilledMultiColor(rect.Min, rect.Max, col1, col2, col2, col1);
113 rect.TranslateX(step);
114 }
115 }
116 }
117
118 bool ColormapButton(const char* label, Colormap& cmap, const ImVec2& size_arg)
119 {
120 ImGuiContext& G = *GImGui;
121 const ImGuiStyle& style = G.Style;
122 ImGuiWindow* Window = G.CurrentWindow;
123 if (Window->SkipItems) { return false; }
124
125 const ImVec2 pos = ImGui::GetCurrentWindow()->DC.CursorPos;
126 const ImVec2 label_size = ImGui::CalcTextSize(label, nullptr, true);
127 ImVec2 size = ImGui::CalcItemSize(size_arg, label_size.x + style.FramePadding.x * 2.0F, label_size.y + style.FramePadding.y * 2.0F);
128 const ImRect rect = ImRect(pos.x, pos.y, pos.x + size.x, pos.y + size.y);
129 cmap.render(rect);
130 ImGui::PushStyleColor(ImGuiCol_Button, IM_COL32_BLACK_TRANS);
131 ImGui::PushStyleColor(ImGuiCol_ButtonHovered, ImVec4(1, 1, 1, 0.1F));
132 ImGui::PushStyleColor(ImGuiCol_ButtonActive, ImVec4(1, 1, 1, 0.2F));
133 ImGui::PushStyleVar(ImGuiStyleVar_FrameRounding, 0);
134 const bool pressed = ImGui::Button(label, size);
135 ImGui::PopStyleColor(3);
136 ImGui::PopStyleVar(1);
137
138 bool changed = false;
139
140 if (pressed) { ImGui::OpenPopup(fmt::format("Colormap##{}", label).c_str()); }
141 if (ImGui::BeginPopup(fmt::format("Colormap##{}", label).c_str()))
142 {
143 if (ImGui::BeginTable(fmt::format("##{} colormap table", label).c_str(), 5, ImGuiTableFlags_SizingFixedFit, ImVec2(0.0F, 0.0F)))
144 {
145 ImGui::TableSetupColumn("Value");
146 ImGui::TableSetupColumn("Color");
147 ImGui::TableSetupColumn("");
148 ImGui::TableSetupColumn("");
149 ImGui::TableSetupColumn("");
150
151 int colormapRemovalIdx = -1;
152 int insertIdx = -1;
153 ImGui::TableHeadersRow();
154 for (size_t i = 0; i < cmap.colormap.size(); i++)
155 {
156 auto& [value, color] = cmap.colormap.at(i);
157 ImGui::TableNextColumn();
158 ImGui::SetNextItemWidth(100.0F);
159 if (ImGui::InputDoubleL(fmt::format("##{} colormap value {}", label, i).c_str(), &value,
160 i != 0 ? cmap.colormap.at(i - 1).first : std::numeric_limits<double>::lowest(),
161 i != cmap.colormap.size() - 1 ? cmap.colormap.at(i + 1).first : std::numeric_limits<double>::max(),
162 0.0, 0.0, "%.6g", ImGuiInputTextFlags_CharsScientific))
163 {
164 cmap.version++;
165 changed = true;
166 }
167
168 ImGui::TableNextColumn();
169 ImGui::SetNextItemWidth(300.0F);
170 if (ImGui::ColorEdit4(fmt::format("##{} colormap color {}", label, i).c_str(), &color.Value.x))
171 {
172 cmap.version++;
173 changed = true;
174 }
175
176 ImGui::TableNextColumn();
177 if (ImGui::Button(fmt::format("⌃##{} insert colormap above {}", label, i).c_str())) { insertIdx = static_cast<int>(i); }
178 if (ImGui::IsItemHovered()) { ImGui::SetTooltip("Insert entry above?"); }
179
180 ImGui::TableNextColumn();
181 if (ImGui::Button(fmt::format("⌄##{} insert colormap below {}", label, i).c_str())) { insertIdx = static_cast<int>(i) + 1; }
182 if (ImGui::IsItemHovered()) { ImGui::SetTooltip("Insert entry below?"); }
183
184 ImGui::TableNextColumn();
185 if (cmap.colormap.size() > 1)
186 {
187 if (ImGui::Button(fmt::format("X##{} remove colormap {}", label, i).c_str())) { colormapRemovalIdx = static_cast<int>(i); }
188 if (ImGui::IsItemHovered()) { ImGui::SetTooltip("Remove?"); }
189 }
190 }
191 if (colormapRemovalIdx >= 0)
192 {
193 cmap.colormap.erase(cmap.colormap.begin() + static_cast<std::ptrdiff_t>(colormapRemovalIdx));
194 cmap.version++;
195 changed = true;
196 }
197 if (insertIdx >= 0)
198 {
199 cmap.colormap.insert(cmap.colormap.begin() + static_cast<std::ptrdiff_t>(insertIdx),
200 std::make_pair(cmap.colormap.at(static_cast<size_t>(insertIdx != static_cast<int>(cmap.colormap.size()) ? insertIdx : insertIdx - 1)).first,
201 ImColor(1.0F, 1.0F, 1.0F, 1.0F)));
202 cmap.version++;
203 changed = true;
204 }
205
206 ImGui::EndTable();
207 }
208
209 ImGui::EndPopup();
210 }
211
212 return changed;
213 }
214
215 void to_json(json& j, const Colormap& cmap)
216 {
217 j = json{
218 { "name", cmap.name },
219 { "discrete", cmap.discrete },
220 { "id", cmap.id },
221 { "colormap", cmap.colormap },
222 };
223 }
224
225 void from_json(const json& j, Colormap& cmap)
226 {
227 if (j.contains("name"))
228 {
229 j.at("name").get_to(cmap.name);
230 }
231 if (j.contains("discrete"))
232 {
233 j.at("discrete").get_to(cmap.discrete);
234 }
235 if (j.contains("id"))
236 {
237 j.at("id").get_to(cmap.id);
238 }
239 if (j.contains("colormap"))
240 {
241 j.at("colormap").get_to(cmap.colormap);
242 }
243 }
244
245 bool ShowColormapSelector(ColormapMaskType& type, int64_t& id, const char* label)
246 {
247 bool changes = false;
248
249 auto activeColormap = ColormapSearch(type, id);
250
251 std::string colormapName = activeColormap ? activeColormap->get().name : "";
252 if (ImGui::BeginCombo(fmt::format("{}Colormap Mask", label).c_str(), colormapName.c_str()))
253 {
254 if (ImGui::Selectable("##Empty colormap mask", colormapName.empty(), 0))
255 {
256 if (id != -1 || type != ColormapMaskType::None)
257 {
258 id = -1;
259 type = ColormapMaskType::None;
260 changes = true;
261 }
262 }
263 // Set the initial focus when opening the combo (scrolling + keyboard navigation focus)
264 if (colormapName.empty()) { ImGui::SetItemDefaultFocus(); }
265
266 for (const auto& cmap : ColormapsGlobal)
267 {
268 const bool is_selected = (cmap.getId() == id);
269 if (ImGui::Selectable(fmt::format("G: {}##Global colormap", cmap.name).c_str(), is_selected, 0))
270 {
271 if (id != cmap.getId() || type != ColormapMaskType::Global)
272 {
273 id = cmap.getId();
274 type = ColormapMaskType::Global;
275 changes = true;
276 }
277 }
278 // Set the initial focus when opening the combo (scrolling + keyboard navigation focus)
279 if (is_selected) { ImGui::SetItemDefaultFocus(); }
280 }
281 for (const auto& cmap : ColormapsFlow)
282 {
283 const bool is_selected = (cmap.getId() == id);
284 if (ImGui::Selectable(fmt::format("F: {}##Flow colormap", cmap.name).c_str(), is_selected, 0))
285 {
286 if (id != cmap.getId() || type != ColormapMaskType::Flow)
287 {
288 id = cmap.getId();
289 type = ColormapMaskType::Flow;
290 changes = true;
291 }
292 }
293 // Set the initial focus when opening the combo (scrolling + keyboard navigation focus)
294 if (is_selected) { ImGui::SetItemDefaultFocus(); }
295 }
296 ImGui::EndCombo();
297 }
298 return changes;
299 }
300
301 std::optional<std::reference_wrapper<const Colormap>> ColormapSearch(const ColormapMaskType& type, const int64_t& id)
302 {
303 switch (type)
304 {
305 case ColormapMaskType::None:
306 break;
307 case ColormapMaskType::Global:
308 if (auto iter = std::ranges::find_if(ColormapsGlobal, [&id](const Colormap& cmap) { return id == cmap.getId(); });
309 iter != ColormapsGlobal.end())
310 {
311 return std::cref(*iter);
312 }
313 break;
314 case ColormapMaskType::Flow:
315 if (auto iter = std::ranges::find_if(ColormapsFlow, [&id](const Colormap& cmap) { return id == cmap.getId(); });
316 iter != ColormapsFlow.end())
317 {
318 return std::cref(*iter);
319 }
320 break;
321 }
322 return {};
323 }
324
325 } // namespace NAV
326