@@ -1027,6 +1027,140 @@ export default class StoreImp implements Store {
10271027 return Math . ceil ( this . coordinateToFloatIndex ( x ) ) - 1
10281028 }
10291029
1030+ /**
1031+ * Converts a float data index to an interpolated timestamp.
1032+ * This allows sub-bar precision for smooth freehand drawings.
1033+ * Supports extrapolation beyond the data range (drawing in the "future").
1034+ * @param floatIndex - A floating point index (e.g., 42.75)
1035+ * @returns An interpolated timestamp between two bars
1036+ */
1037+ floatIndexToTimestamp ( floatIndex : number ) : Nullable < number > {
1038+ const length = this . _dataList . length
1039+ if ( length === 0 ) {
1040+ return null
1041+ }
1042+
1043+ const lastIndex = length - 1
1044+
1045+ // Handle float index beyond the last bar (extrapolate into the future)
1046+ if ( floatIndex > lastIndex && length >= 2 ) {
1047+ const lastTimestamp = this . _dataList [ lastIndex ] . timestamp
1048+ const secondLastTimestamp = this . _dataList [ lastIndex - 1 ] . timestamp
1049+ const barDuration = lastTimestamp - secondLastTimestamp
1050+ if ( barDuration > 0 ) {
1051+ const barsBeyondLast = floatIndex - lastIndex
1052+ return Math . round ( lastTimestamp + barsBeyondLast * barDuration )
1053+ }
1054+ }
1055+
1056+ // Handle float index before the first bar (extrapolate into the past)
1057+ if ( floatIndex < 0 && length >= 2 ) {
1058+ const firstTimestamp = this . _dataList [ 0 ] . timestamp
1059+ const secondTimestamp = this . _dataList [ 1 ] . timestamp
1060+ const barDuration = secondTimestamp - firstTimestamp
1061+ if ( barDuration > 0 ) {
1062+ return Math . round ( firstTimestamp + floatIndex * barDuration )
1063+ }
1064+ }
1065+
1066+ // Normal case: interpolate between two bars within the data range
1067+ const intIndex = Math . floor ( floatIndex )
1068+ const fraction = floatIndex - intIndex
1069+
1070+ // Get timestamp at the integer index
1071+ const timestampAtInt = this . dataIndexToTimestamp ( intIndex )
1072+
1073+ // If no fractional part, return the integer timestamp
1074+ if ( fraction === 0 || ! isNumber ( timestampAtInt ) ) {
1075+ return timestampAtInt
1076+ }
1077+
1078+ // Get timestamp at the next index for interpolation
1079+ const timestampAtNext = this . dataIndexToTimestamp ( intIndex + 1 )
1080+
1081+ if ( isNumber ( timestampAtNext ) ) {
1082+ // Linear interpolation between the two timestamps
1083+ return Math . round ( timestampAtInt + ( timestampAtNext - timestampAtInt ) * fraction )
1084+ }
1085+
1086+ return timestampAtInt
1087+ }
1088+
1089+ /**
1090+ * Converts a precise timestamp to a float data index.
1091+ * This preserves sub-bar precision for smooth freehand drawings across timeframe changes.
1092+ * Supports extrapolation beyond the data range (drawing in the "future").
1093+ * @param timestamp - A precise timestamp (possibly between or beyond bars)
1094+ * @returns A floating point index representing the exact position
1095+ */
1096+ timestampToFloatIndex ( timestamp : number ) : number {
1097+ const length = this . _dataList . length
1098+ if ( length === 0 ) {
1099+ return 0
1100+ }
1101+
1102+ const firstTimestamp = this . _dataList [ 0 ] . timestamp
1103+ const lastTimestamp = this . _dataList [ length - 1 ] . timestamp
1104+
1105+ // Handle timestamp beyond the last bar (drawing in the "future")
1106+ if ( timestamp > lastTimestamp && length >= 2 ) {
1107+ // Calculate average bar duration from the last two bars
1108+ const secondLastTimestamp = this . _dataList [ length - 2 ] . timestamp
1109+ const barDuration = lastTimestamp - secondLastTimestamp
1110+ if ( barDuration > 0 ) {
1111+ const timeBeyondLast = timestamp - lastTimestamp
1112+ const barsBeyond = timeBeyondLast / barDuration
1113+ return length - 1 + barsBeyond
1114+ }
1115+ }
1116+
1117+ // Handle timestamp before the first bar
1118+ if ( timestamp < firstTimestamp && length >= 2 ) {
1119+ const secondTimestamp = this . _dataList [ 1 ] . timestamp
1120+ const barDuration = secondTimestamp - firstTimestamp
1121+ if ( barDuration > 0 ) {
1122+ const timeBeforeFirst = firstTimestamp - timestamp
1123+ const barsBefore = timeBeforeFirst / barDuration
1124+ return - barsBefore
1125+ }
1126+ }
1127+
1128+ // Find the floor bar index using binary search
1129+ // We need the bar where barTimestamp <= timestamp < nextBarTimestamp
1130+ let left = 0
1131+ let right = length - 1
1132+ let floorIndex = 0
1133+
1134+ while ( left <= right ) {
1135+ const mid = Math . floor ( ( left + right ) / 2 )
1136+ const midTimestamp = this . _dataList [ mid ] . timestamp
1137+
1138+ if ( midTimestamp <= timestamp ) {
1139+ floorIndex = mid
1140+ left = mid + 1
1141+ } else {
1142+ right = mid - 1
1143+ }
1144+ }
1145+
1146+ // Get the floor bar and the next bar for interpolation
1147+ const dataAtFloor = this . _dataList [ floorIndex ]
1148+ const dataAtNext = floorIndex + 1 < length ? this . _dataList [ floorIndex + 1 ] : null
1149+
1150+ if ( isValid ( dataAtFloor ) && isValid ( dataAtNext ) ) {
1151+ const timestampAtFloor = dataAtFloor . timestamp
1152+ const timestampAtNext = dataAtNext . timestamp
1153+
1154+ // Calculate fractional position between the two bars
1155+ if ( timestamp >= timestampAtFloor && timestampAtNext > timestampAtFloor ) {
1156+ const fraction = ( timestamp - timestampAtFloor ) / ( timestampAtNext - timestampAtFloor )
1157+ return floorIndex + Math . min ( fraction , 1 ) // Clamp to max 1
1158+ }
1159+ }
1160+
1161+ return floorIndex
1162+ }
1163+
10301164 zoom ( scale : number , coordinate : Nullable < Partial < Coordinate > > , position : 'main' | 'xAxis' ) : void {
10311165 if ( ! this . _zoomEnabled ) {
10321166 return
@@ -1595,7 +1729,11 @@ export default class StoreImp implements Store {
15951729 }
15961730
15971731 isOverlayDrawing ( ) : boolean {
1598- return this . _progressOverlayInfo ?. overlay . isDrawing ( ) ?? false
1732+ const info = this . _progressOverlayInfo
1733+ if ( info !== null ) {
1734+ return info . overlay . isDrawing ( )
1735+ }
1736+ return false
15991737 }
16001738
16011739 private _clearLastPriceMarkExtendTextUpdateTimer ( ) : void {
0 commit comments