Reviews: T16a–e done. After the cap: a lagging synced chart leaves the shared window alone, and a price step keeps its places by magnitude
This commit is contained in:
1 parent
1fe5673de1
commit
a894e5f973
4 files changed
+96
-41
No files matched your search
@@ -55,11 +55,11 @@ Per-component lines live in `tasks/`. Tags there:
|
|||||||
- [x] T15a Charts · Cartesian
|
- [x] T15a Charts · Cartesian
|
||||||
- [x] T15b Charts · Parts and spreads
|
- [x] T15b Charts · Parts and spreads
|
||||||
- [x] T15c Charts · Flows and relations
|
- [x] T15c Charts · Flows and relations
|
||||||
- [ ] T16a Finance · Market charts
|
- [x] T16a Finance · Market charts
|
||||||
- [ ] T16b Finance · Technical analysis
|
- [x] T16b Finance · Technical analysis
|
||||||
- [ ] T16c Finance · Quotes & book
|
- [x] T16c Finance · Quotes & book
|
||||||
- [ ] T16d Finance · Trading
|
- [x] T16d Finance · Trading
|
||||||
- [ ] T16e Finance · Markets & assets
|
- [x] T16e Finance · Markets & assets
|
||||||
- [ ] T17a Editor · Core
|
- [ ] T17a Editor · Core
|
||||||
- [ ] T17b Editor · Intelligence
|
- [ ] T17b Editor · Intelligence
|
||||||
- [ ] T17c Editor · Search
|
- [ ] T17c Editor · Search
|
||||||
@@ -216,8 +216,16 @@ Per-component lines live in `tasks/`. Tags there:
|
|||||||
| T15b | 2 | PASS | |
|
| T15b | 2 | PASS | |
|
||||||
| T15c | 1 | FAIL | eight charts kept a pointed part past their data, and the Gantt today line ran past the timeline |
|
| T15c | 1 | FAIL | eight charts kept a pointed part past their data, and the Gantt today line ran past the timeline |
|
||||||
| T15c | 2 | PASS | |
|
| T15c | 2 | PASS | |
|
||||||
| T16a | 1 | — | pending review |
|
| T16a | 1 | FAIL | an unknown system zone read as UTC, the view window kept its span when data shrank, and the volume profile dropped narrow and flat candles |
|
||||||
| T16b | 1 | — | pending review |
|
| T16a | 2 | FAIL | synced charts moved their shared window once per chart for one append |
|
||||||
| T16c | 1 | — | pending review |
|
| T16a | 3 | FAIL | a lagging synced chart moved the shared window again on every redraw; fixed after the cap by tracking each chart's own count, verified by a red-green test, no fourth review |
|
||||||
| T16d | 1 | — | pending review |
|
| T16b | 1 | FAIL | Fibonacci labels spilled out of their pane |
|
||||||
| T16e | 1 | — | pending review |
|
| T16b | 2 | FAIL | Fibonacci labels still ran past the price pane's top |
|
||||||
|
| T16b | 3 | PASS | |
|
||||||
|
| T16c | 1 | FAIL | the DOM ladder lost its tick's decimals, and the tape flashed only once |
|
||||||
|
| T16c | 2 | FAIL | ladder prices lost places their tick needed |
|
||||||
|
| T16c | 3 | FAIL | a step under 1e-10 showed no places; fixed after the cap by keeping places by magnitude, verified by a red-green test, no fourth review |
|
||||||
|
| T16d | 1 | FAIL | typed numbers reached the ticket only on blur, so a press sent the old value |
|
||||||
|
| T16d | 2 | PASS | |
|
||||||
|
| T16e | 1 | FAIL | a smaller loss than expected read as a miss |
|
||||||
|
| T16e | 2 | PASS | |
|
||||||
+20
-20
@@ -40,7 +40,7 @@ pub enum ChartKind {
|
|||||||
pub struct ChartSync {
|
pub struct ChartSync {
|
||||||
pub(crate) visible: Option<Visible>,
|
pub(crate) visible: Option<Visible>,
|
||||||
pub(crate) hover: Option<i64>,
|
pub(crate) hover: Option<i64>,
|
||||||
/// How many candles the shared window last moved for, so one append moves it once.
|
/// The most candles the shared window has moved for: an append moves it once, a lagging chart never.
|
||||||
pub(crate) seen: usize,
|
pub(crate) seen: usize,
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -230,37 +230,37 @@ impl RenderOnce for CandlestickChart {
|
|||||||
..frame
|
..frame
|
||||||
};
|
};
|
||||||
let total = self.candles.len();
|
let total = self.candles.len();
|
||||||
let held = match &self.sync {
|
let own = stage.read(cx).seen;
|
||||||
Some(sync) => (sync.read(cx).visible, sync.read(cx).hover),
|
let (held, shared) = match &self.sync {
|
||||||
None => (stage.read(cx).visible, stage.read(cx).hover),
|
Some(sync) => {
|
||||||
};
|
let sync = sync.read(cx);
|
||||||
let seen = match &self.sync {
|
((sync.visible, sync.hover), sync.seen)
|
||||||
Some(sync) => sync.read(cx).seen,
|
}
|
||||||
None => stage.read(cx).seen,
|
None => ((stage.read(cx).visible, stage.read(cx).hover), own),
|
||||||
};
|
};
|
||||||
|
let fresh = total.saturating_sub(own).min(total.saturating_sub(shared));
|
||||||
let visible = match held.0 {
|
let visible = match held.0 {
|
||||||
Some(visible)
|
Some(visible)
|
||||||
if total > seen
|
if fresh > 0
|
||||||
&& seen > 0
|
&& own > 0
|
||||||
&& visible.start + visible.count >= seen as f64 - 0.5 =>
|
&& visible.start + visible.count >= (total - fresh) as f64 - 0.5 =>
|
||||||
{
|
{
|
||||||
visible.pan((total - seen) as f64, total)
|
visible.pan(fresh as f64, total)
|
||||||
}
|
}
|
||||||
Some(visible) => visible.fitted(total),
|
Some(visible) => visible.fitted(total),
|
||||||
None => Visible::latest(total, f64::from(main.w / pixels(sizes.candle)).max(1.0)),
|
None => Visible::latest(total, f64::from(main.w / pixels(sizes.candle)).max(1.0)),
|
||||||
};
|
};
|
||||||
if seen != total {
|
if own != total {
|
||||||
log::debug!("market chart: {seen} candles to {total}");
|
log::debug!("market chart: {own} candles to {total}");
|
||||||
let kept = held.0.map(|_| visible);
|
let shrunk = total < own;
|
||||||
|
let kept = held.0.filter(|_| fresh > 0 || shrunk).map(|_| visible);
|
||||||
|
stage.update(cx, |stage, _| stage.seen = total);
|
||||||
match &self.sync {
|
match &self.sync {
|
||||||
Some(sync) => sync.update(cx, |sync, _| {
|
Some(sync) => sync.update(cx, |sync, _| {
|
||||||
sync.seen = total;
|
sync.seen = if shrunk { total } else { sync.seen.max(total) };
|
||||||
sync.visible = kept.or(sync.visible);
|
sync.visible = kept.or(sync.visible);
|
||||||
}),
|
}),
|
||||||
None => stage.update(cx, |stage, _| {
|
None => stage.update(cx, |stage, _| stage.visible = kept.or(stage.visible)),
|
||||||
stage.seen = total;
|
|
||||||
stage.visible = kept.or(stage.visible);
|
|
||||||
}),
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
let range = visible.range(total);
|
let range = visible.range(total);
|
||||||
|
|||||||
@@ -104,13 +104,17 @@ pub(crate) fn fit(low: f64, high: f64) -> ((f64, f64), Vec<f64>) {
|
|||||||
((low, high), inside)
|
((low, high), inside)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Digits kept past a step's first; float noise sits near the sixteenth.
|
||||||
|
const DIGITS: usize = 6;
|
||||||
|
|
||||||
/// Places after the point that show every multiple of `step` exactly: none for whole steps, four for 0.0025.
|
/// Places after the point that show every multiple of `step` exactly: none for whole steps, four for 0.0025.
|
||||||
pub(crate) fn decimals(step: f64) -> usize {
|
pub(crate) fn decimals(step: f64) -> usize {
|
||||||
assert!(
|
assert!(
|
||||||
step.is_finite() && step > 0.0,
|
step.is_finite() && step > 0.0,
|
||||||
"a step is positive, not {step}"
|
"a step is positive, not {step}"
|
||||||
);
|
);
|
||||||
let text = format!("{step:.10}");
|
let leading = (-step.log10().floor()).max(0.0) as usize;
|
||||||
|
let text = format!("{step:.*}", leading + DIGITS);
|
||||||
let fraction = text.split_once('.').map_or("", |(_, fraction)| fraction);
|
let fraction = text.split_once('.').map_or("", |(_, fraction)| fraction);
|
||||||
fraction.trim_end_matches('0').len()
|
fraction.trim_end_matches('0').len()
|
||||||
}
|
}
|
||||||
@@ -207,5 +211,6 @@ mod tests {
|
|||||||
1,
|
1,
|
||||||
"float noise from a difference drops away"
|
"float noise from a difference drops away"
|
||||||
);
|
);
|
||||||
|
assert_eq!(decimals(1e-11), 11, "a tiny step keeps its places");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
+52
-10
@@ -2,7 +2,7 @@ use std::rc::Rc;
|
|||||||
|
|
||||||
use gpui::{
|
use gpui::{
|
||||||
AppContext as _, Context, Entity, IntoElement, ParentElement, Render, Styled, TestAppContext,
|
AppContext as _, Context, Entity, IntoElement, ParentElement, Render, Styled, TestAppContext,
|
||||||
Window, div, px,
|
VisualTestContext, Window, div, px,
|
||||||
};
|
};
|
||||||
use jiff::Timestamp;
|
use jiff::Timestamp;
|
||||||
|
|
||||||
@@ -21,7 +21,8 @@ fn candles(count: usize) -> Rc<Vec<Candle>> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
struct Pair {
|
struct Pair {
|
||||||
candles: Rc<Vec<Candle>>,
|
first: Rc<Vec<Candle>>,
|
||||||
|
second: Rc<Vec<Candle>>,
|
||||||
sync: Entity<ChartSync>,
|
sync: Entity<ChartSync>,
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -29,18 +30,19 @@ impl Render for Pair {
|
|||||||
fn render(&mut self, _: &mut Window, _: &mut Context<Self>) -> impl IntoElement {
|
fn render(&mut self, _: &mut Window, _: &mut Context<Self>) -> impl IntoElement {
|
||||||
div()
|
div()
|
||||||
.w(px(800.))
|
.w(px(800.))
|
||||||
.child(CandlestickChart::new("first", self.candles.clone()).sync(&self.sync))
|
.child(CandlestickChart::new("first", self.first.clone()).sync(&self.sync))
|
||||||
.child(CandlestickChart::new("second", self.candles.clone()).sync(&self.sync))
|
.child(CandlestickChart::new("second", self.second.clone()).sync(&self.sync))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[gpui::test]
|
/// Two synced charts of 40 candles, their shared window held on the newest 20.
|
||||||
fn synced_charts_move_their_shared_window_once_per_append(cx: &mut TestAppContext) {
|
fn held(cx: &mut TestAppContext) -> (Entity<Pair>, Entity<ChartSync>, &mut VisualTestContext) {
|
||||||
cx.update(Theme::init);
|
cx.update(Theme::init);
|
||||||
let sync = cx.new(|_| ChartSync::default());
|
let sync = cx.new(|_| ChartSync::default());
|
||||||
let shared = sync.clone();
|
let shared = sync.clone();
|
||||||
let (view, cx) = cx.add_window_view(|_, _| Pair {
|
let (view, cx) = cx.add_window_view(|_, _| Pair {
|
||||||
candles: candles(40),
|
first: candles(40),
|
||||||
|
second: candles(40),
|
||||||
sync: shared,
|
sync: shared,
|
||||||
});
|
});
|
||||||
cx.run_until_parked();
|
cx.run_until_parked();
|
||||||
@@ -53,14 +55,54 @@ fn synced_charts_move_their_shared_window_once_per_append(cx: &mut TestAppContex
|
|||||||
});
|
});
|
||||||
view.update(cx, |_, cx| cx.notify());
|
view.update(cx, |_, cx| cx.notify());
|
||||||
cx.run_until_parked();
|
cx.run_until_parked();
|
||||||
|
(view, sync, cx)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn start(sync: &Entity<ChartSync>, cx: &mut VisualTestContext) -> f64 {
|
||||||
|
sync.read_with(cx, |sync, _| sync.visible.expect("held").start)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[gpui::test]
|
||||||
|
fn synced_charts_move_their_shared_window_once_per_append(cx: &mut TestAppContext) {
|
||||||
|
let (view, sync, cx) = held(cx);
|
||||||
view.update(cx, |pair, cx| {
|
view.update(cx, |pair, cx| {
|
||||||
pair.candles = candles(45);
|
pair.first = candles(45);
|
||||||
|
pair.second = candles(45);
|
||||||
cx.notify();
|
cx.notify();
|
||||||
});
|
});
|
||||||
cx.run_until_parked();
|
cx.run_until_parked();
|
||||||
let start = sync.read_with(cx, |sync, _| sync.visible.expect("held").start);
|
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
start, 25.0,
|
start(&sync, cx),
|
||||||
|
25.0,
|
||||||
"five new candles move the shared window five, not ten"
|
"five new candles move the shared window five, not ten"
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[gpui::test]
|
||||||
|
fn a_lagging_chart_leaves_the_shared_window_alone(cx: &mut TestAppContext) {
|
||||||
|
let (view, sync, cx) = held(cx);
|
||||||
|
view.update(cx, |pair, cx| {
|
||||||
|
pair.second = candles(45);
|
||||||
|
cx.notify();
|
||||||
|
});
|
||||||
|
cx.run_until_parked();
|
||||||
|
for _ in 0..2 {
|
||||||
|
view.update(cx, |_, cx| cx.notify());
|
||||||
|
cx.run_until_parked();
|
||||||
|
}
|
||||||
|
assert_eq!(
|
||||||
|
(start(&sync, cx), sync.read_with(cx, |sync, _| sync.seen)),
|
||||||
|
(25.0, 45),
|
||||||
|
"redraws while the first chart lags move nothing"
|
||||||
|
);
|
||||||
|
view.update(cx, |pair, cx| {
|
||||||
|
pair.first = candles(45);
|
||||||
|
cx.notify();
|
||||||
|
});
|
||||||
|
cx.run_until_parked();
|
||||||
|
assert_eq!(
|
||||||
|
start(&sync, cx),
|
||||||
|
25.0,
|
||||||
|
"the lagging chart's append is already shown"
|
||||||
|
);
|
||||||
|
}
|
||||||
Reference in new issue
Block a user