aboutsummaryrefslogtreecommitdiff
path: root/ui/src/frontend/time_scale.ts
diff options
context:
space:
mode:
Diffstat (limited to 'ui/src/frontend/time_scale.ts')
-rw-r--r--ui/src/frontend/time_scale.ts129
1 files changed, 63 insertions, 66 deletions
diff --git a/ui/src/frontend/time_scale.ts b/ui/src/frontend/time_scale.ts
index 6f0307af8..7804348c8 100644
--- a/ui/src/frontend/time_scale.ts
+++ b/ui/src/frontend/time_scale.ts
@@ -12,95 +12,92 @@
// See the License for the specific language governing permissions and
// limitations under the License.
-import {assertFalse, assertTrue} from '../base/logging';
-import {TimeSpan} from '../common/time';
+import {assertTrue} from '../base/logging';
+import {
+ HighPrecisionTime,
+ HighPrecisionTimeSpan,
+} from '../common/high_precision_time';
+import {Span} from '../common/time';
+import {
+ TPDuration,
+ TPTime,
+} from '../common/time';
-const MAX_ZOOM_SPAN_SEC = 1e-8; // 10 ns.
-
-/**
- * Defines a mapping between number and seconds for the entire application.
- * Linearly scales time values from boundsMs to pixel values in boundsPx and
- * back.
- */
export class TimeScale {
- private timeBounds: TimeSpan;
- private _startPx: number;
- private _endPx: number;
- private secPerPx = 0;
-
- constructor(timeBounds: TimeSpan, boundsPx: [number, number]) {
- this.timeBounds = timeBounds;
- this._startPx = boundsPx[0];
- this._endPx = boundsPx[1];
- this.updateSlope();
- }
-
- private updateSlope() {
- this.secPerPx = this.timeBounds.duration / (this._endPx - this._startPx);
+ private _start: HighPrecisionTime;
+ private _durationNanos: number;
+ readonly pxSpan: PxSpan;
+ private _nanosPerPx = 0;
+ private _startSec: number;
+
+ constructor(start: HighPrecisionTime, durationNanos: number, pxSpan: PxSpan) {
+ // TODO(stevegolton): Ensure duration & pxSpan > 0.
+ // assertTrue(pxSpan.start < pxSpan.end, 'Px start >= end');
+ // assertTrue(durationNanos < 0, 'Duration <= 0');
+ this.pxSpan = pxSpan;
+ this._start = start;
+ this._durationNanos = durationNanos;
+ if (durationNanos <= 0 || pxSpan.delta <= 0) {
+ this._nanosPerPx = 1;
+ } else {
+ this._nanosPerPx = durationNanos / (pxSpan.delta);
+ }
+ this._startSec = this._start.seconds;
}
- deltaTimeToPx(time: number): number {
- return Math.round(time / this.secPerPx);
+ get timeSpan(): Span<HighPrecisionTime> {
+ const end = this._start.addNanos(this._durationNanos);
+ return new HighPrecisionTimeSpan(this._start, end);
}
- timeToPx(time: number): number {
- return this._startPx + (time - this.timeBounds.start) / this.secPerPx;
+ tpTimeToPx(ts: TPTime): number {
+ // WARNING: Number(bigint) can be surprisingly slow. Avoid in hotpath.
+ const timeOffsetNanos = Number(ts - this._start.base) - this._start.offset;
+ return this.pxSpan.start + timeOffsetNanos / this._nanosPerPx;
}
- pxToTime(px: number): number {
- return this.timeBounds.start + (px - this._startPx) * this.secPerPx;
+ secondsToPx(seconds: number): number {
+ const timeOffset = (seconds - this._startSec) * 1e9;
+ return this.pxSpan.start + timeOffset / this._nanosPerPx;
}
- deltaPxToDuration(px: number): number {
- return px * this.secPerPx;
+ hpTimeToPx(time: HighPrecisionTime): number {
+ const timeOffsetNanos = time.subtract(this._start).nanos;
+ return this.pxSpan.start + timeOffsetNanos / this._nanosPerPx;
}
- setTimeBounds(timeBounds: TimeSpan) {
- this.timeBounds = timeBounds;
- this.updateSlope();
+ // Convert pixels to a high precision time object, which can be futher
+ // converted to other time formats.
+ pxToHpTime(px: number): HighPrecisionTime {
+ const offsetNanos = (px - this.pxSpan.start) * this._nanosPerPx;
+ return this._start.addNanos(offsetNanos);
}
- setLimitsPx(pxStart: number, pxEnd: number) {
- assertFalse(pxStart === pxEnd);
- assertTrue(pxStart >= 0 && pxEnd >= 0);
- this._startPx = pxStart;
- this._endPx = pxEnd;
- this.updateSlope();
+ durationToPx(dur: TPDuration): number {
+ // WARNING: Number(bigint) can be surprisingly slow. Avoid in hotpath.
+ return Number(dur) / this._nanosPerPx;
}
- timeInBounds(time: number): boolean {
- return this.timeBounds.isInBounds(time);
+ pxDeltaToDuration(pxDelta: number): HighPrecisionTime {
+ const time = pxDelta * this._nanosPerPx;
+ return HighPrecisionTime.fromNanos(time);
}
+}
- get startPx(): number {
- return this._startPx;
+export class PxSpan {
+ constructor(private _start: number, private _end: number) {
+ assertTrue(_start <= _end, 'PxSpan start > end');
}
- get endPx(): number {
- return this._endPx;
+ get start(): number {
+ return this._start;
}
- get widthPx(): number {
- return this._endPx - this._startPx;
+ get end(): number {
+ return this._end;
}
- get timeSpan(): TimeSpan {
- return this.timeBounds;
+ get delta(): number {
+ return this._end - this._start;
}
}
-
-export function computeZoom(
- scale: TimeScale, span: TimeSpan, zoomFactor: number, zoomPx: number):
- TimeSpan {
- const startPx = scale.startPx;
- const endPx = scale.endPx;
- const deltaPx = endPx - startPx;
- const deltaTime = span.end - span.start;
- const newDeltaTime = Math.max(deltaTime * zoomFactor, MAX_ZOOM_SPAN_SEC);
- const clampedZoomPx = Math.max(startPx, Math.min(endPx, zoomPx));
- const zoomTime = scale.pxToTime(clampedZoomPx);
- const r = (clampedZoomPx - startPx) / deltaPx;
- const newStartTime = zoomTime - newDeltaTime * r;
- const newEndTime = newStartTime + newDeltaTime;
- return new TimeSpan(newStartTime, newEndTime);
-}