diff --git a/config/build-utils.js b/config/build-utils.js index 2a889f550..6e94e92ff 100644 --- a/config/build-utils.js +++ b/config/build-utils.js @@ -3,8 +3,11 @@ const path = require('path'); const fs = require('fs'); const helpers = require('./helpers'); +const APP_COMMON_CONFIG = require('./config.common.json'); + const DEFAULT_METADATA = { - title: 'Wall of Targets', + title: APP_COMMON_CONFIG.title, + description: APP_COMMON_CONFIG.description, baseUrl: '/', isDevServer: helpers.isWebpackDevServer(), HMR: helpers.hasProcessFlag('hot'), diff --git a/config/config.common.json b/config/config.common.json new file mode 100644 index 000000000..24a178546 --- /dev/null +++ b/config/config.common.json @@ -0,0 +1,4 @@ +{ + "title": "Wall of Targets", + "description": "Wall of Targets is an application to aggregate genomic evidence supporting candidate gene and module targets nominated by members of the AMP-AD consortium." +} \ No newline at end of file diff --git a/config/config.dev.json b/config/config.dev.json new file mode 100644 index 000000000..691531f96 --- /dev/null +++ b/config/config.dev.json @@ -0,0 +1,10 @@ +{ + "firebase": { + "apiKey": "AIzaSyBMS96wgJfydRf7BLDVh4DGtRKAZT8UpTM", + "authDomain": "wall-of-targets.firebaseapp.com", + "databaseURL": "https://wall-of-targets.firebaseio.com", + "projectId": "wall-of-targets", + "storageBucket": "wall-of-targets.appspot.com", + "messagingSenderId": "256222676709" + } +} \ No newline at end of file diff --git a/config/config.prod.json b/config/config.prod.json new file mode 100644 index 000000000..86edfcdd2 --- /dev/null +++ b/config/config.prod.json @@ -0,0 +1,11 @@ +{ + "firebase": { + "apiKey": "AIzaSyBMS96wgJfydRf7BLDVh4DGtRKAZT8UpTM", + "authDomain": "wall-of-targets.firebaseapp.com", + "databaseURL": "https://wall-of-targets.firebaseio.com", + "projectId": "wall-of-targets", + "storageBucket": "wall-of-targets.appspot.com", + "messagingSenderId": "256222676709" + }, + "gtmKey" : "GTM-XXXXXXX" +} \ No newline at end of file diff --git a/config/karma.conf.js b/config/karma.conf.js index 46799a5c9..f98787bb1 100644 --- a/config/karma.conf.js +++ b/config/karma.conf.js @@ -1,11 +1,11 @@ /** - * @author: @AngularClass + * @author: tipe.io */ module.exports = function (config) { - var testWebpackConfig = require('./webpack.test.js')({ env: 'test' }); + const testWebpackConfig = require('./webpack.test.js')({ env: 'test' }); - var configuration = { + const configuration = { /** * Base path that will be used to resolve all patterns (e.g. files, exclude). @@ -74,7 +74,7 @@ module.exports = function (config) { * webpack-dev-middleware configuration * i.e. */ - noInfo: true, + logLevel: 'warn', /** * and use stats to turn off verbose output */ @@ -120,13 +120,14 @@ module.exports = function (config) { * available browser launchers: https://npmjs.org/browse/keyword/karma-launcher */ browsers: [ - 'Chrome' + 'Chrome', + 'ChromeTravisCi' ], customLaunchers: { ChromeTravisCi: { - base: 'Chrome', - flags: ['--no-sandbox'] + base: 'ChromeHeadless', + flags: ['--no-sandbox', '--disable-gpu'] } }, @@ -134,9 +135,35 @@ module.exports = function (config) { * Continuous Integration mode * if true, Karma captures browsers, runs the tests and exits */ - singleRun: true + singleRun: true, + /** + * For slower machines you may need to have a longer browser + * wait time . Uncomment the line below if required. + */ + // browserNoActivityTimeout: 30000 + }; + // Optional Sonar Qube Reporter + if (process.env.SONAR_QUBE) { + + // SonarQube reporter plugin configuration + configuration.sonarQubeUnitReporter = { + sonarQubeVersion: '5.x', + outputFile: 'reports/ut_report.xml', + overrideTestDescription: true, + testPath: 'src/app', + testFilePattern: '.spec.ts', + useBrowserName: false + }; + + // Additional lcov format required for + // sonarqube + configuration.remapCoverageReporter.lcovonly = './coverage/coverage.lcov'; + + configuration.reporters.push('sonarqubeUnit'); + } + if (process.env.TRAVIS) { configuration.browsers = [ 'ChromeTravisCi' @@ -144,4 +171,4 @@ module.exports = function (config) { } config.set(configuration); -}; +}; \ No newline at end of file diff --git a/config/nginx-custom.conf b/config/nginx-custom.conf new file mode 100644 index 000000000..ca0ed1021 --- /dev/null +++ b/config/nginx-custom.conf @@ -0,0 +1,19 @@ +server { + listen 80; + + gzip on; + gzip_http_version 1.1; + gzip_disable "MSIE [1-6]\."; + gzip_min_length 1100; + gzip_vary on; + gzip_proxied expired no-cache no-store private auth; + gzip_types text/plain text/css application/json application/javascript application/x-javascript text/xml application/xml application/xml+rss text/javascript; + gzip_comp_level 5; + + root /usr/share/nginx/html; + + location / { + index index.html index.htm; + try_files $uri $uri/ /index.html =404; + } +} \ No newline at end of file diff --git a/config/protractor.conf.js b/config/protractor.conf.js index acaa95cb4..acca1ea4d 100644 --- a/config/protractor.conf.js +++ b/config/protractor.conf.js @@ -1,5 +1,5 @@ /** - * @author: @AngularClass + * @author: tipe.io */ require('ts-node/register'); @@ -17,24 +17,23 @@ exports.config = { ], exclude: [], - framework: 'jasmine2', + framework: 'jasmine', - allScriptsTimeout: 110000, + allScriptsTimeout: 11000, jasmineNodeOpts: { showTiming: true, showColors: true, isVerbose: false, includeStackTrace: false, - defaultTimeoutInterval: 400000 + defaultTimeoutInterval: 40000 }, - directConnect: true, + directConnect: true, capabilities: { - 'browserName': 'chrome', - 'chromeOptions': { - //'args': ["--headless", "--disable-gpu", "--window-size=1280x800", "--no-sandbox"] - 'args': ['show-fps-counter=true'] + browserName: 'chrome', + chromeOptions: { + args: [ "--headless", "--disable-gpu", "--window-size=800x600", "--no-sandbox" ] } }, @@ -51,4 +50,4 @@ exports.config = { useAllAngular2AppRoots: true, SELENIUM_PROMISE_MANAGER: false, -}; +}; \ No newline at end of file diff --git a/config/spec-bundle.js b/config/spec-bundle.js index d4ca968dd..c814277e4 100644 --- a/config/spec-bundle.js +++ b/config/spec-bundle.js @@ -1,5 +1,5 @@ /** - * @author: @AngularClass + * @author: tipe.io */ /** @@ -60,4 +60,4 @@ function requireAll(requireContext) { /** * Requires and returns all modules that match */ -var modules = requireAll(testContext); +var modules = requireAll(testContext); \ No newline at end of file diff --git a/config/webpack.common.js b/config/webpack.common.js index 881d60fcb..818136ab3 100644 --- a/config/webpack.common.js +++ b/config/webpack.common.js @@ -17,6 +17,7 @@ const HtmlWebpackPlugin = require('html-webpack-plugin'); const InlineManifestWebpackPlugin = require('inline-manifest-webpack-plugin'); const LoaderOptionsPlugin = require('webpack/lib/LoaderOptionsPlugin'); const ScriptExtHtmlWebpackPlugin = require('script-ext-html-webpack-plugin'); +const ProvidePlugin = require('webpack/lib/ProvidePlugin'); const ngcWebpack = require('ngc-webpack'); const buildUtils = require('./build-utils'); @@ -28,20 +29,24 @@ const buildUtils = require('./build-utils'); * See: http://webpack.github.io/docs/configuration.html#cli */ module.exports = function (options) { - const isProd = options.env === 'production'; - const METADATA = Object.assign({}, buildUtils.DEFAULT_METADATA, options.metadata || {}); - const ngcWebpackConfig = buildUtils.ngcWebpackSetup(isProd, METADATA); - const supportES2015 = buildUtils.supportES2015(METADATA.tsConfigPath); - - const entry = { - polyfills: './src/polyfills.browser.ts', - main: './src/main.browser.ts' - }; + const isProd = options.env === 'production'; + const APP_CONFIG = require(process.env.ANGULAR_CONF_FILE || (isProd ? './config.prod.json' : './config.dev.json')); + + const METADATA = Object.assign({}, buildUtils.DEFAULT_METADATA,options.metadata || {}); + const GTM_API_KEY = process.env.GTM_API_KEY || APP_CONFIG.gtmKey; + + const ngcWebpackConfig = buildUtils.ngcWebpackSetup(isProd, METADATA); + const supportES2015 = buildUtils.supportES2015(METADATA.tsConfigPath); - Object.assign(ngcWebpackConfig.plugin, { - tsConfigPath: METADATA.tsConfigPath, - mainPath: entry.main - }); + const entry = { + polyfills: './src/polyfills.browser.ts', + main: './src/main.browser.ts' + }; + + Object.assign(ngcWebpackConfig.plugin, { + tsConfigPath: METADATA.tsConfigPath, + mainPath: entry.main + }); return { /** @@ -179,7 +184,8 @@ module.exports = function (options) { 'AOT': METADATA.AOT, 'process.env.ENV': JSON.stringify(METADATA.ENV), 'process.env.NODE_ENV': JSON.stringify(METADATA.ENV), - 'process.env.HMR': METADATA.HMR + 'process.env.HMR': METADATA.HMR, + 'FIREBASE_CONFIG': JSON.stringify(APP_CONFIG.firebase) }), /** @@ -303,6 +309,10 @@ module.exports = function (options) { * https://github.com/szrenwei/inline-manifest-webpack-plugin */ new InlineManifestWebpackPlugin(), + + new ProvidePlugin({ + 'dc': 'dc' + }) ], /** @@ -312,14 +322,12 @@ module.exports = function (options) { * See: https://webpack.github.io/docs/configuration.html#node */ node: { - global: true, - crypto: 'empty', - process: true, - module: false, - clearImmediate: false, - setImmediate: false, - fs: 'empty', - readline: 'empty' + global: true, + crypto: 'empty', + process: true, + module: false, + clearImmediate: false, + setImmediate: false } }; diff --git a/src/app/charts/charts.module.ts b/src/app/charts/charts.module.ts index 794aeaf7a..5858e7247 100644 --- a/src/app/charts/charts.module.ts +++ b/src/app/charts/charts.module.ts @@ -4,7 +4,6 @@ import { CommonModule } from '@angular/common'; import { AppSharedModule } from '../shared'; import { ScatterPlotViewComponent } from './scatter-plot/scatter-plot-view'; -import { LineChartViewComponent } from './line-chart/line-chart-view'; import { SelectMenuViewComponent } from './select-menu/select-menu-view'; import { RowChartViewComponent } from './row-chart/row-chart-view'; @@ -22,7 +21,6 @@ import { @NgModule({ declarations: [ ScatterPlotViewComponent, - LineChartViewComponent, SelectMenuViewComponent, RowChartViewComponent ], @@ -41,7 +39,6 @@ import { ], exports: [ ScatterPlotViewComponent, - LineChartViewComponent, SelectMenuViewComponent, RowChartViewComponent ] diff --git a/src/app/charts/line-chart/line-chart-view/index.ts b/src/app/charts/line-chart/line-chart-view/index.ts deleted file mode 100644 index 186956419..000000000 --- a/src/app/charts/line-chart/line-chart-view/index.ts +++ /dev/null @@ -1 +0,0 @@ -export * from './line-chart-view.component'; diff --git a/src/app/charts/line-chart/line-chart-view/line-chart-view.component.html b/src/app/charts/line-chart/line-chart-view/line-chart-view.component.html deleted file mode 100644 index f239521ac..000000000 --- a/src/app/charts/line-chart/line-chart-view/line-chart-view.component.html +++ /dev/null @@ -1 +0,0 @@ -

Nested

diff --git a/src/app/charts/line-chart/line-chart-view/line-chart-view.component.scss b/src/app/charts/line-chart/line-chart-view/line-chart-view.component.scss deleted file mode 100644 index e69de29bb..000000000 diff --git a/src/app/charts/line-chart/line-chart-view/line-chart-view.component.ts b/src/app/charts/line-chart/line-chart-view/line-chart-view.component.ts deleted file mode 100644 index 41ac583bf..000000000 --- a/src/app/charts/line-chart/line-chart-view/line-chart-view.component.ts +++ /dev/null @@ -1,26 +0,0 @@ -import { Component, OnInit, ViewEncapsulation } from '@angular/core'; - -import { - Router, - ActivatedRoute -} from '@angular/router'; - -@Component({ - selector: 'line-chart', - templateUrl: './line-chart-view.component.html', - styleUrls: [ './line-chart-view.component.scss' ] -}) -export class LineChartViewComponent implements OnInit { - - constructor( - private router : Router, - private route: ActivatedRoute - ) { } - - ngOnInit() { - } - - goToRoute(path: string, outlets?: any) { - (outlets) ? this.router.navigate([path, outlets], {relativeTo: this.route}) : this.router.navigate([path], {relativeTo: this.route}); - } -} diff --git a/src/app/charts/row-chart/row-chart-view/row-chart-view.component.html b/src/app/charts/row-chart/row-chart-view/row-chart-view.component.html index 954127a60..2a18d0fd5 100644 --- a/src/app/charts/row-chart/row-chart-view/row-chart-view.component.html +++ b/src/app/charts/row-chart/row-chart-view/row-chart-view.component.html @@ -8,7 +8,7 @@

{{title}}

-
+
diff --git a/src/app/charts/row-chart/row-chart-view/row-chart-view.component.ts b/src/app/charts/row-chart/row-chart-view/row-chart-view.component.ts index 292cc152f..d957ec266 100644 --- a/src/app/charts/row-chart/row-chart-view/row-chart-view.component.ts +++ b/src/app/charts/row-chart/row-chart-view/row-chart-view.component.ts @@ -1,4 +1,5 @@ import { Component, OnInit, ViewEncapsulation, ViewChild, ElementRef, Input } from '@angular/core'; +import { DecimalPipe } from '@angular/common'; import { ActivatedRoute @@ -14,7 +15,7 @@ import { import * as d3 from 'd3'; import * as dc from 'dc'; -// Using a d3 v4 function to get all node +// Using a d3 v4 function to get all nodes d3.selection.prototype['nodes'] = function(){ var nodes = new Array(this.size()), i = -1; this.each(function() { nodes[++i] = this; }); @@ -37,11 +38,13 @@ export class RowChartViewComponent implements OnInit { @ViewChild('studies') stdCol: ElementRef; changedLabels: boolean = false; + display: boolean = false; constructor( private route: ActivatedRoute, private geneService: GeneService, - private chartService: ChartService + private chartService: ChartService, + private decimalPipe: DecimalPipe ) { } ngOnInit() { @@ -64,7 +67,7 @@ export class RowChartViewComponent implements OnInit { .elasticX(true) .gap(4) .title(function(d) { - return d.value.logFC || 0; + return 'Log Fold Change: ' + (self.decimalPipe.transform(+d.value.logFC) || 0); }) .valueAccessor(function(d) { return d.value.logFC || 0; @@ -77,74 +80,126 @@ export class RowChartViewComponent implements OnInit { .dimension(this.chartService.getDimension(this.label)) .group(this.chartService.getGroup(this.label)); + // Add this number of ticks so the x axis don't get cluttered with text this.chart.xAxis().ticks(5); - this.chart.on('filtered', function(chart, filter){ - console.log(chart, filter); - }); + // Register the row chart renderlet + this.registerChartEvent(this.chart); - this.chart.on('renderlet', function (chart) { - let barHeight = chart.select('g.row rect').attr('height'); - let newSvg = d3.select(self.stdCol.nativeElement).append('svg'); - let textGroup = newSvg.append('g') - .attr('class', 'textGroup'); - - let allText = chart.selectAll('g.row text'); - let removed = allText.remove(); - - // Copy the texts to another div, so they show up - if (!self.changedLabels) { - let stdColHeight = chart.height(); - let step = chart.select('g.axis g.tick line.grid-line').node().getBBox().height / (removed.nodes().length); - removed.nodes().forEach(n => { - textGroup.append(function() { - return n; + this.chart.render(); + } + + // A custom renderlet function for this chart, allows us to change + // what happens to the chart after rendering + registerChartEvent(chart: dc.RowChart, type: string = 'renderlet') { + let self = this; + this.chart.on(type, function (chart) { + let rectHeight = chart.select('g.row rect').attr('height'); + let squareSize = 10; + let lineWidth = 60; + + // Test if we should display the chart. Using this variable so we don't see + // the rows rectangles change into small squares abruptly + if (!self.display) { + // Copy all vertical texts to another div, so they don't get hidden by + // the row chart svg after being translated + self.moveTextToElement(chart, self.stdCol.nativeElement, squareSize/2); + + // Insert a line for each row of the chart + self.insertLinesInRows(chart); + + // Draw the inserted lines in each row + self.drawLines(chart, rectHeight/2, lineWidth); + } else { + // This part will be called on redraw after filtering, so at this point we just need + // to move the lines to the correct position again. First translate the parent element + let hlines = chart.selectAll('g.row g.hline'); + hlines.each(function(d, i) { + d3.select(this).attr('transform', function(d) { + return 'translate('+d.value.logFC+')'; }) - }) - d3.select(self.stdCol.nativeElement).selectAll('g.textGroup text').each(function(d, i) { - let currentStep = step * i; - // your update code here as it was in your example - d3.select(this).attr('transform', function () { - return 'translate(0,' + (currentStep+5) + ')'; - }); }); - // Draw the lines through the squares - let bar = chart.selectAll('g.row') - .insert('g', ':first-child') - .attr('class', 'hline'); - bar - .insert('line') - .attr({ - 'stroke-width': 1.5, - stroke: 'wheat', - x1: function(d) { - return chart.x()(d.value.logFC) - 30; - }, - y1: function(d) { - return barHeight/2; - }, - x2: function(d) { - return chart.x()(d.value.logFC) + 40; - }, - y2: function(d) { - return barHeight/2; - } - }); - - self.changedLabels = true; + // Finally redraw the lines in each row + self.drawLines(chart, rectHeight/2, lineWidth); } - // Change the rectangles to small ones - chart - .selectAll('g.row rect') - .attr('transform', function(d) { - return 'translate(' + chart.x()(d.value.logFC) + ',' + ((barHeight/2)-5) + ')'; - }) - .attr('width', '10') - .attr('height', '10'); + // Change the row rectangles into small squares, this happens on + // every render or redraw + self.rectToSquares(chart, squareSize, rectHeight); + + // Finally show the chart + self.display = true; }); + } - this.chart.render(); + // Moves all text in textGroups to a new HTML element + moveTextToElement(chart: dc.RowChart, el: HTMLElement, vSpacing: number = 0) { + let newSvg = d3.select(el).append('svg'); + let textGroup = newSvg.append('g') + .attr('class', 'textGroup'); + + // Remove the old texts and append to the new group + let allText = chart.selectAll('g.row text'); + let removed = allText.remove(); + removed['nodes']().forEach(n => { + textGroup.append(function() { + return n; + }) + }); + + // Move the text to the correct position in the new svg + let stdColHeight = chart.height(); + let step = (chart.select('g.axis g.tick line.grid-line').node() as SVGGraphicsElement).getBBox().height / (removed['nodes']().length); + + d3.select(el).selectAll('g.textGroup text').each(function(d, i) { + let currentStep = step * i; + d3.select(this).attr('transform', function () { + return 'translate(0,' + (currentStep+(vSpacing)) + ')'; + }); + }); + } + + insertLinesInRows(chart: dc.RowChart) { + chart.selectAll('g.row') + .insert('g', ':first-child') + .attr('class', 'hline') + .insert('line'); + } + + // Draw the lines through the chart rows + drawLines(chart: dc.RowChart, yPos: number, lineWidth: number) { + let lines = chart.selectAll('g.row g.hline line') + .attr({ + 'stroke-width': 1.5, + stroke: 'wheat', + x1: function(d) { + return chart.x()(d.value.logFC) - lineWidth/2; + }, + y1: function(d) { + return yPos; + }, + x2: function(d) { + return chart.x()(d.value.logFC) + lineWidth/2; + }, + y2: function(d) { + return yPos; + } + }); + } + + // Changes the chart row rects into squares of the square size + rectToSquares(chart: dc.RowChart, squareSize: number, rectHeight: number) { + chart + .selectAll('g.row rect') + .attr('transform', function(d) { + return 'translate(' + (chart.x()(d.value.logFC)-(squareSize/2)) + ',' + ((rectHeight/2)-(squareSize/2)) + ')'; + }) + .attr('width', squareSize) + .attr('height', squareSize); + } + + displayChart() { + return {'opacity': (this.display) ? 1 : 0}; } } diff --git a/src/app/charts/scatter-plot/scatter-plot-view/scatter-plot-view.component.scss b/src/app/charts/scatter-plot/scatter-plot-view/scatter-plot-view.component.scss index 96ef71a53..5c917e383 100644 --- a/src/app/charts/scatter-plot/scatter-plot-view/scatter-plot-view.component.scss +++ b/src/app/charts/scatter-plot/scatter-plot-view/scatter-plot-view.component.scss @@ -28,7 +28,3 @@ .dc-chart { height: 100%; } - -.sc.dc-chart { - width: 100%; -} diff --git a/src/app/charts/scatter-plot/scatter-plot-view/scatter-plot-view.component.ts b/src/app/charts/scatter-plot/scatter-plot-view/scatter-plot-view.component.ts index c6c306130..5f37506c2 100644 --- a/src/app/charts/scatter-plot/scatter-plot-view/scatter-plot-view.component.ts +++ b/src/app/charts/scatter-plot/scatter-plot-view/scatter-plot-view.component.ts @@ -1,4 +1,5 @@ import { Component, OnInit, ViewEncapsulation, ViewChild, ElementRef, Input, ContentChild, AfterContentInit } from '@angular/core'; +import { DecimalPipe } from '@angular/common'; import { ActivatedRoute @@ -7,14 +8,14 @@ import { import { Gene } from '../../../models'; import { - ChartService, - ColorService + ChartService } from '../../../core/services'; import { GeneService } from '../../../core/services'; import * as d3 from 'd3'; import * as dc from 'dc'; +import '../../../../scripts/dc-canvas-scatterplot.js'; @Component({ selector: 'scatter-plot', @@ -29,6 +30,8 @@ export class ScatterPlotViewComponent implements OnInit, AfterContentInit { @Input() label: string; @ViewChild('chart') scatterPlot: ElementRef; + subChart: any; + svgAdded: boolean = false; private dim: CrossFilter.Dimension; private group: CrossFilter.Group; @@ -36,8 +39,8 @@ export class ScatterPlotViewComponent implements OnInit, AfterContentInit { constructor( private route: ActivatedRoute, private chartService: ChartService, - private colorService: ColorService, - private geneService: GeneService + private geneService: GeneService, + private decimalPipe: DecimalPipe ) { } ngOnInit() {} @@ -62,38 +65,110 @@ export class ScatterPlotViewComponent implements OnInit, AfterContentInit { this.dim = this.chartService.getDimension(this.label); this.group = this.chartService.getGroup(this.label); - this.chart = dc.scatterPlot(this.scatterPlot.nativeElement) - .x(d3.scale.linear().domain([this.geneService.minLogFC, this.geneService.maxLogFC])) - .y(d3.scale.linear().domain([0, this.geneService.maxAdjPVal])) + let currentGene = this.geneService.getCurrentGene(); + // Create a symbol scale based on d3 types, then make the accessor + // return two different types + let symbolScale = d3.scale.ordinal().range(d3.svg.symbolTypes); + let symbolAccessor = function(d) { + return symbolScale( + (d.key[2] === currentGene.hgnc_symbol) ? '1' : '0' + ) + }; + // Add the scatter plot as a sub chart of a series chartso we can render two + // series of genes, the selected and the non selected ones + this.subChart = function(c) { + return dc.scatterPlot(c) + //['useCanvas'](true) + .symbol(symbolAccessor) + .symbolSize(5) + .highlightedSize(10) + .renderTitle(true) + .brushOn(false) + .title(function (p) { + return [ + 'Log Fold Change: ' + self.decimalPipe.transform(+p.key[0]), + '-log10(Adjusted p-value): ' + self.decimalPipe.transform(+p.key[1]) + ].join('\n'); + }); + }; + this.chart = dc.seriesChart(this.scatterPlot.nativeElement) + .x(d3.scale.linear().domain([this.geneService.minLogFC*1.1, this.geneService.maxLogFC*1.1])) + .chart(this.subChart) .brushOn(false) .xAxisLabel(this.info.xAxisLabel) .yAxisLabel(this.info.yAxisLabel) + .clipPadding(10) .dimension(this.dim) .group(this.group) - //.linearColors(['#b30000', '#fdd49e']) - .linearColors(['#000000', '#bbbbbb']) - .colorAccessor(function(d) { - if (Number.isNaN(+d.key[0]) || Number.isNaN(+d.key[1])) return 0; - let a = 0 - +d.key[0]; - let b = 0 - +d.key[1]; - //return Math.sqrt(a*a + (b*b)); - return Math.abs(a); - }); + //.elasticY(true) + //.mouseZoomable(true) + .shareTitle(false) + // Using this notation because the typings for dc do not show this method for this chart + ['seriesAccessor'](function(d) { + return (d.key[2] === currentGene.hgnc_symbol) ? '1' : '0'; + }) + .keyAccessor(function(d) {return +d.key[0];}) + .valueAccessor(function(d) { return +d.key[1]; }); + + // Separate this call so we can get the correct chart reference below + this.chart.yAxis().tickFormat(function(d) { return d3.format(',d')(d); }); + + // Register the scatter plot pretransition event + this.registerChartEvent(this.chart, 'pretransition'); + this.chart.render(); + } - if (this.info.xUnits) this.chart.xUnits(this.info.xUnits); + registerChartEvent(chart: dc.SeriesChart, type: string = 'renderlet') { + let self = this; + chart.on(type, function (chart) { + if (!self.svgAdded) { + let blackGenes = chart.selectAll('g.sub._0 path.symbol'); + let redGenes = chart.selectAll('g.sub._1 path.symbol'); + + // Make the selection render for last + self.moveToFront(redGenes); + + // Add a black and white gradient to the non selected genes + let svg = d3.select(self.scatterPlot.nativeElement).select('svg'); + self.addGradientToSVG(blackGenes, svg, 'black-gradient', [ + { + 'offset': '0%', 'stop-color': 'white' + }, { + 'offset': '60%', 'stop-color': 'black' + } + ]); + + // Add a red color tothe selected genes + redGenes + .style('stroke', 'white') + .style('fill', 'red') + .style('stroke-width', 0.5); + + self.svgAdded = true; + } + }); + } - this.chart.render(); + // Make the selection the last in the parent order + moveToFront(sel: d3.Selection) { + sel.each(function() { + this.parentNode.appendChild(this); + }); } - getValue (d: any) { - let value: number - if (!(isNaN(d.value))) { - value = d.value; - } else if (!d.value) { - value = 0; - } + // Adds a gradient color to an SVG, uses offset and stop color + addGradientToSVG(parent: d3.Selection, svg: d3.Selection, name: string, options: any[]) { + let gradient = svg.append("defs") + .append("radialGradient") + .attr('id', name); + + options.forEach(o => { + gradient.append('stop') + .attr('offset', o.offset) + .attr('stop-color', o['stop-color']) + }) - return value; + parent.style('fill', 'url(#'+name+')'); } } diff --git a/src/app/core/core.module.ts b/src/app/core/core.module.ts index 8c9678f36..149edf89f 100644 --- a/src/app/core/core.module.ts +++ b/src/app/core/core.module.ts @@ -29,10 +29,10 @@ import { import { NgSelectModule } from '@ng-select/ng-select'; // Backend modules and extra config -import { environment } from 'environments/environment'; +import { environment, ENV_FIREBASE_CONFIG } from 'environments/environment'; import { AngularFireModule } from 'angularfire2'; -export const firebaseConfig = environment.firebaseConfig; +export const firebaseConfig = ENV_FIREBASE_CONFIG; import { AngularFirestoreModule } from 'angularfire2/firestore'; import { diff --git a/src/app/core/services/chart.service.ts b/src/app/core/services/chart.service.ts index 70457592b..647424958 100644 --- a/src/app/core/services/chart.service.ts +++ b/src/app/core/services/chart.service.ts @@ -57,7 +57,8 @@ export class ChartService { case 'scatter-plot': return [ Number.isNaN(+d[dimValue[0]]) ? 0 : +d[dimValue[0]], - Number.isNaN(+d[dimValue[1]]) ? 0 : +d[dimValue[1]] + Number.isNaN(+d[dimValue[1]]) ? 0 : +d[dimValue[1]], + d[dimValue[2]] ]; case 'select-menu': if (info.filter) { diff --git a/src/app/targets/gene-details/gene-rnaseq-de/gene-rnaseq-de.component.ts b/src/app/targets/gene-details/gene-rnaseq-de/gene-rnaseq-de.component.ts index 7b538340b..d3152ea49 100644 --- a/src/app/targets/gene-details/gene-rnaseq-de/gene-rnaseq-de.component.ts +++ b/src/app/targets/gene-details/gene-rnaseq-de/gene-rnaseq-de.component.ts @@ -52,12 +52,12 @@ export class GeneRNASeqDEComponent implements OnInit { this.chartService.addChartInfo( 'volcano-plot', { - dimension: ['logFC', 'neg_log10_adj_P_Val'], + dimension: ['logFC', 'neg_log10_adj_P_Val', 'hgnc_symbol'], group: 'self', type: 'scatter-plot', title: 'Volcano Plot', xAxisLabel: 'Log Fold Change', - yAxisLabel: '-log10(Adjusted p-value', + yAxisLabel: '-log10(Adjusted p-value)', x: ['logFC'], y: ['neg_log10_adj_P_Val'] } @@ -79,8 +79,7 @@ export class GeneRNASeqDEComponent implements OnInit { { dimension: ['tissue_study_pretty'], group: 'self', - type: 'select-menu', - filter: true + type: 'select-menu' } ); this.chartService.addChartInfo( @@ -88,8 +87,7 @@ export class GeneRNASeqDEComponent implements OnInit { { dimension: ['comparison_model_sex'], group: 'self', - type: 'select-menu', - filter: true + type: 'select-menu' } ); } diff --git a/src/app/targets/gene-search/gene-search.component.html b/src/app/targets/gene-search/gene-search.component.html index 57e6fe885..3d6d40adf 100644 --- a/src/app/targets/gene-search/gene-search.component.html +++ b/src/app/targets/gene-search/gene-search.component.html @@ -9,7 +9,7 @@

Please type a gene symbol in the search box below.

Gene Symbol
- ; + @Input() genes: Gene[]; constructor( private router: Router, diff --git a/src/app/targets/targets-list/targets-list.component.html b/src/app/targets/targets-list/targets-list.component.html index cea47c516..3e7fb0eaa 100644 --- a/src/app/targets/targets-list/targets-list.component.html +++ b/src/app/targets/targets-list/targets-list.component.html @@ -14,7 +14,7 @@

; + @Input() genes: Gene[]; msgs: Message[] = []; totalRecords: number; diff --git a/src/app/targets/targets-view/targets-view.component.html b/src/app/targets/targets-view/targets-view.component.html index fd4028d61..a5dee3601 100644 --- a/src/app/targets/targets-view/targets-view.component.html +++ b/src/app/targets/targets-view/targets-view.component.html @@ -22,10 +22,10 @@
- + - +
diff --git a/src/app/targets/targets-view/targets-view.component.ts b/src/app/targets/targets-view/targets-view.component.ts index 904b1e462..7bfdf177a 100644 --- a/src/app/targets/targets-view/targets-view.component.ts +++ b/src/app/targets/targets-view/targets-view.component.ts @@ -16,6 +16,7 @@ import { Observable } from 'rxjs/Observable'; }) export class TargetsViewComponent implements OnInit { genes$: Observable; + genes: Gene[]; dataLoaded: boolean = false; @@ -35,6 +36,15 @@ export class TargetsViewComponent implements OnInit { this.genes$ = this.geneService.loadGenesFile('sampleData.json'); this.genes$.subscribe(data => { this.geneService.setGenes(data); + let gids = new Set(); + let fgenes = data.filter((d: Gene) => { + if (gids.has(d.hgnc_symbol)) { + return false; + } + gids.add(d.hgnc_symbol); + return true; + }) + this.genes = fgenes; this.dataLoaded = true; }); } diff --git a/src/assets/data/service-key.json b/src/assets/data/service-key.json deleted file mode 100644 index 03a996cd4..000000000 --- a/src/assets/data/service-key.json +++ /dev/null @@ -1,12 +0,0 @@ -{ - "type": "service_account", - "project_id": "wall-of-targets", - "private_key_id": "59223371f5fd4c30bdbf629f74938f0ef35fbdec", - "private_key": "-----BEGIN PRIVATE KEY-----\nMIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQCf6BowLI0/ZzxJ\nZthE8G9oH2cBdZpmo6rJq54nvSUl1oAHl5lcAHAh8qNM84K+mPKUBK/mCEnu9eAO\nrGD7YLma49/i2Hwdr/LK5mwcg2HfKmzQAFP/IAIbnQTi1hutgPfs5Yn9M+uNOu1o\no2rCMdsKAyCJfCYVYdq0GT4NTlMbqe/uHIvOoWLGeLDeP1UJF0VgMIUKYw/b6NrM\ngllk8yt4vzddusTucevLxpYEMUN82GGO0MQTS2yz3xFNkqDjVrBgXermIVEnbNYl\nj+AlUPVKD5Cjb6sG6/jhm3pk/5tDxJXIhr4JFXq4xBbWrfa20qPLrJST8O44A9U6\nefAddc93AgMBAAECggEANqAfch2GEwa9PYX4k3RfWn5/vavjFvUKPuJ1oLWonwhr\nrCxJo3gy1iYBSyRUoa+2DoKbZEWonA06gUi5yKX+Qb9emCvEGpAkrNOSUxs+r/Ha\nQiQajajtj2lQZf0mb1sDVtYDEZBPA2wB9iD9M2G8perzjoPurpdDEwsce4cqNOSJ\n8AvLKHFQaG2PA5r7Zp23m3sqv8vhdMTASfO25BjfmUzInw/76/XLUOnVKdisbeI+\nqx7wWOeLn4/SwcgT8zumbhpcQIAYAUtqKWQGE6+AskiuurrW/LAtuo1cUWhn3rFT\nHCt9eLKXMenQjbwyJGk10u34Zm34h7yOC1kOIBjKAQKBgQDb09t5mP5wqOq3i0li\nxb+66jgY9SC1LuaCgaSzLGMAxd/+0h98peFY36fZNLHfj2+Z/8gMmOiWe3dbs7nJ\npdZWrfCidA+ejGNTTodmdMJykIjQ19UIo0uZJO9PKM7KdN2H9waQhaG9U+m4KZ+y\nh1ZmG0LZL8A+639PluPGzeMiwQKBgQC6OBoQptWpDPmDHyleO+tjkbO+Jnj/a2T9\nyktGiZ1vPH7ffmGSVc8y1wV3So9ICxAtW489lgN3I7zNgQYxoMJVGmHSRQMxVPjS\nmWXhSpY8cxKnDq9VmWELOepuqw2gvz8I6D+2i3Sn5BY2tJgUJx9dnPn0PXfSCnVc\ntDM2JndYNwKBgA8IpB/iyLPnuXWn/vLUV1JvJjr+uKfrdzRBrYPba0HoRnNk52Zc\n6dZsJUyDgA6r/pPnnETQSEfSVc1dsz0JLGltJ+HUUiUjsL9HxZbKMWooXFL8D8Ox\nd0AX+I3PWqUZ7PSZfkdv/Cm4jWlc32EURGJjRuUXkOw8qs5EoXs9JWWBAoGAYt7e\nl/C8iGEnKQMmh63wsJDvigoLPDHdERKTHQR73k+UXT1QuM1ukWOwt0GTCYE2OZ7f\nUqePd3Y2DDZw/3CHPYAgZ+/UXiDra4Wenhtn4PiH8sLNPPmOmNNLMS8IsUxZP3Vw\nPo+UhH+Je8ou5KRQ9vcdjxwDvjVnUq0qc5nAy8kCgYEAw6J0mHSWoef5MaV8jr+m\ncRqVgHo2JH/K6RkvBT9Rl6rDgP3WnrTTINgrQtpZwogjiSdqQJCkpGplhzhFOay0\nWXQQgfJcim0asyBH8kUO1wI9nm5wRWW6Te0iZR3rDEUGo6CZ6/XV5zUuLWYBFurV\ncnu0NNyskp6VU2XRqIEKdKA=\n-----END PRIVATE KEY-----\n", - "client_email": "data-transfer@wall-of-targets.iam.gserviceaccount.com", - "client_id": "104573732850432607336", - "auth_uri": "https://accounts.google.com/o/oauth2/auth", - "token_uri": "https://accounts.google.com/o/oauth2/token", - "auth_provider_x509_cert_url": "https://www.googleapis.com/oauth2/v1/certs", - "client_x509_cert_url": "https://www.googleapis.com/robot/v1/metadata/x509/data-transfer%40wall-of-targets.iam.gserviceaccount.com" -} diff --git a/src/custom-typings.d.ts b/src/custom-typings.d.ts index 621a416b4..8c409fff6 100644 --- a/src/custom-typings.d.ts +++ b/src/custom-typings.d.ts @@ -60,6 +60,16 @@ declare module 'modern-lru' { declare var ENV: string; declare var HMR: boolean; declare var System: SystemJS; +declare const FIREBASE_CONFIG: FirebaseConfig; + +interface FirebaseConfig { + apiKey: string; + authDomain: string; + databaseURL: string; + projectId: string; + storageBucket: string; + messagingSenderId: string; +} interface SystemJS { import: (path?: string) => Promise; @@ -70,6 +80,7 @@ interface GlobalEnvironment { HMR: boolean; SystemJS: SystemJS; System: SystemJS; + FIREBASE_CONFIG: FirebaseConfig; } interface Es6PromiseLoader { diff --git a/src/environments/environment.e2e.prod.ts b/src/environments/environment.e2e.prod.ts index 71565abc3..2aaa245f2 100644 --- a/src/environments/environment.e2e.prod.ts +++ b/src/environments/environment.e2e.prod.ts @@ -5,6 +5,8 @@ import { Environment } from './model'; enableProdMode(); +// export const ENV_FIREBASE_CONFIG: any = FIREBASE_CONFIG; + export const environment: Environment = { production: true, showDevModule: true, diff --git a/src/environments/environment.prod.ts b/src/environments/environment.prod.ts index d9e846024..7c04dd7b9 100644 --- a/src/environments/environment.prod.ts +++ b/src/environments/environment.prod.ts @@ -5,6 +5,8 @@ import { Environment } from './model'; enableProdMode(); +// export const ENV_FIREBASE_CONFIG: any = FIREBASE_CONFIG; + export const environment: Environment = { production: true, showDevModule: false, diff --git a/src/environments/environment.ts b/src/environments/environment.ts index 714a15b86..4e108594f 100644 --- a/src/environments/environment.ts +++ b/src/environments/environment.ts @@ -7,6 +7,8 @@ import { Environment } from './model'; Error.stackTraceLimit = Infinity; require('zone.js/dist/long-stack-trace-zone'); +export const ENV_FIREBASE_CONFIG: any = FIREBASE_CONFIG; + export const environment = { production: false, @@ -29,16 +31,6 @@ export const environment = { }, ENV_PROVIDERS: [ - ], - - // Initialize Firebase - firebaseConfig: { - apiKey: 'AIzaSyBMS96wgJfydRf7BLDVh4DGtRKAZT8UpTM', - authDomain: 'wall-of-targets.firebaseapp.com', - databaseURL: 'https://wall-of-targets.firebaseio.com', - projectId: 'wall-of-targets', - storageBucket: 'wall-of-targets.appspot.com', - messagingSenderId: '256222676709' - } + ] }; diff --git a/src/index.html b/src/index.html index 84effd651..d71175edb 100644 --- a/src/index.html +++ b/src/index.html @@ -1,40 +1,58 @@ - - - - <%= htmlWebpackPlugin.options.title %> - - - - <% if (webpackConfig.htmlElements.headTags) { %> - - <%= webpackConfig.htmlElements.headTags %> - <% } %> - - - <%= htmlWebpackPlugin.files.webpackManifest %> - - <% if (htmlWebpackPlugin.options.metadata.isDevServer && htmlWebpackPlugin.options.metadata.HMR !== true) { %> - - - <% } %> - - - - - - + + + + <%= htmlWebpackPlugin.options.title %> + + + + <% if (webpackConfig.htmlElements.headTags) { %> + + <%= webpackConfig.htmlElements.headTags %> + <% } %> + + + <%= htmlWebpackPlugin.files.webpackManifest %> + + <% if (htmlWebpackPlugin.options.metadata.isDevServer && htmlWebpackPlugin.options.metadata.HMR !== true) { %> + + + <% } %> + + + <% if (htmlWebpackPlugin.options.gtmKey) { %> + + + + <% } %> + + + + + + + - - - Loading... - - - +<% if (htmlWebpackPlugin.options.gtmKey) { %> + + + +<% } %> + + + Loading... + + +