Skip to content

Commit e02e04b

Browse files
committed
Testing and Algorithm Improvements
* Constrained MDS initial layout * Randomized testing of venn.js performance * Comparison to the VennEuler package
1 parent 0f9b02c commit e02e04b

16 files changed

Lines changed: 9180 additions & 87 deletions

Gruntfile.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,7 @@ module.exports = function(grunt) {
2828
},
2929

3030
jshint: {
31-
all: ['Gruntfile.js', 'src/*.js'],
31+
all: ['Gruntfile.js', 'src/*.js', 'tests/*js'],
3232
}
3333
});
3434

README.md

Lines changed: 3 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -3,9 +3,10 @@ venn.js
33

44
A javascript library for laying out area proportional venn and euler diagrams.
55

6-
Details of how this library works can be found on the [blog
6+
Details of how this library works can be found on the [blog
77
post](http://www.benfrederickson.com/venn-diagrams-with-d3.js/)
8-
I wrote about this.
8+
I wrote about this. A follow up post [discusses testing strategy and
9+
algorithmic improvements](http://www.benfrederickson.con/better-venn-diagrams).
910

1011
#### Usage
1112

@@ -137,32 +138,6 @@ div.selectAll("g")
137138
```
138139
[View this example](http://benfred.github.io/venn.js/examples/intersection_tooltip.html)
139140

140-
##### MDS Layout
141-
142-
In most cases the greedy initial layout does a good job of positioning the
143-
sets, but there are cases where it breaks down. One case is detailed in [this
144-
blog post](http://www.benfrederickson.com/2013/05/16/multidimensional-scaling.html),
145-
and it can be better laid out using [multidimensional
146-
scaling](https://en.wikipedia.org/wiki/Multidimensional_scaling) to generate
147-
the initial layout.
148-
149-
To enable this just include the [mds.js](http://github.com/benfred/mds.js)
150-
and [numeric.js](http://numericjs.com) libraries first, and then change the
151-
layout function on the VennDiagam object:
152-
153-
```javascript
154-
var chart = venn.VennDiagram()
155-
.width(600)
156-
.height(400)
157-
.layoutFunction(
158-
function(d) { return venn.venn(d, { initialLayout: venn.classicMDSLayout });}
159-
);
160-
161-
d3.select("#venn").datum(sets).call(chart);
162-
```
163-
164-
[View this example](http://benfred.github.io/venn.js/examples/mds.html)
165-
166141
#### Building
167142

168143
To build venn.js and venn.min.js from the files in src/ - you should first

src/diagram.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -255,7 +255,7 @@
255255
return margin;
256256
}
257257

258-
// compute the center of some circles by maximizing the margin of
258+
// compute the center of some circles by maximizing the margin of
259259
// the center point relative to the circles (interior) after subtracting
260260
// nearby circles (exterior)
261261
function computeTextCentre(interior, exterior) {

src/export.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,5 +3,5 @@
33
window.venn = lib;
44
} else {
55
module.exports = lib;
6-
}
6+
}
77
})(venn);

src/fmin.js

Lines changed: 152 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,33 @@
3333
return a + delta;
3434
};
3535

36+
// need some basic operations on vectors, rather than adding a dependency,
37+
// just define here
38+
function zeros(x) { var r = new Array(x); for (var i = 0; i < x; ++i) { r[i] = 0; } return r; }
39+
function zerosM(x,y) { return zeros(x).map(function() { return zeros(y); }); }
40+
venn.zerosM = zerosM;
41+
venn.zeros = zeros;
42+
43+
function dot(a, b) {
44+
var ret = 0;
45+
for (var i = 0; i < a.length; ++i) {
46+
ret += a[i] * b[i];
47+
}
48+
return ret;
49+
}
50+
51+
function norm2(a) {
52+
return Math.sqrt(dot(a, a));
53+
}
54+
venn.norm2 = norm2;
55+
56+
function multiplyBy(a, c) {
57+
for (var i = 0; i < a.length; ++i) {
58+
a[i] *= c;
59+
}
60+
}
61+
venn.multiplyBy = multiplyBy;
62+
3663
function weightedSum(ret, w1, v1, w2, v2) {
3764
for (var j = 0; j < ret.length; ++j) {
3865
ret[j] = w1 * v1[j] + w2 * v2[j];
@@ -161,4 +188,129 @@
161188
return {f : simplex[0].fx,
162189
solution : simplex[0]};
163190
};
191+
192+
193+
venn.minimizeConjugateGradient = function(f, initial, params) {
194+
// allocate all memory up front here, keep out of the loop for perfomance
195+
// reasons
196+
var current = {x: initial.slice(), fx: 0, fxprime: initial.slice()},
197+
next = {x: initial.slice(), fx: 0, fxprime: initial.slice()},
198+
yk = initial.slice(),
199+
pk, temp,
200+
a = 1,
201+
maxIterations;
202+
203+
params = params || {};
204+
maxIterations = params.maxIterations || initial.length * 5;
205+
206+
current.fx = f(current.x, current.fxprime);
207+
pk = current.fxprime.slice();
208+
multiplyBy(pk, -1);
209+
210+
for (var i = 0; i < maxIterations; ++i) {
211+
if (params.history) {
212+
params.history.push({x: current.x.slice(),
213+
fx: current.fx,
214+
fxprime: current.fxprime.slice()});
215+
}
216+
217+
a = venn.wolfeLineSearch(f, pk, current, next, a);
218+
if (!a) {
219+
// faiiled to find point that satifies wolfe conditions.
220+
// reset direction for next iteration
221+
for (var j = 0; j < pk.length; ++j) {
222+
pk[j] = -1 * current.fxprime[j];
223+
}
224+
} else {
225+
// update direction using Polak–Ribiere CG method
226+
weightedSum(yk, 1, next.fxprime, -1, current.fxprime);
227+
228+
var delta_k = dot(current.fxprime, current.fxprime),
229+
beta_k = Math.max(0, dot(yk, next.fxprime) / delta_k);
230+
231+
weightedSum(pk, beta_k, pk, -1, next.fxprime);
232+
233+
temp = current;
234+
current = next;
235+
next = temp;
236+
}
237+
238+
if (norm2(current.fxprime) <= 1e-5) {
239+
break;
240+
}
241+
}
242+
243+
if (params.history) {
244+
params.history.push({x: current.x.slice(),
245+
fx: current.fx,
246+
fxprime: current.fxprime.slice()});
247+
}
248+
249+
return current;
250+
};
251+
252+
var c1 = 1e-6, c2 = 0.1;
253+
254+
/// searches along line 'pk' for a point that satifies the wolfe conditions
255+
/// See 'Numerical Optimization' by Nocedal and Wright p59-60
256+
venn.wolfeLineSearch = function(f, pk, current, next, a) {
257+
var phi0 = current.fx, phiPrime0 = dot(current.fxprime, pk),
258+
phi = phi0, phi_old = phi0,
259+
phiPrime = phiPrime0,
260+
a0 = 0;
261+
262+
a = a || 1;
263+
264+
function zoom(a_lo, a_high, phi_lo) {
265+
for (var iteration = 0; iteration < 16; ++iteration) {
266+
a = (a_lo + a_high)/2;
267+
weightedSum(next.x, 1.0, current.x, a, pk);
268+
phi = next.fx = f(next.x, next.fxprime);
269+
phiPrime = dot(next.fxprime, pk);
270+
271+
if ((phi > (phi0 + c1 * a * phiPrime0)) ||
272+
(phi >= phi_lo)) {
273+
a_high = a;
274+
275+
} else {
276+
if (Math.abs(phiPrime) <= -c2 * phiPrime0) {
277+
return a;
278+
}
279+
280+
if (phiPrime * (a_high - a_lo) >=0) {
281+
a_high = a_lo;
282+
}
283+
284+
a_lo = a;
285+
phi_lo = phi;
286+
}
287+
}
288+
289+
return 0;
290+
}
291+
292+
for (var iteration = 0; iteration < 10; ++iteration) {
293+
weightedSum(next.x, 1.0, current.x, a, pk);
294+
phi = next.fx = f(next.x, next.fxprime);
295+
phiPrime = dot(next.fxprime, pk);
296+
if ((phi > (phi0 + c1 * a * phiPrime0)) ||
297+
(iteration && (phi >= phi_old))) {
298+
return zoom(a0, a, phi_old);
299+
}
300+
301+
if (Math.abs(phiPrime) <= -c2 * phiPrime0) {
302+
return a;
303+
}
304+
305+
if (phiPrime >= 0 ) {
306+
return zoom(a, a0, phi);
307+
}
308+
309+
phi_old = phi;
310+
a0 = a;
311+
a *= 2;
312+
}
313+
314+
return 0;
315+
};
164316
})(venn);

src/init.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
var venn = venn || {};
1+
var venn = venn || {'version' : '0.2'};

0 commit comments

Comments
 (0)