Browse Source

improved sequence numbers in sequence widget UI

Alexander Rose 5 years ago
parent
commit
f57aafba19

+ 17 - 4
src/mol-plugin/skin/base/components/sequence.scss

@@ -14,7 +14,8 @@
 
 .msp-sequence-wrapper {
     word-break: break-all;
-    padding: $info-vertical-padding $control-spacing $info-vertical-padding $control-spacing;
+    // use $control-spacing for top to have space for sequence numebrs
+    padding: $control-spacing $control-spacing $info-vertical-padding $control-spacing;
     user-select: none;
     height: 100%;
     overflow-y: auto;
@@ -32,15 +33,27 @@
         cursor: pointer;
     }
 
-    .msp-sequence-marker {
-        color: $sequence-marker-color;
+    .msp-sequence-residue-long {
+        margin: 0em 0.2em 0em 0.2em;
+    }
+
+    .msp-sequence-residue-long-begin {
+        margin: 0em 0.2em 0em 0em;
+    }
+
+    .msp-sequence-number {
+        color: $sequence-number-color;
         word-break: keep-all;
         cursor: default;
         position: relative;
         top: -1.1em;
-        left: 3em;
+        left: 3.1em;
         padding: 0px;
         margin-left: -3em;
         font-size: 80%;
     }
+
+    .msp-sequence-number-long {
+        left: 3.3em;
+    }
 }

+ 1 - 1
src/mol-plugin/skin/base/variables.scss

@@ -80,5 +80,5 @@ $entity-tag-color: color-lower-contrast($font-color, 20%);
 
 // sequence
 $sequence-background: $default-background;
-$sequence-marker-color: $hover-font-color;
+$sequence-number-color: $hover-font-color;
 $sequence-select-width: 300px;

+ 52 - 24
src/mol-plugin/ui/sequence/sequence.tsx

@@ -16,9 +16,14 @@ import { Subject } from 'rxjs';
 import { debounceTime } from 'rxjs/operators';
 import { Color } from '../../../mol-util/color';
 
-type SequenceProps = { sequenceWrapper: SequenceWrapper.Any, hideMarkers?: boolean }
+type SequenceProps = {
+    sequenceWrapper: SequenceWrapper.Any,
+    sequenceNumberPeriod?: number,
+    hideSequenceNumbers?: boolean
+}
 
-const SequenceMarkerPeriod = 50
+/** Note, if this is changed, the CSS for `msp-sequence-number` needs adjustment too */
+const MaxSequenceNumberSize = 5
 
 // TODO: this is somewhat inefficient and should be done using a canvas.
 export class Sequence<P extends SequenceProps> extends PluginUIComponent<P> {
@@ -36,6 +41,12 @@ export class Sequence<P extends SequenceProps> extends PluginUIComponent<P> {
         if (changed) this.updateMarker();
     }
 
+    private get sequenceNumberPeriod() {
+        return this.props.sequenceNumberPeriod !== undefined
+            ? this.props.sequenceNumberPeriod as number
+            : (this.props.sequenceWrapper.length > 10 ? 10 : 1)
+    }
+
     componentDidMount() {
         this.plugin.interactivity.lociHighlights.addProvider(this.lociHighlightProvider)
         this.plugin.interactivity.lociSelects.addProvider(this.lociSelectionProvider)
@@ -94,31 +105,58 @@ export class Sequence<P extends SequenceProps> extends PluginUIComponent<P> {
         return marker === 0 ? '' : marker % 2 === 0 ? 'rgb(51, 255, 25)' /* selected */ : 'rgb(255, 102, 153)' /* highlighted */;
     }
 
+    private getResidueClass(seqIdx: number, label: string) {
+        return label.length > 1
+            ? (seqIdx === 0 ? 'msp-sequence-residue-long-begin' : 'msp-sequence-residue-long')
+            : void 0
+    }
+
     private residue(seqIdx: number, label: string, marker: number, color: Color) {
-        const margin = label.length > 1 ? (seqIdx === 0 ? `0px 2px 0px 0px` : `0px 2px 0px 2px`) : void 0
-        return <span key={seqIdx} data-seqid={seqIdx} style={{ color: Color.toStyle(color), backgroundColor: this.getBackgroundColor(marker), margin }}>{label}</span>;
+        return <span key={seqIdx} data-seqid={seqIdx} style={{ color: Color.toStyle(color), backgroundColor: this.getBackgroundColor(marker) }} className={this.getResidueClass(seqIdx, label)}>{label}</span>;
+    }
+
+    private getSequenceNumberClass(seqIdx: number, label: string) {
+        return label.length > 1 && seqIdx > 0
+            ? 'msp-sequence-number msp-sequence-number-long'
+            : 'msp-sequence-number'
+    }
+
+    private location = StructureElement.Location.create();
+    private getSequenceNumber(seqIdx: number, label: string) {
+        let sequenceNumber = ''
+        const loci = this.props.sequenceWrapper.getLoci(seqIdx)
+        const l = StructureElement.Loci.getFirstLocation(loci, this.location);
+        if (l) {
+            const seqId = StructureProperties.residue.auth_seq_id(l)
+            const insCode = StructureProperties.residue.pdbx_PDB_ins_code(l)
+            sequenceNumber = `${seqId}${insCode ? insCode : ''}`
+        }
+        return <span key={`marker-${seqIdx}`} className={this.getSequenceNumberClass(seqIdx, label)}>{sequenceNumber.padEnd(MaxSequenceNumberSize, '\u00A0')}</span>
     }
 
     private updateMarker() {
         if (!this.parentDiv.current) return;
         const xs = this.parentDiv.current.children;
         const { markerArray } = this.props.sequenceWrapper;
-        const markers = !this.props.hideMarkers;
 
         let o = 0;
         for (let i = 0, _i = markerArray.length; i < _i; i++) {
+            if (this.hasSeqenceNumber(i)) o++;
             const span = xs[o] as HTMLSpanElement;
             if (!span) return;
             o++;
-            if (markers && i > 0 && i % SequenceMarkerPeriod === 0) {
-                o++;
-            }
 
             const backgroundColor = this.getBackgroundColor(markerArray[i]);
             if (span.style.backgroundColor !== backgroundColor) span.style.backgroundColor = backgroundColor;
         }
     }
 
+    private hasSeqenceNumber(seqIdx: number) {
+        return !this.props.hideSequenceNumbers &&
+            seqIdx % this.sequenceNumberPeriod === 0 &&
+            seqIdx < this.props.sequenceWrapper.length
+    }
+
     mouseMove = (e: React.MouseEvent) => {
         e.stopPropagation();
 
@@ -155,24 +193,14 @@ export class Sequence<P extends SequenceProps> extends PluginUIComponent<P> {
         const sw = this.props.sequenceWrapper
 
         const elems: JSX.Element[] = [];
-        const markers = !this.props.hideMarkers;
-        const location = StructureElement.Location.create();
+
         for (let i = 0, il = sw.length; i < il; ++i) {
-            elems[elems.length] = this.residue(i, sw.residueLabel(i), sw.markerArray[i], sw.residueColor(i));
-            if (markers && i > 0 && i % SequenceMarkerPeriod === 0) {
-                if (i === sw.length - (1 + 5)) break;
-                // TODO: is this correct way to show the offset?
-                const l = StructureElement.Loci.getFirstLocation(sw.getLoci(i + 1), location);
-                if (l) {
-                    const seqId = StructureProperties.residue.auth_seq_id(l)
-                    const insCode = StructureProperties.residue.pdbx_PDB_ins_code(l)
-                    const label = `${seqId}${insCode ? insCode : ''}`
-                    elems[elems.length] = <span key={`marker-${i}`} className='msp-sequence-marker'>{label.padEnd(5, '\u00A0')}</span>
-                } else {
-                    // show empty marker if the seq id is not known.
-                    elems[elems.length] = <span key={`marker-${i}`} className='msp-sequence-marker'>{'\u00A0'.repeat(5)}</span>
-                }
+            const label = sw.residueLabel(i)
+            // add sequence number before name so the html element do not get separated by a line-break
+            if (this.hasSeqenceNumber(i)) {
+                elems[elems.length] = this.getSequenceNumber(i, label)
             }
+            elems[elems.length] = this.residue(i, label, sw.markerArray[i], sw.residueColor(i));
         }
 
         // calling .updateMarker here is neccesary to ensure existing