@teselagen/ove 0.5.13 → 0.5.14

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/index.umd.js CHANGED
@@ -41069,25 +41069,16 @@ ${latestSubscriptionCallbackError.current.stack}
41069
41069
  var _cof = /* @__PURE__ */ __name(function(it) {
41070
41070
  return toString$3.call(it).slice(8, -1);
41071
41071
  }, "_cof");
41072
- var _iobject;
41073
- var hasRequired_iobject;
41074
- function require_iobject() {
41075
- if (hasRequired_iobject)
41076
- return _iobject;
41077
- hasRequired_iobject = 1;
41078
- var cof2 = _cof;
41079
- _iobject = Object("z").propertyIsEnumerable(0) ? Object : function(it) {
41080
- return cof2(it) == "String" ? it.split("") : Object(it);
41081
- };
41082
- return _iobject;
41083
- }
41084
- __name(require_iobject, "require_iobject");
41072
+ var cof$2 = _cof;
41073
+ var _iobject = Object("z").propertyIsEnumerable(0) ? Object : function(it) {
41074
+ return cof$2(it) == "String" ? it.split("") : Object(it);
41075
+ };
41085
41076
  var _defined = /* @__PURE__ */ __name(function(it) {
41086
41077
  if (it == void 0)
41087
41078
  throw TypeError("Can't call method on " + it);
41088
41079
  return it;
41089
41080
  }, "_defined");
41090
- var IObject = require_iobject();
41081
+ var IObject = _iobject;
41091
41082
  var defined$2 = _defined;
41092
41083
  var _toIobject = /* @__PURE__ */ __name(function(it) {
41093
41084
  return IObject(defined$2(it));
@@ -41208,7 +41199,7 @@ ${latestSubscriptionCallbackError.current.stack}
41208
41199
  var gOPS2 = _objectGops;
41209
41200
  var pIE2 = require_objectPie();
41210
41201
  var toObject2 = _toObject;
41211
- var IObject2 = require_iobject();
41202
+ var IObject2 = _iobject;
41212
41203
  var $assign = Object.assign;
41213
41204
  _objectAssign = !$assign || _fails(function() {
41214
41205
  var A2 = {};
@@ -108790,123 +108781,167 @@ ${latestSubscriptionCallbackError.current.stack}
108790
108781
  return proteinAlphabet[letter.toUpperCase()];
108791
108782
  }
108792
108783
  __name(getAminoAcidFromSequenceTriplet, "getAminoAcidFromSequenceTriplet");
108793
- function getAminoAcidDataForEachBaseOfDna(originalSequenceString, forward, optionalSubrangeRange, isProteinSequence) {
108784
+ function getNextTriplet(index2, sequenceString, exonRange) {
108785
+ let triplet = "";
108786
+ let internalIndex;
108787
+ const codonPositions = [];
108788
+ const isBaseInExon = /* @__PURE__ */ __name((baseIndex) => exonRange.some(
108789
+ (r2) => isPositionWithinRange(baseIndex, r2, sequenceString.length, true, false)
108790
+ ), "isBaseInExon");
108791
+ for (internalIndex = index2; internalIndex < sequenceString.length; internalIndex++) {
108792
+ if (triplet.length === 3) {
108793
+ break;
108794
+ }
108795
+ if (isBaseInExon(internalIndex)) {
108796
+ triplet += sequenceString[internalIndex];
108797
+ codonPositions.push(internalIndex);
108798
+ }
108799
+ }
108800
+ return { triplet, basesRead: internalIndex - index2, codonPositions };
108801
+ }
108802
+ __name(getNextTriplet, "getNextTriplet");
108803
+ function getTranslatedSequenceProperties(originalSequenceString, forward, optionalSubrangeRange, isProteinSequence) {
108794
108804
  const originalSequenceStringLength = isProteinSequence ? originalSequenceString.length * 3 : originalSequenceString.length;
108795
108805
  let sequenceString = originalSequenceString;
108796
- let startOffset = 0;
108806
+ const translationRange = { start: 0, end: originalSequenceStringLength - 1 };
108797
108807
  if (optionalSubrangeRange) {
108798
108808
  sequenceString = getSequenceWithinRange(
108799
108809
  optionalSubrangeRange,
108800
108810
  originalSequenceString
108801
108811
  );
108802
- startOffset = optionalSubrangeRange.start;
108812
+ translationRange.start = optionalSubrangeRange.start;
108813
+ translationRange.end = optionalSubrangeRange.end;
108803
108814
  }
108804
108815
  const sequenceStringLength = isProteinSequence ? sequenceString.length * 3 : sequenceString.length;
108805
- const aminoAcidDataForEachBaseOfDNA = [];
108806
- let codonRange;
108807
- let revCompGapLength = 0;
108808
- let aminoAcidIndex = 0;
108809
- if (!forward) {
108810
- aminoAcidIndex = Math.floor((sequenceStringLength - 1) / 3);
108811
- revCompGapLength = sequenceStringLength % 3;
108812
- codonRange = translateRange(
108813
- {
108814
- start: 0,
108815
- end: revCompGapLength - 1
108816
- },
108817
- startOffset,
108816
+ if (!isProteinSequence && !forward) {
108817
+ sequenceString = getReverseComplementSequenceString(sequenceString);
108818
+ }
108819
+ const absoluteExonRange = !isProteinSequence && optionalSubrangeRange && optionalSubrangeRange.locations ? optionalSubrangeRange.locations : [translationRange];
108820
+ const exonRange = absoluteExonRange.map((range2) => {
108821
+ let outputRange = translateRange(
108822
+ range2,
108823
+ -translationRange.start,
108818
108824
  originalSequenceStringLength
108819
108825
  );
108820
- if (revCompGapLength > 0) {
108821
- for (let i2 = 0; i2 < revCompGapLength; i2++) {
108822
- aminoAcidDataForEachBaseOfDNA.push({
108823
- aminoAcid: getAminoAcidFromSequenceTriplet("xxx"),
108824
- //fake xxx triplet returns the ambiguous X amino acid
108825
- positionInCodon: revCompGapLength - i2 - 1,
108826
- aminoAcidIndex,
108827
- sequenceIndex: codonRange.start + i2,
108828
- codonRange,
108829
- fullCodon: false
108830
- });
108831
- }
108832
- aminoAcidIndex--;
108826
+ if (!forward) {
108827
+ outputRange = flipRelativeRange(
108828
+ outputRange,
108829
+ { start: 0, end: sequenceStringLength - 1 },
108830
+ sequenceStringLength
108831
+ );
108833
108832
  }
108833
+ return outputRange;
108834
+ });
108835
+ return {
108836
+ sequenceString,
108837
+ translationRange,
108838
+ sequenceStringLength,
108839
+ originalSequenceStringLength,
108840
+ exonRange
108841
+ };
108842
+ }
108843
+ __name(getTranslatedSequenceProperties, "getTranslatedSequenceProperties");
108844
+ function positionInCdsToPositionInMainSequence(index2, forward, translationRange, mainSequenceLength) {
108845
+ let outputRange = translateRange(
108846
+ { start: index2, end: index2 },
108847
+ translationRange.start,
108848
+ mainSequenceLength
108849
+ );
108850
+ if (!forward) {
108851
+ outputRange = flipRelativeRange(
108852
+ outputRange,
108853
+ translationRange,
108854
+ mainSequenceLength
108855
+ );
108834
108856
  }
108835
- for (let index2 = 2 + revCompGapLength; index2 < sequenceStringLength; index2 += 3) {
108857
+ return outputRange.start;
108858
+ }
108859
+ __name(positionInCdsToPositionInMainSequence, "positionInCdsToPositionInMainSequence");
108860
+ function getAminoAcidDataForEachBaseOfDna(originalSequenceString, forward, optionalSubrangeRange, isProteinSequence) {
108861
+ const {
108862
+ sequenceString,
108863
+ translationRange,
108864
+ sequenceStringLength,
108865
+ originalSequenceStringLength,
108866
+ exonRange
108867
+ } = getTranslatedSequenceProperties(
108868
+ originalSequenceString,
108869
+ forward,
108870
+ optionalSubrangeRange,
108871
+ isProteinSequence
108872
+ );
108873
+ const aminoAcidDataForEachBaseOfDNA = [];
108874
+ for (let index2 = 0; index2 < sequenceStringLength; index2 += 3) {
108836
108875
  let aminoAcid;
108876
+ const aminoAcidIndex = index2 / 3;
108877
+ let codonPositionsInCDS;
108878
+ let basesRead;
108837
108879
  if (isProteinSequence) {
108838
- aminoAcid = proteinAlphabet[sequenceString[(index2 - 2) / 3].toUpperCase()];
108880
+ codonPositionsInCDS = [0, 1, 2].map((i2) => index2 + i2);
108881
+ basesRead = 3;
108882
+ aminoAcid = proteinAlphabet[sequenceString[index2 / 3].toUpperCase()];
108839
108883
  } else {
108840
- let triplet = sequenceString.slice(index2 - 2, index2 + 1);
108841
- if (!forward) {
108842
- triplet = getReverseComplementSequenceString(triplet);
108843
- }
108844
- aminoAcid = getAminoAcidFromSequenceTriplet(triplet);
108845
- }
108846
- codonRange = translateRange(
108847
- {
108848
- start: index2 - 2,
108849
- end: index2
108850
- },
108851
- startOffset,
108852
- originalSequenceStringLength
108884
+ const {
108885
+ triplet,
108886
+ basesRead: _basesRead,
108887
+ codonPositions
108888
+ } = getNextTriplet(index2, sequenceString, exonRange);
108889
+ basesRead = _basesRead;
108890
+ codonPositionsInCDS = codonPositions;
108891
+ aminoAcid = triplet.length === 3 ? getAminoAcidFromSequenceTriplet(triplet) : getAminoAcidFromSequenceTriplet("xxx");
108892
+ }
108893
+ const absoluteCodonPositions = codonPositionsInCDS.map(
108894
+ (i2) => positionInCdsToPositionInMainSequence(
108895
+ i2,
108896
+ forward,
108897
+ translationRange,
108898
+ originalSequenceStringLength
108899
+ )
108853
108900
  );
108854
- aminoAcidDataForEachBaseOfDNA.push({
108855
- aminoAcid,
108856
- //gap amino acid
108857
- positionInCodon: forward ? 0 : 2,
108858
- aminoAcidIndex,
108859
- sequenceIndex: codonRange.start,
108860
- codonRange,
108861
- fullCodon: true
108862
- });
108863
- aminoAcidDataForEachBaseOfDNA.push({
108864
- aminoAcid,
108865
- //gap amino acid
108866
- positionInCodon: 1,
108867
- aminoAcidIndex,
108868
- sequenceIndex: codonRange.start + 1,
108869
- codonRange,
108870
- fullCodon: true
108871
- });
108872
- aminoAcidDataForEachBaseOfDNA.push({
108873
- aminoAcid,
108874
- //gap amino acid
108875
- positionInCodon: forward ? 2 : 0,
108876
- aminoAcidIndex,
108877
- sequenceIndex: codonRange.start + 2,
108878
- codonRange,
108879
- fullCodon: true
108880
- });
108881
- if (forward) {
108882
- aminoAcidIndex++;
108883
- } else {
108884
- aminoAcidIndex--;
108901
+ const codonRange = forward ? {
108902
+ start: absoluteCodonPositions[0],
108903
+ end: absoluteCodonPositions[codonPositionsInCDS.length - 1]
108904
+ } : {
108905
+ start: absoluteCodonPositions[codonPositionsInCDS.length - 1],
108906
+ end: absoluteCodonPositions[0]
108907
+ };
108908
+ let positionInCodon = 0;
108909
+ for (let i2 = 0; i2 < basesRead; i2++) {
108910
+ const posInCds = i2 + index2;
108911
+ if (codonPositionsInCDS.includes(posInCds)) {
108912
+ aminoAcidDataForEachBaseOfDNA.push({
108913
+ aminoAcid,
108914
+ positionInCodon,
108915
+ aminoAcidIndex,
108916
+ sequenceIndex: absoluteCodonPositions[i2],
108917
+ codonRange,
108918
+ fullCodon: codonPositionsInCDS.length === 3
108919
+ });
108920
+ positionInCodon++;
108921
+ } else {
108922
+ aminoAcidDataForEachBaseOfDNA.push({
108923
+ aminoAcid: null,
108924
+ positionInCodon: null,
108925
+ aminoAcidIndex: null,
108926
+ sequenceIndex: positionInCdsToPositionInMainSequence(
108927
+ posInCds,
108928
+ forward,
108929
+ translationRange,
108930
+ originalSequenceStringLength
108931
+ ),
108932
+ codonRange: null,
108933
+ fullCodon: null
108934
+ });
108935
+ }
108885
108936
  }
108886
- }
108887
- const lengthOfEndBpsNotCoveredByAminoAcids = sequenceStringLength - aminoAcidDataForEachBaseOfDNA.length;
108888
- codonRange = translateRange(
108889
- {
108890
- start: sequenceStringLength - lengthOfEndBpsNotCoveredByAminoAcids,
108891
- end: sequenceStringLength - 1
108892
- },
108893
- startOffset,
108894
- originalSequenceStringLength
108895
- );
108896
- for (let j2 = 0; j2 < lengthOfEndBpsNotCoveredByAminoAcids; j2++) {
108897
- aminoAcidDataForEachBaseOfDNA.push({
108898
- aminoAcid: getAminoAcidFromSequenceTriplet("xxx"),
108899
- //fake xxx triplet returns the gap amino acid
108900
- positionInCodon: j2,
108901
- aminoAcidIndex,
108902
- sequenceIndex: codonRange.start + j2,
108903
- fullCodon: false,
108904
- codonRange
108905
- });
108937
+ index2 += basesRead - codonPositionsInCDS.length;
108906
108938
  }
108907
108939
  if (sequenceStringLength !== aminoAcidDataForEachBaseOfDNA.length) {
108908
108940
  throw new Error("something went wrong!");
108909
108941
  }
108942
+ if (!forward) {
108943
+ aminoAcidDataForEachBaseOfDNA.reverse();
108944
+ }
108910
108945
  return aminoAcidDataForEachBaseOfDNA;
108911
108946
  }
108912
108947
  __name(getAminoAcidDataForEachBaseOfDna, "getAminoAcidDataForEachBaseOfDna");
@@ -147931,7 +147966,7 @@ double click --> edit`}`;
147931
147966
  fill: color2 || "gray"
147932
147967
  }
147933
147968
  )),
147934
- !isFiller && /* @__PURE__ */ React$2.createElement(
147969
+ (!isFiller || isTruncatedEnd && isTruncatedStart) && /* @__PURE__ */ React$2.createElement(
147935
147970
  "text",
147936
147971
  {
147937
147972
  fontSize: 25,
@@ -148008,18 +148043,39 @@ double click --> edit`}`;
148008
148043
  subrangeStartRelativeToAnnotationStart,
148009
148044
  aminoAcids
148010
148045
  );
148046
+ let prevAaSliver;
148047
+ let nextAaSliver = aminoAcidsForSubrange[1];
148048
+ const lastIndex = aminoAcidsForSubrange.length - 1;
148011
148049
  const translationSVG = aminoAcidsForSubrange.map(
148012
148050
  function(aminoAcidSliver, index2) {
148013
- const isEndFiller = index2 === 0 && aminoAcidSliver.positionInCodon === (annotation.forward ? 2 : 0);
148014
- let isTruncatedEnd = index2 === 0 && aminoAcidSliver.positionInCodon === 1;
148015
- let isTruncatedStart = index2 === aminoAcidsForSubrange.length - 1 && aminoAcidSliver.positionInCodon === 1;
148051
+ if (aminoAcidSliver.positionInCodon === null) {
148052
+ prevAaSliver = aminoAcidSliver;
148053
+ nextAaSliver = aminoAcidsForSubrange[index2 + 2];
148054
+ return /* @__PURE__ */ React$2.createElement(
148055
+ "rect",
148056
+ {
148057
+ x: index2 * charWidth2,
148058
+ y: height2 / 2 - height2 / 16,
148059
+ width: charWidth2,
148060
+ height: height2 / 8,
148061
+ fill: "grey",
148062
+ stroke: "black",
148063
+ strokeWidth: 1
148064
+ }
148065
+ );
148066
+ }
148067
+ const isEndFiller = (index2 === 0 || (prevAaSliver == null ? void 0 : prevAaSliver.positionInCodon) === null) && aminoAcidSliver.positionInCodon === (annotation.forward ? 2 : 0);
148068
+ const isStartFiller = (index2 === lastIndex || (nextAaSliver == null ? void 0 : nextAaSliver.positionInCodon) === null) && aminoAcidSliver.positionInCodon === (annotation.forward ? 0 : 2);
148069
+ let isTruncatedEnd = (index2 === 0 || (prevAaSliver == null ? void 0 : prevAaSliver.positionInCodon) === null) && aminoAcidSliver.positionInCodon === 1;
148070
+ let isTruncatedStart = (index2 === lastIndex || (nextAaSliver == null ? void 0 : nextAaSliver.positionInCodon) === null) && aminoAcidSliver.positionInCodon === 1;
148016
148071
  if (!annotation.forward) {
148017
148072
  const holder = isTruncatedEnd;
148018
148073
  isTruncatedEnd = isTruncatedStart;
148019
148074
  isTruncatedStart = holder;
148020
148075
  }
148021
- const isStartFiller = index2 === aminoAcidsForSubrange.length - 1 && aminoAcidSliver.positionInCodon === (annotation.forward ? 0 : 2);
148022
148076
  if (aminoAcidSliver.positionInCodon !== 1 && !isStartFiller && !isEndFiller) {
148077
+ prevAaSliver = aminoAcidSliver;
148078
+ nextAaSliver = aminoAcidsForSubrange[index2 + 2];
148023
148079
  return null;
148024
148080
  }
148025
148081
  const { gapsInside, gapsBefore } = getGaps(aminoAcidSliver.codonRange);
@@ -148036,10 +148092,12 @@ double click --> edit`}`;
148036
148092
  } else {
148037
148093
  color2 = aminoAcid.color;
148038
148094
  }
148095
+ prevAaSliver = aminoAcidSliver;
148096
+ nextAaSliver = aminoAcidsForSubrange[index2 + 2];
148039
148097
  return /* @__PURE__ */ React$2.createElement(
148040
148098
  AASliver$1,
148041
148099
  {
148042
- isFiller: isEndFiller || isStartFiller,
148100
+ isFiller: isEndFiller || isStartFiller || isTruncatedEnd && isTruncatedStart,
148043
148101
  isTruncatedEnd,
148044
148102
  isTruncatedStart,
148045
148103
  onClick: function(event) {
@@ -150251,7 +150309,7 @@ Part of ${annotation.translationType} Translation from BPs ${annotation.start +
150251
150309
  }
150252
150310
  __name(showFileDialog, "showFileDialog");
150253
150311
  const name = "@teselagen/ove";
150254
- const version = "0.5.12";
150312
+ const version = "0.5.13";
150255
150313
  const main = "./src/index.js";
150256
150314
  const type = "module";
150257
150315
  const exports$1 = {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@teselagen/ove",
3
- "version": "0.5.13",
3
+ "version": "0.5.14",
4
4
  "main": "./src/index.js",
5
5
  "exports": {
6
6
  ".": {
@@ -12,7 +12,7 @@
12
12
  "dependencies": {
13
13
  "@teselagen/sequence-utils": "0.3.24",
14
14
  "@teselagen/range-utils": "0.3.7",
15
- "@teselagen/ui": "0.4.10",
15
+ "@teselagen/ui": "0.4.11",
16
16
  "@teselagen/file-utils": "0.3.16",
17
17
  "@teselagen/bounce-loader": "0.3.11",
18
18
  "@teselagen/bio-parsers": "0.4.18",
@@ -36,8 +36,8 @@ function AASliver(props) {
36
36
  path = isFiller
37
37
  ? "25,0 49,0 60,50 49,100 25,100 38,50 25,0"
38
38
  : isTruncatedStart
39
- ? // ? "0,0 50,0 60,50 50,100 00,100 16,50 0,0"
40
- `M ${roundedCorner / 3}, 0
39
+ ? // ? "0,0 50,0 60,50 50,100 00,100 16,50 0,0"
40
+ `M ${roundedCorner / 3}, 0
41
41
  L ${50 - roundedCorner / 3}, 0
42
42
  Q 50 0 ${50 + roundedCorner * dirX1} ${roundedCorner * dirY1}
43
43
  L ${60 - roundedCorner * dirX1}, ${50 - roundedCorner * dirY1}
@@ -59,9 +59,9 @@ function AASliver(props) {
59
59
  L ${roundedCorner * dirX2}, ${roundedCorner * dirY2}
60
60
  Q 0 0 ${roundedCorner / 3} 0
61
61
  z`
62
- : isTruncatedEnd
63
- ? // ? "24,0 74,0 84,50 74,100 24,100 40,50 24,0"
64
- `M ${24 + roundedCorner / 3}, 0
62
+ : isTruncatedEnd
63
+ ? // ? "24,0 74,0 84,50 74,100 24,100 40,50 24,0"
64
+ `M ${24 + roundedCorner / 3}, 0
65
65
  L ${74 - roundedCorner / 3}, 0
66
66
  Q 74 0 ${74 + roundedCorner * dirX1} ${roundedCorner * dirY1}
67
67
  L ${84 - roundedCorner * dirX1}, ${50 - roundedCorner * dirY1}
@@ -83,7 +83,7 @@ function AASliver(props) {
83
83
  L ${24 + roundedCorner * dirX2}, ${roundedCorner * dirY2}
84
84
  Q 24 0 ${24 + roundedCorner / 3} 0
85
85
  z`
86
- : `M ${roundedCorner / 3}, 0
86
+ : `M ${roundedCorner / 3}, 0
87
87
  L ${74 - roundedCorner / 3}, 0
88
88
  Q 74 0 ${74 + roundedCorner * dirX1} ${roundedCorner * dirY1}
89
89
  L ${84 - roundedCorner * dirX1}, ${50 - roundedCorner * dirY1}
@@ -130,10 +130,10 @@ function AASliver(props) {
130
130
  isFiller
131
131
  ? "25,0 49,0 60,50 49,100 25,100 38,50 25,0"
132
132
  : isTruncatedStart
133
- ? "0,0 50,0 60,50 50,100 00,100 16,50 0,0"
134
- : isTruncatedEnd
135
- ? "24,0 74,0 84,50 74,100 24,100 40,50 24,0"
136
- : "0,0 74,0 85,50 74,100 0,100 16,50 0,0"
133
+ ? "0,0 50,0 60,50 50,100 00,100 16,50 0,0"
134
+ : isTruncatedEnd
135
+ ? "24,0 74,0 84,50 74,100 24,100 40,50 24,0"
136
+ : "0,0 74,0 85,50 74,100 0,100 16,50 0,0"
137
137
  }
138
138
  strokeWidth="5"
139
139
  fill={color || "gray"}
@@ -148,7 +148,8 @@ function AASliver(props) {
148
148
  />
149
149
  ))}
150
150
 
151
- {!isFiller && (
151
+ {/* isTruncatedEnd && isTruncatedStart is the special case of a single base exon */}
152
+ {(!isFiller || (isTruncatedEnd && isTruncatedStart)) && (
152
153
  <text
153
154
  fontSize={25}
154
155
  stroke="black"
@@ -57,31 +57,55 @@ class Translation extends React.Component {
57
57
  );
58
58
 
59
59
  //we then loop over all the amino acids in the sub range and draw them onto the row
60
+ let prevAaSliver;
61
+ let nextAaSliver = aminoAcidsForSubrange[1];
62
+ // The last index in the sequence
63
+ const lastIndex = aminoAcidsForSubrange.length - 1;
64
+
60
65
  const translationSVG = aminoAcidsForSubrange.map(
61
66
  function (aminoAcidSliver, index) {
67
+ if (aminoAcidSliver.positionInCodon === null) {
68
+ prevAaSliver = aminoAcidSliver;
69
+ nextAaSliver = aminoAcidsForSubrange[index + 2];
70
+ return (
71
+ <rect
72
+ x={index * charWidth}
73
+ y={height / 2 - height / 16}
74
+ width={charWidth}
75
+ height={height / 8}
76
+ fill="grey"
77
+ stroke="black"
78
+ strokeWidth={1}
79
+ ></rect>
80
+ );
81
+ }
62
82
  const isEndFiller =
63
- index === 0 &&
83
+ (index === 0 || prevAaSliver?.positionInCodon === null) &&
64
84
  aminoAcidSliver.positionInCodon === (annotation.forward ? 2 : 0);
65
- // const isStartFiller = false
85
+
86
+ const isStartFiller =
87
+ (index === lastIndex || nextAaSliver?.positionInCodon === null) &&
88
+ aminoAcidSliver.positionInCodon === (annotation.forward ? 0 : 2);
89
+
66
90
  let isTruncatedEnd =
67
- index === 0 && aminoAcidSliver.positionInCodon === 1;
91
+ (index === 0 || prevAaSliver?.positionInCodon === null) &&
92
+ aminoAcidSliver.positionInCodon === 1;
68
93
  let isTruncatedStart =
69
- index === aminoAcidsForSubrange.length - 1 &&
94
+ (index === lastIndex || nextAaSliver?.positionInCodon === null) &&
70
95
  aminoAcidSliver.positionInCodon === 1;
96
+
71
97
  if (!annotation.forward) {
72
98
  const holder = isTruncatedEnd;
73
99
  isTruncatedEnd = isTruncatedStart;
74
100
  isTruncatedStart = holder;
75
101
  }
76
- const isStartFiller =
77
- index === aminoAcidsForSubrange.length - 1 &&
78
- aminoAcidSliver.positionInCodon === (annotation.forward ? 0 : 2);
79
-
80
102
  if (
81
103
  aminoAcidSliver.positionInCodon !== 1 &&
82
104
  !isStartFiller &&
83
105
  !isEndFiller
84
106
  ) {
107
+ prevAaSliver = aminoAcidSliver;
108
+ nextAaSliver = aminoAcidsForSubrange[index + 2];
85
109
  return null;
86
110
  }
87
111
  const { gapsInside, gapsBefore } = getGaps(aminoAcidSliver.codonRange);
@@ -103,9 +127,17 @@ class Translation extends React.Component {
103
127
  } else {
104
128
  color = aminoAcid.color;
105
129
  }
130
+ prevAaSliver = aminoAcidSliver;
131
+ nextAaSliver = aminoAcidsForSubrange[index + 2];
132
+
106
133
  return (
107
134
  <AASliver
108
- isFiller={isEndFiller || isStartFiller}
135
+ // isTruncatedEnd && isTruncatedStart is the special case of a single base exon
136
+ isFiller={
137
+ isEndFiller ||
138
+ isStartFiller ||
139
+ (isTruncatedEnd && isTruncatedStart)
140
+ }
109
141
  isTruncatedEnd={isTruncatedEnd}
110
142
  isTruncatedStart={isTruncatedStart}
111
143
  onClick={function (event) {