Press n or j to go to the next uncovered block, b, p or k for the previous block.
| 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 | import type { Point2 } from '../../../types';
import type { ViewAnchor } from '../ViewportCameraTypes';
import type { ECGViewState, RenderWindowMetrics } from './ECGViewportTypes';
export interface ECGCanvasMapping {
anchorWorld: [number, number];
anchorCanvas: ViewAnchor;
canvasHeight: number;
canvasWidth: number;
centeredXOffset: number;
centeredYOffset: number;
effectiveRatio: number;
xOffset: number;
yOffset: number;
}
export function createDefaultECGViewState(args: {
timeRange: [number, number];
valueRange: [number, number];
}): ECGViewState {
return {
timeRange: [...args.timeRange] as [number, number],
valueRange: [...args.valueRange] as [number, number],
scrollOffset: 0,
anchorCanvas: [0.5, 0.5],
scale: 1,
scaleMode: 'fit',
rotation: 0,
};
}
export function normalizeECGViewState(camera: ECGViewState): ECGViewState {
return {
timeRange: [...camera.timeRange] as [number, number],
valueRange: [...camera.valueRange] as [number, number],
...(camera.scrollOffset !== undefined
? { scrollOffset: camera.scrollOffset }
: {}),
anchorCanvas: cloneAnchorCanvas(camera.anchorCanvas ?? [0.5, 0.5]),
scale: Math.max(camera.scale ?? 1, 0.001),
scaleMode: 'fit',
rotation: 0,
...(camera.anchorWorld
? {
anchorWorld: clonePoint(camera.anchorWorld),
}
: {}),
};
}
export function resolveECGCanvasMapping(args: {
metrics: RenderWindowMetrics;
camera?: ECGViewState;
canvas: HTMLCanvasElement;
}): ECGCanvasMapping {
const { metrics, camera, canvas } = args;
const scale = Math.max(camera?.scale ?? 1, 0.001);
const effectiveRatio = metrics.worldToCanvasRatio * scale;
const drawWidth = metrics.ecgWidth * effectiveRatio;
const drawHeight = metrics.ecgHeight * effectiveRatio;
const centeredXOffset = (canvas.clientWidth - drawWidth) / 2;
const centeredYOffset = (canvas.clientHeight - drawHeight) / 2;
const anchorCanvas = cloneAnchorCanvas(camera?.anchorCanvas ?? [0.5, 0.5]);
const anchorWorld =
clonePoint(camera?.anchorWorld) ??
([metrics.ecgWidth / 2, metrics.ecgHeight / 2] as [number, number]);
const xOffset =
canvas.clientWidth * anchorCanvas[0] - anchorWorld[0] * effectiveRatio;
const yOffset =
canvas.clientHeight * anchorCanvas[1] - anchorWorld[1] * effectiveRatio;
return {
anchorWorld,
anchorCanvas,
canvasHeight: canvas.clientHeight,
canvasWidth: canvas.clientWidth,
centeredXOffset,
centeredYOffset,
effectiveRatio,
xOffset,
yOffset,
};
}
export function getPanForECGCanvasMapping(mapping: ECGCanvasMapping): Point2 {
return [
mapping.xOffset - mapping.centeredXOffset,
mapping.yOffset - mapping.centeredYOffset,
];
}
export function getAnchorWorldForPan(
pan: Point2,
mapping: ECGCanvasMapping
): [number, number] {
return [
(mapping.canvasWidth * mapping.anchorCanvas[0] -
(mapping.centeredXOffset + pan[0])) /
mapping.effectiveRatio,
(mapping.canvasHeight * mapping.anchorCanvas[1] -
(mapping.centeredYOffset + pan[1])) /
mapping.effectiveRatio,
];
}
export function getAnchorWorldForCanvasPoint(
canvasPoint: Point2,
mapping: ECGCanvasMapping
): [number, number] {
return [
(canvasPoint[0] - mapping.xOffset) / mapping.effectiveRatio,
(canvasPoint[1] - mapping.yOffset) / mapping.effectiveRatio,
];
}
function clonePoint(point?: [number, number]): [number, number] | undefined {
return point ? [point[0], point[1]] : undefined;
}
function cloneAnchorCanvas(anchorCanvas: ViewAnchor): ViewAnchor {
return [anchorCanvas[0], anchorCanvas[1]];
}
|