static/js/render.js
/**
 * Render arena given current data and view.
 * @function renderArena
 * @param {Object} mo - main object
 * @description This is the main rendering engine of the program. It is also a
 * computationally expensive task. Several resorts have been adopted to improve
 * performance, including:
 * - Minimize fill style changes.
 * - Minimize number of paths.
 * - Round numbers to integers.
 * - For small circles draw squares instead.
 * - Skip contigs outside the visible region.
 * 
 * @todo {@link https://stackoverflow.com/questions/21089959/}
 * 
 * @todo Rounding floats into integers improves performance. There are more
 * performant methods than the built-in `Math.round` function. Examples are
 * bitwise operations like `~~ (0.5 + num)`. However they don't work well
 * with negative numbers. In this case, x- and y-axis can be negative numbers.
 * 
 * A solution may be changing the way the canvas is positioned to make all
 * coordinates positive.
 * 
 * @todo Skipping contigs outside the visible region results in significant
 * performance gain when zooming in. However it voids another potential
 * optimization: draw the entire image (not matter how large it is) in an
 * off-screen canvas and draw part of it as needed to the main canvas using
 * `drawImage`. Needs further thinking.`
 */
Render arena given current data and view.
@function renderArena
@param {Object} mo - main object
@description This is the main rendering engine of the program. It is also a
computationally expensive task. Several resorts have been adopted to improve
performance, including:
- Minimize fill style changes.
- Minimize number of paths.
- Round numbers to integers.
- For small circles draw squares instead.
- Skip contigs outside the visible region.



@todo Rounding floats into integers improves performance. There are more
performant methods than the built-in `Math.round` function. Examples are
bitwise operations like `~~ (0.5 + num)`. However they don't work well
with negative numbers. In this case, x- and y-axis can be negative numbers.

A solution may be changing the way the canvas is positioned to make all
coordinates positive.

@todo Skipping contigs outside the visible region results in significant
performance gain when zooming in. However it voids another potential
optimization: draw the entire image (not matter how large it is) in an
off-screen canvas and draw part of it as needed to the main canvas using
`drawImage`. Needs further thinking.`
Render arena given current data and view . @function renderArena @param { Object } mo - main object @description This is the main rendering engine of the program . It is also a computationally expensive task . Several resorts have been adopted to improve performance including : - Minimize fill style changes . - Minimize number of paths . - Round numbers to integers . - For small circles draw squares instead . - Skip contigs outside the visible region . @todo Rounding floats into integers improves performance . There are more performant methods than the built - in ` Math . round ` function . Examples are bitwise operations like ` ~~ ( 0 . 5 + num ) ` . However they don ' t work well with negative numbers . In this case x - and y - axis can be negative numbers . A solution may be changing the way the canvas is positioned to make all coordinates positive . @todo Skipping contigs outside the visible region results in significant performance gain when zooming in . However it voids another potential optimization : draw the entire image ( not matter how large it is ) in an off - screen canvas and draw part of it as needed to the main canvas using ` drawImage ` . Needs further thinking . `
function renderArena(mo) {
  const view = mo.view,
        rena = mo.rena,
        mask = mo.masked,
        high = mo.highed;
  let n = mo.cache.nctg;
  const ctx = rena.getContext('2d');
  const w = rena.width,
        h = rena.height;
  ctx.clearRect(0, 0, w, h);
  ctx.save();
  ctx.translate(view.posX, view.posY);
  const scale = view.scale;
  ctx.scale(scale, scale);
  if (!n || !view.x.i || !view.y.i) {
    ctx.restore();
    return;
  }
  const pi2 = Math.PI * 2,
        pi1_2 = Math.sqrt(Math.PI),
        min1 = Math.sqrt(1 / Math.PI),
        min2 = Math.sqrt(4 / Math.PI);
  const trans = mo.trans;
  const X = trans.x,
        Y = trans.y,
        S = trans.size,
        C = trans.rgba;
  const vleft = -view.posX / scale,
        vright = (w - view.posX) / scale,
        vtop = -view.posY / scale,
        vbottom = (h - view.posY) / scale;
  const paths = {};
  let rad, vrad, x, y, fs, hi;
  const nhigh = HIGHLIGHT_PALETTE.length;
  const highs = Array(nhigh).fill().map(() => Array());
  const hirad = 8 / scale;
  for (let i = 0; i < n; i++) {
    if (mask[i]) continue;
    rad = S[i];
    vrad = rad * scale;
    if (vrad < min1) continue;
    x = Math.round(X[i] * w);
    if (x + rad < vleft || x - rad > vright) continue;
    y = Math.round(Y[i] * h);
    if (y + rad < vtop || y - rad > vbottom) continue;
    fs = `rgba(${C[i]})`;
    if (!(fs in paths)) paths[fs] = { 'square': [], 'circle': [] };
    if (vrad < min2) {
      paths[fs].square.push([x, y, Math.round(rad * pi1_2)]);
    }
    else {
      paths[fs].circle.push([x, y, Math.round(rad)]);
    }
    hi = high[i];
    if (hi) {
      highs[hi - 1].push([x, y, Math.round(rad + hirad)]);
    }
  } 
  let j, hs, m, hl;
  for (let i = 0; i < nhigh; i++) {
    hs = highs[i];
    m = hs.length;
    if (!m) continue;
    ctx.fillStyle = HIGHLIGHT_PALETTE[i] + '66'; 
    ctx.beginPath();
    for (j = 0; j < m; j++) {
      hl = hs[j];
      ctx.moveTo(hl[0], hl[1]);
      ctx.arc(hl[0], hl[1], hl[2], 0, pi2, true);
    }
    ctx.fill();
  }
  let squares, sq, circles, circ;
  for (let fs in paths) {
    ctx.fillStyle = fs;
    squares = paths[fs].square;
    n = squares.length;
    for (let i = 0; i < n; i++) {
      sq = squares[i];
      ctx.fillRect(sq[0], sq[1], sq[2], sq[2]);
    }
    circles = paths[fs].circle;
    n = circles.length;
    if (n === 0) continue;
    ctx.beginPath();
    for (let i = 0; i < n; i++) {
      circ = circles[i];
      ctx.moveTo(circ[0], circ[1]);
      ctx.arc(circ[0], circ[1], circ[2], 0, pi2, true);
    }
    ctx.fill();
  } 
  if (view.grid) drawGrid(rena, view);
  ctx.restore();
}
function renderArena ( mo ) { const view = mo . view , rena = mo . rena , mask = mo . masked , high = mo . highed ; let n = mo . cache . nctg ; const ctx = rena . getContext ( '2d' ) ; const w = rena . width , h = rena . height ; ctx . clearRect ( 0 , 0 , w , h ) ; ctx . save ( ) ; ctx . translate ( view . posX , view . posY ) ; const scale = view . scale ; ctx . scale ( scale , scale ) ; if ( ! n || ! view . x . i || ! view . y . i ) { ctx . restore ( ) ; return ; } const pi2 = Math . PI * 2 , pi1_2 = Math . sqrt ( Math . PI ) , min1 = Math . sqrt ( 1 / Math . PI ) , min2 = Math . sqrt ( 4 / Math . PI ) ; const trans = mo . trans ; const X = trans . x , Y = trans . y , S = trans . size , C = trans . rgba ; const vleft = - view . posX / scale , vright = ( w - view . posX ) / scale , vtop = - view . posY / scale , vbottom = ( h - view . posY ) / scale ; const paths = { } ; let rad , vrad , x , y , fs , hi ; const nhigh = HIGHLIGHT_PALETTE . length ; const highs = Array ( nhigh ) . fill ( ) . map ( ( ) => Array ( ) ) ; const hirad = 8 / scale ; for ( let i = 0 ; i < n ; i ++ ) { if ( mask [ i ] ) continue ; rad = S [ i ] ; vrad = rad * scale ; if ( vrad < min1 ) continue ; x = Math . round ( X [ i ] * w ) ; if ( x + rad < vleft || x - rad > vright ) continue ; y = Math . round ( Y [ i ] * h ) ; if ( y + rad < vtop || y - rad > vbottom ) continue ; fs = ` ${ C [ i ] } ` ; if ( ! ( fs in paths ) ) paths [ fs ] = { 'square' : [ ] , 'circle' : [ ] } ; if ( vrad < min2 ) { paths [ fs ] . square . push ( [ x , y , Math . round ( rad * pi1_2 ) ] ) ; } else { paths [ fs ] . circle . push ( [ x , y , Math . round ( rad ) ] ) ; } hi = high [ i ] ; if ( hi ) { highs [ hi - 1 ] . push ( [ x , y , Math . round ( rad + hirad ) ] ) ; } } let j , hs , m , hl ; for ( let i = 0 ; i < nhigh ; i ++ ) { hs = highs [ i ] ; m = hs . length ; if ( ! m ) continue ; ctx . fillStyle = HIGHLIGHT_PALETTE [ i ] + '66' ; ctx . beginPath ( ) ; for ( j = 0 ; j < m ; j ++ ) { hl = hs [ j ] ; ctx . moveTo ( hl [ 0 ] , hl [ 1 ] ) ; ctx . arc ( hl [ 0 ] , hl [ 1 ] , hl [ 2 ] , 0 , pi2 , true ) ; } ctx . fill ( ) ; } let squares , sq , circles , circ ; for ( let fs in paths ) { ctx . fillStyle = fs ; squares = paths [ fs ] . square ; n = squares . length ; for ( let i = 0 ; i < n ; i ++ ) { sq = squares [ i ] ; ctx . fillRect ( sq [ 0 ] , sq [ 1 ] , sq [ 2 ] , sq [ 2 ] ) ; } circles = paths [ fs ] . circle ; n = circles . length ; if ( n === 0 ) continue ; ctx . beginPath ( ) ; for ( let i = 0 ; i < n ; i ++ ) { circ = circles [ i ] ; ctx . moveTo ( circ [ 0 ] , circ [ 1 ] ) ; ctx . arc ( circ [ 0 ] , circ [ 1 ] , circ [ 2 ] , 0 , pi2 , true ) ; } ctx . fill ( ) ; } if ( view . grid ) drawGrid ( rena , view ) ; ctx . restore ( ) ; }
Render arena given current data and view.
Render arena given current data and view .
// prepare canvas context // note: origin (0, 0) is at the upper-left corner // clear canvas // move to current position // scale canvas // cannot render if there is no data, or no x- or y-axis // alternative: css scale, which is theoretically faster, but it blurs when // zoom in // rena.style.transformOrigin = '0 0'; // rena.style.transform = 'scale(' + view.scale + ')'; // cache constants // transformed data // calculate edges of visible region // elements to be rendered, grouped by fill style, then by circle or square // intermediates // highlights // determine appearance of contigs // determine radius (size) // if contig occupies less than one pixel on screen, skip // determine x- and y-coordinates // skip contigs outside visible region // determine fill style (color and opacity) // if a contig occupies less than four pixels on screen, draw a square // if bigger, draw a circle // highlight circle // end for i // render highlights // alpha = 0.4 // render contigs // draw squares // draw circles // end for fs // draw grid