Developing a new visualisation
This documentation describes how to build an interactive visualization using Fabric.js and jdispatcher-viewers. The visualisation leverages Fabric.js objects (e.g., rectangles) to create a dynamic, customizable canvas-based experience.
Overview
Fabric.js supports features like gradient fills, object manipulation, and dynamic updates, making it suitable for applications such as biological data visualisation. Here we demonstrate how to build a small basic visualisation for viewing protein domain annotations on a sequence track.
Prerequisites
- Fabric.js: Tested with version 5.4.0
- TypeScript: Tested with version 4.9
- Browser: Modern browser with Canvas support
Installation
- Include Fabric.js in your new project:
   npm install fabric
- Likewise, you need to install jdispatcher-viewers:
   npm install jdispatcher-viewers
Using jdispatcher-viewers
For an example of how jdispatcher-viewers is used to provide the two main visualisations, Visual Output and Functional Predictions, see index.ts.
Creating a new visualisation with Fabric.js and some of the jdispatcher-viewers utilities
Before starting, ensure you have basic knowledge of Fabric.js for canvas rendering as jdispatcher-viewers builds on top of that.
Below is a step-by-step guide to setting up a basic Fabric.js visualization using a <script> tag to load Fabric.js and a <canvas> element to render the visualization.
HTML based example
- Create wew HTML file (e.g. 'demo.html') and set up a canvas element:
  <!DOCTYPE html>
  <html lang="en">
  <head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Fabric.js and jdispatcher-viewers Demo</title>
    <style>
    canvas {
      border: 1px solid #ccc;
    }
  </style>
  <!-- Load Fabric.js from a CDN with defer -->
  <script
    type="text/javascript"
    src="https://cdnjs.cloudflare.com/ajax/libs/fabric.js/5.4.0/fabric.min.js"
    defer
  ></script>
  <!-- Load jdispatcher-viewers -->
  <script
    type="text/javascript"
    src="https://ebi-jdispatcher.github.io/jdispatcher-viewers/assets/jd_viewers_0.1.6.bundle.min.js"
    defer
  ></script>
  </head>
  <body>
    <!-- Canvas -->
    <div id="canvas-wrapper">
      <canvas id="canvas" />
    </div>
    <!-- Your custom script -->
    <script>
      // Your Fabric.js code goes here
    </script>
  </body>
  </html>
Inside the <script> tag, initialize Fabric.js and create a canvas object:
  // Wrap script with a DOMContentLoaded event listener 
  document.addEventListener("DOMContentLoaded", () => {
  // Get the canvas element
  const canvasElement = document.getElementById("canvas");
  // Initialize Fabric.js canvas
  const startupDef = {
    defaultCursor: 'default',
    moveCursor: 'default',
    hoverCursor: 'default',
    backgroundColor: '#ffffff',
  };
  const canvas = new fabric.Canvas(canvasElement, startupDef);
});
Now, you can add objects like title, domain label and domain track:
    // set a number of variables
    let topPadding = 0;
    let canvasWidth = 800;
    let canvasHeight = 100;
    let contentWidth = 300;
    let marginWidth = (0.15 * canvasWidth) / 100;
    let fontSize = 14;
    let fontWeigth = "normal";
    let fontFamily = "Arial";
    // set height and width of the canvas
    canvas.setWidth(canvasWidth);
    canvas.setHeight(canvasHeight);
    // draw title
    topPadding += 20;
    const titleObj = { ...JDViewers.textDefaults };
    titleObj.fontWeight = "bold";
    titleObj.fontSize = fontSize + 1;
    titleObj.top = topPadding;
    titleObj.left = contentWidth;
    const titleText = new fabric.Text("Demo Visualisation", titleObj);
    canvas.add(titleText);
    topPadding += 30;
    // draw sequence label
    const labelObj = { ...JDViewers.textDefaults };
    labelObj.fontWeight = "bold";
    labelObj.fontSize = fontSize + 1;
    labelObj.top = topPadding - 10 ;
    labelObj.left = 40;
    const domainLabel = new fabric.Text("Domain", labelObj);
    canvas.add(domainLabel);
    // draw sequence track
    const lineTrackGroup = JDViewers.drawLineTracks(
    {
        startPixels: 100,
        endPixels: 750,
    },
    { strokeWidth: 1 },
    topPadding
    );
    canvas.add(lineTrackGroup);
    // draw a domain
    topPadding += 10;
    const seqDomain = JDViewers.drawDomain(150, 550, topPadding, "green");
    canvas.add(seqDomain);
You can make the visualization interactive reacting to mouse hovering and clicking. For example:
  // domain mouse hovering and click
  const domainObj = { id: "domain", name: "test domain", type: "protein" };
  JDViewers.mouseOverDomain(
      seqDomain,
      150,
      550,
      150,
      550,
      domainObj,
      { fontSize: fontSize },
      canvas
  );
  JDViewers.mouseOutDomain(seqDomain, canvas);
  // Mouse click display/hide domain tooltip
  JDViewers.mouseClickDomain(
      seqDomain,
      150,
      550,
      150,
      550,
      domainObj,
      {
      fontSize: fontSize,
      },
      canvas
  );
  // finally, do not forget to render all elements
  canvas.renderAll();
- 
Open the demo.htmlfile in your browser.
- 
You should see a canvas with a sequence track on domain in green. 
- 
Interact with the objects (e.g., mouse hover over one of the domains or click on them to reveal the tooltip information). 
Develop a new JavaScript/TypeScript project
- Initialize a new project (if you don’t already have one):
mkdir visualisation-demo
cd visualisation-demo
npm init -y
npm install fabric @ebi-jdispatcher/jdispatcher-viewers
npm install --save-dev @types/fabric
fabric-demo/
├── src/
│   ├── index.ts (or index.js)
│   ├── styles.css
├── index.html
├── package.json
├── tsconfig.json (if using TypeScript)
└── webpack.config.js (or vite.config.js, etc.)
Create an index.html file in the root of your project:
<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Fabric.js and jdispatcher-viewers Demo</title>
</head>
<body>
  <div id="canvas-wrapper">
    <canvas id="canvas" />
  </div>
  <script src="./dist/bundle.js"></script> <!-- Output bundle file -->
</body>
</html>
Create a styles.css file in the src/ folder:
canvas {
  border: 1px solid #ccc;
  background-color: #fff;
}
Create an index.ts (or index.js) file in the src/ folder. For example for TypeScript:
import { fabric } from "fabric";
import {
  BasicCanvasRenderer,
  RenderOptions,
  textDefaults,
  drawLineTracks,
  mouseOverDomain,
  mouseClickDomain,
  mouseOutDomain,
  drawDomain,
} from "@ebi-jdispatcher/jdispatcher-viewers";
// Get the canvas element
export class DemoVisualisation extends BasicCanvasRenderer {
  private topPadding: number = 0;
  private queryLen: number = 0;
  private subjLen: number = 0;
  private startQueryPixels: number;
  private endQueryPixels: number;
  private startEvalPixels: number;
  private startSubjPixels: number;
  private endSubjPixels: number;
  private gradientSteps: number[] = [];
  private queryFactor: number = 1.0;
  private subjFactor: number = 1.0;
  constructor(
    element: string | HTMLCanvasElement,
    renderOptions: RenderOptions
  ) {
    super(element);
    console.log("vlieeded");
    renderOptions.canvasWidth != undefined
      ? (this.canvasWidth = renderOptions.canvasWidth)
      : (this.canvasWidth = 800);
    renderOptions.canvasHeight != undefined
      ? (this.canvasHeight = renderOptions.canvasHeight)
      : (this.canvasHeight = 100);
    renderOptions.contentWidth != undefined
      ? (this.contentWidth = renderOptions.contentWidth)
      : (this.contentWidth = 300);
    renderOptions.marginWidth != undefined
      ? (this.marginWidth = renderOptions.marginWidth)
      : (this.marginWidth = (0.15 * this.canvasWidth) / 100);
    renderOptions.fontSize != undefined
      ? (this.fontSize = renderOptions.fontSize)
      : (this.fontSize = 14);
    renderOptions.fontWeigth != undefined
      ? (this.fontWeigth = renderOptions.fontWeigth)
      : (this.fontWeigth = "normal");
    renderOptions.fontFamily != undefined
      ? (this.fontFamily = renderOptions.fontFamily)
      : (this.fontFamily = "Arial");
    this.topPadding = 0;
    this.getFabricCanvas();
  }
  public render() {
    // clear the canvas
    // this.canvas.clear();
    console.log("rendering from canvas");
    // canvas title
    this.drawHeaderGroup();
    // canvas content
    this.drawContentGroup();
    // finishing off
    this.setFrameSize();
    // render all
    this.renderCanvas();
    this.canvas.renderAll();
  }
  protected drawHeaderGroup() {
    this.topPadding += 2;
    // draw title
    const textObj = { ...textDefaults };
    textObj.fontWeight = "bold";
    textObj.fontSize = this.fontSize! + 1;
    textObj.top = this.topPadding;
    textObj.left = this.contentWidth;
    const titleText = new fabric.Text("Demo Visualisation", textObj);
    this.canvas.add(titleText);
  }
  protected drawContentGroup() {
    this.topPadding += 30;
    // draw sequence label
    const labelObj = { ...textDefaults };
    labelObj.fontWeight = "bold";
    labelObj.fontSize = this.fontSize + 1;
    labelObj.top = this.topPadding - 10;
    labelObj.left = 40;
    const domainLabel = new fabric.Text("Domain", labelObj);
    this.canvas.add(domainLabel);
    // draw sequence track
    const lineTrackGroup = drawLineTracks(
      {
        startPixels: 100,
        endPixels: 750,
      },
      { strokeWidth: 1 },
      this.topPadding
    );
    this.canvas.add(lineTrackGroup);
    // draw a domain
    const seqDomain = drawDomain(150, 550, this.topPadding, "orange");
    this.canvas.add(seqDomain);
    // domain mouse hovering and click
    const domainObj = { id: "domain", name: "test domain", type: "protein" };
    mouseOverDomain(
      seqDomain,
      150,
      550,
      150,
      550,
      domainObj,
      { fontSize: this.fontSize },
      this
    );
    mouseOutDomain(seqDomain, this);
    // Mouse click display/hide domain tooltip
    mouseClickDomain(
      seqDomain,
      150,
      550,
      150,
      550,
      domainObj,
      {
        fontSize: this.fontSize,
      },
      this
    );
  }
  protected setFrameSize() {
    this.canvas.setWidth(this.canvasWidth);
    this.canvas.setHeight(this.canvasHeight);
  }
  protected renderCanvas() {
    this.canvas.renderAll();
  }
}
(window as any).DemoVisualisation = DemoVisualisation;
Using Webpack. Install Webpack and its CLI:
npm install --save-dev webpack webpack-cli webpack-dev-server ts-loader typescript
Create a webpack.config.js file:
  
const path = require('path');
module.exports = {
  entry: './src/index.ts',
  output: {
    filename: 'bundle.js',
    path: path.resolve(__dirname, 'dist'),
  },
  devServer: {
    static: './',
    hot: true,
  },
  module: {
    rules: [
      {
        test: /\.ts$/,
        use: 'ts-loader',
        exclude: /node_modules/,
      },
      {
        test: /\.css$/,
        use: ['style-loader', 'css-loader'],
      },
    ],
  },
  resolve: {
    extensions: ['.ts', '.js'],
  },
};
Add the following scripts to package.json:
  
"scripts": {
  "start": "webpack serve --open",
  "build": "webpack"
}
Finally run the project, and open your browser and navigate to http://localhost:3000 (or the port specified by your dev server). You should see the Fabric.js canvas visualisation.
npm start
API Reference
- Check Fabric.js documentation for advanced features like groups, animations, or filters.
- For a complete overview of the jdispatcher-viewers modules and utilities, check the API Docs.