435{
440
441 return {
442 .fn = [x_mappings, y_mappings, global_palette, baseline_data](
443 const std::shared_ptr<Kakshya::PlotContainer>& container,
444 std::vector<uint8_t>& out,
445 Element&) mutable {
446 if (!container)
447 return;
448
449 container->process_default();
450
451 struct SeriesEntry {
452 std::span<const double> data;
453 size_t mapping_index;
454 size_t index_within_mapping;
455 };
456 std::vector<SeriesEntry> y_entries;
457 for (size_t mi = 0; mi < y_mappings.size(); ++mi) {
458 auto sv = series_for_mapping(*container, y_mappings[mi]);
459 for (size_t si = 0; si < sv.size(); ++si)
460 y_entries.push_back({ .data = sv[si], .mapping_index = mi, .index_within_mapping = si });
461 }
462 if (y_entries.empty())
463 return;
464
465 AxisRange x_range = merge_axis(x_mappings);
466 AxisRange y_range = merge_axis(y_mappings);
467
468 std::vector<std::span<const double>> all_y;
469 all_y.reserve(y_entries.size());
470 for (const auto& e : y_entries)
471 all_y.push_back(e.data);
473
474 std::vector<std::span<const double>> all_x;
475 if (!x_mappings.empty()) {
476 all_x = series_for_mapping(*container, x_mappings[0]);
478 } else {
479 x_range = AxisRange {}.range(-1.F, 1.F);
480 }
481
482 const float baseline_ndc = y_range.to_ndc(baseline_data);
483
484 out.clear();
485
486 for (size_t ei = 0; ei < y_entries.size(); ++ei) {
487 const auto& entry = y_entries[ei];
488 const size_t n = entry.data.size();
489 if (n == 0)
490 continue;
491
492 const glm::vec3 color = resolve_color(
493 y_mappings[entry.mapping_index], global_palette, entry.index_within_mapping);
494
495 const size_t x_idx = std::min(ei, all_x.empty() ? size_t(0) : all_x.size() - 1);
496 const bool has_x = !all_x.empty() && all_x[x_idx].size() == n;
497
498 const size_t pre_size = out.size();
499
500 for (size_t i = 0; i < n; ++i) {
501 const float px = has_x
502 ? x_range.to_ndc(static_cast<float>(all_x[x_idx][i]))
503 : x_range.to_ndc(x_range.min
504 + static_cast<float>(i) / static_cast<float>(std::max<size_t>(n - 1, 1))
505 * (x_range.max - x_range.min));
506
507 const float py = y_range.to_ndc(static_cast<float>(entry.data[i]));
508
509 write_vertex(out, { px, py, 0.F }, color, 1.F);
510 write_vertex(out, { px, baseline_ndc, 0.F }, color, 0.F);
511 }
512
513 if (ei + 1 < y_entries.size() && out.size() > pre_size) {
514 const size_t last = out.size() - k_stride;
515 out.insert(out.end(), out.begin() + static_cast<ptrdiff_t>(last), out.end());
516 }
517 } },
519 .capacity_for = [](uint64_t n) { return n * 2 * k_stride + 128; },
521 ? std::optional<GeometryFn<float>> {
background(
523 : std::nullopt,
528 };
529}