UNPKG

ncats-protvista-viewer

Version:

A collection of nightingale and custom components to show details of a protein sequence

264 lines (223 loc) 8.76 kB
import {axisLeft, axisBottom, line, scaleLinear, select} from "d3"; import ProtvistaZoomable from "protvista-zoomable"; import {getPathMap} from "./pathmap"; const NUMBER_OF_TICKS = 3; class NcatsSequenceLogo extends ProtvistaZoomable { constructor() { super(); this.pathmap = getPathMap(); } connectedCallback() { super.connectedCallback(); this.setSequence(); this.addEventListener("load", e => { this.data = e.detail.payload; }); } static get observedAttributes() { return ProtvistaZoomable.observedAttributes.concat( "highlightstart", "highlightend", "sequence", "height" ); } setSequence(inSeq) { if (inSeq) { this.sequence = inSeq; } else { this.sequence = JSON.parse(this.getAttribute('sequence')); } if (this.sequence) { this.setAttribute("length", this.sequence.length); if (!super.svg) { this._createSequence(); } else { this.refresh(); } } } attributeChangedCallback(name, oldValue, newValue) { super.attributeChangedCallback(name, oldValue, newValue); if (name == 'sequence') { this.setSequence(JSON.parse(newValue)); } } get data() { return this.sequence; } set data(data) { let seq; if (typeof data == "string") seq = data; else if ("sequence" in data) seq = data.sequence; this.setSequence(JSON.parse(seq)); } _createSequence() { this.margin.left = 30; super.svg = select(this) .append("div") .attr("style", `height: ${this._height}px`) .attr("class", "") .append("svg") .attr("id", "") .attr("width", this.width) .attr("height", this._height); this.seq_bg = super.svg.append("g").attr("class", "background"); this.seq_g = super.svg .append("g") .attr("class", "sequence") .attr("transform", `translate(0,${this.standardOffset()})`); this.line_path = super.svg .append("g") .attr("class", "linePlot") .attr("transform", `translate(0,${this.standardOffset()})`) .append("path"); this.marginBlock = super.svg.append("rect") .attr("height", this._height) .attr("width", this.margin.left) .attr('fill','white'); this.x_axis_g = super.svg.append("g").attr("class", "x axis") .attr("transform", `translate(${this.margin.left},${this._height - 1})`); this.y_axis_g = super.svg.append("g").attr("class", "y axis") .attr("transform", `translate(${this.margin.left},-1)`) .attr("style", "background-color:white;"); this.trackHighlighter.appendHighlightTo(this.svg); this.refresh(); } standardOffset() { return 0.75 * this._height; } get heightFactor() { return this._height / this.maxBits; } get maxBits() { return 4.322; } get yScale() { return scaleLinear() .domain([0, this.maxBits]) .range([this.height, 0]); } refresh() { if (this.x_axis_g) { const ftWidth = this.getSingleBaseWidth(); const sequenceOpacity = ftWidth - 10; const lineOpacity = 1 - sequenceOpacity; const first = Math.round(Math.max(0, this._displaystart - 2)); const last = Math.round(Math.min(this.sequence.length, this._displayend + 1)); const bases = []; let lineData = []; try { if (sequenceOpacity > 0) { this.sequence.slice(first, last).forEach((seqObj, i) => { seqObj.forEach((aaObj, j) => { const last = ((j + 1) == seqObj.length); bases.push({ start: 1 + first + i, end: 1 + first + i, aa: aaObj.aa, bits: aaObj.bits, yOffset: last ? 0 : seqObj.slice(j + 1).map(eachAA => eachAA.bits).reduce((a, c) => a + c) }); }); }); } if (lineOpacity > 0) { lineData = this.sequence.slice(first, last).map((seqObj, i) => { const retObj = {}; const only = (seqObj.length == 1); retObj.y = only ? seqObj[0].bits : seqObj.map(eachAA => eachAA.bits).reduce((a, c) => a + c); retObj.x = i; retObj.position = 1 + first + i; return retObj; }) } } catch (e) { console.log('error: ' + JSON.stringify(e)); } this.xAxis = axisBottom(this.xScale).ticks(5); this.yAxis = axisLeft(this.yScale).tickValues([1,2,3,4]); this.x_axis_g.call(this.xAxis); this.y_axis_g.call(this.yAxis); this.bases = this.seq_g.selectAll("path.base").data(bases, d => d.start); this.bases.enter() .append("path") .attr("class", "base feature") .attr("d", d => this.pathmap.get(d.aa)) .attr("stroke", d => this.colorByChemistry(d.aa)) .attr("fill", d => this.colorByChemistry(d.aa)) .attr('transform', d => { return `translate(${this.getXFromSeqPosition(d.start)}, ${-this.standardOffset() + (this._height - (this.heightFactor * (d.bits + d.yOffset)))}) scale(${ftWidth / this._height * this._height / 100}, ${d.bits / this.maxBits * this._height / 100})`; }).call(this.bindEvents, this); this.bases.attr('transform', d => { return `translate(${this.getXFromSeqPosition(d.start)}, ${-this.standardOffset() + (this._height - (this.heightFactor * (d.bits + d.yOffset)))}) scale(${ftWidth / this._height * this._height / 100}, ${d.bits / this.maxBits * this._height / 100})`; }).call(this.bindEvents, this); this.bases.exit().remove(); this.line_path.data([lineData]) .attr("d", line() .x(d => this.getXFromSeqPosition(d.position) + (ftWidth / 2)) .y(d => (1 - d.y) * this.heightFactor + 2)) .attr("stroke", "black") .attr("fill", "none"); this.background = this.seq_bg .selectAll("rect.base_bg") .data(bases, d => d.start); this.background .enter() .append("rect") .attr("class", "base_bg feature") .attr("height", this._height) .merge(this.background) .attr("width", ftWidth) .attr("fill", d => { return Math.round(d.start) % 2 ? "#ddd" : "#eee"; }) .attr("x", d => this.getXFromSeqPosition(d.start)) .call(this.bindEvents, this); this.background.exit().remove(); this.seq_g.style("opacity", Math.min(1, sequenceOpacity)); this.line_path.style("opacity", Math.min(1, lineOpacity)); this.seq_bg.style("opacity", Math.min(1, sequenceOpacity)); this._updateHighlight(); } } getXFromSeqPosition(position) { return this.margin.left + this.xScale(position); } colorByChemistry(aa) { switch (aa) { case 'G': case 'S': case 'T': case 'Y': case 'C': return "green"; // Polar case 'Q': case 'N': return "purple"; // Neutral case 'K': case 'R': case 'H': return "blue"; // Basic case 'D': case 'E': return "red"; // Acidic case 'A': case 'V': case 'L': case 'I': case 'P': case 'W': case 'F': case 'M': return "black"; // Hydrophobic } return "papayawhip"; // unknown } _updateHighlight() { this.trackHighlighter.updateHighlight(); } } export default NcatsSequenceLogo;