467{
472
473 return fitted(SeriesSpec {
474 .fn = [x_mappings, y_mappings, global_palette, baseline_data](
475 const std::shared_ptr<Kakshya::PlotContainer>& container,
476 std::vector<uint8_t>&
out,
477 Element&) mutable {
478 if (!container)
479 return;
480
481 container->process_default();
482
483 struct SeriesEntry {
484 std::span<const double> data;
485 size_t mapping_index;
486 size_t index_within_mapping;
487 };
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 });
493 }
494 if (y_entries.empty())
495 return;
496
497 AxisRange x_range = merge_axis(x_mappings);
498 AxisRange y_range = merge_axis(y_mappings);
499
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);
505
506 std::vector<std::span<const double>> all_x;
507 if (!x_mappings.empty()) {
508 all_x = series_for_mapping(*container, x_mappings[0]);
510 } else {
511 x_range = AxisRange {}.range(-1.F, 1.F);
512 }
513
514 const float baseline_ndc = y_range.to_ndc(baseline_data);
515
517
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();
521 if (n == 0)
522 continue;
523
524 const glm::vec3
color = resolve_color(
525 y_mappings[entry.mapping_index], global_palette, entry.index_within_mapping);
526
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;
529
530 const size_t pre_size =
out.size();
531
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]))
535 : x_range.to_ndc(x_range.min
536 + static_cast<float>(i) / static_cast<float>(std::max<size_t>(n - 1, 1))
537 * (x_range.max - x_range.min));
538
539 const float py = y_range.to_ndc(
static_cast<float>(entry.data[i]));
540
542 write_vertex(
out, {
px, baseline_ndc, 0.F },
color, 0.F);
543 }
544
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());
550 }
551
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());
555 }
556 } },
558 .capacity_for = [](uint64_t n) { return n * 2 * k_stride + 128; },
560 ? std::optional<GeometryFn<float>> {
background(
562 : std::nullopt,
567 });
568}
std::vector< float > * out