| use plotters::prelude::*; |
| use plotters::style::text_anchor::{HPos, VPos}; |
| use plotters_backend::{ |
| BackendColor, BackendStyle, BackendTextStyle, DrawingBackend, DrawingErrorKind, |
| }; |
| use std::error::Error; |
| |
| #[derive(Copy, Clone)] |
| enum PixelState { |
| Empty, |
| HLine, |
| VLine, |
| Cross, |
| Pixel, |
| Text(char), |
| Circle(bool), |
| } |
| |
| impl PixelState { |
| fn to_char(self) -> char { |
| match self { |
| Self::Empty => ' ', |
| Self::HLine => '-', |
| Self::VLine => '|', |
| Self::Cross => '+', |
| Self::Pixel => '.', |
| Self::Text(c) => c, |
| Self::Circle(filled) => { |
| if filled { |
| '@' |
| } else { |
| 'O' |
| } |
| } |
| } |
| } |
| |
| fn update(&mut self, new_state: PixelState) { |
| let next_state = match (*self, new_state) { |
| (Self::HLine, Self::VLine) => Self::Cross, |
| (Self::VLine, Self::HLine) => Self::Cross, |
| (_, Self::Circle(what)) => Self::Circle(what), |
| (Self::Circle(what), _) => Self::Circle(what), |
| (_, Self::Pixel) => Self::Pixel, |
| (Self::Pixel, _) => Self::Pixel, |
| (_, new) => new, |
| }; |
| |
| *self = next_state; |
| } |
| } |
| |
| pub struct TextDrawingBackend(Vec<PixelState>); |
| |
| impl DrawingBackend for TextDrawingBackend { |
| type ErrorType = std::io::Error; |
| |
| fn get_size(&self) -> (u32, u32) { |
| (100, 30) |
| } |
| |
| fn ensure_prepared(&mut self) -> Result<(), DrawingErrorKind<std::io::Error>> { |
| Ok(()) |
| } |
| |
| fn present(&mut self) -> Result<(), DrawingErrorKind<std::io::Error>> { |
| for r in 0..30 { |
| let mut buf = String::new(); |
| for c in 0..100 { |
| buf.push(self.0[r * 100 + c].to_char()); |
| } |
| println!("{}", buf); |
| } |
| |
| Ok(()) |
| } |
| |
| fn draw_pixel( |
| &mut self, |
| pos: (i32, i32), |
| color: BackendColor, |
| ) -> Result<(), DrawingErrorKind<std::io::Error>> { |
| if color.alpha > 0.3 { |
| self.0[(pos.1 * 100 + pos.0) as usize].update(PixelState::Pixel); |
| } |
| Ok(()) |
| } |
| |
| fn draw_line<S: BackendStyle>( |
| &mut self, |
| from: (i32, i32), |
| to: (i32, i32), |
| style: &S, |
| ) -> Result<(), DrawingErrorKind<Self::ErrorType>> { |
| if from.0 == to.0 { |
| let x = from.0; |
| let y0 = from.1.min(to.1); |
| let y1 = from.1.max(to.1); |
| for y in y0..y1 { |
| self.0[(y * 100 + x) as usize].update(PixelState::VLine); |
| } |
| return Ok(()); |
| } |
| |
| if from.1 == to.1 { |
| let y = from.1; |
| let x0 = from.0.min(to.0); |
| let x1 = from.0.max(to.0); |
| for x in x0..x1 { |
| self.0[(y * 100 + x) as usize].update(PixelState::HLine); |
| } |
| return Ok(()); |
| } |
| |
| plotters_backend::rasterizer::draw_line(self, from, to, style) |
| } |
| |
| fn estimate_text_size<S: BackendTextStyle>( |
| &self, |
| text: &str, |
| _: &S, |
| ) -> Result<(u32, u32), DrawingErrorKind<Self::ErrorType>> { |
| Ok((text.len() as u32, 1)) |
| } |
| |
| fn draw_text<S: BackendTextStyle>( |
| &mut self, |
| text: &str, |
| style: &S, |
| pos: (i32, i32), |
| ) -> Result<(), DrawingErrorKind<Self::ErrorType>> { |
| let (width, height) = self.estimate_text_size(text, style)?; |
| let (width, height) = (width as i32, height as i32); |
| let dx = match style.anchor().h_pos { |
| HPos::Left => 0, |
| HPos::Right => -width, |
| HPos::Center => -width / 2, |
| }; |
| let dy = match style.anchor().v_pos { |
| VPos::Top => 0, |
| VPos::Center => -height / 2, |
| VPos::Bottom => -height, |
| }; |
| let offset = (pos.1 + dy).max(0) * 100 + (pos.0 + dx).max(0); |
| for (idx, chr) in (offset..).zip(text.chars()) { |
| self.0[idx as usize].update(PixelState::Text(chr)); |
| } |
| Ok(()) |
| } |
| } |
| |
| fn draw_chart<DB: DrawingBackend>( |
| b: DrawingArea<DB, plotters::coord::Shift>, |
| ) -> Result<(), Box<dyn Error>> |
| where |
| DB::ErrorType: 'static, |
| { |
| let mut chart = ChartBuilder::on(&b) |
| .margin(1) |
| .caption("Sine and Cosine", ("sans-serif", (10).percent_height())) |
| .set_label_area_size(LabelAreaPosition::Left, (5i32).percent_width()) |
| .set_label_area_size(LabelAreaPosition::Bottom, (10i32).percent_height()) |
| .build_cartesian_2d(-std::f64::consts::PI..std::f64::consts::PI, -1.2..1.2)?; |
| |
| chart |
| .configure_mesh() |
| .disable_x_mesh() |
| .disable_y_mesh() |
| .draw()?; |
| |
| chart.draw_series(LineSeries::new( |
| (-314..314).map(|x| x as f64 / 100.0).map(|x| (x, x.sin())), |
| &RED, |
| ))?; |
| |
| chart.draw_series(LineSeries::new( |
| (-314..314).map(|x| x as f64 / 100.0).map(|x| (x, x.cos())), |
| &RED, |
| ))?; |
| |
| b.present()?; |
| |
| Ok(()) |
| } |
| |
| fn main() -> Result<(), Box<dyn Error>> { |
| draw_chart(TextDrawingBackend(vec![PixelState::Empty; 5000]).into_drawing_area())?; |
| let b = BitMapBackend::new("plotters-doc-data/console-example.png", (1024, 768)) |
| .into_drawing_area(); |
| b.fill(&WHITE)?; |
| draw_chart(b)?; |
| Ok(()) |
| } |
| #[test] |
| fn entry_point() { |
| main().unwrap() |
| } |