Skip to content

Instantly share code, notes, and snippets.

@kbroman
Last active December 16, 2015 15:08
Show Gist options
  • Save kbroman/5453249 to your computer and use it in GitHub Desktop.
Save kbroman/5453249 to your computer and use it in GitHub Desktop.
Cis-trans plot using the old version of d3-tip.
# 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)
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;
}
// 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);
<!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