The Scrawl-canvas positioning system

The key purpose of SC is to position graphical entitys onto a <canvas> element, in a given order, so that those entitys build some form of graphical representation, chart, imagery, infographic, artwork (etc) that can be displayed as part of a web page.

Most of the code associated with the SC positioning system can be found in the mixin/position.js mixin file.

Background

As background knowledge, we need to understand that a DOM <canvas> element, and SC's representation of that element (as Canvas and Cell wrapper artefacts) are very different things.

tl;dr: We only need to care about Cell artefact dimensions. All SC graphical operations happen in Cell artefacts and end up in the Canvas artefact's base Cell. Every Cell we create will have its own dimensions – a cell coordinate space – which can diverge from the associated DOM <canvas> element's dimensions. Canvas artefacts handle the transfer of graphical data from their base Cell to their DOM <canvas> element automatically.

From this point on:

Furthermore, positioning refers to the location of an entity on the Cell, alongside that entity's dimensions, scale (relative to its default dimensions) and rotation (relative to the Cell's coordinate space axes).

Also, while we concentrate on the positioning of graphical entitys in this document, SC extends the positioning system to include DOM elements within an SC "stack" element.

The rotation-reflection point

When we position an entity, we are in fact positioning that entity's rotation-reflection point, around which the entity will stamp itself onto the Cell.

By default, the rotation-reflection point is located at the top-left corner of the entity, and represents the [0, 0] coordinate of that entity's local coordinate system.

We position this rotation-reflection point in the Cell's host coordinate system, which starts at the Cell's top left corner and extends the width (x axis) and height (y axis) of its drawing space.

SC allows us to position an entity's rotation-reflection point in several different ways:

Absolute and relative positioning

The most direct way to position an entity is to give it start, offset and handle values, from which it will calculate its position on the Cell. Each of these attributes are Coordinates with a default value of [0,0]. Each attribute also comes with a set of pseudo-attributes which allow the dev-user to set and get the x and y components of the Coordinate separately.

The start artefact attribute

We set an artefact's rotation-reflection point using its start attribute. For convenience, we can also set each part of the point's coordinate using the startX and startY pseudo-attributes.

We can mix-and-match absolute and relative values in a coordinate: [200, '40%'] is a legitimate coordinate, as is ['40%', 200].

Setting an artefact's start attribute will also set the artefact's dirtyStart boolean flag to true. At the start of the next Display cycle the SC system performs a check across all entitys and, for those marked dirty, will perform calculations to clean their start attribute, placing the result (measured in cell coordinate space pixels) into the private currentStart attribute.

When we get an artefact's start coordinate, the currentStart attribute will be returned. Note that any attempt to set the currentStart will have unexpected effects on the artefact.

Directly setting an artefact's start or (worse!) currentStart value will lead to unexpected behaviours and bugs. Always use the artefact.set({ start: [x, y]}) or artefact.deltaSet({ start: [x, y]}) functionality!

The offset and position artefact attributes

An artefact's start (and currentStart) value does not fully represent its rotation-reflection point. We are able to offset the rotation-reflection point from the start coordinate using the artefact's offset attribute. See Demo Canvas-002 for an example of this functionality.

These offset values, like start values, can be absolute (measured in pixels) or relative (as a percentage of the host Cell's current dimensions). When we set the artefact's offset (offsetX, offsetY) value, we also set its dirtyOffset boolean flag to true. Similar to the start attribute, entitys will clean their dirty offsets and store the result in the private currentOffset attribute.

When we get an artefact's offset coordinate, the currentOffset value will be returned. Note that any attempt to set the currentOffset will have unexpected effects on the artefact.

We can get an artefact's current rotation-reflection coordinate at any time using the position pseudo-attribute; the positionX and positionY pseudo-attributes are also supported. Note that any attempt to set these pseudo-attributes will have no effect on the artefact.

{0,0} host coordinate system
    .---------|---------|---------|---------|---------.
    |                                                 |
    |    start        offset        handle            | @: start coordinate
    |    [10,5]       [5,2]         [0,0]             |
    |                                                 | o: rotation-reflection point
    |         @                                       | = currentStart + currentOffset
    |                                                 | = [10,5]       + [5,2]
    |              o---------+                        | = [15,7]
    |              |         | artefact width: 10     |
    |              |         | artefact height: 5     |
    -              |         | roll: 0                -
    |              |         | scale: 1               |
    |              +---------+                        |
    |                                                 | *: path-start-coordinate
    |    currentStart currentOffset currentHandle     | = rot-ref-point - (currentHandle * scale)
    |    [10,5]       [5,2]         [0,0]             | = [15,7]        - [0*1,0*1]
    |                                                 | = [15,7]        - [0,0]
    |                                                 | = [15,7]
    |                                                 |
    |                                                 |
    .---------|---------|---------|---------|---------.
                                                      {50,20}


{0,0} host coordinate system
    .---------|---------|---------|---------|---------.
    |                                                 |
    |    start         offset        handle           | @: start coordinate
    |    ['20%','50%'] ['10%','10%'] [0,0]            |
    |                                                 | o: rotation-reflection point
    |         @                                       | = currentStart + currentOffset
    |                                                 | = [10,5]       + [5,2]
    |              o---------+                        | = [15,7]
    |              |         | artefact width: 10     |
    |              |         | artefact height: 5     |
    -              |         | roll: 0                -
    |              |         | scale: 1               |
    |              +---------+                        |
    |                                                 | *: path-start-coordinate
    |    currentStart  currentOffset currentHandle    | = rot-ref-point - (currentHandle * scale)
    |    [10,5]        [5,2]         [0,0]            | = [15,7]        - [0*1,0*1]
    |                                                 | = [15,7]        - [0,0]
    |                                                 | = [15,7]
    |                                                 |
    |                                                 |
    .---------|---------|---------|---------|---------.
                                                      {50,20}

The handle artefact attribute.

In brief, SC paints an artefact onto the canvas using the following protocol:

  1. If necessary, clean the artefact's dirty attributes and recalculate its rotation-reflection point.
  2. If necessary, recalculate the artefact's path2D object, which will be used during the painting step to fill and/or stroke the artefact onto the Cell.
  3. Position and rotate the host Cell's context engine using the Canvas API setTransform() function – it is at this moment that we move the Cell's engine's coordinate system origin point – [0,0] – to match the artefact's rotation-reflection point.
  4. Update the Cell's engine state to match the artefact's engine state.
  5. Stamp the artefact onto the Cell's DOM <canvas> element.

The start and offset attributes discussed above both feed into the first step of the protocol.

Every artefact has a path2D object, which SC uses for stroke/fill painting operations as well as the artefact's hit functionality (for example: hover, and drag-and-drop, operations).

When building the artefact's path2D object, SC takes into account the artefact's dimensions and scale. It also includes a (scaled) local displacement value which has the apparent effect of moving the rotation-reflection point away from the artefact's top-left corner. Dev-users can set this displacement value in the artefact's handle attribute.

Similar to the start and offset values, handle values can be absolute (measured in pixels) or relative (as a percentage of the artefact's current scaled dimensions). When we set the artefact's handle (handleX, handleY) value, we also set its dirtyHandle boolean flag to true. After cleaning, the handle's calculated values get stored in the private currentHandle attribute.

When we get an artefact's handle coordinate, the currentHandle value will be returned. Note that any attempt to set the currentHandle will have unexpected effects on the artefact.

{0,0} host coordinate system
    .---------|---------|---------|---------|---------.
    |                                                 |
    |    start        offset        handle            | @: start coordinate
    |    [10,5]       [5,2]         [-4,1]            |
    |                                                 | o: rotation-reflection point
    |         @                                       | = currentStart + currentOffset
    |                  *---------+                    | = [10,5]       + [5,2]
    |              o   |         | artefact width: 10 | = [15,7]
    |                  |         | artefact height: 5 |
    -                  |         | roll: 0            |
    |                  |         | scale: 1           -
    |                  +---------+                    |
    |                                                 |
    |                                                 | *: path-start-coordinate
    |    currentStart currentOffset currentHandle     | = rot-ref-point - (currentHandle * scale)
    |    [10,5]       [5,2]         [-4,1]            | = [15,7]        - [-4*1,1*1]
    |                                                 | = [15,7]        - [-4,1]
    |                                                 | = [19,6]
    |                                                 |
    |                                                 |
    .---------|---------|---------|---------|---------.
                                                      {50,20}


{0,0} host coordinate system
    .---------|---------|---------|---------|---------.
    |                                                 |
    |    start        offset         handle           | @: start coordinate
    |    ['20%','50%'] ['10%','10%'] ['-40%','20%']   |
    |                                                 | o: rotation-reflection point
    |         @                                       | = currentStart + currentOffset
    |                  *---------+                    | = [10,5]       + [5,2]
    |              o   |         | artefact width: 10 | = [15,7]
    |                  |         | artefact height: 5 |
    -                  |         | roll: 0            |
    |                  |         | scale: 1           -
    |                  +---------+                    |
    |                                                 |
    |                                                 | *: path-start-coordinate
    |    currentStart currentOffset currentHandle     | = rot-ref-point - (currentHandle * scale)
    |    [10,5]       [5,2]         [-4,1]            | = [15,7]        - [-4*1,1*1]
    |                                                 | = [15,7]        - [-4,1]
    |                                                 | = [19,6]
    |                                                 |
    |                                                 |
    .---------|---------|---------|---------|---------.
                                                      {50,20}

Positioning by reference

A foundational tenet of the SC positioning system is that any artefact can position itself on the Cell by referencing any other artefact.

In essence, this means that instead of using its own currentStart values when calculating the value of its rotation-reflection point, our artefact will instead use the referenced artefact's currentStart values. SC manages this through a system of locks, alongside a (bespoke, and rudimentary) signals system.

The lockTo artefact attribute

The lockTo attribute is an Array containing two String values. Each value indicates how the artefact wants to calculate its position along the Cell's x and y axes – ['x-axis-string', 'y-axis-string']. The default value is ['start', 'start'], indicating that the artefact wishes both parts of its start coordinate to use absolute or relative positioning as described above.

Like the other coordinate-like attributes, lockTo comes with a set of pseudo-attributes – lockXTo, lockYTo – which dev-users can use to set the individual elements of the attribute.

The following String values can be used in the lockTo attribute's Array:

Dev-users are able to set an artefact to reference multiple artefacts, one each for the pivot, mimic, path and particle attributes. These attributes can be updated at any time. It is the lockTo attribute which determines which reference will be used to position the artefact.

Pivot specifics

Mimic specifics

Mimic functionality allows an artefact to mimic a range of the referenced artefacts attributes, as follows:

Path specifics