#![allow(unused_assignments)]
#![allow(unused_variables)]
#![allow(dead_code)]
#![allow(clippy::explicit_counter_loop, clippy::float_cmp)]
use animate::prelude::*;
use animate::{
easing::{get_easing, Easing},
interpolate::lerp,
BaseLine, CanvasContext, Point, TextStyle, TextWeight,
};
use dataflow::*;
use std::{cell::RefCell, fmt};
use crate::*;
const START_ANGLE: f64 = -std::f64::consts::FRAC_PI_2;
#[derive(Default, Clone)]
pub struct PieEntity<D> {
color: Fill,
highlight_color: Fill,
formatted_value: String,
index: usize,
old_value: Option<D>,
value: Option<D>,
old_start_angle: f64,
old_end_angle: f64,
start_angle: f64,
end_angle: f64,
center: Point<f64>,
inner_radius: f64,
outer_radius: f64,
name: String,
}
impl<D> PieEntity<D> {
pub fn is_empty(&self) -> bool {
self.start_angle == self.end_angle
}
fn contains_point(&self, p: Point<f64>) -> bool {
let mag = p.distance_to(Point::default());
if mag > self.outer_radius || mag < self.inner_radius {
return false;
}
let mut angle = f64::atan2(p.y, p.x);
if self.start_angle > self.end_angle {
angle -= TAU;
}
if self.start_angle <= self.end_angle {
utils::is_in_range(angle, self.start_angle, self.end_angle)
} else {
utils::is_in_range(angle, self.end_angle, self.start_angle)
}
}
}
impl<D> Entity for PieEntity<D> {
fn free(&mut self) {
}
fn save(&self) {
}
}
impl<C, D> Drawable<C> for PieEntity<D>
where
C: CanvasContext,
{
fn draw(&self, ctx: &C, percent: f64, highlight: bool) {
let mut a1 = lerp(self.old_start_angle, self.start_angle, percent);
let mut a2 = lerp(self.old_end_angle, self.end_angle, percent);
if a1 > a2 {
std::mem::swap(&mut a1, &mut a2);
}
let center = &self.center;
if highlight {
let highlight_outer_radius = HIGHLIGHT_OUTER_RADIUS_FACTOR * self.outer_radius;
match &self.highlight_color {
Fill::Solid(color) => {
ctx.set_fill_color(*color);
ctx.begin_path();
ctx.arc(center.x, center.y, highlight_outer_radius, a1, a2, false);
ctx.arc(center.x, center.y, self.inner_radius, a2, a1, true);
ctx.fill();
}
Fill::Gradient(gradient) => {
ctx.set_fill_gradient(gradient);
ctx.begin_path();
ctx.arc(center.x, center.y, highlight_outer_radius, a1, a2, false);
ctx.arc(center.x, center.y, self.inner_radius, a2, a1, true);
ctx.fill();
}
Fill::None => {}
}
}
match &self.color {
Fill::Solid(color) => {
ctx.set_fill_color(*color);
ctx.begin_path();
ctx.arc(center.x, center.y, self.outer_radius, a1, a2, false);
ctx.arc(center.x, center.y, self.inner_radius, a2, a1, true);
ctx.fill();
ctx.stroke();
}
Fill::Gradient(gradient) => {
ctx.set_fill_gradient(gradient);
ctx.begin_path();
ctx.arc(center.x, center.y, self.outer_radius, a1, a2, false);
ctx.arc(center.x, center.y, self.inner_radius, a2, a1, true);
ctx.fill();
ctx.stroke();
}
Fill::None => {}
}
if !self.formatted_value.is_empty() && a2 - a1 > PI / 36.0 {
let r = 0.25 * self.inner_radius + 0.65 * self.outer_radius;
let a = 0.5 * (a1 + a2);
let p = utils::polar2cartesian(center, r, a);
ctx.set_fill_color(color::WHITE);
let w = ctx.measure_text(self.formatted_value.as_str()).width;
ctx.fill_text(self.formatted_value.as_str(), p.x - w / 2., p.y + 4.);
}
}
}
#[derive(Default, Clone)]
struct PieChartProperties {
center: Point<f64>,
outer_radius: f64,
inner_radius: f64,
start_angle: f64,
direction: i64,
}
pub struct PieChart<C, M, D>
where
C: CanvasContext,
M: fmt::Display,
D: fmt::Display + Copy,
{
props: RefCell<PieChartProperties>,
base: BaseChart<C, PieEntity<D>, M, D, PieChartOptions>,
}
impl<C, M, D> PieChart<C, M, D>
where
C: CanvasContext,
M: fmt::Display,
D: fmt::Display + Copy + Into<f64> + Ord + Default,
{
pub fn new(options: PieChartOptions) -> Self {
Self {
props: Default::default(),
base: BaseChart::new(options),
}
}
fn data_rows_changed(&self, record: DataCollectionChangeRecord) {
self.base
.update_channel_visible(record.index, record.removed_count, record.added_count);
self.base.data_rows_changed(record);
self.base.update_legend_content();
}
fn get_entity_group_index(&self, x: f64, y: f64) -> i64 {
let p = Point::new(x, y);
unimplemented!()
}
pub fn get_legend_labels(&self) -> Vec<String> {
unimplemented!()
}
fn channel_visibility_changed(&self, index: usize) {
self.update_channel(0);
}
fn update_tooltip_content(&self) {
unimplemented!()
}
}
impl<C, M, D> Chart<C, M, D, PieEntity<D>> for PieChart<C, M, D>
where
C: CanvasContext,
M: fmt::Display,
D: fmt::Display + Copy + Into<f64> + Ord + Default,
{
fn calculate_drawing_sizes(&self, ctx: &C) {
self.base.calculate_drawing_sizes(ctx);
let mut props = self.props.borrow_mut();
props.center = {
let rect = &self.base.props.borrow().area;
let half_w = rect.size.width as i64 >> 1;
let half_h = rect.size.height as i64 >> 1;
props.outer_radius = (half_w.min(half_h) as f64) / HIGHLIGHT_OUTER_RADIUS_FACTOR;
let x = rect.origin.x + half_w as f64;
let y = rect.origin.y + half_h as f64;
Point::new(x, y)
};
let mut pie_hole = self.base.options.pie_hole;
if pie_hole > 1.0 {
pie_hole = 0.0;
}
if pie_hole < 0.0 {
pie_hole = 0.0;
}
props.inner_radius = pie_hole * props.outer_radius;
let opt = &self.base.options.channel;
let mut baseprops = self.base.props.borrow_mut();
baseprops.entity_value_formatter = if let Some(formatter) = opt.labels.formatter {
Some(formatter)
} else {
Some(default_value_formatter)
};
props.direction = if opt.counterclockwise {
COUNTERCLOCKWISE
} else {
CLOCKWISE
};
props.start_angle = utils::deg2rad(opt.start_angle);
}
fn set_stream(&mut self, stream: DataStream<M, D>) {
self.base.data = stream;
self.create_channels(0, self.base.data.meta.len());
}
fn draw(&self, ctx: &C) {
self.base.dispose();
self.calculate_drawing_sizes(ctx);
self.update_channel(0);
self.draw_frame(ctx, None);
}
fn resize(&self, w: f64, h: f64) {
self.base.resize(w, h);
}
fn draw_axes_and_grid(&self, ctx: &C) {
self.base.draw_axes_and_grid(ctx);
}
fn draw_frame(&self, ctx: &C, time: Option<i64>) {
self.base.draw_frame(ctx, time);
self.draw_axes_and_grid(ctx);
let mut percent = self.base.calculate_percent(time);
if percent >= 1.0 {
percent = 1.0;
let mut channels = self.base.channels.borrow_mut();
for channel in channels.iter_mut() {
if channel.state == Visibility::Showing {
channel.state = Visibility::Shown;
} else if channel.state == Visibility::Hiding {
channel.state = Visibility::Hidden;
}
}
}
let props = self.base.props.borrow();
let ease = match props.easing {
Some(val) => val,
None => get_easing(Easing::Linear),
};
self.draw_channels(ctx, ease(percent));
self.base.draw_title(ctx);
if percent < 1.0 {
} else if time.is_some() {
self.base.animation_end();
}
}
fn draw_channels(&self, ctx: &C, percent: f64) -> bool {
ctx.set_line_width(2.);
ctx.set_stroke_color(color::WHITE);
ctx.set_text_baseline(BaseLine::Middle);
let channels = self.base.channels.borrow();
let channel = channels.first().unwrap();
let labels = &self.base.options.channel.labels.style;
let fontfamily = match &labels.fontfamily {
Some(val) => val.as_str(),
None => DEFAULT_FONT_FAMILY,
};
ctx.set_font(
fontfamily,
labels.fontstyle.unwrap_or(TextStyle::Normal),
TextWeight::Normal,
labels.fontsize.unwrap_or(12.),
);
let baseprops = self.base.props.borrow();
let mut focused_channel_index = baseprops.focused_channel_index;
focused_channel_index = -1;
let mut focused_entity_index = baseprops.focused_entity_index;
focused_entity_index = -1;
for entity in channel.entities.iter() {
if entity.is_empty() && percent == 1.0 {
continue;
}
let highlight = entity.index as i64 == focused_channel_index
|| entity.index as i64 == focused_entity_index;
entity.draw(ctx, percent, highlight);
}
false
}
fn update_channel(&self, _: usize) {
let props = self.props.borrow();
let mut channels = self.base.channels.borrow_mut();
for channel in channels.iter_mut() {
if channel.state == Visibility::Showing || channel.state == Visibility::Shown {
let mut sum: f64 = 0.0;
for entity in channel.entities.iter() {
if let Some(value) = entity.value {
sum += value.into();
}
}
let mut start_angle = START_ANGLE;
let mut idx = 0;
for entity in channel.entities.iter_mut() {
match entity.value {
Some(value) => {
let color = self.base.get_fill(idx);
entity.index = idx;
entity.color = color.clone();
entity.highlight_color = self.base.get_highlight_color(&color);
entity.center = props.center;
entity.inner_radius = props.inner_radius;
entity.outer_radius = props.outer_radius;
entity.start_angle = start_angle;
entity.end_angle =
start_angle - props.direction as f64 * value.into() * TAU / sum;
start_angle = entity.end_angle;
}
None => {
println!("hole in channel data");
}
}
idx += 1;
}
}
}
}
fn create_entity(
&self,
channel_index: usize,
entity_index: usize,
value: Option<D>,
color: Fill,
highlight_color: Fill,
) -> PieEntity<D> {
let color = self.base.get_fill(entity_index);
let highlight_color = self.base.change_fill_alpha(&color, 0.5);
let stream = &self.base.data;
let frame = stream.frames.get(entity_index).unwrap();
let name = format!("{}", frame.metric);
let props = self.props.borrow();
let baseprops = self.base.props.borrow();
let start_angle = START_ANGLE;
let formatted_value = match value {
Some(value) => match baseprops.entity_value_formatter {
Some(formatter) => formatter(value.into()),
None => default_value_formatter(value.into()),
},
None => "".into(),
};
PieEntity {
index: entity_index,
old_value: None,
value,
formatted_value,
name,
color,
highlight_color,
old_start_angle: start_angle,
old_end_angle: start_angle,
center: props.center,
inner_radius: props.inner_radius,
outer_radius: props.outer_radius,
start_angle,
end_angle: start_angle,
}
}
fn create_channels(&self, start: usize, end: usize) {
let mut start = start;
let mut result = Vec::new();
let count = self.base.data.frames.len();
let meta = &self.base.data.meta;
while start < end {
let channel = meta.get(start).unwrap();
let name = channel.name.as_str();
let color = self.base.get_fill(start);
let highlight = self.base.get_highlight_color(&color);
let entities = self.create_entities(start, 0, count, color.clone(), highlight.clone());
result.push(ChartChannel::new(name, color, highlight, entities));
start += 1;
}
let mut channels = self.base.channels.borrow_mut();
*channels = result;
}
fn create_entities(
&self,
channel_index: usize,
start: usize,
end: usize,
color: Fill,
highlight: Fill,
) -> Vec<PieEntity<D>> {
let mut start = start;
let mut result = Vec::new();
while start < end {
let frame = self.base.data.frames.get(start).unwrap();
let value = frame.data.get(channel_index as u64);
let entity = match frame.data.get(channel_index as u64) {
Some(value) => {
let value = *value;
self.create_entity(
channel_index,
start,
Some(value),
color.clone(),
highlight.clone(),
)
}
None => {
self.create_entity(channel_index, start, None, color.clone(), highlight.clone())
}
};
result.push(entity);
start += 1;
}
result
}
fn get_tooltip_position(&self, tooltip_width: f64, tooltip_height: f64) -> Point<f64> {
let channels = self.base.channels.borrow();
let channel = channels.first().unwrap();
let focused_entity_index = self.base.props.borrow().focused_entity_index as usize;
let props = self.props.borrow();
let pie = channel.entities.get(focused_entity_index).unwrap();
let angle = 0.5 * (pie.start_angle + pie.end_angle);
let radius = 0.5 * (props.inner_radius + props.outer_radius);
let point = utils::polar2cartesian(&props.center, radius, angle);
let x = point.x - 0.5 * tooltip_width;
let y = point.y - tooltip_height;
Point::new(x, y)
}
}