1use runmat_builtins::{StringArray, StructValue, Tensor, Value};
2use runmat_plot::plots::{LegendStyle, TextStyle};
3
4use super::state::{
5 axes_handle_exists, axes_handles_for_figure, axes_metadata_snapshot, axes_state_snapshot,
6 current_axes_handle_for_figure, decode_axes_handle, decode_plot_object_handle,
7 figure_handle_exists, figure_has_sg_title, legend_entries_snapshot, select_axes_for_figure,
8 set_legend_for_axes, set_sg_title_properties_for_figure,
9 set_text_annotation_properties_for_axes, set_text_properties_for_axes, FigureHandle,
10 PlotObjectKind,
11};
12use super::style::{
13 parse_color_value, value_as_bool, value_as_f64, value_as_string, LineStyleParseOptions,
14};
15use super::{plotting_error, plotting_error_with_source};
16use crate::builtins::plotting::op_common::limits::limit_value;
17use crate::builtins::plotting::op_common::value_as_text_string;
18use crate::BuiltinResult;
19
20#[derive(Clone, Debug)]
21pub enum PlotHandle {
22 Figure(FigureHandle),
23 Axes(FigureHandle, usize),
24 Text(FigureHandle, usize, PlotObjectKind),
25 Legend(FigureHandle, usize),
26 PlotChild(super::state::PlotChildHandleState),
27}
28
29pub fn resolve_plot_handle(value: &Value, builtin: &'static str) -> BuiltinResult<PlotHandle> {
30 let scalar = handle_scalar(value, builtin)?;
31 if let Ok(state) = super::state::plot_child_handle_snapshot(scalar) {
32 return Ok(PlotHandle::PlotChild(state));
33 }
34 if let Ok((handle, axes_index, kind)) = decode_plot_object_handle(scalar) {
35 if axes_handle_exists(handle, axes_index) {
36 return Ok(match kind {
37 PlotObjectKind::Legend => PlotHandle::Legend(handle, axes_index),
38 _ => PlotHandle::Text(handle, axes_index, kind),
39 });
40 }
41 }
42 if let Ok((handle, axes_index)) = decode_axes_handle(scalar) {
43 if axes_handle_exists(handle, axes_index) {
44 return Ok(PlotHandle::Axes(handle, axes_index));
45 }
46 return Err(plotting_error(
47 builtin,
48 format!("{builtin}: invalid axes handle"),
49 ));
50 }
51 let figure = FigureHandle::from(scalar.round() as u32);
52 if figure_handle_exists(figure) {
53 return Ok(PlotHandle::Figure(figure));
54 }
55 Err(plotting_error(
56 builtin,
57 format!("{builtin}: unsupported or invalid plotting handle"),
58 ))
59}
60
61pub fn get_properties(
62 handle: PlotHandle,
63 property: Option<&str>,
64 builtin: &'static str,
65) -> BuiltinResult<Value> {
66 match handle {
67 PlotHandle::Axes(handle, axes_index) => {
68 get_axes_property(handle, axes_index, property, builtin)
69 }
70 PlotHandle::Text(handle, axes_index, kind) => {
71 get_text_property(handle, axes_index, kind, property, builtin)
72 }
73 PlotHandle::Legend(handle, axes_index) => {
74 get_legend_property(handle, axes_index, property, builtin)
75 }
76 PlotHandle::Figure(handle) => get_figure_property(handle, property, builtin),
77 PlotHandle::PlotChild(state) => get_plot_child_property(&state, property, builtin),
78 }
79}
80
81pub fn set_properties(
82 handle: PlotHandle,
83 args: &[Value],
84 builtin: &'static str,
85) -> BuiltinResult<()> {
86 if args.is_empty() || !args.len().is_multiple_of(2) {
87 return Err(plotting_error(
88 builtin,
89 format!("{builtin}: property/value arguments must come in pairs"),
90 ));
91 }
92 match handle {
93 PlotHandle::Figure(handle) => {
94 for pair in args.chunks_exact(2) {
95 let key = property_name(&pair[0], builtin)?;
96 apply_figure_property(handle, &key, &pair[1], builtin)?;
97 }
98 Ok(())
99 }
100 PlotHandle::Axes(handle, axes_index) => {
101 for pair in args.chunks_exact(2) {
102 let key = property_name(&pair[0], builtin)?;
103 apply_axes_property(handle, axes_index, &key, &pair[1], builtin)?;
104 }
105 Ok(())
106 }
107 PlotHandle::Text(handle, axes_index, kind) => {
108 let mut text: Option<String> = None;
109 let mut style = if matches!(kind, PlotObjectKind::SuperTitle) {
110 super::state::clone_figure(handle)
111 .ok_or_else(|| plotting_error(builtin, format!("{builtin}: invalid figure")))?
112 .sg_title_style
113 } else {
114 axes_metadata_snapshot(handle, axes_index)
115 .map_err(|err| map_figure_error(builtin, err))?
116 .text_style_for(kind)
117 };
118 for pair in args.chunks_exact(2) {
119 let key = property_name(&pair[0], builtin)?;
120 apply_text_property(&mut text, &mut style, &key, &pair[1], builtin)?;
121 }
122 if matches!(kind, PlotObjectKind::SuperTitle) {
123 set_sg_title_properties_for_figure(handle, text, Some(style))
124 .map_err(|err| map_figure_error(builtin, err))?;
125 } else {
126 set_text_properties_for_axes(handle, axes_index, kind, text, Some(style))
127 .map_err(|err| map_figure_error(builtin, err))?;
128 }
129 Ok(())
130 }
131 PlotHandle::Legend(handle, axes_index) => {
132 let snapshot = axes_metadata_snapshot(handle, axes_index)
133 .map_err(|err| map_figure_error(builtin, err))?;
134 let mut style = snapshot.legend_style;
135 let mut enabled = snapshot.legend_enabled;
136 let mut labels: Option<Vec<String>> = None;
137 for pair in args.chunks_exact(2) {
138 let key = property_name(&pair[0], builtin)?;
139 apply_legend_property(
140 &mut style,
141 &mut enabled,
142 &mut labels,
143 &key,
144 &pair[1],
145 builtin,
146 )?;
147 }
148 set_legend_for_axes(handle, axes_index, enabled, labels.as_deref(), Some(style))
149 .map_err(|err| map_figure_error(builtin, err))?;
150 Ok(())
151 }
152 PlotHandle::PlotChild(state) => {
153 for pair in args.chunks_exact(2) {
154 let key = property_name(&pair[0], builtin)?;
155 apply_plot_child_property(&state, &key, &pair[1], builtin)?;
156 }
157 Ok(())
158 }
159 }
160}
161
162pub fn parse_text_style_pairs(builtin: &'static str, args: &[Value]) -> BuiltinResult<TextStyle> {
163 if args.is_empty() {
164 return Ok(TextStyle::default());
165 }
166 if !args.len().is_multiple_of(2) {
167 return Err(plotting_error(
168 builtin,
169 format!("{builtin}: property/value arguments must come in pairs"),
170 ));
171 }
172 let mut style = TextStyle::default();
173 let mut text = None;
174 for pair in args.chunks_exact(2) {
175 let key = property_name(&pair[0], builtin)?;
176 apply_text_property(&mut text, &mut style, &key, &pair[1], builtin)?;
177 }
178 Ok(style)
179}
180
181pub fn validate_heatmap_property_pairs(
182 args: &[Value],
183 x_label_len: usize,
184 y_label_len: usize,
185 builtin: &'static str,
186) -> BuiltinResult<()> {
187 if args.is_empty() {
188 return Ok(());
189 }
190 if !args.len().is_multiple_of(2) {
191 return Err(plotting_error(
192 builtin,
193 format!("{builtin}: property/value arguments must come in pairs"),
194 ));
195 }
196 for pair in args.chunks_exact(2) {
197 let key = property_name(&pair[0], builtin)?;
198 match key.as_str() {
199 "title" => validate_axes_text_alias(PlotObjectKind::Title, &pair[1], builtin)?,
200 "xlabel" => validate_axes_text_alias(PlotObjectKind::XLabel, &pair[1], builtin)?,
201 "ylabel" => validate_axes_text_alias(PlotObjectKind::YLabel, &pair[1], builtin)?,
202 "colorbar" | "colorbarvisible" => {
203 value_as_bool(&pair[1]).ok_or_else(|| {
204 plotting_error(builtin, format!("{builtin}: Colorbar must be logical"))
205 })?;
206 }
207 "colormap" => {
208 let name = value_as_string(&pair[1]).ok_or_else(|| {
209 plotting_error(builtin, format!("{builtin}: Colormap must be a string"))
210 })?;
211 parse_colormap_name(&name, builtin)?;
212 }
213 "xdisplaylabels" => {
214 let labels = label_strings_from_value(&pair[1], builtin, "labels")?;
215 if labels.len() != x_label_len {
216 return Err(plotting_error(
217 builtin,
218 format!("{builtin}: XDisplayLabels length must match heatmap columns"),
219 ));
220 }
221 }
222 "ydisplaylabels" => {
223 let labels = label_strings_from_value(&pair[1], builtin, "labels")?;
224 if labels.len() != y_label_len {
225 return Err(plotting_error(
226 builtin,
227 format!("{builtin}: YDisplayLabels length must match heatmap rows"),
228 ));
229 }
230 }
231 other => {
232 return Err(plotting_error(
233 builtin,
234 format!("{builtin}: unsupported heatmap property `{other}`"),
235 ));
236 }
237 }
238 }
239 Ok(())
240}
241
242pub fn split_legend_style_pairs<'a>(
243 builtin: &'static str,
244 args: &'a [Value],
245) -> BuiltinResult<(&'a [Value], LegendStyle)> {
246 let mut style = LegendStyle::default();
247 let mut enabled = true;
248 let mut labels = None;
249 let mut split = args.len();
250 while split >= 2 {
251 let key_idx = split - 2;
252 let Ok(key) = property_name(&args[key_idx], builtin) else {
253 break;
254 };
255 if !matches!(
256 key.as_str(),
257 "location"
258 | "fontsize"
259 | "fontweight"
260 | "fontangle"
261 | "interpreter"
262 | "textcolor"
263 | "color"
264 | "visible"
265 | "string"
266 | "box"
267 | "orientation"
268 ) {
269 break;
270 }
271 apply_legend_property(
272 &mut style,
273 &mut enabled,
274 &mut labels,
275 &key,
276 &args[key_idx + 1],
277 builtin,
278 )?;
279 split -= 2;
280 }
281 Ok((&args[..split], style))
282}
283
284pub fn map_figure_error(
285 builtin: &'static str,
286 err: impl std::error::Error + Send + Sync + 'static,
287) -> crate::RuntimeError {
288 let message = format!("{builtin}: {err}");
289 plotting_error_with_source(builtin, message, err)
290}
291
292fn get_figure_property(
293 handle: FigureHandle,
294 property: Option<&str>,
295 builtin: &'static str,
296) -> BuiltinResult<Value> {
297 let axes = axes_handles_for_figure(handle).map_err(|err| map_figure_error(builtin, err))?;
298 let current_axes =
299 current_axes_handle_for_figure(handle).map_err(|err| map_figure_error(builtin, err))?;
300 let sg_title_handle =
301 super::state::encode_plot_object_handle(handle, 0, PlotObjectKind::SuperTitle);
302 let has_sg_title = figure_has_sg_title(handle);
303 match property.map(canonical_property_name) {
304 None => {
305 let mut st = StructValue::new();
306 st.insert("Handle", Value::Num(handle.as_u32() as f64));
307 st.insert("Number", Value::Num(handle.as_u32() as f64));
308 st.insert("Type", Value::String("figure".into()));
309 st.insert("CurrentAxes", Value::Num(current_axes));
310 let mut children = axes;
311 if has_sg_title {
312 children.push(sg_title_handle);
313 }
314 st.insert("Children", handles_value(children));
315 st.insert("Parent", Value::Num(f64::NAN));
316 st.insert("Name", Value::String(format!("Figure {}", handle.as_u32())));
317 st.insert("SGTitle", Value::Num(sg_title_handle));
318 Ok(Value::Struct(st))
319 }
320 Some("number") => Ok(Value::Num(handle.as_u32() as f64)),
321 Some("type") => Ok(Value::String("figure".into())),
322 Some("currentaxes") => Ok(Value::Num(current_axes)),
323 Some("children") => Ok(handles_value({
324 let mut children = axes;
325 if has_sg_title {
326 children.push(sg_title_handle);
327 }
328 children
329 })),
330 Some("parent") => Ok(Value::Num(f64::NAN)),
331 Some("name") => Ok(Value::String(format!("Figure {}", handle.as_u32()))),
332 Some("sgtitle") => Ok(Value::Num(sg_title_handle)),
333 Some(other) => Err(plotting_error(
334 builtin,
335 format!("{builtin}: unsupported figure property `{other}`"),
336 )),
337 }
338}
339
340fn get_axes_property(
341 handle: FigureHandle,
342 axes_index: usize,
343 property: Option<&str>,
344 builtin: &'static str,
345) -> BuiltinResult<Value> {
346 let meta =
347 axes_metadata_snapshot(handle, axes_index).map_err(|err| map_figure_error(builtin, err))?;
348 let axes =
349 axes_state_snapshot(handle, axes_index).map_err(|err| map_figure_error(builtin, err))?;
350 match property.map(canonical_property_name) {
351 None => {
352 let mut st = StructValue::new();
353 st.insert(
354 "Handle",
355 Value::Num(super::state::encode_axes_handle(handle, axes_index)),
356 );
357 st.insert("Figure", Value::Num(handle.as_u32() as f64));
358 st.insert("Rows", Value::Num(axes.rows as f64));
359 st.insert("Cols", Value::Num(axes.cols as f64));
360 st.insert("Index", Value::Num((axes_index + 1) as f64));
361 st.insert(
362 "Title",
363 Value::Num(super::state::encode_plot_object_handle(
364 handle,
365 axes_index,
366 PlotObjectKind::Title,
367 )),
368 );
369 st.insert(
370 "XLabel",
371 Value::Num(super::state::encode_plot_object_handle(
372 handle,
373 axes_index,
374 PlotObjectKind::XLabel,
375 )),
376 );
377 st.insert(
378 "YLabel",
379 Value::Num(super::state::encode_plot_object_handle(
380 handle,
381 axes_index,
382 PlotObjectKind::YLabel,
383 )),
384 );
385 st.insert(
386 "ZLabel",
387 Value::Num(super::state::encode_plot_object_handle(
388 handle,
389 axes_index,
390 PlotObjectKind::ZLabel,
391 )),
392 );
393 st.insert(
394 "Legend",
395 Value::Num(super::state::encode_plot_object_handle(
396 handle,
397 axes_index,
398 PlotObjectKind::Legend,
399 )),
400 );
401 st.insert("LegendVisible", Value::Bool(meta.legend_enabled));
402 st.insert("Type", Value::String("axes".into()));
403 st.insert("Parent", Value::Num(handle.as_u32() as f64));
404 st.insert(
405 "Children",
406 handles_value(vec![
407 super::state::encode_plot_object_handle(
408 handle,
409 axes_index,
410 PlotObjectKind::Title,
411 ),
412 super::state::encode_plot_object_handle(
413 handle,
414 axes_index,
415 PlotObjectKind::XLabel,
416 ),
417 super::state::encode_plot_object_handle(
418 handle,
419 axes_index,
420 PlotObjectKind::YLabel,
421 ),
422 super::state::encode_plot_object_handle(
423 handle,
424 axes_index,
425 PlotObjectKind::ZLabel,
426 ),
427 super::state::encode_plot_object_handle(
428 handle,
429 axes_index,
430 PlotObjectKind::Legend,
431 ),
432 ]),
433 );
434 st.insert("Grid", Value::Bool(meta.grid_enabled));
435 st.insert("Box", Value::Bool(meta.box_enabled));
436 st.insert("AxisEqual", Value::Bool(meta.axis_equal));
437 st.insert("Colorbar", Value::Bool(meta.colorbar_enabled));
438 st.insert(
439 "Colormap",
440 Value::String(format!("{:?}", meta.colormap).to_ascii_lowercase()),
441 );
442 st.insert("XLim", limit_value(meta.x_limits));
443 st.insert("YLim", limit_value(meta.y_limits));
444 st.insert("ZLim", limit_value(meta.z_limits));
445 st.insert("CLim", limit_value(meta.color_limits));
446 st.insert(
447 "XScale",
448 Value::String(if meta.x_log { "log" } else { "linear" }.into()),
449 );
450 st.insert(
451 "YScale",
452 Value::String(if meta.y_log { "log" } else { "linear" }.into()),
453 );
454 Ok(Value::Struct(st))
455 }
456 Some("title") => Ok(Value::Num(super::state::encode_plot_object_handle(
457 handle,
458 axes_index,
459 PlotObjectKind::Title,
460 ))),
461 Some("xlabel") => Ok(Value::Num(super::state::encode_plot_object_handle(
462 handle,
463 axes_index,
464 PlotObjectKind::XLabel,
465 ))),
466 Some("ylabel") => Ok(Value::Num(super::state::encode_plot_object_handle(
467 handle,
468 axes_index,
469 PlotObjectKind::YLabel,
470 ))),
471 Some("zlabel") => Ok(Value::Num(super::state::encode_plot_object_handle(
472 handle,
473 axes_index,
474 PlotObjectKind::ZLabel,
475 ))),
476 Some("legend") => Ok(Value::Num(super::state::encode_plot_object_handle(
477 handle,
478 axes_index,
479 PlotObjectKind::Legend,
480 ))),
481 Some("view") => {
482 let az = meta.view_azimuth_deg.unwrap_or(-37.5) as f64;
483 let el = meta.view_elevation_deg.unwrap_or(30.0) as f64;
484 Ok(Value::Tensor(runmat_builtins::Tensor {
485 rows: 1,
486 cols: 2,
487 shape: vec![1, 2],
488 data: vec![az, el],
489 dtype: runmat_builtins::NumericDType::F64,
490 }))
491 }
492 Some("grid") => Ok(Value::Bool(meta.grid_enabled)),
493 Some("box") => Ok(Value::Bool(meta.box_enabled)),
494 Some("axisequal") => Ok(Value::Bool(meta.axis_equal)),
495 Some("colorbar") => Ok(Value::Bool(meta.colorbar_enabled)),
496 Some("colormap") => Ok(Value::String(
497 format!("{:?}", meta.colormap).to_ascii_lowercase(),
498 )),
499 Some("xlim") => Ok(limit_value(meta.x_limits)),
500 Some("ylim") => Ok(limit_value(meta.y_limits)),
501 Some("zlim") => Ok(limit_value(meta.z_limits)),
502 Some("clim") => Ok(limit_value(meta.color_limits)),
503 Some("xscale") => Ok(Value::String(
504 if meta.x_log { "log" } else { "linear" }.into(),
505 )),
506 Some("yscale") => Ok(Value::String(
507 if meta.y_log { "log" } else { "linear" }.into(),
508 )),
509 Some("type") => Ok(Value::String("axes".into())),
510 Some("parent") => Ok(Value::Num(handle.as_u32() as f64)),
511 Some("legendvisible") => Ok(Value::Bool(meta.legend_enabled)),
512 Some("children") => Ok(handles_value(vec![
513 super::state::encode_plot_object_handle(handle, axes_index, PlotObjectKind::Title),
514 super::state::encode_plot_object_handle(handle, axes_index, PlotObjectKind::XLabel),
515 super::state::encode_plot_object_handle(handle, axes_index, PlotObjectKind::YLabel),
516 super::state::encode_plot_object_handle(handle, axes_index, PlotObjectKind::ZLabel),
517 super::state::encode_plot_object_handle(handle, axes_index, PlotObjectKind::Legend),
518 ])),
519 Some(other) => Err(plotting_error(
520 builtin,
521 format!("{builtin}: unsupported axes property `{other}`"),
522 )),
523 }
524}
525
526fn get_text_property(
527 handle: FigureHandle,
528 axes_index: usize,
529 kind: PlotObjectKind,
530 property: Option<&str>,
531 builtin: &'static str,
532) -> BuiltinResult<Value> {
533 let (text, style) = match kind {
534 PlotObjectKind::SuperTitle => {
535 let figure = super::state::clone_figure(handle)
536 .ok_or_else(|| plotting_error(builtin, format!("{builtin}: invalid figure")))?;
537 (figure.sg_title, figure.sg_title_style)
538 }
539 PlotObjectKind::Title
540 | PlotObjectKind::XLabel
541 | PlotObjectKind::YLabel
542 | PlotObjectKind::ZLabel => {
543 let meta = axes_metadata_snapshot(handle, axes_index)
544 .map_err(|err| map_figure_error(builtin, err))?;
545 match kind {
546 PlotObjectKind::Title => (meta.title, meta.title_style),
547 PlotObjectKind::XLabel => (meta.x_label, meta.x_label_style),
548 PlotObjectKind::YLabel => (meta.y_label, meta.y_label_style),
549 PlotObjectKind::ZLabel => (meta.z_label, meta.z_label_style),
550 PlotObjectKind::Legend | PlotObjectKind::SuperTitle => unreachable!(),
551 }
552 }
553 PlotObjectKind::Legend => unreachable!(),
554 };
555 let parent = if matches!(kind, PlotObjectKind::SuperTitle) {
556 Value::Num(handle.as_u32() as f64)
557 } else {
558 Value::Num(super::state::encode_axes_handle(handle, axes_index))
559 };
560 match property.map(canonical_property_name) {
561 None => {
562 let mut st = StructValue::new();
563 st.insert("Type", Value::String("text".into()));
564 st.insert("Parent", parent.clone());
565 st.insert("Children", handles_value(Vec::new()));
566 st.insert("String", text_value(text));
567 st.insert("Visible", Value::Bool(style.visible));
568 if let Some(size) = style.font_size {
569 st.insert("FontSize", Value::Num(size as f64));
570 }
571 if let Some(weight) = style.font_weight {
572 st.insert("FontWeight", Value::String(weight));
573 }
574 if let Some(angle) = style.font_angle {
575 st.insert("FontAngle", Value::String(angle));
576 }
577 if let Some(interpreter) = style.interpreter {
578 st.insert("Interpreter", Value::String(interpreter));
579 }
580 if let Some(color) = style.color {
581 st.insert("Color", Value::String(color_to_short_name(color)));
582 }
583 Ok(Value::Struct(st))
584 }
585 Some("type") => Ok(Value::String("text".into())),
586 Some("parent") => Ok(parent),
587 Some("children") => Ok(handles_value(Vec::new())),
588 Some("string") => Ok(text_value(text)),
589 Some("visible") => Ok(Value::Bool(style.visible)),
590 Some("fontsize") => Ok(style
591 .font_size
592 .map(|v| Value::Num(v as f64))
593 .unwrap_or(Value::Num(f64::NAN))),
594 Some("fontweight") => Ok(style
595 .font_weight
596 .map(Value::String)
597 .unwrap_or_else(|| Value::String(String::new()))),
598 Some("fontangle") => Ok(style
599 .font_angle
600 .map(Value::String)
601 .unwrap_or_else(|| Value::String(String::new()))),
602 Some("interpreter") => Ok(style
603 .interpreter
604 .map(Value::String)
605 .unwrap_or_else(|| Value::String(String::new()))),
606 Some("color") => Ok(style
607 .color
608 .map(|c| Value::String(color_to_short_name(c)))
609 .unwrap_or_else(|| Value::String(String::new()))),
610 Some(other) => Err(plotting_error(
611 builtin,
612 format!("{builtin}: unsupported text property `{other}`"),
613 )),
614 }
615}
616
617fn get_legend_property(
618 handle: FigureHandle,
619 axes_index: usize,
620 property: Option<&str>,
621 builtin: &'static str,
622) -> BuiltinResult<Value> {
623 let meta =
624 axes_metadata_snapshot(handle, axes_index).map_err(|err| map_figure_error(builtin, err))?;
625 let entries = legend_entries_snapshot(handle, axes_index)
626 .map_err(|err| map_figure_error(builtin, err))?;
627 match property.map(canonical_property_name) {
628 None => {
629 let mut st = StructValue::new();
630 st.insert("Type", Value::String("legend".into()));
631 st.insert(
632 "Parent",
633 Value::Num(super::state::encode_axes_handle(handle, axes_index)),
634 );
635 st.insert("Children", handles_value(Vec::new()));
636 st.insert(
637 "Visible",
638 Value::Bool(meta.legend_enabled && meta.legend_style.visible),
639 );
640 st.insert(
641 "String",
642 legend_labels_value(entries.iter().map(|e| e.label.clone()).collect()),
643 );
644 if let Some(location) = meta.legend_style.location {
645 st.insert("Location", Value::String(location));
646 }
647 if let Some(size) = meta.legend_style.font_size {
648 st.insert("FontSize", Value::Num(size as f64));
649 }
650 if let Some(weight) = meta.legend_style.font_weight {
651 st.insert("FontWeight", Value::String(weight));
652 }
653 if let Some(angle) = meta.legend_style.font_angle {
654 st.insert("FontAngle", Value::String(angle));
655 }
656 if let Some(interpreter) = meta.legend_style.interpreter {
657 st.insert("Interpreter", Value::String(interpreter));
658 }
659 if let Some(box_visible) = meta.legend_style.box_visible {
660 st.insert("Box", Value::Bool(box_visible));
661 }
662 if let Some(orientation) = meta.legend_style.orientation {
663 st.insert("Orientation", Value::String(orientation));
664 }
665 if let Some(color) = meta.legend_style.text_color {
666 st.insert("TextColor", Value::String(color_to_short_name(color)));
667 }
668 Ok(Value::Struct(st))
669 }
670 Some("visible") => Ok(Value::Bool(
671 meta.legend_enabled && meta.legend_style.visible,
672 )),
673 Some("type") => Ok(Value::String("legend".into())),
674 Some("parent") => Ok(Value::Num(super::state::encode_axes_handle(
675 handle, axes_index,
676 ))),
677 Some("children") => Ok(handles_value(Vec::new())),
678 Some("string") => Ok(legend_labels_value(
679 entries.into_iter().map(|e| e.label).collect(),
680 )),
681 Some("location") => Ok(meta
682 .legend_style
683 .location
684 .map(Value::String)
685 .unwrap_or_else(|| Value::String(String::new()))),
686 Some("fontsize") => Ok(meta
687 .legend_style
688 .font_size
689 .map(|v| Value::Num(v as f64))
690 .unwrap_or(Value::Num(f64::NAN))),
691 Some("fontweight") => Ok(meta
692 .legend_style
693 .font_weight
694 .map(Value::String)
695 .unwrap_or_else(|| Value::String(String::new()))),
696 Some("fontangle") => Ok(meta
697 .legend_style
698 .font_angle
699 .map(Value::String)
700 .unwrap_or_else(|| Value::String(String::new()))),
701 Some("interpreter") => Ok(meta
702 .legend_style
703 .interpreter
704 .map(Value::String)
705 .unwrap_or_else(|| Value::String(String::new()))),
706 Some("box") => Ok(meta
707 .legend_style
708 .box_visible
709 .map(Value::Bool)
710 .unwrap_or(Value::Bool(true))),
711 Some("orientation") => Ok(meta
712 .legend_style
713 .orientation
714 .map(Value::String)
715 .unwrap_or_else(|| Value::String(String::new()))),
716 Some("textcolor") | Some("color") => Ok(meta
717 .legend_style
718 .text_color
719 .map(|c| Value::String(color_to_short_name(c)))
720 .unwrap_or_else(|| Value::String(String::new()))),
721 Some(other) => Err(plotting_error(
722 builtin,
723 format!("{builtin}: unsupported legend property `{other}`"),
724 )),
725 }
726}
727
728fn property_name(value: &Value, builtin: &'static str) -> BuiltinResult<String> {
729 value_as_string(value)
730 .map(|s| canonical_property_name(s.trim()).to_string())
731 .ok_or_else(|| {
732 plotting_error(
733 builtin,
734 format!("{builtin}: property names must be strings"),
735 )
736 })
737}
738
739fn canonical_property_name(name: &str) -> &str {
740 match name.to_ascii_lowercase().as_str() {
741 "textcolor" => "textcolor",
742 "color" => "color",
743 "fontsize" => "fontsize",
744 "fontweight" => "fontweight",
745 "fontangle" => "fontangle",
746 "interpreter" => "interpreter",
747 "visible" => "visible",
748 "location" => "location",
749 "box" => "box",
750 "orientation" => "orientation",
751 "string" => "string",
752 "title" => "title",
753 "xlabel" => "xlabel",
754 "ylabel" => "ylabel",
755 "zlabel" => "zlabel",
756 "view" => "view",
757 "grid" => "grid",
758 "axisequal" => "axisequal",
759 "colorbar" => "colorbar",
760 "colorbarvisible" => "colorbarvisible",
761 "colormap" => "colormap",
762 "xdisplaylabels" => "xdisplaylabels",
763 "ydisplaylabels" => "ydisplaylabels",
764 "colordata" | "cdata" => "colordata",
765 "xlim" => "xlim",
766 "ylim" => "ylim",
767 "zlim" => "zlim",
768 "clim" => "clim",
769 "caxis" => "clim",
770 "xscale" => "xscale",
771 "yscale" => "yscale",
772 "currentaxes" => "currentaxes",
773 "sgtitle" | "supertitle" => "sgtitle",
774 "children" => "children",
775 "parent" => "parent",
776 "type" => "type",
777 "number" => "number",
778 "name" => "name",
779 "legend" => "legend",
780 "legendvisible" => "legendvisible",
781 other => Box::leak(other.to_string().into_boxed_str()),
782 }
783}
784
785fn apply_text_property(
786 text: &mut Option<String>,
787 style: &mut TextStyle,
788 key: &str,
789 value: &Value,
790 builtin: &'static str,
791) -> BuiltinResult<()> {
792 let opts = LineStyleParseOptions::generic(builtin);
793 match key {
794 "string" => {
795 *text = Some(value_as_text_string(value).ok_or_else(|| {
796 plotting_error(builtin, format!("{builtin}: String must be text"))
797 })?);
798 }
799 "color" => style.color = Some(parse_color_value(&opts, value)?),
800 "fontsize" => {
801 style.font_size = Some(value_as_f64(value).ok_or_else(|| {
802 plotting_error(builtin, format!("{builtin}: FontSize must be numeric"))
803 })? as f32)
804 }
805 "fontweight" => {
806 style.font_weight = Some(value_as_string(value).ok_or_else(|| {
807 plotting_error(builtin, format!("{builtin}: FontWeight must be a string"))
808 })?)
809 }
810 "fontangle" => {
811 style.font_angle = Some(value_as_string(value).ok_or_else(|| {
812 plotting_error(builtin, format!("{builtin}: FontAngle must be a string"))
813 })?)
814 }
815 "interpreter" => {
816 style.interpreter = Some(value_as_string(value).ok_or_else(|| {
817 plotting_error(builtin, format!("{builtin}: Interpreter must be a string"))
818 })?)
819 }
820 "visible" => {
821 style.visible = value_as_bool(value).ok_or_else(|| {
822 plotting_error(builtin, format!("{builtin}: Visible must be logical"))
823 })?
824 }
825 other => {
826 return Err(plotting_error(
827 builtin,
828 format!("{builtin}: unsupported property `{other}`"),
829 ))
830 }
831 }
832 Ok(())
833}
834
835fn apply_legend_property(
836 style: &mut LegendStyle,
837 enabled: &mut bool,
838 labels: &mut Option<Vec<String>>,
839 key: &str,
840 value: &Value,
841 builtin: &'static str,
842) -> BuiltinResult<()> {
843 let opts = LineStyleParseOptions::generic(builtin);
844 match key {
845 "string" => *labels = Some(collect_label_strings(builtin, std::slice::from_ref(value))?),
846 "location" => {
847 style.location = Some(
848 value_as_string(value)
849 .ok_or_else(|| plotting_error(builtin, "legend: Location must be a string"))?,
850 )
851 }
852 "fontsize" => {
853 style.font_size = Some(
854 value_as_f64(value)
855 .ok_or_else(|| plotting_error(builtin, "legend: FontSize must be numeric"))?
856 as f32,
857 )
858 }
859 "fontweight" => {
860 style.font_weight =
861 Some(value_as_string(value).ok_or_else(|| {
862 plotting_error(builtin, "legend: FontWeight must be a string")
863 })?)
864 }
865 "fontangle" => {
866 style.font_angle = Some(
867 value_as_string(value)
868 .ok_or_else(|| plotting_error(builtin, "legend: FontAngle must be a string"))?,
869 )
870 }
871 "interpreter" => {
872 style.interpreter =
873 Some(value_as_string(value).ok_or_else(|| {
874 plotting_error(builtin, "legend: Interpreter must be a string")
875 })?)
876 }
877 "textcolor" | "color" => style.text_color = Some(parse_color_value(&opts, value)?),
878 "visible" => {
879 let visible = value_as_bool(value)
880 .ok_or_else(|| plotting_error(builtin, "legend: Visible must be logical"))?;
881 style.visible = visible;
882 *enabled = visible;
883 }
884 "box" => {
885 style.box_visible = Some(
886 value_as_bool(value)
887 .ok_or_else(|| plotting_error(builtin, "legend: Box must be logical"))?,
888 )
889 }
890 "orientation" => {
891 style.orientation =
892 Some(value_as_string(value).ok_or_else(|| {
893 plotting_error(builtin, "legend: Orientation must be a string")
894 })?)
895 }
896 other => {
897 return Err(plotting_error(
898 builtin,
899 format!("{builtin}: unsupported property `{other}`"),
900 ))
901 }
902 }
903 Ok(())
904}
905
906fn apply_axes_property(
907 handle: FigureHandle,
908 axes_index: usize,
909 key: &str,
910 value: &Value,
911 builtin: &'static str,
912) -> BuiltinResult<()> {
913 match key {
914 "legendvisible" => {
915 let visible = value_as_bool(value).ok_or_else(|| {
916 plotting_error(builtin, format!("{builtin}: LegendVisible must be logical"))
917 })?;
918 set_legend_for_axes(handle, axes_index, visible, None, None)
919 .map_err(|err| map_figure_error(builtin, err))?;
920 Ok(())
921 }
922 "title" => apply_axes_text_alias(handle, axes_index, PlotObjectKind::Title, value, builtin),
923 "xlabel" => {
924 apply_axes_text_alias(handle, axes_index, PlotObjectKind::XLabel, value, builtin)
925 }
926 "ylabel" => {
927 apply_axes_text_alias(handle, axes_index, PlotObjectKind::YLabel, value, builtin)
928 }
929 "zlabel" => {
930 apply_axes_text_alias(handle, axes_index, PlotObjectKind::ZLabel, value, builtin)
931 }
932 "view" => {
933 let tensor = runmat_builtins::Tensor::try_from(value)
934 .map_err(|e| plotting_error(builtin, format!("{builtin}: {e}")))?;
935 if tensor.data.len() != 2 || !tensor.data[0].is_finite() || !tensor.data[1].is_finite()
936 {
937 return Err(plotting_error(
938 builtin,
939 format!("{builtin}: View must be a 2-element finite numeric vector"),
940 ));
941 }
942 crate::builtins::plotting::state::set_view_for_axes(
943 handle,
944 axes_index,
945 tensor.data[0] as f32,
946 tensor.data[1] as f32,
947 )
948 .map_err(|err| map_figure_error(builtin, err))?;
949 Ok(())
950 }
951 "grid" => {
952 let enabled = value_as_bool(value).ok_or_else(|| {
953 plotting_error(builtin, format!("{builtin}: Grid must be logical"))
954 })?;
955 crate::builtins::plotting::state::set_grid_enabled_for_axes(
956 handle, axes_index, enabled,
957 )
958 .map_err(|err| map_figure_error(builtin, err))?;
959 Ok(())
960 }
961 "box" => {
962 let enabled = value_as_bool(value).ok_or_else(|| {
963 plotting_error(builtin, format!("{builtin}: Box must be logical"))
964 })?;
965 crate::builtins::plotting::state::set_box_enabled_for_axes(handle, axes_index, enabled)
966 .map_err(|err| map_figure_error(builtin, err))?;
967 Ok(())
968 }
969 "axisequal" => {
970 let enabled = value_as_bool(value).ok_or_else(|| {
971 plotting_error(builtin, format!("{builtin}: AxisEqual must be logical"))
972 })?;
973 crate::builtins::plotting::state::set_axis_equal_for_axes(handle, axes_index, enabled)
974 .map_err(|err| map_figure_error(builtin, err))?;
975 Ok(())
976 }
977 "colorbar" => {
978 let enabled = value_as_bool(value).ok_or_else(|| {
979 plotting_error(builtin, format!("{builtin}: Colorbar must be logical"))
980 })?;
981 crate::builtins::plotting::state::set_colorbar_enabled_for_axes(
982 handle, axes_index, enabled,
983 )
984 .map_err(|err| map_figure_error(builtin, err))?;
985 Ok(())
986 }
987 "colormap" => {
988 let name = value_as_string(value).ok_or_else(|| {
989 plotting_error(builtin, format!("{builtin}: Colormap must be a string"))
990 })?;
991 let cmap = parse_colormap_name(&name, builtin)?;
992 crate::builtins::plotting::state::set_colormap_for_axes(handle, axes_index, cmap)
993 .map_err(|err| map_figure_error(builtin, err))?;
994 Ok(())
995 }
996 "xlim" => {
997 let limits = limits_from_optional_value(value, builtin)?;
998 let meta = axes_metadata_snapshot(handle, axes_index)
999 .map_err(|err| map_figure_error(builtin, err))?;
1000 crate::builtins::plotting::state::set_axis_limits_for_axes(
1001 handle,
1002 axes_index,
1003 limits,
1004 meta.y_limits,
1005 )
1006 .map_err(|err| map_figure_error(builtin, err))?;
1007 Ok(())
1008 }
1009 "ylim" => {
1010 let limits = limits_from_optional_value(value, builtin)?;
1011 let meta = axes_metadata_snapshot(handle, axes_index)
1012 .map_err(|err| map_figure_error(builtin, err))?;
1013 crate::builtins::plotting::state::set_axis_limits_for_axes(
1014 handle,
1015 axes_index,
1016 meta.x_limits,
1017 limits,
1018 )
1019 .map_err(|err| map_figure_error(builtin, err))?;
1020 Ok(())
1021 }
1022 "zlim" => {
1023 let limits = limits_from_optional_value(value, builtin)?;
1024 crate::builtins::plotting::state::set_z_limits_for_axes(handle, axes_index, limits)
1025 .map_err(|err| map_figure_error(builtin, err))?;
1026 Ok(())
1027 }
1028 "clim" => {
1029 let limits = limits_from_optional_value(value, builtin)?;
1030 crate::builtins::plotting::state::set_color_limits_for_axes(handle, axes_index, limits)
1031 .map_err(|err| map_figure_error(builtin, err))?;
1032 Ok(())
1033 }
1034 "xscale" => {
1035 let mode = value_as_string(value).ok_or_else(|| {
1036 plotting_error(builtin, format!("{builtin}: XScale must be a string"))
1037 })?;
1038 let meta = axes_metadata_snapshot(handle, axes_index)
1039 .map_err(|err| map_figure_error(builtin, err))?;
1040 crate::builtins::plotting::state::set_log_modes_for_axes(
1041 handle,
1042 axes_index,
1043 mode.trim().eq_ignore_ascii_case("log"),
1044 meta.y_log,
1045 )
1046 .map_err(|err| map_figure_error(builtin, err))?;
1047 Ok(())
1048 }
1049 "yscale" => {
1050 let mode = value_as_string(value).ok_or_else(|| {
1051 plotting_error(builtin, format!("{builtin}: YScale must be a string"))
1052 })?;
1053 let meta = axes_metadata_snapshot(handle, axes_index)
1054 .map_err(|err| map_figure_error(builtin, err))?;
1055 crate::builtins::plotting::state::set_log_modes_for_axes(
1056 handle,
1057 axes_index,
1058 meta.x_log,
1059 mode.trim().eq_ignore_ascii_case("log"),
1060 )
1061 .map_err(|err| map_figure_error(builtin, err))?;
1062 Ok(())
1063 }
1064 other => Err(plotting_error(
1065 builtin,
1066 format!("{builtin}: unsupported axes property `{other}`"),
1067 )),
1068 }
1069}
1070
1071fn apply_figure_property(
1072 figure_handle: FigureHandle,
1073 key: &str,
1074 value: &Value,
1075 builtin: &'static str,
1076) -> BuiltinResult<()> {
1077 match key {
1078 "currentaxes" => {
1079 let resolved = resolve_plot_handle(value, builtin)?;
1080 let PlotHandle::Axes(fig, axes_index) = resolved else {
1081 return Err(plotting_error(
1082 builtin,
1083 format!("{builtin}: CurrentAxes must be an axes handle"),
1084 ));
1085 };
1086 if fig != figure_handle {
1087 return Err(plotting_error(
1088 builtin,
1089 format!("{builtin}: CurrentAxes must belong to the target figure"),
1090 ));
1091 }
1092 select_axes_for_figure(figure_handle, axes_index)
1093 .map_err(|err| map_figure_error(builtin, err))?;
1094 Ok(())
1095 }
1096 "sgtitle" => {
1097 apply_figure_text_alias(figure_handle, PlotObjectKind::SuperTitle, value, builtin)
1098 }
1099 other => Err(plotting_error(
1100 builtin,
1101 format!("{builtin}: unsupported figure property `{other}`"),
1102 )),
1103 }
1104}
1105
1106fn get_histogram_property(
1107 hist: &super::state::HistogramHandleState,
1108 property: Option<&str>,
1109 builtin: &'static str,
1110) -> BuiltinResult<Value> {
1111 let normalized =
1112 apply_histogram_normalization(&hist.raw_counts, &hist.bin_edges, &hist.normalization);
1113 match property.map(canonical_property_name) {
1114 None => {
1115 let mut st = StructValue::new();
1116 st.insert("Type", Value::String("histogram".into()));
1117 st.insert(
1118 "Parent",
1119 Value::Num(super::state::encode_axes_handle(
1120 hist.figure,
1121 hist.axes_index,
1122 )),
1123 );
1124 st.insert("Children", handles_value(Vec::new()));
1125 st.insert("BinEdges", tensor_from_vec(hist.bin_edges.clone()));
1126 st.insert("BinCounts", tensor_from_vec(normalized));
1127 st.insert("Normalization", Value::String(hist.normalization.clone()));
1128 st.insert("NumBins", Value::Num(hist.raw_counts.len() as f64));
1129 Ok(Value::Struct(st))
1130 }
1131 Some("type") => Ok(Value::String("histogram".into())),
1132 Some("parent") => Ok(Value::Num(super::state::encode_axes_handle(
1133 hist.figure,
1134 hist.axes_index,
1135 ))),
1136 Some("children") => Ok(handles_value(Vec::new())),
1137 Some("binedges") => Ok(tensor_from_vec(hist.bin_edges.clone())),
1138 Some("bincounts") => Ok(tensor_from_vec(normalized)),
1139 Some("normalization") => Ok(Value::String(hist.normalization.clone())),
1140 Some("numbins") => Ok(Value::Num(hist.raw_counts.len() as f64)),
1141 Some(other) => Err(plotting_error(
1142 builtin,
1143 format!("{builtin}: unsupported histogram property `{other}`"),
1144 )),
1145 }
1146}
1147
1148fn get_plot_child_property(
1149 state: &super::state::PlotChildHandleState,
1150 property: Option<&str>,
1151 builtin: &'static str,
1152) -> BuiltinResult<Value> {
1153 match state {
1154 super::state::PlotChildHandleState::Histogram(hist) => {
1155 get_histogram_property(hist, property, builtin)
1156 }
1157 super::state::PlotChildHandleState::Line(plot) => {
1158 get_line_property(plot, property, builtin)
1159 }
1160 super::state::PlotChildHandleState::Scatter(plot) => {
1161 get_scatter_property(plot, property, builtin)
1162 }
1163 super::state::PlotChildHandleState::Bar(plot) => get_bar_property(plot, property, builtin),
1164 super::state::PlotChildHandleState::Stem(stem) => {
1165 get_stem_property(stem, property, builtin)
1166 }
1167 super::state::PlotChildHandleState::ErrorBar(errorbar) => {
1168 get_errorbar_property(errorbar, property, builtin)
1169 }
1170 super::state::PlotChildHandleState::Stairs(plot) => {
1171 get_stairs_property(plot, property, builtin)
1172 }
1173 super::state::PlotChildHandleState::Quiver(quiver) => {
1174 get_quiver_property(quiver, property, builtin)
1175 }
1176 super::state::PlotChildHandleState::Image(image) => {
1177 get_image_property(image, property, builtin)
1178 }
1179 super::state::PlotChildHandleState::Heatmap(heatmap) => {
1180 get_heatmap_property(heatmap, property, builtin)
1181 }
1182 super::state::PlotChildHandleState::Area(area) => {
1183 get_area_property(area, property, builtin)
1184 }
1185 super::state::PlotChildHandleState::Surface(plot) => {
1186 get_surface_property(plot, property, builtin)
1187 }
1188 super::state::PlotChildHandleState::Patch(plot) => {
1189 get_patch_property(plot, property, builtin)
1190 }
1191 super::state::PlotChildHandleState::Line3(plot) => {
1192 get_line3_property(plot, property, builtin)
1193 }
1194 super::state::PlotChildHandleState::Scatter3(plot) => {
1195 get_scatter3_property(plot, property, builtin)
1196 }
1197 super::state::PlotChildHandleState::Contour(plot) => {
1198 get_contour_property(plot, property, builtin)
1199 }
1200 super::state::PlotChildHandleState::ContourFill(plot) => {
1201 get_contour_fill_property(plot, property, builtin)
1202 }
1203 super::state::PlotChildHandleState::ReferenceLine(plot) => {
1204 get_reference_line_property(plot, property, builtin)
1205 }
1206 super::state::PlotChildHandleState::Pie(plot) => get_pie_property(plot, property, builtin),
1207 super::state::PlotChildHandleState::Text(text) => {
1208 get_world_text_property(text, property, builtin)
1209 }
1210 }
1211}
1212
1213fn apply_plot_child_property(
1214 state: &super::state::PlotChildHandleState,
1215 key: &str,
1216 value: &Value,
1217 builtin: &'static str,
1218) -> BuiltinResult<()> {
1219 match state {
1220 super::state::PlotChildHandleState::Histogram(hist) => {
1221 apply_histogram_property(hist, key, value, builtin)
1222 }
1223 super::state::PlotChildHandleState::Line(plot) => {
1224 apply_line_property(plot, key, value, builtin)
1225 }
1226 super::state::PlotChildHandleState::Scatter(plot) => {
1227 apply_scatter_property(plot, key, value, builtin)
1228 }
1229 super::state::PlotChildHandleState::Bar(plot) => {
1230 apply_bar_property(plot, key, value, builtin)
1231 }
1232 super::state::PlotChildHandleState::Stem(stem) => {
1233 apply_stem_property(stem, key, value, builtin)
1234 }
1235 super::state::PlotChildHandleState::ErrorBar(errorbar) => {
1236 apply_errorbar_property(errorbar, key, value, builtin)
1237 }
1238 super::state::PlotChildHandleState::Stairs(plot) => {
1239 apply_stairs_property(plot, key, value, builtin)
1240 }
1241 super::state::PlotChildHandleState::Quiver(quiver) => {
1242 apply_quiver_property(quiver, key, value, builtin)
1243 }
1244 super::state::PlotChildHandleState::Image(image) => {
1245 apply_image_property(image, key, value, builtin)
1246 }
1247 super::state::PlotChildHandleState::Heatmap(heatmap) => {
1248 apply_heatmap_property(heatmap, key, value, builtin)
1249 }
1250 super::state::PlotChildHandleState::Area(area) => {
1251 apply_area_property(area, key, value, builtin)
1252 }
1253 super::state::PlotChildHandleState::Surface(plot) => {
1254 apply_surface_property(plot, key, value, builtin)
1255 }
1256 super::state::PlotChildHandleState::Patch(plot) => {
1257 apply_patch_property(plot, key, value, builtin)
1258 }
1259 super::state::PlotChildHandleState::Line3(plot) => {
1260 apply_line3_property(plot, key, value, builtin)
1261 }
1262 super::state::PlotChildHandleState::Scatter3(plot) => {
1263 apply_scatter3_property(plot, key, value, builtin)
1264 }
1265 super::state::PlotChildHandleState::Contour(plot) => {
1266 apply_contour_property(plot, key, value, builtin)
1267 }
1268 super::state::PlotChildHandleState::ContourFill(plot) => {
1269 apply_contour_fill_property(plot, key, value, builtin)
1270 }
1271 super::state::PlotChildHandleState::ReferenceLine(plot) => {
1272 apply_reference_line_property(plot, key, value, builtin)
1273 }
1274 super::state::PlotChildHandleState::Pie(plot) => {
1275 apply_pie_property(plot, key, value, builtin)
1276 }
1277 super::state::PlotChildHandleState::Text(text) => {
1278 apply_world_text_property(text, key, value, builtin)
1279 }
1280 }
1281}
1282
1283fn child_parent_handle(figure: FigureHandle, axes_index: usize) -> Value {
1284 Value::Num(super::state::encode_axes_handle(figure, axes_index))
1285}
1286
1287fn child_base_struct(kind: &str, figure: FigureHandle, axes_index: usize) -> StructValue {
1288 let mut st = StructValue::new();
1289 st.insert("Type", Value::String(kind.into()));
1290 st.insert("Parent", child_parent_handle(figure, axes_index));
1291 st.insert("Children", handles_value(Vec::new()));
1292 st
1293}
1294
1295fn text_position_value(position: glam::Vec3) -> Value {
1296 Value::Tensor(Tensor {
1297 rows: 1,
1298 cols: 3,
1299 shape: vec![1, 3],
1300 data: vec![position.x as f64, position.y as f64, position.z as f64],
1301 dtype: runmat_builtins::NumericDType::F64,
1302 })
1303}
1304
1305fn parse_text_position(value: &Value, builtin: &'static str) -> BuiltinResult<glam::Vec3> {
1306 match value {
1307 Value::Tensor(t) if t.data.len() == 2 || t.data.len() == 3 => Ok(glam::Vec3::new(
1308 t.data[0] as f32,
1309 t.data[1] as f32,
1310 t.data.get(2).copied().unwrap_or(0.0) as f32,
1311 )),
1312 _ => Err(plotting_error(
1313 builtin,
1314 format!("{builtin}: Position must be a 2-element or 3-element vector"),
1315 )),
1316 }
1317}
1318
1319fn get_world_text_property(
1320 handle: &super::state::TextAnnotationHandleState,
1321 property: Option<&str>,
1322 builtin: &'static str,
1323) -> BuiltinResult<Value> {
1324 let figure = super::state::clone_figure(handle.figure)
1325 .ok_or_else(|| plotting_error(builtin, format!("{builtin}: invalid text figure")))?;
1326 let annotation = figure
1327 .axes_text_annotation(handle.axes_index, handle.annotation_index)
1328 .cloned()
1329 .ok_or_else(|| plotting_error(builtin, format!("{builtin}: invalid text handle")))?;
1330 match property.map(canonical_property_name) {
1331 None => {
1332 let mut st = child_base_struct("text", handle.figure, handle.axes_index);
1333 st.insert("String", Value::String(annotation.text.clone()));
1334 st.insert("Position", text_position_value(annotation.position));
1335 if let Some(weight) = annotation.style.font_weight.clone() {
1336 st.insert("FontWeight", Value::String(weight));
1337 }
1338 if let Some(angle) = annotation.style.font_angle.clone() {
1339 st.insert("FontAngle", Value::String(angle));
1340 }
1341 if let Some(interpreter) = annotation.style.interpreter.clone() {
1342 st.insert("Interpreter", Value::String(interpreter));
1343 }
1344 if let Some(color) = annotation.style.color {
1345 st.insert("Color", Value::String(color_to_short_name(color)));
1346 }
1347 if let Some(font_size) = annotation.style.font_size {
1348 st.insert("FontSize", Value::Num(font_size as f64));
1349 }
1350 st.insert("Visible", Value::Bool(annotation.style.visible));
1351 Ok(Value::Struct(st))
1352 }
1353 Some("type") => Ok(Value::String("text".into())),
1354 Some("parent") => Ok(child_parent_handle(handle.figure, handle.axes_index)),
1355 Some("children") => Ok(handles_value(Vec::new())),
1356 Some("string") => Ok(Value::String(annotation.text)),
1357 Some("position") => Ok(text_position_value(annotation.position)),
1358 Some("fontweight") => Ok(annotation
1359 .style
1360 .font_weight
1361 .map(Value::String)
1362 .unwrap_or_else(|| Value::String(String::new()))),
1363 Some("fontangle") => Ok(annotation
1364 .style
1365 .font_angle
1366 .map(Value::String)
1367 .unwrap_or_else(|| Value::String(String::new()))),
1368 Some("interpreter") => Ok(annotation
1369 .style
1370 .interpreter
1371 .map(Value::String)
1372 .unwrap_or_else(|| Value::String(String::new()))),
1373 Some("color") => Ok(annotation
1374 .style
1375 .color
1376 .map(|c| Value::String(color_to_short_name(c)))
1377 .unwrap_or_else(|| Value::String(String::new()))),
1378 Some("fontsize") => Ok(Value::Num(
1379 annotation.style.font_size.unwrap_or_default() as f64
1380 )),
1381 Some("visible") => Ok(Value::Bool(annotation.style.visible)),
1382 Some(other) => Err(plotting_error(
1383 builtin,
1384 format!("{builtin}: unsupported text property `{other}`"),
1385 )),
1386 }
1387}
1388
1389fn apply_world_text_property(
1390 handle: &super::state::TextAnnotationHandleState,
1391 key: &str,
1392 value: &Value,
1393 builtin: &'static str,
1394) -> BuiltinResult<()> {
1395 let figure = super::state::clone_figure(handle.figure)
1396 .ok_or_else(|| plotting_error(builtin, format!("{builtin}: invalid text figure")))?;
1397 let annotation = figure
1398 .axes_text_annotation(handle.axes_index, handle.annotation_index)
1399 .cloned()
1400 .ok_or_else(|| plotting_error(builtin, format!("{builtin}: invalid text handle")))?;
1401 let mut text = None;
1402 let mut position = None;
1403 let mut style = annotation.style;
1404 match canonical_property_name(key) {
1405 "string" => {
1406 text = Some(value_as_text_string(value).ok_or_else(|| {
1407 plotting_error(builtin, format!("{builtin}: String must be text"))
1408 })?);
1409 }
1410 "position" => position = Some(parse_text_position(value, builtin)?),
1411 other => apply_text_property(&mut text, &mut style, other, value, builtin)?,
1412 }
1413 set_text_annotation_properties_for_axes(
1414 handle.figure,
1415 handle.axes_index,
1416 handle.annotation_index,
1417 text,
1418 position,
1419 Some(style),
1420 )
1421 .map_err(|err| map_figure_error(builtin, err))?;
1422 Ok(())
1423}
1424
1425fn get_simple_plot(
1426 plot: &super::state::SimplePlotHandleState,
1427 builtin: &'static str,
1428) -> BuiltinResult<runmat_plot::plots::figure::PlotElement> {
1429 let figure = super::state::clone_figure(plot.figure)
1430 .ok_or_else(|| plotting_error(builtin, format!("{builtin}: invalid plot figure")))?;
1431 let resolved = figure
1432 .plots()
1433 .nth(plot.plot_index)
1434 .cloned()
1435 .ok_or_else(|| plotting_error(builtin, format!("{builtin}: invalid plot handle")))?;
1436 Ok(resolved)
1437}
1438
1439fn get_line_property(
1440 line_handle: &super::state::SimplePlotHandleState,
1441 property: Option<&str>,
1442 builtin: &'static str,
1443) -> BuiltinResult<Value> {
1444 let plot = get_simple_plot(line_handle, builtin)?;
1445 let runmat_plot::plots::figure::PlotElement::Line(line) = plot else {
1446 return Err(plotting_error(
1447 builtin,
1448 format!("{builtin}: invalid line handle"),
1449 ));
1450 };
1451 match property.map(canonical_property_name) {
1452 None => {
1453 let mut st = child_base_struct("line", line_handle.figure, line_handle.axes_index);
1454 st.insert("XData", tensor_from_vec(line.x_data.clone()));
1455 st.insert("YData", tensor_from_vec(line.y_data.clone()));
1456 st.insert("Color", Value::String(color_to_short_name(line.color)));
1457 st.insert("LineWidth", Value::Num(line.line_width as f64));
1458 st.insert(
1459 "LineStyle",
1460 Value::String(line_style_name(line.line_style).into()),
1461 );
1462 if let Some(label) = line.label.clone() {
1463 st.insert("DisplayName", Value::String(label));
1464 }
1465 insert_line_marker_struct_props(&mut st, line.marker.as_ref());
1466 Ok(Value::Struct(st))
1467 }
1468 Some("type") => Ok(Value::String("line".into())),
1469 Some("parent") => Ok(child_parent_handle(
1470 line_handle.figure,
1471 line_handle.axes_index,
1472 )),
1473 Some("children") => Ok(handles_value(Vec::new())),
1474 Some("xdata") => Ok(tensor_from_vec(line.x_data.clone())),
1475 Some("ydata") => Ok(tensor_from_vec(line.y_data.clone())),
1476 Some("color") => Ok(Value::String(color_to_short_name(line.color))),
1477 Some("linewidth") => Ok(Value::Num(line.line_width as f64)),
1478 Some("linestyle") => Ok(Value::String(line_style_name(line.line_style).into())),
1479 Some("displayname") => Ok(Value::String(line.label.unwrap_or_default())),
1480 Some(name) => line_marker_property_value(&line.marker, name, builtin),
1481 }
1482}
1483
1484fn get_reference_line_property(
1485 line_handle: &super::state::SimplePlotHandleState,
1486 property: Option<&str>,
1487 builtin: &'static str,
1488) -> BuiltinResult<Value> {
1489 let plot = get_simple_plot(line_handle, builtin)?;
1490 let runmat_plot::plots::figure::PlotElement::ReferenceLine(line) = plot else {
1491 return Err(plotting_error(
1492 builtin,
1493 format!("{builtin}: invalid reference line handle"),
1494 ));
1495 };
1496 let orientation = match line.orientation {
1497 runmat_plot::plots::ReferenceLineOrientation::Vertical => "vertical",
1498 runmat_plot::plots::ReferenceLineOrientation::Horizontal => "horizontal",
1499 };
1500 match property.map(canonical_property_name) {
1501 None => {
1502 let mut st =
1503 child_base_struct("constantline", line_handle.figure, line_handle.axes_index);
1504 st.insert("Value", Value::Num(line.value));
1505 st.insert("Orientation", Value::String(orientation.into()));
1506 st.insert("Color", Value::String(color_to_short_name(line.color)));
1507 st.insert("LineWidth", Value::Num(line.line_width as f64));
1508 st.insert(
1509 "LineStyle",
1510 Value::String(line_style_name(line.line_style).into()),
1511 );
1512 st.insert(
1513 "Label",
1514 Value::String(line.label.clone().unwrap_or_default()),
1515 );
1516 st.insert(
1517 "LabelOrientation",
1518 Value::String(line.label_orientation.clone()),
1519 );
1520 st.insert(
1521 "DisplayName",
1522 Value::String(line.display_name.clone().unwrap_or_default()),
1523 );
1524 st.insert(
1525 "Visible",
1526 Value::String(if line.visible { "on" } else { "off" }.into()),
1527 );
1528 Ok(Value::Struct(st))
1529 }
1530 Some("type") => Ok(Value::String("constantline".into())),
1531 Some("parent") => Ok(child_parent_handle(
1532 line_handle.figure,
1533 line_handle.axes_index,
1534 )),
1535 Some("children") => Ok(handles_value(Vec::new())),
1536 Some("value") => Ok(Value::Num(line.value)),
1537 Some("orientation") => Ok(Value::String(orientation.into())),
1538 Some("color") => Ok(Value::String(color_to_short_name(line.color))),
1539 Some("linewidth") => Ok(Value::Num(line.line_width as f64)),
1540 Some("linestyle") => Ok(Value::String(line_style_name(line.line_style).into())),
1541 Some("label") => Ok(Value::String(line.label.unwrap_or_default())),
1542 Some("labelorientation") => Ok(Value::String(line.label_orientation)),
1543 Some("displayname") => Ok(Value::String(line.display_name.unwrap_or_default())),
1544 Some("visible") => Ok(Value::String(
1545 if line.visible { "on" } else { "off" }.into(),
1546 )),
1547 Some(other) => Err(plotting_error(
1548 builtin,
1549 format!("{builtin}: unsupported reference line property `{other}`"),
1550 )),
1551 }
1552}
1553
1554fn get_stairs_property(
1555 stairs_handle: &super::state::SimplePlotHandleState,
1556 property: Option<&str>,
1557 builtin: &'static str,
1558) -> BuiltinResult<Value> {
1559 let plot = get_simple_plot(stairs_handle, builtin)?;
1560 let runmat_plot::plots::figure::PlotElement::Stairs(stairs) = plot else {
1561 return Err(plotting_error(
1562 builtin,
1563 format!("{builtin}: invalid stairs handle"),
1564 ));
1565 };
1566 match property.map(canonical_property_name) {
1567 None => {
1568 let mut st =
1569 child_base_struct("stairs", stairs_handle.figure, stairs_handle.axes_index);
1570 st.insert("XData", tensor_from_vec(stairs.x.clone()));
1571 st.insert("YData", tensor_from_vec(stairs.y.clone()));
1572 st.insert("Color", Value::String(color_to_short_name(stairs.color)));
1573 st.insert("LineWidth", Value::Num(stairs.line_width as f64));
1574 if let Some(label) = stairs.label.clone() {
1575 st.insert("DisplayName", Value::String(label));
1576 }
1577 Ok(Value::Struct(st))
1578 }
1579 Some("type") => Ok(Value::String("stairs".into())),
1580 Some("parent") => Ok(child_parent_handle(
1581 stairs_handle.figure,
1582 stairs_handle.axes_index,
1583 )),
1584 Some("children") => Ok(handles_value(Vec::new())),
1585 Some("xdata") => Ok(tensor_from_vec(stairs.x.clone())),
1586 Some("ydata") => Ok(tensor_from_vec(stairs.y.clone())),
1587 Some("color") => Ok(Value::String(color_to_short_name(stairs.color))),
1588 Some("linewidth") => Ok(Value::Num(stairs.line_width as f64)),
1589 Some("displayname") => Ok(Value::String(stairs.label.unwrap_or_default())),
1590 Some(other) => Err(plotting_error(
1591 builtin,
1592 format!("{builtin}: unsupported stairs property `{other}`"),
1593 )),
1594 }
1595}
1596
1597fn get_scatter_property(
1598 scatter_handle: &super::state::SimplePlotHandleState,
1599 property: Option<&str>,
1600 builtin: &'static str,
1601) -> BuiltinResult<Value> {
1602 let plot = get_simple_plot(scatter_handle, builtin)?;
1603 let runmat_plot::plots::figure::PlotElement::Scatter(scatter) = plot else {
1604 return Err(plotting_error(
1605 builtin,
1606 format!("{builtin}: invalid scatter handle"),
1607 ));
1608 };
1609 match property.map(canonical_property_name) {
1610 None => {
1611 let mut st =
1612 child_base_struct("scatter", scatter_handle.figure, scatter_handle.axes_index);
1613 st.insert("XData", tensor_from_vec(scatter.x_data.clone()));
1614 st.insert("YData", tensor_from_vec(scatter.y_data.clone()));
1615 st.insert(
1616 "Marker",
1617 Value::String(marker_style_name(scatter.marker_style).into()),
1618 );
1619 st.insert("SizeData", Value::Num(scatter.marker_size as f64));
1620 st.insert(
1621 "MarkerFaceColor",
1622 Value::String(color_to_short_name(scatter.color)),
1623 );
1624 st.insert(
1625 "MarkerEdgeColor",
1626 Value::String(color_to_short_name(scatter.edge_color)),
1627 );
1628 st.insert("LineWidth", Value::Num(scatter.edge_thickness as f64));
1629 if let Some(label) = scatter.label.clone() {
1630 st.insert("DisplayName", Value::String(label));
1631 }
1632 Ok(Value::Struct(st))
1633 }
1634 Some("type") => Ok(Value::String("scatter".into())),
1635 Some("parent") => Ok(child_parent_handle(
1636 scatter_handle.figure,
1637 scatter_handle.axes_index,
1638 )),
1639 Some("children") => Ok(handles_value(Vec::new())),
1640 Some("xdata") => Ok(tensor_from_vec(scatter.x_data.clone())),
1641 Some("ydata") => Ok(tensor_from_vec(scatter.y_data.clone())),
1642 Some("marker") => Ok(Value::String(
1643 marker_style_name(scatter.marker_style).into(),
1644 )),
1645 Some("sizedata") => Ok(Value::Num(scatter.marker_size as f64)),
1646 Some("markerfacecolor") => Ok(Value::String(color_to_short_name(scatter.color))),
1647 Some("markeredgecolor") => Ok(Value::String(color_to_short_name(scatter.edge_color))),
1648 Some("linewidth") => Ok(Value::Num(scatter.edge_thickness as f64)),
1649 Some("displayname") => Ok(Value::String(scatter.label.unwrap_or_default())),
1650 Some(other) => Err(plotting_error(
1651 builtin,
1652 format!("{builtin}: unsupported scatter property `{other}`"),
1653 )),
1654 }
1655}
1656
1657fn get_bar_property(
1658 bar_handle: &super::state::SimplePlotHandleState,
1659 property: Option<&str>,
1660 builtin: &'static str,
1661) -> BuiltinResult<Value> {
1662 let plot = get_simple_plot(bar_handle, builtin)?;
1663 let runmat_plot::plots::figure::PlotElement::Bar(bar) = plot else {
1664 return Err(plotting_error(
1665 builtin,
1666 format!("{builtin}: invalid bar handle"),
1667 ));
1668 };
1669 match property.map(canonical_property_name) {
1670 None => {
1671 let mut st = child_base_struct("bar", bar_handle.figure, bar_handle.axes_index);
1672 st.insert("FaceColor", Value::String(color_to_short_name(bar.color)));
1673 st.insert("BarWidth", Value::Num(bar.bar_width as f64));
1674 if let Some(label) = bar.label.clone() {
1675 st.insert("DisplayName", Value::String(label));
1676 }
1677 Ok(Value::Struct(st))
1678 }
1679 Some("type") => Ok(Value::String("bar".into())),
1680 Some("parent") => Ok(child_parent_handle(
1681 bar_handle.figure,
1682 bar_handle.axes_index,
1683 )),
1684 Some("children") => Ok(handles_value(Vec::new())),
1685 Some("facecolor") | Some("color") => Ok(Value::String(color_to_short_name(bar.color))),
1686 Some("barwidth") => Ok(Value::Num(bar.bar_width as f64)),
1687 Some("displayname") => Ok(Value::String(bar.label.unwrap_or_default())),
1688 Some(other) => Err(plotting_error(
1689 builtin,
1690 format!("{builtin}: unsupported bar property `{other}`"),
1691 )),
1692 }
1693}
1694
1695fn get_surface_property(
1696 surface_handle: &super::state::SimplePlotHandleState,
1697 property: Option<&str>,
1698 builtin: &'static str,
1699) -> BuiltinResult<Value> {
1700 let plot = get_simple_plot(surface_handle, builtin)?;
1701 let runmat_plot::plots::figure::PlotElement::Surface(surface) = plot else {
1702 return Err(plotting_error(
1703 builtin,
1704 format!("{builtin}: invalid surface handle"),
1705 ));
1706 };
1707 match property.map(canonical_property_name) {
1708 None => {
1709 let mut st =
1710 child_base_struct("surface", surface_handle.figure, surface_handle.axes_index);
1711 st.insert("XData", tensor_from_vec(surface.x_data.clone()));
1712 st.insert("YData", tensor_from_vec(surface.y_data.clone()));
1713 if let Some(z) = surface.z_data.clone() {
1714 st.insert("ZData", tensor_from_matrix(z));
1715 }
1716 st.insert("FaceAlpha", Value::Num(surface.alpha as f64));
1717 if let Some(label) = surface.label.clone() {
1718 st.insert("DisplayName", Value::String(label));
1719 }
1720 Ok(Value::Struct(st))
1721 }
1722 Some("type") => Ok(Value::String("surface".into())),
1723 Some("parent") => Ok(child_parent_handle(
1724 surface_handle.figure,
1725 surface_handle.axes_index,
1726 )),
1727 Some("children") => Ok(handles_value(Vec::new())),
1728 Some("xdata") => Ok(tensor_from_vec(surface.x_data.clone())),
1729 Some("ydata") => Ok(tensor_from_vec(surface.y_data.clone())),
1730 Some("zdata") => Ok(surface
1731 .z_data
1732 .clone()
1733 .map(tensor_from_matrix)
1734 .unwrap_or_else(|| tensor_from_vec(Vec::new()))),
1735 Some("facealpha") => Ok(Value::Num(surface.alpha as f64)),
1736 Some("displayname") => Ok(Value::String(surface.label.unwrap_or_default())),
1737 Some(other) => Err(plotting_error(
1738 builtin,
1739 format!("{builtin}: unsupported surface property `{other}`"),
1740 )),
1741 }
1742}
1743
1744fn get_patch_property(
1745 patch_handle: &super::state::SimplePlotHandleState,
1746 property: Option<&str>,
1747 builtin: &'static str,
1748) -> BuiltinResult<Value> {
1749 let plot = get_simple_plot(patch_handle, builtin)?;
1750 let runmat_plot::plots::figure::PlotElement::Patch(patch) = plot else {
1751 return Err(plotting_error(
1752 builtin,
1753 format!("{builtin}: invalid patch handle"),
1754 ));
1755 };
1756 match property.map(canonical_property_name) {
1757 None => {
1758 let mut st = child_base_struct("patch", patch_handle.figure, patch_handle.axes_index);
1759 st.insert("Faces", faces_tensor(patch.faces()));
1760 st.insert("Vertices", vertices_tensor(patch.vertices()));
1761 st.insert(
1762 "XData",
1763 tensor_from_vec(patch.vertices().iter().map(|p| p.x as f64).collect()),
1764 );
1765 st.insert(
1766 "YData",
1767 tensor_from_vec(patch.vertices().iter().map(|p| p.y as f64).collect()),
1768 );
1769 st.insert(
1770 "ZData",
1771 tensor_from_vec(patch.vertices().iter().map(|p| p.z as f64).collect()),
1772 );
1773 st.insert(
1774 "FaceColor",
1775 patch_color_property(patch.face_color_mode(), patch.face_color()),
1776 );
1777 st.insert(
1778 "EdgeColor",
1779 patch_edge_color_property(patch.edge_color_mode(), patch.edge_color()),
1780 );
1781 st.insert("FaceAlpha", Value::Num(patch.face_alpha() as f64));
1782 st.insert("EdgeAlpha", Value::Num(patch.edge_alpha() as f64));
1783 st.insert("LineWidth", Value::Num(patch.line_width() as f64));
1784 st.insert("Visible", Value::Bool(patch.is_visible()));
1785 if let Some(label) = patch.label() {
1786 st.insert("DisplayName", Value::String(label.to_string()));
1787 }
1788 Ok(Value::Struct(st))
1789 }
1790 Some("type") => Ok(Value::String("patch".into())),
1791 Some("parent") => Ok(child_parent_handle(
1792 patch_handle.figure,
1793 patch_handle.axes_index,
1794 )),
1795 Some("children") => Ok(handles_value(Vec::new())),
1796 Some("faces") => Ok(faces_tensor(patch.faces())),
1797 Some("vertices") => Ok(vertices_tensor(patch.vertices())),
1798 Some("xdata") => Ok(tensor_from_vec(
1799 patch.vertices().iter().map(|p| p.x as f64).collect(),
1800 )),
1801 Some("ydata") => Ok(tensor_from_vec(
1802 patch.vertices().iter().map(|p| p.y as f64).collect(),
1803 )),
1804 Some("zdata") => Ok(tensor_from_vec(
1805 patch.vertices().iter().map(|p| p.z as f64).collect(),
1806 )),
1807 Some("facecolor") | Some("color") => Ok(patch_color_property(
1808 patch.face_color_mode(),
1809 patch.face_color(),
1810 )),
1811 Some("edgecolor") => Ok(patch_edge_color_property(
1812 patch.edge_color_mode(),
1813 patch.edge_color(),
1814 )),
1815 Some("facealpha") => Ok(Value::Num(patch.face_alpha() as f64)),
1816 Some("edgealpha") => Ok(Value::Num(patch.edge_alpha() as f64)),
1817 Some("linewidth") => Ok(Value::Num(patch.line_width() as f64)),
1818 Some("displayname") => Ok(Value::String(patch.label().unwrap_or_default().to_string())),
1819 Some("visible") => Ok(Value::Bool(patch.is_visible())),
1820 Some(other) => Err(plotting_error(
1821 builtin,
1822 format!("{builtin}: unsupported patch property `{other}`"),
1823 )),
1824 }
1825}
1826
1827fn get_line3_property(
1828 line_handle: &super::state::SimplePlotHandleState,
1829 property: Option<&str>,
1830 builtin: &'static str,
1831) -> BuiltinResult<Value> {
1832 let plot = get_simple_plot(line_handle, builtin)?;
1833 let runmat_plot::plots::figure::PlotElement::Line3(line) = plot else {
1834 return Err(plotting_error(
1835 builtin,
1836 format!("{builtin}: invalid plot3 handle"),
1837 ));
1838 };
1839 match property.map(canonical_property_name) {
1840 None => {
1841 let mut st = child_base_struct("line", line_handle.figure, line_handle.axes_index);
1842 st.insert("XData", tensor_from_vec(line.x_data.clone()));
1843 st.insert("YData", tensor_from_vec(line.y_data.clone()));
1844 st.insert("ZData", tensor_from_vec(line.z_data.clone()));
1845 st.insert("Color", Value::String(color_to_short_name(line.color)));
1846 st.insert("LineWidth", Value::Num(line.line_width as f64));
1847 st.insert(
1848 "LineStyle",
1849 Value::String(line_style_name(line.line_style).into()),
1850 );
1851 if let Some(label) = line.label.clone() {
1852 st.insert("DisplayName", Value::String(label));
1853 }
1854 Ok(Value::Struct(st))
1855 }
1856 Some("type") => Ok(Value::String("line".into())),
1857 Some("parent") => Ok(child_parent_handle(
1858 line_handle.figure,
1859 line_handle.axes_index,
1860 )),
1861 Some("children") => Ok(handles_value(Vec::new())),
1862 Some("xdata") => Ok(tensor_from_vec(line.x_data.clone())),
1863 Some("ydata") => Ok(tensor_from_vec(line.y_data.clone())),
1864 Some("zdata") => Ok(tensor_from_vec(line.z_data.clone())),
1865 Some("color") => Ok(Value::String(color_to_short_name(line.color))),
1866 Some("linewidth") => Ok(Value::Num(line.line_width as f64)),
1867 Some("linestyle") => Ok(Value::String(line_style_name(line.line_style).into())),
1868 Some("displayname") => Ok(Value::String(line.label.unwrap_or_default())),
1869 Some(other) => Err(plotting_error(
1870 builtin,
1871 format!("{builtin}: unsupported plot3 property `{other}`"),
1872 )),
1873 }
1874}
1875
1876fn get_scatter3_property(
1877 scatter_handle: &super::state::SimplePlotHandleState,
1878 property: Option<&str>,
1879 builtin: &'static str,
1880) -> BuiltinResult<Value> {
1881 let plot = get_simple_plot(scatter_handle, builtin)?;
1882 let runmat_plot::plots::figure::PlotElement::Scatter3(scatter) = plot else {
1883 return Err(plotting_error(
1884 builtin,
1885 format!("{builtin}: invalid scatter3 handle"),
1886 ));
1887 };
1888 let (x, y, z): (Vec<f64>, Vec<f64>, Vec<f64>) = scatter
1889 .points
1890 .iter()
1891 .map(|p| (p.x as f64, p.y as f64, p.z as f64))
1892 .unzip_n_vec();
1893 match property.map(canonical_property_name) {
1894 None => {
1895 let mut st =
1896 child_base_struct("scatter", scatter_handle.figure, scatter_handle.axes_index);
1897 st.insert("XData", tensor_from_vec(x));
1898 st.insert("YData", tensor_from_vec(y));
1899 st.insert("ZData", tensor_from_vec(z));
1900 st.insert("SizeData", Value::Num(scatter.point_size as f64));
1901 if let Some(label) = scatter.label.clone() {
1902 st.insert("DisplayName", Value::String(label));
1903 }
1904 Ok(Value::Struct(st))
1905 }
1906 Some("type") => Ok(Value::String("scatter".into())),
1907 Some("parent") => Ok(child_parent_handle(
1908 scatter_handle.figure,
1909 scatter_handle.axes_index,
1910 )),
1911 Some("children") => Ok(handles_value(Vec::new())),
1912 Some("sizedata") => Ok(Value::Num(scatter.point_size as f64)),
1913 Some("displayname") => Ok(Value::String(scatter.label.unwrap_or_default())),
1914 Some(other) => Err(plotting_error(
1915 builtin,
1916 format!("{builtin}: unsupported scatter3 property `{other}`"),
1917 )),
1918 }
1919}
1920
1921fn get_pie_property(
1922 pie_handle: &super::state::SimplePlotHandleState,
1923 property: Option<&str>,
1924 builtin: &'static str,
1925) -> BuiltinResult<Value> {
1926 let plot = get_simple_plot(pie_handle, builtin)?;
1927 let runmat_plot::plots::figure::PlotElement::Pie(pie) = plot else {
1928 return Err(plotting_error(
1929 builtin,
1930 format!("{builtin}: invalid pie handle"),
1931 ));
1932 };
1933 match property.map(canonical_property_name) {
1934 None => {
1935 let mut st = child_base_struct("pie", pie_handle.figure, pie_handle.axes_index);
1936 if let Some(label) = pie.label.clone() {
1937 st.insert("DisplayName", Value::String(label));
1938 }
1939 Ok(Value::Struct(st))
1940 }
1941 Some("type") => Ok(Value::String("pie".into())),
1942 Some("parent") => Ok(child_parent_handle(
1943 pie_handle.figure,
1944 pie_handle.axes_index,
1945 )),
1946 Some("children") => Ok(handles_value(Vec::new())),
1947 Some("displayname") => Ok(Value::String(pie.label.unwrap_or_default())),
1948 Some(other) => Err(plotting_error(
1949 builtin,
1950 format!("{builtin}: unsupported pie property `{other}`"),
1951 )),
1952 }
1953}
1954
1955fn get_contour_property(
1956 contour_handle: &super::state::SimplePlotHandleState,
1957 property: Option<&str>,
1958 builtin: &'static str,
1959) -> BuiltinResult<Value> {
1960 let plot = get_simple_plot(contour_handle, builtin)?;
1961 let runmat_plot::plots::figure::PlotElement::Contour(contour) = plot else {
1962 return Err(plotting_error(
1963 builtin,
1964 format!("{builtin}: invalid contour handle"),
1965 ));
1966 };
1967 match property.map(canonical_property_name) {
1968 None => {
1969 let mut st =
1970 child_base_struct("contour", contour_handle.figure, contour_handle.axes_index);
1971 st.insert("ZData", Value::Num(contour.base_z as f64));
1972 if let Some(label) = contour.label.clone() {
1973 st.insert("DisplayName", Value::String(label));
1974 }
1975 Ok(Value::Struct(st))
1976 }
1977 Some("type") => Ok(Value::String("contour".into())),
1978 Some("parent") => Ok(child_parent_handle(
1979 contour_handle.figure,
1980 contour_handle.axes_index,
1981 )),
1982 Some("children") => Ok(handles_value(Vec::new())),
1983 Some("zdata") => Ok(Value::Num(contour.base_z as f64)),
1984 Some("displayname") => Ok(Value::String(contour.label.unwrap_or_default())),
1985 Some(other) => Err(plotting_error(
1986 builtin,
1987 format!("{builtin}: unsupported contour property `{other}`"),
1988 )),
1989 }
1990}
1991
1992fn get_contour_fill_property(
1993 fill_handle: &super::state::SimplePlotHandleState,
1994 property: Option<&str>,
1995 builtin: &'static str,
1996) -> BuiltinResult<Value> {
1997 let plot = get_simple_plot(fill_handle, builtin)?;
1998 let runmat_plot::plots::figure::PlotElement::ContourFill(fill) = plot else {
1999 return Err(plotting_error(
2000 builtin,
2001 format!("{builtin}: invalid contourf handle"),
2002 ));
2003 };
2004 match property.map(canonical_property_name) {
2005 None => {
2006 let mut st = child_base_struct("contour", fill_handle.figure, fill_handle.axes_index);
2007 if let Some(label) = fill.label.clone() {
2008 st.insert("DisplayName", Value::String(label));
2009 }
2010 Ok(Value::Struct(st))
2011 }
2012 Some("type") => Ok(Value::String("contour".into())),
2013 Some("parent") => Ok(child_parent_handle(
2014 fill_handle.figure,
2015 fill_handle.axes_index,
2016 )),
2017 Some("children") => Ok(handles_value(Vec::new())),
2018 Some("displayname") => Ok(Value::String(fill.label.unwrap_or_default())),
2019 Some(other) => Err(plotting_error(
2020 builtin,
2021 format!("{builtin}: unsupported contourf property `{other}`"),
2022 )),
2023 }
2024}
2025
2026fn get_stem_property(
2027 stem_handle: &super::state::StemHandleState,
2028 property: Option<&str>,
2029 builtin: &'static str,
2030) -> BuiltinResult<Value> {
2031 let figure = super::state::clone_figure(stem_handle.figure)
2032 .ok_or_else(|| plotting_error(builtin, format!("{builtin}: invalid stem figure")))?;
2033 let plot = figure
2034 .plots()
2035 .nth(stem_handle.plot_index)
2036 .ok_or_else(|| plotting_error(builtin, format!("{builtin}: invalid stem handle")))?;
2037 let runmat_plot::plots::figure::PlotElement::Stem(stem) = plot else {
2038 return Err(plotting_error(
2039 builtin,
2040 format!("{builtin}: invalid stem handle"),
2041 ));
2042 };
2043 match property.map(canonical_property_name) {
2044 None => {
2045 let mut st = StructValue::new();
2046 st.insert("Type", Value::String("stem".into()));
2047 st.insert(
2048 "Parent",
2049 Value::Num(super::state::encode_axes_handle(
2050 stem_handle.figure,
2051 stem_handle.axes_index,
2052 )),
2053 );
2054 st.insert("Children", handles_value(Vec::new()));
2055 st.insert("BaseValue", Value::Num(stem.baseline));
2056 st.insert("BaseLine", Value::Bool(stem.baseline_visible));
2057 st.insert("LineWidth", Value::Num(stem.line_width as f64));
2058 st.insert(
2059 "LineStyle",
2060 Value::String(line_style_name(stem.line_style).into()),
2061 );
2062 st.insert("Color", Value::String(color_to_short_name(stem.color)));
2063 if let Some(marker) = &stem.marker {
2064 st.insert(
2065 "Marker",
2066 Value::String(marker_style_name(marker.kind).into()),
2067 );
2068 st.insert("MarkerSize", Value::Num(marker.size as f64));
2069 st.insert(
2070 "MarkerFaceColor",
2071 Value::String(color_to_short_name(marker.face_color)),
2072 );
2073 st.insert(
2074 "MarkerEdgeColor",
2075 Value::String(color_to_short_name(marker.edge_color)),
2076 );
2077 st.insert("Filled", Value::Bool(marker.filled));
2078 }
2079 Ok(Value::Struct(st))
2080 }
2081 Some("type") => Ok(Value::String("stem".into())),
2082 Some("parent") => Ok(Value::Num(super::state::encode_axes_handle(
2083 stem_handle.figure,
2084 stem_handle.axes_index,
2085 ))),
2086 Some("children") => Ok(handles_value(Vec::new())),
2087 Some("basevalue") => Ok(Value::Num(stem.baseline)),
2088 Some("baseline") => Ok(Value::Bool(stem.baseline_visible)),
2089 Some("linewidth") => Ok(Value::Num(stem.line_width as f64)),
2090 Some("linestyle") => Ok(Value::String(line_style_name(stem.line_style).into())),
2091 Some("color") => Ok(Value::String(color_to_short_name(stem.color))),
2092 Some("marker") => Ok(Value::String(
2093 stem.marker
2094 .as_ref()
2095 .map(|m| marker_style_name(m.kind).to_string())
2096 .unwrap_or("none".into()),
2097 )),
2098 Some("markersize") => Ok(Value::Num(
2099 stem.marker.as_ref().map(|m| m.size as f64).unwrap_or(0.0),
2100 )),
2101 Some("markerfacecolor") => Ok(Value::String(
2102 stem.marker
2103 .as_ref()
2104 .map(|m| color_to_short_name(m.face_color))
2105 .unwrap_or("none".into()),
2106 )),
2107 Some("markeredgecolor") => Ok(Value::String(
2108 stem.marker
2109 .as_ref()
2110 .map(|m| color_to_short_name(m.edge_color))
2111 .unwrap_or("none".into()),
2112 )),
2113 Some("filled") => Ok(Value::Bool(
2114 stem.marker.as_ref().map(|m| m.filled).unwrap_or(false),
2115 )),
2116 Some(other) => Err(plotting_error(
2117 builtin,
2118 format!("{builtin}: unsupported stem property `{other}`"),
2119 )),
2120 }
2121}
2122
2123fn get_errorbar_property(
2124 error_handle: &super::state::ErrorBarHandleState,
2125 property: Option<&str>,
2126 builtin: &'static str,
2127) -> BuiltinResult<Value> {
2128 let figure = super::state::clone_figure(error_handle.figure)
2129 .ok_or_else(|| plotting_error(builtin, format!("{builtin}: invalid errorbar figure")))?;
2130 let plot = figure
2131 .plots()
2132 .nth(error_handle.plot_index)
2133 .ok_or_else(|| plotting_error(builtin, format!("{builtin}: invalid errorbar handle")))?;
2134 let runmat_plot::plots::figure::PlotElement::ErrorBar(errorbar) = plot else {
2135 return Err(plotting_error(
2136 builtin,
2137 format!("{builtin}: invalid errorbar handle"),
2138 ));
2139 };
2140 match property.map(canonical_property_name) {
2141 None => {
2142 let mut st = StructValue::new();
2143 st.insert("Type", Value::String("errorbar".into()));
2144 st.insert(
2145 "Parent",
2146 Value::Num(super::state::encode_axes_handle(
2147 error_handle.figure,
2148 error_handle.axes_index,
2149 )),
2150 );
2151 st.insert("Children", handles_value(Vec::new()));
2152 st.insert("LineWidth", Value::Num(errorbar.line_width as f64));
2153 st.insert(
2154 "LineStyle",
2155 Value::String(line_style_name(errorbar.line_style).into()),
2156 );
2157 st.insert("Color", Value::String(color_to_short_name(errorbar.color)));
2158 st.insert("CapSize", Value::Num(errorbar.cap_size as f64));
2159 if let Some(marker) = &errorbar.marker {
2160 st.insert(
2161 "Marker",
2162 Value::String(marker_style_name(marker.kind).into()),
2163 );
2164 st.insert("MarkerSize", Value::Num(marker.size as f64));
2165 }
2166 Ok(Value::Struct(st))
2167 }
2168 Some("type") => Ok(Value::String("errorbar".into())),
2169 Some("parent") => Ok(Value::Num(super::state::encode_axes_handle(
2170 error_handle.figure,
2171 error_handle.axes_index,
2172 ))),
2173 Some("children") => Ok(handles_value(Vec::new())),
2174 Some("linewidth") => Ok(Value::Num(errorbar.line_width as f64)),
2175 Some("linestyle") => Ok(Value::String(line_style_name(errorbar.line_style).into())),
2176 Some("color") => Ok(Value::String(color_to_short_name(errorbar.color))),
2177 Some("capsize") => Ok(Value::Num(errorbar.cap_size as f64)),
2178 Some("marker") => Ok(Value::String(
2179 errorbar
2180 .marker
2181 .as_ref()
2182 .map(|m| marker_style_name(m.kind).to_string())
2183 .unwrap_or("none".into()),
2184 )),
2185 Some("markersize") => Ok(Value::Num(
2186 errorbar
2187 .marker
2188 .as_ref()
2189 .map(|m| m.size as f64)
2190 .unwrap_or(0.0),
2191 )),
2192 Some(other) => Err(plotting_error(
2193 builtin,
2194 format!("{builtin}: unsupported errorbar property `{other}`"),
2195 )),
2196 }
2197}
2198
2199fn get_quiver_property(
2200 quiver_handle: &super::state::QuiverHandleState,
2201 property: Option<&str>,
2202 builtin: &'static str,
2203) -> BuiltinResult<Value> {
2204 let figure = super::state::clone_figure(quiver_handle.figure)
2205 .ok_or_else(|| plotting_error(builtin, format!("{builtin}: invalid quiver figure")))?;
2206 let plot = figure
2207 .plots()
2208 .nth(quiver_handle.plot_index)
2209 .ok_or_else(|| plotting_error(builtin, format!("{builtin}: invalid quiver handle")))?;
2210 let runmat_plot::plots::figure::PlotElement::Quiver(quiver) = plot else {
2211 return Err(plotting_error(
2212 builtin,
2213 format!("{builtin}: invalid quiver handle"),
2214 ));
2215 };
2216 match property.map(canonical_property_name) {
2217 None => {
2218 let mut st = StructValue::new();
2219 st.insert("Type", Value::String("quiver".into()));
2220 st.insert(
2221 "Parent",
2222 Value::Num(super::state::encode_axes_handle(
2223 quiver_handle.figure,
2224 quiver_handle.axes_index,
2225 )),
2226 );
2227 st.insert("Children", handles_value(Vec::new()));
2228 st.insert("Color", Value::String(color_to_short_name(quiver.color)));
2229 st.insert("LineWidth", Value::Num(quiver.line_width as f64));
2230 st.insert("AutoScaleFactor", Value::Num(quiver.scale as f64));
2231 st.insert("MaxHeadSize", Value::Num(quiver.head_size as f64));
2232 Ok(Value::Struct(st))
2233 }
2234 Some("type") => Ok(Value::String("quiver".into())),
2235 Some("parent") => Ok(Value::Num(super::state::encode_axes_handle(
2236 quiver_handle.figure,
2237 quiver_handle.axes_index,
2238 ))),
2239 Some("children") => Ok(handles_value(Vec::new())),
2240 Some("color") => Ok(Value::String(color_to_short_name(quiver.color))),
2241 Some("linewidth") => Ok(Value::Num(quiver.line_width as f64)),
2242 Some("autoscalefactor") => Ok(Value::Num(quiver.scale as f64)),
2243 Some("maxheadsize") => Ok(Value::Num(quiver.head_size as f64)),
2244 Some(other) => Err(plotting_error(
2245 builtin,
2246 format!("{builtin}: unsupported quiver property `{other}`"),
2247 )),
2248 }
2249}
2250
2251fn get_image_property(
2252 image_handle: &super::state::ImageHandleState,
2253 property: Option<&str>,
2254 builtin: &'static str,
2255) -> BuiltinResult<Value> {
2256 let figure = super::state::clone_figure(image_handle.figure)
2257 .ok_or_else(|| plotting_error(builtin, format!("{builtin}: invalid image figure")))?;
2258 let plot = figure
2259 .plots()
2260 .nth(image_handle.plot_index)
2261 .ok_or_else(|| plotting_error(builtin, format!("{builtin}: invalid image handle")))?;
2262 let runmat_plot::plots::figure::PlotElement::Surface(surface) = plot else {
2263 return Err(plotting_error(
2264 builtin,
2265 format!("{builtin}: invalid image handle"),
2266 ));
2267 };
2268 if !surface.image_mode {
2269 return Err(plotting_error(
2270 builtin,
2271 format!("{builtin}: handle does not reference an image plot"),
2272 ));
2273 }
2274 match property.map(canonical_property_name) {
2275 None => {
2276 let mut st = StructValue::new();
2277 st.insert("Type", Value::String("image".into()));
2278 st.insert(
2279 "Parent",
2280 Value::Num(super::state::encode_axes_handle(
2281 image_handle.figure,
2282 image_handle.axes_index,
2283 )),
2284 );
2285 st.insert("Children", handles_value(Vec::new()));
2286 st.insert("XData", tensor_from_vec(surface.x_data.clone()));
2287 st.insert("YData", tensor_from_vec(surface.y_data.clone()));
2288 st.insert(
2289 "CDataMapping",
2290 Value::String(if surface.color_grid.is_some() {
2291 "direct".into()
2292 } else {
2293 "scaled".into()
2294 }),
2295 );
2296 Ok(Value::Struct(st))
2297 }
2298 Some("type") => Ok(Value::String("image".into())),
2299 Some("parent") => Ok(Value::Num(super::state::encode_axes_handle(
2300 image_handle.figure,
2301 image_handle.axes_index,
2302 ))),
2303 Some("children") => Ok(handles_value(Vec::new())),
2304 Some("xdata") => Ok(tensor_from_vec(surface.x_data.clone())),
2305 Some("ydata") => Ok(tensor_from_vec(surface.y_data.clone())),
2306 Some("cdatamapping") => Ok(Value::String(if surface.color_grid.is_some() {
2307 "direct".into()
2308 } else {
2309 "scaled".into()
2310 })),
2311 Some(other) => Err(plotting_error(
2312 builtin,
2313 format!("{builtin}: unsupported image property `{other}`"),
2314 )),
2315 }
2316}
2317
2318fn get_heatmap_property(
2319 heatmap_handle: &super::state::HeatmapHandleState,
2320 property: Option<&str>,
2321 builtin: &'static str,
2322) -> BuiltinResult<Value> {
2323 let figure = super::state::clone_figure(heatmap_handle.figure)
2324 .ok_or_else(|| plotting_error(builtin, format!("{builtin}: invalid heatmap figure")))?;
2325 let plot = figure
2326 .plots()
2327 .nth(heatmap_handle.plot_index)
2328 .ok_or_else(|| plotting_error(builtin, format!("{builtin}: invalid heatmap handle")))?;
2329 let runmat_plot::plots::figure::PlotElement::Surface(surface) = plot else {
2330 return Err(plotting_error(
2331 builtin,
2332 format!("{builtin}: invalid heatmap handle"),
2333 ));
2334 };
2335 if !surface.image_mode {
2336 return Err(plotting_error(
2337 builtin,
2338 format!("{builtin}: handle does not reference a heatmap plot"),
2339 ));
2340 }
2341 let meta = figure
2342 .axes_metadata(heatmap_handle.axes_index)
2343 .ok_or_else(|| plotting_error(builtin, format!("{builtin}: invalid heatmap axes")))?;
2344 match property.map(canonical_property_name) {
2345 None => {
2346 let mut st = StructValue::new();
2347 st.insert("Type", Value::String("heatmap".into()));
2348 st.insert(
2349 "Parent",
2350 Value::Num(super::state::encode_axes_handle(
2351 heatmap_handle.figure,
2352 heatmap_handle.axes_index,
2353 )),
2354 );
2355 st.insert("Children", handles_value(Vec::new()));
2356 st.insert(
2357 "Title",
2358 Value::String(meta.title.clone().unwrap_or_default()),
2359 );
2360 st.insert(
2361 "XLabel",
2362 Value::String(meta.x_label.clone().unwrap_or_default()),
2363 );
2364 st.insert(
2365 "YLabel",
2366 Value::String(meta.y_label.clone().unwrap_or_default()),
2367 );
2368 st.insert(
2369 "XDisplayLabels",
2370 string_array_from_vec(heatmap_handle.x_labels.clone())?,
2371 );
2372 st.insert(
2373 "YDisplayLabels",
2374 string_array_from_vec(heatmap_handle.y_labels.clone())?,
2375 );
2376 st.insert(
2377 "ColorData",
2378 Value::Tensor(heatmap_handle.color_data.clone()),
2379 );
2380 st.insert("ColorbarVisible", Value::Bool(meta.colorbar_enabled));
2381 st.insert(
2382 "Colormap",
2383 Value::String(format!("{:?}", meta.colormap).to_ascii_lowercase()),
2384 );
2385 Ok(Value::Struct(st))
2386 }
2387 Some("type") => Ok(Value::String("heatmap".into())),
2388 Some("parent") => Ok(Value::Num(super::state::encode_axes_handle(
2389 heatmap_handle.figure,
2390 heatmap_handle.axes_index,
2391 ))),
2392 Some("children") => Ok(handles_value(Vec::new())),
2393 Some("title") => Ok(Value::String(meta.title.clone().unwrap_or_default())),
2394 Some("xlabel") => Ok(Value::String(meta.x_label.clone().unwrap_or_default())),
2395 Some("ylabel") => Ok(Value::String(meta.y_label.clone().unwrap_or_default())),
2396 Some("xdisplaylabels") => string_array_from_vec(heatmap_handle.x_labels.clone()),
2397 Some("ydisplaylabels") => string_array_from_vec(heatmap_handle.y_labels.clone()),
2398 Some("colordata") => Ok(Value::Tensor(heatmap_handle.color_data.clone())),
2399 Some("colorbarvisible") | Some("colorbar") => Ok(Value::Bool(meta.colorbar_enabled)),
2400 Some("colormap") => Ok(Value::String(
2401 format!("{:?}", meta.colormap).to_ascii_lowercase(),
2402 )),
2403 Some(other) => Err(plotting_error(
2404 builtin,
2405 format!("{builtin}: unsupported heatmap property `{other}`"),
2406 )),
2407 }
2408}
2409
2410fn get_area_property(
2411 area_handle: &super::state::AreaHandleState,
2412 property: Option<&str>,
2413 builtin: &'static str,
2414) -> BuiltinResult<Value> {
2415 let figure = super::state::clone_figure(area_handle.figure)
2416 .ok_or_else(|| plotting_error(builtin, format!("{builtin}: invalid area figure")))?;
2417 let plot = figure
2418 .plots()
2419 .nth(area_handle.plot_index)
2420 .ok_or_else(|| plotting_error(builtin, format!("{builtin}: invalid area handle")))?;
2421 let runmat_plot::plots::figure::PlotElement::Area(area) = plot else {
2422 return Err(plotting_error(
2423 builtin,
2424 format!("{builtin}: invalid area handle"),
2425 ));
2426 };
2427 match property.map(canonical_property_name) {
2428 None => {
2429 let mut st = StructValue::new();
2430 st.insert("Type", Value::String("area".into()));
2431 st.insert(
2432 "Parent",
2433 Value::Num(super::state::encode_axes_handle(
2434 area_handle.figure,
2435 area_handle.axes_index,
2436 )),
2437 );
2438 st.insert("Children", handles_value(Vec::new()));
2439 st.insert("XData", tensor_from_vec(area.x.clone()));
2440 st.insert("YData", tensor_from_vec(area.y.clone()));
2441 st.insert("BaseValue", Value::Num(area.baseline));
2442 st.insert("Color", Value::String(color_to_short_name(area.color)));
2443 Ok(Value::Struct(st))
2444 }
2445 Some("type") => Ok(Value::String("area".into())),
2446 Some("parent") => Ok(Value::Num(super::state::encode_axes_handle(
2447 area_handle.figure,
2448 area_handle.axes_index,
2449 ))),
2450 Some("children") => Ok(handles_value(Vec::new())),
2451 Some("xdata") => Ok(tensor_from_vec(area.x.clone())),
2452 Some("ydata") => Ok(tensor_from_vec(area.y.clone())),
2453 Some("basevalue") => Ok(Value::Num(area.baseline)),
2454 Some("color") => Ok(Value::String(color_to_short_name(area.color))),
2455 Some(other) => Err(plotting_error(
2456 builtin,
2457 format!("{builtin}: unsupported area property `{other}`"),
2458 )),
2459 }
2460}
2461
2462fn apply_histogram_property(
2463 hist: &super::state::HistogramHandleState,
2464 key: &str,
2465 value: &Value,
2466 builtin: &'static str,
2467) -> BuiltinResult<()> {
2468 match key {
2469 "normalization" => {
2470 let norm = value_as_string(value)
2471 .ok_or_else(|| {
2472 plotting_error(
2473 builtin,
2474 format!("{builtin}: Normalization must be a string"),
2475 )
2476 })?
2477 .trim()
2478 .to_ascii_lowercase();
2479 validate_histogram_normalization(&norm, builtin)?;
2480 let normalized =
2481 apply_histogram_normalization(&hist.raw_counts, &hist.bin_edges, &norm);
2482 let labels = histogram_labels_from_edges(&hist.bin_edges);
2483 super::state::update_histogram_plot_data(
2484 hist.figure,
2485 hist.plot_index,
2486 labels,
2487 normalized,
2488 )
2489 .map_err(|err| map_figure_error(builtin, err))?;
2490 super::state::update_histogram_handle_for_plot(
2491 hist.figure,
2492 hist.axes_index,
2493 hist.plot_index,
2494 norm,
2495 hist.raw_counts.clone(),
2496 )
2497 .map_err(|err| map_figure_error(builtin, err))?;
2498 Ok(())
2499 }
2500 other => Err(plotting_error(
2501 builtin,
2502 format!("{builtin}: unsupported histogram property `{other}`"),
2503 )),
2504 }
2505}
2506
2507fn apply_stem_property(
2508 stem_handle: &super::state::StemHandleState,
2509 key: &str,
2510 value: &Value,
2511 builtin: &'static str,
2512) -> BuiltinResult<()> {
2513 super::state::update_stem_plot(
2514 stem_handle.figure,
2515 stem_handle.plot_index,
2516 |stem| match key {
2517 "basevalue" => {
2518 if let Some(v) = value_as_f64(value) {
2519 stem.baseline = v;
2520 }
2521 }
2522 "baseline" => {
2523 if let Some(v) = value_as_bool(value) {
2524 stem.baseline_visible = v;
2525 }
2526 }
2527 "linewidth" => {
2528 if let Some(v) = value_as_f64(value) {
2529 stem.line_width = v as f32;
2530 }
2531 }
2532 "linestyle" => {
2533 if let Some(s) = value_as_string(value) {
2534 stem.line_style = parse_line_style_name_for_props(&s);
2535 }
2536 }
2537 "color" => {
2538 if let Ok(c) = parse_color_value(&LineStyleParseOptions::generic(builtin), value) {
2539 stem.color = c;
2540 }
2541 }
2542 "marker" => {
2543 if let Some(s) = value_as_string(value) {
2544 stem.marker = marker_from_name(&s, stem.marker.clone());
2545 }
2546 }
2547 "markersize" => {
2548 if let Some(v) = value_as_f64(value) {
2549 if let Some(marker) = &mut stem.marker {
2550 marker.size = v as f32;
2551 }
2552 }
2553 }
2554 "markerfacecolor" => {
2555 if let Ok(c) = parse_color_value(&LineStyleParseOptions::generic(builtin), value) {
2556 if let Some(marker) = &mut stem.marker {
2557 marker.face_color = c;
2558 }
2559 }
2560 }
2561 "markeredgecolor" => {
2562 if let Ok(c) = parse_color_value(&LineStyleParseOptions::generic(builtin), value) {
2563 if let Some(marker) = &mut stem.marker {
2564 marker.edge_color = c;
2565 }
2566 }
2567 }
2568 "filled" => {
2569 if let Some(v) = value_as_bool(value) {
2570 if let Some(marker) = &mut stem.marker {
2571 marker.filled = v;
2572 }
2573 }
2574 }
2575 _ => {}
2576 },
2577 )
2578 .map_err(|err| map_figure_error(builtin, err))?;
2579 Ok(())
2580}
2581
2582fn apply_errorbar_property(
2583 error_handle: &super::state::ErrorBarHandleState,
2584 key: &str,
2585 value: &Value,
2586 builtin: &'static str,
2587) -> BuiltinResult<()> {
2588 super::state::update_errorbar_plot(error_handle.figure, error_handle.plot_index, |errorbar| {
2589 match key {
2590 "linewidth" => {
2591 if let Some(v) = value_as_f64(value) {
2592 errorbar.line_width = v as f32;
2593 }
2594 }
2595 "linestyle" => {
2596 if let Some(s) = value_as_string(value) {
2597 errorbar.line_style = parse_line_style_name_for_props(&s);
2598 }
2599 }
2600 "color" => {
2601 if let Ok(c) = parse_color_value(&LineStyleParseOptions::generic(builtin), value) {
2602 errorbar.color = c;
2603 }
2604 }
2605 "capsize" => {
2606 if let Some(v) = value_as_f64(value) {
2607 errorbar.cap_size = v as f32;
2608 }
2609 }
2610 "marker" => {
2611 if let Some(s) = value_as_string(value) {
2612 errorbar.marker = marker_from_name(&s, errorbar.marker.clone());
2613 }
2614 }
2615 "markersize" => {
2616 if let Some(v) = value_as_f64(value) {
2617 if let Some(marker) = &mut errorbar.marker {
2618 marker.size = v as f32;
2619 }
2620 }
2621 }
2622 _ => {}
2623 }
2624 })
2625 .map_err(|err| map_figure_error(builtin, err))?;
2626 Ok(())
2627}
2628
2629fn apply_quiver_property(
2630 quiver_handle: &super::state::QuiverHandleState,
2631 key: &str,
2632 value: &Value,
2633 builtin: &'static str,
2634) -> BuiltinResult<()> {
2635 super::state::update_quiver_plot(quiver_handle.figure, quiver_handle.plot_index, |quiver| {
2636 match key {
2637 "color" => {
2638 if let Ok(c) = parse_color_value(&LineStyleParseOptions::generic(builtin), value) {
2639 quiver.color = c;
2640 }
2641 }
2642 "linewidth" => {
2643 if let Some(v) = value_as_f64(value) {
2644 quiver.line_width = v as f32;
2645 }
2646 }
2647 "autoscalefactor" => {
2648 if let Some(v) = value_as_f64(value) {
2649 quiver.scale = v as f32;
2650 }
2651 }
2652 "maxheadsize" => {
2653 if let Some(v) = value_as_f64(value) {
2654 quiver.head_size = v as f32;
2655 }
2656 }
2657 _ => {}
2658 }
2659 })
2660 .map_err(|err| map_figure_error(builtin, err))?;
2661 Ok(())
2662}
2663
2664fn apply_image_property(
2665 image_handle: &super::state::ImageHandleState,
2666 key: &str,
2667 value: &Value,
2668 builtin: &'static str,
2669) -> BuiltinResult<()> {
2670 super::state::update_image_plot(image_handle.figure, image_handle.plot_index, |surface| {
2671 match key {
2672 "xdata" => {
2673 if let Ok(tensor) = Tensor::try_from(value) {
2674 surface.x_data = tensor.data;
2675 }
2676 }
2677 "ydata" => {
2678 if let Ok(tensor) = Tensor::try_from(value) {
2679 surface.y_data = tensor.data;
2680 }
2681 }
2682 "cdatamapping" => {
2683 if let Some(text) = value_as_string(value) {
2684 if text.trim().eq_ignore_ascii_case("direct") {
2685 surface.image_mode = true;
2686 }
2687 }
2688 }
2689 _ => {}
2690 }
2691 })
2692 .map_err(|err| map_figure_error(builtin, err))?;
2693 Ok(())
2694}
2695
2696fn apply_heatmap_property(
2697 heatmap_handle: &super::state::HeatmapHandleState,
2698 key: &str,
2699 value: &Value,
2700 builtin: &'static str,
2701) -> BuiltinResult<()> {
2702 match key {
2703 "title" => apply_axes_property(
2704 heatmap_handle.figure,
2705 heatmap_handle.axes_index,
2706 "title",
2707 value,
2708 builtin,
2709 ),
2710 "xlabel" => apply_axes_property(
2711 heatmap_handle.figure,
2712 heatmap_handle.axes_index,
2713 "xlabel",
2714 value,
2715 builtin,
2716 ),
2717 "ylabel" => apply_axes_property(
2718 heatmap_handle.figure,
2719 heatmap_handle.axes_index,
2720 "ylabel",
2721 value,
2722 builtin,
2723 ),
2724 "colorbar" | "colorbarvisible" => apply_axes_property(
2725 heatmap_handle.figure,
2726 heatmap_handle.axes_index,
2727 "colorbar",
2728 value,
2729 builtin,
2730 ),
2731 "colormap" => apply_axes_property(
2732 heatmap_handle.figure,
2733 heatmap_handle.axes_index,
2734 "colormap",
2735 value,
2736 builtin,
2737 ),
2738 "xdisplaylabels" => {
2739 let labels = label_strings_from_value(value, builtin, "labels")?;
2740 if labels.len() != heatmap_handle.x_labels.len() {
2741 return Err(plotting_error(
2742 builtin,
2743 format!("{builtin}: XDisplayLabels length must match heatmap columns"),
2744 ));
2745 }
2746 super::state::set_heatmap_display_labels(
2747 heatmap_handle.figure,
2748 heatmap_handle.axes_index,
2749 heatmap_handle.plot_index,
2750 Some(labels),
2751 None,
2752 )
2753 .map_err(|err| map_figure_error(builtin, err))
2754 }
2755 "ydisplaylabels" => {
2756 let labels = label_strings_from_value(value, builtin, "labels")?;
2757 if labels.len() != heatmap_handle.y_labels.len() {
2758 return Err(plotting_error(
2759 builtin,
2760 format!("{builtin}: YDisplayLabels length must match heatmap rows"),
2761 ));
2762 }
2763 super::state::set_heatmap_display_labels(
2764 heatmap_handle.figure,
2765 heatmap_handle.axes_index,
2766 heatmap_handle.plot_index,
2767 None,
2768 Some(labels),
2769 )
2770 .map_err(|err| map_figure_error(builtin, err))
2771 }
2772 other => Err(plotting_error(
2773 builtin,
2774 format!("{builtin}: unsupported heatmap property `{other}`"),
2775 )),
2776 }
2777}
2778
2779fn apply_area_property(
2780 area_handle: &super::state::AreaHandleState,
2781 key: &str,
2782 value: &Value,
2783 builtin: &'static str,
2784) -> BuiltinResult<()> {
2785 super::state::update_area_plot(
2786 area_handle.figure,
2787 area_handle.plot_index,
2788 |area| match key {
2789 "color" => {
2790 if let Ok(c) = parse_color_value(&LineStyleParseOptions::generic(builtin), value) {
2791 area.color = c;
2792 }
2793 }
2794 "basevalue" => {
2795 if let Some(v) = value_as_f64(value) {
2796 area.baseline = v;
2797 area.lower_y = None;
2798 }
2799 }
2800 _ => {}
2801 },
2802 )
2803 .map_err(|err| map_figure_error(builtin, err))?;
2804 Ok(())
2805}
2806
2807fn apply_line_property(
2808 line_handle: &super::state::SimplePlotHandleState,
2809 key: &str,
2810 value: &Value,
2811 builtin: &'static str,
2812) -> BuiltinResult<()> {
2813 super::state::update_plot_element(line_handle.figure, line_handle.plot_index, |plot| {
2814 if let runmat_plot::plots::figure::PlotElement::Line(line) = plot {
2815 apply_line_plot_properties(line, key, value, builtin);
2816 }
2817 })
2818 .map_err(|err| map_figure_error(builtin, err))?;
2819 Ok(())
2820}
2821
2822fn apply_reference_line_property(
2823 line_handle: &super::state::SimplePlotHandleState,
2824 key: &str,
2825 value: &Value,
2826 builtin: &'static str,
2827) -> BuiltinResult<()> {
2828 super::state::update_plot_element(line_handle.figure, line_handle.plot_index, |plot| {
2829 if let runmat_plot::plots::figure::PlotElement::ReferenceLine(line) = plot {
2830 match key {
2831 "value" => {
2832 if let Some(v) = value_as_f64(value) {
2833 if v.is_finite() {
2834 line.value = v;
2835 }
2836 }
2837 }
2838 "color" => {
2839 if let Ok(c) =
2840 parse_color_value(&LineStyleParseOptions::generic(builtin), value)
2841 {
2842 line.color = c;
2843 }
2844 }
2845 "linewidth" => {
2846 if let Some(v) = value_as_f64(value) {
2847 if v > 0.0 {
2848 line.line_width = v as f32;
2849 }
2850 }
2851 }
2852 "linestyle" => {
2853 if let Some(s) = value_as_string(value) {
2854 line.line_style = parse_line_style_name_for_props(&s);
2855 }
2856 }
2857 "label" => {
2858 line.label = value_as_string(value).map(|s| s.to_string());
2859 }
2860 "labelorientation" => {
2861 if let Some(s) = value_as_string(value) {
2862 line.label_orientation = s.to_ascii_lowercase();
2863 }
2864 }
2865 "displayname" => {
2866 line.display_name = value_as_string(value).map(|s| s.to_string());
2867 }
2868 "visible" => {
2869 if let Some(v) = value_as_bool(value) {
2870 line.visible = v;
2871 } else if let Some(s) = value_as_string(value) {
2872 line.visible =
2873 !matches!(s.trim().to_ascii_lowercase().as_str(), "off" | "false");
2874 }
2875 }
2876 _ => {}
2877 }
2878 }
2879 })
2880 .map_err(|err| map_figure_error(builtin, err))?;
2881 Ok(())
2882}
2883
2884fn apply_stairs_property(
2885 stairs_handle: &super::state::SimplePlotHandleState,
2886 key: &str,
2887 value: &Value,
2888 builtin: &'static str,
2889) -> BuiltinResult<()> {
2890 super::state::update_plot_element(stairs_handle.figure, stairs_handle.plot_index, |plot| {
2891 if let runmat_plot::plots::figure::PlotElement::Stairs(stairs) = plot {
2892 match key {
2893 "color" => {
2894 if let Ok(c) =
2895 parse_color_value(&LineStyleParseOptions::generic(builtin), value)
2896 {
2897 stairs.color = c;
2898 }
2899 }
2900 "linewidth" => {
2901 if let Some(v) = value_as_f64(value) {
2902 stairs.line_width = v as f32;
2903 }
2904 }
2905 "displayname" => {
2906 stairs.label = value_as_string(value).map(|s| s.to_string());
2907 }
2908 _ => {}
2909 }
2910 }
2911 })
2912 .map_err(|err| map_figure_error(builtin, err))?;
2913 Ok(())
2914}
2915
2916fn apply_scatter_property(
2917 scatter_handle: &super::state::SimplePlotHandleState,
2918 key: &str,
2919 value: &Value,
2920 builtin: &'static str,
2921) -> BuiltinResult<()> {
2922 super::state::update_plot_element(scatter_handle.figure, scatter_handle.plot_index, |plot| {
2923 if let runmat_plot::plots::figure::PlotElement::Scatter(scatter) = plot {
2924 match key {
2925 "marker" => {
2926 if let Some(s) = value_as_string(value) {
2927 scatter.marker_style = scatter_marker_from_name(&s, scatter.marker_style);
2928 }
2929 }
2930 "sizedata" => {
2931 if let Some(v) = value_as_f64(value) {
2932 scatter.marker_size = v as f32;
2933 }
2934 }
2935 "markerfacecolor" => {
2936 if let Ok(c) =
2937 parse_color_value(&LineStyleParseOptions::generic(builtin), value)
2938 {
2939 scatter.set_face_color(c);
2940 }
2941 }
2942 "markeredgecolor" => {
2943 if let Ok(c) =
2944 parse_color_value(&LineStyleParseOptions::generic(builtin), value)
2945 {
2946 scatter.set_edge_color(c);
2947 }
2948 }
2949 "linewidth" => {
2950 if let Some(v) = value_as_f64(value) {
2951 scatter.set_edge_thickness(v as f32);
2952 }
2953 }
2954 "displayname" => {
2955 scatter.label = value_as_string(value).map(|s| s.to_string());
2956 }
2957 _ => {}
2958 }
2959 }
2960 })
2961 .map_err(|err| map_figure_error(builtin, err))?;
2962 Ok(())
2963}
2964
2965fn apply_bar_property(
2966 bar_handle: &super::state::SimplePlotHandleState,
2967 key: &str,
2968 value: &Value,
2969 builtin: &'static str,
2970) -> BuiltinResult<()> {
2971 super::state::update_plot_element(bar_handle.figure, bar_handle.plot_index, |plot| {
2972 if let runmat_plot::plots::figure::PlotElement::Bar(bar) = plot {
2973 match key {
2974 "facecolor" | "color" => {
2975 if let Ok(c) =
2976 parse_color_value(&LineStyleParseOptions::generic(builtin), value)
2977 {
2978 bar.color = c;
2979 }
2980 }
2981 "barwidth" => {
2982 if let Some(v) = value_as_f64(value) {
2983 bar.bar_width = v as f32;
2984 }
2985 }
2986 "displayname" => {
2987 bar.label = value_as_string(value).map(|s| s.to_string());
2988 }
2989 _ => {}
2990 }
2991 }
2992 })
2993 .map_err(|err| map_figure_error(builtin, err))?;
2994 Ok(())
2995}
2996
2997fn apply_surface_property(
2998 surface_handle: &super::state::SimplePlotHandleState,
2999 key: &str,
3000 value: &Value,
3001 builtin: &'static str,
3002) -> BuiltinResult<()> {
3003 super::state::update_plot_element(surface_handle.figure, surface_handle.plot_index, |plot| {
3004 if let runmat_plot::plots::figure::PlotElement::Surface(surface) = plot {
3005 match key {
3006 "facealpha" => {
3007 if let Some(v) = value_as_f64(value) {
3008 surface.alpha = v as f32;
3009 }
3010 }
3011 "displayname" => {
3012 surface.label = value_as_string(value).map(|s| s.to_string());
3013 }
3014 _ => {}
3015 }
3016 }
3017 })
3018 .map_err(|err| map_figure_error(builtin, err))?;
3019 Ok(())
3020}
3021
3022fn apply_patch_property(
3023 patch_handle: &super::state::SimplePlotHandleState,
3024 key: &str,
3025 value: &Value,
3026 builtin: &'static str,
3027) -> BuiltinResult<()> {
3028 super::state::update_plot_element(patch_handle.figure, patch_handle.plot_index, |plot| {
3029 if let runmat_plot::plots::figure::PlotElement::Patch(patch) = plot {
3030 match key {
3031 "facecolor" | "color" => {
3032 if let Some(text) = value_as_string(value) {
3033 match text.trim().to_ascii_lowercase().as_str() {
3034 "none" => patch
3035 .set_face_color_mode(runmat_plot::plots::PatchFaceColorMode::None),
3036 "flat" => patch
3037 .set_face_color_mode(runmat_plot::plots::PatchFaceColorMode::Flat),
3038 _ => {
3039 if let Ok(c) = parse_color_value(
3040 &LineStyleParseOptions::generic(builtin),
3041 value,
3042 ) {
3043 patch.set_face_color(c);
3044 patch.set_face_color_mode(
3045 runmat_plot::plots::PatchFaceColorMode::Color,
3046 );
3047 }
3048 }
3049 }
3050 } else if let Ok(c) =
3051 parse_color_value(&LineStyleParseOptions::generic(builtin), value)
3052 {
3053 patch.set_face_color(c);
3054 patch.set_face_color_mode(runmat_plot::plots::PatchFaceColorMode::Color);
3055 }
3056 }
3057 "edgecolor" => {
3058 if let Some(text) = value_as_string(value) {
3059 if text.trim().eq_ignore_ascii_case("none") {
3060 patch.set_edge_color_mode(runmat_plot::plots::PatchEdgeColorMode::None);
3061 } else if let Ok(c) =
3062 parse_color_value(&LineStyleParseOptions::generic(builtin), value)
3063 {
3064 patch.set_edge_color(c);
3065 patch
3066 .set_edge_color_mode(runmat_plot::plots::PatchEdgeColorMode::Color);
3067 }
3068 } else if let Ok(c) =
3069 parse_color_value(&LineStyleParseOptions::generic(builtin), value)
3070 {
3071 patch.set_edge_color(c);
3072 patch.set_edge_color_mode(runmat_plot::plots::PatchEdgeColorMode::Color);
3073 }
3074 }
3075 "facealpha" => {
3076 if let Some(v) = value_as_f64(value) {
3077 patch.set_face_alpha(v as f32);
3078 }
3079 }
3080 "edgealpha" => {
3081 if let Some(v) = value_as_f64(value) {
3082 patch.set_edge_alpha(v as f32);
3083 }
3084 }
3085 "linewidth" => {
3086 if let Some(v) = value_as_f64(value) {
3087 patch.set_line_width(v as f32);
3088 }
3089 }
3090 "displayname" => {
3091 patch.set_label(value_as_string(value).map(|s| s.to_string()));
3092 }
3093 "visible" => {
3094 if let Some(v) = value_as_bool(value) {
3095 patch.set_visible(v);
3096 }
3097 }
3098 _ => {}
3099 }
3100 }
3101 })
3102 .map_err(|err| map_figure_error(builtin, err))?;
3103 Ok(())
3104}
3105
3106fn apply_line3_property(
3107 line_handle: &super::state::SimplePlotHandleState,
3108 key: &str,
3109 value: &Value,
3110 builtin: &'static str,
3111) -> BuiltinResult<()> {
3112 super::state::update_plot_element(line_handle.figure, line_handle.plot_index, |plot| {
3113 if let runmat_plot::plots::figure::PlotElement::Line3(line) = plot {
3114 match key {
3115 "color" => {
3116 if let Ok(c) =
3117 parse_color_value(&LineStyleParseOptions::generic(builtin), value)
3118 {
3119 line.color = c;
3120 }
3121 }
3122 "linewidth" => {
3123 if let Some(v) = value_as_f64(value) {
3124 line.line_width = v as f32;
3125 }
3126 }
3127 "linestyle" => {
3128 if let Some(s) = value_as_string(value) {
3129 line.line_style = parse_line_style_name_for_props(&s);
3130 }
3131 }
3132 "displayname" => {
3133 line.label = value_as_string(value).map(|s| s.to_string());
3134 }
3135 _ => {}
3136 }
3137 }
3138 })
3139 .map_err(|err| map_figure_error(builtin, err))?;
3140 Ok(())
3141}
3142
3143fn apply_scatter3_property(
3144 scatter_handle: &super::state::SimplePlotHandleState,
3145 key: &str,
3146 value: &Value,
3147 builtin: &'static str,
3148) -> BuiltinResult<()> {
3149 super::state::update_plot_element(scatter_handle.figure, scatter_handle.plot_index, |plot| {
3150 if let runmat_plot::plots::figure::PlotElement::Scatter3(scatter) = plot {
3151 match key {
3152 "sizedata" => {
3153 if let Some(v) = value_as_f64(value) {
3154 scatter.point_size = v as f32;
3155 }
3156 }
3157 "displayname" => {
3158 scatter.label = value_as_string(value).map(|s| s.to_string());
3159 }
3160 _ => {}
3161 }
3162 }
3163 })
3164 .map_err(|err| map_figure_error(builtin, err))?;
3165 Ok(())
3166}
3167
3168fn apply_pie_property(
3169 pie_handle: &super::state::SimplePlotHandleState,
3170 key: &str,
3171 value: &Value,
3172 builtin: &'static str,
3173) -> BuiltinResult<()> {
3174 super::state::update_plot_element(pie_handle.figure, pie_handle.plot_index, |plot| {
3175 if let runmat_plot::plots::figure::PlotElement::Pie(pie) = plot {
3176 if key == "displayname" {
3177 pie.label = value_as_string(value).map(|s| s.to_string());
3178 }
3179 }
3180 })
3181 .map_err(|err| map_figure_error(builtin, err))?;
3182 Ok(())
3183}
3184
3185fn apply_contour_property(
3186 contour_handle: &super::state::SimplePlotHandleState,
3187 key: &str,
3188 value: &Value,
3189 builtin: &'static str,
3190) -> BuiltinResult<()> {
3191 super::state::update_plot_element(contour_handle.figure, contour_handle.plot_index, |plot| {
3192 if let runmat_plot::plots::figure::PlotElement::Contour(contour) = plot {
3193 if key == "displayname" {
3194 contour.label = value_as_string(value).map(|s| s.to_string());
3195 }
3196 }
3197 })
3198 .map_err(|err| map_figure_error(builtin, err))?;
3199 Ok(())
3200}
3201
3202fn apply_contour_fill_property(
3203 fill_handle: &super::state::SimplePlotHandleState,
3204 key: &str,
3205 value: &Value,
3206 builtin: &'static str,
3207) -> BuiltinResult<()> {
3208 super::state::update_plot_element(fill_handle.figure, fill_handle.plot_index, |plot| {
3209 if let runmat_plot::plots::figure::PlotElement::ContourFill(fill) = plot {
3210 if key == "displayname" {
3211 fill.label = value_as_string(value).map(|s| s.to_string());
3212 }
3213 }
3214 })
3215 .map_err(|err| map_figure_error(builtin, err))?;
3216 Ok(())
3217}
3218
3219fn apply_line_plot_properties(
3220 line: &mut runmat_plot::plots::LinePlot,
3221 key: &str,
3222 value: &Value,
3223 builtin: &'static str,
3224) {
3225 match key {
3226 "color" => {
3227 if let Ok(c) = parse_color_value(&LineStyleParseOptions::generic(builtin), value) {
3228 line.color = c;
3229 }
3230 }
3231 "linewidth" => {
3232 if let Some(v) = value_as_f64(value) {
3233 line.line_width = v as f32;
3234 }
3235 }
3236 "linestyle" => {
3237 if let Some(s) = value_as_string(value) {
3238 line.line_style = parse_line_style_name_for_props(&s);
3239 }
3240 }
3241 "displayname" => {
3242 line.label = value_as_string(value).map(|s| s.to_string());
3243 }
3244 "marker" => {
3245 if let Some(s) = value_as_string(value) {
3246 line.marker = marker_from_name(&s, line.marker.clone());
3247 }
3248 }
3249 "markersize" => {
3250 if let Some(v) = value_as_f64(value) {
3251 if let Some(marker) = &mut line.marker {
3252 marker.size = v as f32;
3253 }
3254 }
3255 }
3256 "markerfacecolor" => {
3257 if let Ok(c) = parse_color_value(&LineStyleParseOptions::generic(builtin), value) {
3258 if let Some(marker) = &mut line.marker {
3259 marker.face_color = c;
3260 }
3261 }
3262 }
3263 "markeredgecolor" => {
3264 if let Ok(c) = parse_color_value(&LineStyleParseOptions::generic(builtin), value) {
3265 if let Some(marker) = &mut line.marker {
3266 marker.edge_color = c;
3267 }
3268 }
3269 }
3270 "filled" => {
3271 if let Some(v) = value_as_bool(value) {
3272 if let Some(marker) = &mut line.marker {
3273 marker.filled = v;
3274 }
3275 }
3276 }
3277 _ => {}
3278 }
3279}
3280
3281fn limits_from_optional_value(
3282 value: &Value,
3283 builtin: &'static str,
3284) -> BuiltinResult<Option<(f64, f64)>> {
3285 if let Some(text) = value_as_string(value) {
3286 let norm = text.trim().to_ascii_lowercase();
3287 if matches!(norm.as_str(), "auto" | "tight") {
3288 return Ok(None);
3289 }
3290 }
3291 Ok(Some(
3292 crate::builtins::plotting::op_common::limits::limits_from_value(value, builtin)?,
3293 ))
3294}
3295
3296fn parse_colormap_name(
3297 name: &str,
3298 builtin: &'static str,
3299) -> BuiltinResult<runmat_plot::plots::surface::ColorMap> {
3300 match name.trim().to_ascii_lowercase().as_str() {
3301 "parula" => Ok(runmat_plot::plots::surface::ColorMap::Parula),
3302 "viridis" => Ok(runmat_plot::plots::surface::ColorMap::Viridis),
3303 "plasma" => Ok(runmat_plot::plots::surface::ColorMap::Plasma),
3304 "inferno" => Ok(runmat_plot::plots::surface::ColorMap::Inferno),
3305 "magma" => Ok(runmat_plot::plots::surface::ColorMap::Magma),
3306 "turbo" => Ok(runmat_plot::plots::surface::ColorMap::Turbo),
3307 "jet" => Ok(runmat_plot::plots::surface::ColorMap::Jet),
3308 "hot" => Ok(runmat_plot::plots::surface::ColorMap::Hot),
3309 "cool" => Ok(runmat_plot::plots::surface::ColorMap::Cool),
3310 "spring" => Ok(runmat_plot::plots::surface::ColorMap::Spring),
3311 "summer" => Ok(runmat_plot::plots::surface::ColorMap::Summer),
3312 "autumn" => Ok(runmat_plot::plots::surface::ColorMap::Autumn),
3313 "winter" => Ok(runmat_plot::plots::surface::ColorMap::Winter),
3314 "gray" | "grey" => Ok(runmat_plot::plots::surface::ColorMap::Gray),
3315 "bone" => Ok(runmat_plot::plots::surface::ColorMap::Bone),
3316 "copper" => Ok(runmat_plot::plots::surface::ColorMap::Copper),
3317 "pink" => Ok(runmat_plot::plots::surface::ColorMap::Pink),
3318 "lines" => Ok(runmat_plot::plots::surface::ColorMap::Lines),
3319 other => Err(plotting_error(
3320 builtin,
3321 format!("{builtin}: unknown colormap '{other}'"),
3322 )),
3323 }
3324}
3325
3326fn apply_axes_text_alias(
3327 handle: FigureHandle,
3328 axes_index: usize,
3329 kind: PlotObjectKind,
3330 value: &Value,
3331 builtin: &'static str,
3332) -> BuiltinResult<()> {
3333 if let Some(text) = value_as_string(value) {
3334 set_text_properties_for_axes(handle, axes_index, kind, Some(text), None)
3335 .map_err(|err| map_figure_error(builtin, err))?;
3336 return Ok(());
3337 }
3338
3339 let scalar = handle_scalar(value, builtin)?;
3340 let (src_handle, src_axes, src_kind) =
3341 decode_plot_object_handle(scalar).map_err(|err| map_figure_error(builtin, err))?;
3342 if src_kind != kind {
3343 return Err(plotting_error(
3344 builtin,
3345 format!(
3346 "{builtin}: expected a matching text handle for `{}`",
3347 key_name(kind)
3348 ),
3349 ));
3350 }
3351 let meta = axes_metadata_snapshot(src_handle, src_axes)
3352 .map_err(|err| map_figure_error(builtin, err))?;
3353 let (text, style) = match kind {
3354 PlotObjectKind::Title => (meta.title, meta.title_style),
3355 PlotObjectKind::XLabel => (meta.x_label, meta.x_label_style),
3356 PlotObjectKind::YLabel => (meta.y_label, meta.y_label_style),
3357 PlotObjectKind::ZLabel => (meta.z_label, meta.z_label_style),
3358 PlotObjectKind::Legend => unreachable!(),
3359 PlotObjectKind::SuperTitle => unreachable!(),
3360 };
3361 set_text_properties_for_axes(handle, axes_index, kind, text, Some(style))
3362 .map_err(|err| map_figure_error(builtin, err))?;
3363 Ok(())
3364}
3365
3366fn validate_axes_text_alias(
3367 kind: PlotObjectKind,
3368 value: &Value,
3369 builtin: &'static str,
3370) -> BuiltinResult<()> {
3371 if value_as_string(value).is_some() {
3372 return Ok(());
3373 }
3374
3375 let scalar = handle_scalar(value, builtin)?;
3376 let (src_handle, src_axes, src_kind) =
3377 decode_plot_object_handle(scalar).map_err(|err| map_figure_error(builtin, err))?;
3378 if src_kind != kind {
3379 return Err(plotting_error(
3380 builtin,
3381 format!(
3382 "{builtin}: expected a matching text handle for `{}`",
3383 key_name(kind)
3384 ),
3385 ));
3386 }
3387 axes_metadata_snapshot(src_handle, src_axes).map_err(|err| map_figure_error(builtin, err))?;
3388 Ok(())
3389}
3390
3391fn apply_figure_text_alias(
3392 handle: FigureHandle,
3393 kind: PlotObjectKind,
3394 value: &Value,
3395 builtin: &'static str,
3396) -> BuiltinResult<()> {
3397 if let Some(text) = value_as_text_string(value) {
3398 match kind {
3399 PlotObjectKind::SuperTitle => {
3400 set_sg_title_properties_for_figure(handle, Some(text), None)
3401 .map_err(|err| map_figure_error(builtin, err))?;
3402 }
3403 _ => unreachable!(),
3404 }
3405 return Ok(());
3406 }
3407
3408 let scalar = handle_scalar(value, builtin)?;
3409 let (src_handle, _src_axes, src_kind) =
3410 decode_plot_object_handle(scalar).map_err(|err| map_figure_error(builtin, err))?;
3411 if src_kind != kind {
3412 return Err(plotting_error(
3413 builtin,
3414 format!(
3415 "{builtin}: expected a matching text handle for `{}`",
3416 key_name(kind)
3417 ),
3418 ));
3419 }
3420
3421 let figure = super::state::clone_figure(src_handle)
3422 .ok_or_else(|| plotting_error(builtin, format!("{builtin}: invalid figure handle")))?;
3423 let (text, style) = match kind {
3424 PlotObjectKind::SuperTitle => (figure.sg_title, figure.sg_title_style),
3425 _ => unreachable!(),
3426 };
3427 set_sg_title_properties_for_figure(handle, text, Some(style))
3428 .map_err(|err| map_figure_error(builtin, err))?;
3429 Ok(())
3430}
3431
3432fn collect_label_strings(builtin: &'static str, args: &[Value]) -> BuiltinResult<Vec<String>> {
3433 let mut labels = Vec::new();
3434 for arg in args {
3435 match arg {
3436 Value::StringArray(arr) => labels.extend(arr.data.iter().cloned()),
3437 Value::Cell(cell) => {
3438 for row in 0..cell.rows {
3439 for col in 0..cell.cols {
3440 let value = cell.get(row, col).map_err(|err| {
3441 plotting_error(builtin, format!("legend: invalid label cell: {err}"))
3442 })?;
3443 labels.push(value_as_string(&value).ok_or_else(|| {
3444 plotting_error(builtin, "legend: labels must be strings or char arrays")
3445 })?);
3446 }
3447 }
3448 }
3449 _ => labels.push(value_as_string(arg).ok_or_else(|| {
3450 plotting_error(builtin, "legend: labels must be strings or char arrays")
3451 })?),
3452 }
3453 }
3454 Ok(labels)
3455}
3456
3457fn handle_scalar(value: &Value, builtin: &'static str) -> BuiltinResult<f64> {
3458 match value {
3459 Value::Num(v) => Ok(*v),
3460 Value::Int(i) => Ok(i.to_f64()),
3461 Value::Tensor(t) if t.data.len() == 1 => Ok(t.data[0]),
3462 _ => Err(plotting_error(
3463 builtin,
3464 format!("{builtin}: expected plotting handle"),
3465 )),
3466 }
3467}
3468
3469fn legend_labels_value(labels: Vec<String>) -> Value {
3470 Value::StringArray(StringArray {
3471 rows: 1,
3472 cols: labels.len().max(1),
3473 shape: vec![1, labels.len().max(1)],
3474 data: labels,
3475 })
3476}
3477
3478fn text_value(text: Option<String>) -> Value {
3479 match text {
3480 Some(text) if text.contains('\n') => {
3481 let lines: Vec<String> = text.split('\n').map(|s| s.to_string()).collect();
3482 Value::StringArray(StringArray {
3483 rows: 1,
3484 cols: lines.len().max(1),
3485 shape: vec![1, lines.len().max(1)],
3486 data: lines,
3487 })
3488 }
3489 Some(text) => Value::String(text),
3490 None => Value::String(String::new()),
3491 }
3492}
3493
3494fn handles_value(handles: Vec<f64>) -> Value {
3495 Value::Tensor(runmat_builtins::Tensor {
3496 rows: 1,
3497 cols: handles.len(),
3498 shape: vec![1, handles.len()],
3499 data: handles,
3500 dtype: runmat_builtins::NumericDType::F64,
3501 })
3502}
3503
3504fn tensor_from_vec(data: Vec<f64>) -> Value {
3505 Value::Tensor(runmat_builtins::Tensor {
3506 rows: 1,
3507 cols: data.len(),
3508 shape: vec![1, data.len()],
3509 data,
3510 dtype: runmat_builtins::NumericDType::F64,
3511 })
3512}
3513
3514fn string_array_from_vec(data: Vec<String>) -> BuiltinResult<Value> {
3515 let cols = data.len();
3516 let array = StringArray::new(data, vec![1, cols])
3517 .map_err(|e| plotting_error("get", format!("get: {e}")))?;
3518 Ok(Value::StringArray(array))
3519}
3520
3521pub(crate) fn label_strings_from_value(
3522 value: &Value,
3523 builtin: &'static str,
3524 label_context: &str,
3525) -> BuiltinResult<Vec<String>> {
3526 match value {
3527 Value::StringArray(array) => Ok(array.data.clone()),
3528 Value::Cell(cell) => cell
3529 .data
3530 .iter()
3531 .map(|item| {
3532 value_as_text_string(item).ok_or_else(|| {
3533 plotting_error(
3534 builtin,
3535 format!("{builtin}: {label_context} must contain text values"),
3536 )
3537 })
3538 })
3539 .collect(),
3540 Value::CharArray(chars) if chars.rows == 1 => Ok(vec![chars.data.iter().collect()]),
3541 Value::String(text) => Ok(vec![text.clone()]),
3542 Value::Tensor(tensor) => Ok(tensor.data.iter().map(|v| v.to_string()).collect()),
3543 Value::Int(i) => Ok(vec![i.to_i64().to_string()]),
3544 Value::Num(v) => Ok(vec![v.to_string()]),
3545 other => Err(plotting_error(
3546 builtin,
3547 format!("{builtin}: unsupported {label_context} value {other:?}"),
3548 )),
3549 }
3550}
3551
3552fn tensor_from_matrix(data: Vec<Vec<f64>>) -> Value {
3553 let rows = data.len();
3554 let cols = data.first().map(|row| row.len()).unwrap_or(0);
3555 let flat = data.into_iter().flat_map(|row| row.into_iter()).collect();
3556 Value::Tensor(runmat_builtins::Tensor {
3557 rows,
3558 cols,
3559 shape: vec![rows, cols],
3560 data: flat,
3561 dtype: runmat_builtins::NumericDType::F64,
3562 })
3563}
3564
3565fn vertices_tensor(vertices: &[glam::Vec3]) -> Value {
3566 let rows = vertices.len();
3567 let cols = 3;
3568 let mut data = Vec::with_capacity(rows * cols);
3569 for col in 0..cols {
3570 for vertex in vertices {
3571 data.push(match col {
3572 0 => vertex.x as f64,
3573 1 => vertex.y as f64,
3574 _ => vertex.z as f64,
3575 });
3576 }
3577 }
3578 Value::Tensor(runmat_builtins::Tensor {
3579 rows,
3580 cols,
3581 shape: vec![rows, cols],
3582 data,
3583 dtype: runmat_builtins::NumericDType::F64,
3584 })
3585}
3586
3587fn faces_tensor(faces: &[Vec<usize>]) -> Value {
3588 let rows = faces.len();
3589 let cols = faces.iter().map(|face| face.len()).max().unwrap_or(0);
3590 let mut data = Vec::with_capacity(rows * cols);
3591 for col in 0..cols {
3592 for face in faces {
3593 data.push(
3594 face.get(col)
3595 .map(|idx| *idx as f64 + 1.0)
3596 .unwrap_or(f64::NAN),
3597 );
3598 }
3599 }
3600 Value::Tensor(runmat_builtins::Tensor {
3601 rows,
3602 cols,
3603 shape: vec![rows, cols],
3604 data,
3605 dtype: runmat_builtins::NumericDType::F64,
3606 })
3607}
3608
3609fn patch_color_property(mode: runmat_plot::plots::PatchFaceColorMode, color: glam::Vec4) -> Value {
3610 match mode {
3611 runmat_plot::plots::PatchFaceColorMode::None => Value::String("none".into()),
3612 runmat_plot::plots::PatchFaceColorMode::Flat => Value::String("flat".into()),
3613 runmat_plot::plots::PatchFaceColorMode::Color => Value::String(color_to_short_name(color)),
3614 }
3615}
3616
3617fn patch_edge_color_property(
3618 mode: runmat_plot::plots::PatchEdgeColorMode,
3619 color: glam::Vec4,
3620) -> Value {
3621 match mode {
3622 runmat_plot::plots::PatchEdgeColorMode::None => Value::String("none".into()),
3623 runmat_plot::plots::PatchEdgeColorMode::Color => Value::String(color_to_short_name(color)),
3624 }
3625}
3626
3627fn insert_line_marker_struct_props(
3628 st: &mut StructValue,
3629 marker: Option<&runmat_plot::plots::line::LineMarkerAppearance>,
3630) {
3631 if let Some(marker) = marker {
3632 st.insert(
3633 "Marker",
3634 Value::String(marker_style_name(marker.kind).into()),
3635 );
3636 st.insert("MarkerSize", Value::Num(marker.size as f64));
3637 st.insert(
3638 "MarkerFaceColor",
3639 Value::String(color_to_short_name(marker.face_color)),
3640 );
3641 st.insert(
3642 "MarkerEdgeColor",
3643 Value::String(color_to_short_name(marker.edge_color)),
3644 );
3645 st.insert("Filled", Value::Bool(marker.filled));
3646 }
3647}
3648
3649fn line_marker_property_value(
3650 marker: &Option<runmat_plot::plots::line::LineMarkerAppearance>,
3651 name: &str,
3652 builtin: &'static str,
3653) -> BuiltinResult<Value> {
3654 match name {
3655 "marker" => Ok(Value::String(
3656 marker
3657 .as_ref()
3658 .map(|m| marker_style_name(m.kind).to_string())
3659 .unwrap_or_else(|| "none".into()),
3660 )),
3661 "markersize" => Ok(Value::Num(
3662 marker.as_ref().map(|m| m.size as f64).unwrap_or(0.0),
3663 )),
3664 "markerfacecolor" => Ok(Value::String(
3665 marker
3666 .as_ref()
3667 .map(|m| color_to_short_name(m.face_color))
3668 .unwrap_or_else(|| "none".into()),
3669 )),
3670 "markeredgecolor" => Ok(Value::String(
3671 marker
3672 .as_ref()
3673 .map(|m| color_to_short_name(m.edge_color))
3674 .unwrap_or_else(|| "none".into()),
3675 )),
3676 "filled" => Ok(Value::Bool(
3677 marker.as_ref().map(|m| m.filled).unwrap_or(false),
3678 )),
3679 other => Err(plotting_error(
3680 builtin,
3681 format!("{builtin}: unsupported line property `{other}`"),
3682 )),
3683 }
3684}
3685
3686fn histogram_labels_from_edges(edges: &[f64]) -> Vec<String> {
3687 edges
3688 .windows(2)
3689 .map(|pair| format!("[{:.3}, {:.3})", pair[0], pair[1]))
3690 .collect()
3691}
3692
3693fn validate_histogram_normalization(norm: &str, builtin: &'static str) -> BuiltinResult<()> {
3694 match norm {
3695 "count" | "probability" | "countdensity" | "pdf" | "cumcount" | "cdf" => Ok(()),
3696 other => Err(plotting_error(
3697 builtin,
3698 format!("{builtin}: unsupported histogram normalization `{other}`"),
3699 )),
3700 }
3701}
3702
3703fn apply_histogram_normalization(raw_counts: &[f64], edges: &[f64], norm: &str) -> Vec<f64> {
3704 let widths: Vec<f64> = edges.windows(2).map(|pair| pair[1] - pair[0]).collect();
3705 let total: f64 = raw_counts.iter().sum();
3706 match norm {
3707 "count" => raw_counts.to_vec(),
3708 "probability" => {
3709 if total > 0.0 {
3710 raw_counts.iter().map(|&c| c / total).collect()
3711 } else {
3712 vec![0.0; raw_counts.len()]
3713 }
3714 }
3715 "countdensity" => raw_counts
3716 .iter()
3717 .zip(widths.iter())
3718 .map(|(&c, &w)| if w > 0.0 { c / w } else { 0.0 })
3719 .collect(),
3720 "pdf" => {
3721 if total > 0.0 {
3722 raw_counts
3723 .iter()
3724 .zip(widths.iter())
3725 .map(|(&c, &w)| if w > 0.0 { c / (total * w) } else { 0.0 })
3726 .collect()
3727 } else {
3728 vec![0.0; raw_counts.len()]
3729 }
3730 }
3731 "cumcount" => {
3732 let mut acc = 0.0;
3733 raw_counts
3734 .iter()
3735 .map(|&c| {
3736 acc += c;
3737 acc
3738 })
3739 .collect()
3740 }
3741 "cdf" => {
3742 if total > 0.0 {
3743 let mut acc = 0.0;
3744 raw_counts
3745 .iter()
3746 .map(|&c| {
3747 acc += c;
3748 acc / total
3749 })
3750 .collect()
3751 } else {
3752 vec![0.0; raw_counts.len()]
3753 }
3754 }
3755 _ => raw_counts.to_vec(),
3756 }
3757}
3758
3759fn line_style_name(style: runmat_plot::plots::line::LineStyle) -> &'static str {
3760 match style {
3761 runmat_plot::plots::line::LineStyle::Solid => "-",
3762 runmat_plot::plots::line::LineStyle::Dashed => "--",
3763 runmat_plot::plots::line::LineStyle::Dotted => ":",
3764 runmat_plot::plots::line::LineStyle::DashDot => "-.",
3765 }
3766}
3767
3768fn parse_line_style_name_for_props(name: &str) -> runmat_plot::plots::line::LineStyle {
3769 match name.trim() {
3770 "--" | "dashed" => runmat_plot::plots::line::LineStyle::Dashed,
3771 ":" | "dotted" => runmat_plot::plots::line::LineStyle::Dotted,
3772 "-." | "dashdot" => runmat_plot::plots::line::LineStyle::DashDot,
3773 _ => runmat_plot::plots::line::LineStyle::Solid,
3774 }
3775}
3776
3777fn marker_style_name(style: runmat_plot::plots::scatter::MarkerStyle) -> &'static str {
3778 match style {
3779 runmat_plot::plots::scatter::MarkerStyle::Circle => "o",
3780 runmat_plot::plots::scatter::MarkerStyle::Square => "s",
3781 runmat_plot::plots::scatter::MarkerStyle::Triangle => "^",
3782 runmat_plot::plots::scatter::MarkerStyle::Diamond => "d",
3783 runmat_plot::plots::scatter::MarkerStyle::Plus => "+",
3784 runmat_plot::plots::scatter::MarkerStyle::Cross => "x",
3785 runmat_plot::plots::scatter::MarkerStyle::Star => "*",
3786 runmat_plot::plots::scatter::MarkerStyle::Hexagon => "h",
3787 }
3788}
3789
3790fn marker_from_name(
3791 name: &str,
3792 current: Option<runmat_plot::plots::line::LineMarkerAppearance>,
3793) -> Option<runmat_plot::plots::line::LineMarkerAppearance> {
3794 let mut marker = current.unwrap_or(runmat_plot::plots::line::LineMarkerAppearance {
3795 kind: runmat_plot::plots::scatter::MarkerStyle::Circle,
3796 size: 6.0,
3797 edge_color: glam::Vec4::new(0.0, 0.447, 0.741, 1.0),
3798 face_color: glam::Vec4::new(0.0, 0.447, 0.741, 1.0),
3799 filled: false,
3800 });
3801 marker.kind = match name.trim() {
3802 "o" => runmat_plot::plots::scatter::MarkerStyle::Circle,
3803 "s" => runmat_plot::plots::scatter::MarkerStyle::Square,
3804 "^" => runmat_plot::plots::scatter::MarkerStyle::Triangle,
3805 "d" => runmat_plot::plots::scatter::MarkerStyle::Diamond,
3806 "+" => runmat_plot::plots::scatter::MarkerStyle::Plus,
3807 "x" => runmat_plot::plots::scatter::MarkerStyle::Cross,
3808 "*" => runmat_plot::plots::scatter::MarkerStyle::Star,
3809 "h" => runmat_plot::plots::scatter::MarkerStyle::Hexagon,
3810 "none" => return None,
3811 _ => marker.kind,
3812 };
3813 Some(marker)
3814}
3815
3816fn scatter_marker_from_name(
3817 name: &str,
3818 current: runmat_plot::plots::scatter::MarkerStyle,
3819) -> runmat_plot::plots::scatter::MarkerStyle {
3820 match name.trim() {
3821 "o" => runmat_plot::plots::scatter::MarkerStyle::Circle,
3822 "s" => runmat_plot::plots::scatter::MarkerStyle::Square,
3823 "^" => runmat_plot::plots::scatter::MarkerStyle::Triangle,
3824 "d" => runmat_plot::plots::scatter::MarkerStyle::Diamond,
3825 "+" => runmat_plot::plots::scatter::MarkerStyle::Plus,
3826 "x" => runmat_plot::plots::scatter::MarkerStyle::Cross,
3827 "*" => runmat_plot::plots::scatter::MarkerStyle::Star,
3828 "h" => runmat_plot::plots::scatter::MarkerStyle::Hexagon,
3829 _ => current,
3830 }
3831}
3832
3833trait Unzip3Vec<A, B, C> {
3834 fn unzip_n_vec(self) -> (Vec<A>, Vec<B>, Vec<C>);
3835}
3836
3837impl<I, A, B, C> Unzip3Vec<A, B, C> for I
3838where
3839 I: Iterator<Item = (A, B, C)>,
3840{
3841 fn unzip_n_vec(self) -> (Vec<A>, Vec<B>, Vec<C>) {
3842 let mut a = Vec::new();
3843 let mut b = Vec::new();
3844 let mut c = Vec::new();
3845 for (va, vb, vc) in self {
3846 a.push(va);
3847 b.push(vb);
3848 c.push(vc);
3849 }
3850 (a, b, c)
3851 }
3852}
3853
3854fn color_to_short_name(color: glam::Vec4) -> String {
3855 let candidates = [
3856 (glam::Vec4::new(1.0, 0.0, 0.0, 1.0), "r"),
3857 (glam::Vec4::new(0.0, 1.0, 0.0, 1.0), "g"),
3858 (glam::Vec4::new(0.0, 0.0, 1.0, 1.0), "b"),
3859 (glam::Vec4::new(0.0, 0.0, 0.0, 1.0), "k"),
3860 (glam::Vec4::new(1.0, 1.0, 1.0, 1.0), "w"),
3861 (glam::Vec4::new(1.0, 1.0, 0.0, 1.0), "y"),
3862 (glam::Vec4::new(1.0, 0.0, 1.0, 1.0), "m"),
3863 (glam::Vec4::new(0.0, 1.0, 1.0, 1.0), "c"),
3864 ];
3865 for (candidate, name) in candidates {
3866 if (candidate - color).abs().max_element() < 1e-6 {
3867 return name.to_string();
3868 }
3869 }
3870 format!("[{:.3},{:.3},{:.3}]", color.x, color.y, color.z)
3871}
3872
3873fn key_name(kind: PlotObjectKind) -> &'static str {
3874 match kind {
3875 PlotObjectKind::Title => "Title",
3876 PlotObjectKind::XLabel => "XLabel",
3877 PlotObjectKind::YLabel => "YLabel",
3878 PlotObjectKind::ZLabel => "ZLabel",
3879 PlotObjectKind::Legend => "Legend",
3880 PlotObjectKind::SuperTitle => "SGTitle",
3881 }
3882}
3883
3884trait AxesMetadataExt {
3885 fn text_style_for(&self, kind: PlotObjectKind) -> TextStyle;
3886}
3887
3888impl AxesMetadataExt for runmat_plot::plots::AxesMetadata {
3889 fn text_style_for(&self, kind: PlotObjectKind) -> TextStyle {
3890 match kind {
3891 PlotObjectKind::Title => self.title_style.clone(),
3892 PlotObjectKind::XLabel => self.x_label_style.clone(),
3893 PlotObjectKind::YLabel => self.y_label_style.clone(),
3894 PlotObjectKind::ZLabel => self.z_label_style.clone(),
3895 PlotObjectKind::Legend => TextStyle::default(),
3896 PlotObjectKind::SuperTitle => TextStyle::default(),
3897 }
3898 }
3899}