MayaFlux 0.4.0
Digital-First Multimedia Processing Framework
Loading...
Searching...
No Matches
PlotSpec.cpp
Go to the documentation of this file.
1#include "PlotSpec.hpp"
2
5
7
8// =============================================================================
9// series_by_role
10// =============================================================================
11
12std::vector<std::span<const double>> series_by_role(
13 const Kakshya::PlotContainer& container,
15{
16 const auto& dims = container.get_structure().dimensions;
17 const auto& data = container.get_processed_data();
18
19 std::vector<std::span<const double>> result;
20 for (size_t i = 0; i < dims.size() && i < data.size(); ++i) {
21 if (dims[i].role != role)
22 continue;
23 const auto* vec = std::get_if<std::vector<double>>(&data[i]);
24 if (!vec || vec->empty())
25 continue;
26 result.emplace_back(vec->data(), vec->size());
27 }
28 return result;
29}
30
31// =============================================================================
32// data_range / apply_auto_scale
33// =============================================================================
34
35std::pair<float, float> data_range(std::span<const double> series)
36{
37 if (series.empty())
38 return { 0.F, 1.F };
39
40 double lo = std::numeric_limits<double>::max();
41 double hi = std::numeric_limits<double>::lowest();
42 for (double v : series) {
43 if (v < lo)
44 lo = v;
45 if (v > hi)
46 hi = v;
47 }
48 if (lo == hi)
49 hi = lo + 1.0;
50 return { static_cast<float>(lo), static_cast<float>(hi) };
51}
52
54 const std::vector<std::span<const double>>& series)
55{
56 if (!range.auto_scaling || series.empty())
57 return;
58 if (range.scale_predicate && !range.scale_predicate())
59 return;
60
61 float lo = std::numeric_limits<float>::max();
62 float hi = std::numeric_limits<float>::lowest();
63 for (const auto& s : series) {
64 auto [slo, shi] = data_range(s);
65 lo = std::min(lo, slo);
66 hi = std::max(hi, shi);
67 }
68 range.min = lo;
69 range.max = hi;
70}
71
72// =============================================================================
73// palette_color
74// =============================================================================
75
76glm::vec3 palette_color(const std::vector<glm::vec3>& palette,
77 size_t index) noexcept
78{
79 if (palette.empty())
80 return glm::vec3(1.F);
81 return palette[index % palette.size()];
82}
83
84// =============================================================================
85// background
86// =============================================================================
87
89 Kinesis::AABB2D bounds,
90 glm::vec3 color,
91 const std::shared_ptr<Core::VKImage>& texture)
92{
93 return [bounds, color, texture](
94 float,
95 std::vector<uint8_t>& out,
96 Element& el) {
97 out.clear();
98
99 if (texture) {
101 const std::array<std::pair<glm::vec3, glm::vec2>, 4> verts = { {
102 { { bounds.min.x, bounds.min.y, 0.F }, { 0.F, 1.F } },
103 { { bounds.max.x, bounds.min.y, 0.F }, { 1.F, 1.F } },
104 { { bounds.min.x, bounds.max.y, 0.F }, { 0.F, 0.F } },
105 { { bounds.max.x, bounds.max.y, 0.F }, { 1.F, 0.F } },
106 } };
107 out.resize(static_cast<size_t>(4) * stride);
108 for (size_t i = 0; i < 4; ++i) {
109 uint8_t* v = out.data() + i * stride;
110 std::memcpy(v, &verts[i].first, 12);
111 std::memcpy(v + 12, &verts[i].second, 8);
112 }
113 } else {
114 const uint32_t stride = Kakshya::VertexLayout::for_meshes().stride_bytes;
115 constexpr glm::vec3 k_normal { 0.F, 0.F, 1.F };
116 constexpr glm::vec3 k_tangent { 1.F, 0.F, 0.F };
117 constexpr glm::vec2 k_uv { 0.F, 0.F };
118 constexpr float k_weight = 0.F;
119
120 const std::array<glm::vec3, 4> positions = { {
121 { bounds.min.x, bounds.min.y, 0.F },
122 { bounds.max.x, bounds.min.y, 0.F },
123 { bounds.min.x, bounds.max.y, 0.F },
124 { bounds.max.x, bounds.max.y, 0.F },
125 } };
126
127 out.resize(static_cast<size_t>(4) * stride, 0);
128 for (size_t i = 0; i < 4; ++i) {
129 uint8_t* v = out.data() + i * stride;
130 std::memcpy(v, &positions[i], 12);
131 std::memcpy(v + 12, &color, 12);
132 std::memcpy(v + 24, &k_weight, 4);
133 std::memcpy(v + 28, &k_uv, 8);
134 std::memcpy(v + 36, &k_normal, 12);
135 std::memcpy(v + 48, &k_tangent, 12);
136 }
137 }
138
139 el.bounds_hint = bounds;
140 };
141}
142
143std::vector<Kakshya::LineVertex> plot_grid(
144 Kinesis::AABB2D bounds,
145 uint32_t x_divisions,
146 uint32_t y_divisions,
147 glm::vec3 color,
148 float thickness)
149{
150 std::vector<Kakshya::LineVertex> out;
151 out.reserve((static_cast<size_t>(x_divisions + y_divisions)) * 2);
152
153 auto lv = [&](glm::vec2 p) {
154 return Kakshya::LineVertex {
155 .position = { p.x, p.y, 0.F },
156 .color = color,
157 .thickness = thickness,
158 };
159 };
160
161 for (uint32_t i = 0; i < x_divisions; ++i) {
162 const float t = (x_divisions > 1)
163 ? static_cast<float>(i) / static_cast<float>(x_divisions - 1)
164 : 0.5F;
165 const float x = bounds.min.x + t * bounds.width();
166 out.push_back(lv({ x, bounds.min.y }));
167 out.push_back(lv({ x, bounds.max.y }));
168 }
169
170 for (uint32_t i = 0; i < y_divisions; ++i) {
171 const float t = (y_divisions > 1)
172 ? static_cast<float>(i) / static_cast<float>(y_divisions - 1)
173 : 0.5F;
174 const float y = bounds.min.y + t * bounds.height();
175 out.push_back(lv({ bounds.min.x, y }));
176 out.push_back(lv({ bounds.max.x, y }));
177 }
178
179 return out;
180}
181
183 Kinesis::AABB2D bounds,
184 bool vertical,
185 glm::vec3 color,
186 float thickness)
187{
188 return [bounds, vertical, color, thickness](
189 float v, std::vector<uint8_t>& out, Element& el) {
190 const float t = std::clamp(v, 0.F, 1.F);
191 using V = Kakshya::LineVertex;
192 std::array<V, 2> verts;
193 if (vertical) {
194 const float x = bounds.min.x + t * bounds.width();
195 verts = { {
196 { .position = { x, bounds.min.y, 0.F }, .color = color, .thickness = thickness },
197 { .position = { x, bounds.max.y, 0.F }, .color = color, .thickness = thickness },
198 } };
199 el.bounds_hint = Kinesis::AABB2D {
200 .min = { x - 0.01F, bounds.min.y },
201 .max = { x + 0.01F, bounds.max.y },
202 };
203 } else {
204 const float y = bounds.min.y + t * bounds.height();
205 verts = { {
206 { .position = { bounds.min.x, y, 0.F }, .color = color, .thickness = thickness },
207 { .position = { bounds.max.x, y, 0.F }, .color = color, .thickness = thickness },
208 } };
209 el.bounds_hint = Kinesis::AABB2D {
210 .min = { bounds.min.x, y - 0.01F },
211 .max = { bounds.max.x, y + 0.01F },
212 };
213 }
214 Geometry::write_verts(out, verts);
215 };
216}
217
218// =============================================================================
219// Label / tick / legend spec helpers
220// =============================================================================
221
223 std::string text,
224 Kinesis::AABB2D bounds,
225 glm::vec4 color,
226 std::string name)
227{
228 return LabelSpec {
229 .text = std::move(text),
230 .bounds = bounds,
231 .color = color,
232 .name = std::move(name),
233 .interactive = false,
234 };
235}
236
237std::vector<LabelSpec> plot_tick_labels(const TickLabelsSpec& spec)
238{
239 const uint32_t count = std::max(spec.count, 2U);
240 const bool horizontal = spec.edge == TickEdge::Bottom || spec.edge == TickEdge::Top;
241
242 std::vector<LabelSpec> labels;
243 labels.reserve(count);
244
245 for (uint32_t i = 0; i < count; ++i) {
246 const float t = static_cast<float>(i) / static_cast<float>(count - 1);
247 const float value = spec.range.min + t * (spec.range.max - spec.range.min);
248 const std::string text = std::format("{:.{}f}", value,
249 static_cast<int>(spec.decimal_places));
250
251 Kinesis::AABB2D label_bounds {};
252 if (horizontal) {
253 const float cx = spec.plot_bounds.min.x + t * spec.plot_bounds.width();
254 const float half_w = spec.plot_bounds.width() / static_cast<float>(count) * 0.5F;
255
256 if (spec.edge == TickEdge::Bottom) {
257 label_bounds = Kinesis::AABB2D {
258 .min = { cx - half_w, spec.plot_bounds.min.y - spec.label_h },
259 .max = { cx + half_w, spec.plot_bounds.min.y },
260 };
261 } else {
262 label_bounds = Kinesis::AABB2D {
263 .min = { cx - half_w, spec.plot_bounds.max.y },
264 .max = { cx + half_w, spec.plot_bounds.max.y + spec.label_h },
265 };
266 }
267 } else {
268 const float cy = spec.plot_bounds.min.y + t * spec.plot_bounds.height();
269 const float half_h = spec.plot_bounds.height() / static_cast<float>(count) * 0.5F;
270
271 if (spec.edge == TickEdge::Left) {
272 label_bounds = Kinesis::AABB2D {
273 .min = { spec.plot_bounds.min.x - spec.label_w, cy - half_h },
274 .max = { spec.plot_bounds.min.x, cy + half_h },
275 };
276 } else {
277 label_bounds = Kinesis::AABB2D {
278 .min = { spec.plot_bounds.max.x, cy - half_h },
279 .max = { spec.plot_bounds.max.x + spec.label_w, cy + half_h },
280 };
281 }
282 }
283
284 labels.push_back(LabelSpec {
285 .text = text,
286 .bounds = label_bounds,
287 .color = spec.color,
288 .name = spec.name_prefix + "_" + std::to_string(i),
289 .interactive = false,
290 });
291 }
292
293 return labels;
294}
295
296std::vector<LabelSpec> plot_tick_labels(
297 Kinesis::AABB2D bounds,
298 const AxisRange& range,
299 uint32_t count,
300 TickEdge edge,
301 glm::vec4 color,
302 uint8_t decimal_places,
303 float label_h,
304 float label_w)
305{
307 .plot_bounds = bounds,
308 .range = range,
309 .count = count,
310 .edge = edge,
311 .color = color,
312 .decimal_places = decimal_places,
313 .label_h = label_h,
314 .label_w = label_w,
315 });
316}
317
319 glm::vec2 origin,
320 std::span<const std::string> labels,
321 std::span<const glm::vec3> colors,
322 float row_h,
323 float swatch_w,
324 glm::vec4 text_color)
325{
326 const size_t n = std::min(labels.size(), colors.size());
327
328 LegendSpec spec {
329 .origin = origin,
330 .row_h = row_h,
331 .swatch_w = swatch_w,
332 .text_color = text_color,
333 };
334 spec.entries.reserve(n);
335
336 for (size_t i = 0; i < n; ++i) {
337 spec.entries.push_back(LegendEntry {
338 .label = labels[i],
339 .color = colors[i],
340 });
341 }
342
343 return spec;
344}
345
347{
348 LegendLayout layout;
349 layout.swatches.reserve(spec.entries.size());
350 layout.labels.reserve(spec.entries.size());
351
352 for (size_t i = 0; i < spec.entries.size(); ++i) {
353 const float y_top = spec.origin.y
354 - static_cast<float>(i) * (spec.row_h + spec.gap);
355 const float y_bot = y_top - spec.row_h;
356
357 const Kinesis::AABB2D swatch_bounds {
358 .min = { spec.origin.x, y_bot },
359 .max = { spec.origin.x + spec.swatch_w, y_top },
360 };
361
362 const Kinesis::AABB2D text_bounds {
363 .min = { spec.origin.x + spec.swatch_w + spec.gap, y_bot },
364 .max = { spec.origin.x + spec.swatch_w + spec.gap + spec.text_w, y_top },
365 };
366
367 layout.swatches.push_back(RectSpec {
368 .bounds = swatch_bounds,
369 .color = spec.entries[i].color,
370 .name = spec.name_prefix + "_swatch_" + std::to_string(i),
371 .interactive = spec.interactive,
372 });
373
374 layout.labels.push_back(LabelSpec {
375 .text = spec.entries[i].label,
376 .bounds = text_bounds,
377 .color = spec.text_color,
378 .name = spec.name_prefix + "_label_" + std::to_string(i),
379 .interactive = spec.interactive,
380 });
381 }
382
383 return layout;
384}
385
386} // namespace MayaFlux::Portal::Forma::Plot
Illustrative geometry functions for common Mapped use cases.
size_t count
std::vector< DataVariant > & get_processed_data() override
Get a mutable reference to the processed data buffer.
ContainerDataStructure & get_structure() override
Get the data structure defining this container's layout.
SignalSourceContainer holding N named scalar series for plotting and signal use.
void write_verts(std::vector< uint8_t > &out, const std::vector< V > &verts)
Write a vertex array into a GeometryFn output buffer.
Definition Geometry.hpp:35
glm::vec3 palette_color(const std::vector< glm::vec3 > &palette, size_t index) noexcept
Resolve a per-series color from a palette.
Definition PlotSpec.cpp:76
TickEdge
Edge along which tick labels are placed.
Definition PlotSpec.hpp:21
@ Bottom
Labels below bounds, values mapped from range.min (left) to range.max (right).
@ Left
Labels left of bounds, values mapped from range.min (bottom) to range.max (top).
std::vector< Kakshya::LineVertex > plot_grid(Kinesis::AABB2D bounds, uint32_t x_divisions, uint32_t y_divisions, glm::vec3 color, float thickness)
LINE_LIST grid geometry for a plot area.
Definition PlotSpec.cpp:143
std::pair< float, float > data_range(std::span< const double > series)
Compute [min, max] over a scalar series.
Definition PlotSpec.cpp:35
LegendLayout layout_legend(const LegendSpec &spec)
Expand a legend spec into swatch rectangle specs and text label specs.
Definition PlotSpec.cpp:346
Series series()
Begin a Series chain.
Definition Plot.hpp:109
void apply_auto_scale(AxisRange &range, const std::vector< std::span< const double > > &series)
Apply auto-scaling to an AxisRange from a set of series.
Definition PlotSpec.cpp:53
std::vector< LabelSpec > plot_tick_labels(const TickLabelsSpec &spec)
Generate construction-free tick label specs.
Definition PlotSpec.cpp:237
LegendSpec plot_legend(glm::vec2 origin, std::span< const std::string > labels, std::span< const glm::vec3 > colors, float row_h, float swatch_w, glm::vec4 text_color)
Create a construction-free legend spec from labels and colors.
Definition PlotSpec.cpp:318
GeometryFn< float > plot_cursor(Kinesis::AABB2D bounds, bool vertical, glm::vec3 color, float thickness)
GeometryFn<float> for a cursor or playhead line within a plot area.
Definition PlotSpec.cpp:182
LabelSpec plot_label(std::string text, Kinesis::AABB2D bounds, glm::vec4 color, std::string name)
Create a single construction-free label spec.
Definition PlotSpec.cpp:222
std::vector< std::span< const double > > series_by_role(const Kakshya::PlotContainer &container, Kakshya::DataDimension::Role role)
Collect all series from processed_data whose DataDimension role matches role.
Definition PlotSpec.cpp:12
GeometryFn< float > background(Kinesis::AABB2D bounds, glm::vec3 color, const std::shared_ptr< Core::VKImage > &texture)
TRIANGLE_STRIP background quad for a plot area.
Definition PlotSpec.cpp:88
std::function< void(T value, std::vector< uint8_t > &out_bytes, Element &element)> GeometryFn
Geometry function signature.
Definition Mapped.hpp:64
Role
Semantic role of the dimension.
Definition NDData.hpp:150
Vertex type for line primitives (LINE_LIST / LINE_STRIP topology)
uint32_t stride_bytes
Total bytes per vertex (stride in Vulkan terms) e.g., 3 floats (position) + 3 floats (normal) = 24 by...
static VertexLayout for_textured_quad(uint32_t vertex_count=4)
Factory: Create layout for textured quad primitives (position, texcoord).
static VertexLayout for_meshes(uint32_t stride=60)
Factory: layout for MeshVertex (position, color, weight, uv, normal, tangent)
float height() const noexcept
Definition Bounds.hpp:38
float width() const noexcept
Definition Bounds.hpp:37
Axis-aligned bounding rectangle in a 2D coordinate space.
Definition Bounds.hpp:21
A bounded, renderable region on a window surface.
Definition Element.hpp:58
Scalar domain extent for one plot axis.
Definition AxisRange.hpp:22
Construction-free text label description.
Definition PlotSpec.hpp:35
Expanded construction-free legend layout.
Definition PlotSpec.hpp:128
std::vector< LegendEntry > entries
Definition PlotSpec.hpp:109
Construction-free vertical legend configuration.
Definition PlotSpec.hpp:107
Lightweight filled rectangle description.
Definition PlotSpec.hpp:65
float label_h
NDC height of each label strip for Bottom/Top ticks.
Definition PlotSpec.hpp:86
float label_w
NDC width of each label strip for Left/Right ticks.
Definition PlotSpec.hpp:91
Config for generating numeric tick labels on one plot edge.
Definition PlotSpec.hpp:75