Skip to content

Grid Renderers

Jonathan Eiten edited this page Dec 12, 2017 · 5 revisions

Hypergrid's grid renderer loops through the visible cells and calls each one's cell renderer to paint the cell.

This article covers:

Stopping/starting grid rendering

In the following examples, the variable grid holds an instantiated Hypergrid:

var grid = new Hypergrid({ data: [...] });

The paint loop

Hypergrid's Canvas object's standard paint loop automatically renders all grid instances on a regular periodic schedule. This periodic event is called the interval. Specifically, the interval used is window.requestAnimationFrame's callback, which is keyed to the monitor's refresh rate.

The paint loop starts automatically upon grid instantiation.

Note: Each grid instance is actually only rendered when requested to do so; see the next section, Requesting grid rendering.

Note: As JavaScript is single-threaded, the requestAnimationFrame callback will be blocked by code that runs too long. In this case, the re-render will not happen on schedule; requestAnimationFrame cancels the callback and the "frame is dropped." Rendering will resume when the thread is free. If your application has very lengthy operations and you want to show partial updates to the grid while it is running, consider breaking into smaller pieces using setTimeout. This will (1) give the callback scheduled by requestAnimationFrame a chance to run in-between; and (b) give the browser's event loop a chance to run (to service mouse clicks, etc.).

Stopping the standard paint loop

To stop background rendering:

grid.canvas.stopPaintLoop();

Starting the standard paint loop

To restart background rendering:

grid.canvas.restartPaintLoop();

Manual rendering

To render the grid immediately (before the next interval) or while the loop is stalled:

grid.canvas.paintNow();

Implementing a custom paint loop

To create a custom paint loop:

var myOwnLoop = setInterval(function() { grid.canvas.paintNow(); }, 0);

The above loop renders as fast as possible ("blast" mode) (because second parameter is 0). There is generally no benefit to rendering faster than the monitor's refresh rate, except to measure rendering speed by seeing how many renders can be completed per second.

Note: Never run more than one loop at a time! In particular, do not create a custom paint loop without first stopping the standard paint loop.

Requesting grid rendering

It is very important to note that the standard paint loop actually only performs a render when requested to do so.

The repaint() method and the dirty flag

To request a render on the next interval, applications make a repaint request. Calling grid.repaint() normally sets the Canvas's dirty flag true. Inside the paint loop, Canvas checks the flag and only renders when truthy. After rendering, the flag is cleared.

Whenever an application makes a logical change to the grid model in memory, it should set the dirty flag to re-render the grid on the next interval, making the change visible:

grid.repaint();

Note: Applications usually do not need to call repaint() explicitly; it is called implicitly by Hypergrid's higher-level methods.

Repaint modes

As noted above, the repaint() method normally simply set's the dirty flag and no more. Actually, repaint() has two modes:

  • Deferred mode:
    This is the default mode. The dirty flag is set.
    • Paint loop running:
      The grid eventually re-renders (i.e., on the next interval) and the flag is reset. No matter how many times repaint() is called, the grid will only be re-rendered once, on the interval callback. (As the paint loop is running by default, this is the normative case.)
    • Paint loop not running:
      The grid is rendered immediately (as in immediate mode).
  • Immediate mode:
    The grid is re-rendered immediately, on every call to repaint(). This mode is useful for debugging, in order to see changes to grid while stepping through code. However, It should not be used otherwise. This is important; the reason is: While repaint is in immediate mode, if Hypergrid (or your application) makes multiple calls to repaint() before giving up the thread (as is often the case), the immediate grid renders will happen repeatedly (for each call), which is "not good." (It is pointless and will dominate the CPU.)

Note: In immediate mode, since the dirty flag is never set, the paint loop never renders, essentially the same as if the loop had been stopped.

Continuous repaint

To tell the paint loop to render every time, regardless of whether or not repaint() is ever called:

grid.properties.enableContinuousRepaint = true;

The enableContinuousRepaint property overrides the dirty flag by telling Canvas to ignore it.

Limiting the interval rate

The grid.properties.repaintIntervalRate property limits the frequency of grid renders by dropping frames. Not sure why you would want to do this, but I suppose if you wanted to limit the amount of CPU time devoted to rendering....

This grid property was added early on at the request of an application developer. It is of dubious usefulness and subject to deprecation.

The way it works is:

  • Values >= the requestAnimationFrame rate ("usually 60 times per second, but will generally match the display refresh rate in most web browsers as per W3C recommendation"), including the default value of 60 has no effect, have no effect.
  • For values < requestAnimationFrame rate, frames will be dropped.
    For example:
    • A value of 30 will drop every other frame.
    • A value of 1 renders once per second.
    • Values less than 0 will limit rendering to once every 1 / value seconds. For example:
      • A value of 0.5 will render once every 2 seconds.
      • A value of 0.2 will render once every 5 seconds.
  • A value of 0 is a special case:
    • The paint loop is essentially stopped; rendering is skipped entirely — unless repaint() is called, because...
    • repaint() behaves as if it's in immediate mode (see above), so any call to repaint() will trigger an immediate render.

Actual FPS

This value, returned by grid.canvas.getCurrentFPS(), reflects the actual frame rate in frames (grid renders) per second averaged over the last 1.0 seconds.

This value is only updated when in continuous repaint mode (grid.properties.enableContinuousRepaint truthy).

Used in testing to check rendering performance. Useful for grid renderer and cell renderer development.

Built-in grid renderers

Hypergrid is currently packaged with five grid renderers:

      Grid Renderer       Description
by-columns Renders all cells over consolidated column backgrounds, if any.
by-rows Renders all cells over consolidated row backgrounds, if any. Unsuitable as a clipping alternative.
by-columns-and-rows
(default)
Renders all cells over consolidated row backgrounds, if any; otherwise over consolidated column backgrounds, if any.
by-columns-discrete
(obsolete)
Renders each column discretely with its own background rect, and then its cells.
by-cells This is the partial renderer, only rendering the cells that have changed since the last render. This only works for a static grid; when grid shape changes (including scrolling), it resets and defers to by-columns-and-rows for a full grid render.

Choosing a renderer is all about performance. The various renderers reflect different strategies for efficiently rendering the grid using the <canvas> element's API, in particular the way cell backgrounds are painted.

Painting each individual cell's background proved too expensive. And unnecessary, considering that large regions of cells are "solid" — share the same background color, such as whole columns or rows. The view might have hundreds of cells but only a handful of solid regions. Painting the background of these regions in a single operation greatly improved the renderer speed because although the total number of pixels that needed to be painted was the same, reducing the number of fill operations had a significant impact. In other words, while the actual blitter operation was super fast, repeatedly setting up the blitter for each cell's fillRect was taking a lot of time.

The first four renderer choices above (excepting by-cells, which is a special case) are adapted to different region shapes. As noted, all four are much faster than painting each cell's background individually. The difference between them is the strategies they use to discover rectangular regions of cell with the same background color. One approach is to fill a whole column of cells with the column background color (by-columns-discrete). An even faster approach is to "consolidate" adjacent regions, i.e., to find and fill each whole group of contiguous columns (by-columns) or rows (by-rows) of like background color in a single operation. Finally, the by-columns-and-rows renderer .

The by-cells renderer is a "difference engine." If all the cells have changed, this is the least performant renderer; but when only some of the cells have changed, it is the most performant (> 4x faster). This renderer turns out to be the best overall choice.

Rendering transparent or translucent cells and grids

The way transparency is handled depends entirely on the cell renderer.

The included SimpleCell cell renderer is sample code that demonstrates a number of features, among which is full support for transparency:

  • When a cell's background color is set to transparent (or #000000 or #rgba(x,x,x,0)) the grid background color will show through.
  • When a cell's background color is set to translucent (rgba(x,x,x,n) where 0 < n < 1), the CanvasRenderingContext2D blends the cell background color with the grid background color.
  • If the grid's background color is also transparent or translucent, any page content (i.e., other visible HTML elements positioned behind the grid) will also show through.
  • If the page background is also transparent or translucent, the desktop will also show through. (Note that although browsers do not generally offer this capability, other HTML containers might.)
  • When a cell's foreground color is translucent, text is also blended against the cell's background, etc.

Regarding transparency and rendering efficiency:

  • When a cell is transparent, it's background is not painted at all, which saves time.
  • When a cell is translucent, the color blending that takes place actually takes more time.

Setting the grid renderer

Grid instances can specify different grid renderers.

Changing the default renderer

The default renderer is set in Hypergrid.defaults (./src/defaults.js):

    gridRenderer: 'by-columns-and-rows',

(This default pre-dated the by-cells renderer.)

Resetting the default grid renderer

To change the default renderer for all new grid instances:

Hypergrid.defaults.gridRenderer = 'by-cells';

Resetting a grid's renderer

To change a particular grid instance's renderer (at any time):

grid.renderer.setGridRenderer('by-cells');

The following calls the above:

(This is intended to allow setting grid renderer when loading a properties object.)

Writing a grid renderer

[Section to be written.]