feat(tahuantinsuyu): GR dual-ring + topo ascensional pegado al dial + coords
Dos cambios mayores que cierran el sistema GR/ascensional:
1. Reordenamiento radial — la capa ascensional (topocéntrico
Polich-Page) se ubica AHORA pegada al sign dial, y la
geocéntrica clásica queda más adentro. Layout outer→inner:
- sign_dial (1.00 → 0.88)
- topo_houses_outer (0.875) / topo_houses_inner (0.79) ← P-P pegadas al zodiaco
- topocentric (0.755) ← planetas topo con coords
- transits (0.71)
- houses_outer (0.66) / houses_inner (0.54) ← Placidus geo
- midpoints (0.50) / bodies (0.47) / bodies_inner (0.44) ← natal geo con coords
- pd_direct (0.495) / pd_converse (0.425) ← dual-ring GR
- aspects (0.41) / progression (0.36) / solar_arc (0.30)
Topocéntrico default ON (era OFF en la fase previa).
Coord labels ahora se pintan también en planetas topocéntricos
(label hacia adentro, no afuera, para no chocar con casas P-P).
2. Sistema GR Direcciones Primarias (dual-ring):
- Nuevo `PipelineRequest::PrimaryDirections { target_age_years }`.
- `build_primary_directions_overlay` proyecta cada cuerpo natal
con `directed_longitude` (key Naibod) en dos direcciones —
directa y conversa — y emite dos Layer Bodies con
`module_id` "pd_direct" / "pd_converse".
- Canvas: nuevos `pd_direct` y `pd_converse` en Radii; en el
render de Bodies disco más chico y alpha 0.80. Los dos anillos
se marcan con punteado fino que "abraza" el cinturón natal
por afuera y por adentro — el natal queda en el centro.
- Nuevo `PrimaryDirectionsModule` con toggle + slider de edad
(0..120, step 0.05a). Activable desde el panel.
Tests: 6 shell + 5 coord siguen verdes; el motor matemático
(eternal-astrology directed_longitude) y house system Polich-Page
están testeados desde el commit `e385ab2` en eternal.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
diff --git a/crates/apps/tahuantinsuyu/src/shell.rs b/crates/apps/tahuantinsuyu/src/shell.rs
index ca47d5f..4f79ab1 100644
--- a/crates/apps/tahuantinsuyu/src/shell.rs
+++ b/crates/apps/tahuantinsuyu/src/shell.rs
@@ -550,6 +550,12 @@ impl Shell {
if module_enabled(&self.module_configs, "topocentric") {
requests.push(PipelineRequest::Topocentric);
}
+ if module_enabled(&self.module_configs, "primary_directions") {
+ let age = self.module_age_or_current("primary_directions");
+ requests.push(PipelineRequest::PrimaryDirections {
+ target_age_years: age,
+ });
+ }
if module_enabled(&self.module_configs, "composite") {
if let Some(partner) = self.resolve_composite_partner() {
requests.push(PipelineRequest::Composite {
diff --git a/crates/modules/tahuantinsuyu/tahuantinsuyu-canvas/src/lib.rs b/crates/modules/tahuantinsuyu/tahuantinsuyu-canvas/src/lib.rs
index e1c3f54..e61d532 100644
--- a/crates/modules/tahuantinsuyu/tahuantinsuyu-canvas/src/lib.rs
+++ b/crates/modules/tahuantinsuyu/tahuantinsuyu-canvas/src/lib.rs
@@ -1115,22 +1115,25 @@ fn render_wheel(
if matches!(layer.kind, LayerKind::Bodies) {
let is_natal = layer.module_id == "natal";
let is_topo = layer.module_id == "topocentric";
+ let is_pd_direct = layer.module_id == "pd_direct";
+ let is_pd_converse = layer.module_id == "pd_converse";
+ let is_pd = is_pd_direct || is_pd_converse;
let ring = radii.body_ring(&layer.module_id);
let alpha = if is_natal {
1.0
} else if is_topo {
0.75
+ } else if is_pd {
+ 0.80
} else {
0.88
};
- // Topocéntrico va con disco un poco más chico que el
- // natal, y con desaturación implícita en `alpha`. El
- // shift respecto al natal es lo que el ojo lee, no el
- // tamaño individual.
let font_size = (if is_natal {
18.0
} else if is_topo {
15.0
+ } else if is_pd {
+ 13.0
} else {
14.0
}) * s;
@@ -1138,6 +1141,8 @@ fn render_wheel(
26.0
} else if is_topo {
22.0
+ } else if is_pd {
+ 20.0
} else {
22.0
}) * s;
@@ -1163,23 +1168,31 @@ fn render_wheel(
));
// Coord label: grado dentro del signo + glyph del
- // signo, pintado justo afuera del disco del
- // planeta (radialmente). Sólo en natal (los
- // overlays ya cargan info en su badge / tooltip).
- if show_coords && is_natal {
+ // signo, pintado afuera del disco del planeta
+ // (radialmente). Se pinta para el natal (afuera)
+ // y para el topocéntrico (más afuera aún, hacia
+ // el sign dial) — los dos sistemas conviven con
+ // sus coords. Otros overlays (progression, solar
+ // arc) usan badges en el footer.
+ if show_coords && (is_natal || is_topo) {
let coord = format_coord_compact(g.deg);
- let label_r = ring + disk_size * 0.7;
+ // Topo: label hacia ADENTRO (entre planeta y
+ // casas P-P arriba); natal: label hacia
+ // AFUERA (entre planeta y casas Placidus).
+ let label_r = if is_topo {
+ ring - disk_size * 0.7
+ } else {
+ ring + disk_size * 0.7
+ };
let (lx, ly) = polar_to_screen(g.deg, asc, rot_offset, label_r);
- wheel = wheel.child(
- coord_label(
- cx_center + lx,
- cy_center + ly,
- coord.into(),
- theme.fg_muted,
- halo_bg,
- 9.5 * s,
- ),
- );
+ wheel = wheel.child(coord_label(
+ cx_center + lx,
+ cy_center + ly,
+ coord.into(),
+ theme.fg_muted,
+ halo_bg,
+ 9.5 * s,
+ ));
}
}
}
@@ -1604,27 +1617,40 @@ fn format_offset(minutes: i64) -> String {
#[derive(Clone, Copy)]
struct Radii {
+ // Layout outer→inner. La capa **ascensional** (sistema topocéntrico
+ // Polich-Page) va pegada al sign dial — su lógica nace del eje
+ // ASC/MC y de la ascensión recta del observador, conceptualmente
+ // hermana del zodiaco. La capa **geocéntrica** clásica
+ // (casas Placidus + planetas tropicales sin paralaje) vive más
+ // adentro, alrededor del cinturón de planetas natales.
sign_outer: f32,
sign_inner: f32,
+ /// Anillo exterior de las casas Polich-Page (topocéntricas), pegado
+ /// al sign dial. Los cusps llegan hasta el `sign_inner`.
+ topo_houses_outer: f32,
+ topo_houses_inner: f32,
+ /// Carril de planetas topocéntricos — apenas dentro del anillo
+ /// de casas P-P. Lleva sus propios coord labels.
+ topocentric: f32,
/// Anillo de glifos de tránsito (cuando el overlay está activo).
transits: f32,
+ /// Casas geocéntricas (Placidus default) — más adentro que las
+ /// topocéntricas, claramente diferenciables.
houses_outer: f32,
houses_inner: f32,
- /// Anillo de midpoints — entre bodies natales y houses_inner.
+ /// Anillo de midpoints — entre cuerpos geocéntricos y `houses_inner`.
midpoints: f32,
- /// Anillo principal de cuerpos natales — donde se posan los
- /// glyphs. Junto con `bodies_inner` forman el "cinturón" de los
- /// planetas (doble línea visual).
+ /// Anillo principal de cuerpos natales geocéntricos.
bodies: f32,
- /// Borde interior del cinturón de planetas. Marca dónde "termina"
- /// la zona de cuerpos y empieza la zona de aspectos.
+ /// Borde interior del cinturón de planetas geocéntricos.
bodies_inner: f32,
- /// Cuerpos topocéntricos (capa "ascensional") — un poco hacia
- /// adentro de `bodies` para que un mismo planeta se vea como
- /// "doble glyph": natal afuera, topocéntrico justo dentro. En
- /// Luna la separación angular es visible (~1°); en exteriores
- /// los dos glyphs se superponen casi exactamente.
- topocentric: f32,
+ /// Direcciones Primarias DIRECTAS (Sistema GR): ring exterior del
+ /// "abrazo" GR — justo afuera del cinturón natal.
+ pd_direct: f32,
+ /// Direcciones Primarias CONVERSAS (Sistema GR): ring interior —
+ /// el cinturón natal queda entre `pd_direct` (afuera) y
+ /// `pd_converse` (dentro), formando el dual-ring de rectificación.
+ pd_converse: f32,
/// Anillo interno con cuerpos progresados (overlay opcional).
progression: f32,
/// Anillo más interno con cuerpos dirigidos por Solar Arc.
@@ -1632,9 +1658,7 @@ struct Radii {
/// Anillo de carta compuesta (midpoint Davison) con un partner.
composite: f32,
/// Círculo donde anclan las líneas de aspecto entre cuerpos
- /// natales. Justo dentro del cinturón de planetas, no en el
- /// centro — así las líneas conectan cuerpos cercanos al ring
- /// donde se ven, no atraviesan toda la rueda.
+ /// natales. Justo dentro del cinturón de planetas.
aspects: f32,
}
@@ -1643,28 +1667,36 @@ impl Radii {
Self {
sign_outer: r,
sign_inner: r * 0.88,
- transits: r * 0.82,
- houses_outer: r * 0.78,
- houses_inner: r * 0.66,
- midpoints: r * 0.62,
- bodies: r * 0.60,
- // bodies_inner cerca de bodies — los dos anillos juntos
- // forman un "carril" estrecho que delimita la franja de
- // planetas, no dos líneas separadas que confunden.
- bodies_inner: r * 0.57,
- // Topocéntrico justo bajo el carril natal: los dos
- // glyphs comparten ancho visual, el shift relativo
- // (Luna en particular) se lee como "el natal apunta a
- // este grado, el topo a este otro".
- topocentric: r * 0.555,
- // aspects justo bajo el carril de cuerpos. Las líneas
- // de aspecto entran a este radio, pero el círculo en sí
- // no se pinta — son las líneas las que importan, no
- // un anillo extra que sume ruido.
- aspects: r * 0.54,
- progression: r * 0.46,
- solar_arc: r * 0.38,
- composite: r * 0.30,
+ // Ascensional (Polich-Page): pegado al sign dial.
+ topo_houses_outer: r * 0.875,
+ topo_houses_inner: r * 0.79,
+ // Carril topocéntrico de planetas: justo dentro de las
+ // casas P-P. Lleva sus coord labels al borde interior.
+ topocentric: r * 0.755,
+ // Tránsitos: ring intermedio entre las dos coronas (topo y
+ // geo), apenas debajo del topocéntrico.
+ transits: r * 0.71,
+ // Capa geocéntrica clásica más adentro — claramente
+ // separada del bloque ascensional.
+ houses_outer: r * 0.66,
+ houses_inner: r * 0.54,
+ midpoints: r * 0.50,
+ bodies: r * 0.47,
+ bodies_inner: r * 0.44,
+ // Dual-ring GR — abraza el cinturón natal por afuera y por
+ // adentro. Para Saturno los dos rings caen casi en la misma
+ // posición angular (poca rotación diurna afecta su lon en
+ // años humanos); para luminarias los rings divergen
+ // visiblemente y leen "directa rumbo a este grado, conversa
+ // hacia este otro".
+ pd_direct: r * 0.495,
+ pd_converse: r * 0.425,
+ // aspects justo bajo el carril natal — las líneas entran
+ // a este radio sin pintar el círculo (sería ruido extra).
+ aspects: r * 0.41,
+ progression: r * 0.36,
+ solar_arc: r * 0.30,
+ composite: r * 0.24,
}
}
@@ -1676,6 +1708,8 @@ impl Radii {
"composite" => self.composite,
"midpoints" => self.midpoints,
"topocentric" => self.topocentric,
+ "pd_direct" => self.pd_direct,
+ "pd_converse" => self.pd_converse,
_ => self.bodies,
}
}
@@ -1762,15 +1796,31 @@ fn paint_wheel(
let house_color = with_alpha(house_base, 0.85);
stroke_circle_3d(window, cx, cy, radii.houses_outer, 1.1, house_color, theme);
stroke_circle_3d(window, cx, cy, radii.houses_inner, 1.1, house_color, theme);
+ // Si hay capa topocéntrica activa, pintar también sus dos
+ // anillos (con stroke más sutil que el geocéntrico, para que
+ // se lea como "sistema ascensional" sin competir).
+ if layers
+ .iter()
+ .any(|l| matches!(l.kind, LayerKind::Houses) && l.module_id == "topocentric")
+ {
+ let topo_color = with_alpha(house_base, 0.55);
+ stroke_circle(window, cx, cy, radii.topo_houses_outer, 0.8, topo_color);
+ stroke_circle(window, cx, cy, radii.topo_houses_inner, 0.8, topo_color);
+ }
for layer in layers {
if matches!(layer.kind, LayerKind::Houses) {
let is_topo = layer.module_id == "topocentric";
+ let (r_in, r_out) = if is_topo {
+ (radii.topo_houses_inner, radii.topo_houses_outer)
+ } else {
+ (radii.houses_inner, radii.houses_outer)
+ };
if let Geometry::Ring { cusps_deg } = &layer.geometry {
for (i, c) in cusps_deg.iter().enumerate() {
let is_angle = i == 0 || i == 3 || i == 6 || i == 9;
let color = if is_topo {
- with_alpha(house_base, 0.55)
+ with_alpha(house_base, 0.60)
} else if is_angle {
palette.angle_highlight
} else {
@@ -1779,27 +1829,44 @@ fn paint_wheel(
let width = if is_angle && !is_topo { 2.0 } else { 0.8 };
if is_topo {
// Topocéntrico: cusp como línea punteada
- // en su propio anillo (un poco más
- // adentro que las casas geocéntricas) →
- // se distingue como sistema alternativo.
- let (xi, yi) = polar_to_screen(
- *c,
- ascendant_deg,
- rot_offset_deg,
- radii.houses_inner - 4.0,
- );
- let (xo, yo) = polar_to_screen(
- *c,
- ascendant_deg,
- rot_offset_deg,
- radii.houses_inner - 28.0,
- );
+ // en su propio anillo cercano al sign
+ // dial — se distingue del Placidus
+ // geocéntrico por el dash pattern y la
+ // ubicación más exterior.
paint_segment(
window,
- cx + xi,
- cy + yi,
- cx + xo,
- cy + yo,
+ cx
+ + polar_to_screen(
+ *c,
+ ascendant_deg,
+ rot_offset_deg,
+ r_in,
+ )
+ .0,
+ cy
+ + polar_to_screen(
+ *c,
+ ascendant_deg,
+ rot_offset_deg,
+ r_in,
+ )
+ .1,
+ cx
+ + polar_to_screen(
+ *c,
+ ascendant_deg,
+ rot_offset_deg,
+ r_out,
+ )
+ .0,
+ cy
+ + polar_to_screen(
+ *c,
+ ascendant_deg,
+ rot_offset_deg,
+ r_out,
+ )
+ .1,
color,
Some((3.0, 2.5)),
1.0,
@@ -1812,8 +1879,8 @@ fn paint_wheel(
*c,
ascendant_deg,
rot_offset_deg,
- radii.houses_inner,
- radii.houses_outer,
+ r_in,
+ r_out,
color,
width,
);
@@ -1866,6 +1933,35 @@ fn paint_wheel(
0.6,
with_alpha(palette.dial_ring, 0.35),
);
+ // GR dual-ring: si las capas de direcciones primarias están
+ // presentes, marcar sus anillos para que el visual lea como
+ // "abrazo" del cinturón natal. La directa va punteada,
+ // la conversa también — la diferencia entre las dos es la
+ // ubicación radial (afuera vs adentro del cinturón natal).
+ let has_pd = layers.iter().any(|l| {
+ matches!(l.kind, LayerKind::Bodies)
+ && (l.module_id == "pd_direct" || l.module_id == "pd_converse")
+ });
+ if has_pd {
+ let pd_color = with_alpha(palette.angle_highlight, 0.50);
+ for r in [radii.pd_direct, radii.pd_converse] {
+ // Pintamos el anillo como tramo punteado fino: 24
+ // segmentos cortos a lo largo del círculo.
+ let steps = 96;
+ for i in 0..steps {
+ if i % 2 != 0 {
+ continue;
+ }
+ let a0 = (i as f32) / (steps as f32) * std::f32::consts::TAU;
+ let a1 = ((i + 1) as f32) / (steps as f32) * std::f32::consts::TAU;
+ let x0 = cx + r * a0.cos();
+ let y0 = cy + r * a0.sin();
+ let x1 = cx + r * a1.cos();
+ let y1 = cy + r * a1.sin();
+ paint_segment(window, x0, y0, x1, y1, pd_color, None, 0.6);
+ }
+ }
+ }
}
// 3. Aspectos. Cada module_id usa su par de radios — natal-natal
diff --git a/crates/modules/tahuantinsuyu/tahuantinsuyu-engine/src/bridge.rs b/crates/modules/tahuantinsuyu/tahuantinsuyu-engine/src/bridge.rs
index ee53387..acde26f 100644
--- a/crates/modules/tahuantinsuyu/tahuantinsuyu-engine/src/bridge.rs
+++ b/crates/modules/tahuantinsuyu/tahuantinsuyu-engine/src/bridge.rs
@@ -9,10 +9,11 @@ use std::sync::{Arc, OnceLock};
use std::time::Instant;
use eternal_astrology::{
- all_lots, composite, find_aspects, find_synastry_aspects, next_return, secondary_progression,
- solar_arc_true, topocentric_ecliptic, Aspect, AspectKind as EAspectKind, BirthData, BodySet,
- ChartConfig, HouseSystem as EHouseSystem, Houses as EHouses, NatalChart, OrbTable,
- Zodiac as EZodiac,
+ all_lots, composite, directed_longitude, find_aspects, find_synastry_aspects, next_return,
+ primary_direction::PrimaryDirection, secondary_progression, solar_arc_true, topocentric_ecliptic,
+ Aspect, AspectKind as EAspectKind, BirthData, BodySet, ChartConfig,
+ DirectionKey as EDirectionKey, HouseSystem as EHouseSystem, Houses as EHouses, NatalChart,
+ OrbTable, Zodiac as EZodiac,
};
use eternal_sky::{Ayanamsha, Body, EphemerisSession, Instant as ESInstant, Observer, SessionConfig};
@@ -389,6 +390,14 @@ pub fn compose(
"Topocéntrico (Polich-Page)".into(),
);
}
+ crate::PipelineRequest::PrimaryDirections { target_age_years } => {
+ build_primary_directions_overlay(&natal, *target_age_years, &mut render);
+ push_overlay_meta(
+ &mut render,
+ "primary_directions",
+ format!("GR Direcciones · {:.1}a", target_age_years),
+ );
+ }
}
}
@@ -558,6 +567,69 @@ fn build_topocentric_overlay(
Ok(())
}
+/// GR dual-ring de Direcciones Primarias: a la edad pedida, cada
+/// cuerpo natal se proyecta dos veces — directa (rotación diurna
+/// forward, anillo afuera) y conversa (rotación inversa, anillo
+/// dentro). En rectificación, los dos rings se ven simultáneamente
+/// y si un evento real cayó cerca de un ángulo, debe aparecer
+/// "cruzado" con ambos arcos coincidentes — eso valida la hora.
+///
+/// Usa el key Naibod (0°59'08″/año) como default — convención GR.
+fn build_primary_directions_overlay(
+ natal: &NatalChart,
+ target_age_years: f64,
+ render: &mut RenderModel,
+) {
+ let key = EDirectionKey::Naibod;
+ let eps = natal.obliquity_rad;
+
+ let project = |dir: PrimaryDirection| -> Vec<Glyph> {
+ natal
+ .placements
+ .iter()
+ .map(|p| {
+ let new_lon_rad = directed_longitude(
+ p.right_ascension_rad,
+ p.declination_rad,
+ target_age_years,
+ dir,
+ key,
+ eps,
+ );
+ let new_lon_deg = new_lon_rad.to_degrees() as f32;
+ Glyph {
+ deg: new_lon_deg,
+ symbol: body_symbol(p.body).into(),
+ annotation: Some(format!("{:.2}°", new_lon_deg)),
+ retrograde: p.longitude_rate_rad_per_day < 0.0,
+ house: None,
+ dignity_marker: None,
+ }
+ })
+ .collect()
+ };
+
+ let direct_glyphs = project(PrimaryDirection::Direct);
+ let converse_glyphs = project(PrimaryDirection::Converse);
+
+ render.layers.push(Layer {
+ module_id: "pd_direct".into(),
+ kind: LayerKind::Bodies,
+ ring: 0.0,
+ z: 10,
+ geometry: Geometry::GlyphsOnly,
+ glyphs: direct_glyphs,
+ });
+ render.layers.push(Layer {
+ module_id: "pd_converse".into(),
+ kind: LayerKind::Bodies,
+ ring: 0.0,
+ z: 11,
+ geometry: Geometry::GlyphsOnly,
+ glyphs: converse_glyphs,
+ });
+}
+
fn build_progression_overlay(
natal: &NatalChart,
target_age_years: f64,
diff --git a/crates/modules/tahuantinsuyu/tahuantinsuyu-engine/src/lib.rs b/crates/modules/tahuantinsuyu/tahuantinsuyu-engine/src/lib.rs
index 6f7ca5b..64f14e5 100644
--- a/crates/modules/tahuantinsuyu/tahuantinsuyu-engine/src/lib.rs
+++ b/crates/modules/tahuantinsuyu/tahuantinsuyu-engine/src/lib.rs
@@ -333,6 +333,14 @@ pub enum PipelineRequest {
/// (~1° de shift); imperceptible en planetas exteriores. La capa
/// convive con la natal geocéntrica como overlay comparativo.
Topocentric,
+ /// `module_id = "pd_direct"` + `"pd_converse"` — Direcciones
+ /// Primarias del Sistema GR (García Rosas). Cada cuerpo natal se
+ /// proyecta dos veces: hacia adelante en el tiempo diurno
+ /// (direct) y hacia atrás (converse). Los dos resultados a la
+ /// edad pedida pintan un dual-ring para rectificación en vivo.
+ PrimaryDirections {
+ target_age_years: f64,
+ },
}
/// Opciones que afectan la pasada natal (qué aspectos pintar, qué
diff --git a/crates/modules/tahuantinsuyu/tahuantinsuyu-modules/src/lib.rs b/crates/modules/tahuantinsuyu/tahuantinsuyu-modules/src/lib.rs
index 3f9fa8b..228d248 100644
--- a/crates/modules/tahuantinsuyu/tahuantinsuyu-modules/src/lib.rs
+++ b/crates/modules/tahuantinsuyu/tahuantinsuyu-modules/src/lib.rs
@@ -143,6 +143,7 @@ impl Registry {
r.register(Box::new(lots::LotsModule));
r.register(Box::new(fixed_stars::FixedStarsModule));
r.register(Box::new(topocentric::TopocentricModule));
+ r.register(Box::new(primary_directions::PrimaryDirectionsModule));
r
}
@@ -840,13 +841,13 @@ pub mod topocentric {
matches!(kind, ChartKind::Natal)
}
fn enabled_by_default(&self) -> bool {
- false
+ true
}
fn controls(&self) -> Vec<Control> {
vec![Control::Toggle {
key: "enabled".into(),
label: "Activar".into(),
- default: false,
+ default: true,
hotkey: None,
}]
}
@@ -855,3 +856,59 @@ pub mod topocentric {
}
}
}
+
+// =====================================================================
+// PrimaryDirectionsModule — GR dual-ring (Direct + Converse)
+// =====================================================================
+
+pub mod primary_directions {
+ use super::*;
+
+ /// Direcciones Primarias del Sistema GR (García Rosas): cada
+ /// cuerpo natal se proyecta en dos rings — directa (rotación
+ /// diurna forward) y conversa (rotación inversa). El usuario
+ /// scrubea `target_age_years` para ver el movimiento en vivo.
+ /// Útil para rectificación: un evento real debe coincidir con
+ /// arcos directos y conversos consistentes si la hora natal es
+ /// correcta.
+ pub struct PrimaryDirectionsModule;
+
+ impl Module for PrimaryDirectionsModule {
+ fn id(&self) -> &'static str {
+ "primary_directions"
+ }
+ fn label(&self) -> &'static str {
+ "Direcciones primarias (GR)"
+ }
+ fn description(&self) -> &'static str {
+ "Dual-ring directas + conversas para rectificación en vivo."
+ }
+ fn applies_to(&self, kind: ChartKind) -> bool {
+ matches!(kind, ChartKind::Natal)
+ }
+ fn enabled_by_default(&self) -> bool {
+ false
+ }
+ fn controls(&self) -> Vec<Control> {
+ vec![
+ Control::Toggle {
+ key: "enabled".into(),
+ label: "Activar".into(),
+ default: false,
+ hotkey: None,
+ },
+ Control::Slider {
+ key: "target_age_years".into(),
+ label: "Edad (años)".into(),
+ min: 0.0,
+ max: 120.0,
+ step: 0.05,
+ default: 30.0,
+ },
+ ]
+ }
+ fn compute_layers(&self, _chart: &Chart, _cfg: &serde_json::Value) -> Vec<Layer> {
+ Vec::new()
+ }
+ }
+}