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 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);