@apollo-annotation/jbrowse-plugin-apollo 0.3.2 → 0.3.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -101,7 +101,7 @@ var ExpandMoreIcon__default = /*#__PURE__*/_interopDefaultLegacy(ExpandMoreIcon)
101
101
  var ErrorIcon__default = /*#__PURE__*/_interopDefaultLegacy(ErrorIcon);
102
102
  var SaveIcon__default = /*#__PURE__*/_interopDefaultLegacy(SaveIcon);
103
103
 
104
- var version = "0.3.2";
104
+ var version = "0.3.4";
105
105
 
106
106
  const ApolloConfigSchema = configuration.ConfigurationSchema('ApolloInternetAccount', {
107
107
  baseURL: {
@@ -1297,6 +1297,7 @@ const OntologyRecordType = mobxStateTree.types
1297
1297
  })
1298
1298
  .volatile((_self) => ({
1299
1299
  dataStore: undefined,
1300
+ startedEquivalentTypeRequests: new Set(),
1300
1301
  }))
1301
1302
  .actions((self) => ({
1302
1303
  /** does nothing, just used to access the model to force its lifecycle hooks to run */
@@ -1320,12 +1321,33 @@ const OntologyRecordType = mobxStateTree.types
1320
1321
  if (!self.dataStore) {
1321
1322
  return;
1322
1323
  }
1324
+ if (self.startedEquivalentTypeRequests.has(type)) {
1325
+ return;
1326
+ }
1327
+ self.startedEquivalentTypeRequests.add(type);
1323
1328
  const terms = (yield self.dataStore.getTermsWithLabelOrSynonym(type));
1324
1329
  const equivalents = terms
1325
1330
  .map((term) => term.lbl)
1326
1331
  .filter((term) => term != undefined);
1327
1332
  self.setEquivalentTypes(type, equivalents);
1328
1333
  }),
1334
+ }))
1335
+ .actions((self) => ({
1336
+ afterCreate() {
1337
+ mobx.autorun((reaction) => {
1338
+ if (!self.dataStore) {
1339
+ return;
1340
+ }
1341
+ void self.loadEquivalentTypes('gene');
1342
+ void self.loadEquivalentTypes('transcript');
1343
+ void self.loadEquivalentTypes('CDS');
1344
+ void self.loadEquivalentTypes('mRNA');
1345
+ reaction.dispose();
1346
+ });
1347
+ },
1348
+ setEquivalentTypes(type, equivalentTypes) {
1349
+ self.equivalentTypes.set(type, equivalentTypes);
1350
+ },
1329
1351
  }))
1330
1352
  .views((self) => ({
1331
1353
  isTypeOf(queryType, typeOf) {
@@ -5828,7 +5850,6 @@ const ApolloTranscriptDetailsModel = mobxStateTree.types
5828
5850
  })),
5829
5851
  assembly: mobxStateTree.types.string,
5830
5852
  refName: mobxStateTree.types.string,
5831
- changeManager: mobxStateTree.types.frozen(),
5832
5853
  })
5833
5854
  .volatile(() => ({
5834
5855
  tryReload: undefined,
@@ -6811,13 +6832,13 @@ const FilterFeatures = mobxReact.observer(function FilterFeatures({ featureTypes
6811
6832
  React__default["default"].createElement(material.DialogContent, null,
6812
6833
  React__default["default"].createElement(material.DialogContentText, null, "Select the feature types you want to display in the apollo track"),
6813
6834
  React__default["default"].createElement(material.Grid2, { container: true, spacing: 2 },
6814
- React__default["default"].createElement(material.Grid2, null,
6835
+ React__default["default"].createElement(material.Grid2, { size: 8 },
6815
6836
  React__default["default"].createElement(OntologyTermAutocomplete, { session: session, ontologyName: "Sequence Ontology", style: { width: '100%' }, value: type, filterTerms: isOntologyClass, renderInput: (params) => (React__default["default"].createElement(material.TextField, { ...params, label: "Feature type", variant: "outlined", fullWidth: true })), onChange: (oldValue, newValue) => {
6816
6837
  if (newValue) {
6817
6838
  handleChange(newValue);
6818
6839
  }
6819
6840
  } })),
6820
- React__default["default"].createElement(material.Grid2, null,
6841
+ React__default["default"].createElement(material.Grid2, { size: 4 },
6821
6842
  React__default["default"].createElement(material.Button, { variant: "contained", onClick: handleAddFeatureType, disabled: !type, style: { marginTop: 9 }, size: "medium" }, "Add"))),
6822
6843
  selectedFeatureTypes.length > 0 && (React__default["default"].createElement("div", null,
6823
6844
  React__default["default"].createElement("hr", null),
@@ -6838,6 +6859,7 @@ function baseModelFactory(_pluginManager, configSchema) {
6838
6859
  table: false,
6839
6860
  heightPreConfig: mobxStateTree.types.maybe(mobxStateTree.types.refinement('displayHeight', mobxStateTree.types.number, (n) => n >= minDisplayHeight)),
6840
6861
  filteredFeatureTypes: mobxStateTree.types.array(mobxStateTree.types.string),
6862
+ loadingState: false,
6841
6863
  })
6842
6864
  .views((self) => {
6843
6865
  const { configuration, renderProps: superRenderProps } = self;
@@ -6870,6 +6892,9 @@ function baseModelFactory(_pluginManager, configSchema) {
6870
6892
  }
6871
6893
  return 300;
6872
6894
  },
6895
+ get loading() {
6896
+ return self.loadingState;
6897
+ },
6873
6898
  }))
6874
6899
  .views((self) => ({
6875
6900
  get rendererTypeName() {
@@ -6955,6 +6980,9 @@ function baseModelFactory(_pluginManager, configSchema) {
6955
6980
  updateFilteredFeatureTypes(types) {
6956
6981
  self.filteredFeatureTypes = mobxStateTree.cast(types);
6957
6982
  },
6983
+ setLoading(loading) {
6984
+ self.loadingState = loading;
6985
+ },
6958
6986
  }))
6959
6987
  .views((self) => {
6960
6988
  const { filteredFeatureTypes, trackMenuItems: superTrackMenuItems } = self;
@@ -7025,7 +7053,14 @@ function baseModelFactory(_pluginManager, configSchema) {
7025
7053
  if (!self.lgv.initialized || self.regionCannotBeRendered()) {
7026
7054
  return;
7027
7055
  }
7028
- void self.session.apolloDataStore.loadFeatures(self.regions);
7056
+ self.setLoading(true);
7057
+ void self.session.apolloDataStore
7058
+ .loadFeatures(self.regions)
7059
+ .then(() => {
7060
+ setTimeout(() => {
7061
+ self.setLoading(false);
7062
+ }, 1000);
7063
+ });
7029
7064
  if (self.lgv.bpPerPx <= 3) {
7030
7065
  void self.session.apolloDataStore.loadRefSeq(self.regions);
7031
7066
  }
@@ -7454,7 +7489,6 @@ function draw$1(ctx, feature, row, stateModel, displayedRegionIndex) {
7454
7489
  const displayedRegion = displayedRegions[displayedRegionIndex];
7455
7490
  const { refName, reversed } = displayedRegion;
7456
7491
  const rowHeight = apolloRowHeight;
7457
- const exonHeight = Math.round(0.6 * rowHeight);
7458
7492
  const cdsHeight = Math.round(0.9 * rowHeight);
7459
7493
  const { children, min, strand } = feature;
7460
7494
  if (!children) {
@@ -7488,27 +7522,20 @@ function draw$1(ctx, feature, row, stateModel, displayedRegionIndex) {
7488
7522
  currentRow += 1;
7489
7523
  continue;
7490
7524
  }
7491
- const { children: childrenOfTranscript, min } = transcript;
7492
- if (!childrenOfTranscript) {
7525
+ const { children: transcriptChildren } = transcript;
7526
+ if (!transcriptChildren) {
7493
7527
  continue;
7494
7528
  }
7495
- for (const [, cds] of childrenOfTranscript) {
7496
- if (!featureTypeOntology.isTypeOf(cds.type, 'CDS')) {
7529
+ const cdsCount = getCDSCount(transcript, featureTypeOntology);
7530
+ for (const [, childFeature] of transcriptChildren) {
7531
+ if (!featureTypeOntology.isTypeOf(childFeature.type, 'CDS')) {
7497
7532
  continue;
7498
7533
  }
7499
- const minX = (lgv.bpToPx({
7500
- refName,
7501
- coord: min,
7502
- regionNumber: displayedRegionIndex,
7503
- })?.offsetPx ?? 0) - offsetPx;
7504
- const widthPx = transcript.length / bpPerPx;
7505
- const startPx = reversed ? minX - widthPx : minX;
7506
- const height = Math.round((currentRow + 1 / 2) * rowHeight) + row * rowHeight;
7507
- ctx.strokeStyle = theme?.palette.text.primary ?? 'black';
7508
- ctx.beginPath();
7509
- ctx.moveTo(startPx, height);
7510
- ctx.lineTo(startPx + widthPx, height);
7511
- ctx.stroke();
7534
+ drawLine(ctx, stateModel, displayedRegionIndex, row, transcript, currentRow);
7535
+ currentRow += 1;
7536
+ }
7537
+ if (cdsCount === 0) {
7538
+ drawLine(ctx, stateModel, displayedRegionIndex, row, transcript, currentRow);
7512
7539
  currentRow += 1;
7513
7540
  }
7514
7541
  }
@@ -7522,83 +7549,125 @@ function draw$1(ctx, feature, row, stateModel, displayedRegionIndex) {
7522
7549
  currentRow += 1;
7523
7550
  continue;
7524
7551
  }
7525
- for (const cdsRow of child.cdsLocations) {
7526
- const { _id, children: childrenOfTranscript } = child;
7527
- if (!childrenOfTranscript) {
7528
- continue;
7529
- }
7530
- for (const [, exon] of childrenOfTranscript) {
7531
- if (!featureTypeOntology.isTypeOf(exon.type, 'exon')) {
7552
+ const cdsCount = getCDSCount(child, featureTypeOntology);
7553
+ if (cdsCount != 0) {
7554
+ for (const cdsRow of child.cdsLocations) {
7555
+ const { _id, children: transcriptChildren } = child;
7556
+ if (!transcriptChildren) {
7532
7557
  continue;
7533
7558
  }
7534
- const minX = (lgv.bpToPx({
7535
- refName,
7536
- coord: exon.min,
7537
- regionNumber: displayedRegionIndex,
7538
- })?.offsetPx ?? 0) - offsetPx;
7539
- const widthPx = exon.length / bpPerPx;
7540
- const startPx = reversed ? minX - widthPx : minX;
7541
- const top = (row + currentRow) * rowHeight;
7542
- const exonTop = top + (rowHeight - exonHeight) / 2;
7543
- ctx.fillStyle = theme?.palette.text.primary ?? 'black';
7544
- ctx.fillRect(startPx, exonTop, widthPx, exonHeight);
7545
- if (widthPx > 2) {
7546
- ctx.clearRect(startPx + 1, exonTop + 1, widthPx - 2, exonHeight - 2);
7547
- ctx.fillStyle =
7548
- apolloSelectedFeature && exon._id === apolloSelectedFeature._id
7549
- ? 'rgb(0,0,0)'
7550
- : 'rgb(211,211,211)';
7551
- ctx.fillRect(startPx + 1, exonTop + 1, widthPx - 2, exonHeight - 2);
7552
- if (forwardFill && backwardFill && strand) {
7553
- const reversal = reversed ? -1 : 1;
7554
- const [topFill, bottomFill] = strand * reversal === 1
7555
- ? [forwardFill, backwardFill]
7556
- : [backwardFill, forwardFill];
7557
- ctx.fillStyle = topFill;
7558
- ctx.fillRect(startPx + 1, exonTop + 1, widthPx - 2, (exonHeight - 2) / 2);
7559
- ctx.fillStyle = bottomFill;
7560
- ctx.fillRect(startPx + 1, exonTop + 1 + (exonHeight - 2) / 2, widthPx - 2, (exonHeight - 2) / 2);
7559
+ for (const [, exon] of transcriptChildren) {
7560
+ if (!featureTypeOntology.isTypeOf(exon.type, 'exon')) {
7561
+ continue;
7562
+ }
7563
+ drawExon(ctx, stateModel, displayedRegionIndex, row, exon, currentRow, strand, forwardFill, backwardFill);
7564
+ }
7565
+ for (const cds of cdsRow) {
7566
+ const cdsWidthPx = (cds.max - cds.min) / bpPerPx;
7567
+ const minX = (lgv.bpToPx({
7568
+ refName,
7569
+ coord: cds.min,
7570
+ regionNumber: displayedRegionIndex,
7571
+ })?.offsetPx ?? 0) - offsetPx;
7572
+ const cdsStartPx = reversed ? minX - cdsWidthPx : minX;
7573
+ ctx.fillStyle = theme?.palette.text.primary ?? 'black';
7574
+ const cdsTop = (row + currentRow) * rowHeight + (rowHeight - cdsHeight) / 2;
7575
+ ctx.fillRect(cdsStartPx, cdsTop, cdsWidthPx, cdsHeight);
7576
+ if (cdsWidthPx > 2) {
7577
+ ctx.clearRect(cdsStartPx + 1, cdsTop + 1, cdsWidthPx - 2, cdsHeight - 2);
7578
+ const frame = util.getFrame(cds.min, cds.max, child.strand ?? 1, cds.phase);
7579
+ const frameColor = theme?.palette.framesCDS.at(frame)?.main;
7580
+ const cdsColorCode = frameColor ?? 'rgb(171,71,188)';
7581
+ ctx.fillStyle =
7582
+ apolloSelectedFeature && _id === apolloSelectedFeature._id
7583
+ ? 'rgb(0,0,0)'
7584
+ : cdsColorCode;
7585
+ ctx.fillStyle = cdsColorCode;
7586
+ ctx.fillRect(cdsStartPx + 1, cdsTop + 1, cdsWidthPx - 2, cdsHeight - 2);
7587
+ if (forwardFill && backwardFill && strand) {
7588
+ const reversal = reversed ? -1 : 1;
7589
+ const [topFill, bottomFill] = strand * reversal === 1
7590
+ ? [forwardFill, backwardFill]
7591
+ : [backwardFill, forwardFill];
7592
+ ctx.fillStyle = topFill;
7593
+ ctx.fillRect(cdsStartPx + 1, cdsTop + 1, cdsWidthPx - 2, (cdsHeight - 2) / 2);
7594
+ ctx.fillStyle = bottomFill;
7595
+ ctx.fillRect(cdsStartPx + 1, cdsTop + (cdsHeight - 2) / 2, cdsWidthPx - 2, (cdsHeight - 2) / 2);
7596
+ }
7561
7597
  }
7562
7598
  }
7599
+ currentRow += 1;
7563
7600
  }
7564
- for (const cds of cdsRow) {
7565
- const cdsWidthPx = (cds.max - cds.min) / bpPerPx;
7566
- const minX = (lgv.bpToPx({
7567
- refName,
7568
- coord: cds.min,
7569
- regionNumber: displayedRegionIndex,
7570
- })?.offsetPx ?? 0) - offsetPx;
7571
- const cdsStartPx = reversed ? minX - cdsWidthPx : minX;
7572
- ctx.fillStyle = theme?.palette.text.primary ?? 'black';
7573
- const cdsTop = (row + currentRow) * rowHeight + (rowHeight - cdsHeight) / 2;
7574
- ctx.fillRect(cdsStartPx, cdsTop, cdsWidthPx, cdsHeight);
7575
- if (cdsWidthPx > 2) {
7576
- ctx.clearRect(cdsStartPx + 1, cdsTop + 1, cdsWidthPx - 2, cdsHeight - 2);
7577
- const frame = util.getFrame(cds.min, cds.max, child.strand ?? 1, cds.phase);
7578
- const frameColor = theme?.palette.framesCDS.at(frame)?.main;
7579
- const cdsColorCode = frameColor ?? 'rgb(171,71,188)';
7580
- ctx.fillStyle =
7581
- apolloSelectedFeature && _id === apolloSelectedFeature._id
7582
- ? 'rgb(0,0,0)'
7583
- : cdsColorCode;
7584
- ctx.fillStyle = cdsColorCode;
7585
- ctx.fillRect(cdsStartPx + 1, cdsTop + 1, cdsWidthPx - 2, cdsHeight - 2);
7586
- if (forwardFill && backwardFill && strand) {
7587
- const reversal = reversed ? -1 : 1;
7588
- const [topFill, bottomFill] = strand * reversal === 1
7589
- ? [forwardFill, backwardFill]
7590
- : [backwardFill, forwardFill];
7591
- ctx.fillStyle = topFill;
7592
- ctx.fillRect(cdsStartPx + 1, cdsTop + 1, cdsWidthPx - 2, (cdsHeight - 2) / 2);
7593
- ctx.fillStyle = bottomFill;
7594
- ctx.fillRect(cdsStartPx + 1, cdsTop + (cdsHeight - 2) / 2, cdsWidthPx - 2, (cdsHeight - 2) / 2);
7595
- }
7601
+ }
7602
+ const { children: transcriptChildren } = child;
7603
+ // Draw exons for non-coding genes
7604
+ if (cdsCount === 0 && transcriptChildren) {
7605
+ for (const [, exon] of transcriptChildren) {
7606
+ if (!featureTypeOntology.isTypeOf(exon.type, 'exon')) {
7607
+ continue;
7596
7608
  }
7609
+ drawExon(ctx, stateModel, displayedRegionIndex, row, exon, currentRow, strand, forwardFill, backwardFill);
7597
7610
  }
7598
7611
  currentRow += 1;
7599
7612
  }
7600
7613
  }
7601
7614
  }
7615
+ function drawExon(ctx, stateModel, displayedRegionIndex, row, exon, currentRow, strand, forwardFill, backwardFill) {
7616
+ const { apolloRowHeight, lgv, session, theme } = stateModel;
7617
+ const { bpPerPx, displayedRegions, offsetPx } = lgv;
7618
+ const displayedRegion = displayedRegions[displayedRegionIndex];
7619
+ const { refName, reversed } = displayedRegion;
7620
+ const { apolloSelectedFeature } = session;
7621
+ const minX = (lgv.bpToPx({
7622
+ refName,
7623
+ coord: exon.min,
7624
+ regionNumber: displayedRegionIndex,
7625
+ })?.offsetPx ?? 0) - offsetPx;
7626
+ const widthPx = exon.length / bpPerPx;
7627
+ const startPx = reversed ? minX - widthPx : minX;
7628
+ const top = (row + currentRow) * apolloRowHeight;
7629
+ const exonHeight = Math.round(0.6 * apolloRowHeight);
7630
+ const exonTop = top + (apolloRowHeight - exonHeight) / 2;
7631
+ ctx.fillStyle = theme?.palette.text.primary ?? 'black';
7632
+ ctx.fillRect(startPx, exonTop, widthPx, exonHeight);
7633
+ if (widthPx > 2) {
7634
+ ctx.clearRect(startPx + 1, exonTop + 1, widthPx - 2, exonHeight - 2);
7635
+ ctx.fillStyle =
7636
+ apolloSelectedFeature && exon._id === apolloSelectedFeature._id
7637
+ ? 'rgb(0,0,0)'
7638
+ : 'rgb(211,211,211)';
7639
+ ctx.fillRect(startPx + 1, exonTop + 1, widthPx - 2, exonHeight - 2);
7640
+ if (forwardFill && backwardFill && strand) {
7641
+ const reversal = reversed ? -1 : 1;
7642
+ const [topFill, bottomFill] = strand * reversal === 1
7643
+ ? [forwardFill, backwardFill]
7644
+ : [backwardFill, forwardFill];
7645
+ ctx.fillStyle = topFill;
7646
+ ctx.fillRect(startPx + 1, exonTop + 1, widthPx - 2, (exonHeight - 2) / 2);
7647
+ ctx.fillStyle = bottomFill;
7648
+ ctx.fillRect(startPx + 1, exonTop + 1 + (exonHeight - 2) / 2, widthPx - 2, (exonHeight - 2) / 2);
7649
+ }
7650
+ }
7651
+ }
7652
+ function drawLine(ctx, stateModel, displayedRegionIndex, row, transcript, currentRow) {
7653
+ const { apolloRowHeight, lgv, theme } = stateModel;
7654
+ const { bpPerPx, displayedRegions, offsetPx } = lgv;
7655
+ const displayedRegion = displayedRegions[displayedRegionIndex];
7656
+ const { refName, reversed } = displayedRegion;
7657
+ const minX = (lgv.bpToPx({
7658
+ refName,
7659
+ coord: transcript.min,
7660
+ regionNumber: displayedRegionIndex,
7661
+ })?.offsetPx ?? 0) - offsetPx;
7662
+ const widthPx = transcript.length / bpPerPx;
7663
+ const startPx = reversed ? minX - widthPx : minX;
7664
+ const height = Math.round((currentRow + 1 / 2) * apolloRowHeight) + row * apolloRowHeight;
7665
+ ctx.strokeStyle = theme?.palette.text.primary ?? 'black';
7666
+ ctx.beginPath();
7667
+ ctx.moveTo(startPx, height);
7668
+ ctx.lineTo(startPx + widthPx, height);
7669
+ ctx.stroke();
7670
+ }
7602
7671
  function drawDragPreview$1(stateModel, overlayCtx) {
7603
7672
  const { apolloDragging, apolloRowHeight, lgv, theme } = stateModel;
7604
7673
  const { bpPerPx, displayedRegions, offsetPx } = lgv;
@@ -7682,6 +7751,22 @@ function getFeatureFromLayout$1(feature, bp, row, featureTypeOntology) {
7682
7751
  }
7683
7752
  return feature;
7684
7753
  }
7754
+ function getCDSCount(feature, featureTypeOntology) {
7755
+ const { children, type } = feature;
7756
+ if (!children) {
7757
+ return 0;
7758
+ }
7759
+ const isMrna = featureTypeOntology.isTypeOf(type, 'mRNA');
7760
+ let cdsCount = 0;
7761
+ if (isMrna) {
7762
+ for (const [, child] of children) {
7763
+ if (featureTypeOntology.isTypeOf(child.type, 'CDS')) {
7764
+ cdsCount += 1;
7765
+ }
7766
+ }
7767
+ }
7768
+ return cdsCount;
7769
+ }
7685
7770
  function getRowCount$1(feature, featureTypeOntology, _bpPerPx) {
7686
7771
  const { children, type } = feature;
7687
7772
  if (!children) {
@@ -7691,12 +7776,12 @@ function getRowCount$1(feature, featureTypeOntology, _bpPerPx) {
7691
7776
  let rowCount = 0;
7692
7777
  if (isTranscript) {
7693
7778
  for (const [, child] of children) {
7694
- const isCds = featureTypeOntology.isTypeOf(child.type, 'CDS');
7695
- if (isCds) {
7779
+ if (featureTypeOntology.isTypeOf(child.type, 'CDS')) {
7696
7780
  rowCount += 1;
7697
7781
  }
7698
7782
  }
7699
- return rowCount;
7783
+ // return 1 if there are no CDSs for non coding genes
7784
+ return rowCount === 0 ? 1 : rowCount;
7700
7785
  }
7701
7786
  for (const [, child] of children) {
7702
7787
  rowCount += getRowCount$1(child, featureTypeOntology);
@@ -7740,6 +7825,9 @@ function featuresForRow$1(feature, featureTypeOntology) {
7740
7825
  for (const cds of cdss) {
7741
7826
  features.push([cds, ...exons, child, feature]);
7742
7827
  }
7828
+ if (cdss.length === 0) {
7829
+ features.push([...exons, child, feature]);
7830
+ }
7743
7831
  }
7744
7832
  return features;
7745
7833
  }
@@ -7987,49 +8075,39 @@ const genericChildGlyph = {
7987
8075
  };
7988
8076
 
7989
8077
  /* eslint-disable @typescript-eslint/no-unnecessary-condition */
8078
+ function getRowsForFeature(startingRow, rowCount, filledRowLocations) {
8079
+ const rowsForFeature = [];
8080
+ for (let i = startingRow; i < startingRow + rowCount; i++) {
8081
+ const row = filledRowLocations.get(i);
8082
+ if (row) {
8083
+ rowsForFeature.push(row);
8084
+ }
8085
+ }
8086
+ return rowsForFeature;
8087
+ }
8088
+ function canPlaceFeatureInRows(rowsForFeature, feature) {
8089
+ for (const rowForFeature of rowsForFeature) {
8090
+ for (const [rowStart, rowEnd] of rowForFeature) {
8091
+ if (util.doesIntersect2(feature.min, feature.max, rowStart, rowEnd) ||
8092
+ util.doesIntersect2(rowStart, rowEnd, feature.min, feature.max)) {
8093
+ return false;
8094
+ }
8095
+ }
8096
+ }
8097
+ return true;
8098
+ }
7990
8099
  function layoutsModelFactory(pluginManager, configSchema) {
7991
8100
  const BaseLinearApolloDisplay = baseModelFactory(pluginManager, configSchema);
7992
8101
  return BaseLinearApolloDisplay.named('LinearApolloDisplayLayouts')
7993
8102
  .props({
7994
- featuresMinMaxLimit: 500_000,
8103
+ cleanupBoundary: 200_000,
7995
8104
  })
7996
8105
  .volatile(() => ({
7997
8106
  seenFeatures: mobx.observable.map(),
7998
8107
  }))
7999
8108
  .views((self) => ({
8000
- get featuresMinMax() {
8001
- const { assemblyManager } = self.session;
8002
- return self.lgv.displayedRegions.map((region) => {
8003
- const assembly = assemblyManager.get(region.assemblyName);
8004
- let min;
8005
- let max;
8006
- const { end, refName, start } = region;
8007
- for (const [, feature] of self.seenFeatures) {
8008
- if (refName !== assembly?.getCanonicalRefName(feature.refSeq) ||
8009
- !util.doesIntersect2(start, end, feature.min, feature.max) ||
8010
- feature.length > self.featuresMinMaxLimit ||
8011
- (self.filteredFeatureTypes.length > 0 &&
8012
- !self.filteredFeatureTypes.includes(feature.type))) {
8013
- continue;
8014
- }
8015
- if (min === undefined) {
8016
- ({ min } = feature);
8017
- }
8018
- if (max === undefined) {
8019
- ({ max } = feature);
8020
- }
8021
- if (feature.minWithChildren < min) {
8022
- ({ min } = feature);
8023
- }
8024
- if (feature.maxWithChildren > max) {
8025
- ({ max } = feature);
8026
- }
8027
- }
8028
- if (min !== undefined && max !== undefined) {
8029
- return [min, max];
8030
- }
8031
- return;
8032
- });
8109
+ getAnnotationFeatureById(id) {
8110
+ return self.seenFeatures.get(id);
8033
8111
  },
8034
8112
  getGlyph(feature) {
8035
8113
  if (this.looksLikeGene(feature)) {
@@ -8059,11 +8137,7 @@ function layoutsModelFactory(pluginManager, configSchema) {
8059
8137
  if (!grandChildren?.size) {
8060
8138
  return false;
8061
8139
  }
8062
- const hasCDS = [...grandChildren.values()].some((grandchild) => featureTypeOntology.isTypeOf(grandchild.type, 'CDS'));
8063
- const hasExon = [...grandChildren.values()].some((grandchild) => featureTypeOntology.isTypeOf(grandchild.type, 'exon'));
8064
- if (hasCDS && hasExon) {
8065
- return true;
8066
- }
8140
+ return [...grandChildren.values()].some((grandchild) => featureTypeOntology.isTypeOf(grandchild.type, 'exon'));
8067
8141
  }
8068
8142
  }
8069
8143
  return false;
@@ -8080,15 +8154,11 @@ function layoutsModelFactory(pluginManager, configSchema) {
8080
8154
  .views((self) => ({
8081
8155
  get featureLayouts() {
8082
8156
  const { assemblyManager } = self.session;
8083
- return self.lgv.displayedRegions.map((region, idx) => {
8157
+ return self.lgv.displayedRegions.map((region) => {
8084
8158
  const assembly = assemblyManager.get(region.assemblyName);
8085
8159
  const featureLayout = new Map();
8086
- const minMax = self.featuresMinMax[idx];
8087
- if (!minMax) {
8088
- return featureLayout;
8089
- }
8090
- const [min, max] = minMax;
8091
- const rows = [];
8160
+ // Track the occupied coordinates in each row
8161
+ const filledRowLocations = new Map();
8092
8162
  const { end, refName, start } = region;
8093
8163
  for (const [id, feature] of self.seenFeatures.entries()) {
8094
8164
  if (!mobxStateTree.isAlive(feature)) {
@@ -8111,42 +8181,23 @@ function layoutsModelFactory(pluginManager, configSchema) {
8111
8181
  let startingRow = 0;
8112
8182
  let placed = false;
8113
8183
  while (!placed) {
8114
- let rowsForFeature = rows.slice(startingRow, startingRow + rowCount);
8184
+ let rowsForFeature = getRowsForFeature(startingRow, rowCount, filledRowLocations);
8115
8185
  if (rowsForFeature.length < rowCount) {
8116
8186
  for (let i = 0; i < rowCount - rowsForFeature.length; i++) {
8117
- const newRowNumber = rows.length;
8118
- rows[newRowNumber] = Array.from({ length: max - min });
8187
+ const newRowNumber = filledRowLocations.size;
8188
+ filledRowLocations.set(newRowNumber, []);
8119
8189
  featureLayout.set(newRowNumber, []);
8120
8190
  }
8121
- rowsForFeature = rows.slice(startingRow, startingRow + rowCount);
8191
+ rowsForFeature = getRowsForFeature(startingRow, rowCount, filledRowLocations);
8122
8192
  }
8123
- if (rowsForFeature
8124
- .map((rowForFeature) => {
8125
- // zero-length features are allowed in the spec
8126
- const featureMax = feature.max - feature.min === 0
8127
- ? feature.min + 1
8128
- : feature.max;
8129
- let start = feature.min - min, end = featureMax - min;
8130
- if (feature.min - min < 0) {
8131
- start = 0;
8132
- end = featureMax - feature.min;
8133
- }
8134
- return rowForFeature.slice(start, end).some(Boolean);
8135
- })
8136
- .some(Boolean)) {
8193
+ if (!canPlaceFeatureInRows(rowsForFeature, feature)) {
8137
8194
  startingRow += 1;
8138
8195
  continue;
8139
8196
  }
8140
8197
  for (let rowNum = startingRow; rowNum < startingRow + rowCount; rowNum++) {
8141
- const row = rows[rowNum];
8142
- let start = feature.min - min, end = feature.max - min;
8143
- if (feature.min - min < 0) {
8144
- start = 0;
8145
- end = feature.max - feature.min;
8146
- }
8147
- row.fill(true, start, end);
8198
+ filledRowLocations.get(rowNum)?.push([feature.min, feature.max]);
8148
8199
  const layoutRow = featureLayout.get(rowNum);
8149
- layoutRow?.push([rowNum - startingRow, feature]);
8200
+ layoutRow?.push([rowNum - startingRow, feature._id]);
8150
8201
  }
8151
8202
  placed = true;
8152
8203
  }
@@ -8159,12 +8210,16 @@ function layoutsModelFactory(pluginManager, configSchema) {
8159
8210
  const { featureTypeOntology } = self.session.apolloDataStore.ontologyManager;
8160
8211
  for (const [idx, layout] of featureLayouts.entries()) {
8161
8212
  for (const [layoutRowNum, layoutRow] of layout) {
8162
- for (const [featureRowNum, layoutFeature] of layoutRow) {
8213
+ for (const [featureRowNum, layoutFeatureId] of layoutRow) {
8163
8214
  if (featureRowNum !== 0) {
8164
8215
  // Same top-level feature in all feature rows, so only need to
8165
8216
  // check the first one
8166
8217
  continue;
8167
8218
  }
8219
+ const layoutFeature = self.getAnnotationFeatureById(layoutFeatureId);
8220
+ if (!layoutFeature) {
8221
+ continue;
8222
+ }
8168
8223
  if (feature._id === layoutFeature._id) {
8169
8224
  return {
8170
8225
  layoutIndex: idx,
@@ -8204,6 +8259,23 @@ function layoutsModelFactory(pluginManager, configSchema) {
8204
8259
  if (!self.lgv.initialized || self.regionCannotBeRendered()) {
8205
8260
  return;
8206
8261
  }
8262
+ // Clear out features that are no longer in the view and out of the cleanup boundary
8263
+ // cleanup boundary + region boundary + cleanup boundary
8264
+ for (const [id, feature] of self.seenFeatures.entries()) {
8265
+ let shouldKeep = false;
8266
+ for (const region of self.regions) {
8267
+ const extendedStart = region.start - self.cleanupBoundary;
8268
+ const extendedEnd = region.end + self.cleanupBoundary;
8269
+ if (util.doesIntersect2(extendedStart, extendedEnd, feature.min, feature.max)) {
8270
+ shouldKeep = true;
8271
+ break;
8272
+ }
8273
+ }
8274
+ if (!shouldKeep) {
8275
+ self.deleteSeenFeature(id);
8276
+ }
8277
+ }
8278
+ // Add features that are in the current view
8207
8279
  for (const region of self.regions) {
8208
8280
  const assembly = self.session.apolloDataStore.assemblies.get(region.assemblyName);
8209
8281
  const ref = assembly?.getByRefName(region.refName);
@@ -8477,8 +8549,9 @@ function renderingModelFactory(pluginManager, configSchema) {
8477
8549
  for (const [idx, featureLayout] of featureLayouts.entries()) {
8478
8550
  const displayedRegion = displayedRegions[idx];
8479
8551
  for (const [row, featureLayoutRow] of featureLayout.entries()) {
8480
- for (const [featureRow, feature] of featureLayoutRow) {
8481
- if (featureRow > 0) {
8552
+ for (const [featureRow, featureId] of featureLayoutRow) {
8553
+ const feature = self.getAnnotationFeatureById(featureId);
8554
+ if (featureRow > 0 || !feature) {
8482
8555
  continue;
8483
8556
  }
8484
8557
  if (!util.doesIntersect2(displayedRegion.start, displayedRegion.end, feature.min, feature.max)) {
@@ -8559,11 +8632,18 @@ function mouseEventsModelIntermediateFactory(pluginManager, configSchema) {
8559
8632
  if (!layoutRow) {
8560
8633
  return mousePosition;
8561
8634
  }
8562
- const foundFeature = layoutRow.find((f) => bp >= f[1].min && bp <= f[1].max);
8635
+ const foundFeature = layoutRow.find((f) => {
8636
+ const feature = self.getAnnotationFeatureById(f[1]);
8637
+ return feature && bp >= feature.min && bp <= feature.max;
8638
+ });
8563
8639
  if (!foundFeature) {
8564
8640
  return mousePosition;
8565
8641
  }
8566
- const [featureRow, topLevelFeature] = foundFeature;
8642
+ const [featureRow, topLevelFeatureId] = foundFeature;
8643
+ const topLevelFeature = self.getAnnotationFeatureById(topLevelFeatureId);
8644
+ if (!topLevelFeature) {
8645
+ return mousePosition;
8646
+ }
8567
8647
  const glyph = self.getGlyph(topLevelFeature);
8568
8648
  const { featureTypeOntology } = self.session.apolloDataStore.ontologyManager;
8569
8649
  if (!featureTypeOntology) {
@@ -8847,11 +8927,18 @@ const useStyles$1 = mui.makeStyles()((theme) => ({
8847
8927
  color: theme.palette.warning.light,
8848
8928
  backgroundColor: theme.palette.warning.contrastText,
8849
8929
  },
8930
+ loading: {
8931
+ position: 'absolute',
8932
+ right: theme.spacing(3),
8933
+ zIndex: 10,
8934
+ pointerEvents: 'none',
8935
+ textAlign: 'right',
8936
+ },
8850
8937
  }));
8851
8938
  const LinearApolloDisplay = mobxReact.observer(function LinearApolloDisplay(props) {
8852
8939
  const theme = material.useTheme();
8853
8940
  const { model } = props;
8854
- const { apolloRowHeight, contextMenuItems: getContextMenuItems, cursor, featuresHeight, isShown, onMouseDown, onMouseLeave, onMouseMove, onMouseUp, regionCannotBeRendered, session, setCanvas, setCollaboratorCanvas, setOverlayCanvas, setSeqTrackCanvas, setSeqTrackOverlayCanvas, setTheme, } = model;
8941
+ const { loading, apolloRowHeight, contextMenuItems: getContextMenuItems, cursor, featuresHeight, isShown, onMouseDown, onMouseLeave, onMouseMove, onMouseUp, regionCannotBeRendered, session, setCanvas, setCollaboratorCanvas, setOverlayCanvas, setSeqTrackCanvas, setSeqTrackOverlayCanvas, setTheme, } = model;
8855
8942
  const { classes } = useStyles$1();
8856
8943
  const lgv = util.getContainingView(model);
8857
8944
  React.useEffect(() => {
@@ -8891,65 +8978,68 @@ const LinearApolloDisplay = mobxReact.observer(function LinearApolloDisplay(prop
8891
8978
  setContextCoord(coord);
8892
8979
  setContextMenuItems(getContextMenuItems(coord));
8893
8980
  }
8894
- } }, message ? (React__default["default"].createElement(material.Alert, { severity: "warning", classes: { message: classes.ellipses } },
8895
- React__default["default"].createElement(material.Tooltip, { title: message },
8896
- React__default["default"].createElement("div", null, message)))) : (
8897
- // Promise.resolve() in these 3 callbacks is to avoid infinite rendering loop
8898
- // https://github.com/mobxjs/mobx/issues/3728#issuecomment-1715400931
8899
- React__default["default"].createElement(React__default["default"].Fragment, null,
8900
- React__default["default"].createElement("canvas", { ref: async (node) => {
8901
- await Promise.resolve();
8902
- setCollaboratorCanvas(node);
8903
- }, width: lgv.dynamicBlocks.totalWidthPx, height: featuresHeight, className: classes.canvas, "data-testid": "collaboratorCanvas" }),
8904
- React__default["default"].createElement("canvas", { ref: async (node) => {
8905
- await Promise.resolve();
8906
- setCanvas(node);
8907
- }, width: lgv.dynamicBlocks.totalWidthPx, height: featuresHeight, className: classes.canvas, "data-testid": "canvas" }),
8908
- React__default["default"].createElement("canvas", { ref: async (node) => {
8909
- await Promise.resolve();
8910
- setOverlayCanvas(node);
8911
- }, width: lgv.dynamicBlocks.totalWidthPx, height: featuresHeight, onMouseMove: onMouseMove, onMouseLeave: onMouseLeave, onMouseDown: onMouseDown, onMouseUp: onMouseUp, className: classes.canvas, style: { cursor: cursor ?? 'default' }, "data-testid": "overlayCanvas" }),
8912
- lgv.displayedRegions.flatMap((region, idx) => {
8913
- const assembly = assemblyManager.get(region.assemblyName);
8914
- return [...session.apolloDataStore.checkResults.values()]
8915
- .filter((checkResult) => assembly?.isValidRefName(checkResult.refSeq) &&
8916
- assembly.getCanonicalRefName(checkResult.refSeq) ===
8917
- region.refName &&
8918
- util.doesIntersect2(region.start, region.end, checkResult.start, checkResult.end))
8919
- .map((checkResult) => {
8920
- const left = (lgv.bpToPx({
8921
- refName: region.refName,
8922
- coord: checkResult.start,
8923
- regionNumber: idx,
8924
- })?.offsetPx ?? 0) - lgv.offsetPx;
8925
- const [feature] = checkResult.ids;
8926
- if (!feature) {
8927
- return null;
8928
- }
8929
- const { topLevelFeature } = feature;
8930
- const row = parent
8931
- ? model.getFeatureLayoutPosition(topLevelFeature)
8932
- ?.layoutRow ?? 0
8933
- : 0;
8934
- const top = row * apolloRowHeight;
8935
- const height = apolloRowHeight;
8936
- return (React__default["default"].createElement(material.Tooltip, { key: checkResult._id, title: checkResult.message },
8937
- React__default["default"].createElement(material.Avatar, { className: classes.avatar, style: { top, left, height, width: height } },
8938
- React__default["default"].createElement(ErrorIcon__default["default"], null))));
8939
- });
8940
- }),
8941
- React__default["default"].createElement(ui.Menu, { open: contextMenuItems.length > 0, onMenuItemClick: (_, callback) => {
8942
- callback();
8943
- setContextMenuItems([]);
8944
- }, onClose: () => {
8945
- setContextMenuItems([]);
8946
- }, TransitionProps: {
8947
- onExit: () => {
8981
+ } },
8982
+ loading ? (React__default["default"].createElement("div", { className: classes.loading },
8983
+ React__default["default"].createElement(material.CircularProgress, { size: "18px" }))) : null,
8984
+ message ? (React__default["default"].createElement(material.Alert, { severity: "warning", classes: { message: classes.ellipses } },
8985
+ React__default["default"].createElement(material.Tooltip, { title: message },
8986
+ React__default["default"].createElement("div", null, message)))) : (
8987
+ // Promise.resolve() in these 3 callbacks is to avoid infinite rendering loop
8988
+ // https://github.com/mobxjs/mobx/issues/3728#issuecomment-1715400931
8989
+ React__default["default"].createElement(React__default["default"].Fragment, null,
8990
+ React__default["default"].createElement("canvas", { ref: async (node) => {
8991
+ await Promise.resolve();
8992
+ setCollaboratorCanvas(node);
8993
+ }, width: lgv.dynamicBlocks.totalWidthPx, height: featuresHeight, className: classes.canvas, "data-testid": "collaboratorCanvas" }),
8994
+ React__default["default"].createElement("canvas", { ref: async (node) => {
8995
+ await Promise.resolve();
8996
+ setCanvas(node);
8997
+ }, width: lgv.dynamicBlocks.totalWidthPx, height: featuresHeight, className: classes.canvas, "data-testid": "canvas" }),
8998
+ React__default["default"].createElement("canvas", { ref: async (node) => {
8999
+ await Promise.resolve();
9000
+ setOverlayCanvas(node);
9001
+ }, width: lgv.dynamicBlocks.totalWidthPx, height: featuresHeight, onMouseMove: onMouseMove, onMouseLeave: onMouseLeave, onMouseDown: onMouseDown, onMouseUp: onMouseUp, className: classes.canvas, style: { cursor: cursor ?? 'default' }, "data-testid": "overlayCanvas" }),
9002
+ lgv.displayedRegions.flatMap((region, idx) => {
9003
+ const assembly = assemblyManager.get(region.assemblyName);
9004
+ return [...session.apolloDataStore.checkResults.values()]
9005
+ .filter((checkResult) => assembly?.isValidRefName(checkResult.refSeq) &&
9006
+ assembly.getCanonicalRefName(checkResult.refSeq) ===
9007
+ region.refName &&
9008
+ util.doesIntersect2(region.start, region.end, checkResult.start, checkResult.end))
9009
+ .map((checkResult) => {
9010
+ const left = (lgv.bpToPx({
9011
+ refName: region.refName,
9012
+ coord: checkResult.start,
9013
+ regionNumber: idx,
9014
+ })?.offsetPx ?? 0) - lgv.offsetPx;
9015
+ const [feature] = checkResult.ids;
9016
+ if (!feature) {
9017
+ return null;
9018
+ }
9019
+ const { topLevelFeature } = feature;
9020
+ const row = parent
9021
+ ? model.getFeatureLayoutPosition(topLevelFeature)
9022
+ ?.layoutRow ?? 0
9023
+ : 0;
9024
+ const top = row * apolloRowHeight;
9025
+ const height = apolloRowHeight;
9026
+ return (React__default["default"].createElement(material.Tooltip, { key: checkResult._id, title: checkResult.message },
9027
+ React__default["default"].createElement(material.Avatar, { className: classes.avatar, style: { top, left, height, width: height } },
9028
+ React__default["default"].createElement(ErrorIcon__default["default"], null))));
9029
+ });
9030
+ }),
9031
+ React__default["default"].createElement(ui.Menu, { open: contextMenuItems.length > 0, onMenuItemClick: (_, callback) => {
9032
+ callback();
8948
9033
  setContextMenuItems([]);
8949
- },
8950
- }, anchorReference: "anchorPosition", anchorPosition: contextCoord
8951
- ? { top: contextCoord[1], left: contextCoord[0] }
8952
- : undefined, style: { zIndex: theme.zIndex.tooltip }, menuItems: contextMenuItems }))))));
9034
+ }, onClose: () => {
9035
+ setContextMenuItems([]);
9036
+ }, TransitionProps: {
9037
+ onExit: () => {
9038
+ setContextMenuItems([]);
9039
+ },
9040
+ }, anchorReference: "anchorPosition", anchorPosition: contextCoord
9041
+ ? { top: contextCoord[1], left: contextCoord[0] }
9042
+ : undefined, style: { zIndex: theme.zIndex.tooltip }, menuItems: contextMenuItems }))))));
8953
9043
  });
8954
9044
 
8955
9045
  const TrackLines = mobxReact.observer(function TrackLines({ model, }) {