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>
This commit is contained in:
sergio
2026-05-18 18:08:29 +00:00
parent 1d49b9ff88
commit 7eb620aa17
5 changed files with 323 additions and 84 deletions
+6
View File
@@ -550,6 +550,12 @@ impl Shell {
if module_enabled(&self.module_configs, "topocentric") { if module_enabled(&self.module_configs, "topocentric") {
requests.push(PipelineRequest::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 module_enabled(&self.module_configs, "composite") {
if let Some(partner) = self.resolve_composite_partner() { if let Some(partner) = self.resolve_composite_partner() {
requests.push(PipelineRequest::Composite { requests.push(PipelineRequest::Composite {
@@ -1115,22 +1115,25 @@ fn render_wheel(
if matches!(layer.kind, LayerKind::Bodies) { if matches!(layer.kind, LayerKind::Bodies) {
let is_natal = layer.module_id == "natal"; let is_natal = layer.module_id == "natal";
let is_topo = layer.module_id == "topocentric"; 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 ring = radii.body_ring(&layer.module_id);
let alpha = if is_natal { let alpha = if is_natal {
1.0 1.0
} else if is_topo { } else if is_topo {
0.75 0.75
} else if is_pd {
0.80
} else { } else {
0.88 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 { let font_size = (if is_natal {
18.0 18.0
} else if is_topo { } else if is_topo {
15.0 15.0
} else if is_pd {
13.0
} else { } else {
14.0 14.0
}) * s; }) * s;
@@ -1138,6 +1141,8 @@ fn render_wheel(
26.0 26.0
} else if is_topo { } else if is_topo {
22.0 22.0
} else if is_pd {
20.0
} else { } else {
22.0 22.0
}) * s; }) * s;
@@ -1163,23 +1168,31 @@ fn render_wheel(
)); ));
// Coord label: grado dentro del signo + glyph del // Coord label: grado dentro del signo + glyph del
// signo, pintado justo afuera del disco del // signo, pintado afuera del disco del planeta
// planeta (radialmente). Sólo en natal (los // (radialmente). Se pinta para el natal (afuera)
// overlays ya cargan info en su badge / tooltip). // y para el topocéntrico (más afuera aún, hacia
if show_coords && is_natal { // 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 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); let (lx, ly) = polar_to_screen(g.deg, asc, rot_offset, label_r);
wheel = wheel.child( wheel = wheel.child(coord_label(
coord_label(
cx_center + lx, cx_center + lx,
cy_center + ly, cy_center + ly,
coord.into(), coord.into(),
theme.fg_muted, theme.fg_muted,
halo_bg, halo_bg,
9.5 * s, 9.5 * s,
), ));
);
} }
} }
} }
@@ -1604,27 +1617,40 @@ fn format_offset(minutes: i64) -> String {
#[derive(Clone, Copy)] #[derive(Clone, Copy)]
struct Radii { 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_outer: f32,
sign_inner: 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). /// Anillo de glifos de tránsito (cuando el overlay está activo).
transits: f32, transits: f32,
/// Casas geocéntricas (Placidus default) — más adentro que las
/// topocéntricas, claramente diferenciables.
houses_outer: f32, houses_outer: f32,
houses_inner: 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, midpoints: f32,
/// Anillo principal de cuerpos natales — donde se posan los /// Anillo principal de cuerpos natales geocéntricos.
/// glyphs. Junto con `bodies_inner` forman el "cinturón" de los
/// planetas (doble línea visual).
bodies: f32, bodies: f32,
/// Borde interior del cinturón de planetas. Marca dónde "termina" /// Borde interior del cinturón de planetas geocéntricos.
/// la zona de cuerpos y empieza la zona de aspectos.
bodies_inner: f32, bodies_inner: f32,
/// Cuerpos topocéntricos (capa "ascensional") — un poco hacia /// Direcciones Primarias DIRECTAS (Sistema GR): ring exterior del
/// adentro de `bodies` para que un mismo planeta se vea como /// "abrazo" GR — justo afuera del cinturón natal.
/// "doble glyph": natal afuera, topocéntrico justo dentro. En pd_direct: f32,
/// Luna la separación angular es visible (~1°); en exteriores /// Direcciones Primarias CONVERSAS (Sistema GR): ring interior
/// los dos glyphs se superponen casi exactamente. /// el cinturón natal queda entre `pd_direct` (afuera) y
topocentric: f32, /// `pd_converse` (dentro), formando el dual-ring de rectificación.
pd_converse: f32,
/// Anillo interno con cuerpos progresados (overlay opcional). /// Anillo interno con cuerpos progresados (overlay opcional).
progression: f32, progression: f32,
/// Anillo más interno con cuerpos dirigidos por Solar Arc. /// 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. /// Anillo de carta compuesta (midpoint Davison) con un partner.
composite: f32, composite: f32,
/// Círculo donde anclan las líneas de aspecto entre cuerpos /// Círculo donde anclan las líneas de aspecto entre cuerpos
/// natales. Justo dentro del cinturón de planetas, no en el /// natales. Justo dentro del cinturón de planetas.
/// centro — así las líneas conectan cuerpos cercanos al ring
/// donde se ven, no atraviesan toda la rueda.
aspects: f32, aspects: f32,
} }
@@ -1643,28 +1667,36 @@ impl Radii {
Self { Self {
sign_outer: r, sign_outer: r,
sign_inner: r * 0.88, sign_inner: r * 0.88,
transits: r * 0.82, // Ascensional (Polich-Page): pegado al sign dial.
houses_outer: r * 0.78, topo_houses_outer: r * 0.875,
houses_inner: r * 0.66, topo_houses_inner: r * 0.79,
midpoints: r * 0.62, // Carril topocéntrico de planetas: justo dentro de las
bodies: r * 0.60, // casas P-P. Lleva sus coord labels al borde interior.
// bodies_inner cerca de bodies — los dos anillos juntos topocentric: r * 0.755,
// forman un "carril" estrecho que delimita la franja de // Tránsitos: ring intermedio entre las dos coronas (topo y
// planetas, no dos líneas separadas que confunden. // geo), apenas debajo del topocéntrico.
bodies_inner: r * 0.57, transits: r * 0.71,
// Topocéntrico justo bajo el carril natal: los dos // Capa geocéntrica clásica más adentro — claramente
// glyphs comparten ancho visual, el shift relativo // separada del bloque ascensional.
// (Luna en particular) se lee como "el natal apunta a houses_outer: r * 0.66,
// este grado, el topo a este otro". houses_inner: r * 0.54,
topocentric: r * 0.555, midpoints: r * 0.50,
// aspects justo bajo el carril de cuerpos. Las líneas bodies: r * 0.47,
// de aspecto entran a este radio, pero el círculo en sí bodies_inner: r * 0.44,
// no se pinta — son las líneas las que importan, no // Dual-ring GR — abraza el cinturón natal por afuera y por
// un anillo extra que sume ruido. // adentro. Para Saturno los dos rings caen casi en la misma
aspects: r * 0.54, // posición angular (poca rotación diurna afecta su lon en
progression: r * 0.46, // años humanos); para luminarias los rings divergen
solar_arc: r * 0.38, // visiblemente y leen "directa rumbo a este grado, conversa
composite: r * 0.30, // 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, "composite" => self.composite,
"midpoints" => self.midpoints, "midpoints" => self.midpoints,
"topocentric" => self.topocentric, "topocentric" => self.topocentric,
"pd_direct" => self.pd_direct,
"pd_converse" => self.pd_converse,
_ => self.bodies, _ => self.bodies,
} }
} }
@@ -1762,15 +1796,31 @@ fn paint_wheel(
let house_color = with_alpha(house_base, 0.85); 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_outer, 1.1, house_color, theme);
stroke_circle_3d(window, cx, cy, radii.houses_inner, 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 { for layer in layers {
if matches!(layer.kind, LayerKind::Houses) { if matches!(layer.kind, LayerKind::Houses) {
let is_topo = layer.module_id == "topocentric"; 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 { if let Geometry::Ring { cusps_deg } = &layer.geometry {
for (i, c) in cusps_deg.iter().enumerate() { for (i, c) in cusps_deg.iter().enumerate() {
let is_angle = i == 0 || i == 3 || i == 6 || i == 9; let is_angle = i == 0 || i == 3 || i == 6 || i == 9;
let color = if is_topo { let color = if is_topo {
with_alpha(house_base, 0.55) with_alpha(house_base, 0.60)
} else if is_angle { } else if is_angle {
palette.angle_highlight palette.angle_highlight
} else { } else {
@@ -1779,27 +1829,44 @@ fn paint_wheel(
let width = if is_angle && !is_topo { 2.0 } else { 0.8 }; let width = if is_angle && !is_topo { 2.0 } else { 0.8 };
if is_topo { if is_topo {
// Topocéntrico: cusp como línea punteada // Topocéntrico: cusp como línea punteada
// en su propio anillo (un poco más // en su propio anillo cercano al sign
// adentro que las casas geocéntricas) → // dial — se distingue del Placidus
// se distingue como sistema alternativo. // geocéntrico por el dash pattern y la
let (xi, yi) = polar_to_screen( // ubicación más exterior.
*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,
);
paint_segment( paint_segment(
window, window,
cx + xi, cx
cy + yi, + polar_to_screen(
cx + xo, *c,
cy + yo, 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, color,
Some((3.0, 2.5)), Some((3.0, 2.5)),
1.0, 1.0,
@@ -1812,8 +1879,8 @@ fn paint_wheel(
*c, *c,
ascendant_deg, ascendant_deg,
rot_offset_deg, rot_offset_deg,
radii.houses_inner, r_in,
radii.houses_outer, r_out,
color, color,
width, width,
); );
@@ -1866,6 +1933,35 @@ fn paint_wheel(
0.6, 0.6,
with_alpha(palette.dial_ring, 0.35), 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 // 3. Aspectos. Cada module_id usa su par de radios — natal-natal
@@ -9,10 +9,11 @@ use std::sync::{Arc, OnceLock};
use std::time::Instant; use std::time::Instant;
use eternal_astrology::{ use eternal_astrology::{
all_lots, composite, find_aspects, find_synastry_aspects, next_return, secondary_progression, all_lots, composite, directed_longitude, find_aspects, find_synastry_aspects, next_return,
solar_arc_true, topocentric_ecliptic, Aspect, AspectKind as EAspectKind, BirthData, BodySet, primary_direction::PrimaryDirection, secondary_progression, solar_arc_true, topocentric_ecliptic,
ChartConfig, HouseSystem as EHouseSystem, Houses as EHouses, NatalChart, OrbTable, Aspect, AspectKind as EAspectKind, BirthData, BodySet, ChartConfig,
Zodiac as EZodiac, 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}; use eternal_sky::{Ayanamsha, Body, EphemerisSession, Instant as ESInstant, Observer, SessionConfig};
@@ -389,6 +390,14 @@ pub fn compose(
"Topocéntrico (Polich-Page)".into(), "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(()) 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( fn build_progression_overlay(
natal: &NatalChart, natal: &NatalChart,
target_age_years: f64, target_age_years: f64,
@@ -333,6 +333,14 @@ pub enum PipelineRequest {
/// (~1° de shift); imperceptible en planetas exteriores. La capa /// (~1° de shift); imperceptible en planetas exteriores. La capa
/// convive con la natal geocéntrica como overlay comparativo. /// convive con la natal geocéntrica como overlay comparativo.
Topocentric, 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é /// Opciones que afectan la pasada natal (qué aspectos pintar, qué
@@ -143,6 +143,7 @@ impl Registry {
r.register(Box::new(lots::LotsModule)); r.register(Box::new(lots::LotsModule));
r.register(Box::new(fixed_stars::FixedStarsModule)); r.register(Box::new(fixed_stars::FixedStarsModule));
r.register(Box::new(topocentric::TopocentricModule)); r.register(Box::new(topocentric::TopocentricModule));
r.register(Box::new(primary_directions::PrimaryDirectionsModule));
r r
} }
@@ -840,13 +841,13 @@ pub mod topocentric {
matches!(kind, ChartKind::Natal) matches!(kind, ChartKind::Natal)
} }
fn enabled_by_default(&self) -> bool { fn enabled_by_default(&self) -> bool {
false true
} }
fn controls(&self) -> Vec<Control> { fn controls(&self) -> Vec<Control> {
vec![Control::Toggle { vec![Control::Toggle {
key: "enabled".into(), key: "enabled".into(),
label: "Activar".into(), label: "Activar".into(),
default: false, default: true,
hotkey: None, 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()
}
}
}