/ src / plotting.rs
plotting.rs
  1  use std::{f64::{self, consts::PI}, path::PathBuf};
  2  
  3  pub fn plot(ui: &mut egui::Ui, circles: &mut Vec<crate::Circle>, num_points: &usize, line_colors: &Vec<egui::Color32>) {
  4      egui_plot::Plot::new("Encoded Message")
  5          .height(ui.available_height())
  6          .width(ui.available_width())
  7          .show_axes(false)
  8          .show_grid(false)
  9          .show_x(false)
 10          .show_y(false)
 11          .data_aspect(1.0)
 12          .view_aspect(1.0)
 13          .show(ui, |plot_ui| {
 14              circles.iter_mut().enumerate().for_each(|(count, circle)| {
 15                  let points = match count {
 16                      count if count % 2 == 0 => crate::circle_to_points(circle),
 17                      _ => {
 18                          circle.reverse();
 19                          crate::circle_to_points(circle)
 20                      },
 21                  };
 22                  plot_ui.line(egui_plot::Line::new(
 23                          gaussian_distribution(num_points, &points, &(count as f64))
 24                  ).color(line_colors[count]));
 25              });
 26      });
 27  }
 28  
 29  
 30  pub fn save(path: &PathBuf, message: &mut crate::Message, circle_length: &usize) -> anyhow::Result<()> { //TODO: Add line_colors 
 31      use plotters::prelude::*;
 32      let root  = BitMapBackend::new(path, (1000,1000)).into_drawing_area();
 33      root.fill(&WHITE)?;
 34  
 35      let mut circles = crate::encrypt_message(message, circle_length);
 36      let length = (circles.len() as f64) + 1.0;
 37  
 38      let mut chart = ChartBuilder::on(&root)
 39          .margin(5)
 40          .build_cartesian_2d(-length..length, -length..length)?;
 41  
 42      circles.iter_mut().enumerate().for_each(|(count, circle)| {
 43          let peak_values = match count {
 44              count if count % 2 == 0 => crate::circle_to_points(circle),
 45              _ => {
 46                  circle.reverse();
 47                  crate::circle_to_points(circle)
 48              },
 49          };
 50  
 51          let points: Vec<(f64, f64)> = gaussian_distribution(circle_length, &peak_values, &(count as f64)).points().iter()
 52              .map(|point| {
 53                  (point.x, point.y)
 54              }).collect();
 55  
 56          chart.draw_series(LineSeries::new(points, &RED)).unwrap();
 57      });
 58      root.present()?;
 59          Ok(())
 60  }
 61  
 62  fn gaussian_distribution(num_points: &usize, peak_values: &Vec<f64>, offset: &f64) -> egui_plot::PlotPoints {
 63      let mut radii: Vec<f64> = vec![1.0; *num_points];
 64      let num_peaks = peak_values.len();
 65      let peak_angles = generate_peak_angles(&num_peaks);
 66      let theta = generate_theta(&num_points);  
 67      
 68      for (peak_value, peak_angle) in peak_values.iter().zip(peak_angles.iter()) {
 69          let peak_width: f64 = 1.0 / *num_points as f64;
 70          for (j, theta_value) in theta.iter().enumerate() {
 71              let delta = (theta_value - peak_angle + PI).rem_euclid(2.0 * PI) - PI;
 72              radii[j] += peak_value * (-((delta.powf(2.0)) / (2.0 * peak_width.powf(2.0)))).exp();
 73          }
 74      } 
 75      radii = normalize(&mut radii, &offset);
 76  
 77      cartesian_to_polar(&radii, &theta)
 78  }
 79  
 80  fn normalize(radii: &mut Vec<f64>, offset: &f64) -> Vec<f64> {
 81      let min_r = radii.iter().cloned().fold(f64::INFINITY, f64::min);
 82      let max_r = radii.iter().cloned().fold(f64::NEG_INFINITY, f64::max);
 83   
 84      radii.iter_mut().map(|value| {     
 85          1.0 + offset + (*value - min_r) / (max_r - min_r)
 86      }).collect()
 87  }
 88  
 89  fn cartesian_to_polar(radii: &Vec<f64>, theta: &Vec<f64>) -> egui_plot::PlotPoints {
 90      theta.iter()
 91          .zip(radii.iter())
 92          .map(|(&theta, &radius)| {
 93              let x = radius * theta.cos();
 94              let y = radius * theta.sin();
 95              [x, y]
 96          }).collect()
 97  }
 98  
 99  fn generate_peak_angles(num_peaks: &usize) -> Vec<f64> {
100      (0..*num_peaks)
101          .map(|i| {
102              let angle = 2.0 * PI * (i as f64) / (*num_peaks as f64);
103              angle + (PI / 2.0)
104          }).collect()
105  }
106  
107  fn generate_theta(num_points: &usize) -> Vec<f64> {
108      (0..*num_points)
109          .map(|i| 2.0 * PI * (i as f64) / (*num_points as f64))
110          .collect()
111  }
112  
113