dc
Version:
A multi-dimensional charting library built to work natively with crossfilter and rendered using d3.js
186 lines (168 loc) • 6.63 kB
HTML
<html lang="en">
<head>
<title>dc.js - Range / Focus Ordinal Bar Chart</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"/>
</head>
<body>
<div class="container">
<script type="text/javascript" src="header.js"></script>
<p>This example demonstrates using range and focus charts to scroll an ordinal bar chart
with too many items. Instead of filtering, the brush only changes the view; bars are
clickable as in an ordinary ordinal bar chart.</p>
<p>Since ordinal-scale charts are not ordinarily brushable or zoomable, the example maps
the ordinal values to integers, and then maps the integers back to the values for the
axis ticks.</p>
<div id="range"></div>
<div id="focus"></div>
<div id="first-letters"></div>
<script type="text/javascript" src="../js/promise-polyfill.js"></script>
<script type="text/javascript" src="../js/fetch.umd.js"></script>
<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>
<style>
#focus .axis.x .tick text {
text-anchor: end;
transform: rotate(-60deg) translate(-10px,-12px);
}
#range.dc-chart .axis {
display: none;
}
#range svg {
display: block; /* default inline causes padding? */
}
</style>
<script type="text/javascript">
// we need a slight delay to enable debounce, but a large delay is annoying for brushing
dc.constants.EVENT_DELAY = 10;
// index a group key -> i and i -> key
function ordinal_to_linear_group(group, sort) {
var _ord2int, _int2ord;
return {
all: function() {
var ret = group.all();
_ord2int = {};
_int2ord = [];
ret.forEach(function(d, i) {
_ord2int[d.key] = i;
_int2ord[i] = d.key;
});
return ret;
},
ord2int: function(o) {
if(!_ord2int)
this.all();
return _ord2int[o];
},
int2ord: function(i) {
if(!_int2ord)
this.all();
return _int2ord[i];
}
};
}
// phrases generated with https://www.fourmilab.ch/javascrypt/pass_phrase.html
var focus, range;
d3.json("wide-ordinal.json").then(function(wide) {
// dummy crossfilter taking group data and regrouping it to the same thing
var cf = crossfilter(wide),
dimension = cf.dimension(function(d) {return d.key;}),
group = dimension.group().reduceSum(function(d) {return d.value;});
group = ordinal_to_linear_group(group);
focus = dc.barChart('#focus');
var linear_domain = [-0.5, wide.length - 0.5];
focus
.width(1000).height(330)
.margins({left: 60, top: 0, right: 10, bottom: 145})
.x(d3.scaleLinear().domain(linear_domain))
.xUnits(dc.units.integers)
.keyAccessor(kv => group.ord2int(kv.key))
.centerBar(true)
.yAxisLabel('counts')
.elasticY(true)
.brushOn(false)
.dimension(dimension)
.mouseZoomable(true)
.zoomScale([4,8])
.group(group)
.title(kv => kv.key)
.transitionDuration(0);
focus.xAxis()
.tickFormat(function(d) { return group.int2ord(d); });
// unfortunately we have to recreate click-selection, since a focus chart
// ordinarily filters to the visible area (and we don't want a brush either)
var focusFilter = [];
focus.filterHandler(function() {}); // disable built-in filtering
focus.fadeDeselectedArea = function (brushSelection) {
var _chart = this;
var bars = _chart.chartBodyG().selectAll('rect.bar');
if (focusFilter.length) {
bars.classed(dc.constants.SELECTED_CLASS, function (d) {
return focusFilter.includes(d.data.key);
});
bars.classed(dc.constants.DESELECTED_CLASS, function (d) {
return !focusFilter.includes(d.data.key);
});
} else {
bars.classed(dc.constants.SELECTED_CLASS, false);
bars.classed(dc.constants.DESELECTED_CLASS, false);
}
};
focus.on('pretransition', function(chart) {
chart.selectAll('rect.bar').on('click.ordinal-select', function(d) {
var i = focusFilter.indexOf(d.data.key);
if(i >= 0)
focusFilter.splice(i, 1);
else
focusFilter.push(d.data.key);
if(focusFilter.length)
chart.dimension().filterFunction(function(k) {
return focusFilter.includes(k);
});
else chart.dimension().filter(null);
chart.redrawGroup();
});
});
focus.on('preRedraw', function(chart) {
var domain = chart.x().domain(),
min = Math.ceil(domain[0]), max = Math.floor(domain[1]);
chart.xAxis().tickValues(d3.range(min, max+1));
});
range = dc.barChart('#range');
range.filterHandler(function() {}); // disable built-in filtering
range
.margins({left: 74, top: 0, right: 20, bottom: 2})
.width(1000).height(20)
.x(d3.scaleLinear().domain(linear_domain))
.xUnits(dc.units.integers)
.keyAccessor(kv => group.ord2int(kv.key))
.elasticY(true)
.brushOn(true)
.dimension(dimension)
.group(group)
.transitionDuration(0);
focus
.rangeChart(range)
// display a row chart of first letters, to test filtering
var letterDimension = cf.dimension(function(d) {
return d.key.split(' ').map(function(s) { return s[0]; });
}, true);
var letterGroup = letterDimension.group();
var row = dc.rowChart('#first-letters');
row
.width(1000)
.height(350)
.gap(1)
.dimension(letterDimension)
.group(letterGroup);
dc.renderAll();
focus
.focus([-0.5,60])
});
</script>
</div>
</body>
</html>