Skip to content

Commit

Permalink
Merge branch 'issue-1928-cytoband-name'
Browse files Browse the repository at this point in the history
# Conflicts:
#	js/ideogramViewport.js
  • Loading branch information
yarong-lifemap authored and turner committed Feb 3, 2025
1 parent 3080f54 commit 0db7a0b
Show file tree
Hide file tree
Showing 6 changed files with 206 additions and 17 deletions.
30 changes: 30 additions & 0 deletions css/igv.css

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

25 changes: 25 additions & 0 deletions examples/cytoband-names.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
<html>
<head>
<title>igv.js embedding example</title>
</head>

<body>

<div id="igvDiv"></div>

<script type="module">
import igv from "../dist/igv.esm.min.js"

var config = {
genome: 'hg38',
locus: 'chr14:58,972,030-61,596,605',
showCytobandNames: true,
queryParametersSupported: true
};

const browser = await igv.createBrowser(document.getElementById("igvDiv"), config)
</script>

</body>

</html>
2 changes: 1 addition & 1 deletion js/embedCss.js

Large diffs are not rendered by default.

88 changes: 72 additions & 16 deletions js/ideogramTrack.js
Original file line number Diff line number Diff line change
Expand Up @@ -35,10 +35,13 @@ class IdeogramTrack {
this.browser = browser
this.type = 'ideogram'
this.id = 'ideogram'
this.height = 16
this.height = browser.config.showCytobandNames ? 20 : 16
this.order = Number.MIN_SAFE_INTEGER
this.disableButtons = true
this.ignoreTrackMenu = true

// Check whether we should show the cytoband names in the ideogram
this.showCytobandNames = browser.config.showCytobandNames
}

computePixelHeight(ignore) {
Expand All @@ -64,7 +67,8 @@ class IdeogramTrack {
genome: referenceFrame.genome,
width: pixelWidth,
height: pixelHeight,
stainColors
stainColors,
showCytobandNames: this.showCytobandNames
})

const widthBP = Math.round(referenceFrame.bpPerPixel * pixelWidth)
Expand Down Expand Up @@ -107,16 +111,14 @@ class IdeogramTrack {
// Pop current context
context.restore()
}

}

dispose() {
this.trackView = undefined
}
}

function drawIdeogram({ctx, chr, referenceFrame, genome, width, height, stainColors, features}) {

function drawIdeogram({ctx, chr, referenceFrame, genome, width, height, stainColors, features, showCytobandNames}) {
const shim = 1
const shim2 = 0.5 * shim
const ideogramTop = 0
Expand Down Expand Up @@ -178,10 +180,15 @@ function drawIdeogram({ctx, chr, referenceFrame, genome, width, height, stainCol
ctx.fillStyle = "rgb(150, 0, 0)"
ctx.strokeStyle = "rgb(150, 0, 0)"
IGVGraphics.polygon(ctx, xC, yC, 1, 0)
} else {

ctx.fillStyle = getCytobandColor(stainColors, cytoband)
}
else {
const backgroundColor = getCytobandColor(stainColors, cytoband);
ctx.fillStyle = backgroundColor.color;
IGVGraphics.fillRect(ctx, start, shim + ideogramTop, (end - start), height - 2 * shim)

if (showCytobandNames) {
drawIdeogramCytobandName(ctx, cytoband.name, start, end, ideogramTop, height, backgroundColor.shade)
}
}
}
}
Expand All @@ -191,24 +198,73 @@ function drawIdeogram({ctx, chr, referenceFrame, genome, width, height, stainCol
IGVGraphics.roundRect(ctx, shim2, shim2 + ideogramTop, width - 2 * shim2, height - 2 * shim2, (height - 2 * shim2) / 2, 0, 1)
}

function getCytobandColor(colors, data) {

if (data.type === 'c') { // centermere: "acen"
return "rgb(150, 10, 10)"
function drawIdeogramCytobandName(ctx, name, start, end, ideogramTop, height, shade) {
const padding = 2; // Padding between the rect and the sides of the ideogram

// Calculate font size to fit the rectangle height and width
const rectHeight = height - 2 * padding;
const rectWidth = end - start;

const maxFontSize = rectHeight - 2 * padding; // Leave some padding for text
let fontSize = maxFontSize;
do {
ctx.font = `${fontSize}px sans-serif`;
const textWidth = ctx.measureText(name).width;
if (textWidth <= rectWidth) break;
fontSize -= 1;
} while (fontSize > 4); // Minimum font size to avoid infinite loop

// For safety, we're going to clip the text to the rectangle bounds
ctx.save(); // Save the current state of the context
ctx.beginPath();
ctx.rect(start, padding + ideogramTop, rectWidth, rectHeight);
ctx.clip();

// Draw the name of the cytoband, centered within the rectangle
const centerX = start + rectWidth / 2.0;
const centerY = padding + ideogramTop + rectHeight / 2.0 + 1;

// Determine the luminance
let luminance;
if (shade !== null) {
luminance = 0.2126 * shade + 0.7152 * shade + 0.0722 * shade; // Simplified since R=G=B=shade
} else {
var stain = data.stain // + 4;
luminance = 0.2126 * 150 + 0.7152 * 10 + 0.0722 * 10; // Hardcoded for "acen"
}

// Choose text color based on luminance
const textColor = luminance < 128 ? "white" : "black";

IGVGraphics.fillText(ctx, name, centerX, centerY, {
fillStyle: textColor,
textAlign: "center",
textBaseline: "middle",
font: `${fontSize}px sans-serif` // Ensure proper font size
});

ctx.restore(); // Restore the context to remove clipping
}

function getCytobandColor(colors, data) {
if (data.type === 'c') { // centromere: "acen"
return { color: "rgb(150, 10, 10)", shade: null }; // Shade is not relevant here
}
else {
let stain = data.stain; // Stain value
let shade = 230; // Default shade for 'g'

var shade = 230
if (data.type === 'p') {
shade = Math.floor(230 - stain / 100.0 * 230)
shade = Math.floor(230 - stain / 100.0 * 230);
}

// Cache the color if not already stored
var c = colors[shade]
if (!c) {
c = "rgb(" + shade + "," + shade + "," + shade + ")"
colors[shade] = c
}
return c

return { color: c, shade }; // Return both the color and shade
}
}

Expand Down
74 changes: 74 additions & 0 deletions js/ideogramViewport.js
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,10 @@
import IGVGraphics from './igv-canvas.js'
import * as DOMUtils from "./ui/utils/dom-utils.js"
import TrackViewport from "./trackViewport.js"
import { IGVMath } from "../node_modules/igv-utils/src/index.js"

let timer
const toolTipTimeout = 1e4

class IdeogramViewport extends TrackViewport {

Expand All @@ -43,6 +47,19 @@ class IdeogramViewport extends TrackViewport {
this.viewportElement.appendChild(this.canvas);
this.ideogram_ctx = this.canvas.getContext('2d')

// Create the tooltip
this.tooltip = document.createElement('div');
this.tooltip.className = 'igv-cytoband-tooltip';
this.tooltip.style.height = `${this.viewportElement.clientHeight}px`;
this.viewportElement.appendChild(this.tooltip);

// Add tooltip for cytoband names
this.tooltipContent = document.createElement('div');
this.tooltip.appendChild(this.tooltipContent);

// Initially hide the tooltip
this.tooltip.style.display = 'none';

this.addMouseHandlers()
}

Expand Down Expand Up @@ -89,6 +106,63 @@ class IdeogramViewport extends TrackViewport {

addMouseHandlers() {
this.addViewportClickHandler(this.viewportElement)

// Add tooltip when showing contig name
if (this.trackView.track.showCytobandNames) {
this.viewportElement.addEventListener('mousemove', this.mouseMove.bind(this))
this.viewportElement.addEventListener('mouseleave', this.mouseLeave.bind(this))
}
}

mouseMove(event) {
const {x} = DOMUtils.translateMouseCoordinates(event, this.viewportElement)

// Get features
const features = this.featureCache.get(this.referenceFrame.chr)
if (features) {
const {width: w} = this.viewportElement.getBoundingClientRect()

const chrLength = features[features.length - 1].end
const scale = w / chrLength

let found = false;
// Find cytoband that the mouse is over
for (let i = 0; i < features.length; i++) {
const cytoband = features[i]
const start = cytoband.start * scale
const end = cytoband.end * scale

// If the mouse is over the cytoband, show the tooltip
if (x >= start && x <= end) {
this.tooltipContent.textContent = cytoband.name;
const {width: ww} = this.tooltipContent.getBoundingClientRect()
let center = (start + end) / 2 - ww / 2

const tooltipLeft = IGVMath.clamp(center, 0, w - ww);
this.tooltip.style.left = `${tooltipLeft}px`;

// hide tooltip when movement stops
clearTimeout(timer);
timer = setTimeout(() => {
if (this.tooltip) this.tooltip.style.display = "none";
}, toolTipTimeout);

this.tooltip.style.display = "block";

found = true
break
}
}
if (found)
return;
}

// If the mouse is not over a cytoband, or there are no features, hide the tooltip
this.tooltip.style.display = 'none';
}

mouseLeave(event) {
this.tooltip.style.display = 'none';
}

addViewportClickHandler(viewport) {
Expand Down
4 changes: 4 additions & 0 deletions js/igv-create.js
Original file line number Diff line number Diff line change
Expand Up @@ -125,6 +125,10 @@ function setDefaults(config) {
config.showIdeogram = true
}

if (undefined == config.showCytobandNames) {
config.showCytobandNames = false
}

if (undefined === config.showCircularView) {
config.showCircularView = false
}
Expand Down

0 comments on commit 0db7a0b

Please sign in to comment.