Skip to content

Instantly share code, notes, and snippets.

@nachocab
Created May 20, 2012 22:16
Show Gist options
  • Save nachocab/2759731 to your computer and use it in GitHub Desktop.
Save nachocab/2759731 to your computer and use it in GitHub Desktop.
D3 heatmap using Backbone.js and CoffeeScript
gene_name lassa_day_0 lassa_day_3 lassa_day_6 lassa_day_8 lassa_day_10 lassa_day_12 marburg_day_0 marburg_day_1 marburg_day_3 marburg_day_5 marburg_day_7 marburg_day_9 ebola2_day_0 ebola2_day_1 ebola2_day_3 ebola2_day_4 ebola2_day_5 ebola2_day_6 ebola2_day_7 ebola2_day_8 lcmv_day_1 lcmv_day_2 lcmv_day_3 lcmv_day_4 lcmv_day_6 lcmv_day_7
GENE_1 0 0.685 1.047 1.020 0.957 8e-01 0 -0.855 -1e+00 -1.546 -1.359 -1.077 0 -6e-01 -0.219 1.295 0.923 1.216 1e+00 1.632 0.550218483523337 -0.117805570597282 0.0732205574710542 -0.564914153485835 -0.359415199104997 0.00898927502093986
GENE_2 0 2.459 3.299 3.086 2.658 3e+00 0 0.193 9e-01 1.433 1.246 1.091 0 -1e-01 0.392 2.142 2.734 3.863 4e+00 4.216 -0.0754593963815329 0.0967734690139892 0.28316769523287 -0.412279746153753 -0.324753442770228 0.288227993676913
GENE_3 0 0.121 2.131 1.628 1.561 1e+00 0 -0.939 -1e+00 -1.544 -1.632 -1.055 0 2e-02 -0.049 0.008 0.393 1.031 3e-01 0.324 0.0499325227230727 -0.0397401531526083 0.2124328794823 0.0731657480808578 0.0189570855935306 -0.0184828331741145
GENE_4 0 1.676 4.346 4.042 3.269 4e+00 0 0.115 8e-01 3.771 4.191 3.518 0 4e-02 -0.035 0.762 2.161 1.605 3e+00 3.167 2.06449965734009 -0.594799956525603 -0.584504652469251 -0.474603894859595 -0.259726435432555 0.0579806583150321
GENE_5 0 -1.130 -1.348 -1.989 -1.535 -2e+00 0 -2.844 -3e+00 -3.151 -3.681 -3.138 0 4e-01 0.706 0.878 0.994 -0.876 -2e+00 -1.453 -0.279590446885608 1.02224961630431 0.744880787446191 1.29460691183579 -0.816449305811056 -1.65694286101226
GENE_6 0 0.065 0.668 1.288 0.038 8e-01 0 2.041 4e+00 7.365 4.109 2.953 0 -1e-01 -0.138 0.047 -0.372 0.940 6e-01 0.371 1.36429228597838 -0.0719999688548307 0.0451050774042987 -0.453632098514707 0.0825695132840576 0.310973766985588
GENE_7 0 -0.842 -1.330 -0.383 -0.452 -6e-01 0 1.572 2e+00 1.370 1.675 1.577 0 1e-01 0.378 0.625 -0.041 1.103 2e+00 2.277 -0.0783406988717628 -0.26408360918273 -0.23333812657729 -0.206511162329491 -0.465512156559216 -0.452181609819895
GENE_8 0 -1.223 -1.071 -1.381 -1.294 -1e+00 0 -0.520 -7e-01 -0.552 -0.298 -0.021 0 1e-01 0.102 0.672 0.252 -0.030 2e-01 0.252 -0.286173221491367 0.129377596439413 0.193374893715429 0.366838960713438 -0.142400769977739 -0.276932933803457
GENE_9 0 0.052 1.205 2.849 2.986 2e+00 0 1.087 1e+00 2.391 4.399 4.194 0 -4e-02 -0.018 1.014 1.859 4.500 6e+00 6.571 0.00422716134743392 -0.162662158890658 -0.0792976044306068 -0.108238651309485 -0.320366084048266 -0.255239845585953
GENE_10 0 1.263 1.931 1.826 2.448 2e+00 0 0.321 1e+00 3.149 2.945 2.887 0 -2e-02 0.211 3.930 4.839 4.302 4e+00 4.675 1.64521963175084 -0.754892799863792 -0.526039721636022 -0.478968546603597 -0.0684222766654527 -0.122200661558602
<!DOCTYPE html>
<html lang='en'>
<head>
<title>Backboning</title>
<meta charset='UTF-8'/>
<meta content='Nacho Caballero' name='author' />
<script src='http://code.jquery.com/jquery-1.7.1.min.js'></script>
<script src='http://documentcloud.github.com/underscore/underscore.js'></script>
<script src='http://documentcloud.github.com/backbone/backbone.js'></script>
<script src='http://mbostock.github.com/d3/d3.js'></script>
<script src='main.js'></script>
<link href='main.css' rel='stylesheet' />
<style>
body {
width: 1400px;
margin: 0 auto; }
h1 {
font-size: 20px;
font-weight: bold;
color: white; }
text {
font-family: Helvetica, Arial, sans-serif;
font-size: 16px; }
#heatmap .row {
cursor: pointer; }
#heatmap .row rect {
stroke: black;
stroke-width: 2px; }
#heatmap .row.current.clicked rect {
stroke: black; }
#heatmap .row.current text {
font-weight: bold; }
#heatmap .column text {
font-weight: bold; }
</style>
</head>
<body>
<header></header>
<main>
<svg id='heatmap'></svg>
<svg id='pcp'></svg>
</main>
<footer>
<p>
Built with
<a href='http://d3js.org/'>d3.js</a>.
</p>
<a href='#' rel='author'>Nacho Caballero</a>
</footer>
</body>
</html>
$(document).ready ->
d3.text "genes.csv", (text) ->
genes = d3.csv.parse text
geneExpressionModel = new Backbone.Model
geneExpressionModel.set conditionNames: getConditionNames(genes)
geneExpressionModel.set geneNames: genes.map((gene) -> gene.gene_name)
geneExpressionModel.set geneExpressions: getGeneExpressions(genes, geneExpressionModel.get "conditionNames")
geneExpressionModel.set extent: d3.extent($.map(geneExpressionModel.get("geneExpressions"), (item)-> item )) # flatten matrix
geneExpressionModel.set clusters: genes.map (gene) -> gene.cluster
geneExpressionModel.set clusterColor: d3.scale.category20()
heatmap = new Heatmap(el: "#heatmap", model: geneExpressionModel)
Heatmap = Backbone.View.extend
initialize: ->
@render()
render: ->
geneExpressions = @model.get "geneExpressions"
conditionNames = @model.get "conditionNames"
geneNames = @model.get "geneNames"
extent = @model.get "extent"
clusters = @model.get "clusters"
clusterColor = @model.get "clusterColor"
heatmapColor = d3.scale.linear().domain([-1.5,0,1.5]).range(["#278DD6","#fff","#d62728"])
textScaleFactor = 15
conditionNamesMargin = d3.max(conditionNames.map((conditionName) -> conditionName.length))
geneNamesMargin = d3.max(geneNames.map((geneName) -> geneName.length))
margin =
top: conditionNamesMargin*textScaleFactor
right: 150
bottom: conditionNamesMargin*textScaleFactor
left: geneNamesMargin*textScaleFactor
cell_size = 30
width = cell_size*geneExpressions[0].length
height = cell_size*geneNames.length
heatmap = d3.select(@el).append("svg")
.attr("width", width + margin.right + margin.left)
.attr("height", height + margin.top + margin.bottom)
.attr("id","heatmap")
.append("g")
.attr("transform", "translate(" + margin.left + "," + margin.top + ")")
x = d3.scale.ordinal().domain(d3.range(geneExpressions[0].length)).rangeBands([0, width])
y = d3.scale.ordinal().domain(d3.range(geneNames.length)).rangeBands([0, height])
columns = heatmap.selectAll(".column")
.data(conditionNames)
.enter().append("g")
.attr("class", "column")
.attr("transform", (d, i) -> "translate(" + x(i) + ")rotate(-90)" )
# Add condition names (top)
columns.append("text")
.attr("x", 6)
.attr("y", x.rangeBand() / 2)
.attr("dy", "-.5em") # .32em before rotating
.attr("dx", ".5em")
.attr("text-anchor", "start")
.attr("transform","rotate(45)")
.text((d, i) -> conditionNames[i] )
getRow = (row) ->
cell = d3.select(this).selectAll(".cell")
.data(row)
.enter().append("rect")
.attr("class", "cell")
.attr("x", (d,i) -> x(i))
.attr("width", x.rangeBand())
.attr("height", x.rangeBand())
.text((d) -> d)
.style("fill", (d) -> heatmapColor(d))
rows = heatmap.selectAll(".row")
.data(geneExpressions)
.enter().append("g")
.attr("class", "row")
.attr("name", (d,i) -> "gene_" + i)
.attr("transform", (d, i) -> "translate(0," + y(i) + ")")
.each(getRow)
# Add gene names
rows.append("text")
.attr("x", -6)
.attr("y", x.rangeBand() / 2)
.attr("dy", ".32em")
.attr("text-anchor", "end")
.text((d, i) -> geneNames[i])
getGeneExpressions = (genes, conditionNames) ->
genes.map (gene) ->
conditionNames.map (condition) ->
+gene[condition] # make numeric
getConditionNames = (genes) ->
Object.keys(genes[0]).filter (columnName) ->
!columnName.match(/cluster/) && isNumber(genes[1][columnName])
isNumber = (n) ->
!isNaN(parseFloat(n)) and isFinite(n)
// Generated by CoffeeScript 1.3.1
(function() {
var Heatmap, getConditionNames, getGeneExpressions, isNumber;
$(document).ready(function() {
return d3.text("genes.csv", function(text) {
var geneExpressionModel, genes, heatmap;
genes = d3.csv.parse(text);
geneExpressionModel = new Backbone.Model;
geneExpressionModel.set({
conditionNames: getConditionNames(genes)
});
geneExpressionModel.set({
geneNames: genes.map(function(gene) {
return gene.gene_name;
})
});
geneExpressionModel.set({
geneExpressions: getGeneExpressions(genes, geneExpressionModel.get("conditionNames"))
});
geneExpressionModel.set({
extent: d3.extent($.map(geneExpressionModel.get("geneExpressions"), function(item) {
return item;
}))
});
geneExpressionModel.set({
clusters: genes.map(function(gene) {
return gene.cluster;
})
});
geneExpressionModel.set({
clusterColor: d3.scale.category20()
});
return heatmap = new Heatmap({
el: "#heatmap",
model: geneExpressionModel
});
});
});
Heatmap = Backbone.View.extend({
initialize: function() {
return this.render();
},
render: function() {
var cell_size, clusterColor, clusters, columns, conditionNames, conditionNamesMargin, extent, geneExpressions, geneNames, geneNamesMargin, getRow, heatmap, heatmapColor, height, margin, rows, textScaleFactor, width, x, y;
geneExpressions = this.model.get("geneExpressions");
conditionNames = this.model.get("conditionNames");
geneNames = this.model.get("geneNames");
extent = this.model.get("extent");
clusters = this.model.get("clusters");
clusterColor = this.model.get("clusterColor");
heatmapColor = d3.scale.linear().domain([-1.5, 0, 1.5]).range(["#278DD6", "#fff", "#d62728"]);
textScaleFactor = 15;
conditionNamesMargin = d3.max(conditionNames.map(function(conditionName) {
return conditionName.length;
}));
geneNamesMargin = d3.max(geneNames.map(function(geneName) {
return geneName.length;
}));
margin = {
top: conditionNamesMargin * textScaleFactor,
right: 150,
bottom: conditionNamesMargin * textScaleFactor,
left: geneNamesMargin * textScaleFactor
};
cell_size = 30;
width = cell_size * geneExpressions[0].length;
height = cell_size * geneNames.length;
heatmap = d3.select(this.el).append("svg").style("margin-top", "100px").attr("width", width + margin.right + margin.left).attr("height", height + margin.top + margin.bottom).attr("id", "heatmap").append("g").attr("transform", "translate(" + margin.left + "," + margin.top + ")");
x = d3.scale.ordinal().domain(d3.range(geneExpressions[0].length)).rangeBands([0, width]);
y = d3.scale.ordinal().domain(d3.range(geneNames.length)).rangeBands([0, height]);
columns = heatmap.selectAll(".column").data(conditionNames).enter().append("g").attr("class", "column").attr("transform", function(d, i) {
return "translate(" + x(i) + ")rotate(-90)";
});
columns.append("text").attr("x", 6).attr("y", x.rangeBand() / 2).attr("dy", "-.5em").attr("dx", ".5em").attr("text-anchor", "start").attr("transform", "rotate(45)").text(function(d, i) {
return conditionNames[i];
});
getRow = function(row) {
var cell;
return cell = d3.select(this).selectAll(".cell").data(row).enter().append("rect").attr("class", "cell").attr("x", function(d, i) {
return x(i);
}).attr("width", x.rangeBand()).attr("height", x.rangeBand()).text(function(d) {
return d;
}).style("fill", function(d) {
return heatmapColor(d);
});
};
rows = heatmap.selectAll(".row").data(geneExpressions).enter().append("g").attr("class", "row").attr("name", function(d, i) {
return "gene_" + i;
}).attr("transform", function(d, i) {
return "translate(0," + y(i) + ")";
}).each(getRow);
return rows.append("text").attr("x", -6).attr("y", x.rangeBand() / 2).attr("dy", ".32em").attr("text-anchor", "end").text(function(d, i) {
return geneNames[i];
});
}
});
getGeneExpressions = function(genes, conditionNames) {
return genes.map(function(gene) {
return conditionNames.map(function(condition) {
return +gene[condition];
});
});
};
getConditionNames = function(genes) {
return Object.keys(genes[0]).filter(function(columnName) {
return !columnName.match(/cluster/) && isNumber(genes[1][columnName]);
});
};
isNumber = function(n) {
return !isNaN(parseFloat(n)) && isFinite(n);
};
}).call(this);
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment