1use std::cmp::max;
2
3use unicode_width::UnicodeWidthStr;
4
5use crate::buffer::Buffer;
6use crate::layout::Rect;
7use crate::style::Style;
8use crate::symbols;
9use crate::widgets::canvas::{Canvas, Points};
10use crate::widgets::{Block, Borders, Widget};
11
12pub struct Axis<'a, L>
14where
15 L: AsRef<str> + 'a,
16{
17 title: Option<&'a str>,
19 title_style: Style,
21 bounds: [f64; 2],
23 labels: Option<&'a [L]>,
25 labels_style: Style,
27 style: Style,
29}
30
31impl<'a, L> Default for Axis<'a, L>
32where
33 L: AsRef<str>,
34{
35 fn default() -> Axis<'a, L> {
36 Axis {
37 title: None,
38 title_style: Default::default(),
39 bounds: [0.0, 0.0],
40 labels: None,
41 labels_style: Default::default(),
42 style: Default::default(),
43 }
44 }
45}
46
47impl<'a, L> Axis<'a, L>
48where
49 L: AsRef<str>,
50{
51 pub fn title(mut self, title: &'a str) -> Axis<'a, L> {
52 self.title = Some(title);
53 self
54 }
55
56 pub fn title_style(mut self, style: Style) -> Axis<'a, L> {
57 self.title_style = style;
58 self
59 }
60
61 pub fn bounds(mut self, bounds: [f64; 2]) -> Axis<'a, L> {
62 self.bounds = bounds;
63 self
64 }
65
66 pub fn labels(mut self, labels: &'a [L]) -> Axis<'a, L> {
67 self.labels = Some(labels);
68 self
69 }
70
71 pub fn labels_style(mut self, style: Style) -> Axis<'a, L> {
72 self.labels_style = style;
73 self
74 }
75
76 pub fn style(mut self, style: Style) -> Axis<'a, L> {
77 self.style = style;
78 self
79 }
80}
81
82pub enum Marker {
84 Dot,
86 Braille,
88}
89
90pub struct Dataset<'a> {
92 name: &'a str,
94 data: &'a [(f64, f64)],
96 marker: Marker,
98 style: Style,
100}
101
102impl<'a> Default for Dataset<'a> {
103 fn default() -> Dataset<'a> {
104 Dataset {
105 name: "",
106 data: &[],
107 marker: Marker::Dot,
108 style: Style::default(),
109 }
110 }
111}
112
113impl<'a> Dataset<'a> {
114 pub fn name(mut self, name: &'a str) -> Dataset<'a> {
115 self.name = name;
116 self
117 }
118
119 pub fn data(mut self, data: &'a [(f64, f64)]) -> Dataset<'a> {
120 self.data = data;
121 self
122 }
123
124 pub fn marker(mut self, marker: Marker) -> Dataset<'a> {
125 self.marker = marker;
126 self
127 }
128
129 pub fn style(mut self, style: Style) -> Dataset<'a> {
130 self.style = style;
131 self
132 }
133}
134
135#[derive(Debug)]
138struct ChartLayout {
139 title_x: Option<(u16, u16)>,
140 title_y: Option<(u16, u16)>,
141 label_x: Option<u16>,
142 label_y: Option<u16>,
143 axis_x: Option<u16>,
144 axis_y: Option<u16>,
145 legend_area: Option<Rect>,
146 graph_area: Rect,
147}
148
149impl Default for ChartLayout {
150 fn default() -> ChartLayout {
151 ChartLayout {
152 title_x: None,
153 title_y: None,
154 label_x: None,
155 label_y: None,
156 axis_x: None,
157 axis_y: None,
158 legend_area: None,
159 graph_area: Rect::default(),
160 }
161 }
162}
163
164pub struct Chart<'a, LX, LY>
199where
200 LX: AsRef<str> + 'a,
201 LY: AsRef<str> + 'a,
202{
203 block: Option<Block<'a>>,
205 x_axis: Axis<'a, LX>,
207 y_axis: Axis<'a, LY>,
209 datasets: &'a [Dataset<'a>],
211 style: Style,
213}
214
215impl<'a, LX, LY> Default for Chart<'a, LX, LY>
216where
217 LX: AsRef<str>,
218 LY: AsRef<str>,
219{
220 fn default() -> Chart<'a, LX, LY> {
221 Chart {
222 block: None,
223 x_axis: Axis::default(),
224 y_axis: Axis::default(),
225 style: Default::default(),
226 datasets: &[],
227 }
228 }
229}
230
231impl<'a, LX, LY> Chart<'a, LX, LY>
232where
233 LX: AsRef<str>,
234 LY: AsRef<str>,
235{
236 pub fn block(mut self, block: Block<'a>) -> Chart<'a, LX, LY> {
237 self.block = Some(block);
238 self
239 }
240
241 pub fn style(mut self, style: Style) -> Chart<'a, LX, LY> {
242 self.style = style;
243 self
244 }
245
246 pub fn x_axis(mut self, axis: Axis<'a, LX>) -> Chart<'a, LX, LY> {
247 self.x_axis = axis;
248 self
249 }
250
251 pub fn y_axis(mut self, axis: Axis<'a, LY>) -> Chart<'a, LX, LY> {
252 self.y_axis = axis;
253 self
254 }
255
256 pub fn datasets(mut self, datasets: &'a [Dataset<'a>]) -> Chart<'a, LX, LY> {
257 self.datasets = datasets;
258 self
259 }
260
261 fn layout(&self, area: Rect) -> ChartLayout {
264 let mut layout = ChartLayout::default();
265 if area.height == 0 || area.width == 0 {
266 return layout;
267 }
268 let mut x = area.left();
269 let mut y = area.bottom() - 1;
270
271 if self.x_axis.labels.is_some() && y > area.top() {
272 layout.label_x = Some(y);
273 y -= 1;
274 }
275
276 if let Some(y_labels) = self.y_axis.labels {
277 let mut max_width = y_labels
278 .iter()
279 .fold(0, |acc, l| max(l.as_ref().width(), acc)) as u16;
280 if let Some(x_labels) = self.x_axis.labels {
281 if !x_labels.is_empty() {
282 max_width = max(max_width, x_labels[0].as_ref().width() as u16);
283 }
284 }
285 if x + max_width < area.right() {
286 layout.label_y = Some(x);
287 x += max_width;
288 }
289 }
290
291 if self.x_axis.labels.is_some() && y > area.top() {
292 layout.axis_x = Some(y);
293 y -= 1;
294 }
295
296 if self.y_axis.labels.is_some() && x + 1 < area.right() {
297 layout.axis_y = Some(x);
298 x += 1;
299 }
300
301 if x < area.right() && y > 1 {
302 layout.graph_area = Rect::new(x, area.top(), area.right() - x, y - area.top() + 1);
303 }
304
305 if let Some(title) = self.x_axis.title {
306 let w = title.width() as u16;
307 if w < layout.graph_area.width && layout.graph_area.height > 2 {
308 layout.title_x = Some((x + layout.graph_area.width - w, y));
309 }
310 }
311
312 if let Some(title) = self.y_axis.title {
313 let w = title.width() as u16;
314 if w + 1 < layout.graph_area.width && layout.graph_area.height > 2 {
315 layout.title_y = Some((x + 1, area.top()));
316 }
317 }
318
319 if let Some(inner_width) = self.datasets.iter().map(|d| d.name.width() as u16).max() {
320 let legend_width = inner_width + 2;
321 let legend_height = self.datasets.len() as u16 + 2;
322 if legend_width < layout.graph_area.width
323 && legend_height < layout.graph_area.height
324 && inner_width > 0
325 {
326 layout.legend_area = Some(Rect::new(
327 layout.graph_area.right() - legend_width,
328 layout.graph_area.top(),
329 legend_width,
330 legend_height,
331 ));
332 }
333 }
334 layout
335 }
336}
337
338impl<'a, LX, LY> Widget for Chart<'a, LX, LY>
339where
340 LX: AsRef<str>,
341 LY: AsRef<str>,
342{
343 fn draw(&mut self, area: Rect, buf: &mut Buffer) {
344 let chart_area = match self.block {
345 Some(ref mut b) => {
346 b.draw(area, buf);
347 b.inner(area)
348 }
349 None => area,
350 };
351
352 let layout = self.layout(chart_area);
353 let graph_area = layout.graph_area;
354 if graph_area.width < 1 || graph_area.height < 1 {
355 return;
356 }
357
358 self.background(chart_area, buf, self.style.bg);
359
360 if let Some((x, y)) = layout.title_x {
361 let title = self.x_axis.title.unwrap();
362 buf.set_string(x, y, title, self.x_axis.style);
363 }
364
365 if let Some((x, y)) = layout.title_y {
366 let title = self.y_axis.title.unwrap();
367 buf.set_string(x, y, title, self.y_axis.style);
368 }
369
370 if let Some(y) = layout.label_x {
371 let labels = self.x_axis.labels.unwrap();
372 let total_width = labels.iter().fold(0, |acc, l| l.as_ref().width() + acc) as u16;
373 let labels_len = labels.len() as u16;
374 if total_width < graph_area.width && labels_len > 1 {
375 for (i, label) in labels.iter().enumerate() {
376 buf.set_string(
377 graph_area.left() + i as u16 * (graph_area.width - 1) / (labels_len - 1)
378 - label.as_ref().width() as u16,
379 y,
380 label.as_ref(),
381 self.x_axis.labels_style,
382 );
383 }
384 }
385 }
386
387 if let Some(x) = layout.label_y {
388 let labels = self.y_axis.labels.unwrap();
389 let labels_len = labels.len() as u16;
390 for (i, label) in labels.iter().enumerate() {
391 let dy = i as u16 * (graph_area.height - 1) / (labels_len - 1);
392 if dy < graph_area.bottom() {
393 buf.set_string(
394 x,
395 graph_area.bottom() - 1 - dy,
396 label.as_ref(),
397 self.y_axis.labels_style,
398 );
399 }
400 }
401 }
402
403 if let Some(y) = layout.axis_x {
404 for x in graph_area.left()..graph_area.right() {
405 buf.get_mut(x, y)
406 .set_symbol(symbols::line::HORIZONTAL)
407 .set_style(self.x_axis.style);
408 }
409 }
410
411 if let Some(x) = layout.axis_y {
412 for y in graph_area.top()..graph_area.bottom() {
413 buf.get_mut(x, y)
414 .set_symbol(symbols::line::VERTICAL)
415 .set_style(self.y_axis.style);
416 }
417 }
418
419 if let Some(y) = layout.axis_x {
420 if let Some(x) = layout.axis_y {
421 buf.get_mut(x, y)
422 .set_symbol(symbols::line::BOTTOM_LEFT)
423 .set_style(self.x_axis.style);
424 }
425 }
426
427 for dataset in self.datasets {
428 match dataset.marker {
429 Marker::Dot => {
430 for &(x, y) in dataset.data.iter().filter(|&&(x, y)| {
431 !(x < self.x_axis.bounds[0]
432 || x > self.x_axis.bounds[1] || y < self.y_axis.bounds[0]
433 || y > self.y_axis.bounds[1])
434 }) {
435 let dy = ((self.y_axis.bounds[1] - y) * f64::from(graph_area.height - 1)
436 / (self.y_axis.bounds[1] - self.y_axis.bounds[0])) as u16;
437 let dx = ((x - self.x_axis.bounds[0]) * f64::from(graph_area.width - 1)
438 / (self.x_axis.bounds[1] - self.x_axis.bounds[0])) as u16;
439
440 buf.get_mut(graph_area.left() + dx, graph_area.top() + dy)
441 .set_symbol(symbols::DOT)
442 .set_fg(dataset.style.fg)
443 .set_bg(dataset.style.bg);
444 }
445 }
446 Marker::Braille => {
447 Canvas::default()
448 .background_color(self.style.bg)
449 .x_bounds(self.x_axis.bounds)
450 .y_bounds(self.y_axis.bounds)
451 .paint(|ctx| {
452 ctx.draw(&Points {
453 coords: dataset.data,
454 color: dataset.style.fg,
455 });
456 })
457 .draw(graph_area, buf);
458 }
459 }
460 }
461
462 if let Some(legend_area) = layout.legend_area {
463 Block::default()
464 .borders(Borders::ALL)
465 .draw(legend_area, buf);
466 for (i, dataset) in self.datasets.iter().enumerate() {
467 buf.set_string(
468 legend_area.x + 1,
469 legend_area.y + 1 + i as u16,
470 dataset.name,
471 dataset.style,
472 );
473 }
474 }
475 }
476}