Skip to content
Snippets Groups Projects
README.md 18.5 KiB
Newer Older
Elora-V's avatar
Elora-V committed
# viz-layout

Elora-V's avatar
Elora-V committed

## Description
Elora-V's avatar
Elora-V committed

Elora-V's avatar
Elora-V committed
The viz-layout is a hierarchical layout to draw metabolic pathways. To use the layout, a network with nodes and directed links is required. User can provide an object describing the network style. If defined in the network style, height and width of nodes can be taken into account. The layout change the position of nodes, and can add some classes for nodes and links.  
The layout is designed to be applied on directed bipartite graphs, where metabolite nodes are only connected to reaction nodes. Reactions can be reversible, but for each reversible reaction, all links must be declared for one direction. To reverse the direction of a reaction, the source and target of the links are swapped. Additionally, metabolite nodes can be marked as side compounds, with common examples being water and ATP. A step of the layout can duplicate them and place them after the other nodes.

The y-axis is inverted compared to the usual Cartesian convention (where smaller y values are lower on the graph). It was designed for systems like SVG where smaller y values are positioned at the top of the screen. Therefore, the initial reactants, placed at the start of the reaction sequence, will have smaller y values, positioning them at the top of the diagram. The products of the reaction will have larger y values, placing them lower.

Elora-V's avatar
Elora-V committed
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" class="bi bi-exclamation-triangle-fill" viewBox="0 0 16 16">
  <path d="M8.982 1.566a1.13 1.13 0 0 0-1.96 0L.165 13.233c-.457.778.091 1.767.98 1.767h13.713c.889 0 1.438-.99.98-1.767zM8 5c.535 0 .954.462.9.995l-.35 3.507a.552.552 0 0 1-1.1 0L7.1 5.995A.905.905 0 0 1 8 5m.002 6a1 1 0 1 1 0 2 1 1 0 0 1 0-2"/>
</svg> The layout doesn’t work for large graphs. The exact size limit is currently unknown, but it will be determined as development progresses.

---

## Table of Contents

- [Getting Started](#getting-started)
  - [Create a Typescript Project](#create-a-typescript-project)
  - [Install via npm](#install-via-npm)
  - [Typescript Configuration](#typescript-configuration)
- [Usage](#usage)
- [Types](#types)
  - [Types for Network](#types-for-network)
    - [Network](#network)
    - [Node](#node)
    - [Link](#link)
  - [Types for Style](#types-for-style)
    - [GraphStyleProperties](#graphstyleproperties)
    - [NodeStyle](#nodestyle)
  - [Types for Parameters](#types-for-parameters)
Elora-V's avatar
Elora-V committed
- [getDefaultParam()](#getdefaultparam)
- [layoutOnNetwork()](#layoutonnetwork)
    - [Step of the Layout](#step-of-the-layout)
        - [Base Layout](#base-layout)
        - [Management of Side Compounds](#management-of-side-compounds)
        - [Management of Reversible Reaction](#management-of-reversible-reaction)
        - [Management of Directed Cycles](#management-of-directed-cycles)
        - [Management of Main Chains](#management-of-main-chains)
        - [Shifting Coordinates](#shifting-coordinates)

---
Elora-V's avatar
Elora-V committed

## Getting started

#### Create a typescript project

1. **Initialize a new project**

   First, create a new directory for your project and initialize it with `npm`:

   ```bash
   mkdir my-project
   cd my-project
   npm init -y
   ```

2. **Install TypeScript**

    You'll need to install TypeScript as a development dependency in your project:

    ```bash
    npm install --save-dev typescript
    ```

3. **Set up the TypeScript configuration**

    TypeScript requires a `tsconfig.json` file (at the root of the project) to specify how your TypeScript code should be compiled. You can generate a default configuration file by running:

    ```bash
    npx tsc --init
    ```

   

4. **Create your source files**

    Inside your project, create a `src` directory and a `index.ts` file for your TypeScript code:
Elora-V's avatar
Elora-V committed

    ```bash
    mkdir src
    touch src/index.ts
Elora-V's avatar
Elora-V committed
    ```


#### Install via npm

The viz-layout package is currently only available on the MetaboHUB forge. To install it, you need to configure an `.npmrc` file (at the root of the project) to specify the retrieval path.

```.npmrc
@metabohub:registry=https://forgemia.inra.fr/api/v4/packages/npm/
```

Then you can install the package:

```
npm install @metabohub/viz-layout
```

#### Typescript configuration


Once the installation step is completed, you need to declare the module. To do this, add the following line in the `env.d.ts` file (at the root of the project):

```ts 
declare module "@metabohub/viz-layout";
```

Elora-V's avatar
Elora-V committed
## Usage


```typescript
// Imports
Elora-V's avatar
Elora-V committed
import type { UserParameters, Network , Node, Link, GraphStyleProperties, NodeStyle} from "@metabohub/viz-layout";
Elora-V's avatar
Elora-V committed
import { layoutOnNetwork, getDefaultParam, PathType } from "@metabohub/viz-layout";
Elora-V's avatar
Elora-V committed

// Creation of network
const nodes : {[key:string]:Node} = {
Elora-V's avatar
Elora-V committed
	MetaboliteA: {
		id: 'MetaboliteA',
Elora-V's avatar
Elora-V committed
		x: 50,
		y: 50,
        classes: ["metabolite"],
        metadata :{
            isSideCompound : false
        }
	},
Elora-V's avatar
Elora-V committed
	Reaction1: {
		id: 'Reaction1',
Elora-V's avatar
Elora-V committed
		x: 100,
		y: 100,
        classes: ["reaction"],
        metadata : {
            isReversible : true
        }
Elora-V's avatar
Elora-V committed
	},
  MetaboliteB: {
		id: 'MetaboliteB',
		x: 100,
Elora-V's avatar
Elora-V committed
		y: 150,
Elora-V's avatar
Elora-V committed
        classes: ["metabolite"],
        metadata : {
            isSideCompound : false
        }
Elora-V's avatar
Elora-V committed
	}
}
const links : Link[] = [
	{
Elora-V's avatar
Elora-V committed
		source: nodes.MetaboliteA,
		target: nodes.Reaction1,
		id: 'MetaboliteA->Reaction1'
	},
  {
		source: nodes.Reaction1,
		target: nodes.MetaboliteB,
		id: 'Reaction1->MetaboliteB'
Elora-V's avatar
Elora-V committed
	}
]
const network:Network = { id: 'network', nodes: nodes, links: links };


// Creation of network styles
Elora-V's avatar
Elora-V committed
const nodeStyle :  {[key:string]:NodeStyle} = {
Elora-V's avatar
Elora-V committed
            metabolite: {
                height: 50,
                width: 50
            },
            reaction: {
Elora-V's avatar
Elora-V committed
                height: 25,
                width: 25
Elora-V's avatar
Elora-V committed
            }
        };
const networkStyle :GraphStyleProperties ={ nodeStyles: nodeStyle };

// Choosing parameters
//      get default parameters and modify some of them
Elora-V's avatar
Elora-V committed
const parameters:UserParameters=getDefaultParam();
Elora-V's avatar
Elora-V committed
parameters.doCycle = false;
Elora-V's avatar
Elora-V committed
parameters.pathType = PathType.ALL_LONGEST;
//      estimate parameters of spacing depending on network and network style (not necessary)
const defaultSpacing= getDefaultSpacingAttributesPixel(network,styleNetwork,2);
parameters.spacePixelHorizontal=defaultSpacing.spacePixelHorizontal;
parameters.spacePixelVertical=defaultSpacing.spacePixelVertical;
Elora-V's avatar
Elora-V committed

// Application of layout
const newNetwork = await layoutOnNetwork(network, networkStyle, parameters);
Elora-V's avatar
Elora-V committed
```

Elora-V's avatar
Elora-V committed
Another example of a more complex network can be found in the `public` folder. It includes variables for the nodes, the links, network, and a svg of the drawing with default parameters.

<img src="public/HistidineMetabolism.png" height="500px">
Elora-V's avatar
Elora-V committed

Elora-V's avatar
Elora-V committed

## Types 

### Types for network

##### Network

| Attribut | Type | Description |
| -------- | ---- | ----------- |
| id | `string` | Network's id |
| nodes | `{[key: string] Node}` | Object that contains nodes |
| links | `Array<Link>` | List that contains links |


##### Node

| Attribut | Type | Description |
| -------- | ---- | ----------- |
| id | `string` | Node's id |
| x | `number` | X node's position |
| y | `number` | Y node's position |
| classes | `Array<string>` | Node's classes to manage style |
| metadata | `{[key: string]: string \| number \| {[key: string]: string \| number} \| Array<string> \| boolean}` | Node's metadata |


To have a bipartite metabolic network, the classes of a node can contain at least either `metabolite` or `reaction`.
Elora-V's avatar
Elora-V committed

Metadata can contains those elements :
| Key | Type | Description |
| -------- | ---- | ----------- |
| isSideCompound | `boolean` | Node is declared as a side compound |
| duplicate | `boolean` | Node is declared as a duplicated node |
| isReversible | `boolean` | Node is declared as a reversible |
Elora-V's avatar
Elora-V committed


If `isSideCompound` or `duplicate` is not set, the step to handle side compounds will not perform any actions, as no nodes are marked as side compounds or duplicated node. A `duplicate` class will be added to side compound nodes that are duplicated.
If `isReversible` is not set, and the reaction node does not have a `reaction` class, the step to handle reversible reactions will not perform any actions, as no reactions are marked as reversible.
Elora-V's avatar
Elora-V committed
##### Link

| Attribut | Type | Description |
| -------- | ---- | ----------- |
| id | string | Link's id |
| source | Node | Source node of the link |
| target | Node | Target node of the link |
Elora-V's avatar
Elora-V committed
| classes | Array<string> | Link's classes|
Elora-V's avatar
Elora-V committed

<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" class="bi bi-exclamation-triangle-fill" viewBox="0 0 16 16">
  <path d="M8.982 1.566a1.13 1.13 0 0 0-1.96 0L.165 13.233c-.457.778.091 1.767.98 1.767h13.713c.889 0 1.438-.99.98-1.767zM8 5c.535 0 .954.462.9.995l-.35 3.507a.552.552 0 0 1-1.1 0L7.1 5.995A.905.905 0 0 1 8 5m.002 6a1 1 0 1 1 0 2 1 1 0 0 1 0-2"/>
Elora-V's avatar
Elora-V committed
</svg> Both the source and target must be references to nodes that are present in the network (see <a href="#usage">usage</a> for details)!
Elora-V's avatar
Elora-V committed

In order to have a bipartite graph, links should associate a metabolite node with a reaction node.
A class `reversible` will be added by the layout for links associated with a reversible reaction if the step to manage reversible reaction is done. 

### Types for style

#### GraphStyleProperties

| Attribut | Type | Description |
| -------- | ---- | ----------- |
| nodeStyles | { [key: string]: NodeStyle } | Object that contains nodes classes name associated to their style |

Elora-V's avatar
Elora-V committed
The keys of nodeStyles must match the node classes to correctly associate styles with the corresponding nodes.
Elora-V's avatar
Elora-V committed
#### NodeStyle

| Attribut | Type | Description |
| -------- | ---- | ----------- |
Elora-V's avatar
Elora-V committed
| height | `number` | Node's height (in pixels)|
| width | `number` | Node's width (in pixels) |

If not defined for a node, the default value used is 25 pixels.
Elora-V's avatar
Elora-V committed


### Types for parameters

Elora-V's avatar
Elora-V committed
#### UserParameters

To initialize the parameters, use the `getDefaultParam()` function from the package, it return a type `UserParameters`. You can then modify the desired parameters as needed (see [usage](#usage)). Refer to the [Step of the layout](#step-of-the-layout) section for a better understanding of certain parameters.

| Parameter                | Type        | Default       | Description                                                                                      |
|--------------------------|-------------|---------------|--------------------------------------------------------------------------------------------------|
Elora-V's avatar
Elora-V committed
| spacePixelHorizontal | `number` | 100 | Minimum horizontal spacing (in pixels) for hierarchical layout |
| spacePixelVertical | `number`|100 |  Minimum vertical spacing (in pixels) for hierarchical layout |
Elora-V's avatar
Elora-V committed
|doDuplicateSideCompounds  | `boolean`     | true          | Whether to duplicate side compounds                                                            |
| doPutAsideSideCompounds   | `boolean`     | true          | Whether to remove (temporarily) side compounds          |
|doInsertionSideCompounds  | `boolean`     | true          | Whether to reinsert side compounds                                                            |
Elora-V's avatar
Elora-V committed
| doMainChain               | `boolean`     | true          | Whether to find and clusterize main chain                                       |
| pathType                  | `PathType`    | ALL_LONGEST   | Defines the path type for the main chain step: LONGEST, ALL_LONGEST, or ALL.      |
Elora-V's avatar
Elora-V committed
| merge | `boolean` | true| Whether to merge path with common nodes |
| doCycle                   | `boolean`     | true          | Whether to process directed cycles                                                          |
| shiftCoord                | `boolean`     | false          | Whether to get top left corner coordinates of nodes (if false, center of nodes is returned)     |
Elora-V's avatar
Elora-V committed

Elora-V's avatar
Elora-V committed

Elora-V's avatar
Elora-V committed


Elora-V's avatar
Elora-V committed
## getDefaultParam

Get default parameters for the layout. 

`getDefaultParam()` 

##### Output

Return default parameters.

 | Type | Description | 
| ----------- | --|
  `UserParameters` | default user parameters |
Elora-V's avatar
Elora-V committed



## getDefaultSpacingAttributesPixel

Estimate minimal spacing parameters (`spacePixelHorizontal`, `spacePixelVertical`) depending on the size of nodes in the network.

`getDefaultSpacingAttributesPixel(network:Network,styleNetwork?:GraphStyleProperties,factor?:number)`
Elora-V's avatar
Elora-V committed

##### Input

| Arguments | Type | default | Description | Optional |
| ----- | ---- | ------- | ----------- | -------- |
| network | `Network` | - | Network object that contains nodes and links of the network | No |
| networkStyle | `GraphStyleProperties` | *undefined* | Network style object that contains classes and style associated with nodes | Yes |
| factor | `number` | 3 | A factor to increase the minimal spacing | yes |


The network style input is not mandatory but the function isn't usefull if not provided as all nodes will be considered of size 25 pixels.
Elora-V's avatar
Elora-V committed


##### Output

Return an object with spacing parameters.

 |Key| Type | Description | 
 ---- | ----------- | --|
 | spacePixelHorizontal | `number` | Mean of width of all nodes in the network multiply by the factor |
 | spacePixelVertical | `number` | Mean of height of all nodes in the network multiply by the factor |

Elora-V's avatar
Elora-V committed


## layoutOnNetwork
Elora-V's avatar
Elora-V committed
Apply the layout on a network.

`layoutOnNetwork(network, networkStyle?, parameters?)` is a asynchronous function.
Elora-V's avatar
Elora-V committed

##### Input

| Arguments | Type | default | Description | Optional |
| ----- | ---- | ------- | ----------- | -------- |
Elora-V's avatar
Elora-V committed
| network | `Network` | - | Network object that contains nodes and links of the network | No |
| networkStyle | `GraphStyleProperties` | { } | Network style object that contains classes and style associated with nodes | Yes |
| parameters | `Parameters` | *undefined* | Parameters of the layout | Yes |


If parameters is undefined, default parameters defined above are used (see ([defaultParameters](#types-for-parameters))) and the values for `spacePixelHorizontal` and `spacePixelVertical` are estimated with `getDefaultSpacingAttributesPixel`.
Elora-V's avatar
Elora-V committed


##### Output

 Type | Description 
 ---- | ----------- 
 `Promise<Network>` | network with modified node positions, and some added classes  
Elora-V's avatar
Elora-V committed


#### Step of the layout

##### Base Layout

The base layout used in the algorithm is a Sugiyama layout. It is implemented by the viz.js library (https://github.com/mdaines/viz-js), a JS wrapper of the dot layout of GraphViz (https://graphviz.org/documentation/). The separation between nodes in the dot layout is define with `spacePixelVertical` (graphviz attribut `ranksep`) and by `spacePixelHorizontal` (graphviz attribut `nodesep`).

<small>E. R. GANSNER, E. KOUTSOFIOS, S. C. NORTH et K.-P. VO, “A technique for drawing directed graphs”, IEEE Transactions on Software Engineering, t. 19, no 3, p. 214-230, 1993.</small>

Elora-V's avatar
Elora-V committed
  
##### Management of side compounds

Nodes declared as side compounds are duplicated if `doDuplicateSideCompounds = true`. It will add a `duplicate` attribut and class on those nodes. If side compounds nodes are already duplicated and  `doDuplicateSideCompounds = false`, they need to be declared as duplicated nodes (see [type node](#node)).
Elora-V's avatar
Elora-V committed

 If `doPutAsideSideCompounds = true`, duplicated nodes are temporarily removed from the network object.
 They will be reinserted based on the locations of other nodes during the layout process if `doInsertionSideCompounds = true`.
Elora-V's avatar
Elora-V committed

If side compounds are temporarily removed and then reinserted without being duplicated, there is currently no method to reintegrate them and that can create an error in the algorithm.
Elora-V's avatar
Elora-V committed

##### Management of reversible reaction

Elora-V's avatar
Elora-V committed
For this step to take effect, reaction nodes representing a reversible reaction must have the class `reaction` in their classes and `metadata.isReversible = true`.
Elora-V's avatar
Elora-V committed

In this step, a direction is choosen to each reversible reaction not yet determined. The method promotes the continuity of the reaction sequence in the drawing. Links corresponding to these reactions are assigned the class `reversible`.
Elora-V's avatar
Elora-V committed

##### Management of directed cycles

The step is done if `doCycle = true`.
Elora-V's avatar
Elora-V committed

Elora-V's avatar
Elora-V committed
Directed cycles with more than three nodes are identified and arranged in a circle as much as possible. When multiple directed cycles share common nodes, not all nodes may be positioned in a circle; those nodes will be placed using a force layout (utilizing the D3 library: https://d3js.org/).
Elora-V's avatar
Elora-V committed

Elora-V's avatar
Elora-V committed
It is important to note that not all cycles may be detected. A timer is implemented to limit the duration of the cycle search, ensuring the process does not take too long. As a result, the outcomes are not deterministic, and the set of cycles found may vary between runs.

For the cycle search, the algorithm considers both directions of reversible reactions, enabling the identification of different directed cycles. The direction of a reversible reaction that allows for cycle formation is retained. This selection is made prior to the direction choice in the reversible reaction management step.
Elora-V's avatar
Elora-V committed

##### Management of main chains

The step is done if `doMainChain = true`.

Elora-V's avatar
Elora-V committed
Some nodes representing the "main chain" are grouped into a cluster subgraph in the viz.js library (Sugiyama layout), which may enhance or negatively impact the resulting drawing. You can test the effect of this step by enabling or disabling it.
Main chains are defined as merges of paths in the network. Start nodes are determined by the algorithm, after which the network is converted into a Directed Acyclic Graph (DAG) with no directed cycles. The longest path(s) is identified from each start nodes. If multiple paths share common nodes, they can be merged (`merge = true`), with each merged path representing a main chain. If `merge = false` and paths have common nodes, only the longest of them is defined as a main chain.
Several versions of the path search are implemented :
    - `LONGEST` : when longest paths are searched from a start node, if several are found, only one will be keeped
    - `ALL_LONGEST` : when longest paths are searched from a start node, all the longest are keeped
    - `ALL` : when longest paths are searched from a start node, all paths between start node and terminal node of longest paths are keeped

`ALL_LONGEST` is the default version, to change it you need to change the parameter pathType with the defined type `PathType` (ex : `parameters.pathType = PathType.ALL`).
Elora-V's avatar
Elora-V committed


##### Shifting coordinates
Elora-V's avatar
Elora-V committed

Elora-V's avatar
Elora-V committed
If `shiftCoord = true`, the node's coordinates represent its top-left corner; otherwise, they represent the node's center (default). When shifting the coordinates, the adjustment is made so that positioning the node by its top-left corner keeps it centered in its original location. Specifically, the coordinates are shifted by half the node's height and width. To define a node's `height` and `width`, refer to [type for style](#types-for-style). This is useful when rendering networks in SVG format.