-
Notifications
You must be signed in to change notification settings - Fork 0
Api demos #2
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: master
Are you sure you want to change the base?
Api demos #2
Changes from all commits
898a7f3
def01ae
f63d770
551c58e
82324ef
f5a58a1
41a5292
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,123 @@ | ||
## Graph Animation Demos | ||
|
||
The demo serves to list the features that I am going to implement for graph animation using Javis. The example links at the end will take you to use cases that I think are worth considering before implementing the API. | ||
|
||
### List of features | ||
|
||
* Graph type invariance - The user should have the flexibility in terms of the type of the graph. The default graph type supported would be from LightGraphs.jl | ||
* Add/Remove edges or nodes - Upon such changes the layout should change automatically to take into account the new arrangement | ||
* Utilities to update the graph | ||
1. `addNode!` - add a node on the canvas | ||
2. `addEdge!` - add an edge on the canvas | ||
3. `changeNodeProperty!` - Update drawing style of node(s) on canvas | ||
4. `changeEdgeProperty!` - Update drawing style of edge(s) on canvas | ||
5. `updateGraph!` - Takes in the updated input graph object and updates the drawing properties of nodes and edges correspondingly | ||
* Animation tools on graph | ||
1. `animate_inneighbors` - Incoming neighbors (for a directed graph) | ||
2. `animate_outneighbors` - Outgoing neighbors (for a directed graph) | ||
3. `animate_neighbors` - All neighbors | ||
4. `highlightNode` - Highlight node(s) using flicker animation of a node property | ||
5. `highlightEdge` - Highlight edge(s) using flicker animation of an edge property | ||
6. `animatePath` - Animate a path on the graph | ||
7. `bfs` - Animate bfs at a node | ||
8. `dfs` - Animate dfs at a node | ||
|
||
### Examples | ||
|
||
1. [Graph Traversal](example1.md) | ||
2. [Depth First Search](example2.md) | ||
2. [Shortest Path](example3.md) | ||
3. [Cycle Detection]() | ||
4. [Minimum Spanning Tree]() | ||
5. [Bipartite Matching]() | ||
6. [Strongly connected components]() | ||
7. [Graph Coloring]() | ||
8. [Gradient backpropagation]() | ||
|
||
### Reference implementation till now | ||
|
||
The struct definitions of `GraphAnimation`, `GraphNode` & `GraphEdge` are provided in [Graphs.jl](../../src/structs/Graphs.jl) | ||
|
||
### Updates | ||
|
||
#### 22 June | ||
Completed: | ||
* ~~A way to create graphs using `Object(1:100, JGraph(...)`~~ | ||
* Solved temporarily using multiple dispatch on the object constructor and `@Object` macro that expands the tuple `(draw_func, metadata)` returned by `JGraph`. | ||
* ~~How to provide layout options to users.~~ | ||
* Provide 2 layout options for now and keep a `none` mode so that user can specify his own layout. | ||
* ~~How to access reference to node/edge objects from the parent graph.~~ | ||
* Keep a lightweight adjacency list in the parent with nodes and edges having an attribute to store their position in the ordering list. | ||
|
||
Working on: | ||
* How to make a graph node customizable. Having predefined drawing functions like `drawNode(shape="circle", radius=12, text="node", text_align="top_left")` with lots of options does not seem extendible. | ||
* Use a plugin mechanism to let the user write their own drawing functions for certain node properties while reuse other default options. | ||
* Add nodes to a graph object. The usual method of `Object(1:100, GraphNode(...))` has a problem of how to register a graph node object to a graph. | ||
* Using a macro syntax `@Graph g 1:100 GraphNode(...)` to register a created node object with the parent graph. | ||
|
||
Approach(s) thought of (or used): | ||
* To provide custom drawing options, provide an interface like | ||
```julia | ||
@Graph g 1:100 GraphNode(12, [draw_shape(:square, 12), draw_text(:inside, "123"), fill(:image, "./img.png"), custom_border()]) | ||
``` | ||
This requires custom functions to adhere to some rules and export some parameters to other functions. For example, if nodes are drawn as square return `text_bbx` to support the option `:inside` of `draw_text`. Similarly to have your own custom border you can use `border_bbx` from a drawing function to create a border around the node. | ||
|
||
Stuck on: | ||
* How to manage keywords arguments passed to different drawing functions. For example, an object passes all the change keyword arguments to the drawing function, but to support node drawing functions like `draw_shape(:square, 12)` or custom functions like `star(...)` which may return something similar to `args(...; length=12)` only specific keyword armguents need to be supplied. | ||
|
||
#### 29 June | ||
Completed: | ||
* A demo to do a basic graph animation. | ||
* Add custom node shapes (square and circle), node borders (square and circular borders), node filling (color), node annotation (text within a box) | ||
```julia | ||
@Graph g 1:100 GraphNode(12, [draw_shape(:square, 12), draw_text(:inside, "123"), fill(:color, "red"), border("yellow")]) | ||
``` | ||
* The predefined functions above like `draw_shape`, `draw_text` etc. each return a function having some keyword arguments to be used by this draw function. | ||
* These options need to be provided by the user or exposed by some draw function like `draw_shape`--exposes-->`:text_box`--usedby-->`draw_text`. | ||
* The issue was how to identify and compile this pool of keywords into a single draw function. | ||
|
||
Working on: | ||
* Extending the options available for node drawing configuration. | ||
* Edge drawing functions and line animation options. | ||
* Need to handle self-loops and curves in graph. | ||
* Animate a line generated from source to destination. | ||
|
||
Approach(s) thought of (or used): | ||
* Add a regular polygon option for node draw shape and add compatible support for it for borders and text box. | ||
|
||
Stuck on: | ||
* Aligning text on straight edges depending on the direction of edge. | ||
* Approximate area to draw self loop edges to prevent clutter. | ||
|
||
#### 6th July | ||
Completed: | ||
* Node drawing configurations and demo. | ||
* Divided node property into shape, text, fill & border. | ||
* Animating line (curved lines) from a source to destination node | ||
|
||
Working on: | ||
* Edge drawing properties :- shape, style, label, arrowheads. | ||
* Shape will provide a clip over the edge which maybe a curved or straight line. Shape also includes line width, end offsets and curvature. | ||
* Styles incorporate features like color blends and dash type. | ||
* Arrow deals with options to set arrows on the edge. | ||
* Label/text allows positioning text boxes/latex relative to the edge | ||
|
||
Stuck on: | ||
* For both nodes and edges, the `node_shape` and `edge_shape` function was supposed to provide a clip around the edge and any custom function provided by the user would be clipped within that region. `:clip` action does not work as expected on a line. | ||
* How to return a edge outline for edges of different shapes? For example, for a line it an be 2 points for a circle it can be 3 points etc. This is required when positioning labels/glyphs with relative positioning on the edge. | ||
|
||
#### 13th July | ||
Completed: | ||
* Add self-loops to graph | ||
|
||
Plans for this week: | ||
* complete implementation for `edge_label`, `edge_arrow`, `edge_style` | ||
* complete `dynamic` mode for graph creation | ||
* complete graph animation utlities highlight, change property, update graph, animate* | ||
* Update documentation for prevailing code | ||
* Add unit tests for node and edge drawing functions | ||
* Update examples-(1, 2, 3) with working code | ||
|
||
Stuck on (Backlog for now): | ||
* Self-loop egde orientation | ||
* Edge flickering when using `Luxor.text` |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,193 @@ | ||
## Graph traversal using BFS | ||
|
||
### Points covered | ||
1. Graph creation | ||
2. Algorithm explanation through two different visualisations | ||
|
||
### Graph Creation | ||
|
||
The graph object can be created/initialized by the use of a LightGraph typed object or any arbitrary graph types. In the latter case, there is a need to specify some additional accessibility functions to make use of some advanced visualisation features. It is discussed in later examples. This one covers how to do it with the use of a simple adacency list. It is supposed to be the most simplest way one can use the API with almost a zero learning curve. | ||
|
||
**Graph representation** | ||
```julia | ||
graph = [[2, 3, 4, 5], | ||
[6, 7], | ||
[8], | ||
[], | ||
[], | ||
[], | ||
[], | ||
[]] | ||
``` | ||
The input graph can be of any Julia data type. This does not impose any restriction on the type of graph but for advanced features it requires some extra work. An alternative to this is using `LightGraphs.jl` for which there are many convenience functions available. Its usage is covered in [Example 2](example2.md). | ||
|
||
**Graph initialization** | ||
```julia | ||
# Parameters - Graph object | is directed? | width | height | starting position | ||
ga = GraphAnimation(graph, true, 300, 300, O) | ||
``` | ||
|
||
**Node registration** | ||
```julia | ||
nodes = [Object(@Frames(prev_start()+5, stop=100), GraphNode(i, drawNode; animate_on=:scale, fill_color="yellow", border_color="black", text=string(i), text_valign=:middle, text_halign=:center)) for i in range(1, 8; step=1)] | ||
|
||
# TODO: Need to find the best way to map drawing arguments like text_align (specified before) into the drawing function. Using a dictionary, seems a good idea. | ||
function drawNode(draw_opts) | ||
sethue(draw_opts[:fill_color]) | ||
circle(draw_opts[:position], 5, :fill) | ||
sethue(draw_opts[:border_color]) | ||
circle(draw_opts[:position], 5, :stroke) | ||
text(draw_opts[:text], draw_opts[:position], valign = draw_opts[:text_valign], halign = draw_opts[:text_halign]) | ||
end | ||
``` | ||
The set of drawing options (like `border_color`) that can be supported depends solely on the user provided parameters and the drawing function used. The utility functions like `highlightNode` take as input one of these drawing parameters and a new value for it and perform highlighting operations on them. | ||
|
||
In [Example 3](example3.md), I will demonstrate how to map these drawing options to node properties which are part of the input graph object. That will help animate changes in node properties simultaneously without any additional coding. | ||
|
||
The additional option `animate_on` controls the appearance of the node on the canvas. The same option is used during removal of the nodes/edges. | ||
|
||
The options available are: | ||
* `:opacity` - both nodes and edges | ||
* `:scale` - only for nodes | ||
* `:line_width` - only for edges | ||
* `:length` - only for edges | ||
|
||
The result is eight balls drawn on the canvas at fixed locations unaltered by changes in the graph by addition/deletion of new nodes. | ||
|
||
**Edge registration** | ||
|
||
```julia | ||
edges=[] | ||
for (index, node) in enumerate(graph) | ||
for j in node | ||
push!(edges, Object(@Frames(prev_start()+5, stop=100), GraphEdge(index, j[1], drawEdge; animate_on=:length, color="black"))) | ||
end | ||
end | ||
|
||
# Need to provide custom drawEdge functions to account for self-loops, curved edges etc. | ||
function drawEdge(opts) | ||
sethue(opts[:color]) | ||
line(opts[:position1], opts[:position2], :stroke) | ||
end | ||
``` | ||
|
||
### Graph visualisation | ||
|
||
The nodes already visited need to be marked and a data structure like queue providing FIFO access is needed to store the order of traversal of nodes. | ||
```julia | ||
using DataStructures | ||
# vis[x] indicates if a node is visited and Q is a queue data structure | ||
vis=[false for i in 1:8] | ||
Q=Queue{Int}() | ||
``` | ||
|
||
The algorithm can be explained in two ways: | ||
|
||
**Using simple coloring** | ||
* Use a different fill color to convey the new nodes in the queue | ||
* Change the color of visited nodes permanently | ||
|
||
```julia | ||
enqueue!(Q, 1) | ||
# The frames argument is optional here. | ||
changeNodeProperty!(ga, 1, :fill_color, "green") | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. does this increase some kind of global frame counter? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Yes, that will be the case for now. This will create an action on the node based on the last animation created on the graph through the utility functions. |
||
while !isempty(Q) | ||
i=dequeue!(Q) | ||
vis[i]=true | ||
changeNodeProperty!(ga, i, :fill_color, "blue") | ||
for j in neighbors(i) | ||
if !vis[j] | ||
enqueue!(Q, j) | ||
changeNodeProperty!(ga, j, :fill_color, "green") | ||
end | ||
end | ||
end | ||
``` | ||
|
||
**Using node color or border color highlighting** | ||
* Use a different fill or border color to convey the currently highlighted node | ||
* Change the color of visited nodes permanently | ||
|
||
```julia | ||
# vis[x] indicates if a node is visited and Q is a queue data structure | ||
highlightNode(ga, 1, :fill_color, "white") # Flicker between original yellow and white color for some default number of frames | ||
vis[1]=true | ||
changeNodeProperty!(ga, 1, :fill_color, "orange") | ||
enqueue!(Q, 1) | ||
while !Q.empty() | ||
i=dequeue!(Q) | ||
for j in neighbors(i) | ||
if !vis[j] | ||
highlightNode(ga, j, :fill_color, "white") | ||
vis[j]=true | ||
changeNodeProperty!(ga, j, :fill_color, "orange") | ||
enqueue!(Q, j) | ||
end | ||
end | ||
end | ||
``` | ||
|
||
## Full Code | ||
|
||
```julia | ||
using Javis, DataStructures | ||
|
||
function ground(args...) | ||
background("white") | ||
sethue("black") | ||
end | ||
|
||
function drawNode(draw_opts) | ||
sethue(draw_opts[:fill_color]) | ||
circle(draw_opts[:position], 5, :fill) | ||
sethue(draw_opts[:border_color]) | ||
circle(draw_opts[:position], 5, :stroke) | ||
text(draw_opts[:text], draw_opts[:position], valign = draw_opts[:text_valign], halign = draw_opts[:text_halign]) | ||
end | ||
|
||
function drawEdge(opts) | ||
sethue(opts[:color]) | ||
line(opts[:position1], opts[:position2], :stroke) | ||
end | ||
|
||
graph = [[2, 3, 4, 5], | ||
[6, 7], | ||
[8], | ||
[], | ||
[], | ||
[], | ||
[], | ||
[]] | ||
|
||
video=Video(300, 300) | ||
Background(1:100, ground) | ||
|
||
ga = GraphAnimation(graph, true, 300, 300, O) | ||
nodes = [Object(@Frames(prev_start()+5, stop=100), GraphNode(i, drawNode; animate_on=:scale, fill_color="yellow", border_color="black", text=string(i), text_valign=:middle, text_halign=:center)) for i in range(1, 8; step=1)] | ||
|
||
edges=[] | ||
for (index, node) in enumerate(graph) | ||
for j in node | ||
push!(edges, Object(@Frames(prev_start()+5, stop=100), GraphEdge(index, j[1], drawEdge; animate_on=:length, color="black"))) | ||
end | ||
end | ||
|
||
vis=[false for i in 1:8] | ||
Q=Queue{Int}() | ||
enqueue!(Q, 1) | ||
changeNodeProperty!(ga, 1, :fill_color, "green") | ||
|
||
while !isempty(Q) | ||
i=dequeue!(Q) | ||
vis[i]=true | ||
changeNodeProperty!(ga, i, :fill_color, "blue") | ||
for j in neighbors(i) | ||
if !vis[j] | ||
enqueue!(Q, j) | ||
changeNodeProperty!(ga, j, :fill_color, "green") | ||
end | ||
end | ||
end | ||
|
||
render(video; pathname="example1.gif") | ||
``` |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It's not clear how this is linked to
ga
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
In general what does
ga
do if I need to create the graph nodes and edges myself?Uh oh!
There was an error while loading. Please reload this page.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I thought of keeping a reference internally to the currently active graph, like the way there is a
CURRENT_VIDEO
. This however is not completely free of problems and restricts the way nodes can be added to graphs. I can change it toGraphNode(ga, i, drawNode); ...)
.When adding a node, the first important thing it needs to do is generate a new layout. This however, is not done if the
mode
isstatic
in which case it is generated only once when the whole layout is known.The second thing, an ordering is kept in the graph object as to which node/edge appeared first. This helps in the utility functions like
animate_neighbors
where instead of placing actions on all neighboring nodes in the same frame range this ordering can be used to organize them sequentially.There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm just wondering what
ga
does with thegraph
object itself when one has to add the edges and nodes later anyway and what would happen if I add more nodes than in mygraph
?There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
And yeah I prefer
GraphNode(ga, i, drawNode
and optionally remove thei
when one adds them in order anyway.