@libs-ui/utils 0.2.282 → 0.2.284

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.
@@ -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,66 +2511,60 @@ const convertUrlToFile = (url, fileName) => {
2447
2511
  });
2448
2512
  };
2449
2513
 
2514
+ const DEFAULT_MIN_TICK_COUNT = 5;
2515
+ const DEFAULT_MAX_TICK_COUNT = 10;
2516
+ const MIN_DISTANCE_TO_ZERO = 3;
2517
+ const NEGATIVE_THRESHOLD = -5;
2518
+ const FALLBACK_NEGATIVE_VALUE = -4;
2519
+ const MAX_POW_NUMBER = 14;
2520
+ const MAX_TEMPLATE_NUMBER = 10;
2450
2521
  /**
2451
- * Hàm thể trả về lỗi nếu:
2452
- * - maxData < 0 và acceptNegative là false
2453
- * - minNegative undefined acceptNegative true
2454
- * - maxData < minNegative (chỉ xảy ra khi acceptNegative là true)
2455
- * - minNegative > 0 (chỉ xảy ra khi acceptNegative true)
2456
- * @throws {Error}
2457
- * @required luôn đặt hàm này trong try catch xử ngoại lệ
2458
- * @hint Truyền 3 tham số: min,max tick
2522
+ * Tính toán smart axis scale cho biểu đồ
2523
+ *
2524
+ * @param maxData - Giá trị tối đa của dữ liệu
2525
+ * @param options - Các tùy chọn cấu hình axis scale
2526
+ * @returns Cấu hình axis scale bao gồm stepSize, max, min, tickAmount
2527
+ *
2528
+ * @throws {Error} INVALID_NEGATIVE_DATA - khi maxData < 0 acceptNegative = false
2529
+ * @throws {Error} MISSING_MIN_NEGATIVE - khi acceptNegative = true nhưng thiếu minNegative
2530
+ * @throws {Error} INVALID_RANGE - khi maxData < minNegative
2531
+ * @throws {Error} INVALID_MIN_NEGATIVE - khi minNegative >= 0
2532
+ *
2533
+ * @example
2534
+ * ```typescript
2535
+ * const result = getSmartAxisScale(100, { minTickCount: 5, maxTickCount: 10 });
2536
+ * // returns { stepSize: 20, max: 120, min: 0, tickAmount: 6 }
2537
+ * ```
2459
2538
  */
2460
- const getSmartAxisScale = (maxData, options) => {
2461
- const minTickCount = options?.minTickCount || 5;
2462
- const maxTickCount = options?.maxTickCount || 10;
2463
- const stepCandidatesDefault = new Array();
2464
- const loopStepCandidates = Array.from({ length: 10 }, (_, index) => Math.pow(10, index)).findIndex(item => item > Math.abs(Math.max(maxData, Math.abs(options?.minNegative || 0)))) + 1;
2465
- Array.from({ length: loopStepCandidates }, (_, index) => {
2466
- for (let templateNumber = 1; templateNumber < 10; templateNumber++) {
2467
- if (options?.acceptStepIsTypeFloat) {
2468
- stepCandidatesDefault.push(Math.pow(10, index) * (((templateNumber - 1) * 2 + 1) / 2));
2469
- }
2470
- stepCandidatesDefault.push(Math.pow(10, index) * templateNumber);
2471
- }
2472
- });
2473
- const stepCandidates = options?.stepCandidates || stepCandidatesDefault;
2474
- let distanceToZero = maxData;
2475
- let reverse = 1;
2476
- if (maxData < 0 && !options?.acceptNegative) {
2477
- throw new Error("maxData is less than 0 and acceptNegative is false");
2478
- }
2479
- if (options?.acceptNegative) {
2480
- if (isNil(options.minNegative)) {
2481
- throw new Error("minNegative is required when acceptNegative is true");
2482
- }
2483
- if (maxData < options.minNegative) {
2484
- throw new Error("maxData is less than minNegative");
2485
- }
2486
- if (options.minNegative >= 0) {
2487
- throw new Error("minNegative must be negative");
2488
- }
2539
+ const getSmartAxisScale = (originalMaxData, options) => {
2540
+ let maxData = originalMaxData;
2541
+ validateInputs(maxData, options);
2542
+ const minTickCount = options?.minTickCount || DEFAULT_MIN_TICK_COUNT;
2543
+ const maxTickCount = options?.maxTickCount || DEFAULT_MAX_TICK_COUNT;
2544
+ let scaleDirection = 1;
2545
+ let rangeDistance = maxData;
2546
+ if (options?.acceptNegative && !isNil(options.minNegative)) {
2489
2547
  if (maxData === 0) {
2490
2548
  maxData = -1;
2491
2549
  }
2492
- stepCandidates.unshift(...stepCandidates.map(item => item * -1));
2493
2550
  if (maxData <= 0) {
2494
- distanceToZero = options.minNegative;
2551
+ rangeDistance = options.minNegative;
2495
2552
  }
2496
- if (distanceToZero > -5) {
2497
- distanceToZero = -4;
2553
+ if (rangeDistance > NEGATIVE_THRESHOLD) {
2554
+ rangeDistance = FALLBACK_NEGATIVE_VALUE;
2498
2555
  }
2499
2556
  if (maxData > 0) {
2500
- distanceToZero = maxData + Math.abs(options.minNegative);
2501
- reverse = -1;
2557
+ rangeDistance = maxData + Math.abs(options.minNegative);
2558
+ scaleDirection = -1;
2502
2559
  }
2503
2560
  }
2504
- if (Math.abs(distanceToZero) < 3) {
2505
- distanceToZero = 3;
2561
+ if (Math.abs(rangeDistance) < MIN_DISTANCE_TO_ZERO) {
2562
+ rangeDistance = MIN_DISTANCE_TO_ZERO;
2506
2563
  }
2564
+ const stepCandidates = getStepCandidates(maxData, minTickCount, options?.minNegative || 0, options?.acceptStepIsTypeFloat, options?.stepCandidates, options?.acceptNegative);
2507
2565
  for (const step of stepCandidates) {
2508
- let tickCount = Math.abs(Math.ceil(distanceToZero / step)) + (reverse === -1 ? 2 : 1);
2509
- let maxValue = maxData <= 0 ? 0 : step * tickCount * reverse;
2566
+ let tickCount = Math.abs(Math.ceil(rangeDistance / step)) + (scaleDirection === -1 ? 2 : 1);
2567
+ let maxValue = maxData <= 0 ? 0 : step * tickCount * scaleDirection;
2510
2568
  let minValue = 0;
2511
2569
  if (tickCount >= minTickCount && tickCount <= maxTickCount) {
2512
2570
  if (maxData < maxValue) {
@@ -2517,8 +2575,9 @@ const getSmartAxisScale = (maxData, options) => {
2517
2575
  tick++;
2518
2576
  }
2519
2577
  }
2520
- tickCount = distanceToZero - (tickCount - 1) * step < -1 * (step / 4) ? tickCount - 1 : tickCount;
2521
- maxValue = (step * tickCount * reverse) - (minValue * reverse);
2578
+ const maxValuePositive = maxValue - Math.abs(minValue);
2579
+ tickCount = maxValuePositive - originalMaxData > Math.abs(step) && tickCount - 1 >= minTickCount ? tickCount - 1 : tickCount;
2580
+ maxValue = (step * tickCount * scaleDirection) - (minValue * scaleDirection);
2522
2581
  return {
2523
2582
  stepSize: Math.abs(step),
2524
2583
  max: maxValue,
@@ -2528,9 +2587,9 @@ const getSmartAxisScale = (maxData, options) => {
2528
2587
  }
2529
2588
  }
2530
2589
  }
2531
- let step = Math.ceil(distanceToZero / minTickCount) || 1;
2590
+ let step = Math.ceil(rangeDistance / minTickCount) || 1;
2532
2591
  let maxValue = step * minTickCount;
2533
- if (distanceToZero === maxValue) {
2592
+ if (rangeDistance === maxValue) {
2534
2593
  step = step + Math.ceil(step / 10);
2535
2594
  maxValue = step * minTickCount;
2536
2595
  }
@@ -2541,6 +2600,89 @@ const getSmartAxisScale = (maxData, options) => {
2541
2600
  tickAmount: minTickCount
2542
2601
  };
2543
2602
  };
2603
+ // Cache cho các giá trị lũy thừa 10 để tối ưu hiệu suất
2604
+ const POWER_CACHE = new Map();
2605
+ /**
2606
+ * Tạo danh sách các step candidates cho việc tính toán scale
2607
+ * @param maxData - Giá trị dữ liệu tối đa
2608
+ * @param minStep - Số bước tối thiểu
2609
+ * @param minNegative - Giá trị âm tối thiểu
2610
+ * @param acceptStepIsTypeFloat - Có chấp nhận step thập phân không
2611
+ * @param stepCandidatesByOptions - Step candidates do người dùng cung cấp
2612
+ * @param includeNegativeSteps - Có bao gồm các step âm không
2613
+ * @returns Danh sách các step candidates
2614
+ */
2615
+ const getStepCandidates = (maxData, minStep, minNegative, acceptStepIsTypeFloat = false, stepCandidatesByOptions, includeNegativeSteps = false) => {
2616
+ // Nếu có step candidates tùy chỉnh, sử dụng chúng
2617
+ if (stepCandidatesByOptions && stepCandidatesByOptions.length > 0) {
2618
+ return stepCandidatesByOptions;
2619
+ }
2620
+ const stepCandidates = new Array();
2621
+ const maxValueStep = Math.abs(Math.max(maxData, Math.abs(minNegative || 0))) / minStep;
2622
+ // Tạo step candidates dựa trên lũy thừa của 10
2623
+ for (let powNumber = 0; powNumber <= MAX_POW_NUMBER; powNumber++) {
2624
+ // Sử dụng cache để tối ưu hiệu suất
2625
+ if (!POWER_CACHE.has(powNumber)) {
2626
+ POWER_CACHE.set(powNumber, Math.pow(10, powNumber));
2627
+ }
2628
+ const powValue = POWER_CACHE.get(powNumber);
2629
+ for (let templateNumber = 1; templateNumber < MAX_TEMPLATE_NUMBER; templateNumber++) {
2630
+ // Thêm step thập phân nếu được chấp nhận
2631
+ if (acceptStepIsTypeFloat) {
2632
+ const step = powValue * (((templateNumber - 1) * 2 + 1) / 2);
2633
+ stepCandidates.push(step);
2634
+ }
2635
+ const step = powValue * templateNumber;
2636
+ stepCandidates.push(step);
2637
+ // Dừng khi step đã đủ lớn
2638
+ if (step >= maxValueStep) {
2639
+ checkAndSetNegativeSteps(stepCandidates, includeNegativeSteps, minNegative);
2640
+ return stepCandidates;
2641
+ }
2642
+ }
2643
+ }
2644
+ checkAndSetNegativeSteps(stepCandidates, includeNegativeSteps, minNegative);
2645
+ return stepCandidates;
2646
+ };
2647
+ /**
2648
+ * Kiểm tra và thêm các step âm vào danh sách candidates nếu cần
2649
+ * @param stepCandidates - Danh sách step candidates hiện tại
2650
+ * @param acceptNegative - Có chấp nhận giá trị âm không
2651
+ * @param minNegative - Giá trị âm tối thiểu
2652
+ */
2653
+ const checkAndSetNegativeSteps = (stepCandidates, acceptNegative, minNegative) => {
2654
+ if (acceptNegative && minNegative < 0) {
2655
+ // Tạo các step âm và thêm vào đầu danh sách
2656
+ const negativeSteps = [...stepCandidates].map(step => -step);
2657
+ stepCandidates.unshift(...negativeSteps);
2658
+ }
2659
+ };
2660
+ /**
2661
+ * Kiểm tra tính hợp lệ của các tham số đầu vào
2662
+ * @param maxData - Giá trị dữ liệu tối đa
2663
+ * @param options - Các tùy chọn cấu hình
2664
+ * @throws {Error} Khi các tham số không hợp lệ
2665
+ */
2666
+ const validateInputs = (maxData, options) => {
2667
+ // Kiểm tra maxData âm khi không chấp nhận giá trị âm
2668
+ if (maxData < 0 && !options?.acceptNegative) {
2669
+ throw new Error("maxData is less than 0 and acceptNegative is false");
2670
+ }
2671
+ if (options?.acceptNegative) {
2672
+ // Kiểm tra minNegative có được cung cấp không
2673
+ if (isNil(options.minNegative)) {
2674
+ throw new Error("minNegative is required when acceptNegative is true");
2675
+ }
2676
+ // Kiểm tra maxData phải >= minNegative
2677
+ if (maxData < options.minNegative) {
2678
+ throw new Error("maxData is less than minNegative");
2679
+ }
2680
+ // Kiểm tra minNegative phải là số âm
2681
+ if (options.minNegative >= 0) {
2682
+ throw new Error("minNegative must be negative");
2683
+ }
2684
+ }
2685
+ };
2544
2686
 
2545
2687
  /**
2546
2688
  * Generated bundle index. Do not edit.