timjs/elm-collage/1.3.0

NOTE: this package is not compatible with Elm 0.19.1

Collage.Layout

With this module, you can compose collages in a more automatic way. Instead of shifting collages manually, this module figures out the dimensions of each part of your drawing and places them seamlessly next to each other, above each other or on top of each other. This is all possible by keeping track of the envelope or bounding box of each collage.

Collages are positioned relative to their internal origin. By default, a collage's internal origin lays in its exact center. By shifting a collage, we change its internal origin and the way it composes with other collages. Another method to change the origin is by using alignments.

All these ideas are based on the Diagrams library for Haskell and the Scalable Graphics library for Clean. You can regard this module as a simplified version of above libraries.

Contents

Envelopes

An envelope defines the bounding box of a collage. It is similar to a bounding box. Envelopes can become quite complex when we calculate them in all possible directions. Here, we restrict ourselves to four: up, down, right and left.

Envelopes answer the question: “If I want to put my collage in a rectangular envelope, what are the distances from its local origin to the envelope edges?”

envelope : Direction -> Collage msg -> Float

Calculate the envelope of a collage relative to its internal origin.

The figure below illustrates the four distances that can be calculated. We represent the origin with (X). When calling, for example, envelope Up, we calculate the distance from (X) to the upper edge of the rectangle.

    +–––––––––––––––+
    |       ˄       |
    |    Up |       |
    |       | Right |
    | ˂––––(X)––––˃ |
    |  Left |       |
    |       | Down  |
    |       ˅       |
    +–––––––––––––––+

The same holds for the other three directions.

= Up
| Down
| Right
| Left

The four different directions in which we can calculate an envelope.

facing : Direction -> Direction

Calculate the facing direction.

Up   <-> Down
Left <-> Right
opposite : Direction -> Direction

Same as facing.

Will be REMOVED in the next major version in favor of facing. This change will avoid name clashes with Collage.opposite.

distances : Collage msg -> Distances

Calculate the envelope in all four directions at once.

The result is a Distances record with up, down, right, and left fields. Use this function if you need envelopes in multiple directions at the same time.

{up, down} = distances collage
...use up and down...
type alias Distances =
{ up : Float
, down : Float
, right : Float
, left : Float
}

Type alias collecting envelope distances in all four directions.

width : Collage msg -> Float

Calculates the width of a collage.

The width is equivalent to the envelopes in the left and right directions:

width collage  ==  envelope Left collage + envelope Right collage
height : Collage msg -> Float

Calculates the height of a collage.

The height is equivalent to the envelopes in the up and down directions:

height collage  ==  envelope Up collage + envelope Down collage

Layouting

Collages can be composed in three ways:

  • horizontally, or next to each other
  • vertically, or above each other
  • on the "out of page" axis, or stacked on top of each other

A more advanced way of combining collages is by imposition: placing one collage on top of a background and forgetting about the dimensions of the foreground. All ways of combining are expressed used one basic placing function, which you can use in some extreme cases where you need to place a collage but not combine it with others.

horizontal : List (Collage msg) -> Collage msg

Place a list of collages next to each other, such that their origins are along a horizontal line. The first element in the list will be on the left, the last on the right.

horizontal [a, b, c]


    +–––+–––+–––+
    |(a)| b | c |
    +–––+–––+–––+

The new origin will be the origin of the first element in the list.

  • Note: this is called hcat in Diagrams.
vertical : List (Collage msg) -> Collage msg

Place a list of collages next to each other, such that their origins are along a vertical line. The first element in the list will be on the top, the last on the bottom.

vertical [a, b, c]


    +–––+
    |(a)|
    +–––+
    | b |
    +–––+
    | c |
    +–––+

The new origin will be the origin of the first element in the list.

  • Note: this is called vcat in Diagrams.
stack : List (Collage msg) -> Collage msg

Place a list of collages on top of each other, with their origin points stacked on the "out of page" axis.

The first collage in the list is on top. This actually is the same as the group operation in the Collage module.

stack [a, b, c]


    +–––+
    |(a)|
    +–––+

(Yes, b and c are somewhere below a...)

The new origin will be the origin of the first element in the list.

  • Note: this is called concat in Diagrams.

  • Note: when we create an operator (<>) like

    (<>) a b =
        stack [ a, b ]

    then (<>) forms a monoid together with empty. (<>) is called atop in Diagrams.

impose : Collage msg -> Collage msg -> Collage msg

Impose a collage on a background.

The call

impose fore back

stacks fore on back. The envelope of fore will be "forgotten" and back will be used to calculate the envelope of the resulting collage.

    +–––––––––––+
    |(b)+–––+   |
    |   | a |   | <-- new envelope
    |   +–––+   |
    +–––––––––––+

Obviously, this also works with the background having a smaller envelope than the foreground.

The new origin will be the origin of the background.

beside : Direction -> Collage msg -> Collage msg -> Collage msg

Place a collage beside another one in the given direction and combine them into a new one.

Most of the time it is way nicer to use horizontal or vertical to express your layout, but beside dir can come into hand as a curried function.

The new origin will be the origin of the first argument.

  • Note: beside dir forms a monoid with empty for every dir.
  • Note: same as beside in Diagrams.
place : Direction -> Collage msg -> Collage msg -> Collage msg

Shift a collage in a direction to precisely that position where it would touch the first one, without composing them.

Use this to position a collage next to another collage without actually composing them.

  • Note: called juxtapose in Diagrams.

Spacers

What is placing collages without being able to space them accordingly? These two collages are invisible and only take up some space. (Or they even don't...)

spacer : Float -> Float -> Collage msg

Create an empty collage of given width and height.

This is useful for getting your spacing right and for making borders.

hspace =
    spacer 10 0


horizontal <| List.intersperse hspace [a, b, c]


    +–––+  +–––+  +–––+
    | a |  | b |  | c |
    +–––+  +–––+  +–––+
empty : Collage msg

A collage that takes up no space. Good for things that appear conditionally:

horizontal [ a, if showMore then b else empty ]


    +–––+– – – – – –+
    |(a)|  maybe b? |
    +–––+– – – – – –+
  • Note: this is the identity element of the monoid on collages.

Aligning

align : Anchor msg -> Collage msg -> Collage msg

Shift a collage such that the origin is on the given anchor.

Use this, for example, when you like to align some collages to the top:

[a, b, c]
    |> List.map (align top)
    |> horizontal


    +–(X)–+––––X––––+–X–+
    |  a  |    b    | c |
    |     +–––––––––+   |
    +–––––+         |   |
                    +–––+

Anchors are created by the functions from the section below.

at : Anchor msg -> Collage msg -> Collage msg -> Collage msg

Stack a collage on top of a specified anchor of a host.

Makes placing objects on a collage a lot easier:

drawing
    |> at bottom dot
    |> at upperRight dot


    +–––––––––0
    | drawing |
    +––––0––––+

instead of:

stack
    [ dot
    , align upperRight <| stack
        [ dot
        , align bottom drawing
        ]
    ]

This does not change the origin of collage.

center : Collage msg -> Collage msg

Shift a collage such that the envelope in all directions is equal.

This is the same as aligning on the base anchor:

center collage  ==  align base collage

Anchors

type alias Anchor msg =
Collage msg -> Point

Anchors are technically just functions. You can use any function as an anchor as long as it takes a collage and returns a displacement relative to its internal origin.

top : Anchor msg
    +(X)+
    |   |
    +–––+
topRight : Anchor msg
    +––(X)
    |   |
    +–––+
bottomRight : Anchor msg
    +–––+
    |   |
    +––(X)
bottom : Anchor msg
    +–––+
    |   |
    +(X)+
bottomLeft : Anchor msg
    +–––+
    |   |
   (X)––+
left : Anchor msg
    +–––+
   (X)  |
    +–––+
topLeft : Anchor msg
   (X)––+
    |   |
    +–––+
base : Anchor msg
    +–––+
    |(X)|
    +–––+

Debugging

These two functions can aid in discovering how collages are composed.

showOrigin : Collage msg -> Collage msg

Draw a red dot at the local origin of the collage.

showEnvelope : Collage msg -> Collage msg

Draw a red dotted box around the collage representing the envelope.

debug : Collage msg -> Collage msg

Show both the envelope and the origin of a collage.

Possible extensions

We can imagine some possible extensions which could aid in designing composable drawings:

  • way to add padding
  • add baseX, and baseY (horizontalBase and vertialBase)?
  • and add centerX, and centerY too?
  • ...