@eaprelsky/nocturna-wheel 4.0.2 → 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 +117 -104
- package/dist/nocturna-wheel.bundle.js.map +1 -1
- package/dist/nocturna-wheel.es.js +117 -104
- 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 +117 -104
- package/dist/nocturna-wheel.umd.js.map +1 -1
- package/package.json +1 -1
|
@@ -287,7 +287,7 @@ class IconProvider {
|
|
|
287
287
|
/**
|
|
288
288
|
* IconData.js
|
|
289
289
|
* Auto-generated module containing inline SVG icons as data URLs
|
|
290
|
-
* Generated at: 2025-12-
|
|
290
|
+
* Generated at: 2025-12-21T15:10:29.083Z
|
|
291
291
|
*
|
|
292
292
|
* This file is automatically generated by the build process.
|
|
293
293
|
* Do not edit manually - changes will be overwritten.
|
|
@@ -2744,6 +2744,16 @@ class PlanetSymbolRenderer extends BasePlanetRenderer {
|
|
|
2744
2744
|
*/
|
|
2745
2745
|
|
|
2746
2746
|
class PlanetPositionCalculator {
|
|
2747
|
+
static _normalizeAngle(angle) {
|
|
2748
|
+
return ((angle % 360) + 360) % 360;
|
|
2749
|
+
}
|
|
2750
|
+
|
|
2751
|
+
static _getAngle(position) {
|
|
2752
|
+
return position && position.adjustedLongitude !== undefined
|
|
2753
|
+
? position.adjustedLongitude
|
|
2754
|
+
: position.longitude;
|
|
2755
|
+
}
|
|
2756
|
+
|
|
2747
2757
|
/**
|
|
2748
2758
|
* Calculate position for a planet on a circle
|
|
2749
2759
|
* @param {Object} params - Position parameters
|
|
@@ -2828,7 +2838,8 @@ class PlanetPositionCalculator {
|
|
|
2828
2838
|
centerX,
|
|
2829
2839
|
centerY,
|
|
2830
2840
|
baseRadius,
|
|
2831
|
-
iconSize = 24
|
|
2841
|
+
iconSize = 24,
|
|
2842
|
+
maxIterations = 5
|
|
2832
2843
|
} = options;
|
|
2833
2844
|
|
|
2834
2845
|
if (!positions || positions.length <= 1) {
|
|
@@ -2842,72 +2853,51 @@ class PlanetPositionCalculator {
|
|
|
2842
2853
|
|
|
2843
2854
|
console.log(`PlanetPositionCalculator: Adjusting overlaps for ${positions.length} positions`);
|
|
2844
2855
|
|
|
2845
|
-
// Make a copy to not modify originals
|
|
2846
|
-
const adjustedPositions =
|
|
2856
|
+
// Make a copy to not modify originals (also ensures we can add fields safely)
|
|
2857
|
+
const adjustedPositions = positions.map(p => ({ ...p }));
|
|
2847
2858
|
|
|
2848
2859
|
// The minimum angular distance needed to prevent overlap at base radius
|
|
2849
2860
|
// minDistance already includes the desired spacing (iconSize * 1.5)
|
|
2850
2861
|
const minAngularDistance = (minDistance / baseRadius) * (180 / Math.PI);
|
|
2851
2862
|
console.log(`PlanetPositionCalculator: Minimum angular distance: ${minAngularDistance.toFixed(2)}°`);
|
|
2852
|
-
|
|
2853
|
-
//
|
|
2854
|
-
|
|
2855
|
-
|
|
2856
|
-
|
|
2857
|
-
|
|
2858
|
-
const sortedPositions = sortedPositionIndices.map(item => ({
|
|
2859
|
-
...adjustedPositions[item.idx],
|
|
2860
|
-
originalIndex: item.idx
|
|
2861
|
-
}));
|
|
2862
|
-
|
|
2863
|
-
// Find clusters of planets that are too close angularly
|
|
2864
|
-
const clusters = this._findOverlappingClusters(sortedPositions, minAngularDistance);
|
|
2865
|
-
console.log(`PlanetPositionCalculator: Found ${clusters.length} clusters of overlapping positions`);
|
|
2866
|
-
clusters.forEach((cluster, i) => {
|
|
2867
|
-
console.log(`PlanetPositionCalculator: Cluster ${i+1} has ${cluster.length} positions`);
|
|
2868
|
-
});
|
|
2869
|
-
|
|
2870
|
-
// Process each cluster
|
|
2871
|
-
clusters.forEach((cluster, clusterIndex) => {
|
|
2872
|
-
console.log(`PlanetPositionCalculator: Processing cluster ${clusterIndex+1}`);
|
|
2873
|
-
|
|
2874
|
-
if (cluster.length <= 1) {
|
|
2875
|
-
// Single planet - just place at exact base radius with no angle change
|
|
2876
|
-
const planet = cluster[0];
|
|
2877
|
-
console.log(`PlanetPositionCalculator: Single position in cluster, keeping at original longitude ${planet.longitude.toFixed(2)}°`);
|
|
2878
|
-
this._setExactPosition(planet, planet.longitude, baseRadius, centerX, centerY, iconSize);
|
|
2879
|
-
} else {
|
|
2880
|
-
// Handle cluster with multiple planets - distribute by angle
|
|
2881
|
-
console.log(`PlanetPositionCalculator: Distributing ${cluster.length} positions in cluster`);
|
|
2882
|
-
this._distributeClusterByAngle(cluster, baseRadius, minAngularDistance, centerX, centerY, iconSize);
|
|
2883
|
-
|
|
2884
|
-
// Log the distributions
|
|
2885
|
-
cluster.forEach((pos, i) => {
|
|
2886
|
-
console.log(`PlanetPositionCalculator: Position ${i+1} in cluster ${clusterIndex+1} adjusted from ${pos.longitude.toFixed(2)}° to ${pos.adjustedLongitude.toFixed(2)}°`);
|
|
2887
|
-
});
|
|
2863
|
+
|
|
2864
|
+
// Initialize adjustedLongitude for all positions
|
|
2865
|
+
adjustedPositions.forEach(pos => {
|
|
2866
|
+
if (pos.adjustedLongitude === undefined) {
|
|
2867
|
+
pos.adjustedLongitude = pos.longitude;
|
|
2888
2868
|
}
|
|
2869
|
+
this._setExactPosition(pos, this._getAngle(pos), baseRadius, centerX, centerY, iconSize);
|
|
2889
2870
|
});
|
|
2890
|
-
|
|
2891
|
-
//
|
|
2892
|
-
|
|
2893
|
-
|
|
2894
|
-
|
|
2895
|
-
|
|
2896
|
-
|
|
2897
|
-
|
|
2898
|
-
|
|
2899
|
-
|
|
2900
|
-
|
|
2901
|
-
|
|
2902
|
-
|
|
2903
|
-
|
|
2904
|
-
|
|
2905
|
-
|
|
2906
|
-
adjustedPositions[origIndex].adjustedLongitude = pos.adjustedLongitude;
|
|
2907
|
-
}
|
|
2871
|
+
|
|
2872
|
+
// Iteratively resolve overlaps. This avoids "new overlaps" created after distributing a cluster.
|
|
2873
|
+
for (let iteration = 0; iteration < maxIterations; iteration++) {
|
|
2874
|
+
// Sort by current (possibly adjusted) angle for overlap detection
|
|
2875
|
+
const sortedPositions = [...adjustedPositions].sort(
|
|
2876
|
+
(a, b) => this._getAngle(a) - this._getAngle(b)
|
|
2877
|
+
);
|
|
2878
|
+
|
|
2879
|
+
// Find clusters of planets that are too close angularly
|
|
2880
|
+
const clusters = this._findOverlappingClusters(sortedPositions, minAngularDistance);
|
|
2881
|
+
console.log(`PlanetPositionCalculator: Iteration ${iteration + 1}/${maxIterations}, clusters: ${clusters.length}`);
|
|
2882
|
+
|
|
2883
|
+
// No overlaps -> done
|
|
2884
|
+
const hasRealClusters = clusters.some(c => c.length > 1);
|
|
2885
|
+
if (!hasRealClusters) {
|
|
2886
|
+
break;
|
|
2908
2887
|
}
|
|
2909
|
-
|
|
2910
|
-
|
|
2888
|
+
|
|
2889
|
+
// Process each cluster
|
|
2890
|
+
clusters.forEach(cluster => {
|
|
2891
|
+
if (cluster.length <= 1) {
|
|
2892
|
+
const planet = cluster[0];
|
|
2893
|
+
this._setExactPosition(planet, this._getAngle(planet), baseRadius, centerX, centerY, iconSize);
|
|
2894
|
+
return;
|
|
2895
|
+
}
|
|
2896
|
+
|
|
2897
|
+
this._distributeClusterByPush(cluster, baseRadius, minAngularDistance, centerX, centerY, iconSize);
|
|
2898
|
+
});
|
|
2899
|
+
}
|
|
2900
|
+
|
|
2911
2901
|
return adjustedPositions;
|
|
2912
2902
|
}
|
|
2913
2903
|
|
|
@@ -2937,7 +2927,9 @@ class PlanetPositionCalculator {
|
|
|
2937
2927
|
const currPosition = sortedPositions[i];
|
|
2938
2928
|
|
|
2939
2929
|
// Check angular distance, considering wrap-around at 360°
|
|
2940
|
-
|
|
2930
|
+
const prevAngle = this._getAngle(prevPosition);
|
|
2931
|
+
const currAngle = this._getAngle(currPosition);
|
|
2932
|
+
let angleDiff = currAngle - prevAngle;
|
|
2941
2933
|
if (angleDiff < 0) angleDiff += 360;
|
|
2942
2934
|
|
|
2943
2935
|
if (angleDiff < minAngularDistance) {
|
|
@@ -2961,7 +2953,9 @@ class PlanetPositionCalculator {
|
|
|
2961
2953
|
const lastPlanet = sortedPositions[posCount - 1];
|
|
2962
2954
|
const firstPlanetOriginal = sortedPositions[0];
|
|
2963
2955
|
|
|
2964
|
-
|
|
2956
|
+
const lastAngle = this._getAngle(lastPlanet);
|
|
2957
|
+
const firstAngle = this._getAngle(firstPlanetOriginal);
|
|
2958
|
+
let wrapDiff = (firstAngle + 360) - lastAngle;
|
|
2965
2959
|
if (wrapDiff < 0) wrapDiff += 360;
|
|
2966
2960
|
|
|
2967
2961
|
if (wrapDiff < minAngularDistance) {
|
|
@@ -2985,7 +2979,8 @@ class PlanetPositionCalculator {
|
|
|
2985
2979
|
}
|
|
2986
2980
|
|
|
2987
2981
|
/**
|
|
2988
|
-
* Distribute positions in a cluster
|
|
2982
|
+
* Distribute positions in a cluster using a "push-apart" algorithm.
|
|
2983
|
+
* This keeps adjustments as small as possible while ensuring minimum spacing.
|
|
2989
2984
|
* @private
|
|
2990
2985
|
* @param {Array} positions - Array of positions in the cluster
|
|
2991
2986
|
* @param {number} radius - The exact radius to place all positions
|
|
@@ -2994,49 +2989,62 @@ class PlanetPositionCalculator {
|
|
|
2994
2989
|
* @param {number} centerY - Y coordinate of center
|
|
2995
2990
|
* @param {number} iconSize - Size of the icon
|
|
2996
2991
|
*/
|
|
2997
|
-
static
|
|
2992
|
+
static _distributeClusterByPush(positions, radius, minAngularDistance, centerX, centerY, iconSize) {
|
|
2998
2993
|
const n = positions.length;
|
|
2999
|
-
|
|
3000
|
-
|
|
3001
|
-
|
|
3002
|
-
this._setExactPosition(positions[0], positions[0].longitude, radius, centerX, centerY, iconSize);
|
|
2994
|
+
if (n <= 1) {
|
|
2995
|
+
const p = positions[0];
|
|
2996
|
+
this._setExactPosition(p, this._getAngle(p), radius, centerX, centerY, iconSize);
|
|
3003
2997
|
return;
|
|
3004
2998
|
}
|
|
3005
|
-
|
|
3006
|
-
//
|
|
3007
|
-
positions.
|
|
3008
|
-
|
|
3009
|
-
|
|
3010
|
-
const
|
|
3011
|
-
|
|
3012
|
-
|
|
3013
|
-
|
|
3014
|
-
//
|
|
3015
|
-
|
|
3016
|
-
|
|
2999
|
+
|
|
3000
|
+
// Unwrap angles if cluster spans the 0°/360° boundary
|
|
3001
|
+
const baseAngles = positions.map(p => this._getAngle(p));
|
|
3002
|
+
const min = Math.min(...baseAngles);
|
|
3003
|
+
const max = Math.max(...baseAngles);
|
|
3004
|
+
const unwrappedAngles = (max - min > 180)
|
|
3005
|
+
? baseAngles.map(a => (a < 180 ? a + 360 : a))
|
|
3006
|
+
: baseAngles;
|
|
3007
|
+
|
|
3008
|
+
// Sort by unwrapped angle
|
|
3009
|
+
const items = positions
|
|
3010
|
+
.map((p, idx) => ({ p, orig: unwrappedAngles[idx] }))
|
|
3011
|
+
.sort((a, b) => a.orig - b.orig);
|
|
3012
|
+
|
|
3013
|
+
const origAngles = items.map(it => it.orig);
|
|
3014
|
+
let adjusted = [...origAngles];
|
|
3015
|
+
|
|
3016
|
+
// Forward pass: enforce minimum distance
|
|
3017
|
+
for (let i = 1; i < n; i++) {
|
|
3018
|
+
const minAllowed = adjusted[i - 1] + minAngularDistance;
|
|
3019
|
+
if (adjusted[i] < minAllowed) {
|
|
3020
|
+
adjusted[i] = minAllowed;
|
|
3021
|
+
}
|
|
3017
3022
|
}
|
|
3018
|
-
|
|
3019
|
-
//
|
|
3020
|
-
|
|
3021
|
-
|
|
3022
|
-
|
|
3023
|
+
|
|
3024
|
+
// Shift the whole cluster to keep it centered around the original mean angle
|
|
3025
|
+
const origCenter = origAngles.reduce((s, a) => s + a, 0) / n;
|
|
3026
|
+
const adjustedCenter = adjusted.reduce((s, a) => s + a, 0) / n;
|
|
3027
|
+
const shift = origCenter - adjustedCenter;
|
|
3028
|
+
adjusted = adjusted.map(a => a + shift);
|
|
3029
|
+
|
|
3030
|
+
// Re-enforce constraints after shifting
|
|
3031
|
+
for (let i = 1; i < n; i++) {
|
|
3032
|
+
const minAllowed = adjusted[i - 1] + minAngularDistance;
|
|
3033
|
+
if (adjusted[i] < minAllowed) {
|
|
3034
|
+
adjusted[i] = minAllowed;
|
|
3035
|
+
}
|
|
3023
3036
|
}
|
|
3024
|
-
let
|
|
3025
|
-
|
|
3026
|
-
|
|
3027
|
-
|
|
3028
|
-
|
|
3029
|
-
|
|
3030
|
-
|
|
3031
|
-
|
|
3032
|
-
|
|
3033
|
-
// Calculate start angle (center - half of span)
|
|
3034
|
-
const startAngle = (centerAngle - spanToUse/2 + 360) % 360;
|
|
3035
|
-
|
|
3036
|
-
// Distribute planets evenly from the start angle
|
|
3037
|
+
for (let i = n - 2; i >= 0; i--) {
|
|
3038
|
+
const maxAllowed = adjusted[i + 1] - minAngularDistance;
|
|
3039
|
+
if (adjusted[i] > maxAllowed) {
|
|
3040
|
+
adjusted[i] = maxAllowed;
|
|
3041
|
+
}
|
|
3042
|
+
}
|
|
3043
|
+
|
|
3044
|
+
// Apply results
|
|
3037
3045
|
for (let i = 0; i < n; i++) {
|
|
3038
|
-
const angle = (
|
|
3039
|
-
this._setExactPosition(
|
|
3046
|
+
const angle = this._normalizeAngle(adjusted[i]);
|
|
3047
|
+
this._setExactPosition(items[i].p, angle, radius, centerX, centerY, iconSize);
|
|
3040
3048
|
}
|
|
3041
3049
|
}
|
|
3042
3050
|
|
|
@@ -3236,7 +3244,7 @@ class PrimaryPlanetRenderer extends BasePlanetRenderer {
|
|
|
3236
3244
|
this.symbolRenderer.addPlanetTooltip(planetGroup, planet);
|
|
3237
3245
|
|
|
3238
3246
|
// Render connector if needed
|
|
3239
|
-
const connector = this.symbolRenderer.renderConnector(planetGroup, planet, iconSize);
|
|
3247
|
+
const connector = this.symbolRenderer.renderConnector(planetGroup, planet, iconSize, 0.9);
|
|
3240
3248
|
if (connector) {
|
|
3241
3249
|
planetGroup.appendChild(connector);
|
|
3242
3250
|
}
|
|
@@ -3418,7 +3426,7 @@ class SecondaryPlanetRenderer extends BasePlanetRenderer {
|
|
|
3418
3426
|
this.symbolRenderer.addPlanetTooltip(planetGroup, planet);
|
|
3419
3427
|
|
|
3420
3428
|
// Render connector if needed
|
|
3421
|
-
const connector = this.symbolRenderer.renderConnector(planetGroup, planet, iconSize);
|
|
3429
|
+
const connector = this.symbolRenderer.renderConnector(planetGroup, planet, iconSize, 0.9);
|
|
3422
3430
|
if (connector) {
|
|
3423
3431
|
planetGroup.appendChild(connector);
|
|
3424
3432
|
}
|
|
@@ -5457,8 +5465,13 @@ class ChartRenderer {
|
|
|
5457
5465
|
const centerY = this.chart.config.svg.center.y;
|
|
5458
5466
|
const c3Radius = this.chart.config.radius.innermost; // C3
|
|
5459
5467
|
|
|
5460
|
-
// Only draw the innermost circle if secondary planets are enabled
|
|
5461
|
-
|
|
5468
|
+
// Only draw the innermost circle if secondary planets are enabled AND we actually have secondary data
|
|
5469
|
+
const hasSecondaryPlanetsData =
|
|
5470
|
+
this.chart?.secondaryPlanets &&
|
|
5471
|
+
typeof this.chart.secondaryPlanets === 'object' &&
|
|
5472
|
+
Object.keys(this.chart.secondaryPlanets).length > 0;
|
|
5473
|
+
|
|
5474
|
+
if (this.chart.config.planetSettings.secondaryEnabled !== false && hasSecondaryPlanetsData) {
|
|
5462
5475
|
this.drawInnermostCircle(zodiacGroup, this.svgUtils, centerX, centerY, c3Radius);
|
|
5463
5476
|
}
|
|
5464
5477
|
|