use std::f64;
use failure::{Error, err_msg};
use cairo::{Context};
use palette::Rgba;
use ::{utils, coord, frame, text, mark};
#[derive(Clone, Debug)]
pub struct Axis {
local_start: coord::Coord,
local_end: coord::Coord,
global_start: coord::Coord,
global_end: coord::Coord,
direction: coord::Coord,
color: Rgba,
line_width: f64,
data_range: [f64; 2],
label: text::Text,
ca_num_marks: usize,
marks: Vec<mark::Mark>,
}
impl Axis {
pub fn new() -> Axis {
Axis {
local_start: coord::Coord::new(0.0, 0.0),
local_end: coord::Coord::new(0.0, 0.0),
global_start: coord::Coord::new(0.0, 0.0),
global_end: coord::Coord::new(0.0, 0.0),
direction: coord::Coord::new(0.0, 0.0),
color: Rgba::new(0.0, 0.0, 0.0, 1.0),
line_width: 0.005,
data_range: [0.0, 1.0],
label: text::Text::new(""),
ca_num_marks: 6,
marks: Vec::<mark::Mark>::new(),
}
}
pub fn from_coord(start: coord::Coord, end: coord::Coord) -> Axis {
Axis {
local_start: start.clone(),
local_end: end.clone(),
global_start: coord::Coord::new(0.0, 0.0),
global_end: coord::Coord::new(0.0, 0.0),
direction: start.unit_direction_to(&end),
color: Rgba::new(0.0, 0.0, 0.0, 1.0),
line_width: 0.005,
data_range: [0.0, 1.0],
label: text::Text::new(""),
ca_num_marks: 6,
marks: Vec::<mark::Mark>::new(),
}
}
pub fn set_color(&mut self, color: Rgba) {
self.color = color;
}
pub fn set_color_rgb(&mut self, red: f32, green: f32, blue: f32) {
let red = red.max(0.0);
let red = red.min(1.0);
let green = green.max(0.0);
let green = green.min(1.0);
let blue = blue.max(0.0);
let blue = blue.min(1.0);
self.color = Rgba::new(red, green, blue, 1.0);
}
pub fn set_color_rgba(&mut self, red: f32, green: f32, blue: f32, alpha: f32) {
let red = red.max(0.0);
let red = red.min(1.0);
let green = green.max(0.0);
let green = green.min(1.0);
let blue = blue.max(0.0);
let blue = blue.min(1.0);
let alpha = alpha.max(0.0);
let alpha = alpha.min(1.0);
self.color = Rgba::new(red, green, blue, alpha);
}
pub fn set_line_width(&mut self, val: f64) {
self.line_width = val;
}
pub fn set_label(&mut self, content: &str) {
self.label.set_content(content);
}
pub fn set_label_angle(&mut self, angle: f64) {
self.label.set_angle(angle);
}
pub fn set_num_ticks(&mut self, val: usize) {
self.ca_num_marks = val;
}
pub fn set_positive_tick_length(&mut self, val: f64) {
for mark in self.marks.iter_mut() {
mark.set_positive_tick_length(val);
}
}
pub fn set_negative_tick_length(&mut self, val: f64) {
for mark in self.marks.iter_mut() {
mark.set_negative_tick_length(val);
}
}
pub fn set_tick_font_size(&mut self, val: f64) {
for mark in self.marks.iter_mut() {
mark.set_font_size(val);
}
}
pub fn set_tick_label_offset(&mut self, hor: f64, ver: f64) {
for mark in self.marks.iter_mut() {
mark.set_label_offset(hor, ver);
}
}
pub fn set_data_range(&mut self, data_min: f64, data_max: f64) {
self.data_range = [data_min, data_max];
}
pub fn set_label_offset(&mut self, hor: f64, ver: f64) {
self.label.set_offset(hor, ver);
}
pub fn scale_label_offset(&mut self, factor: f64) {
self.label.scale_offset(factor);
}
pub fn scale_tick_label_offset(&mut self, factor: f64) {
for mark in self.marks.iter_mut() {
mark.scale_label_offset(factor);
}
}
pub fn data_min(&self) -> f64 {
self.data_range[0]
}
pub fn data_max(&self) -> f64 {
self.data_range[1]
}
pub fn mark_coords(&self) -> Vec<coord::Coord> {
let mut coords = Vec::<coord::Coord>::new();
for mark in self.marks.iter() {
coords.push(mark.global_coord());
}
coords
}
pub fn compute_marks(&mut self) -> Result<(), Error> {
let data_diff = self.data_range[1] - self.data_range[0];
let ca_dist = data_diff / self.ca_num_marks as f64;
let omagn = utils::order_of_magnitude(ca_dist);
let mut smallest_diff = f64::MAX;
let mut round_number = 0f64;
for &i in [2.0, 5.0, 10.0].iter() {
let nearest = utils::round_nearest(ca_dist, omagn, i);
let diff = (ca_dist - nearest).abs();
if diff < smallest_diff {
smallest_diff = diff;
round_number = i;
}
}
let actual_min_point = utils::round_down(self.data_range[0], omagn, round_number);
let ca_max_point = *self.data_range.last().ok_or(err_msg("No final element"))?;
let mark_distance = utils::round_nearest(ca_dist, omagn, round_number);
let mut data_locations = vec![actual_min_point];
let mut data_location_k = actual_min_point;
let mut marks = Vec::<mark::Mark>::new();
let mut add_next = true;
while add_next {
data_location_k += mark_distance;
data_locations.push(data_location_k);
if data_location_k > ca_max_point {
add_next = false;
}
}
let min_data = data_locations[0];
let max_data = *data_locations.last().ok_or(err_msg("No final element"))?;
for data_location in data_locations {
let mark_x = utils::map_range(data_location, min_data, max_data,
self.local_start.x(), self.local_end.x());
let mark_y = utils::map_range(data_location, min_data, max_data,
self.local_start.y(), self.local_end.y());
let mark_location = coord::Coord::new(mark_x, mark_y);
let mut mark_k = mark::Mark::new(mark_location);
mark_k.set_label_content(&utils::prettify(data_location));
marks.push(mark_k);
}
self.data_range = [min_data, max_data];
self.marks = marks;
Ok(())
}
fn scale_size(&mut self, factor: f64) {
self.line_width *= factor;
self.label.scale_size(factor);
}
pub fn fit(&mut self, canvas_frame: &frame::Frame) {
self.global_start = self.local_start.relative_to(&canvas_frame);
self.global_end = self.local_end.relative_to(&canvas_frame);
let unit_perp_direction = self.global_start.perp_direction(&self.global_end);
let scale_factor = canvas_frame.diag_len() / 2f64.sqrt();
self.scale_size(scale_factor);
for mark in self.marks.iter_mut() {
mark.set_tick_direction(&unit_perp_direction);
mark.fit(canvas_frame);
}
}
pub fn draw(&self, cr: &Context, fig_rel_height: f64, fig_rel_width: f64) {
for mark in self.marks.iter() {
mark.draw(cr, fig_rel_height, fig_rel_width);
}
cr.set_source_rgba(self.color.red as f64, self.color.green as f64,
self.color.blue as f64, self.color.alpha as f64);
cr.set_line_width(self.line_width * (self.direction.x().abs() * fig_rel_width +
self.direction.y().abs() * fig_rel_height));
cr.move_to(self.global_start.x(), self.global_start.y());
cr.line_to(self.global_end.x(), self.global_end.y());
cr.stroke();
let mid_point_x = (self.global_start.x() + self.global_end.x()) / 2.0;
let mid_point_y = (self.global_start.y() + self.global_end.y()) / 2.0;
cr.move_to(mid_point_x + self.label.hor_offset(), mid_point_y + self.label.ver_offset());
self.label.draw(cr, fig_rel_height, fig_rel_width);
}
}