d3fc-sample
Version:
A data subsampler that chooses representative data points using a largest triangle or mode-median approach.
269 lines (194 loc) • 7.65 kB
JavaScript
(function (global, factory) {
typeof exports === 'object' && typeof module !== 'undefined' ? factory(exports, require('d3-array'), require('d3fc-rebind')) :
typeof define === 'function' && define.amd ? define(['exports', 'd3-array', 'd3fc-rebind'], factory) :
(factory((global.fc = global.fc || {}),global.d3,global.fc));
}(this, (function (exports,d3Array,d3fcRebind) { 'use strict';
var bucket = function () {
var bucketSize = 10;
var bucket = function bucket(data) {
return bucketSize <= 1 ? data.map(function (d) {
return [d];
}) : d3Array.range(0, Math.ceil(data.length / bucketSize)).map(function (i) {
return data.slice(i * bucketSize, (i + 1) * bucketSize);
});
};
bucket.bucketSize = function (x) {
if (!arguments.length) {
return bucketSize;
}
bucketSize = x;
return bucket;
};
return bucket;
};
var largestTriangleOneBucket = function () {
var dataBucketer = bucket();
var x = function x(d) {
return d;
};
var y = function y(d) {
return d;
};
var largestTriangleOneBucket = function largestTriangleOneBucket(data) {
if (dataBucketer.bucketSize() >= data.length) {
return data;
}
var pointAreas = calculateAreaOfPoints(data);
var pointAreaBuckets = dataBucketer(pointAreas);
var buckets = dataBucketer(data.slice(1, data.length - 1));
var subsampledData = buckets.map(function (thisBucket, i) {
var pointAreaBucket = pointAreaBuckets[i];
var maxArea = d3Array.max(pointAreaBucket);
var currentMaxIndex = pointAreaBucket.indexOf(maxArea);
return thisBucket[currentMaxIndex];
});
// First and last data points are their own buckets.
return [].concat([data[0]], subsampledData, [data[data.length - 1]]);
};
function calculateAreaOfPoints(data) {
var xyData = data.map(function (point) {
return [x(point), y(point)];
});
var pointAreas = d3Array.range(1, xyData.length - 1).map(function (i) {
var lastPoint = xyData[i - 1];
var thisPoint = xyData[i];
var nextPoint = xyData[i + 1];
return 0.5 * Math.abs((lastPoint[0] - nextPoint[0]) * (thisPoint[1] - lastPoint[1]) - (lastPoint[0] - thisPoint[0]) * (nextPoint[1] - lastPoint[1]));
});
return pointAreas;
}
d3fcRebind.rebind(largestTriangleOneBucket, dataBucketer, 'bucketSize');
largestTriangleOneBucket.x = function (d) {
if (!arguments.length) {
return x;
}
x = d;
return largestTriangleOneBucket;
};
largestTriangleOneBucket.y = function (d) {
if (!arguments.length) {
return y;
}
y = d;
return largestTriangleOneBucket;
};
return largestTriangleOneBucket;
};
var largestTriangleThreeBucket = function () {
var x = function x(d) {
return d;
};
var y = function y(d) {
return d;
};
var dataBucketer = bucket();
var largestTriangleThreeBucket = function largestTriangleThreeBucket(data) {
if (dataBucketer.bucketSize() >= data.length) {
return data;
}
var buckets = dataBucketer(data.slice(1, data.length - 1));
var firstBucket = data[0];
var lastBucket = data[data.length - 1];
// Keep track of the last selected bucket info and all buckets
// (for the next bucket average)
var allBuckets = [].concat([firstBucket], buckets, [lastBucket]);
var lastSelectedX = x(firstBucket);
var lastSelectedY = y(firstBucket);
var subsampledData = buckets.map(function (thisBucket, i) {
var nextAvgX = d3Array.mean(allBuckets[i + 1], x);
var nextAvgY = d3Array.mean(allBuckets[i + 1], y);
var xyData = thisBucket.map(function (item) {
return [x(item), y(item)];
});
var areas = xyData.map(function (item) {
return 0.5 * Math.abs((lastSelectedX - nextAvgX) * (item[1] - lastSelectedY) - (lastSelectedX - item[0]) * (nextAvgY - lastSelectedY));
});
var highestIndex = areas.indexOf(d3Array.max(areas));
var highestXY = xyData[highestIndex];
lastSelectedX = highestXY[0];
lastSelectedY = highestXY[1];
return thisBucket[highestIndex];
});
// First and last data points are their own buckets.
return [].concat([data[0]], subsampledData, [data[data.length - 1]]);
};
d3fcRebind.rebind(largestTriangleThreeBucket, dataBucketer, 'bucketSize');
largestTriangleThreeBucket.x = function (d) {
if (!arguments.length) {
return x;
}
x = d;
return largestTriangleThreeBucket;
};
largestTriangleThreeBucket.y = function (d) {
if (!arguments.length) {
return y;
}
y = d;
return largestTriangleThreeBucket;
};
return largestTriangleThreeBucket;
};
var modeMedian = function () {
var dataBucketer = bucket();
var value = function value(d) {
return d;
};
var modeMedian = function modeMedian(data) {
if (dataBucketer.bucketSize() > data.length) {
return data;
}
var minMax = d3Array.extent(data, value);
var buckets = dataBucketer(data.slice(1, data.length - 1));
var subsampledData = buckets.map(function (thisBucket, i) {
var frequencies = {};
var mostFrequent;
var mostFrequentIndex;
var singleMostFrequent = true;
var values = thisBucket.map(value);
var globalMinMax = values.filter(function (value) {
return value === minMax[0] || value === minMax[1];
}).map(function (value) {
return values.indexOf(value);
})[0];
if (globalMinMax !== undefined) {
return thisBucket[globalMinMax];
}
values.forEach(function (item, i) {
if (frequencies[item] === undefined) {
frequencies[item] = 0;
}
frequencies[item]++;
if (frequencies[item] > frequencies[mostFrequent] || mostFrequent === undefined) {
mostFrequent = item;
mostFrequentIndex = i;
singleMostFrequent = true;
} else if (frequencies[item] === frequencies[mostFrequent]) {
singleMostFrequent = false;
}
});
if (singleMostFrequent) {
return thisBucket[mostFrequentIndex];
} else {
return thisBucket[Math.floor(thisBucket.length / 2)];
}
});
// First and last data points are their own buckets.
return [].concat([data[0]], subsampledData, [data[data.length - 1]]);
};
d3fcRebind.rebind(modeMedian, dataBucketer, 'bucketSize');
modeMedian.value = function (x) {
if (!arguments.length) {
return value;
}
value = x;
return modeMedian;
};
return modeMedian;
};
exports.bucket = bucket;
exports.largestTriangleOneBucket = largestTriangleOneBucket;
exports.largestTriangleThreeBucket = largestTriangleThreeBucket;
exports.modeMedian = modeMedian;
Object.defineProperty(exports, '__esModule', { value: true });
})));