scirs2_cluster/hierarchy/visualization/
types.rs

1//! Core types for dendrogram visualization
2//!
3//! This module contains all the fundamental types and configurations
4//! used for visualizing hierarchical clustering results.
5
6use scirs2_core::numeric::{Float, FromPrimitive};
7use serde::{Deserialize, Serialize};
8
9/// Color scheme options for dendrogram visualization
10#[derive(Debug, Clone, Copy, PartialEq, Eq)]
11pub enum ColorScheme {
12    /// Default color scheme with standard cluster colors
13    Default,
14    /// High contrast colors for better visibility
15    HighContrast,
16    /// Viridis color map (blue to green to yellow)
17    Viridis,
18    /// Plasma color map (purple to pink to yellow)
19    Plasma,
20    /// Grayscale for black and white publications
21    Grayscale,
22}
23
24/// Color threshold configuration for dendrogram visualization
25#[derive(Debug, Clone)]
26pub struct ColorThreshold<F: Float> {
27    /// Threshold value for coloring clusters
28    pub threshold: F,
29    /// Color to use above threshold
30    pub above_color: String,
31    /// Color to use below threshold
32    pub below_color: String,
33    /// Whether to use automatic threshold based on cluster count
34    pub auto_threshold: bool,
35    /// Number of clusters for automatic threshold (if auto_threshold is true)
36    pub target_clusters: Option<usize>,
37}
38
39impl<F: Float + FromPrimitive> Default for ColorThreshold<F> {
40    fn default() -> Self {
41        Self {
42            threshold: F::zero(),
43            above_color: "#1f77b4".to_string(), // Blue
44            below_color: "#ff7f0e".to_string(), // Orange
45            auto_threshold: true,
46            target_clusters: Some(4),
47        }
48    }
49}
50
51/// Enhanced dendrogram visualization configuration
52#[derive(Debug, Clone)]
53pub struct DendrogramConfig<F: Float> {
54    /// Color scheme to use
55    pub color_scheme: ColorScheme,
56    /// Color threshold configuration
57    pub color_threshold: ColorThreshold<F>,
58    /// Whether to show cluster labels
59    pub show_labels: bool,
60    /// Whether to show distance labels on branches
61    pub show_distances: bool,
62    /// Orientation of the dendrogram
63    pub orientation: DendrogramOrientation,
64    /// Line width for dendrogram branches
65    pub line_width: f32,
66    /// Font size for labels
67    pub font_size: f32,
68    /// Whether to truncate the dendrogram at a certain level
69    pub truncate_mode: Option<TruncateMode>,
70    /// Advanced styling options
71    pub styling: DendrogramStyling,
72}
73
74/// Advanced styling options for dendrograms
75#[derive(Debug, Clone)]
76pub struct DendrogramStyling {
77    /// Background color
78    pub background_color: String,
79    /// Branch style (solid, dashed, dotted)
80    pub branch_style: BranchStyle,
81    /// Node marker style
82    pub node_markers: NodeMarkerStyle,
83    /// Label styling
84    pub label_style: LabelStyle,
85    /// Grid options
86    pub grid: Option<GridStyle>,
87    /// Shadow effects
88    pub shadows: bool,
89    /// Border around the plot
90    pub border: Option<BorderStyle>,
91}
92
93/// Branch styling options
94#[derive(Debug, Clone, Copy, PartialEq, Eq)]
95pub enum BranchStyle {
96    Solid,
97    Dashed,
98    Dotted,
99    DashDot,
100}
101
102/// Node marker styling
103#[derive(Debug, Clone)]
104pub struct NodeMarkerStyle {
105    /// Show markers at internal nodes
106    pub show_internal_nodes: bool,
107    /// Show markers at leaf nodes
108    pub show_leaf_nodes: bool,
109    /// Marker shape
110    pub markershape: MarkerShape,
111    /// Marker size
112    pub marker_size: f32,
113    /// Marker color
114    pub marker_color: String,
115}
116
117/// Available marker shapes
118#[derive(Debug, Clone, Copy, PartialEq, Eq)]
119pub enum MarkerShape {
120    Circle,
121    Square,
122    Triangle,
123    Diamond,
124    Cross,
125}
126
127/// Label styling options
128#[derive(Debug, Clone)]
129pub struct LabelStyle {
130    /// Label font family
131    pub font_family: String,
132    /// Label font weight
133    pub font_weight: FontWeight,
134    /// Label color
135    pub color: String,
136    /// Label rotation angle in degrees
137    pub rotation: f32,
138    /// Label background
139    pub background: Option<String>,
140    /// Label padding
141    pub padding: f32,
142}
143
144/// Font weight options
145#[derive(Debug, Clone, Copy, PartialEq, Eq)]
146pub enum FontWeight {
147    Normal,
148    Bold,
149    Light,
150}
151
152/// Grid styling options
153#[derive(Debug, Clone)]
154pub struct GridStyle {
155    /// Show horizontal grid lines
156    pub show_horizontal: bool,
157    /// Show vertical grid lines
158    pub show_vertical: bool,
159    /// Grid line color
160    pub color: String,
161    /// Grid line width
162    pub line_width: f32,
163    /// Grid line style
164    pub style: BranchStyle,
165}
166
167/// Border styling options
168#[derive(Debug, Clone)]
169pub struct BorderStyle {
170    /// Border color
171    pub color: String,
172    /// Border width
173    pub width: f32,
174    /// Border radius
175    pub radius: f32,
176}
177
178impl Default for DendrogramStyling {
179    fn default() -> Self {
180        Self {
181            background_color: "#ffffff".to_string(),
182            branch_style: BranchStyle::Solid,
183            node_markers: NodeMarkerStyle::default(),
184            label_style: LabelStyle::default(),
185            grid: None,
186            shadows: false,
187            border: None,
188        }
189    }
190}
191
192impl Default for NodeMarkerStyle {
193    fn default() -> Self {
194        Self {
195            show_internal_nodes: false,
196            show_leaf_nodes: true,
197            markershape: MarkerShape::Circle,
198            marker_size: 4.0,
199            marker_color: "#333333".to_string(),
200        }
201    }
202}
203
204impl Default for LabelStyle {
205    fn default() -> Self {
206        Self {
207            font_family: "Arial, sans-serif".to_string(),
208            font_weight: FontWeight::Normal,
209            color: "#000000".to_string(),
210            rotation: 0.0,
211            background: None,
212            padding: 2.0,
213        }
214    }
215}
216
217impl Default for GridStyle {
218    fn default() -> Self {
219        Self {
220            show_horizontal: true,
221            show_vertical: false,
222            color: "#e0e0e0".to_string(),
223            line_width: 0.5,
224            style: BranchStyle::Solid,
225        }
226    }
227}
228
229impl Default for BorderStyle {
230    fn default() -> Self {
231        Self {
232            color: "#cccccc".to_string(),
233            width: 1.0,
234            radius: 0.0,
235        }
236    }
237}
238
239/// Dendrogram orientation options
240#[derive(Debug, Clone, Copy, PartialEq, Eq)]
241pub enum DendrogramOrientation {
242    /// Top to bottom (leaves at bottom)
243    Top,
244    /// Bottom to top (leaves at top)
245    Bottom,
246    /// Left to right (leaves on right)
247    Left,
248    /// Right to left (leaves on left)
249    Right,
250}
251
252/// Truncation modes for large dendrograms
253#[derive(Debug, Clone)]
254pub enum TruncateMode {
255    /// Show only the last N merges
256    LastMerges(usize),
257    /// Show only merges above a distance threshold
258    DistanceThreshold(f64),
259    /// Show only the top N levels of the tree
260    TopLevels(usize),
261}
262
263impl<F: Float + FromPrimitive> Default for DendrogramConfig<F> {
264    fn default() -> Self {
265        Self {
266            color_scheme: ColorScheme::Default,
267            color_threshold: ColorThreshold::default(),
268            show_labels: true,
269            show_distances: false,
270            orientation: DendrogramOrientation::Top,
271            line_width: 1.0,
272            font_size: 10.0,
273            truncate_mode: None,
274            styling: DendrogramStyling::default(),
275        }
276    }
277}
278
279/// Enhanced dendrogram visualization data structure
280#[derive(Debug, Clone)]
281pub struct DendrogramPlot<F: Float> {
282    /// Branch coordinates for drawing
283    pub branches: Vec<Branch<F>>,
284    /// Leaf positions and labels
285    pub leaves: Vec<Leaf>,
286    /// Color assignments for each branch
287    pub colors: Vec<String>,
288    /// Legend information
289    pub legend: Vec<LegendEntry>,
290    /// Plot bounds (min_x, max_x, min_y, max_y)
291    pub bounds: (F, F, F, F),
292    /// Configuration used to create this plot
293    pub config: DendrogramConfig<F>,
294}
295
296/// Branch representation for visualization
297#[derive(Debug, Clone)]
298pub struct Branch<F: Float> {
299    /// Starting point coordinates (x, y)
300    pub start: (F, F),
301    /// Ending point coordinates (x, y)
302    pub end: (F, F),
303    /// Distance value at this merge
304    pub distance: F,
305    /// Cluster ID or merge index
306    pub cluster_id: Option<usize>,
307    /// Color for this branch
308    pub color: String,
309    /// Line width override (if different from default)
310    pub line_width: Option<f32>,
311}
312
313/// Leaf node representation for visualization
314#[derive(Debug, Clone)]
315pub struct Leaf {
316    /// Position coordinates (x, y)
317    pub position: (f64, f64),
318    /// Label text to display
319    pub label: String,
320    /// Color for the leaf
321    pub color: String,
322    /// Original data index
323    pub data_index: usize,
324}
325
326/// Legend entry for cluster visualization
327#[derive(Debug, Clone)]
328pub struct LegendEntry {
329    /// Color swatch
330    pub color: String,
331    /// Descriptive label
332    pub label: String,
333    /// Distance threshold (if applicable)
334    pub threshold: Option<f64>,
335}
336
337#[cfg(test)]
338mod tests {
339    use super::*;
340
341    #[test]
342    fn test_color_scheme_variants() {
343        let schemes = [
344            ColorScheme::Default,
345            ColorScheme::HighContrast,
346            ColorScheme::Viridis,
347            ColorScheme::Plasma,
348            ColorScheme::Grayscale,
349        ];
350
351        for scheme in &schemes {
352            assert!(format!("{:?}", scheme).len() > 0);
353        }
354    }
355
356    #[test]
357    fn test_color_threshold_default() {
358        let threshold: ColorThreshold<f64> = ColorThreshold::default();
359        assert_eq!(threshold.threshold, 0.0);
360        assert!(threshold.auto_threshold);
361        assert_eq!(threshold.target_clusters, Some(4));
362    }
363
364    #[test]
365    fn test_dendrogram_config_default() {
366        let config: DendrogramConfig<f64> = DendrogramConfig::default();
367        assert_eq!(config.color_scheme, ColorScheme::Default);
368        assert!(config.show_labels);
369        assert!(!config.show_distances);
370        assert_eq!(config.orientation, DendrogramOrientation::Top);
371    }
372
373    #[test]
374    fn test_styling_defaults() {
375        let styling = DendrogramStyling::default();
376        assert_eq!(styling.background_color, "#ffffff");
377        assert_eq!(styling.branch_style, BranchStyle::Solid);
378        assert!(!styling.shadows);
379    }
380
381    #[test]
382    fn test_branch_style_variants() {
383        let styles = [
384            BranchStyle::Solid,
385            BranchStyle::Dashed,
386            BranchStyle::Dotted,
387            BranchStyle::DashDot,
388        ];
389
390        for style in &styles {
391            assert!(format!("{:?}", style).len() > 0);
392        }
393    }
394
395    #[test]
396    fn test_marker_shape_variants() {
397        let shapes = [
398            MarkerShape::Circle,
399            MarkerShape::Square,
400            MarkerShape::Triangle,
401            MarkerShape::Diamond,
402            MarkerShape::Cross,
403        ];
404
405        for shape in &shapes {
406            assert!(format!("{:?}", shape).len() > 0);
407        }
408    }
409
410    #[test]
411    fn test_truncate_mode_variants() {
412        let modes = [
413            TruncateMode::LastMerges(10),
414            TruncateMode::DistanceThreshold(0.5),
415            TruncateMode::TopLevels(5),
416        ];
417
418        for mode in &modes {
419            assert!(format!("{:?}", mode).len() > 0);
420        }
421    }
422}