Created
May 10, 2023 17:00
-
-
Save rctlmk/d386fe0a9d6c36daa042192c970ed6e0 to your computer and use it in GitHub Desktop.
egui pie charts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
[package] | |
name = "egui-pie-chart" | |
version = "0.1.0" | |
edition = "2021" | |
[dependencies] | |
egui = "0.21" | |
egui_extras = "0.21" | |
eframe = "0.21" |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
use eframe::{egui, NativeOptions}; | |
use egui_extras::{Size, StripBuilder}; | |
use pie_chart::PieChart; | |
mod pie_chart; | |
fn main() -> Result<(), Box<dyn std::error::Error>> { | |
let options = NativeOptions { | |
initial_window_size: Some(egui::Vec2::new(600.0, 480.0)), | |
..NativeOptions::default() | |
}; | |
eframe::run_native("Pie charts", options, Box::new(|ctx| Box::new(PieCharts::new(ctx))))?; | |
Ok(()) | |
} | |
struct PieCharts { | |
pie_charts: Vec<PieChart>, | |
} | |
impl Default for PieCharts { | |
fn default() -> Self { | |
let data: Vec<_> = (0..8).map(|i| (0.125, format!("{}: 12.5%", i + 1))).collect(); | |
let pie_charts = vec![ | |
PieChart::new("First", &[(15.0, "25%"), (45.0, "75%")]), | |
PieChart::new("Second", &data), | |
PieChart::new( | |
"Third", | |
&[(600.0, "Big"), (200.0, "Small"), (600.0, "Also Big"), (400.0, "Medium")], | |
), | |
PieChart::new("Fourth", &[(14.0, "100%")]), | |
]; | |
Self { pie_charts } | |
} | |
} | |
impl PieCharts { | |
fn new(_cc: &eframe::CreationContext<'_>) -> Self { | |
Self::default() | |
} | |
fn show(&mut self, ui: &mut egui::Ui) { | |
StripBuilder::new(ui).sizes(Size::remainder(), 2).vertical(|mut strip| { | |
strip.strip(|builder| { | |
builder.sizes(Size::remainder(), 2).horizontal(|mut strip| { | |
strip.cell(|ui| { | |
self.pie_charts[0].show(ui); | |
}); | |
strip.cell(|ui| { | |
self.pie_charts[1].show(ui); | |
}); | |
}); | |
}); | |
strip.strip(|builder| { | |
builder.sizes(Size::remainder(), 2).horizontal(|mut strip| { | |
strip.cell(|ui| { | |
self.pie_charts[2].show(ui); | |
}); | |
strip.cell(|ui| { | |
self.pie_charts[3].show(ui); | |
}); | |
}); | |
}); | |
}); | |
} | |
} | |
impl eframe::App for PieCharts { | |
fn update(&mut self, ctx: &egui::Context, _frame: &mut eframe::Frame) { | |
let central_panel = egui::CentralPanel::default(); | |
central_panel.show(ctx, |ui| self.show(ui)); | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
use std::f64::consts::TAU; | |
use egui::plot::{Legend, Plot, PlotPoint, PlotPoints, Polygon, Text}; | |
use egui::{Align2, RichText}; | |
const FULL_CIRCLE_VERTICES: f64 = 240.0; | |
const RADIUS: f64 = 1.0; | |
pub struct PieChart { | |
name: String, | |
sectors: Vec<Sector>, | |
} | |
impl PieChart { | |
pub fn new<S: AsRef<str>, L: AsRef<str>>(name: S, data: &[(f64, L)]) -> Self { | |
let sum: f64 = data.iter().map(|(f, _)| f).sum(); | |
let slices: Vec<_> = data.iter().map(|(f, n)| (f / sum, n)).collect(); | |
let step = TAU / FULL_CIRCLE_VERTICES; | |
let mut offset = 0.0_f64; | |
let sectors = slices | |
.iter() | |
.map(|(p, n)| { | |
let vertices = (FULL_CIRCLE_VERTICES * p).round() as usize; | |
let start = TAU * offset; | |
let end = TAU * (offset + p); | |
let sector = Sector::new(n, start, end, vertices, step); | |
offset += p; | |
sector | |
}) | |
.collect(); | |
Self { | |
name: name.as_ref().to_string(), | |
sectors, | |
} | |
} | |
pub fn show(&mut self, ui: &mut egui::Ui) { | |
let sectors = self.sectors.clone(); | |
Plot::new(&self.name) | |
.label_formatter(|_: &str, _: &PlotPoint| String::default()) | |
.show_background(false) | |
.legend(Legend::default()) | |
.show_axes([false; 2]) | |
.clamp_grid(true) | |
.allow_boxed_zoom(false) | |
.allow_drag(false) | |
.allow_zoom(false) | |
.allow_scroll(false) | |
.data_aspect(1.0) | |
// .set_margin_fraction([0.7; 2].into()) // this won't prevent the plot from moving | |
// `include_*` will lock it into place | |
.include_x(-2.0) | |
.include_x(2.0) | |
.include_y(-2.0) | |
.include_y(2.0) | |
.show(ui, |plot_ui| { | |
for sector in sectors.into_iter() { | |
let highlight = plot_ui.pointer_coordinate().map(|p| sector.contains(&p)).unwrap_or_default(); | |
let Sector { name, points, .. } = sector; | |
plot_ui.polygon(Polygon::new(PlotPoints::new(points)).name(&name).highlight(highlight)); | |
if highlight { | |
let p = plot_ui.pointer_coordinate().unwrap(); | |
// TODO proper zoom | |
let text = RichText::new(&name).size(15.0).heading(); | |
plot_ui.text(Text::new(p, text).name(&name).anchor(Align2::LEFT_BOTTOM)); | |
} | |
} | |
}); | |
} | |
} | |
#[derive(Clone)] | |
struct Sector { | |
name: String, | |
start: f64, | |
end: f64, | |
points: Vec<[f64; 2]>, | |
} | |
impl Sector { | |
pub fn new<S: AsRef<str>>(name: S, start: f64, end: f64, vertices: usize, step: f64) -> Self { | |
let mut points = vec![]; | |
if end - TAU != start { | |
points.push([0.0, 0.0]); | |
} | |
points.push([RADIUS * start.sin(), RADIUS * start.cos()]); | |
for v in 1..vertices { | |
let t = start + step * v as f64; | |
points.push([RADIUS * t.sin(), RADIUS * t.cos()]); | |
} | |
points.push([RADIUS * end.sin(), RADIUS * end.cos()]); | |
Self { | |
name: name.as_ref().to_string(), | |
start, | |
end, | |
points, | |
} | |
} | |
pub fn contains(&self, &PlotPoint { x, y }: &PlotPoint) -> bool { | |
let r = y.hypot(x); | |
let mut theta = x.atan2(y); | |
if theta < 0.0 { | |
theta += TAU; | |
} | |
r < RADIUS && theta > self.start && theta < self.end | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment