-
Notifications
You must be signed in to change notification settings - Fork 144
Grid Renderers
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
- Requesting grid rendering
- Built-in grid renderers
- Rendering transparent or translucent cells and grids
- Setting the grid renderer
- Writing a grid renderer
In the following examples, the variable grid
holds an instantiated Hypergrid:
var grid = new Hypergrid({ data: [...] });
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 usingsetTimeout
. This will (1) give the callback scheduled byrequestAnimationFrame
a chance to run in-between; and (b) give the browser's event loop a chance to run (to service mouse clicks, etc.).
To stop background rendering:
grid.canvas.stopPaintLoop();
To restart background rendering:
grid.canvas.restartPaintLoop();
To render the grid immediately (before the next interval) or while the loop is stalled:
grid.canvas.paintNow();
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.
It is very important to note that the standard paint loop actually only performs a render when requested to do so.
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.
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. Thedirty
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 timesrepaint()
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).
-
Paint loop running:
-
Immediate mode:
The grid is re-rendered immediately, on every call torepaint()
. 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: Whilerepaint
is in immediate mode, if Hypergrid (or your application) makes multiple calls torepaint()
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.
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.
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 of60
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
- A value of
- 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 torepaint()
will trigger an immediate render.
- The paint loop is essentially stopped; rendering is skipped entirely — unless
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.
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.
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), theCanvasRenderingContext2D
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.
Grid instances can specify different grid renderers.
The default renderer is set in Hypergrid.defaults
(./src/defaults.js):
gridRenderer: 'by-columns-and-rows',
(This default pre-dated the by-cells
renderer.)
To change the default renderer for all new grid instances:
Hypergrid.defaults.gridRenderer = 'by-cells';
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.)
[Section to be written.]