@rfkit/charts 1.10.3 → 1.10.5

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);
@@ -23144,6 +23175,18 @@ function getUnifiedSignalKey(item) {
23144
23175
  }
23145
23176
  const buildSignalTree = (signalItems)=>{
23146
23177
  const leafKeysWithData = new Set(signalItems.map(getSignalLeafKey));
23178
+ const explicitLeafColors = new Map();
23179
+ for (const item of signalItems){
23180
+ if ('string' != typeof item.color || '' === item.color.trim()) continue;
23181
+ const leafKey = getSignalLeafKey(item);
23182
+ if (!explicitLeafColors.has(leafKey)) explicitLeafColors.set(leafKey, item.color);
23183
+ }
23184
+ const resolveSharedExplicitColor = (leafKeys)=>{
23185
+ const colors = new Set(leafKeys.map((leafKey)=>explicitLeafColors.get(leafKey)).filter((color)=>null != color));
23186
+ return 1 === colors.size ? [
23187
+ ...colors
23188
+ ][0] : void 0;
23189
+ };
23147
23190
  const sources = [];
23148
23191
  for (const source of SIGNAL_SOURCE_ORDER){
23149
23192
  const sourceDataLeafKeys = [
@@ -23168,7 +23211,8 @@ const buildSignalTree = (signalItems)=>{
23168
23211
  level,
23169
23212
  label: SIGNAL_LEVEL_MAP[level].name,
23170
23213
  leafKey,
23171
- hasData: leafKeysWithData.has(leafKey)
23214
+ hasData: leafKeysWithData.has(leafKey),
23215
+ color: explicitLeafColors.get(leafKey)
23172
23216
  };
23173
23217
  });
23174
23218
  const leafKeys = levels.map((level)=>level.leafKey);
@@ -23180,6 +23224,7 @@ const buildSignalTree = (signalItems)=>{
23180
23224
  leafKeys,
23181
23225
  dataLeafKeys,
23182
23226
  hasData: dataLeafKeys.length > 0,
23227
+ color: resolveSharedExplicitColor(dataLeafKeys),
23183
23228
  levels: signalType === SignalType.LEGITIMATE ? levels.filter((level)=>level.hasData) : []
23184
23229
  };
23185
23230
  });
@@ -24376,16 +24421,16 @@ function calculateAreaInfo({ endLeft, startLeft, startTop, endTop, frequencyForm
24376
24421
  return result;
24377
24422
  }
24378
24423
  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);
24424
+ const getHeatmapCaptureSelectionKey = (globalID)=>`heatmapCaptureSelection-${globalID}`;
24425
+ const getHeatmapCaptureSelectionSubscriptionKey = (globalID)=>`${getHeatmapCaptureSelectionKey(globalID)}-update`;
24426
+ 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;
24427
+ const getHeatmapCaptureSelection = (globalID)=>subscription_openData(getHeatmapCaptureSelectionKey(globalID), void 0, null, globalID);
24428
+ const setHeatmapCaptureSelection = (globalID, selection)=>{
24429
+ if (!globalID || isSelectionEqual(getHeatmapCaptureSelection(globalID), selection)) return;
24430
+ subscription_openData(getHeatmapCaptureSelectionKey(globalID), selection, null, globalID);
24431
+ subscription_createSubscriptionManager(getHeatmapCaptureSelectionSubscriptionKey(globalID))(selection);
24432
+ };
24433
+ const subscribeHeatmapCaptureSelection = (globalID, func)=>subscription_createSubscriptionManager(getHeatmapCaptureSelectionSubscriptionKey(globalID), 'HeatmapOverview', func, globalID);
24389
24434
  const Area_COMPONENT_KEY = constants_ToolType.HeatmapCapture;
24390
24435
  const Area_Area = (props)=>{
24391
24436
  const { state: { heatmapCapture, axisX: { frequencyFormat, unit }, system, globalID } } = useStore_useStore();
@@ -25491,7 +25536,7 @@ const DragSelectBox = ({ enabled, defaultRect, minSize = 0, minWidth, minHeight,
25491
25536
  return nextRect;
25492
25537
  };
25493
25538
  const handleBoxMouseDown = (event, type, startRect)=>{
25494
- if (onDragStart?.() === false) return;
25539
+ if (onDragStart?.(type) === false) return;
25495
25540
  event.preventDefault();
25496
25541
  event.stopPropagation();
25497
25542
  handleMouseDown(event, type, startRect);
@@ -25686,12 +25731,191 @@ const DragSelectBox = ({ enabled, defaultRect, minSize = 0, minWidth, minHeight,
25686
25731
  });
25687
25732
  };
25688
25733
  const components_DragSelectBox = DragSelectBox;
25689
- const sliderInfo_PRECISION = 1e4;
25690
- const calculateHeatmapSliderInfo = ({ formatBandwidth, frequencyFormat, heatmapData, rect, sourceRangeOverride, waterfallSnapshot })=>{
25734
+ const sliderInfo_PRECISION = 1e6;
25735
+ const sliderInfo_clamp = (value1, min, max)=>Math.min(max, Math.max(min, value1));
25736
+ const resolveFrequencyExtent = (frequencyFormat)=>{
25737
+ if (!frequencyFormat) return null;
25738
+ const start = Number(frequencyFormat(0));
25739
+ const end = Number(frequencyFormat(100));
25740
+ if (!Number.isFinite(start) || !Number.isFinite(end) || start === end) return null;
25741
+ return [
25742
+ start,
25743
+ end
25744
+ ];
25745
+ };
25746
+ const resolveContinuousFrequencyFromSegments = (ratio, segments, edge)=>{
25747
+ if (!segments?.length) return null;
25748
+ const percent = 100 * sliderInfo_clamp(ratio, 0, 1);
25749
+ const lastIndex = segments.length - 1;
25750
+ for(let index = 0; index < segments.length; index += 1){
25751
+ const segment = segments[index];
25752
+ const progressStart = Math.min(segment.progress[0], segment.progress[1]);
25753
+ const progressEnd = Math.max(segment.progress[0], segment.progress[1]);
25754
+ const contains = 'start' === edge ? percent >= progressStart && (percent < progressEnd || index === lastIndex && percent <= progressEnd) : (percent > progressStart || 0 === index) && percent <= progressEnd;
25755
+ if (!contains) continue;
25756
+ const progressSpan = progressEnd - progressStart;
25757
+ const localRatio = progressSpan ? (percent - progressStart) / progressSpan : 0;
25758
+ return segment.start + (segment.stop - segment.start) * localRatio;
25759
+ }
25760
+ return null;
25761
+ };
25762
+ const resolveFrequencyFromRatio = (ratio, segments, frequencyFormat, edge)=>{
25763
+ const value1 = frequencyFormat?.(100 * sliderInfo_clamp(ratio, 0, 1));
25764
+ const frequency = void 0 === value1 ? Number.NaN : Number(value1);
25765
+ if (Number.isFinite(frequency)) return frequency;
25766
+ return resolveContinuousFrequencyFromSegments(ratio, segments, edge);
25767
+ };
25768
+ const resolveRatioFromFrequency = (frequency, segments, frequencyFormat)=>{
25769
+ if (segments?.length) {
25770
+ let nearest = null;
25771
+ for (const segment of segments){
25772
+ const frequencyStart = segment.start;
25773
+ const frequencyEnd = segment.stop;
25774
+ const frequencyMin = Math.min(frequencyStart, frequencyEnd);
25775
+ const frequencyMax = Math.max(frequencyStart, frequencyEnd);
25776
+ const progressStart = segment.progress[0] / 100;
25777
+ const progressEnd = segment.progress[1] / 100;
25778
+ if (frequency >= frequencyMin && frequency <= frequencyMax) {
25779
+ const frequencySpan = frequencyEnd - frequencyStart;
25780
+ const localRatio = frequencySpan ? (frequency - frequencyStart) / frequencySpan : 0;
25781
+ return sliderInfo_clamp(progressStart + (progressEnd - progressStart) * localRatio, 0, 1);
25782
+ }
25783
+ const candidates = [
25784
+ {
25785
+ distance: Math.abs(frequency - frequencyStart),
25786
+ ratio: progressStart
25787
+ },
25788
+ {
25789
+ distance: Math.abs(frequency - frequencyEnd),
25790
+ ratio: progressEnd
25791
+ }
25792
+ ];
25793
+ for (const candidate of candidates)if (!nearest || candidate.distance < nearest.distance) nearest = candidate;
25794
+ }
25795
+ if (nearest) return sliderInfo_clamp(nearest.ratio, 0, 1);
25796
+ }
25797
+ const extent = resolveFrequencyExtent(frequencyFormat);
25798
+ if (!extent) return null;
25799
+ return sliderInfo_clamp((frequency - extent[0]) / (extent[1] - extent[0]), 0, 1);
25800
+ };
25801
+ const resolveHeatmapSliderFrequencyRatioRange = ({ frequencyFormat, frequencyRange, segments })=>{
25802
+ const start = resolveRatioFromFrequency(frequencyRange[0], segments, frequencyFormat);
25803
+ const end = resolveRatioFromFrequency(frequencyRange[1], segments, frequencyFormat);
25804
+ if (null === start || null === end) return null;
25805
+ return [
25806
+ Math.min(start, end),
25807
+ Math.max(start, end)
25808
+ ];
25809
+ };
25810
+ const resolveFrequencyRangeFromRatios = (ratios, segments, frequencyFormat)=>{
25811
+ const start = resolveFrequencyFromRatio(ratios[0], segments, frequencyFormat, 'start');
25812
+ const end = resolveFrequencyFromRatio(ratios[1], segments, frequencyFormat, 'end');
25813
+ if (null === start || null === end) return null;
25814
+ return [
25815
+ Math.min(start, end),
25816
+ Math.max(start, end)
25817
+ ];
25818
+ };
25819
+ const resolveHeatmapSliderSelectionFromRect = ({ frequencyFormat, rect, segments, waterfallSnapshot })=>{
25820
+ const continuousRange = resolveHeatmapProjectedContinuousSourceRange(waterfallSnapshot, {
25821
+ startLeft: rect.leftPosition,
25822
+ endLeft: rect.leftPosition + rect.width,
25823
+ startTop: rect.position + rect.height,
25824
+ endTop: rect.position
25825
+ });
25826
+ if (!continuousRange || waterfallSnapshot.sourceSize.width <= 0) return null;
25827
+ const x = [
25828
+ continuousRange.x[0] / waterfallSnapshot.sourceSize.width,
25829
+ continuousRange.x[1] / waterfallSnapshot.sourceSize.width
25830
+ ];
25831
+ return {
25832
+ frequency: resolveFrequencyRangeFromRatios(x, segments, frequencyFormat),
25833
+ x,
25834
+ y: continuousRange.y
25835
+ };
25836
+ };
25837
+ const resolveHeatmapSliderSelectionFromInteraction = ({ dragType, frequencyFormat, rect, segments, startSelection, waterfallSnapshot })=>{
25838
+ const current = resolveHeatmapSliderSelectionFromRect({
25839
+ frequencyFormat,
25840
+ rect,
25841
+ segments,
25842
+ waterfallSnapshot
25843
+ });
25844
+ if (!current || !startSelection) return current;
25845
+ switch(dragType){
25846
+ case 'left':
25847
+ return {
25848
+ frequency: current.frequency && startSelection.frequency ? [
25849
+ current.frequency[0],
25850
+ startSelection.frequency[1]
25851
+ ] : current.frequency,
25852
+ x: [
25853
+ current.x[0],
25854
+ startSelection.x[1]
25855
+ ],
25856
+ y: startSelection.y
25857
+ };
25858
+ case 'right':
25859
+ return {
25860
+ frequency: current.frequency && startSelection.frequency ? [
25861
+ startSelection.frequency[0],
25862
+ current.frequency[1]
25863
+ ] : current.frequency,
25864
+ x: [
25865
+ startSelection.x[0],
25866
+ current.x[1]
25867
+ ],
25868
+ y: startSelection.y
25869
+ };
25870
+ case 'top':
25871
+ return {
25872
+ frequency: startSelection.frequency,
25873
+ x: startSelection.x,
25874
+ y: [
25875
+ startSelection.y[0],
25876
+ current.y[1]
25877
+ ]
25878
+ };
25879
+ case 'bottom':
25880
+ return {
25881
+ frequency: startSelection.frequency,
25882
+ x: startSelection.x,
25883
+ y: [
25884
+ current.y[0],
25885
+ startSelection.y[1]
25886
+ ]
25887
+ };
25888
+ case 'middle':
25889
+ return current;
25890
+ }
25891
+ };
25892
+ const resolveHeatmapSliderSelectionSourceRange = ({ selection, waterfallSnapshot })=>{
25893
+ const { sourceSize } = waterfallSnapshot;
25894
+ if (sourceSize.width <= 0 || sourceSize.height <= 0) return null;
25895
+ const xStart = sliderInfo_clamp(Math.min(selection.x[0], selection.x[1]), 0, 1);
25896
+ const xEnd = sliderInfo_clamp(Math.max(selection.x[0], selection.x[1]), xStart, 1);
25897
+ const yStart = sliderInfo_clamp(Math.min(selection.y[0], selection.y[1]), 0, sourceSize.height);
25898
+ const yEnd = sliderInfo_clamp(Math.max(selection.y[0], selection.y[1]), yStart, sourceSize.height);
25899
+ return {
25900
+ x: [
25901
+ xStart * sourceSize.width,
25902
+ xEnd * sourceSize.width
25903
+ ],
25904
+ y: [
25905
+ yStart,
25906
+ yEnd
25907
+ ]
25908
+ };
25909
+ };
25910
+ const calculateHeatmapSliderInfo = ({ formatBandwidth, frequencyFormat, heatmapData, rect, segments, selectionOverride, sourceRangeOverride, waterfallSnapshot })=>{
25691
25911
  const len = heatmapData.length;
25692
25912
  if (len <= 0) return null;
25693
25913
  const { position, height, leftPosition: left, width } = rect;
25694
- const sourceRange = sourceRangeOverride ?? (waterfallSnapshot ? resolveHeatmapProjectedSourceRange(waterfallSnapshot, {
25914
+ const continuousSourceRangeOverride = waterfallSnapshot && selectionOverride ? resolveHeatmapSliderSelectionSourceRange({
25915
+ selection: selectionOverride,
25916
+ waterfallSnapshot
25917
+ }) : null;
25918
+ const sourceRange = sourceRangeOverride ?? (waterfallSnapshot && continuousSourceRangeOverride ? quantizeHeatmapContinuousSourceRange(waterfallSnapshot, continuousSourceRangeOverride) : null) ?? (waterfallSnapshot ? resolveHeatmapProjectedSourceRange(waterfallSnapshot, {
25695
25919
  startLeft: left,
25696
25920
  endLeft: left + width,
25697
25921
  startTop: position + height,
@@ -25709,12 +25933,16 @@ const calculateHeatmapSliderInfo = ({ formatBandwidth, frequencyFormat, heatmapD
25709
25933
  if (!columnRange) return null;
25710
25934
  const { startCol, endCol } = columnRange;
25711
25935
  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;
25936
+ const startLeft = selectionOverride ? 100 * selectionOverride.x[0] : sourceRangeOverride && sourceWidth > 0 ? 100 * startCol / sourceWidth : waterfallSnapshot ? resolveHeatmapSourceLeft(left, waterfallSnapshot) : left;
25937
+ const endLeft = selectionOverride ? 100 * selectionOverride.x[1] : sourceRangeOverride && sourceWidth > 0 ? (endCol + 1) * 100 / sourceWidth : waterfallSnapshot ? resolveHeatmapSourceLeft(left + width, waterfallSnapshot) : left + width;
25938
+ const fallbackFrequencyRange = resolveFrequencyRangeFromRatios([
25939
+ startLeft / 100,
25940
+ endLeft / 100
25941
+ ], segments, frequencyFormat);
25942
+ const startFrequencyValue = selectionOverride?.frequency?.[0] ?? fallbackFrequencyRange?.[0];
25943
+ const endFrequencyValue = selectionOverride?.frequency?.[1] ?? fallbackFrequencyRange?.[1];
25944
+ const startFrequency = Number.isFinite(startFrequencyValue) ? Math.round(Number(startFrequencyValue) * sliderInfo_PRECISION) / sliderInfo_PRECISION : 0;
25945
+ const endFrequency = Number.isFinite(endFrequencyValue) ? Math.round(Number(endFrequencyValue) * sliderInfo_PRECISION) / sliderInfo_PRECISION : 0;
25718
25946
  const bandwidth = Math.abs(endFrequency - startFrequency);
25719
25947
  return {
25720
25948
  startIndex,
@@ -25747,7 +25975,7 @@ const isRectEqual = (left, right)=>Math.abs(left.position - right.position) <= 0
25747
25975
  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
25976
  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
25977
  const Slider = ({ id })=>{
25750
- const { state: { globalID, heatmapCapture: { type, show, display, interval, onChange }, axisX: { frequencyFormat, unit } } } = useStore_useStore();
25978
+ const { state: { globalID, heatmapCapture: { type, show, display, interval, onChange }, axisX: { frequencyFormat, unit }, segments } } = useStore_useStore();
25751
25979
  const canSlider = (0, __WEBPACK_EXTERNAL_MODULE_react__.useMemo)(()=>type === heatmap_HeatmapCaptureType.Slider && show && display, [
25752
25980
  type,
25753
25981
  show,
@@ -25759,8 +25987,14 @@ const Slider = ({ id })=>{
25759
25987
  const [currentRect, setCurrentRect] = (0, __WEBPACK_EXTERNAL_MODULE_react__.useState)(DEFAULT_RECT);
25760
25988
  const [interactionBounds, setInteractionBounds] = (0, __WEBPACK_EXTERNAL_MODULE_react__.useState)();
25761
25989
  const currentRectRef = (0, __WEBPACK_EXTERNAL_MODULE_react__.useRef)(currentRect);
25990
+ const selectionRef = (0, __WEBPACK_EXTERNAL_MODULE_react__.useRef)(null);
25991
+ const dragStartSelectionRef = (0, __WEBPACK_EXTERNAL_MODULE_react__.useRef)(null);
25992
+ const dragStartRectRef = (0, __WEBPACK_EXTERNAL_MODULE_react__.useRef)(null);
25993
+ const activeDragTypeRef = (0, __WEBPACK_EXTERNAL_MODULE_react__.useRef)(null);
25762
25994
  const frequencyFormatRef = (0, __WEBPACK_EXTERNAL_MODULE_react__.useRef)(frequencyFormat);
25995
+ const segmentsRef = (0, __WEBPACK_EXTERNAL_MODULE_react__.useRef)(segments);
25763
25996
  frequencyFormatRef.current = frequencyFormat;
25997
+ segmentsRef.current = segments;
25764
25998
  const updateCurrentRect = (0, __WEBPACK_EXTERNAL_MODULE_react__.useCallback)((rect)=>{
25765
25999
  currentRectRef.current = rect;
25766
26000
  setCurrentRect((current)=>isRectEqual(current, rect) ? current : rect);
@@ -25771,40 +26005,39 @@ const Slider = ({ id })=>{
25771
26005
  return isBoundsEqual(current, bounds) ? current : bounds;
25772
26006
  });
25773
26007
  }, []);
26008
+ const updateSelectionSnapshot = (0, __WEBPACK_EXTERNAL_MODULE_react__.useCallback)((nextInfo)=>{
26009
+ const waterfallSnapshot = getHeatmapWaterfallSnapshot(globalID);
26010
+ const selection = selectionRef.current;
26011
+ const continuousRange = waterfallSnapshot && selection ? resolveHeatmapSliderSelectionSourceRange({
26012
+ selection,
26013
+ waterfallSnapshot
26014
+ }) : null;
26015
+ setHeatmapCaptureSelection(globalID, canSlider && nextInfo && continuousRange ? continuousRange : null);
26016
+ }, [
26017
+ canSlider,
26018
+ globalID
26019
+ ]);
25774
26020
  const updateInfo = (0, __WEBPACK_EXTERNAL_MODULE_react__.useCallback)((nextInfo, notify)=>{
25775
26021
  infoRef.current = nextInfo;
25776
26022
  setInfo(nextInfo);
25777
- if (canSlider) setHeatmapCaptureSourceRange(globalID, {
25778
- endCol: nextInfo.endCol,
25779
- endIndex: nextInfo.endIndex,
25780
- startCol: nextInfo.startCol,
25781
- startIndex: nextInfo.startIndex
25782
- });
26023
+ updateSelectionSnapshot(nextInfo);
25783
26024
  if (notify) {
25784
26025
  notifiedRawDataRef.current = getHeatmapWaterfallSnapshot(globalID)?.rawData ?? null;
25785
26026
  onChange?.(nextInfo);
25786
26027
  }
25787
26028
  }, [
25788
- canSlider,
25789
26029
  globalID,
25790
- onChange
26030
+ onChange,
26031
+ updateSelectionSnapshot
25791
26032
  ]);
25792
26033
  (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);
26034
+ if (!globalID) return;
26035
+ updateSelectionSnapshot(canSlider ? infoRef.current ?? null : null);
26036
+ return ()=>setHeatmapCaptureSelection(globalID, null);
25805
26037
  }, [
25806
26038
  canSlider,
25807
- globalID
26039
+ globalID,
26040
+ updateSelectionSnapshot
25808
26041
  ]);
25809
26042
  const getHeatmapData = (0, __WEBPACK_EXTERNAL_MODULE_react__.useCallback)(()=>{
25810
26043
  const waterfallData = withDatabase(globalID).getAllRawData().waterfallData ?? [];
@@ -25815,20 +26048,42 @@ const Slider = ({ id })=>{
25815
26048
  }, [
25816
26049
  globalID
25817
26050
  ]);
25818
- const calculateSliderInfo = (0, __WEBPACK_EXTERNAL_MODULE_react__.useCallback)((r, sourceRangeOverride)=>{
26051
+ const calculateSliderInfo = (0, __WEBPACK_EXTERNAL_MODULE_react__.useCallback)((r, selectionOverride)=>{
25819
26052
  const [heatmapData] = getHeatmapData();
25820
26053
  return calculateHeatmapSliderInfo({
25821
26054
  formatBandwidth: getFrequencyToFixed,
25822
26055
  frequencyFormat: frequencyFormatRef.current,
25823
26056
  heatmapData,
25824
26057
  rect: r,
25825
- sourceRangeOverride,
26058
+ segments: segmentsRef.current,
26059
+ selectionOverride,
25826
26060
  waterfallSnapshot: getHeatmapWaterfallSnapshot(globalID)
25827
26061
  });
25828
26062
  }, [
25829
26063
  getHeatmapData,
25830
26064
  globalID
25831
26065
  ]);
26066
+ const updateSelection = (0, __WEBPACK_EXTERNAL_MODULE_react__.useCallback)((rect, dragType)=>{
26067
+ const waterfallSnapshot = getHeatmapWaterfallSnapshot(globalID);
26068
+ if (!waterfallSnapshot) return null;
26069
+ const nextSelection = dragType ? resolveHeatmapSliderSelectionFromInteraction({
26070
+ dragType,
26071
+ frequencyFormat: frequencyFormatRef.current,
26072
+ rect,
26073
+ segments: segmentsRef.current,
26074
+ startSelection: dragStartSelectionRef.current,
26075
+ waterfallSnapshot
26076
+ }) : resolveHeatmapSliderSelectionFromRect({
26077
+ frequencyFormat: frequencyFormatRef.current,
26078
+ rect,
26079
+ segments: segmentsRef.current,
26080
+ waterfallSnapshot
26081
+ });
26082
+ if (nextSelection) selectionRef.current = nextSelection;
26083
+ return nextSelection;
26084
+ }, [
26085
+ globalID
26086
+ ]);
25832
26087
  const [updateKey, setUpdateKey] = (0, __WEBPACK_EXTERNAL_MODULE_react__.useState)(0);
25833
26088
  (0, __WEBPACK_EXTERNAL_MODULE_react__.useEffect)(()=>{
25834
26089
  if (!id || !globalID) return;
@@ -25838,18 +26093,32 @@ const Slider = ({ id })=>{
25838
26093
  globalID
25839
26094
  ]);
25840
26095
  const handleChange = (0, __WEBPACK_EXTERNAL_MODULE_react__.useCallback)((rect)=>{
26096
+ const startRect = dragStartRectRef.current;
26097
+ if (startRect && isRectEqual(startRect, rect)) return;
25841
26098
  updateCurrentRect(rect);
25842
- const newInfo = calculateSliderInfo(rect);
26099
+ const selection = updateSelection(rect, activeDragTypeRef.current);
26100
+ const newInfo = calculateSliderInfo(rect, selection ?? void 0);
25843
26101
  if (newInfo) updateInfo(newInfo, false);
25844
26102
  }, [
25845
26103
  calculateSliderInfo,
25846
26104
  updateCurrentRect,
25847
- updateInfo
26105
+ updateInfo,
26106
+ updateSelection
25848
26107
  ]);
25849
- const claimCaptureInteraction = (0, __WEBPACK_EXTERNAL_MODULE_react__.useCallback)(()=>compareEventPriority(id, constants_ToolType.HeatmapCapture), [
26108
+ const claimCaptureInteraction = (0, __WEBPACK_EXTERNAL_MODULE_react__.useCallback)((dragType)=>{
26109
+ const claimed = compareEventPriority(id, constants_ToolType.HeatmapCapture);
26110
+ if (false === claimed) return false;
26111
+ activeDragTypeRef.current = dragType;
26112
+ dragStartSelectionRef.current = selectionRef.current;
26113
+ dragStartRectRef.current = currentRectRef.current;
26114
+ return claimed;
26115
+ }, [
25850
26116
  id
25851
26117
  ]);
25852
26118
  const releaseCaptureInteraction = (0, __WEBPACK_EXTERNAL_MODULE_react__.useCallback)(()=>{
26119
+ activeDragTypeRef.current = null;
26120
+ dragStartSelectionRef.current = null;
26121
+ dragStartRectRef.current = null;
25853
26122
  createMouseUpEventManager(getEID(id))();
25854
26123
  resetEventLevel(id, constants_ToolType.HeatmapCapture);
25855
26124
  }, [
@@ -25857,8 +26126,8 @@ const Slider = ({ id })=>{
25857
26126
  ]);
25858
26127
  const handleDragEnd = (0, __WEBPACK_EXTERNAL_MODULE_react__.useCallback)((rect)=>{
25859
26128
  try {
25860
- updateCurrentRect(rect);
25861
- const newInfo = calculateSliderInfo(rect);
26129
+ const selection = selectionRef.current ?? updateSelection(rect, null);
26130
+ const newInfo = calculateSliderInfo(rect, selection ?? void 0);
25862
26131
  if (newInfo) updateInfo(newInfo, true);
25863
26132
  } finally{
25864
26133
  releaseCaptureInteraction();
@@ -25866,8 +26135,8 @@ const Slider = ({ id })=>{
25866
26135
  }, [
25867
26136
  calculateSliderInfo,
25868
26137
  releaseCaptureInteraction,
25869
- updateCurrentRect,
25870
- updateInfo
26138
+ updateInfo,
26139
+ updateSelection
25871
26140
  ]);
25872
26141
  (0, __WEBPACK_EXTERNAL_MODULE_react__.useEffect)(()=>releaseCaptureInteraction, [
25873
26142
  canSlider,
@@ -25879,34 +26148,54 @@ const Slider = ({ id })=>{
25879
26148
  const waterfallSnapshot = getHeatmapWaterfallSnapshot(globalID);
25880
26149
  const currentInfo = infoRef.current;
25881
26150
  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;
26151
+ const currentFrequencyRange = selectionRef.current?.frequency;
26152
+ 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
26153
  if (rowsUnchanged && frequenciesUnchanged) return;
25884
26154
  const activeRect = currentRectRef.current;
25885
26155
  if (waterfallSnapshot) {
26156
+ const { sourceSize } = waterfallSnapshot;
26157
+ if (sourceSize.width <= 0 || sourceSize.height <= 0) return;
25886
26158
  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
26159
+ const currentSelection = selectionRef.current ?? resolveHeatmapSliderSelectionFromRect({
26160
+ frequencyFormat: frequencyFormatRef.current,
26161
+ rect: activeRect,
26162
+ segments: segmentsRef.current,
26163
+ waterfallSnapshot
26164
+ });
26165
+ const externalFrequencyRange = void 0 !== startFrequency && void 0 !== endFrequency ? [
26166
+ Math.min(startFrequency, endFrequency),
26167
+ Math.max(startFrequency, endFrequency)
26168
+ ] : null;
26169
+ const externalFrequencyRatios = externalFrequencyRange ? resolveHeatmapSliderFrequencyRatioRange({
26170
+ frequencyFormat: frequencyFormatRef.current,
26171
+ frequencyRange: externalFrequencyRange,
26172
+ segments: segmentsRef.current
26173
+ }) : null;
26174
+ if (externalFrequencyRange && !externalFrequencyRatios) return;
26175
+ const targetSelection = {
26176
+ frequency: externalFrequencyRange ?? currentSelection?.frequency ?? null,
26177
+ x: externalFrequencyRatios ?? currentSelection?.x ?? [
26178
+ waterfallSnapshot.range.x[0] / sourceSize.width,
26179
+ waterfallSnapshot.range.x[1] / sourceSize.width
26180
+ ],
26181
+ y: validRows ? [
26182
+ Math.min(startIndex, endIndex),
26183
+ Math.max(startIndex, endIndex) + 1
26184
+ ] : currentSelection?.y ?? [
26185
+ currentInfo?.startIndex ?? waterfallSnapshot.range.y[0],
26186
+ (currentInfo?.endIndex ?? waterfallSnapshot.range.y[1] - 1) + 1
26187
+ ]
25892
26188
  };
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);
26189
+ const continuousRange = resolveHeatmapSliderSelectionSourceRange({
26190
+ selection: targetSelection,
26191
+ waterfallSnapshot
26192
+ });
26193
+ if (!continuousRange) return;
26194
+ const nextRect = resolveHeatmapContinuousSourceRangeScreenRect(waterfallSnapshot, continuousRange);
25907
26195
  if (!nextRect) return;
26196
+ selectionRef.current = targetSelection;
25908
26197
  updateCurrentRect(nextRect);
25909
- const newInfo = calculateSliderInfo(nextRect, targetRange);
26198
+ const newInfo = calculateSliderInfo(nextRect, targetSelection);
25910
26199
  if (newInfo) updateInfo(newInfo, true);
25911
26200
  return;
25912
26201
  }
@@ -25983,21 +26272,46 @@ const Slider = ({ id })=>{
25983
26272
  top: sourceRect.position
25984
26273
  } : void 0);
25985
26274
  } else updateInteractionBounds(void 0);
25986
- if (!waterfallSnapshot || !sourceInfo) {
26275
+ if (!waterfallSnapshot) {
25987
26276
  const newInfo = calculateSliderInfo(currentRectRef.current);
25988
26277
  if (newInfo) updateInfo(newInfo, true);
25989
26278
  return;
25990
26279
  }
25991
- const nextRect = resolveHeatmapSourceRangeScreenRect(waterfallSnapshot, sourceInfo);
26280
+ let selection = selectionRef.current ?? resolveHeatmapSliderSelectionFromRect({
26281
+ frequencyFormat: frequencyFormatRef.current,
26282
+ rect: currentRectRef.current,
26283
+ segments: segmentsRef.current,
26284
+ waterfallSnapshot
26285
+ });
26286
+ if (!selection) return;
26287
+ if (selection.frequency) {
26288
+ const x = resolveHeatmapSliderFrequencyRatioRange({
26289
+ frequencyFormat: frequencyFormatRef.current,
26290
+ frequencyRange: selection.frequency,
26291
+ segments: segmentsRef.current
26292
+ });
26293
+ if (x) selection = {
26294
+ ...selection,
26295
+ x
26296
+ };
26297
+ }
26298
+ selectionRef.current = selection;
26299
+ const continuousSourceRange = resolveHeatmapSliderSelectionSourceRange({
26300
+ selection,
26301
+ waterfallSnapshot
26302
+ });
26303
+ if (!continuousSourceRange) return;
26304
+ const nextRect = resolveHeatmapContinuousSourceRangeScreenRect(waterfallSnapshot, continuousSourceRange);
25992
26305
  if (nextRect) updateCurrentRect(nextRect);
25993
- const refreshedInfo = calculateSliderInfo(nextRect ?? currentRectRef.current, sourceInfo);
26306
+ const refreshedInfo = calculateSliderInfo(nextRect ?? currentRectRef.current, selection);
25994
26307
  if (!refreshedInfo) return;
25995
- const infoChanged = !isSliderInfoEqual(refreshedInfo, sourceInfo);
26308
+ const infoChanged = !sourceInfo || !isSliderInfoEqual(refreshedInfo, sourceInfo);
25996
26309
  const rawDataChanged = notifiedRawDataRef.current !== waterfallSnapshot.rawData;
25997
26310
  if (infoChanged || rawDataChanged) updateInfo(refreshedInfo, true);
25998
26311
  }, [
25999
26312
  updateKey,
26000
26313
  frequencyFormat,
26314
+ segments,
26001
26315
  canSlider,
26002
26316
  globalID,
26003
26317
  calculateSliderInfo,
@@ -26463,7 +26777,7 @@ const HeatmapOverview = /*#__PURE__*/ (0, __WEBPACK_EXTERNAL_MODULE_react__.forw
26463
26777
  context.restore();
26464
26778
  const selectionRange = selectionRangeRef.current;
26465
26779
  if (!selectionRange) return;
26466
- const selectionRect = resolveHeatmapOverviewSourceRangeRect(snapshot, selectionRange);
26780
+ const selectionRect = resolveHeatmapOverviewContinuousSourceRangeRect(snapshot, selectionRange);
26467
26781
  const selectionLeft = selectionRect.left * canvas.width;
26468
26782
  const selectionTop = selectionRect.top * canvas.height;
26469
26783
  const selectionWidth = Math.max(selectionRect.width * canvas.width, 2 * lineWidth);
@@ -26572,12 +26886,12 @@ const HeatmapOverview = /*#__PURE__*/ (0, __WEBPACK_EXTERNAL_MODULE_react__.forw
26572
26886
  updateOverlayColors
26573
26887
  ]);
26574
26888
  (0, __WEBPACK_EXTERNAL_MODULE_react__.useEffect)(()=>{
26575
- const updateSelection = (range)=>{
26576
- selectionRangeRef.current = range;
26889
+ const updateSelection = (selection)=>{
26890
+ selectionRangeRef.current = selection;
26577
26891
  scheduleDraw(false);
26578
26892
  };
26579
- const unsubscribe = subscribeHeatmapCaptureSourceRange(globalID, updateSelection);
26580
- updateSelection(getHeatmapCaptureSourceRange(globalID));
26893
+ const unsubscribe = subscribeHeatmapCaptureSelection(globalID, updateSelection);
26894
+ updateSelection(getHeatmapCaptureSelection(globalID));
26581
26895
  return unsubscribe;
26582
26896
  }, [
26583
26897
  globalID,
@@ -26644,13 +26958,13 @@ const HeatmapOverview = /*#__PURE__*/ (0, __WEBPACK_EXTERNAL_MODULE_react__.forw
26644
26958
  const sourcePoint = resolveHeatmapOverviewSourcePoint(snapshot, point);
26645
26959
  const pointInViewport = isHeatmapOverviewSourcePointInViewport(snapshot, sourcePoint);
26646
26960
  const selectionRange = selectionRangeRef.current;
26647
- if (!pointInViewport && selectionRange && !isHeatmapSourceRangeVisibleInViewport(snapshot, selectionRange)) {
26961
+ if (!pointInViewport && selectionRange && !isHeatmapContinuousSourceRangeVisibleInViewport(snapshot, selectionRange)) {
26648
26962
  const bounds = event.currentTarget.getBoundingClientRect();
26649
26963
  const tolerance = {
26650
26964
  x: bounds.width ? 6 * snapshot.sourceSize.width / bounds.width : 0,
26651
26965
  y: bounds.height ? 6 * snapshot.sourceSize.height / bounds.height : 0
26652
26966
  };
26653
- if (isHeatmapOverviewSourcePointInRange(selectionRange, sourcePoint, tolerance)) return void scheduleRange(calculateHeatmapOverviewSourceRangeCenteredRange(snapshot, selectionRange));
26967
+ if (isHeatmapOverviewSourcePointInContinuousRange(selectionRange, sourcePoint, tolerance)) return void scheduleRange(calculateHeatmapOverviewContinuousSourceRangeCenteredRange(snapshot, selectionRange));
26654
26968
  }
26655
26969
  if (!pointInViewport) return void scheduleRange(calculateHeatmapOverviewCenteredRange(snapshot, point));
26656
26970
  dragRef.current = {
@@ -28953,7 +29267,7 @@ const SignalTypeRow = ({ source, type, leafKeysWithData, hiddenKeys, expanded, o
28953
29267
  className: cn_cn('size-2 shrink-0 rounded-full ring-1 ring-inset ring-background/30', 'disabled' === state && 'opacity-35'),
28954
29268
  "data-testid": `signal-tree-type-color-${source.source}-${type.signalType}`,
28955
29269
  style: {
28956
- backgroundColor: SIGNAL_TYPE_MAP[type.signalType].color
29270
+ backgroundColor: type.color ?? SIGNAL_TYPE_MAP[type.signalType].color
28957
29271
  }
28958
29272
  }),
28959
29273
  /*#__PURE__*/ (0, __WEBPACK_EXTERNAL_MODULE_react_jsx_runtime_225474f2__.jsx)("span", {
@@ -29004,7 +29318,7 @@ const SignalLevelRow = ({ source, signalType, level, leafKeysWithData, hiddenKey
29004
29318
  className: cn_cn('size-2 shrink-0 rounded-full ring-1 ring-inset ring-background/30', 'disabled' === state && 'opacity-35'),
29005
29319
  "data-testid": `signal-tree-level-color-${source}-${signalType}-${level.level}`,
29006
29320
  style: {
29007
- backgroundColor: SIGNAL_LEVEL_MAP[level.level].color
29321
+ backgroundColor: level.color ?? SIGNAL_LEVEL_MAP[level.level].color
29008
29322
  }
29009
29323
  }),
29010
29324
  /*#__PURE__*/ (0, __WEBPACK_EXTERNAL_MODULE_react_jsx_runtime_225474f2__.jsx)("span", {
@@ -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;
@@ -27,6 +27,7 @@ export interface SignalTreeLevelNode {
27
27
  label: string;
28
28
  leafKey: string;
29
29
  hasData: boolean;
30
+ color?: string;
30
31
  }
31
32
  export interface SignalTreeTypeNode {
32
33
  signalType: string;
@@ -34,6 +35,7 @@ export interface SignalTreeTypeNode {
34
35
  leafKeys: string[];
35
36
  dataLeafKeys: string[];
36
37
  hasData: boolean;
38
+ color?: string;
37
39
  levels: SignalTreeLevelNode[];
38
40
  }
39
41
  export interface SignalTreeSourceNode {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@rfkit/charts",
3
- "version": "1.10.3",
3
+ "version": "1.10.5",
4
4
  "type": "module",
5
5
  "description": "Chart components for wireless monitoring web applications",
6
6
  "exports": {