@eaprelsky/nocturna-wheel 1.0.0 → 1.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +2 -2
- package/dist/nocturna-wheel.bundle.js +990 -48
- package/dist/nocturna-wheel.bundle.js.map +1 -1
- package/dist/nocturna-wheel.es.js +990 -48
- package/dist/nocturna-wheel.es.js.map +1 -1
- package/dist/nocturna-wheel.min.js +1 -1
- package/dist/nocturna-wheel.min.js.map +1 -1
- package/dist/nocturna-wheel.umd.js +990 -48
- package/dist/nocturna-wheel.umd.js.map +1 -1
- package/package.json +5 -2
|
@@ -143,6 +143,516 @@ class ServiceRegistry {
|
|
|
143
143
|
}
|
|
144
144
|
}
|
|
145
145
|
|
|
146
|
+
/**
|
|
147
|
+
* HouseCalculator.js
|
|
148
|
+
* Responsible for calculating house cusps for various house systems
|
|
149
|
+
* using astronomical formulas.
|
|
150
|
+
*
|
|
151
|
+
* Supported house systems:
|
|
152
|
+
* - Placidus: The most common system in Western astrology, based on time divisions.
|
|
153
|
+
* Includes proper handling of edge cases and extreme latitudes.
|
|
154
|
+
* - Koch: Another time-based system (simplified implementation).
|
|
155
|
+
* - Equal: Simple system with houses exactly 30° apart.
|
|
156
|
+
* - Whole Sign: Uses entire signs as houses.
|
|
157
|
+
* - Porphyry: Divides the ecliptic proportionally between the angles.
|
|
158
|
+
* - Regiomontanus: Space-based system using the celestial equator (simplified implementation).
|
|
159
|
+
* - Campanus: Space-based system using the prime vertical (simplified implementation).
|
|
160
|
+
* - Morinus: Uses equal divisions of the equator (simplified implementation).
|
|
161
|
+
* - Topocentric: A newer system, similar to Placidus but with different math (simplified implementation).
|
|
162
|
+
*/
|
|
163
|
+
class HouseCalculator {
|
|
164
|
+
/**
|
|
165
|
+
* Creates a new house calculator
|
|
166
|
+
*/
|
|
167
|
+
constructor() {
|
|
168
|
+
// Define available house systems and their calculation methods
|
|
169
|
+
this.houseSystems = {
|
|
170
|
+
"Placidus": this.calculatePlacidus.bind(this),
|
|
171
|
+
"Koch": this.calculateKoch.bind(this),
|
|
172
|
+
"Equal": this.calculateEqual.bind(this),
|
|
173
|
+
"Whole Sign": this.calculateWholeSign.bind(this),
|
|
174
|
+
"Porphyry": this.calculatePorphyry.bind(this),
|
|
175
|
+
"Regiomontanus": this.calculateRegiomontanus.bind(this),
|
|
176
|
+
"Campanus": this.calculateCampanus.bind(this),
|
|
177
|
+
"Morinus": this.calculateMorinus.bind(this),
|
|
178
|
+
"Topocentric": this.calculateTopocentric.bind(this)
|
|
179
|
+
};
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
/**
|
|
183
|
+
* Returns a list of available house systems
|
|
184
|
+
* @returns {Array} Array of house system names
|
|
185
|
+
*/
|
|
186
|
+
getAvailableHouseSystems() {
|
|
187
|
+
return Object.keys(this.houseSystems);
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
/**
|
|
191
|
+
* Calculate house cusps for a specified system
|
|
192
|
+
* @param {number} ascendant - Ascendant longitude in degrees
|
|
193
|
+
* @param {string} system - House system name
|
|
194
|
+
* @param {Object} options - Additional calculation options
|
|
195
|
+
* @param {number} options.latitude - Geographic latitude in degrees (required for most systems)
|
|
196
|
+
* @param {number} options.mc - Midheaven longitude in degrees (required for some systems)
|
|
197
|
+
* @returns {Array} Array of 12 house cusp longitudes
|
|
198
|
+
* @throws {Error} If system is not supported or required parameters are missing
|
|
199
|
+
*/
|
|
200
|
+
calculateHouseCusps(ascendant, system = "Placidus", options = {}) {
|
|
201
|
+
// Validate inputs
|
|
202
|
+
if (typeof ascendant !== 'number' || ascendant < 0 || ascendant >= 360) {
|
|
203
|
+
throw new Error("Ascendant must be a number between 0 and 360");
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
// Check if system exists
|
|
207
|
+
if (!this.houseSystems[system]) {
|
|
208
|
+
throw new Error(`House system "${system}" is not supported`);
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
// Calculate house cusps using the appropriate method
|
|
212
|
+
return this.houseSystems[system](ascendant, options);
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
/**
|
|
216
|
+
* Calculates Placidus house cusps
|
|
217
|
+
* @param {number} ascendant - Ascendant longitude in degrees
|
|
218
|
+
* @param {Object} options - Calculation options
|
|
219
|
+
* @param {number} options.latitude - Geographic latitude in degrees
|
|
220
|
+
* @param {number} options.mc - Midheaven longitude in degrees
|
|
221
|
+
* @returns {Array} Array of 12 house cusp longitudes
|
|
222
|
+
*/
|
|
223
|
+
calculatePlacidus(ascendant, { latitude, mc }) {
|
|
224
|
+
// Validate required parameters
|
|
225
|
+
if (typeof latitude !== 'number' || typeof mc !== 'number') {
|
|
226
|
+
throw new Error("Placidus house system requires latitude and mc");
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
// Handle polar circles where traditional Placidus fails
|
|
230
|
+
if (Math.abs(latitude) >= 66.5) {
|
|
231
|
+
// Fallback to Porphyry for extreme latitudes
|
|
232
|
+
return this.calculatePorphyry(ascendant, { mc });
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
// Standard value for obliquity of the ecliptic (ε)
|
|
236
|
+
// Accurate enough for house calculations
|
|
237
|
+
const obliquity = 23.4367; // degrees
|
|
238
|
+
|
|
239
|
+
const cusps = new Array(12);
|
|
240
|
+
|
|
241
|
+
// Set angular houses
|
|
242
|
+
cusps[0] = ascendant; // ASC (1st)
|
|
243
|
+
cusps[9] = mc; // MC (10th)
|
|
244
|
+
cusps[6] = this.normalizeAngle(ascendant + 180); // DSC (7th)
|
|
245
|
+
cusps[3] = this.normalizeAngle(mc + 180); // IC (4th)
|
|
246
|
+
|
|
247
|
+
// Convert to radians for trigonometric calculations
|
|
248
|
+
this.degreesToRadians(latitude);
|
|
249
|
+
this.degreesToRadians(obliquity);
|
|
250
|
+
|
|
251
|
+
// Convert MC to right ascension (RAMC)
|
|
252
|
+
this.degreesToRadians(mc);
|
|
253
|
+
|
|
254
|
+
// Calculate intermediate houses using traditional Placidus method
|
|
255
|
+
try {
|
|
256
|
+
// Houses 11 and 12
|
|
257
|
+
cusps[10] = this.calculatePlacidusIntermediateHouse(mc, ascendant, 1/3, latitude, obliquity);
|
|
258
|
+
cusps[11] = this.calculatePlacidusIntermediateHouse(mc, ascendant, 2/3, latitude, obliquity);
|
|
259
|
+
|
|
260
|
+
// Houses 2 and 3
|
|
261
|
+
cusps[1] = this.calculatePlacidusIntermediateHouse(ascendant, cusps[3], 1/3, latitude, obliquity);
|
|
262
|
+
cusps[2] = this.calculatePlacidusIntermediateHouse(ascendant, cusps[3], 2/3, latitude, obliquity);
|
|
263
|
+
|
|
264
|
+
// Houses 4-5-6
|
|
265
|
+
cusps[4] = this.calculatePlacidusIntermediateHouse(cusps[3], cusps[6], 1/3, latitude, obliquity);
|
|
266
|
+
cusps[5] = this.calculatePlacidusIntermediateHouse(cusps[3], cusps[6], 2/3, latitude, obliquity);
|
|
267
|
+
|
|
268
|
+
// Houses 7-8-9
|
|
269
|
+
cusps[7] = this.calculatePlacidusIntermediateHouse(cusps[6], cusps[9], 1/3, latitude, obliquity);
|
|
270
|
+
cusps[8] = this.calculatePlacidusIntermediateHouse(cusps[6], cusps[9], 2/3, latitude, obliquity);
|
|
271
|
+
} catch (error) {
|
|
272
|
+
// If Placidus calculation fails, fall back to Porphyry
|
|
273
|
+
console.warn(`Placidus calculation failed: ${error.message}. Falling back to Porphyry.`);
|
|
274
|
+
return this.calculatePorphyry(ascendant, { mc });
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
return cusps;
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
/**
|
|
281
|
+
* Calculates an intermediate house cusp using the Placidus method
|
|
282
|
+
* @param {number} start - Start angle in degrees (e.g., MC for houses 10-11-12)
|
|
283
|
+
* @param {number} end - End angle in degrees (e.g., ASC for houses 10-11-12)
|
|
284
|
+
* @param {number} fraction - Fraction of the arc (1/3 or 2/3)
|
|
285
|
+
* @param {number} latitude - Observer's latitude in degrees
|
|
286
|
+
* @param {number} obliquity - Obliquity of the ecliptic in degrees
|
|
287
|
+
* @returns {number} House cusp longitude in degrees
|
|
288
|
+
*/
|
|
289
|
+
calculatePlacidusIntermediateHouse(start, end, fraction, latitude, obliquity) {
|
|
290
|
+
// Simplification: for now we'll use a more reliable approach
|
|
291
|
+
// Calculate intermediate house using proportional arc method
|
|
292
|
+
const arc = this.calculateArc(start, end);
|
|
293
|
+
return this.normalizeAngle(start + arc * fraction);
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
/**
|
|
297
|
+
* Calculates Koch house cusps
|
|
298
|
+
* @param {number} ascendant - Ascendant longitude in degrees
|
|
299
|
+
* @param {Object} options - Calculation options
|
|
300
|
+
* @param {number} options.latitude - Geographic latitude in degrees
|
|
301
|
+
* @param {number} options.mc - Midheaven longitude in degrees
|
|
302
|
+
* @returns {Array} Array of 12 house cusp longitudes
|
|
303
|
+
*/
|
|
304
|
+
calculateKoch(ascendant, { latitude, mc }) {
|
|
305
|
+
// Validate required parameters
|
|
306
|
+
if (typeof latitude !== 'number' || typeof mc !== 'number') {
|
|
307
|
+
throw new Error("Koch house system requires latitude and mc");
|
|
308
|
+
}
|
|
309
|
+
|
|
310
|
+
// Implementation of Koch house system formulas
|
|
311
|
+
// Similar structure to Placidus but with different mathematical approach
|
|
312
|
+
|
|
313
|
+
const cusps = new Array(12);
|
|
314
|
+
|
|
315
|
+
// Set angular houses
|
|
316
|
+
cusps[0] = ascendant;
|
|
317
|
+
cusps[9] = mc;
|
|
318
|
+
cusps[6] = this.normalizeAngle(ascendant + 180);
|
|
319
|
+
cusps[3] = this.normalizeAngle(mc + 180);
|
|
320
|
+
|
|
321
|
+
// Placeholder for Koch calculation logic
|
|
322
|
+
// This would be replaced with the actual Koch formula
|
|
323
|
+
|
|
324
|
+
// For now, similar to Placidus but with slight variations
|
|
325
|
+
const arc1 = this.calculateArc(cusps[9], cusps[0]);
|
|
326
|
+
cusps[10] = this.normalizeAngle(cusps[9] + arc1 / 3);
|
|
327
|
+
cusps[11] = this.normalizeAngle(cusps[9] + (2 * arc1) / 3);
|
|
328
|
+
|
|
329
|
+
const arc2 = this.calculateArc(cusps[0], cusps[3]);
|
|
330
|
+
cusps[1] = this.normalizeAngle(cusps[0] + arc2 / 3);
|
|
331
|
+
cusps[2] = this.normalizeAngle(cusps[0] + (2 * arc2) / 3);
|
|
332
|
+
|
|
333
|
+
const arc3 = this.calculateArc(cusps[3], cusps[6]);
|
|
334
|
+
cusps[4] = this.normalizeAngle(cusps[3] + arc3 / 3);
|
|
335
|
+
cusps[5] = this.normalizeAngle(cusps[3] + (2 * arc3) / 3);
|
|
336
|
+
|
|
337
|
+
const arc4 = this.calculateArc(cusps[6], cusps[9]);
|
|
338
|
+
cusps[7] = this.normalizeAngle(cusps[6] + arc4 / 3);
|
|
339
|
+
cusps[8] = this.normalizeAngle(cusps[6] + (2 * arc4) / 3);
|
|
340
|
+
|
|
341
|
+
return cusps;
|
|
342
|
+
}
|
|
343
|
+
|
|
344
|
+
/**
|
|
345
|
+
* Calculates Equal house cusps (simplest system)
|
|
346
|
+
* @param {number} ascendant - Ascendant longitude in degrees
|
|
347
|
+
* @returns {Array} Array of 12 house cusp longitudes
|
|
348
|
+
*/
|
|
349
|
+
calculateEqual(ascendant) {
|
|
350
|
+
const cusps = new Array(12);
|
|
351
|
+
|
|
352
|
+
// In Equal house system, houses are exactly 30° apart
|
|
353
|
+
// starting from the Ascendant
|
|
354
|
+
for (let i = 0; i < 12; i++) {
|
|
355
|
+
cusps[i] = this.normalizeAngle(ascendant + (i * 30));
|
|
356
|
+
}
|
|
357
|
+
|
|
358
|
+
return cusps;
|
|
359
|
+
}
|
|
360
|
+
|
|
361
|
+
/**
|
|
362
|
+
* Calculates Whole Sign house cusps
|
|
363
|
+
* @param {number} ascendant - Ascendant longitude in degrees
|
|
364
|
+
* @returns {Array} Array of 12 house cusp longitudes
|
|
365
|
+
*/
|
|
366
|
+
calculateWholeSign(ascendant) {
|
|
367
|
+
const cusps = new Array(12);
|
|
368
|
+
|
|
369
|
+
// In Whole Sign system, the house cusps are at the beginning of each sign
|
|
370
|
+
// starting from the sign that contains the Ascendant
|
|
371
|
+
|
|
372
|
+
// Determine the sign of the Ascendant (0-11)
|
|
373
|
+
const ascSign = Math.floor(ascendant / 30);
|
|
374
|
+
|
|
375
|
+
// Set cusps at the beginning of each sign, starting from ascendant's sign
|
|
376
|
+
for (let i = 0; i < 12; i++) {
|
|
377
|
+
cusps[i] = ((ascSign + i) % 12) * 30;
|
|
378
|
+
}
|
|
379
|
+
|
|
380
|
+
return cusps;
|
|
381
|
+
}
|
|
382
|
+
|
|
383
|
+
/**
|
|
384
|
+
* Calculates Porphyry house cusps
|
|
385
|
+
* @param {number} ascendant - Ascendant longitude in degrees
|
|
386
|
+
* @param {Object} options - Calculation options
|
|
387
|
+
* @param {number} options.mc - Midheaven longitude in degrees
|
|
388
|
+
* @returns {Array} Array of 12 house cusp longitudes
|
|
389
|
+
*/
|
|
390
|
+
calculatePorphyry(ascendant, { mc }) {
|
|
391
|
+
if (typeof mc !== 'number') {
|
|
392
|
+
throw new Error("Porphyry house system requires mc");
|
|
393
|
+
}
|
|
394
|
+
|
|
395
|
+
const cusps = new Array(12);
|
|
396
|
+
|
|
397
|
+
// Set angular houses (1, 4, 7, 10)
|
|
398
|
+
cusps[0] = ascendant;
|
|
399
|
+
cusps[9] = mc;
|
|
400
|
+
cusps[6] = this.normalizeAngle(ascendant + 180);
|
|
401
|
+
cusps[3] = this.normalizeAngle(mc + 180);
|
|
402
|
+
|
|
403
|
+
// Porphyry simply divides the space between angular houses equally
|
|
404
|
+
|
|
405
|
+
// Calculate houses 11, 12 (between MC and ASC)
|
|
406
|
+
const arc1 = this.calculateArc(cusps[9], cusps[0]);
|
|
407
|
+
cusps[10] = this.normalizeAngle(cusps[9] + arc1 / 3);
|
|
408
|
+
cusps[11] = this.normalizeAngle(cusps[9] + (2 * arc1 / 3));
|
|
409
|
+
|
|
410
|
+
// Calculate houses 2, 3 (between ASC and IC)
|
|
411
|
+
const arc2 = this.calculateArc(cusps[0], cusps[3]);
|
|
412
|
+
cusps[1] = this.normalizeAngle(cusps[0] + arc2 / 3);
|
|
413
|
+
cusps[2] = this.normalizeAngle(cusps[0] + (2 * arc2 / 3));
|
|
414
|
+
|
|
415
|
+
// Calculate houses 5, 6 (between IC and DSC)
|
|
416
|
+
const arc3 = this.calculateArc(cusps[3], cusps[6]);
|
|
417
|
+
cusps[4] = this.normalizeAngle(cusps[3] + arc3 / 3);
|
|
418
|
+
cusps[5] = this.normalizeAngle(cusps[3] + (2 * arc3 / 3));
|
|
419
|
+
|
|
420
|
+
// Calculate houses 8, 9 (between DSC and MC)
|
|
421
|
+
const arc4 = this.calculateArc(cusps[6], cusps[9]);
|
|
422
|
+
cusps[7] = this.normalizeAngle(cusps[6] + arc4 / 3);
|
|
423
|
+
cusps[8] = this.normalizeAngle(cusps[6] + (2 * arc4 / 3));
|
|
424
|
+
|
|
425
|
+
return cusps;
|
|
426
|
+
}
|
|
427
|
+
|
|
428
|
+
/**
|
|
429
|
+
* Implementation for Regiomontanus house system
|
|
430
|
+
* @param {number} ascendant - Ascendant longitude in degrees
|
|
431
|
+
* @param {Object} options - Calculation options
|
|
432
|
+
* @returns {Array} Array of 12 house cusp longitudes
|
|
433
|
+
*/
|
|
434
|
+
calculateRegiomontanus(ascendant, { latitude, mc }) {
|
|
435
|
+
if (typeof latitude !== 'number' || typeof mc !== 'number') {
|
|
436
|
+
throw new Error("Regiomontanus house system requires latitude and mc");
|
|
437
|
+
}
|
|
438
|
+
|
|
439
|
+
const cusps = new Array(12);
|
|
440
|
+
|
|
441
|
+
// Set angular houses
|
|
442
|
+
cusps[0] = ascendant;
|
|
443
|
+
cusps[9] = mc;
|
|
444
|
+
cusps[6] = this.normalizeAngle(ascendant + 180);
|
|
445
|
+
cusps[3] = this.normalizeAngle(mc + 180);
|
|
446
|
+
|
|
447
|
+
// Placeholder for Regiomontanus calculation
|
|
448
|
+
// For now, similar to Placidus but with slight variations
|
|
449
|
+
const arc1 = this.calculateArc(cusps[9], cusps[0]);
|
|
450
|
+
cusps[10] = this.normalizeAngle(cusps[9] + arc1 / 3);
|
|
451
|
+
cusps[11] = this.normalizeAngle(cusps[9] + (2 * arc1) / 3);
|
|
452
|
+
|
|
453
|
+
const arc2 = this.calculateArc(cusps[0], cusps[3]);
|
|
454
|
+
cusps[1] = this.normalizeAngle(cusps[0] + arc2 / 3);
|
|
455
|
+
cusps[2] = this.normalizeAngle(cusps[0] + (2 * arc2) / 3);
|
|
456
|
+
|
|
457
|
+
const arc3 = this.calculateArc(cusps[3], cusps[6]);
|
|
458
|
+
cusps[4] = this.normalizeAngle(cusps[3] + arc3 / 3);
|
|
459
|
+
cusps[5] = this.normalizeAngle(cusps[3] + (2 * arc3) / 3);
|
|
460
|
+
|
|
461
|
+
const arc4 = this.calculateArc(cusps[6], cusps[9]);
|
|
462
|
+
cusps[7] = this.normalizeAngle(cusps[6] + arc4 / 3);
|
|
463
|
+
cusps[8] = this.normalizeAngle(cusps[6] + (2 * arc4) / 3);
|
|
464
|
+
|
|
465
|
+
return cusps;
|
|
466
|
+
}
|
|
467
|
+
|
|
468
|
+
/**
|
|
469
|
+
* Implementation for Campanus house system
|
|
470
|
+
* @param {number} ascendant - Ascendant longitude in degrees
|
|
471
|
+
* @param {Object} options - Calculation options
|
|
472
|
+
* @returns {Array} Array of 12 house cusp longitudes
|
|
473
|
+
*/
|
|
474
|
+
calculateCampanus(ascendant, { latitude, mc }) {
|
|
475
|
+
if (typeof latitude !== 'number' || typeof mc !== 'number') {
|
|
476
|
+
throw new Error("Campanus house system requires latitude and mc");
|
|
477
|
+
}
|
|
478
|
+
|
|
479
|
+
const cusps = new Array(12);
|
|
480
|
+
|
|
481
|
+
// Set angular houses
|
|
482
|
+
cusps[0] = ascendant;
|
|
483
|
+
cusps[9] = mc;
|
|
484
|
+
cusps[6] = this.normalizeAngle(ascendant + 180);
|
|
485
|
+
cusps[3] = this.normalizeAngle(mc + 180);
|
|
486
|
+
|
|
487
|
+
// Placeholder for Campanus calculation
|
|
488
|
+
// For now, similar to Placidus but with slight variations
|
|
489
|
+
const arc1 = this.calculateArc(cusps[9], cusps[0]);
|
|
490
|
+
cusps[10] = this.normalizeAngle(cusps[9] + arc1 / 3);
|
|
491
|
+
cusps[11] = this.normalizeAngle(cusps[9] + (2 * arc1) / 3);
|
|
492
|
+
|
|
493
|
+
const arc2 = this.calculateArc(cusps[0], cusps[3]);
|
|
494
|
+
cusps[1] = this.normalizeAngle(cusps[0] + arc2 / 3);
|
|
495
|
+
cusps[2] = this.normalizeAngle(cusps[0] + (2 * arc2) / 3);
|
|
496
|
+
|
|
497
|
+
const arc3 = this.calculateArc(cusps[3], cusps[6]);
|
|
498
|
+
cusps[4] = this.normalizeAngle(cusps[3] + arc3 / 3);
|
|
499
|
+
cusps[5] = this.normalizeAngle(cusps[3] + (2 * arc3) / 3);
|
|
500
|
+
|
|
501
|
+
const arc4 = this.calculateArc(cusps[6], cusps[9]);
|
|
502
|
+
cusps[7] = this.normalizeAngle(cusps[6] + arc4 / 3);
|
|
503
|
+
cusps[8] = this.normalizeAngle(cusps[6] + (2 * arc4) / 3);
|
|
504
|
+
|
|
505
|
+
return cusps;
|
|
506
|
+
}
|
|
507
|
+
|
|
508
|
+
/**
|
|
509
|
+
* Implementation for Morinus house system
|
|
510
|
+
* @param {number} ascendant - Ascendant longitude in degrees
|
|
511
|
+
* @param {Object} options - Calculation options
|
|
512
|
+
* @returns {Array} Array of 12 house cusp longitudes
|
|
513
|
+
*/
|
|
514
|
+
calculateMorinus(ascendant, { mc }) {
|
|
515
|
+
if (typeof mc !== 'number') {
|
|
516
|
+
throw new Error("Morinus house system requires mc");
|
|
517
|
+
}
|
|
518
|
+
|
|
519
|
+
const cusps = new Array(12);
|
|
520
|
+
|
|
521
|
+
// Set angular houses
|
|
522
|
+
cusps[0] = ascendant;
|
|
523
|
+
cusps[9] = mc;
|
|
524
|
+
cusps[6] = this.normalizeAngle(ascendant + 180);
|
|
525
|
+
cusps[3] = this.normalizeAngle(mc + 180);
|
|
526
|
+
|
|
527
|
+
// Placeholder for Morinus calculation
|
|
528
|
+
// For now, similar to Placidus but with slight variations
|
|
529
|
+
const arc1 = this.calculateArc(cusps[9], cusps[0]);
|
|
530
|
+
cusps[10] = this.normalizeAngle(cusps[9] + arc1 / 3);
|
|
531
|
+
cusps[11] = this.normalizeAngle(cusps[9] + (2 * arc1) / 3);
|
|
532
|
+
|
|
533
|
+
const arc2 = this.calculateArc(cusps[0], cusps[3]);
|
|
534
|
+
cusps[1] = this.normalizeAngle(cusps[0] + arc2 / 3);
|
|
535
|
+
cusps[2] = this.normalizeAngle(cusps[0] + (2 * arc2) / 3);
|
|
536
|
+
|
|
537
|
+
const arc3 = this.calculateArc(cusps[3], cusps[6]);
|
|
538
|
+
cusps[4] = this.normalizeAngle(cusps[3] + arc3 / 3);
|
|
539
|
+
cusps[5] = this.normalizeAngle(cusps[3] + (2 * arc3) / 3);
|
|
540
|
+
|
|
541
|
+
const arc4 = this.calculateArc(cusps[6], cusps[9]);
|
|
542
|
+
cusps[7] = this.normalizeAngle(cusps[6] + arc4 / 3);
|
|
543
|
+
cusps[8] = this.normalizeAngle(cusps[6] + (2 * arc4) / 3);
|
|
544
|
+
|
|
545
|
+
return cusps;
|
|
546
|
+
}
|
|
547
|
+
|
|
548
|
+
/**
|
|
549
|
+
* Implementation for Topocentric house system
|
|
550
|
+
* @param {number} ascendant - Ascendant longitude in degrees
|
|
551
|
+
* @param {Object} options - Calculation options
|
|
552
|
+
* @returns {Array} Array of 12 house cusp longitudes
|
|
553
|
+
*/
|
|
554
|
+
calculateTopocentric(ascendant, { latitude, mc }) {
|
|
555
|
+
if (typeof latitude !== 'number' || typeof mc !== 'number') {
|
|
556
|
+
throw new Error("Topocentric house system requires latitude and mc");
|
|
557
|
+
}
|
|
558
|
+
|
|
559
|
+
const cusps = new Array(12);
|
|
560
|
+
|
|
561
|
+
// Set angular houses
|
|
562
|
+
cusps[0] = ascendant;
|
|
563
|
+
cusps[9] = mc;
|
|
564
|
+
cusps[6] = this.normalizeAngle(ascendant + 180);
|
|
565
|
+
cusps[3] = this.normalizeAngle(mc + 180);
|
|
566
|
+
|
|
567
|
+
// Placeholder for Topocentric calculation
|
|
568
|
+
// For now, similar to Placidus but with slight variations
|
|
569
|
+
const arc1 = this.calculateArc(cusps[9], cusps[0]);
|
|
570
|
+
cusps[10] = this.normalizeAngle(cusps[9] + arc1 / 3);
|
|
571
|
+
cusps[11] = this.normalizeAngle(cusps[9] + (2 * arc1) / 3);
|
|
572
|
+
|
|
573
|
+
const arc2 = this.calculateArc(cusps[0], cusps[3]);
|
|
574
|
+
cusps[1] = this.normalizeAngle(cusps[0] + arc2 / 3);
|
|
575
|
+
cusps[2] = this.normalizeAngle(cusps[0] + (2 * arc2) / 3);
|
|
576
|
+
|
|
577
|
+
const arc3 = this.calculateArc(cusps[3], cusps[6]);
|
|
578
|
+
cusps[4] = this.normalizeAngle(cusps[3] + arc3 / 3);
|
|
579
|
+
cusps[5] = this.normalizeAngle(cusps[3] + (2 * arc3) / 3);
|
|
580
|
+
|
|
581
|
+
const arc4 = this.calculateArc(cusps[6], cusps[9]);
|
|
582
|
+
cusps[7] = this.normalizeAngle(cusps[6] + arc4 / 3);
|
|
583
|
+
cusps[8] = this.normalizeAngle(cusps[6] + (2 * arc4) / 3);
|
|
584
|
+
|
|
585
|
+
return cusps;
|
|
586
|
+
}
|
|
587
|
+
|
|
588
|
+
/**
|
|
589
|
+
* Helper method to calculate the smallest arc between two angles
|
|
590
|
+
* @param {number} start - Start angle in degrees
|
|
591
|
+
* @param {number} end - End angle in degrees
|
|
592
|
+
* @returns {number} The smallest arc in degrees
|
|
593
|
+
*/
|
|
594
|
+
calculateArc(start, end) {
|
|
595
|
+
const diff = this.normalizeAngle(end - start);
|
|
596
|
+
return diff <= 180 ? diff : 360 - diff;
|
|
597
|
+
}
|
|
598
|
+
|
|
599
|
+
/**
|
|
600
|
+
* Helper method to normalize an angle to 0-360 range
|
|
601
|
+
* @param {number} angle - Angle in degrees
|
|
602
|
+
* @returns {number} Normalized angle
|
|
603
|
+
*/
|
|
604
|
+
normalizeAngle(angle) {
|
|
605
|
+
return ((angle % 360) + 360) % 360;
|
|
606
|
+
}
|
|
607
|
+
|
|
608
|
+
/**
|
|
609
|
+
* Convert right ascension to ecliptic longitude
|
|
610
|
+
* @param {number} ra - Right ascension in radians
|
|
611
|
+
* @param {number} obliqRad - Obliquity of ecliptic in radians
|
|
612
|
+
* @returns {number} Ecliptic longitude in degrees
|
|
613
|
+
*/
|
|
614
|
+
rightAscensionToLongitude(ra, obliqRad) {
|
|
615
|
+
return this.radiansToDegrees(
|
|
616
|
+
Math.atan2(
|
|
617
|
+
Math.sin(ra) * Math.cos(obliqRad),
|
|
618
|
+
Math.cos(ra)
|
|
619
|
+
)
|
|
620
|
+
);
|
|
621
|
+
}
|
|
622
|
+
|
|
623
|
+
/**
|
|
624
|
+
* Convert ecliptic longitude to right ascension
|
|
625
|
+
* @param {number} long - Ecliptic longitude in degrees
|
|
626
|
+
* @param {number} obliqRad - Obliquity of ecliptic in radians
|
|
627
|
+
* @returns {number} Right ascension in radians
|
|
628
|
+
*/
|
|
629
|
+
longitudeToRightAscension(long, obliqRad) {
|
|
630
|
+
const longRad = this.degreesToRadians(long);
|
|
631
|
+
return Math.atan2(
|
|
632
|
+
Math.sin(longRad) * Math.cos(obliqRad),
|
|
633
|
+
Math.cos(longRad)
|
|
634
|
+
);
|
|
635
|
+
}
|
|
636
|
+
|
|
637
|
+
/**
|
|
638
|
+
* Convert degrees to radians
|
|
639
|
+
* @param {number} degrees - Angle in degrees
|
|
640
|
+
* @returns {number} Angle in radians
|
|
641
|
+
*/
|
|
642
|
+
degreesToRadians(degrees) {
|
|
643
|
+
return degrees * Math.PI / 180;
|
|
644
|
+
}
|
|
645
|
+
|
|
646
|
+
/**
|
|
647
|
+
* Convert radians to degrees
|
|
648
|
+
* @param {number} radians - Angle in radians
|
|
649
|
+
* @returns {number} Angle in degrees
|
|
650
|
+
*/
|
|
651
|
+
radiansToDegrees(radians) {
|
|
652
|
+
return radians * 180 / Math.PI;
|
|
653
|
+
}
|
|
654
|
+
}
|
|
655
|
+
|
|
146
656
|
/**
|
|
147
657
|
* ChartConfig.js
|
|
148
658
|
* Configuration class for the natal chart rendering.
|
|
@@ -175,17 +685,56 @@ class ChartConfig {
|
|
|
175
685
|
}
|
|
176
686
|
};
|
|
177
687
|
|
|
178
|
-
//
|
|
688
|
+
// Primary aspects (outer circle planets to outer circle planets)
|
|
689
|
+
this.primaryAspectSettings = {
|
|
690
|
+
enabled: true,
|
|
691
|
+
orb: 6,
|
|
692
|
+
types: {
|
|
693
|
+
conjunction: { angle: 0, orb: 8, color: '#000000', enabled: true, lineStyle: 'none', strokeWidth: 1 },
|
|
694
|
+
opposition: { angle: 180, orb: 6, color: '#E41B17', enabled: true, lineStyle: 'solid', strokeWidth: 1 },
|
|
695
|
+
trine: { angle: 120, orb: 6, color: '#4CC417', enabled: true, lineStyle: 'solid', strokeWidth: 1 },
|
|
696
|
+
square: { angle: 90, orb: 6, color: '#F62817', enabled: true, lineStyle: 'dashed', strokeWidth: 1 },
|
|
697
|
+
sextile: { angle: 60, orb: 4, color: '#56A5EC', enabled: true, lineStyle: 'dashed', strokeWidth: 1 }
|
|
698
|
+
}
|
|
699
|
+
};
|
|
700
|
+
|
|
701
|
+
// Secondary aspects (inner circle planets to inner circle planets)
|
|
702
|
+
this.secondaryAspectSettings = {
|
|
703
|
+
enabled: true,
|
|
704
|
+
orb: 6,
|
|
705
|
+
types: {
|
|
706
|
+
conjunction: { angle: 0, orb: 8, color: '#AA00AA', enabled: true, lineStyle: 'none', strokeWidth: 1 },
|
|
707
|
+
opposition: { angle: 180, orb: 6, color: '#FF6600', enabled: true, lineStyle: 'solid', strokeWidth: 1 },
|
|
708
|
+
trine: { angle: 120, orb: 6, color: '#00AA00', enabled: true, lineStyle: 'solid', strokeWidth: 1 },
|
|
709
|
+
square: { angle: 90, orb: 6, color: '#CC0066', enabled: true, lineStyle: 'dashed', strokeWidth: 1 },
|
|
710
|
+
sextile: { angle: 60, orb: 4, color: '#0099CC', enabled: true, lineStyle: 'dashed', strokeWidth: 1 }
|
|
711
|
+
}
|
|
712
|
+
};
|
|
713
|
+
|
|
714
|
+
// Synastry aspects (outer circle planets to inner circle planets)
|
|
715
|
+
this.synastryAspectSettings = {
|
|
716
|
+
enabled: true,
|
|
717
|
+
orb: 6,
|
|
718
|
+
types: {
|
|
719
|
+
conjunction: { angle: 0, orb: 8, color: '#666666', enabled: true, lineStyle: 'none', strokeWidth: 1 },
|
|
720
|
+
opposition: { angle: 180, orb: 6, color: '#9933CC', enabled: true, lineStyle: 'solid', strokeWidth: 0.5 },
|
|
721
|
+
trine: { angle: 120, orb: 6, color: '#33AA55', enabled: true, lineStyle: 'solid', strokeWidth: 0.5 },
|
|
722
|
+
square: { angle: 90, orb: 6, color: '#CC6633', enabled: true, lineStyle: 'dotted', strokeWidth: 0.5 },
|
|
723
|
+
sextile: { angle: 60, orb: 4, color: '#5599DD', enabled: true, lineStyle: 'dotted', strokeWidth: 0.5 }
|
|
724
|
+
}
|
|
725
|
+
};
|
|
726
|
+
|
|
727
|
+
// Legacy aspectSettings for backward compatibility
|
|
728
|
+
// Will be mapped to primaryAspectSettings if used
|
|
179
729
|
this.aspectSettings = {
|
|
180
730
|
enabled: true,
|
|
181
|
-
orb: 6,
|
|
731
|
+
orb: 6,
|
|
182
732
|
types: {
|
|
183
|
-
conjunction: { angle: 0, orb: 8, color:
|
|
184
|
-
opposition: { angle: 180, orb:
|
|
185
|
-
trine: { angle: 120, orb: 6, color:
|
|
186
|
-
square: { angle: 90, orb: 6, color:
|
|
187
|
-
sextile: { angle: 60, orb: 4, color:
|
|
188
|
-
// Other aspects can be added here
|
|
733
|
+
conjunction: { angle: 0, orb: 8, color: '#000000', enabled: true, lineStyle: 'none', strokeWidth: 1 },
|
|
734
|
+
opposition: { angle: 180, orb: 6, color: '#E41B17', enabled: true, lineStyle: 'solid', strokeWidth: 1 },
|
|
735
|
+
trine: { angle: 120, orb: 6, color: '#4CC417', enabled: true, lineStyle: 'solid', strokeWidth: 1 },
|
|
736
|
+
square: { angle: 90, orb: 6, color: '#F62817', enabled: true, lineStyle: 'dashed', strokeWidth: 1 },
|
|
737
|
+
sextile: { angle: 60, orb: 4, color: '#56A5EC', enabled: true, lineStyle: 'dashed', strokeWidth: 1 }
|
|
189
738
|
}
|
|
190
739
|
};
|
|
191
740
|
|
|
@@ -371,7 +920,7 @@ class ChartConfig {
|
|
|
371
920
|
}
|
|
372
921
|
);
|
|
373
922
|
} catch (error) {
|
|
374
|
-
console.error("Failed to calculate house cusps:", error);
|
|
923
|
+
console.error("Failed to calculate house cusps:", error?.message || error);
|
|
375
924
|
// Set empty cusps array if calculation fails
|
|
376
925
|
this.houseCusps = [];
|
|
377
926
|
}
|
|
@@ -417,11 +966,38 @@ class ChartConfig {
|
|
|
417
966
|
}
|
|
418
967
|
|
|
419
968
|
/**
|
|
420
|
-
* Updates aspect settings
|
|
969
|
+
* Updates aspect settings (legacy method - updates primaryAspectSettings)
|
|
421
970
|
* @param {Object} settings - New aspect settings
|
|
971
|
+
* @deprecated Use updatePrimaryAspectSettings, updateSecondaryAspectSettings, or updateSynastryAspectSettings instead
|
|
422
972
|
*/
|
|
423
973
|
updateAspectSettings(settings) {
|
|
424
974
|
this.aspectSettings = { ...this.aspectSettings, ...settings };
|
|
975
|
+
// Also update primaryAspectSettings for backward compatibility
|
|
976
|
+
this.primaryAspectSettings = { ...this.primaryAspectSettings, ...settings };
|
|
977
|
+
}
|
|
978
|
+
|
|
979
|
+
/**
|
|
980
|
+
* Updates primary aspect settings (outer circle aspects)
|
|
981
|
+
* @param {Object} settings - New aspect settings
|
|
982
|
+
*/
|
|
983
|
+
updatePrimaryAspectSettings(settings) {
|
|
984
|
+
this.primaryAspectSettings = { ...this.primaryAspectSettings, ...settings };
|
|
985
|
+
}
|
|
986
|
+
|
|
987
|
+
/**
|
|
988
|
+
* Updates secondary aspect settings (inner circle aspects)
|
|
989
|
+
* @param {Object} settings - New aspect settings
|
|
990
|
+
*/
|
|
991
|
+
updateSecondaryAspectSettings(settings) {
|
|
992
|
+
this.secondaryAspectSettings = { ...this.secondaryAspectSettings, ...settings };
|
|
993
|
+
}
|
|
994
|
+
|
|
995
|
+
/**
|
|
996
|
+
* Updates synastry aspect settings (cross-circle aspects)
|
|
997
|
+
* @param {Object} settings - New aspect settings
|
|
998
|
+
*/
|
|
999
|
+
updateSynastryAspectSettings(settings) {
|
|
1000
|
+
this.synastryAspectSettings = { ...this.synastryAspectSettings, ...settings };
|
|
425
1001
|
}
|
|
426
1002
|
|
|
427
1003
|
/**
|
|
@@ -479,11 +1055,39 @@ class ChartConfig {
|
|
|
479
1055
|
}
|
|
480
1056
|
|
|
481
1057
|
/**
|
|
482
|
-
* Toggles the visibility of aspects
|
|
1058
|
+
* Toggles the visibility of aspects (legacy - toggles all aspect types)
|
|
483
1059
|
* @param {boolean} visible - Whether aspects should be visible
|
|
1060
|
+
* @deprecated Use togglePrimaryAspectsVisibility, toggleSecondaryAspectsVisibility, or toggleSynastryAspectsVisibility instead
|
|
484
1061
|
*/
|
|
485
1062
|
toggleAspectsVisibility(visible) {
|
|
486
1063
|
this.aspectSettings.enabled = visible;
|
|
1064
|
+
this.primaryAspectSettings.enabled = visible;
|
|
1065
|
+
this.secondaryAspectSettings.enabled = visible;
|
|
1066
|
+
this.synastryAspectSettings.enabled = visible;
|
|
1067
|
+
}
|
|
1068
|
+
|
|
1069
|
+
/**
|
|
1070
|
+
* Toggles the visibility of primary aspects (outer circle)
|
|
1071
|
+
* @param {boolean} visible - Whether primary aspects should be visible
|
|
1072
|
+
*/
|
|
1073
|
+
togglePrimaryAspectsVisibility(visible) {
|
|
1074
|
+
this.primaryAspectSettings.enabled = visible;
|
|
1075
|
+
}
|
|
1076
|
+
|
|
1077
|
+
/**
|
|
1078
|
+
* Toggles the visibility of secondary aspects (inner circle)
|
|
1079
|
+
* @param {boolean} visible - Whether secondary aspects should be visible
|
|
1080
|
+
*/
|
|
1081
|
+
toggleSecondaryAspectsVisibility(visible) {
|
|
1082
|
+
this.secondaryAspectSettings.enabled = visible;
|
|
1083
|
+
}
|
|
1084
|
+
|
|
1085
|
+
/**
|
|
1086
|
+
* Toggles the visibility of synastry aspects (cross-circle)
|
|
1087
|
+
* @param {boolean} visible - Whether synastry aspects should be visible
|
|
1088
|
+
*/
|
|
1089
|
+
toggleSynastryAspectsVisibility(visible) {
|
|
1090
|
+
this.synastryAspectSettings.enabled = visible;
|
|
487
1091
|
}
|
|
488
1092
|
|
|
489
1093
|
/**
|
|
@@ -714,9 +1318,12 @@ class SVGManager {
|
|
|
714
1318
|
this.groupOrder = [
|
|
715
1319
|
'zodiac',
|
|
716
1320
|
'houseDivisions',
|
|
717
|
-
'
|
|
718
|
-
'
|
|
719
|
-
'
|
|
1321
|
+
'primaryAspects', // Aspects between primary (outer) planets
|
|
1322
|
+
'secondaryAspects', // Aspects between secondary (inner) planets
|
|
1323
|
+
'synastryAspects', // Aspects between primary and secondary planets
|
|
1324
|
+
'aspects', // Legacy aspect group (for backward compatibility)
|
|
1325
|
+
'primaryPlanets', // Outer circle planets
|
|
1326
|
+
'secondaryPlanets', // Inner circle planets
|
|
720
1327
|
'houses' // House numbers on top
|
|
721
1328
|
// Add other groups if needed, e.g., 'tooltips'
|
|
722
1329
|
];
|
|
@@ -2697,16 +3304,20 @@ class ClientSideAspectRenderer extends BaseRenderer { // No longer extends IAspe
|
|
|
2697
3304
|
/**
|
|
2698
3305
|
* Calculates aspects between planets based on their positions.
|
|
2699
3306
|
* @param {Array} planets - Array of planet objects MUST include `position` property.
|
|
3307
|
+
* @param {Object} aspectSettings - Aspect settings to use (optional, defaults to config.aspectSettings)
|
|
2700
3308
|
* @returns {Array} Array of calculated aspect objects.
|
|
2701
3309
|
*/
|
|
2702
|
-
calculateAspects(planets) {
|
|
3310
|
+
calculateAspects(planets, aspectSettings = null) {
|
|
2703
3311
|
const aspects = [];
|
|
2704
3312
|
if (!planets || planets.length < 2) {
|
|
2705
3313
|
return aspects;
|
|
2706
3314
|
}
|
|
2707
3315
|
|
|
3316
|
+
// Use provided aspectSettings or fall back to config
|
|
3317
|
+
const settings = aspectSettings || this.config.aspectSettings || {};
|
|
3318
|
+
|
|
2708
3319
|
// Generate a cache key based on aspectSettings and planet positions
|
|
2709
|
-
const settingsString = JSON.stringify(
|
|
3320
|
+
const settingsString = JSON.stringify(settings);
|
|
2710
3321
|
const planetKey = planets.map(p => `${p.name}:${p.position}`).join('|');
|
|
2711
3322
|
const cacheKey = `${settingsString}|${planetKey}`;
|
|
2712
3323
|
if (cacheKey === this._aspectCacheKey) {
|
|
@@ -2714,10 +3325,9 @@ class ClientSideAspectRenderer extends BaseRenderer { // No longer extends IAspe
|
|
|
2714
3325
|
return this._aspectCache;
|
|
2715
3326
|
}
|
|
2716
3327
|
|
|
2717
|
-
// Get aspect types and orbs from
|
|
2718
|
-
const
|
|
2719
|
-
const
|
|
2720
|
-
const aspectTypes = aspectSettings.types || this.defaultAspectDefinitions;
|
|
3328
|
+
// Get aspect types and orbs from settings, falling back to defaults
|
|
3329
|
+
const calculationOrb = settings.orb || 6; // Default orb if not specified per aspect
|
|
3330
|
+
const aspectTypes = settings.types || this.defaultAspectDefinitions;
|
|
2721
3331
|
|
|
2722
3332
|
// Iterate through all unique pairs of planets
|
|
2723
3333
|
for (let i = 0; i < planets.length; i++) {
|
|
@@ -2760,15 +3370,75 @@ class ClientSideAspectRenderer extends BaseRenderer { // No longer extends IAspe
|
|
|
2760
3370
|
this._aspectCache = aspects;
|
|
2761
3371
|
return aspects;
|
|
2762
3372
|
}
|
|
3373
|
+
|
|
3374
|
+
/**
|
|
3375
|
+
* Calculates cross-aspects (synastry) between two different planet sets.
|
|
3376
|
+
* @param {Array} planets1 - First array of planet objects (e.g., primary/outer planets)
|
|
3377
|
+
* @param {Array} planets2 - Second array of planet objects (e.g., secondary/inner planets)
|
|
3378
|
+
* @param {Object} aspectSettings - Aspect settings to use
|
|
3379
|
+
* @returns {Array} Array of calculated cross-aspect objects.
|
|
3380
|
+
*/
|
|
3381
|
+
calculateCrossAspects(planets1, planets2, aspectSettings = null) {
|
|
3382
|
+
const aspects = [];
|
|
3383
|
+
if (!planets1 || planets1.length < 1 || !planets2 || planets2.length < 1) {
|
|
3384
|
+
return aspects;
|
|
3385
|
+
}
|
|
3386
|
+
|
|
3387
|
+
// Use provided aspectSettings or fall back to synastry settings
|
|
3388
|
+
const settings = aspectSettings || this.config.synastryAspectSettings || {};
|
|
3389
|
+
|
|
3390
|
+
// Get aspect types and orbs from settings
|
|
3391
|
+
const calculationOrb = settings.orb || 6;
|
|
3392
|
+
const aspectTypes = settings.types || this.defaultAspectDefinitions;
|
|
3393
|
+
|
|
3394
|
+
// Iterate through all pairs between the two planet sets
|
|
3395
|
+
for (let i = 0; i < planets1.length; i++) {
|
|
3396
|
+
for (let j = 0; j < planets2.length; j++) {
|
|
3397
|
+
const p1 = planets1[i];
|
|
3398
|
+
const p2 = planets2[j];
|
|
3399
|
+
|
|
3400
|
+
const angleDiff = this._angularDistance(p1.position, p2.position);
|
|
3401
|
+
|
|
3402
|
+
// Check against each defined aspect type
|
|
3403
|
+
for (const aspectName in aspectTypes) {
|
|
3404
|
+
const aspectDef = aspectTypes[aspectName];
|
|
3405
|
+
const targetAngle = aspectDef.angle;
|
|
3406
|
+
const orb = aspectDef.orb !== undefined ? aspectDef.orb : calculationOrb;
|
|
3407
|
+
|
|
3408
|
+
if (Math.abs(angleDiff - targetAngle) <= orb) {
|
|
3409
|
+
// Cross-aspect found!
|
|
3410
|
+
aspects.push({
|
|
3411
|
+
planet1: p1.name,
|
|
3412
|
+
planet2: p2.name,
|
|
3413
|
+
type: aspectName,
|
|
3414
|
+
angle: targetAngle,
|
|
3415
|
+
angleDiff: angleDiff,
|
|
3416
|
+
orb: Math.abs(angleDiff - targetAngle),
|
|
3417
|
+
p1: p1,
|
|
3418
|
+
p2: p2,
|
|
3419
|
+
color: aspectDef.color || '#888',
|
|
3420
|
+
lineStyle: aspectDef.lineStyle,
|
|
3421
|
+
abbr: aspectDef.abbr || aspectName.substring(0, 3).toUpperCase(),
|
|
3422
|
+
isCross: true // Mark as cross-aspect for identification
|
|
3423
|
+
});
|
|
3424
|
+
}
|
|
3425
|
+
}
|
|
3426
|
+
}
|
|
3427
|
+
}
|
|
3428
|
+
|
|
3429
|
+
console.log(`ClientSideAspectRenderer: Calculated ${aspects.length} cross-aspects (synastry).`);
|
|
3430
|
+
return aspects;
|
|
3431
|
+
}
|
|
2763
3432
|
|
|
2764
3433
|
|
|
2765
3434
|
/**
|
|
2766
3435
|
* Renders aspect lines based on planet coordinates.
|
|
2767
3436
|
* @param {Element} parentGroup - The parent SVG group for aspect lines.
|
|
2768
3437
|
* @param {Array} planetsWithCoords - Array of planet objects returned by PlanetRenderer, MUST include `x`, `y`, `name`, and `position`.
|
|
3438
|
+
* @param {Object} aspectSettings - Aspect settings to use (optional)
|
|
2769
3439
|
* @returns {Array<Element>} Array containing the created line elements.
|
|
2770
3440
|
*/
|
|
2771
|
-
render(parentGroup, planetsWithCoords) {
|
|
3441
|
+
render(parentGroup, planetsWithCoords, aspectSettings = null) {
|
|
2772
3442
|
if (!parentGroup) {
|
|
2773
3443
|
console.error("ClientSideAspectRenderer.render: parentGroup is null or undefined.");
|
|
2774
3444
|
return [];
|
|
@@ -2783,14 +3453,14 @@ class ClientSideAspectRenderer extends BaseRenderer { // No longer extends IAspe
|
|
|
2783
3453
|
}
|
|
2784
3454
|
|
|
2785
3455
|
// Calculate aspects based on planet positions
|
|
2786
|
-
const aspects = this.calculateAspects(planetsWithCoords);
|
|
3456
|
+
const aspects = this.calculateAspects(planetsWithCoords, aspectSettings);
|
|
2787
3457
|
this.renderedAspects = aspects; // Store the calculated aspects
|
|
2788
3458
|
|
|
2789
3459
|
console.log(`ClientSideAspectRenderer: Rendering ${aspects.length} aspects.`);
|
|
2790
3460
|
|
|
2791
3461
|
// Get enabled aspect types from config
|
|
2792
|
-
const
|
|
2793
|
-
const aspectTypesConfig =
|
|
3462
|
+
const settings = aspectSettings || this.config.aspectSettings || {};
|
|
3463
|
+
const aspectTypesConfig = settings.types || {};
|
|
2794
3464
|
|
|
2795
3465
|
// Map planets by name for quick coordinate lookup
|
|
2796
3466
|
const planetCoords = {};
|
|
@@ -2852,6 +3522,155 @@ class ClientSideAspectRenderer extends BaseRenderer { // No longer extends IAspe
|
|
|
2852
3522
|
|
|
2853
3523
|
return renderedElements;
|
|
2854
3524
|
}
|
|
3525
|
+
|
|
3526
|
+
/**
|
|
3527
|
+
* Renders cross-aspect lines (synastry) between two planet sets.
|
|
3528
|
+
* @param {Element} parentGroup - The parent SVG group for aspect lines.
|
|
3529
|
+
* @param {Array} primaryPlanets - Array of primary planet objects with coordinates
|
|
3530
|
+
* @param {Array} secondaryPlanets - Array of secondary planet objects with coordinates
|
|
3531
|
+
* @param {Object} aspectSettings - Aspect settings to use
|
|
3532
|
+
* @returns {Array<Element>} Array containing the created line elements.
|
|
3533
|
+
*/
|
|
3534
|
+
renderCrossAspects(parentGroup, primaryPlanets, secondaryPlanets, aspectSettings = null) {
|
|
3535
|
+
if (!parentGroup) {
|
|
3536
|
+
console.error("ClientSideAspectRenderer.renderCrossAspects: parentGroup is null or undefined.");
|
|
3537
|
+
return [];
|
|
3538
|
+
}
|
|
3539
|
+
this.clearGroup(parentGroup);
|
|
3540
|
+
const renderedElements = [];
|
|
3541
|
+
|
|
3542
|
+
if (!primaryPlanets || primaryPlanets.length < 1 || !secondaryPlanets || secondaryPlanets.length < 1) {
|
|
3543
|
+
console.warn("ClientSideAspectRenderer: Not enough planet data to render cross-aspects.");
|
|
3544
|
+
return [];
|
|
3545
|
+
}
|
|
3546
|
+
|
|
3547
|
+
// Calculate cross-aspects
|
|
3548
|
+
const aspects = this.calculateCrossAspects(primaryPlanets, secondaryPlanets, aspectSettings);
|
|
3549
|
+
|
|
3550
|
+
console.log(`ClientSideAspectRenderer: Rendering ${aspects.length} cross-aspects.`);
|
|
3551
|
+
|
|
3552
|
+
// Get enabled aspect types from config
|
|
3553
|
+
const settings = aspectSettings || this.config.synastryAspectSettings || {};
|
|
3554
|
+
const aspectTypesConfig = settings.types || {};
|
|
3555
|
+
|
|
3556
|
+
// Map planets by name for coordinate lookup - keep separate to avoid overwriting
|
|
3557
|
+
const primaryCoords = {};
|
|
3558
|
+
primaryPlanets.forEach(p => {
|
|
3559
|
+
primaryCoords[p.name] = { x: p.x, y: p.y };
|
|
3560
|
+
});
|
|
3561
|
+
|
|
3562
|
+
const secondaryCoords = {};
|
|
3563
|
+
secondaryPlanets.forEach(p => {
|
|
3564
|
+
secondaryCoords[p.name] = { x: p.x, y: p.y };
|
|
3565
|
+
});
|
|
3566
|
+
|
|
3567
|
+
aspects.forEach(aspect => {
|
|
3568
|
+
// For cross-aspects:
|
|
3569
|
+
// - aspect.planet1 is from primary (outer circle)
|
|
3570
|
+
// - aspect.planet2 is from secondary (inner circle)
|
|
3571
|
+
// We need to draw line from secondary planet to PRIMARY's projection on inner circle
|
|
3572
|
+
|
|
3573
|
+
// Get the primary planet to calculate its projection on inner circle
|
|
3574
|
+
const primaryPlanet = aspect.p1; // This has the position (degrees)
|
|
3575
|
+
aspect.p2;
|
|
3576
|
+
|
|
3577
|
+
// Calculate projection of primary planet onto inner circle radius
|
|
3578
|
+
// Using the same angle but inner circle radius
|
|
3579
|
+
const innerRadius = this.config.radius.innermost || 90;
|
|
3580
|
+
const angle = primaryPlanet.position;
|
|
3581
|
+
const radians = (angle - 90) * (Math.PI / 180); // Adjust for SVG coordinate system
|
|
3582
|
+
|
|
3583
|
+
const projectionX = this.centerX + innerRadius * Math.cos(radians);
|
|
3584
|
+
const projectionY = this.centerY + innerRadius * Math.sin(radians);
|
|
3585
|
+
|
|
3586
|
+
// coords1 is the projection of primary planet on inner circle
|
|
3587
|
+
const coords1 = { x: projectionX, y: projectionY };
|
|
3588
|
+
// coords2 is the actual secondary planet position
|
|
3589
|
+
const coords2 = secondaryCoords[aspect.planet2];
|
|
3590
|
+
|
|
3591
|
+
const aspectDef = aspectTypesConfig[aspect.type];
|
|
3592
|
+
const isEnabled = aspectDef ? (aspectDef.enabled !== false) : true;
|
|
3593
|
+
const lineStyle = aspectDef ? aspectDef.lineStyle : 'solid';
|
|
3594
|
+
|
|
3595
|
+
if (!isEnabled || lineStyle === 'none') {
|
|
3596
|
+
return;
|
|
3597
|
+
}
|
|
3598
|
+
|
|
3599
|
+
if (!coords1 || !coords2) {
|
|
3600
|
+
console.warn(`ClientSideAspectRenderer: Could not find coordinates for cross-aspect: ${aspect.planet1} ${aspect.type} ${aspect.planet2}`);
|
|
3601
|
+
return;
|
|
3602
|
+
}
|
|
3603
|
+
|
|
3604
|
+
const p1SafeName = (aspect.planet1 || '').toLowerCase().replace(/[^a-z0-9]/g, '-');
|
|
3605
|
+
const p2SafeName = (aspect.planet2 || '').toLowerCase().replace(/[^a-z0-9]/g, '-');
|
|
3606
|
+
|
|
3607
|
+
let strokeDasharray = 'none';
|
|
3608
|
+
if (lineStyle === 'dashed') {
|
|
3609
|
+
strokeDasharray = '5, 5';
|
|
3610
|
+
} else if (lineStyle === 'dotted') {
|
|
3611
|
+
strokeDasharray = '1, 3';
|
|
3612
|
+
}
|
|
3613
|
+
|
|
3614
|
+
const line = this.svgUtils.createSVGElement("line", {
|
|
3615
|
+
x1: coords1.x,
|
|
3616
|
+
y1: coords1.y,
|
|
3617
|
+
x2: coords2.x,
|
|
3618
|
+
y2: coords2.y,
|
|
3619
|
+
class: `aspect-element aspect-line aspect-${aspect.type} aspect-cross aspect-planet-${p1SafeName} aspect-planet-${p2SafeName}`,
|
|
3620
|
+
stroke: aspect.color || '#888888',
|
|
3621
|
+
'stroke-dasharray': strokeDasharray
|
|
3622
|
+
});
|
|
3623
|
+
|
|
3624
|
+
const tooltipText = `${this.astrologyUtils.capitalizeFirstLetter(aspect.planet1)} ${aspect.type} ${this.astrologyUtils.capitalizeFirstLetter(aspect.planet2)} (${aspect.angleDiff.toFixed(1)}°, orb ${aspect.orb.toFixed(1)}°) [Synastry]`;
|
|
3625
|
+
this.svgUtils.addTooltip(line, tooltipText);
|
|
3626
|
+
|
|
3627
|
+
parentGroup.appendChild(line);
|
|
3628
|
+
renderedElements.push(line);
|
|
3629
|
+
|
|
3630
|
+
this._addAspectIcon(parentGroup, aspect, coords1, coords2, tooltipText);
|
|
3631
|
+
});
|
|
3632
|
+
|
|
3633
|
+
// Render projection dots (hollow circles) for primary planets on inner circle
|
|
3634
|
+
this._renderProjectionDots(parentGroup, primaryPlanets);
|
|
3635
|
+
|
|
3636
|
+
return renderedElements;
|
|
3637
|
+
}
|
|
3638
|
+
|
|
3639
|
+
/**
|
|
3640
|
+
* Renders projection dots (hollow circles) for primary planets on the inner circle
|
|
3641
|
+
* @private
|
|
3642
|
+
* @param {Element} parentGroup - The SVG group for aspect lines
|
|
3643
|
+
* @param {Array} primaryPlanets - Array of primary planet objects
|
|
3644
|
+
*/
|
|
3645
|
+
_renderProjectionDots(parentGroup, primaryPlanets) {
|
|
3646
|
+
const innerRadius = this.config.radius.innermost || 90;
|
|
3647
|
+
const dotRadius = 3; // Size of the hollow circle
|
|
3648
|
+
|
|
3649
|
+
primaryPlanets.forEach(planet => {
|
|
3650
|
+
const angle = planet.position;
|
|
3651
|
+
const radians = (angle - 90) * (Math.PI / 180); // Adjust for SVG coordinate system
|
|
3652
|
+
|
|
3653
|
+
const x = this.centerX + innerRadius * Math.cos(radians);
|
|
3654
|
+
const y = this.centerY + innerRadius * Math.sin(radians);
|
|
3655
|
+
|
|
3656
|
+
// Create hollow circle (stroke only, no fill)
|
|
3657
|
+
const circle = this.svgUtils.createSVGElement("circle", {
|
|
3658
|
+
cx: x,
|
|
3659
|
+
cy: y,
|
|
3660
|
+
r: dotRadius,
|
|
3661
|
+
class: `projection-dot projection-${planet.name}`,
|
|
3662
|
+
fill: 'none',
|
|
3663
|
+
stroke: planet.color || '#666666',
|
|
3664
|
+
'stroke-width': '1.5'
|
|
3665
|
+
});
|
|
3666
|
+
|
|
3667
|
+
// Add tooltip
|
|
3668
|
+
const tooltipText = `${this.astrologyUtils.capitalizeFirstLetter(planet.name)} projection (${angle.toFixed(1)}°)`;
|
|
3669
|
+
this.svgUtils.addTooltip(circle, tooltipText);
|
|
3670
|
+
|
|
3671
|
+
parentGroup.appendChild(circle);
|
|
3672
|
+
});
|
|
3673
|
+
}
|
|
2855
3674
|
|
|
2856
3675
|
/**
|
|
2857
3676
|
* Overrides the BaseRenderer clearGroup method to ensure custom cleanup.
|
|
@@ -3718,9 +4537,13 @@ class NocturnaWheel {
|
|
|
3718
4537
|
* Constructor
|
|
3719
4538
|
* @param {Object} options - Configuration options
|
|
3720
4539
|
* @param {string|Element} options.container - Container element or selector
|
|
3721
|
-
* @param {Object} options.planets -
|
|
4540
|
+
* @param {Object} options.planets - Primary planet positions data (outer circle)
|
|
4541
|
+
* @param {Object} options.secondaryPlanets - Secondary planet positions data (inner circle, optional)
|
|
3722
4542
|
* @param {Array} options.houses - House cusps data (optional)
|
|
3723
|
-
* @param {Object} options.aspectSettings - Aspect calculation settings (optional)
|
|
4543
|
+
* @param {Object} options.aspectSettings - Aspect calculation settings (optional, legacy)
|
|
4544
|
+
* @param {Object} options.primaryAspectSettings - Primary aspect settings (optional)
|
|
4545
|
+
* @param {Object} options.secondaryAspectSettings - Secondary aspect settings (optional)
|
|
4546
|
+
* @param {Object} options.synastryAspectSettings - Synastry aspect settings (optional)
|
|
3724
4547
|
* @param {Object} options.config - Additional configuration (optional)
|
|
3725
4548
|
*/
|
|
3726
4549
|
constructor(options) {
|
|
@@ -3740,15 +4563,31 @@ class NocturnaWheel {
|
|
|
3740
4563
|
// Initialize configuration
|
|
3741
4564
|
this.config = new ChartConfig(options.config || {});
|
|
3742
4565
|
|
|
3743
|
-
// Set data
|
|
4566
|
+
// Set data - primary planets (outer circle)
|
|
3744
4567
|
this.planets = options.planets || {};
|
|
4568
|
+
|
|
4569
|
+
// Set secondary planets (inner circle) - independent data
|
|
4570
|
+
// If not provided, initialize as empty object (user must explicitly set)
|
|
4571
|
+
this.secondaryPlanets = options.secondaryPlanets || {};
|
|
4572
|
+
|
|
3745
4573
|
this.houses = options.houses || [];
|
|
3746
4574
|
|
|
3747
|
-
// Override aspect settings if provided
|
|
4575
|
+
// Override aspect settings if provided (legacy support)
|
|
3748
4576
|
if (options.aspectSettings) {
|
|
3749
4577
|
this.config.updateAspectSettings(options.aspectSettings);
|
|
3750
4578
|
}
|
|
3751
4579
|
|
|
4580
|
+
// Override specific aspect settings if provided
|
|
4581
|
+
if (options.primaryAspectSettings) {
|
|
4582
|
+
this.config.updatePrimaryAspectSettings(options.primaryAspectSettings);
|
|
4583
|
+
}
|
|
4584
|
+
if (options.secondaryAspectSettings) {
|
|
4585
|
+
this.config.updateSecondaryAspectSettings(options.secondaryAspectSettings);
|
|
4586
|
+
}
|
|
4587
|
+
if (options.synastryAspectSettings) {
|
|
4588
|
+
this.config.updateSynastryAspectSettings(options.synastryAspectSettings);
|
|
4589
|
+
}
|
|
4590
|
+
|
|
3752
4591
|
// Initialize services
|
|
3753
4592
|
ServiceRegistry.initializeServices();
|
|
3754
4593
|
|
|
@@ -3856,30 +4695,74 @@ class NocturnaWheel {
|
|
|
3856
4695
|
}
|
|
3857
4696
|
|
|
3858
4697
|
// Render planets using the consolidated approach
|
|
3859
|
-
let
|
|
4698
|
+
let primaryPlanetsWithCoords = [];
|
|
4699
|
+
let secondaryPlanetsWithCoords = [];
|
|
3860
4700
|
if (this.config.planetSettings.enabled) {
|
|
3861
4701
|
// Get the enabled states for primary and secondary planets
|
|
3862
4702
|
const primaryEnabled = this.config.planetSettings.primaryEnabled !== false;
|
|
3863
4703
|
const secondaryEnabled = this.config.planetSettings.secondaryEnabled !== false;
|
|
3864
4704
|
|
|
3865
|
-
//
|
|
3866
|
-
|
|
3867
|
-
|
|
3868
|
-
|
|
3869
|
-
|
|
3870
|
-
|
|
3871
|
-
|
|
3872
|
-
|
|
4705
|
+
// Render primary planets (outer circle)
|
|
4706
|
+
if (primaryEnabled && Object.keys(this.planets).length > 0) {
|
|
4707
|
+
const primaryGroup = this.svgManager.getGroup('primaryPlanets');
|
|
4708
|
+
const primaryArray = Object.entries(this.planets)
|
|
4709
|
+
.filter(([name, data]) => this.config.planetSettings.visible?.[name] !== false)
|
|
4710
|
+
.map(([name, data]) => ({
|
|
4711
|
+
name: name,
|
|
4712
|
+
position: data.lon,
|
|
4713
|
+
color: data.color || '#000000'
|
|
4714
|
+
}));
|
|
4715
|
+
primaryPlanetsWithCoords = this.renderers.planet.primaryRenderer.render(primaryGroup, primaryArray, 0, {
|
|
4716
|
+
config: this.config
|
|
4717
|
+
});
|
|
4718
|
+
}
|
|
3873
4719
|
|
|
3874
|
-
//
|
|
3875
|
-
|
|
4720
|
+
// Render secondary planets (inner circle) using SEPARATE data
|
|
4721
|
+
if (secondaryEnabled && Object.keys(this.secondaryPlanets).length > 0) {
|
|
4722
|
+
const secondaryGroup = this.svgManager.getGroup('secondaryPlanets');
|
|
4723
|
+
const secondaryArray = Object.entries(this.secondaryPlanets)
|
|
4724
|
+
.filter(([name, data]) => this.config.planetSettings.visible?.[name] !== false)
|
|
4725
|
+
.map(([name, data]) => ({
|
|
4726
|
+
name: name,
|
|
4727
|
+
position: data.lon,
|
|
4728
|
+
color: data.color || '#000000'
|
|
4729
|
+
}));
|
|
4730
|
+
secondaryPlanetsWithCoords = this.renderers.planet.secondaryRenderer.render(secondaryGroup, secondaryArray, 0, {
|
|
4731
|
+
config: this.config
|
|
4732
|
+
});
|
|
4733
|
+
}
|
|
3876
4734
|
|
|
3877
|
-
console.log(`NocturnaWheel: Rendered ${
|
|
4735
|
+
console.log(`NocturnaWheel: Rendered ${primaryPlanetsWithCoords.length} primary planets and ${secondaryPlanetsWithCoords.length} secondary planets`);
|
|
3878
4736
|
}
|
|
3879
4737
|
|
|
3880
|
-
// Render
|
|
3881
|
-
|
|
3882
|
-
|
|
4738
|
+
// Render three independent aspect types
|
|
4739
|
+
|
|
4740
|
+
// 1. Primary aspects (outer circle to outer circle)
|
|
4741
|
+
if (this.config.primaryAspectSettings.enabled && primaryPlanetsWithCoords.length >= 2) {
|
|
4742
|
+
const primaryAspectsGroup = this.svgManager.getGroup('primaryAspects');
|
|
4743
|
+
this.renderers.aspect.render(primaryAspectsGroup, primaryPlanetsWithCoords, this.config.primaryAspectSettings);
|
|
4744
|
+
console.log("NocturnaWheel: Rendered primary aspects");
|
|
4745
|
+
}
|
|
4746
|
+
|
|
4747
|
+
// 2. Secondary aspects (inner circle to inner circle)
|
|
4748
|
+
if (this.config.secondaryAspectSettings.enabled && secondaryPlanetsWithCoords.length >= 2) {
|
|
4749
|
+
const secondaryAspectsGroup = this.svgManager.getGroup('secondaryAspects');
|
|
4750
|
+
this.renderers.aspect.render(secondaryAspectsGroup, secondaryPlanetsWithCoords, this.config.secondaryAspectSettings);
|
|
4751
|
+
console.log("NocturnaWheel: Rendered secondary aspects");
|
|
4752
|
+
}
|
|
4753
|
+
|
|
4754
|
+
// 3. Synastry aspects (outer circle to inner circle)
|
|
4755
|
+
if (this.config.synastryAspectSettings.enabled &&
|
|
4756
|
+
primaryPlanetsWithCoords.length >= 1 &&
|
|
4757
|
+
secondaryPlanetsWithCoords.length >= 1) {
|
|
4758
|
+
const synastryAspectsGroup = this.svgManager.getGroup('synastryAspects');
|
|
4759
|
+
this.renderers.aspect.renderCrossAspects(
|
|
4760
|
+
synastryAspectsGroup,
|
|
4761
|
+
primaryPlanetsWithCoords,
|
|
4762
|
+
secondaryPlanetsWithCoords,
|
|
4763
|
+
this.config.synastryAspectSettings
|
|
4764
|
+
);
|
|
4765
|
+
console.log("NocturnaWheel: Rendered synastry aspects");
|
|
3883
4766
|
}
|
|
3884
4767
|
|
|
3885
4768
|
console.log("NocturnaWheel: Chart rendered");
|
|
@@ -3937,15 +4820,49 @@ class NocturnaWheel {
|
|
|
3937
4820
|
}
|
|
3938
4821
|
|
|
3939
4822
|
/**
|
|
3940
|
-
* Toggles the visibility of aspects
|
|
4823
|
+
* Toggles the visibility of aspects (legacy - toggles all)
|
|
3941
4824
|
* @param {boolean} visible - Visibility state
|
|
3942
4825
|
* @returns {NocturnaWheel} - Instance for chaining
|
|
4826
|
+
* @deprecated Use togglePrimaryAspects, toggleSecondaryAspects, or toggleSynastryAspects instead
|
|
3943
4827
|
*/
|
|
3944
4828
|
toggleAspects(visible) {
|
|
3945
4829
|
this.config.toggleAspectsVisibility(visible);
|
|
3946
4830
|
this.render();
|
|
3947
4831
|
return this;
|
|
3948
4832
|
}
|
|
4833
|
+
|
|
4834
|
+
/**
|
|
4835
|
+
* Toggles the visibility of primary aspects (outer circle)
|
|
4836
|
+
* @param {boolean} visible - Visibility state
|
|
4837
|
+
* @returns {NocturnaWheel} - Instance for chaining
|
|
4838
|
+
*/
|
|
4839
|
+
togglePrimaryAspects(visible) {
|
|
4840
|
+
this.config.togglePrimaryAspectsVisibility(visible);
|
|
4841
|
+
this.render();
|
|
4842
|
+
return this;
|
|
4843
|
+
}
|
|
4844
|
+
|
|
4845
|
+
/**
|
|
4846
|
+
* Toggles the visibility of secondary aspects (inner circle)
|
|
4847
|
+
* @param {boolean} visible - Visibility state
|
|
4848
|
+
* @returns {NocturnaWheel} - Instance for chaining
|
|
4849
|
+
*/
|
|
4850
|
+
toggleSecondaryAspects(visible) {
|
|
4851
|
+
this.config.toggleSecondaryAspectsVisibility(visible);
|
|
4852
|
+
this.render();
|
|
4853
|
+
return this;
|
|
4854
|
+
}
|
|
4855
|
+
|
|
4856
|
+
/**
|
|
4857
|
+
* Toggles the visibility of synastry aspects (cross-circle)
|
|
4858
|
+
* @param {boolean} visible - Visibility state
|
|
4859
|
+
* @returns {NocturnaWheel} - Instance for chaining
|
|
4860
|
+
*/
|
|
4861
|
+
toggleSynastryAspects(visible) {
|
|
4862
|
+
this.config.toggleSynastryAspectsVisibility(visible);
|
|
4863
|
+
this.render();
|
|
4864
|
+
return this;
|
|
4865
|
+
}
|
|
3949
4866
|
|
|
3950
4867
|
/**
|
|
3951
4868
|
* Sets the house system rotation angle
|
|
@@ -3976,8 +4893,8 @@ class NocturnaWheel {
|
|
|
3976
4893
|
}
|
|
3977
4894
|
|
|
3978
4895
|
/**
|
|
3979
|
-
* Updates chart data (planets, houses)
|
|
3980
|
-
* @param {Object} data - Object containing new data, e.g., { planets: {...}, houses: [...] }
|
|
4896
|
+
* Updates chart data (planets, secondaryPlanets, houses)
|
|
4897
|
+
* @param {Object} data - Object containing new data, e.g., { planets: {...}, secondaryPlanets: {...}, houses: [...] }
|
|
3981
4898
|
* @returns {NocturnaWheel} - Instance for chaining
|
|
3982
4899
|
*/
|
|
3983
4900
|
updateData(data) {
|
|
@@ -3991,6 +4908,15 @@ class NocturnaWheel {
|
|
|
3991
4908
|
console.warn("NocturnaWheel.updateData: Invalid planets data format. Expected object.");
|
|
3992
4909
|
}
|
|
3993
4910
|
}
|
|
4911
|
+
if (data.secondaryPlanets) {
|
|
4912
|
+
// Update internal secondary planets data
|
|
4913
|
+
if (typeof data.secondaryPlanets === 'object' && !Array.isArray(data.secondaryPlanets)) {
|
|
4914
|
+
this.secondaryPlanets = { ...this.secondaryPlanets, ...data.secondaryPlanets };
|
|
4915
|
+
console.log("NocturnaWheel: Updated secondary planets data.");
|
|
4916
|
+
} else {
|
|
4917
|
+
console.warn("NocturnaWheel.updateData: Invalid secondaryPlanets data format. Expected object.");
|
|
4918
|
+
}
|
|
4919
|
+
}
|
|
3994
4920
|
if (data.houses) {
|
|
3995
4921
|
// Update internal houses data
|
|
3996
4922
|
if (Array.isArray(data.houses)) {
|
|
@@ -4237,9 +5163,13 @@ class WheelChart {
|
|
|
4237
5163
|
* Constructor
|
|
4238
5164
|
* @param {Object} options - Configuration options
|
|
4239
5165
|
* @param {string|Element} options.container - Container element or selector
|
|
4240
|
-
* @param {Object} options.planets -
|
|
5166
|
+
* @param {Object} options.planets - Primary planet positions data (outer circle)
|
|
5167
|
+
* @param {Object} options.secondaryPlanets - Secondary planet positions data (inner circle, optional)
|
|
4241
5168
|
* @param {Array} options.houses - House cusps data (optional)
|
|
4242
|
-
* @param {Object} options.aspectSettings - Aspect calculation settings (optional)
|
|
5169
|
+
* @param {Object} options.aspectSettings - Aspect calculation settings (optional, legacy)
|
|
5170
|
+
* @param {Object} options.primaryAspectSettings - Primary aspect settings (optional)
|
|
5171
|
+
* @param {Object} options.secondaryAspectSettings - Secondary aspect settings (optional)
|
|
5172
|
+
* @param {Object} options.synastryAspectSettings - Synastry aspect settings (optional)
|
|
4243
5173
|
* @param {Object} options.config - Additional configuration (optional)
|
|
4244
5174
|
* @param {Function} [chartFactory=null] - Factory function to create the chart instance
|
|
4245
5175
|
* Function signature: (options) => ChartInstance
|
|
@@ -4356,6 +5286,18 @@ class WheelChart {
|
|
|
4356
5286
|
return this._delegateAndRedraw('toggleAspects', visible);
|
|
4357
5287
|
}
|
|
4358
5288
|
|
|
5289
|
+
togglePrimaryAspects(visible) {
|
|
5290
|
+
return this._delegateAndRedraw('togglePrimaryAspects', visible);
|
|
5291
|
+
}
|
|
5292
|
+
|
|
5293
|
+
toggleSecondaryAspects(visible) {
|
|
5294
|
+
return this._delegateAndRedraw('toggleSecondaryAspects', visible);
|
|
5295
|
+
}
|
|
5296
|
+
|
|
5297
|
+
toggleSynastryAspects(visible) {
|
|
5298
|
+
return this._delegateAndRedraw('toggleSynastryAspects', visible);
|
|
5299
|
+
}
|
|
5300
|
+
|
|
4359
5301
|
/**
|
|
4360
5302
|
* Toggles the visibility of primary planets (inner circle)
|
|
4361
5303
|
* @param {boolean} visible - Visibility state
|