Skip to content

Instantly share code, notes, and snippets.

@tgotwig
Last active December 9, 2017 21:27
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save tgotwig/2ea023d4296af7772e2797c90bb50012 to your computer and use it in GitHub Desktop.
Save tgotwig/2ea023d4296af7772e2797c90bb50012 to your computer and use it in GitHub Desktop.
Syntenyplot (+tooltip)
<!DOCTYPE html>
<meta charset="utf-8">
<svg width="960" height="500"
></svg>
<script src="https://d3js.org/d3.v4.min.js"></script>
<script src="https://unpkg.com/tippy.js@2.0.3/dist/tippy.all.min.js"></script>
<link href="https://fonts.googleapis.com/css?family=Roboto+Mono" rel="stylesheet">
<script>
;(function (global, d3) {
const OPACITY_LOW = 0.4
const OPACITY_HIGH = 1
const STROKE_COLORS = d3.scaleOrdinal(['#2196f3', '#ff9800']) // material colors by material.io
const INBOX_PADDING = 8
let STROKE_WIDTH = 3
/**
* Creates an interactive dotplot from json like this:
* { contig: <string>, rStart: <NUM>, rEnd: <NUM>, cStart: <NUM>, cEnd: <NUM>, strand: <string: + or -> }
* and renders it into selector.
*
* @param {string} data The data: [{"1.1": "1.2", "2.1": 2.2, ...}].
* @param {string} SELECTOR The seletor.
* @param {string} REF_LAB The name of x-axis-label.
* @param {string} QRY_LAB The name of y-axis-label.
*/
let drawDotPlot = function (data, REF_LAB, QRY_LAB) {
const svg = d3.select('svg')
const margin = {top: 20, right: 20, bottom: 30, left: 40}
const WIDTH = +svg.attr('width') - margin.left - margin.right
const HEIGHT = +svg.attr('height') - margin.top - margin.bottom
const xScale = d3.scaleLinear().range([120, WIDTH])
const xAxis = d3.axisBottom(xScale)
const yScale = d3.scaleLinear().range([HEIGHT, 0])
const yAxis = d3.axisLeft(yScale)
svg
.attr('class', 'dotPlot')
.attr('width', WIDTH + margin.left + margin.right)
.attr('height', HEIGHT + margin.top + margin.bottom)
.append('g')
.attr('transform', `translate(${margin.left}, ${margin.top})`)
const xMin = d3.min(data, d => d.rStart)
const xMax = d3.max(data, d => d.rEnd)
const xPadding = (xMax - xMin) / WIDTH * (STROKE_WIDTH + INBOX_PADDING)
let yMin = -1
let yMax = 0
Array.from(data, (e) => {
if (yMin === -1) {
yMin = e.cStart
yMax = e.cStart
}
yMax += e.cEnd - e.cStart
})
const yPadding = (yMax - yMin) / HEIGHT * (STROKE_WIDTH + INBOX_PADDING)
// x-axis
xScale.domain([xMin - xPadding, xMax + xPadding])
svg.append('g')
.attr('id', 'xxx')
.attr('transform', 'translate(0,' + HEIGHT + ')')
.call(xAxis)
// x-label
svg.append('text')
.attr('transform', 'translate(' + (WIDTH / 2) + ' ,' + (HEIGHT + margin.top + 16) + ')') // from 10 to 16!!!
.style('text-anchor', 'middle')
.text(`${REF_LAB}`)
// x-axis-2
svg.append('line')
.attr('x1', 0)
.attr('y1', 0.5)
.attr('x2', WIDTH)
.attr('y2', 0.5)
.style('stroke', 'black')
// y-axis
yScale.domain([yMin - yPadding, yMax + yPadding])
svg.append('g')
.attr('transform', 'translate(120)')
.call(yAxis)
// y-label
svg.append('text')
.attr('transform', 'translate(45, 280) rotate(-90)')
.style('text-ancho', 'middle')
.text(`${QRY_LAB}`)
// y-axis-2
svg.append('line')
.attr('x1', WIDTH + 0.5)
.attr('y1', 0)
.attr('x2', WIDTH + 0.5)
.attr('y2', HEIGHT)
.style('stroke', 'black')
// memory for main-strokes
let memory = -1
const reset = function () { memory = -1 }
// main-strokes
svg.selectAll('.line')
.data(data)
.enter()
.append('line')
.attr('class', 'line')
.attr('x1', d => {
return xScale(d.rStart) + STROKE_WIDTH / 2
})
.attr('y1', d => {
if (memory === -1) {
memory = d.cEnd
return yScale(d.strand === '+' ? d.cStart : d.cEnd)
}
let dif = d.cEnd - d.cStart
let out = d.strand === '+' ? memory : memory + dif
memory += dif
return yScale(out)
})
.call(reset)
.attr('x2', d => {
return xScale(d.rEnd) - STROKE_WIDTH / 2
})
.attr('y2', d => {
if (memory === -1) {
memory = d.cEnd
return yScale(d.strand === '+' ? d.cEnd : d.cStart)
}
let dif = d.cEnd - d.cStart
let out = d.strand === '+' ? memory + dif : memory
memory += dif
return yScale(out)
})
.attr('title', d => {
const tipText = `<div style="text-align: left; font-family: 'Roboto Mono', monospace;">
<b>Contig</b> ${'&nbsp;'.repeat(3)}${d.contig}<br>
<b>Length</b> ${'&nbsp;'.repeat(2)}
${d.cEnd > d.cStart ? (d.cEnd - d.cStart).toLocaleString() : (d.cStart - d.cEnd).toLocaleString()}<br>
<b>Strand</b> ${'&nbsp;'.repeat(3)}${d.strand}<br>
<b>Position</b> ${'&nbsp;'.repeat(1)}${d.rStart.toLocaleString()} - ${d.rEnd.toLocaleString()}<br>
</div>`
return tipText
})
.style('stroke', d => STROKE_COLORS(d.strand))
.style('stroke-width', STROKE_WIDTH)
.style('stroke-linecap', 'round')
.style('opacity', OPACITY_LOW)
.on('mouseover', function (element) {
this.style.opacity = OPACITY_HIGH
})
.on('mouseout', function (element) {
this.style.opacity = OPACITY_LOW
})
} // drawDotPlot(..)
let data = [
{
'contig': 'Ecoli_R37_1',
'contigLength': '2266003',
'rStart': 1,
'rEnd': 15390,
'cStart': 1067066,
'cEnd': 1082454,
'length': 15388,
'noNonIdentities': 176,
'strand': '+'
},
{
'contig': 'Ecoli_R37_1',
'contigLength': '2266003',
'rStart': 15391,
'rEnd': 20390,
'cStart': 786678,
'cEnd': 787183,
'length': 505,
'noNonIdentities': 10,
'strand': '-'
},
{
'contig': 'Ecoli_R37_1',
'contigLength': '2266003',
'rStart': 20390,
'rEnd': 30000,
'cStart': 262451,
'cEnd': 285872,
'length': 23421,
'noNonIdentities': 284,
'strand': '+'
}
]
drawDotPlot(data, 'X-Axis (reference) [nuc. pos.]', 'Y-Axis (contig)')
tippy('[title]', {
followCursor: true,
performance: true,
arrow: true,
size: 'large',
animation: 'scale',
})
}(window, window.d3))
</script>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment