diff --git a/frontend/app/components/draw-map.js b/frontend/app/components/draw-map.js index 4164f4e12..53dd2450f 100644 --- a/frontend/app/components/draw-map.js +++ b/frontend/app/components/draw-map.js @@ -18,7 +18,7 @@ scheduleIntoAnimationFrame = scheduleFrame.default; import config from '../config/environment'; import { EventedListener } from '../utils/eventedListener'; -import { chrData } from '../utils/utility-chromosome'; +import { chrData, cmNameAdd } from '../utils/utility-chromosome'; import { eltWidthResizable, eltResizeToAvailableWidth, noShiftKeyfilter, eltClassName, tabActive, inputRangeValue, expRange } from '../utils/domElements'; import { /*fromSelectionArray,*/ logSelectionLevel, logSelection, logSelectionNodes, selectImmediateChildNodes } from '../utils/log-selection'; import { parseOptions } from '../utils/common/strings'; @@ -484,7 +484,10 @@ export default Ember.Component.extend(Ember.Evented, { axisName2MapChr, axisStackChanged, axisScaleChanged, - axisRange2Domain + axisRange2Domain, + cmNameAdd, + makeMapChrName, + axisIDAdd }; console.log('draw-map stacks', stacks); this.set('stacks', stacks); @@ -1176,8 +1179,21 @@ export default Ember.Component.extend(Ember.Evented, { sBlock = oa.stacks.blocks[d], addedBlock = ! sBlock; if (! sBlock) { - oa.stacks.blocks[d] = sBlock = new Block(dBlock); - dBlock.set('view', sBlock); + /** sBlock may already be associated with dBlock */ + let view = dBlock.get('view'); + sBlock = view || new Block(dBlock); + oa.stacks.blocks[d] = sBlock; + if (! view) { + /* this .set() was getting assertion fail (https://github.com/emberjs/ember.js/issues/13948), + * hence the catch and trace; this has been resolved by not displaying .view in .hbs + */ + try { + dBlock.set('view', sBlock); + } + catch (exc) { + console.log('ensureAxis', d, dBlock, sBlock, addedBlock, view, oa.stacks.blocks, exc.stack || exc); + } + } } let s = Stacked.getStack(d); if (trace_stack > 1) diff --git a/frontend/app/components/draw/axis-1d.js b/frontend/app/components/draw/axis-1d.js index 35212f050..257b2466a 100644 --- a/frontend/app/components/draw/axis-1d.js +++ b/frontend/app/components/draw/axis-1d.js @@ -15,6 +15,9 @@ import { selectAxis } from '../../utils/draw/stacksAxes'; import { breakPoint } from '../../utils/breakPoint'; import { configureHorizTickHover } from '../../utils/hover'; import { getAttrOrCP } from '../../utils/ember-devel'; +import { intervalExtent } from '../../utils/interval-calcs'; +import { updateDomain } from '../../utils/stacksLayout'; + /* global d3 */ /* global require */ @@ -310,6 +313,75 @@ export default Ember.Component.extend(Ember.Evented, AxisEvents, AxisPosition, { } return dataBlocks; }), + /** @return the domains of the data blocks of this axis. + * The result does not contain a domain for data blocks with no features loaded. + */ + dataBlocksDomains : Ember.computed('dataBlocks.@each.featuresDomain', function () { + let dataBlocks = this.get('dataBlocks'), + dataBlockDomains = dataBlocks.map(function (b) { return b.get('featuresDomain'); } ) + /* featuresDomain() will return undefined when block has no features loaded. */ + .filter(d => d !== undefined); + return dataBlockDomains; + }), + referenceBlock : Ember.computed.alias('axisS.referenceBlock'), + /** @return the domains of all the blocks of this axis, including the reference block if any. + * @description related @see axesDomains() (draw/block-adj) + */ + blocksDomains : Ember.computed('dataBlocksDomains.[]', 'referenceBlock.range', function () { + let + /* alternative : + * dataBlocksMap = this.get('blockService.dataBlocks'), + * axisId = this.get('axis.id'), + * datablocks = dataBlocksMap.get(axisId), + */ + /** see also domainCalc(), blocksUpdateDomain() */ + blocksDomains = this.get('dataBlocksDomains'), + /** equivalent : Stacked:referenceDomain() */ + referenceRange = this.get('referenceBlock.range'); + if (referenceRange) { + console.log('referenceRange', referenceRange, blocksDomains); + blocksDomains.push(referenceRange); + } + return blocksDomains; + }), + /** @return the union of blocksDomains[], i.e. the interval which contains all + * the blocksDomains intervals. + */ + blocksDomain : Ember.computed('blocksDomains.[]', function () { + let + blocksDomains = this.get('blocksDomains'), + domain = intervalExtent(blocksDomains); + console.log('blocksDomain', blocksDomains, domain); + return domain; + }), + blocksDomainEffect : Ember.computed('blocksDomain', function () { + let domain = this.get('blocksDomain'), + /** if domain is [0,0] or [false, false] then consider that undefined. */ + domainDefined = domain && domain.length && (domain[0] || domain[1]); + if (domainDefined && ! this.get('zoomed')) + /* defer setting yDomain to the end of this render, to avoid assert fail + * re. change of domainChanged, refn issues/13948; + * that also breaks progressive loading and axis & path updates from zoom. + */ + Ember.run.later(() => { + this.setDomain(domain); + }); + }), + /** same as domainChanged, not used. */ + domainEffect : Ember.computed('domain', function () { + let domain = this.get('domain'); + if (domain) { + /* Similar to this.updateDomain(), defined in axis-position.js, */ + let axisS = this.get('axisS'); + console.log('domainEffect', domain, axisS); + if (axisS) { + let y = axisS.getY(), ys = axisS.ys; + updateDomain(axisS.y, axisS.ys, axisS); + } + } + return domain; + }), + /** count of features of .dataBlocks */ featureLength : Ember.computed('dataBlocks.@each.featuresLength', function () { let dataBlocks = this.get('dataBlocks'), @@ -324,6 +396,7 @@ export default Ember.Component.extend(Ember.Evented, AxisEvents, AxisPosition, { */ featureLengthEffect : Ember.computed('featureLength', 'axisS', function () { let featureLength = this.get('featureLength'); + this.renderTicksDebounce(); let axisApi = stacks.oa.axisApi, /** defined after first brushHelper() call. */ @@ -394,20 +467,24 @@ export default Ember.Component.extend(Ember.Evented, AxisEvents, AxisPosition, { /** position as of the last zoom. */ domain : Ember.computed.alias('currentPosition.yDomain'), - /** this is an alias of .domain, but it updates when the array elements update. */ + /** Updates when the array elements of .domain[] update. + * @return undefined; value is unused. + */ domainChanged : Ember.computed( 'domain.0', 'domain.1', function () { let domain = this.get('domain'); - // use the VLinePosition:toString() for the position-s - console.log('domainChanged', domain, this.get('axisS'), ''+this.get('currentPosition'), ''+this.get('lastDrawn')); - // this.notifyChanges(); - if (! this.get('axisS')) - console.log('domainChanged() no axisS yet', domain, this.get('axis.id')); - else - this.updateAxis(); - - return domain; + // domain is initially undefined + if (domain) { + // use the VLinePosition:toString() for the position-s + console.log('domainChanged', domain, this.get('axisS'), ''+this.get('currentPosition'), ''+this.get('lastDrawn')); + // this.notifyChanges(); + if (! this.get('axisS')) + console.log('domainChanged() no axisS yet', domain, this.get('axis.id')); + else + this.updateAxis(); + } + return undefined; }), notifyChanges() { let axisID = this.get('axis.id'); diff --git a/frontend/app/components/draw/block-adj.js b/frontend/app/components/draw/block-adj.js index edf042416..c2e6c8f52 100644 --- a/frontend/app/components/draw/block-adj.js +++ b/frontend/app/components/draw/block-adj.js @@ -401,6 +401,20 @@ export default Ember.Component.extend(Ember.Evented, AxisEvents, { .attr("d", function(d) { return d.pathU() /*get('pathU')*/; }); }, + /** Call updateAxis() for the axes which bound this block-adj. + * See comment in updatePathsPositionDebounce(). + */ + updateAxesScale() { + let + axes = this.get('axes'), + /** reference blocks */ + axesBlocks = axes.mapBy('blocks'); + console.log('updateAxesScale', axesBlocks.map((blocks) => blocks.mapBy('axisName'))); + axesBlocks.forEach(function (blocks) { + blocks[0].axis.axis1d.updateAxis(); + }); + }, + /*--------------------------------------------------------------------------*/ axesDomains : Ember.computed.alias('blockAdj.axesDomains'), @@ -419,6 +433,14 @@ export default Ember.Component.extend(Ember.Evented, AxisEvents, { domainsChanged = this.get('axesDomains'); console.log('updatePathsPositionDebounce', this.get('blockAdjId'), heightChanged, count, domainsChanged); this.updatePathsPosition(); + + /* this update is an alternative trigger for updating the axes ticks and + * scale when their domains change, e.g. when loaded features extend a + * block's domain. The solution used instead is the ComputedProperty + * side-effect axis-1d : domainChanged(), which is a similar approach, but + * it localises the dependencies to a single axis whereas this would + * duplicate updates. */ + // this.updateAxesScale(); return count; }), diff --git a/frontend/app/mixins/axis-position.js b/frontend/app/mixins/axis-position.js index 4856facf3..a285cb99f 100644 --- a/frontend/app/mixins/axis-position.js +++ b/frontend/app/mixins/axis-position.js @@ -49,6 +49,7 @@ export default Mixin.create({ if (! axisS) { /** This replicates the role of axis-1d.js:axisS(); this will be solved * when Stacked is created and owned by axis-1d. + * (also : now using ensureAxis() in data/block.js : axesBlocks()) */ let axisName = this.get('axis.id'); axisS = Stacked.getAxis(axisName); diff --git a/frontend/app/models/block.js b/frontend/app/models/block.js index ca0493e04..2cebf3663 100644 --- a/frontend/app/models/block.js +++ b/frontend/app/models/block.js @@ -3,6 +3,9 @@ import DS from 'ember-data'; import attr from 'ember-data/attr'; // import { PartialModel, partial } from 'ember-data-partial-model/utils/model'; +import { intervalMerge } from '../utils/interval-calcs'; + + export default DS.Model.extend({ datasetId: DS.belongsTo('dataset'), annotations: DS.hasMany('annotation', { async: false }), @@ -71,6 +74,21 @@ export default DS.Model.extend({ console.log('featuresLength', featuresLength, this.get('id')); return featuresLength; }), + /** @return undefined if ! features.length, + * otherwise [min, max] of block's feature.value + */ + featuresDomain : Ember.computed('features.[]', function () { + let featuresDomain, features = this.get('features'); + if (features.length) { + featuresDomain = features + .mapBy('value') + .reduce(intervalMerge, []); + + console.log('featuresDomain', featuresDomain, this.get('id')); + } + return featuresDomain; + }), + isChartable : Ember.computed('datasetId.tags', function () { let tags = this.get('datasetId.tags'), diff --git a/frontend/app/models/feature.js b/frontend/app/models/feature.js index 543d75cce..8d7ffabd2 100644 --- a/frontend/app/models/feature.js +++ b/frontend/app/models/feature.js @@ -1,3 +1,4 @@ +import { computed } from '@ember/object'; import DS from 'ember-data'; import attr from 'ember-data/attr'; //import Fragment from 'model-fragments/fragment'; @@ -10,5 +11,21 @@ export default DS.Model.extend({ value: attr(), range: attr(), parentId: DS.belongsTo('feature', {inverse: 'features'}), - features: DS.hasMany('feature', {inverse: 'parentId'}) + features: DS.hasMany('feature', {inverse: 'parentId'}), + + /*--------------------------------------------------------------------------*/ + + /** feature can have a direction, i.e. (value[0] > value[1]) + * For domain calculation, the ordered value is required. + */ + valueOrdered : computed('value', function () { + let value = this.get('value'); + if (value[0] > value[1]) { + let value = [value[1], value[0]]; + } + return value; + }) + + /*--------------------------------------------------------------------------*/ + }); diff --git a/frontend/app/services/data/block.js b/frontend/app/services/data/block.js index e0ef28030..1e2a73068 100644 --- a/frontend/app/services/data/block.js +++ b/frontend/app/services/data/block.js @@ -266,9 +266,10 @@ export default Service.extend(Ember.Evented, { return records; // .toArray() }), viewed: Ember.computed( + 'blockValues.[]', 'blockValues.@each.isViewed', function() { - let records = this.get('blockValues') + let records = this.get('store').peekAll('block') // this.get('blockValues') .filterBy('isViewed', true); if (trace_block) console.log('viewed', records.toArray()); @@ -374,8 +375,10 @@ export default Service.extend(Ember.Evented, { function (map, block) { let axis = block.get('axis'); if (! axis) { + let oa = stacks.oa, axisApi = oa.axisApi; + axisApi.cmNameAdd(oa, block); console.log('axesBlocks ensureAxis', block.get('id')); - stacks.oa.axisApi.ensureAxis(block.get('id')); + axisApi.ensureAxis(block.get('id')); stacks.forEach(function(s){s.log();}); axis = block.get('axis'); console.log('axesBlocks', axis); diff --git a/frontend/app/templates/components/draw/axes-1d.hbs b/frontend/app/templates/components/draw/axes-1d.hbs index 257a29832..338f33f03 100644 --- a/frontend/app/templates/components/draw/axes-1d.hbs +++ b/frontend/app/templates/components/draw/axes-1d.hbs @@ -1,6 +1,6 @@