/**
* Jyotish App — 高级渲染函数 v1.0
* Karaka, Arudha, Vargas, Ashtakavarga, Shadbala, Tithi, Dasha三级
*/
import { SIGNS, SIGNS_CN, PLANET_CN, PLANET_SYMBOLS } from './jyotish-engine.js';
import { VARGA_DEFS } from './jyotish-advanced.js';
import { renderSouthIndianChart } from './chart-renderer.js';
import { t, signName, planetName, statusName, houseLabel, yearsLabel } from './i18n.js';
import { DASHA_THEMES, NAKSHATRA_DATA, PLANET_DIGNITY, ASCENDANT_TABLE } from './interpretation.js';
import { escapeHtml, escapeAttr, safeNumber } from './security.js';
const $ = id => document.getElementById(id);
// ========== Varga 分盘星座计算(与 jyotish-advanced.js 同逻辑) ==========
function vargaSignIdx(degInSign, si, d) {
const sw = 30 / d, pi = Math.floor(degInSign / sw);
const o = si % 2 === 0;
switch (d) {
case 2: return pi === 0 ? (o ? 4 : 3) : (o ? 3 : 4);
case 3: return o ? (si + pi * 4) % 12 : (si + 8 + pi * 4) % 12;
case 4: return o ? (si + pi) % 12 : (si + 8 + pi) % 12;
case 7: return o ? (si + pi) % 12 : (si + 6 + pi) % 12;
case 9: { const el = [0,9,6,3]; return (el[si % 4] + pi) % 12; }
case 10: return o ? (si + pi) % 12 : (si + 8 + pi) % 12;
case 12: return (si + pi) % 12;
case 16: return ((o ? 0 : 1) + pi) % 12;
case 20: return ((o ? 0 : 8) + pi) % 12;
case 24: return ((o ? 4 : 3) + pi) % 12;
case 27: return ((o ? 0 : 6) + pi) % 12;
case 30: {
if (o) { return pi<5?0:pi<10?10:pi<18?8:pi<25?2:6; }
else { return pi<5?1:pi<12?5:pi<20?9:pi<25?7:11; }
}
case 40: return ((o ? 0 : 6) + pi) % 12;
case 45: return ((o ? 0 : 6) + pi) % 12;
case 60: return o ? (si + pi) % 12 : (si + 1 + pi) % 12;
default: return (si + pi) % 12;
}
}
// ========== Tithi / Yoga 信息 ==========
export function renderTithiInfo(ty) {
const el = $('tithi-info-section');
if (!ty) { el.innerHTML = ''; return; }
el.innerHTML = `
${t('panch.title')}
Vara 星期
${escapeHtml(ty.vara || '-')}
Tithi 月相
${escapeHtml(ty.tithi.name)}
Paksha 半月
${escapeHtml(ty.tithi.paksha)}
Karana 半月相
${escapeHtml(ty.karana?.name || '-')}
Yoga 日月瑜伽
${escapeHtml(ty.yoga.name)}
`;
}
// ========== Arudha 快速概览(本命盘Tab底部) ==========
export function renderArudhaQuick(arudha) {
const el = $('arudha-quick-section');
if (!arudha || Object.keys(arudha).length === 0) { el.innerHTML = ''; return; }
const items = Object.entries(arudha).map(([k, v]) =>
`${escapeHtml(k)}${v.label ? ' ' + escapeHtml(v.label) : ''}
${escapeHtml(signName(v.sign))} ${escapeHtml(houseLabel(v.pada_house))}
`
).join('');
el.innerHTML = `${t('arudha.overview')} ${items}
`;
}
// ========== Karaka ==========
export function renderKaraka(karaka) {
renderKarakaGrid($('karaka7-grid'), karaka.karaka7);
renderKarakaGrid($('karaka8-grid'), karaka.karaka8);
}
function renderKarakaGrid(container, data) {
if (!container || !data) return;
const labels = { AK:'AK 自我灵魂', AmK:'AmK 事业顾问', BK:'BK 兄弟姐妹', MK:'MK 母亲', PK:'PK 子女', GK:'GK 障碍冲突', DK:'DK 配偶', PiK:'PiK 父亲' };
container.innerHTML = Object.entries(data).map(([k, v]) => {
const isDK = k === 'DK';
return `
${escapeHtml(labels[k] || k)}
${escapeHtml(PLANET_SYMBOLS[v.planet] || '')} ${escapeHtml(planetName(v.planet))}
${safeNumber(v.degree).toFixed(2)}° · ${escapeHtml(signName(v.sign))} H${escapeHtml(v.house)}
`;
}).join('');
}
// ========== Vargas 分盘 ==========
export function renderVargas(allV, planets, ascendant) {
const selector = $('varga-selector');
const order = ['Sun','Moon','Mars','Mercury','Jupiter','Venus','Saturn','Rahu','Ketu'];
// 渲染选择器
selector.innerHTML = `
${VARGA_DEFS.map(v => `${escapeHtml(v.id)} ${escapeHtml(v.cn)} `).join('')}
`;
const selectEl = document.getElementById('vargaSelect');
if (selectEl) {
selectEl.addEventListener('change', (e) => {
renderSingleVarga(e.target.value, planets, ascendant);
});
}
renderSingleVarga('D9', planets, ascendant);
}
function renderSingleVarga(vargaId, planets, ascendant) {
const def = VARGA_DEFS.find(v => v.id === vargaId);
if (!def) return;
const d = def.d;
// 计算分盘上升
const ascSi = SIGNS.indexOf(ascendant.sign);
const ascDegRaw = safeNumber(ascendant.degree_in_sign ?? ascendant.degree);
const ascDegInSign = ascDegRaw >= 30 ? ((ascDegRaw % 30) + 30) % 30 : ascDegRaw;
const vargaAscIdx = d === 1 ? ascSi : vargaSignIdx(ascDegInSign, ascSi, d);
const vargaAscSign = SIGNS[vargaAscIdx];
// 构建分盘星盘数据
const vPlanets = {};
for (const [pn, pi] of Object.entries(planets)) {
if (pi.error) continue;
if (d === 1) {
vPlanets[pn] = { ...pi };
continue;
}
const psi = SIGNS.indexOf(pi.sign);
const vi = vargaSignIdx(pi.degree_in_sign, psi, d);
const vHouse = ((vi - vargaAscIdx + 12) % 12) + 1;
vPlanets[pn] = {
sign: SIGNS[vi], sign_cn: SIGNS_CN[SIGNS[vi]],
house: vHouse, degree: 0, degree_in_sign: 0,
status: '中性', retrograde: false, nakshatra: '', nakshatra_pada: 1, combust: false,
};
}
const chartData = { ascendant: { sign: vargaAscSign }, planets: vPlanets };
renderSouthIndianChart($('varga-chart'), chartData);
// 表格
$('varga-table-title').textContent = `${def.id} ${def.cn} — ${t('varga.positions')}`;
const tbody = $('varga-table').querySelector('tbody');
const order = ['Sun','Moon','Mars','Mercury','Jupiter','Venus','Saturn','Rahu','Ketu'];
tbody.innerHTML = order.filter(pn => vPlanets[pn]).map(pn => {
const vp = vPlanets[pn];
return `${escapeHtml(PLANET_SYMBOLS[pn]||'')} ${escapeHtml(planetName(pn))}${escapeHtml(signName(vp.sign))} H${escapeHtml(vp.house)} `;
}).join('');
}
// ========== Arudha 详细(分盘Tab底部) ==========
export function renderArudha(arudha) {
const el = $('arudha-section');
if (!arudha || Object.keys(arudha).length === 0) { el.innerHTML = ''; return; }
const items = Object.entries(arudha).map(([k, v]) =>
`${escapeHtml(k)}${v.label ? ' · ' + escapeHtml(v.label) : ''}
${escapeHtml(signName(v.sign))}
${escapeHtml(houseLabel(v.pada_house))} · ${t('house.lord')}: ${escapeHtml(planetName(v.lord))}
`
).join('');
el.innerHTML = `${t('arudha.title')}
${items}
`;
}
// ========== Ashtakavarga ==========
export function renderAshtakavarga(av, ascSign) {
// 概览
$('av-summary').innerHTML = `${t('av.savtotal')}: ${av.sav_total} / 337
`;
// SAV 图表(南印度风格格子)
renderSAVChart(av, ascSign);
// 详细卡片
const details = $('av-details');
const pOrder = ['Sun','Moon','Mars','Mercury','Jupiter','Venus','Saturn'];
details.innerHTML = '';
for (let h = 1; h <= 12; h++) {
const sh = av.sav_by_house[h];
if (!sh) continue;
const score = safeNumber(sh.score);
const pct = Math.max(0, Math.min(100, Math.round(score / 56 * 100)));
const bavRow = pOrder.map(pn => {
const has = av.bav_by_house[h]?.[pn] ? 'contrib' : '';
return ` `;
}).join('');
details.innerHTML += `
${escapeHtml(houseLabel(h))} · ${escapeHtml(sh.sign)}
${score}
${bavRow}
`;
}
}
function renderSAVChart(av, ascSign) {
const container = $('av-chart');
const ai = SIGNS.indexOf(ascSign);
// 南印度布局位置
const layout = [
[0,0],[0,1],[0,2],[0,3],[1,3],[2,3],[3,3],[3,2],[3,1],[3,0],[2,0],[1,0]
];
const size = 400, cell = size / 4;
let svg = ``;
// 画格子
for (let i = 0; i < 12; i++) {
const [r, c] = layout[i];
const signIdx = i; // South Indian: Pisces=0, Aries=1, ...
const house = ((signIdx - ai + 12) % 12) + 1;
const score = safeNumber(av.sav_by_house[house]?.score);
const color = score >= 30 ? 'rgba(22,163,74,0.15)' : score >= 25 ? 'rgba(109,40,217,0.08)' : score < 20 ? 'rgba(220,38,38,0.1)' : '#fff';
const x = c * cell, y = r * cell;
svg += ` `;
svg += `${SIGNS[signIdx].slice(0,3)} `;
svg += `${score} `;
svg += `H${house} `;
}
svg += ' ';
container.innerHTML = svg;
}
// ========== Shadbala ==========
export function renderShadbala(sb) {
const tbody = $('shadbala-table').querySelector('tbody');
tbody.innerHTML = '';
for (const pn of ['Sun','Moon','Mars','Mercury','Jupiter','Venus','Saturn']) {
const s = sb[pn]; if (!s) continue;
const pct = safeNumber(s.percentage);
const barClass = pct >= 100 ? 'strong' : pct >= 75 ? 'medium' : 'weak';
tbody.innerHTML += `
${escapeHtml(PLANET_SYMBOLS[pn]||'')} ${escapeHtml(planetName(pn))}
${escapeHtml(s.total)} R
${escapeHtml(s.required)} R
${escapeHtml(s.status)}
`;
}
}
// ========== Dasha 三级 ==========
export function renderDasha3Level(data) {
const container = document.getElementById('dasha-timeline');
const currentEl = document.getElementById('dasha-current');
container.innerHTML = '';
document.getElementById('antardasha-panel').innerHTML = '';
document.getElementById('pratyantardasha-panel').innerHTML = '';
if (data.current_dasha) {
const cd = data.current_dasha;
const curSub = cd.antardasha?.find(a => a.is_current);
const curPraty = curSub?.pratyantardasha?.find(p => p.is_current);
const theme = DASHA_THEMES[cd.lord];
const themeHtml = theme ? `${escapeHtml(theme.theme)}
✦ ${escapeHtml(theme.positive)} ⚠ ${escapeHtml(theme.negative)}
` : '';
currentEl.innerHTML = `
${t('dasha.maha')}
${escapeHtml(planetName(cd.lord))}
${t('dasha.antar')}
${curSub ? escapeHtml(planetName(curSub.lord)) : '-'}
${t('dasha.praty')}
${curPraty ? escapeHtml(planetName(curPraty.lord)) : '-'}
${escapeHtml(cd.start)} ~ ${escapeHtml(cd.end)}
${themeHtml}
`;
}
let html = '';
for (const md of data.timeline) {
const isCurMd = data.current_dasha && data.current_dasha.lord === md.lord;
html += `
`;
html += ``;
html += `${escapeHtml(planetName(md.lord))} (${escapeHtml(yearsLabel(md.years))}) : ${escapeHtml(String(md.start || '').slice(0, 10))} ~ ${escapeHtml(String(md.end || '').slice(0, 10))}`;
html += ` `;
if (md.antardasha && md.antardasha.length > 0) {
html += ``;
for (const ad of md.antardasha) {
const isCurAd = isCurMd && ad.is_current;
html += `
`;
html += ``;
html += `${escapeHtml(planetName(ad.lord))} : ${escapeHtml(String(ad.start || '').slice(0, 10))} ~ ${escapeHtml(String(ad.end || '').slice(0, 10))}`;
html += ` `;
if (ad.pratyantardasha && ad.pratyantardasha.length > 0) {
html += ``;
for (const pd of ad.pratyantardasha) {
const isCurPd = isCurAd && pd.is_current;
html += `
`;
html += `↳ ${escapeHtml(planetName(pd.lord))} : ${escapeHtml(String(pd.start || '').slice(0, 10))} ~ ${escapeHtml(String(pd.end || '').slice(0, 10))}`;
html += `
`;
}
html += `
`;
}
html += ` `;
}
html += `
`;
}
html += ` `;
}
html += '
';
container.innerHTML = html;
}
// ========== 行星表更新(含D9列) ==========
export function updatePlanetsTable(planets, d9Varga) {
const tbody = $('planets-table').querySelector('tbody');
tbody.innerHTML = '';
const order = ['Sun','Moon','Mars','Mercury','Jupiter','Venus','Saturn','Rahu','Ketu'];
for (const pn of order) {
const p = planets[pn]; if (!p || p.error) continue;
let sc = '';
if (p.status === '入旺') sc = 'status-exalted';
else if (p.status === '落陷') sc = 'status-debilitated';
else if (p.status === '入庙') sc = 'status-own';
const retro = p.retrograde ? ' ℞' : '';
const combust = p.combust ? ' [燃]' : '';
// D9 sign
let d9sign = '-';
if (d9Varga?.planets?.[pn]) {
d9sign = signName(d9Varga.planets[pn].sign) || '-';
}
tbody.innerHTML += `
${escapeHtml(PLANET_SYMBOLS[pn]||'')} ${escapeHtml(pn)}${retro}${combust}
${escapeHtml(signName(p.sign))}
${safeNumber(p.degree_in_sign).toFixed(2)}°
H${escapeHtml(p.house)}
${statusName(p.status)}
${escapeHtml(p.nakshatra)} P${escapeHtml(p.nakshatra_pada)}
${escapeHtml(d9sign)}
`;
}
}
// ========== Extended: Bhava Bala / Vimsopaka / Vaiseshikamsas / Planet States / AV Pinda / Extra Dasas ==========
export function renderBhavaBala(bb) {
const el = $('bhava-bala-section'); if (!el || !bb) return;
const rows = bb.houses.map(h => {
const bar = Math.min(100, safeNumber(h.in_rupas) / 12 * 100);
return `${escapeHtml(h.house)} ${escapeHtml(signName(h.sign))} ${escapeHtml(planetName(h.lord)||h.lord)}
${escapeHtml(h.in_rupas)} ${escapeHtml(h.lord_bala)} ${escapeHtml(h.dig_bala)} ${escapeHtml(h.drig_bala)}
`;
}).join('');
el.innerHTML = `${t('ext.bhava')}
${t('col.house')} ${t('col.sign')} ${t('col.lord')} Rupas ${t('col.lordbala')} ${t('col.dig')} ${t('col.drig')} ${t('col.strength')} ${rows}
${t('bb.strongest')}: ${signName(bb.ranked[0].sign)}(${bb.ranked[0].in_rupas}) | ${t('bb.weakest')}: ${signName(bb.ranked[bb.ranked.length-1].sign)}(${bb.ranked[bb.ranked.length-1].in_rupas})
`;
}
export function renderVimsopakaVaiseshikamsa(vim, vais) {
const el = $('vimsopaka-section'); if (!el || !vim) return;
const PL = ['Sun','Moon','Mars','Mercury','Jupiter','Venus','Saturn','Rahu','Ketu'];
const rows = PL.map(pn => {
const d = vim.dasa[pn], s = vim.shodasa[pn], v = vais?.[pn];
return `${escapeHtml(planetName(pn))}
${escapeHtml(d?d.score:'-')} ${escapeHtml(d?d.pct+'%':'-')}
${escapeHtml(v?v.dasa.name:'-')} ${escapeHtml(v?v.dasa.level:'-')}
${escapeHtml(s?s.score:'-')} ${escapeHtml(s?s.pct+'%':'-')}
${escapeHtml(v?v.shodasa.name:'-')} ${escapeHtml(v?v.shodasa.level:'-')} `;
}).join('');
el.innerHTML = `${t('ext.vim')}
${t('col.planet')}
${t('col.dasa10')} % ${t('col.level')} ${t('col.level')}
${t('col.shodasa')} % ${t('col.level')} ${t('col.level')} ${rows}
`;
}
export function renderPlanetStates(activity, age, alertness, mood) {
const el = $('planet-states-section'); if (!el || !activity) return;
const PL = ['Sun','Moon','Mars','Mercury','Jupiter','Venus','Saturn','Rahu','Ketu'];
const rows = PL.map(pn => {
const a=activity[pn], ag=age?.[pn], al=alertness?.[pn], m=mood?.[pn];
return `${escapeHtml(planetName(pn))}
${escapeHtml(a?.activity||'-')} ${escapeHtml(ag?.age||'-')}
${escapeHtml(al?.alertness||'-')} ${escapeHtml(m?.moods?.join(', ')||'-')} `;
}).join('');
el.innerHTML = `${t('ext.states')}
${t('col.planet')} ${t('col.activity')} ${t('col.age')} ${t('col.alertness')} ${t('col.mood')} ${rows}
`;
}
export function renderAVPinda(pinda) {
const el = $('av-pinda-section'); if (!el || !pinda) return;
const PL = ['Sun','Moon','Mars','Mercury','Jupiter','Venus','Saturn','Lagna'];
const rows = PL.map(pn => {
const p = pinda[pn];
return `${escapeHtml(pn==='Lagna'?'Lagna':planetName(pn))}
${escapeHtml(p?p.sodhya_pinda:'-')} ${escapeHtml(p?p.rasi_pinda:'-')} ${escapeHtml(p?p.graha_pinda:'-')} `;
}).join('');
el.innerHTML = `${t('ext.pinda')}
${t('col.planet')} Sodhya Pinda Rasi Pinda Graha Pinda ${rows}
`;
}
export function renderExtraDasas(dasas) {
const el = $('extra-dasa-section'); if (!el || !dasas) return;
const systems = Object.entries(dasas);
let html = `${t('ext.dasa')} `;
for (const [key, dasa] of systems) {
if (!dasa) continue;
const cur = dasa.current;
html += `${escapeHtml(dasa.system)}${cur?' — 当前: '+escapeHtml(cur.lord_cn||cur.lord)+' ('+escapeHtml(cur.start)+' ~ '+escapeHtml(cur.end)+')':''} `;
// Antardasha
if (cur?.antardasha) {
html += '
';
for (const s of cur.antardasha) {
const cls = s.is_current ? 'style="font-weight:bold;color:#222;"' : 'style="color:#888;"';
html += `${escapeHtml(s.lord_cn||s.lord)} ${escapeHtml(s.start)}~${escapeHtml(s.end)} `;
}
html += '
';
}
// Timeline
html += '
';
for (const d of dasa.timeline) {
const cls = d.is_current ? 'dasha-active' : '';
html += `${escapeHtml(d.lord_cn||d.lord)} `;
}
html += '
';
}
el.innerHTML = html;
}