1use super::*;
2
3impl SeriesRenderer {
5 pub fn new() -> Self {
7 Self {}
8 }
9
10 pub fn draw(&mut self, list: &mut DisplayList, option: &ChartOption, layout: &ChartLayout) {
18 for (index, series) in option.series.iter().enumerate() {
19 if !series.show {
20 continue;
21 }
22 let color: Color = palette_color(option, index);
23 let series_layout: Option<&SeriesLayout> = layout
24 .series
25 .iter()
26 .find(|entry: &&SeriesLayout| entry.index == index);
27 let Some(series_layout) = series_layout else {
28 continue;
29 };
30 let z: i32 = Z_SERIES + index as i32 * 10;
31 match series.kind {
32 SeriesType::Line => {
33 self.draw_line(list, series, series_layout, color, z);
34 }
35 SeriesType::ThemeRiver => {
36 draw_theme_river(list, series, series_layout, color, z);
37 }
38 SeriesType::Bar => {
39 self.draw_bar(list, series, series_layout, color, z);
40 }
41 SeriesType::PictorialBar => {
42 draw_pictorial_bar(list, series, series_layout, color, z);
43 }
44 SeriesType::Pie => {
45 self.draw_pie(list, series, series_layout, color, z);
46 }
47 SeriesType::Funnel => {
48 draw_funnel(list, series, series_layout, color, z);
49 }
50 SeriesType::Radar => {
51 draw_radar(list, option, series, series_layout, color, z);
52 }
53 SeriesType::Gauge => {
54 draw_gauge(list, series, series_layout, color, z);
55 }
56 SeriesType::Scatter | SeriesType::EffectScatter => {
57 self.draw_scatter(list, series, series_layout, color, z);
58 }
59 SeriesType::Candlestick => {
60 self.draw_candlestick(list, series, series_layout, color, z);
61 }
62 SeriesType::Boxplot => {
63 self.draw_boxplot(list, series, series_layout, color, z);
64 }
65 SeriesType::Heatmap => {
66 self.draw_heatmap(list, series, series_layout, color, z);
67 }
68 SeriesType::Graph => {
69 draw_graph(list, series, series_layout, color, z);
70 }
71 SeriesType::Treemap => {
72 draw_treemap(list, series, series_layout, color, z);
73 }
74 SeriesType::Sunburst => {
75 draw_sunburst(list, series, series_layout, color, z);
76 }
77 SeriesType::Sankey => {
78 draw_sankey(list, series, series_layout, color, z);
79 }
80 SeriesType::Parallel => {
81 draw_parallel(list, series, series_layout, color, z);
82 }
83 SeriesType::Lines => {
84 draw_lines(list, series, series_layout, color, z);
85 }
86 SeriesType::Map => {
87 draw_map(list, option, series, series_layout, layout, color, z);
88 }
89 SeriesType::Custom => {
90 draw_custom(list, series, series_layout, layout, color, z);
91 }
92 }
93 }
94 }
95
96 fn draw_labels(
110 &mut self,
111 list: &mut DisplayList,
112 series: &SeriesOption,
113 layout: &SeriesLayout,
114 color: Color,
115 z: i32,
116 ) {
117 let Some(label) = &series.label else {
118 return;
119 };
120 if !label.show {
121 return;
122 }
123 let style: TextStyle = series_label_style(series, color);
124 let distance: f64 = label.distance.unwrap_or(0.0);
125 for point in &layout.points {
126 let text: String = if point.name.is_empty() {
127 format_number(point.value)
128 } else {
129 point.name.clone()
130 };
131 if text.is_empty() {
132 continue;
133 }
134 let mut label_style: TextStyle = style.clone();
135 if let Some(align) = label.align {
136 label_style.align = align;
137 }
138 if let Some(vertical_align) = label.vertical_align {
139 label_style.vertical_align = vertical_align;
140 }
141 list.text(text, point.x, point.y - distance, label_style, z);
142 }
143 }
144
145 fn draw_line(
155 &mut self,
156 list: &mut DisplayList,
157 series: &SeriesOption,
158 layout: &SeriesLayout,
159 color: Color,
160 z: i32,
161 ) {
162 if layout.points.is_empty() {
163 return;
164 }
165 let points: Vec<Point> = layout
166 .points
167 .iter()
168 .map(|point: &SeriesPoint| Point::new(point.x as f32, point.y as f32))
169 .collect();
170
171 if let Some(line_options) = &series.line
173 && let Some(area_style) = &line_options.area_style
174 {
175 let mut area: SeriesOption = series.clone();
176 area.item_style = Some(area_style.clone());
177 let area_color: Color = area_style
178 .color
179 .as_ref()
180 .and_then(|value: &String| Color::parse(value))
181 .unwrap_or(color);
182 let fill: Fill = series_fill(&area, area_color);
183 let baseline: f64 = baseline_of(&layout.points);
184 list.strip(points.clone(), baseline as f32, fill, 1.0, z);
185 }
186
187 let path: Path = match series
188 .line
189 .as_ref()
190 .and_then(|line: &LineOptions| line.smooth)
191 {
192 Some(LineSmooth::Smooth) => smooth_path(&points),
193 Some(LineSmooth::Step) => step_path(&points, StepPosition::Middle),
194 _ => polyline_path(&points),
195 };
196 let stroke: Stroke = series_stroke(series, color);
197 let style: LineStyle = series_line_style(series, color);
198 list.stroke(path, stroke, style, 1.0, z + 1);
199
200 let show_symbol: bool = series
202 .line
203 .as_ref()
204 .and_then(|line: &LineOptions| line.show_symbol)
205 .unwrap_or(true)
206 && series
207 .label
208 .as_ref()
209 .map(|label: &LabelOption| label.show)
210 .unwrap_or(false);
211 let symbol: SymbolType = series
212 .line
213 .as_ref()
214 .and_then(|line: &LineOptions| line.symbol)
215 .unwrap_or(SymbolType::Circle);
216 let size: f64 = series
217 .line
218 .as_ref()
219 .and_then(|line: &LineOptions| line.symbol_size)
220 .map(|size: SymbolSize| match size {
221 SymbolSize::Uniform(value) => value,
222 SymbolSize::NonUniform(width, _) => width,
223 })
224 .unwrap_or(DEFAULT_SYMBOL_SIZE);
225 let dense: bool = average_spacing(&layout.points) < size;
228 let show_all: bool = series
229 .line
230 .as_ref()
231 .and_then(|line: &LineOptions| line.show_all_symbol)
232 .unwrap_or(false);
233 if show_symbol && (!dense || show_all) {
234 let fill: Fill = series_fill(series, color);
235 for point in &layout.points {
236 let path: Path = symbol_path(symbol, point.x, point.y, size);
237 list.fill(path, fill.clone(), 1.0, z + 2);
238 }
239 }
240
241 self.draw_labels(list, series, layout, color, z + 3);
242 }
243
244 fn draw_bar(
254 &mut self,
255 list: &mut DisplayList,
256 series: &SeriesOption,
257 layout: &SeriesLayout,
258 color: Color,
259 z: i32,
260 ) {
261 if layout.points.is_empty() {
262 return;
263 }
264 let fill: Fill = series_fill(series, color);
265 let item_style: ItemStyleOption = series.item_style.clone().unwrap_or_default();
270 let border_width: f64 = item_style.border_width.unwrap_or(0.0);
271 let border_color: Color = item_style
272 .border_color
273 .as_deref()
274 .and_then(Color::parse)
275 .unwrap_or(color);
276
277 let bar_options: BarOptions = series.bar.clone().unwrap_or_default();
278 let horizontal: bool = bar_options.horizontal;
279 let category_gap: f64 = bar_options
280 .bar_category_gap
281 .as_deref()
282 .and_then(parse_percent)
283 .unwrap_or(DEFAULT_BAR_CATEGORY_GAP);
284 let group_gap: f64 = bar_options
285 .bar_gap
286 .as_deref()
287 .and_then(parse_percent)
288 .unwrap_or(DEFAULT_BAR_GAP);
289
290 let group_size: f64 = layout.stack_size.max(1) as f64;
292 let available: f64 = (layout.band_width * (1.0 - category_gap)).max(1.0);
293 let bar_width: f64 = match bar_options.bar_width {
294 Some(width) => width,
295 None => (available / group_size * (1.0 - group_gap)).max(1.0),
296 };
297 let clamped: f64 = bar_options.bar_max_width.unwrap_or(f64::INFINITY);
298 let minimum: f64 = bar_options.bar_min_width.unwrap_or(0.0);
299 let bar_width: f64 = bar_width.clamp(minimum, clamped);
300
301 let baseline: f64 = baseline_of(&layout.points);
303
304 for (index, point) in layout.points.iter().enumerate() {
305 let offset: f64 = (index % group_size as usize) as f64
306 * (bar_width / (1.0 - group_gap).max(f64::EPSILON));
307 let center: f64 = point.x;
308 let value: f64 = point.value;
309 let path: Path = if horizontal {
310 let left: f64 = baseline.min(point.y);
311 let right: f64 = baseline.max(point.y);
312 Path::builder()
313 .rect(
314 (left + offset) as f32,
315 (center - bar_width * 0.5) as f32,
316 (right - left).max(0.0) as f32,
317 bar_width as f32,
318 )
319 .build()
320 } else {
321 let top: f64 = baseline.min(point.y);
322 let height: f64 = (baseline - point.y).abs();
323 Path::builder()
324 .rect(
325 (center - bar_width * 0.5 + offset) as f32,
326 top as f32,
327 bar_width as f32,
328 height.max(0.0) as f32,
329 )
330 .build()
331 };
332 let _: f64 = value;
333 list.fill(path.clone(), fill.clone(), 1.0, z);
334 if border_width > 0.0 {
335 let mut style: LineStyle = LineStyle::new();
336 style.width = border_width;
337 style.color = border_color;
338 list.stroke(path, Stroke::Solid(border_color), style, 1.0, z + 1);
339 }
340 }
341 }
342
343 fn draw_pie(
353 &mut self,
354 list: &mut DisplayList,
355 series: &SeriesOption,
356 layout: &SeriesLayout,
357 color: Color,
358 z: i32,
359 ) {
360 if layout.points.is_empty() {
361 return;
362 }
363 let fill: Fill = series_fill(series, color);
364 let total: f64 = layout
365 .points
366 .iter()
367 .map(|point: &SeriesPoint| point.value.max(0.0))
368 .sum();
369 if total <= 0.0 {
370 return;
371 }
372 let (center_x, center_y, radius) = pie_geometry(series, layout);
373 let inner_radius: f64 = series
374 .pie
375 .as_ref()
376 .and_then(|pie: &PieOptions| pie.inner_radius.as_deref())
377 .and_then(parse_percent)
378 .map(|value: f64| value * radius)
379 .unwrap_or(0.0);
380 let start_angle: f64 = series
381 .pie
382 .as_ref()
383 .and_then(|pie: &PieOptions| pie.start_angle)
384 .unwrap_or(90.0)
385 .to_radians();
386 let mut angle: f64 = start_angle;
387 for (index, point) in layout.points.iter().enumerate() {
388 let share: f64 = point.value.max(0.0) / total;
389 let sweep: f64 = share * core::f64::consts::TAU;
390 let path: Path = sector_path(
391 center_x,
392 center_y,
393 radius,
394 inner_radius,
395 angle,
396 angle + sweep,
397 );
398 list.fill(path, fill.clone(), 1.0, z + index as i32);
399 angle += sweep;
400 }
401 }
402
403 fn draw_scatter(
413 &mut self,
414 list: &mut DisplayList,
415 series: &SeriesOption,
416 layout: &SeriesLayout,
417 color: Color,
418 z: i32,
419 ) {
420 if layout.points.is_empty() {
421 return;
422 }
423 let fill: Fill = series_fill(series, color);
424 let symbol: SymbolType = series
425 .scatter
426 .as_ref()
427 .and_then(|scatter: &ScatterOptions| scatter.symbol)
428 .unwrap_or(SymbolType::Circle);
429 let size: f64 = series
430 .scatter
431 .as_ref()
432 .and_then(|scatter: &ScatterOptions| scatter.symbol_size)
433 .map(|size: SymbolSize| match size {
434 SymbolSize::Uniform(value) => value,
435 SymbolSize::NonUniform(width, _) => width,
436 })
437 .unwrap_or(DEFAULT_SYMBOL_SIZE);
438 let dense: bool = average_spacing(&layout.points) < size;
441 if dense {
442 return;
443 }
444 for point in &layout.points {
445 let path: Path = symbol_path(symbol, point.x, point.y, size);
446 list.fill(path, fill.clone(), 1.0, z);
447 }
448 self.draw_labels(list, series, layout, color, z + 1);
449 }
450
451 fn draw_candlestick(
461 &mut self,
462 list: &mut DisplayList,
463 series: &SeriesOption,
464 layout: &SeriesLayout,
465 color: Color,
466 z: i32,
467 ) {
468 if layout.points.is_empty() {
469 return;
470 }
471 let rising: Color = series
472 .candlestick
473 .as_ref()
474 .and_then(|candle: &CandlestickOptions| candle.color.as_deref())
475 .and_then(Color::parse)
476 .unwrap_or(color);
477 let falling: Color = series
478 .candlestick
479 .as_ref()
480 .and_then(|candle: &CandlestickOptions| candle.color0.as_deref())
481 .and_then(Color::parse)
482 .unwrap_or(Color::rgb(30, 130, 220));
483 let width: f64 = series
484 .candlestick
485 .as_ref()
486 .and_then(|candle: &CandlestickOptions| candle.bar_width)
487 .unwrap_or(DEFAULT_BAR_WIDTH * 0.5);
488 let baseline: f64 = baseline_of(&layout.points);
489
490 for point in &layout.points {
491 let body: f64 = series
492 .candlestick
493 .as_ref()
494 .and_then(|candle: &CandlestickOptions| candle.bar_max_width)
495 .unwrap_or(width);
496 let fill: Color = if point.value >= 0.0 { rising } else { falling };
497 let top: f64 = baseline.min(point.y);
498 let height: f64 = (baseline - point.y).abs();
499 let body_path: Path = Path::builder()
500 .rect(
501 (point.x - body * 0.5) as f32,
502 top as f32,
503 body as f32,
504 height.max(1.0) as f32,
505 )
506 .build();
507 list.fill(body_path, Fill::Solid(fill), 1.0, z);
508 }
509 }
510
511 fn draw_boxplot(
521 &mut self,
522 list: &mut DisplayList,
523 series: &SeriesOption,
524 layout: &SeriesLayout,
525 color: Color,
526 z: i32,
527 ) {
528 if layout.points.is_empty() {
529 return;
530 }
531 let fill: Fill = series_fill(series, color);
532 let width: f64 = series
533 .boxplot
534 .as_ref()
535 .and_then(|boxplot: &BoxplotOptions| boxplot.box_width)
536 .unwrap_or(DEFAULT_BAR_WIDTH);
537 for point in &layout.points {
538 let path: Path = Path::builder()
539 .rect(
540 (point.x - width * 0.5) as f32,
541 (point.y - width * 0.5) as f32,
542 width as f32,
543 width as f32,
544 )
545 .build();
546 list.fill(path, fill.clone(), 1.0, z);
547 }
548 }
549
550 fn draw_heatmap(
560 &mut self,
561 list: &mut DisplayList,
562 series: &SeriesOption,
563 layout: &SeriesLayout,
564 color: Color,
565 z: i32,
566 ) {
567 if layout.points.is_empty() {
568 return;
569 }
570 let fill: Fill = series_fill(series, color);
571 let size: f64 = series
572 .heatmap
573 .as_ref()
574 .and_then(|heatmap: &HeatmapOptions| heatmap.min_size)
575 .unwrap_or(1.0);
576 for point in &layout.points {
577 let path: Path = Path::builder()
578 .rect(
579 (point.x - size * 0.5) as f32,
580 (point.y - size * 0.5) as f32,
581 size as f32,
582 size as f32,
583 )
584 .build();
585 list.fill(path, fill.clone(), 1.0, z);
586 }
587 }
588}
589
590impl Default for SeriesRenderer {
592 fn default() -> Self {
594 Self::new()
595 }
596}
597
598impl RadarGeometry {
600 pub fn point(&self, index: usize, ratio: f64) -> (f64, f64) {
614 let angle: f64 = self.get_start_angle()
615 + index as f64 / self.get_sides().max(1) as f64 * core::f64::consts::TAU;
616 (
617 self.get_center_x() + self.get_radius() * ratio * angle.cos(),
618 self.get_center_y() + self.get_radius() * ratio * angle.sin(),
619 )
620 }
621}