1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
use crate::figure::{
canvas::pixelcanvas::PixelCanvas, configuration::figureconfig::FigureConfig,
datasets::areachartdataset::AreaChartDataset,
};
/// Represents an area chart, including its title, axis labels, datasets, and configuration.
pub struct AreaChart {
/// Title of the area chart.
pub title: String,
/// Label for the X-axis.
pub x_label: String,
/// Label for the Y-axis.
pub y_label: String,
/// A collection of datasets to be visualized in the area chart.
pub datasets: Vec<AreaChartDataset>,
/// Configuration settings for rendering the chart (e.g., colors, fonts, grid).
pub config: FigureConfig,
/// Minimum x-value
pub x_min: f64,
/// Maximum x-value
pub x_max: f64,
/// Minimum y-value
pub y_min: f64,
/// Maximum y-value
pub y_max: f64,
}
impl AreaChart {
/// Creates a new `AreaChart` instance with the specified title, axis labels, and configuration.
///
/// # Parameters
/// - `title`: The title of the area chart.
/// - `x_label`: The label for the X-axis.
/// - `y_label`: The label for the Y-axis.
/// - `config`: The `FigureConfig` containing appearance and behavior settings for the chart.
///
/// # Returns
/// A new `AreaChart` instance with an empty dataset.
///
/// # Example
/// ```rust,ignore
/// use dataviz::figure::configuration::figureconfig::FigureConfig;
/// use dataviz::figure::areachart::AreaChart;
///
/// let config = FigureConfig::default();
/// let area_chart = AreaChart::new("Example Chart", "X Axis", "Y Axis", config);
/// ```
pub fn new(title: &str, x_label: &str, y_label: &str, config: FigureConfig) -> Self {
Self {
title: title.to_string(),
x_label: x_label.to_string(),
y_label: y_label.to_string(),
datasets: Vec::new(),
config,
x_min: f64::INFINITY, // Initialize to max range
x_max: f64::NEG_INFINITY, // Initialize to min range
y_min: f64::INFINITY, // Initialize to max range
y_max: f64::NEG_INFINITY, // Initialize to min range
}
}
/// Adds a dataset to the area chart.
///
/// # Parameters
/// - `dataset`: The `AreaChartDataset` to be added to the chart.
///
/// # Example
/// ```rust,ignore
/// use dataviz::figure::datasets::areachartdataset::AreaChartDataset;
/// let dataset = AreaChartDataset::new([255, 0, 0], "Example Dataset", 0.5);
/// area_chart.add_dataset(dataset);
/// ```
pub fn add_dataset(&mut self, dataset: AreaChartDataset) {
self.datasets.push(dataset);
self.update_range();
}
/// Draws the area under a dataset on the canvas.
///
/// This method fills the area under the dataset line, interpolating between points
/// and blending the pixels into the canvas.
///
/// # Parameters
/// - `canvas`: The `PixelCanvas` on which to draw the area.
/// - `dataset`: The `AreaChartDataset` whose area is to be drawn.
/// - `origin_x`: The x-coordinate of the chart's origin on the canvas.
/// - `origin_y`: The y-coordinate of the chart's origin on the canvas.
/// - `scale_x`: The scaling factor for converting X-axis values to canvas coordinates.
/// - `scale_y`: The scaling factor for converting Y-axis values to canvas coordinates.
///
/// # Details
/// The method interpolates between adjacent points in the dataset to fill the area
/// under the line segment and blend it into the canvas using the dataset's color and transparency.
///
/// # Example
/// ```rust,ignore
/// let scale_x = 10.0;
/// let scale_y = 10.0;
/// area_chart.draw_area(&mut canvas, &dataset, 50, 400, scale_x, scale_y);
/// ```
pub fn draw_area(
&self,
canvas: &mut PixelCanvas,
dataset: &AreaChartDataset,
origin_x: i32,
origin_y: i32,
scale_x: f64,
scale_y: f64,
) {
let mut points = dataset.points.clone();
points.sort_by(|a, b| a.0.partial_cmp(&b.0).unwrap_or(std::cmp::Ordering::Equal));
for window in points.windows(2) {
if let [p1, p2] = window {
let x1 = origin_x + ((p1.0) * scale_x) as i32;
let y1 = origin_y - ((p1.1) * scale_y) as i32;
let x2 = origin_x + ((p2.0) * scale_x) as i32;
let y2 = origin_y - ((p2.1) * scale_y) as i32;
// Fill the area under the line
for x in x1.min(x2)..=x1.max(x2) {
let interpolated_y =
y1 + ((x - x1) as f64 * (y2 - y1) as f64 / (x2 - x1).abs() as f64) as i32;
for y in interpolated_y..=origin_y {
canvas.blend_pixel(x as u32, y as u32, dataset.color, dataset.alpha);
}
}
}
}
}
pub fn update_range(&mut self) {
for dataset in &self.datasets {
for &(x, y) in &dataset.points {
if x < self.x_min {
self.x_min = x;
}
if x > self.x_max {
self.x_max = x;
}
if y < self.y_min {
self.y_min = y;
}
if y > self.y_max {
self.y_max = y;
}
}
}
let mut is_empty = self.datasets.is_empty();
for dataset in &self.datasets {
if dataset.points.is_empty() {
is_empty = true;
break;
}
}
if !is_empty {
let abs_x_min = self.x_min.abs();
let abs_x_max = self.x_max.abs();
if abs_x_min > abs_x_max {
self.x_max = abs_x_min;
} else {
self.x_min = -abs_x_max;
}
let abs_y_min = self.y_min.abs();
let abs_y_max = self.y_max.abs();
if abs_y_min > abs_y_max {
self.y_max = abs_y_min;
} else {
self.y_min = -abs_y_max;
}
}
}
}