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 mut p = 1.0f32;
268 while p * 10.0 <= m {
269 p *= 10.0;
270 }
271 while p > m {
272 p /= 10.0;
273 }
274 for f in [1.0f32, 2.0, 2.5, 5.0, 10.0] {
275 let step = p * f / 2.0;
276 let mut n = (m / step) as i32;
277 if (n as f32) * step < m {
278 n += 1;
279 }
280 if n <= maxn {
281 return (step * n.max(1) as f32, n.max(1));
282 }
283 }
284 (p * 10.0, 4.min(maxn))
285}
286
287fn axis_labels(ws: &[i32], x0: i32, cw: i32, lo: i32, hi: i32, k: usize, sep: i32) -> Option<Vec<(usize, i32)>> {
291 let n = ws.len();
292 let mut cols: Vec<usize> = (0..n).step_by(k).collect();
293 if n > 1 && cols.last() != Some(&(n - 1)) {
294 if cols.len() > 1 {
295 cols.pop();
296 }
297 cols.push(n - 1);
298 }
299 let mut out: Vec<(usize, i32)> = Vec::with_capacity(cols.len());
300 for i in cols {
301 let x = (x0 + cw * i as i32 + ((cw - ws[i]) >> 1)).min(hi - ws[i]).max(lo);
302 if let Some(&(j, px)) = out.last() {
303 if x < px + ws[j] + sep {
304 return None;
305 }
306 }
307 out.push((i, x));
308 }
309 Some(out)
310}
311
312static SERIES_VER: AtomicU32 = AtomicU32::new(1);
315
316fn series_ver() -> u32 {
319 SERIES_VER.fetch_add(1, Ordering::Relaxed)
320}
321
322#[derive(Clone, Debug)]
328pub struct SeriesBuf {
329 pts: VecDeque<(f64, f64)>,
330 cap: usize,
331 ver: u32,
332}
333
334#[derive(Clone, Copy, Debug, PartialEq)]
337pub struct SeriesCol {
338 pub col: u32,
340 pub n: u32,
342 pub min: f64,
343 pub max: f64,
344 pub first: f64,
346 pub last: f64,
347 pub gap: bool,
350}
351
352fn min_max_of(parts: [&[(f64, f64)]; 2]) -> Option<(f64, f64)> {
354 let (mut lo, mut hi) = (f64::INFINITY, f64::NEG_INFINITY);
355 for part in parts {
356 for &(_, v) in part {
357 if v.is_finite() {
358 if v < lo {
359 lo = v;
360 }
361 if v > hi {
362 hi = v;
363 }
364 }
365 }
366 }
367 if lo <= hi { Some((lo, hi)) } else { None }
368}
369
370impl SeriesBuf {
371 pub fn new(capacity: usize) -> SeriesBuf {
373 SeriesBuf { pts: VecDeque::new(), cap: capacity.max(1), ver: series_ver() }
374 }
375
376 pub fn push(&mut self, t: f64, v: f64) -> bool {
379 if !t.is_finite() || self.pts.back().is_some_and(|p| t < p.0) {
380 return false;
381 }
382 let n = self.pts.len();
383 if n >= self.cap {
384 self.pts.pop_front();
385 } else if n == self.pts.capacity() {
386 self.pts.reserve_exact(n.max(16).min(self.cap - n));
388 }
389 self.pts.push_back((t, v));
390 self.ver = series_ver();
391 true
392 }
393
394 pub fn clear(&mut self) {
396 self.pts.clear();
397 self.ver = series_ver();
398 }
399
400 pub fn len(&self) -> usize {
402 self.pts.len()
403 }
404
405 pub fn is_empty(&self) -> bool {
407 self.pts.is_empty()
408 }
409
410 pub fn capacity(&self) -> usize {
412 self.cap
413 }
414
415 pub fn ver(&self) -> u32 {
417 self.ver
418 }
419
420 pub fn get(&self, i: usize) -> Option<(f64, f64)> {
422 self.pts.get(i).copied()
423 }
424
425 pub fn first(&self) -> Option<(f64, f64)> {
427 self.pts.front().copied()
428 }
429
430 pub fn last(&self) -> Option<(f64, f64)> {
432 self.pts.back().copied()
433 }
434
435 pub fn iter(&self) -> impl DoubleEndedIterator<Item = (f64, f64)> + ExactSizeIterator + '_ {
437 self.pts.iter().copied()
438 }
439
440 pub fn lower_bound(&self, t: f64) -> usize {
442 self.pts.partition_point(|p| p.0 < t)
443 }
444
445 fn upper_bound(&self, t: f64) -> usize {
447 self.pts.partition_point(|p| p.0 <= t)
448 }
449
450 fn slices(&self, a: usize, b: usize) -> [&[(f64, f64)]; 2] {
452 let (s0, s1) = self.pts.as_slices();
453 let n0 = s0.len();
454 let b = b.min(self.pts.len()).max(a);
455 if b <= n0 {
456 [&s0[a..b], &[]]
457 } else if a >= n0 {
458 [&s1[a - n0..b - n0], &[]]
459 } else {
460 [&s0[a..], &s1[..b - n0]]
461 }
462 }
463
464 pub fn nearest(&self, t: f64) -> Option<usize> {
466 let i = self.lower_bound(t);
467 let after = (i..self.pts.len()).find(|&k| self.pts[k].1.is_finite());
468 let before = (0..i).rev().find(|&k| self.pts[k].1.is_finite());
469 match (before, after) {
470 (Some(b), Some(a)) => Some(if t - self.pts[b].0 <= self.pts[a].0 - t { b } else { a }),
471 (b, a) => b.or(a),
472 }
473 }
474
475 pub fn min_max(&self) -> Option<(f64, f64)> {
477 min_max_of(self.slices(0, self.pts.len()))
478 }
479
480 pub fn min_max_in(&self, t0: f64, t1: f64) -> Option<(f64, f64)> {
482 let (a, b) = (self.lower_bound(t0), self.upper_bound(t1));
483 if a >= b {
484 return None;
485 }
486 min_max_of(self.slices(a, b))
487 }
488
489 pub fn columns(&self, t0: f64, t1: f64, n: usize) -> Vec<SeriesCol> {
494 let mut out: Vec<SeriesCol> = Vec::new();
495 if n == 0 || !(t1 > t0) {
496 return out;
497 }
498 let (a, b) = (self.lower_bound(t0), self.upper_bound(t1));
499 out.reserve(n.min(b.saturating_sub(a)));
500 let k = n as f64 / (t1 - t0);
501 let last = (n - 1).min(u32::MAX as usize - 1) as u32;
502 let lastf = last as f64;
503 let mut c = SeriesCol { col: u32::MAX, n: 0, min: 0.0, max: 0.0, first: 0.0, last: 0.0, gap: false };
505 let mut gap = false;
506 for part in self.slices(a, b) {
507 for &(t, v) in part {
508 if !v.is_finite() {
509 gap = true;
510 continue;
511 }
512 let f = (t - t0) * k;
513 let col = if f >= lastf { last } else if f > 0.0 { f as u32 } else { 0 };
514 if col == c.col && !gap {
515 c.n += 1;
516 if v < c.min {
517 c.min = v;
518 }
519 if v > c.max {
520 c.max = v;
521 }
522 c.last = v;
523 continue;
524 }
525 if c.n > 0 {
526 out.push(c);
527 }
528 c = SeriesCol { col, n: 1, min: v, max: v, first: v, last: v, gap };
529 gap = false;
530 }
531 }
532 if c.n > 0 {
533 out.push(c);
534 }
535 out
536 }
537}
538
539#[derive(Clone, Copy, PartialEq, Eq, Debug)]
543#[non_exhaustive]
544pub enum LcUnit<'a> {
545 Num(&'a str),
548 Si(&'a str),
550 Secs,
552}
553
554const PREFIX: [(f64, &str); 5] = [(1.0, ""), (1e3, "k"), (1e6, "M"), (1e9, "G"), (1e12, "T")];
556const DURATION: [(f64, &str); 4] = [(1e-9, "ns"), (1e-6, "µs"), (1e-3, "ms"), (1.0, "s")];
557static POW10F: [f64; 10] = [1.0, 10.0, 100.0, 1e3, 1e4, 1e5, 1e6, 1e7, 1e8, 1e9];
558static 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];
559
560fn abs(x: f64) -> f64 {
561 if x < 0.0 { -x } else { x }
562}
563
564fn floor_f(x: f64) -> f64 {
565 let f = x as i64 as f64;
566 if f > x { f - 1.0 } else { f }
567}
568
569fn ceil_f(x: f64) -> f64 {
570 -floor_f(-x)
571}
572
573fn pow10(e: i32) -> f64 {
575 let mut p = 1.0f64;
576 for _ in 0..e.unsigned_abs().min(320) {
577 p *= 10.0;
578 }
579 if e < 0 { 1.0 / p } else { p }
580}
581
582fn sig_decimals(x: f64) -> u32 {
584 if !(x > 0.0) || x >= 100.0 {
585 0
586 } else if x >= 10.0 {
587 1
588 } else if x >= 1.0 {
589 2
590 } else {
591 let (mut d, mut s) = (2, x);
592 while s < 1.0 && d < 6 {
593 s *= 10.0;
594 d += 1;
595 }
596 d
597 }
598}
599
600fn step_decimals(x: f64) -> u32 {
602 let mut s = abs(x);
603 let mut d = 0;
604 while d < 9 && s > 0.0 && abs(s - floor_f(s + 0.5)) > s * 1e-9 {
605 s *= 10.0;
606 d += 1;
607 }
608 d
609}
610
611fn trim_zeros(s: String, sep: u32) -> String {
613 let Some(c) = char::from_u32(sep) else { return s };
614 if !s.contains(c) {
615 return s;
616 }
617 String::from(s.trim_end_matches('0').trim_end_matches(c))
618}
619
620impl LcUnit<'_> {
621 fn scales(&self) -> &'static [(f64, &'static str)] {
622 if *self == LcUnit::Secs { &DURATION } else { &PREFIX }
623 }
624
625 fn base(&self) -> usize {
627 if *self == LcUnit::Secs { 3 } else { 0 }
628 }
629
630 fn scale_of(&self, m: f64) -> usize {
632 if !(m > 0.0) {
633 return self.base();
634 }
635 let s = self.scales();
636 let mut k = 0;
637 while k + 1 < s.len() && m >= s[k + 1].0 * 0.999_999_999 {
638 k += 1;
639 }
640 k
641 }
642
643 fn unit_text(&self, num: &str, k: usize) -> String {
645 let p = self.scales()[k].1;
646 match *self {
647 LcUnit::Num("") => format!("{num}{p}"),
648 LcUnit::Num(sfx) => format!("{num}{p} {sfx}"),
649 LcUnit::Si(u) if p.is_empty() && u.is_empty() => String::from(num),
650 LcUnit::Si(u) => format!("{num} {p}{u}"),
651 LcUnit::Secs => format!("{num} {p}"),
652 }
653 }
654
655 pub fn text(&self, v: f64, sep: u32) -> String {
659 if !v.is_finite() {
660 return String::from("–");
661 }
662 let a = abs(v);
663 let s = self.scales();
664 let mut k = self.scale_of(a);
665 loop {
666 let x = a / s[k].0;
667 let d = sig_decimals(x);
668 let p = POW10F[d as usize];
669 let r = floor_f(x * p + 0.5) / p;
670 if r >= 1000.0 && k + 1 < s.len() {
671 k += 1;
672 continue;
673 }
674 let num = trim_zeros(fmt_f64_sep(if v < 0.0 { -r } else { r }, d, sep), sep);
675 return self.unit_text(&num, k);
676 }
677 }
678
679 pub fn ticks(&self, first: f64, step: f64, n: u32, sep: u32) -> Vec<String> {
683 let m = abs(first).max(abs(first + step * n as f64));
684 let m = if m > 0.0 { m } else { abs(step) };
685 let k = self.scale_of(if matches!(self, LcUnit::Num(_)) { m / 10.0 } else { m });
686 let sc = self.scales()[k].0;
687 let d = step_decimals(step / sc).min(6);
688 (0..=n)
689 .map(|i| {
690 let v = first + step * i as f64;
691 if abs(v) <= abs(step) * 1e-6 {
692 let z = if matches!(self, LcUnit::Num(_)) { self.base() } else { k };
693 return self.unit_text("0", z);
694 }
695 self.unit_text(&fmt_f64_sep(v / sc, d, sep), k)
696 })
697 .collect()
698 }
699}
700
701pub fn lc_nice(lo: f64, hi: f64, steps: u32) -> (f64, f64, u32) {
705 let m = steps.max(1) as f64;
706 let (lo, hi) = if lo <= hi { (lo, hi) } else { (hi, lo) };
707 let span = hi - lo;
708 if !span.is_finite() || !lo.is_finite() {
709 return (0.0, 1.0, 1);
710 }
711 let span = if span > 0.0 { span } else if lo != 0.0 { abs(lo) } else { 1.0 };
712 let raw = span / m;
713 let mut e = 0i32;
714 while e < 300 && pow10(e + 1) <= raw {
715 e += 1;
716 }
717 while e > -300 && pow10(e) > raw {
718 e -= 1;
719 }
720 let p = pow10(e);
721 for f in [1.0, 2.0, 2.5, 5.0, 10.0, 20.0, 25.0, 50.0, 100.0] {
722 let step = f * p;
723 let a = floor_f(lo / step + 1e-9);
724 let b = ceil_f(hi / step - 1e-9);
725 let n = (b - a).max(1.0);
726 if n <= m {
727 return (a * step, step, n as u32);
728 }
729 }
730 let step = 100.0 * p;
731 let a = floor_f(lo / step + 1e-9);
732 (a * step, step, (ceil_f(hi / step - 1e-9) - a).max(1.0) as u32)
733}
734
735const CLOCK: [f64; 18] =
738 [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];
739
740pub fn lc_time_step(min: f64) -> f64 {
744 if !(min > 1e-9) {
745 return 1e-9;
746 }
747 if min <= 1.0 {
748 for e in -9..0 {
749 for f in [1.0, 2.0, 5.0] {
750 let s = f * pow10(e);
751 if s >= min * (1.0 - 1e-12) {
752 return s;
753 }
754 }
755 }
756 return 1.0;
757 }
758 for s in CLOCK {
759 if s >= min {
760 return s;
761 }
762 }
763 let d = min / 86400.0;
764 for e in 0..300 {
765 for f in [1.0, 2.0, 5.0] {
766 if f * pow10(e) >= d {
767 return f * pow10(e) * 86400.0;
768 }
769 }
770 }
771 min
772}
773
774pub fn lc_time_text(t: f64, dec: u32, hours: bool, sep: u32) -> String {
777 let dec = dec.min(9);
778 let p = POW10U[dec as usize];
779 let neg = t < 0.0;
780 let u = (abs(t) * p as f64 + 0.5) as u64;
781 let (whole, frac) = (u / p, u % p);
782 let mut s = String::new();
783 if neg && u != 0 {
784 s.push('-');
785 }
786 if hours {
787 s.push_str(&format!("{}:{:02}:{:02}", whole / 3600, whole / 60 % 60, whole % 60));
788 } else {
789 s.push_str(&format!("{}:{:02}", whole / 60, whole % 60));
790 }
791 if dec > 0 {
792 if let Some(c) = char::from_u32(sep) {
793 s.push(c);
794 }
795 s.push_str(&format!("{:0w$}", frac, w = dec as usize));
796 }
797 s
798}
799
800pub 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)]
813pub struct LcSeries<'a> {
814 pub label: &'a str,
815 pub data: &'a SeriesBuf,
816 pub color: u32,
818 pub pal: Option<u8>,
819 pub area: bool,
821 pub unit: LcUnit<'a>,
823}
824
825impl<'a> LcSeries<'a> {
826 pub const fn new(label: &'a str, data: &'a SeriesBuf) -> LcSeries<'a> {
828 LcSeries { label, data, color: 0, pal: None, area: false, unit: LcUnit::Num("") }
829 }
830 pub const fn color(mut self, c: u32) -> LcSeries<'a> {
832 self.color = c;
833 self
834 }
835 pub const fn pal(mut self, k: u8) -> LcSeries<'a> {
837 self.pal = Some(k);
838 self
839 }
840 pub const fn area(mut self) -> LcSeries<'a> {
842 self.area = true;
843 self
844 }
845 pub const fn unit(mut self, u: LcUnit<'a>) -> LcSeries<'a> {
847 self.unit = u;
848 self
849 }
850}
851
852#[derive(Clone, Copy, Debug)]
854pub struct LcMarker<'a> {
855 pub t0: f64,
856 pub t1: f64,
858 pub label: &'a str,
860 pub color: u32,
862}
863
864impl<'a> LcMarker<'a> {
865 pub const fn line(t: f64, label: &'a str) -> LcMarker<'a> {
867 LcMarker { t0: t, t1: t, label, color: 0 }
868 }
869 pub const fn range(t0: f64, t1: f64, label: &'a str) -> LcMarker<'a> {
871 LcMarker { t0, t1, label, color: 0 }
872 }
873 pub const fn color(mut self, c: u32) -> LcMarker<'a> {
875 self.color = c;
876 self
877 }
878}
879
880#[derive(Clone, Copy, Debug)]
882pub struct LcThreshold<'a> {
883 pub value: f64,
884 pub label: &'a str,
885 pub color: u32,
887}
888
889impl<'a> LcThreshold<'a> {
890 pub const fn new(value: f64, label: &'a str) -> LcThreshold<'a> {
892 LcThreshold { value, label, color: 0 }
893 }
894 pub const fn color(mut self, c: u32) -> LcThreshold<'a> {
896 self.color = c;
897 self
898 }
899}
900
901#[derive(Clone, Copy, Debug)]
904pub struct LineChart<'a> {
905 pub series: &'a [LcSeries<'a>],
906 pub x: u8,
908 pub x_range: (f64, f64),
910 pub x_span: f64,
912 pub y_range: (f64, f64),
915 pub markers: &'a [LcMarker<'a>],
916 pub thresholds: &'a [LcThreshold<'a>],
917 pub flags: u32,
919 pub name: &'a str,
921 pub empty: &'a str,
923}
924
925impl<'a> LineChart<'a> {
926 pub const fn new(series: &'a [LcSeries<'a>]) -> LineChart<'a> {
928 LineChart {
929 series,
930 x: LX_SECS,
931 x_range: (0.0, 0.0),
932 x_span: 0.0,
933 y_range: (f64::NAN, f64::NAN),
934 markers: &[],
935 thresholds: &[],
936 flags: 0,
937 name: "",
938 empty: "",
939 }
940 }
941 pub const fn x_axis(mut self, x: u8) -> LineChart<'a> {
943 self.x = x;
944 self
945 }
946 pub const fn x_range(mut self, lo: f64, hi: f64) -> LineChart<'a> {
948 self.x_range = (lo, hi);
949 self
950 }
951 pub const fn follow(mut self, span: f64) -> LineChart<'a> {
953 self.x_span = span;
954 self
955 }
956 pub const fn y_range(mut self, lo: f64, hi: f64) -> LineChart<'a> {
958 self.y_range = (lo, hi);
959 self
960 }
961 pub const fn markers(mut self, m: &'a [LcMarker<'a>]) -> LineChart<'a> {
963 self.markers = m;
964 self
965 }
966 pub const fn thresholds(mut self, t: &'a [LcThreshold<'a>]) -> LineChart<'a> {
968 self.thresholds = t;
969 self
970 }
971 pub const fn flags(mut self, f: u32) -> LineChart<'a> {
973 self.flags = f;
974 self
975 }
976 pub const fn name(mut self, n: &'a str) -> LineChart<'a> {
978 self.name = n;
979 self
980 }
981 pub const fn empty(mut self, s: &'a str) -> LineChart<'a> {
983 self.empty = s;
984 self
985 }
986
987 pub fn view(&self) -> (f64, f64) {
991 let (a, b) = self.x_range;
992 if a.is_finite() && b.is_finite() && b > a {
993 return (a, b);
994 }
995 let one = match self.x {
996 LX_NANOS => 1e9,
997 _ => 1.0,
998 };
999 let (mut lo, mut hi) = (f64::INFINITY, f64::NEG_INFINITY);
1000 for s in self.series {
1001 if let (Some(f), Some(l)) = (s.data.first(), s.data.last()) {
1002 lo = lo.min(f.0);
1003 hi = hi.max(l.0);
1004 }
1005 }
1006 let span = if self.x_span.is_finite() && self.x_span > 0.0 { self.x_span } else { 0.0 };
1007 let v = if lo > hi {
1008 (0.0, if span > 0.0 { span } else { one })
1009 } else if span > 0.0 {
1010 let end = hi.max(lo + span);
1011 (end - span, end)
1012 } else if hi > lo {
1013 (lo, hi)
1014 } else {
1015 (lo - one / 2.0, lo + one / 2.0)
1016 };
1017 if v.1 > v.0 { v } else { (v.0, v.0 + one) }
1018 }
1019}
1020
1021#[derive(Default)]
1024pub(crate) struct LcState {
1025 stats_key: u64,
1026 stats: Vec<Stat>,
1027 sum_key: u64,
1028 summary: String,
1029 obj_key: u64,
1030 obj: Option<DrawRef>,
1031 hover: Option<(usize, u64)>,
1033 cursor: Option<(usize, f64)>,
1035 rect: [i32; 4],
1037}
1038
1039#[derive(Clone, Copy, Default)]
1041struct Stat {
1042 lo: f64,
1043 hi: f64,
1044 last: f64,
1045 has: bool,
1046 n: usize,
1047}
1048
1049fn stat_of(d: &SeriesBuf, v: (f64, f64)) -> Stat {
1050 let (a, b) = (d.lower_bound(v.0), d.upper_bound(v.1));
1051 let mut s = Stat { n: b.saturating_sub(a), ..Stat::default() };
1052 if let Some((lo, hi)) = d.min_max_in(v.0, v.1) {
1053 s.lo = lo;
1054 s.hi = hi;
1055 s.has = true;
1056 s.last = (a..b).rev().filter_map(|i| d.get(i)).map(|p| p.1).find(|x| x.is_finite()).unwrap_or(f64::NAN);
1057 }
1058 s
1059}
1060
1061#[derive(Clone, Copy)]
1063struct Look {
1064 color: u32,
1065 dash: u32,
1066 area: bool,
1067}
1068
1069const HC_DASH: [u32; 4] = [DASH_SOLID, DASH_DASH, DASH_DOT, DASH_DASHDOT];
1072
1073struct Trace {
1075 polys: Vec<Vec<(f32, f32)>>,
1076 band: bool,
1077 color: u32,
1078 width: i32,
1079 dash: u32,
1080}
1081
1082struct Plot {
1085 m: i32,
1086 w: i32,
1087 h: i32,
1088 hair: i32,
1089 grid: Vec<(i32, bool)>,
1091 grid_c: u32,
1092 dotted: bool,
1093 ranges: Vec<(i32, i32, u32)>,
1095 areas: Vec<(Vec<Vec<(f32, f32)>>, u32)>,
1096 traces: Vec<Trace>,
1097 limits: Vec<(i32, u32)>,
1099 lim: (i32, i32, i32),
1100 rules: Vec<(i32, i32, u32)>,
1102}
1103
1104fn trace_path(path: &mut Path, polys: &[Vec<(f32, f32)>], ox: f32, oy: f32, close: bool) {
1106 path.reset();
1107 path.set_origin(0, 0);
1108 for p in polys {
1109 for (k, &(x, y)) in p.iter().enumerate() {
1110 if k == 0 {
1111 path.move_f(ox + x, oy + y);
1112 } else {
1113 path.line_f(ox + x, oy + y);
1114 }
1115 }
1116 if close {
1117 path.close();
1118 }
1119 }
1120}
1121
1122impl Draw for Plot {
1123 fn draw(&self, g: &mut Gfx, r: [i32; 4]) {
1124 let (ox, oy) = (r[0] + self.m, r[1] + self.m);
1125 let (w, h, t) = (self.w, self.h, self.hair);
1126 g.dc.set_color(self.grid_c);
1127 for &(p, vert) in &self.grid {
1128 if self.dotted {
1129 let n = if vert { h } else { w };
1131 let mut k = 0;
1132 while k < n {
1133 if vert {
1134 g.dc.fill(g.sf, ox + p, oy + k, t, t);
1135 } else {
1136 g.dc.fill(g.sf, ox + k, oy + p, t, t);
1137 }
1138 k += t * 3;
1139 }
1140 } else if vert {
1141 g.dc.fill(g.sf, ox + p, oy, t, h);
1142 } else {
1143 g.dc.fill(g.sf, ox, oy + p, w, t);
1144 }
1145 }
1146 for &(x0, x1, c) in &self.ranges {
1147 g.dc.set_color(c);
1148 g.dc.fill(g.sf, ox + x0, oy, x1 - x0, h);
1149 }
1150 let (fx, fy) = (ox as f32, oy as f32);
1151 for (polys, c) in &self.areas {
1152 trace_path(&mut g.text.path, polys, fx, fy, true);
1153 g.dc.set_color(*c);
1154 g.text.path.fill(&mut g.text.ras, g.dc, g.sf, gfx::RULE_NONZERO);
1155 }
1156 for tr in &self.traces {
1157 trace_path(&mut g.text.path, &tr.polys, fx, fy, tr.band);
1158 g.dc.set_color(tr.color);
1159 if tr.band {
1160 g.text.path.fill(&mut g.text.ras, g.dc, g.sf, gfx::RULE_NONZERO);
1161 } else if tr.dash == DASH_SOLID {
1162 g.text.path.stroke(&mut g.text.ras, g.dc, g.sf, tr.width, gfx::JOIN_ROUND | gfx::CAP_ROUND << 4);
1163 } else {
1164 let st = DStyle {
1165 width: tr.width as f64 / 256.0,
1166 pat: dash_pattern(tr.dash),
1167 join: gfx::JOIN_ROUND,
1168 cap0: LCAP_FLAT,
1169 cap1: LCAP_FLAT,
1170 dcap: LCAP_FLAT,
1171 offset: 0.0,
1172 miter: 0.0,
1173 };
1174 dash_stroke(&mut g.text.path, &st);
1175 g.text.path.fill(&mut g.text.ras, g.dc, g.sf, gfx::RULE_NONZERO);
1176 }
1177 }
1178 let (lt, on, off) = self.lim;
1179 for &(y, c) in &self.limits {
1180 g.dc.set_color(c);
1181 let mut x = 0;
1182 while x < w {
1183 g.dc.fill(g.sf, ox + x, oy + y, on.min(w - x), lt);
1184 x += on + off;
1185 }
1186 }
1187 for &(x, lw, c) in &self.rules {
1188 g.dc.set_color(c);
1189 g.dc.fill(g.sf, ox + x, oy, lw, h);
1190 }
1191 }
1192}
1193
1194fn polyline(run: &mut [(f64, f64)], w: f64) -> Vec<(f32, f32)> {
1197 const M: f64 = 64.0;
1198 let n = run.len();
1199 if n >= 2 {
1200 let (a, b) = (run[0], run[1]);
1201 if a.0 < -M && b.0 > a.0 {
1202 let f = (-M - a.0) / (b.0 - a.0);
1203 run[0] = (-M, a.1 + (b.1 - a.1) * f);
1204 }
1205 let (a, b) = (run[n - 2], run[n - 1]);
1206 if b.0 > w + M && b.0 > a.0 {
1207 let f = (w + M - a.0) / (b.0 - a.0);
1208 run[n - 1] = (w + M, a.1 + (b.1 - a.1) * f);
1209 }
1210 }
1211 if n == 1 {
1212 let (x, y) = run[0];
1214 return alloc::vec![((x - 0.5) as f32, y as f32), ((x + 0.5) as f32, y as f32)];
1215 }
1216 run.iter().map(|&(x, y)| (x as f32, y as f32)).collect()
1217}
1218
1219type Polys = Vec<Vec<(f32, f32)>>;
1220
1221#[allow(clippy::too_many_arguments)]
1227fn 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) {
1228 let sx = w as f64 / (v.1 - v.0);
1229 let sy = h as f64 / (y.1 - y.0);
1230 let hf = h as f64;
1231 let fy = |val: f64| (hf - (val - y.0) * sy).clamp(-hf - 64.0, hf * 2.0 + 64.0);
1232 let base = fy(0.0f64.clamp(y.0, y.1));
1233 let mut lines: Vec<Vec<(f32, f32)>> = Vec::new();
1234 let mut areas: Vec<Vec<(f32, f32)>> = Vec::new();
1235 if !dense {
1236 let a = d.lower_bound(v.0).saturating_sub(1);
1237 let b = (d.upper_bound(v.1) + 1).min(d.len());
1238 let mut run: Vec<(f64, f64)> = Vec::new();
1239 let mut flush = |run: &mut Vec<(f64, f64)>| {
1240 if run.is_empty() {
1241 return;
1242 }
1243 let p = polyline(run, w as f64);
1244 if area && p.len() >= 2 {
1245 let mut q = p.clone();
1246 q.push((p[p.len() - 1].0, base as f32));
1247 q.push((p[0].0, base as f32));
1248 areas.push(q);
1249 }
1250 lines.push(p);
1251 run.clear();
1252 };
1253 for i in a..b {
1254 let Some((t, val)) = d.get(i) else { break };
1255 if !val.is_finite() {
1256 flush(&mut run);
1257 continue;
1258 }
1259 run.push(((t - v.0) * sx, fy(val)));
1260 }
1261 flush(&mut run);
1262 return (lines, false, areas);
1263 }
1264 let cols = d.columns(v.0, v.1, w.max(1) as usize);
1265 let half = lw / 2.0;
1266 let pat: Vec<f64> = dash.iter().map(|k| k * lw).collect();
1268 let period: f64 = pat.iter().sum::<f64>() * if pat.len() % 2 == 1 { 2.0 } else { 1.0 };
1269 let on = |x: f64| -> bool {
1270 if pat.is_empty() || !(period > 0.0) {
1271 return true;
1272 }
1273 let mut p = x - floor_f(x / period) * period;
1274 let mut k = 0;
1275 loop {
1276 let len = pat[k % pat.len()];
1277 if p < len {
1278 return k % 2 == 0;
1279 }
1280 p -= len;
1281 k += 1;
1282 if k > 64 {
1283 return true;
1284 }
1285 }
1286 };
1287 let mut i = 0;
1288 while i < cols.len() {
1289 let mut j = i + 1;
1290 while j < cols.len() && !cols[j].gap {
1291 j += 1;
1292 }
1293 let mut ext: Vec<(f64, f64, f64)> = Vec::with_capacity(j - i);
1295 for k in i..j {
1296 let c = cols[k];
1297 let (mut lo, mut hi) = (c.min, c.max);
1298 if k + 1 < j {
1299 lo = lo.min(cols[k + 1].first);
1300 hi = hi.max(cols[k + 1].first);
1301 }
1302 ext.push((c.col as f64 + 0.5, fy(hi), fy(lo)));
1303 }
1304 if area {
1305 let mut q: Vec<(f32, f32)> = Vec::with_capacity(ext.len() + 4);
1306 q.push(((ext[0].0 - 0.5) as f32, ext[0].1 as f32));
1307 q.extend(ext.iter().map(|e| (e.0 as f32, e.1 as f32)));
1308 let l = ext[ext.len() - 1];
1309 q.push(((l.0 + 0.5) as f32, l.1 as f32));
1310 q.push(((l.0 + 0.5) as f32, base as f32));
1311 q.push(((ext[0].0 - 0.5) as f32, base as f32));
1312 areas.push(q);
1313 }
1314 let mut k = 0;
1316 while k < ext.len() {
1317 if !on(ext[k].0) {
1318 k += 1;
1319 continue;
1320 }
1321 let mut e = k + 1;
1322 while e < ext.len() && on(ext[e].0) {
1323 e += 1;
1324 }
1325 let part = &ext[k..e];
1326 let mut q: Vec<(f32, f32)> = Vec::with_capacity(part.len() * 2 + 4);
1327 q.push(((part[0].0 - 0.5) as f32, (part[0].1 - half) as f32));
1328 q.extend(part.iter().map(|p| (p.0 as f32, (p.1 - half) as f32)));
1329 let l = part[part.len() - 1];
1330 q.push(((l.0 + 0.5) as f32, (l.1 - half) as f32));
1331 q.push(((l.0 + 0.5) as f32, (l.2 + half) as f32));
1332 q.extend(part.iter().rev().map(|p| (p.0 as f32, (p.2 + half) as f32)));
1333 q.push(((part[0].0 - 0.5) as f32, (part[0].2 + half) as f32));
1334 lines.push(q);
1335 k = e;
1336 }
1337 i = j;
1338 }
1339 (lines, true, areas)
1340}
1341
1342fn near(a: [i32; 4], b: [i32; 4], sep: i32) -> bool {
1344 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]
1345}
1346
1347fn lc_x_label(x: u8, t: f64, hours: bool, sep: u32) -> String {
1350 if x == LX_NUM {
1351 return LcUnit::Num("").text(t, sep);
1352 }
1353 let s = if x == LX_NANOS { t * 1e-9 } else { t };
1354 let full = lc_time_text(s, 9, hours, sep);
1355 match char::from_u32(sep) {
1356 Some(c) => String::from(full.trim_end_matches('0').trim_end_matches(c)),
1357 None => full,
1358 }
1359}
1360
1361fn mark_end(m: &LcMarker) -> f64 {
1363 if m.t1 > m.t0 { m.t1 } else { m.t0 }
1364}
1365
1366fn lc_step(d: &SeriesBuf, i: usize, back: bool, v: (f64, f64)) -> Option<f64> {
1368 let mut k = i;
1369 loop {
1370 if back {
1371 k = k.checked_sub(1)?;
1372 }
1373 let (t, val) = d.get(k)?;
1374 if (back && t < v.0) || (!back && t > v.1) {
1375 return None;
1376 }
1377 if val.is_finite() && t >= v.0 && t <= v.1 {
1378 return Some(t);
1379 }
1380 if !back {
1381 k += 1;
1382 }
1383 }
1384}
1385
1386impl Ui {
1387 fn chart_color(&self, c: u32, i: usize) -> u32 {
1388 if c != 0 {
1389 return c;
1390 }
1391 let p = self.style.data().chart_palette(self);
1392 p[i % 8]
1393 }
1394
1395 fn chart_obj(&mut self, id: u32, hash: u64, make: impl FnOnce() -> DrawRef) -> DrawRef {
1397 if let Some((h, d)) = self.m.chart.cache.get(&id) {
1398 if *h == hash {
1399 return d.clone();
1400 }
1401 }
1402 let d = make();
1403 if self.m.chart.cache.len() > 256 {
1404 self.m.chart.cache.clear();
1405 }
1406 self.m.chart.cache.insert(id, (hash, d.clone()));
1407 d
1408 }
1409
1410 #[cfg_attr(jfgui_dev, track_caller)]
1412 pub fn sparkline(&mut self, id: u32, values: &[f32], color: u32) {
1413 let _src = self.src_scope();
1414 let r = self.place(self.dp(120), self.dp(32));
1415 let line = self.chart_color(color, 0);
1416 let area = (line & 0x00FF_FFFF) | 0x2E00_0000;
1417 let (mut lo, mut hi) = (f32::MAX, f32::MIN);
1418 for &v in values {
1419 lo = lo.min(v);
1420 hi = hi.max(v);
1421 }
1422 if hi <= lo {
1423 hi = lo + 1.0;
1424 lo -= 1.0;
1425 }
1426 let width = self.dp_fx(2 * 256).max(256);
1427 let mut h = Fnv::new();
1428 for &v in values {
1429 h.f32(v);
1430 }
1431 h.u32(line);
1432 h.u32(width as u32);
1433 let n = values.len().max(2) - 1;
1434 let hash = h.0;
1435 let d = self.chart_obj(id, hash, || {
1436 let pts = values.iter().enumerate().map(|(i, &v)| (i as f32 / n as f32, (v - lo) / (hi - lo))).collect();
1437 Rc::new(Spark { pts, line, area, width })
1438 });
1439 self.cmd_custom(r[0], r[1], r[2], r[3], Some(d), 0, hash as u32);
1440 self.wp.ctx.last_state = 0;
1441 self.sem(K_CANVAS, id, None, values.len() as i32);
1442 }
1443
1444 #[cfg_attr(jfgui_dev, track_caller)]
1446 pub fn bar_chart(&mut self, id: u32, labels: &[&str], series: &[BarSeries], stacked: bool) -> i32 {
1447 let _src = self.src_scope();
1448 let r = self.place(self.dp(360), self.dp(180));
1449 let n = labels.len();
1450 let mut m = 0.0f32;
1452 for i in 0..n {
1453 if stacked {
1454 let s: f32 = series.iter().map(|s| s.values.get(i).copied().unwrap_or(0.0).max(0.0)).sum();
1455 m = m.max(s);
1456 } else {
1457 for s in series {
1458 m = m.max(s.values.get(i).copied().unwrap_or(0.0));
1459 }
1460 }
1461 }
1462 let lh = self.ts_line(TS_CAPTION);
1464 let ph = r[3] - lh - self.dp(6) - (lh >> 1);
1465 let (top, steps) = nice_max(m, (ph / lh.max(1)).clamp(1, 5));
1466 let vals: Vec<String> = (0..=steps).map(|k| short_num(top * k as f32 / steps as f32)).collect();
1467 self.ts_font(TS_CAPTION);
1469 let vw = vals.iter().map(|s| self.text.width(s)).max().unwrap_or(0);
1470 let lw = vw.max(self.dp(16)) + self.dp(8);
1471 let plot = [r[0] + lw, r[1] + (lh >> 1), r[2] - lw, ph];
1472 let hair = self.hair();
1474 let grid = self.color(T_DIVIDER);
1475 let t3 = self.color(T_TEXT3);
1476 for (k, s) in vals.iter().enumerate() {
1477 let y = plot[1] + plot[3] - (plot[3] as i64 * k as i64 / steps as i64) as i32;
1478 self.draw_color(grid);
1479 self.draw_rect(plot[0], y, plot[2], hair);
1480 self.ts_font(TS_CAPTION);
1481 let tw = self.text.width(s);
1482 text_at(self, TS_CAPTION, plot[0] - tw - self.dp(6), [r[0], y - (lh >> 1), lw, lh], s, t3, 0);
1483 }
1484 if n == 0 {
1485 return -1;
1486 }
1487 let cw = plot[2] / n as i32;
1489 self.ts_font(TS_CAPTION);
1492 let lws: Vec<i32> = labels.iter().map(|l| self.text.width(crate::basic::cstr(l))).collect();
1493 let (lo, hi) = (plot[0], r[0] + r[2]);
1494 let sep = self.dp(4);
1495 let shown = (1..=n)
1496 .find_map(|k| axis_labels(&lws, plot[0], cw, lo, hi, k, sep))
1497 .unwrap_or_else(|| alloc::vec![(0, lo)]);
1498 let ly = plot[1] + plot[3] + self.dp(6);
1499 let gw = ((cw * 3) / 5).max(2);
1500 let ns = series.len().max(1) as i32;
1501 let mut hovered = -1;
1502 let rad = self.dp(2);
1503 let mut next = 0; for (i, label) in labels.iter().enumerate() {
1505 let cx = plot[0] + cw * i as i32;
1506 let ev = self.hit_rect(id.wrapping_add(1 + i as u32), cx, plot[1], cw, plot[3] + lh + self.dp(6), 0);
1508 if ev & EV_HOVER != 0 {
1509 hovered = i as i32;
1510 let c = self.color(T_SUBTLE_HOVER);
1511 if c & 0xFF00_0000 != 0 {
1512 self.cmd_rrect(cx + self.dp(2), plot[1], cw - self.dp(4), plot[3], self.dp(4), 0, c, 0, 0);
1513 }
1514 }
1515 let x0 = cx + ((cw - gw) >> 1);
1516 let mut base = plot[1] + plot[3];
1517 for (si, s) in series.iter().enumerate() {
1518 let v = s.values.get(i).copied().unwrap_or(0.0).max(0.0);
1519 let h = ((v / top) * plot[3] as f32) as i32;
1520 let c = self.chart_color(s.color, si);
1521 if stacked {
1522 if h > 0 {
1523 self.cmd_rrect(x0, base - h, gw, h, if si as i32 == ns - 1 { rad } else { 0 }, 0, c, 0, 0);
1524 }
1525 base -= h;
1526 } else {
1527 let bw = (gw / ns).max(1);
1528 let bx = x0 + bw * si as i32;
1529 if h > 0 {
1530 self.cmd_rrect(bx, base - h, (bw - hair).max(1), h, rad.min(bw / 2), 0, c, 0, 0);
1531 }
1532 }
1533 }
1534 let lab = crate::basic::cstr(label);
1536 if let Some(&(_, x)) = shown.get(next).filter(|s| s.0 == i) {
1537 next += 1;
1538 let room = hi - x;
1539 self.ts_font(TS_CAPTION);
1540 text_at(self, TS_CAPTION, x, [x, ly, room, lh], lab, t3, room.max(1));
1541 self.wp.ctx.last_rect = [x, ly, lws[i].min(room), lh];
1542 self.wp.ctx.last_state = 0;
1543 self.sem(K_TEXT, 0, Some(lab), (lws[i] > room) as i32);
1544 }
1545 if ev & EV_HOVER != 0 {
1546 let mut tip = String::from(lab);
1547 for s in series {
1548 let v = s.values.get(i).copied().unwrap_or(0.0);
1549 tip.push_str(&format!("\n{}: {}", s.label, short_num(v)));
1550 }
1551 self.tooltip(&tip);
1552 }
1553 }
1554 self.wp.ctx.last_rect = r;
1555 self.wp.ctx.last_state = 0;
1556 self.sem(K_CANVAS, id, None, n as i32);
1557 hovered
1558 }
1559
1560 #[cfg_attr(jfgui_dev, track_caller)]
1564 pub fn donut(&mut self, id: u32, slices: &[DonutSlice], center: &str, sub: &str) {
1565 let _src = self.src_scope();
1566 let s = self.dp(160);
1567 let r = self.place(s, s);
1568 let d = r[2].min(r[3]);
1569 let sq = [r[0] + ((r[2] - d) >> 1), r[1] + ((r[3] - d) >> 1), d, d];
1570 let total: f32 = slices.iter().map(|s| s.value.max(0.0)).sum();
1571 let track = self.color(T_TRACK);
1572 let mut h = Fnv::new();
1573 let mut parts = Vec::new();
1574 let mut a = 0.0f32;
1575 for (i, sl) in slices.iter().enumerate() {
1576 let f = if total > 0.0 { sl.value.max(0.0) / total } else { 0.0 };
1577 let c = self.chart_color(sl.color, i);
1578 parts.push((a, a + f, c));
1579 h.f32(f);
1580 h.u32(c);
1581 a += f;
1582 }
1583 h.u32(track);
1584 h.u32(d as u32);
1585 let hash = h.0;
1586 let gap = if slices.len() > 1 { 0.006 } else { 0.0 };
1587 let obj = self.chart_obj(id, hash, || Rc::new(Ring { slices: parts, thick: 0.22, track, gap }));
1588 self.cmd_custom(sq[0], sq[1], sq[2], sq[3], Some(obj), 0, hash as u32);
1589 let t = self.color(T_TEXT);
1592 let t2 = self.color(T_TEXT2);
1593 let hole = (d as f32 * 0.78) as i32 - self.dp(8);
1594 let mut ts1 = TS_SUBTITLE;
1595 self.ts_font(ts1);
1596 if self.text.width(center) > hole {
1597 ts1 = TS_BODY_STRONG;
1598 }
1599 let l1 = self.ts_line(ts1);
1600 let cap = self.ts_line(TS_CAPTION);
1601 let sub = if sub.is_empty() || l1 + cap > hole { "" } else { sub };
1602 let l2 = if sub.is_empty() { 0 } else { cap };
1603 let y = sq[1] + ((d - l1 - l2) >> 1);
1604 self.ts_font(ts1);
1605 let w1 = self.text.width(center).min(hole);
1606 text_at(self, ts1, sq[0] + ((d - w1) >> 1), [sq[0], y, d, l1], center, t, hole.max(1));
1607 if !sub.is_empty() {
1608 self.ts_font(TS_CAPTION);
1609 let w2 = self.text.width(sub).min(hole);
1610 text_at(self, TS_CAPTION, sq[0] + ((d - w2) >> 1), [sq[0], y + l1, d, l2], sub, t2, hole.max(1));
1611 }
1612 let ev = self.hit_rect(id, sq[0], sq[1], sq[2], sq[3], 0);
1614 if ev & EV_HOVER != 0 && total > 0.0 {
1615 let (mx, my) = self.mouse();
1616 let dx = (mx - (sq[0] + d / 2)) as f32;
1617 let dy = (my - (sq[1] + d / 2)) as f32;
1618 let dist = rt::fx::sqrt(dx * dx + dy * dy);
1619 let ro = d as f32 / 2.0;
1620 if dist <= ro && dist >= ro * 0.78 {
1621 let ang = rt::x87::atan2(dx as f64, -dy as f64) as f32 / core::f32::consts::TAU;
1622 let ang = if ang < 0.0 { ang + 1.0 } else { ang };
1623 let mut acc = 0.0f32;
1624 for sl in slices {
1625 let f = sl.value.max(0.0) / total;
1626 if ang >= acc && ang < acc + f {
1627 let tip = format!("{}: {} ({:.0} %)", sl.label, short_num(sl.value), f * 100.0);
1628 self.tooltip(&tip);
1629 break;
1630 }
1631 acc += f;
1632 }
1633 }
1634 }
1635 self.wp.ctx.last_rect = r;
1636 self.wp.ctx.last_state = 0;
1637 self.sem(K_CANVAS, id, Some(center), slices.len() as i32);
1638 }
1639
1640 #[cfg_attr(jfgui_dev, track_caller)]
1643 pub fn legend(&mut self, items: &[(&str, u32)]) {
1644 let _src = self.src_scope();
1645 self.legend_marks(items, &[], 0);
1646 }
1647
1648 fn legend_marks(&mut self, items: &[(&str, u32)], dashes: &[u32], lw: i32) {
1651 let s = self.dp(10);
1652 let sl = self.dp(20);
1653 let (gap, igap) = (self.dp(12), self.dp(6));
1654 self.ts_font(TS_CAPTION);
1657 let ws: Vec<i32> = items
1658 .iter()
1659 .enumerate()
1660 .map(|(i, &(label, _))| if i < dashes.len() { sl } else { s } + igap + self.text.width(crate::basic::cstr(label)))
1661 .collect();
1662 let one_row = ws.iter().sum::<i32>() + gap * (ws.len() as i32 - 1).max(0);
1663 let (room, _) = self.avail_width(one_row);
1664 self.box_begin(0, BOX_H | BOX_WRAP | BOX_XCENTER, 12);
1665 for (i, &(label, c)) in items.iter().enumerate() {
1666 let col = self.chart_color(c, i);
1667 let cut = room > 0 && ws[i] > room;
1668 if cut {
1669 self.next_size(self.px2dip(room), 0);
1670 }
1671 self.box_begin(0, BOX_H | BOX_XCENTER, 6);
1672 if let Some(&dash) = dashes.get(i) {
1673 let r = self.place(sl, s);
1674 self.lc_sample(r, col, dash, lw);
1675 } else {
1676 let r = self.place(s, s);
1677 self.draw_color(col);
1678 self.draw_circle((r[0] << 8) + (s << 7), (r[1] << 8) + (r[3] << 7), s << 7, 0);
1679 }
1680 if cut {
1681 self.next_size(-1, 0);
1682 }
1683 self.text(TS_CAPTION | TX_SECONDARY | TX_ELLIPSIS, label);
1684 self.box_end();
1685 }
1686 self.box_end();
1687 }
1688
1689 fn lc_sample(&mut self, r: [i32; 4], c: u32, dash: u32, lw: i32) {
1691 let t = ((lw + 128) >> 8).max(1);
1692 let y = r[1] + ((r[3] - t) >> 1);
1693 self.draw_color(c);
1694 let pat = dash_pattern(dash);
1695 if pat.is_empty() {
1696 self.draw_rect(r[0], y, r[2], t);
1697 return;
1698 }
1699 let (mut x, mut k) = (0, 0);
1700 while x < r[2] {
1701 let len = ((pat[k % pat.len()] * t as f64) as i32).max(1);
1702 if k % 2 == 0 {
1703 self.draw_rect(r[0] + x, y, len.min(r[2] - x), t);
1704 }
1705 x += len;
1706 k += 1;
1707 }
1708 }
1709
1710 fn lc_x_ticks(&mut self, x: u8, v: (f64, f64), pw: i32, sep: u32) -> Vec<(f64, String)> {
1713 self.ts_font(TS_CAPTION);
1714 let room = self.dp(12);
1715 if x == LX_NUM {
1716 let pps = pw as f64 / (v.1 - v.0);
1717 let mut steps = (pw / self.dp(72).max(1)).clamp(1, 12) as u32;
1718 loop {
1719 let (_, step, _) = lc_nice(v.0, v.1, steps);
1720 let (k0, k1) = (ceil_f(v.0 / step - 1e-9), floor_f(v.1 / step + 1e-9));
1721 if k1 < k0 || k1 - k0 > 64.0 {
1722 return Vec::new();
1723 }
1724 let n = (k1 - k0) as u32;
1725 let labels = LcUnit::Num("").ticks(k0 * step, step, n, sep);
1726 let wmax = labels.iter().map(|s| self.text.width(s)).max().unwrap_or(0);
1727 if step * pps >= (wmax + room) as f64 || steps == 1 {
1728 return labels.into_iter().enumerate().map(|(i, s)| ((k0 + i as f64) * step, s)).collect();
1729 }
1730 steps -= 1;
1731 }
1732 }
1733 let xs = if x == LX_NANOS { 1e-9 } else { 1.0 };
1734 let (a, b) = (v.0 * xs, v.1 * xs);
1735 let pps = pw as f64 / (b - a);
1736 let hours = abs(a).max(abs(b)) >= 3600.0;
1737 let mut step = lc_time_step(self.dp(72) as f64 / pps);
1738 for _ in 0..12 {
1739 let dec = if step >= 1.0 { 0 } else { step_decimals(step) };
1740 let (k0, k1) = (ceil_f(a / step - 1e-9), floor_f(b / step + 1e-9));
1741 if k1 < k0 || k1 - k0 > 64.0 {
1742 break;
1743 }
1744 let ticks: Vec<(f64, String)> = (0..=(k1 - k0) as i64)
1745 .map(|i| {
1746 let t = (k0 + i as f64) * step;
1747 (t / xs, lc_time_text(t, dec, hours, sep))
1748 })
1749 .collect();
1750 let wmax = ticks.iter().map(|t| self.text.width(&t.1)).max().unwrap_or(0);
1751 if step * pps >= (wmax + room) as f64 {
1752 return ticks;
1753 }
1754 step = lc_time_step(step * 1.0001);
1755 }
1756 Vec::new()
1757 }
1758
1759 fn lc_summary(c: &LineChart, stats: &[Stat], empty: bool, sep: u32) -> String {
1762 let mut s = String::from(c.name);
1763 if !s.is_empty() {
1764 s.push_str(" – ");
1765 }
1766 let none = if c.empty.is_empty() { tr("Keine Daten") } else { c.empty };
1767 if empty || c.series.is_empty() {
1768 s.push_str(none);
1769 return s;
1770 }
1771 for (i, (ser, st)) in c.series.iter().zip(stats).enumerate() {
1772 if i > 0 {
1773 s.push_str("; ");
1774 }
1775 s.push_str(crate::basic::cstr(ser.label));
1776 s.push_str(": ");
1777 if st.has {
1778 let u = ser.unit;
1779 s.push_str(&format!(
1780 "{} {}, {} {}, {} {}",
1781 tr("aktuell"),
1782 u.text(st.last, sep),
1783 tr("min."),
1784 u.text(st.lo, sep),
1785 tr("max."),
1786 u.text(st.hi, sep)
1787 ));
1788 } else {
1789 s.push_str(none);
1790 }
1791 }
1792 s
1793 }
1794
1795 fn lc_tag(&mut self, rect: [i32; 4], label: &str, c: u32, lx: impl Fn(i32, i32) -> i32) {
1798 let hc = self.high_contrast;
1799 let rad = self.dp(3);
1800 let (fill, stroke, t) = if hc {
1801 (self.color(T_BG), self.color(T_TEXT), self.hair())
1802 } else {
1803 (color_mix(card_opaque(self), c | 0xFF00_0000, 52), 0, 0)
1804 };
1805 self.cmd_rrect(rect[0], rect[1], rect[2], rect[3], rad, t, fill, stroke, stroke);
1806 let tc = self.color(T_TEXT);
1807 let pad = self.dp(5);
1808 text_at(self, TS_CAPTION, rect[0] + pad, rect, label, tc, rect[2] - pad * 2);
1809 self.wp.ctx.last_rect = [lx(rect[0], rect[2]), rect[1], rect[2], rect[3]];
1810 self.wp.ctx.last_state = 0;
1811 self.sem(K_TEXT, 0, Some(label), 0);
1812 }
1813
1814 fn lc_keys(&mut self, c: &LineChart, v: (f64, f64), cur: Option<(usize, f64)>, rtl: bool) -> Option<(usize, f64)> {
1819 let swap = rtl && self.mods & (MOD_CTRL | MOD_ALT) == 0;
1820 let (kp, kn) = if swap { (VK_RIGHT, VK_LEFT) } else { (VK_LEFT, VK_RIGHT) };
1821 let n = c.series.len();
1822 let last = |s: usize| {
1823 let d = c.series[s].data;
1824 lc_step(d, d.upper_bound(v.1), true, v).map(|t| (s, t))
1825 };
1826 let mut q = cur;
1827 let mut moved = false;
1828 for (key, back) in [(kp, true), (kn, false)] {
1829 if self.key(key) {
1830 moved = true;
1831 q = match q {
1832 Some((s, t)) => {
1833 let d = c.series[s].data;
1834 let i = if back { d.lower_bound(t) } else { d.upper_bound(t) };
1835 lc_step(d, i, back, v).map(|t| (s, t)).or(q)
1836 }
1837 None => last(0),
1838 };
1839 }
1840 }
1841 if self.key(VK_HOME) {
1842 moved = true;
1843 let s = q.map_or(0, |x| x.0);
1844 let d = c.series[s].data;
1845 q = lc_step(d, d.lower_bound(v.0), false, v).map(|t| (s, t)).or(q);
1846 }
1847 if self.key(VK_END) {
1848 moved = true;
1849 q = last(q.map_or(0, |x| x.0)).or(q);
1850 }
1851 for (key, up) in [(VK_UP, true), (VK_DOWN, false)] {
1852 if self.key(key) {
1853 moved = true;
1854 let (s, t) = q.unwrap_or((0, v.1));
1855 let s2 = if up { s.saturating_sub(1) } else { (s + 1).min(n - 1) };
1856 let d = c.series[s2].data;
1857 let p = d.nearest(t).and_then(|i| d.get(i)).filter(|p| p.0 >= v.0 && p.0 <= v.1);
1858 q = p.map(|p| (s2, p.0)).or(q);
1859 }
1860 }
1861 if q.is_some() && self.key(VK_ESCAPE) {
1862 q = None;
1863 }
1864 if moved {
1865 self.wp.ctx.focus_visible = true;
1866 }
1867 q
1868 }
1869
1870 #[cfg_attr(jfgui_dev, track_caller)]
1874 pub fn line_chart(&mut self, id: u32, c: &LineChart) -> Option<(usize, f64)> {
1875 let _src = self.src_scope();
1876 let hc = self.high_contrast;
1877 let sep = self.m.value.decimal_sep;
1878 let lh = self.ts_line(TS_CAPTION);
1879 let hair = self.hair();
1880 let n = c.series.len();
1881 let empty = c.series.iter().all(|s| s.data.is_empty());
1882 let xs = if c.x == LX_NANOS { 1e-9 } else { 1.0 }; self.box_begin(0, BOX_V, 6);
1885 self.next_size(-1, -1);
1886 let r = self.place(self.dp(480), self.dp(200));
1887 let pal = self.style.data().chart_palette(self);
1889 let hcc = [self.color(T_ACCENT), self.color(T_TEXT), self.color(T_ACCENT_TEXT)];
1890 let looks: Vec<Look> = c
1891 .series
1892 .iter()
1893 .enumerate()
1894 .map(|(i, s)| {
1895 if hc {
1896 Look { color: hcc[i % 3], dash: HC_DASH[i % 4], area: false }
1897 } else {
1898 let color = if s.color != 0 { s.color } else { pal[s.pal.map_or(i, |k| k as usize) % 8] };
1899 Look { color, dash: DASH_SOLID, area: s.area }
1900 }
1901 })
1902 .collect();
1903 let lw = (if hc { self.dp_fx(512) } else { self.dp_fx(384) }).max(256);
1904 let (t2, tx) = (self.color(T_TEXT2), self.color(T_TEXT));
1905 let mut st = self.m.chart.lines.remove(&id).unwrap_or_default();
1906 let view = c.view();
1908 let mut h = Fnv::new();
1909 for s in c.series {
1910 h.u32(s.data.ver());
1911 }
1912 h.f64(view.0);
1913 h.f64(view.1);
1914 let skey = h.0;
1915 if st.stats_key != skey || st.stats.len() != n {
1916 st.stats = c.series.iter().map(|s| stat_of(s.data, view)).collect();
1917 st.stats_key = skey;
1918 }
1919 let mut h = Fnv::new();
1921 h.u64(skey);
1922 h.u32(sep);
1923 h.u32(crate::i18n::language());
1924 h.str(c.name);
1925 h.str(c.empty);
1926 h.u32(empty as u32);
1927 for s in c.series {
1928 h.str(s.label);
1929 match s.unit {
1930 LcUnit::Num(u) => h.str(u),
1931 LcUnit::Si(u) => {
1932 h.u32(1);
1933 h.str(u);
1934 }
1935 LcUnit::Secs => h.u32(2),
1936 }
1937 }
1938 if st.sum_key != h.0 {
1939 st.summary = Ui::lc_summary(c, &st.stats, empty, sep);
1940 st.sum_key = h.0;
1941 }
1942 let (mut lo, mut hi) = (f64::INFINITY, f64::NEG_INFINITY);
1944 for s in st.stats.iter().filter(|s| s.has) {
1945 lo = lo.min(s.lo);
1946 hi = hi.max(s.hi);
1947 }
1948 for t in c.thresholds.iter().filter(|t| t.value.is_finite()) {
1949 lo = lo.min(t.value);
1950 hi = hi.max(t.value);
1951 }
1952 let (flo, fhi) = (c.y_range.0.is_finite(), c.y_range.1.is_finite());
1953 if flo {
1954 lo = c.y_range.0;
1955 }
1956 if fhi {
1957 hi = c.y_range.1;
1958 }
1959 if !(lo <= hi) {
1960 (lo, hi) = if flo {
1961 (lo, lo + 1.0)
1962 } else if fhi {
1963 (hi - 1.0, hi)
1964 } else {
1965 (0.0, 1.0)
1966 };
1967 }
1968 if hi == lo {
1969 let d = abs(lo).max(1.0) * if flo || fhi { 1.0 } else { 0.1 };
1970 if !fhi {
1971 hi = lo + d;
1972 } else if !flo {
1973 lo = hi - d;
1974 } else {
1975 hi = lo + 1.0;
1976 }
1977 }
1978 let top = lh / 2;
1981 let ph = (r[3] - top - lh - self.dp(4)).max(1);
1982 let steps = (ph / (lh * 2).max(self.dp(28)).max(1)).clamp(1, 8) as u32;
1983 let (first, step, nst) = lc_nice(lo, hi, steps);
1984 let ylo = if flo { lo } else { first };
1985 let yhi = if fhi { hi } else { first + step * nst as f64 };
1986 let (ylo, yhi) = if yhi > ylo { (ylo, yhi) } else { (ylo, ylo + 1.0) };
1987 let (k0, k1) = (ceil_f(ylo / step - 1e-9), floor_f(yhi / step + 1e-9));
1988 let unit = c.series.first().map_or(LcUnit::Num(""), |s| s.unit);
1989 let ylab = if empty || k1 < k0 || k1 - k0 > 64.0 { Vec::new() } else { unit.ticks(k0 * step, step, (k1 - k0) as u32, sep) };
1990 self.ts_font(TS_CAPTION);
1991 let yw = ylab.iter().map(|s| self.text.width(s)).max().unwrap_or(0);
1992 let ylw = if yw > 0 { yw + self.dp(8) } else { 0 };
1993 let px0 = r[0] + ylw;
1994 let pw = (r[2] - ylw - self.dp(4)).max(1);
1995 let py0 = r[1] + top;
1996 let xt = if empty { Vec::new() } else { self.lc_x_ticks(c.x, view, pw, sep) };
1997 let sx = pw as f64 / (view.1 - view.0);
1998 let sy = ph as f64 / (yhi - ylo);
1999 let xpx = |x: f64| ((x - view.0) * sx).clamp(-1e6, 1e6);
2000 let ypx = |v: f64| (ph as f64 - (v - ylo) * sy).clamp(-1e6, 1e6);
2001 let mut grid: Vec<(i32, bool)> = Vec::new();
2003 let gy: Vec<i32> =
2004 (0..ylab.len()).map(|k| (floor_f(ypx((k0 + k as f64) * step) + 0.5) as i32).min(ph - hair).max(0)).collect();
2005 let gx: Vec<i32> = xt.iter().map(|t| (floor_f(xpx(t.0) + 0.5) as i32).min(pw - hair).max(0)).collect();
2006 if c.flags & LC_NO_GRID == 0 {
2007 grid.extend(gy.iter().map(|&y| (y, false)));
2008 grid.extend(gx.iter().map(|&x| (x, true)));
2009 }
2010 let mlw = self.dp(1).max(1);
2011 let mcolor = |m: &LcMarker| if hc { tx } else if m.color != 0 { m.color } else { t2 };
2012 let mut ranges: Vec<(i32, i32, u32)> = Vec::new();
2013 let mut rules: Vec<(i32, i32, u32)> = Vec::new();
2014 let mut tags: Vec<(i32, &str, u32)> = Vec::new(); for m in c.markers.iter().filter(|m| !empty && m.t0.is_finite()) {
2016 let (a, b) = (m.t0, mark_end(m));
2017 if b < view.0 || a > view.1 {
2018 continue;
2019 }
2020 let col = mcolor(m);
2021 let xa = floor_f(xpx(a) + 0.5) as i32;
2022 let rule = |x: i32| (x - mlw / 2).min(pw - mlw).max(0);
2023 if b > a {
2024 let xb = floor_f(xpx(b) + 0.5) as i32;
2025 let (ca, cb) = (xa.clamp(0, pw), xb.clamp(0, pw));
2026 if !hc && cb > ca {
2027 ranges.push((ca, cb, (col & 0x00FF_FFFF) | 0x1C00_0000));
2028 }
2029 let edge = if hc { col } else { (col & 0x00FF_FFFF) | 0x9000_0000 };
2030 for x in [xa, xb] {
2031 if x >= 0 && x <= pw {
2032 rules.push((rule(x), mlw, edge));
2033 }
2034 }
2035 tags.push((ca, m.label, col));
2036 } else {
2037 rules.push((rule(xa), mlw, col));
2038 tags.push((xa, m.label, col));
2039 }
2040 }
2041 let lt = self.dp(1).max(1);
2042 let mut limits: Vec<(i32, u32)> = Vec::new();
2043 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) {
2045 let col = if hc { tx } else if t.color != 0 { t.color } else { t2 };
2046 let y = (floor_f(ypx(t.value) + 0.5) as i32 - lt / 2).min(ph - lt).max(0);
2047 limits.push((y, col));
2048 ltags.push((y + lt / 2, t.label, col));
2049 }
2050 let m = ((lw + 255) >> 8) + 1;
2052 let grid_c = self.color(T_DIVIDER);
2053 let mut h = Fnv::new();
2054 h.u64(skey);
2055 h.f64(ylo);
2056 h.f64(yhi);
2057 for v in [pw, ph, lw, hair, m, hc as i32, grid_c as i32, lt, mlw] {
2058 h.u32(v as u32);
2059 }
2060 for l in &looks {
2061 h.u32(l.color);
2062 h.u32(l.dash);
2063 h.u32(l.area as u32);
2064 }
2065 for &(p, v) in &grid {
2066 h.u32(p as u32 | (v as u32) << 31);
2067 }
2068 for &(a, b, col) in ranges.iter().chain(rules.iter()) {
2069 h.u32(a as u32);
2070 h.u32(b as u32);
2071 h.u32(col);
2072 }
2073 for &(y, col) in &limits {
2074 h.u32(y as u32);
2075 h.u32(col);
2076 }
2077 let okey = h.0;
2078 if st.obj_key != okey || st.obj.is_none() {
2079 let lwf = lw as f64 / 256.0;
2080 let mut areas = Vec::new();
2081 let mut traces = Vec::new();
2082 if !empty {
2083 for (i, s) in c.series.iter().enumerate() {
2084 let l = looks[i];
2085 let dense = st.stats[i].n > pw as usize * 2;
2086 let (polys, band, area) = trace_of(s.data, view, (pw, ph), (ylo, yhi), lwf, dense, l.area, dash_pattern(l.dash));
2087 if l.area && !area.is_empty() {
2088 areas.push((area, (l.color & 0x00FF_FFFF) | 0x2E00_0000));
2089 }
2090 traces.push(Trace { polys, band, color: l.color, width: lw, dash: l.dash });
2091 }
2092 }
2093 let lim = (lt, self.dp(4), self.dp(3));
2094 let plot = Plot { m, w: pw, h: ph, hair, grid, grid_c, dotted: hc, ranges, areas, traces, limits, lim, rules };
2095 st.obj = Some(Rc::new(plot));
2096 st.obj_key = okey;
2097 }
2098 self.ltr_begin(r[0], r[2]);
2100 let rtl = self.rtl;
2101 let lx = move |x: i32, w: i32| if rtl { r[0] + r[0] + r[2] - x - w } else { x };
2102 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 };
2103 let focused = ev & EV_FOCUS != 0;
2105 let mut cursor = st.cursor.filter(|q| focused && q.0 < n && q.1 >= view.0 && q.1 <= view.1);
2106 if focused && n > 0 {
2107 cursor = self.lc_keys(c, view, cursor, rtl);
2108 }
2109 let hours = abs(view.0 * xs).max(abs(view.1 * xs)) >= 3600.0;
2112 let point = cursor.and_then(|(s, t)| {
2113 let ser = &c.series[s];
2114 let v = ser.data.nearest(t).and_then(|i| ser.data.get(i))?;
2115 Some(format!("{}, {}: {}. ", crate::basic::cstr(ser.label), lc_x_label(c.x, t, hours, sep), ser.unit.text(v.1, sep)))
2116 });
2117 let label = match &point {
2118 Some(p) => format!("{}{}", p, st.summary),
2119 None => st.summary.clone(),
2120 };
2121 self.wp.ctx.last_rect = r;
2122 let mut ss = if ev & EV_HOVER != 0 { SS_HOVER as u32 } else { 0 };
2123 if focused {
2124 ss |= SS_FOCUSED as u32;
2125 }
2126 self.wp.ctx.last_state = ss;
2127 self.sem(K_CANVAS, id, Some(&label), n as i32);
2128 self.cmd_custom(px0 - m, py0 - m, pw + m * 2, ph + m * 2, st.obj.clone(), 0, okey as u32);
2129 for (k, s) in ylab.iter().enumerate() {
2132 self.ts_font(TS_CAPTION);
2133 let w = self.text.width(s);
2134 let y = py0 + gy[k] + hair / 2 - lh / 2;
2135 let x = px0 - self.dp(6) - w;
2136 text_at(self, TS_CAPTION, x, [x, y, w, lh], s, t2, 0);
2137 self.wp.ctx.last_rect = [lx(x, w), y, w, lh];
2138 self.wp.ctx.last_state = 0;
2139 self.sem(K_TEXT, 0, Some(s), 0);
2140 }
2141 let ly = py0 + ph + self.dp(4);
2142 let mut end = i32::MIN;
2143 for (k, (_, s)) in xt.iter().enumerate() {
2144 self.ts_font(TS_CAPTION);
2145 let w = self.text.width(s);
2146 let x = (px0 + gx[k] - w / 2).min(r[0] + r[2] - w).max(px0);
2147 if x < end + self.dp(4) {
2148 continue;
2149 }
2150 end = x + w;
2151 text_at(self, TS_CAPTION, x, [x, ly, w, lh], s, t2, 0);
2152 self.wp.ctx.last_rect = [lx(x, w), ly, w, lh];
2153 self.wp.ctx.last_state = 0;
2154 self.sem(K_TEXT, 0, Some(s), 0);
2155 }
2156 let pad = self.dp(5);
2159 let tsep = self.dp(4);
2160 let mut placed: Vec<[i32; 4]> = Vec::new();
2161 for &(y, label, col) in <ags {
2162 if label.is_empty() {
2163 continue;
2164 }
2165 self.ts_font(TS_CAPTION);
2166 let w = (self.text.width(label) + pad * 2).min(pw);
2167 let ty = (py0 + y - lh / 2).min(py0 + ph - lh).max(py0);
2168 let rect = [px0 + pw - w - self.dp(4), ty, w, lh];
2169 if rect[0] < px0 || placed.iter().any(|&p| near(p, rect, tsep)) {
2170 continue;
2171 }
2172 placed.push(rect);
2173 self.lc_tag(rect, label, col, lx);
2174 }
2175 let rows = ((ph / 3) / (lh + self.dp(2)).max(1)).clamp(1, 3);
2176 for &(x, label, col) in &tags {
2177 if label.is_empty() {
2178 continue;
2179 }
2180 self.ts_font(TS_CAPTION);
2181 let w = (self.text.width(label) + pad * 2).min(pw);
2182 let xm = px0 + x;
2183 let x0 = if xm + self.dp(4) + w <= px0 + pw { xm + self.dp(4) } else { (xm - self.dp(4) - w).max(px0) };
2184 for row in 0..rows {
2185 let rect = [x0, py0 + self.dp(2) + row * (lh + self.dp(2)), w, lh];
2186 if !placed.iter().any(|&p| near(p, rect, tsep)) {
2187 placed.push(rect);
2188 self.lc_tag(rect, label, col, lx);
2189 break;
2190 }
2191 }
2192 }
2193 if empty {
2195 let s = if c.empty.is_empty() { tr("Keine Daten") } else { c.empty };
2196 self.ts_font(TS_BODY);
2197 let lb = self.ts_line(TS_BODY);
2198 let w = self.text.width(s).min(r[2]);
2199 let (x, y) = (r[0] + (r[2] - w) / 2, r[1] + (r[3] - lb) / 2);
2200 text_at(self, TS_BODY, x, [x, y, w, lb], s, t2, r[2]);
2201 self.wp.ctx.last_rect = [lx(x, w), y, w, lb];
2202 self.wp.ctx.last_state = 0;
2203 self.sem(K_TEXT, 0, Some(s), 0);
2204 }
2205 let mut out = None;
2208 let probe = if ev & EV_HOVER != 0 {
2209 let mx = self.ltr_x(self.wp.ctx.mx);
2210 Some((view.0 + ((mx - px0) as f64 + 0.5) / sx, None, Some(self.wp.ctx.my)))
2211 } else {
2212 cursor.map(|(s, t)| (t, Some(s), None))
2213 };
2214 if let Some((tp, fixed, my)) = probe {
2215 let inside = |p: &(f64, f64)| p.0 >= view.0 && p.0 <= view.1;
2216 let snap = if fixed.is_some() {
2217 Some(tp)
2218 } else {
2219 c.series
2220 .iter()
2221 .filter_map(|s| s.data.nearest(tp).and_then(|i| s.data.get(i)).filter(inside))
2222 .map(|p| p.0)
2223 .min_by(|a, b| abs(a - tp).total_cmp(&abs(b - tp)))
2224 };
2225 if let Some(ts) = snap {
2226 let tol = self.dp(24) as f64 / sx;
2228 let at: Vec<Option<(f64, f64)>> = c
2229 .series
2230 .iter()
2231 .map(|s| s.data.nearest(ts).and_then(|i| s.data.get(i)).filter(|p| inside(p) && abs(p.0 - ts) <= tol))
2232 .collect();
2233 let best = fixed.or_else(|| {
2236 let my = my.unwrap_or(0) as f64;
2237 at.iter()
2238 .enumerate()
2239 .filter_map(|(i, p)| p.map(|p| (i, abs(py0 as f64 + ypx(p.1) - my))))
2240 .min_by(|a, b| a.1.total_cmp(&b.1))
2241 .map(|b| b.0)
2242 });
2243 if let Some(bi) = best {
2244 out = Some((bi, ts));
2245 if ev & EV_ACT != 0 && fixed.is_none() {
2246 cursor = Some((bi, ts));
2247 }
2248 }
2249 let my = my.unwrap_or_else(|| {
2251 let v = best.and_then(|b| at[b]).map_or(0.0, |p| ypx(p.1).clamp(0.0, ph as f64));
2252 py0 + v as i32
2253 });
2254 let cx = px0 + floor_f(xpx(ts) + 0.5) as i32;
2255 let cross = self.color(T_TEXT3);
2256 self.draw_color(cross);
2257 self.draw_rect(cx, py0, hair, ph);
2258 let ring = if hc { self.color(T_BG) } else { card_opaque(self) };
2259 let (ro, ri, grow) = (self.dp_fx(1152), self.dp_fx(768), self.dp_fx(256));
2260 for (i, p) in at.iter().enumerate() {
2261 let Some(p) = p else { continue };
2262 let y = ypx(p.1);
2263 if !(y >= 0.0 && y <= ph as f64) {
2264 continue;
2265 }
2266 let (fx, fy) = (((px0 as f64 + xpx(p.0)) * 256.0) as i32, ((py0 as f64 + y) * 256.0) as i32);
2267 let g = if Some(i) == best { grow } else { 0 };
2268 self.draw_color(ring);
2269 self.draw_circle(fx, fy, ro + g, 0);
2270 self.draw_color(looks[i].color);
2271 self.draw_circle(fx, fy, ri + g, 0);
2272 }
2273 let head = lc_x_label(c.x, ts, hours, sep);
2275 let mut rows: Vec<(u32, bool, &str, String)> = c
2276 .series
2277 .iter()
2278 .enumerate()
2279 .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))))
2280 .collect();
2281 let half = self.dp(3) as f64 / sx;
2283 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) {
2284 let mut when = lc_x_label(c.x, mk.t0, hours, sep);
2285 if mk.t1 > mk.t0 {
2286 when = format!("{} – {}", when, lc_x_label(c.x, mk.t1, hours, sep));
2287 }
2288 rows.push((mcolor(mk), true, crate::basic::cstr(mk.label), when));
2289 }
2290 self.lc_card(r, cx, my, &head, &rows);
2291 }
2292 }
2293 if self.focus_ring(ev) {
2294 let s = self.style;
2295 s.focus_ring(self, [px0, py0, pw, ph], 4);
2296 }
2297 self.ltr_end();
2298 let hk = out.map(|(i, t): (usize, f64)| (i, t.to_bits()));
2302 let moved = cursor.map(|q| (q.0, q.1.to_bits())) != st.cursor.map(|q| (q.0, q.1.to_bits()));
2303 let rect = [px0, py0, pw, ph];
2304 if (st.hover != hk || moved) && st.rect == rect {
2305 self.repass();
2306 }
2307 st.hover = hk;
2308 st.cursor = cursor;
2309 st.rect = rect;
2310 if self.m.chart.lines.len() > 64 {
2311 self.m.chart.lines.clear();
2312 }
2313 self.m.chart.lines.insert(id, st);
2314 if c.flags & LC_NO_LEGEND == 0 && n > 0 {
2316 let items: Vec<(&str, u32)> = c.series.iter().enumerate().map(|(i, s)| (s.label, looks[i].color)).collect();
2317 let dashes: Vec<u32> = if hc { looks.iter().map(|l| l.dash).collect() } else { Vec::new() };
2318 self.legend_marks(&items, &dashes, lw);
2319 }
2320 self.box_end();
2321 out
2322 }
2323
2324 fn lc_card(&mut self, r: [i32; 4], cx: i32, my: i32, head: &str, rows: &[(u32, bool, &str, String)]) {
2329 let prev = self.layer_set(L_TIP);
2330 self.ltr_begin(r[0], r[2]);
2331 let rtl = self.rtl;
2332 let lx = move |x: i32, w: i32| if rtl { r[0] + r[0] + r[2] - x - w } else { x };
2333 let lh = self.ts_line(TS_CAPTION);
2334 let (padx, pady) = (self.dp(10), self.dp(6));
2335 let (dot, dgap, cgap) = (self.dp(8), self.dp(6), self.dp(16));
2336 self.ts_font(TS_CAPTION);
2337 let hw = self.text.width(head);
2338 let lws: Vec<i32> = rows.iter().map(|q| self.text.width(q.2)).collect();
2339 let vws: Vec<i32> = rows.iter().map(|q| self.text.width(&q.3)).collect();
2340 let (lmax, vmax) = (lws.iter().copied().max().unwrap_or(0), vws.iter().copied().max().unwrap_or(0));
2341 let w = (padx * 2 + hw.max(dot + dgap + lmax + cgap + vmax)).min(self.dp(360));
2342 let h = pady * 2 + lh * (1 + rows.len()) as i32;
2343 let (fw, fh) = (self.wp.rt.fb.w, self.wp.rt.fb.h);
2346 let d = if rtl { fw - r[0] - r[0] - r[2] } else { 0 };
2347 let (lo, hi) = (-d, fw - d);
2348 let mut x = cx + self.dp(14);
2349 if x + w > hi {
2350 x = cx - self.dp(14) - w;
2351 }
2352 let x = x.min(hi - w).max(lo);
2353 let y = (my - h / 2).min(fh - h).max(0);
2354 let st = self.style;
2356 let mut sp = Sp { label: Some(""), ..Sp::default() };
2357 sp.place([x, y, w, h]);
2358 st.tooltip(self, &sp);
2359 let (tx, t2) = (self.color(T_TEXT), self.color(T_TEXT2));
2360 let inner = w - padx * 2;
2361 let mut ty = y + pady;
2362 text_at(self, TS_CAPTION, x + padx, [x + padx, ty, inner, lh], head, t2, inner);
2363 self.wp.ctx.last_rect = [lx(x + padx, hw.min(inner)), ty, hw.min(inner), lh];
2364 self.wp.ctx.last_state = 0;
2365 self.sem(K_TEXT, 0, Some(head), 0);
2366 ty += lh;
2367 let dy = (lh - dot) / 2;
2368 for (k, (col, mark, label, value)) in rows.iter().enumerate() {
2369 self.draw_color(*col);
2370 if *mark {
2371 self.draw_rect(x + padx + dot / 2 - self.dp(2), ty + dy, self.dp(4), dot);
2372 } else {
2373 self.draw_circle(((x + padx) << 8) + (dot << 7), ((ty + dy) << 8) + (dot << 7), dot << 7, 0);
2374 }
2375 let lx0 = x + padx + dot + dgap;
2376 let vw = vws[k].min(inner);
2377 let room = (inner - dot - dgap - vw - cgap).max(1);
2378 text_at(self, TS_CAPTION, lx0, [lx0, ty, room, lh], label, tx, room);
2379 let vx = x + w - padx - vw;
2380 text_at(self, TS_CAPTION, vx, [vx, ty, vw, lh], value, if *mark { t2 } else { tx }, inner);
2381 let rec = format!("{}: {}", label, value);
2382 self.wp.ctx.last_rect = [lx(lx0, inner - dot - dgap), ty, inner - dot - dgap, lh];
2383 self.wp.ctx.last_state = 0;
2384 self.sem(K_TEXT, 0, Some(&rec), 0);
2385 ty += lh;
2386 }
2387 self.ltr_end();
2388 self.layer_set(prev);
2389 }
2390}