UNPKG

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
<!DOCTYPE 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>