You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
198 lines
5.3 KiB
198 lines
5.3 KiB
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()
|
|
}
|