Some checks failed
nigig-build (CAD) / supply-chain (push) Has been cancelled
nigig-build (CAD) / cad-module (push) Has been cancelled
nigig-build (CAD) / full-crate-check (push) Has been cancelled
nigig-build (CAD) / cad-engine-coverage (push) Has been cancelled
nigig-build (CAD) / doc-workspace-coverage (push) Has been cancelled
nigig-build (CAD) / cad-widget-coverage (push) Has been cancelled
repo hygiene / hygiene (push) Has been cancelled
spreadsheet / engine-coverage (push) Has been cancelled
spreadsheet / ui-controller-coverage (push) Has been cancelled
Close gap #11 (chart integration) from the datagrid gap analysis, split as usual into testable headless logic plus a thin DSL widget: - chart.rs (measured): candle bucketing (OHLC from a price series), series bounds, line-point mapping with the reference's 8% padding, the vertical strips that trace a polyline with axis-aligned quads, candle body/wick geometry with up/down classification, and the 1/2/5×10^k axis tick step. - market.rs (measured): a deterministic live market — the reference's splitmix-style `mix64`, a HISTORY-capped random walk per symbol, lazy per-row symbol growth (so any selected row charts), tick() with roll-off, and the derived stats (last/change/pct_change/day_range/candles). - trend_chart.rs: a `TrendChart` widget (excluded from coverage like grid.rs) that colours and draws chart.rs output — gridlines, a polyline for a series, or candle bodies + wicks — via `set_series`/`set_candles`. - workspace.rs glue: a 220px chart panel under the grid (line + candlesticks side by side) fed from a 0.25s `Timer` ticker; selecting a grid row switches the charted symbol and updates the title label. Engine untouched. UI controllers: 112 tests (+16 chart/market). Coverage: ui-controllers 99.32% (floor 96), chart.rs 100%, market.rs 99.36%.
148 lines
4.2 KiB
Rust
148 lines
4.2 KiB
Rust
//! `TrendChart` widget: a self-contained line or candlestick chart drawn
|
|
//! with axis-aligned quads.
|
|
//!
|
|
//! This file carries the `script_mod!` DSL and is excluded from coverage
|
|
//! (like `grid.rs`): all of the geometry lives in `chart.rs`, and this is a
|
|
//! thin loop that colours and draws those rectangles.
|
|
|
|
use makepad_widgets::*;
|
|
|
|
use crate::chart::{self, Candle, PlotRect};
|
|
|
|
#[derive(Script, ScriptHook, Widget)]
|
|
pub struct TrendChart {
|
|
#[uid]
|
|
uid: WidgetUid,
|
|
#[source]
|
|
source: ScriptObjectRef,
|
|
#[walk]
|
|
walk: Walk,
|
|
#[layout]
|
|
layout: Layout,
|
|
#[redraw]
|
|
#[live]
|
|
draw_bg: DrawColor,
|
|
#[live]
|
|
draw_grid: DrawColor,
|
|
#[live]
|
|
draw_line: DrawColor,
|
|
#[live]
|
|
draw_candle: DrawColor,
|
|
#[live]
|
|
pub color_bg: Vec4f,
|
|
#[live]
|
|
pub color_grid: Vec4f,
|
|
#[live]
|
|
pub color_line: Vec4f,
|
|
#[live]
|
|
pub color_up: Vec4f,
|
|
#[live]
|
|
pub color_down: Vec4f,
|
|
#[rust]
|
|
series: Vec<f64>,
|
|
#[rust]
|
|
candles: Vec<Candle>,
|
|
}
|
|
|
|
impl TrendChart {
|
|
pub fn set_series(&mut self, values: &[f64]) {
|
|
self.series.clear();
|
|
self.series.extend_from_slice(values);
|
|
self.candles.clear();
|
|
}
|
|
|
|
pub fn set_candles(&mut self, candles: Vec<Candle>) {
|
|
self.candles = candles;
|
|
self.series.clear();
|
|
}
|
|
}
|
|
|
|
impl Widget for TrendChart {
|
|
fn handle_event(&mut self, _cx: &mut Cx, _event: &Event, _scope: &mut Scope) {}
|
|
|
|
fn draw_walk(&mut self, cx: &mut Cx2d, _scope: &mut Scope, walk: Walk) -> DrawStep {
|
|
let rect = cx.walk_turtle(walk);
|
|
self.draw_bg.color = self.color_bg;
|
|
self.draw_bg.draw_abs(cx, rect);
|
|
|
|
if rect.size.x < 40.0 || rect.size.y < 20.0 {
|
|
return DrawStep::done();
|
|
}
|
|
|
|
let plot = PlotRect {
|
|
x: rect.pos.x + 6.0,
|
|
y: rect.pos.y + 4.0,
|
|
width: (rect.size.x - 12.0).max(1.0),
|
|
height: (rect.size.y - 8.0).max(1.0),
|
|
};
|
|
|
|
// Faint horizontal gridlines at 25/50/75%.
|
|
self.draw_grid.color = self.color_grid;
|
|
for k in 1..4 {
|
|
let gy = plot.y + plot.height * k as f64 / 4.0;
|
|
self.draw_grid.draw_abs(
|
|
cx,
|
|
Rect {
|
|
pos: DVec2 { x: plot.x, y: gy },
|
|
size: DVec2 {
|
|
x: plot.width,
|
|
y: 1.0,
|
|
},
|
|
},
|
|
);
|
|
}
|
|
|
|
if !self.candles.is_empty() {
|
|
let shapes = chart::candle_geometry(&self.candles, plot);
|
|
for s in &shapes {
|
|
self.draw_candle.color = if s.up { self.color_up } else { self.color_down };
|
|
let (wx, wy, ww, wh) = s.wick;
|
|
self.draw_candle.draw_abs(
|
|
cx,
|
|
Rect {
|
|
pos: DVec2 { x: wx, y: wy },
|
|
size: DVec2 { x: ww, y: wh },
|
|
},
|
|
);
|
|
let (bx, by, bw, bh) = s.body;
|
|
self.draw_candle.draw_abs(
|
|
cx,
|
|
Rect {
|
|
pos: DVec2 { x: bx, y: by },
|
|
size: DVec2 { x: bw, y: bh },
|
|
},
|
|
);
|
|
}
|
|
} else {
|
|
self.draw_line.color = self.color_line;
|
|
for &(x, y, w, h) in &chart::line_strips(&self.series, plot) {
|
|
self.draw_line.draw_abs(
|
|
cx,
|
|
Rect {
|
|
pos: DVec2 { x, y },
|
|
size: DVec2 { x: w, y: h },
|
|
},
|
|
);
|
|
}
|
|
}
|
|
|
|
DrawStep::done()
|
|
}
|
|
}
|
|
|
|
// The script_mod! macro generates a `script_mod` function.
|
|
// We wrap it in `pub fn` so `lib.rs` can call it.
|
|
script_mod! {
|
|
use mod.prelude.widgets.*
|
|
|
|
mod.widgets.TrendChart = #(TrendChart::register_widget(vm)) {
|
|
width: Fill, height: Fill
|
|
color_bg: #x10101f, color_grid: #x232338
|
|
color_line: #x4fc3f7, color_up: #x81c995, color_down: #xef6a6a
|
|
|
|
draw_bg +: { draw_depth: 0.0 }
|
|
draw_grid +: { draw_depth: 0.1 }
|
|
draw_line +: { draw_depth: 0.2 }
|
|
draw_candle +: { draw_depth: 0.2 }
|
|
}
|
|
}
|