8000 Reduce dataset based on zoom by danelkhen · Pull Request #310 · c3js/c3 · GitHub
[go: up one dir, main page]
More Web Proxy on the site http://driver.im/
Skip to content

Reduce dataset based on zoom #310

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 3 commits into from
Jun 4, 2014
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
10000
Diff view
287 changes: 287 additions & 0 deletions c3ext.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,287 @@
var c3ext = {};
c3ext.generate = function (options) {

if (options.zoom2 != null) {
zoom2_reducers = options.zoom2.reducers || {};
zoom2_enabled = options.zoom2.enabled;
_zoom2_factor = options.zoom2.factor || 1;
_zoom2_maxItems = options.zoom2.maxItems;
}

if (!zoom2_enabled) {
return c3.generate(options);
}


var originalData = Q.copy(options.data);
var zoom2_reducers;
var zoom2_enabled;
var _zoom2_maxItems;

if (_zoom2_maxItems == null) {
var el = d3.select(options.bindto)[0][0];
if (el != null) {
var availWidth = el.clientWidth;

var pointSize = 20;
_zoom2_maxItems = Math.ceil(availWidth / pointSize);
}
if (_zoom2_maxItems == null || _zoom2_maxItems < 10) {
_zoom2_maxItems = 10;
}
}

function onZoomChanged(e) {
refresh();
}

var zoom2 = ZoomBehavior.create({ changed: onZoomChanged, bindto:options.bindto });

zoom2.enhance = function () {
_zoom2_maxItems *= 2;
var totalItems = zoom2.getZoom().totalItems;
if (_zoom2_maxItems > totalItems)
_zoom2_maxItems = totalItems;
refresh();
}
zoom2.dehance = function () {
_zoom2_maxItems = Math.ceil(_zoom2_maxItems / 2) + 1;
refresh();
}

zoom2.maxItems = function () { return _zoom2_maxItems; };
function zoomAndReduceData(list, zoomRange, func, maxItems) {
//var maxItems = 10;//Math.ceil(10 * zoomFactor);
var list2 = list.slice(zoomRange[0], zoomRange[1]);
var chunkSize = 1;
var list3 = list2;
if (list3.length > maxItems) {
var chunkSize = Math.ceil(list2.length / maxItems);
list3 = list3.splitIntoChunksOf(chunkSize).select(func);
}
//console.log("x" + getCurrentZoomLevel() + ", maxItems=" + maxItems + " chunkSize=" + chunkSize + " totalBefore=" + list2.length + ", totalAfter=" + list3.length);
return list3;
}

var getDataForZoom = function (data) {
if (data.columns == null || data.columns.length == 0)
return;

var zoomInfo = zoom2.getZoom();
if (zoomInfo.totalItems != data.columns[0].length - 1) {
zoom2.setOptions({ totalItems: data.columns[0].length - 1 });
zoomInfo = zoom2.getZoom();
}
data.columns = originalData.columns.select(function (column) {
var name = column[0];
var reducer = zoom2_reducers[name] || "t=>t[0]".toLambda(); //by default take the first

var values = column.slice(1);
var newValues = zoomAndReduceData(values, zoomInfo.currentZoom, reducer, _zoom2_maxItems);
return [name].concat(newValues);
});
return data;
};

getDataForZoom(options.data);
var chart = c3.generate(options);
var _chart_load_org = chart.load.bind(chart);
chart.zoom2 = zoom2;
chart.load = function (data) {
if (data.unload) {
unload(data.unload);
delete data.unload;
}
Q.copy(data, originalData);
refresh();
}
chart.unload = function (names) {
unload(names);
refresh();
}

function unload(names) {
originalData.columns.removeAll(function (t) { names.contains(t); });
}


function refresh() {
var data = Q.copy(originalData)
getDataForZoom(data);
_chart_load_org(data);
};


return chart;
}


var ZoomBehavior = {};
ZoomBehavior.create = function (options) {
var zoom = { __type: "ZoomBehavior" };

var _zoom2_factor;
var _left;
var totalItems;
var currentZoom;
var bindto = options.bindto;
var _zoomChanged = options.changed || function () { };
var element;
var mousewheelTimer;
var deltaY = 0;
var leftRatio = 0;


zoom.setOptions = function (options) {
if (options == null)
options = {};
_zoom2_factor = options.factor || 1;
_left = 0;
totalItems = options.totalItems || 0;
currentZoom = [0, totalItems];
_zoomChanged = options.changed || _zoomChanged;
}

zoom.setOptions(options);


function verifyZoom(newZoom) {
//newZoom.sort();
if (newZoom[1] > totalItems) {
var diff = newZoom[1] - totalItems;
newZoom[0] -= diff;
newZoom[1] -= diff;
}
if (newZoom[0] < 0) {
var diff = newZoom[0] * -1;
newZoom[0] += diff;
newZoom[1] += diff;
}
if (newZoom[1] > totalItems)
newZoom[1] = totalItems;
if (newZoom[0] < 0)
newZoom[0] = 0;
}

function zoomAndPan(zoomFactor, left) {
var itemsToShow = Math.ceil(totalItems / zoomFactor);
var newZoom = [left, left + itemsToShow];
verifyZoom(newZoom);
currentZoom = newZoom;
onZoomChanged();
}

function onZoomChanged() {
if (_zoomChanged != null)
_zoomChanged(zoom.getZoom());
}
function applyZoomAndPan() {
zoomAndPan(_zoom2_factor, _left);
}
function getItemsToShow() {
var itemsToShow = Math.ceil(totalItems / _zoom2_factor);
return itemsToShow;
}


zoom.getZoom = function () {
return { totalItems: totalItems, currentZoom: currentZoom.toArray() };
}

zoom.factor = function (factor, skipDraw) {
if (arguments.length == 0)
return _zoom2_factor;
_zoom2_factor = factor;
if (_zoom2_factor < 1)
_zoom2_factor = 1;
if (skipDraw)
return;
applyZoomAndPan();
}
zoom.left = function (left, skipDraw) {
if (arguments.length == 0)
return _left;
_left = left;
if (_left < 0)
_left = 0;
var pageSize = getItemsToShow();
//_left += pageSize;
if (_left + pageSize > totalItems)
_left = totalItems - pageSize;
console.log({ left: _left, pageSize: pageSize });
if (skipDraw)
return;
applyZoomAndPan();
}

zoom.zoomAndPanByRatio = function (zoomRatio, panRatio) {

var pageSize = getItemsToShow();
var leftOffset = Math.round(pageSize * panRatio);
var mouseLeft = _left + leftOffset;
zoom.factor(zoom.factor() * zoomRatio, true);

var finalLeft = mouseLeft;
if (zoomRatio != 1) {
var pageSize2 = getItemsToShow();
var leftOffset2 = Math.round(pageSize2 * panRatio);
finalLeft = mouseLeft - leftOffset2;
}
zoom.left(finalLeft, true);
applyZoomAndPan();
}

zoom.zoomIn = function () {
zoom.zoomAndPanByRatio(2, 0);
}

zoom.zoomOut = function () {
zoom.zoomAndPanByRatio(0.5, 0);
}

zoom.panLeft = function () {
zoom.zoomAndPanByRatio(1, -1);
}
zoom.panRight = function () {
zoom.zoomAndPanByRatio(1, 1);
}

zoom.reset = function () {
_left = 0;
_zoom2_factor = 1;
applyZoomAndPan();
}



function doZoom() {
if (deltaY != 0) {
var maxDelta = 10;
var multiply = (maxDelta + deltaY) / maxDelta;
//var factor = chart.zoom2.factor()*multiply;
//factor= Math.ceil(factor*100) / 100;
console.log({ deltaY: deltaY, multiply: multiply });
zoom.zoomAndPanByRatio(multiply, leftRatio);//0.5);//leftRatio);
deltaY = 0;
}
}

function element_mousewheel(e) {
deltaY += e.deltaY;
leftRatio = (e.offsetX - 70) / (e.currentTarget.offsetWidth - 70);
//console.log({ "e.offsetX": e.offsetX, "e.currentTarget.offsetWidth": e.currentTarget.offsetWidth, leftRatio: leftRatio });
mousewheelTimer.set(150);
e.preventDefault();
}

if (bindto != null) {
element = $(options.bindto);
if (element.mousewheel) {
mousewheelTimer = new Timer(doZoom);
element.mousewheel(element_mousewheel);
}
}

return zoom;

}
54 changes: 54 additions & 0 deletions htdocs/js/samples/zoom2.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
var chart;
function refresh() {
if (suspendRefresh)
return;
chart.load({
columns: [
["Value"].concat(zoom(column, currentZoom, "t=>Math.round(t.avg())".toLambda())),
["xColumn"].concat(zoom(xColumn, currentZoom, "t=>t[0]".toLambda())),
]
});
}

function getChart() {
return chart;
}
function main() {
var last = 0;
var max = 10000;
var column = Array.generate(max, function (i) {
return last += Math.randomInt(-10, 10);
});
var xColumn = Array.generateNumbers(0, max);
var options = {
bindto: "#divChart",
data: {
columns: [
["Value"].concat(column),
["x"].concat(xColumn),
],
type: "line",
x: "x"
},
zoom2: {
enabled: true,
}
};
chart = c3ext.generate(options);

window.setInterval(refreshStatus, 1000);

function refreshStatus() {
var zoomInfo = chart.zoom2.getZoom();
var info = {
reduced:chart.zoom2.maxItems(),
actual:(zoomInfo.currentZoom[1]-zoomInfo.currentZoom[0]),
range:zoomInfo.currentZoom[0] + "-" + zoomInfo.currentZoom[1],
total: zoomInfo.totalItems
};
$("#status").text(JSON.stringify(info, null, " "));
}

};


47 changes: 47 additions & 0 deletions htdocs/samples/zoom2.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<title>c3ext</title>
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<link rel="shortcut icon" href="/images/logo_128.ico" />
<script src="../../utils.js"></script>
<script src="http://code.jquery.com/jquery-2.0.3.min.js"></script>
<script src="https://rawgithub.com/brandonaaron/jquery-mousewheel/master/jquery.mousewheel.min.js"></script>
<script src="http://d3js.org/d3.v3.min.js" charset="utf-8"></script>
<link href="../../c3.css" rel="stylesheet" />
<script src="../../c3.js"></script>
<script src="../../c3ext.js"></script>
<script src="../js/samples/zoom2.js"></script>
</head>
<body >
<div class="container-fluid">
<h1>C3 DataSet Reduction by Zoom Level</h1>
<h2>Hackathon May 2014</h2>
<h4>By Dan-el Khen</h4>
<p>Rendering graphs in the browser has many advantages, the downside is that takes a long time to render when having large datasets. </p>
<p>This feature allows you reduces the dataset according to your current zoom level.
It allows the developer to implement the reduction algorithm in a simple function that accepts an array of values, and returns a reduced single value.
The default reducer will take the first item, but avg/sum/first/last or any other algorithm is simple to implement.
</p>
<h3>Example</h3>
<p>
In the following example, we'll render 10K data points, each time we'll reduce those to about 100 items (depending on available size on your screen),
when zooming in, the resolution of the data will be better and more accurate. This would help in showing the big picture, even when the amount of data is bigger than the numbers of pixels on the screen.
</p>
<p>Click on the buttons or scroll with your mouse wheel inside the graph to zoom and/or pan.</p>
<pre id="status"></pre>
<div>
<button >
<button >
<button >
<button >
<button >
<button >
<button >
</div>
<div id="divChart" style="height:300px"></div>
<h3>Notes</h3>
<p>Only 'columns' data format is supported for now.</p>
</div>
</body>
</html>
Loading
0