client/client/app/components/ngbGenesTablePanel/ngbGenesTable/ngbGenesTable.service.js (527 lines of code) (raw):

import {calculateColor} from '../../../shared/utils/calculateColor'; const DEFAULT_GENES_COLUMNS = [ 'chromosome', 'featureName', 'featureId', 'featureType', 'startIndex', 'endIndex', 'strand' ]; const SERVICE_GENES_COLUMNS = ['info']; const OPTIONAL_GENE_COLUMNS = [ 'featureFileId', 'source', 'score', 'frame' ]; const DEFAULT_PREFIX = 'ngb_default_'; const DEFAULT_ORDERBY_GENES_COLUMNS = { [`${DEFAULT_PREFIX}chromosome`]: 'CHROMOSOME_NAME', [`${DEFAULT_PREFIX}featureName`]: 'FEATURE_NAME', [`${DEFAULT_PREFIX}featureId`]: 'FEATURE_ID', [`${DEFAULT_PREFIX}featureType`]: 'FEATURE_TYPE', [`${DEFAULT_PREFIX}startIndex`]: 'START_INDEX', [`${DEFAULT_PREFIX}endIndex`]: 'END_INDEX', [`${DEFAULT_PREFIX}strand`]: 'STRAND', [`${DEFAULT_PREFIX}featureFileId`]: 'FEATURE_FILE_ID', [`${DEFAULT_PREFIX}source`]: 'SOURCE', [`${DEFAULT_PREFIX}score`]: 'SCORE', [`${DEFAULT_PREFIX}frame`]: 'FRAME', }; const SERVER_COLUMN_NAMES = { featureType: 'feature' }; const GENES_COLUMN_TITLES = { [`${DEFAULT_PREFIX}chromosome`]: 'Chr', [`${DEFAULT_PREFIX}featureName`]: 'Name', [`${DEFAULT_PREFIX}featureId`]: 'Id', [`${DEFAULT_PREFIX}featureType`]: 'Type', [`${DEFAULT_PREFIX}startIndex`]: 'Start', [`${DEFAULT_PREFIX}endIndex`]: 'End', [`${DEFAULT_PREFIX}strand`]: 'Strand', [`${DEFAULT_PREFIX}info`]: 'Info', [`${DEFAULT_PREFIX}featureFileId`]: 'FeatureFileId', [`${DEFAULT_PREFIX}source`]: 'Source', [`${DEFAULT_PREFIX}score`]: 'Score', [`${DEFAULT_PREFIX}frame`]: 'Frame' }; const PAGE_SIZE = 100; const MAX_VISIBLE_PAGES = 3; const blockFilterGenesTimeout = 500; export default class ngbGenesTableService { _hasMoreGenes = true; _blockFilterGenes; constructor(dispatcher, genomeDataService, projectContext, uiGridConstants) { this.dispatcher = dispatcher; this.genomeDataService = genomeDataService; this.projectContext = projectContext; this.uiGridConstants = uiGridConstants; this.initEvents(); this.initialize(); } _nextPageMarker = undefined; get nextPageMarker() { return this._nextPageMarker; } set nextPageMarker(value) { this._nextPageMarker = value; } get hasMoreData() { return this._hasMoreGenes; } get genesPageSize() { return PAGE_SIZE; } _genesTableError = null; get genesTableError() { return this._genesTableError; } _orderByGenes = null; get orderByGenes() { return this._orderByGenes; } set orderByGenes(orderByGenes) { this._orderByGenes = orderByGenes; } _pageList = []; get nextPagePointer() { return this._pageList[this.lastPage]; } get prevPagePointer() { return this._pageList[this.firstPage - 2]; } get totalPages() { return this._pageList.length; } get maxVisiblePages() { return MAX_VISIBLE_PAGES; } _firstPage = 0; get firstPage() { return this._firstPage; } set firstPage(value) { this._firstPage = value; } _lastPage = -1; // because initial loading is the same method as scrollDown get lastPage() { return this._lastPage; } set lastPage(value) { this._lastPage = value; } _lastPageLength = 0; get lastPageLength() { return this._lastPageLength; } getPage(page) { return this._pageList[page]; } _geneTypeList = []; _displayGenesFilter; get displayGenesFilter() { if (this._displayGenesFilter !== undefined) { return this._displayGenesFilter; } else { this._displayGenesFilter = JSON.parse(localStorage.getItem('displayGenesFilter')) || false; return this._displayGenesFilter; } } _genesFilter = { additionalFilters: {} }; get genesFilter() { return this._genesFilter; } get genesColumnTitleMap() { return GENES_COLUMN_TITLES; } get orderByColumnsGenes() { return DEFAULT_ORDERBY_GENES_COLUMNS; } get defaultGenesColumns() { return DEFAULT_GENES_COLUMNS.concat(SERVICE_GENES_COLUMNS); } get prefixedDefaultGenesColumns() { return this.prefixColumns(this.defaultGenesColumns); } get nonAttributeColumns() { return this.defaultGenesColumns.concat(OPTIONAL_GENE_COLUMNS); } get prefixedNonAttributeColumns() { return this.prefixColumns(this.nonAttributeColumns); } get genesTableColumns() { if (!localStorage.getItem('genesTableColumns') || localStorage.getItem('genesTableColumns') === '[]') { localStorage.setItem('genesTableColumns', JSON.stringify(this.prefixedDefaultGenesColumns)); } return JSON.parse(localStorage.getItem('genesTableColumns')); } get defaultPrefix() { return DEFAULT_PREFIX; } getColumnOriginalName(column) { return column.startsWith(this.defaultPrefix) ? column.substring(this.defaultPrefix.length) : column; } prefixColumns(columns = []) { const result = []; columns.forEach(c => result.push(this.nonAttributeColumns.includes(c) ? this.defaultPrefix + c : c)); return result; } set genesTableColumns(columns) { localStorage.setItem('genesTableColumns', JSON.stringify(columns || [])); } _optionalGenesColumns = []; get optionalGenesColumns() { return this._optionalGenesColumns; } get geneTypeList() { return this._geneTypeList; } resetPagination() { this._pageList = []; this._firstPage = 0; this._lastPage = -1; } static instance(dispatcher, genomeDataService, projectContext, uiGridConstants) { return new ngbGenesTableService(dispatcher, genomeDataService, projectContext, uiGridConstants); } initEvents() { this.dispatcher.on('genes:reset:filter', this.resetGenesFilter.bind(this)); this.dispatcher.on('reference:change', this.initialize.bind(this)); } initialize() { if (!this.projectContext.reference) { return; } this.genomeDataService.getGenesInfo(this.projectContext.reference.id).then(data => { this._optionalGenesColumns = this.prefixColumns(OPTIONAL_GENE_COLUMNS).concat(data.availableFilters); this.genesTableColumns = this.prefixColumns(DEFAULT_GENES_COLUMNS) .concat(this.genesTableColumns.filter(c => this._optionalGenesColumns.includes(c))) .concat(this.prefixColumns(SERVICE_GENES_COLUMNS)); this.dispatcher.emit('genes:info:loaded'); }); this.genomeDataService.filterGeneValues(this.projectContext.reference.id, 'featureType').then(data => { this._geneTypeList = data.map(type => ({ label: type.toUpperCase(), value: type.toUpperCase() })); this.dispatcher.emit('genes:values:loaded'); }); this.orderByGenes = null; this.clearGenesFilter(); } setDisplayGenesFilter(value, updateScope = true) { if (value !== this._displayGenesFilter) { this._displayGenesFilter = value; localStorage.setItem('displayGenesFilter', JSON.stringify(value)); this.dispatcher.emitSimpleEvent('display:genes:filter', updateScope); } } getRequestFilter(isScrollTop) { const filter = { chromosomeIds: this.genesFilter[`${this.defaultPrefix}chromosome`] || [], startIndex: this.genesFilter[`${this.defaultPrefix}startIndex`], endIndex: this.genesFilter[`${this.defaultPrefix}endIndex`], featureNames: this.genesFilter[`${this.defaultPrefix}featureName`] || [], featureId: this.genesFilter[`${this.defaultPrefix}featureId`], featureTypes: this.genesFilter[`${this.defaultPrefix}featureType`] || [], featureFileId: this.genesFilter[`${this.defaultPrefix}featureFileId`], additionalFilters: this.genesFilter.additionalFilters || {}, attributesFields: this.genesTableColumns.filter(c => !this.prefixedNonAttributeColumns.includes(c)), pageSize: this.genesPageSize, pointer: isScrollTop ? this.prevPagePointer : this.nextPagePointer, orderBy: (this.orderByGenes || []).map(config => ({ field: config.field, desc: !config.ascending })) }; if (this.genesFilter[`${this.defaultPrefix}frame`]) { filter.frames = [this.genesFilter[`${this.defaultPrefix}frame`]]; } if (this.genesFilter[`${this.defaultPrefix}source`]) { filter.sources = [this.genesFilter[`${this.defaultPrefix}source`]]; } if (this.genesFilter[`${this.defaultPrefix}strand`]) { switch (this.genesFilter[`${this.defaultPrefix}strand`]) { case '+': { filter.strands = ['POSITIVE']; break; } case '-': { filter.strands = ['NEGATIVE']; break; } } } if (this.genesFilter[`${this.defaultPrefix}score`]) { filter.score = { left: this.genesFilter[`${this.defaultPrefix}score`][0], right: this.genesFilter[`${this.defaultPrefix}score`][1] }; } const tracks = (this.projectContext.tracks || []).filter(track => track.format === 'GENE'); if (tracks.length) { filter.geneFileIdsByProject = {}; tracks.forEach(track => { let datasetId = track.project ? track.project.id.toString() : undefined; if (!datasetId && track.projectId) { if (Number.isNaN(Number(track.projectId))) { const [dataset] = (this.projectContext.datasets || []) .filter(d => d.name === track.projectId); if (dataset) { datasetId = dataset.id; } } else { datasetId = track.projectId; } } if (datasetId) { if (!filter.geneFileIdsByProject[datasetId]) { filter.geneFileIdsByProject[datasetId] = []; } filter.geneFileIdsByProject[datasetId].push(track.id); } }); } return filter; } async loadGenes(reference, isScrollTop) { const filter = this.getRequestFilter(isScrollTop); this.refreshGenesFilterEmptyStatus(); try { const data = await this.genomeDataService.loadGenes( reference, filter ); this._genesTableError = null; this._hasMoreGenes = !!data.pointer; if (isScrollTop) { this._pageList.pop(); } else if (data.pointer) { this._pageList.push(data.pointer); } this._lastPageLength = (data.entries || []).length; this.nextPageMarker = data.pointer; return (data.entries || []) .map(this._formatServerToClient.bind(this)) .map(feature => ({...feature, referenceId: reference})); } catch (e) { this._hasMoreGenes = false; this._genesTableError = e.message; return []; } } downloadFile(reference, format, includeHeader) { const exportFields = this.genesTableColumns .filter(column => !this.prefixColumns(SERVICE_GENES_COLUMNS).includes(column)) .map(column => { const c = this.getColumnOriginalName(column); return SERVER_COLUMN_NAMES[c] || c; }); const filter = this.getRequestFilter(false); delete filter.pointer; return this.genomeDataService.downloadGenes( reference, { format: format, includeHeader: includeHeader }, { exportFields: exportFields, ...filter } ); } getGenesGridColumns() { const infoCell = require('./ngbGenesTable_info.tpl.html'); const headerCells = require('./ngbGenesTable_header.tpl.html'); const result = []; const columnsList = this.genesTableColumns; for (let i = 0; i < columnsList.length; i++) { let sortDirection = 0; let sortingPriority = 0; let columnSettings = null; const column = columnsList[i]; if (this.orderByGenes) { const fieldName = (this.orderByColumnsGenes[column] || column); const [columnSortingConfiguration] = this.orderByGenes.filter(o => o.field === fieldName); if (columnSortingConfiguration) { sortingPriority = this.orderByGenes.indexOf(columnSortingConfiguration); sortDirection = columnSortingConfiguration.ascending ? 'asc' : 'desc'; } } switch (column) { case `${this.defaultPrefix}info`: { columnSettings = { cellTemplate: infoCell, enableSorting: false, enableHiding: false, enableFiltering: false, enableColumnMenu: false, field: '', maxWidth: 70, minWidth: 60, name: this.genesColumnTitleMap[column] }; break; } case `${this.defaultPrefix}featureType`: { columnSettings = { cellTemplate: `<div ng-if="row.entity.feature" class="md-label variation-type" ng-style="grid.appScope.$ctrl.getStyle(COL_FIELD)" ng-class="COL_FIELD CUSTOM_FILTERS" >{{row.entity.feature}}</div>`, enableHiding: false, field: `${this.defaultPrefix}featureType`, filter: { selectOptions: this.geneTypeList, term: '', type: this.uiGridConstants.filter.SELECT }, headerCellTemplate: headerCells, headerTooltip: 'Type', maxWidth: 104, minWidth: 104, displayName: 'Type', filterApplied: () => this.genesFieldIsFiltered(column), menuItems: [ { title: 'Clear column filter', action: () => this.clearGeneFieldFilter(column), shown: () => this.genesFieldIsFiltered(column) } ] }; break; } default: { const displayName = this.getColumnDisplayName(column); columnSettings = { enableHiding: !this.prefixedDefaultGenesColumns.includes(column), enableFiltering: true, enableSorting: true, field: column, headerCellTemplate: headerCells, headerTooltip: displayName, minWidth: 40, displayName: displayName, filterApplied: () => this.genesFieldIsFiltered(column), menuItems: [ { title: 'Clear column filter', action: () => this.clearGeneFieldFilter(column), shown: () => this.genesFieldIsFiltered(column) } ], width: '*' }; break; } } if (columnSettings) { if (sortDirection) { columnSettings.sort = { direction: sortDirection, priority: sortingPriority }; } result.push(columnSettings); } } return result; } getColumnDisplayName(column) { let result = this.genesColumnTitleMap[column] || column; if (!this.prefixedNonAttributeColumns.includes(column)) { result += ' (attr)'; } return result; } _formatServerToClient(search) { const result = {}; for (const key in search) { if (search.hasOwnProperty(key)) { result[this.nonAttributeColumns.includes(key) ? this.defaultPrefix + key : key] = search[key]; } } delete result.attributes; result[`${this.defaultPrefix}chromosome`] = search.chromosome ? search.chromosome.name : undefined; return { ...result, ...search.attributes, chromosomeObj: search.chromosome }; } refreshGenesFilterEmptyStatus() { const {additionalFilters, ...defaultFilters} = this.genesFilter; const additionalFiltersAreEmpty = Object.entries(additionalFilters).every(field => { if (typeof field[1] === 'object') { return !Object.keys(field[1]).length; } else { return field[1] === undefined; } }); const defaultFiltersAreEmpty = Object.entries(defaultFilters).every(field => { if (typeof field[1] === 'object') { return !Object.keys(field[1]).length; } else { return field[1] === undefined; } }); this.projectContext.genesFilterIsDefault = additionalFiltersAreEmpty && defaultFiltersAreEmpty; } genesFieldIsFiltered(fieldName) { return this.prefixedDefaultGenesColumns.includes(fieldName) ? this.genesFilter[fieldName] !== undefined : this.genesFilter.additionalFilters[fieldName] !== undefined; } clearGeneFieldFilter(fieldName) { if (this.prefixedDefaultGenesColumns.includes(fieldName)) { this.genesFilter[fieldName] = undefined; } else { this.genesFilter.additionalFilters[fieldName] = undefined; } this.dispatcher.emit('genes:refresh'); } clearGenesFilter() { if (this._blockFilterGenes) { clearTimeout(this._blockFilterGenes); this._blockFilterGenes = null; } this._hasMoreGenes = true; this._genesFilter = { additionalFilters: {} }; this.dispatcher.emit('genes:refresh'); this._blockFilterGenes = setTimeout(() => { this._blockFilterGenes = null; }, blockFilterGenesTimeout); } canScheduleFilterGenes() { return !this._blockFilterGenes; } scheduleFilterGenes() { if (this._blockFilterGenes) { return; } this.dispatcher.emit('genes:refresh'); } resetGenesFilter() { if (!this.projectContext.genesFilterIsDefault) { this.clearGenesFilter(); } } determineDarkness(color) { const parsedColor = /^#?([a-f\d]{2})([a-f\d]{2})([a-f\d]{2})$/i.exec(color); const r = parseInt(parsedColor[1], 16); const g = parseInt(parsedColor[2], 16); const b = parseInt(parsedColor[3], 16); // HSP (Highly Sensitive Poo) equation from http://alienryderflex.com/hsp.html const hsp = Math.sqrt( 0.299 * (r * r) + 0.587 * (g * g) + 0.114 * (b * b) ); // Using the HSP value, determine whether the color is light or dark return hsp > 127.5 ? '#000' : '#fff'; } getStyle(context) { return str => { const color = calculateColor(str); return { 'background-color': color, 'color': context.determineDarkness(color) }; }; } }