Skip to content

Commit

Permalink
Initial website setup
Browse files Browse the repository at this point in the history
  • Loading branch information
david-yz-liu committed Aug 25, 2023
1 parent 79a6387 commit c0623e1
Show file tree
Hide file tree
Showing 66 changed files with 15,229 additions and 0 deletions.
4 changes: 4 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,10 @@
/tmp
/out-tsc

# Static web output
/build
.docusaurus

# Runtime data
pids
*.pid
Expand Down
10 changes: 10 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,16 @@ This will use `webpack` to watch for changes to the Javascript source files and

_Note_: this command will keep running until you manually terminate it (Ctrl + C), and so you'll need to open a new terminal window to enter new terminal commands like running the demo below.

### Previewing the website

We use [Docusaurus](https://docusaurus.io/) to generate the website from the source files under `docs/`.
To preview the website (e.g., when you make changes to the documentation):

```console
$ cd docs
$ npm run start
```

## Usage

The only function that a user will ever have to call is `user_functions.draw`.
Expand Down
3 changes: 3 additions & 0 deletions jsconfig.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
{
"exclude": ["node_modules", "docs", "website"]
}
20 changes: 20 additions & 0 deletions website/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
# Dependencies
/node_modules

# Production
/build

# Generated files
.docusaurus
.cache-loader

# Misc
.DS_Store
.env.local
.env.development.local
.env.test.local
.env.production.local

npm-debug.log*
yarn-debug.log*
yarn-error.log*
3 changes: 3 additions & 0 deletions website/babel.config.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
module.exports = {
presets: [require.resolve("@docusaurus/core/lib/babel/preset")],
};
74 changes: 74 additions & 0 deletions website/docs/02-object_structure.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
---
title: Inputs to the draw function
---

# Structure of `objects` argument in `draw` function

Arguably the most significant parameter the user has to specify for the `draw` function is `objects`, representing
the array of objects to be drawn.

To be successfully rendered, the array must contain objects that strictly follow a specific structure. Every object
must contain the following attributes:

- `isClass` - `boolean`: denotes whether the object to be drawn is a user-defined class (or a stack-frame) or a built-in
object. Pass true to draw a class or a stack-frame, and false to draw any built-in types.
- `name` - `string`: denotes the type of the object to draw (if `isClass===true`, then this is the name of the
corresponding class or stackframe).
- If the user want to hardcode the coordinates (implying the `automation` parameter of `draw` is false), each object
must include `x` and `y` attributes (for x-y coordinates).
- `id` - `string`|`number`: denotes the id value of this object. If we are to draw a StackFrame, then this MUST be `null`.
- `value` - `*`: denotes the value of the object. This could be anything, from an empty string to a JS object,
which would be passed for the purpose of drawing a user-defined class object, a
stackframe, or a dictionary.
**Note that in such cases where we want to do draw a 'container'
object (an object that contains other objects), we pass a _JS object_ where the keys are the
attributes/variables and the values are the id's of the corresponding objects (not the
objects themselves)**.
- `stack_frame` - `boolean`: denotes whether a stack frame will be drawn or not. NOTE that this is only
applicable if the object's `isClass` attribute is true (since the
`MemoryModel.drawClass` covers both classes and stack-frames). By default,
`stack_frame` is set to null (_which is false_).
- `show_indexes` - `boolean`: This is applicable only when drawing tuples or lists (when drawSequence
method will be used). It denotes whether the memory box of the underlying
sequence will include indices (for sequences) or not. This
has a default value of `false`, and it should be manually set to `true`
only if the object corresponds to a sequence (list or
tuple).
- `style` - `object` | `array`: a JS object or array specifying the "style" of the object. See `style.md` for information
on the required structure (also see `presets.md` for the full capabilities).

### Examples

```javascript
{"isClass": true, "name": "__main__", "id": null, "value": {"lst1": 82, "lst2": 84, "p": 99, "d": 10, "t": 11}, "stack_frame": true},
{"name": "BLANK", "width": 100, "height": 200, "stack_frame" : true},
{"isClass": true, "name": "func", "id": null, "value": {"age": 12, "name": 17}, "stack_frame": true},

{"name": "BLANK", "width": 100, "height": 200, "stack_frame" : true}

{"isClass": false, "name": "list", "id": 82, "value": [19, 43, 28, 49]}

{"isClass": false, "name": "list", "id": 84, "value": [32, 10, 90, 57], "show_indexes": true}

{"isClass": false, "name": "int", "id": 19, "value": 1969}

{"isClass": false, "name": "bool", "id": 32, "value": true}

{"name": "BLANK", "width": 200, "height": 100}

{"isClass": false, "name": "str", "id": 43, "value": "David is cool"}

{"isClass": false, "name": "tuple", "id": 11, "value": [82, 76]}

{"isClass": false, "name": "set", "id": 90, "value": [36, 49, 64]}

{"isClass": false, "name": "dict", "id": 10, "value": {"x": 81, "y": 100, "z": 121}}

{"isClass": false, "name": "None", "id": 13, "value": "None",
"style": {
"text_value" : {"font-style": "italic"},
"box_id": {"fill": "red", "fillStyle": "dots"},
"box_type": {"fill": "red", "fillStyle": "solid"},
"box_container": {"fill":"black", "fillStyle": "solid"}}
}
```
112 changes: 112 additions & 0 deletions website/docs/03-automation_algorithms.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,112 @@
---
title: Automatic Layout
---

# Automatic Layout

## Discussion

How does the automation of `drawAutomatedOtherItems` and `drawAutomatedStackFrames`,
responsible for drawing all non-stackframe objects actually work? The algorithm
is outlined below.

Given a list of objects in the format described in `MemoryModel.drawAll`
but without `x` and `y` coordinates as properties (as the point of
`drawAutomatedOtherItems` and `drawAutomatedStackFrames` is to assign
coordinates to each object) we separate these given objects into to collections as _stack frames_ and _other items_
by using the `seperateObjects` function. We pass these two lists to the functions aforementioned above.

We equip the objects with their hypothetical
dimensions, we sort them by height, and then begin equipping
them with actual coordinates (of the top-left corner),
dynamically moving to a new row and continuing if necessary.

In particular, all stackframe object are passed to `drawAutomatedStackFrames`, which not only equips the objects
the coordinates, but importantly also returns the total width of the stack-frame "section" in the canvas, which
is then passed to `drawAutomatedOtherItems` 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:
`drawAutomatedOtherItems` and `drawAutomatedStackFrames`.

## `drawAutomatedStackFrames`

1. First we make sure that no configuration options (padding etc.)
remain undefined. To achieve this, we loop through the configurations
attributes and check whether they have already been defined by the user. If not,
we pass in the default configuration settings.

2. Then, 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.

3. Afterward, we iterate through the given stack-frames objects. Inside the loop,
we first acquire the width and the height of the object. If the box is "BLANK", we obtain the height and width
by accessing the attributes of the object. If not, we obtain the dimensions by using the `getSize` function. Refer
to the function for details.

4. Moreover, we update the width accumulator, if the box width is greater than the size stored in the accumulator.

5. After that, we add the `x` and `y` attributes to the stack frames object (if it's not a BLANK box), and add the object
the collection accumulator.

6. At the end, we update the min required height by adding the height of the box to the accumulator and return
the object. After the loop, we update the accumulator in accordance with the `configuration.padding`.

7. At the end, we return a JS object that has the following format
`{StackFrames: ..., requiredHeight: ..., requiredWidth: ...}`

## `drawAutomatedOtherItems`

1. Determine the size --width and height-- of the (outer) box of each object if it were to
be drawn on canvas, and **equip the object with its calculated dimensions, by adding width and height properties**. This will tell us exactly how much space each
object will require in the canvas. For this purpose I created the `getSize` function
in the `automate.js` module, which accepts any object and returns the
size it would have on canvas.\*

2. Sort the list of objects by descending height of the objects
(the height and weight properties were added to each object in
STEP 1). The purpose of this step is to economize on the height
of the canvas; **all “tall” boxes ought to be together in the first
(few) “rows” of the canvas, so not all “rows” need to have a
large vertical value**. This sorting allows us to do this because
once you place an object in a new row, you know all remaining objects
will be shorter that this object.

3. Loop through the passed list of objects (which now is equipped
with dimensions, and is sorted by descending height), and let the
current object be denoted as `obj`:

- If `obj` fits horizontally (which is determined by checking
if the top-right coordinate of the last iteration's object,
plus some padding, plus the width of `obj` is less than the
width of the entire canvas), **equip `obj` with the (top-left)
coordinates** it would take if it were drawn.
- If `obj` does NOT fit horizontally, we move to a new row.
The height of the new row will be the height of the current
object that does not fit (plus some padding), `obj`, since (due to the sorting
in step 2) `obj` is tallest amongst all remaining objects
(so it just makes sense to the make this the height of
the row itself).
Again, **equip `obj` with 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).

4. Return the mutated list of objects.

\*In case the object has `name="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).

## Summary

As a result, the caller of the function (may that be an actual user
or another function) has a list of objects equipped with coordinates,
and can use `drawAll` (assuming the existence of a MemoryModel object)
to actually draw them.

Thus, **the process of determining coordinates is now fully automated**.
121 changes: 121 additions & 0 deletions website/docs/04-style.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,121 @@
---
title: Style API
---

# Style API

A user is able to add custom styling to an object
(of the list of objects to be drawn on canvas) by
adding an attribute named `style` which maps to an object of the
following format:

```javascript
style : {
"text_id" : {},
"text_type" : {},
"text_value" : {},
"box_id" : {},
"box_type" : {},
"box_container" : {},
}
```

The object has up to six attributes (the ones observed above), each
corresponding to a particular component of an object with the potential
to be styled.

Also, the user can pass in an array for the style attribute. This array can be
a mixture of presents (name of the preset in the string data format) and a user-defined
Object. To better illustrate this, here is an example:

```javascript
["highlight", "hide_type", { text_id: { "font-style": "italic" } }];
```

Crucially, each of the attributes in the `style` object (if user passes in an Object)
refer themselves to another object:

- Text-related attributes can contain any attribute specified in the
documentation of the text element for svg graphics, found on
https://developer.mozilla.org/en-US/docs/Web/SVG/Element/text.
- Box-related attributes can contain any styling attributes specified in the
documentation of the rough library, found on https://roughjs.com/ and
https://github.com/rough-stuff/rough/wiki.

## Insights for the Implementation

The user specifies a desired style for each of the objects in the list.

### Merging

First, there is a default object that contains all key-value pairs of style attributes
that (be default) are common across objects of all types. This object is called `common_style`.

Then we use a list objects to represent some styling properties specific to certain type categories
(for instance, `text_value` for collections need to have the id color by default, which
is not true for primitive types). The `deepmerge` library is used to merge the common styles
mentioned above with these category-specific styles.

Finally, any user-supplied styling attributes are merged to the current style object.
This is the second and final merge.

### Cascade

The above "merging" procedure takes place inside the `drawAll` method (using the
`populateDefaultStyle` helper function).

Then, inside the `drawClass` and `drawObject` methods (which are called from within
`drawALl`), for every invocation of `drawText` and `drawRect` (responsible for drawing
text and boxes, respectively), the corresponding attribute of the object's style
object is passed (for instance, in the case where `drawText` is called to draw the id value
inside the id box, `style.text_id` is passed as an argument to `drawText`).

Finally, inside drawText and drawRect, we loop through all the attributes of the
passed style object, and apply each of those in the appropriate manner (
for drawRect, you simply pass the style when initializing a rectangle by doing
`this.rough_svg.rectangle(x, y, width, height, style)`, and with the text element you
use the DOM method `setAttribute` by doing `newElement.setAttribute(style_attribute, style[style_attribute])`
).

Thus, the style is cascaded down from user to drawText/drawRect as follows:

user-defined style --> drawAll --> drawClass/drawObject --> drawText/drawRect

## Examples

### Example 1

`style:
{"text_value" : {"font-style" : "italic"},
'box_id': {fill: 'blue', fillStyle: "dots"}}
`
In this example, it is important to note that for box styles
(the ones which uses rough.js module), `fill` must be passed in if
fillStyle argument will be passed in by the user. Otherwise, the behavior of the style will be in the default format
(refer to the rough.js module for the default style). Also, it points outs that the user has the
flexibility for defining the styles related to both boxes and text drawn on the canvas.

### Example 2

`style: {
"box_container": {fill:"green", fillStyle: "zigzag"},
"box_id": {fill: 'red', fillWeight: 3},
"box_type": {bowing: 8, stroke: "green", strokeWidth: 3}}`

This example illustrates that our implementation for box styles are defined on
three boxes: the container, the box that represents the id and the box that represents
the data type. Therefore, the user can utilize the rough.js module for these three boxes that
are drawn on the canvas (two for stack-frames, as they do not have ids associated with them)

### Example 3

`style:{
"text_id" : {fill : "yellow", stroke: "green"},
"text_value": {"font-style" : "italic"},
"text_type": {"font-size: "xx-large"}}`

This example shows that for each container (for each data that is represented on the canvas)
there are the texts that the user can alter its style: the text representing the value and/or values,
the text representing the id number of the data type (if it's not a stack-frame) and the text representing
the type of the data. The user can pass in configurations based on the SVG documentation for modifying these texts
simultaneously.
Loading

0 comments on commit c0623e1

Please sign in to comment.