@apollo-annotation/jbrowse-plugin-apollo 0.3.3 → 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.
package/dist/index.esm.js CHANGED
@@ -10,7 +10,7 @@ import { getSnapshot, getParent, getRoot, types, addDisposer, flow, cast, isAliv
10
10
  import { io } from 'socket.io-client';
11
11
  import gff from '@gmod/gff';
12
12
  import LinkIcon from '@mui/icons-material/Link';
13
- import { DialogTitle, IconButton, DialogContent, DialogContentText, Select, MenuItem, TextField, FormControl, FormLabel, RadioGroup, FormControlLabel, Radio, Box, Typography, FormGroup, Checkbox, DialogActions, Button, Autocomplete, InputLabel, TableContainer, Paper, Table, TableHead, TableRow, TableCell, TableBody, Grid2, Tooltip, Chip, useTheme, FormHelperText, SvgIcon, Divider, Menu, InputAdornment as InputAdornment$1, alpha, Alert, Avatar } from '@mui/material';
13
+ import { DialogTitle, IconButton, DialogContent, DialogContentText, Select, MenuItem, TextField, FormControl, FormLabel, RadioGroup, FormControlLabel, Radio, Box, Typography, FormGroup, Checkbox, DialogActions, Button, Autocomplete, InputLabel, TableContainer, Paper, Table, TableHead, TableRow, TableCell, TableBody, Grid2, Tooltip, Chip, useTheme, FormHelperText, SvgIcon, Divider, Menu, InputAdornment as InputAdornment$1, alpha, CircularProgress, Alert, Avatar } from '@mui/material';
14
14
  import InputAdornment from '@mui/material/InputAdornment';
15
15
  import LinearProgress from '@mui/material/LinearProgress';
16
16
  import ObjectID from 'bson-objectid';
@@ -50,7 +50,7 @@ import ExpandMoreIcon from '@mui/icons-material/ExpandMore';
50
50
  import ErrorIcon from '@mui/icons-material/Error';
51
51
  import SaveIcon from '@mui/icons-material/Save';
52
52
 
53
- var version = "0.3.3";
53
+ var version = "0.3.4";
54
54
 
55
55
  const ApolloConfigSchema = ConfigurationSchema('ApolloInternetAccount', {
56
56
  baseURL: {
@@ -1246,6 +1246,7 @@ const OntologyRecordType = types
1246
1246
  })
1247
1247
  .volatile((_self) => ({
1248
1248
  dataStore: undefined,
1249
+ startedEquivalentTypeRequests: new Set(),
1249
1250
  }))
1250
1251
  .actions((self) => ({
1251
1252
  /** does nothing, just used to access the model to force its lifecycle hooks to run */
@@ -1269,12 +1270,33 @@ const OntologyRecordType = types
1269
1270
  if (!self.dataStore) {
1270
1271
  return;
1271
1272
  }
1273
+ if (self.startedEquivalentTypeRequests.has(type)) {
1274
+ return;
1275
+ }
1276
+ self.startedEquivalentTypeRequests.add(type);
1272
1277
  const terms = (yield self.dataStore.getTermsWithLabelOrSynonym(type));
1273
1278
  const equivalents = terms
1274
1279
  .map((term) => term.lbl)
1275
1280
  .filter((term) => term != undefined);
1276
1281
  self.setEquivalentTypes(type, equivalents);
1277
1282
  }),
1283
+ }))
1284
+ .actions((self) => ({
1285
+ afterCreate() {
1286
+ autorun((reaction) => {
1287
+ if (!self.dataStore) {
1288
+ return;
1289
+ }
1290
+ void self.loadEquivalentTypes('gene');
1291
+ void self.loadEquivalentTypes('transcript');
1292
+ void self.loadEquivalentTypes('CDS');
1293
+ void self.loadEquivalentTypes('mRNA');
1294
+ reaction.dispose();
1295
+ });
1296
+ },
1297
+ setEquivalentTypes(type, equivalentTypes) {
1298
+ self.equivalentTypes.set(type, equivalentTypes);
1299
+ },
1278
1300
  }))
1279
1301
  .views((self) => ({
1280
1302
  isTypeOf(queryType, typeOf) {
@@ -5777,7 +5799,6 @@ const ApolloTranscriptDetailsModel = types
5777
5799
  })),
5778
5800
  assembly: types.string,
5779
5801
  refName: types.string,
5780
- changeManager: types.frozen(),
5781
5802
  })
5782
5803
  .volatile(() => ({
5783
5804
  tryReload: undefined,
@@ -6787,6 +6808,7 @@ function baseModelFactory(_pluginManager, configSchema) {
6787
6808
  table: false,
6788
6809
  heightPreConfig: types.maybe(types.refinement('displayHeight', types.number, (n) => n >= minDisplayHeight)),
6789
6810
  filteredFeatureTypes: types.array(types.string),
6811
+ loadingState: false,
6790
6812
  })
6791
6813
  .views((self) => {
6792
6814
  const { configuration, renderProps: superRenderProps } = self;
@@ -6819,6 +6841,9 @@ function baseModelFactory(_pluginManager, configSchema) {
6819
6841
  }
6820
6842
  return 300;
6821
6843
  },
6844
+ get loading() {
6845
+ return self.loadingState;
6846
+ },
6822
6847
  }))
6823
6848
  .views((self) => ({
6824
6849
  get rendererTypeName() {
@@ -6904,6 +6929,9 @@ function baseModelFactory(_pluginManager, configSchema) {
6904
6929
  updateFilteredFeatureTypes(types) {
6905
6930
  self.filteredFeatureTypes = cast(types);
6906
6931
  },
6932
+ setLoading(loading) {
6933
+ self.loadingState = loading;
6934
+ },
6907
6935
  }))
6908
6936
  .views((self) => {
6909
6937
  const { filteredFeatureTypes, trackMenuItems: superTrackMenuItems } = self;
@@ -6974,7 +7002,14 @@ function baseModelFactory(_pluginManager, configSchema) {
6974
7002
  if (!self.lgv.initialized || self.regionCannotBeRendered()) {
6975
7003
  return;
6976
7004
  }
6977
- void self.session.apolloDataStore.loadFeatures(self.regions);
7005
+ self.setLoading(true);
7006
+ void self.session.apolloDataStore
7007
+ .loadFeatures(self.regions)
7008
+ .then(() => {
7009
+ setTimeout(() => {
7010
+ self.setLoading(false);
7011
+ }, 1000);
7012
+ });
6978
7013
  if (self.lgv.bpPerPx <= 3) {
6979
7014
  void self.session.apolloDataStore.loadRefSeq(self.regions);
6980
7015
  }
@@ -7403,7 +7438,6 @@ function draw$1(ctx, feature, row, stateModel, displayedRegionIndex) {
7403
7438
  const displayedRegion = displayedRegions[displayedRegionIndex];
7404
7439
  const { refName, reversed } = displayedRegion;
7405
7440
  const rowHeight = apolloRowHeight;
7406
- const exonHeight = Math.round(0.6 * rowHeight);
7407
7441
  const cdsHeight = Math.round(0.9 * rowHeight);
7408
7442
  const { children, min, strand } = feature;
7409
7443
  if (!children) {
@@ -7437,27 +7471,20 @@ function draw$1(ctx, feature, row, stateModel, displayedRegionIndex) {
7437
7471
  currentRow += 1;
7438
7472
  continue;
7439
7473
  }
7440
- const { children: childrenOfTranscript, min } = transcript;
7441
- if (!childrenOfTranscript) {
7474
+ const { children: transcriptChildren } = transcript;
7475
+ if (!transcriptChildren) {
7442
7476
  continue;
7443
7477
  }
7444
- for (const [, cds] of childrenOfTranscript) {
7445
- if (!featureTypeOntology.isTypeOf(cds.type, 'CDS')) {
7478
+ const cdsCount = getCDSCount(transcript, featureTypeOntology);
7479
+ for (const [, childFeature] of transcriptChildren) {
7480
+ if (!featureTypeOntology.isTypeOf(childFeature.type, 'CDS')) {
7446
7481
  continue;
7447
7482
  }
7448
- const minX = (lgv.bpToPx({
7449
- refName,
7450
- coord: min,
7451
- regionNumber: displayedRegionIndex,
7452
- })?.offsetPx ?? 0) - offsetPx;
7453
- const widthPx = transcript.length / bpPerPx;
7454
- const startPx = reversed ? minX - widthPx : minX;
7455
- const height = Math.round((currentRow + 1 / 2) * rowHeight) + row * rowHeight;
7456
- ctx.strokeStyle = theme?.palette.text.primary ?? 'black';
7457
- ctx.beginPath();
7458
- ctx.moveTo(startPx, height);
7459
- ctx.lineTo(startPx + widthPx, height);
7460
- ctx.stroke();
7483
+ drawLine(ctx, stateModel, displayedRegionIndex, row, transcript, currentRow);
7484
+ currentRow += 1;
7485
+ }
7486
+ if (cdsCount === 0) {
7487
+ drawLine(ctx, stateModel, displayedRegionIndex, row, transcript, currentRow);
7461
7488
  currentRow += 1;
7462
7489
  }
7463
7490
  }
@@ -7471,83 +7498,125 @@ function draw$1(ctx, feature, row, stateModel, displayedRegionIndex) {
7471
7498
  currentRow += 1;
7472
7499
  continue;
7473
7500
  }
7474
- for (const cdsRow of child.cdsLocations) {
7475
- const { _id, children: childrenOfTranscript } = child;
7476
- if (!childrenOfTranscript) {
7477
- continue;
7478
- }
7479
- for (const [, exon] of childrenOfTranscript) {
7480
- if (!featureTypeOntology.isTypeOf(exon.type, 'exon')) {
7501
+ const cdsCount = getCDSCount(child, featureTypeOntology);
7502
+ if (cdsCount != 0) {
7503
+ for (const cdsRow of child.cdsLocations) {
7504
+ const { _id, children: transcriptChildren } = child;
7505
+ if (!transcriptChildren) {
7481
7506
  continue;
7482
7507
  }
7483
- const minX = (lgv.bpToPx({
7484
- refName,
7485
- coord: exon.min,
7486
- regionNumber: displayedRegionIndex,
7487
- })?.offsetPx ?? 0) - offsetPx;
7488
- const widthPx = exon.length / bpPerPx;
7489
- const startPx = reversed ? minX - widthPx : minX;
7490
- const top = (row + currentRow) * rowHeight;
7491
- const exonTop = top + (rowHeight - exonHeight) / 2;
7492
- ctx.fillStyle = theme?.palette.text.primary ?? 'black';
7493
- ctx.fillRect(startPx, exonTop, widthPx, exonHeight);
7494
- if (widthPx > 2) {
7495
- ctx.clearRect(startPx + 1, exonTop + 1, widthPx - 2, exonHeight - 2);
7496
- ctx.fillStyle =
7497
- apolloSelectedFeature && exon._id === apolloSelectedFeature._id
7498
- ? 'rgb(0,0,0)'
7499
- : 'rgb(211,211,211)';
7500
- ctx.fillRect(startPx + 1, exonTop + 1, widthPx - 2, exonHeight - 2);
7501
- if (forwardFill && backwardFill && strand) {
7502
- const reversal = reversed ? -1 : 1;
7503
- const [topFill, bottomFill] = strand * reversal === 1
7504
- ? [forwardFill, backwardFill]
7505
- : [backwardFill, forwardFill];
7506
- ctx.fillStyle = topFill;
7507
- ctx.fillRect(startPx + 1, exonTop + 1, widthPx - 2, (exonHeight - 2) / 2);
7508
- ctx.fillStyle = bottomFill;
7509
- ctx.fillRect(startPx + 1, exonTop + 1 + (exonHeight - 2) / 2, widthPx - 2, (exonHeight - 2) / 2);
7508
+ for (const [, exon] of transcriptChildren) {
7509
+ if (!featureTypeOntology.isTypeOf(exon.type, 'exon')) {
7510
+ continue;
7511
+ }
7512
+ drawExon(ctx, stateModel, displayedRegionIndex, row, exon, currentRow, strand, forwardFill, backwardFill);
7513
+ }
7514
+ for (const cds of cdsRow) {
7515
+ const cdsWidthPx = (cds.max - cds.min) / bpPerPx;
7516
+ const minX = (lgv.bpToPx({
7517
+ refName,
7518
+ coord: cds.min,
7519
+ regionNumber: displayedRegionIndex,
7520
+ })?.offsetPx ?? 0) - offsetPx;
7521
+ const cdsStartPx = reversed ? minX - cdsWidthPx : minX;
7522
+ ctx.fillStyle = theme?.palette.text.primary ?? 'black';
7523
+ const cdsTop = (row + currentRow) * rowHeight + (rowHeight - cdsHeight) / 2;
7524
+ ctx.fillRect(cdsStartPx, cdsTop, cdsWidthPx, cdsHeight);
7525
+ if (cdsWidthPx > 2) {
7526
+ ctx.clearRect(cdsStartPx + 1, cdsTop + 1, cdsWidthPx - 2, cdsHeight - 2);
7527
+ const frame = getFrame(cds.min, cds.max, child.strand ?? 1, cds.phase);
7528
+ const frameColor = theme?.palette.framesCDS.at(frame)?.main;
7529
+ const cdsColorCode = frameColor ?? 'rgb(171,71,188)';
7530
+ ctx.fillStyle =
7531
+ apolloSelectedFeature && _id === apolloSelectedFeature._id
7532
+ ? 'rgb(0,0,0)'
7533
+ : cdsColorCode;
7534
+ ctx.fillStyle = cdsColorCode;
7535
+ ctx.fillRect(cdsStartPx + 1, cdsTop + 1, cdsWidthPx - 2, cdsHeight - 2);
7536
+ if (forwardFill && backwardFill && strand) {
7537
+ const reversal = reversed ? -1 : 1;
7538
+ const [topFill, bottomFill] = strand * reversal === 1
7539
+ ? [forwardFill, backwardFill]
7540
+ : [backwardFill, forwardFill];
7541
+ ctx.fillStyle = topFill;
7542
+ ctx.fillRect(cdsStartPx + 1, cdsTop + 1, cdsWidthPx - 2, (cdsHeight - 2) / 2);
7543
+ ctx.fillStyle = bottomFill;
7544
+ ctx.fillRect(cdsStartPx + 1, cdsTop + (cdsHeight - 2) / 2, cdsWidthPx - 2, (cdsHeight - 2) / 2);
7545
+ }
7510
7546
  }
7511
7547
  }
7548
+ currentRow += 1;
7512
7549
  }
7513
- for (const cds of cdsRow) {
7514
- const cdsWidthPx = (cds.max - cds.min) / bpPerPx;
7515
- const minX = (lgv.bpToPx({
7516
- refName,
7517
- coord: cds.min,
7518
- regionNumber: displayedRegionIndex,
7519
- })?.offsetPx ?? 0) - offsetPx;
7520
- const cdsStartPx = reversed ? minX - cdsWidthPx : minX;
7521
- ctx.fillStyle = theme?.palette.text.primary ?? 'black';
7522
- const cdsTop = (row + currentRow) * rowHeight + (rowHeight - cdsHeight) / 2;
7523
- ctx.fillRect(cdsStartPx, cdsTop, cdsWidthPx, cdsHeight);
7524
- if (cdsWidthPx > 2) {
7525
- ctx.clearRect(cdsStartPx + 1, cdsTop + 1, cdsWidthPx - 2, cdsHeight - 2);
7526
- const frame = getFrame(cds.min, cds.max, child.strand ?? 1, cds.phase);
7527
- const frameColor = theme?.palette.framesCDS.at(frame)?.main;
7528
- const cdsColorCode = frameColor ?? 'rgb(171,71,188)';
7529
- ctx.fillStyle =
7530
- apolloSelectedFeature && _id === apolloSelectedFeature._id
7531
- ? 'rgb(0,0,0)'
7532
- : cdsColorCode;
7533
- ctx.fillStyle = cdsColorCode;
7534
- ctx.fillRect(cdsStartPx + 1, cdsTop + 1, cdsWidthPx - 2, cdsHeight - 2);
7535
- if (forwardFill && backwardFill && strand) {
7536
- const reversal = reversed ? -1 : 1;
7537
- const [topFill, bottomFill] = strand * reversal === 1
7538
- ? [forwardFill, backwardFill]
7539
- : [backwardFill, forwardFill];
7540
- ctx.fillStyle = topFill;
7541
- ctx.fillRect(cdsStartPx + 1, cdsTop + 1, cdsWidthPx - 2, (cdsHeight - 2) / 2);
7542
- ctx.fillStyle = bottomFill;
7543
- ctx.fillRect(cdsStartPx + 1, cdsTop + (cdsHeight - 2) / 2, cdsWidthPx - 2, (cdsHeight - 2) / 2);
7544
- }
7550
+ }
7551
+ const { children: transcriptChildren } = child;
7552
+ // Draw exons for non-coding genes
7553
+ if (cdsCount === 0 && transcriptChildren) {
7554
+ for (const [, exon] of transcriptChildren) {
7555
+ if (!featureTypeOntology.isTypeOf(exon.type, 'exon')) {
7556
+ continue;
7545
7557
  }
7558
+ drawExon(ctx, stateModel, displayedRegionIndex, row, exon, currentRow, strand, forwardFill, backwardFill);
7546
7559
  }
7547
7560
  currentRow += 1;
7548
7561
  }
7549
7562
  }
7550
7563
  }
7564
+ function drawExon(ctx, stateModel, displayedRegionIndex, row, exon, currentRow, strand, forwardFill, backwardFill) {
7565
+ const { apolloRowHeight, lgv, session, theme } = stateModel;
7566
+ const { bpPerPx, displayedRegions, offsetPx } = lgv;
7567
+ const displayedRegion = displayedRegions[displayedRegionIndex];
7568
+ const { refName, reversed } = displayedRegion;
7569
+ const { apolloSelectedFeature } = session;
7570
+ const minX = (lgv.bpToPx({
7571
+ refName,
7572
+ coord: exon.min,
7573
+ regionNumber: displayedRegionIndex,
7574
+ })?.offsetPx ?? 0) - offsetPx;
7575
+ const widthPx = exon.length / bpPerPx;
7576
+ const startPx = reversed ? minX - widthPx : minX;
7577
+ const top = (row + currentRow) * apolloRowHeight;
7578
+ const exonHeight = Math.round(0.6 * apolloRowHeight);
7579
+ const exonTop = top + (apolloRowHeight - exonHeight) / 2;
7580
+ ctx.fillStyle = theme?.palette.text.primary ?? 'black';
7581
+ ctx.fillRect(startPx, exonTop, widthPx, exonHeight);
7582
+ if (widthPx > 2) {
7583
+ ctx.clearRect(startPx + 1, exonTop + 1, widthPx - 2, exonHeight - 2);
7584
+ ctx.fillStyle =
7585
+ apolloSelectedFeature && exon._id === apolloSelectedFeature._id
7586
+ ? 'rgb(0,0,0)'
7587
+ : 'rgb(211,211,211)';
7588
+ ctx.fillRect(startPx + 1, exonTop + 1, widthPx - 2, exonHeight - 2);
7589
+ if (forwardFill && backwardFill && strand) {
7590
+ const reversal = reversed ? -1 : 1;
7591
+ const [topFill, bottomFill] = strand * reversal === 1
7592
+ ? [forwardFill, backwardFill]
7593
+ : [backwardFill, forwardFill];
7594
+ ctx.fillStyle = topFill;
7595
+ ctx.fillRect(startPx + 1, exonTop + 1, widthPx - 2, (exonHeight - 2) / 2);
7596
+ ctx.fillStyle = bottomFill;
7597
+ ctx.fillRect(startPx + 1, exonTop + 1 + (exonHeight - 2) / 2, widthPx - 2, (exonHeight - 2) / 2);
7598
+ }
7599
+ }
7600
+ }
7601
+ function drawLine(ctx, stateModel, displayedRegionIndex, row, transcript, currentRow) {
7602
+ const { apolloRowHeight, lgv, theme } = stateModel;
7603
+ const { bpPerPx, displayedRegions, offsetPx } = lgv;
7604
+ const displayedRegion = displayedRegions[displayedRegionIndex];
7605
+ const { refName, reversed } = displayedRegion;
7606
+ const minX = (lgv.bpToPx({
7607
+ refName,
7608
+ coord: transcript.min,
7609
+ regionNumber: displayedRegionIndex,
7610
+ })?.offsetPx ?? 0) - offsetPx;
7611
+ const widthPx = transcript.length / bpPerPx;
7612
+ const startPx = reversed ? minX - widthPx : minX;
7613
+ const height = Math.round((currentRow + 1 / 2) * apolloRowHeight) + row * apolloRowHeight;
7614
+ ctx.strokeStyle = theme?.palette.text.primary ?? 'black';
7615
+ ctx.beginPath();
7616
+ ctx.moveTo(startPx, height);
7617
+ ctx.lineTo(startPx + widthPx, height);
7618
+ ctx.stroke();
7619
+ }
7551
7620
  function drawDragPreview$1(stateModel, overlayCtx) {
7552
7621
  const { apolloDragging, apolloRowHeight, lgv, theme } = stateModel;
7553
7622
  const { bpPerPx, displayedRegions, offsetPx } = lgv;
@@ -7631,6 +7700,22 @@ function getFeatureFromLayout$1(feature, bp, row, featureTypeOntology) {
7631
7700
  }
7632
7701
  return feature;
7633
7702
  }
7703
+ function getCDSCount(feature, featureTypeOntology) {
7704
+ const { children, type } = feature;
7705
+ if (!children) {
7706
+ return 0;
7707
+ }
7708
+ const isMrna = featureTypeOntology.isTypeOf(type, 'mRNA');
7709
+ let cdsCount = 0;
7710
+ if (isMrna) {
7711
+ for (const [, child] of children) {
7712
+ if (featureTypeOntology.isTypeOf(child.type, 'CDS')) {
7713
+ cdsCount += 1;
7714
+ }
7715
+ }
7716
+ }
7717
+ return cdsCount;
7718
+ }
7634
7719
  function getRowCount$1(feature, featureTypeOntology, _bpPerPx) {
7635
7720
  const { children, type } = feature;
7636
7721
  if (!children) {
@@ -7640,12 +7725,12 @@ function getRowCount$1(feature, featureTypeOntology, _bpPerPx) {
7640
7725
  let rowCount = 0;
7641
7726
  if (isTranscript) {
7642
7727
  for (const [, child] of children) {
7643
- const isCds = featureTypeOntology.isTypeOf(child.type, 'CDS');
7644
- if (isCds) {
7728
+ if (featureTypeOntology.isTypeOf(child.type, 'CDS')) {
7645
7729
  rowCount += 1;
7646
7730
  }
7647
7731
  }
7648
- return rowCount;
7732
+ // return 1 if there are no CDSs for non coding genes
7733
+ return rowCount === 0 ? 1 : rowCount;
7649
7734
  }
7650
7735
  for (const [, child] of children) {
7651
7736
  rowCount += getRowCount$1(child, featureTypeOntology);
@@ -7689,6 +7774,9 @@ function featuresForRow$1(feature, featureTypeOntology) {
7689
7774
  for (const cds of cdss) {
7690
7775
  features.push([cds, ...exons, child, feature]);
7691
7776
  }
7777
+ if (cdss.length === 0) {
7778
+ features.push([...exons, child, feature]);
7779
+ }
7692
7780
  }
7693
7781
  return features;
7694
7782
  }
@@ -7936,49 +8024,39 @@ const genericChildGlyph = {
7936
8024
  };
7937
8025
 
7938
8026
  /* eslint-disable @typescript-eslint/no-unnecessary-condition */
8027
+ function getRowsForFeature(startingRow, rowCount, filledRowLocations) {
8028
+ const rowsForFeature = [];
8029
+ for (let i = startingRow; i < startingRow + rowCount; i++) {
8030
+ const row = filledRowLocations.get(i);
8031
+ if (row) {
8032
+ rowsForFeature.push(row);
8033
+ }
8034
+ }
8035
+ return rowsForFeature;
8036
+ }
8037
+ function canPlaceFeatureInRows(rowsForFeature, feature) {
8038
+ for (const rowForFeature of rowsForFeature) {
8039
+ for (const [rowStart, rowEnd] of rowForFeature) {
8040
+ if (doesIntersect2(feature.min, feature.max, rowStart, rowEnd) ||
8041
+ doesIntersect2(rowStart, rowEnd, feature.min, feature.max)) {
8042
+ return false;
8043
+ }
8044
+ }
8045
+ }
8046
+ return true;
8047
+ }
7939
8048
  function layoutsModelFactory(pluginManager, configSchema) {
7940
8049
  const BaseLinearApolloDisplay = baseModelFactory(pluginManager, configSchema);
7941
8050
  return BaseLinearApolloDisplay.named('LinearApolloDisplayLayouts')
7942
8051
  .props({
7943
- featuresMinMaxLimit: 500_000,
8052
+ cleanupBoundary: 200_000,
7944
8053
  })
7945
8054
  .volatile(() => ({
7946
8055
  seenFeatures: observable.map(),
7947
8056
  }))
7948
8057
  .views((self) => ({
7949
- get featuresMinMax() {
7950
- const { assemblyManager } = self.session;
7951
- return self.lgv.displayedRegions.map((region) => {
7952
- const assembly = assemblyManager.get(region.assemblyName);
7953
- let min;
7954
- let max;
7955
- const { end, refName, start } = region;
7956
- for (const [, feature] of self.seenFeatures) {
7957
- if (refName !== assembly?.getCanonicalRefName(feature.refSeq) ||
7958
- !doesIntersect2(start, end, feature.min, feature.max) ||
7959
- feature.length > self.featuresMinMaxLimit ||
7960
- (self.filteredFeatureTypes.length > 0 &&
7961
- !self.filteredFeatureTypes.includes(feature.type))) {
7962
- continue;
7963
- }
7964
- if (min === undefined) {
7965
- ({ min } = feature);
7966
- }
7967
- if (max === undefined) {
7968
- ({ max } = feature);
7969
- }
7970
- if (feature.minWithChildren < min) {
7971
- ({ min } = feature);
7972
- }
7973
- if (feature.maxWithChildren > max) {
7974
- ({ max } = feature);
7975
- }
7976
- }
7977
- if (min !== undefined && max !== undefined) {
7978
- return [min, max];
7979
- }
7980
- return;
7981
- });
8058
+ getAnnotationFeatureById(id) {
8059
+ return self.seenFeatures.get(id);
7982
8060
  },
7983
8061
  getGlyph(feature) {
7984
8062
  if (this.looksLikeGene(feature)) {
@@ -8008,11 +8086,7 @@ function layoutsModelFactory(pluginManager, configSchema) {
8008
8086
  if (!grandChildren?.size) {
8009
8087
  return false;
8010
8088
  }
8011
- const hasCDS = [...grandChildren.values()].some((grandchild) => featureTypeOntology.isTypeOf(grandchild.type, 'CDS'));
8012
- const hasExon = [...grandChildren.values()].some((grandchild) => featureTypeOntology.isTypeOf(grandchild.type, 'exon'));
8013
- if (hasCDS && hasExon) {
8014
- return true;
8015
- }
8089
+ return [...grandChildren.values()].some((grandchild) => featureTypeOntology.isTypeOf(grandchild.type, 'exon'));
8016
8090
  }
8017
8091
  }
8018
8092
  return false;
@@ -8029,15 +8103,11 @@ function layoutsModelFactory(pluginManager, configSchema) {
8029
8103
  .views((self) => ({
8030
8104
  get featureLayouts() {
8031
8105
  const { assemblyManager } = self.session;
8032
- return self.lgv.displayedRegions.map((region, idx) => {
8106
+ return self.lgv.displayedRegions.map((region) => {
8033
8107
  const assembly = assemblyManager.get(region.assemblyName);
8034
8108
  const featureLayout = new Map();
8035
- const minMax = self.featuresMinMax[idx];
8036
- if (!minMax) {
8037
- return featureLayout;
8038
- }
8039
- const [min, max] = minMax;
8040
- const rows = [];
8109
+ // Track the occupied coordinates in each row
8110
+ const filledRowLocations = new Map();
8041
8111
  const { end, refName, start } = region;
8042
8112
  for (const [id, feature] of self.seenFeatures.entries()) {
8043
8113
  if (!isAlive(feature)) {
@@ -8060,42 +8130,23 @@ function layoutsModelFactory(pluginManager, configSchema) {
8060
8130
  let startingRow = 0;
8061
8131
  let placed = false;
8062
8132
  while (!placed) {
8063
- let rowsForFeature = rows.slice(startingRow, startingRow + rowCount);
8133
+ let rowsForFeature = getRowsForFeature(startingRow, rowCount, filledRowLocations);
8064
8134
  if (rowsForFeature.length < rowCount) {
8065
8135
  for (let i = 0; i < rowCount - rowsForFeature.length; i++) {
8066
- const newRowNumber = rows.length;
8067
- rows[newRowNumber] = Array.from({ length: max - min });
8136
+ const newRowNumber = filledRowLocations.size;
8137
+ filledRowLocations.set(newRowNumber, []);
8068
8138
  featureLayout.set(newRowNumber, []);
8069
8139
  }
8070
- rowsForFeature = rows.slice(startingRow, startingRow + rowCount);
8140
+ rowsForFeature = getRowsForFeature(startingRow, rowCount, filledRowLocations);
8071
8141
  }
8072
- if (rowsForFeature
8073
- .map((rowForFeature) => {
8074
- // zero-length features are allowed in the spec
8075
- const featureMax = feature.max - feature.min === 0
8076
- ? feature.min + 1
8077
- : feature.max;
8078
- let start = feature.min - min, end = featureMax - min;
8079
- if (feature.min - min < 0) {
8080
- start = 0;
8081
- end = featureMax - feature.min;
8082
- }
8083
- return rowForFeature.slice(start, end).some(Boolean);
8084
- })
8085
- .some(Boolean)) {
8142
+ if (!canPlaceFeatureInRows(rowsForFeature, feature)) {
8086
8143
  startingRow += 1;
8087
8144
  continue;
8088
8145
  }
8089
8146
  for (let rowNum = startingRow; rowNum < startingRow + rowCount; rowNum++) {
8090
- const row = rows[rowNum];
8091
- let start = feature.min - min, end = feature.max - min;
8092
- if (feature.min - min < 0) {
8093
- start = 0;
8094
- end = feature.max - feature.min;
8095
- }
8096
- row.fill(true, start, end);
8147
+ filledRowLocations.get(rowNum)?.push([feature.min, feature.max]);
8097
8148
  const layoutRow = featureLayout.get(rowNum);
8098
- layoutRow?.push([rowNum - startingRow, feature]);
8149
+ layoutRow?.push([rowNum - startingRow, feature._id]);
8099
8150
  }
8100
8151
  placed = true;
8101
8152
  }
@@ -8108,12 +8159,16 @@ function layoutsModelFactory(pluginManager, configSchema) {
8108
8159
  const { featureTypeOntology } = self.session.apolloDataStore.ontologyManager;
8109
8160
  for (const [idx, layout] of featureLayouts.entries()) {
8110
8161
  for (const [layoutRowNum, layoutRow] of layout) {
8111
- for (const [featureRowNum, layoutFeature] of layoutRow) {
8162
+ for (const [featureRowNum, layoutFeatureId] of layoutRow) {
8112
8163
  if (featureRowNum !== 0) {
8113
8164
  // Same top-level feature in all feature rows, so only need to
8114
8165
  // check the first one
8115
8166
  continue;
8116
8167
  }
8168
+ const layoutFeature = self.getAnnotationFeatureById(layoutFeatureId);
8169
+ if (!layoutFeature) {
8170
+ continue;
8171
+ }
8117
8172
  if (feature._id === layoutFeature._id) {
8118
8173
  return {
8119
8174
  layoutIndex: idx,
@@ -8153,6 +8208,23 @@ function layoutsModelFactory(pluginManager, configSchema) {
8153
8208
  if (!self.lgv.initialized || self.regionCannotBeRendered()) {
8154
8209
  return;
8155
8210
  }
8211
+ // Clear out features that are no longer in the view and out of the cleanup boundary
8212
+ // cleanup boundary + region boundary + cleanup boundary
8213
+ for (const [id, feature] of self.seenFeatures.entries()) {
8214
+ let shouldKeep = false;
8215
+ for (const region of self.regions) {
8216
+ const extendedStart = region.start - self.cleanupBoundary;
8217
+ const extendedEnd = region.end + self.cleanupBoundary;
8218
+ if (doesIntersect2(extendedStart, extendedEnd, feature.min, feature.max)) {
8219
+ shouldKeep = true;
8220
+ break;
8221
+ }
8222
+ }
8223
+ if (!shouldKeep) {
8224
+ self.deleteSeenFeature(id);
8225
+ }
8226
+ }
8227
+ // Add features that are in the current view
8156
8228
  for (const region of self.regions) {
8157
8229
  const assembly = self.session.apolloDataStore.assemblies.get(region.assemblyName);
8158
8230
  const ref = assembly?.getByRefName(region.refName);
@@ -8426,8 +8498,9 @@ function renderingModelFactory(pluginManager, configSchema) {
8426
8498
  for (const [idx, featureLayout] of featureLayouts.entries()) {
8427
8499
  const displayedRegion = displayedRegions[idx];
8428
8500
  for (const [row, featureLayoutRow] of featureLayout.entries()) {
8429
- for (const [featureRow, feature] of featureLayoutRow) {
8430
- if (featureRow > 0) {
8501
+ for (const [featureRow, featureId] of featureLayoutRow) {
8502
+ const feature = self.getAnnotationFeatureById(featureId);
8503
+ if (featureRow > 0 || !feature) {
8431
8504
  continue;
8432
8505
  }
8433
8506
  if (!doesIntersect2(displayedRegion.start, displayedRegion.end, feature.min, feature.max)) {
@@ -8508,11 +8581,18 @@ function mouseEventsModelIntermediateFactory(pluginManager, configSchema) {
8508
8581
  if (!layoutRow) {
8509
8582
  return mousePosition;
8510
8583
  }
8511
- const foundFeature = layoutRow.find((f) => bp >= f[1].min && bp <= f[1].max);
8584
+ const foundFeature = layoutRow.find((f) => {
8585
+ const feature = self.getAnnotationFeatureById(f[1]);
8586
+ return feature && bp >= feature.min && bp <= feature.max;
8587
+ });
8512
8588
  if (!foundFeature) {
8513
8589
  return mousePosition;
8514
8590
  }
8515
- const [featureRow, topLevelFeature] = foundFeature;
8591
+ const [featureRow, topLevelFeatureId] = foundFeature;
8592
+ const topLevelFeature = self.getAnnotationFeatureById(topLevelFeatureId);
8593
+ if (!topLevelFeature) {
8594
+ return mousePosition;
8595
+ }
8516
8596
  const glyph = self.getGlyph(topLevelFeature);
8517
8597
  const { featureTypeOntology } = self.session.apolloDataStore.ontologyManager;
8518
8598
  if (!featureTypeOntology) {
@@ -8796,11 +8876,18 @@ const useStyles$1 = makeStyles()((theme) => ({
8796
8876
  color: theme.palette.warning.light,
8797
8877
  backgroundColor: theme.palette.warning.contrastText,
8798
8878
  },
8879
+ loading: {
8880
+ position: 'absolute',
8881
+ right: theme.spacing(3),
8882
+ zIndex: 10,
8883
+ pointerEvents: 'none',
8884
+ textAlign: 'right',
8885
+ },
8799
8886
  }));
8800
8887
  const LinearApolloDisplay = observer(function LinearApolloDisplay(props) {
8801
8888
  const theme = useTheme();
8802
8889
  const { model } = props;
8803
- const { apolloRowHeight, contextMenuItems: getContextMenuItems, cursor, featuresHeight, isShown, onMouseDown, onMouseLeave, onMouseMove, onMouseUp, regionCannotBeRendered, session, setCanvas, setCollaboratorCanvas, setOverlayCanvas, setSeqTrackCanvas, setSeqTrackOverlayCanvas, setTheme, } = model;
8890
+ const { loading, apolloRowHeight, contextMenuItems: getContextMenuItems, cursor, featuresHeight, isShown, onMouseDown, onMouseLeave, onMouseMove, onMouseUp, regionCannotBeRendered, session, setCanvas, setCollaboratorCanvas, setOverlayCanvas, setSeqTrackCanvas, setSeqTrackOverlayCanvas, setTheme, } = model;
8804
8891
  const { classes } = useStyles$1();
8805
8892
  const lgv = getContainingView(model);
8806
8893
  useEffect(() => {
@@ -8840,65 +8927,68 @@ const LinearApolloDisplay = observer(function LinearApolloDisplay(props) {
8840
8927
  setContextCoord(coord);
8841
8928
  setContextMenuItems(getContextMenuItems(coord));
8842
8929
  }
8843
- } }, message ? (React__default.createElement(Alert, { severity: "warning", classes: { message: classes.ellipses } },
8844
- React__default.createElement(Tooltip, { title: message },
8845
- React__default.createElement("div", null, message)))) : (
8846
- // Promise.resolve() in these 3 callbacks is to avoid infinite rendering loop
8847
- // https://github.com/mobxjs/mobx/issues/3728#issuecomment-1715400931
8848
- React__default.createElement(React__default.Fragment, null,
8849
- React__default.createElement("canvas", { ref: async (node) => {
8850
- await Promise.resolve();
8851
- setCollaboratorCanvas(node);
8852
- }, width: lgv.dynamicBlocks.totalWidthPx, height: featuresHeight, className: classes.canvas, "data-testid": "collaboratorCanvas" }),
8853
- React__default.createElement("canvas", { ref: async (node) => {
8854
- await Promise.resolve();
8855
- setCanvas(node);
8856
- }, width: lgv.dynamicBlocks.totalWidthPx, height: featuresHeight, className: classes.canvas, "data-testid": "canvas" }),
8857
- React__default.createElement("canvas", { ref: async (node) => {
8858
- await Promise.resolve();
8859
- setOverlayCanvas(node);
8860
- }, width: lgv.dynamicBlocks.totalWidthPx, height: featuresHeight, onMouseMove: onMouseMove, onMouseLeave: onMouseLeave, onMouseDown: onMouseDown, onMouseUp: onMouseUp, className: classes.canvas, style: { cursor: cursor ?? 'default' }, "data-testid": "overlayCanvas" }),
8861
- lgv.displayedRegions.flatMap((region, idx) => {
8862
- const assembly = assemblyManager.get(region.assemblyName);
8863
- return [...session.apolloDataStore.checkResults.values()]
8864
- .filter((checkResult) => assembly?.isValidRefName(checkResult.refSeq) &&
8865
- assembly.getCanonicalRefName(checkResult.refSeq) ===
8866
- region.refName &&
8867
- doesIntersect2(region.start, region.end, checkResult.start, checkResult.end))
8868
- .map((checkResult) => {
8869
- const left = (lgv.bpToPx({
8870
- refName: region.refName,
8871
- coord: checkResult.start,
8872
- regionNumber: idx,
8873
- })?.offsetPx ?? 0) - lgv.offsetPx;
8874
- const [feature] = checkResult.ids;
8875
- if (!feature) {
8876
- return null;
8877
- }
8878
- const { topLevelFeature } = feature;
8879
- const row = parent
8880
- ? model.getFeatureLayoutPosition(topLevelFeature)
8881
- ?.layoutRow ?? 0
8882
- : 0;
8883
- const top = row * apolloRowHeight;
8884
- const height = apolloRowHeight;
8885
- return (React__default.createElement(Tooltip, { key: checkResult._id, title: checkResult.message },
8886
- React__default.createElement(Avatar, { className: classes.avatar, style: { top, left, height, width: height } },
8887
- React__default.createElement(ErrorIcon, null))));
8888
- });
8889
- }),
8890
- React__default.createElement(Menu$1, { open: contextMenuItems.length > 0, onMenuItemClick: (_, callback) => {
8891
- callback();
8892
- setContextMenuItems([]);
8893
- }, onClose: () => {
8894
- setContextMenuItems([]);
8895
- }, TransitionProps: {
8896
- onExit: () => {
8930
+ } },
8931
+ loading ? (React__default.createElement("div", { className: classes.loading },
8932
+ React__default.createElement(CircularProgress, { size: "18px" }))) : null,
8933
+ message ? (React__default.createElement(Alert, { severity: "warning", classes: { message: classes.ellipses } },
8934
+ React__default.createElement(Tooltip, { title: message },
8935
+ React__default.createElement("div", null, message)))) : (
8936
+ // Promise.resolve() in these 3 callbacks is to avoid infinite rendering loop
8937
+ // https://github.com/mobxjs/mobx/issues/3728#issuecomment-1715400931
8938
+ React__default.createElement(React__default.Fragment, null,
8939
+ React__default.createElement("canvas", { ref: async (node) => {
8940
+ await Promise.resolve();
8941
+ setCollaboratorCanvas(node);
8942
+ }, width: lgv.dynamicBlocks.totalWidthPx, height: featuresHeight, className: classes.canvas, "data-testid": "collaboratorCanvas" }),
8943
+ React__default.createElement("canvas", { ref: async (node) => {
8944
+ await Promise.resolve();
8945
+ setCanvas(node);
8946
+ }, width: lgv.dynamicBlocks.totalWidthPx, height: featuresHeight, className: classes.canvas, "data-testid": "canvas" }),
8947
+ React__default.createElement("canvas", { ref: async (node) => {
8948
+ await Promise.resolve();
8949
+ setOverlayCanvas(node);
8950
+ }, width: lgv.dynamicBlocks.totalWidthPx, height: featuresHeight, onMouseMove: onMouseMove, onMouseLeave: onMouseLeave, onMouseDown: onMouseDown, onMouseUp: onMouseUp, className: classes.canvas, style: { cursor: cursor ?? 'default' }, "data-testid": "overlayCanvas" }),
8951
+ lgv.displayedRegions.flatMap((region, idx) => {
8952
+ const assembly = assemblyManager.get(region.assemblyName);
8953
+ return [...session.apolloDataStore.checkResults.values()]
8954
+ .filter((checkResult) => assembly?.isValidRefName(checkResult.refSeq) &&
8955
+ assembly.getCanonicalRefName(checkResult.refSeq) ===
8956
+ region.refName &&
8957
+ doesIntersect2(region.start, region.end, checkResult.start, checkResult.end))
8958
+ .map((checkResult) => {
8959
+ const left = (lgv.bpToPx({
8960
+ refName: region.refName,
8961
+ coord: checkResult.start,
8962
+ regionNumber: idx,
8963
+ })?.offsetPx ?? 0) - lgv.offsetPx;
8964
+ const [feature] = checkResult.ids;
8965
+ if (!feature) {
8966
+ return null;
8967
+ }
8968
+ const { topLevelFeature } = feature;
8969
+ const row = parent
8970
+ ? model.getFeatureLayoutPosition(topLevelFeature)
8971
+ ?.layoutRow ?? 0
8972
+ : 0;
8973
+ const top = row * apolloRowHeight;
8974
+ const height = apolloRowHeight;
8975
+ return (React__default.createElement(Tooltip, { key: checkResult._id, title: checkResult.message },
8976
+ React__default.createElement(Avatar, { className: classes.avatar, style: { top, left, height, width: height } },
8977
+ React__default.createElement(ErrorIcon, null))));
8978
+ });
8979
+ }),
8980
+ React__default.createElement(Menu$1, { open: contextMenuItems.length > 0, onMenuItemClick: (_, callback) => {
8981
+ callback();
8897
8982
  setContextMenuItems([]);
8898
- },
8899
- }, anchorReference: "anchorPosition", anchorPosition: contextCoord
8900
- ? { top: contextCoord[1], left: contextCoord[0] }
8901
- : undefined, style: { zIndex: theme.zIndex.tooltip }, menuItems: contextMenuItems }))))));
8983
+ }, onClose: () => {
8984
+ setContextMenuItems([]);
8985
+ }, TransitionProps: {
8986
+ onExit: () => {
8987
+ setContextMenuItems([]);
8988
+ },
8989
+ }, anchorReference: "anchorPosition", anchorPosition: contextCoord
8990
+ ? { top: contextCoord[1], left: contextCoord[0] }
8991
+ : undefined, style: { zIndex: theme.zIndex.tooltip }, menuItems: contextMenuItems }))))));
8902
8992
  });
8903
8993
 
8904
8994
  const TrackLines = observer(function TrackLines({ model, }) {