@eaprelsky/nocturna-wheel 4.0.1 → 4.0.3
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/dist/nocturna-wheel.bundle.js +139 -139
- package/dist/nocturna-wheel.bundle.js.map +1 -1
- package/dist/nocturna-wheel.es.js +139 -139
- 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 +139 -139
- package/dist/nocturna-wheel.umd.js.map +1 -1
- package/package.json +1 -1
|
@@ -293,7 +293,7 @@
|
|
|
293
293
|
/**
|
|
294
294
|
* IconData.js
|
|
295
295
|
* Auto-generated module containing inline SVG icons as data URLs
|
|
296
|
-
* Generated at: 2025-
|
|
296
|
+
* Generated at: 2025-12-21T15:10:29.834Z
|
|
297
297
|
*
|
|
298
298
|
* This file is automatically generated by the build process.
|
|
299
299
|
* Do not edit manually - changes will be overwritten.
|
|
@@ -2337,41 +2337,26 @@
|
|
|
2337
2337
|
// Offset needed to place Ascendant (house 1 cusp) at 0 degrees (top side)
|
|
2338
2338
|
ascendantAlignmentOffset = (360 - ascendantLon) % 360;
|
|
2339
2339
|
}
|
|
2340
|
-
|
|
2341
|
-
//
|
|
2342
|
-
|
|
2340
|
+
|
|
2341
|
+
// Render house numbers at the CENTER of each house sector.
|
|
2342
|
+
// If house cusp data is available, compute the midpoint between cusp[i] and cusp[i+1] (with proper 0/360 wrap).
|
|
2343
|
+
// Otherwise, fall back to equal 30° houses.
|
|
2343
2344
|
for (let i = 0; i < 12; i++) {
|
|
2344
|
-
|
|
2345
|
+
const houseNumber = i + 1;
|
|
2346
|
+
|
|
2347
|
+
let midAngle;
|
|
2345
2348
|
if (this.houseData && this.houseData.length >= 12) {
|
|
2346
|
-
|
|
2349
|
+
const startLon = this.getHouseLongitude(this.houseData[i]);
|
|
2350
|
+
const endLon = this.getHouseLongitude(this.houseData[(i + 1) % 12]);
|
|
2351
|
+
const arc = (endLon - startLon + 360) % 360; // always move forward through the zodiac
|
|
2352
|
+
const midLon = (startLon + arc / 2) % 360;
|
|
2353
|
+
midAngle = (midLon + ascendantAlignmentOffset + rotationAngle) % 360;
|
|
2347
2354
|
} else {
|
|
2348
|
-
|
|
2355
|
+
// Equal houses: center of each 30° segment
|
|
2356
|
+
midAngle = (i * 30 + 15 + rotationAngle) % 360;
|
|
2349
2357
|
}
|
|
2350
|
-
const rotatedAngle = (baseAngle + ascendantAlignmentOffset + rotationAngle) % 360;
|
|
2351
2358
|
|
|
2352
|
-
|
|
2353
|
-
originalIndex: i,
|
|
2354
|
-
baseAngle: baseAngle,
|
|
2355
|
-
rotatedAngle: rotatedAngle
|
|
2356
|
-
});
|
|
2357
|
-
}
|
|
2358
|
-
|
|
2359
|
-
// Sort by rotated angle to determine visual order
|
|
2360
|
-
housesWithAngles.sort((a, b) => a.rotatedAngle - b.rotatedAngle);
|
|
2361
|
-
|
|
2362
|
-
// Find which house is Ascendant (originally index 0) after rotation
|
|
2363
|
-
const ascendantVisualIndex = housesWithAngles.findIndex(h => h.originalIndex === 0);
|
|
2364
|
-
|
|
2365
|
-
// Render houses with correct numbering based on visual position
|
|
2366
|
-
housesWithAngles.forEach((house, visualIndex) => {
|
|
2367
|
-
const houseAngle = house.rotatedAngle;
|
|
2368
|
-
|
|
2369
|
-
// Calculate house number based on position relative to Ascendant
|
|
2370
|
-
// Counter-clockwise from Ascendant
|
|
2371
|
-
const houseNumber = ((visualIndex - ascendantVisualIndex + 12) % 12) + 1;
|
|
2372
|
-
|
|
2373
|
-
// Offset text from house line clockwise
|
|
2374
|
-
const angle = (houseAngle + 15) % 360; // Place in middle of house segment, apply modulo
|
|
2359
|
+
const angle = midAngle;
|
|
2375
2360
|
|
|
2376
2361
|
// Calculate position for house number
|
|
2377
2362
|
const point = this.svgUtils.pointOnCircle(this.centerX, this.centerY, this.numberRadius, angle);
|
|
@@ -2412,7 +2397,7 @@
|
|
|
2412
2397
|
|
|
2413
2398
|
parentGroup.appendChild(text);
|
|
2414
2399
|
elements.push(text);
|
|
2415
|
-
}
|
|
2400
|
+
}
|
|
2416
2401
|
|
|
2417
2402
|
return elements;
|
|
2418
2403
|
}
|
|
@@ -2765,6 +2750,16 @@
|
|
|
2765
2750
|
*/
|
|
2766
2751
|
|
|
2767
2752
|
class PlanetPositionCalculator {
|
|
2753
|
+
static _normalizeAngle(angle) {
|
|
2754
|
+
return ((angle % 360) + 360) % 360;
|
|
2755
|
+
}
|
|
2756
|
+
|
|
2757
|
+
static _getAngle(position) {
|
|
2758
|
+
return position && position.adjustedLongitude !== undefined
|
|
2759
|
+
? position.adjustedLongitude
|
|
2760
|
+
: position.longitude;
|
|
2761
|
+
}
|
|
2762
|
+
|
|
2768
2763
|
/**
|
|
2769
2764
|
* Calculate position for a planet on a circle
|
|
2770
2765
|
* @param {Object} params - Position parameters
|
|
@@ -2849,7 +2844,8 @@
|
|
|
2849
2844
|
centerX,
|
|
2850
2845
|
centerY,
|
|
2851
2846
|
baseRadius,
|
|
2852
|
-
iconSize = 24
|
|
2847
|
+
iconSize = 24,
|
|
2848
|
+
maxIterations = 5
|
|
2853
2849
|
} = options;
|
|
2854
2850
|
|
|
2855
2851
|
if (!positions || positions.length <= 1) {
|
|
@@ -2863,72 +2859,51 @@
|
|
|
2863
2859
|
|
|
2864
2860
|
console.log(`PlanetPositionCalculator: Adjusting overlaps for ${positions.length} positions`);
|
|
2865
2861
|
|
|
2866
|
-
// Make a copy to not modify originals
|
|
2867
|
-
const adjustedPositions =
|
|
2862
|
+
// Make a copy to not modify originals (also ensures we can add fields safely)
|
|
2863
|
+
const adjustedPositions = positions.map(p => ({ ...p }));
|
|
2868
2864
|
|
|
2869
2865
|
// The minimum angular distance needed to prevent overlap at base radius
|
|
2870
|
-
//
|
|
2871
|
-
const minAngularDistance = (minDistance / baseRadius) * (180 / Math.PI)
|
|
2866
|
+
// minDistance already includes the desired spacing (iconSize * 1.5)
|
|
2867
|
+
const minAngularDistance = (minDistance / baseRadius) * (180 / Math.PI);
|
|
2872
2868
|
console.log(`PlanetPositionCalculator: Minimum angular distance: ${minAngularDistance.toFixed(2)}°`);
|
|
2873
|
-
|
|
2874
|
-
//
|
|
2875
|
-
|
|
2876
|
-
|
|
2877
|
-
|
|
2878
|
-
|
|
2879
|
-
const sortedPositions = sortedPositionIndices.map(item => ({
|
|
2880
|
-
...adjustedPositions[item.idx],
|
|
2881
|
-
originalIndex: item.idx
|
|
2882
|
-
}));
|
|
2883
|
-
|
|
2884
|
-
// Find clusters of planets that are too close angularly
|
|
2885
|
-
const clusters = this._findOverlappingClusters(sortedPositions, minAngularDistance);
|
|
2886
|
-
console.log(`PlanetPositionCalculator: Found ${clusters.length} clusters of overlapping positions`);
|
|
2887
|
-
clusters.forEach((cluster, i) => {
|
|
2888
|
-
console.log(`PlanetPositionCalculator: Cluster ${i+1} has ${cluster.length} positions`);
|
|
2889
|
-
});
|
|
2890
|
-
|
|
2891
|
-
// Process each cluster
|
|
2892
|
-
clusters.forEach((cluster, clusterIndex) => {
|
|
2893
|
-
console.log(`PlanetPositionCalculator: Processing cluster ${clusterIndex+1}`);
|
|
2894
|
-
|
|
2895
|
-
if (cluster.length <= 1) {
|
|
2896
|
-
// Single planet - just place at exact base radius with no angle change
|
|
2897
|
-
const planet = cluster[0];
|
|
2898
|
-
console.log(`PlanetPositionCalculator: Single position in cluster, keeping at original longitude ${planet.longitude.toFixed(2)}°`);
|
|
2899
|
-
this._setExactPosition(planet, planet.longitude, baseRadius, centerX, centerY, iconSize);
|
|
2900
|
-
} else {
|
|
2901
|
-
// Handle cluster with multiple planets - distribute by angle
|
|
2902
|
-
console.log(`PlanetPositionCalculator: Distributing ${cluster.length} positions in cluster`);
|
|
2903
|
-
this._distributeClusterByAngle(cluster, baseRadius, minAngularDistance, centerX, centerY, iconSize);
|
|
2904
|
-
|
|
2905
|
-
// Log the distributions
|
|
2906
|
-
cluster.forEach((pos, i) => {
|
|
2907
|
-
console.log(`PlanetPositionCalculator: Position ${i+1} in cluster ${clusterIndex+1} adjusted from ${pos.longitude.toFixed(2)}° to ${pos.adjustedLongitude.toFixed(2)}°`);
|
|
2908
|
-
});
|
|
2869
|
+
|
|
2870
|
+
// Initialize adjustedLongitude for all positions
|
|
2871
|
+
adjustedPositions.forEach(pos => {
|
|
2872
|
+
if (pos.adjustedLongitude === undefined) {
|
|
2873
|
+
pos.adjustedLongitude = pos.longitude;
|
|
2909
2874
|
}
|
|
2875
|
+
this._setExactPosition(pos, this._getAngle(pos), baseRadius, centerX, centerY, iconSize);
|
|
2910
2876
|
});
|
|
2911
|
-
|
|
2912
|
-
//
|
|
2913
|
-
|
|
2914
|
-
|
|
2915
|
-
|
|
2916
|
-
|
|
2917
|
-
|
|
2918
|
-
|
|
2919
|
-
|
|
2920
|
-
|
|
2921
|
-
|
|
2922
|
-
|
|
2923
|
-
|
|
2924
|
-
|
|
2925
|
-
|
|
2926
|
-
|
|
2927
|
-
adjustedPositions[origIndex].adjustedLongitude = pos.adjustedLongitude;
|
|
2928
|
-
}
|
|
2877
|
+
|
|
2878
|
+
// Iteratively resolve overlaps. This avoids "new overlaps" created after distributing a cluster.
|
|
2879
|
+
for (let iteration = 0; iteration < maxIterations; iteration++) {
|
|
2880
|
+
// Sort by current (possibly adjusted) angle for overlap detection
|
|
2881
|
+
const sortedPositions = [...adjustedPositions].sort(
|
|
2882
|
+
(a, b) => this._getAngle(a) - this._getAngle(b)
|
|
2883
|
+
);
|
|
2884
|
+
|
|
2885
|
+
// Find clusters of planets that are too close angularly
|
|
2886
|
+
const clusters = this._findOverlappingClusters(sortedPositions, minAngularDistance);
|
|
2887
|
+
console.log(`PlanetPositionCalculator: Iteration ${iteration + 1}/${maxIterations}, clusters: ${clusters.length}`);
|
|
2888
|
+
|
|
2889
|
+
// No overlaps -> done
|
|
2890
|
+
const hasRealClusters = clusters.some(c => c.length > 1);
|
|
2891
|
+
if (!hasRealClusters) {
|
|
2892
|
+
break;
|
|
2929
2893
|
}
|
|
2930
|
-
|
|
2931
|
-
|
|
2894
|
+
|
|
2895
|
+
// Process each cluster
|
|
2896
|
+
clusters.forEach(cluster => {
|
|
2897
|
+
if (cluster.length <= 1) {
|
|
2898
|
+
const planet = cluster[0];
|
|
2899
|
+
this._setExactPosition(planet, this._getAngle(planet), baseRadius, centerX, centerY, iconSize);
|
|
2900
|
+
return;
|
|
2901
|
+
}
|
|
2902
|
+
|
|
2903
|
+
this._distributeClusterByPush(cluster, baseRadius, minAngularDistance, centerX, centerY, iconSize);
|
|
2904
|
+
});
|
|
2905
|
+
}
|
|
2906
|
+
|
|
2932
2907
|
return adjustedPositions;
|
|
2933
2908
|
}
|
|
2934
2909
|
|
|
@@ -2958,7 +2933,9 @@
|
|
|
2958
2933
|
const currPosition = sortedPositions[i];
|
|
2959
2934
|
|
|
2960
2935
|
// Check angular distance, considering wrap-around at 360°
|
|
2961
|
-
|
|
2936
|
+
const prevAngle = this._getAngle(prevPosition);
|
|
2937
|
+
const currAngle = this._getAngle(currPosition);
|
|
2938
|
+
let angleDiff = currAngle - prevAngle;
|
|
2962
2939
|
if (angleDiff < 0) angleDiff += 360;
|
|
2963
2940
|
|
|
2964
2941
|
if (angleDiff < minAngularDistance) {
|
|
@@ -2982,7 +2959,9 @@
|
|
|
2982
2959
|
const lastPlanet = sortedPositions[posCount - 1];
|
|
2983
2960
|
const firstPlanetOriginal = sortedPositions[0];
|
|
2984
2961
|
|
|
2985
|
-
|
|
2962
|
+
const lastAngle = this._getAngle(lastPlanet);
|
|
2963
|
+
const firstAngle = this._getAngle(firstPlanetOriginal);
|
|
2964
|
+
let wrapDiff = (firstAngle + 360) - lastAngle;
|
|
2986
2965
|
if (wrapDiff < 0) wrapDiff += 360;
|
|
2987
2966
|
|
|
2988
2967
|
if (wrapDiff < minAngularDistance) {
|
|
@@ -3006,7 +2985,8 @@
|
|
|
3006
2985
|
}
|
|
3007
2986
|
|
|
3008
2987
|
/**
|
|
3009
|
-
* Distribute positions in a cluster
|
|
2988
|
+
* Distribute positions in a cluster using a "push-apart" algorithm.
|
|
2989
|
+
* This keeps adjustments as small as possible while ensuring minimum spacing.
|
|
3010
2990
|
* @private
|
|
3011
2991
|
* @param {Array} positions - Array of positions in the cluster
|
|
3012
2992
|
* @param {number} radius - The exact radius to place all positions
|
|
@@ -3015,49 +2995,62 @@
|
|
|
3015
2995
|
* @param {number} centerY - Y coordinate of center
|
|
3016
2996
|
* @param {number} iconSize - Size of the icon
|
|
3017
2997
|
*/
|
|
3018
|
-
static
|
|
2998
|
+
static _distributeClusterByPush(positions, radius, minAngularDistance, centerX, centerY, iconSize) {
|
|
3019
2999
|
const n = positions.length;
|
|
3020
|
-
|
|
3021
|
-
|
|
3022
|
-
|
|
3023
|
-
this._setExactPosition(positions[0], positions[0].longitude, radius, centerX, centerY, iconSize);
|
|
3000
|
+
if (n <= 1) {
|
|
3001
|
+
const p = positions[0];
|
|
3002
|
+
this._setExactPosition(p, this._getAngle(p), radius, centerX, centerY, iconSize);
|
|
3024
3003
|
return;
|
|
3025
3004
|
}
|
|
3026
|
-
|
|
3027
|
-
//
|
|
3028
|
-
positions.
|
|
3029
|
-
|
|
3030
|
-
|
|
3031
|
-
const
|
|
3032
|
-
|
|
3033
|
-
|
|
3034
|
-
|
|
3035
|
-
//
|
|
3036
|
-
|
|
3037
|
-
|
|
3005
|
+
|
|
3006
|
+
// Unwrap angles if cluster spans the 0°/360° boundary
|
|
3007
|
+
const baseAngles = positions.map(p => this._getAngle(p));
|
|
3008
|
+
const min = Math.min(...baseAngles);
|
|
3009
|
+
const max = Math.max(...baseAngles);
|
|
3010
|
+
const unwrappedAngles = (max - min > 180)
|
|
3011
|
+
? baseAngles.map(a => (a < 180 ? a + 360 : a))
|
|
3012
|
+
: baseAngles;
|
|
3013
|
+
|
|
3014
|
+
// Sort by unwrapped angle
|
|
3015
|
+
const items = positions
|
|
3016
|
+
.map((p, idx) => ({ p, orig: unwrappedAngles[idx] }))
|
|
3017
|
+
.sort((a, b) => a.orig - b.orig);
|
|
3018
|
+
|
|
3019
|
+
const origAngles = items.map(it => it.orig);
|
|
3020
|
+
let adjusted = [...origAngles];
|
|
3021
|
+
|
|
3022
|
+
// Forward pass: enforce minimum distance
|
|
3023
|
+
for (let i = 1; i < n; i++) {
|
|
3024
|
+
const minAllowed = adjusted[i - 1] + minAngularDistance;
|
|
3025
|
+
if (adjusted[i] < minAllowed) {
|
|
3026
|
+
adjusted[i] = minAllowed;
|
|
3027
|
+
}
|
|
3038
3028
|
}
|
|
3039
|
-
|
|
3040
|
-
//
|
|
3041
|
-
|
|
3042
|
-
|
|
3043
|
-
|
|
3029
|
+
|
|
3030
|
+
// Shift the whole cluster to keep it centered around the original mean angle
|
|
3031
|
+
const origCenter = origAngles.reduce((s, a) => s + a, 0) / n;
|
|
3032
|
+
const adjustedCenter = adjusted.reduce((s, a) => s + a, 0) / n;
|
|
3033
|
+
const shift = origCenter - adjustedCenter;
|
|
3034
|
+
adjusted = adjusted.map(a => a + shift);
|
|
3035
|
+
|
|
3036
|
+
// Re-enforce constraints after shifting
|
|
3037
|
+
for (let i = 1; i < n; i++) {
|
|
3038
|
+
const minAllowed = adjusted[i - 1] + minAngularDistance;
|
|
3039
|
+
if (adjusted[i] < minAllowed) {
|
|
3040
|
+
adjusted[i] = minAllowed;
|
|
3041
|
+
}
|
|
3044
3042
|
}
|
|
3045
|
-
let
|
|
3046
|
-
|
|
3047
|
-
|
|
3048
|
-
|
|
3049
|
-
|
|
3050
|
-
|
|
3051
|
-
|
|
3052
|
-
|
|
3053
|
-
|
|
3054
|
-
// Calculate start angle (center - half of span)
|
|
3055
|
-
const startAngle = (centerAngle - spanToUse/2 + 360) % 360;
|
|
3056
|
-
|
|
3057
|
-
// Distribute planets evenly from the start angle
|
|
3043
|
+
for (let i = n - 2; i >= 0; i--) {
|
|
3044
|
+
const maxAllowed = adjusted[i + 1] - minAngularDistance;
|
|
3045
|
+
if (adjusted[i] > maxAllowed) {
|
|
3046
|
+
adjusted[i] = maxAllowed;
|
|
3047
|
+
}
|
|
3048
|
+
}
|
|
3049
|
+
|
|
3050
|
+
// Apply results
|
|
3058
3051
|
for (let i = 0; i < n; i++) {
|
|
3059
|
-
const angle = (
|
|
3060
|
-
this._setExactPosition(
|
|
3052
|
+
const angle = this._normalizeAngle(adjusted[i]);
|
|
3053
|
+
this._setExactPosition(items[i].p, angle, radius, centerX, centerY, iconSize);
|
|
3061
3054
|
}
|
|
3062
3055
|
}
|
|
3063
3056
|
|
|
@@ -3134,7 +3127,8 @@
|
|
|
3134
3127
|
// Define parameters for collision detection and distribution
|
|
3135
3128
|
const iconSize = 24;
|
|
3136
3129
|
const baseRadius = planets[0].iconRadius; // Use the iconRadius from the first planet
|
|
3137
|
-
|
|
3130
|
+
// Minimum distance = icon diameter + half diameter (0.5 * iconSize spacing between icons)
|
|
3131
|
+
const minDistance = iconSize * 1.5;
|
|
3138
3132
|
|
|
3139
3133
|
// Prepare planets array in format expected by PlanetPositionCalculator
|
|
3140
3134
|
const positions = planets.map((planet, index) => ({
|
|
@@ -3256,7 +3250,7 @@
|
|
|
3256
3250
|
this.symbolRenderer.addPlanetTooltip(planetGroup, planet);
|
|
3257
3251
|
|
|
3258
3252
|
// Render connector if needed
|
|
3259
|
-
const connector = this.symbolRenderer.renderConnector(planetGroup, planet, iconSize);
|
|
3253
|
+
const connector = this.symbolRenderer.renderConnector(planetGroup, planet, iconSize, 0.9);
|
|
3260
3254
|
if (connector) {
|
|
3261
3255
|
planetGroup.appendChild(connector);
|
|
3262
3256
|
}
|
|
@@ -3316,7 +3310,8 @@
|
|
|
3316
3310
|
// Define parameters for collision detection and distribution
|
|
3317
3311
|
const iconSize = 18; // Smaller size for secondary planets
|
|
3318
3312
|
const baseRadius = planets[0].iconRadius; // Use the iconRadius from the first planet
|
|
3319
|
-
|
|
3313
|
+
// Minimum distance = icon diameter + half diameter (0.5 * iconSize spacing between icons)
|
|
3314
|
+
const minDistance = iconSize * 1.5;
|
|
3320
3315
|
|
|
3321
3316
|
// Prepare planets array in format expected by PlanetPositionCalculator
|
|
3322
3317
|
const positions = planets.map((planet, index) => ({
|
|
@@ -3437,7 +3432,7 @@
|
|
|
3437
3432
|
this.symbolRenderer.addPlanetTooltip(planetGroup, planet);
|
|
3438
3433
|
|
|
3439
3434
|
// Render connector if needed
|
|
3440
|
-
const connector = this.symbolRenderer.renderConnector(planetGroup, planet, iconSize);
|
|
3435
|
+
const connector = this.symbolRenderer.renderConnector(planetGroup, planet, iconSize, 0.9);
|
|
3441
3436
|
if (connector) {
|
|
3442
3437
|
planetGroup.appendChild(connector);
|
|
3443
3438
|
}
|
|
@@ -5476,8 +5471,13 @@
|
|
|
5476
5471
|
const centerY = this.chart.config.svg.center.y;
|
|
5477
5472
|
const c3Radius = this.chart.config.radius.innermost; // C3
|
|
5478
5473
|
|
|
5479
|
-
// Only draw the innermost circle if secondary planets are enabled
|
|
5480
|
-
|
|
5474
|
+
// Only draw the innermost circle if secondary planets are enabled AND we actually have secondary data
|
|
5475
|
+
const hasSecondaryPlanetsData =
|
|
5476
|
+
this.chart?.secondaryPlanets &&
|
|
5477
|
+
typeof this.chart.secondaryPlanets === 'object' &&
|
|
5478
|
+
Object.keys(this.chart.secondaryPlanets).length > 0;
|
|
5479
|
+
|
|
5480
|
+
if (this.chart.config.planetSettings.secondaryEnabled !== false && hasSecondaryPlanetsData) {
|
|
5481
5481
|
this.drawInnermostCircle(zodiacGroup, this.svgUtils, centerX, centerY, c3Radius);
|
|
5482
5482
|
}
|
|
5483
5483
|
|