| 1 | use plotters::prelude::*; |
| 2 | use plotters::style::text_anchor::{HPos, VPos}; |
| 3 | use plotters_backend::{ |
| 4 | BackendColor, BackendStyle, BackendTextStyle, DrawingBackend, DrawingErrorKind, |
| 5 | }; |
| 6 | use std::error::Error; |
| 7 | |
| 8 | #[derive(Copy, Clone)] |
| 9 | enum PixelState { |
| 10 | Empty, |
| 11 | HLine, |
| 12 | VLine, |
| 13 | Cross, |
| 14 | Pixel, |
| 15 | Text(char), |
| 16 | Circle(bool), |
| 17 | } |
| 18 | |
| 19 | impl PixelState { |
| 20 | fn to_char(self) -> char { |
| 21 | match self { |
| 22 | Self::Empty => ' ' , |
| 23 | Self::HLine => '-' , |
| 24 | Self::VLine => '|' , |
| 25 | Self::Cross => '+' , |
| 26 | Self::Pixel => '.' , |
| 27 | Self::Text(c) => c, |
| 28 | Self::Circle(filled) => { |
| 29 | if filled { |
| 30 | '@' |
| 31 | } else { |
| 32 | 'O' |
| 33 | } |
| 34 | } |
| 35 | } |
| 36 | } |
| 37 | |
| 38 | fn update(&mut self, new_state: PixelState) { |
| 39 | let next_state = match (*self, new_state) { |
| 40 | (Self::HLine, Self::VLine) => Self::Cross, |
| 41 | (Self::VLine, Self::HLine) => Self::Cross, |
| 42 | (_, Self::Circle(what)) => Self::Circle(what), |
| 43 | (Self::Circle(what), _) => Self::Circle(what), |
| 44 | (_, Self::Pixel) => Self::Pixel, |
| 45 | (Self::Pixel, _) => Self::Pixel, |
| 46 | (_, new) => new, |
| 47 | }; |
| 48 | |
| 49 | *self = next_state; |
| 50 | } |
| 51 | } |
| 52 | |
| 53 | pub struct TextDrawingBackend(Vec<PixelState>); |
| 54 | |
| 55 | impl DrawingBackend for TextDrawingBackend { |
| 56 | type ErrorType = std::io::Error; |
| 57 | |
| 58 | fn get_size(&self) -> (u32, u32) { |
| 59 | (100, 30) |
| 60 | } |
| 61 | |
| 62 | fn ensure_prepared(&mut self) -> Result<(), DrawingErrorKind<std::io::Error>> { |
| 63 | Ok(()) |
| 64 | } |
| 65 | |
| 66 | fn present(&mut self) -> Result<(), DrawingErrorKind<std::io::Error>> { |
| 67 | for r in 0..30 { |
| 68 | let mut buf = String::new(); |
| 69 | for c in 0..100 { |
| 70 | buf.push(self.0[r * 100 + c].to_char()); |
| 71 | } |
| 72 | println!("{}" , buf); |
| 73 | } |
| 74 | |
| 75 | Ok(()) |
| 76 | } |
| 77 | |
| 78 | fn draw_pixel( |
| 79 | &mut self, |
| 80 | pos: (i32, i32), |
| 81 | color: BackendColor, |
| 82 | ) -> Result<(), DrawingErrorKind<std::io::Error>> { |
| 83 | if color.alpha > 0.3 { |
| 84 | self.0[(pos.1 * 100 + pos.0) as usize].update(PixelState::Pixel); |
| 85 | } |
| 86 | Ok(()) |
| 87 | } |
| 88 | |
| 89 | fn draw_line<S: BackendStyle>( |
| 90 | &mut self, |
| 91 | from: (i32, i32), |
| 92 | to: (i32, i32), |
| 93 | style: &S, |
| 94 | ) -> Result<(), DrawingErrorKind<Self::ErrorType>> { |
| 95 | if from.0 == to.0 { |
| 96 | let x = from.0; |
| 97 | let y0 = from.1.min(to.1); |
| 98 | let y1 = from.1.max(to.1); |
| 99 | for y in y0..y1 { |
| 100 | self.0[(y * 100 + x) as usize].update(PixelState::VLine); |
| 101 | } |
| 102 | return Ok(()); |
| 103 | } |
| 104 | |
| 105 | if from.1 == to.1 { |
| 106 | let y = from.1; |
| 107 | let x0 = from.0.min(to.0); |
| 108 | let x1 = from.0.max(to.0); |
| 109 | for x in x0..x1 { |
| 110 | self.0[(y * 100 + x) as usize].update(PixelState::HLine); |
| 111 | } |
| 112 | return Ok(()); |
| 113 | } |
| 114 | |
| 115 | plotters_backend::rasterizer::draw_line(self, from, to, style) |
| 116 | } |
| 117 | |
| 118 | fn estimate_text_size<S: BackendTextStyle>( |
| 119 | &self, |
| 120 | text: &str, |
| 121 | _: &S, |
| 122 | ) -> Result<(u32, u32), DrawingErrorKind<Self::ErrorType>> { |
| 123 | Ok((text.len() as u32, 1)) |
| 124 | } |
| 125 | |
| 126 | fn draw_text<S: BackendTextStyle>( |
| 127 | &mut self, |
| 128 | text: &str, |
| 129 | style: &S, |
| 130 | pos: (i32, i32), |
| 131 | ) -> Result<(), DrawingErrorKind<Self::ErrorType>> { |
| 132 | let (width, height) = self.estimate_text_size(text, style)?; |
| 133 | let (width, height) = (width as i32, height as i32); |
| 134 | let dx = match style.anchor().h_pos { |
| 135 | HPos::Left => 0, |
| 136 | HPos::Right => -width, |
| 137 | HPos::Center => -width / 2, |
| 138 | }; |
| 139 | let dy = match style.anchor().v_pos { |
| 140 | VPos::Top => 0, |
| 141 | VPos::Center => -height / 2, |
| 142 | VPos::Bottom => -height, |
| 143 | }; |
| 144 | let offset = (pos.1 + dy).max(0) * 100 + (pos.0 + dx).max(0); |
| 145 | for (idx, chr) in (offset..).zip(text.chars()) { |
| 146 | self.0[idx as usize].update(PixelState::Text(chr)); |
| 147 | } |
| 148 | Ok(()) |
| 149 | } |
| 150 | } |
| 151 | |
| 152 | fn draw_chart<DB: DrawingBackend>( |
| 153 | b: DrawingArea<DB, plotters::coord::Shift>, |
| 154 | ) -> Result<(), Box<dyn Error>> |
| 155 | where |
| 156 | DB::ErrorType: 'static, |
| 157 | { |
| 158 | let mut chart = ChartBuilder::on(&b) |
| 159 | .margin(1) |
| 160 | .caption("Sine and Cosine" , ("sans-serif" , (10).percent_height())) |
| 161 | .set_label_area_size(LabelAreaPosition::Left, (5i32).percent_width()) |
| 162 | .set_label_area_size(LabelAreaPosition::Bottom, (10i32).percent_height()) |
| 163 | .build_cartesian_2d(-std::f64::consts::PI..std::f64::consts::PI, -1.2..1.2)?; |
| 164 | |
| 165 | chart |
| 166 | .configure_mesh() |
| 167 | .disable_x_mesh() |
| 168 | .disable_y_mesh() |
| 169 | .draw()?; |
| 170 | |
| 171 | chart.draw_series(LineSeries::new( |
| 172 | (-314..314).map(|x| x as f64 / 100.0).map(|x| (x, x.sin())), |
| 173 | &RED, |
| 174 | ))?; |
| 175 | |
| 176 | chart.draw_series(LineSeries::new( |
| 177 | (-314..314).map(|x| x as f64 / 100.0).map(|x| (x, x.cos())), |
| 178 | &RED, |
| 179 | ))?; |
| 180 | |
| 181 | b.present()?; |
| 182 | |
| 183 | Ok(()) |
| 184 | } |
| 185 | |
| 186 | const OUT_FILE_NAME: &'static str = "plotters-doc-data/console-example.png" ; |
| 187 | fn main() -> Result<(), Box<dyn Error>> { |
| 188 | draw_chart(TextDrawingBackend(vec![PixelState::Empty; 5000]).into_drawing_area())?; |
| 189 | let b = BitMapBackend::new(OUT_FILE_NAME, (1024, 768)).into_drawing_area(); |
| 190 | b.fill(&WHITE)?; |
| 191 | draw_chart(b)?; |
| 192 | |
| 193 | println!("Image result has been saved to {}" , OUT_FILE_NAME); |
| 194 | |
| 195 | Ok(()) |
| 196 | } |
| 197 | #[test] |
| 198 | fn entry_point() { |
| 199 | main().unwrap() |
| 200 | } |
| 201 | |