chart(pie): Support dynamic inner and outer radius (#1444)

## ⚠️Breaking Change
Add `inner_radius` and `outer_radius` options for `Arc` paint function.

```diff
- arc.paint(a, "#9d9d9d", bounds, window)
+ arc.paint(a, "#9d9d9d", Some(20.), Some(30.), bounds, window)
```
## Demo
<img width="410" height="442" alt="SCR-20251028-kohu"
src="https://github.com/user-attachments/assets/8b849053-22e6-4d6f-933f-b9fb2a47f5e3"
/>
This commit is contained in:
Floyd Wang 2025-10-28 13:53:28 +08:00 committed by GitHub
parent 904c9f3fa7
commit 19a54d6065
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
4 changed files with 69 additions and 14 deletions

View file

@ -1,13 +1,14 @@
use gpui::{ use gpui::{
div, linear_color_stop, linear_gradient, prelude::FluentBuilder, px, App, AppContext, Context, App, AppContext, Context, Entity, FocusHandle, Focusable, Hsla, IntoElement, ParentElement,
Entity, FocusHandle, Focusable, Hsla, IntoElement, ParentElement, Render, SharedString, Styled, Render, SharedString, Styled, Window, div, linear_color_stop, linear_gradient,
Window, prelude::FluentBuilder, px,
}; };
use gpui_component::{ use gpui_component::{
ActiveTheme, StyledExt,
chart::{AreaChart, BarChart, LineChart, PieChart}, chart::{AreaChart, BarChart, LineChart, PieChart},
divider::Divider, divider::Divider,
dock::PanelControl, dock::PanelControl,
h_flex, v_flex, ActiveTheme, StyledExt, h_flex, v_flex,
}; };
use serde::Deserialize; use serde::Deserialize;
@ -174,8 +175,8 @@ impl Render for ChartStory {
"Pie Chart - Donut", "Pie Chart - Donut",
PieChart::new(self.monthly_devices.clone()) PieChart::new(self.monthly_devices.clone())
.value(|d| d.desktop as f32) .value(|d| d.desktop as f32)
.outer_radius(100.)
.inner_radius(60.) .inner_radius(60.)
.outer_radius_fn(|d| 100. - d.index as f32 * 4.)
.color(move |d| d.color(color)), .color(move |d| d.color(color)),
true, true,
cx, cx,
@ -184,8 +185,8 @@ impl Render for ChartStory {
"Pie Chart - Pad Angle", "Pie Chart - Pad Angle",
PieChart::new(self.monthly_devices.clone()) PieChart::new(self.monthly_devices.clone())
.value(|d| d.desktop as f32) .value(|d| d.desktop as f32)
.outer_radius(100.)
.inner_radius(60.) .inner_radius(60.)
.outer_radius(100.)
.pad_angle(4. / 100.) .pad_angle(4. / 100.)
.color(move |d| d.color(color)), .color(move |d| d.color(color)),
true, true,
@ -227,7 +228,7 @@ impl Render for ChartStory {
.child(Divider::horizontal()) .child(Divider::horizontal())
.child( .child(
h_flex() h_flex()
.gap_x_8() .gap_x_4()
.h(px(400.)) .h(px(400.))
.child(chart_container( .child(chart_container(
"Line Chart", "Line Chart",
@ -268,7 +269,7 @@ impl Render for ChartStory {
.child(Divider::horizontal()) .child(Divider::horizontal())
.child( .child(
h_flex() h_flex()
.gap_x_8() .gap_x_4()
.h(px(400.)) .h(px(400.))
.child(chart_container( .child(chart_container(
"Area Chart", "Area Chart",

View file

@ -6,7 +6,7 @@ use num_traits::Zero;
use crate::{ use crate::{
plot::{ plot::{
shape::{Arc, Pie}, shape::{Arc, ArcData, Pie},
Plot, Plot,
}, },
ActiveTheme, PixelsExt, ActiveTheme, PixelsExt,
@ -16,7 +16,9 @@ use crate::{
pub struct PieChart<T: 'static> { pub struct PieChart<T: 'static> {
data: Vec<T>, data: Vec<T>,
inner_radius: f32, inner_radius: f32,
inner_radius_fn: Option<Rc<dyn Fn(&ArcData<T>) -> f32 + 'static>>,
outer_radius: f32, outer_radius: f32,
outer_radius_fn: Option<Rc<dyn Fn(&ArcData<T>) -> f32 + 'static>>,
pad_angle: f32, pad_angle: f32,
value: Option<Rc<dyn Fn(&T) -> f32>>, value: Option<Rc<dyn Fn(&T) -> f32>>,
color: Option<Rc<dyn Fn(&T) -> Hsla>>, color: Option<Rc<dyn Fn(&T) -> Hsla>>,
@ -30,23 +32,62 @@ impl<T> PieChart<T> {
Self { Self {
data: data.into_iter().collect(), data: data.into_iter().collect(),
inner_radius: 0., inner_radius: 0.,
inner_radius_fn: None,
outer_radius: 0., outer_radius: 0.,
outer_radius_fn: None,
pad_angle: 0., pad_angle: 0.,
value: None, value: None,
color: None, color: None,
} }
} }
/// Set the inner radius of the pie chart.
pub fn inner_radius(mut self, inner_radius: f32) -> Self { pub fn inner_radius(mut self, inner_radius: f32) -> Self {
self.inner_radius = inner_radius; self.inner_radius = inner_radius;
self self
} }
/// Set the inner radius of the pie chart based on the arc data.
pub fn inner_radius_fn(
mut self,
inner_radius_fn: impl Fn(&ArcData<T>) -> f32 + 'static,
) -> Self {
self.inner_radius_fn = Some(Rc::new(inner_radius_fn));
self
}
fn get_inner_radius(&self, arc: &ArcData<T>) -> f32 {
if let Some(inner_radius_fn) = self.inner_radius_fn.as_ref() {
inner_radius_fn(arc)
} else {
self.inner_radius
}
}
/// Set the outer radius of the pie chart.
pub fn outer_radius(mut self, outer_radius: f32) -> Self { pub fn outer_radius(mut self, outer_radius: f32) -> Self {
self.outer_radius = outer_radius; self.outer_radius = outer_radius;
self self
} }
/// Set the outer radius of the pie chart based on the arc data.
pub fn outer_radius_fn(
mut self,
outer_radius_fn: impl Fn(&ArcData<T>) -> f32 + 'static,
) -> Self {
self.outer_radius_fn = Some(Rc::new(outer_radius_fn));
self
}
fn get_outer_radius(&self, arc: &ArcData<T>) -> f32 {
if let Some(outer_radius_fn) = self.outer_radius_fn.as_ref() {
outer_radius_fn(arc)
} else {
self.outer_radius
}
}
/// Set the pad angle of the pie chart.
pub fn pad_angle(mut self, pad_angle: f32) -> Self { pub fn pad_angle(mut self, pad_angle: f32) -> Self {
self.pad_angle = pad_angle; self.pad_angle = pad_angle;
self self
@ -57,6 +98,7 @@ impl<T> PieChart<T> {
self self
} }
/// Set the color of the pie chart.
pub fn color<H>(mut self, color: impl Fn(&T) -> H + 'static) -> Self pub fn color<H>(mut self, color: impl Fn(&T) -> H + 'static) -> Self
where where
H: Into<Hsla> + 'static, H: Into<Hsla> + 'static,
@ -87,6 +129,8 @@ impl<T> Plot for PieChart<T> {
let arcs = pie.arcs(&self.data); let arcs = pie.arcs(&self.data);
for a in &arcs { for a in &arcs {
let inner_radius = self.get_inner_radius(a);
let outer_radius = self.get_outer_radius(a);
arc.paint( arc.paint(
a, a,
if let Some(color_fn) = self.color.as_ref() { if let Some(color_fn) = self.color.as_ref() {
@ -94,6 +138,8 @@ impl<T> Plot for PieChart<T> {
} else { } else {
cx.theme().chart_2 cx.theme().chart_2
}, },
Some(inner_radius),
Some(outer_radius),
&bounds, &bounds,
window, window,
); );

View file

@ -4,7 +4,7 @@ mod bar;
mod line; mod line;
mod pie; mod pie;
pub use arc::Arc; pub use arc::{Arc, ArcData};
pub use area::Area; pub use area::Area;
pub use bar::Bar; pub use bar::Bar;
pub use line::Line; pub use line::Line;

View file

@ -69,7 +69,13 @@ impl Arc {
point(r * a.cos(), r * a.sin()) point(r * a.cos(), r * a.sin())
} }
fn path<T>(&self, arc: &ArcData<T>, bounds: &Bounds<Pixels>) -> Option<Path<Pixels>> { fn path<T>(
&self,
arc: &ArcData<T>,
inner_radius: Option<f32>,
outer_radius: Option<f32>,
bounds: &Bounds<Pixels>,
) -> Option<Path<Pixels>> {
let start_angle = arc.start_angle - HALF_PI; let start_angle = arc.start_angle - HALF_PI;
let end_angle = arc.end_angle - HALF_PI; let end_angle = arc.end_angle - HALF_PI;
let da = end_angle - start_angle; let da = end_angle - start_angle;
@ -80,8 +86,8 @@ impl Arc {
} else { } else {
arc.pad_angle arc.pad_angle
}; };
let r0 = self.inner_radius.max(0.); let r0 = inner_radius.unwrap_or(self.inner_radius).max(0.);
let r1 = self.outer_radius.max(0.); let r1 = outer_radius.unwrap_or(self.outer_radius).max(0.);
// Calculate the center point. // Calculate the center point.
let center_x = bounds.origin.x.as_f32() + bounds.size.width.as_f32() / 2.; let center_x = bounds.origin.x.as_f32() + bounds.size.width.as_f32() / 2.;
@ -173,10 +179,12 @@ impl Arc {
&self, &self,
arc: &ArcData<T>, arc: &ArcData<T>,
color: impl Into<Hsla>, color: impl Into<Hsla>,
inner_radius: Option<f32>,
outer_radius: Option<f32>,
bounds: &Bounds<Pixels>, bounds: &Bounds<Pixels>,
window: &mut Window, window: &mut Window,
) { ) {
let path = self.path(arc, bounds); let path = self.path(arc, inner_radius, outer_radius, bounds);
if let Some(path) = path { if let Some(path) = path {
window.paint_path(path, color.into()); window.paint_path(path, color.into());
} }