Skip to content

Commit adfc9c3

Browse files
committed
adding select menu widget to dc
1 parent ebcd708 commit adfc9c3

File tree

3 files changed

+306
-1
lines changed

3 files changed

+306
-1
lines changed

Gruntfile.js

+1-1
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,6 @@ module.exports = function (grunt) {
1111

1212
grunt.initConfig({
1313
pkg: grunt.file.readJSON('package.json'),
14-
1514
concat: {
1615
js: {
1716
src: jsFiles,
@@ -395,5 +394,6 @@ module.exports.jsFiles = [
395394
'src/heatmap.js',
396395
'src/d3.box.js',
397396
'src/box-plot.js',
397+
'src/select-menu.js',
398398
'src/footer.js' // NOTE: keep this last
399399
];

spec/select-menu-spec.js

+142
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,142 @@
1+
describe('dc.selectMenu', function() {
2+
var id, chart;
3+
var data, regionDimension, regionGroup;
4+
var stateDimension, stateGroup;
5+
6+
beforeEach(function () {
7+
data = crossfilter(loadDateFixture());
8+
regionDimension = data.dimension(function(d) { return d.region; });
9+
stateDimension = data.dimension(function(d) { return d.state; });
10+
11+
regionGroup = regionDimension.group();
12+
stateGroup = stateDimension.group();
13+
14+
id = 'seclect-menu';
15+
appendChartID(id);
16+
17+
chart = dc.selectMenu("#" + id);
18+
chart.dimension(stateDimension)
19+
.group(stateGroup)
20+
.transitionDuration(0);
21+
chart.render();
22+
});
23+
24+
describe('generation', function () {
25+
it('we get something', function() {
26+
expect(chart).not.toBeNull();
27+
});
28+
it('should be registered', function() {
29+
expect(dc.hasChart(chart)).toBeTruthy();
30+
});
31+
it('sets order', function() {
32+
expect(chart.order()).toBeDefined();
33+
});
34+
it('sets prompt text', function() {
35+
expect(chart.promptText()).toBe("Select all");
36+
});
37+
it('creates select tag', function() {
38+
expect(chart.selectAll("select").length).toEqual(1);
39+
});
40+
it('creates prompt option with empty value', function() {
41+
var option = chart.selectAll("option")[0][0];
42+
expect(option).toBeTruthy();
43+
expect(option.value).toEqual("");
44+
});
45+
it('creates prompt option with default prompt text', function() {
46+
var option = chart.selectAll("option")[0][0];
47+
expect(option.text).toEqual("Select all");
48+
});
49+
it('creates correct number of options', function() {
50+
expect(chart.selectAll("option.dc-select-option")[0].length).toEqual(stateGroup.all().length);
51+
});
52+
});
53+
54+
describe('select options', function(){
55+
var first_option, last_option, last_index;
56+
beforeEach(function () {
57+
last_index = stateGroup.all().length - 1;
58+
first_option = getOption(chart,0);
59+
last_option = getOption(chart,last_index);
60+
});
61+
it('display title as default option text', function() {
62+
expect(first_option.text).toEqual("California: 3");
63+
});
64+
it('text property can be changed by changing title', function() {
65+
chart.title(function(d){ return d.key }).redraw();
66+
first_option = getOption(chart,0);
67+
expect(first_option.text).toEqual("California");
68+
});
69+
it('are ordered by ascending group key by default', function(){
70+
expect(first_option.text).toEqual("California: 3");
71+
expect(last_option.text).toEqual("Ontario: 2");
72+
});
73+
it('order can be changed by changing order function', function(){
74+
chart.order(function(a,b) { return a.key.length - b.key.length; });
75+
chart.redraw();
76+
last_option = getOption(chart,last_index);
77+
expect(last_option.text).toEqual("Mississippi: 2");
78+
});
79+
})
80+
81+
describe('selecting an option', function () {
82+
it('filters dimension based on selected option\'s value', function(){
83+
chart.onChange(stateGroup.all()[0].key);
84+
expect(chart.filter()).toEqual("California");
85+
});
86+
it('replaces filter on second selection', function(){
87+
chart.onChange(stateGroup.all()[0].key);
88+
chart.onChange(stateGroup.all()[1].key);
89+
expect(chart.filter()).toEqual("Colorado");
90+
expect(chart.filters().length).toEqual(1);
91+
});
92+
it('actually filters dimension', function(){
93+
chart.onChange(stateGroup.all()[0].key);
94+
expect(regionGroup.all()[0].value).toEqual(0);
95+
expect(regionGroup.all()[3].value).toEqual(2);
96+
});
97+
it('removes filter when prompt option is selected', function(){
98+
chart.onChange('');
99+
expect(chart.hasFilter()).not.toBeTruthy();
100+
expect(regionGroup.all()[0].value).toEqual(1);
101+
});
102+
});
103+
104+
describe('redraw with existing filter', function () {
105+
it('selects option corresponding to active filter', function(){
106+
chart.onChange(stateGroup.all()[0].key);
107+
chart.redraw();
108+
expect(chart.selectAll("select")[0][0].value).toEqual("California");
109+
});
110+
});
111+
112+
describe('filterDisplayed', function () {
113+
it('only displays options whose value > 0 by default', function(){
114+
regionDimension.filter('South');
115+
chart.redraw();
116+
expect(chart.selectAll("option.dc-select-option")[0].length).toEqual(1);
117+
expect(getOption(chart,0).text).toEqual("California: 2");
118+
});
119+
it('can be overridden', function(){
120+
regionDimension.filter('South');
121+
chart.filterDisplayed(function(d) { return true; }).redraw();
122+
expect(chart.selectAll("option.dc-select-option")[0].length).toEqual(stateGroup.all().length);
123+
expect(getOption(chart, stateGroup.all().length - 1).text).toEqual("Ontario: 0");
124+
});
125+
it('retains order with filtered options', function(){
126+
regionDimension.filter('Central');
127+
chart.redraw();
128+
expect(getOption(chart,0).text).toEqual('Mississippi: 2');
129+
expect(getOption(chart,1).text).toEqual('Ontario: 1');
130+
});
131+
afterEach(function(){
132+
regionDimension.filterAll();
133+
})
134+
});
135+
136+
function getOption(chart, i){
137+
return chart.selectAll("option.dc-select-option")[0][i];
138+
}
139+
140+
141+
});
142+

src/select-menu.js

+163
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,163 @@
1+
/**
2+
## Select Menu
3+
Includes: [Base Mixin](#base-mixin)
4+
5+
The select menu is a simple widget that allows filtering a dimension by selecting an option from an HTML <select/> menu.
6+
7+
#### dc.selectMenu(parent[, chartGroup])
8+
Create a select menu instance and attach it to the given parent element.
9+
10+
Parameters:
11+
* parent : string | node | selection - any valid
12+
[d3 single selector](https://github.com/mbostock/d3/wiki/Selections#selecting-elements) specifying
13+
a dom block element such as a div; or a dom element or d3 selection.
14+
15+
* chartGroup : string (optional) - name of the chart group this chart instance should be placed in.
16+
Interaction with a chart will only trigger events and redraws within the chart's group.
17+
18+
Returns:
19+
A newly created select menu instance.
20+
21+
```js
22+
var select = dc.selectMenu('#select-container')
23+
.dimension(states)
24+
.group(stateGroup);
25+
26+
// the option text can be set via the title function
27+
// by default the option text is '`key`: `value`'
28+
select.title(function(d){
29+
return 'STATE: ' + d.key;
30+
})
31+
```
32+
33+
**/
34+
dc.selectMenu = function (parent, chartGroup) {
35+
var SELECT_CSS_CLASS = 'dc-select-menu';
36+
var OPTION_CSS_CLASS = 'dc-select-option';
37+
38+
var _chart = dc.baseMixin({});
39+
40+
var _select;
41+
var _promptText = 'Select all';
42+
var _order = function (a, b) {
43+
return _chart.keyAccessor()(a) > _chart.keyAccessor()(b) ?
44+
1 : _chart.keyAccessor()(b) > _chart.keyAccessor()(a) ?
45+
-1 : 0;
46+
};
47+
48+
var _filterDisplayed = function (d) {
49+
return _chart.valueAccessor()(d) > 0;
50+
};
51+
52+
_chart.data(function (group) {
53+
return group.all().filter(_filterDisplayed);
54+
});
55+
56+
_chart._doRender = function () {
57+
_chart.select('select').remove();
58+
_select = _chart.root().append('select').classed(SELECT_CSS_CLASS, true);
59+
_select.append('option').text(_promptText).attr('value', '');
60+
renderOptions();
61+
return _chart;
62+
};
63+
64+
_chart._doRedraw = function () {
65+
renderOptions();
66+
// select the option that corresponds to current filter
67+
if (_chart.hasFilter()) {
68+
_select.property('value', _chart.filter());
69+
} else {
70+
_select.property('value', '');
71+
}
72+
return _chart;
73+
};
74+
75+
function renderOptions () {
76+
var options = _select.selectAll('option.' + OPTION_CSS_CLASS)
77+
.data(_chart.data(), function (d) { return _chart.keyAccessor()(d); });
78+
79+
options.enter()
80+
.append('option')
81+
.classed(OPTION_CSS_CLASS, true)
82+
.attr('value', function (d) { return _chart.keyAccessor()(d); });
83+
84+
options.text(_chart.title());
85+
options.exit().remove();
86+
_select.selectAll('option.' + OPTION_CSS_CLASS).sort(_order);
87+
88+
_select.on('change', onChange);
89+
return options;
90+
}
91+
92+
function onChange () {
93+
_chart.onChange(this.value);
94+
}
95+
96+
_chart.onChange = function (val) {
97+
if (val) {
98+
_chart.replaceFilter(val);
99+
} else {
100+
_chart.filterAll();
101+
}
102+
dc.events.trigger(function () {
103+
_chart.redrawGroup();
104+
});
105+
};
106+
107+
/**
108+
#### .order([function])
109+
Get or set the function that controls the ordering of option tags in the
110+
select menu. By default options are ordered by the group key in ascending
111+
order. To order by the group's value for example an appropriate comparator
112+
function needs to be specified:
113+
```
114+
chart.order(function(a,b) {
115+
return a.value > b.value ? 1 : b.value > a.value ? -1 : 0;
116+
});
117+
```
118+
**/
119+
_chart.order = function (_) {
120+
if (!arguments.length) {
121+
return _order;
122+
}
123+
_order = _;
124+
return _chart;
125+
};
126+
127+
/**
128+
#### .promptText([value])
129+
Gets or sets the text displayed in the options used to prompt selection.
130+
The default is 'Select all'.
131+
```
132+
chart.promptText('All states');
133+
```
134+
**/
135+
_chart.promptText = function (_) {
136+
if (!arguments.length) {
137+
return _promptText;
138+
}
139+
_promptText = _;
140+
return _chart;
141+
};
142+
143+
/**
144+
#### .filterDisplayed([function])
145+
Get or set the function that filters option tags prior to display.
146+
By default options with a value of < 1 are not displayed.
147+
To always display all options override the `filterDisplayed` function:
148+
```
149+
chart.filterDisplayed(function() {
150+
return true;
151+
});
152+
```
153+
**/
154+
_chart.filterDisplayed = function (_) {
155+
if (!arguments.length) {
156+
return _filterDisplayed;
157+
}
158+
_filterDisplayed = _;
159+
return _chart;
160+
};
161+
162+
return _chart.anchor(parent, chartGroup);
163+
};

0 commit comments

Comments
 (0)