Commit 5cbb1887 authored by Raphael Müller's avatar Raphael Müller
Browse files

Merge branch 'grid' into 'master'

Grid

See merge request !2
parents d9729a35 afb3f209
.idea
.*.swp
This diff is collapsed.
This diff is collapsed.
div.tooltip {
text-align: left;
font: 12px 'Roboto Mono', monospace;
background: black;
color: white;
border: 5px;
border-radius: 5px;
padding: 5px;
pointer-events: none;
position: fixed;
}
div.tooltip:after {
bottom: 100%;
left: 50%;
border: solid transparent;
content: " ";
color: black;
position: absolute;
pointer-events: none;
border-color: rgba(255, 255, 255, 0);
border-bottom-color: #000000;
border-width: 8px;
margin-left: -8px;
}
\ No newline at end of file
<!doctype html>
<html class="no-js" lang="">
<head>
<meta charset="utf-8">
<meta http-equiv="x-ua-compatible" content="ie=edge">
<title></title>
<meta name="description" content="">
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
<script src="../lib/d3.js"></script>
<script src="../lib/vue.js"></script>
<script src="./js/viz-linear-sequence.js" defer></script>
<link href="./css/viz-linear-sequence.css" rel="stylesheet">
</head>
<body>
<div id="app">
<genome-viewer :genome-stats="genome_stats" :feature-data="feature_data"></genome-viewer>
</div>
<script src="js/axis.js"></script>
<script src="js/feature.js"></script>
<script src="js/lane.js"></script>
<script src="js/genome_viewer.js"></script>
<script src="js/app.js"> </script>
</body>
</html>
var app = new Vue({
el: '#app',
data: {
genome_stats: {
id: "my-genome",
start: 1706479,
end: 1718084,
tooltip: "name: {name}</br>description: {description}</br>start: {start}</br>end: {end}"
},
feature_data: [
{ name: "Gene 1",
description: "GMP synthase",
start: 1706479,
end: 1708024,
strand: "+",
type: "gene",
color: '#454aff',
color_border: '#000000'
},
{ name: "Gene 2",
description: "glyoxalase",
start: 1708314,
end: 1708671,
strand: "+",
type: "gene",
color: '#454aff',
color_border: '#000000'
},
{ name: "Gene 3",
description: "ABC transporter ATP-binding protein LlsG",
start: 1709429,
end: 1710317,
strand: '-',
type: "gene",
color: '#454aff',
color_border: '#000000'
},
{ name: "Gene 4",
description: "ABC transporter permease protein LlsH",
start: 1710316,
end: 1711066,
strand: '+',
type: "gene",
color: '#454aff',
color_border: '#000000'
},
{ name: "Gene 5",
description: "LlsX",
start: 1711106,
end: 1711424,
strand: '+',
type: "gene",
color: '#454aff',
color_border: '#000000'
},
{ name: "Gene 6",
description: "SagB family dehydrogenase LlsB",
start: 1711420,
end: 1712296,
strand: '+',
type: "gene",
color: '#454aff',
color_border: '#000000'
},
{ name: "Gene 7",
description: "LlsY",
start: 1712303,
end: 1713221,
strand: '-',
type: "gene",
color: '#454aff',
color_border: '#000000'
},
{ name: "Gene 8",
description: "streptolysin-associated protein LlsD",
start: 1713213,
end: 1714527,
strand: '+',
type: "gene",
color: '#454aff',
color_border: '#000000'
},
{ name: "Gene 9",
description: "hypothetical protein",
start: 1715351,
end: 1716125,
strand: '+',
type: "gene",
color: '#454aff',
color_border: '#000000'
},
{ name: "Gene 10",
description: "glyoxalase family protein, N-terminal part",
start: 1716189,
end: 1716321,
strand: '+',
type: "gene",
color_border: '#000000'
},
{ name: "Gene 11",
description: "hypothetical protein",
start: 1717235,
end: 1718084,
strand: '-',
type: "rRNA"
}
]
}
})
/***********************************
* TODO:
* add logic to print human readable tick sizes, i.e.
* 10 kb instead of 10,000 bp
*/
Vue.component( "axis", {
template:
`
<svg :x="x" :y="y" :width="width" :height="height" ><!--:viewBox="xOffset +' '+ vOffset +' '+ width + xOffset +' '+ vOffset + height"><!-- -->
<line x1="0" :y1="2*vOffset" :x2="width" :y2="2*vOffset" style="stroke:rgb(0,0,0);stroke-width:1" />
<line x1="0" :y1="2*vOffset" x2="0" :y2="vOffset" stroke="black" />
<line :x1="width" :y1="2*vOffset" :x2="width" :y2="vOffset" stroke="black" />
<line v-for="tick in ticks" :x1="tick.panelPos" :y1="2*vOffset" :x2="tick.panelPos" :y2="vOffset+2" stroke="black"/>
<text v-for="tick in ticks" :style="'font: '+vOffset+'px roboto;'"class="small" :x="tick.panelPos" :y="vOffset" text-anchor="middle" fill="black">{{ tick.genomePos }}</text>
</svg>
<!-- -->
`,
props: {
x: Number,
y: Number,
start: Number,
stop: Number,
genomeSize: Number,
width: Number,
height: Number,
xOffset: Number,
vOffset: Number,
},
methods: {
genomeToPixel( pos ) {
return (this.width*(pos-this.start))/(this.length)
}
},
computed:{
power(){
return Math.floor(Math.log10(this.length))
},
length(){
return (this.stop - this.start)
},
genomeSectionSize(){
return (this.start > this.stop) ? (this.genomeSize-this.start + this.end) : Math.abs(this.stop-this.start) // genome section in bp
},
tickSize(){
return Math.pow(10, this.power-1)
//return Math.pow( 10, Math.round( Math.log10( this.genomeSectionSize / this.noTicks ) ) ) // axis tick size in log 10 bp
},
noTicks(){
return Math.floor(this.length/this.tickSize)+2
},
ticks(){
const firstTickPos = Math.round(this.start/this.tickSize)*this.tickSize//Math.pow( 10, Math.floor( Math.log10( this.start ) ) )
let ticks = new Array(this.noTicks)
for(i=0; i<ticks.length; i+=1){
ticks[i] = {}
ticks[i].genomePos = firstTickPos + i*this.tickSize
ticks[i].panelPos = this.genomeToPixel(ticks[i].genomePos)
}
return ticks
},
},
data: function(){
return {
//noTicks: 10
//ticks: []
}
}
} )
Vue.component("feature",{
template:
`
<svg :height="svgHeight" :width="svgWidth" :x="svgX" :y="svgY">
<polygon :points="arrow_coordinates" :style=featureStyle @click="clickEvent" />
</svg>
`,
props: ['svgWidth', 'svgHeight', 'svgX', 'svgY', 'color', 'borderColor', 'featureId'],
computed: {
featureStyle () {
return {
'fill': this.color,
'stroke': this.borderColor,
'stroke-width': 1
}
},
arrow_coordinates () {
let points = [
'0, ' + this.svgHeight * 0.25,
'0, ' + this.svgHeight * 0.75,
this.svgWidth * 0.7 + ', ' + this.svgHeight * 0.75,
this.svgWidth * 0.7 + ', ' + this.svgHeight * 0.95,
this.svgWidth + ', ' + this.svgHeight * 0.5,
this.svgWidth * 0.7 + ', ' + this.svgHeight * 0.05,
this.svgWidth * 0.7 + ', ' + this.svgHeight * 0.25
]
return points.join(' ')
},
},
methods: {
clickEvent (evt) {
this.$emit('click', this.featureId)
}
},
data: function(){
return {
todo: "todo"
}
}
})
Vue.component("genome-viewer",{
template: `
<div>
<!--<div class="container-lin-dna">
<div :id="genomeStats.id" class="lin-dna" :data-cluster-start="genomeStats.start" :data-cluster-end="genomeStats.end" style="width: 100%; height: 50px;">
<div v-for="feature in featureData" :key="feature.name" class="region-to-display" :data-region-name="feature.name" :data-region-description="feature.description" :data-region-start="feature.start" :data-region-end="feature.end" :data-region-strand="feature.strand" :data-region-type="feature.type">
</div>
</div>
</div>
-->
<svg id="genome-vuer" ref="genomeVuer" height='100%' width='100%'>
<!-- -->
<axis :x="to_w(0)" :y="to_h(0)" :xOffset="10" :vOffset="10" :height="one_height" :width="width" :start="axisStart" :stop="axisStop" :genomeSize="length*100">
</axis>
<!-- -->
<lane v-for="lane in lanes" :key="lane.id" :x="to_w(0)" :y="to_h(lane)" :height="2*one_height" :width="to_w(100)">
</lane>
<feature v-for="(feature, index) in featureData" :key="feature.name" :feature-id="feature.name" :svg-width="genomeToPixel(feature.end)-genomeToPixel(feature.start)" :svg-height="one_height" :svg-x="genomeToPixel(feature.start)" :svg-y="to_h(lane(feature.strand))+ one_height/2" :color="getColor(feature)" :border-color="(feature.color_border)?feature.color_border : defaultBorderColor" @click="featureClick">
</feature>
<!-- -->
</svg>
</div>`,
props: {
genomeStats: {
validator (value) {
for (let property of ['id', 'start', 'end']) {
if (!value.hasOwnProperty(property)) {
return false
}
}
return true
}
},
featureData: {
validator (value) {
for (let feature of value) {
for (let property of ['name', 'description', 'start', 'end', 'strand', 'type']) {
if (!feature.hasOwnProperty(property)) {
return false
}
}
return true
}
}
},
defaultColor: {
default: "#000000"
},
defaultBorderColor: {
default: "#000000"
},
colorMap: {
default: function(){
return {
'region':'#bdbdbd', // greyLighten
'gene':'#757575', // greyDarken
'cds': '#000000', // black
'rRNA': '#795548', // brown
'tRNA': '#4caf50', // green
'ncRNA': '#ff9800' // orange
}
}
},
},
computed:{
stops(){
return this.featureData.map(f => f.end)
},
starts(){
return this.featureData.map(f => f.start)
},
genomeStart(){
return Math.min(...this.starts)
},
genomeStop(){
return Math.max(...this.stops)
},
axisStart(){
return this.genomeStart - this.tickSize
},
axisStop(){
return this.genomeStop + this.tickSize
},
genome_size(){},
length(){
if(this.genomeStats.start < this.genomeStats.end){
return this.genomeStop - this.genomeStart
}else{
// TODO: if genome is circular
}
},
one_height(){
return this.height/(1 + 2 * this.lanes.length)
},
tickSize(){
return Math.pow(10,Math.floor(Math.log10(this.length))-1)
},
},
mounted() {
var svg_el = this.$refs.genomeVuer.getBoundingClientRect()
this.width = svg_el.width
this.height = svg_el.height
//this.height / length([axis and lanes * 2])
// lanes are twize as big as the axis
// so height / (1 + no_of_lanes * 2)
//var one_height = this.svg_size[1]/(1 + 2 * this.lanes.length)
//let svg_el = this.$refs.genomeVuer.getBoundingClientRect()
//console.log(svg_el)
////console.log(svg_el)
//this.width = svg_el.width
//this.height = svg_el.height
////return [svg_el.clientWidth, svg_el.clientHeight]
//this.one_height = this.height/(1 + 2 * this.lanes.length)
},
methods:{
getColor(feature){
if(feature.hasOwnProperty("color")){
return feature.color
}else{
if(feature.hasOwnProperty("type")){
if(this.colorMap.hasOwnProperty(feature.type)){
return this.colorMap[feature.type]
}else{
return this.defaultColor
}
}else {
return this.defaultColor
}
}
},
lane(str){
return (str=="+")? 1 : -1
},
to_h(param){
//param is a value between -n and +n, where n is the number of lanes divided by 2
//if param is 0 => axis
//if param < 0 => lane on top
//if param > 0 => lane on bottom
let zero = this.lanes.length * this.one_height
if(param < 0){
return zero + 2 * this.one_height * (param)
}else if(param > 0){
return zero + this.one_height * (param)
}
return zero
},
to_w(param){
//param is a value between 0 and 100, where 100 is the full length of the svg
return (this.width*param)/100
},
genomeToPixel(pos){
return (this.width*(pos - (this.axisStart)))/(this.length+2*this.tickSize)
},
featureClick (featureId) {
console.log(featureId)
},
fill_in_tooltip_template (feature, template) {
let tooltip = template
//TODO: Iterate over custom properties
for ( let property of ['name', 'description', 'start', 'end', 'strand', 'type'] ){
tooltip = tooltip.replace( "{" + property + "}", feature[property] )
}
return( tooltip )
},
get_tooltip (e){
let element = e.currentTarget
let id = element.getAttribute( "name" )
let feature = null
for(let entry of this.featureData){
if(entry.name === id){
feature = entry
}
}
console.log( this.fill_in_tooltip_template( feature, this.genomeStats.tooltip ) )
},
},
data: function(){
return {
lanes: [-1, 1],
width: 0,
height: 0
}
},
})
Vue.component("lane",{
template:
`
<svg :height="height" :x="x" :y="y">
<line :x1="x+10" :y1="height/2" :x2="width" :y2="height/2" stroke="#39FF14" stroke-width="2" stroke-dasharray="20, 5" />
</svg>
`,
props: {
x: {
validator (value) {
return true
}
},
y: {
validator (value) {
return true
}
},
height: {
validator (value) {
return true
}
},
width: {
validator (value) {
return true
}
},
},
methods: {
my_method(){
console.log(this.todo)
console.log(this.Todo)
},
},
computed: {
// y() {
// return y;
// },
// x() {
// return lib.map(0, this.o.xMin, this.o.xMax, 0, this.svg.w);
// },
// tickXs() {
// const ticks = lib.range(this.o.xMin, this.o.xMax, 10)
// return ticks.map(tick =>
// lib.map(tick, this.o.xMin, this.o.xMax, 0, this.svg.w)
// );
// },
},
data: function(){
return {
todo: "todo"
}
},
})
(function (d3) {
var div = d3.select( "body" )
.append( "div" )
.attr( "class", "tooltip" )
.style( "opacity", 0 );
Array.from( document.getElementsByClassName( "container-lin-dna" ) ).forEach( (e) => {
e.style.padding = '4rem 5rem';
} );
Array.from( document.getElementsByClassName( "lin-dna" ) ).forEach( (e) => {
const svgHeight = e.clientHeight;
const svgWidth = e.clientWidth;
const padding = 40;
//create axis
const xScale = d3.scaleLinear()
.domain( [ e.getAttribute( "data-cluster-start"), e.getAttribute( "data-cluster-end" ) ] )
.range( [ 0, svgWidth ] );
const xAxis = d3.axisBottom( xScale );
const svgContainer = d3.select(e).append( "svg" )
.attr( 'width', svgWidth + padding * 2 )
.attr( 'height', svgHeight + padding * 2 )
.append( "g" )
.attr( 'transform', 'translate(' + 0 + ',' + padding + ')' );
svgContainer.append( "g" )
.attr( 'transform', 'translate(' + 0 + ',' + 5 + ')' )
.call( xAxis );
//create visualization of DNA-Elements
const lineGenerator = d3.line();
svgContainer.selectAll('.arrow')
.data( collectDNARegionData( e ) )
.enter()
.append( 'path' )
.attr( 'class', 'arrow' )
.attr( 'data-toggle', 'tooltip' )
.attr( 'data-placement', 'bottom' )
.attr('d', d => { return lineGenerator( returnRegionAreaPoints( d, xScale ) ) } )
.style( 'fill', d => { return returnSectionColor( d.type ) } )
.style( 'opacity', 0.6 )
.on( "mouseover", function(d) {
this.style.opacity = 1;
div.style( "opacity", .9 )
.style( "width", "220px")
.style( "left", ( this.getBoundingClientRect().x + ( this.getBoundingClientRect().width / 2 ) - 110 ) + "px" )
.style( "top", ( this.getBoundingClientRect().bottom + 10 ) + "px" );
div.html( d => returnStringForPopup( this.__data__ ) )
})
.on( "mouseout", function(d) {
this.style.opacity = 0.6;