d4-rails 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
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
@@ -0,0 +1,17 @@
1
+ *.gem
2
+ *.rbc
3
+ .bundle
4
+ .config
5
+ .yardoc
6
+ Gemfile.lock
7
+ InstalledFiles
8
+ _yardoc
9
+ coverage
10
+ doc/
11
+ lib/bundler/man
12
+ pkg
13
+ rdoc
14
+ spec/reports
15
+ test/tmp
16
+ test/version_tmp
17
+ tmp
data/Gemfile ADDED
@@ -0,0 +1,4 @@
1
+ source 'https://rubygems.org'
2
+
3
+ # Specify your gem's dependencies in spicon.gemspec
4
+ gemspec
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);