react-phylogeny-tree-gl
Version:
React integration of phylocanvas.gl. Component and hook for phylogenetic tree visualistion.
273 lines (222 loc) • 7.08 kB
Markdown
React integration of [phylocanvas.gl](https://phylocanvas.gl). Components (and hooks) for phylogenetic tree visualisation for Newick tree format.
It handles up to 100 000+ leaves.
```bash
yarn add react-phylogeny-tree-gl
```
It includes zoom buttons, context menu (adds context menu plugin to plugins array) and support `redoUndo` plugin with control buttons.
Props:
- `initProps`: phylocanvas.gl properties used for initialisation. Should be ref. stable (eg. memoized), if changes phylocanvas instance is reinitialised
- `controlledProps`: phylocanvas.gl properties, when change phylocanvas.gl tree.setProps method is called with new value. During initialisation props `initProps` and `controlledProps` are merged `{...initProps, ...controlledProps}`.
- `plugins`: array of phylocanvas.gl plugins, viz section Plugins bellow
- should be Should be ref stable (eg. memoized), if changes phylogeny-tree instance is reinitialised
- `hooks`: array of hooks, viz section Hooks bellow. Should be ref stable.
- `zoomButtons`: boolean, when `interactive` (in `initProps`) and `zoomButtons` are `true` buttons for zoom appears.
- `zoomButtonsStyle`: `CSSProperties` object passed to zoom buttons container.
- `ref` reference which expose getTree function which returns phylocanvas.gl instance and allows us to perform imperative operations on it.
There have to be `source` prop in either in `initProps` or `controlledProps`. The source type is:
```typescript
type Newick = string;
type BiojsTree = {
children: BiojsTree[];
name: string | number;
branch_length: number;
};
type Source =
| Newick
| {
type: 'newick';
data: Newick;
original?: Source;
}
| {
type: 'biojs';
data: BiojsTree;
original?: Source;
};
```
For correct function of zoom buttons, context menu and redo-undo control buttons it is necessary.
```javascript
import 'react-phylogeny-tree-gl/styles.css';
```
used by PhylogenyTree component. Utilise `usePhylogenyTree` to instantiate phylocanvas.gl.
instantiate phylocanvas.gl
phylocanvas.gl supported plugins of type:
```typescript
export type Plugins<
P extends PhylocanvasInitProps,
M extends Record<string, (...args: unknown[]) => unknown>
> = ((tree: Phylocanvas<P, M>, decorate: Decorate<P, M>) => void)[];
export type Decorate<
P extends PhylocanvasInitProps = PhylocanvasInitProps,
M extends Record<string, (...args: unknown[]) => unknown> = Record<
string,
(...args: unknown[]) => unknown
>
> = <MethodName extends keyof (M & PhylocanvasMethods<P>)>(
fnName: MethodName,
fn: (
delegate: (M & PhylocanvasMethods<P>)[MethodName],
args: Parameters<(PhylocanvasMethods<P> & M)[MethodName]>
) => unknown
) => void;
```
Shows scalebar at right corner
import from `react-phylogeny-tree.gl/hooks`. Pass in array with stable reference between rerenders (memoize). Their type is:
```typescript
type Hooks<P extends PhylocanvasProps, M> = ((
getTree: () => Phylocanvas<P, M> | null,
props: P & PhylocanvasProps
) => void)[];
```
react hook which wraps `setRootNLevelsUp`. Needs to receive folowing object under key `leafSubtree` from `props`.
```typescript
leafSubtree: {
leafID?: string;
noLevels?: number;
minLeafToRootLength?: number;
setLeafLabels?: (ids: (string | number)[]) => void;
};
```
react hook for autoresizing canvas when window size changes.
```typescript
import React from 'react';
import PhylogenyTree from 'react-phylogeny-tree-gl';
import { useLeafSubtree, useAutoResize } from 'react-phylogeny-tree-gl/hooks';
import {
createOnSelectPlugin,
createOnViewSubtreePlugin,
createOnRedrawReRootTreePlugin,
createRedoUndoPlugin,
onClickHighlightOffsprings,
scalebar,
} from 'react-phylogeny-tree-gl/plugins';
import { PhylogenyTreeRef, UndoRedoMethods } from 'react-phylogeny-tree-gl/types';
type TreeNodeColors = {
[]: { fillColour: string } | null;
};
type TreeProps = {
newick: string;
leafColors: TreeNodeColors;
selectedLeafs: string[];
subtreeBaseLeaf: string | undefined;
rootId?: string;
noLevelsUp: number;
minBranchLength: number;
};
export type RefProps = {
source: string;
interactive: boolean;
nodeSize: number;
haloRadius: number;
haloWidth: number;
scalebar: boolean;
highlightColour: [number, number, number, number];
rootId: string;
selectedIds: string[];
styles: TreeNodeColors;
leafSubtree: {
leafID: string;
noLevels: number;
minLeafToRootLength: number;
};
};
export type TreeRef = PhylogenyTreeRef<RefProps, UndoRedoMethods>;
const hooks = [useAutoResize, useLeafSubtree];
export const Tree = React.forwardRef(TreeComponent);
function TreeComponent(
{
newick,
rootId,
leafColors,
selectedLeafs,
subtreeBaseLeaf,
noLevelsUp,
minBranchLength,
}: TreeProps,
ref?: React.RefObject<TreeRef>
): JSX.Element {
const showHelp = React.useContext(ShowHelpContext);
const initOptions = React.useMemo(
() => ({
source: newick,
rootId: null,
interactive: true,
nodeSize: 7,
haloRadius: 6,
haloWidth: 2,
scalebar: true,
highlightColour: tuple(52, 182, 199, 255),
}),
[]
);
const controlledOptions = React.useMemo(
() => ({
selectedIds: selectedLeafs,
styles: leafColors,
leafSubtree: {
leafID: subtreeBaseLeaf,
noLevels: noLevelsUp,
minLeafToRootLength: minBranchLength,
},
}),
[]
);
const plugins = React.useMemo(() => {
return [
createRedoUndoPlugin(),
createOnSelectPlugin((_tree, selectedLeafLabels) => {
if (selectedLeafLabels && selectedLeafLabels.length) {
console.log(selectedLeafLabels)
}
}),
createOnViewSubtreePlugin((tree, leafsInTree) => {
console.log(leafsInTree)
}),
createOnRedrawReRootTreePlugin((tree, leafsInTree) => {
console.log(leafsInTree, tree.props.rootId, false);
}),
onClickHighlightOffsprings,
scalebar,
];
}, []);
React.useEffect(() => {
if (ref.current) {
const tree = ref.current.getTree();
if (tree.props.rootId !== rootId) {
tree.setRoot(rootId);
}
}
}, [rootId, ref]);
return (
<div style={position: 'relative'}>
<PhylogenyTree
ref={ref}
initProps={initOptions}
controlledProps={controlledOptions}
hooks={hooks}
plugins={plugins}
/>
</div>
);
}
const tuple = <T extends unknown[]>(...args: T): T => args;
```