mirror of https://github.com/masayuki0812/c3.git
danelkhen
11 years ago
4 changed files with 1891 additions and 0 deletions
@ -0,0 +1,250 @@
|
||||
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 }); |
||||
|
||||
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 _zoomChanged = options.changed || function () { }; |
||||
|
||||
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); |
||||
var 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(); |
||||
} |
||||
return zoom; |
||||
|
||||
} |
@ -0,0 +1,92 @@
|
||||
///<reference path="http://localhost/cockpit/js/playground/utils.js"></script>
|
||||
|
||||
|
||||
|
||||
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", |
||||
//transition: { duration: 0 },
|
||||
data: { |
||||
columns: [ |
||||
["Value"].concat(column), |
||||
["x"].concat(xColumn), |
||||
],//column
|
||||
type: "line", |
||||
x: "x" |
||||
}, |
||||
zoom2: {
|
||||
enabled: true,
|
||||
//reducers: {
|
||||
// col: "t=>Math.round(t.avg())".toLambda(),
|
||||
// xColumns: "t=>t[0]".toLambda()
|
||||
//}
|
||||
} |
||||
}; |
||||
chart = c3ext.generate(options); |
||||
|
||||
|
||||
var deltaY = 0; |
||||
var leftRatio = 0; |
||||
var el = $("#divChart"); |
||||
var timer = new Timer(doZoom); |
||||
el.mousewheel(function (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 }); |
||||
timer.set(150); |
||||
e.preventDefault(); |
||||
//if(e.deltaY>0)
|
||||
// chart.zoom2.zoomIn();
|
||||
//else if(e.deltaY<0)
|
||||
// chart.zoom2.zoomOut();
|
||||
//console.log(e.deltaX, e.deltaY, e.deltaFactor);
|
||||
}); |
||||
|
||||
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, " ")); |
||||
} |
||||
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 }); |
||||
chart.zoom2.zoomAndPanByRatio(multiply, leftRatio);//0.5);//leftRatio);
|
||||
deltaY = 0; |
||||
} |
||||
} |
||||
|
||||
}; |
||||
|
||||
|
@ -0,0 +1,45 @@
|
||||
<!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 onload="main()"> |
||||
<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 onclick="chart.zoom2.zoomIn()">zoomIn</button> |
||||
<button onclick="chart.zoom2.zoomOut()">zoomOut</button> |
||||
<button onclick="chart.zoom2.panLeft()">panLeft</button> |
||||
<button onclick="chart.zoom2.panRight()">panRight</button> |
||||
<button onclick="chart.zoom2.enhance()">enhance</button> |
||||
<button onclick="chart.zoom2.dehance()">dehance</button> |
||||
<button onclick="chart.zoom2.reset()">reset</button> |
||||
</div> |
||||
<div id="divChart" style="height:300px"></div> |
||||
</div> |
||||
</body> |
||||
</html> |
Loading…
Reference in new issue