Skip to content

Commit

Permalink
Merge branch 'develop' into one_row_viewer
Browse files Browse the repository at this point in the history
  • Loading branch information
josh committed May 7, 2023
2 parents 1a8eb63 + 7b8f1a3 commit 1f8ed93
Show file tree
Hide file tree
Showing 11 changed files with 81 additions and 59 deletions.
28 changes: 13 additions & 15 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@
- [Installation](#installation)
- [Instantiation](#instantiation)
- [Props](#props)
- [Custom Viewer Positioning](#custom-viewer-positioning)
- [Without React](#without-react)
- [Contact Us](#contact-us)

Expand All @@ -37,7 +38,7 @@ You can see a demo at [tools.latticeautomation.com/seqviz](https://tools.lattice

## Features

### Linear and Circular Sequence Viewer
### Linear and Circular Sequence Viewers

- Annotations with names and colors
- Amino acid translations
Expand Down Expand Up @@ -292,40 +293,37 @@ Whether to show the complement sequence.

By default, the circular viewer rotates when scrolling over the viewer. That can be disabled with rotateOnScroll: false.

#### `Custom Rendering`
#### `refs: (={ circular: undefined, linear: undefined })`

See: [custom viewer positioning](#custom-viewer-positioning)

### Custom Viewer Positioning

This makes use of the `children` and `refs` props to allow custom rendering of the sequence viewers. For example, to render the linear viewer above the circular viewer:

```jsx
import { useRef } from "react";
import { SeqViz, Linear, Circular } from "seqviz";
import { Circular, Linear, SeqViz } from "seqviz";

export default () => {
const circular = useRef();
const linearRef = useRef();
const circularRef = useRef();

return (
<SeqViz
name="J23100"
seq="TTGACGGCTAGCTCAGTCCTAGGTACAGTGCTAGC"
refs={{circular: circularRef, linear: linearRef}}
>
<SeqViz name="J23100" seq="TTGACGGCTAGCTCAGTCCTAGGTACAGTGCTAGC" refs={{ circular, linear }}>
{({ circularProps, linearProps, ...props }) => (
<div
style={{ display: "flex", flexDirection: "column", width: "100%" }}
>
<div ref={linearRef} style={{ height: "25%", width: "100%" }}>
<div style={{ display: "flex", flexDirection: "column", width: "100%" }}>
<div ref={linear} style={{ height: "25%", width: "100%" }}>
<Linear {...linearProps} {...props} />
</div>
<div ref={circularRef} style={{ height: "75%", width: "100%" }}>
<div ref={circular} style={{ height: "75%", width: "100%" }}>
<Circular {...circularProps} {...props} />
</div>
</div>
)}
</SeqViz>
);
};

```

### Without React
Expand Down
20 changes: 9 additions & 11 deletions demo/lib/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@ interface AppState {
showIndex: boolean;
showSelectionMeta: boolean;
showSidebar: boolean;
translations: { direction?: 1 | -1; end: number; start: number; }[];
translations: { direction?: 1 | -1; end: number; start: number }[];
viewer: string;
zoom: number;
}
Expand Down Expand Up @@ -104,19 +104,17 @@ export default class App extends React.Component<any, AppState> {
};

render() {

let customChildren = null;
if (this.state.customChildren) {
customChildren = ({ circularProps, linearProps, ...props }) => {
if (this.state.viewer === "linear") {
return (
<div ref={this.linearRef} style={{ height: "100%", width: "100%" }}>
<Linear {...linearProps} {...props} />
</div>
return (
<div ref={this.linearRef} style={{ height: "100%", width: "100%" }}>
<Linear {...linearProps} {...props} />
</div>
);
}
else if (this.state.viewer === "circular") {
return (
} else if (this.state.viewer === "circular") {
return (
<div ref={this.circularRef} style={{ height: "100%", width: "100%" }}>
<Circular {...circularProps} {...props} />
</div>
Expand Down Expand Up @@ -155,7 +153,7 @@ export default class App extends React.Component<any, AppState> {
</div>
);
}
}
};
}

return (
Expand Down Expand Up @@ -222,7 +220,7 @@ export default class App extends React.Component<any, AppState> {
annotations={this.state.annotations}
enzymes={this.state.enzymes}
name={this.state.name}
refs={{circular: this.circularRef, linear: this.linearRef}}
refs={{ circular: this.circularRef, linear: this.linearRef }}
search={this.state.search}
selection={this.state.selection}
seq={this.state.seq}
Expand Down
3 changes: 2 additions & 1 deletion demo/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,8 @@
"deploy": "npm update seqviz && npm run build && aws s3 sync ./out s3://lattice-tools-s3/seqviz --delete && aws cloudfront create-invalidation --distribution-id E3NMX6D92LFTAV --paths '/seqviz/*'",
"fix": "prettier ./lib/** ./pages/** --write && eslint lib --ext ts,tsx --fix",
"lint": "prettier ./lib/** --check && eslint lib --ext ts,tsx --quiet",
"start": "open http://localhost:3010/ && next dev -p 3010"
"start": "open http://localhost:3010/ && next dev -p 3010",
"dev": "next dev -p 3010"
},
"dependencies": {
"browserslist": "^4.21.3",
Expand Down
4 changes: 2 additions & 2 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

3 changes: 2 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
{
"name": "seqviz",
"description": "A viewer for DNA, RNA, and protein sequences that supports many input formats",
"version": "3.8.0",
"version": "3.8.2",
"main": "dist/index.js",
"types": "dist/index.d.ts",
"unpkg": "dist/seqviz.min.js",
Expand All @@ -13,6 +13,7 @@
"minor": "./release.sh minor",
"patch": "./release.sh patch",
"start": "cd demo && npm run start",
"dev": "cd demo && npm install && npm run dev",
"test": "CI=true jest"
},
"keywords": [
Expand Down
4 changes: 2 additions & 2 deletions src/Linear/Linear.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -128,7 +128,7 @@ export default class Linear extends React.Component<LinearProps> {

// TODO: this should also use createMultiRows
const translationRows = translations.length
? createSingleRows(createTranslations(translations, seq, seqType), bpsPerBlock, arrSize)
? createMultiRows(stackElements(createTranslations(translations, seq, seqType), seq.length), bpsPerBlock, arrSize)
: new Array(arrSize).fill([]);

for (let i = 0; i < arrSize; i += 1) {
Expand Down Expand Up @@ -202,7 +202,7 @@ export default class Linear extends React.Component<LinearProps> {
size={size}
stackedAnnotations={stackedAnnotations}
stackedTranslations={stackedTranslations}
translations={translationRows[i]}
translationRows={translationRows[i]}
x={xDiff}
y={yDiff}
zoom={zoom}
Expand Down
2 changes: 1 addition & 1 deletion src/Linear/SeqBlock.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ const defaultProps = {
size: { height: 600, width: 1200 },
stackedAnnotations: [],
stackedTranslations: [],
translations: [],
translationRows: [],
y: 0,
zoom: { linear: 50 },
zoomed: true,
Expand Down
14 changes: 7 additions & 7 deletions src/Linear/SeqBlock.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,7 @@ interface SeqBlockProps {
size: Size;
stackedAnnotations: Annotation[][];
stackedTranslations: NameRange[][];
translations: Translation[];
translationRows: Translation[][];
x: number;
y: number;
zoom: { linear: number };
Expand Down Expand Up @@ -244,7 +244,7 @@ export class SeqBlock extends React.PureComponent<SeqBlockProps> {
size,
stackedAnnotations,
stackedTranslations,
translations,
translationRows,
zoom,
zoomed,
} = this.props;
Expand Down Expand Up @@ -275,14 +275,14 @@ export class SeqBlock extends React.PureComponent<SeqBlockProps> {

// height and yDiff of translations
const translationYDiff = compYDiff + compHeight;
const translationHeight = elementHeight * (oneRow ? stackedTranslations.length : translations.length);
const translationHeight = elementHeight * (oneRow ? stackedTranslations.length : translationRows.length);

// height and yDiff of annotations
const annYDiff = translationYDiff + translationHeight;
const annHeight = elementHeight * (oneRow ? stackedAnnotations.length : annotationRows.length);

// height and ydiff of the index row.
const elementGap = translationHeight || annHeight ? 3 : 0;
// height and yDiff of the index row.
const elementGap = annotationRows.length + translationRows.length ? 3 : 0;
const indexRowYDiff = annYDiff + annHeight + elementGap;

// calc the height necessary for the sequence selection
Expand Down Expand Up @@ -368,7 +368,7 @@ export class SeqBlock extends React.PureComponent<SeqBlockProps> {
listenerOnly={false}
zoomed={zoomed}
/>
{translations.length && (
{translationRows.length && (
<TranslationRows
bpsPerBlock={bpsPerBlock}
charWidth={charWidth}
Expand All @@ -381,7 +381,7 @@ export class SeqBlock extends React.PureComponent<SeqBlockProps> {
oneRow={oneRow}
seqType={seqType}
stackedPositions={stackedTranslations}
translations={translations}
translationRows={translationRows}
yDiff={translationYDiff}
onUnmount={onUnmount}
/>
Expand Down
53 changes: 37 additions & 16 deletions src/Linear/Translations.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ interface TranslationRowsProps {
oneRow: boolean;
seqType: SeqType;
stackedPositions: NameRange[][];
translations: Translation[];
translationRows: Translation[][];
yDiff: number;
}

Expand All @@ -34,46 +34,67 @@ export const TranslationRows = ({
inputRef,
lastBase,
onUnmount,
oneRow,
seqType,
stackedPositions,
translations,
translationRows,
yDiff,
}: TranslationRowsProps) => (
<g className="la-vz-linear-translation" data-testid="la-vz-linear-translation">
{translations.map((t, i) => (
{translationRows.map((translations, i) => (
<TranslationRow
key={`${t.id}-${firstBase}`}
key={`i-${firstBase}`}
bpsPerBlock={bpsPerBlock}
charWidth={charWidth}
findXAndWidth={findXAndWidth}
firstBase={firstBase}
fullSeq={fullSeq}
height={elementHeight * 0.9}
id={t.id}
inputRef={inputRef}
lastBase={lastBase}
seqType={seqType}
translation={t}
y={
yDiff +
elementHeight *
(oneRow ? (stackedPositions.findIndex(rows => rows.some(item => item.id === t.id)) as number) : i)
}
translations={translations}
y={yDiff + elementHeight * i}
onUnmount={onUnmount}
/>
))}
</g>
);

interface TranslationRowProps {
/**
* A single row of translations. Multiple of these may be in one seqBlock
* vertically stacked on top of one another in non-overlapping arrays.
*/
const TranslationRow = (props: {
bpsPerBlock: number;
charWidth: number;
findXAndWidth: FindXAndWidthType;
firstBase: number;
fullSeq: string;
height: number;
inputRef: InputRefFunc;
lastBase: number;
onUnmount: (a: unknown) => void;
seqType: SeqType;
translations: Translation[];
y: number;
}) => (
<>
{props.translations.map((t, i) => (
<SingleNamedElement
{...props} // include overflowLeft in the key to avoid two split annotations in the same row from sharing a key
key={`translation-linear-${t.id}-${i}-${props.firstBase}-${props.lastBase}`}
translation={t}
/>
))}
</>
);

interface SingleNamedElementProps {
bpsPerBlock: number;
charWidth: number;
findXAndWidth: FindXAndWidthType;
firstBase: number;
fullSeq: string;
height: number;
id?: string;
inputRef: InputRefFunc;
lastBase: number;
onUnmount: (a: unknown) => void;
Expand All @@ -86,7 +107,7 @@ interface TranslationRowProps {
* A single row for translations of DNA into Amino Acid sequences so a user can
* see the resulting protein or peptide sequence in the viewer
*/
class TranslationRow extends React.PureComponent<TranslationRowProps> {
class SingleNamedElement extends React.PureComponent<SingleNamedElementProps> {
AAs: string[] = [];

// on unmount, clear all AA references.
Expand Down
3 changes: 2 additions & 1 deletion src/SelectionHandler.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -234,7 +234,7 @@ export default class SelectionHandler extends React.PureComponent<SelectionHandl
* Handle a sequence selection event on the circular viewer
*/
handleCircularSeqEvent = (e: SeqVizMouseEvent) => {
const { seq } = this.props;
const { seq, setCentralIndex } = this.props;
const selection = this.context;

const { start } = selection;
Expand All @@ -250,6 +250,7 @@ export default class SelectionHandler extends React.PureComponent<SelectionHandl
: this.calcSelectionLength(selStart, currBase, true); // check clockwise selection length
this.selectionStarted = lookahead > 0; // update check for whether there is a prior selection
this.resetCircleDragVars(selStart); // begin drag event
setCentralIndex?.("LINEAR", selStart);

this.setSelection({
...defaultSelection,
Expand Down
6 changes: 4 additions & 2 deletions src/SeqViewerContainer.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -253,10 +253,12 @@ class SeqViewerContainer extends React.Component<SeqViewerContainerProps, SeqVie
const linearProps = this.linearProps();
const circularProps = this.circularProps();

const mergedSelection = this.getSelection(selection, selectionProp);

return (
<div ref={this.props.targetRef} className="la-vz-viewer-container" data-testid="la-vz-viewer-container">
<CentralIndexContext.Provider value={centralIndex}>
<SelectionContext.Provider value={this.getSelection(selection, selectionProp)}>
<SelectionContext.Provider value={mergedSelection}>
<SelectionHandler
bpsPerBlock={linearProps.bpsPerBlock}
center={circularProps.center}
Expand All @@ -271,7 +273,7 @@ class SeqViewerContainer extends React.Component<SeqViewerContainerProps, SeqVie
bpsPerBlock={linearProps.bpsPerBlock}
copyEvent={this.props.copyEvent}
handleMouseEvent={handleMouseEvent}
selection={selection}
selection={mergedSelection}
seq={seq}
setSelection={this.setSelection}
>
Expand Down

0 comments on commit 1f8ed93

Please # to comment.