d3.svg.hotTable = function(id, rData){
/*
rData:
{
"table":[
// Log(exp) GeneName domestic url Ensemble ID Plot
[1.85, ["RNA", "/web/?a=1&b=c"], "ENSG00000132341", "url"],
[ ... ... ],
... ...
],
"map":{
x: ["x1", "x2", "x3", ... ],
// y --> referance GeneName coloum
dots: [
[1.5, 2.3, 5.6, 0.3, 4.5, .... ],
[ ... ... ],
... ...
]
}
}
*/
var tooltip = document.getElementById("tooltip");
if(!tooltip){
tooltip = document.createElement("div");
tooltip.setAttribute("id", "tooltip");
tooltip.setAttribute("class", "tooltip");
document.body.appendChild(tooltip);
}
var tableData,
mapData,
tRows,
mapCols,
thead = 0,
tbody = 0,
// Stable link to Ensembl: http://www.ensembl.org/info/docs/webcode/linking.html
stableLink = "http://www.ensembl.org/id/";
if(!rData){
alert("Data is EMPTY!");
return;
}else{
tableData = rData.table;
rData.map.y = rData.table.map(function(d){ return d[1][0]; });
mapData = rData.map;
tRows = tableData.length;
mapCols = mapData.x.length;
}
if(mapData.x.length !== mapData.dots[0].length){
alert("Error Data - the items counts not martch with content\n" +
"mapData.x.length: " + mapData.x.length + "\n" +
"mapData.dots[0].length: " + mapData.dots[0].length);
return;
}
var ul = d3.select("#ge-hot-table").html("");
ul.append("li").html(makeAtable());
// Detecting if the table is rendering thoroutly.
// If rending is finished then drawing heatmap.
var timeID = setInterval(function(){
var table = ul.select("table").node();
var th = table.tHead.getBoundingClientRect().height,
tb = table.tBodies[0].getBoundingClientRect().height;
if((tbody < tb) || (thead < th)){
tbody = tb;
thead = th;
}else{
clearInterval(timeID);
makeAmap(tb, th);
}
}, 50);
function makeAtable(){
var tr = tableData.map(function(row){
return "
" + row[0] + " | " +
"" + turncate(row[1][0]) + " | " +
"" + row[2] + " | " +
"" + "Plot | ";
});
function turncate(text, len){
if(!len){ len = 8; }
if(text.length <= len){
return text;
}else{
return text.slice(0, len - 3) + "...";
}
}
return "| Log(exp) | Gene | Ensemble ID | |
" +
"" + tr.join("
") + "
";
}
function makeAmap(tbodyHeight, theadHeight){
// data processing
var rects = mapData.dots.map(function(row, y){
return row.map(function(cell, x){
return {x: mapData.x[x], y: mapData.y[y], z: cell};
});
});
var zExtent = d3.extent(Array.prototype.concat.apply([], mapData.dots.map(function(d){ return d3.extent(d); })));
var svgWidth,
legendWidth = 70,
minRectWidth = 10,
rectHeight = tbodyHeight / tRows,
colors = ["#3060cf", "#fffbbc", "#c4463a"];
if(minRectWidth * mapCols < (615 - legendWidth)){
svgWidth = 615;
minRectWidth = (svgWidth - legendWidth) / mapCols;
}else{
svgWidth = minRectWidth * mapCols + legendWidth;
}
var cScale = d3.scale.linear()
.domain([zExtent[0], 0.5 * (zExtent[0] + zExtent[1]), zExtent[1]])
.range(colors);
// append a div whithhold the height of table header
var li2 = ul.append("li");
li2.append("div").style({ display: "inline-block", height: theadHeight + "px", width: "100%", margin: 0 });
var svg = li2
.append("svg")
.attr({
width : svgWidth,
height : tbodyHeight,
version : 1.1,
xmlns : "http://www.w3.org/2000/svg"
});
svg.append("defs")
.append("linearGradient")
.attr({
id: "upwards",
x1: "0%", y1: "100%", x2: "0%", y2: "0%"
})
.call(function(s){
var offset = ["0%", "50%", "100%"];
colors.forEach(function(color, index){
s.append("stop").attr({offset: offset[index], "stop-color": color});
});
});
var mainPlot = svg.append("g").attr("class", "ge-main-plot"),
gLegend = svg.append("g").attr({
class: "ge-legend",
transform: "translate(" + [svgWidth - legendWidth, 0] + ")",
"shape-rendering": "crispEdges"
});
mainPlot.selectAll("g.ge-row")
.data(rects)
.enter()
.append("g")
.attr({
transform : function(d,i){ return "translate(0," + (i * rectHeight) + ")"; },
class : "ge-row"
})
.selectAll("rect")
.data(function(d){return d;})
.enter()
.append("rect")
.attr({
width : minRectWidth,
height : rectHeight,
stroke : "#333",
"shape-rendering" : "crispEdges",
"stroke-width" : 1,
x : function(d,i){ return i * minRectWidth; },
fill : function(d){ return cScale(d.z); }
})
.on("mousemove", onMouseMove)
.on("mouseleave", onMouseLeave);
//gLegend
var nonius = 5,
dPath = "M0 0 " +
"L" + -nonius + " " + -nonius + " " +
"L" + -2 * nonius + " " + -nonius + " " +
"L" + -2 * nonius + " " + nonius + " " +
"L" + -nonius + " " + nonius + " Z";
var legendRectHeight, moveScale, marker, lAxis, gRect, axisTicks;
if(tRows > 5){
legendRectHeight = 5 * (tbodyHeight / tRows);
axisTicks = 5;
}else{
legendRectHeight = tbodyHeight - 15;
axisTicks = 3;
}
gRect = gLegend.append("g")
.attr("transform", "translate(" + [20, 0.5 * (tbodyHeight - legendRectHeight)] + ")");
moveScale = d3.scale.linear().domain(zExtent).range([0, legendRectHeight]);
lAxis = d3.svg.axis().scale(moveScale.copy().range([legendRectHeight,0])).orient("right").ticks(axisTicks);
marker = gRect.append("path").attr({fill: "steelblue", d: dPath}).style("display", "none");
gRect.append("rect").attr({width: 10, height: legendRectHeight, fill: "url(#upwards)"});
gRect.append("text").text("Exp.").attr({dx: -2, dy: -15});
gRect.append("g")
.attr("transform", "translate(10,0)")
.call(lAxis)
.call(function(s){
s.select("path").attr("fill", "none");
s.selectAll("line").attr({stroke: "#ccc", "stroke-width": 1});
});
// Events functions
function onMouseMove(d){
d3.select(this).attr({stroke: "#000", "stroke-width": 2});
var msg = [d.x, "Gene: " + d.y, "Expression: " + d.z],
coordinate = [d3.event.clientX, d3.event.clientY];
msg = "" + msg.join("") + "";
var box = tooltip.getBoundingClientRect();
d3.select(this).classed("highlight", true);
d3.select(tooltip)
.html(msg)
.style({
display : "block",
left : coordinate[0] - box.width / 2 + "px",
top : coordinate[1] - box.height - 10 + "px"
});
marker.attr("transform", "translate(0," + (legendRectHeight - moveScale(d.z)) + ")")
.style("display", "block");
}
function onMouseLeave(){
d3.select(this).attr({stroke: "#333", "stroke-width": 1});
tooltip.style.left = "-99999px";
d3.select(this).classed("highlight", false);
marker.style("display", "none");
}
} // heatmap END
}; // hotTable END
d3.svg.jitterPlot = function (id, rData){
var svgWidth = 550,
svgHeight = 300,
margin = {top: 20, right: 30, bottom: 50, left: 70},
width = svgWidth - margin.left - margin.right,
height = svgHeight - margin.top - margin.bottom,
radius = 3,
fillColor = "steelblue",
f = d3.format(".4f");
// data proccessing
var yExtent = d3.extent(Array.prototype.concat.apply([], rData.data.map(function(d){ return d.slice(1); })));
var pData = rData.data.map(function(d){
var y = d.slice(1).sort(function(a,b){ return a - b; });
return {
name : d[0],
y : y,
quantile: getQuantiles(y.slice())
};
});
var yScale = d3.scale.linear().domain(yExtent).range([0, height]).nice(),
yAxis = d3.svg.axis().orient("left")
.tickSize(-width, -width)
.scale(yScale.copy().nice().range([height, 0])),
xScale = d3.scale.ordinal()
.domain(pData.map(function(d){ return d.name; }))
.rangeBands([0, width], 0.1, 0.1),
xAxis = d3.svg.axis().orient("bottom").scale(xScale).tickSize(0,0).tickPadding(10),
rangeBand = xScale.rangeBand(),
boxWidth = rangeBand * 0.8,
whiskerWidth = boxWidth * 0.5;
var svg, plotMain, gXAxis, gYAxis, gTooltip, filterID;
if(typeof id === "string"){
svg = d3.select("#" + id).append("svg");
}else if(id instanceof HTMLElement){
svg = d3.select(id).append("svg");
}else{ alert("Invalid ID!"); return; }
svg.attr({
width : svgWidth,
height : svgHeight,
version : 1.1,
xmlns : "http://www.w3.org/2000/svg"
})
.style("font", "12px arial, sans-serif");
plotMain = svg.append("g").attr({
class: "gen-plot-main",
transform: "translate("+ [margin.left, margin.top] +")"
});
gXAxis = plotMain.append("g").attr({
class : "gen-axis xaxis",
transform : "translate(0," + height + ")",
"shape-rendering" : "crispEdges"
});
gYAxis = plotMain.append("g").attr({
class : "gen-axis yaxis",
"shape-rendering" : "crispEdges"
});
gXAxis.call(xAxis).call(uniform);
gYAxis.call(yAxis).call(uniform);
gYAxis.append("g")
.attr("transform", "translate(" + [-50, 0.5 * height] + ")")
.append("text")
.html("" + rData.gene + "Expression FPKM")
.attr({"text-anchor": "middle", transform: "rotate(-90)"});
plotMain
.selectAll("g.gen-plotbox")
.data(pData)
.enter()
.append("g")
.attr({
class: "gen-plotbox",
transform: function(d,i){
return "translate(" +
[xScale.range()[i] + 0.5 * rangeBand, height] +
")";
}
})
.on("mouseover", onMouseOver)
.on("mouseleave", onMouseLeave)
.each(function(D){
var self = d3.select(this);
if(D.y.length > 5){
self
.append("line")
.attr({
class : "Q4 Q-line",
x1 : -0.5 * whiskerWidth,
x2 : 0.5 * whiskerWidth,
y1 : -yScale(D.quantile[4]),
y2 : -yScale(D.quantile[4])
});
self
.append("line")
.attr({
class : "Q0 Q-line",
x1 : -0.5 * whiskerWidth,
x2 : 0.5 * whiskerWidth,
y1 : -yScale(D.quantile[0]),
y2 : -yScale(D.quantile[0])
});
self
.append("line")
.attr({
class : "center-line Q-line",
x1 : 0,
x2 : 0,
y1 : -yScale(D.quantile[4]),
y2 : -yScale(D.quantile[0])
});
self
.append("rect")
.attr({
class : "Q-box",
x : -0.5 * boxWidth,
y : -yScale(D.quantile[3]),
width : boxWidth,
height : yScale(D.quantile[3]) - yScale(D.quantile[1]),
fill : "#fff",
stroke : "#000",
"stroke-width": 1
});
self
.append("line")
.attr({
class : "Q2 Q-median",
x1 : -0.5 * boxWidth,
x2 : 0.5 * boxWidth,
y1 : -yScale(D.quantile[2]),
y2 : -yScale(D.quantile[2]),
stroke : "#000",
"stroke-width": 2
});
}
// circles
D.y.forEach(function(d){
self
.append("circle")
.attr({
class : "jitter-circle",
cx : 0,
cy : 0,
r : 0,
fill : fillColor,
stroke : fillColor,
"stroke-width": 1,
"fill-opacity": 0.1
})
.transition()
.duration(700)
.attr({
cx: Math.random() * whiskerWidth - 0.5 * whiskerWidth,
cy: -yScale(d),
r : radius
});
});
});
plotMain.selectAll(".Q-line").attr({stroke: "#000", "stroke-width": 1});
/*
*/
filterID = "gen-" + (new Date()).getTime();
svg.insert("filter", "g:first-child")
.attr({
id: filterID
})
.html('');
gTooltip = plotMain
.append("g")
.attr({class: "gen-tooltip", "shape-rendering": "crispEdges"});
function onMouseOver(d){
var html = "",
dy = 15,
labels = ["Max: ", "Q3: ", "Median: ", "Q1: ", "Min: "],
point = d3.mouse(plotMain.node()),
path,
textBox;
d.quantile.slice().reverse().forEach(function(e,i){
html += "" + labels[i] + f(e) + "";
});
gTooltip.html("" + html + "");
textBox = gTooltip.node().getBBox();
path = gTooltip.insert("path", "text").attr({
fill : "white",
stroke : "black",
//filter : "url(#" + filterID + ")",
"stroke-width": 1,
"fill-opacity": 0.8,
transform : "translate(" + [-0.5 * dy, -0.2 * dy] + ")"
});
if(point[1] > textBox.height){
path.attr("d", getTooltipBox([textBox.width + dy, textBox.height + dy]), "down");
gTooltip
.transition()
.delay(50)
.attr("transform", "translate(" + [
point[0] - textBox.width * 0.5 ,
point[1] - textBox.height - dy - 5
] + ")");
}else{
path.attr("d", getTooltipBox([textBox.width + dy, textBox.height + dy], "up"));
gTooltip
.transition()
.delay(50)
.attr("transform", "translate(" + [
point[0] - textBox.width * 0.5 + 5,
point[1] + dy + 5
] + ")");
}
}
function onMouseLeave(d){ gTooltip.html(""); }
function uniform(s){
s.select(".domain").attr({ fill: "none", stroke: "#999", "stroke-width": 1});
s.selectAll(".tick line").attr({ stroke: "#ddd", "stroke-width": 1 });
s.select("path").remove();
}
function getTooltipBox(size, type){
var arrow = 10;
if(!type){ type = "down"; }
if(type === "up"){
return "M0,0 " +
"L" + [0.5 * size[0] - 0.5 * arrow, 0] + " " +
"L" + [0.5 * size[0], -0.5 * arrow] + " " +
"L" + [0.5 * size[0] + 0.5 * arrow, 0] + " " +
"L" + [size[0], 0] + " " +
"V" + size[1] + " " +
"H0" +
"Z";
}else if(type === "down"){
return "M0,0 " +
"H" + size[0] + " " +
"V" + size[1] + " " +
"L" + [0.5 * size[0] + 0.5 * arrow, size[1]] + " " +
"L" + [0.5 * size[0], arrow * 0.5 + size[1]] + " " +
"L" + [0.5 * size[0] - 0.5 * arrow, size[1]] + " " +
"L" + [0, size[1]] + " " +
"Z";
}
}
function getQuantiles(values){
var q1 = d3.quantile(values, 0.25),
q2 = d3.median(values),
q3 = d3.quantile(values, 0.75),
qr = q3 - q1,
fValues;
fValues = values.filter(function(d){
return d >= (q1 - 1.5 * qr) && d <= (q3 + 1.5 * qr);
});
// [Q0, Q1, Q2, Q3, Q4]
return [ fValues[0], q1, q2, q3, fValues[fValues.length - 1] ];
}
}; // jitterPlot END