1use alloc::collections::{BTreeMap, VecDeque};
67use alloc::format;
68use alloc::rc::Rc;
69use alloc::string::String;
70use alloc::vec::Vec;
71use core::sync::atomic::{AtomicU32, Ordering};
72
73use crate::cmd::{Draw, DrawRef, Gfx};
74use crate::consts::*;
75use crate::i18n::tr;
76use crate::keys::*;
77use crate::stock::{card_opaque, sincos, text_at};
78use crate::style::Sp;
79use crate::theme::color_mix;
80use crate::value::fmt_f64_sep;
81use crate::Ui;
82use gfx::dash::{dash_pattern, dash_stroke, DStyle, DASH_DASH, DASH_DASHDOT, DASH_DOT, DASH_SOLID, LCAP_FLAT};
83use gfx::path::Path;
84
85#[derive(Clone, Copy, Debug)]
87pub struct BarSeries<'a> {
88 pub label: &'a str,
89 pub values: &'a [f32],
90 pub color: u32,
92}
93
94#[derive(Clone, Copy, Debug)]
96pub struct DonutSlice<'a> {
97 pub label: &'a str,
98 pub value: f32,
99 pub color: u32,
101}
102
103#[derive(Default)]
106pub(crate) struct S {
107 cache: BTreeMap<u32, (u64, DrawRef)>,
108 lines: BTreeMap<u32, LcState>,
109}
110
111struct Fnv(u64);
112impl Fnv {
113 fn new() -> Fnv {
114 Fnv(0xCBF2_9CE4_8422_2325)
115 }
116 fn u32(&mut self, v: u32) {
117 for b in v.to_le_bytes() {
118 self.0 ^= b as u64;
119 self.0 = self.0.wrapping_mul(0x0000_0100_0000_01B3);
120 }
121 }
122 fn f32(&mut self, v: f32) {
123 self.u32(v.to_bits());
124 }
125 fn u64(&mut self, v: u64) {
126 self.u32(v as u32);
127 self.u32((v >> 32) as u32);
128 }
129 fn f64(&mut self, v: f64) {
130 self.u64(v.to_bits());
131 }
132 fn str(&mut self, s: &str) {
133 for b in s.bytes() {
134 self.0 ^= b as u64;
135 self.0 = self.0.wrapping_mul(0x0000_0100_0000_01B3);
136 }
137 self.u32(s.len() as u32);
138 }
139}
140
141struct Spark {
143 pts: Vec<(f32, f32)>,
144 line: u32,
145 area: u32,
146 width: i32,
147}
148
149impl Draw for Spark {
150 fn draw(&self, g: &mut Gfx, r: [i32; 4]) {
151 if self.pts.len() < 2 {
152 return;
153 }
154 let (x0, y0, w, h) = (r[0] as f32, r[1] as f32, r[2] as f32, r[3] as f32);
155 let m = self.width as f32 / 256.0;
156 let px = |p: (f32, f32)| (x0 + p.0 * w, y0 + m + (1.0 - p.1) * (h - m * 2.0));
157 let path = &mut g.text.path;
159 path.reset();
160 path.set_origin(0, 0);
161 let (sx, _) = px(self.pts[0]);
162 path.move_f(sx, y0 + h);
163 for &p in &self.pts {
164 let (x, y) = px(p);
165 path.line_f(x, y);
166 }
167 let (ex, _) = px(self.pts[self.pts.len() - 1]);
168 path.line_f(ex, y0 + h);
169 path.close();
170 g.dc.set_color(self.area);
171 path.fill(&mut g.text.ras, g.dc, g.sf, gfx::RULE_NONZERO);
172 let path = &mut g.text.path;
174 path.reset();
175 path.set_origin(0, 0);
176 for (i, &p) in self.pts.iter().enumerate() {
177 let (x, y) = px(p);
178 if i == 0 {
179 path.move_f(x, y);
180 } else {
181 path.line_f(x, y);
182 }
183 }
184 g.dc.set_color(self.line);
185 path.stroke(&mut g.text.ras, g.dc, g.sf, self.width, gfx::JOIN_ROUND | gfx::CAP_ROUND << 4);
186 }
187}
188
189struct Ring {
192 slices: Vec<(f32, f32, u32)>,
193 thick: f32,
194 track: u32,
195 gap: f32,
196}
197
198fn ring_path(g: &mut Gfx, cx: f32, cy: f32, ro: f32, ri: f32, a0: f32, a1: f32) {
199 let path = &mut g.text.path;
200 path.reset();
201 path.set_origin(0, 0);
202 let span = a1 - a0;
203 let n = ((span * 48.0) as i32).clamp(2, 96);
204 let tau = core::f32::consts::TAU;
205 for k in 0..=n {
206 let a = (a0 + span * k as f32 / n as f32) * tau - tau / 4.0;
207 let (s, c) = sincos(a);
208 if k == 0 {
209 path.move_f(cx + c * ro, cy + s * ro);
210 } else {
211 path.line_f(cx + c * ro, cy + s * ro);
212 }
213 }
214 for k in (0..=n).rev() {
215 let a = (a0 + span * k as f32 / n as f32) * tau - tau / 4.0;
216 let (s, c) = sincos(a);
217 path.line_f(cx + c * ri, cy + s * ri);
218 }
219 path.close();
220}
221
222impl Draw for Ring {
223 fn draw(&self, g: &mut Gfx, r: [i32; 4]) {
224 let d = r[2].min(r[3]) as f32;
225 let cx = r[0] as f32 + r[2] as f32 / 2.0;
226 let cy = r[1] as f32 + r[3] as f32 / 2.0;
227 let ro = d / 2.0 - 1.0;
228 let ri = ro * (1.0 - self.thick);
229 ring_path(g, cx, cy, ro, ri, 0.0, 1.0);
230 g.dc.set_color(self.track);
231 g.text.path.fill(&mut g.text.ras, g.dc, g.sf, gfx::RULE_NONZERO);
232 for &(a0, a1, c) in &self.slices {
233 let (a0, a1) = if self.slices.len() > 1 { (a0 + self.gap / 2.0, a1 - self.gap / 2.0) } else { (a0, a1) };
234 if a1 <= a0 {
235 continue;
236 }
237 ring_path(g, cx, cy, ro, ri, a0, a1);
238 g.dc.set_color(c);
239 g.text.path.fill(&mut g.text.ras, g.dc, g.sf, gfx::RULE_NONZERO);
240 }
241 }
242}
243
244fn short_num(v: f32) -> String {
246 let a = if v < 0.0 { -v } else { v };
247 if a >= 1.0e9 {
248 format!("{:.1}G", v / 1.0e9)
249 } else if a >= 1.0e6 {
250 format!("{:.1}M", v / 1.0e6)
251 } else if a >= 1.0e4 {
252 format!("{:.0}k", v / 1.0e3)
253 } else if a >= 1.0e3 {
254 format!("{:.1}k", v / 1.0e3)
255 } else if a == (a as i64) as f32 {
256 format!("{}", v as i64)
257 } else {
258 format!("{:.1}", v)
259 }
260}
261
262fn nice_max(m: f32, maxn: i32) -> (f32, i32) {
264 if m <= 0.0 {
265 return (4.0, 4.min(maxn));
266 }
267 let m = if m > 1e30 { 1e30 } else { m };
270 let mut p = 1.0f32;
271 while p * 10.0 <= m {
272 p *= 10.0;
273 }
274 while p > m {
275 p /= 10.0;
276 }
277 for f in [1.0f32, 2.0, 2.5, 5.0, 10.0] {
278 let step = p * f / 2.0;
279 let mut n = (m / step) as i32;
280 if (n as f32) * step < m {
281 n += 1;
282 }
283 if n <= maxn {
284 return (step * n.max(1) as f32, n.max(1));
285 }
286 }
287 (p * 10.0, 4.min(maxn))
288}
289
290fn axis_labels(ws: &[i32], x0: i32, cw: i32, lo: i32, hi: i32, k: usize, sep: i32) -> Option<Vec<(usize, i32)>> {
294 let n = ws.len();
295 let mut cols: Vec<usize> = (0..n).step_by(k).collect();
296 if n > 1 && cols.last() != Some(&(n - 1)) {
297 if cols.len() > 1 {
298 cols.pop();
299 }
300 cols.push(n - 1);
301 }
302 let mut out: Vec<(usize, i32)> = Vec::with_capacity(cols.len());
303 for i in cols {
304 let x = (x0 + cw * i as i32 + ((cw - ws[i]) >> 1)).min(hi - ws[i]).max(lo);
305 if let Some(&(j, px)) = out.last() {
306 if x < px + ws[j] + sep {
307 return None;
308 }
309 }
310 out.push((i, x));
311 }
312 Some(out)
313}
314
315static SERIES_VER: AtomicU32 = AtomicU32::new(1);
318
319fn series_ver() -> u32 {
322 SERIES_VER.fetch_add(1, Ordering::Relaxed)
323}
324
325#[derive(Clone, Debug)]
331pub struct SeriesBuf {
332 pts: VecDeque<(f64, f64)>,
333 cap: usize,
334 ver: u32,
335}
336
337#[derive(Clone, Copy, Debug, PartialEq)]
340pub struct SeriesCol {
341 pub col: u32,
343 pub n: u32,
345 pub min: f64,
346 pub max: f64,
347 pub first: f64,
349 pub last: f64,
350 pub gap: bool,
353}
354
355fn min_max_of(parts: [&[(f64, f64)]; 2]) -> Option<(f64, f64)> {
357 let (mut lo, mut hi) = (f64::INFINITY, f64::NEG_INFINITY);
358 for part in parts {
359 for &(_, v) in part {
360 if v.is_finite() {
361 if v < lo {
362 lo = v;
363 }
364 if v > hi {
365 hi = v;
366 }
367 }
368 }
369 }
370 if lo <= hi { Some((lo, hi)) } else { None }
371}
372
373impl SeriesBuf {
374 pub fn new(capacity: usize) -> SeriesBuf {
376 SeriesBuf { pts: VecDeque::new(), cap: capacity.max(1), ver: series_ver() }
377 }
378
379 pub fn push(&mut self, t: f64, v: f64) -> bool {
382 if !t.is_finite() || self.pts.back().is_some_and(|p| t < p.0) {
383 return false;
384 }
385 let n = self.pts.len();
386 if n >= self.cap {
387 self.pts.pop_front();
388 } else if n == self.pts.capacity() {
389 self.pts.reserve_exact(n.max(16).min(self.cap - n));
391 }
392 self.pts.push_back((t, v));
393 self.ver = series_ver();
394 true
395 }
396
397 pub fn clear(&mut self) {
399 self.pts.clear();
400 self.ver = series_ver();
401 }
402
403 pub fn len(&self) -> usize {
405 self.pts.len()
406 }
407
408 pub fn is_empty(&self) -> bool {
410 self.pts.is_empty()
411 }
412
413 pub fn capacity(&self) -> usize {
415 self.cap
416 }
417
418 pub fn ver(&self) -> u32 {
420 self.ver
421 }
422
423 pub fn get(&self, i: usize) -> Option<(f64, f64)> {
425 self.pts.get(i).copied()
426 }
427
428 pub fn first(&self) -> Option<(f64, f64)> {
430 self.pts.front().copied()
431 }
432
433 pub fn last(&self) -> Option<(f64, f64)> {
435 self.pts.back().copied()
436 }
437
438 pub fn iter(&self) -> impl DoubleEndedIterator<Item = (f64, f64)> + ExactSizeIterator + '_ {
440 self.pts.iter().copied()
441 }
442
443 pub fn lower_bound(&self, t: f64) -> usize {
445 self.pts.partition_point(|p| p.0 < t)
446 }
447
448 fn upper_bound(&self, t: f64) -> usize {
450 self.pts.partition_point(|p| p.0 <= t)
451 }
452
453 fn slices(&self, a: usize, b: usize) -> [&[(f64, f64)]; 2] {
455 let (s0, s1) = self.pts.as_slices();
456 let n0 = s0.len();
457 let b = b.min(self.pts.len()).max(a);
458 if b <= n0 {
459 [&s0[a..b], &[]]
460 } else if a >= n0 {
461 [&s1[a - n0..b - n0], &[]]
462 } else {
463 [&s0[a..], &s1[..b - n0]]
464 }
465 }
466
467 pub fn nearest(&self, t: f64) -> Option<usize> {
469 let i = self.lower_bound(t);
470 let after = (i..self.pts.len()).find(|&k| self.pts[k].1.is_finite());
471 let before = (0..i).rev().find(|&k| self.pts[k].1.is_finite());
472 match (before, after) {
473 (Some(b), Some(a)) => Some(if t - self.pts[b].0 <= self.pts[a].0 - t { b } else { a }),
474 (b, a) => b.or(a),
475 }
476 }
477
478 pub fn min_max(&self) -> Option<(f64, f64)> {
480 min_max_of(self.slices(0, self.pts.len()))
481 }
482
483 pub fn min_max_in(&self, t0: f64, t1: f64) -> Option<(f64, f64)> {
485 let (a, b) = (self.lower_bound(t0), self.upper_bound(t1));
486 if a >= b {
487 return None;
488 }
489 min_max_of(self.slices(a, b))
490 }
491
492 pub fn columns(&self, t0: f64, t1: f64, n: usize) -> Vec<SeriesCol> {
497 let mut out: Vec<SeriesCol> = Vec::new();
498 if n == 0 || !(t1 > t0) {
499 return out;
500 }
501 let (a, b) = (self.lower_bound(t0), self.upper_bound(t1));
502 out.reserve(n.min(b.saturating_sub(a)));
503 let k = n as f64 / (t1 - t0);
504 let last = (n - 1).min(u32::MAX as usize - 1) as u32;
505 let lastf = last as f64;
506 let mut c = SeriesCol { col: u32::MAX, n: 0, min: 0.0, max: 0.0, first: 0.0, last: 0.0, gap: false };
508 let mut gap = false;
509 for part in self.slices(a, b) {
510 for &(t, v) in part {
511 if !v.is_finite() {
512 gap = true;
513 continue;
514 }
515 let f = (t - t0) * k;
516 let col = if f >= lastf { last } else if f > 0.0 { f as u32 } else { 0 };
517 if col == c.col && !gap {
518 c.n += 1;
519 if v < c.min {
520 c.min = v;
521 }
522 if v > c.max {
523 c.max = v;
524 }
525 c.last = v;
526 continue;
527 }
528 if c.n > 0 {
529 out.push(c);
530 }
531 c = SeriesCol { col, n: 1, min: v, max: v, first: v, last: v, gap };
532 gap = false;
533 }
534 }
535 if c.n > 0 {
536 out.push(c);
537 }
538 out
539 }
540}
541
542#[derive(Clone, Copy, PartialEq, Eq, Debug)]
546#[non_exhaustive]
547pub enum LcUnit<'a> {
548 Num(&'a str),
551 Si(&'a str),
553 Secs,
555}
556
557const PREFIX: [(f64, &str); 5] = [(1.0, ""), (1e3, "k"), (1e6, "M"), (1e9, "G"), (1e12, "T")];
559const DURATION: [(f64, &str); 4] = [(1e-9, "ns"), (1e-6, "µs"), (1e-3, "ms"), (1.0, "s")];
560static POW10F: [f64; 10] = [1.0, 10.0, 100.0, 1e3, 1e4, 1e5, 1e6, 1e7, 1e8, 1e9];
561static POW10U: [u64; 10] = [1, 10, 100, 1_000, 10_000, 100_000, 1_000_000, 10_000_000, 100_000_000, 1_000_000_000];
562
563fn abs(x: f64) -> f64 {
564 if x < 0.0 { -x } else { x }
565}
566
567fn floor_f(x: f64) -> f64 {
568 let f = x as i64 as f64;
569 if f > x { f - 1.0 } else { f }
570}
571
572fn ceil_f(x: f64) -> f64 {
573 -floor_f(-x)
574}
575
576fn pow10(e: i32) -> f64 {
578 let mut p = 1.0f64;
579 for _ in 0..e.unsigned_abs().min(320) {
580 p *= 10.0;
581 }
582 if e < 0 { 1.0 / p } else { p }
583}
584
585fn sig_decimals(x: f64) -> u32 {
587 if !(x > 0.0) || x >= 100.0 {
588 0
589 } else if x >= 10.0 {
590 1
591 } else if x >= 1.0 {
592 2
593 } else {
594 let (mut d, mut s) = (2, x);
595 while s < 1.0 && d < 6 {
596 s *= 10.0;
597 d += 1;
598 }
599 d
600 }
601}
602
603fn step_decimals(x: f64) -> u32 {
605 let mut s = abs(x);
606 let mut d = 0;
607 while d < 9 && s > 0.0 && abs(s - floor_f(s + 0.5)) > s * 1e-9 {
608 s *= 10.0;
609 d += 1;
610 }
611 d
612}
613
614fn trim_zeros(s: String, sep: u32) -> String {
616 let Some(c) = char::from_u32(sep) else { return s };
617 if !s.contains(c) {
618 return s;
619 }
620 String::from(s.trim_end_matches('0').trim_end_matches(c))
621}
622
623impl LcUnit<'_> {
624 fn scales(&self) -> &'static [(f64, &'static str)] {
625 if *self == LcUnit::Secs { &DURATION } else { &PREFIX }
626 }
627
628 fn base(&self) -> usize {
630 if *self == LcUnit::Secs { 3 } else { 0 }
631 }
632
633 fn scale_of(&self, m: f64) -> usize {
635 if !(m > 0.0) {
636 return self.base();
637 }
638 let s = self.scales();
639 let mut k = 0;
640 while k + 1 < s.len() && m >= s[k + 1].0 * 0.999_999_999 {
641 k += 1;
642 }
643 k
644 }
645
646 fn unit_text(&self, num: &str, k: usize) -> String {
648 let p = self.scales()[k].1;
649 match *self {
650 LcUnit::Num("") => format!("{num}{p}"),
651 LcUnit::Num(sfx) => format!("{num}{p} {sfx}"),
652 LcUnit::Si(u) if p.is_empty() && u.is_empty() => String::from(num),
653 LcUnit::Si(u) => format!("{num} {p}{u}"),
654 LcUnit::Secs => format!("{num} {p}"),
655 }
656 }
657
658 pub fn text(&self, v: f64, sep: u32) -> String {
662 if !v.is_finite() {
663 return String::from("–");
664 }
665 let a = abs(v);
666 let s = self.scales();
667 let mut k = self.scale_of(a);
668 loop {
669 let x = a / s[k].0;
670 let d = sig_decimals(x);
671 let p = POW10F[d as usize];
672 let r = floor_f(x * p + 0.5) / p;
673 if r >= 1000.0 && k + 1 < s.len() {
674 k += 1;
675 continue;
676 }
677 let num = trim_zeros(fmt_f64_sep(if v < 0.0 { -r } else { r }, d, sep), sep);
678 return self.unit_text(&num, k);
679 }
680 }
681
682 pub fn ticks(&self, first: f64, step: f64, n: u32, sep: u32) -> Vec<String> {
686 let m = abs(first).max(abs(first + step * n as f64));
687 let m = if m > 0.0 { m } else { abs(step) };
688 let k = self.scale_of(if matches!(self, LcUnit::Num(_)) { m / 10.0 } else { m });
689 let sc = self.scales()[k].0;
690 let d = step_decimals(step / sc).min(6);
691 (0..=n)
692 .map(|i| {
693 let v = first + step * i as f64;
694 if abs(v) <= abs(step) * 1e-6 {
695 let z = if matches!(self, LcUnit::Num(_)) { self.base() } else { k };
696 return self.unit_text("0", z);
697 }
698 self.unit_text(&fmt_f64_sep(v / sc, d, sep), k)
699 })
700 .collect()
701 }
702}
703
704pub fn lc_nice(lo: f64, hi: f64, steps: u32) -> (f64, f64, u32) {
708 let m = steps.max(1) as f64;
709 let (lo, hi) = if lo <= hi { (lo, hi) } else { (hi, lo) };
710 let span = hi - lo;
711 if !span.is_finite() || !lo.is_finite() {
712 return (0.0, 1.0, 1);
713 }
714 let span = if span > 0.0 { span } else if lo != 0.0 { abs(lo) } else { 1.0 };
715 let raw = span / m;
716 let mut e = 0i32;
717 while e < 300 && pow10(e + 1) <= raw {
718 e += 1;
719 }
720 while e > -300 && pow10(e) > raw {
721 e -= 1;
722 }
723 let p = pow10(e);
724 for f in [1.0, 2.0, 2.5, 5.0, 10.0, 20.0, 25.0, 50.0, 100.0] {
725 let step = f * p;
726 let a = floor_f(lo / step + 1e-9);
727 let b = ceil_f(hi / step - 1e-9);
728 let n = (b - a).max(1.0);
729 if n <= m {
730 return (a * step, step, n as u32);
731 }
732 }
733 let step = 100.0 * p;
734 let a = floor_f(lo / step + 1e-9);
735 (a * step, step, (ceil_f(hi / step - 1e-9) - a).max(1.0) as u32)
736}
737
738const CLOCK: [f64; 18] =
741 [1.0, 2.0, 5.0, 10.0, 15.0, 30.0, 60.0, 120.0, 300.0, 600.0, 900.0, 1800.0, 3600.0, 7200.0, 10800.0, 21600.0, 43200.0, 86400.0];
742
743pub fn lc_time_step(min: f64) -> f64 {
747 if !(min > 1e-9) {
748 return 1e-9;
749 }
750 if min <= 1.0 {
751 for e in -9..0 {
752 for f in [1.0, 2.0, 5.0] {
753 let s = f * pow10(e);
754 if s >= min * (1.0 - 1e-12) {
755 return s;
756 }
757 }
758 }
759 return 1.0;
760 }
761 for s in CLOCK {
762 if s >= min {
763 return s;
764 }
765 }
766 let d = min / 86400.0;
767 for e in 0..300 {
768 for f in [1.0, 2.0, 5.0] {
769 if f * pow10(e) >= d {
770 return f * pow10(e) * 86400.0;
771 }
772 }
773 }
774 min
775}
776
777pub fn lc_time_text(t: f64, dec: u32, hours: bool, sep: u32) -> String {
780 let dec = dec.min(9);
781 let p = POW10U[dec as usize];
782 let neg = t < 0.0;
783 let u = (abs(t) * p as f64 + 0.5) as u64;
784 let (whole, frac) = (u / p, u % p);
785 let mut s = String::new();
786 if neg && u != 0 {
787 s.push('-');
788 }
789 if hours {
790 s.push_str(&format!("{}:{:02}:{:02}", whole / 3600, whole / 60 % 60, whole % 60));
791 } else {
792 s.push_str(&format!("{}:{:02}", whole / 60, whole % 60));
793 }
794 if dec > 0 {
795 if let Some(c) = char::from_u32(sep) {
796 s.push(c);
797 }
798 s.push_str(&format!("{:0w$}", frac, w = dec as usize));
799 }
800 s
801}
802
803pub const LX_SECS: u8 = 0; pub const LX_NANOS: u8 = 1; pub const LX_NUM: u8 = 2; pub const LC_NO_LEGEND: u32 = 1; pub const LC_NO_GRID: u32 = 2; pub const LC_NO_HOVER: u32 = 4; #[derive(Clone, Copy, Debug)]
816pub struct LcSeries<'a> {
817 pub label: &'a str,
818 pub data: &'a SeriesBuf,
819 pub color: u32,
821 pub pal: Option<u8>,
822 pub area: bool,
824 pub unit: LcUnit<'a>,
826}
827
828impl<'a> LcSeries<'a> {
829 pub const fn new(label: &'a str, data: &'a SeriesBuf) -> LcSeries<'a> {
831 LcSeries { label, data, color: 0, pal: None, area: false, unit: LcUnit::Num("") }
832 }
833 pub const fn color(mut self, c: u32) -> LcSeries<'a> {
835 self.color = c;
836 self
837 }
838 pub const fn pal(mut self, k: u8) -> LcSeries<'a> {
840 self.pal = Some(k);
841 self
842 }
843 pub const fn area(mut self) -> LcSeries<'a> {
845 self.area = true;
846 self
847 }
848 pub const fn unit(mut self, u: LcUnit<'a>) -> LcSeries<'a> {
850 self.unit = u;
851 self
852 }
853}
854
855#[derive(Clone, Copy, Debug)]
857pub struct LcMarker<'a> {
858 pub t0: f64,
859 pub t1: f64,
861 pub label: &'a str,
863 pub color: u32,
865}
866
867impl<'a> LcMarker<'a> {
868 pub const fn line(t: f64, label: &'a str) -> LcMarker<'a> {
870 LcMarker { t0: t, t1: t, label, color: 0 }
871 }
872 pub const fn range(t0: f64, t1: f64, label: &'a str) -> LcMarker<'a> {
874 LcMarker { t0, t1, label, color: 0 }
875 }
876 pub const fn color(mut self, c: u32) -> LcMarker<'a> {
878 self.color = c;
879 self
880 }
881}
882
883#[derive(Clone, Copy, Debug)]
885pub struct LcThreshold<'a> {
886 pub value: f64,
887 pub label: &'a str,
888 pub color: u32,
890}
891
892impl<'a> LcThreshold<'a> {
893 pub const fn new(value: f64, label: &'a str) -> LcThreshold<'a> {
895 LcThreshold { value, label, color: 0 }
896 }
897 pub const fn color(mut self, c: u32) -> LcThreshold<'a> {
899 self.color = c;
900 self
901 }
902}
903
904#[derive(Clone, Copy, Debug)]
907pub struct LineChart<'a> {
908 pub series: &'a [LcSeries<'a>],
909 pub x: u8,
911 pub x_range: (f64, f64),
913 pub x_span: f64,
915 pub y_range: (f64, f64),
918 pub markers: &'a [LcMarker<'a>],
919 pub thresholds: &'a [LcThreshold<'a>],
920 pub flags: u32,
922 pub name: &'a str,
924 pub empty: &'a str,
926}
927
928impl<'a> LineChart<'a> {
929 pub const fn new(series: &'a [LcSeries<'a>]) -> LineChart<'a> {
931 LineChart {
932 series,
933 x: LX_SECS,
934 x_range: (0.0, 0.0),
935 x_span: 0.0,
936 y_range: (f64::NAN, f64::NAN),
937 markers: &[],
938 thresholds: &[],
939 flags: 0,
940 name: "",
941 empty: "",
942 }
943 }
944 pub const fn x_axis(mut self, x: u8) -> LineChart<'a> {
946 self.x = x;
947 self
948 }
949 pub const fn x_range(mut self, lo: f64, hi: f64) -> LineChart<'a> {
951 self.x_range = (lo, hi);
952 self
953 }
954 pub const fn follow(mut self, span: f64) -> LineChart<'a> {
956 self.x_span = span;
957 self
958 }
959 pub const fn y_range(mut self, lo: f64, hi: f64) -> LineChart<'a> {
961 self.y_range = (lo, hi);
962 self
963 }
964 pub const fn markers(mut self, m: &'a [LcMarker<'a>]) -> LineChart<'a> {
966 self.markers = m;
967 self
968 }
969 pub const fn thresholds(mut self, t: &'a [LcThreshold<'a>]) -> LineChart<'a> {
971 self.thresholds = t;
972 self
973 }
974 pub const fn flags(mut self, f: u32) -> LineChart<'a> {
976 self.flags = f;
977 self
978 }
979 pub const fn name(mut self, n: &'a str) -> LineChart<'a> {
981 self.name = n;
982 self
983 }
984 pub const fn empty(mut self, s: &'a str) -> LineChart<'a> {
986 self.empty = s;
987 self
988 }
989
990 pub fn view(&self) -> (f64, f64) {
994 let (a, b) = self.x_range;
995 if a.is_finite() && b.is_finite() && b > a {
996 return (a, b);
997 }
998 let one = match self.x {
999 LX_NANOS => 1e9,
1000 _ => 1.0,
1001 };
1002 let (mut lo, mut hi) = (f64::INFINITY, f64::NEG_INFINITY);
1003 for s in self.series {
1004 if let (Some(f), Some(l)) = (s.data.first(), s.data.last()) {
1005 lo = lo.min(f.0);
1006 hi = hi.max(l.0);
1007 }
1008 }
1009 let span = if self.x_span.is_finite() && self.x_span > 0.0 { self.x_span } else { 0.0 };
1010 let v = if lo > hi {
1011 (0.0, if span > 0.0 { span } else { one })
1012 } else if span > 0.0 {
1013 let end = hi.max(lo + span);
1014 (end - span, end)
1015 } else if hi > lo {
1016 (lo, hi)
1017 } else {
1018 (lo - one / 2.0, lo + one / 2.0)
1019 };
1020 if v.1 > v.0 { v } else { (v.0, v.0 + one) }
1021 }
1022}
1023
1024#[derive(Default)]
1027pub(crate) struct LcState {
1028 stats_key: u64,
1029 stats: Vec<Stat>,
1030 sum_key: u64,
1031 summary: String,
1032 obj_key: u64,
1033 obj: Option<DrawRef>,
1034 hover: Option<(usize, u64)>,
1036 cursor: Option<(usize, f64)>,
1038 rect: [i32; 4],
1040}
1041
1042#[derive(Clone, Copy, Default)]
1044struct Stat {
1045 lo: f64,
1046 hi: f64,
1047 last: f64,
1048 has: bool,
1049 n: usize,
1050}
1051
1052fn stat_of(d: &SeriesBuf, v: (f64, f64)) -> Stat {
1053 let (a, b) = (d.lower_bound(v.0), d.upper_bound(v.1));
1054 let mut s = Stat { n: b.saturating_sub(a), ..Stat::default() };
1055 if let Some((lo, hi)) = d.min_max_in(v.0, v.1) {
1056 s.lo = lo;
1057 s.hi = hi;
1058 s.has = true;
1059 s.last = (a..b).rev().filter_map(|i| d.get(i)).map(|p| p.1).find(|x| x.is_finite()).unwrap_or(f64::NAN);
1060 }
1061 s
1062}
1063
1064#[derive(Clone, Copy)]
1066struct Look {
1067 color: u32,
1068 dash: u32,
1069 area: bool,
1070}
1071
1072const HC_DASH: [u32; 4] = [DASH_SOLID, DASH_DASH, DASH_DOT, DASH_DASHDOT];
1075
1076struct Trace {
1078 polys: Vec<Vec<(f32, f32)>>,
1079 band: bool,
1080 color: u32,
1081 width: i32,
1082 dash: u32,
1083}
1084
1085struct Plot {
1088 m: i32,
1089 w: i32,
1090 h: i32,
1091 hair: i32,
1092 grid: Vec<(i32, bool)>,
1094 grid_c: u32,
1095 dotted: bool,
1096 ranges: Vec<(i32, i32, u32)>,
1098 areas: Vec<(Vec<Vec<(f32, f32)>>, u32)>,
1099 traces: Vec<Trace>,
1100 limits: Vec<(i32, u32)>,
1102 lim: (i32, i32, i32),
1103 rules: Vec<(i32, i32, u32)>,
1105}
1106
1107fn trace_path(path: &mut Path, polys: &[Vec<(f32, f32)>], ox: f32, oy: f32, close: bool) {
1109 path.reset();
1110 path.set_origin(0, 0);
1111 for p in polys {
1112 for (k, &(x, y)) in p.iter().enumerate() {
1113 if k == 0 {
1114 path.move_f(ox + x, oy + y);
1115 } else {
1116 path.line_f(ox + x, oy + y);
1117 }
1118 }
1119 if close {
1120 path.close();
1121 }
1122 }
1123}
1124
1125impl Draw for Plot {
1126 fn draw(&self, g: &mut Gfx, r: [i32; 4]) {
1127 let (ox, oy) = (r[0] + self.m, r[1] + self.m);
1128 let (w, h, t) = (self.w, self.h, self.hair);
1129 g.dc.set_color(self.grid_c);
1130 for &(p, vert) in &self.grid {
1131 if self.dotted {
1132 let n = if vert { h } else { w };
1134 let mut k = 0;
1135 while k < n {
1136 if vert {
1137 g.dc.fill(g.sf, ox + p, oy + k, t, t);
1138 } else {
1139 g.dc.fill(g.sf, ox + k, oy + p, t, t);
1140 }
1141 k += t * 3;
1142 }
1143 } else if vert {
1144 g.dc.fill(g.sf, ox + p, oy, t, h);
1145 } else {
1146 g.dc.fill(g.sf, ox, oy + p, w, t);
1147 }
1148 }
1149 for &(x0, x1, c) in &self.ranges {
1150 g.dc.set_color(c);
1151 g.dc.fill(g.sf, ox + x0, oy, x1 - x0, h);
1152 }
1153 let (fx, fy) = (ox as f32, oy as f32);
1154 for (polys, c) in &self.areas {
1155 trace_path(&mut g.text.path, polys, fx, fy, true);
1156 g.dc.set_color(*c);
1157 g.text.path.fill(&mut g.text.ras, g.dc, g.sf, gfx::RULE_NONZERO);
1158 }
1159 for tr in &self.traces {
1160 trace_path(&mut g.text.path, &tr.polys, fx, fy, tr.band);
1161 g.dc.set_color(tr.color);
1162 if tr.band {
1163 g.text.path.fill(&mut g.text.ras, g.dc, g.sf, gfx::RULE_NONZERO);
1164 } else if tr.dash == DASH_SOLID {
1165 g.text.path.stroke(&mut g.text.ras, g.dc, g.sf, tr.width, gfx::JOIN_ROUND | gfx::CAP_ROUND << 4);
1166 } else {
1167 let st = DStyle {
1168 width: tr.width as f64 / 256.0,
1169 pat: dash_pattern(tr.dash),
1170 join: gfx::JOIN_ROUND,
1171 cap0: LCAP_FLAT,
1172 cap1: LCAP_FLAT,
1173 dcap: LCAP_FLAT,
1174 offset: 0.0,
1175 miter: 0.0,
1176 };
1177 dash_stroke(&mut g.text.path, &st);
1178 g.text.path.fill(&mut g.text.ras, g.dc, g.sf, gfx::RULE_NONZERO);
1179 }
1180 }
1181 let (lt, on, off) = self.lim;
1182 for &(y, c) in &self.limits {
1183 g.dc.set_color(c);
1184 let mut x = 0;
1185 while x < w {
1186 g.dc.fill(g.sf, ox + x, oy + y, on.min(w - x), lt);
1187 x += on + off;
1188 }
1189 }
1190 for &(x, lw, c) in &self.rules {
1191 g.dc.set_color(c);
1192 g.dc.fill(g.sf, ox + x, oy, lw, h);
1193 }
1194 }
1195}
1196
1197fn polyline(run: &mut [(f64, f64)], w: f64) -> Vec<(f32, f32)> {
1200 const M: f64 = 64.0;
1201 let n = run.len();
1202 if n >= 2 {
1203 let (a, b) = (run[0], run[1]);
1204 if a.0 < -M && b.0 > a.0 {
1205 let f = (-M - a.0) / (b.0 - a.0);
1206 run[0] = (-M, a.1 + (b.1 - a.1) * f);
1207 }
1208 let (a, b) = (run[n - 2], run[n - 1]);
1209 if b.0 > w + M && b.0 > a.0 {
1210 let f = (w + M - a.0) / (b.0 - a.0);
1211 run[n - 1] = (w + M, a.1 + (b.1 - a.1) * f);
1212 }
1213 }
1214 if n == 1 {
1215 let (x, y) = run[0];
1217 return alloc::vec![((x - 0.5) as f32, y as f32), ((x + 0.5) as f32, y as f32)];
1218 }
1219 run.iter().map(|&(x, y)| (x as f32, y as f32)).collect()
1220}
1221
1222type Polys = Vec<Vec<(f32, f32)>>;
1223
1224#[allow(clippy::too_many_arguments)]
1230fn trace_of(d: &SeriesBuf, v: (f64, f64), (w, h): (i32, i32), y: (f64, f64), lw: f64, dense: bool, area: bool, dash: &[f64]) -> (Polys, bool, Polys) {
1231 let sx = w as f64 / (v.1 - v.0);
1232 let sy = h as f64 / (y.1 - y.0);
1233 let hf = h as f64;
1234 let fy = |val: f64| (hf - (val - y.0) * sy).clamp(-hf - 64.0, hf * 2.0 + 64.0);
1235 let base = fy(0.0f64.clamp(y.0, y.1));
1236 let mut lines: Vec<Vec<(f32, f32)>> = Vec::new();
1237 let mut areas: Vec<Vec<(f32, f32)>> = Vec::new();
1238 if !dense {
1239 let a = d.lower_bound(v.0).saturating_sub(1);
1240 let b = (d.upper_bound(v.1) + 1).min(d.len());
1241 let mut run: Vec<(f64, f64)> = Vec::new();
1242 let mut flush = |run: &mut Vec<(f64, f64)>| {
1243 if run.is_empty() {
1244 return;
1245 }
1246 let p = polyline(run, w as f64);
1247 if area && p.len() >= 2 {
1248 let mut q = p.clone();
1249 q.push((p[p.len() - 1].0, base as f32));
1250 q.push((p[0].0, base as f32));
1251 areas.push(q);
1252 }
1253 lines.push(p);
1254 run.clear();
1255 };
1256 for i in a..b {
1257 let Some((t, val)) = d.get(i) else { break };
1258 if !val.is_finite() {
1259 flush(&mut run);
1260 continue;
1261 }
1262 run.push(((t - v.0) * sx, fy(val)));
1263 }
1264 flush(&mut run);
1265 return (lines, false, areas);
1266 }
1267 let cols = d.columns(v.0, v.1, w.max(1) as usize);
1268 let half = lw / 2.0;
1269 let pat: Vec<f64> = dash.iter().map(|k| k * lw).collect();
1271 let period: f64 = pat.iter().sum::<f64>() * if pat.len() % 2 == 1 { 2.0 } else { 1.0 };
1272 let on = |x: f64| -> bool {
1273 if pat.is_empty() || !(period > 0.0) {
1274 return true;
1275 }
1276 let mut p = x - floor_f(x / period) * period;
1277 let mut k = 0;
1278 loop {
1279 let len = pat[k % pat.len()];
1280 if p < len {
1281 return k % 2 == 0;
1282 }
1283 p -= len;
1284 k += 1;
1285 if k > 64 {
1286 return true;
1287 }
1288 }
1289 };
1290 let mut i = 0;
1291 while i < cols.len() {
1292 let mut j = i + 1;
1293 while j < cols.len() && !cols[j].gap {
1294 j += 1;
1295 }
1296 let mut ext: Vec<(f64, f64, f64)> = Vec::with_capacity(j - i);
1298 for k in i..j {
1299 let c = cols[k];
1300 let (mut lo, mut hi) = (c.min, c.max);
1301 if k + 1 < j {
1302 lo = lo.min(cols[k + 1].first);
1303 hi = hi.max(cols[k + 1].first);
1304 }
1305 ext.push((c.col as f64 + 0.5, fy(hi), fy(lo)));
1306 }
1307 if area {
1308 let mut q: Vec<(f32, f32)> = Vec::with_capacity(ext.len() + 4);
1309 q.push(((ext[0].0 - 0.5) as f32, ext[0].1 as f32));
1310 q.extend(ext.iter().map(|e| (e.0 as f32, e.1 as f32)));
1311 let l = ext[ext.len() - 1];
1312 q.push(((l.0 + 0.5) as f32, l.1 as f32));
1313 q.push(((l.0 + 0.5) as f32, base as f32));
1314 q.push(((ext[0].0 - 0.5) as f32, base as f32));
1315 areas.push(q);
1316 }
1317 let mut k = 0;
1319 while k < ext.len() {
1320 if !on(ext[k].0) {
1321 k += 1;
1322 continue;
1323 }
1324 let mut e = k + 1;
1325 while e < ext.len() && on(ext[e].0) {
1326 e += 1;
1327 }
1328 let part = &ext[k..e];
1329 let mut q: Vec<(f32, f32)> = Vec::with_capacity(part.len() * 2 + 4);
1330 q.push(((part[0].0 - 0.5) as f32, (part[0].1 - half) as f32));
1331 q.extend(part.iter().map(|p| (p.0 as f32, (p.1 - half) as f32)));
1332 let l = part[part.len() - 1];
1333 q.push(((l.0 + 0.5) as f32, (l.1 - half) as f32));
1334 q.push(((l.0 + 0.5) as f32, (l.2 + half) as f32));
1335 q.extend(part.iter().rev().map(|p| (p.0 as f32, (p.2 + half) as f32)));
1336 q.push(((part[0].0 - 0.5) as f32, (part[0].2 + half) as f32));
1337 lines.push(q);
1338 k = e;
1339 }
1340 i = j;
1341 }
1342 (lines, true, areas)
1343}
1344
1345fn near(a: [i32; 4], b: [i32; 4], sep: i32) -> bool {
1347 a[0] < b[0] + b[2] + sep && b[0] < a[0] + a[2] + sep && a[1] < b[1] + b[3] && b[1] < a[1] + a[3]
1348}
1349
1350fn lc_x_label(x: u8, t: f64, hours: bool, sep: u32) -> String {
1353 if x == LX_NUM {
1354 return LcUnit::Num("").text(t, sep);
1355 }
1356 let s = if x == LX_NANOS { t * 1e-9 } else { t };
1357 let full = lc_time_text(s, 9, hours, sep);
1358 match char::from_u32(sep) {
1359 Some(c) => String::from(full.trim_end_matches('0').trim_end_matches(c)),
1360 None => full,
1361 }
1362}
1363
1364fn mark_end(m: &LcMarker) -> f64 {
1366 if m.t1 > m.t0 { m.t1 } else { m.t0 }
1367}
1368
1369fn lc_step(d: &SeriesBuf, i: usize, back: bool, v: (f64, f64)) -> Option<f64> {
1371 let mut k = i;
1372 loop {
1373 if back {
1374 k = k.checked_sub(1)?;
1375 }
1376 let (t, val) = d.get(k)?;
1377 if (back && t < v.0) || (!back && t > v.1) {
1378 return None;
1379 }
1380 if val.is_finite() && t >= v.0 && t <= v.1 {
1381 return Some(t);
1382 }
1383 if !back {
1384 k += 1;
1385 }
1386 }
1387}
1388
1389impl Ui {
1390 fn chart_color(&self, c: u32, i: usize) -> u32 {
1391 if c != 0 {
1392 return c;
1393 }
1394 let p = self.style.data().chart_palette(self);
1395 p[i % 8]
1396 }
1397
1398 fn chart_obj(&mut self, id: u32, hash: u64, make: impl FnOnce() -> DrawRef) -> DrawRef {
1400 if let Some((h, d)) = self.m.chart.cache.get(&id) {
1401 if *h == hash {
1402 return d.clone();
1403 }
1404 }
1405 let d = make();
1406 if self.m.chart.cache.len() > 256 {
1407 self.m.chart.cache.clear();
1408 }
1409 self.m.chart.cache.insert(id, (hash, d.clone()));
1410 d
1411 }
1412
1413 #[cfg_attr(jfgui_dev, track_caller)]
1415 pub fn sparkline(&mut self, id: u32, values: &[f32], color: u32) {
1416 let _src = self.src_scope();
1417 let r = self.place(self.dp(120), self.dp(32));
1418 let line = self.chart_color(color, 0);
1419 let area = (line & 0x00FF_FFFF) | 0x2E00_0000;
1420 let (mut lo, mut hi) = (f32::MAX, f32::MIN);
1421 for &v in values {
1422 lo = lo.min(v);
1423 hi = hi.max(v);
1424 }
1425 if hi <= lo {
1426 hi = lo + 1.0;
1427 lo -= 1.0;
1428 }
1429 let width = self.dp_fx(2 * 256).max(256);
1430 let mut h = Fnv::new();
1431 for &v in values {
1432 h.f32(v);
1433 }
1434 h.u32(line);
1435 h.u32(width as u32);
1436 let n = values.len().max(2) - 1;
1437 let hash = h.0;
1438 let d = self.chart_obj(id, hash, || {
1439 let pts = values.iter().enumerate().map(|(i, &v)| (i as f32 / n as f32, (v - lo) / (hi - lo))).collect();
1440 Rc::new(Spark { pts, line, area, width })
1441 });
1442 self.cmd_custom(r[0], r[1], r[2], r[3], Some(d), 0, hash as u32);
1443 self.wp.ctx.last_state = 0;
1444 self.sem(K_CANVAS, id, None, values.len() as i32);
1445 }
1446
1447 #[cfg_attr(jfgui_dev, track_caller)]
1449 pub fn bar_chart(&mut self, id: u32, labels: &[&str], series: &[BarSeries], stacked: bool) -> i32 {
1450 let _src = self.src_scope();
1451 let r = self.place(self.dp(360), self.dp(180));
1452 let n = labels.len();
1453 let mut m = 0.0f32;
1455 for i in 0..n {
1456 if stacked {
1457 let s: f32 = series.iter().map(|s| s.values.get(i).copied().unwrap_or(0.0).max(0.0)).sum();
1458 m = m.max(s);
1459 } else {
1460 for s in series {
1461 m = m.max(s.values.get(i).copied().unwrap_or(0.0));
1462 }
1463 }
1464 }
1465 let lh = self.ts_line(TS_CAPTION);
1467 let ph = r[3] - lh - self.dp(6) - (lh >> 1);
1468 let (top, steps) = nice_max(m, (ph / lh.max(1)).clamp(1, 5));
1469 let vals: Vec<String> = (0..=steps).map(|k| short_num(top * k as f32 / steps as f32)).collect();
1470 self.ts_font(TS_CAPTION);
1472 let vw = vals.iter().map(|s| self.text.width(s)).max().unwrap_or(0);
1473 let lw = vw.max(self.dp(16)) + self.dp(8);
1474 let plot = [r[0] + lw, r[1] + (lh >> 1), r[2] - lw, ph];
1475 let hair = self.hair();
1477 let grid = self.color(T_DIVIDER);
1478 let t3 = self.color(T_TEXT3);
1479 for (k, s) in vals.iter().enumerate() {
1480 let y = plot[1] + plot[3] - (plot[3] as i64 * k as i64 / steps as i64) as i32;
1481 self.draw_color(grid);
1482 self.draw_rect(plot[0], y, plot[2], hair);
1483 self.ts_font(TS_CAPTION);
1484 let tw = self.text.width(s);
1485 text_at(self, TS_CAPTION, plot[0] - tw - self.dp(6), [r[0], y - (lh >> 1), lw, lh], s, t3, 0);
1486 }
1487 if n == 0 {
1488 return -1;
1489 }
1490 let cw = plot[2] / n as i32;
1492 self.ts_font(TS_CAPTION);
1495 let lws: Vec<i32> = labels.iter().map(|l| self.text.width(crate::basic::cstr(l))).collect();
1496 let (lo, hi) = (plot[0], r[0] + r[2]);
1497 let sep = self.dp(4);
1498 let shown = (1..=n)
1499 .find_map(|k| axis_labels(&lws, plot[0], cw, lo, hi, k, sep))
1500 .unwrap_or_else(|| alloc::vec![(0, lo)]);
1501 let ly = plot[1] + plot[3] + self.dp(6);
1502 let gw = ((cw * 3) / 5).max(2);
1503 let ns = series.len().max(1) as i32;
1504 let mut hovered = -1;
1505 let rad = self.dp(2);
1506 let mut next = 0; for (i, label) in labels.iter().enumerate() {
1508 let cx = plot[0] + cw * i as i32;
1509 let ev = self.hit_rect(id.wrapping_add(1 + i as u32), cx, plot[1], cw, plot[3] + lh + self.dp(6), 0);
1511 if ev & EV_HOVER != 0 {
1512 hovered = i as i32;
1513 let c = self.color(T_SUBTLE_HOVER);
1514 if c & 0xFF00_0000 != 0 {
1515 self.cmd_rrect(cx + self.dp(2), plot[1], cw - self.dp(4), plot[3], self.dp(4), 0, c, 0, 0);
1516 }
1517 }
1518 let x0 = cx + ((cw - gw) >> 1);
1519 let mut base = plot[1] + plot[3];
1520 for (si, s) in series.iter().enumerate() {
1521 let v = s.values.get(i).copied().unwrap_or(0.0).max(0.0).min(top);
1523 let h = ((v / top) * plot[3] as f32) as i32;
1524 let c = self.chart_color(s.color, si);
1525 if stacked {
1526 if h > 0 {
1527 self.cmd_rrect(x0, base - h, gw, h, if si as i32 == ns - 1 { rad } else { 0 }, 0, c, 0, 0);
1528 }
1529 base -= h;
1530 } else {
1531 let bw = (gw / ns).max(1);
1532 let bx = x0 + bw * si as i32;
1533 if h > 0 {
1534 self.cmd_rrect(bx, base - h, (bw - hair).max(1), h, rad.min(bw / 2), 0, c, 0, 0);
1535 }
1536 }
1537 }
1538 let lab = crate::basic::cstr(label);
1540 if let Some(&(_, x)) = shown.get(next).filter(|s| s.0 == i) {
1541 next += 1;
1542 let room = hi - x;
1543 self.ts_font(TS_CAPTION);
1544 text_at(self, TS_CAPTION, x, [x, ly, room, lh], lab, t3, room.max(1));
1545 self.wp.ctx.last_rect = [x, ly, lws[i].min(room), lh];
1546 self.wp.ctx.last_state = 0;
1547 self.sem(K_TEXT, 0, Some(lab), (lws[i] > room) as i32);
1548 }
1549 if ev & EV_HOVER != 0 {
1550 let mut tip = String::from(lab);
1551 for s in series {
1552 let v = s.values.get(i).copied().unwrap_or(0.0);
1553 tip.push_str(&format!("\n{}: {}", s.label, short_num(v)));
1554 }
1555 self.tooltip(&tip);
1556 }
1557 }
1558 self.wp.ctx.last_rect = r;
1559 self.wp.ctx.last_state = 0;
1560 self.sem(K_CANVAS, id, None, n as i32);
1561 hovered
1562 }
1563
1564 #[cfg_attr(jfgui_dev, track_caller)]
1568 pub fn donut(&mut self, id: u32, slices: &[DonutSlice], center: &str, sub: &str) {
1569 let _src = self.src_scope();
1570 let s = self.dp(160);
1571 let r = self.place(s, s);
1572 let d = r[2].min(r[3]);
1573 let sq = [r[0] + ((r[2] - d) >> 1), r[1] + ((r[3] - d) >> 1), d, d];
1574 let total: f32 = slices.iter().map(|s| s.value.max(0.0)).sum();
1575 let track = self.color(T_TRACK);
1576 let mut h = Fnv::new();
1577 let mut parts = Vec::new();
1578 let mut a = 0.0f32;
1579 for (i, sl) in slices.iter().enumerate() {
1580 let f = if total > 0.0 { sl.value.max(0.0) / total } else { 0.0 };
1581 let c = self.chart_color(sl.color, i);
1582 parts.push((a, a + f, c));
1583 h.f32(f);
1584 h.u32(c);
1585 a += f;
1586 }
1587 h.u32(track);
1588 h.u32(d as u32);
1589 let hash = h.0;
1590 let gap = if slices.len() > 1 { 0.006 } else { 0.0 };
1591 let obj = self.chart_obj(id, hash, || Rc::new(Ring { slices: parts, thick: 0.22, track, gap }));
1592 self.cmd_custom(sq[0], sq[1], sq[2], sq[3], Some(obj), 0, hash as u32);
1593 let t = self.color(T_TEXT);
1596 let t2 = self.color(T_TEXT2);
1597 let hole = (d as f32 * 0.78) as i32 - self.dp(8);
1598 let mut ts1 = TS_SUBTITLE;
1599 self.ts_font(ts1);
1600 if self.text.width(center) > hole {
1601 ts1 = TS_BODY_STRONG;
1602 }
1603 let l1 = self.ts_line(ts1);
1604 let cap = self.ts_line(TS_CAPTION);
1605 let sub = if sub.is_empty() || l1 + cap > hole { "" } else { sub };
1606 let l2 = if sub.is_empty() { 0 } else { cap };
1607 let y = sq[1] + ((d - l1 - l2) >> 1);
1608 self.ts_font(ts1);
1609 let w1 = self.text.width(center).min(hole);
1610 text_at(self, ts1, sq[0] + ((d - w1) >> 1), [sq[0], y, d, l1], center, t, hole.max(1));
1611 if !sub.is_empty() {
1612 self.ts_font(TS_CAPTION);
1613 let w2 = self.text.width(sub).min(hole);
1614 text_at(self, TS_CAPTION, sq[0] + ((d - w2) >> 1), [sq[0], y + l1, d, l2], sub, t2, hole.max(1));
1615 }
1616 let ev = self.hit_rect(id, sq[0], sq[1], sq[2], sq[3], 0);
1618 if ev & EV_HOVER != 0 && total > 0.0 {
1619 let (mx, my) = self.mouse();
1620 let dx = (mx - (sq[0] + d / 2)) as f32;
1621 let dy = (my - (sq[1] + d / 2)) as f32;
1622 let dist = rt::fx::sqrt(dx * dx + dy * dy);
1623 let ro = d as f32 / 2.0;
1624 if dist <= ro && dist >= ro * 0.78 {
1625 let ang = rt::x87::atan2(dx as f64, -dy as f64) as f32 / core::f32::consts::TAU;
1626 let ang = if ang < 0.0 { ang + 1.0 } else { ang };
1627 let mut acc = 0.0f32;
1628 for sl in slices {
1629 let f = sl.value.max(0.0) / total;
1630 if ang >= acc && ang < acc + f {
1631 let tip = format!("{}: {} ({:.0} %)", sl.label, short_num(sl.value), f * 100.0);
1632 self.tooltip(&tip);
1633 break;
1634 }
1635 acc += f;
1636 }
1637 }
1638 }
1639 self.wp.ctx.last_rect = r;
1640 self.wp.ctx.last_state = 0;
1641 self.sem(K_CANVAS, id, Some(center), slices.len() as i32);
1642 }
1643
1644 #[cfg_attr(jfgui_dev, track_caller)]
1647 pub fn legend(&mut self, items: &[(&str, u32)]) {
1648 let _src = self.src_scope();
1649 self.legend_marks(items, &[], 0);
1650 }
1651
1652 fn legend_marks(&mut self, items: &[(&str, u32)], dashes: &[u32], lw: i32) {
1655 let s = self.dp(10);
1656 let sl = self.dp(20);
1657 let (gap, igap) = (self.dp(12), self.dp(6));
1658 self.ts_font(TS_CAPTION);
1661 let ws: Vec<i32> = items
1662 .iter()
1663 .enumerate()
1664 .map(|(i, &(label, _))| if i < dashes.len() { sl } else { s } + igap + self.text.width(crate::basic::cstr(label)))
1665 .collect();
1666 let one_row = ws.iter().sum::<i32>() + gap * (ws.len() as i32 - 1).max(0);
1667 let (room, _) = self.avail_width(one_row);
1668 self.box_begin(0, BOX_H | BOX_WRAP | BOX_XCENTER, 12);
1669 for (i, &(label, c)) in items.iter().enumerate() {
1670 let col = self.chart_color(c, i);
1671 let cut = room > 0 && ws[i] > room;
1672 if cut {
1673 self.next_size(self.px2dip(room), 0);
1674 }
1675 self.box_begin(0, BOX_H | BOX_XCENTER, 6);
1676 if let Some(&dash) = dashes.get(i) {
1677 let r = self.place(sl, s);
1678 self.lc_sample(r, col, dash, lw);
1679 } else {
1680 let r = self.place(s, s);
1681 self.draw_color(col);
1682 self.draw_circle((r[0] << 8) + (s << 7), (r[1] << 8) + (r[3] << 7), s << 7, 0);
1683 }
1684 if cut {
1685 self.next_size(-1, 0);
1686 }
1687 self.text(TS_CAPTION | TX_SECONDARY | TX_ELLIPSIS, label);
1688 self.box_end();
1689 }
1690 self.box_end();
1691 }
1692
1693 fn lc_sample(&mut self, r: [i32; 4], c: u32, dash: u32, lw: i32) {
1695 let t = ((lw + 128) >> 8).max(1);
1696 let y = r[1] + ((r[3] - t) >> 1);
1697 self.draw_color(c);
1698 let pat = dash_pattern(dash);
1699 if pat.is_empty() {
1700 self.draw_rect(r[0], y, r[2], t);
1701 return;
1702 }
1703 let (mut x, mut k) = (0, 0);
1704 while x < r[2] {
1705 let len = ((pat[k % pat.len()] * t as f64) as i32).max(1);
1706 if k % 2 == 0 {
1707 self.draw_rect(r[0] + x, y, len.min(r[2] - x), t);
1708 }
1709 x += len;
1710 k += 1;
1711 }
1712 }
1713
1714 fn lc_x_ticks(&mut self, x: u8, v: (f64, f64), pw: i32, sep: u32) -> Vec<(f64, String)> {
1717 self.ts_font(TS_CAPTION);
1718 let room = self.dp(12);
1719 if x == LX_NUM {
1720 let pps = pw as f64 / (v.1 - v.0);
1721 let mut steps = (pw / self.dp(72).max(1)).clamp(1, 12) as u32;
1722 loop {
1723 let (_, step, _) = lc_nice(v.0, v.1, steps);
1724 let (k0, k1) = (ceil_f(v.0 / step - 1e-9), floor_f(v.1 / step + 1e-9));
1725 if k1 < k0 || k1 - k0 > 64.0 {
1726 return Vec::new();
1727 }
1728 let n = (k1 - k0) as u32;
1729 let labels = LcUnit::Num("").ticks(k0 * step, step, n, sep);
1730 let wmax = labels.iter().map(|s| self.text.width(s)).max().unwrap_or(0);
1731 if step * pps >= (wmax + room) as f64 || steps == 1 {
1732 return labels.into_iter().enumerate().map(|(i, s)| ((k0 + i as f64) * step, s)).collect();
1733 }
1734 steps -= 1;
1735 }
1736 }
1737 let xs = if x == LX_NANOS { 1e-9 } else { 1.0 };
1738 let (a, b) = (v.0 * xs, v.1 * xs);
1739 let pps = pw as f64 / (b - a);
1740 let hours = abs(a).max(abs(b)) >= 3600.0;
1741 let mut step = lc_time_step(self.dp(72) as f64 / pps);
1742 for _ in 0..12 {
1743 let dec = if step >= 1.0 { 0 } else { step_decimals(step) };
1744 let (k0, k1) = (ceil_f(a / step - 1e-9), floor_f(b / step + 1e-9));
1745 if k1 < k0 || k1 - k0 > 64.0 {
1746 break;
1747 }
1748 let ticks: Vec<(f64, String)> = (0..=(k1 - k0) as i64)
1749 .map(|i| {
1750 let t = (k0 + i as f64) * step;
1751 (t / xs, lc_time_text(t, dec, hours, sep))
1752 })
1753 .collect();
1754 let wmax = ticks.iter().map(|t| self.text.width(&t.1)).max().unwrap_or(0);
1755 if step * pps >= (wmax + room) as f64 {
1756 return ticks;
1757 }
1758 step = lc_time_step(step * 1.0001);
1759 }
1760 Vec::new()
1761 }
1762
1763 fn lc_summary(c: &LineChart, stats: &[Stat], empty: bool, sep: u32) -> String {
1766 let mut s = String::from(c.name);
1767 if !s.is_empty() {
1768 s.push_str(" – ");
1769 }
1770 let none = if c.empty.is_empty() { tr("Keine Daten") } else { c.empty };
1771 if empty || c.series.is_empty() {
1772 s.push_str(none);
1773 return s;
1774 }
1775 for (i, (ser, st)) in c.series.iter().zip(stats).enumerate() {
1776 if i > 0 {
1777 s.push_str("; ");
1778 }
1779 s.push_str(crate::basic::cstr(ser.label));
1780 s.push_str(": ");
1781 if st.has {
1782 let u = ser.unit;
1783 s.push_str(&format!(
1784 "{} {}, {} {}, {} {}",
1785 tr("aktuell"),
1786 u.text(st.last, sep),
1787 tr("min."),
1788 u.text(st.lo, sep),
1789 tr("max."),
1790 u.text(st.hi, sep)
1791 ));
1792 } else {
1793 s.push_str(none);
1794 }
1795 }
1796 s
1797 }
1798
1799 fn lc_tag(&mut self, rect: [i32; 4], label: &str, c: u32, lx: impl Fn(i32, i32) -> i32) {
1802 let hc = self.high_contrast;
1803 let rad = self.dp(3);
1804 let (fill, stroke, t) = if hc {
1805 (self.color(T_BG), self.color(T_TEXT), self.hair())
1806 } else {
1807 (color_mix(card_opaque(self), c | 0xFF00_0000, 52), 0, 0)
1808 };
1809 self.cmd_rrect(rect[0], rect[1], rect[2], rect[3], rad, t, fill, stroke, stroke);
1810 let tc = self.color(T_TEXT);
1811 let pad = self.dp(5);
1812 text_at(self, TS_CAPTION, rect[0] + pad, rect, label, tc, rect[2] - pad * 2);
1813 self.wp.ctx.last_rect = [lx(rect[0], rect[2]), rect[1], rect[2], rect[3]];
1814 self.wp.ctx.last_state = 0;
1815 self.sem(K_TEXT, 0, Some(label), 0);
1816 }
1817
1818 fn lc_keys(&mut self, c: &LineChart, v: (f64, f64), cur: Option<(usize, f64)>, rtl: bool) -> Option<(usize, f64)> {
1823 let swap = rtl && self.mods & (MOD_CTRL | MOD_ALT) == 0;
1824 let (kp, kn) = if swap { (VK_RIGHT, VK_LEFT) } else { (VK_LEFT, VK_RIGHT) };
1825 let n = c.series.len();
1826 let last = |s: usize| {
1827 let d = c.series[s].data;
1828 lc_step(d, d.upper_bound(v.1), true, v).map(|t| (s, t))
1829 };
1830 let mut q = cur;
1831 let mut moved = false;
1832 for (key, back) in [(kp, true), (kn, false)] {
1833 if self.key(key) {
1834 moved = true;
1835 q = match q {
1836 Some((s, t)) => {
1837 let d = c.series[s].data;
1838 let i = if back { d.lower_bound(t) } else { d.upper_bound(t) };
1839 lc_step(d, i, back, v).map(|t| (s, t)).or(q)
1840 }
1841 None => last(0),
1842 };
1843 }
1844 }
1845 if self.key(VK_HOME) {
1846 moved = true;
1847 let s = q.map_or(0, |x| x.0);
1848 let d = c.series[s].data;
1849 q = lc_step(d, d.lower_bound(v.0), false, v).map(|t| (s, t)).or(q);
1850 }
1851 if self.key(VK_END) {
1852 moved = true;
1853 q = last(q.map_or(0, |x| x.0)).or(q);
1854 }
1855 for (key, up) in [(VK_UP, true), (VK_DOWN, false)] {
1856 if self.key(key) {
1857 moved = true;
1858 let (s, t) = q.unwrap_or((0, v.1));
1859 let s2 = if up { s.saturating_sub(1) } else { (s + 1).min(n - 1) };
1860 let d = c.series[s2].data;
1861 let p = d.nearest(t).and_then(|i| d.get(i)).filter(|p| p.0 >= v.0 && p.0 <= v.1);
1862 q = p.map(|p| (s2, p.0)).or(q);
1863 }
1864 }
1865 if q.is_some() && self.key(VK_ESCAPE) {
1866 q = None;
1867 }
1868 if moved {
1869 self.wp.ctx.focus_visible = true;
1870 }
1871 q
1872 }
1873
1874 #[cfg_attr(jfgui_dev, track_caller)]
1878 pub fn line_chart(&mut self, id: u32, c: &LineChart) -> Option<(usize, f64)> {
1879 let _src = self.src_scope();
1880 let hc = self.high_contrast;
1881 let sep = self.m.value.decimal_sep;
1882 let lh = self.ts_line(TS_CAPTION);
1883 let hair = self.hair();
1884 let n = c.series.len();
1885 let empty = c.series.iter().all(|s| s.data.is_empty());
1886 let xs = if c.x == LX_NANOS { 1e-9 } else { 1.0 }; self.box_begin(0, BOX_V, 6);
1889 self.next_size(-1, -1);
1890 let r = self.place(self.dp(480), self.dp(200));
1891 let pal = self.style.data().chart_palette(self);
1893 let hcc = [self.color(T_ACCENT), self.color(T_TEXT), self.color(T_ACCENT_TEXT)];
1894 let looks: Vec<Look> = c
1895 .series
1896 .iter()
1897 .enumerate()
1898 .map(|(i, s)| {
1899 if hc {
1900 Look { color: hcc[i % 3], dash: HC_DASH[i % 4], area: false }
1901 } else {
1902 let color = if s.color != 0 { s.color } else { pal[s.pal.map_or(i, |k| k as usize) % 8] };
1903 Look { color, dash: DASH_SOLID, area: s.area }
1904 }
1905 })
1906 .collect();
1907 let lw = (if hc { self.dp_fx(512) } else { self.dp_fx(384) }).max(256);
1908 let (t2, tx) = (self.color(T_TEXT2), self.color(T_TEXT));
1909 let mut st = self.m.chart.lines.remove(&id).unwrap_or_default();
1910 let view = c.view();
1912 let mut h = Fnv::new();
1913 for s in c.series {
1914 h.u32(s.data.ver());
1915 }
1916 h.f64(view.0);
1917 h.f64(view.1);
1918 let skey = h.0;
1919 if st.stats_key != skey || st.stats.len() != n {
1920 st.stats = c.series.iter().map(|s| stat_of(s.data, view)).collect();
1921 st.stats_key = skey;
1922 }
1923 let mut h = Fnv::new();
1925 h.u64(skey);
1926 h.u32(sep);
1927 h.u32(crate::i18n::language());
1928 h.str(c.name);
1929 h.str(c.empty);
1930 h.u32(empty as u32);
1931 for s in c.series {
1932 h.str(s.label);
1933 match s.unit {
1934 LcUnit::Num(u) => h.str(u),
1935 LcUnit::Si(u) => {
1936 h.u32(1);
1937 h.str(u);
1938 }
1939 LcUnit::Secs => h.u32(2),
1940 }
1941 }
1942 if st.sum_key != h.0 {
1943 st.summary = Ui::lc_summary(c, &st.stats, empty, sep);
1944 st.sum_key = h.0;
1945 }
1946 let (mut lo, mut hi) = (f64::INFINITY, f64::NEG_INFINITY);
1948 for s in st.stats.iter().filter(|s| s.has) {
1949 lo = lo.min(s.lo);
1950 hi = hi.max(s.hi);
1951 }
1952 for t in c.thresholds.iter().filter(|t| t.value.is_finite()) {
1953 lo = lo.min(t.value);
1954 hi = hi.max(t.value);
1955 }
1956 let (flo, fhi) = (c.y_range.0.is_finite(), c.y_range.1.is_finite());
1957 if flo {
1958 lo = c.y_range.0;
1959 }
1960 if fhi {
1961 hi = c.y_range.1;
1962 }
1963 if !(lo <= hi) {
1964 (lo, hi) = if flo {
1965 (lo, lo + 1.0)
1966 } else if fhi {
1967 (hi - 1.0, hi)
1968 } else {
1969 (0.0, 1.0)
1970 };
1971 }
1972 if hi == lo {
1973 let d = abs(lo).max(1.0) * if flo || fhi { 1.0 } else { 0.1 };
1974 if !fhi {
1975 hi = lo + d;
1976 } else if !flo {
1977 lo = hi - d;
1978 } else {
1979 hi = lo + 1.0;
1980 }
1981 }
1982 let top = lh / 2;
1985 let ph = (r[3] - top - lh - self.dp(4)).max(1);
1986 let steps = (ph / (lh * 2).max(self.dp(28)).max(1)).clamp(1, 8) as u32;
1987 let (first, step, nst) = lc_nice(lo, hi, steps);
1988 let ylo = if flo { lo } else { first };
1989 let yhi = if fhi { hi } else { first + step * nst as f64 };
1990 let (ylo, yhi) = if yhi > ylo { (ylo, yhi) } else { (ylo, ylo + 1.0) };
1991 let (k0, k1) = (ceil_f(ylo / step - 1e-9), floor_f(yhi / step + 1e-9));
1992 let unit = c.series.first().map_or(LcUnit::Num(""), |s| s.unit);
1993 let ylab = if empty || k1 < k0 || k1 - k0 > 64.0 { Vec::new() } else { unit.ticks(k0 * step, step, (k1 - k0) as u32, sep) };
1994 self.ts_font(TS_CAPTION);
1995 let yw = ylab.iter().map(|s| self.text.width(s)).max().unwrap_or(0);
1996 let ylw = if yw > 0 { yw + self.dp(8) } else { 0 };
1997 let px0 = r[0] + ylw;
1998 let pw = (r[2] - ylw - self.dp(4)).max(1);
1999 let py0 = r[1] + top;
2000 let xt = if empty { Vec::new() } else { self.lc_x_ticks(c.x, view, pw, sep) };
2001 let sx = pw as f64 / (view.1 - view.0);
2002 let sy = ph as f64 / (yhi - ylo);
2003 let xpx = |x: f64| ((x - view.0) * sx).clamp(-1e6, 1e6);
2004 let ypx = |v: f64| (ph as f64 - (v - ylo) * sy).clamp(-1e6, 1e6);
2005 let mut grid: Vec<(i32, bool)> = Vec::new();
2007 let gy: Vec<i32> =
2008 (0..ylab.len()).map(|k| (floor_f(ypx((k0 + k as f64) * step) + 0.5) as i32).min(ph - hair).max(0)).collect();
2009 let gx: Vec<i32> = xt.iter().map(|t| (floor_f(xpx(t.0) + 0.5) as i32).min(pw - hair).max(0)).collect();
2010 if c.flags & LC_NO_GRID == 0 {
2011 grid.extend(gy.iter().map(|&y| (y, false)));
2012 grid.extend(gx.iter().map(|&x| (x, true)));
2013 }
2014 let mlw = self.dp(1).max(1);
2015 let mcolor = |m: &LcMarker| if hc { tx } else if m.color != 0 { m.color } else { t2 };
2016 let mut ranges: Vec<(i32, i32, u32)> = Vec::new();
2017 let mut rules: Vec<(i32, i32, u32)> = Vec::new();
2018 let mut tags: Vec<(i32, &str, u32)> = Vec::new(); for m in c.markers.iter().filter(|m| !empty && m.t0.is_finite()) {
2020 let (a, b) = (m.t0, mark_end(m));
2021 if b < view.0 || a > view.1 {
2022 continue;
2023 }
2024 let col = mcolor(m);
2025 let xa = floor_f(xpx(a) + 0.5) as i32;
2026 let rule = |x: i32| (x - mlw / 2).min(pw - mlw).max(0);
2027 if b > a {
2028 let xb = floor_f(xpx(b) + 0.5) as i32;
2029 let (ca, cb) = (xa.clamp(0, pw), xb.clamp(0, pw));
2030 if !hc && cb > ca {
2031 ranges.push((ca, cb, (col & 0x00FF_FFFF) | 0x1C00_0000));
2032 }
2033 let edge = if hc { col } else { (col & 0x00FF_FFFF) | 0x9000_0000 };
2034 for x in [xa, xb] {
2035 if x >= 0 && x <= pw {
2036 rules.push((rule(x), mlw, edge));
2037 }
2038 }
2039 tags.push((ca, m.label, col));
2040 } else {
2041 rules.push((rule(xa), mlw, col));
2042 tags.push((xa, m.label, col));
2043 }
2044 }
2045 let lt = self.dp(1).max(1);
2046 let mut limits: Vec<(i32, u32)> = Vec::new();
2047 let mut ltags: Vec<(i32, &str, u32)> = Vec::new(); for t in c.thresholds.iter().filter(|t| !empty && t.value.is_finite() && t.value >= ylo && t.value <= yhi) {
2049 let col = if hc { tx } else if t.color != 0 { t.color } else { t2 };
2050 let y = (floor_f(ypx(t.value) + 0.5) as i32 - lt / 2).min(ph - lt).max(0);
2051 limits.push((y, col));
2052 ltags.push((y + lt / 2, t.label, col));
2053 }
2054 let m = ((lw + 255) >> 8) + 1;
2056 let grid_c = self.color(T_DIVIDER);
2057 let mut h = Fnv::new();
2058 h.u64(skey);
2059 h.f64(ylo);
2060 h.f64(yhi);
2061 for v in [pw, ph, lw, hair, m, hc as i32, grid_c as i32, lt, mlw] {
2062 h.u32(v as u32);
2063 }
2064 for l in &looks {
2065 h.u32(l.color);
2066 h.u32(l.dash);
2067 h.u32(l.area as u32);
2068 }
2069 for &(p, v) in &grid {
2070 h.u32(p as u32 | (v as u32) << 31);
2071 }
2072 for &(a, b, col) in ranges.iter().chain(rules.iter()) {
2073 h.u32(a as u32);
2074 h.u32(b as u32);
2075 h.u32(col);
2076 }
2077 for &(y, col) in &limits {
2078 h.u32(y as u32);
2079 h.u32(col);
2080 }
2081 let okey = h.0;
2082 if st.obj_key != okey || st.obj.is_none() {
2083 let lwf = lw as f64 / 256.0;
2084 let mut areas = Vec::new();
2085 let mut traces = Vec::new();
2086 if !empty {
2087 for (i, s) in c.series.iter().enumerate() {
2088 let l = looks[i];
2089 let dense = st.stats[i].n > pw as usize * 2;
2090 let (polys, band, area) = trace_of(s.data, view, (pw, ph), (ylo, yhi), lwf, dense, l.area, dash_pattern(l.dash));
2091 if l.area && !area.is_empty() {
2092 areas.push((area, (l.color & 0x00FF_FFFF) | 0x2E00_0000));
2093 }
2094 traces.push(Trace { polys, band, color: l.color, width: lw, dash: l.dash });
2095 }
2096 }
2097 let lim = (lt, self.dp(4), self.dp(3));
2098 let plot = Plot { m, w: pw, h: ph, hair, grid, grid_c, dotted: hc, ranges, areas, traces, limits, lim, rules };
2099 st.obj = Some(Rc::new(plot));
2100 st.obj_key = okey;
2101 }
2102 self.ltr_begin(r[0], r[2]);
2104 let rtl = self.rtl;
2105 let lx = move |x: i32, w: i32| if rtl { r[0] + r[0] + r[2] - x - w } else { x };
2106 let ev = if c.flags & LC_NO_HOVER == 0 && !empty { self.hit_rect(id, lx(px0, pw), py0, pw, ph, HB_FOCUS | HB_MOVES) } else { 0 };
2107 let focused = ev & EV_FOCUS != 0;
2109 let mut cursor = st.cursor.filter(|q| focused && q.0 < n && q.1 >= view.0 && q.1 <= view.1);
2110 if focused && n > 0 {
2111 cursor = self.lc_keys(c, view, cursor, rtl);
2112 }
2113 let hours = abs(view.0 * xs).max(abs(view.1 * xs)) >= 3600.0;
2116 let point = cursor.and_then(|(s, t)| {
2117 let ser = &c.series[s];
2118 let v = ser.data.nearest(t).and_then(|i| ser.data.get(i))?;
2119 Some(format!("{}, {}: {}. ", crate::basic::cstr(ser.label), lc_x_label(c.x, t, hours, sep), ser.unit.text(v.1, sep)))
2120 });
2121 let label = match &point {
2122 Some(p) => format!("{}{}", p, st.summary),
2123 None => st.summary.clone(),
2124 };
2125 self.wp.ctx.last_rect = r;
2126 let mut ss = if ev & EV_HOVER != 0 { SS_HOVER as u32 } else { 0 };
2127 if focused {
2128 ss |= SS_FOCUSED as u32;
2129 }
2130 self.wp.ctx.last_state = ss;
2131 self.sem(K_CANVAS, id, Some(&label), n as i32);
2132 self.cmd_custom(px0 - m, py0 - m, pw + m * 2, ph + m * 2, st.obj.clone(), 0, okey as u32);
2133 for (k, s) in ylab.iter().enumerate() {
2136 self.ts_font(TS_CAPTION);
2137 let w = self.text.width(s);
2138 let y = py0 + gy[k] + hair / 2 - lh / 2;
2139 let x = px0 - self.dp(6) - w;
2140 text_at(self, TS_CAPTION, x, [x, y, w, lh], s, t2, 0);
2141 self.wp.ctx.last_rect = [lx(x, w), y, w, lh];
2142 self.wp.ctx.last_state = 0;
2143 self.sem(K_TEXT, 0, Some(s), 0);
2144 }
2145 let ly = py0 + ph + self.dp(4);
2146 let mut end = i32::MIN;
2147 for (k, (_, s)) in xt.iter().enumerate() {
2148 self.ts_font(TS_CAPTION);
2149 let w = self.text.width(s);
2150 let x = (px0 + gx[k] - w / 2).min(r[0] + r[2] - w).max(px0);
2151 if x < end + self.dp(4) {
2152 continue;
2153 }
2154 end = x + w;
2155 text_at(self, TS_CAPTION, x, [x, ly, w, lh], s, t2, 0);
2156 self.wp.ctx.last_rect = [lx(x, w), ly, w, lh];
2157 self.wp.ctx.last_state = 0;
2158 self.sem(K_TEXT, 0, Some(s), 0);
2159 }
2160 let pad = self.dp(5);
2163 let tsep = self.dp(4);
2164 let mut placed: Vec<[i32; 4]> = Vec::new();
2165 for &(y, label, col) in <ags {
2166 if label.is_empty() {
2167 continue;
2168 }
2169 self.ts_font(TS_CAPTION);
2170 let w = (self.text.width(label) + pad * 2).min(pw);
2171 let ty = (py0 + y - lh / 2).min(py0 + ph - lh).max(py0);
2172 let rect = [px0 + pw - w - self.dp(4), ty, w, lh];
2173 if rect[0] < px0 || placed.iter().any(|&p| near(p, rect, tsep)) {
2174 continue;
2175 }
2176 placed.push(rect);
2177 self.lc_tag(rect, label, col, lx);
2178 }
2179 let rows = ((ph / 3) / (lh + self.dp(2)).max(1)).clamp(1, 3);
2180 for &(x, label, col) in &tags {
2181 if label.is_empty() {
2182 continue;
2183 }
2184 self.ts_font(TS_CAPTION);
2185 let w = (self.text.width(label) + pad * 2).min(pw);
2186 let xm = px0 + x;
2187 let x0 = if xm + self.dp(4) + w <= px0 + pw { xm + self.dp(4) } else { (xm - self.dp(4) - w).max(px0) };
2188 for row in 0..rows {
2189 let rect = [x0, py0 + self.dp(2) + row * (lh + self.dp(2)), w, lh];
2190 if !placed.iter().any(|&p| near(p, rect, tsep)) {
2191 placed.push(rect);
2192 self.lc_tag(rect, label, col, lx);
2193 break;
2194 }
2195 }
2196 }
2197 if empty {
2199 let s = if c.empty.is_empty() { tr("Keine Daten") } else { c.empty };
2200 self.ts_font(TS_BODY);
2201 let lb = self.ts_line(TS_BODY);
2202 let w = self.text.width(s).min(r[2]);
2203 let (x, y) = (r[0] + (r[2] - w) / 2, r[1] + (r[3] - lb) / 2);
2204 text_at(self, TS_BODY, x, [x, y, w, lb], s, t2, r[2]);
2205 self.wp.ctx.last_rect = [lx(x, w), y, w, lb];
2206 self.wp.ctx.last_state = 0;
2207 self.sem(K_TEXT, 0, Some(s), 0);
2208 }
2209 let mut out = None;
2212 let probe = if ev & EV_HOVER != 0 {
2213 let mx = self.ltr_x(self.wp.ctx.mx);
2214 Some((view.0 + ((mx - px0) as f64 + 0.5) / sx, None, Some(self.wp.ctx.my)))
2215 } else {
2216 cursor.map(|(s, t)| (t, Some(s), None))
2217 };
2218 if let Some((tp, fixed, my)) = probe {
2219 let inside = |p: &(f64, f64)| p.0 >= view.0 && p.0 <= view.1;
2220 let snap = if fixed.is_some() {
2221 Some(tp)
2222 } else {
2223 c.series
2224 .iter()
2225 .filter_map(|s| s.data.nearest(tp).and_then(|i| s.data.get(i)).filter(inside))
2226 .map(|p| p.0)
2227 .min_by(|a, b| abs(a - tp).total_cmp(&abs(b - tp)))
2228 };
2229 if let Some(ts) = snap {
2230 let tol = self.dp(24) as f64 / sx;
2232 let at: Vec<Option<(f64, f64)>> = c
2233 .series
2234 .iter()
2235 .map(|s| s.data.nearest(ts).and_then(|i| s.data.get(i)).filter(|p| inside(p) && abs(p.0 - ts) <= tol))
2236 .collect();
2237 let best = fixed.or_else(|| {
2240 let my = my.unwrap_or(0) as f64;
2241 at.iter()
2242 .enumerate()
2243 .filter_map(|(i, p)| p.map(|p| (i, abs(py0 as f64 + ypx(p.1) - my))))
2244 .min_by(|a, b| a.1.total_cmp(&b.1))
2245 .map(|b| b.0)
2246 });
2247 if let Some(bi) = best {
2248 out = Some((bi, ts));
2249 if ev & EV_ACT != 0 && fixed.is_none() {
2250 cursor = Some((bi, ts));
2251 }
2252 }
2253 let my = my.unwrap_or_else(|| {
2255 let v = best.and_then(|b| at[b]).map_or(0.0, |p| ypx(p.1).clamp(0.0, ph as f64));
2256 py0 + v as i32
2257 });
2258 let cx = px0 + floor_f(xpx(ts) + 0.5) as i32;
2259 let cross = self.color(T_TEXT3);
2260 self.draw_color(cross);
2261 self.draw_rect(cx, py0, hair, ph);
2262 let ring = if hc { self.color(T_BG) } else { card_opaque(self) };
2263 let (ro, ri, grow) = (self.dp_fx(1152), self.dp_fx(768), self.dp_fx(256));
2264 for (i, p) in at.iter().enumerate() {
2265 let Some(p) = p else { continue };
2266 let y = ypx(p.1);
2267 if !(y >= 0.0 && y <= ph as f64) {
2268 continue;
2269 }
2270 let (fx, fy) = (((px0 as f64 + xpx(p.0)) * 256.0) as i32, ((py0 as f64 + y) * 256.0) as i32);
2271 let g = if Some(i) == best { grow } else { 0 };
2272 self.draw_color(ring);
2273 self.draw_circle(fx, fy, ro + g, 0);
2274 self.draw_color(looks[i].color);
2275 self.draw_circle(fx, fy, ri + g, 0);
2276 }
2277 let head = lc_x_label(c.x, ts, hours, sep);
2279 let mut rows: Vec<(u32, bool, &str, String)> = c
2280 .series
2281 .iter()
2282 .enumerate()
2283 .map(|(i, s)| (looks[i].color, false, crate::basic::cstr(s.label), at[i].map_or(String::from("–"), |p| s.unit.text(p.1, sep))))
2284 .collect();
2285 let half = self.dp(3) as f64 / sx;
2287 for mk in c.markers.iter().filter(|m| !m.label.is_empty() && m.t0.is_finite() && ts >= m.t0 - half && ts <= mark_end(m) + half) {
2288 let mut when = lc_x_label(c.x, mk.t0, hours, sep);
2289 if mk.t1 > mk.t0 {
2290 when = format!("{} – {}", when, lc_x_label(c.x, mk.t1, hours, sep));
2291 }
2292 rows.push((mcolor(mk), true, crate::basic::cstr(mk.label), when));
2293 }
2294 self.lc_card(r, cx, my, &head, &rows);
2295 }
2296 }
2297 if self.focus_ring(ev) {
2298 let s = self.style;
2299 s.focus_ring(self, [px0, py0, pw, ph], 4);
2300 }
2301 self.ltr_end();
2302 let hk = out.map(|(i, t): (usize, f64)| (i, t.to_bits()));
2306 let moved = cursor.map(|q| (q.0, q.1.to_bits())) != st.cursor.map(|q| (q.0, q.1.to_bits()));
2307 let rect = [px0, py0, pw, ph];
2308 if (st.hover != hk || moved) && st.rect == rect {
2309 self.repass();
2310 }
2311 st.hover = hk;
2312 st.cursor = cursor;
2313 st.rect = rect;
2314 if self.m.chart.lines.len() > 64 {
2315 self.m.chart.lines.clear();
2316 }
2317 self.m.chart.lines.insert(id, st);
2318 if c.flags & LC_NO_LEGEND == 0 && n > 0 {
2320 let items: Vec<(&str, u32)> = c.series.iter().enumerate().map(|(i, s)| (s.label, looks[i].color)).collect();
2321 let dashes: Vec<u32> = if hc { looks.iter().map(|l| l.dash).collect() } else { Vec::new() };
2322 self.legend_marks(&items, &dashes, lw);
2323 }
2324 self.box_end();
2325 out
2326 }
2327
2328 fn lc_card(&mut self, r: [i32; 4], cx: i32, my: i32, head: &str, rows: &[(u32, bool, &str, String)]) {
2333 let prev = self.layer_set(L_TIP);
2334 self.ltr_begin(r[0], r[2]);
2335 let rtl = self.rtl;
2336 let lx = move |x: i32, w: i32| if rtl { r[0] + r[0] + r[2] - x - w } else { x };
2337 let lh = self.ts_line(TS_CAPTION);
2338 let (padx, pady) = (self.dp(10), self.dp(6));
2339 let (dot, dgap, cgap) = (self.dp(8), self.dp(6), self.dp(16));
2340 self.ts_font(TS_CAPTION);
2341 let hw = self.text.width(head);
2342 let lws: Vec<i32> = rows.iter().map(|q| self.text.width(q.2)).collect();
2343 let vws: Vec<i32> = rows.iter().map(|q| self.text.width(&q.3)).collect();
2344 let (lmax, vmax) = (lws.iter().copied().max().unwrap_or(0), vws.iter().copied().max().unwrap_or(0));
2345 let w = (padx * 2 + hw.max(dot + dgap + lmax + cgap + vmax)).min(self.dp(360));
2346 let h = pady * 2 + lh * (1 + rows.len()) as i32;
2347 let (fw, fh) = (self.wp.rt.fb.w, self.wp.rt.fb.h);
2350 let d = if rtl { fw - r[0] - r[0] - r[2] } else { 0 };
2351 let (lo, hi) = (-d, fw - d);
2352 let mut x = cx + self.dp(14);
2353 if x + w > hi {
2354 x = cx - self.dp(14) - w;
2355 }
2356 let x = x.min(hi - w).max(lo);
2357 let y = (my - h / 2).min(fh - h).max(0);
2358 let st = self.style;
2360 let mut sp = Sp { label: Some(""), ..Sp::default() };
2361 sp.place([x, y, w, h]);
2362 st.tooltip(self, &sp);
2363 let (tx, t2) = (self.color(T_TEXT), self.color(T_TEXT2));
2364 let inner = w - padx * 2;
2365 let mut ty = y + pady;
2366 text_at(self, TS_CAPTION, x + padx, [x + padx, ty, inner, lh], head, t2, inner);
2367 self.wp.ctx.last_rect = [lx(x + padx, hw.min(inner)), ty, hw.min(inner), lh];
2368 self.wp.ctx.last_state = 0;
2369 self.sem(K_TEXT, 0, Some(head), 0);
2370 ty += lh;
2371 let dy = (lh - dot) / 2;
2372 for (k, (col, mark, label, value)) in rows.iter().enumerate() {
2373 self.draw_color(*col);
2374 if *mark {
2375 self.draw_rect(x + padx + dot / 2 - self.dp(2), ty + dy, self.dp(4), dot);
2376 } else {
2377 self.draw_circle(((x + padx) << 8) + (dot << 7), ((ty + dy) << 8) + (dot << 7), dot << 7, 0);
2378 }
2379 let lx0 = x + padx + dot + dgap;
2380 let vw = vws[k].min(inner);
2381 let room = (inner - dot - dgap - vw - cgap).max(1);
2382 text_at(self, TS_CAPTION, lx0, [lx0, ty, room, lh], label, tx, room);
2383 let vx = x + w - padx - vw;
2384 text_at(self, TS_CAPTION, vx, [vx, ty, vw, lh], value, if *mark { t2 } else { tx }, inner);
2385 let rec = format!("{}: {}", label, value);
2386 self.wp.ctx.last_rect = [lx(lx0, inner - dot - dgap), ty, inner - dot - dgap, lh];
2387 self.wp.ctx.last_state = 0;
2388 self.sem(K_TEXT, 0, Some(&rec), 0);
2389 ty += lh;
2390 }
2391 self.ltr_end();
2392 self.layer_set(prev);
2393 }
2394}