Last active
December 16, 2015 15:08
-
-
Save kbroman/5453249 to your computer and use it in GitHub Desktop.
Cis-trans plot using the old version of d3-tip.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
# cistrans_coffee | |
# | |
# Interactive cis-trans eQTL plot | |
# | |
# In top figure, x-axis corresponds to marker location, y-axis is | |
# genomic position of probes on a gene expression microarray Each | |
# plotted point is an inferred eQTL with LOD > 10; opacity corresponds | |
# to LOD score, though all LOD > 25 are made fully opaque. | |
# | |
# Hover over a point to see probe ID and LOD score; also highlighted | |
# are any other eQTL for that probe. Click on the point to see LOD | |
# curves below. | |
# | |
# If a clicked probe is in known gene, title in lower plot is a link | |
# to the Mouse Genome Informatics (MGI) site at the Jackson Lab. | |
# | |
# Hover over markers in LOD curve plot to view marker names; click on | |
# a marker to see the phenotype-vs-genotype plot to right. In | |
# geno-vs-pheno plot, hover over average to view value, and hover over | |
# points to view individual IDs. | |
# | |
# This is awful code; I just barely know what I'm doing. | |
# function that does all of the work | |
draw = (data) -> | |
d3.select("p#loading").remove() | |
d3.select("div#legend").style("opacity", 1) | |
d3.select("div#geneinput").style("opacity", 1) | |
# dimensions of panels | |
w = [500, 300] | |
h = [w[0], 200] | |
pad = {left:60, top:40, right:40, bottom: 40, inner: 10} | |
w = [w[0], w[0] + w[1] + pad.left + pad.right, w[1]] | |
h = [h[0], h[1], h[0]] | |
left = [pad.left, pad.left, | |
pad.left + w[0] + pad.right + pad.left] | |
top = [pad.top, | |
pad.top + h[0] + pad.bottom + pad.top, | |
pad.top] | |
right = [] | |
bottom = [] | |
for i of left | |
right[i] = left[i] + w[i] | |
bottom[i] = top[i] + h[i] | |
totalw = right[2] + pad.right | |
totalh = bottom[1] + pad.bottom | |
# Size of rectangles in top-left panel | |
peakRad = 2 | |
bigRad = 5 | |
# gap between chromosomes in lower plot | |
chrGap = 8 | |
# transition speeds | |
slowtime = 1000 | |
fasttime = 250 | |
# height of marker ticks in lower panel | |
tickHeight = (bottom[1] - top[1])*0.02 | |
# jitter amounts for PXG plot | |
jitterAmount = (right[2] - left[2])/50 | |
jitter = [] | |
for i of data.individuals | |
jitter[i] = (2.0*Math.random()-1.0) * jitterAmount | |
nodig = d3.format(".0f") | |
onedig = d3.format(".1f") | |
twodig = d3.format(".2f") | |
# colors definitions | |
lightGray = d3.rgb(230, 230, 230) | |
darkGray = d3.rgb(200, 200, 200) | |
darkblue = "darkslateblue" | |
darkgreen = "darkslateblue" # "darkgreen" # <- I didn't like having separate colors for each chr in cis/trans plot | |
pink = "hotpink" | |
altpink = "#E9CFEC" | |
purple = "#8C4374" | |
darkred = "crimson" | |
# bgcolor = "black" | |
labelcolor = "black" # "white" | |
titlecolor = "blue" # "Wheat" | |
maincolor = "darkblue" # "Wheat" | |
# calculate X and Y scales, using cM positions | |
totalChrLength = 0; | |
for c in data.chrnames | |
data.chr[c].length_cM = data.chr[c].end_cM - data.chr[c].start_cM | |
totalChrLength += data.chr[c].length_cM | |
chrXScale = {} | |
chrYScale = {} | |
curXPixel = left[0]+peakRad | |
curYPixel = bottom[0]-peakRad | |
for c in data.chrnames | |
data.chr[c].length_pixel = Math.round((w[0]-peakRad*2) * data.chr[c].length_cM / totalChrLength) | |
data.chr[c].start_Xpixel = curXPixel | |
data.chr[c].end_Xpixel = curXPixel + data.chr[c].length_pixel - 1 | |
data.chr[c].start_Ypixel = curYPixel | |
data.chr[c].end_Ypixel = curYPixel - (data.chr[c].length_pixel - 1) | |
chrXScale[c] = d3.scale.linear() | |
.domain([data.chr[c].start_cM, data.chr[c].end_cM]) | |
.range([data.chr[c].start_Xpixel, data.chr[c].end_Xpixel]) | |
.clamp(true) | |
chrYScale[c] = d3.scale.linear() | |
.domain([data.chr[c].start_cM, data.chr[c].end_cM]) | |
.range([data.chr[c].start_Ypixel, data.chr[c].end_Ypixel]) | |
.clamp(true) | |
curXPixel += data.chr[c].length_pixel | |
curYPixel -= data.chr[c].length_pixel | |
# slight adjustments | |
top[0] = data.chr["X"].end_Ypixel-peakRad | |
h[0] = bottom[0] - top[0] | |
data.chr["1"].start_Xpixel = left[0] | |
data.chr["1"].start_Ypixel = bottom[0] | |
data.chr["X"].end_Xpixel = right[0] | |
data.chr["X"].end_Ypixel = top[0] | |
# chr scales in lower figure | |
chrLowXScale = {} | |
cur = Math.round(pad.left + chrGap/2) | |
for c in data.chrnames | |
data.chr[c].start_lowerXpixel = cur | |
data.chr[c].end_lowerXpixel = cur + Math.round((w[1]-chrGap*(data.chrnames.length))/totalChrLength*data.chr[c].length_cM) | |
chrLowXScale[c] = d3.scale.linear() | |
.domain([data.chr[c].start_cM, data.chr[c].end_cM]) | |
.range([data.chr[c].start_lowerXpixel, data.chr[c].end_lowerXpixel]) | |
cur = data.chr[c].end_lowerXpixel + chrGap | |
# X scales for PXG plot | |
# autosome in intercross: 6 cases | |
pxgXscaleA = d3.scale.ordinal() | |
.domain(d3.range(6)) | |
.rangePoints([left[2], right[2]], 1) | |
# X chromosome in intercross (both sexes, one direction): 4 cases | |
pxgXscaleX = d3.scale.ordinal() | |
.domain(d3.range(4)) | |
.rangePoints([left[2], right[2]], 1) | |
# swap genotypes in females on X chromsome | |
for m in data.markers | |
continue unless data.pmark[m].chr == "X" | |
for g,i in data.geno[m] | |
if data.sex[i] == 0 | |
newg = 0 | |
switch g | |
when -1 then newg = -2 | |
when -2 then newg = -1 | |
when +1 then newg = +2 | |
when +2 then newg = +1 | |
data.geno[m][i] = newg | |
# create SVG | |
svg = d3.select("div#cistrans").append("svg") | |
.attr("width", totalw) | |
.attr("height", totalh) | |
# gray backgrounds | |
for j of left | |
svg.append("rect") | |
.attr("x", left[j]) | |
.attr("y", top[j]) | |
.attr("height", h[j]) | |
.attr("width", w[j]) | |
.attr("class", "innerBox") | |
# add dark gray rectangles to define chromosome boundaries as checkerboard | |
checkerboard = svg.append("g").attr("id", "checkerboard") | |
for ci,i in data.chrnames | |
for cj,j in data.chrnames | |
if((i + j) % 2 == 0) | |
checkerboard.append("rect") | |
.attr("x", data.chr[ci].start_Xpixel) | |
.attr("width", data.chr[ci].end_Xpixel - data.chr[ci].start_Xpixel) | |
.attr("y", data.chr[cj].end_Ypixel) | |
.attr("height", data.chr[cj].start_Ypixel - data.chr[cj].end_Ypixel) | |
.attr("stroke", "none") | |
.attr("fill", darkGray) | |
.style("pointer-events", "none") | |
# same in lower panel | |
checkerboard2 = svg.append("g").attr("id", "checkerboard2") | |
for ci,i in data.chrnames | |
if(i % 2 == 0) | |
checkerboard2.append("rect") | |
.attr("x", data.chr[ci].start_lowerXpixel - chrGap/2) | |
.attr("width", data.chr[ci].end_lowerXpixel - data.chr[ci].start_lowerXpixel + chrGap) | |
.attr("y", top[1]) | |
.attr("height", h[1]) | |
.attr("stroke", "none") | |
.attr("fill", darkGray) | |
.style("pointer-events", "none") | |
# chromosome labels | |
axislabels = svg.append("g").attr("id", "axislabels").style("pointer-events", "none") | |
axislabels.append("g").attr("id", "topleftX").selectAll("empty") | |
.data(data.chrnames) | |
.enter() | |
.append("text") | |
.text((d) -> d) | |
.attr("x", (d) -> (data.chr[d].start_Xpixel + data.chr[d].end_Xpixel)/2) | |
.attr("y", bottom[0] + pad.bottom*0.3) | |
.attr("fill", labelcolor) | |
axislabels.append("g").attr("id", "topleftY") | |
.selectAll("empty") | |
.data(data.chrnames) | |
.enter() | |
.append("text") | |
.text((d) -> d) | |
.attr("x", left[0] - pad.left*0.15) | |
.attr("y", (d) -> (data.chr[d].start_Ypixel + data.chr[d].end_Ypixel)/2) | |
.style("text-anchor", "end") | |
.attr("fill", labelcolor) | |
axislabels.append("g").attr("id", "bottomX").selectAll("empty") | |
.data(data.chrnames) | |
.enter() | |
.append("text") | |
.text((d) -> d) | |
.attr("x", (d) -> (data.chr[d].start_lowerXpixel + data.chr[d].end_lowerXpixel)/2) | |
.attr("y", bottom[1] + pad.bottom*0.3) | |
.attr("fill", labelcolor) | |
axislabels.append("text") | |
.text("Marker position (cM)") | |
.attr("x", (left[0] + right[0])/2) | |
.attr("y", bottom[0] + pad.bottom*0.75) | |
.attr("fill", titlecolor) | |
.attr("text-anchor", "middle") | |
axislabels.append("text") | |
.text("Position (cM)") | |
.attr("x", (left[1] + right[1])/2) | |
.attr("y", bottom[1] + pad.bottom*0.75) | |
.attr("fill", titlecolor) | |
.attr("text-anchor", "middle") | |
xloc = left[0] - pad.left*0.65 | |
yloc = (top[0] + bottom[0])/2 | |
axislabels.append("text") | |
.text("Probe position (cM)") | |
.attr("x", xloc) | |
.attr("y", yloc) | |
.attr("transform", "rotate(270,#{xloc},#{yloc})") | |
.style("text-anchor", "middle") | |
.attr("fill", titlecolor) | |
xloc = left[1] - pad.left*0.65 | |
yloc = (top[1] + bottom[1])/2 | |
axislabels.append("text") | |
.text("LOD score") | |
.attr("x", xloc) | |
.attr("y", yloc) | |
.attr("transform", "rotate(270,#{xloc},#{yloc})") | |
.style("text-anchor", "middle") | |
.attr("fill", titlecolor) | |
# overall maximum lod score | |
maxlod = d3.max(data.peaks, (d) -> d.lod) | |
# sort peaks to have increasing LOD score | |
data.peaks = data.peaks.sort (a,b) -> | |
return if a.lod < b.lod then -1 else +1 | |
# LOD score controls opacity | |
Zscale = d3.scale.linear() | |
.domain([0, 25]) | |
.range([0, 1]) | |
# tool tips using https://github.com/Caged/d3-tip | |
# [slightly modified in https://github.com/kbroman/d3-tip] | |
eqtltip = d3.svg.tip() | |
.orient("right") | |
.padding(3) | |
.text((z) -> "#{z.probe} (LOD = #{onedig(z.lod)})") | |
.attr("class", "d3-tip") | |
.attr("id", "eqtltip") | |
martip = d3.svg.tip() | |
.orient("right") | |
.padding(3) | |
.text((z) -> z) | |
.attr("class", "d3-tip") | |
.attr("id", "martip") | |
indtip = d3.svg.tip() | |
.orient("right") | |
.padding(3) | |
.text((d,i) -> data.individuals[i]) | |
.attr("class", "d3-tip") | |
.attr("id", "indtip") | |
efftip = d3.svg.tip() | |
.orient("right") | |
.padding(3) | |
.text((d) -> twodig(d)) | |
.attr("class", "d3-tip") | |
.attr("id", "efftip") | |
# create indices to lod scores, split by chromosome | |
cur = 0 | |
for c in data.chrnames | |
for p in data.pmarknames[c] | |
data.pmark[p].index = cur | |
cur++ | |
# function for drawing lod curve for probe | |
draw_probe = (probe_data) -> | |
# delete all related stuff | |
svg.selectAll(".probe_data").remove() | |
d3.select("text#pxgtitle").text("") | |
svg.selectAll(".plotPXG").remove() | |
# find marker with maximum LOD score | |
maxlod = -1 | |
maxlod_marker = null | |
for m in data.markers | |
lod = probe_data.lod[data.pmark[m].index] | |
if maxlod < lod | |
maxlod = lod | |
maxlod_marker = m | |
# y-axis scale | |
lodcurve_yScale = d3.scale.linear() | |
.domain([0, maxlod*1.05]) | |
.range([bottom[1], top[1]]) | |
# y-axis | |
yaxis = svg.append("g").attr("class", "probe_data").attr("id", "loweryaxis") | |
ticks = lodcurve_yScale.ticks(6) | |
yaxis.selectAll("empty") | |
.data(ticks) | |
.enter() | |
.append("line") | |
.attr("y1", (d) -> lodcurve_yScale(d)) | |
.attr("y2", (d) -> lodcurve_yScale(d)) | |
.attr("x1", left[1]) | |
.attr("x2", right[1]) | |
.attr("stroke", "white") | |
.attr("stroke-width", "1") | |
yaxis.selectAll("empty") | |
.data(ticks) | |
.enter() | |
.append("text") | |
.text((d) -> | |
return if maxlod > 10 then nodig(d) else onedig(d)) | |
.attr("y", (d) -> lodcurve_yScale(d)) | |
.attr("x", left[1] - pad.left*0.1) | |
.style("text-anchor", "end") | |
.attr("fill", labelcolor) | |
yaxis.append("line") | |
.attr("y1", lodcurve_yScale(5)) | |
.attr("y2", lodcurve_yScale(5)) | |
.attr("x1", left[1]) | |
.attr("x2", right[1]) | |
.attr("stroke", purple) | |
.attr("stroke-width", "1") | |
.attr("stroke-dasharray", "2,2") | |
# lod curves by chr | |
lodcurve = (c) -> | |
d3.svg.line() | |
.x((p) -> chrLowXScale[c](data.pmark[p].pos_cM)) | |
.y((p) -> lodcurve_yScale(probe_data.lod[data.pmark[p].index])) | |
curves = svg.append("g").attr("id", "curves").attr("class", "probe_data") | |
for c in data.chrnames | |
curves.append("path") | |
.datum(data.pmarknames[c]) | |
.attr("d", lodcurve(c)) | |
.attr("class", "thickline") | |
.attr("stroke", darkblue) | |
.style("pointer-events", "none") | |
.attr("fill", "none") | |
# title | |
titletext = probe_data.probe | |
probeaxes = svg.append("g").attr("id", "probe_data_axes").attr("class", "probe_data") | |
gene = data.probes[probe_data.probe].gene | |
ensembl = "http://www.ensembl.org/Mus_musculus/Search/Details?db=core;end=1;idx=Gene;species=Mus_musculus;q=#{gene}" | |
mgi = "http://www.informatics.jax.org/searchtool/Search.do?query=#{gene}" | |
if gene isnt null | |
titletext += " (#{gene})" | |
xlink = probeaxes.append("a").attr("xlink:href", mgi) | |
xlink.append("text") | |
.text(titletext) | |
.attr("x", (left[1]+right[1])/2) | |
.attr("y", top[1] - pad.top/2) | |
.attr("fill", maincolor) | |
.style("font-size", "18px") | |
else | |
probeaxes.append("text") | |
.text(titletext) | |
.attr("x", (left[1]+right[1])/2) | |
.attr("y", top[1] - pad.top/2) | |
.attr("fill", maincolor) | |
.style("font-size", "18px") | |
# black border | |
svg.append("rect").attr("class", "probe_data") | |
.attr("x", left[1]) | |
.attr("y", top[1]) | |
.attr("height", h[1]) | |
.attr("width", w[1]) | |
.attr("class", "outerBox") | |
# point at probe | |
svg.append("circle") | |
.attr("class", "probe_data") | |
.attr("id", "probe_circle") | |
.attr("cx", chrLowXScale[data.probes[probe_data.probe].chr](data.probes[probe_data.probe].pos_cM)) | |
.attr("cy", top[1]) | |
.attr("r", bigRad) | |
.attr("fill", pink) | |
.attr("stroke", darkblue) | |
.attr("stroke-width", 1) | |
.attr("opacity", 1) | |
svg.append("text") | |
.attr("id", "pxgtitle") | |
.attr("x", (left[2]+right[2])/2) | |
.attr("y", pad.top/2) | |
.text("") | |
.attr("fill", maincolor) | |
# keep track of clicked marker | |
markerClick = {} | |
for m in data.markers | |
markerClick[m] = 0 | |
lastMarker = "" | |
# dots at markers on LOD curves | |
svg.append("g").attr("id", "markerCircle").attr("class", "probe_data") | |
.selectAll("empty") | |
.data(data.markers) | |
.enter() | |
.append("circle") | |
.attr("class", "probe_data") | |
.attr("id", (td) -> "marker_#{td}") | |
.attr("cx", (td) -> chrLowXScale[data.pmark[td].chr](data.pmark[td].pos_cM)) | |
.attr("cy", (td) -> lodcurve_yScale(probe_data.lod[data.pmark[td].index])) | |
.attr("r", bigRad) | |
.attr("fill", purple) | |
.attr("stroke", "none") | |
.attr("stroke-width", "2") | |
.attr("opacity", 0) | |
.on("mouseover", (td) -> | |
d3.select(this).attr("opacity", 1) unless markerClick[td] | |
martip.call(this,td)) | |
.on "mouseout", (td) -> | |
d3.select(this).attr("opacity", markerClick[td]) | |
d3.selectAll("#martip").remove() | |
.on "click", (td) -> | |
pos = data.pmark[td].pos_cM | |
chr = data.pmark[td].chr | |
title = "#{td} (chr #{chr}, #{onedig(pos)} cM)" | |
d3.select("text#pxgtitle").text(title) | |
if lastMarker is "" | |
plotPXG td | |
else | |
markerClick[lastMarker] = 0 | |
d3.select("circle#marker_#{lastMarker}").attr("opacity", 0).attr("fill",purple).attr("stroke","none") | |
revPXG td | |
lastMarker = td | |
markerClick[td] = 1 | |
d3.select(this).attr("opacity", 1).attr("fill",altpink).attr("stroke",purple) | |
draw_pxgXaxis = (means, genotypes, chr, sexcenter, male) -> | |
pxgXaxis.selectAll("line.PXGvert") | |
.data(means) | |
.enter() | |
.append("line") | |
.attr("class", "PXGvert") | |
.attr("y1", top[2]) | |
.attr("y2", bottom[2]) | |
.attr("x1", (d,i) -> return if(chr is "X") then pxgXscaleX(i) else pxgXscaleA(i)) | |
.attr("x2", (d,i) -> return if(chr is "X") then pxgXscaleX(i) else pxgXscaleA(i)) | |
.attr("stroke", darkGray) | |
.attr("fill", "none") | |
.attr("stroke-width", "1") | |
pxgXaxis.selectAll("line.PXGvert") | |
.data(means) | |
.transition().duration(fasttime) | |
.attr("x1", (d,i) -> return if(chr is "X") then pxgXscaleX(i) else pxgXscaleA(i)) | |
.attr("x2", (d,i) -> return if(chr is "X") then pxgXscaleX(i) else pxgXscaleA(i)) | |
pxgXaxis.selectAll("line.PXGvert") | |
.data(means) | |
.exit().remove() | |
pxgXaxis.selectAll("text.PXGgeno") | |
.data(genotypes) | |
.enter() | |
.append("text") | |
.attr("class", "PXGgeno") | |
.text((d) -> d) | |
.attr("y", bottom[2] + pad.bottom*0.25) | |
.attr("x", (d,i) -> return if(chr is "X") then pxgXscaleX(i) else pxgXscaleA(i)) | |
.attr("fill", labelcolor) | |
pxgXaxis.selectAll("text.PXGgeno") | |
.data(genotypes) | |
.transition().duration(fasttime) | |
.text((d) -> d) | |
.attr("x", (d,i) -> return if(chr is "X") then pxgXscaleX(i) else pxgXscaleA(i)) | |
pxgXaxis.selectAll("text.PXGgeno") | |
.data(genotypes) | |
.exit().remove() | |
pxgXaxis.selectAll("text.PXGsex") | |
.data(["Female", "Male"]) | |
.enter() | |
.append("text") | |
.attr("class", "PXGsex") | |
.attr("id", "sextext") | |
.text((d) -> d) | |
.attr("y", bottom[j] + pad.bottom*0.75) | |
.attr("x", (d, i) -> sexcenter[i]) | |
.attr("fill", labelcolor) | |
pxgXaxis.selectAll("text.PXGsex") | |
.data(["Female", "Male"]) | |
.transition().duration(fasttime) | |
.attr("x", (d, i) -> sexcenter[i]) | |
pxgXaxis.selectAll("text.PXGsex") | |
.data(["Female", "Male"]) | |
.exit().remove() | |
# add line segments | |
meanmarks.selectAll("line.PXGmeans") | |
.data(means) | |
.enter() | |
.append("line") | |
.attr("class", "PXGmeans") | |
.attr("x1", (d,i) -> | |
return if chr is "X" then pxgXscaleX(i)-jitterAmount*3 else pxgXscaleA(i)-jitterAmount*3) | |
.attr("x2", (d,i) -> | |
return if chr is "X" then pxgXscaleX(i)+jitterAmount*3 else pxgXscaleA(i)+jitterAmount*3) | |
.attr("y1", (d) -> pxgYscale(d)) | |
.attr("y2", (d) -> pxgYscale(d)) | |
.attr("stroke", (d,i) -> return if male[i] then darkblue else darkred) | |
.attr("stroke-width", 4) | |
.on("mouseover", efftip) | |
.on("mouseout", -> d3.selectAll("#efftip").remove()) | |
meanmarks.selectAll("line.PXGmeans") | |
.data(means) | |
.transition().duration(slowtime) | |
.attr("x1", (d,i) -> | |
return if chr is "X" then pxgXscaleX(i)-jitterAmount*3 else pxgXscaleA(i)-jitterAmount*3) | |
.attr("x2", (d,i) -> | |
return if chr is "X" then pxgXscaleX(i)+jitterAmount*3 else pxgXscaleA(i)+jitterAmount*3) | |
.attr("y1", (d) -> pxgYscale(d)) | |
.attr("y2", (d) -> pxgYscale(d)) | |
.attr("stroke", (d,i) -> return if male[i] then darkblue else darkred) | |
meanmarks.selectAll("line.PXGmeans") | |
.data(means).exit().remove() | |
pxgYscale = null | |
pxgXaxis = svg.append("g").attr("class", "probe_data").attr("id", "pxg_xaxis") | |
pxgYaxis = svg.append("g").attr("class", "probe_data").attr("class", "plotPXG").attr("id", "pxg_yaxis") | |
meanmarks = svg.append("g").attr("id", "pxgmeans").attr("class", "probe_data") | |
plotPXG = (marker) -> | |
pxgYscale = d3.scale.linear() | |
.domain([d3.min(probe_data.pheno), | |
d3.max(probe_data.pheno)]) | |
.range([bottom[2]-pad.inner, top[2]+pad.inner]) | |
pxgticks = pxgYscale.ticks(8) | |
pxgYaxis.selectAll("empty") | |
.data(pxgticks) | |
.enter() | |
.append("line") | |
.attr("y1", (d) -> pxgYscale(d)) | |
.attr("y2", (d) -> pxgYscale(d)) | |
.attr("x1", left[2]) | |
.attr("x2", right[2]) | |
.attr("stroke", "white") | |
.attr("stroke-width", "1") | |
pxgYaxis.selectAll("empty") | |
.data(pxgticks) | |
.enter() | |
.append("text") | |
.text((d) -> twodig(d)) | |
.attr("y", (d) -> pxgYscale(d)) | |
.attr("x", left[2] - pad.left*0.1) | |
.style("text-anchor", "end") | |
.attr("fill", labelcolor) | |
# calculate group averages | |
chr = data.pmark[marker].chr | |
if(chr is "X") | |
means = [0,0,0,0] | |
n = [0,0,0,0] | |
male = [0,0,1,1] | |
genotypes = ["BR", "RR", "BY", "RY"] | |
sexcenter = [(pxgXscaleX(0) + pxgXscaleX(1))/2, | |
(pxgXscaleX(2) + pxgXscaleX(3))/2] | |
else | |
means = [0,0,0,0,0,0] | |
n = [0,0,0,0,0,0] | |
male = [0,0,0,1,1,1] | |
genotypes = ["BB", "BR", "RR", "BB", "BR", "RR"] | |
sexcenter = [pxgXscaleA(1), pxgXscaleA(4)] | |
for i of data.individuals | |
g = Math.abs(data.geno[marker][i]) | |
sx = data.sex[i] | |
if(data.pmark[marker].chr is "X") | |
x = sx*2+g-1 | |
else | |
x = sx*3+g-1 | |
means[x] += probe_data.pheno[i] | |
n[x]++ | |
for i of means | |
means[i] /= n[i] | |
draw_pxgXaxis(means, genotypes, chr, sexcenter, male) | |
svg.append("g").attr("id", "plotPXG").attr("class", "probe_data").attr("id","PXGpoints").selectAll("empty") | |
.data(probe_data.pheno) | |
.enter() | |
.append("circle") | |
.attr("class", "plotPXG") | |
.attr("cx", (d,i) -> | |
g = Math.abs(data.geno[marker][i]) | |
sx = data.sex[i] | |
if(data.pmark[marker].chr is "X") | |
return pxgXscaleX(sx*2+g-1)+jitter[i] | |
pxgXscaleA(sx*3+g-1)+jitter[i]) | |
.attr("cy", (d) -> pxgYscale(d)) | |
.attr("r", peakRad) | |
.attr("fill", (d,i) -> | |
g = data.geno[marker][i] | |
return pink if g < 0 | |
darkGray) | |
.attr("stroke", (d,i) -> | |
g = data.geno[marker][i] | |
return purple if g < 0 | |
"black") | |
.attr("stroke-width", (d,i) -> | |
g = data.geno[marker][i] | |
return "2" if g < 0 | |
"1") | |
.on "mouseover", (d,i) -> | |
d3.select(this).attr("r", bigRad) | |
indtip.call(this, d, i) | |
.on "mouseout", -> | |
d3.selectAll("#indtip").remove() | |
d3.select(this).attr("r", peakRad) | |
revPXG = (marker) -> | |
# calculate group averages | |
chr = data.pmark[marker].chr | |
if(chr is "X") | |
means = [0,0,0,0] | |
n = [0,0,0,0] | |
male = [0,0,1,1] | |
genotypes = ["BR", "RR", "BY", "RY"] | |
sexcenter = [(pxgXscaleX(0) + pxgXscaleX(1))/2, | |
(pxgXscaleX(2) + pxgXscaleX(3))/2] | |
else | |
means = [0,0,0,0,0,0] | |
n = [0,0,0,0,0,0] | |
male = [0,0,0,1,1,1] | |
genotypes = ["BB", "BR", "RR", "BB", "BR", "RR"] | |
sexcenter = [pxgXscaleA(1), pxgXscaleA(4)] | |
for i of data.individuals | |
g = Math.abs(data.geno[marker][i]) | |
sx = data.sex[i] | |
if(data.pmark[marker].chr is "X") | |
x = sx*2+g-1 | |
else | |
x = sx*3+g-1 | |
means[x] += probe_data.pheno[i] | |
n[x]++ | |
for i of means | |
means[i] /= n[i] | |
draw_pxgXaxis(means, genotypes, chr, sexcenter, male) | |
svg.selectAll("circle.plotPXG") | |
.transition().duration(slowtime) | |
.attr("cx", (d,i) -> | |
g = Math.abs(data.geno[marker][i]) | |
sx = data.sex[i] | |
if(data.pmark[marker].chr is "X") | |
return pxgXscaleX(sx*2+g-1)+jitter[i] | |
pxgXscaleA(sx*3+g-1)+jitter[i]) | |
.attr("fill", (d,i) -> | |
g = data.geno[marker][i] | |
return pink if g < 0 | |
darkGray) | |
.attr("stroke", (d,i) -> | |
g = data.geno[marker][i] | |
return purple if g < 0 | |
"black") | |
.attr("stroke-width", (d,i) -> | |
g = data.geno[marker][i] | |
return "2" if g < 0 | |
"1") | |
# initially select the marker with maximum LOD | |
lastMarker = maxlod_marker | |
markerClick[lastMarker] = 1 | |
d3.select("circle#marker_#{lastMarker}").attr("opacity", 1).attr("fill",altpink).attr("stroke",purple) | |
pos = data.pmark[lastMarker].pos_cM | |
chr = data.pmark[lastMarker].chr | |
title = "#{lastMarker} (chr #{chr}, #{onedig(pos)} cM)" | |
d3.select("text#pxgtitle").text(title) | |
plotPXG(lastMarker) | |
chrindex = {} | |
for c,i in data.chrnames | |
chrindex[c] = i | |
# circles at eQTL peaks | |
peaks = svg.append("g").attr("id", "peaks") | |
.selectAll("empty") | |
.data(data.peaks) | |
.enter() | |
.append("circle") | |
.attr("class", (d) -> "probe_#{d.probe}") | |
.attr("cx", (d) -> chrXScale[d.chr](d.pos_cM)) | |
.attr("cy", (d) -> chrYScale[data.probes[d.probe].chr](data.probes[d.probe].pos_cM)) | |
.attr("r", peakRad) | |
.attr("stroke", "none") | |
.attr("fill", (d) -> return if(chrindex[d.chr] % 2 is 0) then darkblue else darkgreen) | |
.attr("opacity", (d) -> Zscale(d.lod)) | |
.on "mouseover", (d) -> | |
d3.selectAll("circle.probe_#{d.probe}") | |
.attr("r", bigRad) | |
.attr("fill", pink) | |
.attr("stroke", darkblue) | |
.attr("stroke-width", 1) | |
.attr("opacity", 1) | |
eqtltip.call(this,d) | |
.on "mouseout", (d) -> | |
d3.selectAll("circle.probe_#{d.probe}") | |
.attr("r", peakRad) | |
.attr("fill", (d) -> return if(chrindex[d.chr] % 2 is 0) then darkblue else darkgreen) | |
.attr("stroke", "none") | |
.attr("opacity", (d) -> Zscale(d.lod)) | |
d3.selectAll("#eqtltip").remove() | |
.on "click", (d) -> | |
d3.json("http://www.biostat.wisc.edu/~kbroman/D3/cistrans/data/probe_data/probe#{d.probe}.json", draw_probe) | |
# initial set of LOD curves at the bottom | |
d3.json("http://www.biostat.wisc.edu/~kbroman/D3/cistrans/data/probe_data/probe517761.json", draw_probe) | |
# black borders | |
for j of left | |
svg.append("rect") | |
.attr("x", left[j]) | |
.attr("y", top[j]) | |
.attr("height", h[j]) | |
.attr("width", w[j]) | |
.attr("class", "outerBox") | |
# load json file and call draw function | |
d3.json("http://www.biostat.wisc.edu/~kbroman/D3/cistrans/data/insulin_eqtl.json", draw) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
body { | |
font-family: sans-serif; | |
} | |
.line { | |
stroke-width: 1; | |
fill: none; | |
} | |
.thickline { | |
stroke-width: 2; | |
fill: none; | |
} | |
text { | |
font-family: sans-serif; | |
font-size: 11pt; | |
dominant-baseline: middle; | |
text-anchor: middle; | |
} | |
text.alignright { | |
text-anchor: end; | |
} | |
.outerBox { | |
stroke: black; | |
stroke-width: 2; | |
fill: none; | |
pointer-events: none; | |
} | |
.innerBox { | |
fill: rgb(230, 230, 230); | |
stroke: none; | |
pointer-events: none; | |
} | |
.axis { | |
pointer-events: none; | |
} | |
.d3-tip { | |
fill: darkslateblue; | |
stroke: none; | |
font-weight: bold; | |
} | |
.d3-tip text { | |
fill: white; | |
font-size: 12px; | |
stroke: none; | |
font-family: sans-serif; | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// Generated by CoffeeScript 1.4.0 | |
(function() { | |
var draw; | |
draw = function(data) { | |
var Zscale, altpink, axislabels, bigRad, bottom, c, checkerboard, checkerboard2, chrGap, chrLowXScale, chrXScale, chrYScale, chrindex, ci, cj, cur, curXPixel, curYPixel, darkGray, darkblue, darkgreen, darkred, draw_probe, efftip, eqtltip, fasttime, g, h, i, indtip, j, jitter, jitterAmount, labelcolor, left, lightGray, m, maincolor, martip, maxlod, newg, nodig, onedig, p, pad, peakRad, peaks, pink, purple, pxgXscaleA, pxgXscaleX, right, slowtime, svg, tickHeight, titlecolor, top, totalChrLength, totalh, totalw, twodig, w, xloc, yloc, _i, _j, _k, _l, _len, _len1, _len10, _len2, _len3, _len4, _len5, _len6, _len7, _len8, _len9, _m, _n, _o, _p, _q, _r, _ref, _ref1, _ref10, _ref2, _ref3, _ref4, _ref5, _ref6, _ref7, _ref8, _ref9, _results, _s; | |
d3.select("p#loading").remove(); | |
d3.select("div#legend").style("opacity", 1); | |
d3.select("div#geneinput").style("opacity", 1); | |
w = [500, 300]; | |
h = [w[0], 200]; | |
pad = { | |
left: 60, | |
top: 40, | |
right: 40, | |
bottom: 40, | |
inner: 10 | |
}; | |
w = [w[0], w[0] + w[1] + pad.left + pad.right, w[1]]; | |
h = [h[0], h[1], h[0]]; | |
left = [pad.left, pad.left, pad.left + w[0] + pad.right + pad.left]; | |
top = [pad.top, pad.top + h[0] + pad.bottom + pad.top, pad.top]; | |
right = []; | |
bottom = []; | |
for (i in left) { | |
right[i] = left[i] + w[i]; | |
bottom[i] = top[i] + h[i]; | |
} | |
totalw = right[2] + pad.right; | |
totalh = bottom[1] + pad.bottom; | |
peakRad = 2; | |
bigRad = 5; | |
chrGap = 8; | |
slowtime = 1000; | |
fasttime = 250; | |
tickHeight = (bottom[1] - top[1]) * 0.02; | |
jitterAmount = (right[2] - left[2]) / 50; | |
jitter = []; | |
for (i in data.individuals) { | |
jitter[i] = (2.0 * Math.random() - 1.0) * jitterAmount; | |
} | |
nodig = d3.format(".0f"); | |
onedig = d3.format(".1f"); | |
twodig = d3.format(".2f"); | |
lightGray = d3.rgb(230, 230, 230); | |
darkGray = d3.rgb(200, 200, 200); | |
darkblue = "darkslateblue"; | |
darkgreen = "darkslateblue"; | |
pink = "hotpink"; | |
altpink = "#E9CFEC"; | |
purple = "#8C4374"; | |
darkred = "crimson"; | |
labelcolor = "black"; | |
titlecolor = "blue"; | |
maincolor = "darkblue"; | |
totalChrLength = 0; | |
_ref = data.chrnames; | |
for (_i = 0, _len = _ref.length; _i < _len; _i++) { | |
c = _ref[_i]; | |
data.chr[c].length_cM = data.chr[c].end_cM - data.chr[c].start_cM; | |
totalChrLength += data.chr[c].length_cM; | |
} | |
chrXScale = {}; | |
chrYScale = {}; | |
curXPixel = left[0] + peakRad; | |
curYPixel = bottom[0] - peakRad; | |
_ref1 = data.chrnames; | |
for (_j = 0, _len1 = _ref1.length; _j < _len1; _j++) { | |
c = _ref1[_j]; | |
data.chr[c].length_pixel = Math.round((w[0] - peakRad * 2) * data.chr[c].length_cM / totalChrLength); | |
data.chr[c].start_Xpixel = curXPixel; | |
data.chr[c].end_Xpixel = curXPixel + data.chr[c].length_pixel - 1; | |
data.chr[c].start_Ypixel = curYPixel; | |
data.chr[c].end_Ypixel = curYPixel - (data.chr[c].length_pixel - 1); | |
chrXScale[c] = d3.scale.linear().domain([data.chr[c].start_cM, data.chr[c].end_cM]).range([data.chr[c].start_Xpixel, data.chr[c].end_Xpixel]).clamp(true); | |
chrYScale[c] = d3.scale.linear().domain([data.chr[c].start_cM, data.chr[c].end_cM]).range([data.chr[c].start_Ypixel, data.chr[c].end_Ypixel]).clamp(true); | |
curXPixel += data.chr[c].length_pixel; | |
curYPixel -= data.chr[c].length_pixel; | |
} | |
top[0] = data.chr["X"].end_Ypixel - peakRad; | |
h[0] = bottom[0] - top[0]; | |
data.chr["1"].start_Xpixel = left[0]; | |
data.chr["1"].start_Ypixel = bottom[0]; | |
data.chr["X"].end_Xpixel = right[0]; | |
data.chr["X"].end_Ypixel = top[0]; | |
chrLowXScale = {}; | |
cur = Math.round(pad.left + chrGap / 2); | |
_ref2 = data.chrnames; | |
for (_k = 0, _len2 = _ref2.length; _k < _len2; _k++) { | |
c = _ref2[_k]; | |
data.chr[c].start_lowerXpixel = cur; | |
data.chr[c].end_lowerXpixel = cur + Math.round((w[1] - chrGap * data.chrnames.length) / totalChrLength * data.chr[c].length_cM); | |
chrLowXScale[c] = d3.scale.linear().domain([data.chr[c].start_cM, data.chr[c].end_cM]).range([data.chr[c].start_lowerXpixel, data.chr[c].end_lowerXpixel]); | |
cur = data.chr[c].end_lowerXpixel + chrGap; | |
} | |
pxgXscaleA = d3.scale.ordinal().domain(d3.range(6)).rangePoints([left[2], right[2]], 1); | |
pxgXscaleX = d3.scale.ordinal().domain(d3.range(4)).rangePoints([left[2], right[2]], 1); | |
_ref3 = data.markers; | |
for (_l = 0, _len3 = _ref3.length; _l < _len3; _l++) { | |
m = _ref3[_l]; | |
if (data.pmark[m].chr !== "X") { | |
continue; | |
} | |
_ref4 = data.geno[m]; | |
for (i = _m = 0, _len4 = _ref4.length; _m < _len4; i = ++_m) { | |
g = _ref4[i]; | |
if (data.sex[i] === 0) { | |
newg = 0; | |
switch (g) { | |
case -1: | |
newg = -2; | |
break; | |
case -2: | |
newg = -1; | |
break; | |
case +1: | |
newg = +2; | |
break; | |
case +2: | |
newg = +1; | |
} | |
data.geno[m][i] = newg; | |
} | |
} | |
} | |
svg = d3.select("div#cistrans").append("svg").attr("width", totalw).attr("height", totalh); | |
for (j in left) { | |
svg.append("rect").attr("x", left[j]).attr("y", top[j]).attr("height", h[j]).attr("width", w[j]).attr("class", "innerBox"); | |
} | |
checkerboard = svg.append("g").attr("id", "checkerboard"); | |
_ref5 = data.chrnames; | |
for (i = _n = 0, _len5 = _ref5.length; _n < _len5; i = ++_n) { | |
ci = _ref5[i]; | |
_ref6 = data.chrnames; | |
for (j = _o = 0, _len6 = _ref6.length; _o < _len6; j = ++_o) { | |
cj = _ref6[j]; | |
if ((i + j) % 2 === 0) { | |
checkerboard.append("rect").attr("x", data.chr[ci].start_Xpixel).attr("width", data.chr[ci].end_Xpixel - data.chr[ci].start_Xpixel).attr("y", data.chr[cj].end_Ypixel).attr("height", data.chr[cj].start_Ypixel - data.chr[cj].end_Ypixel).attr("stroke", "none").attr("fill", darkGray).style("pointer-events", "none"); | |
} | |
} | |
} | |
checkerboard2 = svg.append("g").attr("id", "checkerboard2"); | |
_ref7 = data.chrnames; | |
for (i = _p = 0, _len7 = _ref7.length; _p < _len7; i = ++_p) { | |
ci = _ref7[i]; | |
if (i % 2 === 0) { | |
checkerboard2.append("rect").attr("x", data.chr[ci].start_lowerXpixel - chrGap / 2).attr("width", data.chr[ci].end_lowerXpixel - data.chr[ci].start_lowerXpixel + chrGap).attr("y", top[1]).attr("height", h[1]).attr("stroke", "none").attr("fill", darkGray).style("pointer-events", "none"); | |
} | |
} | |
axislabels = svg.append("g").attr("id", "axislabels").style("pointer-events", "none"); | |
axislabels.append("g").attr("id", "topleftX").selectAll("empty").data(data.chrnames).enter().append("text").text(function(d) { | |
return d; | |
}).attr("x", function(d) { | |
return (data.chr[d].start_Xpixel + data.chr[d].end_Xpixel) / 2; | |
}).attr("y", bottom[0] + pad.bottom * 0.3).attr("fill", labelcolor); | |
axislabels.append("g").attr("id", "topleftY").selectAll("empty").data(data.chrnames).enter().append("text").text(function(d) { | |
return d; | |
}).attr("x", left[0] - pad.left * 0.15).attr("y", function(d) { | |
return (data.chr[d].start_Ypixel + data.chr[d].end_Ypixel) / 2; | |
}).style("text-anchor", "end").attr("fill", labelcolor); | |
axislabels.append("g").attr("id", "bottomX").selectAll("empty").data(data.chrnames).enter().append("text").text(function(d) { | |
return d; | |
}).attr("x", function(d) { | |
return (data.chr[d].start_lowerXpixel + data.chr[d].end_lowerXpixel) / 2; | |
}).attr("y", bottom[1] + pad.bottom * 0.3).attr("fill", labelcolor); | |
axislabels.append("text").text("Marker position (cM)").attr("x", (left[0] + right[0]) / 2).attr("y", bottom[0] + pad.bottom * 0.75).attr("fill", titlecolor).attr("text-anchor", "middle"); | |
axislabels.append("text").text("Position (cM)").attr("x", (left[1] + right[1]) / 2).attr("y", bottom[1] + pad.bottom * 0.75).attr("fill", titlecolor).attr("text-anchor", "middle"); | |
xloc = left[0] - pad.left * 0.65; | |
yloc = (top[0] + bottom[0]) / 2; | |
axislabels.append("text").text("Probe position (cM)").attr("x", xloc).attr("y", yloc).attr("transform", "rotate(270," + xloc + "," + yloc + ")").style("text-anchor", "middle").attr("fill", titlecolor); | |
xloc = left[1] - pad.left * 0.65; | |
yloc = (top[1] + bottom[1]) / 2; | |
axislabels.append("text").text("LOD score").attr("x", xloc).attr("y", yloc).attr("transform", "rotate(270," + xloc + "," + yloc + ")").style("text-anchor", "middle").attr("fill", titlecolor); | |
maxlod = d3.max(data.peaks, function(d) { | |
return d.lod; | |
}); | |
data.peaks = data.peaks.sort(function(a, b) { | |
if (a.lod < b.lod) { | |
return -1; | |
} else { | |
return +1; | |
} | |
}); | |
Zscale = d3.scale.linear().domain([0, 25]).range([0, 1]); | |
eqtltip = d3.svg.tip().orient("right").padding(3).text(function(z) { | |
return "" + z.probe + " (LOD = " + (onedig(z.lod)) + ")"; | |
}).attr("class", "d3-tip").attr("id", "eqtltip"); | |
martip = d3.svg.tip().orient("right").padding(3).text(function(z) { | |
return z; | |
}).attr("class", "d3-tip").attr("id", "martip"); | |
indtip = d3.svg.tip().orient("right").padding(3).text(function(d, i) { | |
return data.individuals[i]; | |
}).attr("class", "d3-tip").attr("id", "indtip"); | |
efftip = d3.svg.tip().orient("right").padding(3).text(function(d) { | |
return twodig(d); | |
}).attr("class", "d3-tip").attr("id", "efftip"); | |
cur = 0; | |
_ref8 = data.chrnames; | |
for (_q = 0, _len8 = _ref8.length; _q < _len8; _q++) { | |
c = _ref8[_q]; | |
_ref9 = data.pmarknames[c]; | |
for (_r = 0, _len9 = _ref9.length; _r < _len9; _r++) { | |
p = _ref9[_r]; | |
data.pmark[p].index = cur; | |
cur++; | |
} | |
} | |
draw_probe = function(probe_data) { | |
var chr, curves, draw_pxgXaxis, ensembl, gene, lastMarker, lod, lodcurve, lodcurve_yScale, markerClick, maxlod_marker, meanmarks, mgi, plotPXG, pos, probeaxes, pxgXaxis, pxgYaxis, pxgYscale, revPXG, ticks, title, titletext, xlink, yaxis, _len10, _len11, _len12, _ref10, _ref11, _ref12, _s, _t, _u; | |
svg.selectAll(".probe_data").remove(); | |
d3.select("text#pxgtitle").text(""); | |
svg.selectAll(".plotPXG").remove(); | |
maxlod = -1; | |
maxlod_marker = null; | |
_ref10 = data.markers; | |
for (_s = 0, _len10 = _ref10.length; _s < _len10; _s++) { | |
m = _ref10[_s]; | |
lod = probe_data.lod[data.pmark[m].index]; | |
if (maxlod < lod) { | |
maxlod = lod; | |
maxlod_marker = m; | |
} | |
} | |
lodcurve_yScale = d3.scale.linear().domain([0, maxlod * 1.05]).range([bottom[1], top[1]]); | |
yaxis = svg.append("g").attr("class", "probe_data").attr("id", "loweryaxis"); | |
ticks = lodcurve_yScale.ticks(6); | |
yaxis.selectAll("empty").data(ticks).enter().append("line").attr("y1", function(d) { | |
return lodcurve_yScale(d); | |
}).attr("y2", function(d) { | |
return lodcurve_yScale(d); | |
}).attr("x1", left[1]).attr("x2", right[1]).attr("stroke", "white").attr("stroke-width", "1"); | |
yaxis.selectAll("empty").data(ticks).enter().append("text").text(function(d) { | |
if (maxlod > 10) { | |
return nodig(d); | |
} else { | |
return onedig(d); | |
} | |
}).attr("y", function(d) { | |
return lodcurve_yScale(d); | |
}).attr("x", left[1] - pad.left * 0.1).style("text-anchor", "end").attr("fill", labelcolor); | |
yaxis.append("line").attr("y1", lodcurve_yScale(5)).attr("y2", lodcurve_yScale(5)).attr("x1", left[1]).attr("x2", right[1]).attr("stroke", purple).attr("stroke-width", "1").attr("stroke-dasharray", "2,2"); | |
lodcurve = function(c) { | |
return d3.svg.line().x(function(p) { | |
return chrLowXScale[c](data.pmark[p].pos_cM); | |
}).y(function(p) { | |
return lodcurve_yScale(probe_data.lod[data.pmark[p].index]); | |
}); | |
}; | |
curves = svg.append("g").attr("id", "curves").attr("class", "probe_data"); | |
_ref11 = data.chrnames; | |
for (_t = 0, _len11 = _ref11.length; _t < _len11; _t++) { | |
c = _ref11[_t]; | |
curves.append("path").datum(data.pmarknames[c]).attr("d", lodcurve(c)).attr("class", "thickline").attr("stroke", darkblue).style("pointer-events", "none").attr("fill", "none"); | |
} | |
titletext = probe_data.probe; | |
probeaxes = svg.append("g").attr("id", "probe_data_axes").attr("class", "probe_data"); | |
gene = data.probes[probe_data.probe].gene; | |
ensembl = "http://www.ensembl.org/Mus_musculus/Search/Details?db=core;end=1;idx=Gene;species=Mus_musculus;q=" + gene; | |
mgi = "http://www.informatics.jax.org/searchtool/Search.do?query=" + gene; | |
if (gene !== null) { | |
titletext += " (" + gene + ")"; | |
xlink = probeaxes.append("a").attr("xlink:href", mgi); | |
xlink.append("text").text(titletext).attr("x", (left[1] + right[1]) / 2).attr("y", top[1] - pad.top / 2).attr("fill", maincolor).style("font-size", "18px"); | |
} else { | |
probeaxes.append("text").text(titletext).attr("x", (left[1] + right[1]) / 2).attr("y", top[1] - pad.top / 2).attr("fill", maincolor).style("font-size", "18px"); | |
} | |
svg.append("rect").attr("class", "probe_data").attr("x", left[1]).attr("y", top[1]).attr("height", h[1]).attr("width", w[1]).attr("class", "outerBox"); | |
svg.append("circle").attr("class", "probe_data").attr("id", "probe_circle").attr("cx", chrLowXScale[data.probes[probe_data.probe].chr](data.probes[probe_data.probe].pos_cM)).attr("cy", top[1]).attr("r", bigRad).attr("fill", pink).attr("stroke", darkblue).attr("stroke-width", 1).attr("opacity", 1); | |
svg.append("text").attr("id", "pxgtitle").attr("x", (left[2] + right[2]) / 2).attr("y", pad.top / 2).text("").attr("fill", maincolor); | |
markerClick = {}; | |
_ref12 = data.markers; | |
for (_u = 0, _len12 = _ref12.length; _u < _len12; _u++) { | |
m = _ref12[_u]; | |
markerClick[m] = 0; | |
} | |
lastMarker = ""; | |
svg.append("g").attr("id", "markerCircle").attr("class", "probe_data").selectAll("empty").data(data.markers).enter().append("circle").attr("class", "probe_data").attr("id", function(td) { | |
return "marker_" + td; | |
}).attr("cx", function(td) { | |
return chrLowXScale[data.pmark[td].chr](data.pmark[td].pos_cM); | |
}).attr("cy", function(td) { | |
return lodcurve_yScale(probe_data.lod[data.pmark[td].index]); | |
}).attr("r", bigRad).attr("fill", purple).attr("stroke", "none").attr("stroke-width", "2").attr("opacity", 0).on("mouseover", function(td) { | |
if (!markerClick[td]) { | |
d3.select(this).attr("opacity", 1); | |
} | |
return martip.call(this, td); | |
}).on("mouseout", function(td) { | |
d3.select(this).attr("opacity", markerClick[td]); | |
return d3.selectAll("#martip").remove(); | |
}).on("click", function(td) { | |
var chr, pos, title; | |
pos = data.pmark[td].pos_cM; | |
chr = data.pmark[td].chr; | |
title = "" + td + " (chr " + chr + ", " + (onedig(pos)) + " cM)"; | |
d3.select("text#pxgtitle").text(title); | |
if (lastMarker === "") { | |
plotPXG(td); | |
} else { | |
markerClick[lastMarker] = 0; | |
d3.select("circle#marker_" + lastMarker).attr("opacity", 0).attr("fill", purple).attr("stroke", "none"); | |
revPXG(td); | |
} | |
lastMarker = td; | |
markerClick[td] = 1; | |
return d3.select(this).attr("opacity", 1).attr("fill", altpink).attr("stroke", purple); | |
}); | |
draw_pxgXaxis = function(means, genotypes, chr, sexcenter, male) { | |
pxgXaxis.selectAll("line.PXGvert").data(means).enter().append("line").attr("class", "PXGvert").attr("y1", top[2]).attr("y2", bottom[2]).attr("x1", function(d, i) { | |
if (chr === "X") { | |
return pxgXscaleX(i); | |
} else { | |
return pxgXscaleA(i); | |
} | |
}).attr("x2", function(d, i) { | |
if (chr === "X") { | |
return pxgXscaleX(i); | |
} else { | |
return pxgXscaleA(i); | |
} | |
}).attr("stroke", darkGray).attr("fill", "none").attr("stroke-width", "1"); | |
pxgXaxis.selectAll("line.PXGvert").data(means).transition().duration(fasttime).attr("x1", function(d, i) { | |
if (chr === "X") { | |
return pxgXscaleX(i); | |
} else { | |
return pxgXscaleA(i); | |
} | |
}).attr("x2", function(d, i) { | |
if (chr === "X") { | |
return pxgXscaleX(i); | |
} else { | |
return pxgXscaleA(i); | |
} | |
}); | |
pxgXaxis.selectAll("line.PXGvert").data(means).exit().remove(); | |
pxgXaxis.selectAll("text.PXGgeno").data(genotypes).enter().append("text").attr("class", "PXGgeno").text(function(d) { | |
return d; | |
}).attr("y", bottom[2] + pad.bottom * 0.25).attr("x", function(d, i) { | |
if (chr === "X") { | |
return pxgXscaleX(i); | |
} else { | |
return pxgXscaleA(i); | |
} | |
}).attr("fill", labelcolor); | |
pxgXaxis.selectAll("text.PXGgeno").data(genotypes).transition().duration(fasttime).text(function(d) { | |
return d; | |
}).attr("x", function(d, i) { | |
if (chr === "X") { | |
return pxgXscaleX(i); | |
} else { | |
return pxgXscaleA(i); | |
} | |
}); | |
pxgXaxis.selectAll("text.PXGgeno").data(genotypes).exit().remove(); | |
pxgXaxis.selectAll("text.PXGsex").data(["Female", "Male"]).enter().append("text").attr("class", "PXGsex").attr("id", "sextext").text(function(d) { | |
return d; | |
}).attr("y", bottom[j] + pad.bottom * 0.75).attr("x", function(d, i) { | |
return sexcenter[i]; | |
}).attr("fill", labelcolor); | |
pxgXaxis.selectAll("text.PXGsex").data(["Female", "Male"]).transition().duration(fasttime).attr("x", function(d, i) { | |
return sexcenter[i]; | |
}); | |
pxgXaxis.selectAll("text.PXGsex").data(["Female", "Male"]).exit().remove(); | |
meanmarks.selectAll("line.PXGmeans").data(means).enter().append("line").attr("class", "PXGmeans").attr("x1", function(d, i) { | |
if (chr === "X") { | |
return pxgXscaleX(i) - jitterAmount * 3; | |
} else { | |
return pxgXscaleA(i) - jitterAmount * 3; | |
} | |
}).attr("x2", function(d, i) { | |
if (chr === "X") { | |
return pxgXscaleX(i) + jitterAmount * 3; | |
} else { | |
return pxgXscaleA(i) + jitterAmount * 3; | |
} | |
}).attr("y1", function(d) { | |
return pxgYscale(d); | |
}).attr("y2", function(d) { | |
return pxgYscale(d); | |
}).attr("stroke", function(d, i) { | |
if (male[i]) { | |
return darkblue; | |
} else { | |
return darkred; | |
} | |
}).attr("stroke-width", 4).on("mouseover", efftip).on("mouseout", function() { | |
return d3.selectAll("#efftip").remove(); | |
}); | |
meanmarks.selectAll("line.PXGmeans").data(means).transition().duration(slowtime).attr("x1", function(d, i) { | |
if (chr === "X") { | |
return pxgXscaleX(i) - jitterAmount * 3; | |
} else { | |
return pxgXscaleA(i) - jitterAmount * 3; | |
} | |
}).attr("x2", function(d, i) { | |
if (chr === "X") { | |
return pxgXscaleX(i) + jitterAmount * 3; | |
} else { | |
return pxgXscaleA(i) + jitterAmount * 3; | |
} | |
}).attr("y1", function(d) { | |
return pxgYscale(d); | |
}).attr("y2", function(d) { | |
return pxgYscale(d); | |
}).attr("stroke", function(d, i) { | |
if (male[i]) { | |
return darkblue; | |
} else { | |
return darkred; | |
} | |
}); | |
return meanmarks.selectAll("line.PXGmeans").data(means).exit().remove(); | |
}; | |
pxgYscale = null; | |
pxgXaxis = svg.append("g").attr("class", "probe_data").attr("id", "pxg_xaxis"); | |
pxgYaxis = svg.append("g").attr("class", "probe_data").attr("class", "plotPXG").attr("id", "pxg_yaxis"); | |
meanmarks = svg.append("g").attr("id", "pxgmeans").attr("class", "probe_data"); | |
plotPXG = function(marker) { | |
var chr, genotypes, male, means, n, pxgticks, sexcenter, sx, x; | |
pxgYscale = d3.scale.linear().domain([d3.min(probe_data.pheno), d3.max(probe_data.pheno)]).range([bottom[2] - pad.inner, top[2] + pad.inner]); | |
pxgticks = pxgYscale.ticks(8); | |
pxgYaxis.selectAll("empty").data(pxgticks).enter().append("line").attr("y1", function(d) { | |
return pxgYscale(d); | |
}).attr("y2", function(d) { | |
return pxgYscale(d); | |
}).attr("x1", left[2]).attr("x2", right[2]).attr("stroke", "white").attr("stroke-width", "1"); | |
pxgYaxis.selectAll("empty").data(pxgticks).enter().append("text").text(function(d) { | |
return twodig(d); | |
}).attr("y", function(d) { | |
return pxgYscale(d); | |
}).attr("x", left[2] - pad.left * 0.1).style("text-anchor", "end").attr("fill", labelcolor); | |
chr = data.pmark[marker].chr; | |
if (chr === "X") { | |
means = [0, 0, 0, 0]; | |
n = [0, 0, 0, 0]; | |
male = [0, 0, 1, 1]; | |
genotypes = ["BR", "RR", "BY", "RY"]; | |
sexcenter = [(pxgXscaleX(0) + pxgXscaleX(1)) / 2, (pxgXscaleX(2) + pxgXscaleX(3)) / 2]; | |
} else { | |
means = [0, 0, 0, 0, 0, 0]; | |
n = [0, 0, 0, 0, 0, 0]; | |
male = [0, 0, 0, 1, 1, 1]; | |
genotypes = ["BB", "BR", "RR", "BB", "BR", "RR"]; | |
sexcenter = [pxgXscaleA(1), pxgXscaleA(4)]; | |
} | |
for (i in data.individuals) { | |
g = Math.abs(data.geno[marker][i]); | |
sx = data.sex[i]; | |
if (data.pmark[marker].chr === "X") { | |
x = sx * 2 + g - 1; | |
} else { | |
x = sx * 3 + g - 1; | |
} | |
means[x] += probe_data.pheno[i]; | |
n[x]++; | |
} | |
for (i in means) { | |
means[i] /= n[i]; | |
} | |
draw_pxgXaxis(means, genotypes, chr, sexcenter, male); | |
return svg.append("g").attr("id", "plotPXG").attr("class", "probe_data").attr("id", "PXGpoints").selectAll("empty").data(probe_data.pheno).enter().append("circle").attr("class", "plotPXG").attr("cx", function(d, i) { | |
g = Math.abs(data.geno[marker][i]); | |
sx = data.sex[i]; | |
if (data.pmark[marker].chr === "X") { | |
return pxgXscaleX(sx * 2 + g - 1) + jitter[i]; | |
} | |
return pxgXscaleA(sx * 3 + g - 1) + jitter[i]; | |
}).attr("cy", function(d) { | |
return pxgYscale(d); | |
}).attr("r", peakRad).attr("fill", function(d, i) { | |
g = data.geno[marker][i]; | |
if (g < 0) { | |
return pink; | |
} | |
return darkGray; | |
}).attr("stroke", function(d, i) { | |
g = data.geno[marker][i]; | |
if (g < 0) { | |
return purple; | |
} | |
return "black"; | |
}).attr("stroke-width", function(d, i) { | |
g = data.geno[marker][i]; | |
if (g < 0) { | |
return "2"; | |
} | |
return "1"; | |
}).on("mouseover", function(d, i) { | |
d3.select(this).attr("r", bigRad); | |
return indtip.call(this, d, i); | |
}).on("mouseout", function() { | |
d3.selectAll("#indtip").remove(); | |
return d3.select(this).attr("r", peakRad); | |
}); | |
}; | |
revPXG = function(marker) { | |
var chr, genotypes, male, means, n, sexcenter, sx, x; | |
chr = data.pmark[marker].chr; | |
if (chr === "X") { | |
means = [0, 0, 0, 0]; | |
n = [0, 0, 0, 0]; | |
male = [0, 0, 1, 1]; | |
genotypes = ["BR", "RR", "BY", "RY"]; | |
sexcenter = [(pxgXscaleX(0) + pxgXscaleX(1)) / 2, (pxgXscaleX(2) + pxgXscaleX(3)) / 2]; | |
} else { | |
means = [0, 0, 0, 0, 0, 0]; | |
n = [0, 0, 0, 0, 0, 0]; | |
male = [0, 0, 0, 1, 1, 1]; | |
genotypes = ["BB", "BR", "RR", "BB", "BR", "RR"]; | |
sexcenter = [pxgXscaleA(1), pxgXscaleA(4)]; | |
} | |
for (i in data.individuals) { | |
g = Math.abs(data.geno[marker][i]); | |
sx = data.sex[i]; | |
if (data.pmark[marker].chr === "X") { | |
x = sx * 2 + g - 1; | |
} else { | |
x = sx * 3 + g - 1; | |
} | |
means[x] += probe_data.pheno[i]; | |
n[x]++; | |
} | |
for (i in means) { | |
means[i] /= n[i]; | |
} | |
draw_pxgXaxis(means, genotypes, chr, sexcenter, male); | |
return svg.selectAll("circle.plotPXG").transition().duration(slowtime).attr("cx", function(d, i) { | |
g = Math.abs(data.geno[marker][i]); | |
sx = data.sex[i]; | |
if (data.pmark[marker].chr === "X") { | |
return pxgXscaleX(sx * 2 + g - 1) + jitter[i]; | |
} | |
return pxgXscaleA(sx * 3 + g - 1) + jitter[i]; | |
}).attr("fill", function(d, i) { | |
g = data.geno[marker][i]; | |
if (g < 0) { | |
return pink; | |
} | |
return darkGray; | |
}).attr("stroke", function(d, i) { | |
g = data.geno[marker][i]; | |
if (g < 0) { | |
return purple; | |
} | |
return "black"; | |
}).attr("stroke-width", function(d, i) { | |
g = data.geno[marker][i]; | |
if (g < 0) { | |
return "2"; | |
} | |
return "1"; | |
}); | |
}; | |
lastMarker = maxlod_marker; | |
markerClick[lastMarker] = 1; | |
d3.select("circle#marker_" + lastMarker).attr("opacity", 1).attr("fill", altpink).attr("stroke", purple); | |
pos = data.pmark[lastMarker].pos_cM; | |
chr = data.pmark[lastMarker].chr; | |
title = "" + lastMarker + " (chr " + chr + ", " + (onedig(pos)) + " cM)"; | |
d3.select("text#pxgtitle").text(title); | |
return plotPXG(lastMarker); | |
}; | |
chrindex = {}; | |
_ref10 = data.chrnames; | |
for (i = _s = 0, _len10 = _ref10.length; _s < _len10; i = ++_s) { | |
c = _ref10[i]; | |
chrindex[c] = i; | |
} | |
peaks = svg.append("g").attr("id", "peaks").selectAll("empty").data(data.peaks).enter().append("circle").attr("class", function(d) { | |
return "probe_" + d.probe; | |
}).attr("cx", function(d) { | |
return chrXScale[d.chr](d.pos_cM); | |
}).attr("cy", function(d) { | |
return chrYScale[data.probes[d.probe].chr](data.probes[d.probe].pos_cM); | |
}).attr("r", peakRad).attr("stroke", "none").attr("fill", function(d) { | |
if (chrindex[d.chr] % 2 === 0) { | |
return darkblue; | |
} else { | |
return darkgreen; | |
} | |
}).attr("opacity", function(d) { | |
return Zscale(d.lod); | |
}).on("mouseover", function(d) { | |
d3.selectAll("circle.probe_" + d.probe).attr("r", bigRad).attr("fill", pink).attr("stroke", darkblue).attr("stroke-width", 1).attr("opacity", 1); | |
return eqtltip.call(this, d); | |
}).on("mouseout", function(d) { | |
d3.selectAll("circle.probe_" + d.probe).attr("r", peakRad).attr("fill", function(d) { | |
if (chrindex[d.chr] % 2 === 0) { | |
return darkblue; | |
} else { | |
return darkgreen; | |
} | |
}).attr("stroke", "none").attr("opacity", function(d) { | |
return Zscale(d.lod); | |
}); | |
return d3.selectAll("#eqtltip").remove(); | |
}).on("click", function(d) { | |
return d3.json("http://www.biostat.wisc.edu/~kbroman/D3/cistrans/data/probe_data/probe" + d.probe + ".json", draw_probe); | |
}); | |
d3.json("http://www.biostat.wisc.edu/~kbroman/D3/cistrans/data/probe_data/probe517761.json", draw_probe); | |
_results = []; | |
for (j in left) { | |
_results.push(svg.append("rect").attr("x", left[j]).attr("y", top[j]).attr("height", h[j]).attr("width", w[j]).attr("class", "outerBox")); | |
} | |
return _results; | |
}; | |
d3.json("http://www.biostat.wisc.edu/~kbroman/D3/cistrans/data/insulin_eqtl.json", draw); | |
}).call(this); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<!DOCTYPE html> | |
<html lang="en"> | |
<head> | |
<meta charset="utf-8"> | |
<title>Interactive cis-trans eQTL plot</title> | |
<link rel=stylesheet type="text/css" href="cistrans.css"> | |
<script charset="utf-8" type="text/javascript" src="http://www.biostat.wisc.edu/~kbroman/D3/d3/d3.js"></script> | |
<script type="text/javascript" src="http://www.biostat.wisc.edu/~kbroman/D3/jquery/jquery.js"></script> | |
<script type="text/javascript" src="http://www.biostat.wisc.edu/~kbroman/D3/d3-tip/d3.tip.old.js"></script> | |
</head> | |
<body> | |
<h3>Interactive cis-trans eQTL plot</h3> | |
<p id="loading">[Loading...]</p> | |
<div id="geneinput" style="opacity:0;"> | |
<form name="geneinput"> | |
<input type="submit" id="submit" value="Gene symbol:" /> | |
<input id="genesymbol" type="text" /> | |
<a id="currentgenesymbol" style="opacity:0;"></a> | |
[This search box doesn't do anything yet.] | |
</form> | |
<script type="text/javascript"> | |
$("#geneinput").submit(function () { | |
submitGene(); | |
return false; | |
}); | |
function submitGene(event) { | |
console.log("Click!") | |
var value = document.getElementById("genesymbol").value; | |
console.log(value); | |
d3.selectAll("a#currentgenesymbol") | |
.text(value); | |
} | |
</script> | |
</div> | |
<div id="cistrans"></div> | |
<div id="legend" style="width:700px;margin-left:25px;opacity:0;"> | |
<p>In top-left figure, x-axis corresponds to marker location, y-axis is | |
genomic position of probes on a gene expression microarray. Each | |
plotted point is an inferred eQTL with LOD > 10; opacity corresponds | |
to LOD score, though all LOD > 25 are made fully opaque. | |
<p>Hover over a point to see probe ID and LOD score; also highlighted | |
are any other eQTL for that probe. Click on the point to see LOD | |
curves below. | |
<p>If a clicked probe is in known gene, title in lower plot is a link | |
to the Mouse Genome Informatics (MGI) site at the Jackson Lab. | |
<p>Hover over markers in LOD curve plot to view marker names; click on | |
a marker to see the phenotype-vs-genotype plot to right. In | |
geno-vs-pheno plot, hover over average to view value, and hover over | |
points to view individual IDs. | |
<p>The code is available at <a | |
href="https://github.com/kbroman/d3examples/tree/master/cistrans">github</a>, | |
but it is pretty awful; I just barely know what I'm doing. | |
</div> | |
<script type="text/javascript" src="cistrans.js"></script> | |
</body> | |
</html> |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment