Skip to content

Commit 995f009

Browse files
committed
Add api demos
1 parent 8f53bc5 commit 995f009

File tree

5 files changed

+551
-0
lines changed

5 files changed

+551
-0
lines changed

examples/graph_animations/example0.md

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
## Graph Animation Demos
2+
3+
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.
4+
5+
### List of features
6+
7+
* 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
8+
* Add/Remove edges or nodes - Upon such changes the layout should change automatically to take into account the new arrangement
9+
* Utilities to update the graph
10+
1. `addNode!` - add a node on the canvas
11+
2. `addEdge!` - add an edge on the canvas
12+
3. `changeNodeProperty!` - Update drawing style of node(s) on canvas
13+
4. `changeEdgeProperty!` - Update drawing style of edge(s) on canvas
14+
5. `updateGraph!` - Takes in the updated input graph object and updates the drawing properties of nodes and edges correspondingly
15+
* Animation tools on graph
16+
1. `animate_inneighbors` - Incoming neighbors (for a directed graph)
17+
2. `animate_outneighbors` - Outgoing neighbors (for a directed graph)
18+
3. `animate_neighbors` - All neighbors
19+
4. `highlightNode` - Highlight node(s) using flicker animation of a node property
20+
5. `highlightEdge` - Highlight edge(s) using flicker animation of an edge property
21+
6. `animatePath` - Animate a path on the graph
22+
7. `bfs` - Animate bfs at a node
23+
8. `dfs` - Animate dfs at a node
24+
25+
### Examples
26+
27+
1. [Graph Traversal](example1.md)
28+
2. [Depth First Search](example2.md)
29+
2. [Shortest Path](example3.md)
30+
3. [Cycle Detection]()
31+
4. [Minimum Spanning Tree]()
32+
5. [Bipartite Matching]()
33+
6. [Strongly connected components]()
34+
7. [Graph Coloring]()
35+
36+
### Reference implementation till now
37+
38+
The struct definitions of `GraphAnimation`, `GraphNode` & `GraphEdge` are provided in [Graphs.jl](../../src/structs/Graphs.jl)

examples/graph_animations/example1.md

Lines changed: 128 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,128 @@
1+
## Graph traversal using BFS
2+
3+
### Points covered
4+
1. Graph creation
5+
2. Algorithm explanation through two different visualisations
6+
7+
### Graph Creation
8+
9+
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.
10+
11+
**Graph representation**
12+
```julia
13+
graph = [[2, 3, 4, 5],
14+
[6, 7],
15+
[8],
16+
[],
17+
[],
18+
[],
19+
[],
20+
[]]
21+
```
22+
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).
23+
24+
**Graph initialization**
25+
```julia
26+
# Parameters - Graph object | is directed? | width | height | starting position
27+
ga = GraphAnimation(graph, true, 300, 300, O)
28+
```
29+
30+
**Node registration**
31+
```julia
32+
nodes = [Object(@Frames(prev_start()+5, 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)]
33+
34+
# 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.
35+
function drawNode(draw_opts)
36+
sethue(draw_opts[:fill_color])
37+
circle(draw_opts[:position], 5, :fill)
38+
sethue(draw_opts[:border_color])
39+
circle(draw_opts[:position], 5, :stroke)
40+
text(draw_opts[:text], draw_opts[:position], valign = draw_opts[:text_valign], halign = draw_opts[:text_halign])
41+
end
42+
```
43+
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.
44+
45+
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.
46+
47+
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.
48+
49+
The options available are:
50+
* `:opacity` - both nodes and edges
51+
* `:scale` - only for nodes
52+
* `:line_width` - only for edges
53+
* `:length` - only for edges
54+
55+
The result is eight balls drawn on the canvas at fixed locations unaltered by changes in the graph by addition/deletion of new nodes.
56+
57+
**Edge registration**
58+
59+
```julia
60+
edges=[]
61+
for (index, node) in enumerate(graph)
62+
for j in node
63+
push!(edges, Object(@Frames(prev_start()+5, GraphEdge(index, j[1], drawEdge; animate_on=:length, color="black"))))
64+
end
65+
end
66+
67+
# Need to provide custom drawEdge functions to account for self-loops, curved edges etc.
68+
function drawEdge(opts)
69+
sethue(opts[:color])
70+
line(opts[:position1], opts[:position2], :stroke)
71+
end
72+
```
73+
74+
### Graph visualisation
75+
76+
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.
77+
```julia
78+
using DataStructures
79+
# vis[x] indicates if a node is visited and Q is a queue data structure
80+
vis=[false for i in 1:8]
81+
Q=Queue{Int}()
82+
```
83+
84+
The algorithm can be explained in two ways:
85+
86+
**Using simple coloring**
87+
* Use a different fill color to convey the new nodes in the queue
88+
* Change the color of visited nodes permanently
89+
90+
```julia
91+
enqueue!(Q, 1)
92+
# The frames argument is optional here.
93+
changeNodeProperty!(ga, 1, :fill_color, "green")
94+
while !isempty(Q)
95+
i=dequeue!(Q)
96+
vis[i]=true
97+
changeNodeProperty!(ga, i, :fill_color, "blue")
98+
for j in neighbors(i)
99+
if !vis[j]
100+
enqueue!(Q, j)
101+
changeNodeProperty!(ga, j, :fill_color, "green")
102+
end
103+
end
104+
end
105+
```
106+
107+
**Using node color or border color highlighting**
108+
* Use a different fill or border color to convey the currently highlighted node
109+
* Change the color of visited nodes permanently
110+
111+
```julia
112+
# vis[x] indicates if a node is visited and Q is a queue data structure
113+
highlightNode(ga, 1, :fill_color, "white") # Flicker between original yellow and white color for some default number of frames
114+
vis[1]=true
115+
changeNodeProperty!(ga, 1, :fill_color, "orange")
116+
enqueue!(Q, 1)
117+
while !Q.empty()
118+
i=dequeue!(Q)
119+
for j in neighbors(i)
120+
if !vis[j]
121+
highlightNode(ga, j, :fill_color, "white")
122+
vis[j]=true
123+
changeNodeProperty!(ga, j, :fill_color, "orange")
124+
enqueue!(Q, j)
125+
end
126+
end
127+
end
128+
```

examples/graph_animations/example2.md

Lines changed: 142 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,142 @@
1+
## Depth First Search
2+
3+
### Points covered
4+
1. Graph creation using `LightGraphs.jl`
5+
2. Demonstrate additional utility functions
6+
7+
### Graph creation
8+
9+
In the previous example, it was shown how a graph can be created and animated for a simple graph data type. To simplify a lot of details one can provide a known graph type i.e. from JuliaGraphs. The candidate types for this are:
10+
* `SimpleGraph`
11+
* `SimpleDiGraph`
12+
* `SimpleWeightedGraph`
13+
* `SimpleWeightedDiGraph`
14+
15+
Here I cover the case when it is represented using `SimpleGraph` from the LightGraphs package.
16+
17+
```julia
18+
using LightGraphs
19+
g = SimpleGraph(6)
20+
add_edge!(g, 1, 2)
21+
add_edge!(g, 1, 3)
22+
add_edge!(g, 2, 4)
23+
add_edge!(g, 3, 5)
24+
add_edge!(g, 4, 6)
25+
26+
ag, nodes, edges = create_graph(g, 300, 300; layout=:spring, mode=:static)
27+
```
28+
`ag` - A Javis object storing some useful meta-data corresponding to the graph. Only the translate operation is supported on it as of now.
29+
30+
`nodes` - list of references to node objects in the order of node id
31+
32+
`edges` - list of references to edge objects in the order of creation
33+
34+
The `layout` defines how the nodes shall be arranged on the canvas. The `mode` argument determines whether the graph is to be considered a static or a dynamic graph.
35+
36+
#### Static graphs
37+
* The layout computation is done only once when the entire graph is known.
38+
* Updates to nodes/edge properties in the input graph are not animated i.e. the final graph state is used for the animation
39+
40+
#### Dynamic graphs
41+
* Addition of each new node leads to recomputation of new layout
42+
* Group animations of nodes/edges are separated across frames using a predefined ordering
43+
* Updates to edges and nodes through properties are animated with the help of action transitions leading to visualisation for time evolving graphs
44+
45+
*Note - Dynamic layout will be much more computationally intensive than static layout*
46+
47+
**Adding or removing nodes and edges**
48+
49+
New nodes and edges can be added to the canvas or existing nodes deleted after the graph creation.
50+
```julia
51+
removeNode!(ag, 2)
52+
addNode!(ag, 7)
53+
addEdge!(ag, 1, 7)
54+
```
55+
or
56+
```julia
57+
addEdge!(ag, 1, 7, 20)
58+
```
59+
The last argument is the edge weight. This mimics the `add_edge!` function provided by the LightGraph interface, where the `weight` parameter is valid if the internal graph type is a `SimpleWeightedGraph`.
60+
61+
62+
### Visualisation and demo utility functions
63+
64+
Rendering the video at this stage would just draw and animate a plain graph based on the underlying `LightGraph` type. It picks up reasonable defaults for these animations for e.g. if the graph is of type `SimpleWeightedGraph` the edge weights are simply centered on the lines drawn.
65+
66+
```julia
67+
current=1
68+
dst=6
69+
path=[]
70+
visited=[false for i in 1:nv(g)]
71+
num_visited=0
72+
```
73+
74+
The `path` variable stores the path to the destination node. The additional variable `num_visited` is needed to organize the animation of the call to the utility functions one after another. Once a relative way to define action frames across different objects is available this variable won't be necessary.
75+
76+
```julia
77+
function dfs_and_animate(node, path)
78+
if node==dst
79+
highlightNode(GFrames(20+num_visited*10, 100), ag, current, :border_color, "red")
80+
return
81+
end
82+
# Highlight the current node
83+
highlightNode(GFrames(20+num_visited*10, 100), ag, current, :border_color, "yellow")
84+
visited[current]=true
85+
num_visited+=1
86+
push!(path, current)
87+
# Change node color when highlighting effect ends
88+
changeNodeProperty!(@Frames(prev_end(), stop=parent_end()), ag, current, :color, "blue")
89+
for nb in neighbors(g, current)
90+
if visited[nb]
91+
continue
92+
end
93+
highlightEdge(GFrames(20+num_visited*10, 100), ag, current, nb, :color, "green")
94+
num_visited+=1
95+
dfs_and_animate(nb, path)
96+
end
97+
# Highlight again to indicate return to parent node
98+
highlightNode(GFrames(20+num_visited*10, 100), ag, current, :border_color, "yellow")
99+
num_visited+=1
100+
pop!(path)
101+
end
102+
103+
dfs_and_animate(1, path)
104+
```
105+
106+
Note - Even though the drawing property `:color` was not explicitly defined by passing it to a drawing function, these are provided as defaults for every node and edges in the case the graph is created using `create_graph`.
107+
108+
The default drawing properties provided are -
109+
* `fill_color` - for nodes
110+
* `border_color` - for nodes
111+
* `radius` - for nodes
112+
* `color` - for edges
113+
* `weights` - this property is only available for edges in weighted graphs
114+
* `opacity`, `scale` & `line_width` - defined in [example 1](example1.md)
115+
116+
If frame management becomes an issue, it is possible to skip it completely and let Javis handle the frame management, again through the use of reasonable defaults. For example, skipping frames in `highlightNode` schedules the node highlighting after the end of the previous animation specified on the graph. This animation could be any of the graph utility functions with the exception of `changeNodeProperty` and `changeEdgeProperty` for which the starting frame is used as reference. To avail of this default option, the `default_keyframes=true` option needs to be passed in `create_graph`.
117+
118+
Most utility functions like `changeNodeProperty` or `highlightNode` or `animate_*` use Javis actions underneath in the implementations which makes it easy to arrange them via frames.
119+
120+
**Using `animate_neighbors`**
121+
122+
Animating neighboring nodes and edges can be simplified by using the `animate_neighbors` utlity function
123+
124+
```julia
125+
animate_neighbors(ag, 1; animate_node_on=(:fill_color, "green"), animate_edge_on=(:color, "red"))
126+
```
127+
This internally makes a call to `changeNodeProperty` and `changeEdgeProperty`. If the keyword arguments are not provided, simple switching highlighting is used for the animation.
128+
129+
**Using `animate_path`**
130+
131+
After the traversal has been done and the shortest path found, show the path by animating it.
132+
133+
```julia
134+
# change back the graph to its original form
135+
changeNodeProperty!(ag, (node)->true, :fill_color, "white")
136+
changeEdgeProperty!(ag, (nodes...)->true, :color, "black")
137+
animate_path(ag, path; animate_node_on=(:fill_color, "green"), animate_edge_on=(:color, "red"))
138+
139+
render(video; pathname="tutorial_1.gif")
140+
```
141+
142+
In the default case, when nothing is specified about how to animate nodes/edges in a path simple on/off highlighting is used on the universal property `:opacity`.

0 commit comments

Comments
 (0)