mirror of
https://github.com/storytold/gridcraft.git
synced 2026-10-28 12:46:48 -06:00
Refactor chart for strict Clippy checks
This commit is contained in:
@@ -1,9 +1,8 @@
|
||||
//! Chart layout: title, legend, axes and every chart kind, drawn into [`Prim`]s.
|
||||
|
||||
use std::collections::HashSet;
|
||||
use std::f32::consts::{PI, TAU};
|
||||
|
||||
use gridcraft_model::{Chart, ChartKind, LegendPos, LineDash, QuartileMethod, Theme, TrendlineKind};
|
||||
use gridcraft_model::{Chart, ChartKind, LineDash, QuartileMethod, Theme, TrendlineKind};
|
||||
|
||||
use crate::axis::{Fmt, Scale, clean, fixed_scale, nice_scale};
|
||||
use crate::resolve::{MAX_POINTS, MAX_SERIES, MAX_TRENDLINES, series_color};
|
||||
@@ -70,8 +69,7 @@ impl Ctx<'_> {
|
||||
}
|
||||
String::new()
|
||||
}
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
fn text(&mut self, x: f32, y: f32, s: &str, size: f32, color: Rgba, align: HAlign, valign: VAlign) {
|
||||
fn text(&mut self, x: f32, y: f32, s: &str, size: f32, color: Rgba, (align, valign): (HAlign, VAlign)) {
|
||||
if s.is_empty() {
|
||||
return;
|
||||
}
|
||||
@@ -135,7 +133,7 @@ pub fn render(chart: &Chart, data: &ChartData, w: f32, h: f32, m: &dyn Measure)
|
||||
if let Some(t) = chart_title(chart, &data) {
|
||||
let size = TITLE_SIZE.min(h * 0.12).max(4.0);
|
||||
let t = ctx.fit(&t, size, area.w);
|
||||
ctx.text(w / 2.0, area.y, &t, size, TEXT, HAlign::Center, VAlign::Top);
|
||||
ctx.text(w / 2.0, area.y, &t, size, TEXT, (HAlign::Center, VAlign::Top));
|
||||
let used = size * 1.3 + 4.0;
|
||||
area.y += used;
|
||||
area.h -= used;
|
||||
|
||||
@@ -2,7 +2,10 @@ use super::frame::{Bx, CatAxis, ValAxis, frame, on, value_range};
|
||||
use super::legend::{cat_label, plot_kind};
|
||||
use super::lines::{decimate, draw_markers, draw_runs};
|
||||
use super::trend::{draw_trend_labels, draw_trendlines, max_backward, max_forward, max_forward_slots, supports_trendline};
|
||||
use super::*;
|
||||
use super::{
|
||||
Chart, ChartData, ChartKind, Ctx, Fmt, HAlign, LABEL_SIZE, MAX_CATS, MAX_LABELS, MAX_LINE_PTS, MAX_MARKERS, Prim, Scale, SeriesData, TEXT,
|
||||
VAlign, WHITE, clean, fixed_scale, nice_scale,
|
||||
};
|
||||
|
||||
// ---------------------------------------------------------------- cartesian (column/bar/line/area/combo)
|
||||
|
||||
@@ -18,6 +21,9 @@ enum Fam {
|
||||
Area100,
|
||||
}
|
||||
|
||||
/// Stack accumulator: (axis, family) key with positive and negative running sums.
|
||||
type Stack = ((bool, Fam), Vec<f64>, Vec<f64>);
|
||||
|
||||
fn family(k: ChartKind) -> Fam {
|
||||
use ChartKind::*;
|
||||
match k {
|
||||
@@ -45,16 +51,7 @@ fn is_line(f: Fam) -> bool {
|
||||
matches!(f, Fam::Line | Fam::LineStack)
|
||||
}
|
||||
|
||||
pub(super) fn cartesian(ctx: &mut Ctx, chart: &Chart, data: &ChartData, area: Bx, horizontal: bool) {
|
||||
let ncat = data.categories.len().max(data.series.iter().map(|s| s.values.len()).max().unwrap_or(0)).min(MAX_CATS);
|
||||
if ncat == 0 {
|
||||
return;
|
||||
}
|
||||
let kinds: Vec<ChartKind> = data.series.iter().map(|s| if horizontal { chart.kind } else { plot_kind(chart, s) }).collect();
|
||||
let fams: Vec<Fam> = kinds.iter().map(|k| family(*k)).collect();
|
||||
let any_primary = data.series.iter().any(|s| !s.secondary);
|
||||
let sec: Vec<bool> = data.series.iter().map(|s| s.secondary && any_primary && !horizontal).collect();
|
||||
|
||||
fn series_spans(data: &ChartData, fams: &[Fam], sec: &[bool], ncat: usize) -> Vec<Vec<Option<(f64, f64)>>> {
|
||||
// Totals for 100% stacking, per axis and family (100% columns and 100% areas stack apart).
|
||||
let mut totals: Vec<((bool, Fam), Vec<f64>)> = Vec::new();
|
||||
for (i, s) in data.series.iter().enumerate() {
|
||||
@@ -81,7 +78,7 @@ pub(super) fn cartesian(ctx: &mut Ctx, chart: &Chart, data: &ChartData, area: Bx
|
||||
if t > 0.0 { v / t } else { 0.0 }
|
||||
};
|
||||
// Stack accumulators keyed by (axis, family): positive and negative running sums.
|
||||
let mut stacks: Vec<((bool, Fam), Vec<f64>, Vec<f64>)> = Vec::new();
|
||||
let mut stacks: Vec<Stack> = Vec::new();
|
||||
let mut spans: Vec<Vec<Option<(f64, f64)>>> = Vec::with_capacity(data.series.len());
|
||||
for (i, s) in data.series.iter().enumerate() {
|
||||
let fam = fams.get(i).copied().unwrap_or(Fam::Col);
|
||||
@@ -126,6 +123,10 @@ pub(super) fn cartesian(ctx: &mut Ctx, chart: &Chart, data: &ChartData, area: Bx
|
||||
spans.push(row);
|
||||
}
|
||||
|
||||
spans
|
||||
}
|
||||
|
||||
fn axis_scales(data: &ChartData, fams: &[Fam], sec: &[bool], spans: &[Vec<Option<(f64, f64)>>]) -> Vec<(Scale, Fmt)> {
|
||||
// Axis scales.
|
||||
let mut axes: Vec<(Scale, Fmt)> = Vec::new();
|
||||
for axis in [false, true] {
|
||||
@@ -151,6 +152,46 @@ pub(super) fn cartesian(ctx: &mut Ctx, chart: &Chart, data: &ChartData, area: Bx
|
||||
break;
|
||||
}
|
||||
}
|
||||
axes
|
||||
}
|
||||
|
||||
fn column_slots(fams: &[Fam], sec: &[bool]) -> (Vec<usize>, f32) {
|
||||
// Column slots: each clustered series gets one, each stack group shares one.
|
||||
let mut slot_keys: Vec<(bool, Fam, usize)> = Vec::new();
|
||||
let mut slot_of = vec![0usize; fams.len()];
|
||||
for (i, f) in fams.iter().enumerate() {
|
||||
if !is_col(*f) {
|
||||
continue;
|
||||
}
|
||||
let a = sec.get(i).copied().unwrap_or(false);
|
||||
let key = if *f == Fam::Col { (a, *f, i) } else { (a, *f, usize::MAX) };
|
||||
let k = match slot_keys.iter().position(|k| *k == key) {
|
||||
Some(p) => p,
|
||||
None => {
|
||||
slot_keys.push(key);
|
||||
slot_keys.len() - 1
|
||||
}
|
||||
};
|
||||
if let Some(s) = slot_of.get_mut(i) {
|
||||
*s = k;
|
||||
}
|
||||
}
|
||||
(slot_of, slot_keys.len().max(1) as f32)
|
||||
}
|
||||
|
||||
pub(super) fn cartesian(ctx: &mut Ctx, chart: &Chart, data: &ChartData, area: Bx, horizontal: bool) {
|
||||
let ncat = data.categories.len().max(data.series.iter().map(|s| s.values.len()).max().unwrap_or(0)).min(MAX_CATS);
|
||||
if ncat == 0 {
|
||||
return;
|
||||
}
|
||||
let kinds: Vec<ChartKind> = data.series.iter().map(|s| if horizontal { chart.kind } else { plot_kind(chart, s) }).collect();
|
||||
let fams: Vec<Fam> = kinds.iter().map(|k| family(*k)).collect();
|
||||
let any_primary = data.series.iter().any(|s| !s.secondary);
|
||||
let sec: Vec<bool> = data.series.iter().map(|s| s.secondary && any_primary && !horizontal).collect();
|
||||
|
||||
let spans = series_spans(data, &fams, &sec, ncat);
|
||||
|
||||
let axes = axis_scales(data, &fams, &sec, &spans);
|
||||
let Some((s1, f1)) = axes.first() else { return };
|
||||
let y1 = ValAxis { scale: *s1, fmt: f1 };
|
||||
let y2 = axes.get(1).map(|(s, f)| ValAxis { scale: *s, fmt: f });
|
||||
@@ -177,27 +218,7 @@ pub(super) fn cartesian(ctx: &mut Ctx, chart: &Chart, data: &ChartData, area: Bx
|
||||
let center = |j: usize| if horizontal { plot.bottom() - (j as f32 + 0.5) * cw } else { plot.x + (j as f32 + 0.5) * cw };
|
||||
let vpos = |v: f64, sc: &Scale| if horizontal { plot.x + sc.frac(v) * plot.w } else { plot.bottom() - sc.frac(v) * plot.h };
|
||||
|
||||
// Column slots: each clustered series gets one, each stack group shares one.
|
||||
let mut slot_keys: Vec<(bool, Fam, usize)> = Vec::new();
|
||||
let mut slot_of = vec![0usize; data.series.len()];
|
||||
for (i, f) in fams.iter().enumerate() {
|
||||
if !is_col(*f) {
|
||||
continue;
|
||||
}
|
||||
let a = sec.get(i).copied().unwrap_or(false);
|
||||
let key = if *f == Fam::Col { (a, *f, i) } else { (a, *f, usize::MAX) };
|
||||
let k = match slot_keys.iter().position(|k| *k == key) {
|
||||
Some(p) => p,
|
||||
None => {
|
||||
slot_keys.push(key);
|
||||
slot_keys.len() - 1
|
||||
}
|
||||
};
|
||||
if let Some(s) = slot_of.get_mut(i) {
|
||||
*s = k;
|
||||
}
|
||||
}
|
||||
let nslots = slot_keys.len().max(1) as f32;
|
||||
let (slot_of, nslots) = column_slots(&fams, &sec);
|
||||
let bw = cw / (nslots + 1.5);
|
||||
let labels_on = chart.data_labels && ncat <= MAX_LABELS;
|
||||
// Area labels wait until every area is filled, so a later (stacked) area can't paint over them.
|
||||
@@ -275,20 +296,20 @@ pub(super) fn cartesian(ctx: &mut Ctx, chart: &Chart, data: &ChartData, area: Bx
|
||||
if horizontal {
|
||||
let y = c + nslots * bw / 2.0 - (k + 0.5) * bw;
|
||||
if stacked {
|
||||
ctx.text(mid, y, &t, LABEL_SIZE, on(s.color), HAlign::Center, VAlign::Middle);
|
||||
ctx.text(mid, y, &t, LABEL_SIZE, on(s.color), (HAlign::Center, VAlign::Middle));
|
||||
} else if v >= 0.0 {
|
||||
ctx.text(pb.max(pt) + 3.0, y, &t, LABEL_SIZE, TEXT, HAlign::Left, VAlign::Middle);
|
||||
ctx.text(pb.max(pt) + 3.0, y, &t, LABEL_SIZE, TEXT, (HAlign::Left, VAlign::Middle));
|
||||
} else {
|
||||
ctx.text(lo - 3.0, y, &t, LABEL_SIZE, TEXT, HAlign::Right, VAlign::Middle);
|
||||
ctx.text(lo - 3.0, y, &t, LABEL_SIZE, TEXT, (HAlign::Right, VAlign::Middle));
|
||||
}
|
||||
} else {
|
||||
let x = c - nslots * bw / 2.0 + (k + 0.5) * bw;
|
||||
if stacked {
|
||||
ctx.text(x, mid, &t, LABEL_SIZE, on(s.color), HAlign::Center, VAlign::Middle);
|
||||
ctx.text(x, mid, &t, LABEL_SIZE, on(s.color), (HAlign::Center, VAlign::Middle));
|
||||
} else if v >= 0.0 {
|
||||
ctx.text(x, lo - 2.0, &t, LABEL_SIZE, TEXT, HAlign::Center, VAlign::Bottom);
|
||||
ctx.text(x, lo - 2.0, &t, LABEL_SIZE, TEXT, (HAlign::Center, VAlign::Bottom));
|
||||
} else {
|
||||
ctx.text(x, lo + len + 2.0, &t, LABEL_SIZE, TEXT, HAlign::Center, VAlign::Top);
|
||||
ctx.text(x, lo + len + 2.0, &t, LABEL_SIZE, TEXT, (HAlign::Center, VAlign::Top));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -304,7 +325,7 @@ pub(super) fn cartesian(ctx: &mut Ctx, chart: &Chart, data: &ChartData, area: Bx
|
||||
if labels_on {
|
||||
for (j, p) in pts.iter().enumerate() {
|
||||
if let (Some(p), Some(v)) = (p, s.values.get(j).copied().flatten()) {
|
||||
ctx.text(p[0], p[1] - 5.0, &fmt.format(v, None), LABEL_SIZE, TEXT, HAlign::Center, VAlign::Bottom);
|
||||
ctx.text(p[0], p[1] - 5.0, &fmt.format(v, None), LABEL_SIZE, TEXT, (HAlign::Center, VAlign::Bottom));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -312,7 +333,7 @@ pub(super) fn cartesian(ctx: &mut Ctx, chart: &Chart, data: &ChartData, area: Bx
|
||||
}
|
||||
}
|
||||
for (x, y, t) in area_labels.drain(..) {
|
||||
ctx.text(x, y, &t, LABEL_SIZE, TEXT, HAlign::Center, VAlign::Bottom);
|
||||
ctx.text(x, y, &t, LABEL_SIZE, TEXT, (HAlign::Center, VAlign::Bottom));
|
||||
}
|
||||
}
|
||||
// Trendlines on top: x is the category number 1..n.
|
||||
@@ -412,7 +433,7 @@ pub(super) fn scatter(ctx: &mut Ctx, chart: &Chart, data: &ChartData, area: Bx)
|
||||
if chart.data_labels && pts.len() <= MAX_LABELS {
|
||||
for (p, raw) in px.iter().zip(pts) {
|
||||
if let (Some(p), Some((_, y))) = (p, raw) {
|
||||
ctx.text(p[0], p[1] - 6.0, &fy.format(*y, None), LABEL_SIZE, TEXT, HAlign::Center, VAlign::Bottom);
|
||||
ctx.text(p[0], p[1] - 6.0, &fy.format(*y, None), LABEL_SIZE, TEXT, (HAlign::Center, VAlign::Bottom));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
use super::*;
|
||||
use super::{AXIS, AXIS_TITLE_SIZE, Chart, ChartData, Ctx, DARK_TEXT, Fmt, GRID, HAlign, LABEL_SIZE, PI, Prim, Rgba, Scale, TEXT, VAlign, WHITE};
|
||||
|
||||
#[derive(Clone, Copy, Debug)]
|
||||
pub(super) struct Bx {
|
||||
@@ -44,7 +44,7 @@ pub(super) fn axis_titles(ctx: &mut Ctx, chart: &Chart, mut area: Bx) -> Bx {
|
||||
let lh = AXIS_TITLE_SIZE * 1.4 + 2.0;
|
||||
if let Some(t) = chart.x_title.as_deref().map(str::trim).filter(|t| !t.is_empty()) {
|
||||
let t = ctx.fit(t, AXIS_TITLE_SIZE, area.w);
|
||||
ctx.text(area.cx(), area.bottom(), &t, AXIS_TITLE_SIZE, TEXT, HAlign::Center, VAlign::Bottom);
|
||||
ctx.text(area.cx(), area.bottom(), &t, AXIS_TITLE_SIZE, TEXT, (HAlign::Center, VAlign::Bottom));
|
||||
area.h -= lh;
|
||||
}
|
||||
if let Some(t) = chart.y_title.as_deref().map(str::trim).filter(|t| !t.is_empty()) {
|
||||
@@ -123,12 +123,12 @@ pub(super) fn frame(ctx: &mut Ctx, area: Bx, cat: CatAxis, y: &ValAxis, y2: Opti
|
||||
if grid {
|
||||
ctx.hline(plot.x, plot.right(), yy, GRID, 0.75);
|
||||
}
|
||||
ctx.text(plot.x - 4.0, yy, t, ls, TEXT, HAlign::Right, VAlign::Middle);
|
||||
ctx.text(plot.x - 4.0, yy, t, ls, TEXT, (HAlign::Right, VAlign::Middle));
|
||||
}
|
||||
if let (Some(a), Some(l)) = (y2, &y2labels) {
|
||||
for (v, t) in l {
|
||||
let yy = plot.bottom() - a.scale.frac(*v) * plot.h;
|
||||
ctx.text(plot.right() + 4.0, yy, t, ls, TEXT, HAlign::Left, VAlign::Middle);
|
||||
ctx.text(plot.right() + 4.0, yy, t, ls, TEXT, (HAlign::Left, VAlign::Middle));
|
||||
}
|
||||
}
|
||||
let zero = if y.scale.min <= 0.0 && y.scale.max >= 0.0 { 0.0 } else { y.scale.min };
|
||||
@@ -142,7 +142,7 @@ pub(super) fn frame(ctx: &mut Ctx, area: Bx, cat: CatAxis, y: &ValAxis, y2: Opti
|
||||
let k = (((lw + 6.0) / cw.max(1e-3)).ceil() as usize).clamp(1, n);
|
||||
for (j, t) in l.iter().enumerate().step_by(k) {
|
||||
let t = ctx.fit(t, ls, cw * k as f32 - 2.0);
|
||||
ctx.text(plot.x + (j as f32 + 0.5) * cw, plot.bottom() + 3.0, &t, ls, TEXT, HAlign::Center, VAlign::Top);
|
||||
ctx.text(plot.x + (j as f32 + 0.5) * cw, plot.bottom() + 3.0, &t, ls, TEXT, (HAlign::Center, VAlign::Top));
|
||||
}
|
||||
// Outer levels, one row each below the labels: a group's name centred under its
|
||||
// categories, and a separator at each group boundary down through the rows.
|
||||
@@ -152,7 +152,7 @@ pub(super) fn frame(ctx: &mut Ctx, area: Bx, cat: CatAxis, y: &ValAxis, y2: Opti
|
||||
let (x0, x1) = (plot.x + first as f32 * cw, plot.x + end as f32 * cw);
|
||||
if let Some(t) = level.get(first).filter(|t| !t.is_empty()) {
|
||||
let t = ctx.fit(t, ls, x1 - x0 - 2.0);
|
||||
ctx.text((x0 + x1) / 2.0, top, &t, ls, TEXT, HAlign::Center, VAlign::Top);
|
||||
ctx.text((x0 + x1) / 2.0, top, &t, ls, TEXT, (HAlign::Center, VAlign::Top));
|
||||
}
|
||||
if first > 0 {
|
||||
ctx.vline(x0, plot.bottom(), top + lh - 2.0, AXIS, 0.75);
|
||||
@@ -169,7 +169,7 @@ pub(super) fn frame(ctx: &mut Ctx, area: Bx, cat: CatAxis, y: &ValAxis, y2: Opti
|
||||
let k = (((lw + 6.0) / slot.max(1e-3)).ceil() as usize).max(1);
|
||||
for (v, t) in ticks.iter().zip(&labels).step_by(k) {
|
||||
let xx = plot.x + s.frac(*v) * plot.w;
|
||||
ctx.text(xx, plot.bottom() + 3.0, t, ls, TEXT, HAlign::Center, VAlign::Top);
|
||||
ctx.text(xx, plot.bottom() + 3.0, t, ls, TEXT, (HAlign::Center, VAlign::Top));
|
||||
}
|
||||
if s.min <= 0.0 && s.max >= 0.0 {
|
||||
let xx = plot.x + s.frac(0.0) * plot.w;
|
||||
@@ -202,7 +202,7 @@ pub(super) fn frame(ctx: &mut Ctx, area: Bx, cat: CatAxis, y: &ValAxis, y2: Opti
|
||||
ctx.vline(xx, plot.y, plot.bottom(), GRID, 0.75);
|
||||
}
|
||||
if i % k == 0 {
|
||||
ctx.text(xx, plot.bottom() + 3.0, t, ls, TEXT, HAlign::Center, VAlign::Top);
|
||||
ctx.text(xx, plot.bottom() + 3.0, t, ls, TEXT, (HAlign::Center, VAlign::Top));
|
||||
}
|
||||
}
|
||||
let zero = if y.scale.min <= 0.0 && y.scale.max >= 0.0 { 0.0 } else { y.scale.min };
|
||||
@@ -213,7 +213,7 @@ pub(super) fn frame(ctx: &mut Ctx, area: Bx, cat: CatAxis, y: &ValAxis, y2: Opti
|
||||
let k = ((lh / ch.max(1e-3)).ceil() as usize).clamp(1, n);
|
||||
for (j, t) in labels.iter().enumerate().step_by(k) {
|
||||
let t = ctx.fit(t, ls, lw - 6.0);
|
||||
ctx.text(plot.x - 4.0, plot.bottom() - (j as f32 + 0.5) * ch, &t, ls, TEXT, HAlign::Right, VAlign::Middle);
|
||||
ctx.text(plot.x - 4.0, plot.bottom() - (j as f32 + 0.5) * ch, &t, ls, TEXT, (HAlign::Right, VAlign::Middle));
|
||||
}
|
||||
// Outer levels: a group's name centred beside its categories, separators at the
|
||||
// boundaries running right to the axis.
|
||||
@@ -224,7 +224,7 @@ pub(super) fn frame(ctx: &mut Ctx, area: Bx, cat: CatAxis, y: &ValAxis, y2: Opti
|
||||
let (y0, y1) = (plot.bottom() - end as f32 * ch, plot.bottom() - first as f32 * ch);
|
||||
if let Some(t) = level.get(first).filter(|t| !t.is_empty()) {
|
||||
let t = ctx.fit(t, ls, w - 4.0);
|
||||
ctx.text((x0 + x1) / 2.0, (y0 + y1) / 2.0, &t, ls, TEXT, HAlign::Center, VAlign::Middle);
|
||||
ctx.text((x0 + x1) / 2.0, (y0 + y1) / 2.0, &t, ls, TEXT, (HAlign::Center, VAlign::Middle));
|
||||
}
|
||||
if first > 0 {
|
||||
ctx.hline(x0, plot.x, y1, AXIS, 0.75);
|
||||
|
||||
@@ -1,6 +1,9 @@
|
||||
use super::frame::{Bx, CatAxis, ValAxis, frame, on, value_range};
|
||||
use super::legend::{cat_label, point_color};
|
||||
use super::*;
|
||||
use super::{
|
||||
AXIS, Chart, ChartData, Ctx, DOWN, Fmt, GRID, HAlign, LABEL_SIZE, MAX_CATS, MAX_LABELS, MAX_MARKERS, Prim, QuartileMethod, STOCK, SeriesData,
|
||||
TAU, TEXT, UP, VAlign, WHITE, clean, nice_scale,
|
||||
};
|
||||
|
||||
// ---------------------------------------------------------------- histogram
|
||||
|
||||
@@ -47,7 +50,7 @@ pub(super) fn histogram(ctx: &mut Ctx, chart: &Chart, data: &ChartData, area: Bx
|
||||
let x = plot.x + b as f32 * cw;
|
||||
ctx.rect(x, top, cw, plot.bottom() - top, s.color, Some((WHITE, 0.75)));
|
||||
if chart.data_labels {
|
||||
ctx.text(x + cw / 2.0, top - 2.0, &c.to_string(), LABEL_SIZE, TEXT, HAlign::Center, VAlign::Bottom);
|
||||
ctx.text(x + cw / 2.0, top - 2.0, &c.to_string(), LABEL_SIZE, TEXT, (HAlign::Center, VAlign::Bottom));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -110,7 +113,7 @@ pub(super) fn waterfall(ctx: &mut Ctx, chart: &Chart, data: &ChartData, area: Bx
|
||||
}
|
||||
if chart.data_labels && ncat <= MAX_LABELS {
|
||||
let v = if *kind == 0 { *t } else { t - b };
|
||||
ctx.text(x + bw / 2.0, yb.min(yt) - 2.0, &f.format(v, None), LABEL_SIZE, TEXT, HAlign::Center, VAlign::Bottom);
|
||||
ctx.text(x + bw / 2.0, yb.min(yt) - 2.0, &f.format(v, None), LABEL_SIZE, TEXT, (HAlign::Center, VAlign::Bottom));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -150,10 +153,10 @@ pub(super) fn funnel(ctx: &mut Ctx, chart: &Chart, data: &ChartData, area: Bx) {
|
||||
if chart.data_labels {
|
||||
let t = f.format(s.values.get(j).copied().flatten().unwrap_or(0.0), None);
|
||||
let color = if ctx.tw(&t, LABEL_SIZE) + 4.0 <= bw { on(s.color) } else { TEXT };
|
||||
ctx.text(plot.cx(), yc, &t, LABEL_SIZE, color, HAlign::Center, VAlign::Middle);
|
||||
ctx.text(plot.cx(), yc, &t, LABEL_SIZE, color, (HAlign::Center, VAlign::Middle));
|
||||
}
|
||||
let l = ctx.fit(labels.get(j).map(String::as_str).unwrap_or(""), LABEL_SIZE, lw - 8.0);
|
||||
ctx.text(plot.x - 6.0, yc, &l, LABEL_SIZE, TEXT, HAlign::Right, VAlign::Middle);
|
||||
ctx.text(plot.x - 6.0, yc, &l, LABEL_SIZE, TEXT, (HAlign::Right, VAlign::Middle));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -194,7 +197,7 @@ pub(super) fn pie(ctx: &mut Ctx, chart: &Chart, data: &ChartData, area: Bx, inne
|
||||
a += sweep;
|
||||
}
|
||||
for (x, y, t, c) in labels {
|
||||
ctx.text(x, y, &t, LABEL_SIZE, c, HAlign::Center, VAlign::Middle);
|
||||
ctx.text(x, y, &t, LABEL_SIZE, c, (HAlign::Center, VAlign::Middle));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -281,11 +284,11 @@ pub(super) fn treemap(ctx: &mut Ctx, chart: &Chart, data: &ChartData, area: Bx)
|
||||
ctx.rect(b.x, b.y, b.w, b.h, fill, Some((WHITE, 1.5)));
|
||||
if b.w > 24.0 && b.h > LABEL_SIZE * 1.6 {
|
||||
let name = ctx.fit(&cat_label(data, j), LABEL_SIZE, b.w - 8.0);
|
||||
ctx.text(b.x + 4.0, b.y + 4.0, &name, LABEL_SIZE, on(fill), HAlign::Left, VAlign::Top);
|
||||
ctx.text(b.x + 4.0, b.y + 4.0, &name, LABEL_SIZE, on(fill), (HAlign::Left, VAlign::Top));
|
||||
if chart.data_labels && b.h > LABEL_SIZE * 3.2 {
|
||||
let v = items.iter().find(|x| x.0 == j).map(|x| x.1).unwrap_or(0.0);
|
||||
let t = ctx.fit(&f.format(v, None), LABEL_SIZE, b.w - 8.0);
|
||||
ctx.text(b.x + 4.0, b.y + 4.0 + LABEL_SIZE * 1.4, &t, LABEL_SIZE, on(fill), HAlign::Left, VAlign::Top);
|
||||
ctx.text(b.x + 4.0, b.y + 4.0 + LABEL_SIZE * 1.4, &t, LABEL_SIZE, on(fill), (HAlign::Left, VAlign::Top));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -325,7 +328,7 @@ pub(super) fn radar(ctx: &mut Ctx, chart: &Chart, data: &ChartData, area: Bx) {
|
||||
ctx.line(ring, GRID, 0.75);
|
||||
}
|
||||
if ti % every == 0 {
|
||||
ctx.text(cx - 3.0, cy - rr, &f.format(t, Some(sc.step)), LABEL_SIZE * 0.9, TEXT, HAlign::Right, VAlign::Middle);
|
||||
ctx.text(cx - 3.0, cy - rr, &f.format(t, Some(sc.step)), LABEL_SIZE * 0.9, TEXT, (HAlign::Right, VAlign::Middle));
|
||||
}
|
||||
}
|
||||
let step = ncat.div_ceil(72).max(1);
|
||||
@@ -349,7 +352,7 @@ pub(super) fn radar(ctx: &mut Ctx, chart: &Chart, data: &ChartData, area: Bx) {
|
||||
VAlign::Middle
|
||||
};
|
||||
let t = ctx.fit(labels.get(j).map(String::as_str).unwrap_or(""), LABEL_SIZE, lw.max(20.0));
|
||||
ctx.text(p[0], p[1], &t, LABEL_SIZE, TEXT, align, valign);
|
||||
ctx.text(p[0], p[1], &t, LABEL_SIZE, TEXT, (align, valign));
|
||||
}
|
||||
for s in &data.series {
|
||||
let mut pts: Vec<[f32; 2]> = (0..ncat).map(|j| pt(ang(j), sc.frac(s.values.get(j).copied().flatten().unwrap_or(sc.min)) * r)).collect();
|
||||
|
||||
@@ -1,6 +1,10 @@
|
||||
use super::frame::Bx;
|
||||
use super::trend::supports_trendline;
|
||||
use super::*;
|
||||
use super::{
|
||||
Chart, ChartData, ChartKind, Ctx, DOWN, HAlign, LABEL_SIZE, MAX_CATS, MAX_SERIES, MAX_TRENDLINES, Prim, Rgba, SeriesData, TEXT, Theme, UP,
|
||||
VAlign, auto_name, series_color,
|
||||
};
|
||||
use gridcraft_model::LegendPos;
|
||||
|
||||
/// How a legend entry's swatch is drawn.
|
||||
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
|
||||
@@ -132,7 +136,7 @@ pub(super) fn draw_legend(ctx: &mut Ctx, entries: &[(String, Rgba, Swatch)], pos
|
||||
draw_swatch(ctx, e.2, x, yc, s, e.1);
|
||||
let sx = swatch_w(e.2, s) + gap;
|
||||
let t = ctx.fit(&e.0, size, (iw - sx).max(0.0) + 0.5);
|
||||
ctx.text(x + sx, yc, &t, size, TEXT, HAlign::Left, VAlign::Middle);
|
||||
ctx.text(x + sx, yc, &t, size, TEXT, (HAlign::Left, VAlign::Middle));
|
||||
}
|
||||
x += iw + spacing;
|
||||
}
|
||||
@@ -152,7 +156,7 @@ pub(super) fn draw_legend(ctx: &mut Ctx, entries: &[(String, Rgba, Swatch)], pos
|
||||
draw_swatch(ctx, e.2, x0, yc, s, e.1);
|
||||
let sx = swatch_w(e.2, s) + gap;
|
||||
let t = ctx.fit(&e.0, size, (colw - sx).max(0.0) + 0.5);
|
||||
ctx.text(x0 + sx, yc, &t, size, TEXT, HAlign::Left, VAlign::Middle);
|
||||
ctx.text(x0 + sx, yc, &t, size, TEXT, (HAlign::Left, VAlign::Middle));
|
||||
}
|
||||
let used = colw + 8.0;
|
||||
if pos == LegendPos::Right { Bx { w: area.w - used, ..area } } else { Bx { x: area.x + used, w: area.w - used, ..area } }
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
use super::frame::Bx;
|
||||
use super::*;
|
||||
use super::{Ctx, MAX_LINE_PTS, MAX_MARKERS, Prim, Rgba, WHITE};
|
||||
use std::collections::HashSet;
|
||||
|
||||
// ---------------------------------------------------------------- helpers
|
||||
|
||||
|
||||
@@ -1,6 +1,9 @@
|
||||
use super::frame::{Bx, value_range};
|
||||
use super::lines::decimate;
|
||||
use super::*;
|
||||
use super::{
|
||||
ChartKind, Ctx, HAlign, LABEL_SIZE, LineDash, MAX_CATS, MAX_LINE_PTS, MAX_TRENDLINES, Prim, SeriesData, TEXT, TrendlineKind, VAlign,
|
||||
fit_trendline, moving_average,
|
||||
};
|
||||
|
||||
// ---------------------------------------------------------------- trendlines
|
||||
|
||||
@@ -36,10 +39,11 @@ pub(super) fn max_backward(s: &SeriesData) -> f64 {
|
||||
s.trendlines.iter().filter(|t| !matches!(t.spec.kind, TrendlineKind::MovingAverage(_))).map(|t| forecast(t.spec.backward)).fold(0.0, f64::max)
|
||||
}
|
||||
|
||||
type ClippedSegment = ((f64, f64), (f64, f64), bool, bool);
|
||||
|
||||
/// Liang–Barsky: the part of segment `a`–`b` inside the unit square, with whether the start and
|
||||
/// end were cut.
|
||||
#[allow(clippy::type_complexity)]
|
||||
fn clip_segment(a: (f64, f64), b: (f64, f64)) -> Option<((f64, f64), (f64, f64), bool, bool)> {
|
||||
fn clip_segment(a: (f64, f64), b: (f64, f64)) -> Option<ClippedSegment> {
|
||||
let (dx, dy) = (b.0 - a.0, b.1 - a.1);
|
||||
let (mut t0, mut t1) = (0.0f64, 1.0f64);
|
||||
for (p, q) in [(-dx, a.0), (dx, 1.0 - a.0), (-dy, a.1), (dy, 1.0 - a.1)] {
|
||||
@@ -161,6 +165,6 @@ pub(super) fn draw_trend_labels(ctx: &mut Ctx, plot: Bx, labels: &[String]) {
|
||||
break;
|
||||
}
|
||||
let t = ctx.fit(t, LABEL_SIZE, plot.w - 8.0);
|
||||
ctx.text(plot.right() - 4.0, y, &t, LABEL_SIZE, TEXT, HAlign::Right, VAlign::Top);
|
||||
ctx.text(plot.right() - 4.0, y, &t, LABEL_SIZE, TEXT, (HAlign::Right, VAlign::Top));
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user