sky wheel: element contributor display; sign + house tooltips — TDD
sky_save now re-fetches from PySwiss server-side on save so stored chart_data always carries enriched element format (contributors/stellia/ parades). New sky/data endpoint serves fresh PySwiss data to the My Sky applet on load, replacing the stale inline json_script approach. natus-wheel.js: sign ring slices (data-sign-name) and house ring slices (data-house) now have click handlers with _activateSign/_activateHouse; em-dash fallback added for classic elements with empty contributor lists. Action URLs sky/preview, sky/save, sky/data lose trailing slashes. Jasmine: T12 sign tooltip, T13 house tooltip, T14 enriched element contributor display (symbols, Stellium/Parade formations, em-dash fallback). Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -119,10 +119,12 @@ const NatusWheel = (() => {
|
||||
|
||||
// ── Cycle state ────────────────────────────────────────────────────────────
|
||||
|
||||
let _activeRing = null; // 'planets' | 'elements' | null
|
||||
let _activeRing = null; // 'planets' | 'elements' | 'signs' | 'houses' | null
|
||||
let _activeIdx = null; // index within the active ring's sorted list
|
||||
let _planetItems = []; // [{name, degree}] sorted by ecliptic degree ascending
|
||||
let _elementItems = []; // [{key}] in ELEMENT_ORDER
|
||||
let _signItems = []; // [{name, symbol, element}] in SIGNS order
|
||||
let _houseItems = []; // [{num, label}] houses 1–12
|
||||
|
||||
// Tooltip DOM refs — set by _injectTooltipControls() on each draw().
|
||||
let _tooltipEl = null;
|
||||
@@ -233,6 +235,11 @@ const NatusWheel = (() => {
|
||||
.map(([name, p]) => ({ name, degree: p.degree }))
|
||||
.sort((a, b) => b.degree - a.degree); // descending = clockwise on wheel
|
||||
_elementItems = ELEMENT_ORDER.map(key => ({ key }));
|
||||
_signItems = SIGNS.map(s => ({ name: s.name, symbol: s.symbol, element: s.element }));
|
||||
_houseItems = Array.from({ length: 12 }, (_, i) => ({
|
||||
num: i + 1,
|
||||
label: HOUSE_LABELS[i + 1],
|
||||
}));
|
||||
}
|
||||
|
||||
/** Clear all active-lock classes and reset cycle state. */
|
||||
@@ -320,9 +327,15 @@ const NatusWheel = (() => {
|
||||
if (ring === 'planets') {
|
||||
const grp = svgNode.querySelector(`[data-planet="${_planetItems[idx].name}"]`);
|
||||
el = grp && (grp.querySelector('circle') || grp);
|
||||
} else {
|
||||
} else if (ring === 'elements') {
|
||||
const grp = svgNode.querySelector(`[data-element="${_elementItems[idx].key}"]`);
|
||||
el = grp && (grp.querySelector('path') || grp);
|
||||
} else if (ring === 'signs') {
|
||||
const grp = svgNode.querySelector(`[data-sign-name="${_signItems[idx].name}"]`);
|
||||
el = grp && (grp.querySelector('path') || grp);
|
||||
} else if (ring === 'houses') {
|
||||
const grp = svgNode.querySelector(`[data-house="${_houseItems[idx].num}"]`);
|
||||
el = grp && (grp.querySelector('path') || grp);
|
||||
}
|
||||
if (el) iRect = el.getBoundingClientRect();
|
||||
}
|
||||
@@ -434,6 +447,8 @@ const NatusWheel = (() => {
|
||||
bodyHtml += `<div class="tt-asp-row">${psym} @ ${inDeg}° ${sicon} +1</div>`;
|
||||
});
|
||||
bodyHtml += '</div>';
|
||||
} else {
|
||||
bodyHtml += `<div class="tt-el-formation">—</div>`;
|
||||
}
|
||||
|
||||
} else if (item.key === 'Time') {
|
||||
@@ -506,6 +521,43 @@ const NatusWheel = (() => {
|
||||
}
|
||||
}
|
||||
|
||||
function _activateSign(idx) {
|
||||
_activeRing = 'signs';
|
||||
_activeIdx = idx;
|
||||
const sign = _signItems[idx];
|
||||
if (_ttBody) {
|
||||
_ttBody.innerHTML =
|
||||
`<div class="tt-sign-header">` +
|
||||
`<span class="tt-sign-symbol">${sign.symbol}</span>` +
|
||||
`<span class="tt-title">${sign.name}</span>` +
|
||||
`<span class="tt-sign-element"> · ${sign.element}</span>` +
|
||||
`</div>`;
|
||||
}
|
||||
_positionTooltipAtItem('signs', idx);
|
||||
if (_tooltipEl) {
|
||||
_tooltipEl.querySelector('.nw-asp-don')?.style.setProperty('display', 'none');
|
||||
_tooltipEl.querySelector('.nw-asp-doff')?.style.setProperty('display', 'none');
|
||||
}
|
||||
}
|
||||
|
||||
function _activateHouse(idx) {
|
||||
_activeRing = 'houses';
|
||||
_activeIdx = idx;
|
||||
const house = _houseItems[idx];
|
||||
if (_ttBody) {
|
||||
_ttBody.innerHTML =
|
||||
`<div class="tt-house-header">` +
|
||||
`<span class="tt-title">${house.num}</span>` +
|
||||
`<span class="tt-house-label"> · ${house.label}</span>` +
|
||||
`</div>`;
|
||||
}
|
||||
_positionTooltipAtItem('houses', idx);
|
||||
if (_tooltipEl) {
|
||||
_tooltipEl.querySelector('.nw-asp-don')?.style.setProperty('display', 'none');
|
||||
_tooltipEl.querySelector('.nw-asp-doff')?.style.setProperty('display', 'none');
|
||||
}
|
||||
}
|
||||
|
||||
/** Advance the active ring by +1 (NXT) or -1 (PRV). */
|
||||
function _stepCycle(dir) {
|
||||
if (_activeRing === 'planets') {
|
||||
@@ -514,6 +566,12 @@ const NatusWheel = (() => {
|
||||
} else if (_activeRing === 'elements') {
|
||||
_activeIdx = (_activeIdx + dir + _elementItems.length) % _elementItems.length;
|
||||
_activateElement(_activeIdx);
|
||||
} else if (_activeRing === 'signs') {
|
||||
_activeIdx = (_activeIdx + dir + _signItems.length) % _signItems.length;
|
||||
_activateSign(_activeIdx);
|
||||
} else if (_activeRing === 'houses') {
|
||||
_activeIdx = (_activeIdx + dir + _houseItems.length) % _houseItems.length;
|
||||
_activateHouse(_activeIdx);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -661,7 +719,20 @@ const NatusWheel = (() => {
|
||||
const endA = _toAngle(endDeg, asc);
|
||||
const [sa, ea] = startA > endA ? [endA, startA] : [startA, endA];
|
||||
|
||||
sigGroup.append('path')
|
||||
const signSlice = sigGroup.append('g')
|
||||
.attr('class', `nw-sign-group`)
|
||||
.attr('data-sign-name', sign.name)
|
||||
.on('click', function (event) {
|
||||
event.stopPropagation();
|
||||
const clickIdx = _signItems.findIndex(s => s.name === sign.name);
|
||||
if (_activeRing === 'signs' && _activeIdx === clickIdx) {
|
||||
_closeTooltip();
|
||||
} else {
|
||||
_activateSign(clickIdx);
|
||||
}
|
||||
});
|
||||
|
||||
signSlice.append('path')
|
||||
.attr('transform', `translate(${_cx},${_cy})`)
|
||||
.attr('d', arc({
|
||||
innerRadius: R.signInner,
|
||||
@@ -677,14 +748,14 @@ const NatusWheel = (() => {
|
||||
const cr = _r * 0.065;
|
||||
const sf = (cr * 2 * 0.85) / 640;
|
||||
|
||||
sigGroup.append('circle')
|
||||
signSlice.append('circle')
|
||||
.attr('cx', lx)
|
||||
.attr('cy', ly)
|
||||
.attr('r', cr)
|
||||
.attr('class', `nw-sign-icon-bg--${sign.element.toLowerCase()}`);
|
||||
|
||||
if (_signPaths[sign.name]) {
|
||||
sigGroup.append('path')
|
||||
signSlice.append('path')
|
||||
.attr('d', _signPaths[sign.name])
|
||||
.attr('transform',
|
||||
`translate(${lx},${ly}) scale(${sf}) translate(-320,-320)`)
|
||||
@@ -716,7 +787,17 @@ const NatusWheel = (() => {
|
||||
startAngle: sa + Math.PI / 2,
|
||||
endAngle: ea + Math.PI / 2,
|
||||
}))
|
||||
.attr('class', i % 2 === 0 ? 'nw-house-fill--even' : 'nw-house-fill--odd');
|
||||
.attr('class', i % 2 === 0 ? 'nw-house-fill--even' : 'nw-house-fill--odd')
|
||||
.attr('data-house', i + 1)
|
||||
.on('click', function (event) {
|
||||
event.stopPropagation();
|
||||
const clickIdx = i; // _houseItems[i].num === i+1
|
||||
if (_activeRing === 'houses' && _activeIdx === clickIdx) {
|
||||
_closeTooltip();
|
||||
} else {
|
||||
_activateHouse(clickIdx);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
houses.forEach(({ i, startA, midA }) => {
|
||||
|
||||
Reference in New Issue
Block a user