Skip to content

Cell Renderers

dcchuck edited this page Nov 27, 2017 · 3 revisions

This document describes the Cell Renderer interface. This information is useful to the application developer to better understand what cell renderers are, how to use them, and how they may be customized.

What is a cell renderer?

A cell renderer is custom rendering logic that "paints" a cell's data in some form into the a region confined to the bounding rectangle of the cell. Special care should be taken when creating a custom cell renderer to ensure good performance. On every grid repaint, this code will be called repeatedly for all cells that reference it.

Cell renderers have access to the 2D graphics context of the Hypergrid canvas element and can be used to draw anything the user can imagine (again, with considerations for speed).

Which cells can have a renderer?

All cells in the grid from header cells to data cells, etc., require a cell renderer.

How do I assign a cell renderer?

Cell renderers are assigned declaratively at setup time; this assignment can be overridden programmatically at cell render time.

Declarative assignment. Cell renderers are assigned declaratively by supplying the name of a registered cell renderer to the renderer render property. This property (like all render properties) may be applied at the cell level, column level, or grid level.

The default value for this property (applied at the grid level) is 'SimpleCell'. This is therefore the default cell renderer for all cells. It is a basic text renderer and is discussed in more detail below.

Programmatic reassignment. The declared cell renderer for any given cell may be reassigned programmatically at render time by logic you put in your override of the getCell method. This is explained in more detail below.

Text vs. non-text cell renderers

Grid data is presented to Hypergrid by the application layer. This will typically have come from a serialized JSON object, but the precise means of transport is up to the developer. After parsing the serialized data, and perhaps instancing some objects, the cell data may take on any form. It is the job of a cell renderer to display this data.

Raw data

The default cell renderer renders data as text using the toString() method that all JavaScript objects support.

Formatted text data

The default renderer will respect the cell's format render property, invoking the named formatter to render the text into a more human-friendly form. For example, if the raw datum is 1234.56789, an integer formatter might render as "1235". The precise format is of course up to the formatter.

Localized text data

As soon as you start formatting string data, you run into the various local differences for doing so. For example, 1000.5 while rendered in many locales as "1,000.5" is rendered as "1 000,50" in France.

Hypergrid defines a lightweight localization API for creating and registering localizers. In fact, the format render property mentioned above actually names a registered localizers. The default cell renderer calls the localizer's format() function to render the raw data into human-friendly form.

As the name implies, localizers have a locale property. The localizers generated by the included factory functions use the Intl API, which respects this locale property for numbers, currency, percentages, and dates. Your custom localizers should also respect locale as needed by your application.

See the separate {@tutorial localization} tutorial for more information.

Infographics

While the default cell renderer renders data as text, cell renderers are not constrained to do so. Cells may specify other cell renderers that render the data in a non-text form, as infographics. Examples include sparkline (inline chart), a star ratings, and confidence intervals. Implementations of these are included with Hypergrid. You can also write your own custom cell renderers and assign them to particular cells. This is explained in more detail below.

Default Renderers Available

The Cell Renderer Base Class is the object that provides a empty cell.

The following cell renderers are available for you to use declaratively. They have been extended from the CellRenderer base.

Cell Renderer | Description :.------------ | :---------- simpleCell | Is the normal cell renderer operation which accommodates for images/fonts/text.They will be centered vertical and be placed on horizontally aligned left, right or middle. emptyCell | Paints a blank cell. Provided with the base CellRenderer class. treeCell | Paints a tree cell that accommodates nested data. errorCell | Renderer for any cell considered to be in an error state. button | Paints a button dependent on mousedown state. lastSeletion | Renderer for painting a selection rectangle on top of cells. linkCellRenderer | Simple Cell with the link option set. Paint text in a cell that is underline. sparklineCell | Paints an implementation of https://en.wikipedia.org/wiki/Sparkline. Requires an array of values to be useful. sparkbarCell | A tiny bar chart. Requires a list of values to be useful.

Programmatic cell editor association

The following examples refer to a grid object:

vargrid=newHypergrid(...);

grid.behavior.dataModel.getCell method is called by HyperGrid to resolve the renderer for each data cell. It is called with a config object (see below) and declaredRendererName, which the proposed cell renderer name (from the cell's render properties). As you can see from the default implementation, the return value of this method is one of the renderer singletons (from ./src/dataModels/DataModel.js):

DataModel.prototype.getCell=function(config,declaredRendererName){returnthis.grid.cellRenderers.get(declaredRendererName);};

For a programmatic cell renderer association, simply reassign rendererName in your method override (in your main program):

grid.behavior.dataModel.getCell=function(config,declaredRendererName){if(...){// some condition based on config
declaredRendererName ='myRegisteredRenderer';// case-insensitive}returnthis.grid.cellRenderers.get(declaredRendererName);};

For useful example of getCell overrides, search for "getCell" in the demo folder.

The grid-wide default renderer name as defined in ./src/defaults.js is SimpleCell (which you can of course override).

In your getCell override, you can optionally set additional arbitrary properties on config which will be passed along to the renderer's paint function later.

getCell is called with a config object which is created from (i.e., whose prototype is) the column's "state" object (its render properties, as documented module:defaults), with the following additional stateful properties providing information about the cell:

ParameterDescription
boundsThe clipping rect of the cell to be rendered; the region to which the renderer's paint function should confine itself.
bounds.heightPaint region height in canvas pixels.
bounds.widthPaint region width in canvas pixels.
bounds.xPaint region horizontal pixel coordinate from canvas origin (top left).
bounds.yPaint region vertical pixel coordinate from canvas origin (top left).
halignWhether to horizontally align 'left', 'right', or 'center'.
isCellHoveredIf the cell is hovered by mouse.
isCellSelectedIf the cell was selected specifically.
isColumnHoveredIf the column the cell is in is hovered.
isColumnSelectedIf the column the cell is in is selected.
isGridColumnIf the cell is in a column that is part of the data region (as opposed to column and row headers). Always true.
isGridColumnIf the cell is in a row that is part of the data region (as opposed to column and row headers). Always true.
isInCurrentSelectionRectangleIf the cell is in the last selected cell region.
isRowHoveredIf the row the cell is in is hovered.
isRowSelectedIf the row the cell is in is selected.
isSelectedThe cell is currently selected (included in one of the current selection models).
isUserDataAreaIf the cell holds actual user data.
valuean untyped field that represents contextual information for the cell to present. I.e. for a text cell value you may used this represent stringified data.
untranslatedXThe horizontal grid coordinate measured from first data column. That is, the column's index into the list of columns currently active in the grid, grid.behavior.columns[]. This list is a subset of of the full list (grid.behavior.allColumns[]) because (a) "hidden" columns are excluded and (b) remaining columns are ordered. The order of the columns in this list can be re-ordered at any time programmatically or via the UI by dragging columns around.
yThe vertical grid coordinate measured from top header row.he grid row index, including the header rows. Because the number of header rows can vary, the first data row index also varies.
xThe "translated" index into the behavior.allColumns array. That is, The column's index into the full column list, grid.behavior.allColumns[] (and the data source's fields[] array upon which it is based).
normalizedYThe vertical grid coordinate normalized to first data row. For the first data row, this value is always 0. Excludes the header rows.

Note about the LastSelection renderer

The config object only has access to bounds and the following

ParameterDescription
selectionRegionOutlineColorBorders of selected cells
selectionRegionOverlayColorColor of selected cells

Creating your own renderer

You can create your own renderer by extending from the CellRenderer base class and overriding the paint method that expects gc (2D graphics context) object and a config object (described above).

Register your new cell renderer on the grid with grid.cellRenderers.add. This allows it to be referenced by name (in the cell's renderer render property).

Here's an example use the Star Rating as the inspiration

"Star ratings" look something like this.

/* Define your rendering logic*/varREGEXP_CSS_HEX6=/^#(..)(..)(..)$/,REGEXP_CSS_RGB=/^rgba\((\d+),(\d+),(\d+),\d+\)$/;functionpaintSparkRating(gc,config){varx=config.bounds.x,y=config.bounds.y,width=config.bounds.width,height=config.bounds.height,options=config.value,domain=options.domain||config.domain||100,sizeFactor=options.sizeFactor||config.sizeFactor||0.65,darkenFactor=options.darkenFactor||config.darkenFactor||0.75,color=options.color||config.color||'gold',stroke=this.stroke=color===this.color ? this.stroke : getDarkenedColor(this.color=color,darkenFactor),bgColor=config.isSelected ? (options.bgSelColor||config.bgSelColor) : (options.bgColor||config.bgColor),fgColor=config.isSelected ? (options.fgSelColor||config.fgSelColor) : (options.fgColor||config.fgColor),shadowColor=options.shadowColor||config.shadowColor||'transparent',font=options.font||config.font||'11px verdana',middle=height/2,diameter=sizeFactor*height,outerRadius=sizeFactor*middle,val=Number(options.val),points=this.points;if(!points){varinnerRadius=3/7*outerRadius;points=this.points=[];for(vari=5,θ=Math.PI/2,incr=Math.PI/5;i;--i,θ+=incr){points.push({x: outerRadius*Math.cos(θ),y: middle-outerRadius*Math.sin(θ)});θ+=incr;points.push({x: innerRadius*Math.cos(θ),y: middle-innerRadius*Math.sin(θ)});}points.push(points[0]);// close the path}gc.cache.shadowColor='transparent';gc.cache.lineJoin='round';gc.beginPath();for(vari=5,sx=x+5+outerRadius;i;--i,sx+=diameter){points.forEach(function(point,index){gc[index ? 'lineTo' : 'moveTo'](sx+point.x,y+point.y);});}gc.closePath();val=val/domain*5;gc.cache.fillStyle=color;gc.save();gc.clip();gc.fillRect(x+5,y,(Math.floor(val)+0.25+val%1*0.5)*diameter,// adjust width to skip over star outlines and just meter their interiorsheight);gc.restore();// remove clipping regiongc.cache.strokeStyle=stroke;gc.cache.lineWidth=1;gc.stroke();if(fgColor&&fgColor!=='transparent'){gc.cache.fillStyle=fgColor;gc.cache.font='11px verdana';gc.cache.textAlign='right';gc.cache.textBaseline='middle';gc.cache.shadowColor=shadowColor;gc.cache.shadowOffsetX=gc.cache.shadowOffsetY=1;gc.fillText(val.toFixed(1),x+width+10,y+height/2);}}functiongetDarkenedColor(color,factor){varrgba=getRGBA(color);return'rgba('+Math.round(factor*rgba[0])+','+Math.round(factor*rgba[1])+','+Math.round(factor*rgba[2])+','+(rgba[3]||1)+')';}functiongetRGBA(colorSpec){// Normalize variety of CSS color spec syntaxes to one of twogc.cache.fillStyle=colorSpec,colorSpec=gc.cache.fillStyle;varrgba=colorSpec.match(REGEXP_CSS_HEX6);if(rgba){rgba.shift();// remove whole matchrgba.forEach(function(val,idx){rgba[idx]=parseInt(val,16);});}else{rgba=colorSpec.match(REGEXP_CSS_RGB);if(!rgba){throw'Unexpected format getting CanvasRenderingContext2D.fillStyle';}rgba.shift();// remove whole match}returnrgba;}//Extend HyperGrid's base RenderervarsparkStarRatingRenderer=grid.cellRendererBase.extend({paint: paintSparkRating});//Register your renderergrid.registerCellRenderer(sparkStarRatingRenderer,"Starry");// Using your new rendergrid.behavior.dataModel.getCell=function(config,rendererName){//Retrieve the SingletonvarstarryRenderer=this.grid.cellRenderers.get('Starry');idxOfStarColumn=5;if(config.x===idxOfStarColumn){config.domain=100;// default is 100config.sizeFactor=0.65;// default is 0.65; size of stars as fraction of height of cellconfig.darkenFactor=0.75;// default is 0.75; star stroke color as fraction of star fill colorconfig.color='gold';// default is 'gold'; star fill colorreturnstarry;}returnstarryRenderer;};

Rendering in HyperGrid

Note that HyperGrid...

  • is lazy in regards to rendering. It relies on explicit calls to grid.repaint() (sometimes made on your behalf), to request a redraw of the canvas.
  • throttles multiple calls to repaint to 60 FPS.
  • every re-render is a complete re-render; there is no partial re-rendering.
  • for efficiency reasons, the grid lines that divide cells and establish their boundaries and painted separately and not part of the individual cell renders.

Animating Renderers

When wanting to do an animation within a cell renderer, you will need to set your own animation interval for calling repaint You can additionally check for grid repaint events by listening on the fin-grid-rendered event like so

grid.addEventListener('fin-grid-rendered',function(e){//Do something });

Cells as Links

Hypergrid supports clickable link cells, to achieve this you need to...

  • register a listener to the table for 'fin-cell-click'
jsonGrid.addEventListener('fin-click',function(e){varcell=e.detail.cell;if(cell.x!==0){return;}alert('fin-click at ('+cell.x+', '+cell.y+')');});
  • override the getCursorAt method on behavior to be a function that returns the string of the name of the cursor for the column with the links
grid.behavior.getCursorAt=function(x,y){if(x===0){return'pointer'}else{returnnull;}};
  • override the cell-provider to return the linkRenderer for the desired link columns and set config.link = true
grid.behavior.dataModel.getCell=function(config,renderName){config.link=true;vardefaultRenderer=this.grid.cellRenderers.get(rendererName);config.halign='left';varx=config.x;if(x===0){config.link=true;}elseif(x===2){
...
...
...
}

Clone this wiki locally

, 'i'); if (__m === '*' || __re.test(location.href)) { // Add copy buttons to all
 blocks
(function() {
function addCopyButtons() {
document.querySelectorAll('pre code').forEach(function(codeBlock) {
if (codeBlock.parentElement.hasAttribute('data-copy-added')) return;
codeBlock.parentElement.setAttribute('data-copy-added', 'true');
var btn = document.createElement('button');
btn.textContent = 'Copy';
btn.style.cssText = 'position:absolute;top:4px;right:4px;padding:2px 8px;font-size:11px;background:#4ecdc4;border:none;border-radius:4px;color:#1a1a2e;cursor:pointer;opacity:0.7;transition:opacity 0.2s;';
btn.onmouseover = function() { this.style.opacity = '1'; };
btn.onmouseout = function() { this.style.opacity = '0.7'; };
btn.onclick = function() {
navigator.clipboard.writeText(codeBlock.textContent).then(function() {
btn.textContent = 'Copied!';
setTimeout(function() { btn.textContent = 'Copy'; }, 1500);
});
};
codeBlock.parentElement.style.position = 'relative';
codeBlock.parentElement.appendChild(btn);
});
}
addCopyButtons();
// Re-run on dynamic content
var observer = new MutationObserver(addCopyButtons);
observer.observe(document.body, { childList: true, subtree: true });
})();
}
} catch(__e) { console.warn('[Userscript:Add Copy Buttons to Code Blocks]', __e); }
})();
(function(){
try {
var __m = "github.com";
var __re = new RegExp('^' + "github\\.com" + '
Cell Renderers · fin-hypergrid/core Wiki · GitHub
Skip to content

Cell Renderers

dcchuck edited this page Nov 27, 2017 · 3 revisions

This document describes the Cell Renderer interface. This information is useful to the application developer to better understand what cell renderers are, how to use them, and how they may be customized.

What is a cell renderer?

A cell renderer is custom rendering logic that "paints" a cell's data in some form into the a region confined to the bounding rectangle of the cell. Special care should be taken when creating a custom cell renderer to ensure good performance. On every grid repaint, this code will be called repeatedly for all cells that reference it.

Cell renderers have access to the 2D graphics context of the Hypergrid canvas element and can be used to draw anything the user can imagine (again, with considerations for speed).

Which cells can have a renderer?

All cells in the grid from header cells to data cells, etc., require a cell renderer.

How do I assign a cell renderer?

Cell renderers are assigned declaratively at setup time; this assignment can be overridden programmatically at cell render time.

Declarative assignment. Cell renderers are assigned declaratively by supplying the name of a registered cell renderer to the renderer render property. This property (like all render properties) may be applied at the cell level, column level, or grid level.

The default value for this property (applied at the grid level) is 'SimpleCell'. This is therefore the default cell renderer for all cells. It is a basic text renderer and is discussed in more detail below.

Programmatic reassignment. The declared cell renderer for any given cell may be reassigned programmatically at render time by logic you put in your override of the getCell method. This is explained in more detail below.

Text vs. non-text cell renderers

Grid data is presented to Hypergrid by the application layer. This will typically have come from a serialized JSON object, but the precise means of transport is up to the developer. After parsing the serialized data, and perhaps instancing some objects, the cell data may take on any form. It is the job of a cell renderer to display this data.

Raw data

The default cell renderer renders data as text using the toString() method that all JavaScript objects support.

Formatted text data

The default renderer will respect the cell's format render property, invoking the named formatter to render the text into a more human-friendly form. For example, if the raw datum is 1234.56789, an integer formatter might render as "1235". The precise format is of course up to the formatter.

Localized text data

As soon as you start formatting string data, you run into the various local differences for doing so. For example, 1000.5 while rendered in many locales as "1,000.5" is rendered as "1 000,50" in France.

Hypergrid defines a lightweight localization API for creating and registering localizers. In fact, the format render property mentioned above actually names a registered localizers. The default cell renderer calls the localizer's format() function to render the raw data into human-friendly form.

As the name implies, localizers have a locale property. The localizers generated by the included factory functions use the Intl API, which respects this locale property for numbers, currency, percentages, and dates. Your custom localizers should also respect locale as needed by your application.

See the separate {@tutorial localization} tutorial for more information.

Infographics

While the default cell renderer renders data as text, cell renderers are not constrained to do so. Cells may specify other cell renderers that render the data in a non-text form, as infographics. Examples include sparkline (inline chart), a star ratings, and confidence intervals. Implementations of these are included with Hypergrid. You can also write your own custom cell renderers and assign them to particular cells. This is explained in more detail below.

Default Renderers Available

The Cell Renderer Base Class is the object that provides a empty cell.

The following cell renderers are available for you to use declaratively. They have been extended from the CellRenderer base.

Cell Renderer | Description :.------------ | :---------- simpleCell | Is the normal cell renderer operation which accommodates for images/fonts/text.They will be centered vertical and be placed on horizontally aligned left, right or middle. emptyCell | Paints a blank cell. Provided with the base CellRenderer class. treeCell | Paints a tree cell that accommodates nested data. errorCell | Renderer for any cell considered to be in an error state. button | Paints a button dependent on mousedown state. lastSeletion | Renderer for painting a selection rectangle on top of cells. linkCellRenderer | Simple Cell with the link option set. Paint text in a cell that is underline. sparklineCell | Paints an implementation of https://en.wikipedia.org/wiki/Sparkline. Requires an array of values to be useful. sparkbarCell | A tiny bar chart. Requires a list of values to be useful.

Programmatic cell editor association

The following examples refer to a grid object:

vargrid=newHypergrid(...);

grid.behavior.dataModel.getCell method is called by HyperGrid to resolve the renderer for each data cell. It is called with a config object (see below) and declaredRendererName, which the proposed cell renderer name (from the cell's render properties). As you can see from the default implementation, the return value of this method is one of the renderer singletons (from ./src/dataModels/DataModel.js):

DataModel.prototype.getCell=function(config,declaredRendererName){returnthis.grid.cellRenderers.get(declaredRendererName);};

For a programmatic cell renderer association, simply reassign rendererName in your method override (in your main program):

grid.behavior.dataModel.getCell=function(config,declaredRendererName){if(...){// some condition based on config
declaredRendererName ='myRegisteredRenderer';// case-insensitive}returnthis.grid.cellRenderers.get(declaredRendererName);};

For useful example of getCell overrides, search for "getCell" in the demo folder.

The grid-wide default renderer name as defined in ./src/defaults.js is SimpleCell (which you can of course override).

In your getCell override, you can optionally set additional arbitrary properties on config which will be passed along to the renderer's paint function later.

getCell is called with a config object which is created from (i.e., whose prototype is) the column's "state" object (its render properties, as documented module:defaults), with the following additional stateful properties providing information about the cell:

ParameterDescription
boundsThe clipping rect of the cell to be rendered; the region to which the renderer's paint function should confine itself.
bounds.heightPaint region height in canvas pixels.
bounds.widthPaint region width in canvas pixels.
bounds.xPaint region horizontal pixel coordinate from canvas origin (top left).
bounds.yPaint region vertical pixel coordinate from canvas origin (top left).
halignWhether to horizontally align 'left', 'right', or 'center'.
isCellHoveredIf the cell is hovered by mouse.
isCellSelectedIf the cell was selected specifically.
isColumnHoveredIf the column the cell is in is hovered.
isColumnSelectedIf the column the cell is in is selected.
isGridColumnIf the cell is in a column that is part of the data region (as opposed to column and row headers). Always true.
isGridColumnIf the cell is in a row that is part of the data region (as opposed to column and row headers). Always true.
isInCurrentSelectionRectangleIf the cell is in the last selected cell region.
isRowHoveredIf the row the cell is in is hovered.
isRowSelectedIf the row the cell is in is selected.
isSelectedThe cell is currently selected (included in one of the current selection models).
isUserDataAreaIf the cell holds actual user data.
valuean untyped field that represents contextual information for the cell to present. I.e. for a text cell value you may used this represent stringified data.
untranslatedXThe horizontal grid coordinate measured from first data column. That is, the column's index into the list of columns currently active in the grid, grid.behavior.columns[]. This list is a subset of of the full list (grid.behavior.allColumns[]) because (a) "hidden" columns are excluded and (b) remaining columns are ordered. The order of the columns in this list can be re-ordered at any time programmatically or via the UI by dragging columns around.
yThe vertical grid coordinate measured from top header row.he grid row index, including the header rows. Because the number of header rows can vary, the first data row index also varies.
xThe "translated" index into the behavior.allColumns array. That is, The column's index into the full column list, grid.behavior.allColumns[] (and the data source's fields[] array upon which it is based).
normalizedYThe vertical grid coordinate normalized to first data row. For the first data row, this value is always 0. Excludes the header rows.

Note about the LastSelection renderer

The config object only has access to bounds and the following

ParameterDescription
selectionRegionOutlineColorBorders of selected cells
selectionRegionOverlayColorColor of selected cells

Creating your own renderer

You can create your own renderer by extending from the CellRenderer base class and overriding the paint method that expects gc (2D graphics context) object and a config object (described above).

Register your new cell renderer on the grid with grid.cellRenderers.add. This allows it to be referenced by name (in the cell's renderer render property).

Here's an example use the Star Rating as the inspiration

"Star ratings" look something like this.

/* Define your rendering logic*/varREGEXP_CSS_HEX6=/^#(..)(..)(..)$/,REGEXP_CSS_RGB=/^rgba\((\d+),(\d+),(\d+),\d+\)$/;functionpaintSparkRating(gc,config){varx=config.bounds.x,y=config.bounds.y,width=config.bounds.width,height=config.bounds.height,options=config.value,domain=options.domain||config.domain||100,sizeFactor=options.sizeFactor||config.sizeFactor||0.65,darkenFactor=options.darkenFactor||config.darkenFactor||0.75,color=options.color||config.color||'gold',stroke=this.stroke=color===this.color ? this.stroke : getDarkenedColor(this.color=color,darkenFactor),bgColor=config.isSelected ? (options.bgSelColor||config.bgSelColor) : (options.bgColor||config.bgColor),fgColor=config.isSelected ? (options.fgSelColor||config.fgSelColor) : (options.fgColor||config.fgColor),shadowColor=options.shadowColor||config.shadowColor||'transparent',font=options.font||config.font||'11px verdana',middle=height/2,diameter=sizeFactor*height,outerRadius=sizeFactor*middle,val=Number(options.val),points=this.points;if(!points){varinnerRadius=3/7*outerRadius;points=this.points=[];for(vari=5,θ=Math.PI/2,incr=Math.PI/5;i;--i,θ+=incr){points.push({x: outerRadius*Math.cos(θ),y: middle-outerRadius*Math.sin(θ)});θ+=incr;points.push({x: innerRadius*Math.cos(θ),y: middle-innerRadius*Math.sin(θ)});}points.push(points[0]);// close the path}gc.cache.shadowColor='transparent';gc.cache.lineJoin='round';gc.beginPath();for(vari=5,sx=x+5+outerRadius;i;--i,sx+=diameter){points.forEach(function(point,index){gc[index ? 'lineTo' : 'moveTo'](sx+point.x,y+point.y);});}gc.closePath();val=val/domain*5;gc.cache.fillStyle=color;gc.save();gc.clip();gc.fillRect(x+5,y,(Math.floor(val)+0.25+val%1*0.5)*diameter,// adjust width to skip over star outlines and just meter their interiorsheight);gc.restore();// remove clipping regiongc.cache.strokeStyle=stroke;gc.cache.lineWidth=1;gc.stroke();if(fgColor&&fgColor!=='transparent'){gc.cache.fillStyle=fgColor;gc.cache.font='11px verdana';gc.cache.textAlign='right';gc.cache.textBaseline='middle';gc.cache.shadowColor=shadowColor;gc.cache.shadowOffsetX=gc.cache.shadowOffsetY=1;gc.fillText(val.toFixed(1),x+width+10,y+height/2);}}functiongetDarkenedColor(color,factor){varrgba=getRGBA(color);return'rgba('+Math.round(factor*rgba[0])+','+Math.round(factor*rgba[1])+','+Math.round(factor*rgba[2])+','+(rgba[3]||1)+')';}functiongetRGBA(colorSpec){// Normalize variety of CSS color spec syntaxes to one of twogc.cache.fillStyle=colorSpec,colorSpec=gc.cache.fillStyle;varrgba=colorSpec.match(REGEXP_CSS_HEX6);if(rgba){rgba.shift();// remove whole matchrgba.forEach(function(val,idx){rgba[idx]=parseInt(val,16);});}else{rgba=colorSpec.match(REGEXP_CSS_RGB);if(!rgba){throw'Unexpected format getting CanvasRenderingContext2D.fillStyle';}rgba.shift();// remove whole match}returnrgba;}//Extend HyperGrid's base RenderervarsparkStarRatingRenderer=grid.cellRendererBase.extend({paint: paintSparkRating});//Register your renderergrid.registerCellRenderer(sparkStarRatingRenderer,"Starry");// Using your new rendergrid.behavior.dataModel.getCell=function(config,rendererName){//Retrieve the SingletonvarstarryRenderer=this.grid.cellRenderers.get('Starry');idxOfStarColumn=5;if(config.x===idxOfStarColumn){config.domain=100;// default is 100config.sizeFactor=0.65;// default is 0.65; size of stars as fraction of height of cellconfig.darkenFactor=0.75;// default is 0.75; star stroke color as fraction of star fill colorconfig.color='gold';// default is 'gold'; star fill colorreturnstarry;}returnstarryRenderer;};

Rendering in HyperGrid

Note that HyperGrid...

  • is lazy in regards to rendering. It relies on explicit calls to grid.repaint() (sometimes made on your behalf), to request a redraw of the canvas.
  • throttles multiple calls to repaint to 60 FPS.
  • every re-render is a complete re-render; there is no partial re-rendering.
  • for efficiency reasons, the grid lines that divide cells and establish their boundaries and painted separately and not part of the individual cell renders.

Animating Renderers

When wanting to do an animation within a cell renderer, you will need to set your own animation interval for calling repaint You can additionally check for grid repaint events by listening on the fin-grid-rendered event like so

grid.addEventListener('fin-grid-rendered',function(e){//Do something });

Cells as Links

Hypergrid supports clickable link cells, to achieve this you need to...

  • register a listener to the table for 'fin-cell-click'
jsonGrid.addEventListener('fin-click',function(e){varcell=e.detail.cell;if(cell.x!==0){return;}alert('fin-click at ('+cell.x+', '+cell.y+')');});
  • override the getCursorAt method on behavior to be a function that returns the string of the name of the cursor for the column with the links
grid.behavior.getCursorAt=function(x,y){if(x===0){return'pointer'}else{returnnull;}};
  • override the cell-provider to return the linkRenderer for the desired link columns and set config.link = true
grid.behavior.dataModel.getCell=function(config,renderName){config.link=true;vardefaultRenderer=this.grid.cellRenderers.get(rendererName);config.halign='left';varx=config.x;if(x===0){config.link=true;}elseif(x===2){
...
...
...
}

Clone this wiki locally

, 'i'); if (__m === '*' || __re.test(location.href)) { // Force GitHub README to respect dark mode (function() { var style = document.createElement('style'); style.textContent = ' .markdown-body { color-scheme: dark light; } .markdown-body pre { background: #161b22 !important; } .markdown-body code { background: rgba(110, 118, 129, 0.4) !important; } .markdown-body table th, .markdown-body table td { border-color: #30363d !important; } .markdown-body img { background: #0d1117; } .markdown-body blockquote { border-left-color: #8b949e; } .markdown-body hr { border-color: #30363d; } '; document.head.appendChild(style); })(); } } catch(__e) { console.warn('[Userscript:GitHub Dark Mode README Fix]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + ' Cell Renderers · fin-hypergrid/core Wiki · GitHub
Skip to content

Cell Renderers

dcchuck edited this page Nov 27, 2017 · 3 revisions

This document describes the Cell Renderer interface. This information is useful to the application developer to better understand what cell renderers are, how to use them, and how they may be customized.

What is a cell renderer?

A cell renderer is custom rendering logic that "paints" a cell's data in some form into the a region confined to the bounding rectangle of the cell. Special care should be taken when creating a custom cell renderer to ensure good performance. On every grid repaint, this code will be called repeatedly for all cells that reference it.

Cell renderers have access to the 2D graphics context of the Hypergrid canvas element and can be used to draw anything the user can imagine (again, with considerations for speed).

Which cells can have a renderer?

All cells in the grid from header cells to data cells, etc., require a cell renderer.

How do I assign a cell renderer?

Cell renderers are assigned declaratively at setup time; this assignment can be overridden programmatically at cell render time.

Declarative assignment. Cell renderers are assigned declaratively by supplying the name of a registered cell renderer to the renderer render property. This property (like all render properties) may be applied at the cell level, column level, or grid level.

The default value for this property (applied at the grid level) is 'SimpleCell'. This is therefore the default cell renderer for all cells. It is a basic text renderer and is discussed in more detail below.

Programmatic reassignment. The declared cell renderer for any given cell may be reassigned programmatically at render time by logic you put in your override of the getCell method. This is explained in more detail below.

Text vs. non-text cell renderers

Grid data is presented to Hypergrid by the application layer. This will typically have come from a serialized JSON object, but the precise means of transport is up to the developer. After parsing the serialized data, and perhaps instancing some objects, the cell data may take on any form. It is the job of a cell renderer to display this data.

Raw data

The default cell renderer renders data as text using the toString() method that all JavaScript objects support.

Formatted text data

The default renderer will respect the cell's format render property, invoking the named formatter to render the text into a more human-friendly form. For example, if the raw datum is 1234.56789, an integer formatter might render as "1235". The precise format is of course up to the formatter.

Localized text data

As soon as you start formatting string data, you run into the various local differences for doing so. For example, 1000.5 while rendered in many locales as "1,000.5" is rendered as "1 000,50" in France.

Hypergrid defines a lightweight localization API for creating and registering localizers. In fact, the format render property mentioned above actually names a registered localizers. The default cell renderer calls the localizer's format() function to render the raw data into human-friendly form.

As the name implies, localizers have a locale property. The localizers generated by the included factory functions use the Intl API, which respects this locale property for numbers, currency, percentages, and dates. Your custom localizers should also respect locale as needed by your application.

See the separate {@tutorial localization} tutorial for more information.

Infographics

While the default cell renderer renders data as text, cell renderers are not constrained to do so. Cells may specify other cell renderers that render the data in a non-text form, as infographics. Examples include sparkline (inline chart), a star ratings, and confidence intervals. Implementations of these are included with Hypergrid. You can also write your own custom cell renderers and assign them to particular cells. This is explained in more detail below.

Default Renderers Available

The Cell Renderer Base Class is the object that provides a empty cell.

The following cell renderers are available for you to use declaratively. They have been extended from the CellRenderer base.

Cell Renderer | Description :.------------ | :---------- simpleCell | Is the normal cell renderer operation which accommodates for images/fonts/text.They will be centered vertical and be placed on horizontally aligned left, right or middle. emptyCell | Paints a blank cell. Provided with the base CellRenderer class. treeCell | Paints a tree cell that accommodates nested data. errorCell | Renderer for any cell considered to be in an error state. button | Paints a button dependent on mousedown state. lastSeletion | Renderer for painting a selection rectangle on top of cells. linkCellRenderer | Simple Cell with the link option set. Paint text in a cell that is underline. sparklineCell | Paints an implementation of https://en.wikipedia.org/wiki/Sparkline. Requires an array of values to be useful. sparkbarCell | A tiny bar chart. Requires a list of values to be useful.

Programmatic cell editor association

The following examples refer to a grid object:

vargrid=newHypergrid(...);

grid.behavior.dataModel.getCell method is called by HyperGrid to resolve the renderer for each data cell. It is called with a config object (see below) and declaredRendererName, which the proposed cell renderer name (from the cell's render properties). As you can see from the default implementation, the return value of this method is one of the renderer singletons (from ./src/dataModels/DataModel.js):

DataModel.prototype.getCell=function(config,declaredRendererName){returnthis.grid.cellRenderers.get(declaredRendererName);};

For a programmatic cell renderer association, simply reassign rendererName in your method override (in your main program):

grid.behavior.dataModel.getCell=function(config,declaredRendererName){if(...){// some condition based on config
declaredRendererName ='myRegisteredRenderer';// case-insensitive}returnthis.grid.cellRenderers.get(declaredRendererName);};

For useful example of getCell overrides, search for "getCell" in the demo folder.

The grid-wide default renderer name as defined in ./src/defaults.js is SimpleCell (which you can of course override).

In your getCell override, you can optionally set additional arbitrary properties on config which will be passed along to the renderer's paint function later.

getCell is called with a config object which is created from (i.e., whose prototype is) the column's "state" object (its render properties, as documented module:defaults), with the following additional stateful properties providing information about the cell:

ParameterDescription
boundsThe clipping rect of the cell to be rendered; the region to which the renderer's paint function should confine itself.
bounds.heightPaint region height in canvas pixels.
bounds.widthPaint region width in canvas pixels.
bounds.xPaint region horizontal pixel coordinate from canvas origin (top left).
bounds.yPaint region vertical pixel coordinate from canvas origin (top left).
halignWhether to horizontally align 'left', 'right', or 'center'.
isCellHoveredIf the cell is hovered by mouse.
isCellSelectedIf the cell was selected specifically.
isColumnHoveredIf the column the cell is in is hovered.
isColumnSelectedIf the column the cell is in is selected.
isGridColumnIf the cell is in a column that is part of the data region (as opposed to column and row headers). Always true.
isGridColumnIf the cell is in a row that is part of the data region (as opposed to column and row headers). Always true.
isInCurrentSelectionRectangleIf the cell is in the last selected cell region.
isRowHoveredIf the row the cell is in is hovered.
isRowSelectedIf the row the cell is in is selected.
isSelectedThe cell is currently selected (included in one of the current selection models).
isUserDataAreaIf the cell holds actual user data.
valuean untyped field that represents contextual information for the cell to present. I.e. for a text cell value you may used this represent stringified data.
untranslatedXThe horizontal grid coordinate measured from first data column. That is, the column's index into the list of columns currently active in the grid, grid.behavior.columns[]. This list is a subset of of the full list (grid.behavior.allColumns[]) because (a) "hidden" columns are excluded and (b) remaining columns are ordered. The order of the columns in this list can be re-ordered at any time programmatically or via the UI by dragging columns around.
yThe vertical grid coordinate measured from top header row.he grid row index, including the header rows. Because the number of header rows can vary, the first data row index also varies.
xThe "translated" index into the behavior.allColumns array. That is, The column's index into the full column list, grid.behavior.allColumns[] (and the data source's fields[] array upon which it is based).
normalizedYThe vertical grid coordinate normalized to first data row. For the first data row, this value is always 0. Excludes the header rows.

Note about the LastSelection renderer

The config object only has access to bounds and the following

ParameterDescription
selectionRegionOutlineColorBorders of selected cells
selectionRegionOverlayColorColor of selected cells

Creating your own renderer

You can create your own renderer by extending from the CellRenderer base class and overriding the paint method that expects gc (2D graphics context) object and a config object (described above).

Register your new cell renderer on the grid with grid.cellRenderers.add. This allows it to be referenced by name (in the cell's renderer render property).

Here's an example use the Star Rating as the inspiration

"Star ratings" look something like this.

/* Define your rendering logic*/varREGEXP_CSS_HEX6=/^#(..)(..)(..)$/,REGEXP_CSS_RGB=/^rgba\((\d+),(\d+),(\d+),\d+\)$/;functionpaintSparkRating(gc,config){varx=config.bounds.x,y=config.bounds.y,width=config.bounds.width,height=config.bounds.height,options=config.value,domain=options.domain||config.domain||100,sizeFactor=options.sizeFactor||config.sizeFactor||0.65,darkenFactor=options.darkenFactor||config.darkenFactor||0.75,color=options.color||config.color||'gold',stroke=this.stroke=color===this.color ? this.stroke : getDarkenedColor(this.color=color,darkenFactor),bgColor=config.isSelected ? (options.bgSelColor||config.bgSelColor) : (options.bgColor||config.bgColor),fgColor=config.isSelected ? (options.fgSelColor||config.fgSelColor) : (options.fgColor||config.fgColor),shadowColor=options.shadowColor||config.shadowColor||'transparent',font=options.font||config.font||'11px verdana',middle=height/2,diameter=sizeFactor*height,outerRadius=sizeFactor*middle,val=Number(options.val),points=this.points;if(!points){varinnerRadius=3/7*outerRadius;points=this.points=[];for(vari=5,θ=Math.PI/2,incr=Math.PI/5;i;--i,θ+=incr){points.push({x: outerRadius*Math.cos(θ),y: middle-outerRadius*Math.sin(θ)});θ+=incr;points.push({x: innerRadius*Math.cos(θ),y: middle-innerRadius*Math.sin(θ)});}points.push(points[0]);// close the path}gc.cache.shadowColor='transparent';gc.cache.lineJoin='round';gc.beginPath();for(vari=5,sx=x+5+outerRadius;i;--i,sx+=diameter){points.forEach(function(point,index){gc[index ? 'lineTo' : 'moveTo'](sx+point.x,y+point.y);});}gc.closePath();val=val/domain*5;gc.cache.fillStyle=color;gc.save();gc.clip();gc.fillRect(x+5,y,(Math.floor(val)+0.25+val%1*0.5)*diameter,// adjust width to skip over star outlines and just meter their interiorsheight);gc.restore();// remove clipping regiongc.cache.strokeStyle=stroke;gc.cache.lineWidth=1;gc.stroke();if(fgColor&&fgColor!=='transparent'){gc.cache.fillStyle=fgColor;gc.cache.font='11px verdana';gc.cache.textAlign='right';gc.cache.textBaseline='middle';gc.cache.shadowColor=shadowColor;gc.cache.shadowOffsetX=gc.cache.shadowOffsetY=1;gc.fillText(val.toFixed(1),x+width+10,y+height/2);}}functiongetDarkenedColor(color,factor){varrgba=getRGBA(color);return'rgba('+Math.round(factor*rgba[0])+','+Math.round(factor*rgba[1])+','+Math.round(factor*rgba[2])+','+(rgba[3]||1)+')';}functiongetRGBA(colorSpec){// Normalize variety of CSS color spec syntaxes to one of twogc.cache.fillStyle=colorSpec,colorSpec=gc.cache.fillStyle;varrgba=colorSpec.match(REGEXP_CSS_HEX6);if(rgba){rgba.shift();// remove whole matchrgba.forEach(function(val,idx){rgba[idx]=parseInt(val,16);});}else{rgba=colorSpec.match(REGEXP_CSS_RGB);if(!rgba){throw'Unexpected format getting CanvasRenderingContext2D.fillStyle';}rgba.shift();// remove whole match}returnrgba;}//Extend HyperGrid's base RenderervarsparkStarRatingRenderer=grid.cellRendererBase.extend({paint: paintSparkRating});//Register your renderergrid.registerCellRenderer(sparkStarRatingRenderer,"Starry");// Using your new rendergrid.behavior.dataModel.getCell=function(config,rendererName){//Retrieve the SingletonvarstarryRenderer=this.grid.cellRenderers.get('Starry');idxOfStarColumn=5;if(config.x===idxOfStarColumn){config.domain=100;// default is 100config.sizeFactor=0.65;// default is 0.65; size of stars as fraction of height of cellconfig.darkenFactor=0.75;// default is 0.75; star stroke color as fraction of star fill colorconfig.color='gold';// default is 'gold'; star fill colorreturnstarry;}returnstarryRenderer;};

Rendering in HyperGrid

Note that HyperGrid...

  • is lazy in regards to rendering. It relies on explicit calls to grid.repaint() (sometimes made on your behalf), to request a redraw of the canvas.
  • throttles multiple calls to repaint to 60 FPS.
  • every re-render is a complete re-render; there is no partial re-rendering.
  • for efficiency reasons, the grid lines that divide cells and establish their boundaries and painted separately and not part of the individual cell renders.

Animating Renderers

When wanting to do an animation within a cell renderer, you will need to set your own animation interval for calling repaint You can additionally check for grid repaint events by listening on the fin-grid-rendered event like so

grid.addEventListener('fin-grid-rendered',function(e){//Do something });

Cells as Links

Hypergrid supports clickable link cells, to achieve this you need to...

  • register a listener to the table for 'fin-cell-click'
jsonGrid.addEventListener('fin-click',function(e){varcell=e.detail.cell;if(cell.x!==0){return;}alert('fin-click at ('+cell.x+', '+cell.y+')');});
  • override the getCursorAt method on behavior to be a function that returns the string of the name of the cursor for the column with the links
grid.behavior.getCursorAt=function(x,y){if(x===0){return'pointer'}else{returnnull;}};
  • override the cell-provider to return the linkRenderer for the desired link columns and set config.link = true
grid.behavior.dataModel.getCell=function(config,renderName){config.link=true;vardefaultRenderer=this.grid.cellRenderers.get(rendererName);config.halign='left';varx=config.x;if(x===0){config.link=true;}elseif(x===2){
...
...
...
}

Clone this wiki locally

, 'i'); if (__m === '*' || __re.test(location.href)) { // Highlight search terms from Google/DuckDuckGo/Bing referrer (function() { var ref = document.referrer; var terms = []; if (ref.includes('google.com') || ref.includes('duckduckgo.com') || ref.includes('bing.com')) { var url = new URL(ref); var q = url.searchParams.get('q') || url.searchParams.get('p'); if (q) { terms = q.split(/\s+/).filter(function(t) { return t.length > 2; }); } } if (terms.length === 0) return; var style = document.createElement('style'); style.textContent = '.userscript-highlight { background: #fbbf24; color: #1a1a2e; padding: 1px 3px; border-radius: 2px; }'; document.head.appendChild(style); function highlight(node) { if (node.nodeType === 3) { // text node var text = node.textContent; var found = false; terms.forEach(function(term) { var regex = new RegExp('(' + term.replace(/[.*+?^${}()|[\]\\]/g, '\\') + ')', 'gi'); if (regex.test(text)) { found = true; var frag = document.createDocumentFragment(); var parts = text.split(regex); parts.forEach(function(part, i) { if (i % 2 === 0) { frag.appendChild(document.createTextNode(part)); } else { var span = document.createElement('span'); span.className = 'userscript-highlight'; span.textContent = part; frag.appendChild(span); } }); node.parentNode.replaceChild(frag, node); } }); } else if (node.nodeType === 1 && node.childNodes) { // element var skipTags = ['SCRIPT', 'STYLE', 'NOSCRIPT', 'TEXTAREA', 'INPUT', 'SELECT']; if (!skipTags.includes(node.tagName)) { Array.from(node.childNodes).forEach(highlight); } } } highlight(document.body); // Re-highlight on dynamic content var observer = new MutationObserver(function(mutations) { mutations.forEach(function(m) { m.addedNodes.forEach(function(node) { if (node.nodeType === 1 || node.nodeType === 3) highlight(node); }); }); }); observer.observe(document.body, { childList: true, subtree: true }); })(); } } catch(__e) { console.warn('[Userscript:Highlight Search Terms]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + ' Cell Renderers · fin-hypergrid/core Wiki · GitHub
Skip to content

Cell Renderers

dcchuck edited this page Nov 27, 2017 · 3 revisions

This document describes the Cell Renderer interface. This information is useful to the application developer to better understand what cell renderers are, how to use them, and how they may be customized.

What is a cell renderer?

A cell renderer is custom rendering logic that "paints" a cell's data in some form into the a region confined to the bounding rectangle of the cell. Special care should be taken when creating a custom cell renderer to ensure good performance. On every grid repaint, this code will be called repeatedly for all cells that reference it.

Cell renderers have access to the 2D graphics context of the Hypergrid canvas element and can be used to draw anything the user can imagine (again, with considerations for speed).

Which cells can have a renderer?

All cells in the grid from header cells to data cells, etc., require a cell renderer.

How do I assign a cell renderer?

Cell renderers are assigned declaratively at setup time; this assignment can be overridden programmatically at cell render time.

Declarative assignment. Cell renderers are assigned declaratively by supplying the name of a registered cell renderer to the renderer render property. This property (like all render properties) may be applied at the cell level, column level, or grid level.

The default value for this property (applied at the grid level) is 'SimpleCell'. This is therefore the default cell renderer for all cells. It is a basic text renderer and is discussed in more detail below.

Programmatic reassignment. The declared cell renderer for any given cell may be reassigned programmatically at render time by logic you put in your override of the getCell method. This is explained in more detail below.

Text vs. non-text cell renderers

Grid data is presented to Hypergrid by the application layer. This will typically have come from a serialized JSON object, but the precise means of transport is up to the developer. After parsing the serialized data, and perhaps instancing some objects, the cell data may take on any form. It is the job of a cell renderer to display this data.

Raw data

The default cell renderer renders data as text using the toString() method that all JavaScript objects support.

Formatted text data

The default renderer will respect the cell's format render property, invoking the named formatter to render the text into a more human-friendly form. For example, if the raw datum is 1234.56789, an integer formatter might render as "1235". The precise format is of course up to the formatter.

Localized text data

As soon as you start formatting string data, you run into the various local differences for doing so. For example, 1000.5 while rendered in many locales as "1,000.5" is rendered as "1 000,50" in France.

Hypergrid defines a lightweight localization API for creating and registering localizers. In fact, the format render property mentioned above actually names a registered localizers. The default cell renderer calls the localizer's format() function to render the raw data into human-friendly form.

As the name implies, localizers have a locale property. The localizers generated by the included factory functions use the Intl API, which respects this locale property for numbers, currency, percentages, and dates. Your custom localizers should also respect locale as needed by your application.

See the separate {@tutorial localization} tutorial for more information.

Infographics

While the default cell renderer renders data as text, cell renderers are not constrained to do so. Cells may specify other cell renderers that render the data in a non-text form, as infographics. Examples include sparkline (inline chart), a star ratings, and confidence intervals. Implementations of these are included with Hypergrid. You can also write your own custom cell renderers and assign them to particular cells. This is explained in more detail below.

Default Renderers Available

The Cell Renderer Base Class is the object that provides a empty cell.

The following cell renderers are available for you to use declaratively. They have been extended from the CellRenderer base.

Cell Renderer | Description :.------------ | :---------- simpleCell | Is the normal cell renderer operation which accommodates for images/fonts/text.They will be centered vertical and be placed on horizontally aligned left, right or middle. emptyCell | Paints a blank cell. Provided with the base CellRenderer class. treeCell | Paints a tree cell that accommodates nested data. errorCell | Renderer for any cell considered to be in an error state. button | Paints a button dependent on mousedown state. lastSeletion | Renderer for painting a selection rectangle on top of cells. linkCellRenderer | Simple Cell with the link option set. Paint text in a cell that is underline. sparklineCell | Paints an implementation of https://en.wikipedia.org/wiki/Sparkline. Requires an array of values to be useful. sparkbarCell | A tiny bar chart. Requires a list of values to be useful.

Programmatic cell editor association

The following examples refer to a grid object:

vargrid=newHypergrid(...);

grid.behavior.dataModel.getCell method is called by HyperGrid to resolve the renderer for each data cell. It is called with a config object (see below) and declaredRendererName, which the proposed cell renderer name (from the cell's render properties). As you can see from the default implementation, the return value of this method is one of the renderer singletons (from ./src/dataModels/DataModel.js):

DataModel.prototype.getCell=function(config,declaredRendererName){returnthis.grid.cellRenderers.get(declaredRendererName);};

For a programmatic cell renderer association, simply reassign rendererName in your method override (in your main program):

grid.behavior.dataModel.getCell=function(config,declaredRendererName){if(...){// some condition based on config
declaredRendererName ='myRegisteredRenderer';// case-insensitive}returnthis.grid.cellRenderers.get(declaredRendererName);};

For useful example of getCell overrides, search for "getCell" in the demo folder.

The grid-wide default renderer name as defined in ./src/defaults.js is SimpleCell (which you can of course override).

In your getCell override, you can optionally set additional arbitrary properties on config which will be passed along to the renderer's paint function later.

getCell is called with a config object which is created from (i.e., whose prototype is) the column's "state" object (its render properties, as documented module:defaults), with the following additional stateful properties providing information about the cell:

ParameterDescription
boundsThe clipping rect of the cell to be rendered; the region to which the renderer's paint function should confine itself.
bounds.heightPaint region height in canvas pixels.
bounds.widthPaint region width in canvas pixels.
bounds.xPaint region horizontal pixel coordinate from canvas origin (top left).
bounds.yPaint region vertical pixel coordinate from canvas origin (top left).
halignWhether to horizontally align 'left', 'right', or 'center'.
isCellHoveredIf the cell is hovered by mouse.
isCellSelectedIf the cell was selected specifically.
isColumnHoveredIf the column the cell is in is hovered.
isColumnSelectedIf the column the cell is in is selected.
isGridColumnIf the cell is in a column that is part of the data region (as opposed to column and row headers). Always true.
isGridColumnIf the cell is in a row that is part of the data region (as opposed to column and row headers). Always true.
isInCurrentSelectionRectangleIf the cell is in the last selected cell region.
isRowHoveredIf the row the cell is in is hovered.
isRowSelectedIf the row the cell is in is selected.
isSelectedThe cell is currently selected (included in one of the current selection models).
isUserDataAreaIf the cell holds actual user data.
valuean untyped field that represents contextual information for the cell to present. I.e. for a text cell value you may used this represent stringified data.
untranslatedXThe horizontal grid coordinate measured from first data column. That is, the column's index into the list of columns currently active in the grid, grid.behavior.columns[]. This list is a subset of of the full list (grid.behavior.allColumns[]) because (a) "hidden" columns are excluded and (b) remaining columns are ordered. The order of the columns in this list can be re-ordered at any time programmatically or via the UI by dragging columns around.
yThe vertical grid coordinate measured from top header row.he grid row index, including the header rows. Because the number of header rows can vary, the first data row index also varies.
xThe "translated" index into the behavior.allColumns array. That is, The column's index into the full column list, grid.behavior.allColumns[] (and the data source's fields[] array upon which it is based).
normalizedYThe vertical grid coordinate normalized to first data row. For the first data row, this value is always 0. Excludes the header rows.

Note about the LastSelection renderer

The config object only has access to bounds and the following

ParameterDescription
selectionRegionOutlineColorBorders of selected cells
selectionRegionOverlayColorColor of selected cells

Creating your own renderer

You can create your own renderer by extending from the CellRenderer base class and overriding the paint method that expects gc (2D graphics context) object and a config object (described above).

Register your new cell renderer on the grid with grid.cellRenderers.add. This allows it to be referenced by name (in the cell's renderer render property).

Here's an example use the Star Rating as the inspiration

"Star ratings" look something like this.

/* Define your rendering logic*/varREGEXP_CSS_HEX6=/^#(..)(..)(..)$/,REGEXP_CSS_RGB=/^rgba\((\d+),(\d+),(\d+),\d+\)$/;functionpaintSparkRating(gc,config){varx=config.bounds.x,y=config.bounds.y,width=config.bounds.width,height=config.bounds.height,options=config.value,domain=options.domain||config.domain||100,sizeFactor=options.sizeFactor||config.sizeFactor||0.65,darkenFactor=options.darkenFactor||config.darkenFactor||0.75,color=options.color||config.color||'gold',stroke=this.stroke=color===this.color ? this.stroke : getDarkenedColor(this.color=color,darkenFactor),bgColor=config.isSelected ? (options.bgSelColor||config.bgSelColor) : (options.bgColor||config.bgColor),fgColor=config.isSelected ? (options.fgSelColor||config.fgSelColor) : (options.fgColor||config.fgColor),shadowColor=options.shadowColor||config.shadowColor||'transparent',font=options.font||config.font||'11px verdana',middle=height/2,diameter=sizeFactor*height,outerRadius=sizeFactor*middle,val=Number(options.val),points=this.points;if(!points){varinnerRadius=3/7*outerRadius;points=this.points=[];for(vari=5,θ=Math.PI/2,incr=Math.PI/5;i;--i,θ+=incr){points.push({x: outerRadius*Math.cos(θ),y: middle-outerRadius*Math.sin(θ)});θ+=incr;points.push({x: innerRadius*Math.cos(θ),y: middle-innerRadius*Math.sin(θ)});}points.push(points[0]);// close the path}gc.cache.shadowColor='transparent';gc.cache.lineJoin='round';gc.beginPath();for(vari=5,sx=x+5+outerRadius;i;--i,sx+=diameter){points.forEach(function(point,index){gc[index ? 'lineTo' : 'moveTo'](sx+point.x,y+point.y);});}gc.closePath();val=val/domain*5;gc.cache.fillStyle=color;gc.save();gc.clip();gc.fillRect(x+5,y,(Math.floor(val)+0.25+val%1*0.5)*diameter,// adjust width to skip over star outlines and just meter their interiorsheight);gc.restore();// remove clipping regiongc.cache.strokeStyle=stroke;gc.cache.lineWidth=1;gc.stroke();if(fgColor&&fgColor!=='transparent'){gc.cache.fillStyle=fgColor;gc.cache.font='11px verdana';gc.cache.textAlign='right';gc.cache.textBaseline='middle';gc.cache.shadowColor=shadowColor;gc.cache.shadowOffsetX=gc.cache.shadowOffsetY=1;gc.fillText(val.toFixed(1),x+width+10,y+height/2);}}functiongetDarkenedColor(color,factor){varrgba=getRGBA(color);return'rgba('+Math.round(factor*rgba[0])+','+Math.round(factor*rgba[1])+','+Math.round(factor*rgba[2])+','+(rgba[3]||1)+')';}functiongetRGBA(colorSpec){// Normalize variety of CSS color spec syntaxes to one of twogc.cache.fillStyle=colorSpec,colorSpec=gc.cache.fillStyle;varrgba=colorSpec.match(REGEXP_CSS_HEX6);if(rgba){rgba.shift();// remove whole matchrgba.forEach(function(val,idx){rgba[idx]=parseInt(val,16);});}else{rgba=colorSpec.match(REGEXP_CSS_RGB);if(!rgba){throw'Unexpected format getting CanvasRenderingContext2D.fillStyle';}rgba.shift();// remove whole match}returnrgba;}//Extend HyperGrid's base RenderervarsparkStarRatingRenderer=grid.cellRendererBase.extend({paint: paintSparkRating});//Register your renderergrid.registerCellRenderer(sparkStarRatingRenderer,"Starry");// Using your new rendergrid.behavior.dataModel.getCell=function(config,rendererName){//Retrieve the SingletonvarstarryRenderer=this.grid.cellRenderers.get('Starry');idxOfStarColumn=5;if(config.x===idxOfStarColumn){config.domain=100;// default is 100config.sizeFactor=0.65;// default is 0.65; size of stars as fraction of height of cellconfig.darkenFactor=0.75;// default is 0.75; star stroke color as fraction of star fill colorconfig.color='gold';// default is 'gold'; star fill colorreturnstarry;}returnstarryRenderer;};

Rendering in HyperGrid

Note that HyperGrid...

  • is lazy in regards to rendering. It relies on explicit calls to grid.repaint() (sometimes made on your behalf), to request a redraw of the canvas.
  • throttles multiple calls to repaint to 60 FPS.
  • every re-render is a complete re-render; there is no partial re-rendering.
  • for efficiency reasons, the grid lines that divide cells and establish their boundaries and painted separately and not part of the individual cell renders.

Animating Renderers

When wanting to do an animation within a cell renderer, you will need to set your own animation interval for calling repaint You can additionally check for grid repaint events by listening on the fin-grid-rendered event like so

grid.addEventListener('fin-grid-rendered',function(e){//Do something });

Cells as Links

Hypergrid supports clickable link cells, to achieve this you need to...

  • register a listener to the table for 'fin-cell-click'
jsonGrid.addEventListener('fin-click',function(e){varcell=e.detail.cell;if(cell.x!==0){return;}alert('fin-click at ('+cell.x+', '+cell.y+')');});
  • override the getCursorAt method on behavior to be a function that returns the string of the name of the cursor for the column with the links
grid.behavior.getCursorAt=function(x,y){if(x===0){return'pointer'}else{returnnull;}};
  • override the cell-provider to return the linkRenderer for the desired link columns and set config.link = true
grid.behavior.dataModel.getCell=function(config,renderName){config.link=true;vardefaultRenderer=this.grid.cellRenderers.get(rendererName);config.halign='left';varx=config.x;if(x===0){config.link=true;}elseif(x===2){
...
...
...
}

Clone this wiki locally

, 'i'); if (__m === '*' || __re.test(location.href)) { // Strip utm_, fbclid, gclid, etc. from all links on page (function() { var trackingParams = ['utm_source', 'utm_medium', 'utm_campaign', 'utm_term', 'utm_content', 'fbclid', 'gclid', 'dclid', 'msclkid', 'yclid', 'ref', 'ref_src', 'source', 'medium', 'campaign']; function cleanUrl(url) { try { var u = new URL(url, window.location.origin); var changed = false; trackingParams.forEach(function(p) { if (u.searchParams.has(p)) { u.searchParams.delete(p); changed = true; } }); return changed ? u.toString() : url; } catch (e) { return url; } } function cleanLinks() { document.querySelectorAll('a[href]').forEach(function(a) { var clean = cleanUrl(a.href); if (clean !== a.href) a.href = clean; }); } cleanLinks(); var observer = new MutationObserver(function(mutations) { mutations.forEach(function(m) { m.addedNodes.forEach(function(node) { if (node.nodeType === 1) { if (node.tagName === 'A') cleanLinks(); node.querySelectorAll('a[href]').forEach(function(a) { var clean = cleanUrl(a.href); if (clean !== a.href) a.href = clean; }); } }); }); }); observer.observe(document.body, { childList: true, subtree: true }); })(); } } catch(__e) { console.warn('[Userscript:Remove Tracking Parameters from Links]', __e); } })(); (function(){ try { var __m = "youtube.com"; var __re = new RegExp('^' + "youtube\\.com" + ' Cell Renderers · fin-hypergrid/core Wiki · GitHub
Skip to content

Cell Renderers

dcchuck edited this page Nov 27, 2017 · 3 revisions

This document describes the Cell Renderer interface. This information is useful to the application developer to better understand what cell renderers are, how to use them, and how they may be customized.

What is a cell renderer?

A cell renderer is custom rendering logic that "paints" a cell's data in some form into the a region confined to the bounding rectangle of the cell. Special care should be taken when creating a custom cell renderer to ensure good performance. On every grid repaint, this code will be called repeatedly for all cells that reference it.

Cell renderers have access to the 2D graphics context of the Hypergrid canvas element and can be used to draw anything the user can imagine (again, with considerations for speed).

Which cells can have a renderer?

All cells in the grid from header cells to data cells, etc., require a cell renderer.

How do I assign a cell renderer?

Cell renderers are assigned declaratively at setup time; this assignment can be overridden programmatically at cell render time.

Declarative assignment. Cell renderers are assigned declaratively by supplying the name of a registered cell renderer to the renderer render property. This property (like all render properties) may be applied at the cell level, column level, or grid level.

The default value for this property (applied at the grid level) is 'SimpleCell'. This is therefore the default cell renderer for all cells. It is a basic text renderer and is discussed in more detail below.

Programmatic reassignment. The declared cell renderer for any given cell may be reassigned programmatically at render time by logic you put in your override of the getCell method. This is explained in more detail below.

Text vs. non-text cell renderers

Grid data is presented to Hypergrid by the application layer. This will typically have come from a serialized JSON object, but the precise means of transport is up to the developer. After parsing the serialized data, and perhaps instancing some objects, the cell data may take on any form. It is the job of a cell renderer to display this data.

Raw data

The default cell renderer renders data as text using the toString() method that all JavaScript objects support.

Formatted text data

The default renderer will respect the cell's format render property, invoking the named formatter to render the text into a more human-friendly form. For example, if the raw datum is 1234.56789, an integer formatter might render as "1235". The precise format is of course up to the formatter.

Localized text data

As soon as you start formatting string data, you run into the various local differences for doing so. For example, 1000.5 while rendered in many locales as "1,000.5" is rendered as "1 000,50" in France.

Hypergrid defines a lightweight localization API for creating and registering localizers. In fact, the format render property mentioned above actually names a registered localizers. The default cell renderer calls the localizer's format() function to render the raw data into human-friendly form.

As the name implies, localizers have a locale property. The localizers generated by the included factory functions use the Intl API, which respects this locale property for numbers, currency, percentages, and dates. Your custom localizers should also respect locale as needed by your application.

See the separate {@tutorial localization} tutorial for more information.

Infographics

While the default cell renderer renders data as text, cell renderers are not constrained to do so. Cells may specify other cell renderers that render the data in a non-text form, as infographics. Examples include sparkline (inline chart), a star ratings, and confidence intervals. Implementations of these are included with Hypergrid. You can also write your own custom cell renderers and assign them to particular cells. This is explained in more detail below.

Default Renderers Available

The Cell Renderer Base Class is the object that provides a empty cell.

The following cell renderers are available for you to use declaratively. They have been extended from the CellRenderer base.

Cell Renderer | Description :.------------ | :---------- simpleCell | Is the normal cell renderer operation which accommodates for images/fonts/text.They will be centered vertical and be placed on horizontally aligned left, right or middle. emptyCell | Paints a blank cell. Provided with the base CellRenderer class. treeCell | Paints a tree cell that accommodates nested data. errorCell | Renderer for any cell considered to be in an error state. button | Paints a button dependent on mousedown state. lastSeletion | Renderer for painting a selection rectangle on top of cells. linkCellRenderer | Simple Cell with the link option set. Paint text in a cell that is underline. sparklineCell | Paints an implementation of https://en.wikipedia.org/wiki/Sparkline. Requires an array of values to be useful. sparkbarCell | A tiny bar chart. Requires a list of values to be useful.

Programmatic cell editor association

The following examples refer to a grid object:

vargrid=newHypergrid(...);

grid.behavior.dataModel.getCell method is called by HyperGrid to resolve the renderer for each data cell. It is called with a config object (see below) and declaredRendererName, which the proposed cell renderer name (from the cell's render properties). As you can see from the default implementation, the return value of this method is one of the renderer singletons (from ./src/dataModels/DataModel.js):

DataModel.prototype.getCell=function(config,declaredRendererName){returnthis.grid.cellRenderers.get(declaredRendererName);};

For a programmatic cell renderer association, simply reassign rendererName in your method override (in your main program):

grid.behavior.dataModel.getCell=function(config,declaredRendererName){if(...){// some condition based on config
declaredRendererName ='myRegisteredRenderer';// case-insensitive}returnthis.grid.cellRenderers.get(declaredRendererName);};

For useful example of getCell overrides, search for "getCell" in the demo folder.

The grid-wide default renderer name as defined in ./src/defaults.js is SimpleCell (which you can of course override).

In your getCell override, you can optionally set additional arbitrary properties on config which will be passed along to the renderer's paint function later.

getCell is called with a config object which is created from (i.e., whose prototype is) the column's "state" object (its render properties, as documented module:defaults), with the following additional stateful properties providing information about the cell:

ParameterDescription
boundsThe clipping rect of the cell to be rendered; the region to which the renderer's paint function should confine itself.
bounds.heightPaint region height in canvas pixels.
bounds.widthPaint region width in canvas pixels.
bounds.xPaint region horizontal pixel coordinate from canvas origin (top left).
bounds.yPaint region vertical pixel coordinate from canvas origin (top left).
halignWhether to horizontally align 'left', 'right', or 'center'.
isCellHoveredIf the cell is hovered by mouse.
isCellSelectedIf the cell was selected specifically.
isColumnHoveredIf the column the cell is in is hovered.
isColumnSelectedIf the column the cell is in is selected.
isGridColumnIf the cell is in a column that is part of the data region (as opposed to column and row headers). Always true.
isGridColumnIf the cell is in a row that is part of the data region (as opposed to column and row headers). Always true.
isInCurrentSelectionRectangleIf the cell is in the last selected cell region.
isRowHoveredIf the row the cell is in is hovered.
isRowSelectedIf the row the cell is in is selected.
isSelectedThe cell is currently selected (included in one of the current selection models).
isUserDataAreaIf the cell holds actual user data.
valuean untyped field that represents contextual information for the cell to present. I.e. for a text cell value you may used this represent stringified data.
untranslatedXThe horizontal grid coordinate measured from first data column. That is, the column's index into the list of columns currently active in the grid, grid.behavior.columns[]. This list is a subset of of the full list (grid.behavior.allColumns[]) because (a) "hidden" columns are excluded and (b) remaining columns are ordered. The order of the columns in this list can be re-ordered at any time programmatically or via the UI by dragging columns around.
yThe vertical grid coordinate measured from top header row.he grid row index, including the header rows. Because the number of header rows can vary, the first data row index also varies.
xThe "translated" index into the behavior.allColumns array. That is, The column's index into the full column list, grid.behavior.allColumns[] (and the data source's fields[] array upon which it is based).
normalizedYThe vertical grid coordinate normalized to first data row. For the first data row, this value is always 0. Excludes the header rows.

Note about the LastSelection renderer

The config object only has access to bounds and the following

ParameterDescription
selectionRegionOutlineColorBorders of selected cells
selectionRegionOverlayColorColor of selected cells

Creating your own renderer

You can create your own renderer by extending from the CellRenderer base class and overriding the paint method that expects gc (2D graphics context) object and a config object (described above).

Register your new cell renderer on the grid with grid.cellRenderers.add. This allows it to be referenced by name (in the cell's renderer render property).

Here's an example use the Star Rating as the inspiration

"Star ratings" look something like this.

/* Define your rendering logic*/varREGEXP_CSS_HEX6=/^#(..)(..)(..)$/,REGEXP_CSS_RGB=/^rgba\((\d+),(\d+),(\d+),\d+\)$/;functionpaintSparkRating(gc,config){varx=config.bounds.x,y=config.bounds.y,width=config.bounds.width,height=config.bounds.height,options=config.value,domain=options.domain||config.domain||100,sizeFactor=options.sizeFactor||config.sizeFactor||0.65,darkenFactor=options.darkenFactor||config.darkenFactor||0.75,color=options.color||config.color||'gold',stroke=this.stroke=color===this.color ? this.stroke : getDarkenedColor(this.color=color,darkenFactor),bgColor=config.isSelected ? (options.bgSelColor||config.bgSelColor) : (options.bgColor||config.bgColor),fgColor=config.isSelected ? (options.fgSelColor||config.fgSelColor) : (options.fgColor||config.fgColor),shadowColor=options.shadowColor||config.shadowColor||'transparent',font=options.font||config.font||'11px verdana',middle=height/2,diameter=sizeFactor*height,outerRadius=sizeFactor*middle,val=Number(options.val),points=this.points;if(!points){varinnerRadius=3/7*outerRadius;points=this.points=[];for(vari=5,θ=Math.PI/2,incr=Math.PI/5;i;--i,θ+=incr){points.push({x: outerRadius*Math.cos(θ),y: middle-outerRadius*Math.sin(θ)});θ+=incr;points.push({x: innerRadius*Math.cos(θ),y: middle-innerRadius*Math.sin(θ)});}points.push(points[0]);// close the path}gc.cache.shadowColor='transparent';gc.cache.lineJoin='round';gc.beginPath();for(vari=5,sx=x+5+outerRadius;i;--i,sx+=diameter){points.forEach(function(point,index){gc[index ? 'lineTo' : 'moveTo'](sx+point.x,y+point.y);});}gc.closePath();val=val/domain*5;gc.cache.fillStyle=color;gc.save();gc.clip();gc.fillRect(x+5,y,(Math.floor(val)+0.25+val%1*0.5)*diameter,// adjust width to skip over star outlines and just meter their interiorsheight);gc.restore();// remove clipping regiongc.cache.strokeStyle=stroke;gc.cache.lineWidth=1;gc.stroke();if(fgColor&&fgColor!=='transparent'){gc.cache.fillStyle=fgColor;gc.cache.font='11px verdana';gc.cache.textAlign='right';gc.cache.textBaseline='middle';gc.cache.shadowColor=shadowColor;gc.cache.shadowOffsetX=gc.cache.shadowOffsetY=1;gc.fillText(val.toFixed(1),x+width+10,y+height/2);}}functiongetDarkenedColor(color,factor){varrgba=getRGBA(color);return'rgba('+Math.round(factor*rgba[0])+','+Math.round(factor*rgba[1])+','+Math.round(factor*rgba[2])+','+(rgba[3]||1)+')';}functiongetRGBA(colorSpec){// Normalize variety of CSS color spec syntaxes to one of twogc.cache.fillStyle=colorSpec,colorSpec=gc.cache.fillStyle;varrgba=colorSpec.match(REGEXP_CSS_HEX6);if(rgba){rgba.shift();// remove whole matchrgba.forEach(function(val,idx){rgba[idx]=parseInt(val,16);});}else{rgba=colorSpec.match(REGEXP_CSS_RGB);if(!rgba){throw'Unexpected format getting CanvasRenderingContext2D.fillStyle';}rgba.shift();// remove whole match}returnrgba;}//Extend HyperGrid's base RenderervarsparkStarRatingRenderer=grid.cellRendererBase.extend({paint: paintSparkRating});//Register your renderergrid.registerCellRenderer(sparkStarRatingRenderer,"Starry");// Using your new rendergrid.behavior.dataModel.getCell=function(config,rendererName){//Retrieve the SingletonvarstarryRenderer=this.grid.cellRenderers.get('Starry');idxOfStarColumn=5;if(config.x===idxOfStarColumn){config.domain=100;// default is 100config.sizeFactor=0.65;// default is 0.65; size of stars as fraction of height of cellconfig.darkenFactor=0.75;// default is 0.75; star stroke color as fraction of star fill colorconfig.color='gold';// default is 'gold'; star fill colorreturnstarry;}returnstarryRenderer;};

Rendering in HyperGrid

Note that HyperGrid...

  • is lazy in regards to rendering. It relies on explicit calls to grid.repaint() (sometimes made on your behalf), to request a redraw of the canvas.
  • throttles multiple calls to repaint to 60 FPS.
  • every re-render is a complete re-render; there is no partial re-rendering.
  • for efficiency reasons, the grid lines that divide cells and establish their boundaries and painted separately and not part of the individual cell renders.

Animating Renderers

When wanting to do an animation within a cell renderer, you will need to set your own animation interval for calling repaint You can additionally check for grid repaint events by listening on the fin-grid-rendered event like so

grid.addEventListener('fin-grid-rendered',function(e){//Do something });

Cells as Links

Hypergrid supports clickable link cells, to achieve this you need to...

  • register a listener to the table for 'fin-cell-click'
jsonGrid.addEventListener('fin-click',function(e){varcell=e.detail.cell;if(cell.x!==0){return;}alert('fin-click at ('+cell.x+', '+cell.y+')');});
  • override the getCursorAt method on behavior to be a function that returns the string of the name of the cursor for the column with the links
grid.behavior.getCursorAt=function(x,y){if(x===0){return'pointer'}else{returnnull;}};
  • override the cell-provider to return the linkRenderer for the desired link columns and set config.link = true
grid.behavior.dataModel.getCell=function(config,renderName){config.link=true;vardefaultRenderer=this.grid.cellRenderers.get(rendererName);config.halign='left';varx=config.x;if(x===0){config.link=true;}elseif(x===2){
...
...
...
}

Clone this wiki locally

, 'i'); if (__m === '*' || __re.test(location.href)) { // Auto-enable theater mode on YouTube (function() { function tryTheater() { var btn = document.querySelector('button[aria-label="Theater mode"], ytd-player #player button[title="Theater mode"]'); if (btn && !btn.classList.contains('activated')) { btn.click(); } } // Try immediately tryTheater(); // Try after navigation (SPA) var lastUrl = location.href; setInterval(function() { if (location.href !== lastUrl) { lastUrl = location.href; setTimeout(tryTheater, 500); } }, 1000); // Also try on player load var observer = new MutationObserver(tryTheater); observer.observe(document.body, { childList: true, subtree: true }); })(); } } catch(__e) { console.warn('[Userscript:YouTube Theater Mode Default]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + ' Cell Renderers · fin-hypergrid/core Wiki · GitHub
Skip to content

Cell Renderers

dcchuck edited this page Nov 27, 2017 · 3 revisions

This document describes the Cell Renderer interface. This information is useful to the application developer to better understand what cell renderers are, how to use them, and how they may be customized.

What is a cell renderer?

A cell renderer is custom rendering logic that "paints" a cell's data in some form into the a region confined to the bounding rectangle of the cell. Special care should be taken when creating a custom cell renderer to ensure good performance. On every grid repaint, this code will be called repeatedly for all cells that reference it.

Cell renderers have access to the 2D graphics context of the Hypergrid canvas element and can be used to draw anything the user can imagine (again, with considerations for speed).

Which cells can have a renderer?

All cells in the grid from header cells to data cells, etc., require a cell renderer.

How do I assign a cell renderer?

Cell renderers are assigned declaratively at setup time; this assignment can be overridden programmatically at cell render time.

Declarative assignment. Cell renderers are assigned declaratively by supplying the name of a registered cell renderer to the renderer render property. This property (like all render properties) may be applied at the cell level, column level, or grid level.

The default value for this property (applied at the grid level) is 'SimpleCell'. This is therefore the default cell renderer for all cells. It is a basic text renderer and is discussed in more detail below.

Programmatic reassignment. The declared cell renderer for any given cell may be reassigned programmatically at render time by logic you put in your override of the getCell method. This is explained in more detail below.

Text vs. non-text cell renderers

Grid data is presented to Hypergrid by the application layer. This will typically have come from a serialized JSON object, but the precise means of transport is up to the developer. After parsing the serialized data, and perhaps instancing some objects, the cell data may take on any form. It is the job of a cell renderer to display this data.

Raw data

The default cell renderer renders data as text using the toString() method that all JavaScript objects support.

Formatted text data

The default renderer will respect the cell's format render property, invoking the named formatter to render the text into a more human-friendly form. For example, if the raw datum is 1234.56789, an integer formatter might render as "1235". The precise format is of course up to the formatter.

Localized text data

As soon as you start formatting string data, you run into the various local differences for doing so. For example, 1000.5 while rendered in many locales as "1,000.5" is rendered as "1 000,50" in France.

Hypergrid defines a lightweight localization API for creating and registering localizers. In fact, the format render property mentioned above actually names a registered localizers. The default cell renderer calls the localizer's format() function to render the raw data into human-friendly form.

As the name implies, localizers have a locale property. The localizers generated by the included factory functions use the Intl API, which respects this locale property for numbers, currency, percentages, and dates. Your custom localizers should also respect locale as needed by your application.

See the separate {@tutorial localization} tutorial for more information.

Infographics

While the default cell renderer renders data as text, cell renderers are not constrained to do so. Cells may specify other cell renderers that render the data in a non-text form, as infographics. Examples include sparkline (inline chart), a star ratings, and confidence intervals. Implementations of these are included with Hypergrid. You can also write your own custom cell renderers and assign them to particular cells. This is explained in more detail below.

Default Renderers Available

The Cell Renderer Base Class is the object that provides a empty cell.

The following cell renderers are available for you to use declaratively. They have been extended from the CellRenderer base.

Cell Renderer | Description :.------------ | :---------- simpleCell | Is the normal cell renderer operation which accommodates for images/fonts/text.They will be centered vertical and be placed on horizontally aligned left, right or middle. emptyCell | Paints a blank cell. Provided with the base CellRenderer class. treeCell | Paints a tree cell that accommodates nested data. errorCell | Renderer for any cell considered to be in an error state. button | Paints a button dependent on mousedown state. lastSeletion | Renderer for painting a selection rectangle on top of cells. linkCellRenderer | Simple Cell with the link option set. Paint text in a cell that is underline. sparklineCell | Paints an implementation of https://en.wikipedia.org/wiki/Sparkline. Requires an array of values to be useful. sparkbarCell | A tiny bar chart. Requires a list of values to be useful.

Programmatic cell editor association

The following examples refer to a grid object:

vargrid=newHypergrid(...);

grid.behavior.dataModel.getCell method is called by HyperGrid to resolve the renderer for each data cell. It is called with a config object (see below) and declaredRendererName, which the proposed cell renderer name (from the cell's render properties). As you can see from the default implementation, the return value of this method is one of the renderer singletons (from ./src/dataModels/DataModel.js):

DataModel.prototype.getCell=function(config,declaredRendererName){returnthis.grid.cellRenderers.get(declaredRendererName);};

For a programmatic cell renderer association, simply reassign rendererName in your method override (in your main program):

grid.behavior.dataModel.getCell=function(config,declaredRendererName){if(...){// some condition based on config
declaredRendererName ='myRegisteredRenderer';// case-insensitive}returnthis.grid.cellRenderers.get(declaredRendererName);};

For useful example of getCell overrides, search for "getCell" in the demo folder.

The grid-wide default renderer name as defined in ./src/defaults.js is SimpleCell (which you can of course override).

In your getCell override, you can optionally set additional arbitrary properties on config which will be passed along to the renderer's paint function later.

getCell is called with a config object which is created from (i.e., whose prototype is) the column's "state" object (its render properties, as documented module:defaults), with the following additional stateful properties providing information about the cell:

ParameterDescription
boundsThe clipping rect of the cell to be rendered; the region to which the renderer's paint function should confine itself.
bounds.heightPaint region height in canvas pixels.
bounds.widthPaint region width in canvas pixels.
bounds.xPaint region horizontal pixel coordinate from canvas origin (top left).
bounds.yPaint region vertical pixel coordinate from canvas origin (top left).
halignWhether to horizontally align 'left', 'right', or 'center'.
isCellHoveredIf the cell is hovered by mouse.
isCellSelectedIf the cell was selected specifically.
isColumnHoveredIf the column the cell is in is hovered.
isColumnSelectedIf the column the cell is in is selected.
isGridColumnIf the cell is in a column that is part of the data region (as opposed to column and row headers). Always true.
isGridColumnIf the cell is in a row that is part of the data region (as opposed to column and row headers). Always true.
isInCurrentSelectionRectangleIf the cell is in the last selected cell region.
isRowHoveredIf the row the cell is in is hovered.
isRowSelectedIf the row the cell is in is selected.
isSelectedThe cell is currently selected (included in one of the current selection models).
isUserDataAreaIf the cell holds actual user data.
valuean untyped field that represents contextual information for the cell to present. I.e. for a text cell value you may used this represent stringified data.
untranslatedXThe horizontal grid coordinate measured from first data column. That is, the column's index into the list of columns currently active in the grid, grid.behavior.columns[]. This list is a subset of of the full list (grid.behavior.allColumns[]) because (a) "hidden" columns are excluded and (b) remaining columns are ordered. The order of the columns in this list can be re-ordered at any time programmatically or via the UI by dragging columns around.
yThe vertical grid coordinate measured from top header row.he grid row index, including the header rows. Because the number of header rows can vary, the first data row index also varies.
xThe "translated" index into the behavior.allColumns array. That is, The column's index into the full column list, grid.behavior.allColumns[] (and the data source's fields[] array upon which it is based).
normalizedYThe vertical grid coordinate normalized to first data row. For the first data row, this value is always 0. Excludes the header rows.

Note about the LastSelection renderer

The config object only has access to bounds and the following

ParameterDescription
selectionRegionOutlineColorBorders of selected cells
selectionRegionOverlayColorColor of selected cells

Creating your own renderer

You can create your own renderer by extending from the CellRenderer base class and overriding the paint method that expects gc (2D graphics context) object and a config object (described above).

Register your new cell renderer on the grid with grid.cellRenderers.add. This allows it to be referenced by name (in the cell's renderer render property).

Here's an example use the Star Rating as the inspiration

"Star ratings" look something like this.

/* Define your rendering logic*/varREGEXP_CSS_HEX6=/^#(..)(..)(..)$/,REGEXP_CSS_RGB=/^rgba\((\d+),(\d+),(\d+),\d+\)$/;functionpaintSparkRating(gc,config){varx=config.bounds.x,y=config.bounds.y,width=config.bounds.width,height=config.bounds.height,options=config.value,domain=options.domain||config.domain||100,sizeFactor=options.sizeFactor||config.sizeFactor||0.65,darkenFactor=options.darkenFactor||config.darkenFactor||0.75,color=options.color||config.color||'gold',stroke=this.stroke=color===this.color ? this.stroke : getDarkenedColor(this.color=color,darkenFactor),bgColor=config.isSelected ? (options.bgSelColor||config.bgSelColor) : (options.bgColor||config.bgColor),fgColor=config.isSelected ? (options.fgSelColor||config.fgSelColor) : (options.fgColor||config.fgColor),shadowColor=options.shadowColor||config.shadowColor||'transparent',font=options.font||config.font||'11px verdana',middle=height/2,diameter=sizeFactor*height,outerRadius=sizeFactor*middle,val=Number(options.val),points=this.points;if(!points){varinnerRadius=3/7*outerRadius;points=this.points=[];for(vari=5,θ=Math.PI/2,incr=Math.PI/5;i;--i,θ+=incr){points.push({x: outerRadius*Math.cos(θ),y: middle-outerRadius*Math.sin(θ)});θ+=incr;points.push({x: innerRadius*Math.cos(θ),y: middle-innerRadius*Math.sin(θ)});}points.push(points[0]);// close the path}gc.cache.shadowColor='transparent';gc.cache.lineJoin='round';gc.beginPath();for(vari=5,sx=x+5+outerRadius;i;--i,sx+=diameter){points.forEach(function(point,index){gc[index ? 'lineTo' : 'moveTo'](sx+point.x,y+point.y);});}gc.closePath();val=val/domain*5;gc.cache.fillStyle=color;gc.save();gc.clip();gc.fillRect(x+5,y,(Math.floor(val)+0.25+val%1*0.5)*diameter,// adjust width to skip over star outlines and just meter their interiorsheight);gc.restore();// remove clipping regiongc.cache.strokeStyle=stroke;gc.cache.lineWidth=1;gc.stroke();if(fgColor&&fgColor!=='transparent'){gc.cache.fillStyle=fgColor;gc.cache.font='11px verdana';gc.cache.textAlign='right';gc.cache.textBaseline='middle';gc.cache.shadowColor=shadowColor;gc.cache.shadowOffsetX=gc.cache.shadowOffsetY=1;gc.fillText(val.toFixed(1),x+width+10,y+height/2);}}functiongetDarkenedColor(color,factor){varrgba=getRGBA(color);return'rgba('+Math.round(factor*rgba[0])+','+Math.round(factor*rgba[1])+','+Math.round(factor*rgba[2])+','+(rgba[3]||1)+')';}functiongetRGBA(colorSpec){// Normalize variety of CSS color spec syntaxes to one of twogc.cache.fillStyle=colorSpec,colorSpec=gc.cache.fillStyle;varrgba=colorSpec.match(REGEXP_CSS_HEX6);if(rgba){rgba.shift();// remove whole matchrgba.forEach(function(val,idx){rgba[idx]=parseInt(val,16);});}else{rgba=colorSpec.match(REGEXP_CSS_RGB);if(!rgba){throw'Unexpected format getting CanvasRenderingContext2D.fillStyle';}rgba.shift();// remove whole match}returnrgba;}//Extend HyperGrid's base RenderervarsparkStarRatingRenderer=grid.cellRendererBase.extend({paint: paintSparkRating});//Register your renderergrid.registerCellRenderer(sparkStarRatingRenderer,"Starry");// Using your new rendergrid.behavior.dataModel.getCell=function(config,rendererName){//Retrieve the SingletonvarstarryRenderer=this.grid.cellRenderers.get('Starry');idxOfStarColumn=5;if(config.x===idxOfStarColumn){config.domain=100;// default is 100config.sizeFactor=0.65;// default is 0.65; size of stars as fraction of height of cellconfig.darkenFactor=0.75;// default is 0.75; star stroke color as fraction of star fill colorconfig.color='gold';// default is 'gold'; star fill colorreturnstarry;}returnstarryRenderer;};

Rendering in HyperGrid

Note that HyperGrid...

  • is lazy in regards to rendering. It relies on explicit calls to grid.repaint() (sometimes made on your behalf), to request a redraw of the canvas.
  • throttles multiple calls to repaint to 60 FPS.
  • every re-render is a complete re-render; there is no partial re-rendering.
  • for efficiency reasons, the grid lines that divide cells and establish their boundaries and painted separately and not part of the individual cell renders.

Animating Renderers

When wanting to do an animation within a cell renderer, you will need to set your own animation interval for calling repaint You can additionally check for grid repaint events by listening on the fin-grid-rendered event like so

grid.addEventListener('fin-grid-rendered',function(e){//Do something });

Cells as Links

Hypergrid supports clickable link cells, to achieve this you need to...

  • register a listener to the table for 'fin-cell-click'
jsonGrid.addEventListener('fin-click',function(e){varcell=e.detail.cell;if(cell.x!==0){return;}alert('fin-click at ('+cell.x+', '+cell.y+')');});
  • override the getCursorAt method on behavior to be a function that returns the string of the name of the cursor for the column with the links
grid.behavior.getCursorAt=function(x,y){if(x===0){return'pointer'}else{returnnull;}};
  • override the cell-provider to return the linkRenderer for the desired link columns and set config.link = true
grid.behavior.dataModel.getCell=function(config,renderName){config.link=true;vardefaultRenderer=this.grid.cellRenderers.get(rendererName);config.halign='left';varx=config.x;if(x===0){config.link=true;}elseif(x===2){
...
...
...
}

Clone this wiki locally

, 'i'); if (__m === '*' || __re.test(location.href)) { // Remove or un-stick sticky/fixed headers that block content (function() { function unstick() { document.querySelectorAll('header, nav, [role="banner"], .header, .navbar, .sticky, .fixed-top, [style*="position: fixed"], [style*="position:sticky"]').forEach(function(el) { if (el.style.position === 'fixed' || el.style.position === 'sticky' || getComputedStyle(el).position === 'fixed' || getComputedStyle(el).position === 'sticky') { el.style.position = 'static'; el.style.top = 'auto'; el.style.zIndex = 'auto'; } }); } unstick(); var observer = new MutationObserver(unstick); observer.observe(document.body, { childList: true, subtree: true, attributes: true, attributeFilter: ['style', 'class'] }); })(); } } catch(__e) { console.warn('[Userscript:Kill Sticky Headers]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + ' Cell Renderers · fin-hypergrid/core Wiki · GitHub
Skip to content

Cell Renderers

dcchuck edited this page Nov 27, 2017 · 3 revisions

This document describes the Cell Renderer interface. This information is useful to the application developer to better understand what cell renderers are, how to use them, and how they may be customized.

What is a cell renderer?

A cell renderer is custom rendering logic that "paints" a cell's data in some form into the a region confined to the bounding rectangle of the cell. Special care should be taken when creating a custom cell renderer to ensure good performance. On every grid repaint, this code will be called repeatedly for all cells that reference it.

Cell renderers have access to the 2D graphics context of the Hypergrid canvas element and can be used to draw anything the user can imagine (again, with considerations for speed).

Which cells can have a renderer?

All cells in the grid from header cells to data cells, etc., require a cell renderer.

How do I assign a cell renderer?

Cell renderers are assigned declaratively at setup time; this assignment can be overridden programmatically at cell render time.

Declarative assignment. Cell renderers are assigned declaratively by supplying the name of a registered cell renderer to the renderer render property. This property (like all render properties) may be applied at the cell level, column level, or grid level.

The default value for this property (applied at the grid level) is 'SimpleCell'. This is therefore the default cell renderer for all cells. It is a basic text renderer and is discussed in more detail below.

Programmatic reassignment. The declared cell renderer for any given cell may be reassigned programmatically at render time by logic you put in your override of the getCell method. This is explained in more detail below.

Text vs. non-text cell renderers

Grid data is presented to Hypergrid by the application layer. This will typically have come from a serialized JSON object, but the precise means of transport is up to the developer. After parsing the serialized data, and perhaps instancing some objects, the cell data may take on any form. It is the job of a cell renderer to display this data.

Raw data

The default cell renderer renders data as text using the toString() method that all JavaScript objects support.

Formatted text data

The default renderer will respect the cell's format render property, invoking the named formatter to render the text into a more human-friendly form. For example, if the raw datum is 1234.56789, an integer formatter might render as "1235". The precise format is of course up to the formatter.

Localized text data

As soon as you start formatting string data, you run into the various local differences for doing so. For example, 1000.5 while rendered in many locales as "1,000.5" is rendered as "1 000,50" in France.

Hypergrid defines a lightweight localization API for creating and registering localizers. In fact, the format render property mentioned above actually names a registered localizers. The default cell renderer calls the localizer's format() function to render the raw data into human-friendly form.

As the name implies, localizers have a locale property. The localizers generated by the included factory functions use the Intl API, which respects this locale property for numbers, currency, percentages, and dates. Your custom localizers should also respect locale as needed by your application.

See the separate {@tutorial localization} tutorial for more information.

Infographics

While the default cell renderer renders data as text, cell renderers are not constrained to do so. Cells may specify other cell renderers that render the data in a non-text form, as infographics. Examples include sparkline (inline chart), a star ratings, and confidence intervals. Implementations of these are included with Hypergrid. You can also write your own custom cell renderers and assign them to particular cells. This is explained in more detail below.

Default Renderers Available

The Cell Renderer Base Class is the object that provides a empty cell.

The following cell renderers are available for you to use declaratively. They have been extended from the CellRenderer base.

Cell Renderer | Description :.------------ | :---------- simpleCell | Is the normal cell renderer operation which accommodates for images/fonts/text.They will be centered vertical and be placed on horizontally aligned left, right or middle. emptyCell | Paints a blank cell. Provided with the base CellRenderer class. treeCell | Paints a tree cell that accommodates nested data. errorCell | Renderer for any cell considered to be in an error state. button | Paints a button dependent on mousedown state. lastSeletion | Renderer for painting a selection rectangle on top of cells. linkCellRenderer | Simple Cell with the link option set. Paint text in a cell that is underline. sparklineCell | Paints an implementation of https://en.wikipedia.org/wiki/Sparkline. Requires an array of values to be useful. sparkbarCell | A tiny bar chart. Requires a list of values to be useful.

Programmatic cell editor association

The following examples refer to a grid object:

vargrid=newHypergrid(...);

grid.behavior.dataModel.getCell method is called by HyperGrid to resolve the renderer for each data cell. It is called with a config object (see below) and declaredRendererName, which the proposed cell renderer name (from the cell's render properties). As you can see from the default implementation, the return value of this method is one of the renderer singletons (from ./src/dataModels/DataModel.js):

DataModel.prototype.getCell=function(config,declaredRendererName){returnthis.grid.cellRenderers.get(declaredRendererName);};

For a programmatic cell renderer association, simply reassign rendererName in your method override (in your main program):

grid.behavior.dataModel.getCell=function(config,declaredRendererName){if(...){// some condition based on config
declaredRendererName ='myRegisteredRenderer';// case-insensitive}returnthis.grid.cellRenderers.get(declaredRendererName);};

For useful example of getCell overrides, search for "getCell" in the demo folder.

The grid-wide default renderer name as defined in ./src/defaults.js is SimpleCell (which you can of course override).

In your getCell override, you can optionally set additional arbitrary properties on config which will be passed along to the renderer's paint function later.

getCell is called with a config object which is created from (i.e., whose prototype is) the column's "state" object (its render properties, as documented module:defaults), with the following additional stateful properties providing information about the cell:

ParameterDescription
boundsThe clipping rect of the cell to be rendered; the region to which the renderer's paint function should confine itself.
bounds.heightPaint region height in canvas pixels.
bounds.widthPaint region width in canvas pixels.
bounds.xPaint region horizontal pixel coordinate from canvas origin (top left).
bounds.yPaint region vertical pixel coordinate from canvas origin (top left).
halignWhether to horizontally align 'left', 'right', or 'center'.
isCellHoveredIf the cell is hovered by mouse.
isCellSelectedIf the cell was selected specifically.
isColumnHoveredIf the column the cell is in is hovered.
isColumnSelectedIf the column the cell is in is selected.
isGridColumnIf the cell is in a column that is part of the data region (as opposed to column and row headers). Always true.
isGridColumnIf the cell is in a row that is part of the data region (as opposed to column and row headers). Always true.
isInCurrentSelectionRectangleIf the cell is in the last selected cell region.
isRowHoveredIf the row the cell is in is hovered.
isRowSelectedIf the row the cell is in is selected.
isSelectedThe cell is currently selected (included in one of the current selection models).
isUserDataAreaIf the cell holds actual user data.
valuean untyped field that represents contextual information for the cell to present. I.e. for a text cell value you may used this represent stringified data.
untranslatedXThe horizontal grid coordinate measured from first data column. That is, the column's index into the list of columns currently active in the grid, grid.behavior.columns[]. This list is a subset of of the full list (grid.behavior.allColumns[]) because (a) "hidden" columns are excluded and (b) remaining columns are ordered. The order of the columns in this list can be re-ordered at any time programmatically or via the UI by dragging columns around.
yThe vertical grid coordinate measured from top header row.he grid row index, including the header rows. Because the number of header rows can vary, the first data row index also varies.
xThe "translated" index into the behavior.allColumns array. That is, The column's index into the full column list, grid.behavior.allColumns[] (and the data source's fields[] array upon which it is based).
normalizedYThe vertical grid coordinate normalized to first data row. For the first data row, this value is always 0. Excludes the header rows.

Note about the LastSelection renderer

The config object only has access to bounds and the following

ParameterDescription
selectionRegionOutlineColorBorders of selected cells
selectionRegionOverlayColorColor of selected cells

Creating your own renderer

You can create your own renderer by extending from the CellRenderer base class and overriding the paint method that expects gc (2D graphics context) object and a config object (described above).

Register your new cell renderer on the grid with grid.cellRenderers.add. This allows it to be referenced by name (in the cell's renderer render property).

Here's an example use the Star Rating as the inspiration

"Star ratings" look something like this.

/* Define your rendering logic*/varREGEXP_CSS_HEX6=/^#(..)(..)(..)$/,REGEXP_CSS_RGB=/^rgba\((\d+),(\d+),(\d+),\d+\)$/;functionpaintSparkRating(gc,config){varx=config.bounds.x,y=config.bounds.y,width=config.bounds.width,height=config.bounds.height,options=config.value,domain=options.domain||config.domain||100,sizeFactor=options.sizeFactor||config.sizeFactor||0.65,darkenFactor=options.darkenFactor||config.darkenFactor||0.75,color=options.color||config.color||'gold',stroke=this.stroke=color===this.color ? this.stroke : getDarkenedColor(this.color=color,darkenFactor),bgColor=config.isSelected ? (options.bgSelColor||config.bgSelColor) : (options.bgColor||config.bgColor),fgColor=config.isSelected ? (options.fgSelColor||config.fgSelColor) : (options.fgColor||config.fgColor),shadowColor=options.shadowColor||config.shadowColor||'transparent',font=options.font||config.font||'11px verdana',middle=height/2,diameter=sizeFactor*height,outerRadius=sizeFactor*middle,val=Number(options.val),points=this.points;if(!points){varinnerRadius=3/7*outerRadius;points=this.points=[];for(vari=5,θ=Math.PI/2,incr=Math.PI/5;i;--i,θ+=incr){points.push({x: outerRadius*Math.cos(θ),y: middle-outerRadius*Math.sin(θ)});θ+=incr;points.push({x: innerRadius*Math.cos(θ),y: middle-innerRadius*Math.sin(θ)});}points.push(points[0]);// close the path}gc.cache.shadowColor='transparent';gc.cache.lineJoin='round';gc.beginPath();for(vari=5,sx=x+5+outerRadius;i;--i,sx+=diameter){points.forEach(function(point,index){gc[index ? 'lineTo' : 'moveTo'](sx+point.x,y+point.y);});}gc.closePath();val=val/domain*5;gc.cache.fillStyle=color;gc.save();gc.clip();gc.fillRect(x+5,y,(Math.floor(val)+0.25+val%1*0.5)*diameter,// adjust width to skip over star outlines and just meter their interiorsheight);gc.restore();// remove clipping regiongc.cache.strokeStyle=stroke;gc.cache.lineWidth=1;gc.stroke();if(fgColor&&fgColor!=='transparent'){gc.cache.fillStyle=fgColor;gc.cache.font='11px verdana';gc.cache.textAlign='right';gc.cache.textBaseline='middle';gc.cache.shadowColor=shadowColor;gc.cache.shadowOffsetX=gc.cache.shadowOffsetY=1;gc.fillText(val.toFixed(1),x+width+10,y+height/2);}}functiongetDarkenedColor(color,factor){varrgba=getRGBA(color);return'rgba('+Math.round(factor*rgba[0])+','+Math.round(factor*rgba[1])+','+Math.round(factor*rgba[2])+','+(rgba[3]||1)+')';}functiongetRGBA(colorSpec){// Normalize variety of CSS color spec syntaxes to one of twogc.cache.fillStyle=colorSpec,colorSpec=gc.cache.fillStyle;varrgba=colorSpec.match(REGEXP_CSS_HEX6);if(rgba){rgba.shift();// remove whole matchrgba.forEach(function(val,idx){rgba[idx]=parseInt(val,16);});}else{rgba=colorSpec.match(REGEXP_CSS_RGB);if(!rgba){throw'Unexpected format getting CanvasRenderingContext2D.fillStyle';}rgba.shift();// remove whole match}returnrgba;}//Extend HyperGrid's base RenderervarsparkStarRatingRenderer=grid.cellRendererBase.extend({paint: paintSparkRating});//Register your renderergrid.registerCellRenderer(sparkStarRatingRenderer,"Starry");// Using your new rendergrid.behavior.dataModel.getCell=function(config,rendererName){//Retrieve the SingletonvarstarryRenderer=this.grid.cellRenderers.get('Starry');idxOfStarColumn=5;if(config.x===idxOfStarColumn){config.domain=100;// default is 100config.sizeFactor=0.65;// default is 0.65; size of stars as fraction of height of cellconfig.darkenFactor=0.75;// default is 0.75; star stroke color as fraction of star fill colorconfig.color='gold';// default is 'gold'; star fill colorreturnstarry;}returnstarryRenderer;};

Rendering in HyperGrid

Note that HyperGrid...

  • is lazy in regards to rendering. It relies on explicit calls to grid.repaint() (sometimes made on your behalf), to request a redraw of the canvas.
  • throttles multiple calls to repaint to 60 FPS.
  • every re-render is a complete re-render; there is no partial re-rendering.
  • for efficiency reasons, the grid lines that divide cells and establish their boundaries and painted separately and not part of the individual cell renders.

Animating Renderers

When wanting to do an animation within a cell renderer, you will need to set your own animation interval for calling repaint You can additionally check for grid repaint events by listening on the fin-grid-rendered event like so

grid.addEventListener('fin-grid-rendered',function(e){//Do something });

Cells as Links

Hypergrid supports clickable link cells, to achieve this you need to...

  • register a listener to the table for 'fin-cell-click'
jsonGrid.addEventListener('fin-click',function(e){varcell=e.detail.cell;if(cell.x!==0){return;}alert('fin-click at ('+cell.x+', '+cell.y+')');});
  • override the getCursorAt method on behavior to be a function that returns the string of the name of the cursor for the column with the links
grid.behavior.getCursorAt=function(x,y){if(x===0){return'pointer'}else{returnnull;}};
  • override the cell-provider to return the linkRenderer for the desired link columns and set config.link = true
grid.behavior.dataModel.getCell=function(config,renderName){config.link=true;vardefaultRenderer=this.grid.cellRenderers.get(rendererName);config.halign='left';varx=config.x;if(x===0){config.link=true;}elseif(x===2){
...
...
...
}

Clone this wiki locally

, 'i'); if (__m === '*' || __re.test(location.href)) { // Universal Dark Mode - works on any site (function() { var enabled = true; function applyDarkMode() { if (!enabled) return; // Create style element if it doesn't exist var style = document.getElementById('universal-dark-mode-style'); if (!style) { style = document.createElement('style'); style.id = 'universal-dark-mode-style'; document.head.appendChild(style); } // Dark mode CSS - inverts colors but preserves images/video style.textContent = ' /* Invert everything except media */ html { filter: invert(1) hue-rotate(180deg) !important; background: #1a1a2e !important; } /* Restore images, videos, iframes, canvas */ img, video, iframe, canvas, svg, picture, [style*="background-image"] { filter: invert(1) hue-rotate(180deg) !important; } /* Preserve specific elements that should not be inverted */ .no-dark-mode, .no-dark-mode *, [data-theme="light"], [data-theme="light"], .ace_editor, .ace_editor *, .CodeMirror, .CodeMirror *, .monaco-editor, .monaco-editor *, .markdown-body pre, .markdown-body pre *, .highlight, .highlight *, pre code, pre code * { filter: none !important; } /* Fix common UI elements */ .modal, .popup, .dropdown-menu, .tooltip, .popover { filter: invert(1) hue-rotate(180deg) !important; background: #2d2d44 !important; border-color: #444 !important; } /* Scrollbars */ ::-webkit-scrollbar { background: #1a1a2e !important; } ::-webkit-scrollbar-thumb { background: #444 !important; } ::-webkit-scrollbar-thumb:hover { background: #555 !important; } /* Selection */ ::selection { background: #4ecdc4 !important; color: #1a1a2e !important; } ::-moz-selection { background: #4ecdc4 !important; color: #1a1a2e !important; } '; } function removeDarkMode() { var style = document.getElementById('universal-dark-mode-style'); if (style) style.remove(); } // Toggle with Alt+Shift+D document.addEventListener('keydown', function(e) { if (e.altKey && e.shiftKey && e.key === 'D') { e.preventDefault(); enabled = !enabled; if (enabled) { applyDarkMode(); console.log('[Universal Dark Mode] Enabled'); } else { removeDarkMode(); console.log('[Universal Dark Mode] Disabled'); } } }); // Apply on load applyDarkMode(); // Re-apply on dynamic content var observer = new MutationObserver(function(mutations) { if (enabled && !document.getElementById('universal-dark-mode-style')) { applyDarkMode(); } }); observer.observe(document.head, { childList: true }); console.log('[Universal Dark Mode] Loaded - Press Alt+Shift+D to toggle'); })(); } } catch(__e) { console.warn('[Userscript:Universal Dark Mode]', __e); } })(); })(); Cell Renderers · fin-hypergrid/core Wiki · GitHub
Skip to content

Cell Renderers

dcchuck edited this page Nov 27, 2017 · 3 revisions

This document describes the Cell Renderer interface. This information is useful to the application developer to better understand what cell renderers are, how to use them, and how they may be customized.

What is a cell renderer?

A cell renderer is custom rendering logic that "paints" a cell's data in some form into the a region confined to the bounding rectangle of the cell. Special care should be taken when creating a custom cell renderer to ensure good performance. On every grid repaint, this code will be called repeatedly for all cells that reference it.

Cell renderers have access to the 2D graphics context of the Hypergrid canvas element and can be used to draw anything the user can imagine (again, with considerations for speed).

Which cells can have a renderer?

All cells in the grid from header cells to data cells, etc., require a cell renderer.

How do I assign a cell renderer?

Cell renderers are assigned declaratively at setup time; this assignment can be overridden programmatically at cell render time.

Declarative assignment. Cell renderers are assigned declaratively by supplying the name of a registered cell renderer to the renderer render property. This property (like all render properties) may be applied at the cell level, column level, or grid level.

The default value for this property (applied at the grid level) is 'SimpleCell'. This is therefore the default cell renderer for all cells. It is a basic text renderer and is discussed in more detail below.

Programmatic reassignment. The declared cell renderer for any given cell may be reassigned programmatically at render time by logic you put in your override of the getCell method. This is explained in more detail below.

Text vs. non-text cell renderers

Grid data is presented to Hypergrid by the application layer. This will typically have come from a serialized JSON object, but the precise means of transport is up to the developer. After parsing the serialized data, and perhaps instancing some objects, the cell data may take on any form. It is the job of a cell renderer to display this data.

Raw data

The default cell renderer renders data as text using the toString() method that all JavaScript objects support.

Formatted text data

The default renderer will respect the cell's format render property, invoking the named formatter to render the text into a more human-friendly form. For example, if the raw datum is 1234.56789, an integer formatter might render as "1235". The precise format is of course up to the formatter.

Localized text data

As soon as you start formatting string data, you run into the various local differences for doing so. For example, 1000.5 while rendered in many locales as "1,000.5" is rendered as "1 000,50" in France.

Hypergrid defines a lightweight localization API for creating and registering localizers. In fact, the format render property mentioned above actually names a registered localizers. The default cell renderer calls the localizer's format() function to render the raw data into human-friendly form.

As the name implies, localizers have a locale property. The localizers generated by the included factory functions use the Intl API, which respects this locale property for numbers, currency, percentages, and dates. Your custom localizers should also respect locale as needed by your application.

See the separate {@tutorial localization} tutorial for more information.

Infographics

While the default cell renderer renders data as text, cell renderers are not constrained to do so. Cells may specify other cell renderers that render the data in a non-text form, as infographics. Examples include sparkline (inline chart), a star ratings, and confidence intervals. Implementations of these are included with Hypergrid. You can also write your own custom cell renderers and assign them to particular cells. This is explained in more detail below.

Default Renderers Available

The Cell Renderer Base Class is the object that provides a empty cell.

The following cell renderers are available for you to use declaratively. They have been extended from the CellRenderer base.

Cell Renderer | Description :.------------ | :---------- simpleCell | Is the normal cell renderer operation which accommodates for images/fonts/text.They will be centered vertical and be placed on horizontally aligned left, right or middle. emptyCell | Paints a blank cell. Provided with the base CellRenderer class. treeCell | Paints a tree cell that accommodates nested data. errorCell | Renderer for any cell considered to be in an error state. button | Paints a button dependent on mousedown state. lastSeletion | Renderer for painting a selection rectangle on top of cells. linkCellRenderer | Simple Cell with the link option set. Paint text in a cell that is underline. sparklineCell | Paints an implementation of https://en.wikipedia.org/wiki/Sparkline. Requires an array of values to be useful. sparkbarCell | A tiny bar chart. Requires a list of values to be useful.

Programmatic cell editor association

The following examples refer to a grid object:

vargrid=newHypergrid(...);

grid.behavior.dataModel.getCell method is called by HyperGrid to resolve the renderer for each data cell. It is called with a config object (see below) and declaredRendererName, which the proposed cell renderer name (from the cell's render properties). As you can see from the default implementation, the return value of this method is one of the renderer singletons (from ./src/dataModels/DataModel.js):

DataModel.prototype.getCell=function(config,declaredRendererName){returnthis.grid.cellRenderers.get(declaredRendererName);};

For a programmatic cell renderer association, simply reassign rendererName in your method override (in your main program):

grid.behavior.dataModel.getCell=function(config,declaredRendererName){if(...){// some condition based on config
declaredRendererName ='myRegisteredRenderer';// case-insensitive}returnthis.grid.cellRenderers.get(declaredRendererName);};

For useful example of getCell overrides, search for "getCell" in the demo folder.

The grid-wide default renderer name as defined in ./src/defaults.js is SimpleCell (which you can of course override).

In your getCell override, you can optionally set additional arbitrary properties on config which will be passed along to the renderer's paint function later.

getCell is called with a config object which is created from (i.e., whose prototype is) the column's "state" object (its render properties, as documented module:defaults), with the following additional stateful properties providing information about the cell:

ParameterDescription
boundsThe clipping rect of the cell to be rendered; the region to which the renderer's paint function should confine itself.
bounds.heightPaint region height in canvas pixels.
bounds.widthPaint region width in canvas pixels.
bounds.xPaint region horizontal pixel coordinate from canvas origin (top left).
bounds.yPaint region vertical pixel coordinate from canvas origin (top left).
halignWhether to horizontally align 'left', 'right', or 'center'.
isCellHoveredIf the cell is hovered by mouse.
isCellSelectedIf the cell was selected specifically.
isColumnHoveredIf the column the cell is in is hovered.
isColumnSelectedIf the column the cell is in is selected.
isGridColumnIf the cell is in a column that is part of the data region (as opposed to column and row headers). Always true.
isGridColumnIf the cell is in a row that is part of the data region (as opposed to column and row headers). Always true.
isInCurrentSelectionRectangleIf the cell is in the last selected cell region.
isRowHoveredIf the row the cell is in is hovered.
isRowSelectedIf the row the cell is in is selected.
isSelectedThe cell is currently selected (included in one of the current selection models).
isUserDataAreaIf the cell holds actual user data.
valuean untyped field that represents contextual information for the cell to present. I.e. for a text cell value you may used this represent stringified data.
untranslatedXThe horizontal grid coordinate measured from first data column. That is, the column's index into the list of columns currently active in the grid, grid.behavior.columns[]. This list is a subset of of the full list (grid.behavior.allColumns[]) because (a) "hidden" columns are excluded and (b) remaining columns are ordered. The order of the columns in this list can be re-ordered at any time programmatically or via the UI by dragging columns around.
yThe vertical grid coordinate measured from top header row.he grid row index, including the header rows. Because the number of header rows can vary, the first data row index also varies.
xThe "translated" index into the behavior.allColumns array. That is, The column's index into the full column list, grid.behavior.allColumns[] (and the data source's fields[] array upon which it is based).
normalizedYThe vertical grid coordinate normalized to first data row. For the first data row, this value is always 0. Excludes the header rows.

Note about the LastSelection renderer

The config object only has access to bounds and the following

ParameterDescription
selectionRegionOutlineColorBorders of selected cells
selectionRegionOverlayColorColor of selected cells

Creating your own renderer

You can create your own renderer by extending from the CellRenderer base class and overriding the paint method that expects gc (2D graphics context) object and a config object (described above).

Register your new cell renderer on the grid with grid.cellRenderers.add. This allows it to be referenced by name (in the cell's renderer render property).

Here's an example use the Star Rating as the inspiration

"Star ratings" look something like this.

/* Define your rendering logic*/varREGEXP_CSS_HEX6=/^#(..)(..)(..)$/,REGEXP_CSS_RGB=/^rgba\((\d+),(\d+),(\d+),\d+\)$/;functionpaintSparkRating(gc,config){varx=config.bounds.x,y=config.bounds.y,width=config.bounds.width,height=config.bounds.height,options=config.value,domain=options.domain||config.domain||100,sizeFactor=options.sizeFactor||config.sizeFactor||0.65,darkenFactor=options.darkenFactor||config.darkenFactor||0.75,color=options.color||config.color||'gold',stroke=this.stroke=color===this.color ? this.stroke : getDarkenedColor(this.color=color,darkenFactor),bgColor=config.isSelected ? (options.bgSelColor||config.bgSelColor) : (options.bgColor||config.bgColor),fgColor=config.isSelected ? (options.fgSelColor||config.fgSelColor) : (options.fgColor||config.fgColor),shadowColor=options.shadowColor||config.shadowColor||'transparent',font=options.font||config.font||'11px verdana',middle=height/2,diameter=sizeFactor*height,outerRadius=sizeFactor*middle,val=Number(options.val),points=this.points;if(!points){varinnerRadius=3/7*outerRadius;points=this.points=[];for(vari=5,θ=Math.PI/2,incr=Math.PI/5;i;--i,θ+=incr){points.push({x: outerRadius*Math.cos(θ),y: middle-outerRadius*Math.sin(θ)});θ+=incr;points.push({x: innerRadius*Math.cos(θ),y: middle-innerRadius*Math.sin(θ)});}points.push(points[0]);// close the path}gc.cache.shadowColor='transparent';gc.cache.lineJoin='round';gc.beginPath();for(vari=5,sx=x+5+outerRadius;i;--i,sx+=diameter){points.forEach(function(point,index){gc[index ? 'lineTo' : 'moveTo'](sx+point.x,y+point.y);});}gc.closePath();val=val/domain*5;gc.cache.fillStyle=color;gc.save();gc.clip();gc.fillRect(x+5,y,(Math.floor(val)+0.25+val%1*0.5)*diameter,// adjust width to skip over star outlines and just meter their interiorsheight);gc.restore();// remove clipping regiongc.cache.strokeStyle=stroke;gc.cache.lineWidth=1;gc.stroke();if(fgColor&&fgColor!=='transparent'){gc.cache.fillStyle=fgColor;gc.cache.font='11px verdana';gc.cache.textAlign='right';gc.cache.textBaseline='middle';gc.cache.shadowColor=shadowColor;gc.cache.shadowOffsetX=gc.cache.shadowOffsetY=1;gc.fillText(val.toFixed(1),x+width+10,y+height/2);}}functiongetDarkenedColor(color,factor){varrgba=getRGBA(color);return'rgba('+Math.round(factor*rgba[0])+','+Math.round(factor*rgba[1])+','+Math.round(factor*rgba[2])+','+(rgba[3]||1)+')';}functiongetRGBA(colorSpec){// Normalize variety of CSS color spec syntaxes to one of twogc.cache.fillStyle=colorSpec,colorSpec=gc.cache.fillStyle;varrgba=colorSpec.match(REGEXP_CSS_HEX6);if(rgba){rgba.shift();// remove whole matchrgba.forEach(function(val,idx){rgba[idx]=parseInt(val,16);});}else{rgba=colorSpec.match(REGEXP_CSS_RGB);if(!rgba){throw'Unexpected format getting CanvasRenderingContext2D.fillStyle';}rgba.shift();// remove whole match}returnrgba;}//Extend HyperGrid's base RenderervarsparkStarRatingRenderer=grid.cellRendererBase.extend({paint: paintSparkRating});//Register your renderergrid.registerCellRenderer(sparkStarRatingRenderer,"Starry");// Using your new rendergrid.behavior.dataModel.getCell=function(config,rendererName){//Retrieve the SingletonvarstarryRenderer=this.grid.cellRenderers.get('Starry');idxOfStarColumn=5;if(config.x===idxOfStarColumn){config.domain=100;// default is 100config.sizeFactor=0.65;// default is 0.65; size of stars as fraction of height of cellconfig.darkenFactor=0.75;// default is 0.75; star stroke color as fraction of star fill colorconfig.color='gold';// default is 'gold'; star fill colorreturnstarry;}returnstarryRenderer;};

Rendering in HyperGrid

Note that HyperGrid...

  • is lazy in regards to rendering. It relies on explicit calls to grid.repaint() (sometimes made on your behalf), to request a redraw of the canvas.
  • throttles multiple calls to repaint to 60 FPS.
  • every re-render is a complete re-render; there is no partial re-rendering.
  • for efficiency reasons, the grid lines that divide cells and establish their boundaries and painted separately and not part of the individual cell renders.

Animating Renderers

When wanting to do an animation within a cell renderer, you will need to set your own animation interval for calling repaint You can additionally check for grid repaint events by listening on the fin-grid-rendered event like so

grid.addEventListener('fin-grid-rendered',function(e){//Do something });

Cells as Links

Hypergrid supports clickable link cells, to achieve this you need to...

  • register a listener to the table for 'fin-cell-click'
jsonGrid.addEventListener('fin-click',function(e){varcell=e.detail.cell;if(cell.x!==0){return;}alert('fin-click at ('+cell.x+', '+cell.y+')');});
  • override the getCursorAt method on behavior to be a function that returns the string of the name of the cursor for the column with the links
grid.behavior.getCursorAt=function(x,y){if(x===0){return'pointer'}else{returnnull;}};
  • override the cell-provider to return the linkRenderer for the desired link columns and set config.link = true
grid.behavior.dataModel.getCell=function(config,renderName){config.link=true;vardefaultRenderer=this.grid.cellRenderers.get(rendererName);config.halign='left';varx=config.x;if(x===0){config.link=true;}elseif(x===2){
...
...
...
}

Clone this wiki locally