Rendering
Rendering makes sure that your data is transformed to pixels that eventually display on your screen.
The result of rendering can be a Document Object Model (DOM) structure, a scalable vector graphic (svg), or pixels on a canvas, depending on which output technology you choose. All code samples use svg rendering in d3. However, in your custom visualization you can choose any other way of rendering.
Within customvis-lib, you must implement a create phase and an
update phase:
-
The
createphase is run exactly once, when the visualization gets initialized. -
The
updatephase is run each time an aspect of the visualization changes, like the size, data or properties of the visualization.
Implementing these phases is optional. The RenderBase base class provides a
default implementation for you.
Create
The create phase is run when the visualization gets initialized. You can implement this phase by
overriding the create() method of
RenderBase. In this method, you receive the DOM node of the container element for
your visualization. You don’t must override create if your visualization does not
have initialization code. However, if you do, you can add more DOM or svg nodes to your container
and optionally return an Element as the new root node of your visualization. An
example:
protected create( _node: HTMLElement ): void
{
// Append an svg node that scales to the size of its container.
d3.select( _node ).append( "svg" )
.attr( "width", "100%" )
.attr( "height", "100%" );
// No return value means _node will be the root of our visualization.
}
protected update( _info: UpdateInfo ): void
{
// _info.node will be the same '_node' that was passed to 'create'.
}
If your initialization requires asynchronous calls, you can return a Promise
object that resolves with either an Element or void.
protected create( _node: HTMLElement ): void
{
return new Promise( resolve =>
{
// doInit performs async initialization.
doInit( function cb()
{
// Init is done, resolve the Promise. Optionally you can
// resolve with an `Element` that will become the root
// node of your visualization.
resolve();
} );
} );
}
Update
The update phase gets called each time something changes in the size, properties, or data of the
visualization. You can implement the update phase by overriding the update() method of
RenderBase.
Render completion
The implementation of update in its most basic form is the following code:
protected update( _info: UpdateInfo ): void
{
_info.node.textContent = "Hello World: " + Date.now().toString();
}
There is no return value, which means that the rendering completed at the end of the
update function.
Each visualization has the responsibility to notify its host when it completed rendering in the
update phase. Notification is done through the return type: returning void implies
that rendering is done when the update function ends and returning a
Promise means that the host should wait for that promise to be fulfilled.
If you want your visualization to show animations or transitions, this is often an asynchronous
process. Therefore, the update function can return a Promise
object that resolves as soon as the transition of the visualization completed. An example
illustrates this:
protected update( info: UpdateInfo ): Promise<void>
{
const t = d3.transition().duration( 300 );
d3.select( _info.node )
.selectAll( "rect" )
.data( _info.data.rows )
.join( "rect" )
.attr( "x", d => scale( d.tuple( 0 ).key ) )
.attr( "width", d => scale.bandwidth() )
.transition( t ) // bind to our transition
.attr( "height", d => d.values( 0 ) );
return new Promise( resolve => t.on( "end", () => resolve() ) );
}
In the RenderBase base class, you can see that the update
method has the return signature void | Promise<void>. However, in your override
you can be more explicit and define either void or
Promise<void> as the return type.
If for some reason, you want the host application to know that rendering failed, you can either
return a rejected promise or produces a new Error( "msg" ).
UpdateInfo
The one parameter that is passed to update is an object of type
UpdateInfo. You can see this object as the state that you are about to render. The
UpdateInfo class holds the following information:
reason: The reason why rendering should take place. Check the flags in this object to see which aspects of the visualization need rendering. Available flags are:data: data is changed since last call toupdate.size: size is changed since last call toupdate.properties: properties are changed since last call toupdate.decorations: decorations (selected, highlighted) are changed since last call toupdate.
-
data: The data that should be rendered, or null if there is no data. If there is no data to render, it is the responsibility of the visualization to ensure that the visualization is cleared and rendered in an empty state. -
node: The HTML element on which rendering should take place. This is the element that was returned previously from theRenderBase.createmethod. -
properties: The current set of properties. Properties can also be accessed through theRenderBase.propertiesattribute. locale: The data locale that should be used for rendering. Do not use for translations, only for custom data formatting where that is necessary.
The following example shows how you can use the fields of UpdateInfo to provide
an optimized implementation of update:
protected update( _info: UpdateInfo ): void
{
const reason = _info.reason;
if ( !_info.data )
return renderEmpty( _info.node );
// Call expensive data processing function only if data has changed.
if ( reason.data )
this.data = processData( _info.data );
// Perform rendering, based on `this.data` ...
}