Skip to content

Commit

Permalink
Testing and Algorithm Improvements
Browse files Browse the repository at this point in the history
 * Constrained MDS initial layout
 * Randomized testing of venn.js performance
 * Comparison to the VennEuler package
  • Loading branch information
benfred committed Jun 28, 2015
1 parent 0f9b02c commit e02e04b
Show file tree
Hide file tree
Showing 16 changed files with 9,180 additions and 87 deletions.
2 changes: 1 addition & 1 deletion Gruntfile.js
Expand Up @@ -28,7 +28,7 @@ module.exports = function(grunt) {
},

jshint: {
all: ['Gruntfile.js', 'src/*.js'],
all: ['Gruntfile.js', 'src/*.js', 'tests/*js'],
}
});

Expand Down
31 changes: 3 additions & 28 deletions README.md
Expand Up @@ -3,9 +3,10 @@ venn.js

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

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

#### Usage

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

##### MDS Layout

In most cases the greedy initial layout does a good job of positioning the
sets, but there are cases where it breaks down. One case is detailed in [this
blog post](http://www.benfrederickson.com/2013/05/16/multidimensional-scaling.html),
and it can be better laid out using [multidimensional
scaling](https://en.wikipedia.org/wiki/Multidimensional_scaling) to generate
the initial layout.

To enable this just include the [mds.js](http://github.com/benfred/mds.js)
and [numeric.js](http://numericjs.com) libraries first, and then change the
layout function on the VennDiagam object:

```javascript
var chart = venn.VennDiagram()
.width(600)
.height(400)
.layoutFunction(
function(d) { return venn.venn(d, { initialLayout: venn.classicMDSLayout });}
);

d3.select("#venn").datum(sets).call(chart);
```

[View this example](http://benfred.github.io/venn.js/examples/mds.html)

#### Building

To build venn.js and venn.min.js from the files in src/ - you should first
Expand Down
2 changes: 1 addition & 1 deletion src/diagram.js
Expand Up @@ -255,7 +255,7 @@
return margin;
}

// compute the center of some circles by maximizing the margin of
// compute the center of some circles by maximizing the margin of
// the center point relative to the circles (interior) after subtracting
// nearby circles (exterior)
function computeTextCentre(interior, exterior) {
Expand Down
2 changes: 1 addition & 1 deletion src/export.js
Expand Up @@ -3,5 +3,5 @@
window.venn = lib;
} else {
module.exports = lib;
}
}
})(venn);
152 changes: 152 additions & 0 deletions src/fmin.js
Expand Up @@ -33,6 +33,33 @@
return a + delta;
};

// need some basic operations on vectors, rather than adding a dependency,
// just define here
function zeros(x) { var r = new Array(x); for (var i = 0; i < x; ++i) { r[i] = 0; } return r; }
function zerosM(x,y) { return zeros(x).map(function() { return zeros(y); }); }
venn.zerosM = zerosM;
venn.zeros = zeros;

function dot(a, b) {
var ret = 0;
for (var i = 0; i < a.length; ++i) {
ret += a[i] * b[i];
}
return ret;
}

function norm2(a) {
return Math.sqrt(dot(a, a));
}
venn.norm2 = norm2;

function multiplyBy(a, c) {
for (var i = 0; i < a.length; ++i) {
a[i] *= c;
}
}
venn.multiplyBy = multiplyBy;

function weightedSum(ret, w1, v1, w2, v2) {
for (var j = 0; j < ret.length; ++j) {
ret[j] = w1 * v1[j] + w2 * v2[j];
Expand Down Expand Up @@ -161,4 +188,129 @@
return {f : simplex[0].fx,
solution : simplex[0]};
};


venn.minimizeConjugateGradient = function(f, initial, params) {
// allocate all memory up front here, keep out of the loop for perfomance
// reasons
var current = {x: initial.slice(), fx: 0, fxprime: initial.slice()},
next = {x: initial.slice(), fx: 0, fxprime: initial.slice()},
yk = initial.slice(),
pk, temp,
a = 1,
maxIterations;

params = params || {};
maxIterations = params.maxIterations || initial.length * 5;

current.fx = f(current.x, current.fxprime);
pk = current.fxprime.slice();
multiplyBy(pk, -1);

for (var i = 0; i < maxIterations; ++i) {
if (params.history) {
params.history.push({x: current.x.slice(),
fx: current.fx,
fxprime: current.fxprime.slice()});
}

a = venn.wolfeLineSearch(f, pk, current, next, a);
if (!a) {
// faiiled to find point that satifies wolfe conditions.
// reset direction for next iteration
for (var j = 0; j < pk.length; ++j) {
pk[j] = -1 * current.fxprime[j];
}
} else {
// update direction using Polak–Ribiere CG method
weightedSum(yk, 1, next.fxprime, -1, current.fxprime);

var delta_k = dot(current.fxprime, current.fxprime),
beta_k = Math.max(0, dot(yk, next.fxprime) / delta_k);

weightedSum(pk, beta_k, pk, -1, next.fxprime);

temp = current;
current = next;
next = temp;
}

if (norm2(current.fxprime) <= 1e-5) {
break;
}
}

if (params.history) {
params.history.push({x: current.x.slice(),
fx: current.fx,
fxprime: current.fxprime.slice()});
}

return current;
};

var c1 = 1e-6, c2 = 0.1;

/// searches along line 'pk' for a point that satifies the wolfe conditions
/// See 'Numerical Optimization' by Nocedal and Wright p59-60
venn.wolfeLineSearch = function(f, pk, current, next, a) {
var phi0 = current.fx, phiPrime0 = dot(current.fxprime, pk),
phi = phi0, phi_old = phi0,
phiPrime = phiPrime0,
a0 = 0;

a = a || 1;

function zoom(a_lo, a_high, phi_lo) {
for (var iteration = 0; iteration < 16; ++iteration) {
a = (a_lo + a_high)/2;
weightedSum(next.x, 1.0, current.x, a, pk);
phi = next.fx = f(next.x, next.fxprime);
phiPrime = dot(next.fxprime, pk);

if ((phi > (phi0 + c1 * a * phiPrime0)) ||
(phi >= phi_lo)) {
a_high = a;

} else {
if (Math.abs(phiPrime) <= -c2 * phiPrime0) {
return a;
}

if (phiPrime * (a_high - a_lo) >=0) {
a_high = a_lo;
}

a_lo = a;
phi_lo = phi;
}
}

return 0;
}

for (var iteration = 0; iteration < 10; ++iteration) {
weightedSum(next.x, 1.0, current.x, a, pk);
phi = next.fx = f(next.x, next.fxprime);
phiPrime = dot(next.fxprime, pk);
if ((phi > (phi0 + c1 * a * phiPrime0)) ||
(iteration && (phi >= phi_old))) {
return zoom(a0, a, phi_old);
}

if (Math.abs(phiPrime) <= -c2 * phiPrime0) {
return a;
}

if (phiPrime >= 0 ) {
return zoom(a, a0, phi);
}

phi_old = phi;
a0 = a;
a *= 2;
}

return 0;
};
})(venn);
2 changes: 1 addition & 1 deletion src/init.js
@@ -1 +1 @@
var venn = venn || {};
var venn = venn || {'version' : '0.2'};

0 comments on commit e02e04b

Please sign in to comment.