Skip to content

Commit b548c25

Browse files
Eric RowellEric Rowell
authored andcommitted
1.6.0
1 parent d4b7ed0 commit b548c25

26 files changed

Lines changed: 235 additions & 184 deletions

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
# Changlog
22

33
## v1.6.0
4+
* new edges API (see docs)
45
* labels for nodes
56
* edge arrows for directed graphs
67

README.md

Lines changed: 67 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -47,14 +47,10 @@ let graph = new ElGrapho({
4747
ys: [0.6, 0, 0, -0.6, -0.6, -0.6, -0.6],
4848
colors: [0, 1, 1, 2, 2, 2, 2]
4949
},
50-
edges: [
51-
0, 1,
52-
0, 2,
53-
1, 3,
54-
1, 4,
55-
2, 5,
56-
2, 6
57-
],
50+
edges: {
51+
from: [0, 0, 1, 1, 2, 2],
52+
to: [1, 2, 3, 4, 5, 6]
53+
},
5854
width: 800,
5955
height: 400
6056
}
@@ -63,9 +59,9 @@ let graph = new ElGrapho({
6359

6460
* ```container``` - DOM element that will contain the El Grapho graph.
6561

66-
* ```model.nodes``` - object that contains information about all of the nodes in the graph (graphs are made up of nodes and edges). Each node is defined by a position (x and y), and also a color. El Grapho x and y ranges are between -1 and 1. If x is -1, then the node position is on the very left of the viewport. If x is 0 it is in the center. And if x is 1 it is on the very right of the viewport. Colors are integer values between 0 and 7. These integer values map to the El Grapho color palette.
62+
* ```model.nodes``` - object that defines the nodes in the graph (graphs are made up of nodes and edges). Each node is defined by a position (x and y), and also a color. El Grapho x and y ranges are between -1 and 1. If x is -1, then the node position is on the very left of the viewport. If x is 0 it is in the center. And if x is 1 it is on the very right of the viewport. Colors are integer values between 0 and 7. These integer values map to the El Grapho color palette.
6763

68-
* ```model.edges``` - array that defines the edges between nodes based on their indices. In the example above, the first edge begins at node ```0``` and ends at node ```1```. For non directed graphs, or bi-directional graphs, the order of the first node and second node do not matter. However, for directed graphs, the first index is the *from* node, and the second index is the *to* node.
64+
* ```model.edges``` - object that defines the edges between nodes based on their indices. Each edge is defined by a from-node-index and a to-node-index. In the example above, the first edge begins at node ```0``` and ends at node ```1```. For non directed graphs, or bi-directional graphs, ```from``` and ```to``` are interchangeable.
6965

7066
* ```model.width``` - number that defines the width of the El Grapho viewport in pixels.
7167

@@ -75,9 +71,13 @@ let graph = new ElGrapho({
7571

7672
* ```debug``` - boolean that can be used to enable debug mode. Debug mode will show the node and edge count in the bottom right corner of the visualization. The default is false.
7773

74+
* ```arrows``` - boolean that enables or disables edge arrows. For non directed or bi-directional graphs, you should set ```arrows``` to ```false```. The default is true.
75+
76+
* ```labels``` - array of strings used to define labels for each node. If your visualization has 100 nodes, there should be 100 strings in the ```labels``` array. The default is an empty array which results in no labels.
77+
7878
### Models
7979

80-
Determining the positions of the nodes for your graph can be alot of work! While it's nice to have the power to construct custom graph shapes, most El Grapho users will want to leverage the provided El Grapho models which will generate node positions for you. Currently, ElGrapho supports ```Tree``` and ```Cluster```
80+
Determining the positions of the nodes for your graph can be alot of work! While it's nice to have the power to construct custom graph shapes, most El Grapho users will want to leverage the provided El Grapho models which will generate node positions for you. Currently, ElGrapho supports ```Tree```, ```Ring```, ```Cluster```, and ```ForceDirectedGraph```
8181

8282
#### Tree Model
8383

@@ -86,14 +86,10 @@ let modelConfig = {
8686
nodes: {
8787
colors: [0, 1, 1, 2, 2, 3, 3]
8888
},
89-
edges: [
90-
0, 1,
91-
0, 2,
92-
1, 3,
93-
1, 4,
94-
2, 5,
95-
2, 6
96-
],
89+
edges: {
90+
from: [0, 0, 1, 1, 2, 2],
91+
to: [1, 2, 3, 4, 5, 6]
92+
},
9793
width: 800,
9894
height: 400
9995
};
@@ -104,7 +100,26 @@ let graph = new ElGrapho({
104100
});
105101
```
106102

107-
The ```Tree``` model takes in a nested tree structure and calculates the node positions for you by adding ```xs``` and ```ys``` to the ```nodes``` object. In this example, the root node has two children, and each of those children have two children of their own. In other words, this is a simple binary tree with two levels.
103+
#### Ring Model
104+
105+
```
106+
let modelConfig = {
107+
nodes: {
108+
colors: [0, 1, 1, 2, 2, 3, 3]
109+
},
110+
edges: {
111+
from: [0, 0, 1, 1, 2, 2],
112+
to: [1, 2, 3, 4, 5, 6]
113+
},
114+
width: 800,
115+
height: 400
116+
};
117+
118+
let graph = new ElGrapho({
119+
container: document.getElementById('container'),
120+
model: ElGrapho.models.Ring(modelConfig)
121+
});
122+
```
108123

109124
#### Cluster Model
110125

@@ -113,16 +128,10 @@ let modelConfig = {
113128
nodes: {
114129
colors: [0, 1, 1, 2, 2, 2, 2, 2]
115130
},
116-
edges: [
117-
0, 1,
118-
0, 2,
119-
0, 3,
120-
0, 4,
121-
0, 5,
122-
0, 6,
123-
0, 7,
124-
0, 8
125-
],
131+
edges: {
132+
from: [0, 0, 0, 0, 0, 0, 0, 0],
133+
to: [1, 2, 3, 4, 5, 6, 7, 8]
134+
},
126135
width: 800,
127136
height: 400
128137
};
@@ -133,11 +142,36 @@ let graph = new ElGrapho({
133142
});
134143
```
135144

136-
The ```Cluster``` model takes in an array of colors, and an array of edges. If a single color is used for all of the nodes, ElGrapho will generate a single centered cluster. If there are several colors used, ElGrapho will render distinct clusters. Because Cluster models can be generated in ```O(n)``` time, i.e. linear time, they are very fast to construct compared to other models such as force directed graphs which are polynomial in time.
145+
The Cluster model clusters nodes by color. If a single color is used for all of the nodes, ElGrapho will generate a single centered cluster. If there are several colors used, ElGrapho will render distinct clusters. Because Cluster models can be generated in ```O(n)``` time, i.e. linear time, they are very fast to construct compared to other models such as force directed graphs which are polynomial in time.
146+
147+
#### ForceDirectedGraph Model
148+
149+
```
150+
let modelConfig = {
151+
nodes: {
152+
colors: [0, 1, 1, 2, 2, 2, 2, 2]
153+
},
154+
edges: {
155+
from: [0, 0, 0, 0, 0, 0, 0, 0],
156+
to: [1, 2, 3, 4, 5, 6, 7, 8]
157+
},
158+
width: 800,
159+
height: 400
160+
};
161+
162+
let graph = new ElGrapho({
163+
container: document.getElementById('container'),
164+
model: ElGrapho.models.ForceDirectedGraph(modelConfig)
165+
});
166+
```
167+
168+
The ```ForceDirectedGraph``` uses a physics simulator to repel and attract nodes in order to generate natural layouts. Be warned that force directed graphs take O(n^2) time, which means they may not be appropriate for generating models on the client with lots of nodes. If it's possible to build your models in advance, it's a good idea to build the force directed graph model on the server and then cache it. If you require your models to be constructed at execution time, and the number of nodes is very high, you may consider using an O(n) model such as ```Cluster```
169+
170+
The ```ForceDirectedGraph``` model accepts a ```steps``` property from the modelConfig which can be used to define the accuracy of the result. This is because force directed graphs require multiple passes to obtain the final result. The default is 10. Lowering this number will result in faster model construction but less accurate results. Increasing this number will result in slower model construction but more accurate results.
137171

138-
### Model Polymorphism
172+
## Model Polymorphism
139173

140-
You may have noticed that the model config schema is identical for ```Tree``` and ```Cluster```. In fact, all El Grapho models have the exact same schema. Thus, El Grapho visualizations are polymorphic, meaning you can pass the same data structure into different models and get different, but correct, graph visualizations. Pretty cool!
174+
You may have noticed that the model config schema is identical for all models. As a result, El Grapho visualizations are polymorphic, meaning you can pass the same data structure into different models and get different graph visualizations. Pretty cool!
141175

142176
## Server Side Model Generation
143177

engine/dist/ElGrapho.js

Lines changed: 37 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -2263,7 +2263,7 @@ const VertexBridge = {
22632263
let colors = new Float32Array(nodes.colors);
22642264

22652265
// one edge is defined by two elements (from and to). each edge requires 2 triangles. Each triangle has 3 positions, with an x and y for each
2266-
let numEdges = edges.length / 2;
2266+
let numEdges = edges.from.length;
22672267
let numArrows = showArrows ? numEdges : 0;
22682268

22692269
let trianglePositions = new Float32Array(numEdges * 12 + numArrows * 6);
@@ -2274,9 +2274,9 @@ const VertexBridge = {
22742274
let triangleNormalsIndex = 0;
22752275
let triangleColorsIndex = 0;
22762276

2277-
for (let n=0; n<edges.length; n+=2) {
2278-
let pointIndex0 = edges[n];
2279-
let pointIndex1 = edges[n+1];
2277+
for (let n=0; n<numEdges; n++) {
2278+
let pointIndex0 = edges.from[n];
2279+
let pointIndex1 = edges.to[n];
22802280
let normalDistance = MAX_NODE_SIZE*0.1;
22812281

22822282
let x0 = nodes.xs[pointIndex0];
@@ -3053,9 +3053,12 @@ let Cluster = function(config) {
30533053
ys: [],
30543054
colors: config.nodes.colors.slice()
30553055
},
3056-
edges: config.edges.slice(),
3057-
width: width,
3058-
height: height
3056+
edges: {
3057+
from: config.edges.from.slice(),
3058+
to: config.edges.to.slice()
3059+
},
3060+
width: config.width,
3061+
height: config.height
30593062
};
30603063

30613064
// keys are color integers, values are arrays. The arrays contain node indices
@@ -3139,11 +3142,14 @@ const ForceDirectedGraph = function(config) {
31393142

31403143
let model = {
31413144
nodes: {
3142-
xs: [],
3143-
ys: [],
3144-
colors: []
3145+
xs: [],
3146+
ys: [],
3147+
colors: config.nodes.colors.slice()
3148+
},
3149+
edges: {
3150+
from: config.edges.from.slice(),
3151+
to: config.edges.to.slice()
31453152
},
3146-
edges: [],
31473153
width: config.width,
31483154
height: config.height
31493155
};
@@ -3154,11 +3160,9 @@ const ForceDirectedGraph = function(config) {
31543160
model.nodes.ys.length = numNodes;
31553161
model.nodes.ys.fill(0);
31563162

3157-
model.nodes.colors = config.nodes.colors.slice();
3158-
model.edges = config.edges.slice();
3159-
31603163
let nodes = model.nodes;
31613164
let edges = model.edges;
3165+
let numEdges = edges.from.length;
31623166

31633167
// find color counts
31643168
let colors = [];
@@ -3233,9 +3237,9 @@ const ForceDirectedGraph = function(config) {
32333237
}
32343238

32353239
// attractive forces between nodes sharing an edge
3236-
for (let i=0; i<edges.length; i+=2) {
3237-
let a = edges[i];
3238-
let b = edges[i+1];
3240+
for (let i=0; i<numEdges; i++) {
3241+
let a = edges.from[i];
3242+
let b = edges.to[i];
32393243

32403244
let ax = nodes.xs[a];
32413245
let ay = nodes.ys[a];
@@ -3300,19 +3304,18 @@ const Ring = function(config) {
33003304

33013305
let model = {
33023306
nodes: {
3303-
xs: [],
3304-
ys: [],
3305-
colors: []
3307+
xs: [],
3308+
ys: [],
3309+
colors: config.nodes.colors.slice()
3310+
},
3311+
edges: {
3312+
from: config.edges.from.slice(),
3313+
to: config.edges.to.slice()
33063314
},
3307-
edges: [],
33083315
width: config.width,
33093316
height: config.height
33103317
};
33113318

3312-
// TODO: need to sort colors first and shuffle edges
3313-
model.nodes.colors = config.nodes.colors;
3314-
model.edges = config.edges;
3315-
33163319
for (let n=0; n<numNodes; n++) {
33173320
let angle = (-1*Math.PI*2*n / numNodes) + Math.PI/2;
33183321
model.nodes.xs.push(Math.cos(angle));
@@ -3381,7 +3384,6 @@ let getNestedTree = function(config) {
33813384
let edges = config.edges;
33823385
let colors = config.nodes.colors;
33833386
let nodes = {};
3384-
let edgeIndex = 0;
33853387

33863388
// build nodes
33873389
for (let n=0; n<colors.length; n++) {
@@ -3393,9 +3395,9 @@ let getNestedTree = function(config) {
33933395
};
33943396
}
33953397

3396-
while(edgeIndex < edges.length) {
3397-
let fromIndex = edges[edgeIndex++];
3398-
let toIndex = edges[edgeIndex++];
3398+
for (let n=0; n<edges.from.length; n++) {
3399+
let fromIndex = edges.from[n];
3400+
let toIndex = edges.to[n];
33993401

34003402
// parent child relationship
34013403
nodes[fromIndex].children.push(nodes[toIndex]);
@@ -3446,22 +3448,23 @@ const Tree = function(config) {
34463448
ys: [],
34473449
colors: []
34483450
},
3449-
edges: [], // num edges = num nodes - 1
3451+
edges: {
3452+
from: [],
3453+
to: []
3454+
},
34503455
width: config.width,
34513456
height: config.height
34523457
};
34533458

3454-
let edgeIndex = 0;
3455-
34563459
// O(n)
34573460
nodes.forEach(function(node, n) {
34583461
model.nodes.xs[n] = node.x;
34593462
model.nodes.ys[n] = 1 - (2 * ((node.level - 1) / (maxLevel - 1)));
34603463
model.nodes.colors[n] = node.color;
34613464

34623465
if (node.parent) {
3463-
model.edges[edgeIndex++] = node.parent.index;
3464-
model.edges[edgeIndex++] = node.index;
3466+
model.edges.from[n] = node.parent.index;
3467+
model.edges.to[n] = node.index;
34653468
}
34663469
});
34673470

engine/dist/ElGrapho.min.js

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

engine/src/VertexBridge.js

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,7 @@ const VertexBridge = {
2929
let colors = new Float32Array(nodes.colors);
3030

3131
// one edge is defined by two elements (from and to). each edge requires 2 triangles. Each triangle has 3 positions, with an x and y for each
32-
let numEdges = edges.length / 2;
32+
let numEdges = edges.from.length;
3333
let numArrows = showArrows ? numEdges : 0;
3434

3535
let trianglePositions = new Float32Array(numEdges * 12 + numArrows * 6);
@@ -40,9 +40,9 @@ const VertexBridge = {
4040
let triangleNormalsIndex = 0;
4141
let triangleColorsIndex = 0;
4242

43-
for (let n=0; n<edges.length; n+=2) {
44-
let pointIndex0 = edges[n];
45-
let pointIndex1 = edges[n+1];
43+
for (let n=0; n<numEdges; n++) {
44+
let pointIndex0 = edges.from[n];
45+
let pointIndex1 = edges.to[n];
4646
let normalDistance = MAX_NODE_SIZE*0.1;
4747

4848
let x0 = nodes.xs[pointIndex0];

engine/src/models/Cluster.js

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -21,9 +21,12 @@ let Cluster = function(config) {
2121
ys: [],
2222
colors: config.nodes.colors.slice()
2323
},
24-
edges: config.edges.slice(),
25-
width: width,
26-
height: height
24+
edges: {
25+
from: config.edges.from.slice(),
26+
to: config.edges.to.slice()
27+
},
28+
width: config.width,
29+
height: config.height
2730
};
2831

2932
// keys are color integers, values are arrays. The arrays contain node indices

0 commit comments

Comments
 (0)