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 | |