dc
Version:
A multi-dimensional charting library built to work natively with crossfilter and rendered using d3.js
180 lines (164 loc) • 6.62 kB
HTML
<html lang="en">
<head>
<title>dc.js - Switching Time Intervals</title>
<meta charset="UTF-8">
<link rel="stylesheet" type="text/css" href="../css/bootstrap.min.css">
<link rel="stylesheet" type="text/css" href="../css/dc.css"/>
<style>
dl {
margin-left: 2em;
}
</style>
</head>
<body>
<div class="container">
<script type="text/javascript" src="header.js"></script>
<p>This example demonstrates switching between different intervals of aggregation over time.</p>
<select id="interval"></select>
<span style="font-size: large" id="counter"></span><span id="counter-text" style="display: none"> selected.</span>
<br>
<div id="test" style="min-height: 500px"></div>
<div style="clear: both">
<p>You can supply your own data source with query string parameters:
<dl>
<dt>data</dt><dd>URL to load</dd>
<dt>aggregate</dt><dd>Aggregation method: <code>average</code> or <code>total</code>
<dt>date</dt><dd>Date column name</dd>
<dt>val</dt><dd>Value column name</dd>
</dl>
</p>
</div>
<script type="text/javascript" src="../js/d3.js"></script>
<script type="text/javascript" src="../js/crossfilter.js"></script>
<script type="text/javascript" src="../js/dc.js"></script>
<script type="text/javascript">
var find_query = function() {
var _map = window.location.search.substr(1).split('&').map(function(a) {
return a.split('=');
}).reduce(function(p, v) {
if(v.length > 1)
p[v[0]] = decodeURIComponent(v[1].replace(/\+/g, " "));
else
p[v[0]] = true;
return p;
}, {});
return function(field) {
return _map[field] || null;
};
}();
var data = find_query('data') || 'monthly-move.csv',
date_col = find_query('date') || 'date',
val_col = find_query('val') || 'volume',
aggregate = find_query('aggregate') || 'average';
var chart = dc.barChart("#test"), counter = dc.numberDisplay('#counter');
d3.csv(data, function(error, posts) {
if(error)
throw new Error(error);
posts.forEach(function(d) {
d[date_col] = new Date(d[date_col]);
d[val_col] = +d[val_col];
});
var ndx = crossfilter(posts), dateDim, postsGroup;
var intervals = {
Days: d3.time.day,
Weeks: d3.time.week,
Months: d3.time.month,
Years: d3.time.year
};
var defint = find_query('interval') || 'Weeks';
d3.select('#interval').selectAll('option')
.data(Object.keys(intervals))
.enter().append('option')
.text(function(d) { return d; })
.attr('selected', function(d) { return d === defint ? '' : null; });
function setup() {
if(dateDim) {
dateDim.dispose();
group.dispose();
}
var interval = intervals[d3.select('#interval')[0][0].value];
dateDim = ndx.dimension(function(d) {return interval(d[date_col]);});
chart.xUnits(interval.range);
group = dateDim
.group().reduce(
function(p, v) {
++p.count;
p.total += v[val_col];
return p;
},
function(p, v) {
--p.count;
p.total -= v[val_col];
return p;
},
function() {
return {
count: 0,
total: 0
};
}
);
switch(aggregate) {
case 'average':
chart.valueAccessor(function(kv) {
return kv.value.total / kv.value.count;
});
break;
case 'total':
default:
chart.valueAccessor(function(kv) {
return kv.value.total;
});
}
chart.dimension(dateDim).group(group)
.transitionDuration(!find_query('unsafe') && group.all().length > 2000 ? 0 : 1000)
.render();
}
chart
.width(768)
.height(480)
.x(d3.time.scale())
.xUnits(d3.time.weeks)
.margins({left: 50, top: 0, right: 0, bottom: 20})
.elasticY(true)
.clipPadding(10);
chart.yAxis().tickFormat(d3.format('.3s'));
// this demonstrates solving elasticX manually, avoiding the
// bug where the rightmost bar/box is not displayed, #792
function calc_domain(chart) {
var min = d3.min(chart.group().all(), function(kv) { return kv.key; }),
max = d3.max(chart.group().all(), function(kv) { return kv.key; });
max = d3.time.month.offset(max, 1);
chart.x().domain([min, max]);
}
chart.on('preRender', calc_domain);
chart.on('preRedraw', calc_domain);
var countAll = ndx.groupAll(),
groupAll = ndx.groupAll().reduceSum(function(d) { return d[val_col]; });
counter
.dimension({})
.group(groupAll);
switch(aggregate) {
case 'average':
counter.valueAccessor(function(x) {
return x / countAll.value();
});
break;
case 'total':
default:
counter.valueAccessor(function(x) { return x; });
}
d3.select('#interval').on('change', function() {
setup();
});
counter.on('postRender', function () {
d3.select('#counter-text').style('display', 'inline');
});
setup();
dc.renderAll();
});
</script>
</div>
</body>
</html>