scatter.js |
|
---|---|
Copyright 2013 Allen Institute for Brain Science Licensed under the Apache License, Version 2.0 (the “License”); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an “AS IS” BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. |
|
This page generates a scatter plot of expression values for two gene probes from human microarray samples. Those samples can come from one or more of the donors. |
|
This uses the Modernizr JS library to check to see if the page supports SVG. If it does not, throw an error. |
if (!Modernizr.svg){
$('#chartCell').append(
$('<div>').addClass('svgError')
.html("This demo requires SVG support. " +
"Click <a href='http://caniuse.com/#cats=SVG'>here</a> " +
"to see which browsers support SVG."));
throw 'SVG not supported';
} |
Variables that define properties of the visualization. |
var margin = 50;
var r = 3;
var numTicks = 10;
var apiPath = "http://api.brain-map.org/api/v2/data/query.json"; |
The dimensions of the plot are the dimensions of the HTML div element, minus the margin size. |
var w = $("#chartCell").width()-margin;
var h = $("#chartCell").height()-margin; |
|
var x = d3.scale.linear().range([0,w]);
var y = d3.scale.linear().range([h,0]); |
Initialize the chart element. The translation transform displaces the chart by half the margin size. |
var vis = d3.select("#chartCell").append("svg:svg")
.attr("width", w+margin)
.attr("height", h+margin)
.append("svg:g")
.attr("transform", "translate(" + margin / 2 + "," + margin / 2 + ")"); |
Set some default probe ids, but change them if they are specified in the url parameter string. |
var probe1 = 1053210;
var probe2 = 1022127;
var donor = null;
var urlVars = getUrlVars();
if ('probe1' in urlVars)
probe1 = urlVars['probe1'];
if ('probe2' in urlVars)
probe2 = urlVars['probe2'];
if ('donor' in urlVars)
donor = urlVars['donor']; |
Initialize the home button and show a loading screen until loading’s done. |
$("#chartCell").css("background","no-repeat center url(\"../images/loading.gif\")");
$("#homeButton").button({ icons: { primary: "ui-icon-home" }}); |
Download the expression data from the API. If a donor is specified, download expression for just that donor. Otherwise download everything. |
var url = apiPath + "?criteria=service::human_microarray_expression[probes$in"+probe1+","+probe2+"]";
if (donor)
url += "[donor$eq"+donor+"]"; |
Make the AJAX call that downloads the expression values. |
$.ajax(url, {
dataType: 'json',
success: function(response) {
if (response.success)
buildScatterPlot(response.msg);
else
apiError(response.msg, url);
},
error: function(response) { apiError(response.statusText, url); }
}); |
This function lays out the D3 scatter plot. |
function buildScatterPlot(data) { |
Hide the loading image. |
$("#chartCell").css("background", ""); |
Fill the appropriate table elements with probe labels. |
$('<a>').attr('href','http://human.brain-map.org/probes/'+probe1)
.html(data.probes[0]['gene-symbol'])
.appendTo("#gene1Label");
$('<a>').attr('href','http://human.brain-map.org/probes/'+probe2)
.html(data.probes[1]['gene-symbol'])
.appendTo("#gene2Label"); |
Build an array to hold the sample data points along with relevant structure and donor information. |
var dataPoints = []
for (var i=0; i<data.samples.length; i++) {
var sample = data.samples[i];
dataPoints.push({ x: data.probes[0].expression_level[i],
y: data.probes[1].expression_level[i],
structure: sample.structure,
donor: sample.donor.name,
color: "#"+sample.structure.color });
} |
Update the domain of the x and y scales. It can be helpful if the axes have the same domain so that the slope of the line fit to the data points is clearer. |
if (dataPoints.length > 0) {
var domain = [dataPoints[0].x, dataPoints[0].y];
for (var i=1; i<dataPoints.length; i++) {
p = dataPoints[i];
domain[0] = Math.min(p.x, Math.min(p.y, domain[0]));
domain[1] = Math.max(p.x, Math.max(p.y, domain[1]));
}
domain[0] = Math.floor(domain[0]);
domain[1] = Math.ceil(domain[1]);
x.domain(domain);
y.domain(domain);
numTicks = domain[1] - domain[0] + 1; |
don’t want there to be too many ticks |
var newTicks = numTicks;
var factor = .5;
while (newTicks > 12) {
newTicks = Math.ceil(numTicks * factor);
factor *= .5;
}
numTicks = newTicks;
} |
D3 scales have a |
var axisGroup = vis.append("svg:g").attr("class","axes");
axisGroup.selectAll("line.xticks").data( x.ticks(numTicks) )
.enter().append("svg:line")
.attr("class","xtick")
.attr("x1", x)
.attr("y1", 0)
.attr("x2", x)
.attr("y2", h)
.attr("stroke","#eee");
axisGroup.selectAll("line.yticks").data( y.ticks(numTicks) )
.enter().append("svg:line")
.attr("class","ytick")
.attr("x1", w)
.attr("y1", y)
.attr("x2", 0)
.attr("y2", y)
.attr("stroke","#eee"); |
These two calls simply add the x and y axes. |
axisGroup.append("svg:line")
.attr("stroke-width", 3)
.attr("stroke","gray")
.attr("x1", 0)
.attr("y1", h)
.attr("x2", w)
.attr("y2", h);
axisGroup.append("svg:line")
.attr("stroke-width", 3)
.attr("stroke","gray")
.attr("x1", 0)
.attr("y1", h)
.attr("x2", 0)
.attr("y2", 0); |
Add the scatter plot circles. Their positions are set by the |
var pointGroup = vis.append("svg:g").attr("class","points");
pointGroup.selectAll("circle.points").data(dataPoints)
.enter().append("svg:circle")
.attr("class", "point")
.attr("cx",function(d) { return x(d.x); })
.attr("cy",function(d) { return y(d.y); })
.attr("r",r)
.attr("fill",function(d) { return d.color; })
.on("mouseover", mouseover); |
Finally, we draw text labels next to the x and y axes that indicate the data values at the tick positions we drew awhile ago. Note that these positions have to be offset a bit so that they don’t actually sit on the axes themselves. |
pointGroup.selectAll("text.xlabels").data( x.ticks(numTicks) )
.enter().append("svg:text")
.attr("class","xlabel")
.attr("x", x)
.attr("y", h)
.attr("dx", 0)
.attr("dy", 10)
.text( function(d) { return d; });
pointGroup.selectAll("text.ylabels").data( y.ticks(numTicks) )
.enter().append("svg:text")
.attr("class","ylabel")
.attr("x", 0)
.attr("y", y)
.attr("dx", -20)
.attr("dy", 0)
.text( function(d) { return d; }); |
When the mouse enters one of the data elements, display its properties and highlight its properties by adding a stroke and making it bigger. We have to keep track of which element is currently being highlighted so that we can remove the highlight when it changes. |
var highlightElement = null;
function mouseover(d)
{
$("#structureLabel").html(d.structure.name);
$("#donorLabel").html(d.donor);
$("#exp1Label").html(d.x);
$("#exp2Label").html(d.y);
var element = d3.select(this);
element.transition().duration(100)
.attr("r",r+2)
.attr("stroke","black");
if (highlightElement) {
highlightElement.transition().duration(100)
.attr("r",r)
.attr("stroke","none");
}
highlightElement = element;
}
} |
If something goes wrong, alert the user. |
function apiError(response, url) {
var errorHtml =
"<p>There was an error with the following query:</p>" +
"<p>" + url + "</p>" +
"<p>Error message:</p>" +
"<p>" + response + "</p>";
var dialog = $( "#errorDialog" );
var existingErrors = dialog.html();
$( "#errorDialog" )
.html(existingErrors + errorHtml)
.dialog({
width: 500,
height: 200,
modal: true
});
} |
This function splits the URL parameter string into a JavaScript hash. |
function getUrlVars()
{
var vars = [], hash;
var hashes = window.location.href.slice(window.location.href.indexOf('?') + 1).split('&');
for(var i = 0; i < hashes.length; i++)
{
hash = hashes[i].split('=');
vars.push(hash[0]);
vars[hash[0]] = hash[1];
}
return vars;
} |