1use crate::api1::{cmd, AutoName};
2use crate::size::SizeUnit;
3use crate::style::marker::{Marker, MarkerFill, MarkerLine};
4use crate::style::plot::PlotLine;
5use crate::style::Color;
6use crate::CommandLineEmbeddingInterface;
7use std::borrow::Cow;
8use std::io::Write;
9
10#[derive(Default)]
11pub struct Page {
12 name: AutoName<Self>,
13 items: Vec<PageItem>,
14 width: Option<SizeUnit>,
15 height: Option<SizeUnit>,
16}
17
18impl Page {
19 pub fn add(&mut self, item: impl Into<PageItem>) {
20 self.items.push(item.into());
21 }
22
23 pub fn with_item(mut self, item: impl Into<PageItem>) -> Self {
24 self.add(item);
25 self
26 }
27
28 pub fn with_items(mut self, items: impl IntoIterator<Item = impl Into<PageItem>>) -> Self {
29 self.items.extend(items.into_iter().map(Into::into));
30 self
31 }
32
33 pub fn with_width(mut self, width: impl Into<Option<SizeUnit>>) -> Self {
34 self.width = width.into();
35 self
36 }
37
38 pub fn with_height(mut self, height: impl Into<Option<SizeUnit>>) -> Self {
39 self.height = height.into();
40 self
41 }
42}
43
44impl CommandLineEmbeddingInterface for Page {
45 fn write<W: Write>(&self, writer: &mut W) -> std::io::Result<()> {
46 cmd::Add("page", &self.name).write(writer)?;
47 cmd::ToUnique(&self.name).for_call(writer, |writer| {
48 for item in &self.items {
49 item.write(writer)?;
50 }
51 Ok(())
52 })?;
53
54 if let Some(width) = self.width {
55 cmd::Set("width", &width.to_string()).write(writer)?;
56 }
57
58 if let Some(height) = self.height {
59 cmd::Set("height", &height.to_string()).write(writer)?;
60 }
61
62 Ok(())
63 }
64}
65
66#[derive(derive_more::From)]
67pub enum PageItem {
68 Graph(Graph),
69 Grid(Grid),
70 Label(Label),
71}
72
73impl CommandLineEmbeddingInterface for PageItem {
74 fn write<W: Write>(&self, writer: &mut W) -> std::io::Result<()> {
75 match self {
76 PageItem::Graph(graph) => graph.write(writer),
77 PageItem::Grid(grid) => grid.write(writer),
78 PageItem::Label(label) => label.write(writer),
79 }
80 }
81}
82
83#[derive(Default)]
84pub struct Graph {
85 name: AutoName<Self>,
86 aspect: Option<AspectRatio>,
87 axes: Vec<Axis>,
88 xy_data: Vec<Xy>,
89}
90
91impl Graph {
92 pub fn set_aspect(&mut self, aspect: impl Into<AspectRatio>) {
93 self.aspect = Some(aspect.into());
94 }
95
96 pub fn with_aspect(mut self, aspect: impl Into<AspectRatio>) -> Self {
97 self.set_aspect(aspect);
98 self
99 }
100
101 pub fn add_axis(&mut self, axis: Axis) {
102 self.axes.push(axis);
103 }
104
105 pub fn with_xy_axis(mut self, x: impl Into<String>, y: impl Into<String>) -> Self {
106 self.add_axis(Axis::x(x));
107 self.add_axis(Axis::y(y));
108 self
109 }
110
111 pub fn with_axis(mut self, axis: Axis) -> Self {
112 self.add_axis(axis);
113 self
114 }
115
116 pub fn add_xy(&mut self, xy: Xy) {
117 self.xy_data.push(xy);
118 }
119
120 pub fn with_xy(mut self, xy: Xy) -> Self {
121 self.add_xy(xy);
122 self
123 }
124
125 pub fn with_xy_sets(mut self, sets: impl IntoIterator<Item = Xy>) -> Self {
126 self.xy_data.extend(sets);
127 self
128 }
129}
130
131impl CommandLineEmbeddingInterface for Graph {
132 fn write<W: Write>(&self, writer: &mut W) -> std::io::Result<()> {
133 cmd::Add("graph", &self.name).write(writer)?;
134 cmd::ToUnique(&self.name).for_call(writer, |writer| {
135 if let Some(aspect) = self.aspect {
136 match aspect {
137 AspectRatio::Auto => cmd::Set("aspect", "Auto").write(writer)?,
138 AspectRatio::Fix(value) => cmd::SetRaw("aspect", value).write(writer)?,
139 }
140 }
141 for axis in &self.axes {
142 axis.write(writer)?;
143 }
144 for xy in &self.xy_data {
145 xy.write(writer)?;
146 }
147 Ok(())
148 })
149 }
150}
151#[derive(derive_more::From, Copy, Clone, PartialEq)]
152pub enum AspectRatio {
153 Auto,
154 Fix(f64),
155}
156
157#[derive(Default)]
158pub struct Grid {
159 name: AutoName<Self>,
160 rows: Option<u32>,
161 columns: Option<u32>,
162 items: Vec<PageItem>,
163}
164
165impl Grid {
166 pub fn set_rows(&mut self, rows: u32) {
167 self.rows = Some(rows);
168 }
169
170 pub fn with_rows(mut self, rows: u32) -> Self {
171 self.set_rows(rows);
172 self
173 }
174
175 pub fn set_columns(&mut self, columns: u32) {
176 self.columns = Some(columns);
177 }
178
179 pub fn with_columns(mut self, columns: u32) -> Self {
180 self.set_columns(columns);
181 self
182 }
183
184 pub fn add(&mut self, item: impl Into<PageItem>) {
185 self.items.push(item.into());
186 }
187
188 pub fn with(mut self, item: impl Into<PageItem>) -> Self {
189 self.add(item);
190 self
191 }
192
193 pub fn with_items(mut self, items: impl IntoIterator<Item = impl Into<PageItem>>) -> Self {
194 self.items.extend(items.into_iter().map(Into::into));
195 self
196 }
197}
198
199impl CommandLineEmbeddingInterface for Grid {
200 fn write<W: Write>(&self, writer: &mut W) -> std::io::Result<()> {
201 cmd::Add("grid", &self.name).write(writer)?;
202 cmd::ToUnique(&self.name).for_call(writer, |writer| {
203 if let Some(rows) = self.rows {
204 cmd::SetRaw("rows", rows).write(writer)?;
205 }
206 if let Some(columns) = self.columns {
207 cmd::SetRaw("columns", columns).write(writer)?;
208 }
209 for item in &self.items {
210 item.write(writer)?;
211 }
212 Ok(())
213 })
214 }
215}
216
217#[derive(Copy, Clone, PartialOrd, PartialEq)]
218pub enum AxisDirection {
219 Vertical,
220 Horizontal,
221}
222
223pub struct Axis {
224 name: String,
225 label: String,
226 direction: Option<AxisDirection>,
227 min: Option<f64>,
228 max: Option<f64>,
229}
230
231impl Axis {
232 pub fn x(label: impl Into<String>) -> Self {
233 Self {
234 name: "x".into(),
235 label: label.into(),
236 direction: None,
237 min: None,
238 max: None,
239 }
240 }
241
242 pub fn y(label: impl Into<String>) -> Self {
243 Self {
244 name: "y".into(),
245 label: label.into(),
246 direction: Some(AxisDirection::Vertical),
247 min: None,
248 max: None,
249 }
250 }
251
252 pub fn with_min(mut self, min: impl Into<Option<f64>>) -> Self {
253 self.min = min.into();
254 self
255 }
256
257 pub fn with_max(mut self, max: impl Into<Option<f64>>) -> Self {
258 self.max = max.into();
259 self
260 }
261}
262
263impl CommandLineEmbeddingInterface for Axis {
264 fn write<W: Write>(&self, writer: &mut W) -> std::io::Result<()> {
265 cmd::Add("axis", &self.name).write(writer)?;
266 cmd::ToUnique(&self.name).for_call(writer, |writer| {
267 cmd::Set("label", &self.label).write(writer)?;
268
269 if let Some(direction) = &self.direction {
270 cmd::Set(
271 "direction",
272 match direction {
273 AxisDirection::Vertical => "vertical",
274 AxisDirection::Horizontal => "horizontal",
275 },
276 )
277 .write(writer)?;
278 }
279
280 if let Some(min) = self.min {
281 cmd::SetRaw("min", min).write(writer)?;
282 }
283
284 if let Some(max) = self.max {
285 cmd::SetRaw("max", max).write(writer)?;
286 }
287
288 Ok(())
289 })
290 }
291}
292
293pub struct Xy {
294 name: AutoName<Self>,
295 color: Option<Color>,
296 marker: Option<Marker>,
297 marker_line: Option<MarkerLine>,
298 marker_fill: Option<MarkerFill>,
299 plot_line: Option<PlotLine>,
300 x_data: String,
301 y_data: String,
302}
303
304impl Xy {
305 pub fn data(x_data: impl Into<String>, y_data: impl Into<String>) -> Self {
306 Self {
307 name: AutoName::default(),
308 color: None,
309 marker: None,
310 marker_line: None,
311 marker_fill: None,
312 plot_line: None,
313 x_data: x_data.into(),
314 y_data: y_data.into(),
315 }
316 }
317
318 pub fn set_color(&mut self, color: Color) {
319 self.color = Some(color);
320 }
321
322 pub fn with_color(mut self, color: Color) -> Self {
323 self.set_color(color);
324 self
325 }
326
327 pub fn set_marker(&mut self, marker: Marker) {
328 self.marker = Some(marker);
329 }
330
331 pub fn with_marker(mut self, marker: Marker) -> Self {
332 self.set_marker(marker);
333 self
334 }
335
336 pub fn set_marker_line(&mut self, marker_line: MarkerLine) {
337 self.marker_line = Some(marker_line);
338 }
339
340 pub fn with_marker_line(mut self, marker_line: MarkerLine) -> Self {
341 self.set_marker_line(marker_line);
342 self
343 }
344
345 pub fn set_marker_fill(&mut self, marker_fill: MarkerFill) {
346 self.marker_fill = Some(marker_fill);
347 }
348
349 pub fn with_marker_fill(mut self, marker_fill: MarkerFill) -> Self {
350 self.set_marker_fill(marker_fill);
351 self
352 }
353
354 pub fn set_plot_line(&mut self, plot_line: PlotLine) {
355 self.plot_line = Some(plot_line);
356 }
357
358 pub fn with_plot_line(mut self, plot_line: PlotLine) -> Self {
359 self.set_plot_line(plot_line);
360 self
361 }
362}
363
364impl CommandLineEmbeddingInterface for Xy {
365 fn write<W: Write>(&self, writer: &mut W) -> std::io::Result<()> {
366 cmd::Add("xy", &self.name).write(writer)?;
367 cmd::ToUnique(&self.name).for_call(writer, |writer| {
368 if let Some(marker) = &self.marker {
369 cmd::Set(
370 "marker",
371 match marker {
372 Marker::None => "none",
373 Marker::Circle => "circle",
374 },
375 )
376 .write(writer)?;
377 }
378
379 if let Some(color) = &self.color {
380 color.write(writer)?;
381 }
382
383 if let Some(marker_line) = &self.marker_line {
384 marker_line.write(writer)?;
385 }
386
387 if let Some(marker_fill) = &self.marker_fill {
388 marker_fill.write(writer)?;
389 }
390
391 if let Some(plot_line) = &self.plot_line {
392 plot_line.write(writer)?;
393 }
394
395 cmd::Set("xData", &self.x_data).write(writer)?;
396 cmd::Set("yData", &self.y_data).write(writer)?;
397
398 Ok(())
399 })
400 }
401}
402
403pub struct Label {
404 name: AutoName<Self>,
405 text: Cow<'static, str>,
406 x_positions: Vec<f64>,
407 y_positions: Vec<f64>,
408 align_horizontal: Option<Alignment>,
409 align_vertical: Option<Alignment>,
410 positioning: Option<Positioning>,
411 text_config: Option<TextConfig>,
412 }
414
415impl Label {
416 pub fn set_x_positions(&mut self, positions: impl Into<Vec<f64>>) {
417 self.x_positions = positions.into();
418 }
419
420 pub fn with_x_positions(mut self, positions: impl Into<Vec<f64>>) -> Self {
421 self.set_x_positions(positions);
422 self
423 }
424
425 pub fn set_y_positions(&mut self, positions: impl Into<Vec<f64>>) {
426 self.y_positions = positions.into();
427 }
428
429 pub fn with_y_positions(mut self, positions: impl Into<Vec<f64>>) -> Self {
430 self.set_y_positions(positions);
431 self
432 }
433
434 pub fn set_align_horizontal(&mut self, alignment: Alignment) {
435 self.align_horizontal = Some(alignment);
436 }
437
438 pub fn with_alignment_horizontal(mut self, alignemnt: Alignment) -> Self {
439 self.set_align_horizontal(alignemnt);
440 self
441 }
442
443 pub fn set_align_vertical(&mut self, alignment: Alignment) {
444 self.align_vertical = Some(alignment);
445 }
446
447 pub fn with_alignment_vertical(mut self, alignment: Alignment) -> Self {
448 self.set_align_vertical(alignment);
449 self
450 }
451
452 pub fn set_text_config(&mut self, text_config: impl Into<TextConfig>) {
453 self.text_config = Some(text_config.into());
454 }
455
456 pub fn with_text_config(mut self, text_config: impl Into<TextConfig>) -> Self {
457 self.set_text_config(text_config);
458 self
459 }
460
461 pub fn set_positioning(&mut self, positioning: impl Into<Positioning>) {
462 self.positioning = Some(positioning.into());
463 }
464
465 pub fn with_positioning(mut self, positioning: impl Into<Positioning>) -> Self {
466 self.set_positioning(positioning);
467 self
468 }
469}
470
471impl<I: Into<Cow<'static, str>>> From<I> for Label {
472 fn from(value: I) -> Self {
473 Self {
474 name: Default::default(),
475 text: value.into(),
476 x_positions: Vec::default(),
477 y_positions: Vec::default(),
478 align_horizontal: None,
479 align_vertical: None,
480 positioning: None,
481 text_config: None,
482 }
483 }
484}
485
486impl CommandLineEmbeddingInterface for Label {
487 fn write<W: Write>(&self, writer: &mut W) -> std::io::Result<()> {
488 cmd::Add("label", &self.name).write(writer)?;
489 cmd::ToUnique(&self.name).for_call(writer, |writer| {
490 cmd::Set("label", &self.text).write(writer)?;
491 if !self.x_positions.is_empty() {
492 cmd::SetData(
493 "xPos",
494 &self
495 .x_positions
496 .iter()
497 .map(ToString::to_string)
498 .collect::<Vec<_>>()
499 .join(","),
500 )
501 .write(writer)?;
502 }
503 if !self.y_positions.is_empty() {
504 cmd::SetData(
505 "yPos",
506 &self
507 .y_positions
508 .iter()
509 .map(ToString::to_string)
510 .collect::<Vec<_>>()
511 .join(","),
512 )
513 .write(writer)?;
514 }
515 if let Some(alignment) = &self.align_horizontal {
516 cmd::Set("alignHorz", alignment.as_str()).write(writer)?;
517 }
518 if let Some(alignment) = &self.align_vertical {
519 cmd::Set("alignVert", alignment.as_str()).write(writer)?;
520 }
521 if let Some(positioning) = &self.positioning {
522 cmd::Set("positioning", positioning.as_str()).write(writer)?;
523 }
524 if let Some(text_config) = &self.text_config {
525 if let Some(size) = &text_config.size {
526 cmd::Set("Text/size", &size.to_string()).write(writer)?;
527 }
528 }
529 Ok(())
530 })
531 }
532}
533
534#[derive(Default)]
535pub struct TextConfig {
536 size: Option<TextSize>,
537}
538
539impl<T: Into<TextSize>> From<T> for TextConfig {
540 fn from(value: T) -> Self {
541 Self {
542 size: Some(value.into()),
543 }
544 }
545}
546
547pub enum TextSize {
548 Pt(f64),
549}
550
551impl ToString for TextSize {
552 fn to_string(&self) -> String {
553 match self {
554 TextSize::Pt(pt) => format!("{pt}pt"),
555 }
556 }
557}
558
559pub enum Alignment {
560 Top,
561 Bottom,
562 Left,
563 Right,
564 Center,
565}
566
567impl Alignment {
568 pub const fn as_str(&self) -> &'static str {
569 match self {
570 Alignment::Top => "top",
571 Alignment::Bottom => "bottom",
572 Alignment::Left => "left",
573 Alignment::Right => "right",
574 Alignment::Center => "centre",
575 }
576 }
577}
578
579pub enum Positioning {
580 Relative,
581 Axes,
582}
583
584impl Positioning {
585 pub const fn as_str(&self) -> &'static str {
586 match self {
587 Positioning::Relative => "relative",
588 Positioning::Axes => "axes",
589 }
590 }
591}