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
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
use tiny_skia::{FillRule, Paint, PathBuilder, Point, Stroke, Transform};
use crate::{
drawable::{Bound, Drawable},
primitive::Config,
};
#[derive(Debug)]
pub enum AreaType {
Line,
Step,
}
pub struct Area {
name: String,
x_edge: Vec<f32>,
y_value: Vec<f32>,
area_type: AreaType,
config: Config,
}
impl Area {
/// Creates a new `Area` with the given name and configuration.
///
/// # Arguments
///
/// * `name` - The name identifier for the area.
/// * `config` - The configuration settings for the area.
pub fn new(name: String, config: Config) -> Self {
Area {
name,
x_edge: Vec::new(),
y_value: Vec::new(),
config,
area_type: AreaType::Step,
}
}
/// Adds data to the existing x and y vectors.
///
/// # Arguments
///
/// * `x` - Slice of x coordinates to add.
/// * `y` - Slice of y coordinates to add.
///
/// # Note
///
/// This function does nothing if the lengths of `x` and `y` do not match.
fn _add_data(&mut self, x: &[f32], y: &[f32]) {
if x.len() != y.len() {
return;
}
self.x_edge.extend_from_slice(x);
self.y_value.extend_from_slice(y);
}
/// Sets the data for the area by replacing existing values.
///
/// # Arguments
///
/// * `x` - Slice of x coordinates (edges).
/// * `y` - Slice of y coordinates (values).
///
/// # Note
///
/// Prints a warning if `x.len() != y.len() - 1`.
pub fn set_data(&mut self, x: &[f32], y: &[f32]) {
if x.len() != y.len() - 1 {
println!("make sure x length is less 1 then y length");
}
self.x_edge = x.to_vec();
self.y_value = y.to_vec();
}
/// Sets data for the area using a prototype range definition.
///
/// Generates x coordinates starting from `x_start` with a given `step`.
///
/// # Arguments
///
/// * `y` - Slice of y coordinates.
/// * `x_start` - The starting value for the x coordinates.
/// * `step` - The step size between consecutive x coordinates.
/// # Note
/// this `x` auto generated is not precise(need to optimize)
pub fn set_data_prototype(&mut self, y: &[f32], x_start: f32, step: f32) {
let n = y.len();
if n == 0 {
return;
}
self.x_edge = (0..=n).map(|i| x_start + i as f32 * step).collect();
self.y_value = y.to_vec();
}
/// Sets data for the area using a linear step for x coordinates.
///
/// This is a convenience wrapper around `set_data_prototype`.
///
/// # Arguments
///
/// * `y` - Slice of y coordinates.
/// * `x_start` - The starting value for the x coordinates.
/// * `step` - The step size between consecutive x coordinates.
pub fn set_data_with_step(&mut self, y: &[f32], x_start: f32, step: f32) {
self.set_data_prototype(y, x_start, step);
}
/// Sets data for the area with normalized x coordinates (0, 1, 2, ...).
///
/// This is a convenience wrapper around `set_data_prototype` with `x_start = 0.0` and `step = 1.0`.
///
/// # Arguments
///
/// * `y` - Slice of y coordinates.
pub fn set_data_norm(&mut self, y: &[f32]) {
self.set_data_prototype(y, 0.0, 1.0);
}
/// Changes the area type.
///
/// # Arguments
///
/// * `area_type` - The new area type.
/// * (`AreaType::Line` or `AreaType::Step`)
pub fn change_area_type(&mut self, area_type: AreaType) {
self.area_type = area_type;
}
}
impl Drawable for Area {
fn draw(&self, pixmap: &mut tiny_skia::Pixmap, ts: &tiny_skia::Transform) {
if self.x_edge.is_empty() {
return;
}
let mut pb = PathBuilder::new();
let baseline = 0.0;
match self.area_type {
// --- 模式 1: Step (阶梯状/柱状面积) ---
AreaType::Step => {
// 如果只有一位 x,自动补齐为从 0.0 到 x[0]
let edges: Vec<f32> = if self.x_edge.len() == 1 {
vec![0.0, self.x_edge[0]]
} else {
self.x_edge.clone()
};
for i in 0..edges.len() - 1 {
let x_l = edges[i];
let x_r = edges[i + 1];
let y_val = self.y_value.get(i).cloned().unwrap_or(0.0);
// 构造矩形的四个点并映射
let mut p1 = Point::from_xy(x_l, baseline);
let mut p2 = Point::from_xy(x_l, y_val);
let mut p3 = Point::from_xy(x_r, y_val);
let mut p4 = Point::from_xy(x_r, baseline);
ts.map_point(&mut p1);
ts.map_point(&mut p2);
ts.map_point(&mut p3);
ts.map_point(&mut p4);
pb.move_to(p1.x, p1.y);
pb.line_to(p2.x, p2.y);
pb.line_to(p3.x, p3.y);
pb.line_to(p4.x, p4.y);
pb.close();
}
}
// --- 模式 2: Curve (折线面积填充) ---
AreaType::Line => {
if !self.x_edge.is_empty() {
// 1. 起点:(x_0, baseline)
let mut p_start = Point::from_xy(self.x_edge[0], baseline);
ts.map_point(&mut p_start);
pb.move_to(p_start.x, p_start.y);
// 2. 连接所有数据点 (x_i, y_i)
for i in 0..self.x_edge.len() {
let y_val = self.y_value.get(i).cloned().unwrap_or(0.0);
let mut p = Point::from_xy(self.x_edge[i], y_val);
ts.map_point(&mut p);
pb.line_to(p.x, p.y);
}
// 3. 终点:(x_last, baseline)
let mut p_end = Point::from_xy(*self.x_edge.last().unwrap(), baseline);
ts.map_point(&mut p_end);
pb.line_to(p_end.x, p_end.y);
pb.close();
}
}
}
// --- 统一渲染逻辑 ---
if let Some(path) = pb.finish() {
let mut paint = Paint::default();
let [r, g, b, a] = self.config.color;
// 1. 填充路径 (使用半透明色)
paint.set_color_rgba8(r, g, b, a / 2);
paint.anti_alias = true;
pixmap.fill_path(
&path,
&paint,
FillRule::Winding,
Transform::identity(),
None,
);
// 2. 描边 (使用相同的颜色,或者你可以根据需求调深一点)
let stroke = Stroke {
width: self.config.stroke_width,
..Default::default()
};
pixmap.stroke_path(&path, &paint, &stroke, Transform::identity(), None);
}
}
fn bound(&self) -> Option<Bound> {
if self.x_edge.is_empty() || self.y_value.is_empty() {
return None;
}
let x_min = self.x_edge.iter().fold(f32::INFINITY, |a, &b| a.min(b));
let x_max = self.x_edge.iter().fold(f32::NEG_INFINITY, |a, &b| a.max(b));
let y_min = self.y_value.iter().fold(f32::INFINITY, |a, &b| a.min(b));
let y_max = self
.y_value
.iter()
.fold(f32::NEG_INFINITY, |a, &b| a.max(b));
Some(Bound {
x_min,
x_max,
y_min,
y_max,
})
}
fn name(&self) -> String {
self.name.clone()
}
fn get_color(&self) -> [u8; 4] {
self.config.color
}
fn set_color(&mut self, color: [u8; 4]) {
self.config.color = color;
}
}