/** * 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 = ` `; 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
${pct}%
${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('bb.note')}

${rows}
${t('col.house')}${t('col.sign')}${t('col.lord')}Rupas${t('col.lordbala')}${t('col.dig')}${t('col.drig')}${t('col.strength')}

${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')}

${rows}
${t('col.planet')} ${t('col.dasa10')}%${t('col.level')}${t('col.level')} ${t('col.shodasa')}%${t('col.level')}${t('col.level')}
`; } 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')}

${rows}
${t('col.planet')}${t('col.activity')}${t('col.age')}${t('col.alertness')}${t('col.mood')}
`; } 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')}

${rows}
${t('col.planet')}Sodhya PindaRasi PindaGraha Pinda
`; } 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; }