1pub mod scale;
53pub mod utils;
54
55use drawille::Canvas as BrailleCanvas;
56use drawille::PixelColor;
57use rgb::RGB8;
58use scale::Scale;
59use std::cmp;
60use std::default::Default;
61use std::f32;
62use std::fmt::{Display, Formatter, Result};
63
64#[derive(PartialEq)]
66enum ChartRangeMethod {
67 AutoRange,
69 FixedRange,
71}
72
73pub struct Chart<'a> {
75 width: u32,
77 height: u32,
79 xmin: f32,
81 xmax: f32,
83 ymin: f32,
85 ymax: f32,
87 y_ranging: ChartRangeMethod,
89 shapes: Vec<(&'a Shape<'a>, Option<RGB8>)>,
91 canvas: BrailleCanvas,
93 x_style: LineStyle,
95 y_style: LineStyle,
97 x_label_format: LabelFormat,
99 y_label_format: LabelFormat,
101 y_tick_display: TickDisplay,
103}
104
105pub enum Shape<'a> {
107 Continuous(Box<dyn Fn(f32) -> f32 + 'a>),
109 Points(&'a [(f32, f32)]),
111 Lines(&'a [(f32, f32)]),
113 Steps(&'a [(f32, f32)]),
115 Bars(&'a [(f32, f32)]),
117}
118
119pub trait Plot<'a> {
121 fn lineplot(&'a mut self, shape: &'a Shape) -> &'a mut Chart;
123}
124
125pub trait ColorPlot<'a> {
127 fn linecolorplot(&'a mut self, shape: &'a Shape, color: RGB8) -> &'a mut Chart;
129}
130
131pub trait AxisBuilder<'a> {
133 fn x_axis_style(&'a mut self, style: LineStyle) -> &'a mut Chart<'a>;
135
136 fn y_axis_style(&'a mut self, style: LineStyle) -> &'a mut Chart<'a>;
138}
139
140pub trait LabelBuilder<'a> {
141 fn x_label_format(&'a mut self, format: LabelFormat) -> &'a mut Chart<'a>;
143
144 fn y_label_format(&'a mut self, format: LabelFormat) -> &'a mut Chart<'a>;
146}
147
148pub trait TickDisplayBuilder<'a> {
150 fn y_tick_display(&'a mut self, density: TickDisplay) -> &'a mut Chart<'a>;
155}
156
157impl<'a> Default for Chart<'a> {
158 fn default() -> Self {
159 Self::new(120, 60, -10.0, 10.0)
160 }
161}
162
163#[derive(Clone, Copy)]
166pub enum LineStyle {
167 None,
169 Solid,
171 Dotted,
173 Dashed,
175}
176
177pub enum LabelFormat {
180 None,
182 Value,
184 Custom(Box<dyn Fn(f32) -> String>),
186}
187
188pub enum TickDisplay {
191 None,
193 Sparse,
195 Dense,
197}
198
199impl TickDisplay {
200 fn get_row_spacing(&self) -> u32 {
201 match self {
202 TickDisplay::None => u32::MAX, TickDisplay::Sparse => 4,
204 TickDisplay::Dense => 2,
205 }
206 }
207}
208
209impl<'a> Display for Chart<'a> {
210 fn fmt(&self, f: &mut Formatter<'_>) -> Result {
211 let mut frame = self.canvas.frame().replace(' ', "\u{2800}");
213
214 if let Some(idx) = frame.find('\n') {
215 let xmin = self.format_x_axis_tick(self.xmin);
216 let xmax = self.format_x_axis_tick(self.xmax);
217
218 frame.insert_str(idx, &format!(" {0}", self.format_y_axis_tick(self.ymax)));
219
220 match self.y_tick_display {
222 TickDisplay::None => {}
223 TickDisplay::Sparse | TickDisplay::Dense => {
224 let row_spacing: u32 = self.y_tick_display.get_row_spacing(); let num_steps: u32 = (self.height / 4) / row_spacing; let step_size = (self.ymax - self.ymin) / (num_steps) as f32;
227 for i in 1..(num_steps) {
228 if let Some(index) = frame
229 .match_indices('\n')
230 .collect::<Vec<(usize, &str)>>()
231 .get((i * row_spacing) as usize)
232 {
233 frame.insert_str(
234 index.0,
235 &format!(
236 " {0}",
237 self.format_y_axis_tick(self.ymax - (step_size * i as f32))
238 ),
239 );
240 }
241 }
242 }
243 }
244
245 frame.push_str(&format!(
246 " {0}\n{1: <width$}{2}\n",
247 self.format_y_axis_tick(self.ymin),
248 xmin,
249 xmax,
250 width = (self.width as usize) / 2 - xmax.len()
251 ));
252 }
253 write!(f, "{}", frame)
254 }
255}
256
257impl<'a> Chart<'a> {
258 pub fn new(width: u32, height: u32, xmin: f32, xmax: f32) -> Self {
264 if width < 32 {
265 panic!("width should be at least 32");
266 }
267
268 if height < 3 {
269 panic!("height should be at least 3");
270 }
271
272 Self {
273 xmin,
274 xmax,
275 ymin: f32::INFINITY,
276 ymax: f32::NEG_INFINITY,
277 y_ranging: ChartRangeMethod::AutoRange,
278 width,
279 height,
280 shapes: Vec::new(),
281 canvas: BrailleCanvas::new(width, height),
282 x_style: LineStyle::Dotted,
283 y_style: LineStyle::Dotted,
284 x_label_format: LabelFormat::Value,
285 y_label_format: LabelFormat::Value,
286 y_tick_display: TickDisplay::None,
287 }
288 }
289
290 pub fn new_with_y_range(
296 width: u32,
297 height: u32,
298 xmin: f32,
299 xmax: f32,
300 ymin: f32,
301 ymax: f32,
302 ) -> Self {
303 if width < 32 {
304 panic!("width should be at least 32");
305 }
306
307 if height < 3 {
308 panic!("height should be at least 3");
309 }
310
311 Self {
312 xmin,
313 xmax,
314 ymin,
315 ymax,
316 y_ranging: ChartRangeMethod::FixedRange,
317 width,
318 height,
319 shapes: Vec::new(),
320 canvas: BrailleCanvas::new(width, height),
321 x_style: LineStyle::Dotted,
322 y_style: LineStyle::Dotted,
323 x_label_format: LabelFormat::Value,
324 y_label_format: LabelFormat::Value,
325 y_tick_display: TickDisplay::None,
326 }
327 }
328
329 pub fn borders(&mut self) {
331 let w = self.width;
332 let h = self.height;
333
334 self.vline(0, LineStyle::Dotted);
335 self.vline(w, LineStyle::Dotted);
336 self.hline(0, LineStyle::Dotted);
337 self.hline(h, LineStyle::Dotted);
338 }
339
340 fn vline(&mut self, i: u32, mode: LineStyle) {
342 match mode {
343 LineStyle::None => {}
344 LineStyle::Solid => {
345 if i <= self.width {
346 for j in 0..=self.height {
347 self.canvas.set(i, j);
348 }
349 }
350 }
351 LineStyle::Dotted => {
352 if i <= self.width {
353 for j in 0..=self.height {
354 if j % 3 == 0 {
355 self.canvas.set(i, j);
356 }
357 }
358 }
359 }
360 LineStyle::Dashed => {
361 if i <= self.width {
362 for j in 0..=self.height {
363 if j % 4 == 0 {
364 self.canvas.set(i, j);
365 self.canvas.set(i, j + 1);
366 }
367 }
368 }
369 }
370 }
371 }
372
373 fn hline(&mut self, j: u32, mode: LineStyle) {
375 match mode {
376 LineStyle::None => {}
377 LineStyle::Solid => {
378 if j <= self.height {
379 for i in 0..=self.width {
380 self.canvas.set(i, self.height - j);
381 }
382 }
383 }
384 LineStyle::Dotted => {
385 if j <= self.height {
386 for i in 0..=self.width {
387 if i % 3 == 0 {
388 self.canvas.set(i, self.height - j);
389 }
390 }
391 }
392 }
393 LineStyle::Dashed => {
394 if j <= self.height {
395 for i in 0..=self.width {
396 if i % 4 == 0 {
397 self.canvas.set(i, self.height - j);
398 self.canvas.set(i + 1, self.height - j);
399 }
400 }
401 }
402 }
403 }
404 }
405
406 pub fn display(&mut self) {
408 self.axis();
409 self.figures();
410
411 println!("{}", self);
412 }
413
414 pub fn nice(&mut self) {
416 self.borders();
417 self.display();
418 }
419
420 pub fn axis(&mut self) {
422 self.x_axis();
423 self.y_axis();
424 }
425
426 pub fn x_axis(&mut self) {
428 let y_scale = Scale::new(self.ymin..self.ymax, 0.0..self.height as f32);
429
430 if self.ymin <= 0.0 && self.ymax >= 0.0 {
431 self.hline(y_scale.linear(0.0) as u32, self.x_style);
432 }
433 }
434
435 pub fn y_axis(&mut self) {
437 let x_scale = Scale::new(self.xmin..self.xmax, 0.0..self.width as f32);
438
439 if self.xmin <= 0.0 && self.xmax >= 0.0 {
440 self.vline(x_scale.linear(0.0) as u32, self.y_style);
441 }
442 }
443
444 fn format_x_axis_tick(&self, value: f32) -> String {
446 match &self.x_label_format {
447 LabelFormat::None => "".to_owned(),
448 LabelFormat::Value => format!("{:.1}", value),
449 LabelFormat::Custom(f) => f(value),
450 }
451 }
452
453 fn format_y_axis_tick(&self, value: f32) -> String {
455 match &self.y_label_format {
456 LabelFormat::None => "".to_owned(),
457 LabelFormat::Value => format!("{:.1}", value),
458 LabelFormat::Custom(f) => f(value),
459 }
460 }
461
462 pub fn figures(&mut self) {
464 for (shape, color) in &self.shapes {
465 let x_scale = Scale::new(self.xmin..self.xmax, 0.0..self.width as f32);
466 let y_scale = Scale::new(self.ymin..self.ymax, 0.0..self.height as f32);
467
468 let points: Vec<_> = match shape {
470 Shape::Continuous(f) => (0..self.width)
471 .filter_map(|i| {
472 let x = x_scale.inv_linear(i as f32);
473 let y = f(x);
474 if y.is_normal() {
475 let j = y_scale.linear(y).round();
476 Some((i, self.height - j as u32))
477 } else {
478 None
479 }
480 })
481 .collect(),
482 Shape::Points(dt) | Shape::Lines(dt) | Shape::Steps(dt) | Shape::Bars(dt) => dt
483 .iter()
484 .filter_map(|(x, y)| {
485 let i = x_scale.linear(*x).round() as u32;
486 let j = y_scale.linear(*y).round() as u32;
487 if i <= self.width && j <= self.height {
488 Some((i, self.height - j))
489 } else {
490 None
491 }
492 })
493 .collect(),
494 };
495
496 match shape {
498 Shape::Continuous(_) | Shape::Lines(_) => {
499 for pair in points.windows(2) {
500 let (x1, y1) = pair[0];
501 let (x2, y2) = pair[1];
502 if let Some(color) = color {
503 let color = rgb_to_pixelcolor(color);
504 self.canvas.line_colored(x1, y1, x2, y2, color);
505 } else {
506 self.canvas.line(x1, y1, x2, y2);
507 }
508 }
509 }
510 Shape::Points(_) => {
511 for (x, y) in points {
512 if let Some(color) = color {
513 let color = rgb_to_pixelcolor(color);
514 self.canvas.set_colored(x, y, color);
515 } else {
516 self.canvas.set(x, y);
517 }
518 }
519 }
520 Shape::Steps(_) => {
521 for pair in points.windows(2) {
522 let (x1, y1) = pair[0];
523 let (x2, y2) = pair[1];
524
525 if let Some(color) = color {
526 let color = rgb_to_pixelcolor(color);
527 self.canvas.line_colored(x1, y2, x2, y2, color);
528 self.canvas.line_colored(x1, y1, x1, y2, color);
529 } else {
530 self.canvas.line(x1, y2, x2, y2);
531 self.canvas.line(x1, y1, x1, y2);
532 }
533 }
534 }
535 Shape::Bars(_) => {
536 for pair in points.windows(2) {
537 let (x1, y1) = pair[0];
538 let (x2, y2) = pair[1];
539
540 if let Some(color) = color {
541 let color = rgb_to_pixelcolor(color);
542 self.canvas.line_colored(x1, y2, x2, y2, color);
543 self.canvas.line_colored(x1, y1, x1, y2, color);
544 self.canvas.line_colored(x1, self.height, x1, y1, color);
545 self.canvas.line_colored(x2, self.height, x2, y2, color);
546 } else {
547 self.canvas.line(x1, y2, x2, y2);
548 self.canvas.line(x1, y1, x1, y2);
549 self.canvas.line(x1, self.height, x1, y1);
550 self.canvas.line(x2, self.height, x2, y2);
551 }
552 }
553 }
554 }
555 }
556 }
557
558 pub fn frame(&self) -> String {
560 self.canvas.frame()
561 }
562
563 fn rescale(&mut self, shape: &Shape) {
564 let x_scale = Scale::new(self.xmin..self.xmax, 0.0..self.width as f32);
566
567 let ys: Vec<_> = match shape {
568 Shape::Continuous(f) => (0..self.width)
569 .filter_map(|i| {
570 let x = x_scale.inv_linear(i as f32);
571 let y = f(x);
572 if y.is_normal() {
573 Some(y)
574 } else {
575 None
576 }
577 })
578 .collect(),
579 Shape::Points(dt) | Shape::Lines(dt) | Shape::Steps(dt) | Shape::Bars(dt) => dt
580 .iter()
581 .filter_map(|(x, y)| {
582 if *x >= self.xmin && *x <= self.xmax {
583 Some(*y)
584 } else {
585 None
586 }
587 })
588 .collect(),
589 };
590
591 let ymax = *ys
592 .iter()
593 .max_by(|x, y| x.partial_cmp(y).unwrap_or(cmp::Ordering::Equal))
594 .unwrap_or(&0.0);
595 let ymin = *ys
596 .iter()
597 .min_by(|x, y| x.partial_cmp(y).unwrap_or(cmp::Ordering::Equal))
598 .unwrap_or(&0.0);
599
600 self.ymin = f32::min(self.ymin, ymin);
601 self.ymax = f32::max(self.ymax, ymax);
602 }
603}
604
605impl<'a> ColorPlot<'a> for Chart<'a> {
606 fn linecolorplot(&'a mut self, shape: &'a Shape, color: RGB8) -> &'a mut Chart {
607 self.shapes.push((shape, Some(color)));
608 if self.y_ranging == ChartRangeMethod::AutoRange {
609 self.rescale(shape);
610 }
611 self
612 }
613}
614
615impl<'a> Plot<'a> for Chart<'a> {
616 fn lineplot(&'a mut self, shape: &'a Shape) -> &'a mut Chart {
617 self.shapes.push((shape, None));
618 if self.y_ranging == ChartRangeMethod::AutoRange {
619 self.rescale(shape);
620 }
621 self
622 }
623}
624
625fn rgb_to_pixelcolor(rgb: &RGB8) -> PixelColor {
626 PixelColor::TrueColor {
627 r: rgb.r,
628 g: rgb.g,
629 b: rgb.b,
630 }
631}
632
633impl<'a> AxisBuilder<'a> for Chart<'a> {
634 fn x_axis_style(&'a mut self, style: LineStyle) -> &'a mut Chart {
635 self.x_style = style;
636 self
637 }
638
639 fn y_axis_style(&'a mut self, style: LineStyle) -> &'a mut Chart {
640 self.y_style = style;
641 self
642 }
643}
644
645impl<'a> LabelBuilder<'a> for Chart<'a> {
646 fn x_label_format(&mut self, format: LabelFormat) -> &mut Self {
648 self.x_label_format = format;
649 self
650 }
651
652 fn y_label_format(&mut self, format: LabelFormat) -> &mut Self {
654 self.y_label_format = format;
655 self
656 }
657}
658
659impl<'a> TickDisplayBuilder<'a> for Chart<'a> {
660 fn y_tick_display(&mut self, density: TickDisplay) -> &mut Self {
662 match density {
664 TickDisplay::None => {}
665 TickDisplay::Sparse => {
666 self.height = if self.height < 16 {
668 16
669 } else {
670 ((self.height + 8) / 16) * 16
671 }
672 }
673 TickDisplay::Dense => {
674 self.height = if self.height < 8 {
676 8
677 } else {
678 ((self.height + 4) / 8) * 8
679 }
680 }
681 }
682 self.y_tick_display = density;
683 self
684 }
685}