@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
|
@@ -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-12-
|
|
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.
|
|
@@ -2750,6 +2750,16 @@
|
|
|
2750
2750
|
*/
|
|
2751
2751
|
|
|
2752
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
|
+
|
|
2753
2763
|
/**
|
|
2754
2764
|
* Calculate position for a planet on a circle
|
|
2755
2765
|
* @param {Object} params - Position parameters
|
|
@@ -2834,7 +2844,8 @@
|
|
|
2834
2844
|
centerX,
|
|
2835
2845
|
centerY,
|
|
2836
2846
|
baseRadius,
|
|
2837
|
-
iconSize = 24
|
|
2847
|
+
iconSize = 24,
|
|
2848
|
+
maxIterations = 5
|
|
2838
2849
|
} = options;
|
|
2839
2850
|
|
|
2840
2851
|
if (!positions || positions.length <= 1) {
|
|
@@ -2848,72 +2859,51 @@
|
|
|
2848
2859
|
|
|
2849
2860
|
console.log(`PlanetPositionCalculator: Adjusting overlaps for ${positions.length} positions`);
|
|
2850
2861
|
|
|
2851
|
-
// Make a copy to not modify originals
|
|
2852
|
-
const adjustedPositions =
|
|
2862
|
+
// Make a copy to not modify originals (also ensures we can add fields safely)
|
|
2863
|
+
const adjustedPositions = positions.map(p => ({ ...p }));
|
|
2853
2864
|
|
|
2854
2865
|
// The minimum angular distance needed to prevent overlap at base radius
|
|
2855
2866
|
// minDistance already includes the desired spacing (iconSize * 1.5)
|
|
2856
2867
|
const minAngularDistance = (minDistance / baseRadius) * (180 / Math.PI);
|
|
2857
2868
|
console.log(`PlanetPositionCalculator: Minimum angular distance: ${minAngularDistance.toFixed(2)}°`);
|
|
2858
|
-
|
|
2859
|
-
//
|
|
2860
|
-
|
|
2861
|
-
|
|
2862
|
-
|
|
2863
|
-
|
|
2864
|
-
const sortedPositions = sortedPositionIndices.map(item => ({
|
|
2865
|
-
...adjustedPositions[item.idx],
|
|
2866
|
-
originalIndex: item.idx
|
|
2867
|
-
}));
|
|
2868
|
-
|
|
2869
|
-
// Find clusters of planets that are too close angularly
|
|
2870
|
-
const clusters = this._findOverlappingClusters(sortedPositions, minAngularDistance);
|
|
2871
|
-
console.log(`PlanetPositionCalculator: Found ${clusters.length} clusters of overlapping positions`);
|
|
2872
|
-
clusters.forEach((cluster, i) => {
|
|
2873
|
-
console.log(`PlanetPositionCalculator: Cluster ${i+1} has ${cluster.length} positions`);
|
|
2874
|
-
});
|
|
2875
|
-
|
|
2876
|
-
// Process each cluster
|
|
2877
|
-
clusters.forEach((cluster, clusterIndex) => {
|
|
2878
|
-
console.log(`PlanetPositionCalculator: Processing cluster ${clusterIndex+1}`);
|
|
2879
|
-
|
|
2880
|
-
if (cluster.length <= 1) {
|
|
2881
|
-
// Single planet - just place at exact base radius with no angle change
|
|
2882
|
-
const planet = cluster[0];
|
|
2883
|
-
console.log(`PlanetPositionCalculator: Single position in cluster, keeping at original longitude ${planet.longitude.toFixed(2)}°`);
|
|
2884
|
-
this._setExactPosition(planet, planet.longitude, baseRadius, centerX, centerY, iconSize);
|
|
2885
|
-
} else {
|
|
2886
|
-
// Handle cluster with multiple planets - distribute by angle
|
|
2887
|
-
console.log(`PlanetPositionCalculator: Distributing ${cluster.length} positions in cluster`);
|
|
2888
|
-
this._distributeClusterByAngle(cluster, baseRadius, minAngularDistance, centerX, centerY, iconSize);
|
|
2889
|
-
|
|
2890
|
-
// Log the distributions
|
|
2891
|
-
cluster.forEach((pos, i) => {
|
|
2892
|
-
console.log(`PlanetPositionCalculator: Position ${i+1} in cluster ${clusterIndex+1} adjusted from ${pos.longitude.toFixed(2)}° to ${pos.adjustedLongitude.toFixed(2)}°`);
|
|
2893
|
-
});
|
|
2869
|
+
|
|
2870
|
+
// Initialize adjustedLongitude for all positions
|
|
2871
|
+
adjustedPositions.forEach(pos => {
|
|
2872
|
+
if (pos.adjustedLongitude === undefined) {
|
|
2873
|
+
pos.adjustedLongitude = pos.longitude;
|
|
2894
2874
|
}
|
|
2875
|
+
this._setExactPosition(pos, this._getAngle(pos), baseRadius, centerX, centerY, iconSize);
|
|
2895
2876
|
});
|
|
2896
|
-
|
|
2897
|
-
//
|
|
2898
|
-
|
|
2899
|
-
|
|
2900
|
-
|
|
2901
|
-
|
|
2902
|
-
|
|
2903
|
-
|
|
2904
|
-
|
|
2905
|
-
|
|
2906
|
-
|
|
2907
|
-
|
|
2908
|
-
|
|
2909
|
-
|
|
2910
|
-
|
|
2911
|
-
|
|
2912
|
-
adjustedPositions[origIndex].adjustedLongitude = pos.adjustedLongitude;
|
|
2913
|
-
}
|
|
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;
|
|
2914
2893
|
}
|
|
2915
|
-
|
|
2916
|
-
|
|
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
|
+
|
|
2917
2907
|
return adjustedPositions;
|
|
2918
2908
|
}
|
|
2919
2909
|
|
|
@@ -2943,7 +2933,9 @@
|
|
|
2943
2933
|
const currPosition = sortedPositions[i];
|
|
2944
2934
|
|
|
2945
2935
|
// Check angular distance, considering wrap-around at 360°
|
|
2946
|
-
|
|
2936
|
+
const prevAngle = this._getAngle(prevPosition);
|
|
2937
|
+
const currAngle = this._getAngle(currPosition);
|
|
2938
|
+
let angleDiff = currAngle - prevAngle;
|
|
2947
2939
|
if (angleDiff < 0) angleDiff += 360;
|
|
2948
2940
|
|
|
2949
2941
|
if (angleDiff < minAngularDistance) {
|
|
@@ -2967,7 +2959,9 @@
|
|
|
2967
2959
|
const lastPlanet = sortedPositions[posCount - 1];
|
|
2968
2960
|
const firstPlanetOriginal = sortedPositions[0];
|
|
2969
2961
|
|
|
2970
|
-
|
|
2962
|
+
const lastAngle = this._getAngle(lastPlanet);
|
|
2963
|
+
const firstAngle = this._getAngle(firstPlanetOriginal);
|
|
2964
|
+
let wrapDiff = (firstAngle + 360) - lastAngle;
|
|
2971
2965
|
if (wrapDiff < 0) wrapDiff += 360;
|
|
2972
2966
|
|
|
2973
2967
|
if (wrapDiff < minAngularDistance) {
|
|
@@ -2991,7 +2985,8 @@
|
|
|
2991
2985
|
}
|
|
2992
2986
|
|
|
2993
2987
|
/**
|
|
2994
|
-
* 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.
|
|
2995
2990
|
* @private
|
|
2996
2991
|
* @param {Array} positions - Array of positions in the cluster
|
|
2997
2992
|
* @param {number} radius - The exact radius to place all positions
|
|
@@ -3000,49 +2995,62 @@
|
|
|
3000
2995
|
* @param {number} centerY - Y coordinate of center
|
|
3001
2996
|
* @param {number} iconSize - Size of the icon
|
|
3002
2997
|
*/
|
|
3003
|
-
static
|
|
2998
|
+
static _distributeClusterByPush(positions, radius, minAngularDistance, centerX, centerY, iconSize) {
|
|
3004
2999
|
const n = positions.length;
|
|
3005
|
-
|
|
3006
|
-
|
|
3007
|
-
|
|
3008
|
-
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);
|
|
3009
3003
|
return;
|
|
3010
3004
|
}
|
|
3011
|
-
|
|
3012
|
-
//
|
|
3013
|
-
positions.
|
|
3014
|
-
|
|
3015
|
-
|
|
3016
|
-
const
|
|
3017
|
-
|
|
3018
|
-
|
|
3019
|
-
|
|
3020
|
-
//
|
|
3021
|
-
|
|
3022
|
-
|
|
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
|
+
}
|
|
3023
3028
|
}
|
|
3024
|
-
|
|
3025
|
-
//
|
|
3026
|
-
|
|
3027
|
-
|
|
3028
|
-
|
|
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
|
+
}
|
|
3029
3042
|
}
|
|
3030
|
-
let
|
|
3031
|
-
|
|
3032
|
-
|
|
3033
|
-
|
|
3034
|
-
|
|
3035
|
-
|
|
3036
|
-
|
|
3037
|
-
|
|
3038
|
-
|
|
3039
|
-
// Calculate start angle (center - half of span)
|
|
3040
|
-
const startAngle = (centerAngle - spanToUse/2 + 360) % 360;
|
|
3041
|
-
|
|
3042
|
-
// 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
|
|
3043
3051
|
for (let i = 0; i < n; i++) {
|
|
3044
|
-
const angle = (
|
|
3045
|
-
this._setExactPosition(
|
|
3052
|
+
const angle = this._normalizeAngle(adjusted[i]);
|
|
3053
|
+
this._setExactPosition(items[i].p, angle, radius, centerX, centerY, iconSize);
|
|
3046
3054
|
}
|
|
3047
3055
|
}
|
|
3048
3056
|
|
|
@@ -3242,7 +3250,7 @@
|
|
|
3242
3250
|
this.symbolRenderer.addPlanetTooltip(planetGroup, planet);
|
|
3243
3251
|
|
|
3244
3252
|
// Render connector if needed
|
|
3245
|
-
const connector = this.symbolRenderer.renderConnector(planetGroup, planet, iconSize);
|
|
3253
|
+
const connector = this.symbolRenderer.renderConnector(planetGroup, planet, iconSize, 0.9);
|
|
3246
3254
|
if (connector) {
|
|
3247
3255
|
planetGroup.appendChild(connector);
|
|
3248
3256
|
}
|
|
@@ -3424,7 +3432,7 @@
|
|
|
3424
3432
|
this.symbolRenderer.addPlanetTooltip(planetGroup, planet);
|
|
3425
3433
|
|
|
3426
3434
|
// Render connector if needed
|
|
3427
|
-
const connector = this.symbolRenderer.renderConnector(planetGroup, planet, iconSize);
|
|
3435
|
+
const connector = this.symbolRenderer.renderConnector(planetGroup, planet, iconSize, 0.9);
|
|
3428
3436
|
if (connector) {
|
|
3429
3437
|
planetGroup.appendChild(connector);
|
|
3430
3438
|
}
|
|
@@ -5463,8 +5471,13 @@
|
|
|
5463
5471
|
const centerY = this.chart.config.svg.center.y;
|
|
5464
5472
|
const c3Radius = this.chart.config.radius.innermost; // C3
|
|
5465
5473
|
|
|
5466
|
-
// Only draw the innermost circle if secondary planets are enabled
|
|
5467
|
-
|
|
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) {
|
|
5468
5481
|
this.drawInnermostCircle(zodiacGroup, this.svgUtils, centerX, centerY, c3Radius);
|
|
5469
5482
|
}
|
|
5470
5483
|
|