refactor(monorepo): reorganización lógica + renames + SDDs + split CHANGELOG
Reorganización física de crates/: - core/ (mezclaba 6 propósitos) se divide en protocol/, init/, runtime/, compat/ - shared/ (3 crates) se redistribuye en protocol/ e init/ - lapaloma (sub-módulo de ui_engine) se promueve a modules/pineal/ Renames de proyectos: - shipote → shuma (runtime de sandboxes) - nouser → akasha (explorador de Mónadas) - yahweh → nahual (motor GPUI, antes ui_engine/) - lapaloma → pineal (data-viz agnóstica) Fraccionamiento UI → core agnóstico: - vista-core (DeckState + snap, 175 LOC, 5 tests verdes) - barra-core (Task + render_html + sanitize, 90 LOC, 5 tests verdes) - vista-web y barra-web ahora son thin DOM bindings Documentación nueva: - 16 SDDs por subdirectorio (≤80 LOC c/u): protocol/init/runtime/compat + 10 módulos + apps/ - docs/STATUS.md con cifras reales por proyecto - docs/ROADMAP.md con plan a finalización (6 hitos, ~6-8 semanas) - CHANGELOG.md particionado en docs/changelog/<proyecto>.md (7 buckets) Automatización: - scripts/reorg.py — script idempotente que: git mv directorios, renombra package names, recomputa path = refs, reescribe imports rust, actualiza workspace Cargo.toml. Soporta --dry-run. - scripts/split-changelog.py — particiona CHANGELOG por componente. Validación: - cargo check --workspace pasa (124 crates + 2 nuevos cores). - 10 tests adicionales (5 en vista-core + 5 en barra-core) verdes. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,121 @@
|
||||
//! Render de candlesticks sobre cualquier `Canvas`.
|
||||
//!
|
||||
//! Por cada bar visible:
|
||||
//! - **Wick** = línea vertical de `(t, low)` a `(t, high)`.
|
||||
//! - **Body** = rect de `(t - body_w/2, open)` a `(t + body_w/2, close)`,
|
||||
//! relleno bull (close > open) / bear (close < open) / neutro.
|
||||
//!
|
||||
//! Esta función es agnóstica de gpui — habla contra el trait
|
||||
//! `Canvas`. El `Element` GPUI que la consume vive en `element.rs`.
|
||||
|
||||
use pineal_cartesian::CoordinateSystem;
|
||||
use pineal_render::{Canvas, Color, Point, Rect, StrokeStyle};
|
||||
|
||||
use crate::ohlc_buffer::OhlcBuffer;
|
||||
|
||||
/// Estilo visual de los candlesticks.
|
||||
#[derive(Debug, Clone, Copy)]
|
||||
pub struct CandlestickStyle {
|
||||
pub bull_color: Color,
|
||||
pub bear_color: Color,
|
||||
/// Color del body cuando open == close. Suele ser el axis color.
|
||||
pub neutral_color: Color,
|
||||
/// Ancho del wick (línea central). En píxeles.
|
||||
pub wick_width: f32,
|
||||
/// Ancho mínimo del body, en píxeles. Cuando el spacing entre
|
||||
/// bars cae por debajo, el body usa este floor.
|
||||
pub body_min_width: f32,
|
||||
/// Fracción del spacing entre bars consecutivas que ocupa el body.
|
||||
/// 0.7 deja un gap del 30% entre velas.
|
||||
pub body_width_ratio: f32,
|
||||
}
|
||||
|
||||
impl Default for CandlestickStyle {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
bull_color: Color::from_hex(0x88c08a),
|
||||
bear_color: Color::from_hex(0xbf616a),
|
||||
neutral_color: Color::rgba(0.7, 0.7, 0.75, 1.0),
|
||||
wick_width: 1.0,
|
||||
body_min_width: 2.0,
|
||||
body_width_ratio: 0.7,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Dibuja todas las velas del buffer visibles en el viewport del
|
||||
/// `CoordinateSystem`. Bars fuera de rango se skippean.
|
||||
pub fn paint_candlesticks(
|
||||
canvas: &mut dyn Canvas,
|
||||
cs: &CoordinateSystem,
|
||||
data: &OhlcBuffer,
|
||||
style: CandlestickStyle,
|
||||
) {
|
||||
let n = data.len();
|
||||
if n == 0 {
|
||||
return;
|
||||
}
|
||||
|
||||
let plot = cs.plot;
|
||||
let viewport = cs.viewport;
|
||||
|
||||
// Spacing entre bars consecutivas en píxeles. Asume bars
|
||||
// aproximadamente equiespaciadas en X (caso típico OHLC
|
||||
// post-aggregation).
|
||||
let body_width = if n >= 2 {
|
||||
let first_t = data.bar(0).t as f64;
|
||||
let last_t = data.bar(n - 1).t as f64;
|
||||
let span_t = (last_t - first_t).max(f32::EPSILON as f64);
|
||||
let span_px = (span_t / viewport.x_span()) * plot.w as f64;
|
||||
let spacing = span_px / (n as f64 - 1.0);
|
||||
((spacing * style.body_width_ratio as f64) as f32).max(style.body_min_width)
|
||||
} else {
|
||||
style.body_min_width
|
||||
};
|
||||
let half_body = body_width * 0.5;
|
||||
|
||||
for i in 0..n {
|
||||
let bar = data.bar(i);
|
||||
|
||||
// Clip aproximado: si el bar entero queda fuera del viewport
|
||||
// X, lo saltamos.
|
||||
if (bar.t as f64) < viewport.x_min - viewport.x_span() * 0.05
|
||||
|| (bar.t as f64) > viewport.x_max + viewport.x_span() * 0.05
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
let px_center = cs.data_to_pixel(bar.t as f64, bar.o as f64).x;
|
||||
let py_open = cs.data_to_pixel(bar.t as f64, bar.o as f64).y;
|
||||
let py_close = cs.data_to_pixel(bar.t as f64, bar.c as f64).y;
|
||||
let py_high = cs.data_to_pixel(bar.t as f64, bar.h as f64).y;
|
||||
let py_low = cs.data_to_pixel(bar.t as f64, bar.l as f64).y;
|
||||
|
||||
let color = if bar.is_bull() {
|
||||
style.bull_color
|
||||
} else if bar.is_bear() {
|
||||
style.bear_color
|
||||
} else {
|
||||
style.neutral_color
|
||||
};
|
||||
|
||||
// Wick: línea vertical de high a low.
|
||||
canvas.stroke_line(
|
||||
Point::new(px_center, py_high),
|
||||
Point::new(px_center, py_low),
|
||||
StrokeStyle::new(style.wick_width, color),
|
||||
);
|
||||
|
||||
// Body: rect entre open y close.
|
||||
let (y_top, y_bot) = if py_open < py_close {
|
||||
(py_open, py_close)
|
||||
} else {
|
||||
(py_close, py_open)
|
||||
};
|
||||
let body_h = (y_bot - y_top).max(1.0); // floor 1px para doji
|
||||
canvas.fill_rect(
|
||||
Rect::new(px_center - half_body, y_top, body_width, body_h),
|
||||
color,
|
||||
);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user