176{
182
183 return {
184 .fn = [x_mappings, y_mappings, z_mappings, global_palette,
thickness](
185 const std::shared_ptr<Kakshya::PlotContainer>& container,
186 std::vector<uint8_t>& out,
187 Element&) mutable {
188 if (!container)
189 return;
190
191 container->process_default();
192
193 struct SeriesEntry {
194 std::span<const double> data;
195 size_t mapping_index;
196 size_t index_within_mapping;
197 };
198
199 std::vector<SeriesEntry> y_entries;
200 for (size_t mi = 0; mi < y_mappings.size(); ++mi) {
201 auto series = series_for_mapping(*container, y_mappings[mi]);
202 for (
size_t si = 0; si <
series.size(); ++si)
203 y_entries.push_back({ .data = series[si], .mapping_index = mi, .index_within_mapping = si });
204 }
205 if (y_entries.empty())
206 return;
207
208 AxisRange x_range = merge_axis(x_mappings);
209 AxisRange z_range = z_mappings.empty() ? AxisRange {} : merge_axis(z_mappings);
210
211 std::vector<std::span<const double>> all_x, all_z;
212 for (auto& m : x_mappings) {
213 auto s = series_for_mapping(*container, m);
214 all_x.insert(all_x.end(), s.begin(), s.end());
215 }
216 for (auto& m : z_mappings) {
217 auto s = series_for_mapping(*container, m);
218 all_z.insert(all_z.end(), s.begin(), s.end());
219 }
220
221 apply_auto_scale(x_range, all_x.empty() ? std::vector<std::span<const double>> { y_entries[0].data } : all_x);
223
224 out.clear();
225
226 for (size_t ei = 0; ei < y_entries.size(); ++ei) {
227 const auto& entry = y_entries[ei];
228 const auto& ys = entry.data;
229 const size_t n = ys.size();
230 if (n == 0)
231 continue;
232
233 const glm::vec3 color = resolve_color(
234 y_mappings[entry.mapping_index], global_palette, entry.index_within_mapping);
235
236 auto y_range = y_mappings[entry.mapping_index].range;
238
239 const bool has_x = ei < all_x.size() && all_x[ei].size() == n;
240 const bool has_z = ei < all_z.size() && all_z[ei].size() == n;
241
242 const std::vector<float> xs = has_x
243 ? [&] {
244 std::vector<float> v;
245 v.reserve(n);
246 for (double val : all_x[ei])
247 v.push_back(x_range.
to_ndc(static_cast<float>(val)));
248 return v;
249 }()
250 : uniform_x(n, x_range);
251
252 for (size_t i = 0; i < n; ++i) {
253 write_vertex(out,
254 { xs[i],
255 y_range.to_ndc(static_cast<float>(ys[i])),
256 has_z ? z_range.to_ndc(static_cast<float>(all_z[ei][i])) : 0.F },
257 color,
259 }
260
261 if (ei + 1 < y_entries.size()) {
262 const glm::vec3 sep_pos {
263 xs[n - 1],
264 y_range.to_ndc(static_cast<float>(ys[n - 1])),
265 has_z ? z_range.to_ndc(static_cast<float>(all_z[ei][n - 1])) : 0.F,
266 };
267 write_vertex(out, sep_pos, color, 0.F);
268 write_vertex(out, sep_pos, color, 0.F);
269 }
270 } },
272 .capacity_for = [](uint64_t n) { return (n + 2) * k_stride; },
275 : std::nullopt,
280 };
281}
glm::vec3 to_ndc(double window_x, double window_y, uint32_t width, uint32_t height)
Convert window pixel coordinates to NDC.