Skip to main content

ggplot_rs/render/
layout.rs

1use crate::theme::{LegendPosition, Theme};
2
3use super::Rect;
4
5/// Computed layout areas for the plot.
6pub struct PlotLayout {
7    pub total: Rect,
8    pub plot_area: Rect,
9    pub title_area: Rect,
10    pub subtitle_area: Rect,
11    pub caption_area: Rect,
12    pub x_axis_area: Rect,
13    pub y_axis_area: Rect,
14    pub legend_area: Rect,
15}
16
17impl PlotLayout {
18    /// Compute layout from total dimensions and theme settings.
19    pub fn compute(
20        width: f64,
21        height: f64,
22        theme: &Theme,
23        has_title: bool,
24        has_legend: bool,
25    ) -> Self {
26        Self::compute_full(width, height, theme, has_title, false, false, has_legend)
27    }
28
29    /// Compute layout with full subtitle/caption support.
30    pub fn compute_full(
31        width: f64,
32        height: f64,
33        theme: &Theme,
34        has_title: bool,
35        has_subtitle: bool,
36        has_caption: bool,
37        has_legend: bool,
38    ) -> Self {
39        let margin = &theme.plot_margin;
40
41        let title_height = if has_title {
42            theme.title.size * 2.0
43        } else {
44            0.0
45        };
46
47        let subtitle_height = if has_subtitle {
48            theme.subtitle.size * 1.5
49        } else {
50            0.0
51        };
52
53        let caption_height = if has_caption {
54            theme.caption.size * 1.8
55        } else {
56            0.0
57        };
58
59        let x_axis_height = theme.axis_ticks_length
60            + if theme.axis_text_x.visible {
61                theme.axis_text_x.size + 4.0
62            } else {
63                0.0
64            }
65            + if theme.axis_title_x.visible {
66                theme.axis_title_x.size + 8.0
67            } else {
68                0.0
69            };
70
71        let y_axis_width = theme.axis_ticks_length
72            + if theme.axis_text_y.visible {
73                theme.axis_text_y.size * 3.5 + 4.0
74            } else {
75                0.0
76            }
77            + if theme.axis_title_y.visible {
78                theme.axis_title_y.size + 8.0
79            } else {
80                0.0
81            };
82
83        let legend_size = if has_legend {
84            theme.legend_margin.left
85                + theme.legend_key_width
86                + theme.legend_spacing
87                + theme.legend_text.size * 6.0
88                + theme.legend_margin.right
89        } else {
90            0.0
91        };
92
93        // Determine legend space allocation per position
94        let (legend_right, legend_left, legend_top, legend_bottom) = if has_legend {
95            match theme.legend_position {
96                LegendPosition::Right => (legend_size, 0.0, 0.0, 0.0),
97                LegendPosition::Left => (0.0, legend_size, 0.0, 0.0),
98                LegendPosition::Top => (0.0, 0.0, legend_size, 0.0),
99                LegendPosition::Bottom => (0.0, 0.0, 0.0, legend_size),
100                LegendPosition::None => (0.0, 0.0, 0.0, 0.0),
101            }
102        } else {
103            (0.0, 0.0, 0.0, 0.0)
104        };
105
106        let plot_x = margin.left + y_axis_width + legend_left;
107        let plot_y = margin.top + title_height + subtitle_height + legend_top;
108        let plot_width =
109            width - margin.left - margin.right - y_axis_width - legend_right - legend_left;
110        let plot_height = height
111            - margin.top
112            - margin.bottom
113            - title_height
114            - subtitle_height
115            - caption_height
116            - x_axis_height
117            - legend_top
118            - legend_bottom;
119
120        let plot_width = plot_width.max(50.0);
121        let plot_height = plot_height.max(50.0);
122
123        PlotLayout {
124            total: Rect {
125                x: 0.0,
126                y: 0.0,
127                width,
128                height,
129            },
130            plot_area: Rect {
131                x: plot_x,
132                y: plot_y,
133                width: plot_width,
134                height: plot_height,
135            },
136            title_area: Rect {
137                x: plot_x,
138                y: margin.top,
139                width: plot_width,
140                height: title_height,
141            },
142            subtitle_area: Rect {
143                x: plot_x,
144                y: margin.top + title_height,
145                width: plot_width,
146                height: subtitle_height,
147            },
148            caption_area: Rect {
149                x: plot_x,
150                y: plot_y + plot_height + x_axis_height,
151                width: plot_width,
152                height: caption_height,
153            },
154            x_axis_area: Rect {
155                x: plot_x,
156                y: plot_y + plot_height,
157                width: plot_width,
158                height: x_axis_height,
159            },
160            y_axis_area: Rect {
161                x: margin.left + legend_left,
162                y: plot_y,
163                width: y_axis_width,
164                height: plot_height,
165            },
166            legend_area: Rect {
167                x: plot_x + plot_width,
168                y: plot_y,
169                width: legend_size,
170                height: plot_height,
171            },
172        }
173    }
174}