UNPKG

bio-vis-expression-bar

Version:

Simple barchart to show expression levels across experiments

984 lines (785 loc) 34 kB
var science = require('science'); var colorbrewer = require('colorbrewer'); var d3 = require('d3'); require('string.prototype.startswith'); var exts = require('./d3Extensions.js'); var $ = require("jquery"); // Default Ternary Plot Options var opt = { width:($(window).width() * 0.6), height: $(window).height(), side: ($(window).height() * 0.5), margin: {top:100,left:50,bottom:50,right:50}, axis_labels:['A','B','C'], axis_ticks:[0,20,40,60,80,100], tickLabelMargin:10, axisLabelMargin:40, ternColors: {left: "#579D1C", right: "#FF950E", bottom: "#4B1F6F"}, expressionBiasUnit: ['tpm'] }; var TernaryPlot = function (parent) { this.parent = parent; this.opt = opt; this.selector = parent.chartSVGid; }; // DON'T NEED THIS TernaryPlot.prototype.renderScales = function(i){ }; // RENDERING THE STRUCTURE OF THE TERNARY PLOT TernaryPlot.prototype.renderGlobalScale = function(){ // Initialising local variables var barHeight = this.parent.opt.barHeight; var labelWidth = this.parent.opt.labelWidth; var plotContainerId = this.parent.plotContainer; var offset = this.parent.opt.headerOffset; var data = this.parent.data; var self = this; // Saving this object in self for property method calling withing functions var optionsHeight = $(`#bar_expression_viewer_options`).height(); opt.width = $(document).width() - labelWidth; opt.height = window.innerHeight; opt.side = opt.width; // Remove footer $(`#${this.parent.chartSVGidFoot}`).css('display', 'none'); // Fix the position of the div conatainer of the ternary plot plotContainer = d3.select('#' + this.selector); // Set the svg height and width svg = plotContainer.append('g') .attr('id', 'ternaryPlotSVG') .attr("width", $(document).width()) .attr("height", $('#' + this.selector).height()) .attr(`transform`, `translate(${labelWidth},${-20})`) .style('font-family', this.parent.opt.fontFamily); if(typeof data.expression_bias !== 'undefined'){ this._drawBlockDivisions(); } // Adding a group for the ternary plot structure ternaryPlotGroup = svg.append('g') .attr('id', 'ternaryPlotGroup'); // Add a sub group for the axes axes = ternaryPlotGroup.append('g'); // Deciding the sizing of the ternary plot this._calculateplotSize(); // Setting the position of the corners corners = [ [opt.margin.left, h + opt.margin.top], // a [ w + opt.margin.left, h + opt.margin.top], //b [(w/2) + opt.margin.left, opt.margin.top] ] //c // Loading the corner titles if(data.tern_order){ opt.axis_labels = data.tern_order; } // Render corner labels this._drawCornerLabels(); // Render axis & axis titles opt.axis_ticks.forEach(function(v) { // Getting the 4 coordinates to draw the lines var coordsArr = self._getFourCoords(v); // Draw the axis self._drawAxis(coordsArr[0], coordsArr[1], opt.ternColors.left); self._drawAxis(coordsArr[1], coordsArr[2], opt.ternColors.right); self._drawAxis(coordsArr[2], coordsArr[3], opt.ternColors.bottom); // Tick labels self._drawTicks(coordsArr[0], 60, 'end', -opt.tickLabelMargin, v, opt.ternColors.left); self._drawTicks(coordsArr[1], -60, 'end', -opt.tickLabelMargin, 100-v, opt.ternColors.right); self._drawTicks(coordsArr[2], 0, 'start', opt.tickLabelMargin, v, opt.ternColors.bottom); }); // Render Arrows var rightArrowDistance = w/2 + opt.margin.right/2; var leftArrowDistance = w/2 + opt.margin.left/2; var bottomArrowDistance = h/2 + opt.margin.bottom; this._drawLeftArrows(30, 30, opt.ternColors.left, leftArrowDistance); this._drawRightArrows(30, 30, opt.ternColors.bottom, rightArrowDistance); this._drawBottomArrows(90, 30, opt.ternColors.right, bottomArrowDistance); // Adjust Height this._adustTernaryHeight(); this._responseToScroll(); }; // DON'T NEED THIS TernaryPlot.prototype.calculateBarWidth = function(){ }; // DON'T NEED THIS TernaryPlot.prototype.rangeX = function(){ } // DON'T NEED THIS TernaryPlot.prototype.ScaleRangeX = function(){ }; // PREPARING DATA FOR DISPLAY TernaryPlot.prototype.renderGeneBar = function(i){ // Only run this function once if(i != 0){ return; } // Initialising variables var data = this.parent.data.renderedData; var ternOrder = this.parent.data.tern_order; var showExpBias = this.opt.expressionBiasUnit.indexOf(this.parent.opt.renderProperty) != -1; var tern = this.parent.data.tern; var self = this; var dataMap = new Map(); // Load rendered data into a map/hash to its corresponding tern/key ternOrder.forEach(function(ternOrderElement){ for(var i=0; i<data.length; i++){ for (var key in tern){ if (data[i][0].gene === tern[key] && key === ternOrderElement){ dataMap.set(ternOrderElement, data[i]); } } } }); // Creating hash labels for the circles // Loading all condition text into an array var allConditionsTextArr = []; var allConditionsTextNode = d3.select(`#bar_expression_viewer_chart_svg g`).selectAll('text').nodes(); for(var i=0; i<allConditionsTextNode.length; i++){ allConditionsTextArr.push(allConditionsTextNode[i].innerHTML); } // Creating hashes from the text and repopulating the array var allConditionsHashArr = []; allConditionsTextArr.forEach(function(d){ allConditionsHashArr.push( 'circle' + self._generateHash(d)); // IMPORTANT: had to add a prefix (circle) to the hash to make it selectable by d3 }); // Adding the hash ids to the conditions for linking them to the circles d3.select(`#bar_expression_viewer_chart_svg g`).selectAll('text').attr('id', function(d, i){ return allConditionsHashArr[i]; }); // Creating an array of maps that hold all the data needed for visualisation var mapArray = []; for(var i=0; i<dataMap.values().next().value.length; i++){ // Loop for the number of condition titles var mapElement = new Map(); var coordArguments = []; var sumOfValues = 0; ternOrder.forEach(function(ternOrderElement, index){ // Go through the terns in order and set their value and factors and sum their values coordArguments.push(dataMap.get(ternOrderElement)[i].value); mapElement.set(ternOrderElement, dataMap.get(ternOrderElement)[i].value); mapElement.set(`expval${ternOrderElement}`, dataMap.get(ternOrderElement)[i].value); mapElement.set('factors', dataMap.get(ternOrderElement)[i].factors); sumOfValues += dataMap.get(ternOrderElement)[i].value; if(index===2){ // If this is the last tern (Calculate the contribution percentage) ternOrder.forEach(function(ternOrderElementInnerLoop, indexInnerLoop){ var ternValue = mapElement.get(ternOrderElementInnerLoop); mapElement.set(ternOrderElementInnerLoop, (ternValue / sumOfValues) * 100); }); } }); var circleCoords = self._coord(coordArguments); mapElement.set('cx', circleCoords[0]); mapElement.set('cy', circleCoords[1]); mapElement.set('id', allConditionsHashArr[i]); if(sumOfValues !== 0){ // Ignore if there are no data for a certain condition (avoid rendering the circle) mapArray.push(mapElement); } } var centroid = this._calculateCentroid(mapArray); var expressionBias = this._calculateExpressionBias(mapArray, centroid); if (this.parent.opt.renderProperty === "tpm"){ mapArray = this._removeTPMDataUnderThreshold(mapArray); } showExpBias ? this._indicateExpressionBias(expressionBias) : $('#expression-bias').hide(); mapArray.unshift(centroid); // Load data onto the plot this._loadData(mapArray); }; // FOR DYNAMIC REASONS TernaryPlot.prototype.refreshBar = function(gene, i){ }; // DON'T NEED THIS TernaryPlot.prototype.refreshScale = function(){ }; TernaryPlot.prototype.showHighithRow = function(){ return 0; }; TernaryPlot.prototype.hideHidelightRow = function(){ var self = this; if(typeof self.parent.selectionBox !== 'undefined'){ self.parent.selectionBox .attr('visibility', 'hidden'); self.parent.selectionBoxGene.attr('visibility', 'hidden'); self.parent.selectionBoxTitles.attr('display', 'none'); } } // Private functions --------------------------------------------------------------------------------------------- TernaryPlot.prototype._lineAttributes = function(p1, p2){ return { x1:p1[0], y1:p1[1], x2:p2[0], y2:p2[1] }; } // Array in the parameter must contain the data in the form: A, D, B TernaryPlot.prototype._coord = function(arr){ var a = arr[0], d=arr[1], b=arr[2]; var sum, pos = [0,0]; sum = a + d + b; if(sum !== 0) { a /= sum; d /= sum; b /= sum; pos[0] = corners[0][0] * a + corners[1][0] * d + corners[2][0] * b; pos[1] = corners[0][1] * a + corners[1][1] * d + corners[2][1] * b; } return pos; } TernaryPlot.prototype._getFourCoords = function(tick){ var coordsArr = [] coordsArr.push(this._coord( [tick, 0, 100-tick] )); coordsArr.push(this._coord( [tick, 100-tick, 0] )); coordsArr.push(this._coord( [0, 100-tick, tick] )); coordsArr.push(this._coord( [100-tick, 0, tick] )); return coordsArr; } TernaryPlot.prototype._scale = function(){ return [p[0] * factor, p[1] * factor]; } // Rendering the circles onto the plot TernaryPlot.prototype._loadData = function(data){ //bind by is the dataset property used as an id for the join var self = this; var colorFactor = this.parent.opt.colorFactor; var colors = null; var ternOrder = this.parent.data.tern_order; var tooltip_order = this.parent.data.tooltip_order; var plotContainerId = this.parent.plotContainer; // Getting the color & color factor if(! this.parent.isFactorPresent(colorFactor)){ colorFactor = this.parent.getDefaultColour(); } if(colorFactor != 'renderGroup'){ colors = this.parent.factorColors[colorFactor]; } // Rendering the circles var circles = ternaryPlotGroup.selectAll("circle") .data( data ); circles.exit().remove(); circles .enter() .append("circle") .attr("r", 6) .attr("cx", w/2) .attr("cy", h/2) .merge(circles) .transition() .attr("cx", function (dataElements) { return dataElements.get('cx'); }) .attr("cy", function (dataElements) { return dataElements.get('cy'); }) .attr('id', function (dataElements) { return dataElements.get('id'); }) .attr('display', function(dataElements){ if(dataElements.get('id') === 'centroid'){ return 'none'; } else { return 'block'; } }) .style("fill", function(dataElements, i){ var ret = "red" if(colorFactor != 'renderGroup' && dataElements.get('id') !== 'centroid'){ ret=colors[dataElements.get('factors')[colorFactor]]; } return ret; }) .style("stroke", "#222222") .style("stroke-width", 2); circles.on('mouseenter', function(){ // This variable is used to determine the scroll amount to make the tooltip popup move initialScrollValue = $(`#${plotContainerId}`).scrollTop(); var circle = d3.select(this); var toolTipText = ""; var circleId = circle.attr('id'); d3.select(`text#${circle.attr('id')}`).style('fill', 'red'); self._indicateCircle(toolTipText, circle); }); circles.on('mouseleave', function(){ var circle = d3.select(this); d3.select(`text#${circle.attr('id')}`).style('fill', 'black'); self._setDefaultStateCircle(circle); }); // If a condition text has been hovered over $(`#conditions text`).on('mouseenter', function(){ try{ self.parent.selectionBox.attr('visibility', 'visible'); self.parent.selectionBox.attr('width', self.parent.opt.labelWidth); var conditionHash = 'circle' + self._generateHash(this.innerHTML); // IMPORTANT: had to add a prefix (circle) to the hash to make it selectable by d3 var thisCircle = d3.select("circle#"+conditionHash); var toolTipText = ""; $("#ternaryPlotGroup").get(0).appendChild(thisCircle.node()); self._indicateCircle(toolTipText, thisCircle); } catch(err){ console.warn(`${this.innerHTML} has no value`); } }); // If a condition text has been left $(`#conditions text`).on('mouseleave', function(){ self.parent.selectionBox.attr('visibility', 'hidden'); var conditionHash = 'circle' + self._generateHash(this.innerHTML); // IMPORTANT: had to add a prefix (circle) to the hash to make it selectable by d3 var thisCircle = d3.select("circle#"+conditionHash); self._setDefaultStateCircle(thisCircle); }); return this; } // Form: https://stackoverflow.com/questions/7616461/generate-a-hash-from-string-in-javascript-jquery TernaryPlot.prototype._generateHash = function(str){ var hash = 0, i, chr; if (str.length === 0) return hash; for (i = 0; i < str.length; i++) { chr = str.charCodeAt(i); hash = ((hash << 5) - hash) + chr; hash |= 0; // Convert to 32bit integer } return hash; } TernaryPlot.prototype._drawAxis = function(x, y, color){ var allLineAttributes = this._lineAttributes(x, y); axes.append("line") .attr('x1', allLineAttributes.x1) .attr('x2', allLineAttributes.x2) .attr('y1', allLineAttributes.y1) .attr('y2', allLineAttributes.y2) .style('stroke', color) .style('stroke-width', 0.5); } TernaryPlot.prototype._drawTicks = function(coord, rotate, anchor, margin, tickText, color){ axes.append('g') .attr('transform',function(d){ return 'translate(' + coord[0] + ',' + coord[1] + ')' }) .append("text") .attr('transform',`rotate(${rotate})`) .attr('text-anchor',anchor) .attr('x',margin) .text(tickText) .style('font-weight', 'lighter') .style('font-size', '1rem') .style('fill', color); } TernaryPlot.prototype._drawBlockDivisions = function(){ var data = this.parent.data; var chartHeaderId = this.parent.chartSVGidHead; var labelWidth = this.parent.opt.labelWidth; var headerOffset = this.parent.opt.headerOffset; var blockContainer = d3.select(`#${chartHeaderId}`).append('g').attr('id', 'expression-bias'); var blockSize = 25; var numberOfRows = Object.keys(data.expression_bias).length; var self = this; blockContainer.append('text').text('Homoeolog Expression Bias').attr('x', labelWidth).attr('y', headerOffset - (numberOfRows * 40)); for (var j = 0; j < numberOfRows; j++) { // Append the expression bias label blockContainer.append('text') .text(Object.keys(data.expression_bias)[j]) .attr('text-anchor', 'end') .attr('x', labelWidth) .attr('y', (headerOffset - (blockSize*2) - 8)+(j*blockSize)); var blockDiv = blockContainer.append('g').attr('id', `block-group-${j}`); var numOfBlocks = Object.values(data.expression_bias)[j].length; for (var i = 0; i < numOfBlocks; i++) { blockDiv.append('rect') .attr('width', blockSize) .attr('height', blockSize) .style('stroke-width', 2) .style('stroke', 'black') .style('fill', 'white') .attr('x', (i*blockSize)+labelWidth + 5) .attr('y', (headerOffset - (blockSize*3))+(j*blockSize)); } // Add the expression bias label if(j === (Object.keys(data.expression_bias).length-1)){ blockDiv.append('text') .style('font-size', 12) .text('Stable') .attr('text-anchor', 'middle') .attr('x', labelWidth) .attr('y', headerOffset - blockSize + 20); blockDiv.append('text') .style('font-size', 12) .text('Dynamic') .attr('text-anchor', 'middle') .attr('x', (blockSize * numOfBlocks) + labelWidth) .attr('y', headerOffset - blockSize + 20); } } this._renderExpBiasTooltip(); } TernaryPlot.prototype._getDegree = function(angle){ return (angle/180) * Math.PI; } TernaryPlot.prototype._drawRightArrows = function(arrowPositionAngle, bladePositionAngle, color, moveArrow){ var data = this.parent.data; var middleTick = opt.axis_ticks[(opt.axis_ticks.length - 1)] / 2 var coordsArr = this._getFourCoords(middleTick); var arrowPosX = moveArrow * Math.cos(this._getDegree(arrowPositionAngle)), arrowPosY = -moveArrow * Math.sin(this._getDegree(arrowPositionAngle)); var bladePosX = 10 * Math.cos(this._getDegree(bladePositionAngle)), bladePosY = 10 * Math.sin(this._getDegree(bladePositionAngle)); // Adding arrow base var allLineAttributes = this._lineAttributes(coordsArr[0], coordsArr[1]); var arrowGroup = axes.append('g').attr('transform', `translate(${arrowPosX}, ${arrowPosY})`); arrowGroup.append("line") .attr('x1', allLineAttributes.x1) .attr('x2', allLineAttributes.x2) .attr('y1', allLineAttributes.y1) .attr('y2', allLineAttributes.y2) .style('stroke', color); // Adding one blade arrowGroup.append('line') .attr('x1', allLineAttributes.x1) .attr('x2', allLineAttributes.x1 + bladePosX) .attr('y1', allLineAttributes.y1) .attr('y2', allLineAttributes.y1 + bladePosY) .style('stroke', color); // Adding one blade bladePosX = 10 * Math.cos(this._getDegree(bladePositionAngle+60)); bladePosY = 10 * Math.sin(this._getDegree(bladePositionAngle+60)); arrowGroup.append('line') .attr('x1', allLineAttributes.x1) .attr('x2', allLineAttributes.x1 + bladePosX) .attr('y1', allLineAttributes.y1) .attr('y2', allLineAttributes.y1 + bladePosY) .style('stroke', color); // Adding the gene title on top of the arrow arrowGroup.append('text') .text(data.tern[data.tern_order[2]]) .attr('x', allLineAttributes.x1) .attr('y', allLineAttributes.y1 - 20) .attr('transform', `rotate(${60}, ${allLineAttributes.x1}, ${allLineAttributes.y1})`) .style('fill', color); } TernaryPlot.prototype._drawLeftArrows = function(arrowPositionAngle, bladePositionAngle, color, moveArrow){ var data = this.parent.data; var middleTick = opt.axis_ticks[(opt.axis_ticks.length - 1)] / 2 var coordsArr = this._getFourCoords(middleTick); var arrowPosX = -moveArrow * Math.cos(this._getDegree(arrowPositionAngle)), arrowPosY = -moveArrow * Math.sin(this._getDegree(arrowPositionAngle)); var bladePosX = 10 * Math.cos(this._getDegree(bladePositionAngle)), bladePosY = 10 * Math.sin(this._getDegree(bladePositionAngle)); // Adding arrow base var allLineAttributes = this._lineAttributes(coordsArr[1], coordsArr[2]); var arrowGroup = axes.append('g').attr('transform', `translate(${arrowPosX}, ${arrowPosY})`); arrowGroup.append("line") .attr('x1', allLineAttributes.x1) .attr('x2', allLineAttributes.x2) .attr('y1', allLineAttributes.y1) .attr('y2', allLineAttributes.y2) .style('stroke', color); // Adding one blade arrowGroup.append('line') .attr('x1', allLineAttributes.x1) .attr('x2', allLineAttributes.x1 + bladePosX) .attr('y1', allLineAttributes.y1) .attr('y2', allLineAttributes.y1 - bladePosY) .style('stroke', color); // Adding one blade bladePosX = 10 * Math.cos(this._getDegree(bladePositionAngle+60)); bladePosY = 10 * Math.sin(this._getDegree(bladePositionAngle+60)); arrowGroup.append('line') .attr('x1', allLineAttributes.x1) .attr('x2', allLineAttributes.x1 + bladePosX) .attr('y1', allLineAttributes.y1) .attr('y2', allLineAttributes.y1 - bladePosY) .style('stroke', color); // Adding the gene title on top of the arrow arrowGroup.append('text') .text(data.tern[data.tern_order[0]]) .attr('x', allLineAttributes.x1) .attr('y', allLineAttributes.y1 - 20) .attr('transform', `rotate(${-60}, ${allLineAttributes.x1}, ${allLineAttributes.y1})`) .style('fill', color); } TernaryPlot.prototype._drawBottomArrows = function(arrowPositionAngle, bladePositionAngle, color, moveArrow){ var data = this.parent.data; var middleTick = opt.axis_ticks[(opt.axis_ticks.length - 1)] / 2 var coordsArr = this._getFourCoords(middleTick); var arrowPosX = moveArrow * Math.cos(this._getDegree(arrowPositionAngle)), arrowPosY = moveArrow * Math.sin(this._getDegree(arrowPositionAngle)); var bladePosX = 10 * Math.cos(this._getDegree(bladePositionAngle)), bladePosY = 10 * Math.sin(this._getDegree(bladePositionAngle)); // Adding arrow base var allLineAttributes = this._lineAttributes(coordsArr[2], coordsArr[3]); var arrowGroup = axes.append('g').attr('transform', `translate(${arrowPosX}, ${arrowPosY})`); arrowGroup.append("line") .attr('x1', allLineAttributes.x1) .attr('x2', allLineAttributes.x2) .attr('y1', allLineAttributes.y1) .attr('y2', allLineAttributes.y2) .style('stroke', color); // Adding one blade arrowGroup.append('line') .attr('x1', allLineAttributes.x1) .attr('x2', allLineAttributes.x1 - bladePosX) .attr('y1', allLineAttributes.y1) .attr('y2', allLineAttributes.y1 + bladePosY) .style('stroke', color); // Adding one blade arrowGroup.append('line') .attr('x1', allLineAttributes.x1) .attr('x2', allLineAttributes.x1 - bladePosX) .attr('y1', allLineAttributes.y1) .attr('y2', allLineAttributes.y1 - bladePosY) .style('stroke', color); // Adding the gene title on top of the arrow arrowGroup.append('text') .text(data.tern[data.tern_order[1]]) .attr('x', allLineAttributes.x2) .attr('y', allLineAttributes.y2 + 20) .style('fill', color); } TernaryPlot.prototype._drawCornerLabels = function(){ console.log(opt); axes.selectAll('.axis-title') .data(opt.axis_labels) .enter() .append('g') .attr('transform',function(d,i){ return 'translate('+corners[i][0]+','+corners[i][1]+')'; }) .style('font-size', '1.3rem') .append('text') .text(function(d){ return d; }) .style('fill', function(d, i){ if(i===0) {return opt.ternColors.left;} if(i===1) {return opt.ternColors.right;} if(i===2) {return opt.ternColors.bottom;} }) .attr('text-anchor', function(d,i){ if(i===0) return 'end'; if(i===2) return 'middle'; return 'start'; }) .attr('transform', function(d,i){ var theta = 0; if(i===0){theta = 120;} if(i===1){theta = 60;} if(i===2){theta = -90;} var x = opt.axisLabelMargin * Math.cos(theta * 0.0174532925), y = opt.axisLabelMargin * Math.sin(theta * 0.0174532925); return `translate(${x},${y})` }); } TernaryPlot.prototype._responseToScroll = function(){ var plotContainer = this.parent.plotContainer; var labelWidth = this.parent.opt.labelWidth; var sortDiv = this.parent.sortDivId; var chartSVGidHead = this.parent.chartSVGidHead; var self = this; var lastScrollLeft = 0; // If this is the first ternary plot refresh if(typeof scrollValue === 'undefined'){ scrollValue = $(`#${plotContainer}`).scrollTop(); } $(`#${plotContainer}`).scroll(function(){ if(typeof initialScrollValue === "undefined"){ initialScrollValue = 0; } // Amount of scroll vertically scrollValue = $(`#${plotContainer}`).scrollTop(); var amountOfScroll = scrollValue - initialScrollValue; // Amount of Scroll horizontally var documentScrollLeft = $(`#${plotContainer}`).scrollLeft(); // Move elements vertically in response d3.select('#ternaryPlotSVG').attr(`transform`, `translate(${labelWidth},${-20 + scrollValue})`); $('#toolTipRect').attr(`transform`, `translate(${0},${amountOfScroll})`); $('#toolTipText').attr(`transform`, `translate(${0},${amountOfScroll})`); $('.tempTextElement').attr(`transform`, `translate(${0},${amountOfScroll})`); // Move elements horizontally in response if (lastScrollLeft != documentScrollLeft){ lastScrollLeft = documentScrollLeft; $(`#${chartSVGidHead}`).css(`position`, 'relative'); $(`#${chartSVGidHead}`).css(`left`, -lastScrollLeft); $(`#${sortDiv}`).css(`left`, -lastScrollLeft); } }); // scroll the plot when it is refreshed if(typeof scrollValue !== "undefined"){ d3.select('#ternaryPlotSVG').attr(`transform`, `translate(${labelWidth},${-20 + scrollValue})`); } } TernaryPlot.prototype._calculateCentroid = function(data){ var averageOfA = 0, averageOfB = 0, averageOfD = 0; var dataMap = new Map(); var arr = []; var brr = []; var drr = []; for(var mapElement in data){ averageOfA += data[mapElement].get('A'); averageOfB += data[mapElement].get('B'); averageOfD += data[mapElement].get('D'); } averageOfA /= data.length; averageOfB /= data.length; averageOfD /= data.length; var circleCoords = this._coord([averageOfA, averageOfD, averageOfB]); dataMap.set('id', 'centroid'); dataMap.set('A', averageOfA); dataMap.set('B', averageOfB); dataMap.set('D', averageOfD); dataMap.set('cx', circleCoords[0]); dataMap.set('cy', circleCoords[1]); return dataMap; } TernaryPlot.prototype._calculateExpressionBias = function(data, centroid){ var sumOfDistances = 0, expressionBias = 0; var aDistanceArr = [], bDistancesArr = [], dDistancesArr = []; for(var mapElement in data){ var aDistances = data[mapElement].get('A') - centroid.get('A'); var bDistances = data[mapElement].get('B') - centroid.get('B'); var dDistances = data[mapElement].get('D') - centroid.get('D'); sumOfDistances += Math.sqrt(Math.pow(aDistances, 2) + Math.pow(bDistances, 2) + Math.pow(dDistances, 2)); aDistanceArr.push(aDistances); bDistancesArr.push(bDistances); dDistancesArr.push(dDistances); } expressionBias = sumOfDistances / data.length; return expressionBias; } TernaryPlot.prototype._indicateExpressionBias = function(expressionBias){ var data = this.parent.data; var numOfBlockRows = Object.keys(data.expression_bias).length; var varieties = Object.keys(data.expression_bias); var self = this; var tooltipText = []; // Tooltip text var toolTipTextCentroid = "Centroid: \n"; var expColorCode = data.expression_bias_colors; $('#expression-bias').show(); for(var i=0; i<numOfBlockRows; i++){ var expBiasValues = Object.values(data.expression_bias[varieties[i]]); expBiasValues.unshift(0); var index = 0; for(var value in expBiasValues){ if((expBiasValues[value] - expressionBias/100) > 0){ // Select the first label value that produces a negative value (indicates the index of the block) index = expBiasValues.indexOf(expBiasValues[value]); break; } } // Tooltip text tooltipText[i] = `Average distance from centroid: ${(expressionBias).toFixed(2)}`; tooltipText[i] += `\nHomoeolog Expression bias decile: ${index}`; tooltipText[i] += `\nHomoeolog Expression bias range: ${(expBiasValues[index-1] * 100).toFixed(2)} - ${(expBiasValues[index] * 100).toFixed(2)}`; if(typeof expColorCode !== 'undefined'){ d3.select(`#block-group-${i} rect:nth-child(${index})`).style('fill', expColorCode[Object.keys(expColorCode)[i]][(index - 1)]); } else { d3.select(`#block-group-${i} rect:nth-child(${index})`).style('fill', 'red'); } $(`#block-group-${i}`).on('mouseenter', function(){ var indexOfRow = Number($(this).attr('id').slice(-1)); var centroid = d3.select('#centroid'); self._indicateCircle(toolTipTextCentroid, centroid); // console.log('tooltipText[indexOfRow]: ', tooltipText[indexOfRow]); self.parent.showTooltip(tooltipText[indexOfRow], this, self.tooltip, self.tooltipBox, true); centroid.attr('display', 'block'); self._renderLinesToCentroid(); }); $(`#block-group-${i}`).on('mouseleave', function(){ var centroid = d3.select('#centroid'); self._setDefaultStateCircle(centroid); self._hideExpBiasTooltip(); centroid.attr('display', 'none'); self._hideLinesToCentroid(); }); } } TernaryPlot.prototype._renderExpBiasTooltip = function(){ var barHeight = this.parent.opt.barHeight; var fontSize = this.parent.opt.fontSize; var blockContainer = d3.select('#expression-bias'); this.tooltipBox = blockContainer.append('rect').attr('id', 'expBiasTooltipRect'); this.tooltip = blockContainer.append('text').attr('id', 'expBiasTooltipText'); this.tooltip .attr('x',0) .attr('y', 0) .attr('height', barHeight -2) .attr('fill', 'white') .attr('font-size', fontSize/1.4) .attr('visibility', 'hidden') } TernaryPlot.prototype._hideExpBiasTooltip = function() { this.tooltip.attr('visibility', 'hidden'); this.tooltipBox.attr('visibility', 'hidden'); d3.selectAll('.tempTextElement').remove(); return; } TernaryPlot.prototype._indicateCircle = function(tooltipText, circle){ var thisCircleData = circle.data(); var tooltip_order = this.parent.data.tooltip_order; var self = this; circle.attr('r', 10); tooltip_order.forEach(function(element, index){ if(thisCircleData[0].get('id') === 'centroid'){ tooltipText += `${element} : ${thisCircleData[0].get(element).toFixed(2)}%`; $("#ternaryPlotGroup").get(0).appendChild(circle.node()); } else { tooltipText += `${element} : ${thisCircleData[0].get(element).toFixed(2)}% ${self.parent.opt.renderProperty}: ${thisCircleData[0].get(`expval${element}`).toFixed(2)}`; } if(index !== tooltip_order.length - 1){ tooltipText += '\n'; } }); this.parent.showTooltip(tooltipText, circle.node(), self.parent.tooltip, self.parent.tooltipBox); } TernaryPlot.prototype._setDefaultStateCircle = function(circle){ circle.attr('r', 6); this.parent.hideTooltip(); } TernaryPlot.prototype._renderLinesToCentroid = function(){ var circles = ternaryPlotGroup.selectAll("circle").data(); var centroid = d3.select('#centroid').data(); var centCoor = []; var self = this; centCoor.push(centroid[0].get('cx')); centCoor.push(centroid[0].get('cy')); circles.forEach(function(element, index){ var cirCoor = [] cirCoor.push(element.get('cx')); cirCoor.push(element.get('cy')); var allLineAttributes = self._lineAttributes(cirCoor, centCoor); ternaryPlotGroup.append('line') .attr('class', 'centroidLine') .attr('x1', allLineAttributes.x1) .attr('x2', allLineAttributes.x2) .attr('y1', allLineAttributes.y1) .attr('y2', allLineAttributes.y2) .style('stroke', '#f49090') .style("stroke-width", 1); }); } TernaryPlot.prototype._hideLinesToCentroid = function(){ var lines = d3.selectAll('.centroidLine'); lines.remove(); } TernaryPlot.prototype._calculateplotSize = function(){ var labelWidth = this.parent.opt.labelWidth; var plotContainerHeight = $(`#${this.parent.plotContainer}`).outerHeight() - 20; var plotContainerWidth = $(`#${this.parent.chartSVGid}`).width() - labelWidth; var parentWidth = $(`#${this.parent.opt.target}`).parent().width() - 20; // 50 is the scrollbar width if(parentWidth < 900){ h = plotContainerHeight - 150; w = Math.sqrt((4/3) * (h*h)); } else { if(plotContainerHeight < plotContainerWidth){ h = plotContainerHeight - 150; w = Math.sqrt((4/3) * (h*h)); } else { w = plotContainerWidth - 150; h = Math.sqrt( w*w - (w/2)*(w/2)); } } } TernaryPlot.prototype._adustTernaryHeight = function(){ var plotContainerHeight = $(`#${this.parent.plotContainer}`).height(); var chartSVGHeight = $(`#${this.parent.chartSVGid}`).height(); if(chartSVGHeight <= plotContainerHeight){ $(`#${this.parent.chartSVGid}`).height(plotContainerHeight); } else { $(`#${this.parent.chartSVGid}`).height('auto'); } } TernaryPlot.prototype._removeTPMDataUnderThreshold = function(data){ var tooltip_order = this.parent.data.tooltip_order, sum = 0, tpmThreshold = this.parent.data.opt.tpmThreshold; var filtered = data.filter(function(value, index){ sum = 0; tooltip_order.forEach(function(element, index){ ternValue = value.get(`expval${element}`); sum += ternValue; }); if (sum > tpmThreshold){ return value; } }); return filtered; } module.exports.TernaryPlot = TernaryPlot;