Skip to main content

Build custom renderers

6. Understand connection shapes

A common use case of a custom renderer is changing the shape of connections. This requires a more detailed understanding of how a block is drawn and how SVG paths are defined.

The block outline

The outline of the block is a single SVG path. The outline is built out of many sub-paths (e.g. the path for a previous connection; the path for the top of the block; and the path for an input connection).

Each sub-path is a string of path commands that describe the appropriate shape. These commands must use relative (rather than absolute) coordinates.

SVG path commands can be written as strings, but Blockly provides a set of utility functions to make writing and reading paths easier.

init()

A connection's shape is stored as an object with information about its width, height, and sub-path. These objects are created in the ConstantProviders init() function. Here is the start of the default implementation. The complete definition can be found inside constants.ts.

/**
* Initialize shape objects based on the constants set in the constructor.
*/
init() {
/**
* An object containing sizing and path information about collapsed block
* indicators.
*/
this.JAGGED_TEETH = this.makeJaggedTeeth();

/** An object containing sizing and path information about notches. */
this.NOTCH = this.makeNotch();

// Additional code has been removed for brevity.
}

Properties that are primitives should be set in the constructor(), while objects should be set in init(). This separation allows a subclass to override a constant such as NOTCH_WIDTH and see the change reflected in objects that depend on the constant.

shapeFor(connection)

The shapeFor(connection) function maps from connection to connection shape. Here is the default implementation, which can be found inside constants.ts. It returns a puzzle tab for input/output connections and a notch for previous/next connections:

/**
* Get an object with connection shape and sizing information based on the
* type of the connection.
*
* @param connection The connection to find a shape object for
* @returns The shape object for the connection.
*/
shapeFor(connection: RenderedConnection): Shape {
switch (connection.type) {
case ConnectionType.INPUT_VALUE:
case ConnectionType.OUTPUT_VALUE:
return this.PUZZLE_TAB;
case ConnectionType.PREVIOUS_STATEMENT:
case ConnectionType.NEXT_STATEMENT:
return this.NOTCH;
default:
throw Error('Unknown connection type');
}
}