|
<!DOCTYPE html> |
|
<head> |
|
<meta charset="utf-8"> |
|
<script src="https://cdnjs.cloudflare.com/ajax/libs/js-polyfills/0.1.41/polyfill.min.js"></script> |
|
<script src="https://d3js.org/d3.v3.min.js"></script> |
|
<style> |
|
body { margin:0;position:fixed;top:0;right:0;bottom:0;left:0; } |
|
.extra-tip{ |
|
position: fixed; |
|
max-width: 200px; |
|
left: -9999px; |
|
background-color: white; |
|
border: 1px solid gray; |
|
border-radius: 5px; |
|
font: 12px; |
|
overflow-wrap: break-word; |
|
word-wrap: break-word; |
|
} |
|
.extra-tip > .extra-tip-content{ |
|
padding: 5px; |
|
} |
|
.extra-tip .extra-tip-content a{ |
|
text-decoration: none; |
|
} |
|
.extra-tip > .extra-tip-arrow{ |
|
position: absolute; |
|
height: 15px; |
|
width: 100%; |
|
top: 100%; |
|
|
|
/* indicate area when needed */ |
|
background-color: rgba(200,100,0,0); |
|
} |
|
.extra-tip .extra-tip-wrapper{ |
|
position: absolute; |
|
left: 50%; |
|
} |
|
.extra-tip .extra-tip-wrapper:before, |
|
.extra-tip .extra-tip-wrapper:after{ |
|
content: " "; |
|
width: 0; |
|
height: 0; |
|
top: 100%; |
|
position: absolute; |
|
border: solid transparent; |
|
} |
|
.extra-tip .extra-tip-wrapper:before{ |
|
border-width: 10px; |
|
border-top-color: gray; |
|
left: -8px; |
|
} |
|
.extra-tip .extra-tip-wrapper:after{ |
|
border-width: 8px; |
|
border-top-color: white; |
|
left: -6px; |
|
} |
|
|
|
table{ border-collapse: collapse; } |
|
table td{ border: 1px solid #000; padding: 5px; } |
|
.highlight{ background-color: #fac; } |
|
|
|
</style> |
|
</head> |
|
|
|
<body> |
|
|
|
<table id="mutated"> |
|
<thead></thead> |
|
<tbody> |
|
<tr> |
|
<td>TCGA-C5-A2LT-01A</td> |
|
<td>dolor sit amet</td> |
|
<td>consectetur adipisicing </td> |
|
</tr> |
|
<tr> |
|
<td>TCGA-FU-A3HZ-01A</td> |
|
<td>architecto beatae cum</td> |
|
<td>tenetur quisquam</td> |
|
</tr> |
|
<tr> |
|
<td>TCGA-FU-A23L-01A</td> |
|
<td>nam sunt repellendus</td> |
|
<td>hic inventore nisi</td> |
|
</tr> |
|
<tr> |
|
<td>TCGA-EK-A2PK-01A</td> |
|
<td>Obcaecati ducimus</td> |
|
<td>ab molestias sit</td> |
|
</tr> |
|
<tr> |
|
<td>TCGA-VS-AA62-01A</td> |
|
<td>Obcaecati ducimus</td> |
|
<td>ab molestias sit</td> |
|
</tr> |
|
</tbody> |
|
</table> |
|
|
|
|
|
<div id="container"></div> |
|
|
|
|
|
<script> |
|
|
|
d3.svg.mutate = function(id, rData, tableID){ |
|
var svgWidth = 650, |
|
svgHeight = 180, |
|
margin = {top: 20, right: 70, bottom: 20, left: 50}, |
|
width = svgWidth - margin.left - margin.right, |
|
// height = svgHeight - margin.top - margin.bottom, |
|
pointPlotHeight = 90, |
|
barPlotHeight = 30, |
|
pointRadius = 4, |
|
barHeight1 = 12, |
|
barHeight2 = barHeight1 + 10, |
|
widthPerLetter, |
|
tooltip, |
|
mutateTypeColors = { |
|
"inframe" : "#8b4513", |
|
"others" : "#8b00c9", |
|
"splice site" : "#393b79", |
|
"frameshift" : "#000000", |
|
"missense" : "#008000" |
|
}, |
|
pfamColors = [ |
|
"#6b6ecf", "#9c9ede", "#637939", |
|
"#8ca252", "#b5cf6b", "#cedb9c", "#8c6d31", "#bd9e39", |
|
"#e7ba52", "#e7cb94", "#843c39", "#ad494a", "#d6616b", |
|
"#e7969c", "#7b4173", "#a55194", "#ce6dbd", "#de9ed6", |
|
"#3182bd", "#6baed6", "#9ecae1", "#c6dbef", "#e6550d", |
|
"#fd8d3c", "#fdae6b", "#fdd0a2", "#31a354", "#74c476", |
|
"#a1d99b", "#c7e9c0", "#756bb1", "#9e9ac8", "#bcbddc", |
|
"#393b79", "#5254a3" |
|
]; |
|
|
|
// auto calculate letter width, if fail assign it with 11 |
|
try{ widthPerLetter = averageLetterWith(); }catch(err){ widthPerLetter = 11; } |
|
var table = document.getElementById(tableID); |
|
|
|
var xScale = d3.scale.linear().range([0, width]), |
|
yScale = d3.scale.linear().range([0, pointPlotHeight]), |
|
xAxis = d3.svg.axis().orient("bottom").scale(xScale), |
|
yAxis = d3.svg.axis().orient("left"); |
|
|
|
var svg = d3.select("#" + id) |
|
.append("svg") |
|
.attr({ |
|
width : svgWidth, |
|
height : svgHeight, |
|
version : 1.1, |
|
"shape-rendering" : "crispEdges", |
|
xmlns : "http://www.w3.org/2000/svg" |
|
}) |
|
.style("font", "12px arial, sans-serif"); |
|
|
|
var gPlotMain = svg.append("g").attr({ |
|
class: "gPlotMain", |
|
transform: "translate(" + [margin.left, margin.top] + ")" |
|
}), |
|
gYAxis = gPlotMain.append("g").attr({ |
|
class: "axis gYAxis", |
|
transform: "translate(" + -pointRadius +",0)" |
|
}), |
|
gPoints = gPlotMain.append("g").attr({ |
|
class: "gPoints", |
|
transform: "translate(0," + pointPlotHeight + ")" |
|
}), |
|
gBars = gPlotMain.append("g").attr({ |
|
class: "gBars", |
|
transform: "translate(0," + pointPlotHeight + ")" |
|
}), |
|
gXAxis = gPlotMain.append("g").attr({ |
|
class: "axis gXAxis", |
|
transform: "translate(0," + (pointPlotHeight + barPlotHeight) + ")" |
|
}); |
|
|
|
// data processing |
|
var yMax = (rData.point && rData.point.length > 0) ? |
|
d3.max(rData.point, function(d){ return d.y; }) : 1, |
|
xMax = d3.max(rData.domain, function(d){ return d.end; }); |
|
|
|
// Forcing all search sample strings to upper case format |
|
// before comparing with table cell textContent |
|
try{ // in case there is no point at all: rData.point === undefined |
|
rData.point.forEach(function(d){ |
|
d.sample = d.sample.map(function(e){ return e.toUpperCase(); }); |
|
}); |
|
}catch(error){ |
|
console.log("rData.point: " + error.message + " :-)"); |
|
} |
|
|
|
|
|
xScale.domain([0, xMax]); |
|
yScale.domain([0, yMax]); |
|
yAxis.scale(yScale.copy().range([pointPlotHeight, 0])); |
|
|
|
|
|
gXAxis.call(xAxis).call(styleAxis).call(xAxisMaxLabel); |
|
if(yMax <= 9){ yAxis.ticks(yMax); }else{ yAxis.ticks(5); } |
|
gYAxis.call(yAxis).call(styleAxis) |
|
.append("text") |
|
.attr({ |
|
x : -pointPlotHeight * 0.5, |
|
y : -25, |
|
transform : "rotate(-90)", |
|
"text-anchor" : "middle" |
|
}) |
|
.text("# Mutations"); |
|
|
|
|
|
// begin to draw |
|
if(rData.point && rData.point.length > 0){ |
|
gPoints |
|
.selectAll("g") |
|
.data(rData.point) |
|
.enter() |
|
.append("g") |
|
.each(function(d){ |
|
var g = d3.select(this); |
|
g.attr("transform", |
|
"translate(" + [xScale(d.x), -yScale(d.y)] + ")"); |
|
g.append("line").attr({ |
|
y2 : yScale(d.y) + 0.5 * barPlotHeight, |
|
stroke : "#000", |
|
"stroke-width": 1 |
|
}); |
|
|
|
g.append("circle").attr({ |
|
r : pointRadius, |
|
fill : mutateTypeColors[d.mutation.toLowerCase()] || "red", |
|
"shape-rendering" : "auto" |
|
}) |
|
.on("mouseenter", pointOnMouseEnter) |
|
.on("mouseleave", pointOnMouseLeave); |
|
}); |
|
} // END IF :-) |
|
|
|
|
|
var longBanner, otherBannners = [], otherBannerType = []; |
|
rData.domain.forEach(function(d){ |
|
if(d.end === xMax && d.start === 0){ |
|
longBanner = d; |
|
}else{ otherBannners.push(d); } |
|
}); |
|
otherBannners |
|
.sort(function(a, b){ return a.start - b.start; }) |
|
.forEach(function(d){ |
|
if(otherBannerType.indexOf(d.pfam) === -1){ |
|
otherBannerType.push(d.pfam); |
|
} |
|
}); |
|
|
|
// domain and other protein banners: |
|
gBars |
|
.append("g") |
|
.datum(longBanner) |
|
.on("mouseenter", barOnMouseEnter) |
|
.on("mouseleave", barOnMouseLeave) |
|
.attr("class", "domain") |
|
.attr("transform", "translate(" + |
|
[0, (barPlotHeight - barHeight1) * 0.5 ] + ")") |
|
.append("rect") |
|
.attr({ |
|
width : xScale(xMax), |
|
height : barHeight1, |
|
fill : "#BABDB6", |
|
stroke : "#000", |
|
"stroke-width": 0 |
|
}); |
|
|
|
gBars |
|
.append("g") |
|
.selectAll("g") |
|
.data(otherBannners) |
|
.enter() |
|
.append("g") |
|
.on("mouseenter", barOnMouseEnter) |
|
.on("mouseleave", barOnMouseLeave) |
|
.attr("class", "protein") |
|
.each(function(d, i){ |
|
var g = d3.select(this), barWidth = xScale(d.end - d.start); |
|
g.attr("transform", "translate(" + |
|
[xScale(d.start), (barPlotHeight - barHeight2) * 0.5] + ")"); |
|
|
|
g.append("rect") |
|
.attr({ |
|
width : barWidth, |
|
height : barHeight2, |
|
fill : pfamColors[otherBannerType.indexOf(d.pfam)], |
|
stroke : "#000", |
|
"stroke-width": 0 |
|
}); |
|
|
|
g.append("text") |
|
.text(getLabel(barWidth, d.desc, widthPerLetter)) |
|
.attr({ |
|
x : 0.5 * barWidth, |
|
y : 0.5 * barHeight2, |
|
fill : "#fff", |
|
"text-anchor" : "middle", |
|
"alignment-baseline": "central", |
|
"pointer-events" : "none" |
|
}); |
|
}); |
|
|
|
|
|
tooltip = document.getElementById("extra-tip"); |
|
if(!tooltip){ |
|
tooltip = d3.select("body") |
|
.append("div") |
|
.attr({id: "extra-tip", class: "extra-tip"}) |
|
.html('<div class="extra-tip-content"></div><div class="extra-tip-arrow"><div class="extra-tip-wrapper"></div></div>') |
|
.on("mouseleave", function(){ this.style.left = "-9999px"; }) |
|
.node(); |
|
}else{ |
|
d3.select(tooltip) |
|
.on("mouseleave", function(){ this.style.left = "-9999px"; }); |
|
} |
|
|
|
function pointOnMouseEnter(d){ |
|
var x = d3.event.clientX, y = d3.event.clientY; |
|
var html = "<strong>" + d.y + " mutation</strong></br>" + |
|
"AA change: " + d.change; |
|
d3.select(tooltip).select("div:first-child").html(html); |
|
|
|
d3.select(this).transition().attr("r", pointRadius * 1.5); |
|
tooltip.style.left = x - 0.5 * tooltip.offsetWidth + "px"; |
|
tooltip.style.top = y - tooltip.offsetHeight - 20 + "px"; |
|
|
|
// highlight table row if matched |
|
if(table && d.sample && d.sample.length){ |
|
d3.select(table) |
|
.selectAll("tr > td:first-child") |
|
.each(function(){ |
|
if(d.sample.indexOf(this.textContent.trim().toUpperCase()) !== -1){ |
|
this.parentNode.classList.add("highlight"); |
|
} |
|
}); |
|
} |
|
} |
|
function pointOnMouseLeave(d){ |
|
d3.select(this).transition().attr("r", pointRadius); |
|
tooltip.style.left = "-99999px"; |
|
|
|
// remove highlight class from all table row |
|
if(table){ |
|
d3.select(table) |
|
.selectAll("tr") |
|
.classed("highlight", false); |
|
} |
|
} |
|
|
|
function barOnMouseEnter(d){ |
|
var x = d3.event.clientX, |
|
y = d3.event.clientY, |
|
rCoordinate = d3.mouse(this); |
|
|
|
d3.select(tooltip) |
|
.select("div:first-child") |
|
.html(d.desc + " (" + d.start + " - " + d.end + ") <br />"); |
|
d.link.forEach(function(e){ |
|
d3.select(tooltip) |
|
.select("div:first-child") |
|
.append("a") |
|
.text(" " + e.name + " ") |
|
.attr({ href: e.address, target: "_blank" }) |
|
.style({ display: "inline-block", margin: "2px" }); |
|
}); |
|
|
|
d3.select(this).select("rect").attr("stroke-width", 1); |
|
if(this.classList.contains("domain")){ |
|
tooltip.style.left = x - (0.5 * tooltip.offsetWidth) + "px"; |
|
}else{ |
|
tooltip.style.left = (x - rCoordinate[0]) + |
|
(0.5 * xScale(d.end - d.start)) - |
|
(0.5 * tooltip.offsetWidth) - 2 + "px"; |
|
} |
|
tooltip.style.top = (y - rCoordinate[1]) - |
|
tooltip.offsetHeight - 10 + "px"; |
|
} |
|
|
|
function barOnMouseLeave(d){ |
|
d3.select(this).select("rect").attr("stroke-width", 0); |
|
if(d3.event.toElement.tagName !== "DIV"){ |
|
tooltip.style.left = "-99999px"; |
|
} |
|
} |
|
|
|
function styleAxis(s){ |
|
s.select(".domain") |
|
.attr({ fill: "none", stroke: "#000", "stroke-width": 1}); |
|
s.selectAll(".tick line") |
|
.attr({ stroke: "#000", "stroke-width": 1 }); |
|
} |
|
function xAxisMaxLabel(s){ |
|
var lastTick = s.select("path").node().previousElementSibling, |
|
lastTickNumber = xScale.ticks().slice().pop(); |
|
|
|
// Remove the last tick if the max-label will collision with it. |
|
if(xScale(xMax - lastTickNumber) < (widthPerLetter * lastTickNumber.toString().length / 2)){ |
|
d3.select(lastTick).select("text").remove(); |
|
} |
|
|
|
// minic the normal tick layout |
|
s.insert("g", "path") |
|
.attr({ |
|
class: "tick", |
|
transform: "translate(" + xScale.range()[1] + ",0)" |
|
}) |
|
.append("text") |
|
.text(xMax + "aa") |
|
.attr({ |
|
y : yAxis.outerTickSize() + yAxis.tickPadding(), |
|
dy: ".71em", |
|
// "text-anchor": "middle" |
|
}); |
|
} |
|
function averageLetterWith(){ |
|
var str = "WWWWWWMMMMMMEUMM", |
|
text = svg.append("text").html(str), |
|
width = Math.ceil(text.node().getBBox().width / str.length); |
|
text.remove(); |
|
return width; |
|
} |
|
function getLabel(barWidth, str, letterWidth, maxTextLength){ |
|
if(!maxTextLength){ maxTextLength = 11; } |
|
|
|
if(barWidth < letterWidth * 1.5){ return ""; } |
|
else{ |
|
var length = Math.floor(barWidth / letterWidth); |
|
return str.slice(0, Math.min(length, maxTextLength)); |
|
} |
|
} |
|
}; // mutate END |
|
|
|
|
|
d3.json("protein_seq.json", function(error, rData){ |
|
if(error){ throw error; } |
|
d3.svg.mutate("container", rData, "mutated"); |
|
|
|
}); |
|
|
|
|
|
</script> |
|
</body> |