Automatic Layout
Discussion
When an entity does not have x or y coordinates provided, an algorithm is used to automatically compute coordinates instead. The algorithm is outlined below.
Users may specify the desired width of the canvas. If no width is provided, a width will be computed based on width of the widest object and the widest stack frame.
Given a list of objects in the format described in MemoryModel.drawAll, we separate
these given objects into to collections as stack frames and other items by using the
setDimensionsAll function.
We equip the objects with their hypothetical dimensions and then optionally sort them before
laying them out. By default no sorting is applied; to enable sorting set sort_by to
SortOptions.Height or to SortOptions.Id. If sort_by is unset, the original input order
is preserved. After sorting (or leaving the input order intact), we begin equipping objects
with actual coordinates (the top-left corner), dynamically moving to a new row and continuing if necessary.
In particular, within the setDimensionsAll function, all stackframe objects are passed to
setStackFrameCoordinates, which not only equips the objects with the coordinates, but importantly
also returns the total width of the stack-frame "section" in the canvas, which is then passed
to setOtherItemsCoordinates as a way to tell it where the vertical division between the two
sections takes place (in this way, the divider between the stack-frame section and the objects section
is determined dynamically).
Below we thoroughly describe the steps for each of the two functions:
setStackFrameCoordinates and setOtherItemsCoordinates.
setStackFrameCoordinates
-
First, we initialize the accumulators which helps us to track minimum required canvas height, required width for drawing all stack frames, and a collection of stack frames that will be drawn.
-
Afterward, we iterate through the given stack-frames objects. Inside the loop, we first acquire the width and the height of the object.
-
Then, we update the width accumulator (
stack_endpoint) if the box width is greater than the size stored in the accumulator. -
After that, we add the
xandyattributes to the stack frames object (if it's not of type ".blank-frame"), and add the object to the collection accumulator. We obtain the x and y coordinates by setting x to the left margin and y to the current value of the minimum height accumulator. -
At the end of the loop, we update the min required height by adding the height of the box to the accumulator. Then, we repeat for any remaining given stack-frame objects.
-
Outside the loop, if the given list of stack frames is not empty, add the left margin to the width accumulator. If the height of the canvas is undefined or less than the min required height plus padding, set the height of the canvas to the min required height plus padding.
-
At the end, we return a JS object that has the following format
{StackFrames: ..., stackEndpoint: ...}
setOtherItemsCoordinates
-
First, we determine the width of the canvas by computing a width based on the widest object and endpoint of the stack frames. If the user provided a width, we use the configured width unless it is smaller than the computed width. If no width is provided, use the computed canvas width.
-
Then, given a list of objects with width and height already assigned*, optionally sort the objects. By default no sorting is applied; set
sort_bytoSortOptions.Heightto sort by descending height, or toSortOptions.Idto sort by ascending id. -
Loop through the passed list of objects, and let the current object be denoted as
item:
- If
itemfits horizontally (which is determined by checking if the top-right coordinate of the last iteration's object, plus some padding, plus the width ofitemis less than the width of the entire canvas), equipitemwith the (top-left) coordinates it would take if it were drawn. - If
itemdoes NOT fit horizontally, we move to a new row. Again, equipitemwith the (top-left) coordinates it would take if it were drawn (in this case, the x-coordinate would go back to the initial state since we moved to a new row, and the y-coordinate would increase by the height of the previous row, which is determined from the height of the tallest object in that row).
- Return the mutated list of objects.
*In case the object has type=".blank", it is assumed that the user has
also provided a width and a height attribute corresponding to the desired
width and height of the blank space. If such dimensions have not been provided,
a related warning is printed and the object is skipped (no blank space is
recorded). Note that if a blank stack frame is being drawn, the input should
have type=".blank-frame" and the name attribute should be excluded from the input.
Summary
As a result, the caller of the setDimensionsAll function has a list of objects equipped with
coordinates, and drawAll proceeds to actually draw them.
Thus, the process of determining coordinates is now fully automated.