1use svg::node::element::Rectangle;
2use svg::Document;
3
4use crate::cli::Origin;
5use crate::utils::linear_scale_float;
6use crate::{cli, plot};
7
8use plot::category::Category;
9
10use super::axis::{AxisOptions, ChartAxes, Position, Scale};
11use super::blob::category_legend_full;
12use super::chart::{Chart, Dimensions};
13use super::data::{Line, LineData};
14use super::ShowLegend;
15
16#[derive(Clone, Debug)]
17pub struct CumulativeData {
18 pub values: Vec<f64>,
19 pub cat: Vec<usize>,
20 pub cat_order: Vec<Category>,
21}
22
23pub fn cumulative_lines(
24 cumulative_data: &CumulativeData,
25 dimensions: &Dimensions,
26 options: &cli::PlotOptions,
27) -> LineData {
28 let x_domain = [0.0, cumulative_data.values.len() as f64];
29 let x_range = [0.0, dimensions.width];
30 let x_axis = AxisOptions {
31 position: Position::BOTTOM,
32 label: "cumulative count".to_string(),
33 height: dimensions.height + dimensions.padding.top + dimensions.padding.bottom,
34 padding: [dimensions.padding.left, dimensions.padding.right],
35 offset: dimensions.height + dimensions.padding.top + dimensions.padding.bottom,
36 scale: Scale::LINEAR,
37 domain: x_domain,
38 range: x_range,
39 ..Default::default()
40 };
41 let y_domain = [0.0, cumulative_data.values.iter().sum::<f64>()];
42 let y_range = [dimensions.height, 0.0];
43 let y_axis = AxisOptions {
44 position: Position::LEFT,
45 label_offset: 83.0,
46 label: "cumulative length".to_string(),
47 height: dimensions.width + dimensions.padding.right + dimensions.padding.left,
48 padding: [dimensions.padding.bottom, dimensions.padding.top],
49 scale: Scale::LINEAR,
50 domain: y_domain,
51 range: y_range,
52 rotate: true,
53 ..Default::default()
54 };
55 let mut lines = vec![];
56 let mut cat_order = cumulative_data.cat_order.clone();
57 match options.origin {
58 Some(Origin::Y) => cat_order.sort_by(|x, y| y.span.cmp(&x.span)),
59 _ => (),
60 };
61 let mut end_coords = [0.0, y_range[0]];
63 for (index, cat) in cat_order.iter().enumerate() {
64 let mut lengths = vec![];
65 for i in cat.indices.iter() {
66 lengths.push(cumulative_data.values[*i]);
67 }
68 lengths.sort_by(|a, b| b.partial_cmp(a).unwrap());
69 let mut coords = vec![end_coords.clone()];
70 let mut cumulative_span = 0.0;
71 for (i, length) in lengths.iter().enumerate() {
72 cumulative_span += length;
74 coords.push([
75 coords[0][0] + linear_scale_float((i + 1) as f64, &x_domain, &x_range),
76 coords[0][1] - dimensions.height
77 + linear_scale_float(cumulative_span as f64, &y_domain, &y_range),
78 ]);
79 }
80 if index > 0 {
81 end_coords = match options.origin {
82 Some(Origin::X) | Some(Origin::Y) => {
83 [coords[coords.len() - 1][0], coords[coords.len() - 1][1]]
84 }
85 _ => end_coords,
86 };
87 }
88 lines.push(Line {
89 coords,
90 label: Some(cat.title.clone()),
91 color: Some(cat.color.clone()),
92 weight: 3.0,
93 cat_index: index,
94 ..Default::default()
95 });
96
97 }
107 LineData {
111 lines,
112 x: x_axis,
113 y: y_axis,
114 categories: cumulative_data.cat_order.clone(),
115 }
116}
117
118pub fn plot(dimensions: Dimensions, line_data: LineData, options: &cli::PlotOptions) -> Document {
119 let height = dimensions.height
120 + dimensions.margin.top
121 + dimensions.margin.bottom
122 + dimensions.padding.top
123 + dimensions.padding.bottom;
124
125 let width = dimensions.width
126 + dimensions.margin.right
127 + dimensions.margin.left
128 + dimensions.padding.right
129 + dimensions.padding.left;
130 let x_opts = line_data.x.clone();
131 let y_opts = line_data.y.clone();
132
133 let cumulative = Chart {
134 axes: ChartAxes {
135 x: Some(x_opts.clone()),
136 y: Some(y_opts.clone()),
137 ..Default::default()
138 },
139 line_data: Some(line_data.clone()),
140 dimensions: dimensions.clone(),
141 ..Default::default()
142 };
143
144 let legend_x = match options.show_legend {
145 ShowLegend::Compact => width - 240.0,
146 _ => width - 185.0,
147 };
148
149 let document = Document::new()
150 .set("viewBox", (0, 0, width, height))
151 .add(
152 Rectangle::new()
153 .set("fill", "#ffffff")
154 .set("stroke", "none")
155 .set("width", width)
156 .set("height", height),
157 )
158 .add(cumulative.svg(0.0, 0.0, None).set(
159 "transform",
160 format!(
161 "translate({}, {})",
162 dimensions.margin.left, dimensions.margin.top
163 ),
164 ))
165 .add(
166 category_legend_full(line_data.categories.clone(), options.show_legend.clone()).set(
167 "transform",
168 format!(
169 "translate({}, {})",
170 legend_x,
171 height
172 - dimensions.margin.bottom
173 - dimensions.padding.bottom * 2.0
174 - line_data.categories.len() as f64 * 26.0
175 ),
176 ),
177 );
178
179 document
180}