blobtk/plot/
cumulative.rs

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 ordered_points = vec![vec![]; cat_order.len()];
62    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            // add coords to line
73            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        //     ordered_points[*cat_index].push(ScatterPoint {
98        //         x: x_scaled[i],
99        //         y: y_scaled[i],
100        //         z: z_scaled[i],
101        //         label: Some(cat.label.clone()),
102        //         color: Some(cat.color.clone()),
103        //         cat_index: *cat_index,
104        //         data_index: i,
105        //     })
106    }
107    // for cat_points in ordered_points {
108    //     points.extend(cat_points);
109    // }
110    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}