Published unlisted
Edited
Feb 11, 2022
Insert cell
# Genome Space: zoom zoom zoom (revC3; React test)
Insert cell
viewof chromosome = Inputs.select(chromosomes, {
value: 20,
label: "Chromosome"
})
Insert cell
viewof viewOrder = Inputs.range([1, Math.min(baseOrder, 7)], {
step: 1,
value: 6,
label: "Hilbert curve order"
})
Insert cell
viewof zoomOrder = Inputs.range([viewOrder + 1, viewOrder + 4], {
step: 1,
value: viewOrder + 1,
label: "Zoom level"
})
Insert cell
viewof zoomOptions = Inputs.checkbox(
["Include gaps in score view", "Show Hilbert curve"],
{
value: ["Include gaps in score view", "Show Hilbert curve"],
label: "Zoom options"
}
)
Insert cell
viewof linPos = {
const stepSize = parseInt(bpForChromosome / Math.pow(4, zoomOrder));
// const stepSize = parseInt(bpForChromosome / Math.pow(4, viewOrder));
const min = 0;
const max = bpForChromosome - 1;
return Inputs.range([min, max], {
step: stepSize,
value: 0,
label: `Genomic position (chr${chromosome})`
});
}
Insert cell
viewof genomeSpace = {
let container = html`
<div id="genomeSpace" style="position: relative; width:${viewportWidth}px; height:${viewportHeight}px;">
${genomeSpaceStyle}

<div id="genomeSpaceContent" style="position: relative; width:${
genomeSpaceContentDimensions.w
}px; height:${genomeSpaceContentDimensions.h}; top:${
genomeSpaceContainerMargin.top + genomeSpaceContainerPadding.top
}px; left:${
genomeSpaceContainerMargin.left + genomeSpaceContainerPadding.left
}px">

<div id="genomeSpaceAllView" style="position: absolute; width:${
genomeSpaceAllViewDimensions.w
}px; height:${genomeSpaceAllViewDimensions.h}px; top: ${
genomeSpaceAllViewDimensions.t
}px; left:${genomeSpaceAllViewDimensions.l}px">
<div id="genomeSpaceAllViewHeader" class="genomeSpaceViewHeader">
${genomeSpaceAllViewLabel}
</div>
<div id="genomeSpaceAllViewContent" class="genomeSpaceViewContent" style="position:relative; width:${
genomeSpaceAllViewContentDimensions.w
}px; height:${
genomeSpaceAllViewContentDimensions.h
}px; top:0px; left:0px;"></div>
</div>

<div id="genomeSpaceZoomView" style="position: absolute; width:${
genomeSpaceZoomViewDimensions.w
}px; height:${genomeSpaceZoomViewDimensions.h}px; top: ${
genomeSpaceZoomViewDimensions.t
}px; left:${genomeSpaceZoomViewDimensions.l}px">
<div id="genomeSpaceZoomViewHeader" class="genomeSpaceViewHeader" style="left:${
genomeSpaceZoomViewDimensions.l
}">
${genomeSpaceZoomViewLabel}
</div>
<div style="overflow:hidden">
<div id="genomeSpaceZoomViewContent" class="genomeSpaceViewContent" style="position:relative; width:${
genomeSpaceZoomViewContentDimensions.w
}px; height:${
genomeSpaceZoomViewContentDimensions.h
}px; left:0px; top:0px;"></div>
</div>
</div>

<div id="genomeSpaceZoomScoreView" style="position: absolute; width:${
genomeSpaceZoomScoreViewDimensions.w
}px; height:${genomeSpaceZoomScoreViewDimensions.h}px; top: ${
genomeSpaceZoomScoreViewDimensions.t
}px; left:${genomeSpaceZoomScoreViewDimensions.l}px">
<div id="genomeSpaceZoomScoreViewHeader" class="genomeSpaceViewHeader" style="position:absolute; z-index:1; width:${
genomeSpaceZoomScoreViewContentDimensions.w
}px;">
${genomeSpaceZoomScoreViewLabel}
</div>
<div id="genomeSpaceZoomScoreViewContent" class="genomeSpaceScoreViewContent" style="position:absolute; z-index:0; width:${
genomeSpaceZoomScoreViewContentDimensions.w
}px; height:${
genomeSpaceZoomScoreViewContentDimensions.h
}px; top:-1px; left:-1px;"></div>
</div>

</div>

</div>`;

const genomeSpaceAllViewContent = container.querySelector(
"#genomeSpaceAllViewContent"
);
const genomeSpaceZoomViewContent = container.querySelector(
"#genomeSpaceZoomViewContent"
);
const genomeSpaceZoomScoreViewContent = container.querySelector(
"#genomeSpaceZoomScoreViewContent"
);

genomeSpaceAllViewContent.appendChild(allView);

container.clearFrame = frameView.clearFrame;
container.drawFrame = frameView.drawFrame;
genomeSpaceAllViewContent.appendChild(frameView);

container.drawHover = hoverView.drawHover;
container.clearHover = hoverView.clearHover;
genomeSpaceAllViewContent.appendChild(hoverView);

container.drawZoom = zoomView.drawZoom;
genomeSpaceZoomViewContent.appendChild(zoomView);

container.drawPath = pathView.drawPath;
container.showPathBin = pathView.showPathBin;
container.clearPathBin = pathView.clearPathBin;
container.showPathTooltip = pathView.showPathTooltip;
container.clearPathTooltip = pathView.clearPathTooltip;
container.handlePathClick = pathView.handlePathClick;
container.handlePathDoubleclick = pathView.handlePathDoubleclick;
genomeSpaceZoomViewContent.appendChild(pathView);

container.showZoomScoresBinHighlightForBin =
zoomScoreView.showZoomScoresBinHighlightForBin;
container.showZoomScoresBinHighlightViaXY =
zoomScoreView.showZoomScoresBinHighlightViaXY;
container.showZoomBinHighlightForBin =
zoomScoreView.showZoomBinHighlightForBin;

d3.select(genomeSpaceZoomScoreViewContent).on("click", (evt) => {
container.showZoomBinHighlightForBin(highlightBinIdxForPointX(evt.layerX));
});

d3.select(genomeSpaceZoomScoreViewContent).on("mousemove", (evt) => {
if (evt.layerX < genomeSpaceZoomScoreViewContentDimensions.w) {
let highlightBinIdx = highlightBinIdxForPointX(evt.layerX);
container.showZoomScoresBinHighlightForBin(highlightBinIdx);
const highlightCurveXY = curveZoom.getXyAtVal(highlightBinIdx);
container.showPathBin(highlightCurveXY[0], highlightCurveXY[1]);
}
});

d3.select(genomeSpaceZoomScoreViewContent).on("mouseout", (evt) => {
container.clearPathBin();
container.clearPathTooltip();
});

// const debounceShowPathTooltip = debounce(
// (x, y) => {
// container.showPathTooltip(x, y);
// container.showZoomScoresBinHighlight(x, y);
// },
// 10,
// false
// );

d3.select(genomeSpaceZoomViewContent).on("mousemove", (evt) => {
container.clearHover();
if (evt.layerX < genomeSpaceZoomViewContentDimensions.w) {
container.showPathTooltip(evt.layerX, evt.layerY);
container.showZoomScoresBinHighlightViaXY(evt.layerX, evt.layerY);
// debounceShowPathTooltip(evt.layerX, evt.layerY);
}
});

d3.select(genomeSpaceZoomViewContent).on("mouseout", (evt) => {
container.clearPathTooltip();
});

d3.select(genomeSpaceZoomViewContent).on("click", (evt) => {
// console.log(
// `genomeSpaceZoomViewContent click > ${evt.layerX} ${evt.layerY}`
// );
if (evt.layerX < genomeSpaceZoomViewContentDimensions.w) {
mutable genomeSpaceZoomViewPathEnabled = false;
const clickXY = { x: evt.layerX, y: evt.layerY };
container.handlePathClick(clickXY.x, clickXY.y);
container.clearHover();
container.drawFrame(pointXY.x, pointXY.y);
mutable genomeSpaceZoomViewPathEnabled = true;
container.drawPath(pointXY.x, pointXY.y);
}
});

d3.select(genomeSpaceZoomViewContent).on("dblclick", (evt) => {
// console.log(`genomeSpaceZoomViewContent dblclick > ${evt.layerX}`);
if (evt.layerX < genomeSpaceZoomViewContentDimensions.w) {
const clickXY = { x: evt.layerX, y: evt.layerY };
container.handlePathDoubleclick(clickXY.x, clickXY.y);
}
});

container.drawZoomScores = zoomScoreView.drawZoomScores;
genomeSpaceZoomScoreViewContent.appendChild(zoomScoreView);

d3.select(genomeSpaceAllViewContent).on("wheel", (evt) => {
// console.log(
// `wheel > evt.layerX ${evt.layerX} evt.layerY ${evt.layerY} evt.deltaY ${evt.deltaY}`
// );
evt.preventDefault();
mutable genomeSpaceZoomViewPathEnabled = false;
mutable binSelectionStartIdx = 0;
mutable binSelectionEndIdx = 0;
genomeSpaceAllViewContentHandleWheelEvent(
evt.layerX,
evt.layerY,
Math.sign(evt.deltaY),
() => {
container.drawPath(pointXY.x, pointXY.y);
}
);
});

d3.select(genomeSpaceAllViewContent).on("mousemove", (evt) => {
// console.log(`mousemove > evt.layerX ${evt.layerX} evt.layerY ${evt.layerY}`);
if (evt.layerX < genomeSpaceAllViewContentDimensions.w) {
d3.select("#genomeSpaceAllView").style("cursor", "grab");
container.drawHover(evt.layerX, evt.layerY);
}
});

d3.select(genomeSpaceAllViewContent).on("mouseout", (evt) => {
container.clearHover();
});

d3.select(genomeSpaceAllViewContent).on("click", (evt) => {
// console.log(
// `genomeSpaceAllViewContent | click > evt.layerX ${evt.layerX} | evt.layerY ${evt.layerY}`
// );
if (evt.layerX < genomeSpaceAllViewContentDimensions.w) {
// container.value = { x: evt.layerX, y: evt.layerY };
d3.select("#genomeSpaceAllView").style("cursor", "grabbing");
const clickXY = { x: evt.layerX, y: evt.layerY };
nearestBaseToClickXYInView(clickXY, curveZoom, zoomOrder);
// nearestBaseToClickXYInView(clickXY, curveAll, viewOrder);
}
});

function dragging(evt) {
// console.log(`dragging > ${evt.x} ${evt.y}`);
if (
evt.x < genomeSpaceAllViewContentDimensions.w &&
evt.y < genomeSpaceAllViewContentDimensions.h
) {
d3.select("#genomeSpaceAllView").style("cursor", "grabbing");
const clickXY = { x: evt.x, y: evt.y - genomeSpaceAllViewHeaderHeight };
nearestBaseToClickXYInView(clickXY, curveZoom, zoomOrder);
container.drawHover(clickXY.x, clickXY.y);
}
}
function dragStart(evt) {
// console.log(`dragStart > ${evt.x} ${evt.y}`);
if (
evt.x < genomeSpaceAllViewContentDimensions.w &&
evt.y < genomeSpaceAllViewContentDimensions.h
) {
mutable genomeSpaceZoomViewPathEnabled = false;
}
}
function dragEnd(evt) {
// console.log(`dragEnd > ${evt.x} ${evt.y}`);
if (
evt.x < genomeSpaceAllViewContentDimensions.w &&
evt.y < genomeSpaceAllViewContentDimensions.h
) {
const clickXY = { x: evt.x, y: evt.y - genomeSpaceAllViewHeaderHeight };
nearestBaseToClickXYInView(clickXY, curveZoom, zoomOrder);
mutable genomeSpaceZoomViewPathEnabled = true;
container.drawPath(pointXY.x, pointXY.y);
}
}
let drag = d3
.drag()
.on("drag", dragging)
.on("start", dragStart)
.on("end", dragEnd);
d3.select(container).call(drag);

if (objectIsEmpty(genomeSpaceEstimatedZoomFrame)) {
estimatedGenomicRangeFromAllViewFrame(pointXY);
}

container.value = curveGenomicIntervalToXY(
curveAll,
bpForChromosome,
genomeSpaceZoomViewContentCurveDimensions
);
container.drawFrame(pointXY.x, pointXY.y);
if (
zoomOptions.includes("Show Hilbert curve") &&
mutable genomeSpaceZoomViewPathEnabled
) {
container.drawPath(pointXY.x, pointXY.y);
}
container.drawZoom(pointXY.x, pointXY.y);
container.drawZoomScores();

return container;
}
Insert cell
fullscreen = {
const button = html`<button>Fullscreen`;
button.onclick = () => {
button.parentElement.previousElementSibling
.requestFullscreen()
.then(() => {});
};
return button;
}
Insert cell
### Subviews

The `pointXY` object converts the genomic position slider value (`linPos`) to an xy coordinate relative to the `allView` content and its dimensions.

The `nearestBaseToClickXYInView` function sets the genomic position slider to the start position closest to the mouse click event coordinates, relative to the specified view and order.

The `binSelectionToGenomicInterval` helper function converts the bin selection range to an estimated genomic interval for labeling the zoom view and the region it covers.

Subviews refactor code out of the `genomicSpace` view and keep things neat.
Insert cell
mutable pointXY = curveGenomicIntervalToXY(
curveZoom,
linPos,
genomeSpaceAllViewContentDimensions
)
Insert cell
nearestBaseToClickXYInView = (xy, curve, order) => {
let v = curve.getValAtXY(xy.x, xy.y);
let v2b = genomeSpaceViewToBase(v, order);
console.log(`v2b ${JSON.stringify(v2b)}`);
mutable linPosForReact = v2b;
set(viewof linPos, v2b);
}
Insert cell
mutable linPosForReact = 0
Insert cell
mutable binSelectionStartPosition = 0
Insert cell
mutable binSelectionIdx = null
Insert cell
mutable binSelectionStartIdx = 0
Insert cell
mutable binSelectionEndIdx = Math.pow(4, zoomOrder) - 1
Insert cell
highlightBinIdxForPointX = (x) => {
let highlightBinIdx = -1;
if (x < genomeSpaceZoomScoreViewContentDimensions.w) {
highlightBinIdx =
mutable binSelectionStartIdx +
Math.floor(
(parseFloat(x) / genomeSpaceZoomScoreViewContentDimensions.w) *
(mutable binSelectionEndIdx - mutable binSelectionStartIdx)
);

highlightBinIdx =
highlightBinIdx < mutable binSelectionStartIdx
? mutable binSelectionStartIdx
: highlightBinIdx > mutable binSelectionEndIdx
? mutable binSelectionEndIdx
: highlightBinIdx;
}
return highlightBinIdx;
}
Insert cell
genomeSpaceAllViewContentResetBinSelection(zoomOrder)
Insert cell
genomeSpaceAllViewContentResetBinSelection = (newZoomOrder) => {
if (mutable binSelectionStartPosition !== 0) {
mutable binSelectionStartPosition = 0;
}
if (mutable binSelectionStartIdx !== 0) {
mutable binSelectionStartIdx = 0;
}
if (mutable binSelectionEndIdx !== Math.pow(4, newZoomOrder) - 1) {
mutable binSelectionEndIdx = Math.pow(4, newZoomOrder) - 1;
}
if (mutable binSelectionIdx !== 0) {
mutable binSelectionIdx = 0;
}
}
Insert cell
genomeSpaceAllViewContentHandleWheelEvent = debounce(
(x, y, sign, cb) => {
// console.log(`genomeSpaceAllViewContentHandleWheelEvent > ${x} ${y} ${sign}`);
if (
mutable genomeSpaceAllViewFrame.x < x &&
mutable genomeSpaceAllViewFrame.y < y &&
mutable genomeSpaceAllViewFrame.x + mutable genomeSpaceAllViewFrame.w >
x &&
mutable genomeSpaceAllViewFrame.y + mutable genomeSpaceAllViewFrame.h > y
) {
// console.log(`wheel event inside frame > ${viewof zoomOrder.value}`);
const newZoomOrder = zoomOrder + sign;
if (newZoomOrder > viewOrder) {
genomeSpaceAllViewContentResetBinSelection(newZoomOrder);
set(viewof zoomOrder, newZoomOrder);
}
} else {
// console.log(`wheel event outside frame > ${viewof viewOrder.value}`);
genomeSpaceAllViewContentResetBinSelection(zoomOrder);
const maxViewOrder = 7;
const newViewOrder = viewOrder + sign;
if (newViewOrder <= maxViewOrder) {
set(viewof viewOrder, viewOrder + sign);
}
}
mutable genomeSpaceZoomViewPathEnabled = true;
if (cb) {
cb();
}
},
500,
false
)
Insert cell
mutable genomeSpaceZoomViewPathEnabled = true
Insert cell
mutable genomeSpaceAllViewFrame = null
Insert cell
binSelectionToGenomicInterval = {
let trueBinSelectionStartIdx = binSelectionStartIdx;
let trueBinSelectionEndIdx = binSelectionEndIdx;

let binSelectionStartCurveStateInfo =
curveMappedStatesZoomByBinIndex[trueBinSelectionStartIdx];
let binSelectionStartCurveStatePointsStartRange = [];

binSelectionStartCurveStatePointsStartRange = [
binSelectionStartCurveStateInfo.points[0].start,
binSelectionStartCurveStateInfo.points[
binSelectionStartCurveStateInfo.points.length - 1
].start
];

let binSelectionEndCurveStateInfo =
curveMappedStatesZoomByBinIndex[trueBinSelectionEndIdx];
let binSelectionEndCurveStatePointsStartRange = [];

binSelectionEndCurveStatePointsStartRange = [
binSelectionEndCurveStateInfo.points[0].start,
binSelectionEndCurveStateInfo.points[
binSelectionEndCurveStateInfo.points.length - 1
].start
];

const start = binSelectionStartCurveStateInfo
? adjustToBinWidth(
binSelectionStartCurveStatePointsStartRange[0],
binResolution
)
: 0;
const stop = binSelectionEndCurveStateInfo
? adjustToBinWidth(
binSelectionEndCurveStatePointsStartRange[1],
binResolution
)
: bpForChromosome;

return {
chrom: chromosome,
start: start,
stop: stop
};
}
Insert cell
mutable pointXYToGenomicInterval = {
let iz = genomeSpaceViewXYToGenomicInterval(
curveZoom,
pointXY
// typeof value !== "undefined" ? value : viewof genomeSpace.value
);
const stepSize = parseInt(bpForChromosome / Math.pow(4, zoomOrder));
const overlayGenomicCoverage = parseInt(
bpForChromosome / 4 ** (zoomOrder - viewOrder)
);
if (!iz) {
iz = {};
iz.chrom = chromosome;
iz.start = 0;
iz.stop = iz.start + stepSize;
}
if (iz.start > iz.stop) {
let temp = iz.start;
iz.start = iz.stop;
iz.stop = temp;
} else if (iz.start === iz.stop) {
iz.start += Math.ceil(stepSize / 2) - stepSize;
iz.stop = iz.start + stepSize;
} else if (iz.start >= Math.ceil(stepSize / 2)) {
iz.start -= Math.ceil(stepSize / 2);
} else if (iz.stop >= bpForChromosome) {
iz.start = bpForChromosome - stepSize;
iz.stop = bpForChromosome;
}
const estimatedGenomicRange = {
chrom: iz.chrom,
start:
iz.start - overlayGenomicCoverage / 2 > 0
? parseInt(iz.start - overlayGenomicCoverage / 2)
: 0,
stop:
iz.stop + overlayGenomicCoverage / 2 < bpForChromosome
? parseInt(iz.stop + overlayGenomicCoverage / 2)
: bpForChromosome - 1
};
iz.estRange = estimatedGenomicRange;
iz.estRange.start = adjustToBinWidth(iz.estRange.start, binResolution);
iz.estRange.stop = adjustToBinWidth(iz.estRange.stop, binResolution);
return iz.estRange;
}
Insert cell
#### All
Insert cell
allView = {
// ----------------------------------------------------------------
//
// all view
//

let allViewCanvas = html`<canvas width=${genomeSpaceAllViewContentCurveDimensions.w} height=${genomeSpaceAllViewContentCurveDimensions.h} style="position: absolute; z-index: 0; top:0px; left:0px;"></canvas>`;
allViewCanvas.width = genomeSpaceAllViewContentCurveDimensions.w;
allViewCanvas.height = genomeSpaceAllViewContentCurveDimensions.h;
let avcCtx = allViewCanvas.getContext("2d");
avcCtx.fillStyle = "#f0f0f0";
avcCtx.fillRect(
0,
0,
genomeSpaceAllViewContentCurveDimensions.w,
genomeSpaceAllViewContentCurveDimensions.h
);
for (let m of curveMappedStatesAll) {
// First fill with a solid white
avcCtx.globalAlpha = 1;
avcCtx.fillStyle = "white";
avcCtx.fillRect(
curveScaleAll(m.x),
curveScaleAll(m.y),
curveWidthCellAll,
curveWidthCellAll
);
// Then add the (translucent) state color
avcCtx.globalAlpha = curveScaleStatesAll(m.scores);
avcCtx.fillStyle = curveColorsForStates[m.idx - 1][1];
avcCtx.fillRect(
curveScaleAll(m.x),
curveScaleAll(m.y),
curveWidthCellAll,
curveWidthCellAll
);
}

return allViewCanvas;
}
Insert cell
#### Frame
Insert cell
frameView = {
let frameViewCanvas = html`<canvas id="genomeSpaceAllViewFrameCanvas" width=${genomeSpaceAllViewContentCurveDimensions.w} height=${genomeSpaceAllViewContentCurveDimensions.h} style="position:absolute; z-index:100; top:0px; left:0px;"></canvas>`;
let fvCtx = frameViewCanvas.getContext("2d");

function drawFrame(x, y) {
// viewof genomeSpace.value = { x: x, y: y };
fvCtx.clearRect(
0,
0,
genomeSpaceAllViewContentCurveDimensions.w,
genomeSpaceAllViewContentCurveDimensions.h
);
fvCtx.strokeStyle = "#000000";
const fvCtxW = curveRectSize;
const fvCtxH = fvCtxW;
const origin = {
x: x - fvCtxW / 2, // + curveZoomRange.cellWidth / 2,
y: y - fvCtxH / 2 // + curveZoomRange.cellWidth / 2
};
if (origin.x <= 0) {
origin.x = 0;
}
if (origin.x + fvCtxW >= genomeSpaceAllViewContentCurveDimensions.w) {
origin.x = genomeSpaceAllViewContentCurveDimensions.w - fvCtxW;
}
if (origin.y <= 0) {
origin.y = 0;
}
if (origin.y + fvCtxH >= genomeSpaceAllViewContentCurveDimensions.h) {
origin.y = genomeSpaceAllViewContentCurveDimensions.h - fvCtxH;
}

fvCtx.strokeRect(origin.x, origin.y, fvCtxW, fvCtxH);

mutable genomeSpaceAllViewFrame = {
x: origin.x,
y: origin.y,
w: fvCtxW,
h: fvCtxH
};

const crosshatchLength = 10;
fvCtx.beginPath();
fvCtx.moveTo(
origin.x + fvCtxW / 2 - crosshatchLength,
origin.y + fvCtxH / 2
);
fvCtx.lineTo(
origin.x + fvCtxW / 2 + crosshatchLength,
origin.y + fvCtxH / 2
);
fvCtx.stroke();
fvCtx.moveTo(
origin.x + fvCtxW / 2,
origin.y + fvCtxH / 2 - crosshatchLength
);
fvCtx.lineTo(
origin.x + fvCtxW / 2,
origin.y + fvCtxH / 2 + crosshatchLength
);
fvCtx.stroke();

return { origin: origin, width: fvCtxW, height: fvCtxH };
}

function clearFrame() {
fvCtx.clearRect(
0,
0,
genomeSpaceAllViewContentDimensions.w - 2 * curveZoomRange.cellWidth,
genomeSpaceAllViewContentDimensions.h - 2 * curveZoomRange.cellWidth
);
}

frameViewCanvas.drawFrame = drawFrame;
frameViewCanvas.clearFrame = clearFrame;

return frameViewCanvas;
}
Insert cell
#### Hover
Insert cell
hoverView = {
let hoverViewCanvas = html`<canvas width=${genomeSpaceAllViewContentCurveDimensions.w} height=${genomeSpaceAllViewContentCurveDimensions.h} style="position:absolute; z-index:101; top:0px; left:0px;"></canvas>`;
let hvCtx = hoverViewCanvas.getContext("2d");

function drawHover(x, y) {
hvCtx.clearRect(
0,
0,
genomeSpaceAllViewContentCurveDimensions.w,
genomeSpaceAllViewContentCurveDimensions.h
);
hvCtx.globalAlpha = 0.5;
hvCtx.fillStyle = "#000000";
const hvCtxW = curveRectSize;
const hvCtxH = hvCtxW;
hvCtx.fillRect(x - hvCtxW / 2, y - hvCtxH / 2, hvCtxW, hvCtxH);
}

function clearHover() {
hvCtx.clearRect(
0,
0,
genomeSpaceAllViewContentDimensions.w,
genomeSpaceAllViewContentDimensions.h
);
}

hoverViewCanvas.drawHover = drawHover;
hoverViewCanvas.clearHover = clearHover;

return hoverViewCanvas;
}
Insert cell
#### Path
Insert cell
mutable pathHilbertData = {
return {
start: 0,
length: Math.pow(4, zoomOrder)
};
}
Insert cell
pathHilbert = d3
.hilbert()
.order(zoomOrder)
.canvasWidth(genomeSpaceZoomViewContentCurveDimensions.w)
.simplifyCurves(false)
.layout(mutable pathHilbertData)
Insert cell
pathHilbertPath = (vertices) => {
// console.log(`pathHilbertPath`);
let path = "M 0 0 L 0 0";
vertices.forEach((vert) => {
switch (vert) {
case "U":
path += "v-1";
break;
case "D":
path += "v1";
break;
case "L":
path += "h-1";
break;
case "R":
path += "h1";
break;
}
});
return path;
}
Insert cell
pathView = {
let pathViewCanvas = html`<div style="width:${genomeSpaceZoomViewContentCurveDimensions.w}px; height:${genomeSpaceZoomViewContentCurveDimensions.h}px; max-width:${genomeSpaceZoomViewContentCurveDimensions.w}px; max-height:${genomeSpaceZoomViewContentCurveDimensions.h}px; position: relative; z-index: 1; top:0px; left:0px;"></div>`;

const pathScaleFactor =
curveRectSize / genomeSpaceZoomViewContentCurveDimensions.w;
const pathPanelWidth = curveRectSize;
const pathPanelHeight = pathPanelWidth;

const zoomFactor = 2 ** (zoomOrder - viewOrder);

function rescaleOrigin(x, y) {
const rescaledOrigin = {
x: x - pathPanelWidth / 2,
y: y - pathPanelHeight / 2
};
if (rescaledOrigin.x <= 0) {
rescaledOrigin.x = 0;
}
if (
rescaledOrigin.x + pathPanelWidth >=
genomeSpaceZoomViewContentCurveDimensions.w
) {
rescaledOrigin.x =
genomeSpaceZoomViewContentCurveDimensions.w - pathPanelWidth;
}
if (rescaledOrigin.y <= 0) {
rescaledOrigin.y = 0;
}
if (
rescaledOrigin.y + pathPanelHeight >=
genomeSpaceZoomViewContentCurveDimensions.h
) {
rescaledOrigin.y =
genomeSpaceZoomViewContentCurveDimensions.h - pathPanelHeight;
}
// rescale origin by factor
rescaledOrigin.x /= pathScaleFactor;
rescaledOrigin.y /= pathScaleFactor;

return rescaledOrigin;
}

function handlePathClick(x, y) {
console.log(`handlePathClick > ${[x, y]}`);
const pathPanelOrigin = rescaleOrigin(pointXY.x, pointXY.y);
if (
typeof pathPanelOrigin === "undefined" ||
typeof pathPanelOrigin.x === "undefined"
)
return;
const curveX = (x + pathPanelOrigin.x) / zoomFactor;
const curveY = (y + pathPanelOrigin.y) / zoomFactor;
console.log(`curveX ${curveX} | curveY ${curveY}`);
nearestBaseToClickXYInView({ x: curveX, y: curveY }, curveZoom, zoomOrder);
}

function handlePathDoubleclick(x, y) {
const pathPanelOrigin = rescaleOrigin(pointXY.x, pointXY.y);
if (
typeof pathPanelOrigin === "undefined" ||
typeof pathPanelOrigin.x === "undefined"
)
return;
const curveX = (x + pathPanelOrigin.x) / zoomFactor;
const curveY = (y + pathPanelOrigin.y) / zoomFactor;
// const curveBinIdx = hilbert.getValAtXY(curveX, curveY);
const curveBinIdx = pathHilbert.getValAtXY(curveX, curveY);
const curveStateInfo = curveMappedStatesZoomByBinIndex()[curveBinIdx];
const curveColorIdx = curveStateInfo.idx - 1;
const curveStatePointsStartRange =
curveStateInfo.idx !== -1
? [
curveStateInfo.points[0].start,
curveStateInfo.points[curveStateInfo.points.length - 1].start
]
: [];
if (curveStateInfo.idx !== -1) {
const region = {
chrom: `chr${chromosome}`,
start: curveStatePointsStartRange[0],
stop: curveStatePointsStartRange[1],
midpoint:
curveStatePointsStartRange[0] +
parseInt(
(curveStatePointsStartRange[1] - curveStatePointsStartRange[0]) / 2
),
padding: 25000
};
const urlFromRegion = genomeSpaceZoomScoreViewLabelExtLinkUrlFromPosition(
region.chrom,
region.midpoint,
region.padding
);
// console.log(`urlFromRegion ${JSON.stringify(urlFromRegion)}`);
const body = document.body;
const externalLink = document.createElement("a");
externalLink.id = "epilogos_viewer_from_datum";
externalLink.target = "_blank";
externalLink.href = urlFromRegion;
body.appendChild(externalLink);
const evfd = document.getElementById("epilogos_viewer_from_datum");
document.getElementById("epilogos_viewer_from_datum").click();
body.removeChild(externalLink);
}
}

function renderPathTooltipRow(k, v) {
return (
"<tr>" +
`<td class='curveNodeCell curveNodeCellKey'>${k}</td>` +
`<td class='curveNodeCell curveNodeCellValue'>${v}</td>` +
"</tr>"
);
}

function showPathBin(curveX, curveY) {
// console.log(`pathView > showPathBin ${[curveX, curveY]}`);

const relativeCurveX =
// zoomFactor * curveX * curveZoomRange.cellWidth * 0.9985;
curveX * curveZoomRange.cellWidth * 2 ** (zoomOrder - viewOrder);
const relativeCurveY =
// zoomFactor * curveY * curveZoomRange.cellWidth * 0.9985;
curveY * curveZoomRange.cellWidth * 2 ** (zoomOrder - viewOrder);

const pathPanelOrigin = rescaleOrigin(pointXY.x, pointXY.y);

// showPathTooltip(
// relativeCurveX - pathPanelOrigin.x,
// relativeCurveY - pathPanelOrigin.y
// );

// console.log(
// `relativeCurveX ${relativeCurveX} relativeCurveY ${relativeCurveY}`
// );

const maxZoomOrder = 12;
const strokeWidth = Math.min(maxZoomOrder - zoomOrder, 3);

const root = d3.select("#genomeSpaceZoomViewContent");
root.selectAll("svg.hilbertCurveDecoration").remove(); // clear out old SVG paths, or else they accumulate
const pathSvg = root
.append("svg")
.attr("id", "hilbertCurveDecoration")
.attr("class", "hilbertCurveDecoration")
.style("position", "absolute")
.style("z-index", "1001")
.style("top", relativeCurveY - (maxZoomOrder - zoomOrder) - 1)
.style("left", relativeCurveX - (maxZoomOrder - zoomOrder) - 1)
.attr(
"transform",
`translate(${-pathPanelOrigin.x}, ${-pathPanelOrigin.y})` // move path along with origin
)
.attr(
"width",
curveZoomRange.cellWidth * 2 ** (zoomOrder - viewOrder) + zoomOrder
)
.attr(
"height",
curveZoomRange.cellWidth * 2 ** (zoomOrder - viewOrder) + zoomOrder
);

const selectedBin = pathSvg.append("g");
selectedBin
.append("rect")
.attr("class", "selectionBox")
.attr("x", `${maxZoomOrder - zoomOrder}`)
.attr("y", `${maxZoomOrder - zoomOrder}`)
.attr("width", curveZoomRange.cellWidth * 2 ** (zoomOrder - viewOrder))
.attr("height", curveZoomRange.cellWidth * 2 ** (zoomOrder - viewOrder))
.attr("fill", "none")
.attr("stroke", "#00f")
.attr("stroke-width", `${strokeWidth}`);
}

function clearPathBin() {
d3.select("#genomeSpaceZoomViewContent")
.selectAll("svg.hilbertCurveDecoration")
.remove();
}

function showPathTooltip(x, y) {
// console.log(`pathView > showPathTooltip ${[x, y]}`);

clearPathBin();

const el = d3.select("#val-tooltip");

const valTooltip =
el.size() === 0
? d3
.select("#genomeSpaceZoomViewContent")
.append("div")
.attr("id", "val-tooltip")
: el;

const xMid = genomeSpaceZoomViewContentCurveDimensions.w / 2;
const xRight = x > xMid;
const yMid = genomeSpaceZoomViewContentCurveDimensions.h / 2;
const yBottom = y > yMid;

const pathPanelOrigin = rescaleOrigin(pointXY.x, pointXY.y);

if (
typeof pathPanelOrigin === "undefined" ||
typeof pathPanelOrigin.x === "undefined"
)
return;

const curveX = (x + pathPanelOrigin.x) / zoomFactor;
const curveY = (y + pathPanelOrigin.y) / zoomFactor;

// console.log(`curveX ${curveX} | curveY ${curveY}`);

let nodeLabelHTML = "<table class='curveNodeTable'>";

const curveBinIdx = pathHilbert.getValAtXY(curveX, curveY);
const curveStateInfo = curveMappedStatesZoomByBinIndex[curveBinIdx];

// console.log(`curveStateInfo ${JSON.stringify(curveStateInfo)}`);

try {
const curveColorIdx = curveStateInfo.idx - 1;
const curveStateName =
curveStateInfo.idx !== -1 ? curveColorsForStates[curveColorIdx][0] : "";
const curveStateColorHex =
curveStateInfo.idx !== -1 ? curveColorsForStates[curveColorIdx][1] : "";
const curveStatePointsStartRange =
curveStateInfo.idx !== -1
? [
curveStateInfo.points[0].start,
curveStateInfo.points[curveStateInfo.points.length - 1].start
]
: [];

if (curveStateInfo.idx !== -1) {
nodeLabelHTML += renderPathTooltipRow("Bin", curveBinIdx);
nodeLabelHTML += renderPathTooltipRow(
"State",
`<span style="background-color:${curveStateColorHex}; display:inline-flex; height:8px; width:8px"></span> ${curveStateName}`
);
nodeLabelHTML += renderPathTooltipRow("Chrom", `chr${chromosome}`);
nodeLabelHTML += renderPathTooltipRow(
"Start",
`${curveStatePointsStartRange[0]}`
);
nodeLabelHTML += renderPathTooltipRow(
"End",
`${curveStatePointsStartRange[1]}`
);
nodeLabelHTML += "</table>";

const informationBox = valTooltip
.style("display", "inline")
.style("z-index", "1000")
.style("left", `${x + (xRight ? -180 : 20)}px`)
.style("top", `${y + (yBottom ? -110 : 20)}px`)
.html(nodeLabelHTML);
}
} catch (err) {
// console.log(`pathView | showPathTooltip | err > ${JSON.stringify(err)}`);
}
}

function clearPathTooltip() {
// console.log(`pathView > clearPathTooltip`);
d3.select("#val-tooltip").style("display", "none");
}

function drawPath(x, y) {
// console.log(`drawPath > ${[x, y]}`);

if (
zoomOptions.includes("Show Hilbert curve") === -1 ||
!mutable genomeSpaceZoomViewPathEnabled
)
return;

const pathPanelOrigin = rescaleOrigin(x, y);

if (
typeof pathPanelOrigin === "undefined" ||
typeof pathPanelOrigin.x === "undefined"
)
return;

// console.log(`pathPanelOrigin ${JSON.stringify(pathPanelOrigin)}`);

/*
From the starting curveBinIdx value, we have 2^(viewOrder - 2) allowed path vertices
types or "hits" before we know we are going off-screen, whatever the zoomOrder value
happens to be.

For instance, given a small subpath of a larger Hilbert curve (picture that more of
the curve is described outside the bounds of the subpath endpoints):

... - x
|
x -- x -- x x -- x
| | |
... x -- o x -- x
| |
x x -- x x
| | | |
x -- x x -- x

Starting at point 'o', we can go up, down, left, or right (U, D, L, R) no more than
two node units in either direction, before we are outside the visible bounds of the
subpath.

These labels (U, D, etc.) are contained in the array `curveZoomRange.pathVertices`.

We start at the centerpoint 'o' and walk along the path in reverse, decrementing an
"allowed move" counter for each direction from the values in this array.

In case we reach the start node, we halt. If we reach a negative value for an
allowed move, we halt. Either node index is the lower bound of the contiguous
subpath.

We start at the centerpoint 'o' and walk along the path in forward direction,
decrementing an "allowed move" counter for each direction. In case we reach the
end node, we halt. If we reach a negative value for a move, we halt. Either node
index is the upper bound of the subpath.

Once we have the lower and upper bounds, we can draw a path that starts and ends at
these points. We can also retrieve the genomic interval defined by these node bounds.
This is a "true" genomic interval in that it represents the path drawn from the
centerpoint outwards, until the path ends are out-of-view.

Perhaps we use a dash array (https://stackoverflow.com/questions/56822311/) or
perhaps write out a path that starts and ends with the desired path vertices, at the
required start and end points. Either way we only draw the portion of the path that
reflects the centerpoint selection and its contiguous subpath bounds.
*/

// if (mutable binSelectionStartIdx !== 0) {
// mutable binSelectionStartIdx = 0;
// }
// if (mutable binSelectionEndIdx !== Math.pow(4, zoomOrder) - 1) {
// mutable binSelectionEndIdx !== Math.pow(4, zoomOrder) - 1;
// }

const curveBinStepSize = parseInt(bpForChromosome / Math.pow(4, zoomOrder));
const curveBinIdx = linPos / curveBinStepSize;
const curveBinXY = pathHilbert.getXyAtVal(curveBinIdx);

if (mutable binSelectionIdx !== curveBinIdx) {
mutable binSelectionIdx = curveBinIdx;
mutable binSelectionStartPosition = adjustToBinWidth(
linPos,
binResolution
);
}

// console.log(
// `-> linPos ${linPos} | curveBinIdx ${curveBinIdx} | curveBinXY ${JSON.stringify(
// curveBinXY
// )} | curveZoomRange.pathVertices ${
// curveZoomRange.pathVertices[curveBinIdx]
// }`
// );

const allowedDirectionMoves = Math.pow(2, viewOrder - 1);
const backwardsDirectionChecks = Object.fromEntries(
["U", "D", "L", "R"].map((d) => [d, allowedDirectionMoves])
);
//
// adjust allowed direction moves, if the selected
// bin is along the edge of the functional annotation
// view
//
if (curveBinXY[0] < allowedDirectionMoves) {
backwardsDirectionChecks["L"] = curveBinXY[0];
backwardsDirectionChecks["R"] +=
allowedDirectionMoves - backwardsDirectionChecks["L"] - 1;
}
if (curveBinXY[0] > Math.pow(2, zoomOrder) - 1 - allowedDirectionMoves) {
backwardsDirectionChecks["R"] =
Math.pow(2, zoomOrder) - 1 - curveBinXY[0];
backwardsDirectionChecks["L"] +=
allowedDirectionMoves - backwardsDirectionChecks["R"] - 1;
}
if (curveBinXY[1] < allowedDirectionMoves) {
backwardsDirectionChecks["U"] = curveBinXY[1];
backwardsDirectionChecks["D"] +=
allowedDirectionMoves - backwardsDirectionChecks["U"] - 1;
}
if (curveBinXY[1] > Math.pow(2, zoomOrder) - 1 - allowedDirectionMoves) {
backwardsDirectionChecks["D"] =
Math.pow(2, zoomOrder) - 1 - curveBinXY[1];
backwardsDirectionChecks["U"] +=
allowedDirectionMoves - backwardsDirectionChecks["D"] - 1;
}
const forwardsDirectionChecks = { ...backwardsDirectionChecks };

const flipDirection = { L: "R", R: "L", U: "D", D: "U" };

// console.log(
// `start | backwardsDirectionChecks ${JSON.stringify(
// backwardsDirectionChecks
// )}`
// );

for (
let curveBinTestIdx = curveBinIdx - 1;
curveBinTestIdx >= 0;
--curveBinTestIdx
) {
const direction =
flipDirection[curveZoomRange.pathVertices[curveBinTestIdx]];

backwardsDirectionChecks[direction] -= 1;

switch (direction) {
case "L":
backwardsDirectionChecks["R"] += 1;
break;
case "R":
backwardsDirectionChecks["L"] += 1;
break;
case "U":
backwardsDirectionChecks["D"] += 1;
break;
case "D":
backwardsDirectionChecks["U"] += 1;
break;
}

// console.log(
// `curveBinTestIdx ${curveBinTestIdx} | direction ${direction} | backwardsDirectionChecks ${JSON.stringify(
// backwardsDirectionChecks
// )}`
// );

if (backwardsDirectionChecks[direction] < 0 || curveBinTestIdx === 0) {
if (mutable binSelectionStartIdx !== curveBinTestIdx) {
mutable binSelectionStartIdx = curveBinTestIdx;
}
break;
}
}

// console.log(
// `end | binSelectionStartIdx ${binSelectionStartIdx} | backwardsDirectionChecks ${JSON.stringify(
// backwardsDirectionChecks
// )}`
// );

// console.log(
// `start | forwardsDirectionChecks ${JSON.stringify(
// forwardsDirectionChecks
// )}`
// );

const maxBinTestIdx = Math.pow(4, zoomOrder) - 1;

for (
let curveBinTestIdx = curveBinIdx;
curveBinTestIdx <= maxBinTestIdx;
++curveBinTestIdx
) {
const direction = curveZoomRange.pathVertices[curveBinTestIdx];

forwardsDirectionChecks[direction] -= 1;

switch (direction) {
case "L":
forwardsDirectionChecks["R"] += 1;
break;
case "R":
forwardsDirectionChecks["L"] += 1;
break;
case "U":
forwardsDirectionChecks["D"] += 1;
break;
case "D":
forwardsDirectionChecks["U"] += 1;
break;
}

if (
forwardsDirectionChecks[direction] < 0 ||
curveBinTestIdx === maxBinTestIdx - 1
) {
if (mutable binSelectionEndIdx !== curveBinTestIdx + 1) {
mutable binSelectionEndIdx = curveBinTestIdx + 1;
}
break;
}

// console.log(
// `curveBinTestIdx ${curveBinTestIdx} | direction ${direction} | forwardsDirectionChecks ${JSON.stringify(
// forwardsDirectionChecks
// )}`
// );
}

// console.log(
// `end | binSelectionEndIdx ${binSelectionEndIdx} | forwardsDirectionChecks ${JSON.stringify(
// forwardsDirectionChecks
// )}`
// );

const root = d3.select(pathViewCanvas).style("text-align", "center");
root.selectAll("*").remove(); // clear out old SVG paths, or else they accumulate
const pathSvg = root
.append("svg")
.attr("id", "hilbertCurve")
.attr("class", "hilbertCurve")
.attr(
"transform",
`translate(${-pathPanelOrigin.x}, ${-pathPanelOrigin.y})` // move path along with origin
)
.attr(
"width",
genomeSpaceZoomViewContentCurveDimensions.w * 2 ** zoomOrder
)
.attr(
"height",
genomeSpaceZoomViewContentCurveDimensions.h * 2 ** zoomOrder
);

function constructPath() {
const canvas = pathSvg.append("g");
canvas.append("path").attr("class", "skeleton");
canvas.append("path");
pathSvg
.selectAll("path")
.datum(pathHilbertData)
.attr("d", (d) => pathHilbertPath(d.pathVertices))
.attr(
"stroke-dasharray",
`0 ${binSelectionStartIdx} ${
binSelectionEndIdx - binSelectionStartIdx
} ${Math.pow(4, zoomOrder) - binSelectionEndIdx - 1}`
)
.attr("transform", (d) => {
d.startCell = [0, 0];
// console.log(`d.cellWidth ${JSON.stringify(d.cellWidth)}`);
// console.log(`d.startCell ${JSON.stringify(d.startCell)}`);
return `scale(${d.cellWidth / pathScaleFactor}) translate(${
d.startCell[0] + 0.5
}, ${d.startCell[1] + 0.5})`;
});

const curveX =
zoomFactor * curveBinXY[0] * curveZoomRange.cellWidth * 0.9985;
const curveY =
zoomFactor * curveBinXY[1] * curveZoomRange.cellWidth * 0.9985;

// console.log(
// `curveBinXY ${curveBinXY} | curveX ${curveX} | curveY ${curveY}`
// );

const maxZoomOrder = 12;
const selectedBinAttr = {
x: curveX + curveZoomRange.cellWidth / 25,
y: curveY + curveZoomRange.cellWidth / 25,
width: zoomFactor * curveZoomRange.cellWidth * 0.95,
height: zoomFactor * curveZoomRange.cellWidth * 0.95,
strokeWidth: Math.min(maxZoomOrder - zoomOrder, 3)
};
// console.log(`selectedBinAttr ${JSON.stringify(selectedBinAttr)}`);

const selectedBin = pathSvg.append("g");
selectedBin
.append("rect")
.attr("class", "selectionBox")
.attr("x", selectedBinAttr.x)
.attr("y", selectedBinAttr.y)
.attr("width", selectedBinAttr.width)
.attr("height", selectedBinAttr.height)
.attr("fill", "none")
.attr("stroke", "#300")
.attr("stroke-width", `${selectedBinAttr.strokeWidth}`);
}
constructPath();
}

pathViewCanvas.drawPath = drawPath;
pathViewCanvas.handlePathClick = handlePathClick;
pathViewCanvas.handlePathDoubleclick = handlePathDoubleclick;
pathViewCanvas.showPathBin = showPathBin;
pathViewCanvas.clearPathBin = clearPathBin;
pathViewCanvas.showPathTooltip = showPathTooltip;
pathViewCanvas.clearPathTooltip = clearPathTooltip;

return pathViewCanvas;
}
Insert cell
#### Zoom
Insert cell
zoomView = {
// ----------------------------------------------------------------
//
// zoom view
//

let zoomViewCanvas = html`<canvas width=${genomeSpaceZoomViewContentCurveDimensions.w} height=${genomeSpaceZoomViewContentCurveDimensions.h} style="position: absolute; z-index: 1; top:0px; left:0px;"></canvas>`;
zoomViewCanvas.width = genomeSpaceZoomViewContentCurveDimensions.w;
zoomViewCanvas.height = genomeSpaceZoomViewContentCurveDimensions.h;
let zvcCtx = zoomViewCanvas.getContext("2d");

function drawZoom(x, y) {
// console.log(`drawZoom ${[x, y]}`);
zvcCtx.fillStyle = "#f0f0f0";
zvcCtx.clearRect(
0,
0,
genomeSpaceZoomViewContentCurveDimensions.w,
genomeSpaceZoomViewContentCurveDimensions.h
);
zvcCtx.fillRect(
0,
0,
genomeSpaceZoomViewContentCurveDimensions.w,
genomeSpaceZoomViewContentCurveDimensions.h
);
const zvcW = curveRectSize;
const zvcH = zvcW;

// console.log(
// `drawZoom > raw x rescaled ${JSON.stringify(x / pointXY.stepSize)}`
// );
// console.log(
// `drawZoom > raw y rescaled ${JSON.stringify(y / pointXY.stepSize)}`
// );

const origin = { x: x - zvcW / 2, y: y - zvcH / 2 };

// console.log(`drawZoom > origin (before) ${JSON.stringify(origin)}`);

if (origin.x <= 0) {
origin.x = 0;
}
if (origin.x + zvcW >= genomeSpaceZoomViewContentCurveDimensions.w) {
origin.x = genomeSpaceZoomViewContentCurveDimensions.w - zvcW;
}
if (origin.y <= 0) {
origin.y = 0;
}
if (origin.y + zvcH >= genomeSpaceZoomViewContentCurveDimensions.h) {
origin.y = genomeSpaceZoomViewContentCurveDimensions.h - zvcH;
}

// console.log(`drawZoom > origin (after) ${JSON.stringify(origin)}`);

// console.log(
// `drawZoom > genomeSpaceZoomViewContentCurveDimensions ${JSON.stringify(
// genomeSpaceZoomViewContentCurveDimensions
// )}`
// );

zvcCtx.drawImage(
zoomViewScaledCanvas,
origin.x * zoomViewScale,
origin.y * zoomViewScale,
zvcW * zoomViewScale,
zvcH * zoomViewScale,
0,
0,
genomeSpaceZoomViewContentCurveDimensions.w,
genomeSpaceZoomViewContentCurveDimensions.h
);
}

zoomViewCanvas.drawZoom = drawZoom;

return zoomViewCanvas;
}
Insert cell
#### Zoom score
Insert cell
zoomScoreView = {
let zoomScoreCanvas = html`<canvas name="scores" width=${genomeSpaceZoomScoreViewContentDimensions.w} height=${genomeSpaceZoomScoreViewContentDimensions.h} style="position:absolute; z-index: 0;"></canvas>`;
zoomScoreCanvas.name = "scores";
zoomScoreCanvas.width = genomeSpaceZoomScoreViewContentDimensions.w;
zoomScoreCanvas.height = genomeSpaceZoomScoreViewContentDimensions.h;

const pathScaleFactor =
curveRectSize / genomeSpaceZoomViewContentCurveDimensions.w;
const pathPanelWidth = curveRectSize;
const pathPanelHeight = pathPanelWidth;

const zoomFactor = 2 ** (zoomOrder - viewOrder);

const positionIndicatorHeight = 10;

const markerPoly = [
{ x: 0, y: 10 },
{ x: 3.5, y: 4 },
{ x: 7, y: 10 }
];

const padding = { top: 0, bottom: 0, left: 0, right: 0 };
const scaledPanelWidth = zoomScoreCanvas.width - padding.left - padding.right;
const scaledPanelHeight =
zoomScoreCanvas.height - padding.top - padding.bottom;
let chromatinStatesSubsetReplacement = new Array(scaledPanelWidth);
let chromatinStatesSubsetToUse = genomeSpaceScoresChromatinStatesSubset;

function rescaleOrigin(x, y) {
const rescaledOrigin = {
x: x - pathPanelWidth / 2,
y: y - pathPanelHeight / 2
};
if (rescaledOrigin.x <= 0) {
rescaledOrigin.x = 0;
}
if (
rescaledOrigin.x + pathPanelWidth >=
genomeSpaceZoomViewContentCurveDimensions.w
) {
rescaledOrigin.x =
genomeSpaceZoomViewContentCurveDimensions.w - pathPanelWidth;
}
if (rescaledOrigin.y <= 0) {
rescaledOrigin.y = 0;
}
if (
rescaledOrigin.y + pathPanelHeight >=
genomeSpaceZoomViewContentCurveDimensions.h
) {
rescaledOrigin.y =
genomeSpaceZoomViewContentCurveDimensions.h - pathPanelHeight;
}
// rescale origin by factor
rescaledOrigin.x /= pathScaleFactor;
rescaledOrigin.y /= pathScaleFactor;

return rescaledOrigin;
}

let zsCtx = zoomScoreCanvas.getContext("2d");

function showZoomScoresBinHighlightForBin(binIdx) {
// console.log(`showZoomScoresBinHighlightForBin ${binIdx}`);

// console.log(`highlightBinIdx ${highlightBinIdx}`);

const root = d3.select("#genomeSpaceZoomScoreViewContent");
root.selectAll("svg").remove();
const markerSvg = root
.append("svg")
.attr("class", "scoreBinMarker")
.attr("width", genomeSpaceZoomScoreViewContentDimensions.w)
.attr("height", genomeSpaceZoomScoreViewContentDimensions.h);

function constructMarker() {
// console.log(`constructMarker`);

const verticalFactor =
(mutable binSelectionEndIdx - binIdx) /
(mutable binSelectionEndIdx - mutable binSelectionStartIdx);
// console.log(`verticalFactor ${verticalFactor}`);
const marker = markerSvg.append("g");
marker
.selectAll("polygon")
.data([markerPoly])
.enter()
.append("polygon")
.attr(
"transform",
`translate(${
genomeSpaceZoomScoreViewContentDimensions.w -
verticalFactor * genomeSpaceZoomScoreViewContentDimensions.w -
2.5
}, ${
genomeSpaceZoomScoreViewContentDimensions.h -
positionIndicatorHeight
})`
)
.attr("points", function (d) {
return d
.map(function (d) {
return [d.x, d.y].join(",");
})
.join(" ");
})
.attr("fill", "#00f")
.attr("stroke", "#00f")
.attr("stroke-width", "1");
}
constructMarker();
}

function showZoomScoresBinHighlightViaXY(x, y) {
// console.log(`showZoomScoresBinHighlightViaXY ${[x, y]}`);
const pathPanelOrigin = rescaleOrigin(pointXY.x, pointXY.y);

if (
typeof pathPanelOrigin === "undefined" ||
typeof pathPanelOrigin.x === "undefined"
)
return;

const curveX = (x + pathPanelOrigin.x) / zoomFactor;
const curveY = (y + pathPanelOrigin.y) / zoomFactor;
const curveBinIdx = pathHilbert.getValAtXY(curveX, curveY);
// const curveStateInfo = curveMappedStatesZoomByBinIndex[curveBinIdx];

showZoomScoresBinHighlightForBin(curveBinIdx);
}

function showZoomBinHighlightForBin(binIdx) {
const binDatum = curveZoom.getXyAtVal(binIdx);
const pointX = binDatum[0] * curveZoomRange.cellWidth;
const pointY = binDatum[1] * curveZoomRange.cellWidth;
// console.log(`pointX ${pointX} pointY ${pointY}`);
nearestBaseToClickXYInView({ x: pointX, y: pointY }, curveZoom, zoomOrder);
}

function drawZoomScores() {
zsCtx.fillStyle = "red";
zsCtx.fillRect(0, 0, zoomScoreCanvas.width, zoomScoreCanvas.height);

let fontHeight = 22;
let noIntervalsFoundMessage = "Loading score data...";
const getMaxValue = (numbers) => numbers.reduce((a, b) => Math.max(a, b));
const getMaxIndex = (numbers) =>
numbers.reduce((a, b, i) => (a[0] < b ? [b, i] : a), [
Number.MIN_VALUE,
-1
]);

zsCtx.save();
zsCtx.translate(padding.left, padding.top);
zsCtx.fillStyle = "black";
zsCtx.fillRect(0, 0, scaledPanelWidth, scaledPanelHeight);
zsCtx.restore();

if (
!genomeSpaceScoresChromatinStatesSubset ||
genomeSpaceScoresChromatinStatesSubset.length === 0
) {
// console.log(
// `zoomScoreView | genomeSpaceScoresChromatinStatesSubset.length ${genomeSpaceScoresChromatinStatesSubset.length}`
// );
zsCtx.save();
zsCtx.font = `${fontHeight}px Helvetica`;
zsCtx.fillStyle = "#fff";
zsCtx.textAlign = "center";
zsCtx.fillText(
noIntervalsFoundMessage,
scaledPanelWidth / 2,
scaledPanelHeight / 2 + fontHeight / 2
);
zsCtx.restore();
} else {
try {
// console.log(`zoomScoreView | start`);
zsCtx.save();
let positionIndicatorWidth =
zoomScoreCanvas.width - padding.left - padding.right;
let chromatinStatesSubsetGraphWidth =
zoomScoreCanvas.width - padding.left - padding.right;
let chromatinStatesSubsetGraphHeight =
zoomScoreCanvas.height -
padding.top -
padding.bottom -
positionIndicatorHeight +
2;
let chromatinStatesSubsetBins =
genomeSpaceScoresChromatinStatesSubset.length;
let chromatinStatesSubsetPerBinWidth =
chromatinStatesSubsetGraphWidth / chromatinStatesSubsetBins;

// console.log(
// `zoomScoreView | chromatinStatesSubsetBins ${chromatinStatesSubsetBins}`
// );
// console.log(
// `zoomScoreView | chromatinStatesSubsetPerBinWidth ${chromatinStatesSubsetPerBinWidth}`
// );

// to map 1D score bins in the score subview to 2D bin cells in the zoom subview
// we can walk through and get the start position of each bin in the bin selection
// path, and compare that with chromatin states bin position data

// if there are more bins than available pixels along the panel width,
// then we modify the chromatin state subset by applying a rudimentary
// aggregation function that retrieves the highest scoring element
// from consecutive groups of neighboring bins, constructing a
// replacement subset

// let chromatinStatesSubsetReplacement = new Array(scaledPanelWidth);
// let chromatinStatesSubsetToUse = genomeSpaceScoresChromatinStatesSubset;

let chromatinStatesSubsetBinToUseForCurveBinSelectionLabel = null;
let chromatinStatesSubsetBinToUseForCurveBinSelectionLabelIdx = -1;

let curveBinIdx = -1;

if (chromatinStatesSubsetPerBinWidth < 1) {
chromatinStatesSubsetPerBinWidth = 1;
let chromatinStatesSubsetBinsPerPixel = parseFloat(
chromatinStatesSubsetBins / scaledPanelWidth
);
// console.log(
// `zoomScoreView | aggregate | chromatinStatesSubsetBinsPerPixel ${chromatinStatesSubsetBinsPerPixel}`
// );

// console.log(
// `zoomScoreView | genomeSpaceScoresChromatinStatesSubset[2] ${JSON.stringify(
// genomeSpaceScoresChromatinStatesSubset[2]
// )}`
// );

// {"start":31452200,"score":0.77,"idx":14}

if (zoomOrder < 9) {
// console.log(`zoomScoreView | aggregate | ${zoomOrder} < 9`);
for (let index = 0; index < scaledPanelWidth; ++index) {
let chromatinStatesSubsetSliceStart =
index * chromatinStatesSubsetBinsPerPixel;
let chromatinStatesSubsetSliceEnd =
(index + 1) * chromatinStatesSubsetBinsPerPixel;
if (chromatinStatesSubsetSliceEnd >= chromatinStatesSubsetBins)
chromatinStatesSubsetSliceEnd = chromatinStatesSubsetBins - 1;

// console.log(
// `zoomScoreView | chromatinStatesSubsetSliceStart ${chromatinStatesSubsetSliceStart}`
// );
// console.log(
// `zoomScoreView | chromatinStatesSubsetSliceEnd ${chromatinStatesSubsetSliceEnd}`
// );

if (
chromatinStatesSubsetSliceStart >= chromatinStatesSubsetSliceEnd
)
break;
let chromatinStatesSubsetSlice = genomeSpaceScoresChromatinStatesSubset.slice(
chromatinStatesSubsetSliceStart,
chromatinStatesSubsetSliceEnd
);
let chromatinStatesSubsetSliceMaxScore = getMaxValue(
chromatinStatesSubsetSlice.map((d) => d.score)
);
let chromatinStatesSubsetSliceMaxScoreIndex = getMaxIndex(
chromatinStatesSubsetSlice.map((d) => d.score)
)[1]; // note de-indexing
let chromatinStatesSubsetSliceMaxIndex =
chromatinStatesSubsetSlice[
chromatinStatesSubsetSliceMaxScoreIndex
].idx;
let chromatinStatesSubsetSliceMaxStart =
chromatinStatesSubsetSlice[
chromatinStatesSubsetSliceMaxScoreIndex
].start;

curveBinIdx =
curveMappedStatesZoomPositionToBinIndexMap[
chromatinStatesSubsetSliceMaxStart
];

chromatinStatesSubsetReplacement[index] = {
start: chromatinStatesSubsetSliceMaxStart,
score: chromatinStatesSubsetSliceMaxScore,
idx: chromatinStatesSubsetSliceMaxIndex,
curveBinIdx: curveBinIdx
};

if (
index > 0 &&
chromatinStatesSubsetReplacement[index].curveBinIdx >=
mutable binSelectionIdx &&
chromatinStatesSubsetReplacement[index - 1].curveBinIdx <
mutable binSelectionIdx
) {
chromatinStatesSubsetBinToUseForCurveBinSelectionLabel =
chromatinStatesSubsetReplacement[index];
chromatinStatesSubsetBinToUseForCurveBinSelectionLabelIdx = index;
} else if (index === 0) {
chromatinStatesSubsetBinToUseForCurveBinSelectionLabel =
chromatinStatesSubsetReplacement[index];
chromatinStatesSubsetBinToUseForCurveBinSelectionLabelIdx = index;
}
}
} else if (zoomOrder >= 9) {
// console.log(`zoomScoreView | aggregate | ${zoomOrder} >= 9`);
const binSelectionIdxPixelOffset = parseInt(
(scaledPanelWidth * (binSelectionIdx - binSelectionStartIdx)) /
(binSelectionEndIdx - binSelectionStartIdx)
);
// console.log(`---`);
// console.log(
// `binSelectionStartPosition ${binSelectionStartPosition}`
// );
// console.log(`binSelectionStartIdx ${binSelectionStartIdx}`);
// console.log(`binSelectionEndIdx ${binSelectionEndIdx}`);
// console.log(
// `genomeSpaceScoresChromatinStatesSubset ${genomeSpaceScoresChromatinStatesSubset.length}`
// );
// console.log(`---`);
for (let index = 0; index < scaledPanelWidth; ++index) {
let chromatinStatesSubsetSliceStart =
index * chromatinStatesSubsetBinsPerPixel;
let chromatinStatesSubsetSliceEnd =
(index + 1) * chromatinStatesSubsetBinsPerPixel;
if (chromatinStatesSubsetSliceEnd >= chromatinStatesSubsetBins)
chromatinStatesSubsetSliceEnd = chromatinStatesSubsetBins - 1;

if (
chromatinStatesSubsetSliceStart >= chromatinStatesSubsetSliceEnd
)
break;
let chromatinStatesSubsetSlice = genomeSpaceScoresChromatinStatesSubset.slice(
chromatinStatesSubsetSliceStart,
chromatinStatesSubsetSliceEnd
);
let chromatinStatesSubsetSliceMaxScore = getMaxValue(
chromatinStatesSubsetSlice.map((d) => d.score)
);
let chromatinStatesSubsetSliceMaxScoreIndex = getMaxIndex(
chromatinStatesSubsetSlice.map((d) => d.score)
)[1]; // note de-indexing
let chromatinStatesSubsetSliceMaxIndex =
chromatinStatesSubsetSlice[
chromatinStatesSubsetSliceMaxScoreIndex
].idx;
let chromatinStatesSubsetSliceMaxStart =
chromatinStatesSubsetSlice[
chromatinStatesSubsetSliceMaxScoreIndex
].start;

chromatinStatesSubsetReplacement[index] = {
start: chromatinStatesSubsetSliceMaxStart,
score: chromatinStatesSubsetSliceMaxScore,
idx: chromatinStatesSubsetSliceMaxIndex,
curveBinIdx: curveBinIdx
};

// console.log(
// `test ${index} | binSelectionStartPosition ${binSelectionStartPosition} | ${JSON.stringify(
// chromatinStatesSubsetReplacement[index - 1]
// )} | ${JSON.stringify(
// chromatinStatesSubsetReplacement[index]
// )} `
// );

if (chromatinStatesSubsetReplacement[index] && index > 0) {
if (
chromatinStatesSubsetReplacement[index - 1].start <
binSelectionStartPosition &&
binSelectionStartPosition <=
chromatinStatesSubsetSlice[
chromatinStatesSubsetSliceMaxScoreIndex
].start
) {
chromatinStatesSubsetReplacement[index] = {
start: chromatinStatesSubsetSliceMaxStart,
score: chromatinStatesSubsetSliceMaxScore,
idx: chromatinStatesSubsetSliceMaxIndex,
curveBinIdx: binSelectionIdx
};
chromatinStatesSubsetBinToUseForCurveBinSelectionLabel =
chromatinStatesSubsetReplacement[index];
chromatinStatesSubsetBinToUseForCurveBinSelectionLabelIdx = index;
}
}
}
}

// console.log(
// `chromatinStatesSubsetReplacement[0] ${JSON.stringify(
// chromatinStatesSubsetReplacement[0]
// )} | curveMappedStatesZoomPositionToBinIndexMap[start] ${
// curveMappedStatesZoomPositionToBinIndexMap[33027400]
// }`
// );

chromatinStatesSubsetToUse = chromatinStatesSubsetReplacement;
} else {
// add the curve bin index for later lookup
let chromatinStatesSubsetReplacement = new Array(
chromatinStatesSubsetToUse.length
);
if (zoomOrder < 9) {
// console.log(`zoomScoreView | no agg | ${zoomOrder} < 9`);
chromatinStatesSubsetToUse.forEach((d, i) => {
chromatinStatesSubsetReplacement[i] = { ...d };
chromatinStatesSubsetReplacement[i].curveBinIdx =
curveMappedStatesZoomPositionToBinIndexMap[d.start];
if (
chromatinStatesSubsetReplacement[i].curveBinIdx ===
mutable binSelectionIdx
) {
chromatinStatesSubsetBinToUseForCurveBinSelectionLabel =
chromatinStatesSubsetReplacement[i];
chromatinStatesSubsetBinToUseForCurveBinSelectionLabelIdx = i;
}
});
} else if (zoomOrder >= 9) {
// console.log(`zoomScoreView | no agg | ${zoomOrder} >= 9`);
const binSelectionIdxSubsetOffset = parseInt(
(chromatinStatesSubsetToUse.length *
(binSelectionIdx - binSelectionStartIdx)) /
(binSelectionEndIdx - binSelectionStartIdx)
);
chromatinStatesSubsetToUse.forEach((d, i) => {
chromatinStatesSubsetReplacement[i] = { ...d };
chromatinStatesSubsetReplacement[i].curveBinIdx = curveBinIdx;
if (i === binSelectionIdxSubsetOffset) {
chromatinStatesSubsetBinToUseForCurveBinSelectionLabel =
chromatinStatesSubsetReplacement[i];
chromatinStatesSubsetBinToUseForCurveBinSelectionLabelIdx = i;
}
});
}
chromatinStatesSubsetToUse = chromatinStatesSubsetReplacement;
}

// console.log(
// `chromatinStatesSubsetBinToUseForCurveBinSelectionLabel ${JSON.stringify(
// chromatinStatesSubsetBinToUseForCurveBinSelectionLabel
// )}`
// );

// console.log(
// `zoomScoreView | chromatinStatesSubsetToUse.length ${JSON.stringify(
// chromatinStatesSubsetToUse.length
// )} | path.length ${
// binSelectionEndIdx - binSelectionStartIdx
// } | chromatinStatesSubsetToUse[1143/2] ${JSON.stringify(
// chromatinStatesSubsetToUse[parseInt(1134.2)]
// )}`
// );

let chromatinStatesSubsetMaxScore = getMaxValue(
chromatinStatesSubsetToUse.map((d) => d.score)
);
let chromatinStatesSubsetBinNormalizedHeights = chromatinStatesSubsetToUse.map(
(d) =>
(d.score / chromatinStatesSubsetMaxScore) *
chromatinStatesSubsetGraphHeight
);
chromatinStatesSubsetToUse.forEach((d, i) => {
zsCtx.fillStyle = curveColorsForStates[d.idx - 1][1];
zsCtx.fillRect(
i * chromatinStatesSubsetPerBinWidth,
chromatinStatesSubsetGraphHeight -
chromatinStatesSubsetBinNormalizedHeights[i],
chromatinStatesSubsetPerBinWidth - 0.1,
chromatinStatesSubsetBinNormalizedHeights[i]
);
});

zsCtx.fillStyle = "#fff";
zsCtx.fillRect(
0,
chromatinStatesSubsetGraphHeight + 1,
positionIndicatorWidth,
positionIndicatorHeight
);

// console.log(
// `chromatinStatesSubsetBinToUseForCurveBinSelectionLabelIdx ${chromatinStatesSubsetBinToUseForCurveBinSelectionLabelIdx}`
// );

if (chromatinStatesSubsetBinToUseForCurveBinSelectionLabelIdx !== -1) {
zsCtx.fillStyle = "#000";
zsCtx.fillRect(
chromatinStatesSubsetBinToUseForCurveBinSelectionLabelIdx *
chromatinStatesSubsetPerBinWidth -
3,
chromatinStatesSubsetGraphHeight + 6.5,
1,
positionIndicatorHeight
);
zsCtx.fillRect(
chromatinStatesSubsetBinToUseForCurveBinSelectionLabelIdx *
chromatinStatesSubsetPerBinWidth -
2,
chromatinStatesSubsetGraphHeight + 5,
1,
positionIndicatorHeight
);
zsCtx.fillRect(
chromatinStatesSubsetBinToUseForCurveBinSelectionLabelIdx *
chromatinStatesSubsetPerBinWidth -
1,
chromatinStatesSubsetGraphHeight + 3.5,
1,
positionIndicatorHeight
);
zsCtx.fillRect(
chromatinStatesSubsetBinToUseForCurveBinSelectionLabelIdx *
chromatinStatesSubsetPerBinWidth,
chromatinStatesSubsetGraphHeight + 2,
1,
positionIndicatorHeight
);
zsCtx.fillRect(
chromatinStatesSubsetBinToUseForCurveBinSelectionLabelIdx *
chromatinStatesSubsetPerBinWidth +
1,
chromatinStatesSubsetGraphHeight + 3.5,
1,
positionIndicatorHeight
);
zsCtx.fillRect(
chromatinStatesSubsetBinToUseForCurveBinSelectionLabelIdx *
chromatinStatesSubsetPerBinWidth +
2,
chromatinStatesSubsetGraphHeight + 5,
1,
positionIndicatorHeight
);
zsCtx.fillRect(
chromatinStatesSubsetBinToUseForCurveBinSelectionLabelIdx *
chromatinStatesSubsetPerBinWidth +
3,
chromatinStatesSubsetGraphHeight + 6.5,
1,
positionIndicatorHeight
);
}

// const d = genomeSpaceScoresChromatinStatesSubset.filter((d) => {
// d.start === mutable binSelectionStartPosition;
// });
// const i = genomeSpaceScoresChromatinStatesSubset.indexOf(d);

zsCtx.restore();
} catch (err) {
console.log(`zoomScoreView | error ${JSON.stringify(err)}`);
zsCtx.save();
zsCtx.font = `${fontHeight}px Helvetica`;
zsCtx.fillStyle = "#fff";
zsCtx.textAlign = "center";
zsCtx.fillText(
noIntervalsFoundMessage,
scaledPanelWidth / 2,
scaledPanelHeight / 2 + fontHeight / 2
);
zsCtx.restore();
}
}
}

zoomScoreCanvas.showZoomScoresBinHighlightForBin = showZoomScoresBinHighlightForBin;
zoomScoreCanvas.showZoomScoresBinHighlightViaXY = showZoomScoresBinHighlightViaXY;
zoomScoreCanvas.showZoomBinHighlightForBin = showZoomBinHighlightForBin;
zoomScoreCanvas.drawZoomScores = drawZoomScores;

return zoomScoreCanvas;
}
Insert cell
#### Estimated genomic range
Insert cell
estimatedGenomicRangeFromAllViewFrame = (value) => {
// ---------------------------------------------------------------------------------------------
// estimated genomic position header
//

// console.log(`estimatedGenomicRangeFromAllViewFrame | zoomOrder ${zoomOrder}`);

let iz = genomeSpaceViewXYToGenomicInterval(
curveZoom,
value
// typeof value !== "undefined" ? value : viewof genomeSpace.value
);

// const stepSize = parseInt(bpForChromosome / Math.pow(4, zoomOrder - 1));
const stepSize = parseInt(bpForChromosome / Math.pow(4, zoomOrder));
const overlayGenomicCoverage = parseInt(
bpForChromosome / 4 ** (zoomOrder - viewOrder)
);
if (!iz) {
iz = {};
iz.chrom = chromosome;
iz.start = 0;
iz.stop = iz.start + stepSize;
}
if (iz.start > iz.stop) {
let temp = iz.start;
iz.start = iz.stop;
iz.stop = temp;
} else if (iz.start === iz.stop) {
iz.start += Math.ceil(stepSize / 2) - stepSize;
iz.stop = iz.start + stepSize;
} else if (iz.start >= Math.ceil(stepSize / 2)) {
iz.start -= Math.ceil(stepSize / 2);
} else if (iz.stop >= bpForChromosome) {
iz.start = bpForChromosome - stepSize;
iz.stop = bpForChromosome;
}
const estimatedGenomicRange = {
chrom: iz.chrom,
start:
iz.start - overlayGenomicCoverage / 2 > 0
? parseInt(iz.start - overlayGenomicCoverage / 2)
: 0,
stop:
iz.stop + overlayGenomicCoverage / 2 < bpForChromosome
? parseInt(iz.stop + overlayGenomicCoverage / 2)
: bpForChromosome - 1
};
iz.estRange = estimatedGenomicRange;
iz.estRange.start = adjustToBinWidth(iz.estRange.start, binResolution);
iz.estRange.stop = adjustToBinWidth(iz.estRange.stop, binResolution);

// console.log(
// `estimatedGenomicRangeFromAllViewFrame | iz.estRange ${JSON.stringify(
// iz.estRange
// )}`
// );
// console.log(
// `estimatedGenomicRangeFromAllViewFrame | genomeSpaceEstimatedZoomFrame ${JSON.stringify(
// genomeSpaceEstimatedZoomFrame
// )}`
// );

//
// avoid unnecessary updates and redraws
//
if (
genomeSpaceEstimatedZoomFrame.start === iz.estRange.start &&
genomeSpaceEstimatedZoomFrame.stop === iz.estRange.stop
)
return;

set(viewof linPos, iz.estRange.start);

mutable genomeSpaceEstimatedZoomFrame = iz.estRange;
}
Insert cell
### Data and Hilbert curve functions
Insert cell
function adjustToBinWidth(pos, width) {
return Math.round(pos / width) * width;
}
Insert cell
function adjustIntervalCoordinatesToBinWidth(interval, binSize) {
return interval
? {
chrom: interval.chrom,
start: adjustToBinWidth(interval.start, binSize),
stop: adjustToBinWidth(interval.stop, binSize)
}
: null;
}
Insert cell
baseOrder = Math.ceil(Math.log(bpForChromosome) / Math.log(4))
Insert cell
binResolution = 200
Insert cell
function binarySearch(sortedArray, key) {
let start = 0;
let end = sortedArray.length - 1;
while (start <= end) {
let middle = Math.floor((start + end) / 2);
if (sortedArray[middle] === key) {
// found the key
return middle;
} else if (
middle > 0 &&
sortedArray[middle - 1] < key &&
sortedArray[middle] > key
) {
return middle;
} else if (
middle < sortedArray.length - 1 &&
sortedArray[middle + 1] > key &&
sortedArray[middle] < key
) {
return middle;
} else if (sortedArray[middle] < key) {
// continue searching to the right
start = middle + 1;
} else {
// search searching to the left
end = middle - 1;
}
}
// key wasn't found
return -1;
}
Insert cell
bpForChromosome = {
// Instead of approximating chromosome size, make it a nice even power of four.
// This should limit offset errors when jumping by power-of-four based step sizes.
const maxStart = d3.max(dataStates, (v) => v.start);
const niceEvenPower = Math.ceil(Math.log(maxStart) / Math.log(4));
return 4 ** niceEvenPower;
}
Insert cell
chromosomes = [
1,
2,
3,
4,
5,
6,
7,
8,
9,
10,
11,
12,
13,
14,
15,
16,
17,
18,
19,
20,
21,
22,
"X"
]
Insert cell
dataStates = dataStatesZip
.get(dataStatesCsv)
.then((d) => d3.csvParse(d, d3.autoType))
Insert cell
dataStatesCsv = "chr" + chromosome + "_states.csv"
Insert cell
dataStatesZip = fetch(dataStatesZipUrl).then(zipreader)
Insert cell
dataStatesZipUrl = proxyUrlPrefix + dataStatesCsv + ".zip"
Insert cell
curveAggregator = (points) => {
// This is just taking the first state...
// (points will be a list of the original values, with score and state properties
// return points[0].idx
// Instead, use the mid-point?
//return points[Math.floor(points.length/2)].idx
// Rather, use the max-scoring idx:
let max_score = 0, max_idx = NaN;
for (let p of points) {
if (p.score > max_score) {
max_score = p.score
max_idx = p.idx
}
};
return max_idx
}
Insert cell
curveAll = d3
.hilbert()
.order(viewOrder)
.canvasWidth(genomeSpaceAllViewContentDimensions.w)
.simplifyCurves(false)
Insert cell
curveZoom = d3
.hilbert()
.order(zoomOrder)
.canvasWidth(genomeSpaceZoomViewContentDimensions.w)
.simplifyCurves(false)
.layout(curveZoomRange)
Insert cell
curveZoomRange = {
return { start: 0, length: 4 ** zoomOrder };
}
Insert cell
curveBaseToView = (i, order) => Math.floor(i / Math.pow(4, baseOrder - order))
Insert cell
curveColorsForStates = [
["Active TSS", "#ff0000", "1"],
["Flanking Active TSS", "#ff4500", "2"],
["Transcr at gene 5' and 3'", "#32cd32", "3"],
["Strong transcription", "#008000", "4"],
["Weak transcription", "#006400", "5"],
["Genic enhancers", "#c2e105", "6"],
["Enhancers", "#ffff00", "7"],
["ZNF genes & repeats", "#66cdaa", "8"],
["Heterochromatin", "#8a91d0", "9"],
["Bivalent/Poised TSS", "#cd5c5c", "10"],
["Flanking Bivalent TSS/Enh", "#e9967a", "11"],
["Bivalent Enhancer", "#bdb76b", "12"],
["Repressed PolyComb", "#808080", "13"],
["Weak Repressed PolyComb", "#c0c0c0", "14"],
["Quiescent/Low", "#ffffff", "15"]
]
Insert cell
function curveGenomicIntervalToXY(hilbert, bp_pos, dimensions) {
const containerScale = 1;
const containerOffset = 0;
let xy = hilbert.getXyAtVal(
Math.round(bp_pos / Math.pow(4, baseOrder - hilbert.order()))
);

let stepSize = dimensions.w / 2 ** hilbert.order();
let xScale =
xy[0] * (stepSize * containerScale) + stepSize / 2 + containerOffset;
let yScale =
xy[1] * (stepSize * containerScale) + stepSize / 2 + containerOffset;
return {
x: xScale,
y: yScale,
stepSize: stepSize
};
}
Insert cell
curveGroupedStatesAll = d3.group(dataStates, (d) =>
curveBaseToView(d.start, viewOrder)
)
Insert cell
curveGroupedStatesZoom = d3.group(dataStates, (d) =>
curveBaseToView(d.start, zoomOrder)
)
Insert cell
curveMappedStatesAll = Array.from(curveGroupedStatesAll.entries()).map((d) => {
let xy = curveAll.getXyAtVal(d[0]);
return {
i: d[0],
x: xy[0],
y: xy[1],
idx: curveAggregator(d[1]),
scores: d3.max(d[1], (v) => v.score), // Changing this to max() to emphasize potential local super stars
points: d[1]
};
})
Insert cell
curveMappedStatesZoom = Array.from(curveGroupedStatesZoom.entries()).map(
(d) => {
let xy = curveZoom.getXyAtVal(d[0]);
return {
i: d[0],
x: xy[0],
y: xy[1],
idx: curveAggregator(d[1]),
scores: d3.max(d[1], (v) => v.score), // Changing this to max() to emphasize potential local super stars
points: d[1]
};
}
)
Insert cell
curveMappedStatesZoomBinIndexToPositionMap = {
if (zoomOrder > 8) return {};
const zoomBinsByIndex = curveMappedStatesZoomByBinIndex;
const binIndexToPositionMap = zoomBinsByIndex.map((d, i) => {
return { [i]: adjustToBinWidth(d.points[0].start, binResolution) };
});
const binIndexToPositionMapObject = Object.assign(
{},
...binIndexToPositionMap
);
// console.log(
// `binIndexToPositionMapObject ${JSON.stringify(binIndexToPositionMapObject)}`
// );
return binIndexToPositionMapObject;
}
Insert cell
curveMappedStatesZoomPositionToBinIndexMap = {
if (zoomOrder > 8) return {};
const zoomBinsByIndex = curveMappedStatesZoomByBinIndex;
const positionToBinIndexMap = zoomBinsByIndex.map((d, di) => {
const perBinDatum = {};
d.points.forEach((p, pi) => {
perBinDatum[adjustToBinWidth(d.points[pi].start, binResolution)] = di;
});
return perBinDatum;
});
const positionToBinIndexMapObject = Object.assign(
{},
...positionToBinIndexMap
);
// console.log(
// `positionToBinIndexMapObject ${JSON.stringify(positionToBinIndexMapObject)}`
// );
return positionToBinIndexMapObject;
}
Insert cell
curveMappedStatesZoomByBinIndex = {
const bins = 4 ** zoomOrder;
// console.log(`A | bins ${bins} | zoomOrder ${zoomOrder}`);
const zoomBinsByIndex = Array(bins).fill({});
let testBinIdx = 0;
// const maxZoomLevel = 11; // for all intents and purposes
// const estimatedBinCoverage = 2 ** (maxZoomLevel - zoomOrder) * binResolution; // 10: 200, 9: 400, 8: 800, etc.
let testBin = curveMappedStatesZoom[testBinIdx];
let testBinI = testBin.i;
let oldTestBinI = testBinI;
let testBinStart = testBin.points[0].start;
let oldStart = testBin.points[0].start;
let testBinStartFill = adjustToBinWidth(
Math.floor(testBinStart / testBinI),
binResolution
);
let emptyBinFill = 0;
let emptyBinLength = testBinI;

const emptyBin = {
i: -1,
x: -1,
y: -1,
idx: -1,
scores: 0,
points: []
};

for (let binIdx = 0; binIdx < bins; binIdx++) {
while (testBinI > binIdx) {
zoomBinsByIndex[binIdx] = {
i: binIdx,
x: -1,
y: -1,
idx: -1,
scores: 0,
points: [{ start: emptyBinFill }]
};
binIdx++;
emptyBinFill += testBinStartFill;
}
zoomBinsByIndex[binIdx] = JSON.parse(JSON.stringify(testBin));
emptyBinLength = 0;

testBin = curveMappedStatesZoom[++testBinIdx];
if (!testBin) break;
testBinI = testBin.i;
testBinStart = testBin.points[0].start;
oldStart = zoomBinsByIndex[binIdx].points[0].start;
oldTestBinI = binIdx;
emptyBinLength = testBinI - oldTestBinI + 1;
testBinStartFill = adjustToBinWidth(
parseInt((testBinStart - oldStart) / emptyBinLength),
binResolution
);
emptyBinFill = zoomBinsByIndex[binIdx].points[0].start + testBinStartFill;
}

emptyBinLength = bins - testBinI + 1;
testBinStartFill = adjustToBinWidth(
parseInt((bpForChromosome - oldStart) / emptyBinLength),
binResolution
);
emptyBinFill = testBinStart + testBinStartFill;
for (let binIdx = testBinI + 1; binIdx < bins; binIdx++) {
zoomBinsByIndex[binIdx] = {
i: binIdx,
x: -1,
y: -1,
idx: -1,
scores: 0,
points: [{ start: emptyBinFill }]
};
emptyBinFill += testBinStartFill;
}

// for (let binIdx = 0; binIdx < bins; ++binIdx) {
// try {
// while (testBinI > binIdx) {
// const estimatedBinCoverageElements = [
// { start: binIdx * estimatedBinCoverage },
// { start: (binIdx + 1) * estimatedBinCoverage }
// ];
// emptyBin.points = estimatedBinCoverageElements;
// zoomBinsByIndex[binIdx] = JSON.parse(JSON.stringify(emptyBin));
// binIdx++;
// }
// zoomBinsByIndex[binIdx] = JSON.parse(JSON.stringify(testBin));
// testBin = curveMappedStatesZoom[++testBinIdx];
// testBinI = testBin.i;
// } catch (err) {
// const estimatedBinCoverageElements = [
// { start: binIdx * estimatedBinCoverage },
// { start: (binIdx + 1) * estimatedBinCoverage }
// ];
// emptyBin.points = estimatedBinCoverageElements;
// zoomBinsByIndex[binIdx] = JSON.parse(JSON.stringify(emptyBin));
// }
// }

return zoomBinsByIndex;
}
Insert cell
curvePathAnimationDuration = zoomOrder * 1000
Insert cell
curvePathAnimationEaseFunction = d3.easeSinInOut // https://github.com/d3/d3-ease#api-reference
Insert cell
curveScaleAll = d3
.scaleLinear()
.domain([0, Math.sqrt(Math.pow(4, viewOrder))])
.range([0, genomeSpaceAllViewContentCurveDimensions.w])
Insert cell
curveScaleStatesAll = d3
.scaleLog()
.domain(d3.extent(curveMappedStatesAll, (d) => d.scores))
.range([0, 1])
Insert cell
curveScaleZoom = d3
.scaleLinear()
.domain([0, Math.sqrt(Math.pow(4, zoomOrder))])
.range([0, genomeSpaceZoomViewContentCurveDimensions.w])
Insert cell
curveScaleStatesZoom = d3
.scaleLog()
.domain(d3.extent(curveMappedStatesZoom, (d) => d.scores))
.range([0, 1])
Insert cell
curveWidthCellAll = genomeSpaceAllViewContentCurveDimensions.w /
Math.sqrt(Math.pow(4, viewOrder))
Insert cell
curveWidthCellZoom = genomeSpaceZoomViewContentCurveDimensions.w /
Math.sqrt(Math.pow(4, zoomOrder))
Insert cell
curveRectSizeSeq = 2 ** viewOrder / 2 ** (zoomOrder - viewOrder)
Insert cell
curveRectSize = genomeSpaceZoomViewContentCurveDimensions.w /
Math.pow(2, zoomOrder - viewOrder)
// curveRectSize = (curveRectSizeSeq *
// genomeSpaceZoomViewContentCurveDimensions.w) /
// 2 ** viewOrder
// curveRectSize = pointXY.stepSize * 2
Insert cell
genomeSpaceScoresChromatinStatesSubset = {
return genomeSpaceSliceDatasetByInterval(
dataStates,
binSelectionToGenomicInterval, // pointXYToGenomicInterval, // genomeSpaceEstimatedZoomFrame,
zoomOptions
);
}
Insert cell
function genomeSpaceSliceDatasetByInterval(dataset, interval, zoomOptions) {
const array = dataset.map((d) => adjustToBinWidth(d.start, binResolution));
if (!array || !interval) return [];
// ensure that interval start and stop positions are within searchable bounds
const firstDatasetStart = adjustToBinWidth(dataset[0].start, binResolution);
const lastDatasetStop = adjustToBinWidth(
dataset[dataset.length - 1].start,
binResolution
);
const intervalCopy = { start: interval.start, stop: interval.stop };
intervalCopy.start =
intervalCopy.start >= firstDatasetStart
? intervalCopy.start
: firstDatasetStart;
intervalCopy.stop =
intervalCopy.stop < lastDatasetStop ? intervalCopy.stop : lastDatasetStop;
// fix ordering, if necessary
const tempStartPosition = intervalCopy.start;
const tempStopPosition = intervalCopy.stop;
intervalCopy.start =
tempStartPosition < tempStopPosition
? intervalCopy.start
: tempStopPosition;
intervalCopy.stop =
tempStartPosition < tempStopPosition
? intervalCopy.stop
: tempStartPosition;
const start = binarySearch(array, intervalCopy.start);
const stop = binarySearch(array, intervalCopy.stop);
const dataSlice = dataset.slice(start, stop);
let binAdjustedDataSlice =
start !== -1 && stop !== -1
? dataSlice.map((d) => {
d.start = adjustToBinWidth(d.start, binResolution);
return d;
})
: [];
if (binAdjustedDataSlice.length === 0) return [];
// use zoomOptions list to modify data slice further
if (zoomOptions.includes("Include gaps in score view")) {
let gapAdjustedDataSlice = [];
let gapPosn = interval.start;
const defaultScore = 0.005;
const defaultColorIdx =
curveColorsForStates[curveColorsForStates.length - 1][2];
binAdjustedDataSlice.forEach((b) => {
// add gap bins until filled
while (gapPosn - binResolution < b.start) {
// console.log(`adding gapPosn ${gapPosn}`);
gapAdjustedDataSlice.push({
start: gapPosn,
score: defaultScore,
idx: defaultColorIdx
});
gapPosn += binResolution;
}
gapAdjustedDataSlice.push(b);
gapPosn += binResolution;
});
if (binAdjustedDataSlice) {
for (
let trailingBinStart =
binAdjustedDataSlice[binAdjustedDataSlice.length - 1].start +
binResolution;
trailingBinStart < interval.stop;
trailingBinStart += binResolution
) {
// console.log(`adding trailingBinStart ${trailingBinStart}`);
gapAdjustedDataSlice.push({
start: trailingBinStart,
score: defaultScore,
idx: defaultColorIdx
});
}
}
binAdjustedDataSlice = gapAdjustedDataSlice;
}
return binAdjustedDataSlice;
}
Insert cell
genomeSpaceViewToBase = (i, order) => {
return i * Math.pow(4, baseOrder - order);
}
Insert cell
function genomeSpaceViewXYToGenomicInterval(hilbert, view) {
let order = hilbert.order();
const container_offset = 0;
const container_scale = 1;
let correction =
view.x < genomeSpaceZoomViewContentCurveDimensions.w
? 0
: genomeSpaceZoomViewContentCurveDimensions.w;
let realX = (view.x - container_offset - correction) / container_scale;
let realY = (view.y - container_offset) / container_scale;
// console.log(`---`);
// console.log(
// `realX, realY ${[realX, realY]} ${hilbert.getValAtXY(realX, realY)}`
// );
if ((realX > 0) & (realY > 0)) {
let val = hilbert.getValAtXY(realX, realY);
let width = Math.pow(4, zoomOrder - 1 - 2 ** viewOrder);
// console.log(`val ${val} ${zoomLevel} ${width}`);
let v2b = genomeSpaceViewToBase(val, order);
let startVal = val - (val % width);
let stopVal = startVal + width;
// let intervalStart = genomeSpaceViewToBase(val - Math.floor(curveRectSizeSeq - 1), order);
let intervalStart = genomeSpaceViewToBase(
startVal - Math.ceil(curveRectSizeSeq - 1),
order
);
// let intervalStop = genomeSpaceViewToBase(val + Math.floor(curveRectSizeSeq), order);
let intervalStop = genomeSpaceViewToBase(
stopVal + Math.floor(curveRectSizeSeq),
order
);
// console.log(`intervalStart ${intervalStart} intervalStop ${intervalStop}`);
if (v2b >= 0 && v2b <= bpForChromosome) {
const interval = {
chrom: chromosome,
start: Math.max(0, parseInt(intervalStart)),
stop: Math.min(bpForChromosome, parseInt(intervalStop))
};
// console.log(
// `viewXYToGenomicInterval > interval (pre) ${JSON.stringify(interval)}`
// );
return interval;
}
}
}
Insert cell
genomeSpaceContainerMargin = {
return {
top: 1,
left: 1,
right: 1,
bottom: 1
};
}
Insert cell
genomeSpaceContainerPadding = {
return {
top: 1,
left: 1,
right: 1,
bottom: 1
};
}
Insert cell
genomeSpaceContentDimensions = {
return {
w:
viewportWidth -
genomeSpaceContainerMargin.left -
genomeSpaceContainerMargin.right -
genomeSpaceContainerPadding.left -
genomeSpaceContainerPadding.right,
h:
viewportHeight -
genomeSpaceContainerMargin.top -
genomeSpaceContainerMargin.bottom -
genomeSpaceContainerPadding.top -
genomeSpaceContainerPadding.bottom
};
}
Insert cell
genomeSpaceViewHeaderDimensions = {
return {
w: parseInt(genomeSpaceContentDimensions.w / 2),
h: genomeSpaceViewLabelFontPointSize + 4
};
}
Insert cell
genomeSpaceViewLabelFontPointSize = viewportIsFullscreen
? 18
: viewportWidth > 586
? 12
: 6 // mobile
Insert cell
genomeSpaceAllViewLabel = "Functional annotation"
Insert cell
genomeSpaceZoomViewLabel = {
const estimatedGenomicRangeFormatted = `chr${binSelectionToGenomicInterval.chrom}:${binSelectionToGenomicInterval.start}-${binSelectionToGenomicInterval.stop}`;
return html`<div style="display:flex;"><div style="margin-right:auto;">Zoom view</div><div style="padding-right:8px;">${estimatedGenomicRangeFormatted}</div></div>`;
}
Insert cell
mutable genomeSpaceEstimatedZoomFrame = {
return {}; // use mutable estimated interval for global access
}
Insert cell
function genomeSpaceZoomScoreViewLabelExtLinkClick() {
const body = document.body;
const externalLink = document.createElement("a");
externalLink.id = "epilogos_viewer_from_datum";
externalLink.target = "_blank";
const chrom = `chr${binSelectionToGenomicInterval.chrom}`;
const padding = parseInt(
(binSelectionToGenomicInterval.stop - binSelectionToGenomicInterval.start) /
2
);
const pos = binSelectionToGenomicInterval.start + padding;
externalLink.href = genomeSpaceZoomScoreViewLabelExtLinkUrlFromPosition(
chrom,
pos,
padding
);
body.appendChild(externalLink);
const evfd = document.getElementById("epilogos_viewer_from_datum");
document.getElementById("epilogos_viewer_from_datum").click();
body.removeChild(externalLink);
}
Insert cell
genomeSpaceZoomScoreViewLabelExtLinkUrlFromPosition = (chrom, pos, padding) => {
pos = parseInt(pos);
padding = parseInt(padding);
if (chrom && pos && pos > 0 && padding && padding > 0) {
const epilogosRootUrl = "https://epilogos.altius.org";
const application = "viewer";
const sampleSet = "vA";
const mode = "single";
const genome = "hg38";
const model = "15";
const complexity = "KL"; // i.e., S1
const group = "all";
const chrLeft = chrom;
const chrRight = chrom;
const start = pos - padding > 0 ? pos - padding : 0;
const stop = pos + padding;
const gatt = "cv";
const fqUrl = `${epilogosRootUrl}/?application=${application}&sampleSet=${sampleSet}&mode=${mode}&genome=${genome}&model=${model}&complexity=${complexity}&group=${group}&chrLeft=${chrLeft}&chrRight=${chrRight}&start=${start}&stop=${stop}&gatt=${gatt}`;
return fqUrl;
}
return null;
}
Insert cell
genomeSpaceZoomScoreViewLabel = {
const extLink = html`<button style="background-color: black; border: none; color: white; cursor: pointer; vertical-align: text-top;">${await icon(
"external-link"
).then((i) => {
i.style.fill = "white";
return i;
})}</i></button>`;
// https://talk.observablehq.com/t/why-html-button-onclick-displaydate-the-time-is-button-not-work/2633/4
Object.assign(extLink, {
onclick: genomeSpaceZoomScoreViewLabelExtLinkClick
});
return html`<div style="display:flex;"><div style="margin-right:auto;">Zoom view (scores)</div><div style="padding-right:8px;">${extLink}</div></div>`;
}
Insert cell
genomeSpaceAllViewDimensions = {
return {
w:
parseInt(genomeSpaceContentDimensions.w / 2) -
2 * genomeSpaceContainerMargin.left,
h:
parseInt(genomeSpaceContentDimensions.w / 2) -
2 * genomeSpaceContainerMargin.left +
genomeSpaceZoomViewHeaderHeight,
l: 0,
t: 0
};
}
Insert cell
genomeSpaceAllViewContentDimensions = {
return {
w:
genomeSpaceAllViewDimensions.w -
genomeSpaceContainerMargin.left -
genomeSpaceContainerMargin.right,
h:
genomeSpaceAllViewDimensions.h -
genomeSpaceAllViewHeaderHeight -
genomeSpaceContainerMargin.top -
genomeSpaceContainerMargin.bottom,
l: 0,
t: 0
};
}
Insert cell
genomeSpaceAllViewContentCurveDimensions = {
return {
w:
genomeSpaceAllViewContentDimensions.w -
genomeSpaceContainerMargin.left -
genomeSpaceContainerMargin.right,
h:
genomeSpaceAllViewContentDimensions.h -
genomeSpaceContainerMargin.top -
genomeSpaceContainerMargin.bottom,
l: 0,
t: 0
};
}
Insert cell
genomeSpaceAllViewHeaderHeight = genomeSpaceViewLabelFontPointSize * 2 + 4
Insert cell
genomeSpaceZoomViewDimensions = {
return {
w:
parseInt(genomeSpaceContentDimensions.w / 2) -
2 * genomeSpaceContainerMargin.left,
h:
parseInt(genomeSpaceContentDimensions.w / 2) -
2 * genomeSpaceContainerMargin.left +
genomeSpaceZoomViewHeaderHeight,
l:
genomeSpaceAllViewDimensions.w +
genomeSpaceContainerMargin.left +
genomeSpaceContainerMargin.right,
t: 0
};
}
Insert cell
genomeSpaceZoomViewContentDimensions = {
return {
w:
genomeSpaceZoomViewDimensions.w -
genomeSpaceContainerMargin.left -
genomeSpaceContainerMargin.right,
h:
genomeSpaceZoomViewDimensions.h -
genomeSpaceZoomViewHeaderHeight -
genomeSpaceContainerMargin.top -
genomeSpaceContainerMargin.bottom,
l:
genomeSpaceAllViewDimensions.w +
genomeSpaceContainerMargin.left +
genomeSpaceContainerMargin.right,
t: 0
};
}
Insert cell
genomeSpaceZoomViewContentCurveDimensions = {
return {
w:
genomeSpaceZoomViewContentDimensions.w -
genomeSpaceContainerMargin.left -
genomeSpaceContainerMargin.right,
h:
genomeSpaceZoomViewContentDimensions.h -
genomeSpaceContainerMargin.top -
genomeSpaceContainerMargin.bottom,
l: 0,
t: 0
};
}
Insert cell
genomeSpaceZoomViewHeaderHeight = genomeSpaceViewLabelFontPointSize * 2 + 4
Insert cell
genomeSpaceZoomScoreViewDimensions = {
return {
w:
genomeSpaceContentDimensions.w -
genomeSpaceContainerMargin.left -
genomeSpaceContainerMargin.right,
h:
genomeSpaceContentDimensions.h -
genomeSpaceAllViewDimensions.h -
2 * genomeSpaceContainerMargin.top -
2 * genomeSpaceContainerMargin.bottom,
t:
genomeSpaceAllViewDimensions.h +
genomeSpaceContainerMargin.top +
genomeSpaceContainerMargin.bottom,
l: 0
};
}
Insert cell
genomeSpaceZoomScoreViewContentDimensions = {
return {
w:
genomeSpaceContentDimensions.w -
2 * genomeSpaceContainerMargin.left -
2 * genomeSpaceContainerMargin.right -
1,
h:
genomeSpaceContentDimensions.h -
genomeSpaceAllViewDimensions.h -
3 * genomeSpaceContainerMargin.top -
3 * genomeSpaceContainerMargin.bottom,
t: 0,
l: 0
};
}
Insert cell
genomeSpaceStyle = html`<style>
${
notebookIsEmbedded ? "html,body,.observablehq { padding: 0; margin: 0; }" : ""
}
#genomeSpace {
height: ${viewportHeight}px;
width: ${viewportWidth}px;
// border: ${genomeSpaceContainerMargin.left}px solid black;
background: white;
color: white;
}
#genomeSpaceContent {
height: ${
viewportHeight -
2 * genomeSpaceContainerMargin.top -
2 * genomeSpaceContainerPadding.top
}px;
width: ${
viewportWidth -
2 * genomeSpaceContainerMargin.left -
2 * genomeSpaceContainerPadding.left
}px;
border: ${genomeSpaceContainerMargin.left}px solid rgba(242,242,242,1);
color: rgba(242,242,242,1);
background: rgba(242,242,242,1);
box-sizing: border-box;
}
#genomeSpaceAllView {
cursor: grab;
height: ${genomeSpaceAllViewDimensions.h};
width: ${genomeSpaceAllViewDimensions.w};
background: black;
border: ${genomeSpaceContainerMargin.left}px solid black;
box-sizing: border-box;
}
#genomeSpaceZoomView {
height: ${genomeSpaceZoomViewDimensions.h};
width: ${genomeSpaceZoomViewDimensions.w};
background: black;
border: ${genomeSpaceContainerMargin.left}px solid black;
box-sizing: border-box;
overflow: hidden;
}
#genomeSpaceZoomScoreView {
cursor: crosshair;
height: ${genomeSpaceZoomScoreViewDimensions.h};
width: ${genomeSpaceZoomScoreViewDimensions.w};
background: black;
border: ${genomeSpaceContainerMargin.left}px solid black;
box-sizing: border-box;
}
.genomeSpaceViewHeader {
z-index: 3;
color: white;
font-weight: 400;
font-size: ${genomeSpaceViewLabelFontPointSize}pt;
font-family: "Helvetica Neue", Helvetica, Arial, "Lucida Grande", sans-serif;
margin: 0px;
padding: 0px;
padding-top: 2px;
padding-left: 8px;
padding-bottom: 2px;
text-shadow:
-1px -1px 0 #000,
1px -1px 0 #000,
-1px 1px 0 #000,
1px 1px 0 #000;
}
.genomeSpaceViewContent {
background: white;
margin: 0px;
padding: 0px;
border: ${genomeSpaceContainerMargin.left}px solid black;
box-sizing: border-box;
}
.genomeSpaceScoreViewContent {
background: darkgrey;
margin: 0px;
padding: 0px;
border: ${genomeSpaceContainerMargin.left}px solid black;
box-sizing: border-box;
}

svg.hilbertCurve {
cursor: crosshair;
}
svg.hilbertCurve path {
fill: none;
stroke: #3A5894;
stroke-width: 0.3;
stroke-linecap: square;
stroke-opacity: 0.2;
}
svg.hilbertCurve path.skeleton {
stroke: #444;
stroke-width: 0.1;
}
#val-tooltip {
position: absolute;
padding: 5px;
border-radius: 3px;
font: 11px sans-serif;
color: #eee;
background: rgba(0,0,0,0.5);
text-align: center;
pointer-events: none;
}
.curveNodeTable {
margin: 0;
width: 160px;
max-width: 160px;
}
.curveNodeCell {
color: white;
}
.curveNodeCellKey {
text-align: left;
font-weight: bolder;
margin: 0;
padding: 0;
padding-right: 4px;
}
.curveNodeCellValue {
text-align: left;
font-weight: normal;
margin: 0;
padding: 0;
white-space: nowrap;
}
.scoreBinMarker {
position: absolute;
z-index: 1000;
}
</style>`
Insert cell
objectIsEmpty = (obj) => {
return Object.keys(obj).length === 0;
}
Insert cell
proxyAnnotationSourceUrl = "https://resources.altius.org/~meuleman/hilbert_annots/"
Insert cell
proxyUrlComponents = {
return { protocol: "https", host: "meuleman-proxy.altius.org", port: 9001 };
}
Insert cell
proxyUrlPrefix = `${proxyUrlComponents.protocol}://${proxyUrlComponents.host}:${
proxyUrlComponents.port
}/${encodeURIComponent(proxyAnnotationSourceUrl)}`
Insert cell
function set(input, value) {
input.value = value;
input.dispatchEvent(new Event("input"));
}
Insert cell
viewportHeight = parseInt(viewportWidth / 1.43) // height + (viewportIsFullscreen ? 0 : 0)
Insert cell
viewportIsFullscreen = viewportWidth > 1152
Insert cell
viewportWidth = 800 // width
Insert cell
### Zoomed view
Insert cell
zoomViewScale = 10
Insert cell
zoomViewScaledCanvas = {
let scale = zoomViewScale;
let canvas = html`<canvas width=${genomeSpaceZoomViewContentCurveDimensions.w} height=${genomeSpaceZoomViewContentCurveDimensions.h}></canvas>`;
canvas.width = genomeSpaceZoomViewContentCurveDimensions.w * scale;
canvas.height = genomeSpaceZoomViewContentCurveDimensions.h * scale;
let ctx = canvas.getContext("2d");

ctx.fillStyle = "#F0F0F0";
ctx.fillRect(0, 0, canvas.width, canvas.height);

for (let m of curveMappedStatesZoom) {
// First fill with a solid white
ctx.globalAlpha = 1;
ctx.fillStyle = "white";
ctx.fillRect(
curveScaleZoom(m.x) * scale,
curveScaleZoom(m.y) * scale,
curveWidthCellZoom * scale,
curveWidthCellZoom * scale
);
// Then add the (translucent) state color
ctx.globalAlpha = curveScaleStatesZoom(m.scores);
ctx.fillStyle = curveColorsForStates[m.idx - 1][1];
ctx.fillRect(
curveScaleZoom(m.x) * scale,
curveScaleZoom(m.y) * scale,
curveWidthCellZoom * scale,
curveWidthCellZoom * scale
);
}

return canvas;
}
Insert cell
### Dependencies
Insert cell
#### d3
Insert cell
d3 = require("d3@5", "d3-hilbert", "d3-array", "d3-selection", "d3-drag", "d3-transition", "d3-ease")
Insert cell
#### debounce
Insert cell
debounce = require("debounce@1.2.1").catch(() => window.debounce)
Insert cell
#### font-awesome
Insert cell
import { icon } from "@severo/fork-awesome-icons"
Insert cell
#### height
Insert cell
import { height } from "@fil/height"
Insert cell
notebookIsEmbedded = !notebookIsOutside && notebookUrl.searchParams.has("cells")
Insert cell
notebookIsOutside = !notebookUrl.host.endsWith(".observableusercontent.com")
Insert cell
notebookUrl = new URL(document.location)
Insert cell
#### zip
Insert cell
import { zip, zipreader } from "@fil/jszip"
Insert cell

Purpose-built for displays of data

Observable is your go-to platform for exploring data and creating expressive data visualizations. Use reactive JavaScript notebooks for prototyping and a collaborative canvas for visual data exploration and dashboard creation.
Learn more