d4-rails 0.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +7 -0
- data/.gitignore +17 -0
- data/Gemfile +4 -0
- data/README.md +38 -0
- data/Rakefile +1 -0
- data/app/assets/javascripts/d4.js +2786 -0
- data/d4-rails.gemspec +20 -0
- data/lib/d4-rails.rb +12 -0
- data/lib/d4-rails/version.rb +11 -0
- metadata +102 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: 9356d45175e0db19753c5b87079e655eba12f3b6
|
4
|
+
data.tar.gz: 0e2cdc4cb8e01de00baca441f01a9b0cea49f712
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: abb18938148da445c52ff36379738868448ee083b08802925d7104f2e7307b852fa72fe350245b22adffd9a59914c5ff6a1f31154028e6cdeec3f27cf376cfc1
|
7
|
+
data.tar.gz: 135b779602e5bbf7ce473d4b33bf56a9b531ea9e33bf60006a777cc037a9881af6a030ce0b0f1075026c12a17373d37212e889085df755c8f30f39b8cbbc0723
|
data/.gitignore
ADDED
data/Gemfile
ADDED
data/README.md
ADDED
@@ -0,0 +1,38 @@
|
|
1
|
+
d4-rails
|
2
|
+
========
|
3
|
+
|
4
|
+
Integrate [d4 DSL (for d3 library)](https://github.com/heavysixer/d4) with your rails application
|
5
|
+
|
6
|
+
## Pre-requisites:
|
7
|
+
|
8
|
+
For Rails 3.2+
|
9
|
+
|
10
|
+
## Installling
|
11
|
+
|
12
|
+
Install the gem by
|
13
|
+
|
14
|
+
gem install d4-rails
|
15
|
+
|
16
|
+
OR
|
17
|
+
|
18
|
+
Include the gem in your Gemfile and run bundle install.
|
19
|
+
|
20
|
+
gem 'd4-rails'
|
21
|
+
|
22
|
+
Also require `d4.js` is your `application.js`
|
23
|
+
|
24
|
+
//= require 'd4'
|
25
|
+
|
26
|
+
That's it! :pray:
|
27
|
+
|
28
|
+
## Contributing
|
29
|
+
|
30
|
+
1. Fork it
|
31
|
+
2. Create your feature branch - `git checkout -b my-new-feature`
|
32
|
+
3. Commit your changes - `git commit -m 'Add some feature'`
|
33
|
+
4. Push to the branch - `git push origin my-new-feature`
|
34
|
+
5. Create new Pull Request
|
35
|
+
|
36
|
+
**Note**
|
37
|
+
|
38
|
+
This project uses MIT-LICENSE.
|
data/Rakefile
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
require "bundler/gem_tasks"
|
@@ -0,0 +1,2786 @@
|
|
1
|
+
/* d4-rails includes d4 library to provide DSL for d3 for Rails applications
|
2
|
+
* License: MIT
|
3
|
+
*/
|
4
|
+
/*! d4 - v0.1.0
|
5
|
+
* License: MIT Expat
|
6
|
+
* Date: 2014-02-23
|
7
|
+
*/
|
8
|
+
/*!
|
9
|
+
Functions "each", "extend", and "isFunction" based on Underscore.js 1.5.2
|
10
|
+
http://underscorejs.org
|
11
|
+
(c) 2009-2014 Jeremy Ashkenas, DocumentCloud and Investigative Reporters & Editors
|
12
|
+
Underscore may be freely distributed under the MIT license.
|
13
|
+
*/
|
14
|
+
(function() {
|
15
|
+
/*!
|
16
|
+
* global d3: false
|
17
|
+
* global d4: false
|
18
|
+
*/
|
19
|
+
|
20
|
+
'use strict';
|
21
|
+
|
22
|
+
var root = this;
|
23
|
+
var breaker = {};
|
24
|
+
|
25
|
+
// Create a safe reference to the d4 object.
|
26
|
+
var d4 = function(obj) {
|
27
|
+
if (obj instanceof d4) {
|
28
|
+
return obj;
|
29
|
+
}
|
30
|
+
if (!(this instanceof d4)) {
|
31
|
+
return new d4(obj);
|
32
|
+
}
|
33
|
+
this.d4Wrapped = obj;
|
34
|
+
};
|
35
|
+
|
36
|
+
if (typeof exports !== 'undefined') {
|
37
|
+
if (typeof module !== 'undefined' && module.exports) {
|
38
|
+
exports = module.exports = d4;
|
39
|
+
}
|
40
|
+
exports.d4 = d4;
|
41
|
+
} else {
|
42
|
+
root.d4 = d4;
|
43
|
+
}
|
44
|
+
|
45
|
+
d4.features = {};
|
46
|
+
d4.parsers = {};
|
47
|
+
|
48
|
+
var each = d4.each = d4.forEach = function(obj, iterator, context) {
|
49
|
+
var nativeForEach = Array.prototype.forEach,
|
50
|
+
i, len;
|
51
|
+
if (obj === null) {
|
52
|
+
return;
|
53
|
+
}
|
54
|
+
if (nativeForEach && obj.forEach === nativeForEach) {
|
55
|
+
obj.forEach(iterator, context);
|
56
|
+
} else if (obj.length === +obj.length) {
|
57
|
+
for (i = 0, len = obj.length; i < len; i++) {
|
58
|
+
if (iterator.call(context, obj[i], i, obj) === breaker) {
|
59
|
+
return;
|
60
|
+
}
|
61
|
+
}
|
62
|
+
} else {
|
63
|
+
var keys = d3.keys(obj);
|
64
|
+
for (i = 0, len = keys.length; i < len; i++) {
|
65
|
+
if (iterator.call(context, obj[keys[i]], keys[i], obj) === breaker) {
|
66
|
+
return;
|
67
|
+
}
|
68
|
+
}
|
69
|
+
}
|
70
|
+
};
|
71
|
+
|
72
|
+
var isFunction = function(obj) {
|
73
|
+
return !!(obj && obj.constructor && obj.call && obj.apply);
|
74
|
+
};
|
75
|
+
|
76
|
+
var isNotFunction = function(obj) {
|
77
|
+
return !isFunction(obj);
|
78
|
+
};
|
79
|
+
|
80
|
+
var assert = function(message) {
|
81
|
+
throw new Error('[d4] ' + message);
|
82
|
+
};
|
83
|
+
|
84
|
+
var validateBuilder = function(builder) {
|
85
|
+
each(['configure'], function(funct) {
|
86
|
+
if (!builder[funct] || isNotFunction(builder[funct])) {
|
87
|
+
assert('The supplied builder does not have a ' + funct + ' function');
|
88
|
+
}
|
89
|
+
});
|
90
|
+
return builder;
|
91
|
+
};
|
92
|
+
|
93
|
+
var assignDefaultBuilder = function(defaultBuilder) {
|
94
|
+
if (!this.builder) {
|
95
|
+
this.builder = validateBuilder(defaultBuilder.bind(this)());
|
96
|
+
}
|
97
|
+
return this;
|
98
|
+
};
|
99
|
+
|
100
|
+
var assignDefaults = function(config, defaultBuilder) {
|
101
|
+
if (!defaultBuilder) {
|
102
|
+
assert('No builder defined');
|
103
|
+
}
|
104
|
+
var opts = d4.merge({
|
105
|
+
width: 400,
|
106
|
+
height: 400,
|
107
|
+
features: {},
|
108
|
+
mixins: [],
|
109
|
+
xKey: 'x',
|
110
|
+
yKey: 'y',
|
111
|
+
valueKey: 'y',
|
112
|
+
margin: {
|
113
|
+
top: 20,
|
114
|
+
right: 20,
|
115
|
+
bottom: 20,
|
116
|
+
left: 40
|
117
|
+
}
|
118
|
+
}, config);
|
119
|
+
assignDefaultBuilder.bind(opts)(defaultBuilder);
|
120
|
+
opts.accessors = ['margin', 'width', 'height', 'x', 'y', 'xKey', 'yKey', 'valueKey'].concat(config.accessors || []);
|
121
|
+
return opts;
|
122
|
+
};
|
123
|
+
|
124
|
+
/*!
|
125
|
+
d3 allows events to be bound to selections using the `#on()` function. We
|
126
|
+
want to allow the developer to bind to these events transparently. However,
|
127
|
+
we are not actually dealing with the d3 selection itself and so we need to
|
128
|
+
create this proxy which passes any custom events on to the correct selection.
|
129
|
+
For more information see the #selection.on documentation for d3:
|
130
|
+
https://github.com/mbostock/d3/wiki/Selections#wiki-animation--interaction
|
131
|
+
*/
|
132
|
+
var addEventsProxy = function(feature, selection){
|
133
|
+
if(selection){
|
134
|
+
each(d3.keys(feature._proxiedFunctions), function(key){
|
135
|
+
each(feature._proxiedFunctions[key], function(proxiedArgs){
|
136
|
+
selection[key].apply(selection, proxiedArgs)
|
137
|
+
});
|
138
|
+
})
|
139
|
+
}
|
140
|
+
};
|
141
|
+
|
142
|
+
var linkFeatures = function(opts, data) {
|
143
|
+
opts.mixins.forEach(function(name) {
|
144
|
+
var selection = opts.features[name].render.bind(opts)(opts.features[name], data);
|
145
|
+
addEventsProxy(opts.features[name], selection);
|
146
|
+
});
|
147
|
+
};
|
148
|
+
|
149
|
+
var build = function(opts, data) {
|
150
|
+
if (opts.builder) {
|
151
|
+
opts.builder.configure(opts, data);
|
152
|
+
linkFeatures(opts, data);
|
153
|
+
} else {
|
154
|
+
assert('No builder defined');
|
155
|
+
}
|
156
|
+
};
|
157
|
+
|
158
|
+
var scaffoldChart = function(selection, data) {
|
159
|
+
this.svg = d3.select(selection).selectAll('svg').data([data]);
|
160
|
+
this.featuresGroup = this.svg.enter().append('svg').append('g')
|
161
|
+
.attr('class', 'featuresGroup')
|
162
|
+
.attr('transform', 'translate(' + this.margin.left + ',' + this.margin.top + ')');
|
163
|
+
this.svg.attr('width', this.width).attr('height', this.height).attr('class', 'd4');
|
164
|
+
this.svg.append('defs');
|
165
|
+
};
|
166
|
+
|
167
|
+
var applyScaffold = function(opts) {
|
168
|
+
return function(selection) {
|
169
|
+
selection.each(function(data) {
|
170
|
+
scaffoldChart.bind(opts, this)(data);
|
171
|
+
build(opts, data);
|
172
|
+
});
|
173
|
+
};
|
174
|
+
};
|
175
|
+
|
176
|
+
var extractOverrides = function(feature) {
|
177
|
+
if (feature.overrides) {
|
178
|
+
return feature.overrides(this);
|
179
|
+
} else {
|
180
|
+
return {};
|
181
|
+
}
|
182
|
+
};
|
183
|
+
|
184
|
+
var addToMixins = function(mixins, name, index){
|
185
|
+
if (typeof index !== 'undefined') {
|
186
|
+
index = Math.max(Math.min(index, mixins.length), 0);
|
187
|
+
mixins.splice(index, 0, name);
|
188
|
+
} else {
|
189
|
+
mixins.push(name);
|
190
|
+
}
|
191
|
+
};
|
192
|
+
|
193
|
+
var assignD3SelectionProxy = function(feature){
|
194
|
+
feature._proxiedFunctions = {
|
195
|
+
on : []
|
196
|
+
};
|
197
|
+
feature.on = function(){
|
198
|
+
feature._proxiedFunctions.on.push(Array.prototype.slice.call(arguments));
|
199
|
+
};
|
200
|
+
};
|
201
|
+
|
202
|
+
/*!
|
203
|
+
FIXME: see fixme note related to the chart accessor functions, the same
|
204
|
+
inconsistency applies here.
|
205
|
+
*/
|
206
|
+
var assignMixinAccessors = function(feature){
|
207
|
+
assignD3SelectionProxy(feature);
|
208
|
+
var accessors = feature.accessors;
|
209
|
+
if (accessors) {
|
210
|
+
each(d3.keys(accessors), function(functName) {
|
211
|
+
feature[functName] = function(attr) {
|
212
|
+
if (!arguments.length) {
|
213
|
+
return feature.accessors[functName];
|
214
|
+
}
|
215
|
+
feature.accessors[functName] = attr;
|
216
|
+
return feature;
|
217
|
+
}.bind(this);
|
218
|
+
});
|
219
|
+
}
|
220
|
+
};
|
221
|
+
|
222
|
+
var mixin = function(feature, index) {
|
223
|
+
if (!feature) {
|
224
|
+
assert('You need to supply an object to mixin.');
|
225
|
+
}
|
226
|
+
var name = d3.keys(feature)[0];
|
227
|
+
var overrides = extractOverrides.bind(this)(feature, name);
|
228
|
+
feature[name] = d4.merge(feature[name](name), overrides);
|
229
|
+
d4.extend(this.features, feature);
|
230
|
+
addToMixins(this.mixins, name, index);
|
231
|
+
assignMixinAccessors(this.features[name]);
|
232
|
+
};
|
233
|
+
|
234
|
+
var mixout = function(name) {
|
235
|
+
if (!name) {
|
236
|
+
assert('A name is required in order to mixout a chart feature.');
|
237
|
+
}
|
238
|
+
|
239
|
+
delete this.features[name];
|
240
|
+
this.mixins = this.mixins.filter(function(val) {
|
241
|
+
return val !== name;
|
242
|
+
});
|
243
|
+
};
|
244
|
+
|
245
|
+
var using = function(name, funct) {
|
246
|
+
var feature = this.features[name];
|
247
|
+
if (isNotFunction(funct)) {
|
248
|
+
assert('You must supply a continuation function in order to use a chart feature.');
|
249
|
+
}
|
250
|
+
if (!feature) {
|
251
|
+
assert('Could not find feature: "' + name + '", maybe you forgot to mix it in?');
|
252
|
+
} else {
|
253
|
+
funct.bind(this)(feature);
|
254
|
+
}
|
255
|
+
};
|
256
|
+
|
257
|
+
d4.baseChart = function(config, defaultBuilder) {
|
258
|
+
var opts = assignDefaults(config, defaultBuilder);
|
259
|
+
var chart = applyScaffold(opts);
|
260
|
+
|
261
|
+
/*!
|
262
|
+
FIXME: d4 wraps the inner property object `opts` in a series of class
|
263
|
+
functions. For example: `chart.width(300)` will set the internal
|
264
|
+
`opts.width` property to 300. Additionally chart.width() will return 300.
|
265
|
+
However, this behavior creates ambiguity in API because it is unclear to the
|
266
|
+
developer which accessors require functions and which can simply supply
|
267
|
+
values. Ideally the API should support something like this:
|
268
|
+
chart.xKey('foo') or chart.xKey(function(){ return 'foo'; })
|
269
|
+
Presently only the latter is allowed, which is confusing.
|
270
|
+
*/
|
271
|
+
chart.accessors = opts.accessors;
|
272
|
+
chart.accessors.forEach(function(accessor) {
|
273
|
+
chart[accessor] = function(attr) {
|
274
|
+
if (!arguments.length) {
|
275
|
+
return opts[accessor];
|
276
|
+
}
|
277
|
+
opts[accessor] = attr;
|
278
|
+
return chart;
|
279
|
+
};
|
280
|
+
});
|
281
|
+
|
282
|
+
/**
|
283
|
+
* The heart of the d4 API is the `using` function, which allows you to
|
284
|
+
* contextually modify attributes of the chart or one of its features.
|
285
|
+
*
|
286
|
+
*##### Examples
|
287
|
+
*
|
288
|
+
* chart.mixin({ 'zeroLine': d4.features.referenceLine })
|
289
|
+
* .using('zeroLine', function(zero) {
|
290
|
+
* zero
|
291
|
+
* .x1(function() {
|
292
|
+
* return this.x(0);
|
293
|
+
* })
|
294
|
+
* });
|
295
|
+
*
|
296
|
+
* @param {String} name - accessor name for chart feature.
|
297
|
+
* @param {Function} funct - function which will perform the modifcation.
|
298
|
+
*/
|
299
|
+
chart.using = function(name, funct) {
|
300
|
+
using.bind(opts)(name, funct);
|
301
|
+
return chart;
|
302
|
+
};
|
303
|
+
|
304
|
+
/**
|
305
|
+
* Specifies a feature to be mixed into a given chart.
|
306
|
+
* The feature is an object where the key represents the feature name, and a
|
307
|
+
* value which is a function that when invoked returns a d4 feature object.
|
308
|
+
*
|
309
|
+
*##### Examples
|
310
|
+
*
|
311
|
+
* // Mix in a feature at a specific depth
|
312
|
+
* chart.mixin({ 'grid': d4.features.grid }, 0)
|
313
|
+
*
|
314
|
+
* chart.mixin({ 'zeroLine': d4.features.referenceLine })
|
315
|
+
*
|
316
|
+
* @param {Object} feature - an object describing the feature to mix in.
|
317
|
+
* @param {Integer} index - an optional number to specify the insertion layer.
|
318
|
+
*/
|
319
|
+
chart.mixin = function(feature, index) {
|
320
|
+
mixin.bind(opts)(feature, index);
|
321
|
+
return chart;
|
322
|
+
};
|
323
|
+
|
324
|
+
/**
|
325
|
+
* Specifies an existing feature of a chart to be removed (mixed out).
|
326
|
+
*
|
327
|
+
*##### Examples
|
328
|
+
*
|
329
|
+
* // Mixout the yAxis which is provided as a default
|
330
|
+
* var chart = d4.columnChart()
|
331
|
+
* .mixout('yAxis');
|
332
|
+
*
|
333
|
+
* // Now test that the feature has been removed.
|
334
|
+
* console.log(chart.features());
|
335
|
+
* => ["bars", "barLabels", "xAxis"]
|
336
|
+
*
|
337
|
+
* @param {String} name - accessor name for chart feature.
|
338
|
+
*/
|
339
|
+
chart.mixout = function(feature, index) {
|
340
|
+
mixout.bind(opts)(feature, index);
|
341
|
+
return chart;
|
342
|
+
};
|
343
|
+
|
344
|
+
/**
|
345
|
+
* Specifies an object, which d4 uses to initialize the chart with. By default
|
346
|
+
* d4 expects charts to return a builder object, which will be used to
|
347
|
+
* configure defaults for the chart. Typically this means determining the
|
348
|
+
* the default value for the various axes. This accessor allows you to
|
349
|
+
* override the existing builder provided by a chart and use your own.
|
350
|
+
*
|
351
|
+
*##### Examples
|
352
|
+
*
|
353
|
+
* myChart.builder = function(chart, data){
|
354
|
+
* return {
|
355
|
+
* configure: function(chart, data) {
|
356
|
+
* configureScales.bind(this)(chart, data);
|
357
|
+
* }
|
358
|
+
* };
|
359
|
+
* };
|
360
|
+
*
|
361
|
+
* @param {Function} funct - function which returns a builder object.
|
362
|
+
*/
|
363
|
+
chart.builder = function(funct) {
|
364
|
+
validateBuilder(funct.bind(chart)(opts));
|
365
|
+
return chart;
|
366
|
+
};
|
367
|
+
|
368
|
+
/**
|
369
|
+
* To see what features are currently mixed into your chart you can use
|
370
|
+
* this method.
|
371
|
+
*
|
372
|
+
*##### Examples
|
373
|
+
*
|
374
|
+
* // Mixout the yAxis which is provided as a default
|
375
|
+
* var chart = d4.columnChart()
|
376
|
+
* .mixout('yAxis');
|
377
|
+
*
|
378
|
+
* // Now test that the feature has been removed.
|
379
|
+
* console.log(chart.features());
|
380
|
+
* => ["bars", "barLabels", "xAxis"]
|
381
|
+
*
|
382
|
+
*/
|
383
|
+
chart.features = function() {
|
384
|
+
return opts.mixins;
|
385
|
+
};
|
386
|
+
|
387
|
+
/**
|
388
|
+
* Based on D3's own functor function.
|
389
|
+
* > If the specified value is a function, returns the specified value. Otherwise,
|
390
|
+
* > returns a function that returns the specified value. This method is used
|
391
|
+
* > internally as a lazy way of upcasting constant values to functions, in
|
392
|
+
* > cases where a property may be specified either as a function or a constant.
|
393
|
+
* > For example, many D3 layouts allow properties to be specified this way,
|
394
|
+
* > and it simplifies the implementation if we automatically convert constant
|
395
|
+
* > values to functions.
|
396
|
+
*
|
397
|
+
* @param {Varies} funct - An function or other variable to be wrapped in a function
|
398
|
+
*/
|
399
|
+
d4.functor = function(funct) {
|
400
|
+
return isFunction(funct) ? funct : function() {
|
401
|
+
return funct;
|
402
|
+
};
|
403
|
+
};
|
404
|
+
|
405
|
+
return chart;
|
406
|
+
};
|
407
|
+
|
408
|
+
d4.merge = function(options, overrides) {
|
409
|
+
return d4.extend(d4.extend({}, options), overrides);
|
410
|
+
};
|
411
|
+
|
412
|
+
d4.extend = function(obj) {
|
413
|
+
each(Array.prototype.slice.call(arguments, 1), function(source) {
|
414
|
+
if (source) {
|
415
|
+
for (var prop in source) {
|
416
|
+
if (source[prop] && source[prop].constructor &&
|
417
|
+
source[prop].constructor === Object) {
|
418
|
+
obj[prop] = obj[prop] || {};
|
419
|
+
d4.extend(obj[prop], source[prop]);
|
420
|
+
} else {
|
421
|
+
obj[prop] = source[prop];
|
422
|
+
}
|
423
|
+
}
|
424
|
+
}
|
425
|
+
});
|
426
|
+
return obj;
|
427
|
+
};
|
428
|
+
|
429
|
+
}).call(this);
|
430
|
+
|
431
|
+
(function() {
|
432
|
+
/*!
|
433
|
+
* global d3: false
|
434
|
+
* global d4: false
|
435
|
+
*/
|
436
|
+
'use strict';
|
437
|
+
var columnChartBuilder = function() {
|
438
|
+
var configureX = function(chart, data) {
|
439
|
+
if (!chart.x) {
|
440
|
+
chart.xRoundBands = chart.xRoundBands || 0.3;
|
441
|
+
chart.x = d3.scale.ordinal()
|
442
|
+
.domain(data.map(function(d) {
|
443
|
+
return d[this.xKey];
|
444
|
+
}.bind(chart)))
|
445
|
+
.rangeRoundBands([0, chart.width - chart.margin.left - chart.margin.right], chart.xRoundBands);
|
446
|
+
}
|
447
|
+
};
|
448
|
+
|
449
|
+
var configureY = function(chart, data) {
|
450
|
+
if (!chart.y) {
|
451
|
+
chart.y = d3.scale.linear()
|
452
|
+
.domain(d3.extent(data, function(d) {
|
453
|
+
return d[this.yKey];
|
454
|
+
}.bind(chart)));
|
455
|
+
}
|
456
|
+
chart.y.range([chart.height - chart.margin.top - chart.margin.bottom, 0])
|
457
|
+
.clamp(true)
|
458
|
+
.nice();
|
459
|
+
};
|
460
|
+
|
461
|
+
var configureScales = function(chart, data) {
|
462
|
+
configureX.bind(this)(chart, data);
|
463
|
+
configureY.bind(this)(chart, data);
|
464
|
+
};
|
465
|
+
|
466
|
+
var builder = {
|
467
|
+
configure: function(chart, data) {
|
468
|
+
configureScales.bind(this)(chart, data);
|
469
|
+
}
|
470
|
+
};
|
471
|
+
return builder;
|
472
|
+
};
|
473
|
+
|
474
|
+
/*
|
475
|
+
The column chart has two axes (`x` and `y`). By default the column chart expects
|
476
|
+
linear values for the `y` and ordinal values on the `x`. The basic column chart
|
477
|
+
has four default features:
|
478
|
+
|
479
|
+
* **bars** - series bars
|
480
|
+
* **barLabels** - data labels above the bars
|
481
|
+
* **xAxis** - the axis for the x dimension
|
482
|
+
* **yAxis** - the axis for the y dimension
|
483
|
+
|
484
|
+
##### Example Usage
|
485
|
+
|
486
|
+
var data = [
|
487
|
+
{ x: '2010', y:-10 },
|
488
|
+
{ x: '2011', y:20 },
|
489
|
+
{ x: '2012', y:30 },
|
490
|
+
{ x: '2013', y:40 },
|
491
|
+
{ x: '2014', y:50 },
|
492
|
+
];
|
493
|
+
var chart = d4.columnChart();
|
494
|
+
d3.select('#example')
|
495
|
+
.datum(data)
|
496
|
+
.call(chart);
|
497
|
+
|
498
|
+
By default d4 expects a series object, which uses the following format: `{ x : '2010', y : 10 }`.
|
499
|
+
The default format may not be desired and so we'll override it:
|
500
|
+
|
501
|
+
var data = [
|
502
|
+
['2010', -10],
|
503
|
+
['2011', 20],
|
504
|
+
['2012', 30],
|
505
|
+
['2013', 40],
|
506
|
+
['2014', 50]
|
507
|
+
];
|
508
|
+
var chart = d4.columnChart()
|
509
|
+
.xKey(0)
|
510
|
+
.yKey(1);
|
511
|
+
|
512
|
+
d3.select('#example')
|
513
|
+
.datum(data)
|
514
|
+
.call(chart);
|
515
|
+
|
516
|
+
*/
|
517
|
+
d4.columnChart = function columnChart() {
|
518
|
+
var chart = d4.baseChart({}, columnChartBuilder);
|
519
|
+
[{
|
520
|
+
'bars': d4.features.columnSeries
|
521
|
+
}, {
|
522
|
+
'barLabels': d4.features.columnLabels
|
523
|
+
}, {
|
524
|
+
'xAxis': d4.features.xAxis
|
525
|
+
}, {
|
526
|
+
'yAxis': d4.features.yAxis
|
527
|
+
}].forEach(function(feature) {
|
528
|
+
chart.mixin(feature);
|
529
|
+
});
|
530
|
+
return chart;
|
531
|
+
};
|
532
|
+
}).call(this);
|
533
|
+
|
534
|
+
(function() {
|
535
|
+
/*!
|
536
|
+
* global d3: false
|
537
|
+
* global d4: false
|
538
|
+
*/
|
539
|
+
'use strict';
|
540
|
+
|
541
|
+
var groupedColumnChartBuilder = function() {
|
542
|
+
var extractValues = function(data, key) {
|
543
|
+
var values = data.map(function(obj){
|
544
|
+
return obj.values.map(function(i){
|
545
|
+
return i[key];
|
546
|
+
}.bind(this));
|
547
|
+
}.bind(this));
|
548
|
+
return d3.merge(values);
|
549
|
+
};
|
550
|
+
|
551
|
+
var configureX = function(chart, data) {
|
552
|
+
if (!chart.x) {
|
553
|
+
var xData = extractValues(data, chart.xKey);
|
554
|
+
chart.xRoundBands = chart.xRoundBands || 0.3;
|
555
|
+
chart.x = d3.scale.ordinal()
|
556
|
+
.domain(xData)
|
557
|
+
.rangeRoundBands([0, chart.width - chart.margin.left - chart.margin.right], chart.xRoundBands);
|
558
|
+
}
|
559
|
+
};
|
560
|
+
|
561
|
+
var configureY = function(chart, data) {
|
562
|
+
if (!chart.y) {
|
563
|
+
var yData = extractValues(data, chart.yKey);
|
564
|
+
var ext = d3.extent(yData);
|
565
|
+
chart.y = d3.scale.linear().domain([Math.min(0, ext[0]),ext[1]]);
|
566
|
+
}
|
567
|
+
chart.y.range([chart.height - chart.margin.top - chart.margin.bottom, 0])
|
568
|
+
.clamp(true)
|
569
|
+
.nice();
|
570
|
+
};
|
571
|
+
|
572
|
+
var configureScales = function(chart, data) {
|
573
|
+
configureX.bind(this)(chart, data);
|
574
|
+
configureY.bind(this)(chart, data);
|
575
|
+
};
|
576
|
+
|
577
|
+
var builder = {
|
578
|
+
configure: function(chart, data) {
|
579
|
+
configureScales.bind(this)(chart, data);
|
580
|
+
}
|
581
|
+
};
|
582
|
+
return builder;
|
583
|
+
};
|
584
|
+
|
585
|
+
/*
|
586
|
+
The grouped column chart is used to compare a series of data elements grouped
|
587
|
+
along the xAxis. This chart is often useful in conjunction with a stacked column
|
588
|
+
chart because they can use the same data series, and where the stacked column highlights
|
589
|
+
the sum of the data series across an axis the grouped column can be used to show the
|
590
|
+
relative distribution.
|
591
|
+
|
592
|
+
* **bars** - series bars
|
593
|
+
* **barLabels** - data labels above the bars
|
594
|
+
* **groupsOf** - an integer representing the number of columns in each group
|
595
|
+
* **xAxis** - the axis for the x dimension
|
596
|
+
* **yAxis** - the axis for the y dimension
|
597
|
+
|
598
|
+
##### Example Usage
|
599
|
+
|
600
|
+
var data = [
|
601
|
+
{ year: '2010', unitsSold:-100, salesman : 'Bob' },
|
602
|
+
{ year: '2011', unitsSold:200, salesman : 'Bob' },
|
603
|
+
{ year: '2012', unitsSold:300, salesman : 'Bob' },
|
604
|
+
{ year: '2013', unitsSold:400, salesman : 'Bob' },
|
605
|
+
{ year: '2014', unitsSold:500, salesman : 'Bob' },
|
606
|
+
{ year: '2010', unitsSold:100, salesman : 'Gina' },
|
607
|
+
{ year: '2011', unitsSold:100, salesman : 'Gina' },
|
608
|
+
{ year: '2012', unitsSold:-100, salesman : 'Gina' },
|
609
|
+
{ year: '2013', unitsSold:500, salesman : 'Gina' },
|
610
|
+
{ year: '2014', unitsSold:600, salesman : 'Gina' },
|
611
|
+
{ year: '2010', unitsSold:400, salesman : 'Average' },
|
612
|
+
{ year: '2011', unitsSold:0, salesman : 'Average' },
|
613
|
+
{ year: '2012', unitsSold:400, salesman : 'Average' },
|
614
|
+
{ year: '2013', unitsSold:400, salesman : 'Average' },
|
615
|
+
{ year: '2014', unitsSold:400, salesman : 'Average' }
|
616
|
+
];
|
617
|
+
|
618
|
+
var parsedData = d4.parsers.nestedGroup()
|
619
|
+
.x('year')
|
620
|
+
.y('unitsSold')
|
621
|
+
.value('unitsSold')(data);
|
622
|
+
|
623
|
+
var chart = d4.groupedColumnChart()
|
624
|
+
.width($('#example').width())
|
625
|
+
.xKey('year')
|
626
|
+
.yKey('unitsSold')
|
627
|
+
.groupsOf(parsedData.data[0].values.length);
|
628
|
+
|
629
|
+
d3.select('#example')
|
630
|
+
.datum(parsedData.data)
|
631
|
+
.call(chart);
|
632
|
+
|
633
|
+
*/
|
634
|
+
d4.groupedColumnChart = function groupedColumnChart() {
|
635
|
+
var chart = d4.baseChart({
|
636
|
+
accessors: ['groupsOf'],
|
637
|
+
groupsOf: 1
|
638
|
+
}, groupedColumnChartBuilder);
|
639
|
+
[{
|
640
|
+
'bars': d4.features.groupedColumnSeries
|
641
|
+
}, {
|
642
|
+
'columnLabels': d4.features.groupedColumnLabels
|
643
|
+
}, {
|
644
|
+
'xAxis': d4.features.xAxis
|
645
|
+
}, {
|
646
|
+
'yAxis': d4.features.yAxis
|
647
|
+
}].forEach(function(feature) {
|
648
|
+
chart.mixin(feature);
|
649
|
+
});
|
650
|
+
return chart;
|
651
|
+
};
|
652
|
+
}).call(this);
|
653
|
+
|
654
|
+
(function() {
|
655
|
+
/*!
|
656
|
+
* global d3: false
|
657
|
+
* global d4: false
|
658
|
+
*/
|
659
|
+
'use strict';
|
660
|
+
|
661
|
+
var lineChartBuilder = function() {
|
662
|
+
var extractValues = function(data, key) {
|
663
|
+
var values = data.map(function(obj){
|
664
|
+
return obj.values.map(function(i){
|
665
|
+
return i[key];
|
666
|
+
}.bind(this));
|
667
|
+
}.bind(this));
|
668
|
+
return d3.merge(values);
|
669
|
+
};
|
670
|
+
|
671
|
+
var configureX = function(chart, data) {
|
672
|
+
if (!chart.x) {
|
673
|
+
var xData = extractValues(data, chart.xKey);
|
674
|
+
chart.xRoundBands = chart.xRoundBands || 0.3;
|
675
|
+
chart.x = d3.scale.ordinal()
|
676
|
+
.domain(xData)
|
677
|
+
.rangeRoundBands([0, chart.width - chart.margin.left - chart.margin.right], chart.xRoundBands);
|
678
|
+
}
|
679
|
+
};
|
680
|
+
|
681
|
+
var configureY = function(chart, data) {
|
682
|
+
if (!chart.y) {
|
683
|
+
var yData = extractValues(data, chart.yKey);
|
684
|
+
var ext = d3.extent(yData);
|
685
|
+
chart.y = d3.scale.linear().domain(ext);
|
686
|
+
}
|
687
|
+
chart.y.range([chart.height - chart.margin.top - chart.margin.bottom, 0])
|
688
|
+
.clamp(true)
|
689
|
+
.nice();
|
690
|
+
};
|
691
|
+
|
692
|
+
var configureScales = function(chart, data) {
|
693
|
+
configureX.bind(this)(chart, data);
|
694
|
+
configureY.bind(this)(chart, data);
|
695
|
+
};
|
696
|
+
|
697
|
+
var builder = {
|
698
|
+
configure: function(chart, data) {
|
699
|
+
configureScales.bind(this)(chart, data);
|
700
|
+
}
|
701
|
+
};
|
702
|
+
return builder;
|
703
|
+
};
|
704
|
+
|
705
|
+
/*
|
706
|
+
The line series chart is used to compare a series of data elements grouped
|
707
|
+
along the xAxis.
|
708
|
+
|
709
|
+
* **lineSeries** - series lines
|
710
|
+
* **lineSeriesLabels** - data labels beside the lines
|
711
|
+
* **xAxis** - the axis for the x dimension
|
712
|
+
* **yAxis** - the axis for the y dimension
|
713
|
+
|
714
|
+
##### Example Usage
|
715
|
+
|
716
|
+
var data = [
|
717
|
+
{ year: '2010', unitsSold:-100, salesman : 'Bob' },
|
718
|
+
{ year: '2011', unitsSold:200, salesman : 'Bob' },
|
719
|
+
{ year: '2012', unitsSold:300, salesman : 'Bob' },
|
720
|
+
{ year: '2013', unitsSold:400, salesman : 'Bob' },
|
721
|
+
{ year: '2014', unitsSold:500, salesman : 'Bob' },
|
722
|
+
{ year: '2010', unitsSold:100, salesman : 'Gina' },
|
723
|
+
{ year: '2011', unitsSold:100, salesman : 'Gina' },
|
724
|
+
{ year: '2012', unitsSold:-100, salesman : 'Gina' },
|
725
|
+
{ year: '2013', unitsSold:500, salesman : 'Gina' },
|
726
|
+
{ year: '2014', unitsSold:600, salesman : 'Gina' },
|
727
|
+
{ year: '2010', unitsSold:400, salesman : 'Average' },
|
728
|
+
{ year: '2011', unitsSold:0, salesman : 'Average' },
|
729
|
+
{ year: '2012', unitsSold:400, salesman : 'Average' },
|
730
|
+
{ year: '2013', unitsSold:400, salesman : 'Average' },
|
731
|
+
{ year: '2014', unitsSold:400, salesman : 'Average' }
|
732
|
+
];
|
733
|
+
var parsedData = d4.parsers.nestedGroup()
|
734
|
+
.x(function(){
|
735
|
+
return 'year';
|
736
|
+
})
|
737
|
+
.nestKey(function(){
|
738
|
+
return 'salesman';
|
739
|
+
})
|
740
|
+
.y(function(){
|
741
|
+
return 'unitsSold';
|
742
|
+
})
|
743
|
+
.value(function(){
|
744
|
+
return 'unitsSold';
|
745
|
+
})(data);
|
746
|
+
|
747
|
+
var chart = d4.lineChart()
|
748
|
+
.width($('#example').width())
|
749
|
+
.xKey('year')
|
750
|
+
.yKey('unitsSold');
|
751
|
+
|
752
|
+
d3.select('#example')
|
753
|
+
.datum(parsedData.data)
|
754
|
+
.call(chart);
|
755
|
+
|
756
|
+
*/
|
757
|
+
d4.lineChart = function lineChart() {
|
758
|
+
var chart = d4.baseChart({}, lineChartBuilder);
|
759
|
+
[{
|
760
|
+
'linesSeries': d4.features.lineSeries
|
761
|
+
},{
|
762
|
+
'linesSeriesLabels': d4.features.lineSeriesLabels
|
763
|
+
}, {
|
764
|
+
'xAxis': d4.features.xAxis
|
765
|
+
}, {
|
766
|
+
'yAxis': d4.features.yAxis
|
767
|
+
}].forEach(function(feature) {
|
768
|
+
chart.mixin(feature);
|
769
|
+
});
|
770
|
+
return chart;
|
771
|
+
};
|
772
|
+
}).call(this);
|
773
|
+
(function() {
|
774
|
+
/*!
|
775
|
+
* global d3: false
|
776
|
+
* global d4: false
|
777
|
+
*/
|
778
|
+
'use strict';
|
779
|
+
|
780
|
+
var rowChartBuilder = function() {
|
781
|
+
var configureX = function(chart, data) {
|
782
|
+
if (!chart.x) {
|
783
|
+
chart.x = d3.scale.linear()
|
784
|
+
.domain(d3.extent(data, function(d) {
|
785
|
+
return d[chart.xKey];
|
786
|
+
}.bind(this)));
|
787
|
+
}
|
788
|
+
chart.x.range([0, chart.width - chart.margin.right - chart.margin.left])
|
789
|
+
.clamp(true)
|
790
|
+
.nice();
|
791
|
+
};
|
792
|
+
|
793
|
+
var configureY = function(chart, data) {
|
794
|
+
if (!chart.y) {
|
795
|
+
chart.yRoundBands = chart.yRoundBands || 0.3;
|
796
|
+
chart.y = d3.scale.ordinal()
|
797
|
+
.domain(data.map(function(d) {
|
798
|
+
return d[chart.yKey];
|
799
|
+
}.bind(this)))
|
800
|
+
.rangeRoundBands([chart.height - chart.margin.top - chart.margin.bottom, 0], chart.yRoundBands);
|
801
|
+
}
|
802
|
+
};
|
803
|
+
|
804
|
+
var configureScales = function(chart, data) {
|
805
|
+
configureX.bind(this)(chart, data);
|
806
|
+
configureY.bind(this)(chart, data);
|
807
|
+
};
|
808
|
+
|
809
|
+
var builder = {
|
810
|
+
configure: function(chart, data) {
|
811
|
+
configureScales.bind(this)(chart, data);
|
812
|
+
}
|
813
|
+
};
|
814
|
+
return builder;
|
815
|
+
};
|
816
|
+
|
817
|
+
/*
|
818
|
+
The row chart has two axes (`x` and `y`). By default the column chart expects
|
819
|
+
linear scale values for the `x` and ordinal scale values on the `y`. The basic column chart
|
820
|
+
has four default features:
|
821
|
+
|
822
|
+
* **bars** - series bars
|
823
|
+
* **rowLabels** - data labels to the right of the bars
|
824
|
+
* **xAxis** - the axis for the x dimension
|
825
|
+
* **yAxis** - the axis for the y dimension
|
826
|
+
|
827
|
+
##### Example Usage
|
828
|
+
|
829
|
+
var data = [
|
830
|
+
{ y: '2010', x:-10 },
|
831
|
+
{ y: '2011', x:20 },
|
832
|
+
{ y: '2012', x:30 },
|
833
|
+
{ y: '2013', x:40 },
|
834
|
+
{ y: '2014', x:50 },
|
835
|
+
];
|
836
|
+
var chart = d4.rowChart();
|
837
|
+
d3.select('#example')
|
838
|
+
.datum(data)
|
839
|
+
.call(chart);
|
840
|
+
|
841
|
+
|
842
|
+
*/
|
843
|
+
d4.rowChart = function rowChart() {
|
844
|
+
var chart = d4.baseChart({
|
845
|
+
margin: {
|
846
|
+
top: 20,
|
847
|
+
right: 40,
|
848
|
+
bottom: 20,
|
849
|
+
left: 40
|
850
|
+
}
|
851
|
+
}, rowChartBuilder);
|
852
|
+
[{
|
853
|
+
'bars': d4.features.rowSeries
|
854
|
+
}, {
|
855
|
+
'rowLabels': d4.features.rowLabels
|
856
|
+
}, {
|
857
|
+
'xAxis': d4.features.xAxis
|
858
|
+
}, {
|
859
|
+
'yAxis': d4.features.yAxis
|
860
|
+
}].forEach(function(feature) {
|
861
|
+
chart.mixin(feature);
|
862
|
+
});
|
863
|
+
return chart;
|
864
|
+
};
|
865
|
+
}).call(this);
|
866
|
+
(function() {
|
867
|
+
/*!
|
868
|
+
* global d3: false
|
869
|
+
* global d4: false
|
870
|
+
*/
|
871
|
+
'use strict';
|
872
|
+
|
873
|
+
var scatterPlotBuilder = function() {
|
874
|
+
var configureX = function(chart, data) {
|
875
|
+
if (!chart.x) {
|
876
|
+
var ext = d3.extent(data, function(d) {
|
877
|
+
return d[chart.xKey];
|
878
|
+
}.bind(this));
|
879
|
+
chart.x = d3.scale.linear()
|
880
|
+
.domain(ext)
|
881
|
+
.nice()
|
882
|
+
.clamp(true);
|
883
|
+
}
|
884
|
+
chart.x.range([0, chart.width - chart.margin.left - chart.margin.right]);
|
885
|
+
};
|
886
|
+
|
887
|
+
var configureY = function(chart, data) {
|
888
|
+
if (!chart.y) {
|
889
|
+
var ext = d3.extent(data, function(d) {
|
890
|
+
return d[chart.yKey];
|
891
|
+
}.bind(this));
|
892
|
+
chart.y = d3.scale.linear()
|
893
|
+
.domain(ext)
|
894
|
+
.nice()
|
895
|
+
.clamp(true);
|
896
|
+
}
|
897
|
+
chart.y.range([chart.height - chart.margin.top - chart.margin.bottom, 0]);
|
898
|
+
};
|
899
|
+
|
900
|
+
var configureZ = function(chart, data) {
|
901
|
+
if (!chart.z) {
|
902
|
+
var ext = d3.extent(data, function(d) {
|
903
|
+
return d[chart.zKey];
|
904
|
+
}.bind(this));
|
905
|
+
chart.z = d3.scale.linear()
|
906
|
+
.domain(ext)
|
907
|
+
.nice()
|
908
|
+
.clamp(true);
|
909
|
+
}
|
910
|
+
var maxSize = (chart.height - chart.margin.top - chart.margin.bottom);
|
911
|
+
chart.z.range([maxSize / data.length, maxSize / (data.length * 5)]);
|
912
|
+
};
|
913
|
+
var configureScales = function(chart, data) {
|
914
|
+
configureX.bind(this)(chart, data);
|
915
|
+
configureY.bind(this)(chart, data);
|
916
|
+
configureZ.bind(this)(chart, data);
|
917
|
+
};
|
918
|
+
|
919
|
+
var builder = {
|
920
|
+
configure: function(chart, data) {
|
921
|
+
configureScales.bind(this)(chart, data);
|
922
|
+
}
|
923
|
+
};
|
924
|
+
return builder;
|
925
|
+
};
|
926
|
+
|
927
|
+
d4.scatterPlot = function() {
|
928
|
+
var chart = d4.baseChart({
|
929
|
+
accessors: ['z', 'zKey'],
|
930
|
+
zKey: 'z'
|
931
|
+
}, scatterPlotBuilder);
|
932
|
+
[{
|
933
|
+
'circles': d4.features.scatterSeries
|
934
|
+
}, {
|
935
|
+
'xAxis': d4.features.xAxis
|
936
|
+
}, {
|
937
|
+
'yAxis': d4.features.yAxis
|
938
|
+
}].forEach(function(feature) {
|
939
|
+
chart.mixin(feature);
|
940
|
+
});
|
941
|
+
return chart;
|
942
|
+
};
|
943
|
+
}).call(this);
|
944
|
+
|
945
|
+
(function() {
|
946
|
+
/*!
|
947
|
+
* global d3: false
|
948
|
+
* global d4: false
|
949
|
+
*/
|
950
|
+
'use strict';
|
951
|
+
|
952
|
+
var stackedColumnChartBuilder = function() {
|
953
|
+
var extractValues = function(data, key) {
|
954
|
+
var values = data.map(function(obj){
|
955
|
+
return obj.values.map(function(i){
|
956
|
+
return i[key];
|
957
|
+
}.bind(this));
|
958
|
+
}.bind(this));
|
959
|
+
return d3.merge(values);
|
960
|
+
};
|
961
|
+
|
962
|
+
var configureX = function(chart, data) {
|
963
|
+
if (!chart.x) {
|
964
|
+
var xData = extractValues(data, chart.xKey);
|
965
|
+
chart.xRoundBands = chart.xRoundBands || 0.3;
|
966
|
+
chart.x = d3.scale.ordinal()
|
967
|
+
.domain(xData)
|
968
|
+
.rangeRoundBands([0, chart.width - chart.margin.left - chart.margin.right], chart.xRoundBands);
|
969
|
+
}
|
970
|
+
};
|
971
|
+
|
972
|
+
var configureY = function(chart, data) {
|
973
|
+
if (!chart.y) {
|
974
|
+
var ext = d3.extent(d3.merge(data.map(function(obj){
|
975
|
+
return d3.extent(obj.values, function(d){
|
976
|
+
return d.y + d.y0;
|
977
|
+
});
|
978
|
+
})));
|
979
|
+
chart.y = d3.scale.linear().domain([Math.min(0, ext[0]),ext[1]]);
|
980
|
+
}
|
981
|
+
chart.y.range([chart.height - chart.margin.top - chart.margin.bottom, 0])
|
982
|
+
.clamp(true)
|
983
|
+
.nice();
|
984
|
+
};
|
985
|
+
|
986
|
+
var configureScales = function(chart, data) {
|
987
|
+
configureX.bind(this)(chart, data);
|
988
|
+
configureY.bind(this)(chart, data);
|
989
|
+
};
|
990
|
+
|
991
|
+
var builder = {
|
992
|
+
configure: function(chart, data) {
|
993
|
+
configureScales.bind(this)(chart, data);
|
994
|
+
}
|
995
|
+
};
|
996
|
+
return builder;
|
997
|
+
};
|
998
|
+
|
999
|
+
d4.stackedColumnChart = function stackedColumnChart() {
|
1000
|
+
var chart = d4.baseChart({}, stackedColumnChartBuilder);
|
1001
|
+
[{
|
1002
|
+
'bars': d4.features.stackedColumnSeries
|
1003
|
+
}, {
|
1004
|
+
'columnLabels': d4.features.stackedColumnLabels
|
1005
|
+
}, {
|
1006
|
+
'connectors': d4.features.stackedColumnConnectors
|
1007
|
+
}, {
|
1008
|
+
'xAxis': d4.features.xAxis
|
1009
|
+
}, {
|
1010
|
+
'yAxis': d4.features.yAxis
|
1011
|
+
}].forEach(function(feature) {
|
1012
|
+
chart.mixin(feature);
|
1013
|
+
});
|
1014
|
+
return chart;
|
1015
|
+
};
|
1016
|
+
}).call(this);
|
1017
|
+
|
1018
|
+
(function() {
|
1019
|
+
/*!
|
1020
|
+
* global d3: false
|
1021
|
+
* global d4: false
|
1022
|
+
*/
|
1023
|
+
'use strict';
|
1024
|
+
|
1025
|
+
// This accessor can be overridden
|
1026
|
+
var orientation = function() {
|
1027
|
+
return 'vertical';
|
1028
|
+
};
|
1029
|
+
|
1030
|
+
// FIXME: It would be nice not to continually have to check the orientation.
|
1031
|
+
var columnSeriesOverrides = function() {
|
1032
|
+
return {
|
1033
|
+
accessors: {
|
1034
|
+
y: function(d) {
|
1035
|
+
if (this.orientation() === 'vertical') {
|
1036
|
+
var yVal = (d.y0 + d.y) - Math.min(0, d.y);
|
1037
|
+
return this.y(yVal);
|
1038
|
+
} else {
|
1039
|
+
return this.y(d[this.yKey]);
|
1040
|
+
}
|
1041
|
+
},
|
1042
|
+
|
1043
|
+
x: function(d) {
|
1044
|
+
if (this.orientation() === 'vertical') {
|
1045
|
+
return this.x(d[this.xKey]);
|
1046
|
+
} else {
|
1047
|
+
var xVal = (d.y0 + d.y) - Math.max(0, d.y);
|
1048
|
+
return this.x(xVal);
|
1049
|
+
}
|
1050
|
+
},
|
1051
|
+
|
1052
|
+
width: function(d) {
|
1053
|
+
if (this.orientation() === 'vertical') {
|
1054
|
+
return this.x.rangeBand();
|
1055
|
+
} else {
|
1056
|
+
return Math.abs(this.x(d.y0) - this.x(d.y0 + d.y));
|
1057
|
+
}
|
1058
|
+
},
|
1059
|
+
|
1060
|
+
height: function(d) {
|
1061
|
+
if (this.orientation() === 'vertical') {
|
1062
|
+
return Math.abs(this.y(d.y0) - this.y(d.y0 + d.y));
|
1063
|
+
} else {
|
1064
|
+
return this.y.rangeBand();
|
1065
|
+
}
|
1066
|
+
},
|
1067
|
+
|
1068
|
+
classes: function(d, i, n) {
|
1069
|
+
var klass = (d.y > 0) ? 'positive' : 'negative';
|
1070
|
+
if (n > 0 && d.y0 === 0) {
|
1071
|
+
klass = 'subtotal';
|
1072
|
+
}
|
1073
|
+
return 'bar fill item' + i + ' ' + klass + ' ' + d[this.yKey];
|
1074
|
+
}
|
1075
|
+
}
|
1076
|
+
};
|
1077
|
+
};
|
1078
|
+
|
1079
|
+
var columnLabelOverrides = function() {
|
1080
|
+
return {
|
1081
|
+
accessors: {
|
1082
|
+
y: function(d) {
|
1083
|
+
if (this.orientation() === 'vertical') {
|
1084
|
+
var height = Math.abs(this.y(d.y0) - this.y(d.y0 + d.y));
|
1085
|
+
var yVal = (d.y0 + d.y) - Math.max(0, d.y);
|
1086
|
+
return this.y(yVal) - 10 - height;
|
1087
|
+
} else {
|
1088
|
+
return this.y(d[this.yKey]) + (this.y.rangeBand() / 2);
|
1089
|
+
}
|
1090
|
+
},
|
1091
|
+
|
1092
|
+
x: function(d) {
|
1093
|
+
if (this.orientation() === 'vertical') {
|
1094
|
+
return this.x(d[this.xKey]) + (this.x.rangeBand() / 2);
|
1095
|
+
} else {
|
1096
|
+
var xVal = (d.y0 + d.y) - Math.max(0, d.y);
|
1097
|
+
var width = Math.abs(this.x(d.y0) - this.x(d.y0 + d.y));
|
1098
|
+
return this.x(xVal) + 10 + width;
|
1099
|
+
}
|
1100
|
+
},
|
1101
|
+
|
1102
|
+
text: function(d) {
|
1103
|
+
return d3.format('').call(this, d[this.valueKey]);
|
1104
|
+
}
|
1105
|
+
}
|
1106
|
+
};
|
1107
|
+
};
|
1108
|
+
|
1109
|
+
var waterfallChartBuilder = function() {
|
1110
|
+
var rangeBoundsFor = function(chart, dimension) {
|
1111
|
+
var rangeBounds;
|
1112
|
+
if (dimension === 'x') {
|
1113
|
+
return [0, chart.width - chart.margin.left - chart.margin.right];
|
1114
|
+
} else {
|
1115
|
+
rangeBounds = [0, chart.height - chart.margin.top - chart.margin.bottom];
|
1116
|
+
return (chart.orientation().toLowerCase() === 'vertical') ? rangeBounds.reverse() : rangeBounds;
|
1117
|
+
}
|
1118
|
+
};
|
1119
|
+
|
1120
|
+
var setOrdinal = function(chart, dimension, data) {
|
1121
|
+
if (!chart[dimension]) {
|
1122
|
+
var keys = data.map(function(d) {
|
1123
|
+
return d.key;
|
1124
|
+
}.bind(this));
|
1125
|
+
|
1126
|
+
chart[dimension] = d3.scale.ordinal()
|
1127
|
+
.domain(keys)
|
1128
|
+
.rangeRoundBands(rangeBoundsFor.bind(this)(chart, dimension), chart.xRoundBands || 0.3);
|
1129
|
+
}
|
1130
|
+
};
|
1131
|
+
|
1132
|
+
var setLinear = function(chart, dimension, data) {
|
1133
|
+
if (!chart[dimension]) {
|
1134
|
+
var ext = d3.extent(d3.merge(data.map(function(datum) {
|
1135
|
+
return d3.extent(datum.values, function(d) {
|
1136
|
+
|
1137
|
+
// This is anti-intuative but the stack only returns y and y0 even
|
1138
|
+
// when it applies to the x dimension;
|
1139
|
+
return d.y + d.y0;
|
1140
|
+
});
|
1141
|
+
})));
|
1142
|
+
ext[0] = Math.min(0, ext[0]);
|
1143
|
+
chart[dimension] = d3.scale.linear()
|
1144
|
+
.domain(ext);
|
1145
|
+
}
|
1146
|
+
chart[dimension].range(rangeBoundsFor.bind(this)(chart, dimension))
|
1147
|
+
.clamp(true)
|
1148
|
+
.nice();
|
1149
|
+
};
|
1150
|
+
|
1151
|
+
var configureScales = function(chart, data) {
|
1152
|
+
if (chart.orientation().toLowerCase() === 'vertical') {
|
1153
|
+
setOrdinal.bind(this)(chart, 'x', data);
|
1154
|
+
setLinear.bind(this)(chart, 'y', data);
|
1155
|
+
} else {
|
1156
|
+
setOrdinal.bind(this)(chart, 'y', data);
|
1157
|
+
setLinear.bind(this)(chart, 'x', data);
|
1158
|
+
}
|
1159
|
+
};
|
1160
|
+
|
1161
|
+
var builder = {
|
1162
|
+
configure: function(chart, data) {
|
1163
|
+
configureScales.bind(this)(chart, data);
|
1164
|
+
}
|
1165
|
+
};
|
1166
|
+
return builder;
|
1167
|
+
};
|
1168
|
+
|
1169
|
+
d4.waterfallChart = function waterfallChart() {
|
1170
|
+
var chart = d4.baseChart({
|
1171
|
+
accessors: ['orientation'],
|
1172
|
+
orientation: orientation
|
1173
|
+
}, waterfallChartBuilder);
|
1174
|
+
[{
|
1175
|
+
'bars': d4.features.stackedColumnSeries,
|
1176
|
+
'overrides': columnSeriesOverrides
|
1177
|
+
}, {
|
1178
|
+
'connectors': d4.features.waterfallConnectors
|
1179
|
+
}, {
|
1180
|
+
'columnLabels': d4.features.stackedColumnLabels,
|
1181
|
+
'overrides': columnLabelOverrides
|
1182
|
+
}, {
|
1183
|
+
'xAxis': d4.features.xAxis
|
1184
|
+
}, {
|
1185
|
+
'yAxis': d4.features.yAxis
|
1186
|
+
}].forEach(function(feature) {
|
1187
|
+
chart.mixin(feature);
|
1188
|
+
});
|
1189
|
+
|
1190
|
+
return chart;
|
1191
|
+
};
|
1192
|
+
}).call(this);
|
1193
|
+
|
1194
|
+
(function() {
|
1195
|
+
/*!
|
1196
|
+
* global d3: false
|
1197
|
+
* global d4: false
|
1198
|
+
*/
|
1199
|
+
'use strict';
|
1200
|
+
d4.features.arrow = function(name) {
|
1201
|
+
return {
|
1202
|
+
accessors: {
|
1203
|
+
tipSize: function(){
|
1204
|
+
return 6;
|
1205
|
+
},
|
1206
|
+
x1: function() {
|
1207
|
+
return this.x(0);
|
1208
|
+
},
|
1209
|
+
|
1210
|
+
x2: function() {
|
1211
|
+
return this.x(this.width - this.margin.left - this.margin.right);
|
1212
|
+
},
|
1213
|
+
|
1214
|
+
y1: function() {
|
1215
|
+
return this.y(0);
|
1216
|
+
},
|
1217
|
+
|
1218
|
+
y2: function() {
|
1219
|
+
return this.y(this.height - this.margin.top - this.margin.bottom);
|
1220
|
+
}
|
1221
|
+
},
|
1222
|
+
render: function(scope) {
|
1223
|
+
var defs = this.svg.select('defs');
|
1224
|
+
|
1225
|
+
defs.append('marker')
|
1226
|
+
.attr('id', name + '-end')
|
1227
|
+
.attr('viewBox', '0 0 10 10')
|
1228
|
+
.attr('refX', 10)
|
1229
|
+
.attr('refY', 5)
|
1230
|
+
.attr('markerWidth', scope.accessors.tipSize.bind(this))
|
1231
|
+
.attr('markerHeight', scope.accessors.tipSize.bind(this))
|
1232
|
+
.attr('orient', 'auto')
|
1233
|
+
.append('path')
|
1234
|
+
.attr('d', 'M 0 0 L 10 5 L 0 10 z');
|
1235
|
+
|
1236
|
+
defs.append('marker')
|
1237
|
+
.attr('id', name + '-start')
|
1238
|
+
.attr('viewBox', '0 0 10 10')
|
1239
|
+
.attr('refX', 10)
|
1240
|
+
.attr('refY', 5)
|
1241
|
+
.attr('markerWidth', -scope.accessors.tipSize.bind(this)())
|
1242
|
+
.attr('markerHeight', scope.accessors.tipSize.bind(this))
|
1243
|
+
.attr('orient', 'auto')
|
1244
|
+
.append('path')
|
1245
|
+
.attr('d', 'M 0 0 L 10 5 L 0 10 z');
|
1246
|
+
|
1247
|
+
this.featuresGroup.append('g').attr('class', name);
|
1248
|
+
var arrow = this.svg.select('.' + name)
|
1249
|
+
.append('line')
|
1250
|
+
.attr('class', 'line')
|
1251
|
+
.attr('x1', scope.accessors.x1.bind(this))
|
1252
|
+
.attr('x2', scope.accessors.x2.bind(this))
|
1253
|
+
.attr('y1', scope.accessors.y1.bind(this))
|
1254
|
+
.attr('y2', scope.accessors.y2.bind(this))
|
1255
|
+
.attr('marker-end', 'url(#' + name + '-end)');
|
1256
|
+
|
1257
|
+
return arrow;
|
1258
|
+
}
|
1259
|
+
};
|
1260
|
+
};
|
1261
|
+
}).call(this);
|
1262
|
+
|
1263
|
+
(function() {
|
1264
|
+
/*!
|
1265
|
+
* global d3: false
|
1266
|
+
* global d4: false
|
1267
|
+
*/
|
1268
|
+
|
1269
|
+
'use strict';
|
1270
|
+
d4.features.columnLabels = function(name) {
|
1271
|
+
return {
|
1272
|
+
accessors: {
|
1273
|
+
x: function(d) {
|
1274
|
+
return this.x(d[this.xKey]) + (this.x.rangeBand() / 2);
|
1275
|
+
},
|
1276
|
+
|
1277
|
+
y: function(d) {
|
1278
|
+
var height = Math.abs(this.y(d[this.yKey]) - this.y(0));
|
1279
|
+
return (d[this.yKey] < 0 ? this.y(d[this.yKey]) - height : this.y(d[this.yKey])) - 5;
|
1280
|
+
},
|
1281
|
+
|
1282
|
+
text: function(d) {
|
1283
|
+
return d3.format('').call(this, d[this.yKey]);
|
1284
|
+
}
|
1285
|
+
},
|
1286
|
+
render: function(scope, data) {
|
1287
|
+
this.featuresGroup.append('g').attr('class', name);
|
1288
|
+
var label = this.svg.select('.'+name).selectAll('.'+name).data(data);
|
1289
|
+
label.enter().append('text');
|
1290
|
+
label.exit().remove();
|
1291
|
+
label.attr('class', 'column-label')
|
1292
|
+
.text(scope.accessors.text.bind(this))
|
1293
|
+
.attr('x', scope.accessors.x.bind(this))
|
1294
|
+
.attr('y', scope.accessors.y.bind(this));
|
1295
|
+
return label;
|
1296
|
+
}
|
1297
|
+
};
|
1298
|
+
};
|
1299
|
+
}).call(this);
|
1300
|
+
|
1301
|
+
/*!
|
1302
|
+
|
1303
|
+
DEPRECATION WARNING: This feature is deprecated in favor of using the nested
|
1304
|
+
column series renderer. Intrinsicly this makes sense because a normal column
|
1305
|
+
chart is mearly a stacked column chart with only one series.
|
1306
|
+
*/
|
1307
|
+
(function() {
|
1308
|
+
/*!
|
1309
|
+
* global d3: false
|
1310
|
+
* global d4: false
|
1311
|
+
*/
|
1312
|
+
'use strict';
|
1313
|
+
d4.features.columnSeries = function(name) {
|
1314
|
+
return {
|
1315
|
+
accessors: {
|
1316
|
+
x: function(d) {
|
1317
|
+
return this.x(d[this.xKey]);
|
1318
|
+
},
|
1319
|
+
|
1320
|
+
y: function(d) {
|
1321
|
+
return d[this.yKey] < 0 ? this.y(0) : this.y(d[this.yKey]);
|
1322
|
+
},
|
1323
|
+
|
1324
|
+
width: function() {
|
1325
|
+
return this.x.rangeBand();
|
1326
|
+
},
|
1327
|
+
|
1328
|
+
height: function(d) {
|
1329
|
+
return Math.abs(this.y(d[this.yKey]) - this.y(0));
|
1330
|
+
},
|
1331
|
+
|
1332
|
+
classes: function(d, i) {
|
1333
|
+
return d[this.yKey] < 0 ? 'bar negative fill series' + i : 'bar positive fill series' + i;
|
1334
|
+
}
|
1335
|
+
},
|
1336
|
+
render: function(scope, data) {
|
1337
|
+
this.featuresGroup.append('g').attr('class', name);
|
1338
|
+
var series = this.svg.select('.' + name).selectAll('.' + name + 'Series').data(data);
|
1339
|
+
series.enter().append('g');
|
1340
|
+
series.exit().remove();
|
1341
|
+
series.attr('class', function(d, i) {
|
1342
|
+
return 'series' + i;
|
1343
|
+
});
|
1344
|
+
|
1345
|
+
var bar = series.selectAll('rect').data(function(d) {
|
1346
|
+
return [d];
|
1347
|
+
});
|
1348
|
+
bar.enter().append('rect');
|
1349
|
+
bar.exit().remove();
|
1350
|
+
bar.attr('class', scope.accessors.classes.bind(this))
|
1351
|
+
.attr('x', scope.accessors.x.bind(this))
|
1352
|
+
.attr('y', scope.accessors.y.bind(this))
|
1353
|
+
.attr('width', scope.accessors.width.bind(this))
|
1354
|
+
.attr('height', scope.accessors.height.bind(this));
|
1355
|
+
|
1356
|
+
return bar;
|
1357
|
+
}
|
1358
|
+
};
|
1359
|
+
};
|
1360
|
+
}).call(this);
|
1361
|
+
|
1362
|
+
(function() {
|
1363
|
+
/*!
|
1364
|
+
* global d3: false
|
1365
|
+
* global d4: false
|
1366
|
+
*/
|
1367
|
+
'use strict';
|
1368
|
+
d4.features.grid = function(name) {
|
1369
|
+
|
1370
|
+
return {
|
1371
|
+
accessors: {
|
1372
|
+
formatXAxis: function(xAxis) {
|
1373
|
+
return xAxis.orient('bottom');
|
1374
|
+
},
|
1375
|
+
|
1376
|
+
formatYAxis: function(yAxis) {
|
1377
|
+
return yAxis.orient('left');
|
1378
|
+
}
|
1379
|
+
},
|
1380
|
+
render: function(scope) {
|
1381
|
+
var xAxis = d3.svg.axis().scale(this.x);
|
1382
|
+
var yAxis = d3.svg.axis().scale(this.y);
|
1383
|
+
var formattedXAxis = scope.accessors.formatXAxis.bind(this)(xAxis);
|
1384
|
+
var formattedYAxis = scope.accessors.formatYAxis.bind(this)(yAxis);
|
1385
|
+
|
1386
|
+
this.featuresGroup.append('g').attr('class', 'grid border '+ name)
|
1387
|
+
.attr('transform', 'translate(0,0)')
|
1388
|
+
.append('rect')
|
1389
|
+
.attr('x', 0)
|
1390
|
+
.attr('y', 0)
|
1391
|
+
.attr('width', this.width - this.margin.left - this.margin.right)
|
1392
|
+
.attr('height', this.height - this.margin.top - this.margin.bottom);
|
1393
|
+
|
1394
|
+
this.featuresGroup.append('g').attr('class', 'x grid '+ name)
|
1395
|
+
.attr('transform', 'translate(0,' + (this.height - this.margin.top - this.margin.bottom) + ')')
|
1396
|
+
.call(formattedXAxis
|
1397
|
+
.tickSize(-(this.height - this.margin.top - this.margin.bottom), 0, 0)
|
1398
|
+
.tickFormat(''));
|
1399
|
+
|
1400
|
+
this.featuresGroup.append('g').attr('class', 'y grid '+ name)
|
1401
|
+
.attr('transform', 'translate(0,0)')
|
1402
|
+
.call(formattedYAxis
|
1403
|
+
.tickSize(-(this.width - this.margin.left - this.margin.right), 0, 0)
|
1404
|
+
.tickFormat(''));
|
1405
|
+
}
|
1406
|
+
};
|
1407
|
+
};
|
1408
|
+
}).call(this);
|
1409
|
+
(function() {
|
1410
|
+
/*!
|
1411
|
+
* global d3: false
|
1412
|
+
* global d4: false
|
1413
|
+
*/
|
1414
|
+
'use strict';
|
1415
|
+
d4.features.groupedColumnLabels = function(name) {
|
1416
|
+
return {
|
1417
|
+
accessors: {
|
1418
|
+
x: function(d, i) {
|
1419
|
+
var width = this.x.rangeBand() / this.groupsOf;
|
1420
|
+
var xPos = this.x(d[this.xKey]) + width * i;
|
1421
|
+
var gutter = width * 0.1;
|
1422
|
+
return xPos + width/2 - gutter;
|
1423
|
+
},
|
1424
|
+
|
1425
|
+
y: function(d) {
|
1426
|
+
return (d[this.yKey] < 0 ? this.y(0) : this.y(d[this.yKey])) -5;
|
1427
|
+
},
|
1428
|
+
|
1429
|
+
text: function(d) {
|
1430
|
+
return d3.format('').call(this, d[this.yKey]);
|
1431
|
+
}
|
1432
|
+
},
|
1433
|
+
render: function(scope, data) {
|
1434
|
+
this.featuresGroup.append('g').attr('class', name);
|
1435
|
+
var group = this.svg.select('.' + name).selectAll('.group')
|
1436
|
+
.data(data)
|
1437
|
+
.enter().append('g')
|
1438
|
+
.attr('class', function(d,i) {
|
1439
|
+
return 'series'+ i + ' ' + this.xKey;
|
1440
|
+
}.bind(this));
|
1441
|
+
|
1442
|
+
var text = group.selectAll('text')
|
1443
|
+
.data(function(d) {
|
1444
|
+
return d.values;
|
1445
|
+
}.bind(this));
|
1446
|
+
text.exit().remove();
|
1447
|
+
text.enter().append('text')
|
1448
|
+
.attr('class', 'column-label')
|
1449
|
+
.text(scope.accessors.text.bind(this))
|
1450
|
+
.attr('y', scope.accessors.y.bind(this))
|
1451
|
+
.attr('x', scope.accessors.x.bind(this));
|
1452
|
+
return text;
|
1453
|
+
}
|
1454
|
+
};
|
1455
|
+
};
|
1456
|
+
}).call(this);
|
1457
|
+
|
1458
|
+
(function() {
|
1459
|
+
/*!
|
1460
|
+
* global d3: false
|
1461
|
+
* global d4: false
|
1462
|
+
*/
|
1463
|
+
'use strict';
|
1464
|
+
d4.features.groupedColumnSeries = function(name) {
|
1465
|
+
var sign = function(val) {
|
1466
|
+
return (val > 0) ? 'positive' : 'negative';
|
1467
|
+
};
|
1468
|
+
|
1469
|
+
return {
|
1470
|
+
accessors: {
|
1471
|
+
x: function(d, i) {
|
1472
|
+
var width = this.x.rangeBand() / this.groupsOf;
|
1473
|
+
var xPos = this.x(d[this.xKey]) + width * i;
|
1474
|
+
return xPos;
|
1475
|
+
},
|
1476
|
+
|
1477
|
+
y: function(d) {
|
1478
|
+
return d[this.yKey] < 0 ? this.y(0) : this.y(d[this.yKey]);
|
1479
|
+
},
|
1480
|
+
|
1481
|
+
width: function() {
|
1482
|
+
var width = this.x.rangeBand() / this.groupsOf;
|
1483
|
+
var gutter = width * 0.1;
|
1484
|
+
return width - gutter;
|
1485
|
+
},
|
1486
|
+
|
1487
|
+
height: function(d) {
|
1488
|
+
return Math.abs(this.y(d[this.yKey]) - this.y(0));
|
1489
|
+
},
|
1490
|
+
|
1491
|
+
classes: function(d, i) {
|
1492
|
+
return 'bar fill item' + i + ' ' + sign(d[this.yKey]) + ' ' + d[this.yKey];
|
1493
|
+
}
|
1494
|
+
},
|
1495
|
+
render: function(scope, data) {
|
1496
|
+
this.featuresGroup.append('g').attr('class', name);
|
1497
|
+
var group = this.svg.select('.' + name).selectAll('.group')
|
1498
|
+
.data(data);
|
1499
|
+
group.enter().append('g');
|
1500
|
+
group.exit().remove();
|
1501
|
+
group.attr('class', function(d, i) {
|
1502
|
+
return 'series' + i + ' ' + this.xKey;
|
1503
|
+
}.bind(this));
|
1504
|
+
|
1505
|
+
var rect = group.selectAll('rect')
|
1506
|
+
.data(function(d) {
|
1507
|
+
return d.values;
|
1508
|
+
}.bind(this));
|
1509
|
+
rect.enter().append('rect')
|
1510
|
+
.attr('class', scope.accessors.classes.bind(this))
|
1511
|
+
.attr('x', scope.accessors.x.bind(this))
|
1512
|
+
.attr('y', scope.accessors.y.bind(this))
|
1513
|
+
.attr('width', scope.accessors.width.bind(this))
|
1514
|
+
.attr('height', scope.accessors.height.bind(this));
|
1515
|
+
return rect;
|
1516
|
+
}
|
1517
|
+
};
|
1518
|
+
};
|
1519
|
+
}).call(this);
|
1520
|
+
|
1521
|
+
(function() {
|
1522
|
+
/*!
|
1523
|
+
* global d3: false
|
1524
|
+
* global d4: false
|
1525
|
+
*/
|
1526
|
+
|
1527
|
+
'use strict';
|
1528
|
+
d4.features.lineSeriesLabels = function(name) {
|
1529
|
+
return {
|
1530
|
+
accessors: {
|
1531
|
+
x: function(d) {
|
1532
|
+
return this.x(d.values[d.values.length - 1][this.xKey]);
|
1533
|
+
},
|
1534
|
+
|
1535
|
+
y: function(d) {
|
1536
|
+
return this.y(d.values[d.values.length - 1][this.yKey]);
|
1537
|
+
},
|
1538
|
+
|
1539
|
+
text: function(d) {
|
1540
|
+
return d.key;
|
1541
|
+
},
|
1542
|
+
|
1543
|
+
classes: function(d,n) {
|
1544
|
+
return 'stroke series' + n;
|
1545
|
+
}
|
1546
|
+
},
|
1547
|
+
render: function(scope, data) {
|
1548
|
+
this.featuresGroup.append('g').attr('class', name);
|
1549
|
+
var label = this.svg.select('.'+name).selectAll('.'+name).data(data);
|
1550
|
+
label.enter().append('text');
|
1551
|
+
label.exit().remove();
|
1552
|
+
label.attr('class', 'lineSeriesLabel')
|
1553
|
+
.text(scope.accessors.text.bind(this))
|
1554
|
+
.attr('x', scope.accessors.x.bind(this))
|
1555
|
+
.attr('y', scope.accessors.y.bind(this))
|
1556
|
+
.attr('data-key', function(d){
|
1557
|
+
return d.key;
|
1558
|
+
})
|
1559
|
+
.attr('class', scope.accessors.classes.bind(this));
|
1560
|
+
return label;
|
1561
|
+
}
|
1562
|
+
};
|
1563
|
+
};
|
1564
|
+
}).call(this);
|
1565
|
+
(function() {
|
1566
|
+
/*!
|
1567
|
+
* global d3: false
|
1568
|
+
* global d4: false
|
1569
|
+
*/
|
1570
|
+
|
1571
|
+
'use strict';
|
1572
|
+
d4.features.lineSeries = function(name) {
|
1573
|
+
return {
|
1574
|
+
accessors: {
|
1575
|
+
x: function(d) {
|
1576
|
+
return this.x(d[this.xKey]);
|
1577
|
+
},
|
1578
|
+
y: function(d) {
|
1579
|
+
return this.y(d[this.yKey]);
|
1580
|
+
},
|
1581
|
+
interpolate: function() {
|
1582
|
+
return 'basis';
|
1583
|
+
},
|
1584
|
+
classes: function(d, n) {
|
1585
|
+
return 'line stroke series' + n;
|
1586
|
+
}
|
1587
|
+
},
|
1588
|
+
render: function(scope, data) {
|
1589
|
+
this.featuresGroup.append('g').attr('class', name);
|
1590
|
+
var line = d3.svg.line()
|
1591
|
+
.interpolate(scope.accessors.interpolate.bind(this)())
|
1592
|
+
.x(scope.accessors.x.bind(this))
|
1593
|
+
.y(scope.accessors.y.bind(this));
|
1594
|
+
|
1595
|
+
var group = this.svg.select('.' + name).selectAll('.group')
|
1596
|
+
.data(data);
|
1597
|
+
group.exit().remove();
|
1598
|
+
group.enter().append('g')
|
1599
|
+
.attr('data-key', function(d) {
|
1600
|
+
return d.key;
|
1601
|
+
})
|
1602
|
+
.attr('class', function(d, i) {
|
1603
|
+
return 'series' + i;
|
1604
|
+
}.bind(this))
|
1605
|
+
.append('path')
|
1606
|
+
.attr('d', function(d) {
|
1607
|
+
return line(d.values);
|
1608
|
+
});
|
1609
|
+
}
|
1610
|
+
};
|
1611
|
+
};
|
1612
|
+
}).call(this);
|
1613
|
+
|
1614
|
+
(function() {
|
1615
|
+
/*!
|
1616
|
+
* global d3: false
|
1617
|
+
* global d4: false
|
1618
|
+
*/
|
1619
|
+
|
1620
|
+
'use strict';
|
1621
|
+
d4.features.referenceLine = function(name) {
|
1622
|
+
return {
|
1623
|
+
accessors: {
|
1624
|
+
x1: function() {
|
1625
|
+
return this.x(0);
|
1626
|
+
},
|
1627
|
+
|
1628
|
+
x2: function() {
|
1629
|
+
return this.x(this.width - this.margin.left - this.margin.right);
|
1630
|
+
},
|
1631
|
+
|
1632
|
+
y1: function() {
|
1633
|
+
return this.y(0);
|
1634
|
+
},
|
1635
|
+
|
1636
|
+
y2: function() {
|
1637
|
+
return this.y(this.height);
|
1638
|
+
}
|
1639
|
+
},
|
1640
|
+
render: function(scope) {
|
1641
|
+
this.featuresGroup.append('g').attr('class', name);
|
1642
|
+
var referenceLine = this.svg.select('.' + name)
|
1643
|
+
.append('line')
|
1644
|
+
.attr('class', 'line')
|
1645
|
+
.attr('x1', scope.accessors.x1.bind(this))
|
1646
|
+
.attr('x2', scope.accessors.x2.bind(this))
|
1647
|
+
.attr('y1', scope.accessors.y1.bind(this))
|
1648
|
+
.attr('y2', scope.accessors.y2.bind(this));
|
1649
|
+
return referenceLine;
|
1650
|
+
}
|
1651
|
+
};
|
1652
|
+
};
|
1653
|
+
}).call(this);
|
1654
|
+
|
1655
|
+
(function() {
|
1656
|
+
/*!
|
1657
|
+
* global d3: false
|
1658
|
+
* global d4: false
|
1659
|
+
*/
|
1660
|
+
|
1661
|
+
'use strict';
|
1662
|
+
d4.features.rowLabels = function(name) {
|
1663
|
+
return {
|
1664
|
+
accessors: {
|
1665
|
+
x: function(d) {
|
1666
|
+
return this.x(Math.min(0, d[this.xKey])) + Math.abs(this.x(d[this.xKey]) - this.x(0)) + 20;
|
1667
|
+
},
|
1668
|
+
|
1669
|
+
y: function(d) {
|
1670
|
+
return this.y(d[this.yKey]) + (this.y.rangeBand() / 2);
|
1671
|
+
},
|
1672
|
+
|
1673
|
+
text: function(d) {
|
1674
|
+
return d3.format('').call(this, d[this.xKey]);
|
1675
|
+
}
|
1676
|
+
},
|
1677
|
+
render: function(scope, data) {
|
1678
|
+
this.featuresGroup.append('g').attr('class', name);
|
1679
|
+
var label = this.svg.select('.'+name).selectAll('.'+name).data(data);
|
1680
|
+
label.enter().append('text');
|
1681
|
+
label.exit().remove();
|
1682
|
+
label.attr('class', 'column-label')
|
1683
|
+
.text(scope.accessors.text.bind(this))
|
1684
|
+
.attr('x', scope.accessors.x.bind(this))
|
1685
|
+
.attr('y', scope.accessors.y.bind(this));
|
1686
|
+
return label;
|
1687
|
+
}
|
1688
|
+
};
|
1689
|
+
};
|
1690
|
+
}).call(this);
|
1691
|
+
|
1692
|
+
(function() {
|
1693
|
+
/*!
|
1694
|
+
* global d3: false
|
1695
|
+
* global d4: false
|
1696
|
+
*/
|
1697
|
+
|
1698
|
+
'use strict';
|
1699
|
+
d4.features.rowSeries = function(name) {
|
1700
|
+
return {
|
1701
|
+
accessors: {
|
1702
|
+
x: function(d) {
|
1703
|
+
return this.x(Math.min(0, d[this.xKey]));
|
1704
|
+
},
|
1705
|
+
|
1706
|
+
y: function(d) {
|
1707
|
+
return this.y(d[this.yKey]);
|
1708
|
+
},
|
1709
|
+
|
1710
|
+
height: function() {
|
1711
|
+
return this.y.rangeBand();
|
1712
|
+
},
|
1713
|
+
|
1714
|
+
width: function(d) {
|
1715
|
+
return Math.abs(this.x(d[this.xKey]) - this.x(0));
|
1716
|
+
},
|
1717
|
+
|
1718
|
+
classes: function(d, i) {
|
1719
|
+
return d[this.xKey] < 0 ? 'bar negative fill series' + i : 'bar positive fill series' + i;
|
1720
|
+
}
|
1721
|
+
},
|
1722
|
+
render: function(scope, data) {
|
1723
|
+
this.featuresGroup.append('g').attr('class', name);
|
1724
|
+
var bar = this.svg.select('.'+name).selectAll('.'+name).data(data);
|
1725
|
+
bar.enter().append('rect');
|
1726
|
+
bar.exit().remove();
|
1727
|
+
bar.attr('class', scope.accessors.classes.bind(this))
|
1728
|
+
.attr('x', scope.accessors.x.bind(this))
|
1729
|
+
.attr('y', scope.accessors.y.bind(this))
|
1730
|
+
.attr('width', scope.accessors.width.bind(this))
|
1731
|
+
.attr('height', scope.accessors.height.bind(this));
|
1732
|
+
|
1733
|
+
return bar;
|
1734
|
+
}
|
1735
|
+
};
|
1736
|
+
};
|
1737
|
+
}).call(this);
|
1738
|
+
|
1739
|
+
(function() {
|
1740
|
+
/*!
|
1741
|
+
* global d3: false
|
1742
|
+
* global d4: false
|
1743
|
+
*/
|
1744
|
+
|
1745
|
+
'use strict';
|
1746
|
+
d4.features.scatterSeries = function(name) {
|
1747
|
+
return {
|
1748
|
+
accessors: {
|
1749
|
+
cx: function(d) {
|
1750
|
+
return this.x(d[this.xKey]);
|
1751
|
+
},
|
1752
|
+
|
1753
|
+
cy: function(d) {
|
1754
|
+
return this.y(d[this.yKey]);
|
1755
|
+
},
|
1756
|
+
|
1757
|
+
r: function(d) {
|
1758
|
+
return this.z(d[this.zKey]);
|
1759
|
+
},
|
1760
|
+
|
1761
|
+
classes : function(d, i) {
|
1762
|
+
return 'dot series' + i + ' fill';
|
1763
|
+
}
|
1764
|
+
},
|
1765
|
+
render: function(scope, data) {
|
1766
|
+
this.featuresGroup.append('g').attr('class', name);
|
1767
|
+
var dots = this.svg.select('.'+name).selectAll('.'+name).data(data);
|
1768
|
+
dots.enter().append('circle');
|
1769
|
+
dots.attr('class', scope.accessors.classes.bind(this))
|
1770
|
+
.attr('r', scope.accessors.r.bind(this))
|
1771
|
+
.attr('cx', scope.accessors.cx.bind(this))
|
1772
|
+
.attr('cy', scope.accessors.cy.bind(this));
|
1773
|
+
|
1774
|
+
// returning a selection allows d4 to bind d3 events to it.
|
1775
|
+
return dots;
|
1776
|
+
}
|
1777
|
+
};
|
1778
|
+
};
|
1779
|
+
}).call(this);
|
1780
|
+
|
1781
|
+
(function() {
|
1782
|
+
/*!
|
1783
|
+
* global d3: false
|
1784
|
+
* global d4: false
|
1785
|
+
*/
|
1786
|
+
|
1787
|
+
/*!
|
1788
|
+
Column connectors helpful when displaying a stacked column chart.
|
1789
|
+
A connector will not connect positve and negative columns. This is because
|
1790
|
+
in a stacked column a negative column may move many series below its previous
|
1791
|
+
location. This creates a messy collection of crisscrossing lines.
|
1792
|
+
*/
|
1793
|
+
'use strict';
|
1794
|
+
d4.features.stackedColumnConnectors = function(name) {
|
1795
|
+
|
1796
|
+
return {
|
1797
|
+
accessors: {
|
1798
|
+
x1: function(d) {
|
1799
|
+
var width = 0;
|
1800
|
+
var xVal = (d.y0 + d.y) - Math.max(0, d.y);
|
1801
|
+
if(d.y > 0){
|
1802
|
+
width = Math.abs(this.x(d.y0) - this.x(d.y0 + d.y));
|
1803
|
+
}
|
1804
|
+
return this.x(xVal) + width;
|
1805
|
+
|
1806
|
+
},
|
1807
|
+
|
1808
|
+
y1: function(d) {
|
1809
|
+
return this.y(d[this.yKey]);
|
1810
|
+
},
|
1811
|
+
|
1812
|
+
span: function(){
|
1813
|
+
return this.y.rangeBand();
|
1814
|
+
},
|
1815
|
+
|
1816
|
+
classes : function(d, i){
|
1817
|
+
return 'series' +i;
|
1818
|
+
}
|
1819
|
+
},
|
1820
|
+
|
1821
|
+
render: function(scope) {
|
1822
|
+
this.featuresGroup.append('g').attr('class', name);
|
1823
|
+
var lines = this.svg.select('.' + name).selectAll('.' + name).data(function(d) {
|
1824
|
+
return d.map(function(o) {
|
1825
|
+
return o.values[0];
|
1826
|
+
});
|
1827
|
+
}.bind(this));
|
1828
|
+
lines.enter().append('line');
|
1829
|
+
lines.exit().remove();
|
1830
|
+
lines
|
1831
|
+
.attr('class', scope.accessors.classes.bind(this))
|
1832
|
+
.attr('x1', function() {
|
1833
|
+
// if(i === 0){
|
1834
|
+
// return 0;
|
1835
|
+
// }
|
1836
|
+
// return scope.accessors.x1.bind(this)(data[i - 1].values[0]);
|
1837
|
+
}.bind(this))
|
1838
|
+
|
1839
|
+
.attr('y1', function() {
|
1840
|
+
// if(i === 0){
|
1841
|
+
// return 0;
|
1842
|
+
// }
|
1843
|
+
// return scope.accessors.y1.bind(this)(data[i - 1].values[0]);
|
1844
|
+
}.bind(this))
|
1845
|
+
|
1846
|
+
.attr('x2', function() {
|
1847
|
+
// if(i === 0){
|
1848
|
+
// return 0;
|
1849
|
+
// }
|
1850
|
+
// return scope.accessors.x1.bind(this)(data[i - 1].values[0]);
|
1851
|
+
}.bind(this))
|
1852
|
+
|
1853
|
+
.attr('y2', function() {
|
1854
|
+
// if(i === 0){
|
1855
|
+
// return 0;
|
1856
|
+
// }
|
1857
|
+
// return scope.accessors.y1.bind(this)(d) + scope.accessors.span.bind(this)(d);
|
1858
|
+
}.bind(this));
|
1859
|
+
|
1860
|
+
return lines;
|
1861
|
+
}
|
1862
|
+
};
|
1863
|
+
};
|
1864
|
+
}).call(this);
|
1865
|
+
|
1866
|
+
(function() {
|
1867
|
+
/*!
|
1868
|
+
* global d3: false
|
1869
|
+
* global d4: false
|
1870
|
+
*/
|
1871
|
+
|
1872
|
+
'use strict';
|
1873
|
+
d4.features.stackedColumnLabels = function(name) {
|
1874
|
+
var sign = function(val) {
|
1875
|
+
return val > 0 ? 'positive' : 'negative';
|
1876
|
+
};
|
1877
|
+
|
1878
|
+
return {
|
1879
|
+
accessors: {
|
1880
|
+
x: function(d) {
|
1881
|
+
return this.x(d[this.xKey]) + (this.x.rangeBand() / 2);
|
1882
|
+
},
|
1883
|
+
|
1884
|
+
y: function(d) {
|
1885
|
+
var halfHeight = Math.abs(this.y(d.y0) - this.y(d.y0 + d.y)) / 2;
|
1886
|
+
var yVal = d.y0 + d.y;
|
1887
|
+
return (yVal < 0 ? this.y(d.y0) : this.y(yVal)) + halfHeight;
|
1888
|
+
},
|
1889
|
+
|
1890
|
+
text: function(d) {
|
1891
|
+
if(Math.abs(this.y(d.y0) - this.y(d.y0 + d.y)) > 20) {
|
1892
|
+
return d3.format('').call(this, d[this.valueKey]);
|
1893
|
+
}
|
1894
|
+
}
|
1895
|
+
},
|
1896
|
+
render: function(scope, data) {
|
1897
|
+
this.featuresGroup.append('g').attr('class', name);
|
1898
|
+
var group = this.svg.select('.' + name).selectAll('.group')
|
1899
|
+
.data(data)
|
1900
|
+
.enter().append('g')
|
1901
|
+
.attr('class', function(d, i) {
|
1902
|
+
return 'series' + i + ' '+ sign(d.y) + ' ' + this.xKey;
|
1903
|
+
}.bind(this));
|
1904
|
+
|
1905
|
+
var text = group.selectAll('text')
|
1906
|
+
.data(function(d) {
|
1907
|
+
return d.values;
|
1908
|
+
}.bind(this));
|
1909
|
+
text.exit().remove();
|
1910
|
+
text.enter().append('text')
|
1911
|
+
.text(scope.accessors.text.bind(this))
|
1912
|
+
.attr('class', 'column-label')
|
1913
|
+
.attr('y', scope.accessors.y.bind(this))
|
1914
|
+
.attr('x', scope.accessors.x.bind(this));
|
1915
|
+
return text;
|
1916
|
+
}
|
1917
|
+
};
|
1918
|
+
};
|
1919
|
+
}).call(this);
|
1920
|
+
|
1921
|
+
(function() {
|
1922
|
+
/*!
|
1923
|
+
* global d3: false
|
1924
|
+
* global d4: false
|
1925
|
+
*/
|
1926
|
+
|
1927
|
+
'use strict';
|
1928
|
+
d4.features.stackedColumnSeries = function(name) {
|
1929
|
+
var sign = function(val){
|
1930
|
+
return (val > 0) ? 'positive' : 'negative';
|
1931
|
+
};
|
1932
|
+
|
1933
|
+
return {
|
1934
|
+
accessors: {
|
1935
|
+
x: function(d) {
|
1936
|
+
return this.x(d[this.xKey]);
|
1937
|
+
},
|
1938
|
+
|
1939
|
+
y: function(d) {
|
1940
|
+
var yVal = d.y0 + d.y;
|
1941
|
+
return yVal < 0 ? this.y(d.y0) : this.y(yVal);
|
1942
|
+
},
|
1943
|
+
|
1944
|
+
width: function() {
|
1945
|
+
return this.x.rangeBand();
|
1946
|
+
},
|
1947
|
+
|
1948
|
+
height: function(d) {
|
1949
|
+
return Math.abs(this.y(d.y0) - this.y(d.y0 + d.y));
|
1950
|
+
},
|
1951
|
+
|
1952
|
+
classes: function(d,i) {
|
1953
|
+
return 'bar fill item'+ i + ' ' + sign(d.y) + ' ' + d[this.yKey];
|
1954
|
+
}
|
1955
|
+
},
|
1956
|
+
render: function(scope, data) {
|
1957
|
+
this.featuresGroup.append('g').attr('class', name);
|
1958
|
+
var group = this.svg.select('.' + name).selectAll('.group')
|
1959
|
+
.data(data)
|
1960
|
+
.enter().append('g')
|
1961
|
+
.attr('class', function(d,i) {
|
1962
|
+
return 'series'+ i + ' ' + this.yKey;
|
1963
|
+
}.bind(this));
|
1964
|
+
|
1965
|
+
var rect = group.selectAll('rect')
|
1966
|
+
.data(function(d) {
|
1967
|
+
return d.values;
|
1968
|
+
}.bind(this));
|
1969
|
+
|
1970
|
+
rect.enter().append('rect')
|
1971
|
+
.attr('class', scope.accessors.classes.bind(this))
|
1972
|
+
.attr('x', scope.accessors.x.bind(this))
|
1973
|
+
.attr('y', scope.accessors.y.bind(this))
|
1974
|
+
.attr('width', scope.accessors.width.bind(this))
|
1975
|
+
.attr('height', scope.accessors.height.bind(this));
|
1976
|
+
return rect;
|
1977
|
+
}
|
1978
|
+
};
|
1979
|
+
};
|
1980
|
+
}).call(this);
|
1981
|
+
|
1982
|
+
(function() {
|
1983
|
+
/*!
|
1984
|
+
* global d3: false
|
1985
|
+
* global d4: false
|
1986
|
+
*/
|
1987
|
+
|
1988
|
+
'use strict';
|
1989
|
+
d4.features.trendLine = function(name) {
|
1990
|
+
return {
|
1991
|
+
accessors: {
|
1992
|
+
x1: function() {
|
1993
|
+
return this.x(0);
|
1994
|
+
},
|
1995
|
+
|
1996
|
+
x2: function() {
|
1997
|
+
return this.x(this.width);
|
1998
|
+
},
|
1999
|
+
|
2000
|
+
y1: function() {
|
2001
|
+
return this.y(0);
|
2002
|
+
},
|
2003
|
+
|
2004
|
+
y2: function() {
|
2005
|
+
return this.y(this.height);
|
2006
|
+
},
|
2007
|
+
|
2008
|
+
text: function(d) {
|
2009
|
+
return d3.format('').call(this, d[1]);
|
2010
|
+
},
|
2011
|
+
|
2012
|
+
textX: function() {
|
2013
|
+
return this.x(this.width);
|
2014
|
+
},
|
2015
|
+
|
2016
|
+
textY: function(){
|
2017
|
+
return this.x(this.height);
|
2018
|
+
}
|
2019
|
+
},
|
2020
|
+
render: function(scope) {
|
2021
|
+
var defs = this.svg.select('defs');
|
2022
|
+
|
2023
|
+
defs.append('marker')
|
2024
|
+
.attr('id', name + '-start')
|
2025
|
+
.attr('viewBox', '0 0 10 10')
|
2026
|
+
.attr('refX', 10)
|
2027
|
+
.attr('refY', 5)
|
2028
|
+
.attr('markerWidth', -6)
|
2029
|
+
.attr('markerHeight', 6)
|
2030
|
+
.attr('orient', 'auto')
|
2031
|
+
.append('path')
|
2032
|
+
.attr('d', 'M 0 0 L 10 5 L 0 10 z');
|
2033
|
+
|
2034
|
+
this.featuresGroup.append('g').attr('class', name);
|
2035
|
+
var trendLine = this.svg.select('.' + name)
|
2036
|
+
.append('line')
|
2037
|
+
.attr('class', 'line')
|
2038
|
+
.attr('x1', scope.accessors.x1.bind(this))
|
2039
|
+
.attr('x2', scope.accessors.x2.bind(this))
|
2040
|
+
.attr('y1', scope.accessors.y1.bind(this))
|
2041
|
+
.attr('y2', scope.accessors.y2.bind(this))
|
2042
|
+
.attr('marker-end', 'url(#' + name + '-start)');
|
2043
|
+
|
2044
|
+
this.svg.select('.' + name)
|
2045
|
+
.append('text')
|
2046
|
+
.attr('class', 'trendLine-label')
|
2047
|
+
.text(scope.accessors.text.bind(this))
|
2048
|
+
.attr('x', scope.accessors.textX.bind(this))
|
2049
|
+
.attr('y', scope.accessors.textY.bind(this));
|
2050
|
+
return trendLine;
|
2051
|
+
}
|
2052
|
+
};
|
2053
|
+
};
|
2054
|
+
}).call(this);
|
2055
|
+
|
2056
|
+
(function() {
|
2057
|
+
'use strict';
|
2058
|
+
/*!
|
2059
|
+
* global d3: false
|
2060
|
+
* global d4: false
|
2061
|
+
*/
|
2062
|
+
|
2063
|
+
/*
|
2064
|
+
Waterfall connectors are orthogonal series connectors which visually join
|
2065
|
+
column series together by spanning the top or bottom of adjacent columns.
|
2066
|
+
|
2067
|
+
When using this feature in charts other than waterfall, be aware that the
|
2068
|
+
mixin expects an accessor property for `orientation`, which it uses to render
|
2069
|
+
the direction of the lines.
|
2070
|
+
|
2071
|
+
##### Accessors
|
2072
|
+
|
2073
|
+
`x` - Used in placement of the connector lines.
|
2074
|
+
`y` - Used in placement of the connector lines.
|
2075
|
+
`span` - calculates the length of the connector line
|
2076
|
+
`classes` - applies the class to the connector lines.
|
2077
|
+
|
2078
|
+
*/
|
2079
|
+
d4.features.waterfallConnectors = function(name) {
|
2080
|
+
return {
|
2081
|
+
accessors: {
|
2082
|
+
x: function(d) {
|
2083
|
+
if(this.orientation() === 'horizontal'){
|
2084
|
+
var width = 0;
|
2085
|
+
var xVal = (d.y0 + d.y) - Math.max(0, d.y);
|
2086
|
+
if(d.y > 0){
|
2087
|
+
width = Math.abs(this.x(d.y0) - this.x(d.y0 + d.y));
|
2088
|
+
}
|
2089
|
+
return this.x(xVal) + width;
|
2090
|
+
} else {
|
2091
|
+
return this.x(d[this.xKey]);
|
2092
|
+
}
|
2093
|
+
},
|
2094
|
+
|
2095
|
+
y: function(d) {
|
2096
|
+
if(this.orientation() === 'horizontal'){
|
2097
|
+
return this.y(d[this.yKey]);
|
2098
|
+
} else {
|
2099
|
+
return this.y(d.y0 + d.y);
|
2100
|
+
}
|
2101
|
+
},
|
2102
|
+
|
2103
|
+
span: function(){
|
2104
|
+
if(this.orientation() === 'horizontal'){
|
2105
|
+
return this.y.rangeBand();
|
2106
|
+
} else {
|
2107
|
+
return this.x.rangeBand();
|
2108
|
+
}
|
2109
|
+
},
|
2110
|
+
|
2111
|
+
classes : function(d, i){
|
2112
|
+
return 'series' +i;
|
2113
|
+
}
|
2114
|
+
},
|
2115
|
+
|
2116
|
+
render: function(scope, data) {
|
2117
|
+
this.featuresGroup.append('g').attr('class', name);
|
2118
|
+
var lines = this.svg.select('.' + name).selectAll('.' + name).data(function(d) {
|
2119
|
+
return d.map(function(o) {
|
2120
|
+
return o.values[0];
|
2121
|
+
});
|
2122
|
+
}.bind(this));
|
2123
|
+
lines.enter().append('line');
|
2124
|
+
lines.exit().remove();
|
2125
|
+
lines
|
2126
|
+
.attr('class', scope.accessors.classes.bind(this))
|
2127
|
+
.attr('x1', function(d, i) {
|
2128
|
+
if(i === 0){
|
2129
|
+
return 0;
|
2130
|
+
}
|
2131
|
+
return scope.accessors.x.bind(this)(data[i - 1].values[0]);
|
2132
|
+
}.bind(this))
|
2133
|
+
|
2134
|
+
.attr('y1', function(d, i) {
|
2135
|
+
if(i === 0){
|
2136
|
+
return 0;
|
2137
|
+
}
|
2138
|
+
return scope.accessors.y.bind(this)(data[i - 1].values[0]);
|
2139
|
+
}.bind(this))
|
2140
|
+
|
2141
|
+
.attr('x2', function(d, i) {
|
2142
|
+
if(i === 0){
|
2143
|
+
return 0;
|
2144
|
+
}
|
2145
|
+
if(this.orientation() === 'vertical') {
|
2146
|
+
return scope.accessors.x.bind(this)(d) + scope.accessors.span.bind(this)();
|
2147
|
+
} else {
|
2148
|
+
return scope.accessors.x.bind(this)(data[i - 1].values[0]);
|
2149
|
+
}
|
2150
|
+
}.bind(this))
|
2151
|
+
|
2152
|
+
.attr('y2', function(d, i) {
|
2153
|
+
if(i === 0){
|
2154
|
+
return 0;
|
2155
|
+
}
|
2156
|
+
if(this.orientation() === 'vertical') {
|
2157
|
+
return scope.accessors.y.bind(this)(data[i - 1].values[0]);
|
2158
|
+
}else {
|
2159
|
+
return scope.accessors.y.bind(this)(d) + scope.accessors.span.bind(this)(d);
|
2160
|
+
}
|
2161
|
+
}.bind(this));
|
2162
|
+
|
2163
|
+
return lines;
|
2164
|
+
}
|
2165
|
+
};
|
2166
|
+
};
|
2167
|
+
}).call(this);
|
2168
|
+
|
2169
|
+
(function() {
|
2170
|
+
/*!
|
2171
|
+
* global d3: false
|
2172
|
+
* global d4: false
|
2173
|
+
*/
|
2174
|
+
|
2175
|
+
'use strict';
|
2176
|
+
d4.features.xAxis = function(name) {
|
2177
|
+
return {
|
2178
|
+
accessors: {
|
2179
|
+
format: function(xAxis) {
|
2180
|
+
return xAxis.orient('bottom').tickSize(0);
|
2181
|
+
}
|
2182
|
+
},
|
2183
|
+
render: function(scope) {
|
2184
|
+
var xAxis = d3.svg.axis().scale(this.x);
|
2185
|
+
var formattedAxis = scope.accessors.format.bind(this)(xAxis);
|
2186
|
+
this.featuresGroup.append('g').attr('class', 'x axis '+ name)
|
2187
|
+
.attr('transform', 'translate(0,' + (this.height - this.margin.top - this.margin.bottom) + ')')
|
2188
|
+
.call(formattedAxis);
|
2189
|
+
|
2190
|
+
}
|
2191
|
+
};
|
2192
|
+
};
|
2193
|
+
}).call(this);
|
2194
|
+
|
2195
|
+
(function() {
|
2196
|
+
/*!
|
2197
|
+
* global d3: false
|
2198
|
+
* global d4: false
|
2199
|
+
*/
|
2200
|
+
|
2201
|
+
'use strict';
|
2202
|
+
d4.features.yAxis = function(name) {
|
2203
|
+
|
2204
|
+
// FIXME: This should be a util function
|
2205
|
+
// Extracted from: http://bl.ocks.org/mbostock/7555321
|
2206
|
+
var wrap = function(text, width) {
|
2207
|
+
text.each(function() {
|
2208
|
+
var text = d3.select(this),
|
2209
|
+
words = text.text().split(/\s+/).reverse(),
|
2210
|
+
word,
|
2211
|
+
line = [],
|
2212
|
+
lineNumber = 0,
|
2213
|
+
lineHeight = 1.1, // ems
|
2214
|
+
x = text.attr('x'),
|
2215
|
+
y = text.attr('y'),
|
2216
|
+
dy = parseFloat(text.attr('dy')),
|
2217
|
+
tspan = text.text(null).append('tspan').attr('x', x).attr('y', y).attr('dy', dy + 'em');
|
2218
|
+
word = words.pop();
|
2219
|
+
while (word) {
|
2220
|
+
line.push(word);
|
2221
|
+
tspan.text(line.join(' '));
|
2222
|
+
if (tspan.node().getComputedTextLength() > width - Math.abs(x)) {
|
2223
|
+
line.pop();
|
2224
|
+
tspan.text(line.join(' '));
|
2225
|
+
line = [word];
|
2226
|
+
tspan = text.append('tspan').attr('x', x).attr('y', y).attr('dy', ++lineNumber * lineHeight + dy + 'em').text(word);
|
2227
|
+
}
|
2228
|
+
word = words.pop();
|
2229
|
+
}
|
2230
|
+
});
|
2231
|
+
};
|
2232
|
+
|
2233
|
+
return {
|
2234
|
+
accessors: {
|
2235
|
+
format: function(yAxis) {
|
2236
|
+
return yAxis.orient('left').tickSize(0);
|
2237
|
+
}
|
2238
|
+
},
|
2239
|
+
render: function(scope) {
|
2240
|
+
var yAxis = d3.svg.axis().scale(this.y);
|
2241
|
+
var formattedAxis = scope.accessors.format.bind(this)(yAxis);
|
2242
|
+
this.featuresGroup.append('g').attr('class', 'y axis ' + name)
|
2243
|
+
.attr('transform', 'translate(0,0)')
|
2244
|
+
.call(formattedAxis)
|
2245
|
+
.selectAll('.tick text')
|
2246
|
+
.call(wrap, this.margin.left);
|
2247
|
+
}
|
2248
|
+
};
|
2249
|
+
};
|
2250
|
+
}).call(this);
|
2251
|
+
(function() {
|
2252
|
+
/*! global d3: false */
|
2253
|
+
/*! global d4: false */
|
2254
|
+
'use strict';
|
2255
|
+
|
2256
|
+
/**
|
2257
|
+
The nested group parser is useful for grouped column charts where multiple
|
2258
|
+
data items need to appear relative to the axis value, for example grouped
|
2259
|
+
column charts or multi-series line charts.
|
2260
|
+
|
2261
|
+
_____________________
|
2262
|
+
| _ |
|
2263
|
+
| _ _ | |_ |
|
2264
|
+
| | | | | | | |
|
2265
|
+
----------------------
|
2266
|
+
|
2267
|
+
This module makes use of the d3's "nest" data structure layout
|
2268
|
+
|
2269
|
+
https://github.com/mbostock/d3/wiki/Arrays#-nest
|
2270
|
+
|
2271
|
+
#### Approach
|
2272
|
+
Just like D3, this parser uses a chaining declaritiave style to build up
|
2273
|
+
the necessary prerequistes to create the waterfall data. Here is a simple
|
2274
|
+
example. Given a data item structure like this: {"category" : "Category One", "value" : 23 }
|
2275
|
+
|
2276
|
+
var parser = d4.parsers.nestedGroup()
|
2277
|
+
.x('category')
|
2278
|
+
.y('value')
|
2279
|
+
.value('value');
|
2280
|
+
|
2281
|
+
var groupedColumnData = parser(data);
|
2282
|
+
|
2283
|
+
Keep reading for more information on these various accessor functions.
|
2284
|
+
|
2285
|
+
#### Accessor Methods
|
2286
|
+
* `x` - A function which returns a key to access the x values in the data array
|
2287
|
+
* `y` - A function which returns a key to access the y values in the data array
|
2288
|
+
* `value` - A function which returns a key to access the values in the data array.
|
2289
|
+
* `data` - An array of objects with their dimensions specified like this:
|
2290
|
+
|
2291
|
+
var data = [
|
2292
|
+
{"year" : "2010", "category" : "Category One", "value" : 23 },
|
2293
|
+
{"year" : "2010", "category" : "Category Two", "value" : 55 },
|
2294
|
+
{"year" : "2010", "category" : "Category Three", "value" : -10 },
|
2295
|
+
{"year" : "2010", "category" : "Category Four", "value" : 5 }]
|
2296
|
+
|
2297
|
+
**/
|
2298
|
+
d4.parsers.nestedGroup = function nestedGroup() {
|
2299
|
+
|
2300
|
+
var opts = {
|
2301
|
+
x: {
|
2302
|
+
key: 'x',
|
2303
|
+
values: []
|
2304
|
+
},
|
2305
|
+
y: {
|
2306
|
+
key: 'y',
|
2307
|
+
values: []
|
2308
|
+
},
|
2309
|
+
value: {
|
2310
|
+
key: 'value',
|
2311
|
+
values: []
|
2312
|
+
},
|
2313
|
+
data: []
|
2314
|
+
};
|
2315
|
+
opts.nestKey = function(){
|
2316
|
+
return opts.x.key;
|
2317
|
+
};
|
2318
|
+
|
2319
|
+
var findValues = function(dimensions, items) {
|
2320
|
+
['x', 'y', 'value'].forEach(function(k) {
|
2321
|
+
var layers = items.map(function(d) {
|
2322
|
+
return d[dimensions[k].key];
|
2323
|
+
});
|
2324
|
+
opts[k].values = d3.set(layers).values();
|
2325
|
+
});
|
2326
|
+
};
|
2327
|
+
|
2328
|
+
var nestByDimension = function(key, valueKey, items) {
|
2329
|
+
var nest = d3.nest()
|
2330
|
+
.key(function(d) {
|
2331
|
+
return d[key];
|
2332
|
+
});
|
2333
|
+
return nest.entries(items);
|
2334
|
+
};
|
2335
|
+
|
2336
|
+
var setDimension = function(dim, funct) {
|
2337
|
+
opts[dim].key = d4.functor(funct)();
|
2338
|
+
};
|
2339
|
+
|
2340
|
+
var parser = function(data) {
|
2341
|
+
if (data) {
|
2342
|
+
d4.extend(opts.data, data);
|
2343
|
+
}
|
2344
|
+
|
2345
|
+
findValues(opts, opts.data);
|
2346
|
+
opts.data = nestByDimension(opts.nestKey(), opts.value.key, opts.data);
|
2347
|
+
|
2348
|
+
return opts;
|
2349
|
+
};
|
2350
|
+
|
2351
|
+
parser.nestKey = function(funct) {
|
2352
|
+
opts.nestKey = funct.bind(opts);
|
2353
|
+
return parser;
|
2354
|
+
};
|
2355
|
+
|
2356
|
+
parser.x = function(funct) {
|
2357
|
+
setDimension.bind(opts)('x', funct);
|
2358
|
+
return parser;
|
2359
|
+
};
|
2360
|
+
|
2361
|
+
parser.y = function(funct) {
|
2362
|
+
setDimension.bind(opts)('y', funct);
|
2363
|
+
return parser;
|
2364
|
+
};
|
2365
|
+
|
2366
|
+
parser.value = function(funct) {
|
2367
|
+
setDimension.bind(opts)('value', funct);
|
2368
|
+
return parser;
|
2369
|
+
};
|
2370
|
+
|
2371
|
+
return parser;
|
2372
|
+
};
|
2373
|
+
}).call(this);
|
2374
|
+
|
2375
|
+
(function() {
|
2376
|
+
/*! global d3: false */
|
2377
|
+
/*! global d4: false */
|
2378
|
+
'use strict';
|
2379
|
+
|
2380
|
+
/**
|
2381
|
+
The nested stack parser is useful for charts which take a data series
|
2382
|
+
and wants to sort them across a dimension and then display the results.
|
2383
|
+
The most common usecase would be a stacked column chart like this:
|
2384
|
+
|
2385
|
+
_____________________
|
2386
|
+
| _ |
|
2387
|
+
| | | _ |
|
2388
|
+
| |-| | | _ |
|
2389
|
+
| |-| |-| |-| |
|
2390
|
+
| | | |-| |-| |
|
2391
|
+
----------------------
|
2392
|
+
|
2393
|
+
This module makes use of the d3's "nest" data structure, and "stack" layout
|
2394
|
+
|
2395
|
+
* https://github.com/mbostock/d3/wiki/Arrays#-nest
|
2396
|
+
* https://github.com/mbostock/d3/wiki/Stack-Layout
|
2397
|
+
|
2398
|
+
#### Approach
|
2399
|
+
|
2400
|
+
Just like D3, this parser uses a chaining declaritiave style to build up
|
2401
|
+
the necessary prerequistes to create the stacked data. Here is a simple
|
2402
|
+
example:
|
2403
|
+
|
2404
|
+
var parser = d4.parsers.nestedStack()
|
2405
|
+
.x(function() {
|
2406
|
+
return 'title';
|
2407
|
+
})
|
2408
|
+
.y(function(){
|
2409
|
+
return 'group';
|
2410
|
+
})
|
2411
|
+
.value(function() {
|
2412
|
+
return 'values';
|
2413
|
+
});
|
2414
|
+
|
2415
|
+
var stackedData = parser(data);
|
2416
|
+
|
2417
|
+
Keep reading for more information on these various accessor functions.
|
2418
|
+
|
2419
|
+
##### Benefits
|
2420
|
+
+ Supports negative and positive stacked data series.
|
2421
|
+
|
2422
|
+
##### Limitations
|
2423
|
+
+ The parser expects the stack will occur on the yAxis, which means it is only suitable for column charts presently.
|
2424
|
+
|
2425
|
+
##### Accessor Methods
|
2426
|
+
|
2427
|
+
`x` : - function which returns a key to access the x values in the data array
|
2428
|
+
`y` : - function which returns a key to access the y values in the data array
|
2429
|
+
`value` : - function which returns a key to access the values in the data array.
|
2430
|
+
`data` : array - An array of objects with their dimensions specified like this:
|
2431
|
+
|
2432
|
+
var data = [{ "title": "3 Years", "group" : "one", "value": 30 },
|
2433
|
+
{ "title": "3 Years", "group" : "two", "value": 20 },
|
2434
|
+
{ "title": "3 Years", "group" : "three", "value": 10 },
|
2435
|
+
{ "title": "5 Years", "group" : "one", "value": 3 },
|
2436
|
+
{ "title": "5 Years", "group" : "two", "value": 2 },
|
2437
|
+
{ "title": "5 Years", "group" : "three", "value": 1 }]
|
2438
|
+
|
2439
|
+
##### Example Usage
|
2440
|
+
|
2441
|
+
Given the example data and dimension variables above you can use this module
|
2442
|
+
in the following way:
|
2443
|
+
|
2444
|
+
var parser = d4.parsers.nestedStack()
|
2445
|
+
.x(function() {
|
2446
|
+
return 'title';
|
2447
|
+
})
|
2448
|
+
.y(function(){
|
2449
|
+
return 'group';
|
2450
|
+
})
|
2451
|
+
.value(function() {
|
2452
|
+
return 'value';
|
2453
|
+
})
|
2454
|
+
.call(data);
|
2455
|
+
|
2456
|
+
The `parser` variable will now be an object containing the following structure:
|
2457
|
+
|
2458
|
+
{
|
2459
|
+
data: Array
|
2460
|
+
value: {
|
2461
|
+
key: string,
|
2462
|
+
values: Array
|
2463
|
+
},
|
2464
|
+
x: {
|
2465
|
+
key: string,
|
2466
|
+
values: Array
|
2467
|
+
},
|
2468
|
+
y: {
|
2469
|
+
key: string,
|
2470
|
+
values: Array
|
2471
|
+
}
|
2472
|
+
}
|
2473
|
+
|
2474
|
+
**/
|
2475
|
+
d4.parsers.nestedStack = function nestedStack() {
|
2476
|
+
|
2477
|
+
var opts = {
|
2478
|
+
x: {
|
2479
|
+
key: 'x',
|
2480
|
+
values: []
|
2481
|
+
},
|
2482
|
+
y: {
|
2483
|
+
key: 'y',
|
2484
|
+
values: []
|
2485
|
+
},
|
2486
|
+
value: {
|
2487
|
+
key: 'value',
|
2488
|
+
values: []
|
2489
|
+
},
|
2490
|
+
data: []
|
2491
|
+
};
|
2492
|
+
|
2493
|
+
var findValues = function(dimensions, items) {
|
2494
|
+
['x', 'y', 'value'].forEach(function(k) {
|
2495
|
+
var layers = items.map(function(d) {
|
2496
|
+
return d[dimensions[k].key];
|
2497
|
+
});
|
2498
|
+
opts[k].values = d3.set(layers).values();
|
2499
|
+
});
|
2500
|
+
};
|
2501
|
+
|
2502
|
+
var nestByDimension = function(stackKey, valueKey, items) {
|
2503
|
+
var nest = d3.nest()
|
2504
|
+
.key(function(d) {
|
2505
|
+
return d[stackKey];
|
2506
|
+
});
|
2507
|
+
return nest.entries(items);
|
2508
|
+
};
|
2509
|
+
|
2510
|
+
// By default D3 doesn't handle stacks with negative values very well, we
|
2511
|
+
// need to calulate or our y and y0 values for each group.
|
2512
|
+
var stackByDimension = function(key, items) {
|
2513
|
+
var offsets = {};
|
2514
|
+
|
2515
|
+
var stack = d3.layout.stack()
|
2516
|
+
.values(function(d) {
|
2517
|
+
return d.values;
|
2518
|
+
})
|
2519
|
+
.x(function(d) {
|
2520
|
+
return d[key];
|
2521
|
+
})
|
2522
|
+
.y(function(d) {
|
2523
|
+
return +d[opts.value.key];
|
2524
|
+
})
|
2525
|
+
.out(function(d, y0, y) {
|
2526
|
+
d.y = y;
|
2527
|
+
if (d.y >= 0) {
|
2528
|
+
d.y0 = offsets[d[key] + 'Pos'] = offsets[d[key] + 'Pos'] || 0;
|
2529
|
+
offsets[d[key] + 'Pos'] += y;
|
2530
|
+
} else {
|
2531
|
+
d.y0 = offsets[d[key] + 'Neg'] = offsets[d[key] + 'Neg'] || 0;
|
2532
|
+
offsets[d[key] + 'Neg'] -= Math.abs(y);
|
2533
|
+
}
|
2534
|
+
});
|
2535
|
+
stack(items.reverse());
|
2536
|
+
};
|
2537
|
+
|
2538
|
+
var setDimension = function(dim, funct) {
|
2539
|
+
opts[dim].key = d4.functor(funct)();
|
2540
|
+
};
|
2541
|
+
|
2542
|
+
var parser = function(data) {
|
2543
|
+
if (data) {
|
2544
|
+
d4.extend(opts.data, data);
|
2545
|
+
}
|
2546
|
+
|
2547
|
+
findValues(opts, opts.data);
|
2548
|
+
opts.data = nestByDimension(opts.y.key, opts.value.key, opts.data);
|
2549
|
+
|
2550
|
+
stackByDimension(opts.x.key, opts.data);
|
2551
|
+
return opts;
|
2552
|
+
};
|
2553
|
+
|
2554
|
+
parser.x = function(funct) {
|
2555
|
+
setDimension.bind(opts)('x', funct);
|
2556
|
+
return parser;
|
2557
|
+
};
|
2558
|
+
|
2559
|
+
parser.y = function(funct) {
|
2560
|
+
setDimension.bind(opts)('y', funct);
|
2561
|
+
return parser;
|
2562
|
+
};
|
2563
|
+
|
2564
|
+
parser.value = function(funct) {
|
2565
|
+
setDimension.bind(opts)('value', funct);
|
2566
|
+
return parser;
|
2567
|
+
};
|
2568
|
+
|
2569
|
+
return parser;
|
2570
|
+
};
|
2571
|
+
}).call(this);
|
2572
|
+
|
2573
|
+
(function() {
|
2574
|
+
/*! global d3: false */
|
2575
|
+
/*! global d4: false */
|
2576
|
+
'use strict';
|
2577
|
+
|
2578
|
+
/**
|
2579
|
+
The waterfall parser is useful for waterfall charts where data items need to account
|
2580
|
+
for the position of earlier values:
|
2581
|
+
|
2582
|
+
_____________________
|
2583
|
+
| _ _______ |
|
2584
|
+
| |_|___ | | | | |
|
2585
|
+
| |_|__|_| | | |
|
2586
|
+
| |_| |
|
2587
|
+
----------------------
|
2588
|
+
|
2589
|
+
This module makes use of the d3's "nest" data structure, and "stack" layout
|
2590
|
+
https://github.com/mbostock/d3/wiki/Arrays#-nest
|
2591
|
+
https://github.com/mbostock/d3/wiki/Stack-Layout
|
2592
|
+
|
2593
|
+
|
2594
|
+
Approach:
|
2595
|
+
Just like D3, this parser uses a chaining declaritiave style to build up
|
2596
|
+
the necessary prerequistes to create the waterfall data. Here is a simple
|
2597
|
+
example. Given a data item structure like this: {"category" : "Category One", "value" : 23 }
|
2598
|
+
|
2599
|
+
var parser = d4.parsers.waterfall()
|
2600
|
+
.x(function() {
|
2601
|
+
return 'category';
|
2602
|
+
})
|
2603
|
+
.y(function(){
|
2604
|
+
return 'value';
|
2605
|
+
})
|
2606
|
+
.value(function() {
|
2607
|
+
return 'value';
|
2608
|
+
});
|
2609
|
+
|
2610
|
+
var waterfallData = parser(data);
|
2611
|
+
|
2612
|
+
Keep reading for more information on these various accessor functions.
|
2613
|
+
|
2614
|
+
Benefits:
|
2615
|
+
* Supports horizontal or vertical waterfalls
|
2616
|
+
* Supports totaling series using a special "e" value in a data item.
|
2617
|
+
|
2618
|
+
Limitations:
|
2619
|
+
* Does not support stacked waterfalls.
|
2620
|
+
|
2621
|
+
Accessor Methods:
|
2622
|
+
* x : - function which returns a key to access the x values in the data array
|
2623
|
+
* y : - function which returns a key to access the y values in the data array
|
2624
|
+
* value : - function which returns a key to access the values in the data array.
|
2625
|
+
* data : array - An array of objects with their dimensions specified
|
2626
|
+
like this:
|
2627
|
+
|
2628
|
+
var data = [
|
2629
|
+
{"category" : "Category One", "value" : 23 },
|
2630
|
+
{"category" : "Category Two", "value" : 55 },
|
2631
|
+
{"category" : "Category Three", "value" : -10 },
|
2632
|
+
{"category" : "Category Four", "value" : 5 },
|
2633
|
+
{"category" : "Category Five", "value" : "e" }]
|
2634
|
+
|
2635
|
+
SPECIAL NOTE:
|
2636
|
+
Waterfalls charts typically have the ability to display subtotals at any point.
|
2637
|
+
In order to use this feature simply set the value of your subtotal column to "e."
|
2638
|
+
|
2639
|
+
Example Usage:
|
2640
|
+
Given the example data and dimension variables above you can use this module
|
2641
|
+
in the following way:
|
2642
|
+
|
2643
|
+
var parser = d4.parsers.nestedStack()
|
2644
|
+
.dimensions(dimensions)
|
2645
|
+
.call(data);
|
2646
|
+
|
2647
|
+
The `parser` variable will now be an object containing the following structure:
|
2648
|
+
{
|
2649
|
+
data: Array
|
2650
|
+
value: {
|
2651
|
+
key: string,
|
2652
|
+
values: Array
|
2653
|
+
},
|
2654
|
+
x: {
|
2655
|
+
key: string,
|
2656
|
+
values: Array
|
2657
|
+
},
|
2658
|
+
y: {
|
2659
|
+
key: string,
|
2660
|
+
values: Array
|
2661
|
+
}
|
2662
|
+
}
|
2663
|
+
|
2664
|
+
Taking these attributes one-by-one:
|
2665
|
+
* data - is an array of items stacked by D3
|
2666
|
+
* value - an object with a key representing the value accessor and an array of values
|
2667
|
+
* x - an object with a key representing the x accessor and an array of values
|
2668
|
+
* y - an object with a key representing the y accessor and an array of values
|
2669
|
+
|
2670
|
+
**/
|
2671
|
+
d4.parsers.waterfall = function waterfall() {
|
2672
|
+
|
2673
|
+
var opts = {
|
2674
|
+
x: {
|
2675
|
+
key: 'x',
|
2676
|
+
values: []
|
2677
|
+
},
|
2678
|
+
y: {
|
2679
|
+
key: 'y',
|
2680
|
+
values: []
|
2681
|
+
},
|
2682
|
+
value: {
|
2683
|
+
key: 'value',
|
2684
|
+
values: []
|
2685
|
+
},
|
2686
|
+
data: []
|
2687
|
+
};
|
2688
|
+
opts.nestKey = function(){
|
2689
|
+
return opts.x.key;
|
2690
|
+
};
|
2691
|
+
|
2692
|
+
|
2693
|
+
var findValues = function(dimensions, items) {
|
2694
|
+
['x', 'y', 'value'].forEach(function(k) {
|
2695
|
+
var layers = items.map(function(d) {
|
2696
|
+
return d[dimensions[k].key];
|
2697
|
+
});
|
2698
|
+
opts[k].values = d3.set(layers).values();
|
2699
|
+
});
|
2700
|
+
};
|
2701
|
+
|
2702
|
+
var nestByDimension = function(key, valueKey, items) {
|
2703
|
+
var nest = d3.nest()
|
2704
|
+
.key(function(d) {
|
2705
|
+
return d[key];
|
2706
|
+
});
|
2707
|
+
return nest.entries(items);
|
2708
|
+
};
|
2709
|
+
|
2710
|
+
var stackByDimension = function(key, items) {
|
2711
|
+
var lastOffset = 0;
|
2712
|
+
var noNaN = function(num){
|
2713
|
+
return isNaN(num) ? 0 : num;
|
2714
|
+
};
|
2715
|
+
var stack = d3.layout.stack()
|
2716
|
+
.values(function(d) {
|
2717
|
+
return d.values;
|
2718
|
+
})
|
2719
|
+
.x(function(d) {
|
2720
|
+
return d[key];
|
2721
|
+
})
|
2722
|
+
.y(function(d) {
|
2723
|
+
return +d[opts.value.key];
|
2724
|
+
})
|
2725
|
+
.out(function(d, y0, y) {
|
2726
|
+
if(isNaN(y)){
|
2727
|
+
if(isNaN(y0)){
|
2728
|
+
y0 = lastOffset;
|
2729
|
+
}
|
2730
|
+
d.y0 = 0;
|
2731
|
+
d.y = y0;
|
2732
|
+
d[opts.value.key] = y0;
|
2733
|
+
lastOffset = y0;
|
2734
|
+
} else {
|
2735
|
+
if(isNaN(y0)){
|
2736
|
+
d.y0 = lastOffset;
|
2737
|
+
lastOffset += y;
|
2738
|
+
} else {
|
2739
|
+
d.y0 = y0;
|
2740
|
+
}
|
2741
|
+
d.y = y;
|
2742
|
+
d[opts.value.key] = noNaN(d[opts.value.key]);
|
2743
|
+
}
|
2744
|
+
});
|
2745
|
+
stack(items);
|
2746
|
+
};
|
2747
|
+
|
2748
|
+
var setDimension = function(dim, funct) {
|
2749
|
+
opts[dim].key = d4.functor(funct)();
|
2750
|
+
};
|
2751
|
+
|
2752
|
+
var parser = function(data) {
|
2753
|
+
if (data) {
|
2754
|
+
d4.extend(opts.data, data);
|
2755
|
+
}
|
2756
|
+
|
2757
|
+
findValues(opts, opts.data);
|
2758
|
+
opts.data = nestByDimension(opts.nestKey(), opts.value.key, opts.data);
|
2759
|
+
|
2760
|
+
stackByDimension(opts.x.key, opts.data);
|
2761
|
+
return opts;
|
2762
|
+
};
|
2763
|
+
|
2764
|
+
parser.nestKey = function(funct) {
|
2765
|
+
opts.nestKey = funct.bind(opts);
|
2766
|
+
return parser;
|
2767
|
+
};
|
2768
|
+
|
2769
|
+
parser.x = function(funct) {
|
2770
|
+
setDimension.bind(opts)('x', funct);
|
2771
|
+
return parser;
|
2772
|
+
};
|
2773
|
+
|
2774
|
+
parser.y = function(funct) {
|
2775
|
+
setDimension.bind(opts)('y', funct);
|
2776
|
+
return parser;
|
2777
|
+
};
|
2778
|
+
|
2779
|
+
parser.value = function(funct) {
|
2780
|
+
setDimension.bind(opts)('value', funct);
|
2781
|
+
return parser;
|
2782
|
+
};
|
2783
|
+
|
2784
|
+
return parser;
|
2785
|
+
};
|
2786
|
+
}).call(this);
|