@libs-ui/utils 0.2.281 → 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 +177 -22
- package/esm2022/helpers.mjs +65 -1
- package/fesm2022/libs-ui-utils.mjs +239 -21
- package/fesm2022/libs-ui-utils.mjs.map +1 -1
- package/get-smart-axis-scale.d.ts +29 -7
- 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,36 +2511,190 @@ 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();
|
|
2525
|
+
/**
|
|
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
|
|
2539
|
+
*/
|
|
2450
2540
|
const getSmartAxisScale = (maxData, options) => {
|
|
2451
|
-
|
|
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) {
|
|
2565
|
+
if (maxData < 0 && !options?.acceptNegative) {
|
|
2566
|
+
throw new Error("maxData is less than 0 and acceptNegative is false");
|
|
2567
|
+
}
|
|
2568
|
+
if (options?.acceptNegative) {
|
|
2569
|
+
if (isNil(options.minNegative)) {
|
|
2570
|
+
throw new Error("minNegative is required when acceptNegative is true");
|
|
2571
|
+
}
|
|
2572
|
+
if (maxData < options.minNegative) {
|
|
2573
|
+
throw new Error("maxData is less than minNegative");
|
|
2574
|
+
}
|
|
2575
|
+
if (options.minNegative >= 0) {
|
|
2576
|
+
throw new Error("minNegative must be negative");
|
|
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)) {
|
|
2588
|
+
if (maxData === 0) {
|
|
2589
|
+
adjustedMaxData = -1;
|
|
2590
|
+
}
|
|
2591
|
+
// Add negative step candidates
|
|
2592
|
+
if (stepCandidates) {
|
|
2593
|
+
stepCandidates.unshift(...stepCandidates.map(item => -item));
|
|
2594
|
+
}
|
|
2595
|
+
if (adjustedMaxData <= 0) {
|
|
2596
|
+
distanceToZero = minNegative;
|
|
2597
|
+
}
|
|
2598
|
+
if (distanceToZero > NEGATIVE_THRESHOLD) {
|
|
2599
|
+
distanceToZero = FALLBACK_NEGATIVE_VALUE;
|
|
2600
|
+
}
|
|
2601
|
+
if (adjustedMaxData > 0) {
|
|
2602
|
+
distanceToZero = adjustedMaxData + Math.abs(minNegative);
|
|
2603
|
+
reverse = -1;
|
|
2604
|
+
}
|
|
2605
|
+
}
|
|
2606
|
+
if (Math.abs(distanceToZero) < MIN_DISTANCE_TO_ZERO) {
|
|
2607
|
+
distanceToZero = MIN_DISTANCE_TO_ZERO;
|
|
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;
|
|
2452
2616
|
for (const step of stepCandidates) {
|
|
2453
|
-
let tickCount = Math.ceil(
|
|
2454
|
-
let
|
|
2455
|
-
|
|
2456
|
-
|
|
2457
|
-
|
|
2458
|
-
|
|
2459
|
-
return {
|
|
2460
|
-
step: step,
|
|
2461
|
-
maxY: maxY,
|
|
2462
|
-
ticks: tickCount
|
|
2463
|
-
};
|
|
2617
|
+
let tickCount = Math.abs(Math.ceil(distanceToZero / step)) + (reverse === -1 ? 2 : 1);
|
|
2618
|
+
let maxValue = adjustedMaxData <= 0 ? 0 : step * tickCount * reverse;
|
|
2619
|
+
let minValue = 0;
|
|
2620
|
+
if (tickCount >= minTickCount && tickCount <= maxTickCount && adjustedMaxData < maxValue) {
|
|
2621
|
+
if (options?.acceptNegative) {
|
|
2622
|
+
minValue = calculateMinValue(step, tickCount, options.minNegative);
|
|
2464
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++;
|
|
2465
2650
|
}
|
|
2466
2651
|
}
|
|
2467
|
-
|
|
2468
|
-
|
|
2469
|
-
|
|
2470
|
-
|
|
2652
|
+
return minValue;
|
|
2653
|
+
}
|
|
2654
|
+
/**
|
|
2655
|
+
* Calculates fallback scale when no optimal scale is found
|
|
2656
|
+
*/
|
|
2657
|
+
function calculateFallbackScale(distanceToZero, minTickCount) {
|
|
2658
|
+
let step = Math.ceil(distanceToZero / minTickCount) || 1;
|
|
2659
|
+
let maxValue = step * minTickCount;
|
|
2660
|
+
if (distanceToZero === maxValue) {
|
|
2471
2661
|
step = step + Math.ceil(step / 10);
|
|
2472
|
-
|
|
2662
|
+
maxValue = step * minTickCount;
|
|
2473
2663
|
}
|
|
2474
2664
|
return {
|
|
2475
|
-
|
|
2476
|
-
|
|
2477
|
-
|
|
2665
|
+
stepSize: Math.abs(step),
|
|
2666
|
+
max: maxValue,
|
|
2667
|
+
min: 0,
|
|
2668
|
+
tickAmount: minTickCount
|
|
2478
2669
|
};
|
|
2479
|
-
}
|
|
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
|
+
}
|
|
2480
2698
|
|
|
2481
2699
|
/**
|
|
2482
2700
|
* Generated bundle index. Do not edit.
|