18 std::vector<std::span<const double>> series_for_mapping(
19 const Kakshya::PlotContainer& container,
20 const Series::AxisMapping& mapping)
22 std::vector<std::span<const double>> result;
23 for (
const auto& role : mapping.roles) {
25 result.insert(result.end(), s.begin(), s.end());
33 AxisRange merge_axis(std::vector<Series::AxisMapping>& mappings)
37 if (mappings.size() == 1)
38 return mappings[0].range;
40 AxisRange merged = mappings[0].range;
41 for (
size_t i = 1; i < mappings.size(); ++i) {
42 const auto& r = mappings[i].range;
43 merged.min = std::min(merged.min, r.min);
44 merged.max = std::max(merged.max, r.max);
46 merged.auto_scaling =
true;
47 if (!merged.scale_predicate && r.scale_predicate)
48 merged.scale_predicate = r.scale_predicate;
53 AxisRange merge_axis_const(
const std::vector<Series::AxisMapping>& mappings)
56 return merge_axis(copy);
62 glm::vec3 resolve_color(
63 const Series::AxisMapping& mapping,
64 const std::vector<glm::vec3>& global_palette,
67 if (!mapping.palette.empty())
72 std::vector<float> uniform_x(
size_t n,
const AxisRange& x_range)
74 std::vector<float> xs(n);
76 xs[0] = x_range.to_ndc((x_range.min + x_range.max) * 0.5F);
79 for (
size_t i = 0; i < n; ++i) {
80 const float t =
static_cast<float>(i) /
static_cast<float>(n - 1);
81 xs[i] = x_range.to_ndc(x_range.min + t * (x_range.max - x_range.min));
96 constexpr uint32_t k_stride = 60;
97 constexpr uint32_t k_off_pos = 0;
98 constexpr uint32_t k_off_color = 12;
99 constexpr uint32_t k_off_scalar = 24;
101 constexpr glm::vec3 k_default_normal { 0.F, 0.F, 1.F };
102 constexpr glm::vec3 k_default_tangent { 1.F, 0.F, 0.F };
103 constexpr glm::vec2 k_default_uv { 0.F, 0.F };
106 std::vector<uint8_t>&
out,
111 const size_t base =
out.size();
112 out.resize(base + k_stride, 0);
113 uint8_t* v =
out.data() + base;
114 std::memcpy(v + k_off_pos, &pos, 12);
115 std::memcpy(v + k_off_color, &
color, 12);
116 std::memcpy(v + k_off_scalar, &
scalar, 4);
117 std::memcpy(v + 28, &k_default_uv, 8);
118 std::memcpy(v + 36, &k_default_normal, 12);
119 std::memcpy(v + 48, &k_default_tangent, 12);
129 SeriesSpec fitted(SeriesSpec spec)
131 if (!spec.plot_bounds)
134 const Kinesis::AABB2D bounds = *spec.plot_bounds;
135 spec.fn = [inner = std::move(spec.fn), bounds](
136 const std::shared_ptr<Kakshya::PlotContainer>& container,
137 std::vector<uint8_t>&
out,
138 Element& element)
mutable {
139 inner(container,
out, element);
141 const size_t count =
out.size() / k_stride;
142 for (
size_t i = 0; i <
count; ++i) {
144 uint8_t* vertex =
out.data() + i * k_stride;
162 std::vector<TickLabelsSpec> resolved;
166 resolved.reserve(
m_ticks.size());
168 for (
const auto& req :
m_ticks) {
175 range = merge_axis_const(
m_x);
178 range = merge_axis_const(
m_y);
189 .range = std::move(range),
193 .decimal_places = req.decimal_places,
194 .label_h = req.label_h,
195 .label_w = req.label_w,
196 .name_prefix = req.name_prefix,
216 .
fn = [x_mappings, y_mappings, z_mappings, global_palette,
thickness](
217 const std::shared_ptr<Kakshya::PlotContainer>& container,
218 std::vector<uint8_t>&
out,
223 container->process_default();
226 std::span<const double> data;
227 size_t mapping_index;
228 size_t index_within_mapping;
231 std::vector<SeriesEntry> y_entries;
232 for (
size_t mi = 0; mi < y_mappings.size(); ++mi) {
233 auto series = series_for_mapping(*container, y_mappings[mi]);
234 for (
size_t si = 0; si <
series.size(); ++si)
235 y_entries.push_back({ .data = series[si], .mapping_index = mi, .index_within_mapping = si });
237 if (y_entries.empty())
240 AxisRange x_range = merge_axis(x_mappings);
243 std::vector<std::span<const double>> all_x, all_z;
244 for (
auto& m : x_mappings) {
245 auto s = series_for_mapping(*container, m);
246 all_x.insert(all_x.end(), s.begin(), s.end());
248 for (
auto& m : z_mappings) {
249 auto s = series_for_mapping(*container, m);
250 all_z.insert(all_z.end(), s.begin(), s.end());
253 apply_auto_scale(x_range, all_x.empty() ? std::vector<std::span<const double>> { y_entries[0].data } : all_x);
258 for (
size_t ei = 0; ei < y_entries.size(); ++ei) {
259 const auto& entry = y_entries[ei];
260 const auto& ys = entry.data;
261 const size_t n = ys.size();
265 const glm::vec3
color = resolve_color(
266 y_mappings[entry.mapping_index], global_palette, entry.index_within_mapping);
268 auto y_range = y_mappings[entry.mapping_index].range;
271 const bool has_x = ei < all_x.size() && all_x[ei].size() == n;
272 const bool has_z = ei < all_z.size() && all_z[ei].size() == n;
274 const std::vector<float> xs = has_x
276 std::vector<float> v;
278 for (
double val : all_x[ei])
279 v.push_back(x_range.
to_ndc(
static_cast<float>(val)));
282 : uniform_x(n, x_range);
284 for (
size_t i = 0; i < n; ++i) {
287 y_range.to_ndc(
static_cast<float>(ys[i])),
288 has_z ? z_range.
to_ndc(
static_cast<float>(all_z[ei][i])) : 0.F },
293 if (ei + 1 < y_entries.size()) {
294 const glm::vec3 sep_pos {
296 y_range.to_ndc(
static_cast<float>(ys[n - 1])),
297 has_z ? z_range.
to_ndc(
static_cast<float>(all_z[ei][n - 1])) : 0.F,
299 write_vertex(
out, sep_pos,
color, 0.F);
300 write_vertex(
out, sep_pos,
color, 0.F);
304 .capacity_for = [](uint64_t n) {
return (n + 2) * k_stride; },
328 .
fn = [x_mappings, y_mappings, z_mappings, global_palette,
point_size](
329 const std::shared_ptr<Kakshya::PlotContainer>& container,
330 std::vector<uint8_t>&
out,
335 container->process_default();
337 std::vector<std::span<const double>> all_x, all_z;
338 for (
auto& m : x_mappings) {
339 auto s = series_for_mapping(*container, m);
340 all_x.insert(all_x.end(), s.begin(), s.end());
342 for (
auto& m : z_mappings) {
343 auto s = series_for_mapping(*container, m);
344 all_z.insert(all_z.end(), s.begin(), s.end());
347 AxisRange x_range = merge_axis(x_mappings);
354 size_t global_idx = 0;
355 for (
auto& y_mapping : y_mappings) {
356 auto y_series = series_for_mapping(*container, y_mapping);
357 auto y_range = y_mapping.range;
360 for (
size_t si = 0; si < y_series.size(); ++si, ++global_idx) {
361 const auto& ys = y_series[si];
362 const size_t n = ys.size();
366 const glm::vec3
color = resolve_color(y_mapping, global_palette, si);
367 const size_t x_idx = std::min(global_idx, all_x.empty() ?
size_t(0) : all_x.size() - 1);
368 const bool has_x = !all_x.empty() && all_x[x_idx].size() == n;
369 const bool has_z = global_idx < all_z.size() && all_z[global_idx].size() == n;
371 for (
size_t i = 0; i < n; ++i) {
372 const float px = has_x
373 ? x_range.
to_ndc(
static_cast<float>(all_x[x_idx][i]))
375 +
static_cast<float>(i) /
static_cast<float>(std::max<size_t>(n - 1, 1))
376 * (x_range.
max - x_range.
min));
380 y_range.to_ndc(
static_cast<float>(ys[i])),
381 has_z ? z_range.
to_ndc(
static_cast<float>(all_z[global_idx][i])) : 0.F },
388 .capacity_for = [](uint64_t n) {
return n * k_stride; },
410 .
fn = [x_mappings, y_mappings, global_palette](
411 const std::shared_ptr<Kakshya::PlotContainer>& container,
412 std::vector<uint8_t>&
out,
417 container->process_default();
422 for (
auto& y_mapping : y_mappings) {
423 auto y_series = series_for_mapping(*container, y_mapping);
424 if (y_series.empty())
427 auto y_range = y_mapping.range;
430 for (
size_t si = 0; si < y_series.size(); ++si) {
431 const auto&
series = y_series[si];
432 const size_t n =
series.size();
436 const glm::vec3
color = resolve_color(y_mapping, global_palette, si);
437 const float bar_w = (x_range.
max - x_range.
min) /
static_cast<float>(n);
438 const float y_base = y_range.to_ndc(0.F);
440 for (
size_t i = 0; i < n; ++i) {
441 const float x_left = x_range.
to_ndc(x_range.
min +
static_cast<float>(i) * bar_w);
442 const float x_right = x_range.
to_ndc(x_range.
min +
static_cast<float>(i + 1) * bar_w);
443 const float y_top = y_range.to_ndc(
static_cast<float>(
series[i]));
445 write_vertex(
out, { x_left, y_base, 0.F },
color, 0.F);
446 write_vertex(
out, { x_right, y_base, 0.F },
color, 0.F);
447 write_vertex(
out, { x_left, y_top, 0.F },
color, 0.F);
448 write_vertex(
out, { x_right, y_base, 0.F },
color, 0.F);
449 write_vertex(
out, { x_right, y_top, 0.F },
color, 0.F);
450 write_vertex(
out, { x_left, y_top, 0.F },
color, 0.F);
455 .capacity_for = [](uint64_t n) {
return n * 6 * k_stride; },
474 .
fn = [x_mappings, y_mappings, global_palette, baseline_data](
475 const std::shared_ptr<Kakshya::PlotContainer>& container,
476 std::vector<uint8_t>&
out,
481 container->process_default();
484 std::span<const double> data;
485 size_t mapping_index;
486 size_t index_within_mapping;
488 std::vector<SeriesEntry> y_entries;
489 for (
size_t mi = 0; mi < y_mappings.size(); ++mi) {
490 auto sv = series_for_mapping(*container, y_mappings[mi]);
491 for (
size_t si = 0; si < sv.size(); ++si)
492 y_entries.push_back({ .data = sv[si], .mapping_index = mi, .index_within_mapping = si });
494 if (y_entries.empty())
497 AxisRange x_range = merge_axis(x_mappings);
498 AxisRange y_range = merge_axis(y_mappings);
500 std::vector<std::span<const double>> all_y;
501 all_y.reserve(y_entries.size());
502 for (
const auto& e : y_entries)
503 all_y.push_back(e.data);
506 std::vector<std::span<const double>> all_x;
507 if (!x_mappings.empty()) {
508 all_x = series_for_mapping(*container, x_mappings[0]);
514 const float baseline_ndc = y_range.
to_ndc(baseline_data);
518 for (
size_t ei = 0; ei < y_entries.size(); ++ei) {
519 const auto& entry = y_entries[ei];
520 const size_t n = entry.data.size();
524 const glm::vec3
color = resolve_color(
525 y_mappings[entry.mapping_index], global_palette, entry.index_within_mapping);
527 const size_t x_idx = std::min(ei, all_x.empty() ?
size_t(0) : all_x.size() - 1);
528 const bool has_x = !all_x.empty() && all_x[x_idx].size() == n;
530 const size_t pre_size =
out.size();
532 for (
size_t i = 0; i < n; ++i) {
533 const float px = has_x
534 ? x_range.
to_ndc(
static_cast<float>(all_x[x_idx][i]))
536 +
static_cast<float>(i) /
static_cast<float>(std::max<size_t>(n - 1, 1))
537 * (x_range.
max - x_range.
min));
539 const float py = y_range.
to_ndc(
static_cast<float>(entry.data[i]));
542 write_vertex(
out, {
px, baseline_ndc, 0.F },
color, 0.F);
545 if (ei > 0 &&
out.size() > pre_size) {
546 const std::vector<uint8_t> head(
547 out.begin() +
static_cast<ptrdiff_t
>(pre_size),
548 out.begin() +
static_cast<ptrdiff_t
>(pre_size + k_stride));
549 out.insert(
out.begin() +
static_cast<ptrdiff_t
>(pre_size), head.begin(), head.end());
552 if (ei + 1 < y_entries.size() &&
out.size() > pre_size) {
553 const std::vector<uint8_t> tail(
out.end() -
static_cast<ptrdiff_t
>(k_stride),
out.end());
554 out.insert(
out.end(), tail.begin(), tail.end());
558 .capacity_for = [](uint64_t n) {
return n * 2 * k_stride + 128; },
560 ? std::optional<GeometryFn<float>> {
background(
std::optional< glm::vec3 > position
std::vector< float > * out
CopiedText copy(std::string_view text, const LayoutResult &layout, size_t a, size_t b)
Copy a byte range: the text it spans, and the quads that render it.
Role
Semantic role of the dimension.