@rfkit/charts 1.10.3 → 1.10.4

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.
@@ -4,7 +4,7 @@
4
4
  */
5
5
  import type React from 'react';
6
6
  import { type ReactNode } from 'react';
7
- import { type RectBounds, type RectPosition } from '../../hooks/useDragSelect';
7
+ import { type DragType, type RectBounds, type RectPosition } from '../../hooks/useDragSelect';
8
8
  type DragSelectBoxVariant = 'contained' | 'projected';
9
9
  export interface DragSelectBoxProps {
10
10
  enabled: boolean;
@@ -15,7 +15,7 @@ export interface DragSelectBoxProps {
15
15
  maxSize?: number;
16
16
  snapThreshold?: number;
17
17
  onChange?: (rect: RectPosition) => void;
18
- onDragStart?: () => boolean | undefined;
18
+ onDragStart?: (dragType: Exclude<DragType, null>) => boolean | undefined;
19
19
  onDragEnd?: (rect: RectPosition) => void;
20
20
  children?: ReactNode;
21
21
  labels?: {
package/index.js CHANGED
@@ -22519,16 +22519,26 @@ const resolveHeatmapSourceRange = (snapshot, bounds)=>{
22519
22519
  };
22520
22520
  };
22521
22521
  const resolveHeatmapSnapshotRangeData = (snapshot, range)=>snapshot.rawData.slice(range.startIndex, range.endIndex + 1).map((row)=>Array.from(row.slice(range.startCol, range.endCol + 1)));
22522
- const resolveHeatmapSourceRangeScreenRect = (snapshot, range)=>{
22522
+ const resolveHeatmapSourceRangeScreenRect = (snapshot, range)=>resolveHeatmapContinuousSourceRangeScreenRect(snapshot, {
22523
+ x: [
22524
+ Math.min(range.startCol, range.endCol),
22525
+ Math.max(range.startCol, range.endCol) + 1
22526
+ ],
22527
+ y: [
22528
+ Math.min(range.startIndex, range.endIndex),
22529
+ Math.max(range.startIndex, range.endIndex) + 1
22530
+ ]
22531
+ });
22532
+ const resolveHeatmapContinuousSourceRangeScreenRect = (snapshot, range)=>{
22523
22533
  const [viewportXStart, viewportXEnd] = snapshot.range.x;
22524
22534
  const [viewportYStart, viewportYEnd] = snapshot.range.y;
22525
22535
  const viewportXSpan = viewportXEnd - viewportXStart;
22526
22536
  const viewportYSpan = viewportYEnd - viewportYStart;
22527
22537
  if (viewportXSpan <= 0 || viewportYSpan <= 0) return null;
22528
- const sourceXStart = Math.min(range.startCol, range.endCol);
22529
- const sourceXEnd = Math.max(range.startCol, range.endCol) + 1;
22530
- const sourceYStart = Math.min(range.startIndex, range.endIndex);
22531
- const sourceYEnd = Math.max(range.startIndex, range.endIndex) + 1;
22538
+ const sourceXStart = Math.min(range.x[0], range.x[1]);
22539
+ const sourceXEnd = Math.max(range.x[0], range.x[1]);
22540
+ const sourceYStart = Math.min(range.y[0], range.y[1]);
22541
+ const sourceYEnd = Math.max(range.y[0], range.y[1]);
22532
22542
  return {
22533
22543
  leftPosition: (sourceXStart - viewportXStart) * 100 / viewportXSpan,
22534
22544
  width: (sourceXEnd - sourceXStart) * 100 / viewportXSpan,
@@ -22537,19 +22547,40 @@ const resolveHeatmapSourceRangeScreenRect = (snapshot, range)=>{
22537
22547
  };
22538
22548
  };
22539
22549
  const SOURCE_EDGE_EPSILON = 1e-7;
22540
- const resolveHeatmapProjectedSourceRange = (snapshot, bounds)=>{
22550
+ const resolveHeatmapProjectedContinuousSourceRange = (snapshot, bounds)=>{
22541
22551
  const { sourceSize } = snapshot;
22542
22552
  const [viewportXStart, viewportXEnd] = snapshot.range.x;
22543
22553
  const [viewportYStart, viewportYEnd] = snapshot.range.y;
22544
22554
  const viewportXSpan = viewportXEnd - viewportXStart;
22545
22555
  const viewportYSpan = viewportYEnd - viewportYStart;
22546
22556
  if (sourceSize.width <= 0 || sourceSize.height <= 0 || viewportXSpan <= 0 || viewportYSpan <= 0) return null;
22547
- const sourceXStart = viewportXStart + Math.min(bounds.startLeft, bounds.endLeft) * viewportXSpan / 100;
22548
- const sourceXEnd = viewportXStart + Math.max(bounds.startLeft, bounds.endLeft) * viewportXSpan / 100;
22557
+ const sourceXStart = viewport_clamp(viewportXStart + Math.min(bounds.startLeft, bounds.endLeft) * viewportXSpan / 100, 0, sourceSize.width);
22558
+ const sourceXEnd = viewport_clamp(viewportXStart + Math.max(bounds.startLeft, bounds.endLeft) * viewportXSpan / 100, 0, sourceSize.width);
22549
22559
  const sourceYAtStart = viewportYStart + (100 - bounds.startTop) * viewportYSpan / 100;
22550
22560
  const sourceYAtEnd = viewportYStart + (100 - bounds.endTop) * viewportYSpan / 100;
22551
- const sourceYStart = Math.min(sourceYAtStart, sourceYAtEnd);
22552
- const sourceYEnd = Math.max(sourceYAtStart, sourceYAtEnd);
22561
+ const sourceYStart = viewport_clamp(Math.min(sourceYAtStart, sourceYAtEnd), 0, sourceSize.height);
22562
+ const sourceYEnd = viewport_clamp(Math.max(sourceYAtStart, sourceYAtEnd), 0, sourceSize.height);
22563
+ return {
22564
+ x: [
22565
+ sourceXStart,
22566
+ sourceXEnd
22567
+ ],
22568
+ y: [
22569
+ sourceYStart,
22570
+ sourceYEnd
22571
+ ]
22572
+ };
22573
+ };
22574
+ const resolveHeatmapProjectedSourceRange = (snapshot, bounds)=>{
22575
+ const sourceRange = resolveHeatmapProjectedContinuousSourceRange(snapshot, bounds);
22576
+ if (!sourceRange) return null;
22577
+ return quantizeHeatmapContinuousSourceRange(snapshot, sourceRange);
22578
+ };
22579
+ const quantizeHeatmapContinuousSourceRange = (snapshot, sourceRange)=>{
22580
+ const { sourceSize } = snapshot;
22581
+ if (sourceSize.width <= 0 || sourceSize.height <= 0) return null;
22582
+ const [sourceXStart, sourceXEnd] = sourceRange.x;
22583
+ const [sourceYStart, sourceYEnd] = sourceRange.y;
22553
22584
  const startCol = viewport_clamp(Math.floor(sourceXStart + SOURCE_EDGE_EPSILON), 0, sourceSize.width - 1);
22554
22585
  const endCol = viewport_clamp(Math.max(startCol, Math.ceil(sourceXEnd - SOURCE_EDGE_EPSILON) - 1), startCol, sourceSize.width - 1);
22555
22586
  const startIndex = viewport_clamp(Math.floor(sourceYStart + SOURCE_EDGE_EPSILON), 0, sourceSize.height - 1);
@@ -22618,7 +22649,7 @@ const resolveHeatmapOverviewViewportRect = (snapshot)=>{
22618
22649
  height: (range.y[1] - range.y[0]) / sourceSize.height
22619
22650
  };
22620
22651
  };
22621
- const resolveHeatmapOverviewSourceRangeRect = (snapshot, range)=>{
22652
+ const resolveHeatmapOverviewContinuousSourceRangeRect = (snapshot, range)=>{
22622
22653
  const { sourceSize } = snapshot;
22623
22654
  if (sourceSize.width <= 0 || sourceSize.height <= 0) return {
22624
22655
  height: 0,
@@ -22626,22 +22657,22 @@ const resolveHeatmapOverviewSourceRangeRect = (snapshot, range)=>{
22626
22657
  top: 0,
22627
22658
  width: 0
22628
22659
  };
22629
- const startCol = viewport_clamp(Math.min(range.startCol, range.endCol), 0, sourceSize.width - 1);
22630
- const endCol = viewport_clamp(Math.max(range.startCol, range.endCol), startCol, sourceSize.width - 1);
22631
- const startIndex = viewport_clamp(Math.min(range.startIndex, range.endIndex), 0, sourceSize.height - 1);
22632
- const endIndex = viewport_clamp(Math.max(range.startIndex, range.endIndex), startIndex, sourceSize.height - 1);
22660
+ const xStart = viewport_clamp(Math.min(range.x[0], range.x[1]), 0, sourceSize.width);
22661
+ const xEnd = viewport_clamp(Math.max(range.x[0], range.x[1]), xStart, sourceSize.width);
22662
+ const yStart = viewport_clamp(Math.min(range.y[0], range.y[1]), 0, sourceSize.height);
22663
+ const yEnd = viewport_clamp(Math.max(range.y[0], range.y[1]), yStart, sourceSize.height);
22633
22664
  return {
22634
- left: startCol / sourceSize.width,
22635
- top: 1 - (endIndex + 1) / sourceSize.height,
22636
- width: (endCol + 1 - startCol) / sourceSize.width,
22637
- height: (endIndex + 1 - startIndex) / sourceSize.height
22665
+ left: xStart / sourceSize.width,
22666
+ top: 1 - yEnd / sourceSize.height,
22667
+ width: (xEnd - xStart) / sourceSize.width,
22668
+ height: (yEnd - yStart) / sourceSize.height
22638
22669
  };
22639
22670
  };
22640
- const isHeatmapSourceRangeVisibleInViewport = (snapshot, range)=>Math.min(range.startCol, range.endCol) < snapshot.range.x[1] && Math.max(range.startCol, range.endCol) >= snapshot.range.x[0] && Math.min(range.startIndex, range.endIndex) < snapshot.range.y[1] && Math.max(range.startIndex, range.endIndex) >= snapshot.range.y[0];
22641
- const isHeatmapOverviewSourcePointInRange = (range, point, tolerance = {
22671
+ const isHeatmapContinuousSourceRangeVisibleInViewport = (snapshot, range)=>Math.min(range.x[0], range.x[1]) < snapshot.range.x[1] && Math.max(range.x[0], range.x[1]) > snapshot.range.x[0] && Math.min(range.y[0], range.y[1]) < snapshot.range.y[1] && Math.max(range.y[0], range.y[1]) > snapshot.range.y[0];
22672
+ const isHeatmapOverviewSourcePointInContinuousRange = (range, point, tolerance = {
22642
22673
  x: 0,
22643
22674
  y: 0
22644
- })=>point.x >= Math.min(range.startCol, range.endCol) - tolerance.x && point.x <= Math.max(range.startCol, range.endCol) + 1 + tolerance.x && point.y >= Math.min(range.startIndex, range.endIndex) - tolerance.y && point.y <= Math.max(range.startIndex, range.endIndex) + 1 + tolerance.y;
22675
+ })=>point.x >= Math.min(range.x[0], range.x[1]) - tolerance.x && point.x <= Math.max(range.x[0], range.x[1]) + tolerance.x && point.y >= Math.min(range.y[0], range.y[1]) - tolerance.y && point.y <= Math.max(range.y[0], range.y[1]) + tolerance.y;
22645
22676
  const isHeatmapViewportFullRange = (snapshot)=>{
22646
22677
  const { range, sourceSize } = snapshot;
22647
22678
  if (sourceSize.width <= 0 || sourceSize.height <= 0) return true;
@@ -22654,9 +22685,9 @@ const calculateHeatmapOverviewCenteredRange = (snapshot, point)=>{
22654
22685
  y: centerRange(snapshot.range.y, snapshot.sourceSize.height, sourcePoint.y)
22655
22686
  };
22656
22687
  };
22657
- const calculateHeatmapOverviewSourceRangeCenteredRange = (snapshot, range)=>({
22658
- x: centerRange(snapshot.range.x, snapshot.sourceSize.width, (Math.min(range.startCol, range.endCol) + Math.max(range.startCol, range.endCol) + 1) / 2),
22659
- y: centerRange(snapshot.range.y, snapshot.sourceSize.height, (Math.min(range.startIndex, range.endIndex) + Math.max(range.startIndex, range.endIndex) + 1) / 2)
22688
+ const calculateHeatmapOverviewContinuousSourceRangeCenteredRange = (snapshot, range)=>({
22689
+ x: centerRange(snapshot.range.x, snapshot.sourceSize.width, (Math.min(range.x[0], range.x[1]) + Math.max(range.x[0], range.x[1])) / 2),
22690
+ y: centerRange(snapshot.range.y, snapshot.sourceSize.height, (Math.min(range.y[0], range.y[1]) + Math.max(range.y[0], range.y[1])) / 2)
22660
22691
  });
22661
22692
  const calculateHeatmapOverviewDragRange = (snapshot, point, grabOffset)=>{
22662
22693
  const sourcePoint = resolveHeatmapOverviewSourcePoint(snapshot, point);
@@ -24376,16 +24407,16 @@ function calculateAreaInfo({ endLeft, startLeft, startTop, endTop, frequencyForm
24376
24407
  return result;
24377
24408
  }
24378
24409
  const heatmapCaptureUpdate = (globalID, func)=>subscription_createSubscriptionManager(`heatmapCaptureUpdate-${globalID}`, '0', func, globalID);
24379
- const getHeatmapCaptureSourceRangeKey = (globalID)=>`heatmapCaptureSourceRange-${globalID}`;
24380
- const getHeatmapCaptureSourceRangeSubscriptionKey = (globalID)=>`${getHeatmapCaptureSourceRangeKey(globalID)}-update`;
24381
- const isSourceRangeEqual = (left, right)=>left === right || null !== left && null !== right && left.startCol === right.startCol && left.endCol === right.endCol && left.startIndex === right.startIndex && left.endIndex === right.endIndex;
24382
- const getHeatmapCaptureSourceRange = (globalID)=>subscription_openData(getHeatmapCaptureSourceRangeKey(globalID), void 0, null, globalID);
24383
- const setHeatmapCaptureSourceRange = (globalID, range)=>{
24384
- if (!globalID || isSourceRangeEqual(getHeatmapCaptureSourceRange(globalID), range)) return;
24385
- subscription_openData(getHeatmapCaptureSourceRangeKey(globalID), range, null, globalID);
24386
- subscription_createSubscriptionManager(getHeatmapCaptureSourceRangeSubscriptionKey(globalID))(range);
24387
- };
24388
- const subscribeHeatmapCaptureSourceRange = (globalID, func)=>subscription_createSubscriptionManager(getHeatmapCaptureSourceRangeSubscriptionKey(globalID), 'HeatmapOverview', func, globalID);
24410
+ const getHeatmapCaptureSelectionKey = (globalID)=>`heatmapCaptureSelection-${globalID}`;
24411
+ const getHeatmapCaptureSelectionSubscriptionKey = (globalID)=>`${getHeatmapCaptureSelectionKey(globalID)}-update`;
24412
+ const isSelectionEqual = (left, right)=>left === right || null !== left && null !== right && Math.abs(left.x[0] - right.x[0]) <= 1e-7 && Math.abs(left.x[1] - right.x[1]) <= 1e-7 && Math.abs(left.y[0] - right.y[0]) <= 1e-7 && Math.abs(left.y[1] - right.y[1]) <= 1e-7;
24413
+ const getHeatmapCaptureSelection = (globalID)=>subscription_openData(getHeatmapCaptureSelectionKey(globalID), void 0, null, globalID);
24414
+ const setHeatmapCaptureSelection = (globalID, selection)=>{
24415
+ if (!globalID || isSelectionEqual(getHeatmapCaptureSelection(globalID), selection)) return;
24416
+ subscription_openData(getHeatmapCaptureSelectionKey(globalID), selection, null, globalID);
24417
+ subscription_createSubscriptionManager(getHeatmapCaptureSelectionSubscriptionKey(globalID))(selection);
24418
+ };
24419
+ const subscribeHeatmapCaptureSelection = (globalID, func)=>subscription_createSubscriptionManager(getHeatmapCaptureSelectionSubscriptionKey(globalID), 'HeatmapOverview', func, globalID);
24389
24420
  const Area_COMPONENT_KEY = constants_ToolType.HeatmapCapture;
24390
24421
  const Area_Area = (props)=>{
24391
24422
  const { state: { heatmapCapture, axisX: { frequencyFormat, unit }, system, globalID } } = useStore_useStore();
@@ -25491,7 +25522,7 @@ const DragSelectBox = ({ enabled, defaultRect, minSize = 0, minWidth, minHeight,
25491
25522
  return nextRect;
25492
25523
  };
25493
25524
  const handleBoxMouseDown = (event, type, startRect)=>{
25494
- if (onDragStart?.() === false) return;
25525
+ if (onDragStart?.(type) === false) return;
25495
25526
  event.preventDefault();
25496
25527
  event.stopPropagation();
25497
25528
  handleMouseDown(event, type, startRect);
@@ -25686,12 +25717,191 @@ const DragSelectBox = ({ enabled, defaultRect, minSize = 0, minWidth, minHeight,
25686
25717
  });
25687
25718
  };
25688
25719
  const components_DragSelectBox = DragSelectBox;
25689
- const sliderInfo_PRECISION = 1e4;
25690
- const calculateHeatmapSliderInfo = ({ formatBandwidth, frequencyFormat, heatmapData, rect, sourceRangeOverride, waterfallSnapshot })=>{
25720
+ const sliderInfo_PRECISION = 1e6;
25721
+ const sliderInfo_clamp = (value1, min, max)=>Math.min(max, Math.max(min, value1));
25722
+ const resolveFrequencyExtent = (frequencyFormat)=>{
25723
+ if (!frequencyFormat) return null;
25724
+ const start = Number(frequencyFormat(0));
25725
+ const end = Number(frequencyFormat(100));
25726
+ if (!Number.isFinite(start) || !Number.isFinite(end) || start === end) return null;
25727
+ return [
25728
+ start,
25729
+ end
25730
+ ];
25731
+ };
25732
+ const resolveContinuousFrequencyFromSegments = (ratio, segments, edge)=>{
25733
+ if (!segments?.length) return null;
25734
+ const percent = 100 * sliderInfo_clamp(ratio, 0, 1);
25735
+ const lastIndex = segments.length - 1;
25736
+ for(let index = 0; index < segments.length; index += 1){
25737
+ const segment = segments[index];
25738
+ const progressStart = Math.min(segment.progress[0], segment.progress[1]);
25739
+ const progressEnd = Math.max(segment.progress[0], segment.progress[1]);
25740
+ const contains = 'start' === edge ? percent >= progressStart && (percent < progressEnd || index === lastIndex && percent <= progressEnd) : (percent > progressStart || 0 === index) && percent <= progressEnd;
25741
+ if (!contains) continue;
25742
+ const progressSpan = progressEnd - progressStart;
25743
+ const localRatio = progressSpan ? (percent - progressStart) / progressSpan : 0;
25744
+ return segment.start + (segment.stop - segment.start) * localRatio;
25745
+ }
25746
+ return null;
25747
+ };
25748
+ const resolveFrequencyFromRatio = (ratio, segments, frequencyFormat, edge)=>{
25749
+ const value1 = frequencyFormat?.(100 * sliderInfo_clamp(ratio, 0, 1));
25750
+ const frequency = void 0 === value1 ? Number.NaN : Number(value1);
25751
+ if (Number.isFinite(frequency)) return frequency;
25752
+ return resolveContinuousFrequencyFromSegments(ratio, segments, edge);
25753
+ };
25754
+ const resolveRatioFromFrequency = (frequency, segments, frequencyFormat)=>{
25755
+ if (segments?.length) {
25756
+ let nearest = null;
25757
+ for (const segment of segments){
25758
+ const frequencyStart = segment.start;
25759
+ const frequencyEnd = segment.stop;
25760
+ const frequencyMin = Math.min(frequencyStart, frequencyEnd);
25761
+ const frequencyMax = Math.max(frequencyStart, frequencyEnd);
25762
+ const progressStart = segment.progress[0] / 100;
25763
+ const progressEnd = segment.progress[1] / 100;
25764
+ if (frequency >= frequencyMin && frequency <= frequencyMax) {
25765
+ const frequencySpan = frequencyEnd - frequencyStart;
25766
+ const localRatio = frequencySpan ? (frequency - frequencyStart) / frequencySpan : 0;
25767
+ return sliderInfo_clamp(progressStart + (progressEnd - progressStart) * localRatio, 0, 1);
25768
+ }
25769
+ const candidates = [
25770
+ {
25771
+ distance: Math.abs(frequency - frequencyStart),
25772
+ ratio: progressStart
25773
+ },
25774
+ {
25775
+ distance: Math.abs(frequency - frequencyEnd),
25776
+ ratio: progressEnd
25777
+ }
25778
+ ];
25779
+ for (const candidate of candidates)if (!nearest || candidate.distance < nearest.distance) nearest = candidate;
25780
+ }
25781
+ if (nearest) return sliderInfo_clamp(nearest.ratio, 0, 1);
25782
+ }
25783
+ const extent = resolveFrequencyExtent(frequencyFormat);
25784
+ if (!extent) return null;
25785
+ return sliderInfo_clamp((frequency - extent[0]) / (extent[1] - extent[0]), 0, 1);
25786
+ };
25787
+ const resolveHeatmapSliderFrequencyRatioRange = ({ frequencyFormat, frequencyRange, segments })=>{
25788
+ const start = resolveRatioFromFrequency(frequencyRange[0], segments, frequencyFormat);
25789
+ const end = resolveRatioFromFrequency(frequencyRange[1], segments, frequencyFormat);
25790
+ if (null === start || null === end) return null;
25791
+ return [
25792
+ Math.min(start, end),
25793
+ Math.max(start, end)
25794
+ ];
25795
+ };
25796
+ const resolveFrequencyRangeFromRatios = (ratios, segments, frequencyFormat)=>{
25797
+ const start = resolveFrequencyFromRatio(ratios[0], segments, frequencyFormat, 'start');
25798
+ const end = resolveFrequencyFromRatio(ratios[1], segments, frequencyFormat, 'end');
25799
+ if (null === start || null === end) return null;
25800
+ return [
25801
+ Math.min(start, end),
25802
+ Math.max(start, end)
25803
+ ];
25804
+ };
25805
+ const resolveHeatmapSliderSelectionFromRect = ({ frequencyFormat, rect, segments, waterfallSnapshot })=>{
25806
+ const continuousRange = resolveHeatmapProjectedContinuousSourceRange(waterfallSnapshot, {
25807
+ startLeft: rect.leftPosition,
25808
+ endLeft: rect.leftPosition + rect.width,
25809
+ startTop: rect.position + rect.height,
25810
+ endTop: rect.position
25811
+ });
25812
+ if (!continuousRange || waterfallSnapshot.sourceSize.width <= 0) return null;
25813
+ const x = [
25814
+ continuousRange.x[0] / waterfallSnapshot.sourceSize.width,
25815
+ continuousRange.x[1] / waterfallSnapshot.sourceSize.width
25816
+ ];
25817
+ return {
25818
+ frequency: resolveFrequencyRangeFromRatios(x, segments, frequencyFormat),
25819
+ x,
25820
+ y: continuousRange.y
25821
+ };
25822
+ };
25823
+ const resolveHeatmapSliderSelectionFromInteraction = ({ dragType, frequencyFormat, rect, segments, startSelection, waterfallSnapshot })=>{
25824
+ const current = resolveHeatmapSliderSelectionFromRect({
25825
+ frequencyFormat,
25826
+ rect,
25827
+ segments,
25828
+ waterfallSnapshot
25829
+ });
25830
+ if (!current || !startSelection) return current;
25831
+ switch(dragType){
25832
+ case 'left':
25833
+ return {
25834
+ frequency: current.frequency && startSelection.frequency ? [
25835
+ current.frequency[0],
25836
+ startSelection.frequency[1]
25837
+ ] : current.frequency,
25838
+ x: [
25839
+ current.x[0],
25840
+ startSelection.x[1]
25841
+ ],
25842
+ y: startSelection.y
25843
+ };
25844
+ case 'right':
25845
+ return {
25846
+ frequency: current.frequency && startSelection.frequency ? [
25847
+ startSelection.frequency[0],
25848
+ current.frequency[1]
25849
+ ] : current.frequency,
25850
+ x: [
25851
+ startSelection.x[0],
25852
+ current.x[1]
25853
+ ],
25854
+ y: startSelection.y
25855
+ };
25856
+ case 'top':
25857
+ return {
25858
+ frequency: startSelection.frequency,
25859
+ x: startSelection.x,
25860
+ y: [
25861
+ startSelection.y[0],
25862
+ current.y[1]
25863
+ ]
25864
+ };
25865
+ case 'bottom':
25866
+ return {
25867
+ frequency: startSelection.frequency,
25868
+ x: startSelection.x,
25869
+ y: [
25870
+ current.y[0],
25871
+ startSelection.y[1]
25872
+ ]
25873
+ };
25874
+ case 'middle':
25875
+ return current;
25876
+ }
25877
+ };
25878
+ const resolveHeatmapSliderSelectionSourceRange = ({ selection, waterfallSnapshot })=>{
25879
+ const { sourceSize } = waterfallSnapshot;
25880
+ if (sourceSize.width <= 0 || sourceSize.height <= 0) return null;
25881
+ const xStart = sliderInfo_clamp(Math.min(selection.x[0], selection.x[1]), 0, 1);
25882
+ const xEnd = sliderInfo_clamp(Math.max(selection.x[0], selection.x[1]), xStart, 1);
25883
+ const yStart = sliderInfo_clamp(Math.min(selection.y[0], selection.y[1]), 0, sourceSize.height);
25884
+ const yEnd = sliderInfo_clamp(Math.max(selection.y[0], selection.y[1]), yStart, sourceSize.height);
25885
+ return {
25886
+ x: [
25887
+ xStart * sourceSize.width,
25888
+ xEnd * sourceSize.width
25889
+ ],
25890
+ y: [
25891
+ yStart,
25892
+ yEnd
25893
+ ]
25894
+ };
25895
+ };
25896
+ const calculateHeatmapSliderInfo = ({ formatBandwidth, frequencyFormat, heatmapData, rect, segments, selectionOverride, sourceRangeOverride, waterfallSnapshot })=>{
25691
25897
  const len = heatmapData.length;
25692
25898
  if (len <= 0) return null;
25693
25899
  const { position, height, leftPosition: left, width } = rect;
25694
- const sourceRange = sourceRangeOverride ?? (waterfallSnapshot ? resolveHeatmapProjectedSourceRange(waterfallSnapshot, {
25900
+ const continuousSourceRangeOverride = waterfallSnapshot && selectionOverride ? resolveHeatmapSliderSelectionSourceRange({
25901
+ selection: selectionOverride,
25902
+ waterfallSnapshot
25903
+ }) : null;
25904
+ const sourceRange = sourceRangeOverride ?? (waterfallSnapshot && continuousSourceRangeOverride ? quantizeHeatmapContinuousSourceRange(waterfallSnapshot, continuousSourceRangeOverride) : null) ?? (waterfallSnapshot ? resolveHeatmapProjectedSourceRange(waterfallSnapshot, {
25695
25905
  startLeft: left,
25696
25906
  endLeft: left + width,
25697
25907
  startTop: position + height,
@@ -25709,12 +25919,16 @@ const calculateHeatmapSliderInfo = ({ formatBandwidth, frequencyFormat, heatmapD
25709
25919
  if (!columnRange) return null;
25710
25920
  const { startCol, endCol } = columnRange;
25711
25921
  const sourceWidth = waterfallSnapshot?.sourceSize.width ?? 0;
25712
- const startLeft = sourceRangeOverride && sourceWidth > 0 ? 100 * startCol / sourceWidth : waterfallSnapshot ? resolveHeatmapSourceLeft(left, waterfallSnapshot) : left;
25713
- const endLeft = sourceRangeOverride && sourceWidth > 0 ? (endCol + 1) * 100 / sourceWidth : waterfallSnapshot ? resolveHeatmapSourceLeft(left + width, waterfallSnapshot) : left + width;
25714
- const startFrequencyValue = frequencyFormat?.(startLeft);
25715
- const endFrequencyValue = frequencyFormat?.(endLeft);
25716
- const startFrequency = startFrequencyValue ? Math.round(Number(startFrequencyValue) * sliderInfo_PRECISION) / sliderInfo_PRECISION : 0;
25717
- const endFrequency = endFrequencyValue ? Math.round(Number(endFrequencyValue) * sliderInfo_PRECISION) / sliderInfo_PRECISION : 0;
25922
+ const startLeft = selectionOverride ? 100 * selectionOverride.x[0] : sourceRangeOverride && sourceWidth > 0 ? 100 * startCol / sourceWidth : waterfallSnapshot ? resolveHeatmapSourceLeft(left, waterfallSnapshot) : left;
25923
+ const endLeft = selectionOverride ? 100 * selectionOverride.x[1] : sourceRangeOverride && sourceWidth > 0 ? (endCol + 1) * 100 / sourceWidth : waterfallSnapshot ? resolveHeatmapSourceLeft(left + width, waterfallSnapshot) : left + width;
25924
+ const fallbackFrequencyRange = resolveFrequencyRangeFromRatios([
25925
+ startLeft / 100,
25926
+ endLeft / 100
25927
+ ], segments, frequencyFormat);
25928
+ const startFrequencyValue = selectionOverride?.frequency?.[0] ?? fallbackFrequencyRange?.[0];
25929
+ const endFrequencyValue = selectionOverride?.frequency?.[1] ?? fallbackFrequencyRange?.[1];
25930
+ const startFrequency = Number.isFinite(startFrequencyValue) ? Math.round(Number(startFrequencyValue) * sliderInfo_PRECISION) / sliderInfo_PRECISION : 0;
25931
+ const endFrequency = Number.isFinite(endFrequencyValue) ? Math.round(Number(endFrequencyValue) * sliderInfo_PRECISION) / sliderInfo_PRECISION : 0;
25718
25932
  const bandwidth = Math.abs(endFrequency - startFrequency);
25719
25933
  return {
25720
25934
  startIndex,
@@ -25747,7 +25961,7 @@ const isRectEqual = (left, right)=>Math.abs(left.position - right.position) <= 0
25747
25961
  const isBoundsEqual = (left, right)=>Math.abs(left.top - right.top) <= 0.01 && Math.abs(left.bottom - right.bottom) <= 0.01 && Math.abs(left.left - right.left) <= 0.01 && Math.abs(left.right - right.right) <= 0.01;
25748
25962
  const isSliderInfoEqual = (left, right)=>left.startIndex === right.startIndex && left.endIndex === right.endIndex && left.startTimestamp === right.startTimestamp && left.endTimestamp === right.endTimestamp && left.duration === right.duration && left.startCol === right.startCol && left.endCol === right.endCol && left.startFrequency === right.startFrequency && left.startFrequencyFormat === right.startFrequencyFormat && left.endFrequency === right.endFrequency && left.endFrequencyFormat === right.endFrequencyFormat && left.bandwidth === right.bandwidth && left.bandwidthFormat === right.bandwidthFormat;
25749
25963
  const Slider = ({ id })=>{
25750
- const { state: { globalID, heatmapCapture: { type, show, display, interval, onChange }, axisX: { frequencyFormat, unit } } } = useStore_useStore();
25964
+ const { state: { globalID, heatmapCapture: { type, show, display, interval, onChange }, axisX: { frequencyFormat, unit }, segments } } = useStore_useStore();
25751
25965
  const canSlider = (0, __WEBPACK_EXTERNAL_MODULE_react__.useMemo)(()=>type === heatmap_HeatmapCaptureType.Slider && show && display, [
25752
25966
  type,
25753
25967
  show,
@@ -25759,8 +25973,14 @@ const Slider = ({ id })=>{
25759
25973
  const [currentRect, setCurrentRect] = (0, __WEBPACK_EXTERNAL_MODULE_react__.useState)(DEFAULT_RECT);
25760
25974
  const [interactionBounds, setInteractionBounds] = (0, __WEBPACK_EXTERNAL_MODULE_react__.useState)();
25761
25975
  const currentRectRef = (0, __WEBPACK_EXTERNAL_MODULE_react__.useRef)(currentRect);
25976
+ const selectionRef = (0, __WEBPACK_EXTERNAL_MODULE_react__.useRef)(null);
25977
+ const dragStartSelectionRef = (0, __WEBPACK_EXTERNAL_MODULE_react__.useRef)(null);
25978
+ const dragStartRectRef = (0, __WEBPACK_EXTERNAL_MODULE_react__.useRef)(null);
25979
+ const activeDragTypeRef = (0, __WEBPACK_EXTERNAL_MODULE_react__.useRef)(null);
25762
25980
  const frequencyFormatRef = (0, __WEBPACK_EXTERNAL_MODULE_react__.useRef)(frequencyFormat);
25981
+ const segmentsRef = (0, __WEBPACK_EXTERNAL_MODULE_react__.useRef)(segments);
25763
25982
  frequencyFormatRef.current = frequencyFormat;
25983
+ segmentsRef.current = segments;
25764
25984
  const updateCurrentRect = (0, __WEBPACK_EXTERNAL_MODULE_react__.useCallback)((rect)=>{
25765
25985
  currentRectRef.current = rect;
25766
25986
  setCurrentRect((current)=>isRectEqual(current, rect) ? current : rect);
@@ -25771,40 +25991,39 @@ const Slider = ({ id })=>{
25771
25991
  return isBoundsEqual(current, bounds) ? current : bounds;
25772
25992
  });
25773
25993
  }, []);
25994
+ const updateSelectionSnapshot = (0, __WEBPACK_EXTERNAL_MODULE_react__.useCallback)((nextInfo)=>{
25995
+ const waterfallSnapshot = getHeatmapWaterfallSnapshot(globalID);
25996
+ const selection = selectionRef.current;
25997
+ const continuousRange = waterfallSnapshot && selection ? resolveHeatmapSliderSelectionSourceRange({
25998
+ selection,
25999
+ waterfallSnapshot
26000
+ }) : null;
26001
+ setHeatmapCaptureSelection(globalID, canSlider && nextInfo && continuousRange ? continuousRange : null);
26002
+ }, [
26003
+ canSlider,
26004
+ globalID
26005
+ ]);
25774
26006
  const updateInfo = (0, __WEBPACK_EXTERNAL_MODULE_react__.useCallback)((nextInfo, notify)=>{
25775
26007
  infoRef.current = nextInfo;
25776
26008
  setInfo(nextInfo);
25777
- if (canSlider) setHeatmapCaptureSourceRange(globalID, {
25778
- endCol: nextInfo.endCol,
25779
- endIndex: nextInfo.endIndex,
25780
- startCol: nextInfo.startCol,
25781
- startIndex: nextInfo.startIndex
25782
- });
26009
+ updateSelectionSnapshot(nextInfo);
25783
26010
  if (notify) {
25784
26011
  notifiedRawDataRef.current = getHeatmapWaterfallSnapshot(globalID)?.rawData ?? null;
25785
26012
  onChange?.(nextInfo);
25786
26013
  }
25787
26014
  }, [
25788
- canSlider,
25789
26015
  globalID,
25790
- onChange
26016
+ onChange,
26017
+ updateSelectionSnapshot
25791
26018
  ]);
25792
26019
  (0, __WEBPACK_EXTERNAL_MODULE_react__.useEffect)(()=>{
25793
- if (!globalID || !canSlider) {
25794
- if (globalID) setHeatmapCaptureSourceRange(globalID, null);
25795
- return;
25796
- }
25797
- const currentInfo = infoRef.current;
25798
- if (currentInfo) setHeatmapCaptureSourceRange(globalID, {
25799
- endCol: currentInfo.endCol,
25800
- endIndex: currentInfo.endIndex,
25801
- startCol: currentInfo.startCol,
25802
- startIndex: currentInfo.startIndex
25803
- });
25804
- return ()=>setHeatmapCaptureSourceRange(globalID, null);
26020
+ if (!globalID) return;
26021
+ updateSelectionSnapshot(canSlider ? infoRef.current ?? null : null);
26022
+ return ()=>setHeatmapCaptureSelection(globalID, null);
25805
26023
  }, [
25806
26024
  canSlider,
25807
- globalID
26025
+ globalID,
26026
+ updateSelectionSnapshot
25808
26027
  ]);
25809
26028
  const getHeatmapData = (0, __WEBPACK_EXTERNAL_MODULE_react__.useCallback)(()=>{
25810
26029
  const waterfallData = withDatabase(globalID).getAllRawData().waterfallData ?? [];
@@ -25815,20 +26034,42 @@ const Slider = ({ id })=>{
25815
26034
  }, [
25816
26035
  globalID
25817
26036
  ]);
25818
- const calculateSliderInfo = (0, __WEBPACK_EXTERNAL_MODULE_react__.useCallback)((r, sourceRangeOverride)=>{
26037
+ const calculateSliderInfo = (0, __WEBPACK_EXTERNAL_MODULE_react__.useCallback)((r, selectionOverride)=>{
25819
26038
  const [heatmapData] = getHeatmapData();
25820
26039
  return calculateHeatmapSliderInfo({
25821
26040
  formatBandwidth: getFrequencyToFixed,
25822
26041
  frequencyFormat: frequencyFormatRef.current,
25823
26042
  heatmapData,
25824
26043
  rect: r,
25825
- sourceRangeOverride,
26044
+ segments: segmentsRef.current,
26045
+ selectionOverride,
25826
26046
  waterfallSnapshot: getHeatmapWaterfallSnapshot(globalID)
25827
26047
  });
25828
26048
  }, [
25829
26049
  getHeatmapData,
25830
26050
  globalID
25831
26051
  ]);
26052
+ const updateSelection = (0, __WEBPACK_EXTERNAL_MODULE_react__.useCallback)((rect, dragType)=>{
26053
+ const waterfallSnapshot = getHeatmapWaterfallSnapshot(globalID);
26054
+ if (!waterfallSnapshot) return null;
26055
+ const nextSelection = dragType ? resolveHeatmapSliderSelectionFromInteraction({
26056
+ dragType,
26057
+ frequencyFormat: frequencyFormatRef.current,
26058
+ rect,
26059
+ segments: segmentsRef.current,
26060
+ startSelection: dragStartSelectionRef.current,
26061
+ waterfallSnapshot
26062
+ }) : resolveHeatmapSliderSelectionFromRect({
26063
+ frequencyFormat: frequencyFormatRef.current,
26064
+ rect,
26065
+ segments: segmentsRef.current,
26066
+ waterfallSnapshot
26067
+ });
26068
+ if (nextSelection) selectionRef.current = nextSelection;
26069
+ return nextSelection;
26070
+ }, [
26071
+ globalID
26072
+ ]);
25832
26073
  const [updateKey, setUpdateKey] = (0, __WEBPACK_EXTERNAL_MODULE_react__.useState)(0);
25833
26074
  (0, __WEBPACK_EXTERNAL_MODULE_react__.useEffect)(()=>{
25834
26075
  if (!id || !globalID) return;
@@ -25838,18 +26079,32 @@ const Slider = ({ id })=>{
25838
26079
  globalID
25839
26080
  ]);
25840
26081
  const handleChange = (0, __WEBPACK_EXTERNAL_MODULE_react__.useCallback)((rect)=>{
26082
+ const startRect = dragStartRectRef.current;
26083
+ if (startRect && isRectEqual(startRect, rect)) return;
25841
26084
  updateCurrentRect(rect);
25842
- const newInfo = calculateSliderInfo(rect);
26085
+ const selection = updateSelection(rect, activeDragTypeRef.current);
26086
+ const newInfo = calculateSliderInfo(rect, selection ?? void 0);
25843
26087
  if (newInfo) updateInfo(newInfo, false);
25844
26088
  }, [
25845
26089
  calculateSliderInfo,
25846
26090
  updateCurrentRect,
25847
- updateInfo
26091
+ updateInfo,
26092
+ updateSelection
25848
26093
  ]);
25849
- const claimCaptureInteraction = (0, __WEBPACK_EXTERNAL_MODULE_react__.useCallback)(()=>compareEventPriority(id, constants_ToolType.HeatmapCapture), [
26094
+ const claimCaptureInteraction = (0, __WEBPACK_EXTERNAL_MODULE_react__.useCallback)((dragType)=>{
26095
+ const claimed = compareEventPriority(id, constants_ToolType.HeatmapCapture);
26096
+ if (false === claimed) return false;
26097
+ activeDragTypeRef.current = dragType;
26098
+ dragStartSelectionRef.current = selectionRef.current;
26099
+ dragStartRectRef.current = currentRectRef.current;
26100
+ return claimed;
26101
+ }, [
25850
26102
  id
25851
26103
  ]);
25852
26104
  const releaseCaptureInteraction = (0, __WEBPACK_EXTERNAL_MODULE_react__.useCallback)(()=>{
26105
+ activeDragTypeRef.current = null;
26106
+ dragStartSelectionRef.current = null;
26107
+ dragStartRectRef.current = null;
25853
26108
  createMouseUpEventManager(getEID(id))();
25854
26109
  resetEventLevel(id, constants_ToolType.HeatmapCapture);
25855
26110
  }, [
@@ -25857,8 +26112,8 @@ const Slider = ({ id })=>{
25857
26112
  ]);
25858
26113
  const handleDragEnd = (0, __WEBPACK_EXTERNAL_MODULE_react__.useCallback)((rect)=>{
25859
26114
  try {
25860
- updateCurrentRect(rect);
25861
- const newInfo = calculateSliderInfo(rect);
26115
+ const selection = selectionRef.current ?? updateSelection(rect, null);
26116
+ const newInfo = calculateSliderInfo(rect, selection ?? void 0);
25862
26117
  if (newInfo) updateInfo(newInfo, true);
25863
26118
  } finally{
25864
26119
  releaseCaptureInteraction();
@@ -25866,8 +26121,8 @@ const Slider = ({ id })=>{
25866
26121
  }, [
25867
26122
  calculateSliderInfo,
25868
26123
  releaseCaptureInteraction,
25869
- updateCurrentRect,
25870
- updateInfo
26124
+ updateInfo,
26125
+ updateSelection
25871
26126
  ]);
25872
26127
  (0, __WEBPACK_EXTERNAL_MODULE_react__.useEffect)(()=>releaseCaptureInteraction, [
25873
26128
  canSlider,
@@ -25879,34 +26134,54 @@ const Slider = ({ id })=>{
25879
26134
  const waterfallSnapshot = getHeatmapWaterfallSnapshot(globalID);
25880
26135
  const currentInfo = infoRef.current;
25881
26136
  const rowsUnchanged = startIndex === currentInfo?.startIndex && endIndex === currentInfo.endIndex;
25882
- const frequenciesUnchanged = void 0 === startFrequency || void 0 === endFrequency ? true : Math.abs(startFrequency - (currentInfo?.startFrequency ?? NaN)) < 1e-4 && Math.abs(endFrequency - (currentInfo?.endFrequency ?? NaN)) < 1e-4;
26137
+ const currentFrequencyRange = selectionRef.current?.frequency;
26138
+ const frequenciesUnchanged = void 0 === startFrequency || void 0 === endFrequency ? true : Math.abs(Math.min(startFrequency, endFrequency) - (currentFrequencyRange?.[0] ?? Number.NaN)) <= 1e-7 && Math.abs(Math.max(startFrequency, endFrequency) - (currentFrequencyRange?.[1] ?? Number.NaN)) <= 1e-7;
25883
26139
  if (rowsUnchanged && frequenciesUnchanged) return;
25884
26140
  const activeRect = currentRectRef.current;
25885
26141
  if (waterfallSnapshot) {
26142
+ const { sourceSize } = waterfallSnapshot;
26143
+ if (sourceSize.width <= 0 || sourceSize.height <= 0) return;
25886
26144
  const validRows = len > 0 && startIndex >= 0 && endIndex < len && endIndex - startIndex >= Math.ceil(MIN_SIZE * len / 100);
25887
- const targetRange = {
25888
- startIndex: validRows ? Math.min(startIndex, endIndex) : currentInfo?.startIndex ?? waterfallSnapshot.range.y[0],
25889
- endIndex: validRows ? Math.max(startIndex, endIndex) : currentInfo?.endIndex ?? waterfallSnapshot.range.y[1] - 1,
25890
- startCol: currentInfo?.startCol ?? waterfallSnapshot.range.x[0],
25891
- endCol: currentInfo?.endCol ?? waterfallSnapshot.range.x[1] - 1
26145
+ const currentSelection = selectionRef.current ?? resolveHeatmapSliderSelectionFromRect({
26146
+ frequencyFormat: frequencyFormatRef.current,
26147
+ rect: activeRect,
26148
+ segments: segmentsRef.current,
26149
+ waterfallSnapshot
26150
+ });
26151
+ const externalFrequencyRange = void 0 !== startFrequency && void 0 !== endFrequency ? [
26152
+ Math.min(startFrequency, endFrequency),
26153
+ Math.max(startFrequency, endFrequency)
26154
+ ] : null;
26155
+ const externalFrequencyRatios = externalFrequencyRange ? resolveHeatmapSliderFrequencyRatioRange({
26156
+ frequencyFormat: frequencyFormatRef.current,
26157
+ frequencyRange: externalFrequencyRange,
26158
+ segments: segmentsRef.current
26159
+ }) : null;
26160
+ if (externalFrequencyRange && !externalFrequencyRatios) return;
26161
+ const targetSelection = {
26162
+ frequency: externalFrequencyRange ?? currentSelection?.frequency ?? null,
26163
+ x: externalFrequencyRatios ?? currentSelection?.x ?? [
26164
+ waterfallSnapshot.range.x[0] / sourceSize.width,
26165
+ waterfallSnapshot.range.x[1] / sourceSize.width
26166
+ ],
26167
+ y: validRows ? [
26168
+ Math.min(startIndex, endIndex),
26169
+ Math.max(startIndex, endIndex) + 1
26170
+ ] : currentSelection?.y ?? [
26171
+ currentInfo?.startIndex ?? waterfallSnapshot.range.y[0],
26172
+ (currentInfo?.endIndex ?? waterfallSnapshot.range.y[1] - 1) + 1
26173
+ ]
25892
26174
  };
25893
- if (void 0 !== startFrequency && void 0 !== endFrequency && frequencyFormat) {
25894
- const minFrequency = Number(frequencyFormat(0));
25895
- const maxFrequency = Number(frequencyFormat(100));
25896
- const frequencySpan = maxFrequency - minFrequency;
25897
- if (Number.isFinite(minFrequency) && Number.isFinite(maxFrequency) && frequencySpan > 0) {
25898
- const startPercent = Math.max(0, Math.min(100, (Math.min(startFrequency, endFrequency) - minFrequency) / frequencySpan * 100));
25899
- const endPercent = Math.max(startPercent, Math.min(100, (Math.max(startFrequency, endFrequency) - minFrequency) / frequencySpan * 100));
25900
- const sourceWidth = waterfallSnapshot.sourceSize.width;
25901
- targetRange.startCol = Math.max(0, Math.min(sourceWidth - 1, Math.floor(startPercent * sourceWidth / 100)));
25902
- targetRange.endCol = Math.max(targetRange.startCol, Math.min(sourceWidth - 1, Math.ceil(endPercent * sourceWidth / 100) - 1));
25903
- }
25904
- }
25905
- if (currentInfo?.startIndex === targetRange.startIndex && currentInfo.endIndex === targetRange.endIndex && currentInfo.startCol === targetRange.startCol && currentInfo.endCol === targetRange.endCol) return;
25906
- const nextRect = resolveHeatmapSourceRangeScreenRect(waterfallSnapshot, targetRange);
26175
+ const continuousRange = resolveHeatmapSliderSelectionSourceRange({
26176
+ selection: targetSelection,
26177
+ waterfallSnapshot
26178
+ });
26179
+ if (!continuousRange) return;
26180
+ const nextRect = resolveHeatmapContinuousSourceRangeScreenRect(waterfallSnapshot, continuousRange);
25907
26181
  if (!nextRect) return;
26182
+ selectionRef.current = targetSelection;
25908
26183
  updateCurrentRect(nextRect);
25909
- const newInfo = calculateSliderInfo(nextRect, targetRange);
26184
+ const newInfo = calculateSliderInfo(nextRect, targetSelection);
25910
26185
  if (newInfo) updateInfo(newInfo, true);
25911
26186
  return;
25912
26187
  }
@@ -25983,21 +26258,46 @@ const Slider = ({ id })=>{
25983
26258
  top: sourceRect.position
25984
26259
  } : void 0);
25985
26260
  } else updateInteractionBounds(void 0);
25986
- if (!waterfallSnapshot || !sourceInfo) {
26261
+ if (!waterfallSnapshot) {
25987
26262
  const newInfo = calculateSliderInfo(currentRectRef.current);
25988
26263
  if (newInfo) updateInfo(newInfo, true);
25989
26264
  return;
25990
26265
  }
25991
- const nextRect = resolveHeatmapSourceRangeScreenRect(waterfallSnapshot, sourceInfo);
26266
+ let selection = selectionRef.current ?? resolveHeatmapSliderSelectionFromRect({
26267
+ frequencyFormat: frequencyFormatRef.current,
26268
+ rect: currentRectRef.current,
26269
+ segments: segmentsRef.current,
26270
+ waterfallSnapshot
26271
+ });
26272
+ if (!selection) return;
26273
+ if (selection.frequency) {
26274
+ const x = resolveHeatmapSliderFrequencyRatioRange({
26275
+ frequencyFormat: frequencyFormatRef.current,
26276
+ frequencyRange: selection.frequency,
26277
+ segments: segmentsRef.current
26278
+ });
26279
+ if (x) selection = {
26280
+ ...selection,
26281
+ x
26282
+ };
26283
+ }
26284
+ selectionRef.current = selection;
26285
+ const continuousSourceRange = resolveHeatmapSliderSelectionSourceRange({
26286
+ selection,
26287
+ waterfallSnapshot
26288
+ });
26289
+ if (!continuousSourceRange) return;
26290
+ const nextRect = resolveHeatmapContinuousSourceRangeScreenRect(waterfallSnapshot, continuousSourceRange);
25992
26291
  if (nextRect) updateCurrentRect(nextRect);
25993
- const refreshedInfo = calculateSliderInfo(nextRect ?? currentRectRef.current, sourceInfo);
26292
+ const refreshedInfo = calculateSliderInfo(nextRect ?? currentRectRef.current, selection);
25994
26293
  if (!refreshedInfo) return;
25995
- const infoChanged = !isSliderInfoEqual(refreshedInfo, sourceInfo);
26294
+ const infoChanged = !sourceInfo || !isSliderInfoEqual(refreshedInfo, sourceInfo);
25996
26295
  const rawDataChanged = notifiedRawDataRef.current !== waterfallSnapshot.rawData;
25997
26296
  if (infoChanged || rawDataChanged) updateInfo(refreshedInfo, true);
25998
26297
  }, [
25999
26298
  updateKey,
26000
26299
  frequencyFormat,
26300
+ segments,
26001
26301
  canSlider,
26002
26302
  globalID,
26003
26303
  calculateSliderInfo,
@@ -26463,7 +26763,7 @@ const HeatmapOverview = /*#__PURE__*/ (0, __WEBPACK_EXTERNAL_MODULE_react__.forw
26463
26763
  context.restore();
26464
26764
  const selectionRange = selectionRangeRef.current;
26465
26765
  if (!selectionRange) return;
26466
- const selectionRect = resolveHeatmapOverviewSourceRangeRect(snapshot, selectionRange);
26766
+ const selectionRect = resolveHeatmapOverviewContinuousSourceRangeRect(snapshot, selectionRange);
26467
26767
  const selectionLeft = selectionRect.left * canvas.width;
26468
26768
  const selectionTop = selectionRect.top * canvas.height;
26469
26769
  const selectionWidth = Math.max(selectionRect.width * canvas.width, 2 * lineWidth);
@@ -26572,12 +26872,12 @@ const HeatmapOverview = /*#__PURE__*/ (0, __WEBPACK_EXTERNAL_MODULE_react__.forw
26572
26872
  updateOverlayColors
26573
26873
  ]);
26574
26874
  (0, __WEBPACK_EXTERNAL_MODULE_react__.useEffect)(()=>{
26575
- const updateSelection = (range)=>{
26576
- selectionRangeRef.current = range;
26875
+ const updateSelection = (selection)=>{
26876
+ selectionRangeRef.current = selection;
26577
26877
  scheduleDraw(false);
26578
26878
  };
26579
- const unsubscribe = subscribeHeatmapCaptureSourceRange(globalID, updateSelection);
26580
- updateSelection(getHeatmapCaptureSourceRange(globalID));
26879
+ const unsubscribe = subscribeHeatmapCaptureSelection(globalID, updateSelection);
26880
+ updateSelection(getHeatmapCaptureSelection(globalID));
26581
26881
  return unsubscribe;
26582
26882
  }, [
26583
26883
  globalID,
@@ -26644,13 +26944,13 @@ const HeatmapOverview = /*#__PURE__*/ (0, __WEBPACK_EXTERNAL_MODULE_react__.forw
26644
26944
  const sourcePoint = resolveHeatmapOverviewSourcePoint(snapshot, point);
26645
26945
  const pointInViewport = isHeatmapOverviewSourcePointInViewport(snapshot, sourcePoint);
26646
26946
  const selectionRange = selectionRangeRef.current;
26647
- if (!pointInViewport && selectionRange && !isHeatmapSourceRangeVisibleInViewport(snapshot, selectionRange)) {
26947
+ if (!pointInViewport && selectionRange && !isHeatmapContinuousSourceRangeVisibleInViewport(snapshot, selectionRange)) {
26648
26948
  const bounds = event.currentTarget.getBoundingClientRect();
26649
26949
  const tolerance = {
26650
26950
  x: bounds.width ? 6 * snapshot.sourceSize.width / bounds.width : 0,
26651
26951
  y: bounds.height ? 6 * snapshot.sourceSize.height / bounds.height : 0
26652
26952
  };
26653
- if (isHeatmapOverviewSourcePointInRange(selectionRange, sourcePoint, tolerance)) return void scheduleRange(calculateHeatmapOverviewSourceRangeCenteredRange(snapshot, selectionRange));
26953
+ if (isHeatmapOverviewSourcePointInContinuousRange(selectionRange, sourcePoint, tolerance)) return void scheduleRange(calculateHeatmapOverviewContinuousSourceRangeCenteredRange(snapshot, selectionRange));
26654
26954
  }
26655
26955
  if (!pointInViewport) return void scheduleRange(calculateHeatmapOverviewCenteredRange(snapshot, point));
26656
26956
  dragRef.current = {
@@ -1,18 +1,53 @@
1
1
  import type { TimestampedFloat32Array, WaterfallSnapshot } from '@rfkit/spectrum-analyzer';
2
- import type { AxisXProps, HeatmapSlider2DData } from '../../../types';
3
- import { type HeatmapSourceRange } from '../viewport.ts';
2
+ import type { AxisXProps, HeatmapSlider2DData, SegmentsType } from '../../../types';
3
+ import { type HeatmapContinuousSourceRange, type HeatmapSourceRange } from '../viewport.ts';
4
+ export interface HeatmapSliderSelection {
5
+ /** 业务频率。外部输入保持原值,指针交互按 axisX.frequencyFormat 解析。 */
6
+ frequency: readonly [start: number, end: number] | null;
7
+ /** 相对完整频率域的连续位置,不受瀑布矩阵列密度影响。 */
8
+ x: readonly [start: number, end: number];
9
+ /** 完整瀑布源数据中的连续行边界。 */
10
+ y: readonly [start: number, end: number];
11
+ }
12
+ export type HeatmapSliderDragType = 'top' | 'bottom' | 'left' | 'right' | 'middle';
13
+ interface HeatmapSliderRect {
14
+ height: number;
15
+ leftPosition: number;
16
+ position: number;
17
+ width: number;
18
+ }
4
19
  interface SliderInfoParams {
5
20
  formatBandwidth: (bandwidth: number) => string;
6
21
  frequencyFormat: AxisXProps['frequencyFormat'];
7
22
  heatmapData: readonly TimestampedFloat32Array[];
8
- rect: {
9
- height: number;
10
- leftPosition: number;
11
- position: number;
12
- width: number;
13
- };
23
+ rect: HeatmapSliderRect;
24
+ segments?: SegmentsType;
25
+ selectionOverride?: Readonly<HeatmapSliderSelection>;
14
26
  sourceRangeOverride?: HeatmapSourceRange;
15
27
  waterfallSnapshot: Readonly<WaterfallSnapshot> | null;
16
28
  }
17
- export declare const calculateHeatmapSliderInfo: ({ formatBandwidth, frequencyFormat, heatmapData, rect, sourceRangeOverride, waterfallSnapshot }: SliderInfoParams) => HeatmapSlider2DData | null;
29
+ export declare const resolveHeatmapSliderFrequencyRatioRange: ({ frequencyFormat, frequencyRange, segments }: {
30
+ frequencyFormat: AxisXProps["frequencyFormat"];
31
+ frequencyRange: readonly [start: number, end: number];
32
+ segments?: SegmentsType;
33
+ }) => readonly [start: number, end: number] | null;
34
+ export declare const resolveHeatmapSliderSelectionFromRect: ({ frequencyFormat, rect, segments, waterfallSnapshot }: {
35
+ frequencyFormat: AxisXProps["frequencyFormat"];
36
+ rect: HeatmapSliderRect;
37
+ segments?: SegmentsType;
38
+ waterfallSnapshot: Readonly<WaterfallSnapshot>;
39
+ }) => HeatmapSliderSelection | null;
40
+ export declare const resolveHeatmapSliderSelectionFromInteraction: ({ dragType, frequencyFormat, rect, segments, startSelection, waterfallSnapshot }: {
41
+ dragType: HeatmapSliderDragType;
42
+ frequencyFormat: AxisXProps["frequencyFormat"];
43
+ rect: HeatmapSliderRect;
44
+ segments?: SegmentsType;
45
+ startSelection: Readonly<HeatmapSliderSelection> | null;
46
+ waterfallSnapshot: Readonly<WaterfallSnapshot>;
47
+ }) => HeatmapSliderSelection | null;
48
+ export declare const resolveHeatmapSliderSelectionSourceRange: ({ selection, waterfallSnapshot }: {
49
+ selection: Readonly<HeatmapSliderSelection>;
50
+ waterfallSnapshot: Readonly<WaterfallSnapshot>;
51
+ }) => HeatmapContinuousSourceRange | null;
52
+ export declare const calculateHeatmapSliderInfo: ({ formatBandwidth, frequencyFormat, heatmapData, rect, segments, selectionOverride, sourceRangeOverride, waterfallSnapshot }: SliderInfoParams) => HeatmapSlider2DData | null;
18
53
  export {};
@@ -1,5 +1,5 @@
1
1
  import type { AxisXProps, HeatmapAreaData, HeatmapCaptureProps } from '../../../types';
2
- import { type HeatmapSourceRange } from '../viewport';
2
+ import { type HeatmapContinuousSourceRange } from '../viewport';
3
3
  interface Coordinates {
4
4
  startLeft: number;
5
5
  endLeft: number;
@@ -27,7 +27,7 @@ export declare const defaultAreaInfoResult: {
27
27
  };
28
28
  export default function calculateAreaInfo({ endLeft, startLeft, startTop, endTop, frequencyFormat, globalID }: AreaInfoParams): HeatmapAreaData;
29
29
  export declare const heatmapCaptureUpdate: (globalID: string, func?: (props: Partial<HeatmapCaptureProps>) => void) => (...args: any[]) => void;
30
- export declare const getHeatmapCaptureSourceRange: (globalID: string) => Readonly<HeatmapSourceRange> | null;
31
- export declare const setHeatmapCaptureSourceRange: (globalID: string, range: Readonly<HeatmapSourceRange> | null) => void;
32
- export declare const subscribeHeatmapCaptureSourceRange: (globalID: string, func: (range: Readonly<HeatmapSourceRange> | null) => void) => (...args: any[]) => void;
30
+ export declare const getHeatmapCaptureSelection: (globalID: string) => Readonly<HeatmapContinuousSourceRange> | null;
31
+ export declare const setHeatmapCaptureSelection: (globalID: string, selection: Readonly<HeatmapContinuousSourceRange> | null) => void;
32
+ export declare const subscribeHeatmapCaptureSelection: (globalID: string, func: (selection: Readonly<HeatmapContinuousSourceRange> | null) => void) => (...args: any[]) => void;
33
33
  export {};
@@ -12,6 +12,10 @@ export interface HeatmapSourceRange {
12
12
  startCol: number;
13
13
  startIndex: number;
14
14
  }
15
+ export interface HeatmapContinuousSourceRange {
16
+ x: readonly [start: number, end: number];
17
+ y: readonly [start: number, end: number];
18
+ }
15
19
  export interface HeatmapSourceRangeScreenRect {
16
20
  height: number;
17
21
  leftPosition: number;
@@ -50,12 +54,20 @@ export declare const resolveHeatmapSourceRange: (snapshot: Readonly<WaterfallSna
50
54
  }) => HeatmapSourceRange | null;
51
55
  export declare const resolveHeatmapSnapshotRangeData: (snapshot: Readonly<WaterfallSnapshot>, range: HeatmapSourceRange) => number[][];
52
56
  export declare const resolveHeatmapSourceRangeScreenRect: (snapshot: Readonly<HeatmapViewportSnapshot>, range: HeatmapSourceRange) => HeatmapSourceRangeScreenRect | null;
57
+ export declare const resolveHeatmapContinuousSourceRangeScreenRect: (snapshot: Readonly<HeatmapViewportSnapshot>, range: Readonly<HeatmapContinuousSourceRange>) => HeatmapSourceRangeScreenRect | null;
58
+ export declare const resolveHeatmapProjectedContinuousSourceRange: (snapshot: Readonly<HeatmapViewportSnapshot>, bounds: {
59
+ endLeft: number;
60
+ endTop: number;
61
+ startLeft: number;
62
+ startTop: number;
63
+ }) => HeatmapContinuousSourceRange | null;
53
64
  export declare const resolveHeatmapProjectedSourceRange: (snapshot: Readonly<HeatmapViewportSnapshot>, bounds: {
54
65
  endLeft: number;
55
66
  endTop: number;
56
67
  startLeft: number;
57
68
  startTop: number;
58
69
  }) => HeatmapSourceRange | null;
70
+ export declare const quantizeHeatmapContinuousSourceRange: (snapshot: Readonly<HeatmapViewportSnapshot>, sourceRange: Readonly<HeatmapContinuousSourceRange>) => HeatmapSourceRange | null;
59
71
  export declare const calculateHeatmapWheelRange: (snapshot: Readonly<WaterfallSnapshot>, wheel: number, coord: {
60
72
  left: number;
61
73
  top: number;
@@ -65,14 +77,18 @@ export declare const resolveHeatmapOverviewSourcePoint: (snapshot: Readonly<Heat
65
77
  top: number;
66
78
  }) => HeatmapOverviewSourcePoint;
67
79
  export declare const resolveHeatmapOverviewViewportRect: (snapshot: Readonly<HeatmapViewportSnapshot>) => HeatmapOverviewViewportRect;
80
+ export declare const resolveHeatmapOverviewContinuousSourceRangeRect: (snapshot: Readonly<HeatmapViewportSnapshot>, range: Readonly<HeatmapContinuousSourceRange>) => HeatmapOverviewViewportRect;
68
81
  export declare const resolveHeatmapOverviewSourceRangeRect: (snapshot: Readonly<HeatmapViewportSnapshot>, range: HeatmapSourceRange) => HeatmapOverviewViewportRect;
82
+ export declare const isHeatmapContinuousSourceRangeVisibleInViewport: (snapshot: Readonly<HeatmapViewportSnapshot>, range: Readonly<HeatmapContinuousSourceRange>) => boolean;
69
83
  export declare const isHeatmapSourceRangeVisibleInViewport: (snapshot: Readonly<HeatmapViewportSnapshot>, range: HeatmapSourceRange) => boolean;
84
+ export declare const isHeatmapOverviewSourcePointInContinuousRange: (range: Readonly<HeatmapContinuousSourceRange>, point: HeatmapOverviewSourcePoint, tolerance?: HeatmapOverviewSourcePoint) => boolean;
70
85
  export declare const isHeatmapOverviewSourcePointInRange: (range: HeatmapSourceRange, point: HeatmapOverviewSourcePoint, tolerance?: HeatmapOverviewSourcePoint) => boolean;
71
86
  export declare const isHeatmapViewportFullRange: (snapshot: Readonly<HeatmapViewportSnapshot>) => boolean;
72
87
  export declare const calculateHeatmapOverviewCenteredRange: (snapshot: Readonly<HeatmapViewportSnapshot>, point: {
73
88
  left: number;
74
89
  top: number;
75
90
  }) => HeatmapViewportRange;
91
+ export declare const calculateHeatmapOverviewContinuousSourceRangeCenteredRange: (snapshot: Readonly<HeatmapViewportSnapshot>, range: Readonly<HeatmapContinuousSourceRange>) => HeatmapViewportRange;
76
92
  export declare const calculateHeatmapOverviewSourceRangeCenteredRange: (snapshot: Readonly<HeatmapViewportSnapshot>, range: HeatmapSourceRange) => HeatmapViewportRange;
77
93
  export declare const calculateHeatmapOverviewDragRange: (snapshot: Readonly<HeatmapViewportSnapshot>, point: {
78
94
  left: number;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@rfkit/charts",
3
- "version": "1.10.3",
3
+ "version": "1.10.4",
4
4
  "type": "module",
5
5
  "description": "Chart components for wireless monitoring web applications",
6
6
  "exports": {