@@ -26,6 +26,50 @@ AxesSpec normalize_axes_spec(AxesSpec spec) {
2626 return spec;
2727}
2828
29+ RenderResult invalid_input (std::string msg) {
30+ return RenderResult{
31+ .ok = false , .status = RenderStatus::InvalidInput, .message = std::move (msg)};
32+ }
33+
34+ RenderResult validate_series (const SeriesData& s,
35+ const std::size_t axis_idx,
36+ const std::size_t series_idx) {
37+ if (s.spec .has_opacity && (s.spec .opacity < 0.0 || s.spec .opacity > 1.0 )) {
38+ return invalid_input (" axis " + std::to_string (axis_idx) + " series " +
39+ std::to_string (series_idx) + " : opacity must be in [0,1]" );
40+ }
41+ if (s.x .size () != s.y .size ()) {
42+ return invalid_input (" axis " + std::to_string (axis_idx) + " series " +
43+ std::to_string (series_idx) + " : x/y size mismatch" );
44+ }
45+ switch (s.spec .type ) {
46+ case SeriesType::Band:
47+ if (s.y2 .size () != s.x .size ()) {
48+ return invalid_input (" axis " + std::to_string (axis_idx) + " series " +
49+ std::to_string (series_idx) + " : band y2 size mismatch" );
50+ }
51+ break ;
52+ case SeriesType::ErrorBars:
53+ if (s.yerr_low .size () != s.x .size () || s.yerr_high .size () != s.x .size ()) {
54+ return invalid_input (" axis " + std::to_string (axis_idx) + " series " +
55+ std::to_string (series_idx) +
56+ " : errorbar low/high size mismatch" );
57+ }
58+ break ;
59+ case SeriesType::Heatmap:
60+ if (s.z .size () != s.x .size ()) {
61+ return invalid_input (" axis " + std::to_string (axis_idx) + " series " +
62+ std::to_string (series_idx) + " : heatmap z size mismatch" );
63+ }
64+ break ;
65+ case SeriesType::Line:
66+ case SeriesType::Scatter:
67+ case SeriesType::Histogram:
68+ break ;
69+ }
70+ return RenderResult{};
71+ }
72+
2973} // namespace
3074
3175void Axes::set (AxesSpec spec) { spec_ = normalize_axes_spec (std::move (spec)); }
@@ -155,11 +199,40 @@ RenderResult Figure::save(const std::filesystem::path& out_dir) const {
155199 .message =
156200 " No backend configured. Call set_backend() before save()." };
157201 }
202+ const auto valid = validate_figure_for_render (*this );
203+ if (!valid.ok ) {
204+ return valid;
205+ }
158206 return backend_->render (*this , out_dir);
159207}
160208
161209void Figure::set_backend (std::shared_ptr<IPlotBackend> backend) {
162210 backend_ = std::move (backend);
163211}
164212
213+ RenderResult validate_figure_for_render (const Figure& fig) {
214+ const auto & spec = fig.spec ();
215+ if (spec.rows <= 0 || spec.cols <= 0 ) {
216+ return invalid_input (" rows and cols must be positive" );
217+ }
218+ if (spec.formats .empty ()) {
219+ return invalid_input (" no output formats requested" );
220+ }
221+ const auto & axes = fig.all_axes ();
222+ const auto expected = static_cast <std::size_t >(spec.rows * spec.cols );
223+ if (axes.size () != expected) {
224+ return invalid_input (" axes count does not match rows*cols" );
225+ }
226+ for (std::size_t ai = 0 ; ai < axes.size (); ++ai) {
227+ const auto & axis = axes[ai];
228+ for (std::size_t si = 0 ; si < axis.series ().size (); ++si) {
229+ const auto sres = validate_series (axis.series ()[si], ai, si);
230+ if (!sres.ok ) {
231+ return sres;
232+ }
233+ }
234+ }
235+ return RenderResult{};
236+ }
237+
165238} // namespace gnuplotpp
0 commit comments