@libs-ui/utils 0.2.282 → 0.2.283
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/esm2022/get-smart-axis-scale.mjs +143 -53
- package/esm2022/helpers.mjs +65 -1
- package/fesm2022/libs-ui-utils.mjs +206 -52
- package/fesm2022/libs-ui-utils.mjs.map +1 -1
- package/get-smart-axis-scale.d.ts +22 -14
- package/helpers.d.ts +64 -0
- package/package.json +2 -2
|
@@ -645,6 +645,70 @@ const omitBy = (objData, predicate) => {
|
|
|
645
645
|
});
|
|
646
646
|
return newObj;
|
|
647
647
|
};
|
|
648
|
+
/**
|
|
649
|
+
* Lấy giá trị từ đối tượng theo đường dẫn chỉ định
|
|
650
|
+
*
|
|
651
|
+
* Hàm này giúp bạn truy cập vào các thuộc tính sâu bên trong một đối tượng một cách an toàn,
|
|
652
|
+
* tránh lỗi khi thuộc tính không tồn tại.
|
|
653
|
+
*
|
|
654
|
+
* @param obj Đối tượng nguồn cần lấy giá trị
|
|
655
|
+
* @param path Đường dẫn đến thuộc tính cần lấy. Có thể là:
|
|
656
|
+
* - Chuỗi: 'user.profile.name' hoặc 'items[0].title'
|
|
657
|
+
* - Mảng: ['user', 'profile', 'name'] hoặc ['items', '0', 'title']
|
|
658
|
+
* - Chuỗi rỗng '': trả về chính đối tượng gốc
|
|
659
|
+
* @param defaultValue Giá trị mặc định trả về khi không tìm thấy thuộc tính (mặc định: undefined)
|
|
660
|
+
* @param keepLastValueIfSignal Có giữ nguyên signal cuối cùng hay không (mặc định: false - sẽ gọi signal())
|
|
661
|
+
* @returns Giá trị tìm được hoặc giá trị mặc định
|
|
662
|
+
*
|
|
663
|
+
* @example
|
|
664
|
+
* // Ví dụ cơ bản
|
|
665
|
+
* const user = { name: 'John', age: 30 };
|
|
666
|
+
* get(user, 'name'); // 'John'
|
|
667
|
+
* get(user, 'email'); // undefined
|
|
668
|
+
* get(user, 'email', 'no-email'); // 'no-email'
|
|
669
|
+
*
|
|
670
|
+
* @example
|
|
671
|
+
* // Truyền path rỗng - trả về chính đối tượng gốc
|
|
672
|
+
* const data = { name: 'Alice', age: 25 };
|
|
673
|
+
* get(data, ''); // { name: 'Alice', age: 25 } (chính đối tượng data)
|
|
674
|
+
* get(data, '', 'default'); // { name: 'Alice', age: 25 } (bỏ qua defaultValue)
|
|
675
|
+
*
|
|
676
|
+
* @example
|
|
677
|
+
* // Truy cập thuộc tính sâu
|
|
678
|
+
* const data = {
|
|
679
|
+
* user: {
|
|
680
|
+
* profile: {
|
|
681
|
+
* name: 'Alice',
|
|
682
|
+
* settings: { theme: 'dark' }
|
|
683
|
+
* }
|
|
684
|
+
* }
|
|
685
|
+
* };
|
|
686
|
+
* get(data, 'user.profile.name'); // 'Alice'
|
|
687
|
+
* get(data, 'user.profile.settings.theme'); // 'dark'
|
|
688
|
+
* get(data, 'user.profile.avatar', 'default.jpg'); // 'default.jpg'
|
|
689
|
+
*
|
|
690
|
+
* @example
|
|
691
|
+
* // Truy cập mảng
|
|
692
|
+
* const items = [
|
|
693
|
+
* { name: 'Item 1', price: 100 },
|
|
694
|
+
* { name: 'Item 2', price: 200 }
|
|
695
|
+
* ];
|
|
696
|
+
* get(items, '[0].name'); // 'Item 1'
|
|
697
|
+
* get(items, '0.price'); // 100
|
|
698
|
+
* get(items, '[1].name'); // 'Item 2'
|
|
699
|
+
* get(items, '[2].name', 'Not found'); // 'Not found'
|
|
700
|
+
*
|
|
701
|
+
* @example
|
|
702
|
+
* // Sử dụng với mảng path
|
|
703
|
+
* const nested = { a: { b: { c: 'deep value' } } };
|
|
704
|
+
* get(nested, ['a', 'b', 'c']); // 'deep value'
|
|
705
|
+
* get(nested, ['a', 'b', 'd'], 'default'); // 'default'
|
|
706
|
+
*
|
|
707
|
+
* @example
|
|
708
|
+
* // Trường hợp đặc biệt
|
|
709
|
+
* get(null, 'any.path'); // undefined
|
|
710
|
+
* get(undefined, 'any.path', 'fallback'); // 'fallback'
|
|
711
|
+
*/
|
|
648
712
|
const get = (obj, path, defaultValue = undefined, keepLastValueIfSignal) => {
|
|
649
713
|
if (isNil(obj)) {
|
|
650
714
|
return defaultValue;
|
|
@@ -2447,32 +2511,57 @@ const convertUrlToFile = (url, fileName) => {
|
|
|
2447
2511
|
});
|
|
2448
2512
|
};
|
|
2449
2513
|
|
|
2514
|
+
/* eslint-disable @typescript-eslint/no-non-null-assertion */
|
|
2515
|
+
// Constants for better maintainability
|
|
2516
|
+
const DEFAULT_MIN_TICK_COUNT = 5;
|
|
2517
|
+
const DEFAULT_MAX_TICK_COUNT = 10;
|
|
2518
|
+
const MIN_DISTANCE_TO_ZERO = 3;
|
|
2519
|
+
const NEGATIVE_THRESHOLD = -5;
|
|
2520
|
+
const FALLBACK_NEGATIVE_VALUE = -4;
|
|
2521
|
+
const MAX_POW_NUMBER = 14;
|
|
2522
|
+
const MAX_TEMPLATE_NUMBER = 10;
|
|
2523
|
+
// Cache for power calculations - shared across function calls for better performance
|
|
2524
|
+
const POWER_CACHE = new Map();
|
|
2450
2525
|
/**
|
|
2451
|
-
*
|
|
2452
|
-
*
|
|
2453
|
-
*
|
|
2454
|
-
* - maxData <
|
|
2455
|
-
* - minNegative
|
|
2456
|
-
*
|
|
2457
|
-
*
|
|
2458
|
-
*
|
|
2526
|
+
* Calculates smart axis scale for charts
|
|
2527
|
+
*
|
|
2528
|
+
* @description This function can throw errors if:
|
|
2529
|
+
* - maxData < 0 and acceptNegative is false
|
|
2530
|
+
* - minNegative is undefined and acceptNegative is true
|
|
2531
|
+
* - maxData < minNegative (only when acceptNegative is true)
|
|
2532
|
+
* - minNegative > 0 (only when acceptNegative is true)
|
|
2533
|
+
*
|
|
2534
|
+
* @throws {Error} Validation errors for invalid input combinations
|
|
2535
|
+
* @required Always wrap this function in try-catch and handle exceptions
|
|
2536
|
+
* @param maxData - Maximum data value
|
|
2537
|
+
* @param options - Configuration options for axis scaling
|
|
2538
|
+
* @returns Axis scale configuration
|
|
2459
2539
|
*/
|
|
2460
2540
|
const getSmartAxisScale = (maxData, options) => {
|
|
2461
|
-
|
|
2462
|
-
|
|
2463
|
-
|
|
2464
|
-
const
|
|
2465
|
-
|
|
2466
|
-
|
|
2467
|
-
|
|
2468
|
-
|
|
2469
|
-
|
|
2470
|
-
|
|
2471
|
-
|
|
2472
|
-
|
|
2473
|
-
|
|
2474
|
-
|
|
2475
|
-
|
|
2541
|
+
// Input validation - fail fast approach
|
|
2542
|
+
validateInputs(maxData, options);
|
|
2543
|
+
// Extract and set defaults
|
|
2544
|
+
const { minTickCount = DEFAULT_MIN_TICK_COUNT, maxTickCount = DEFAULT_MAX_TICK_COUNT, stepCandidates = [], acceptNegative = false, minNegative, acceptStepIsTypeFloat = false } = options || {};
|
|
2545
|
+
// Generate step candidates if not provided
|
|
2546
|
+
const finalStepCandidates = stepCandidates.length > 0
|
|
2547
|
+
? [...stepCandidates]
|
|
2548
|
+
: generateStepCandidates(maxData, minTickCount, minNegative, acceptStepIsTypeFloat);
|
|
2549
|
+
// Remove console.log for production code
|
|
2550
|
+
// console.log(`stepCandidates: `, finalStepCandidates);
|
|
2551
|
+
// Calculate scale parameters
|
|
2552
|
+
const scaleParams = calculateScaleParams(maxData, acceptNegative, minNegative, finalStepCandidates);
|
|
2553
|
+
// Find optimal scale
|
|
2554
|
+
const optimalScale = findOptimalScale(scaleParams, finalStepCandidates, minTickCount, maxTickCount, options);
|
|
2555
|
+
if (optimalScale) {
|
|
2556
|
+
return optimalScale;
|
|
2557
|
+
}
|
|
2558
|
+
// Fallback calculation
|
|
2559
|
+
return calculateFallbackScale(scaleParams.distanceToZero, minTickCount);
|
|
2560
|
+
};
|
|
2561
|
+
/**
|
|
2562
|
+
* Validates input parameters
|
|
2563
|
+
*/
|
|
2564
|
+
function validateInputs(maxData, options) {
|
|
2476
2565
|
if (maxData < 0 && !options?.acceptNegative) {
|
|
2477
2566
|
throw new Error("maxData is less than 0 and acceptNegative is false");
|
|
2478
2567
|
}
|
|
@@ -2486,48 +2575,86 @@ const getSmartAxisScale = (maxData, options) => {
|
|
|
2486
2575
|
if (options.minNegative >= 0) {
|
|
2487
2576
|
throw new Error("minNegative must be negative");
|
|
2488
2577
|
}
|
|
2578
|
+
}
|
|
2579
|
+
}
|
|
2580
|
+
/**
|
|
2581
|
+
* Calculates scale parameters for axis calculation
|
|
2582
|
+
*/
|
|
2583
|
+
function calculateScaleParams(maxData, acceptNegative, minNegative, stepCandidates) {
|
|
2584
|
+
let distanceToZero = maxData;
|
|
2585
|
+
let reverse = 1;
|
|
2586
|
+
let adjustedMaxData = maxData;
|
|
2587
|
+
if (acceptNegative && !isNil(minNegative)) {
|
|
2489
2588
|
if (maxData === 0) {
|
|
2490
|
-
|
|
2589
|
+
adjustedMaxData = -1;
|
|
2590
|
+
}
|
|
2591
|
+
// Add negative step candidates
|
|
2592
|
+
if (stepCandidates) {
|
|
2593
|
+
stepCandidates.unshift(...stepCandidates.map(item => -item));
|
|
2491
2594
|
}
|
|
2492
|
-
|
|
2493
|
-
|
|
2494
|
-
distanceToZero = options.minNegative;
|
|
2595
|
+
if (adjustedMaxData <= 0) {
|
|
2596
|
+
distanceToZero = minNegative;
|
|
2495
2597
|
}
|
|
2496
|
-
if (distanceToZero >
|
|
2497
|
-
distanceToZero =
|
|
2598
|
+
if (distanceToZero > NEGATIVE_THRESHOLD) {
|
|
2599
|
+
distanceToZero = FALLBACK_NEGATIVE_VALUE;
|
|
2498
2600
|
}
|
|
2499
|
-
if (
|
|
2500
|
-
distanceToZero =
|
|
2601
|
+
if (adjustedMaxData > 0) {
|
|
2602
|
+
distanceToZero = adjustedMaxData + Math.abs(minNegative);
|
|
2501
2603
|
reverse = -1;
|
|
2502
2604
|
}
|
|
2503
2605
|
}
|
|
2504
|
-
if (Math.abs(distanceToZero) <
|
|
2505
|
-
distanceToZero =
|
|
2606
|
+
if (Math.abs(distanceToZero) < MIN_DISTANCE_TO_ZERO) {
|
|
2607
|
+
distanceToZero = MIN_DISTANCE_TO_ZERO;
|
|
2506
2608
|
}
|
|
2609
|
+
return { distanceToZero, reverse, adjustedMaxData };
|
|
2610
|
+
}
|
|
2611
|
+
/**
|
|
2612
|
+
* Finds optimal scale from step candidates
|
|
2613
|
+
*/
|
|
2614
|
+
function findOptimalScale(scaleParams, stepCandidates, minTickCount, maxTickCount, options) {
|
|
2615
|
+
const { distanceToZero, reverse, adjustedMaxData } = scaleParams;
|
|
2507
2616
|
for (const step of stepCandidates) {
|
|
2508
2617
|
let tickCount = Math.abs(Math.ceil(distanceToZero / step)) + (reverse === -1 ? 2 : 1);
|
|
2509
|
-
let maxValue =
|
|
2618
|
+
let maxValue = adjustedMaxData <= 0 ? 0 : step * tickCount * reverse;
|
|
2510
2619
|
let minValue = 0;
|
|
2511
|
-
if (tickCount >= minTickCount && tickCount <= maxTickCount) {
|
|
2512
|
-
if (
|
|
2513
|
-
|
|
2514
|
-
let tick = 1;
|
|
2515
|
-
while (!isNil(options.minNegative) && tick <= tickCount && minValue >= options.minNegative) {
|
|
2516
|
-
minValue = tick * step;
|
|
2517
|
-
tick++;
|
|
2518
|
-
}
|
|
2519
|
-
}
|
|
2520
|
-
tickCount = distanceToZero - (tickCount - 1) * step < -1 * (step / 4) ? tickCount - 1 : tickCount;
|
|
2521
|
-
maxValue = (step * tickCount * reverse) - (minValue * reverse);
|
|
2522
|
-
return {
|
|
2523
|
-
stepSize: Math.abs(step),
|
|
2524
|
-
max: maxValue,
|
|
2525
|
-
min: minValue,
|
|
2526
|
-
tickAmount: tickCount
|
|
2527
|
-
};
|
|
2620
|
+
if (tickCount >= minTickCount && tickCount <= maxTickCount && adjustedMaxData < maxValue) {
|
|
2621
|
+
if (options?.acceptNegative) {
|
|
2622
|
+
minValue = calculateMinValue(step, tickCount, options.minNegative);
|
|
2528
2623
|
}
|
|
2624
|
+
// Adjust tick count based on distance calculation
|
|
2625
|
+
const distanceCheck = distanceToZero - (tickCount - 1) * step;
|
|
2626
|
+
if (distanceCheck < -1 * (step / 4)) {
|
|
2627
|
+
tickCount -= 1;
|
|
2628
|
+
}
|
|
2629
|
+
maxValue = (step * tickCount * reverse) - (minValue * reverse);
|
|
2630
|
+
return {
|
|
2631
|
+
stepSize: Math.abs(step),
|
|
2632
|
+
max: maxValue,
|
|
2633
|
+
min: minValue,
|
|
2634
|
+
tickAmount: tickCount
|
|
2635
|
+
};
|
|
2636
|
+
}
|
|
2637
|
+
}
|
|
2638
|
+
return null;
|
|
2639
|
+
}
|
|
2640
|
+
/**
|
|
2641
|
+
* Calculates minimum value for negative acceptance
|
|
2642
|
+
*/
|
|
2643
|
+
function calculateMinValue(step, tickCount, minNegative) {
|
|
2644
|
+
let minValue = 0;
|
|
2645
|
+
if (!isNil(minNegative)) {
|
|
2646
|
+
let tick = 1;
|
|
2647
|
+
while (tick <= tickCount && minValue >= minNegative) {
|
|
2648
|
+
minValue = tick * step;
|
|
2649
|
+
tick++;
|
|
2529
2650
|
}
|
|
2530
2651
|
}
|
|
2652
|
+
return minValue;
|
|
2653
|
+
}
|
|
2654
|
+
/**
|
|
2655
|
+
* Calculates fallback scale when no optimal scale is found
|
|
2656
|
+
*/
|
|
2657
|
+
function calculateFallbackScale(distanceToZero, minTickCount) {
|
|
2531
2658
|
let step = Math.ceil(distanceToZero / minTickCount) || 1;
|
|
2532
2659
|
let maxValue = step * minTickCount;
|
|
2533
2660
|
if (distanceToZero === maxValue) {
|
|
@@ -2540,7 +2667,34 @@ const getSmartAxisScale = (maxData, options) => {
|
|
|
2540
2667
|
min: 0,
|
|
2541
2668
|
tickAmount: minTickCount
|
|
2542
2669
|
};
|
|
2543
|
-
}
|
|
2670
|
+
}
|
|
2671
|
+
/**
|
|
2672
|
+
* Generates step candidates with performance optimizations
|
|
2673
|
+
*/
|
|
2674
|
+
function generateStepCandidates(maxData, minStep, minNegative, acceptStepIsTypeFloat) {
|
|
2675
|
+
const stepCandidates = [];
|
|
2676
|
+
const maxValueStep = Math.abs(Math.max(maxData, Math.abs(minNegative || 0))) / minStep;
|
|
2677
|
+
// Pre-populate power cache for better performance
|
|
2678
|
+
for (let powNumber = 0; powNumber < MAX_POW_NUMBER; powNumber++) {
|
|
2679
|
+
if (!POWER_CACHE.has(powNumber)) {
|
|
2680
|
+
POWER_CACHE.set(powNumber, Math.pow(10, powNumber));
|
|
2681
|
+
}
|
|
2682
|
+
const powByTemp = POWER_CACHE.get(powNumber);
|
|
2683
|
+
for (let templateNumber = 1; templateNumber < MAX_TEMPLATE_NUMBER; templateNumber++) {
|
|
2684
|
+
if (acceptStepIsTypeFloat) {
|
|
2685
|
+
const floatStep = powByTemp * (((templateNumber - 1) * 2 + 1) / 2);
|
|
2686
|
+
stepCandidates.push(floatStep);
|
|
2687
|
+
}
|
|
2688
|
+
const step = powByTemp * templateNumber;
|
|
2689
|
+
stepCandidates.push(step);
|
|
2690
|
+
// Early termination for performance
|
|
2691
|
+
if (step >= maxValueStep) {
|
|
2692
|
+
return stepCandidates;
|
|
2693
|
+
}
|
|
2694
|
+
}
|
|
2695
|
+
}
|
|
2696
|
+
return stepCandidates;
|
|
2697
|
+
}
|
|
2544
2698
|
|
|
2545
2699
|
/**
|
|
2546
2700
|
* Generated bundle index. Do not edit.
|