forked from dc-js/dc.js
-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathcoordinate-grid-mixin.js
1328 lines (1172 loc) · 44.8 KB
/
coordinate-grid-mixin.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
/**
* Coordinate Grid is an abstract base chart designed to support a number of coordinate grid based
* concrete chart types, e.g. bar chart, line chart, and bubble chart.
* @name coordinateGridMixin
* @memberof dc
* @mixin
* @mixes dc.colorMixin
* @mixes dc.marginMixin
* @mixes dc.baseMixin
* @param {Object} _chart
* @returns {dc.coordinateGridMixin}
*/
dc.coordinateGridMixin = function (_chart) {
var GRID_LINE_CLASS = 'grid-line';
var HORIZONTAL_CLASS = 'horizontal';
var VERTICAL_CLASS = 'vertical';
var Y_AXIS_LABEL_CLASS = 'y-axis-label';
var X_AXIS_LABEL_CLASS = 'x-axis-label';
var DEFAULT_AXIS_LABEL_PADDING = 12;
_chart = dc.colorMixin(dc.marginMixin(dc.baseMixin(_chart)));
_chart.colors(d3.scale.category10());
_chart._mandatoryAttributes().push('x');
var _parent;
var _g;
var _chartBodyG;
var _x;
var _xOriginalDomain;
var _xAxis = d3.svg.axis().orient('bottom');
var _xUnits = dc.units.integers;
var _xAxisPadding = 0;
var _xAxisPaddingUnit = 'day';
var _xElasticity = false;
var _xAxisLabel;
var _xAxisLabelPadding = 0;
var _lastXDomain;
var _y;
var _yAxis = d3.svg.axis().orient('left');
var _yAxisPadding = 0;
var _yElasticity = false;
var _yAxisLabel;
var _yAxisLabelPadding = 0;
var _brush = d3.svg.brush();
var _brushOn = true;
var _round;
var _renderHorizontalGridLine = false;
var _renderVerticalGridLine = false;
var _refocused = false, _resizing = false;
var _unitCount;
var _zoomScale = [1, Infinity];
var _zoomOutRestrict = true;
var _zoom = d3.behavior.zoom().on('zoom', zoomHandler);
var _nullZoom = d3.behavior.zoom().on('zoom', null);
var _hasBeenMouseZoomable = false;
var _rangeChart;
var _focusChart;
var _mouseZoomable = false;
var _clipPadding = 0;
var _outerRangeBandPadding = 0.5;
var _rangeBandPadding = 0;
var _useRightYAxis = false;
/**
* When changing the domain of the x or y scale, it is necessary to tell the chart to recalculate
* and redraw the axes. (`.rescale()` is called automatically when the x or y scale is replaced
* with {@link dc.coordinateGridMixin+x .x()} or {@link dc.coordinateGridMixin#y .y()}, and has
* no effect on elastic scales.)
* @method rescale
* @memberof dc.coordinateGridMixin
* @instance
* @returns {dc.coordinateGridMixin}
*/
_chart.rescale = function () {
_unitCount = undefined;
_resizing = true;
return _chart;
};
_chart.resizing = function () {
return _resizing;
};
/**
* Get or set the range selection chart associated with this instance. Setting the range selection
* chart using this function will automatically update its selection brush when the current chart
* zooms in. In return the given range chart will also automatically attach this chart as its focus
* chart hence zoom in when range brush updates.
*
* Usually the range and focus charts will share a dimension. The range chart will set the zoom
* boundaries for the focus chart, so its dimension values must be compatible with the domain of
* the focus chart.
*
* See the [Nasdaq 100 Index](http://dc-js.github.com/dc.js/) example for this effect in action.
* @method rangeChart
* @memberof dc.coordinateGridMixin
* @instance
* @param {dc.coordinateGridMixin} [rangeChart]
* @returns {dc.coordinateGridMixin}
*/
_chart.rangeChart = function (rangeChart) {
if (!arguments.length) {
return _rangeChart;
}
_rangeChart = rangeChart;
_rangeChart.focusChart(_chart);
return _chart;
};
/**
* Get or set the scale extent for mouse zooms.
* @method zoomScale
* @memberof dc.coordinateGridMixin
* @instance
* @param {Array<Number|Date>} [extent=[1, Infinity]]
* @returns {Array<Number|Date>|dc.coordinateGridMixin}
*/
_chart.zoomScale = function (extent) {
if (!arguments.length) {
return _zoomScale;
}
_zoomScale = extent;
return _chart;
};
/**
* Get or set the zoom restriction for the chart. If true limits the zoom to origional domain of the chart.
* @method zoomOutRestrict
* @memberof dc.coordinateGridMixin
* @instance
* @param {Boolean} [zoomOutRestrict=true]
* @returns {Boolean|dc.coordinateGridMixin}
*/
_chart.zoomOutRestrict = function (zoomOutRestrict) {
if (!arguments.length) {
return _zoomOutRestrict;
}
_zoomScale[0] = zoomOutRestrict ? 1 : 0;
_zoomOutRestrict = zoomOutRestrict;
return _chart;
};
_chart._generateG = function (parent) {
if (parent === undefined) {
_parent = _chart.svg();
} else {
_parent = parent;
}
var href = window.location.href.split('#')[0];
_g = _parent.append('g');
_chartBodyG = _g.append('g').attr('class', 'chart-body')
.attr('transform', 'translate(' + _chart.margins().left + ', ' + _chart.margins().top + ')')
.attr('clip-path', 'url(' + href + '#' + getClipPathId() + ')');
return _g;
};
/**
* Get or set the root g element. This method is usually used to retrieve the g element in order to
* overlay custom svg drawing programatically. **Caution**: The root g element is usually generated
* by dc.js internals, and resetting it might produce unpredictable result.
* @method g
* @memberof dc.coordinateGridMixin
* @instance
* @param {SVGElement} [gElement]
* @returns {SVGElement|dc.coordinateGridMixin}
*/
_chart.g = function (gElement) {
if (!arguments.length) {
return _g;
}
_g = gElement;
return _chart;
};
/**
* Set or get mouse zoom capability flag (default: false). When turned on the chart will be
* zoomable using the mouse wheel. If the range selector chart is attached zooming will also update
* the range selection brush on the associated range selector chart.
* @method mouseZoomable
* @memberof dc.coordinateGridMixin
* @instance
* @param {Boolean} [mouseZoomable=false]
* @returns {Boolean|dc.coordinateGridMixin}
*/
_chart.mouseZoomable = function (mouseZoomable) {
if (!arguments.length) {
return _mouseZoomable;
}
_mouseZoomable = mouseZoomable;
return _chart;
};
/**
* Retrieve the svg group for the chart body.
* @method chartBodyG
* @memberof dc.coordinateGridMixin
* @instance
* @param {SVGElement} [chartBodyG]
* @returns {SVGElement}
*/
_chart.chartBodyG = function (chartBodyG) {
if (!arguments.length) {
return _chartBodyG;
}
_chartBodyG = chartBodyG;
return _chart;
};
/**
* **mandatory**
*
* Get or set the x scale. The x scale can be any d3
* {@link https://github.com/d3/d3-3.x-api-reference/blob/master/Quantitative-Scales.md quantitive scale} or
* {@link https://github.com/d3/d3-3.x-api-reference/blob/master/Ordinal-Scales.md ordinal scale}.
* @method x
* @memberof dc.coordinateGridMixin
* @instance
* @see {@link https://github.com/d3/d3-3.x-api-reference/blob/master/Scales.md d3.scale}
* @example
* // set x to a linear scale
* chart.x(d3.scale.linear().domain([-2500, 2500]))
* // set x to a time scale to generate histogram
* chart.x(d3.time.scale().domain([new Date(1985, 0, 1), new Date(2012, 11, 31)]))
* @param {d3.scale} [xScale]
* @returns {d3.scale|dc.coordinateGridMixin}
*/
_chart.x = function (xScale) {
if (!arguments.length) {
return _x;
}
_x = xScale;
_xOriginalDomain = _x.domain();
_chart.rescale();
return _chart;
};
_chart.xOriginalDomain = function () {
return _xOriginalDomain;
};
/**
* Set or get the xUnits function. The coordinate grid chart uses the xUnits function to calculate
* the number of data projections on x axis such as the number of bars for a bar chart or the
* number of dots for a line chart. This function is expected to return a Javascript array of all
* data points on x axis, or the number of points on the axis. [d3 time range functions
* d3.time.days, d3.time.months, and
* d3.time.years](https://github.com/d3/d3-3.x-api-reference/blob/master/Time-Intervals.md#aliases) are all valid xUnits
* function. dc.js also provides a few units function, see the {@link dc.units Units Namespace} for
* a list of built-in units functions.
* @method xUnits
* @memberof dc.coordinateGridMixin
* @instance
* @todo Add docs for utilities
* @example
* // set x units to count days
* chart.xUnits(d3.time.days);
* // set x units to count months
* chart.xUnits(d3.time.months);
*
* // A custom xUnits function can be used as long as it follows the following interface:
* // units in integer
* function(start, end, xDomain) {
* // simply calculates how many integers in the domain
* return Math.abs(end - start);
* };
*
* // fixed units
* function(start, end, xDomain) {
* // be aware using fixed units will disable the focus/zoom ability on the chart
* return 1000;
* @param {Function} [xUnits=dc.units.integers]
* @returns {Function|dc.coordinateGridMixin}
*/
_chart.xUnits = function (xUnits) {
if (!arguments.length) {
return _xUnits;
}
_xUnits = xUnits;
return _chart;
};
/**
* Set or get the x axis used by a particular coordinate grid chart instance. This function is most
* useful when x axis customization is required. The x axis in dc.js is an instance of a
* {@link https://github.com/d3/d3-3.x-api-reference/blob/master/SVG-Axes.md#axis d3 axis object};
* therefore it supports any valid d3 axis manipulation.
*
* **Caution**: The x axis is usually generated internally by dc; resetting it may cause
* unexpected results. Note also that when used as a getter, this function is not chainable:
* it returns the axis, not the chart,
* {@link https://github.com/dc-js/dc.js/wiki/FAQ#why-does-everything-break-after-a-call-to-xaxis-or-yaxis
* so attempting to call chart functions after calling `.xAxis()` will fail}.
* @method xAxis
* @memberof dc.coordinateGridMixin
* @instance
* @see {@link https://github.com/d3/d3-3.x-api-reference/blob/master/SVG-Axes.md#axis d3.svg.axis}
* @example
* // customize x axis tick format
* chart.xAxis().tickFormat(function(v) {return v + '%';});
* // customize x axis tick values
* chart.xAxis().tickValues([0, 100, 200, 300]);
* @param {d3.svg.axis} [xAxis=d3.svg.axis().orient('bottom')]
* @returns {d3.svg.axis|dc.coordinateGridMixin}
*/
_chart.xAxis = function (xAxis) {
if (!arguments.length) {
return _xAxis;
}
_xAxis = xAxis;
return _chart;
};
/**
* Turn on/off elastic x axis behavior. If x axis elasticity is turned on, then the grid chart will
* attempt to recalculate the x axis range whenever a redraw event is triggered.
* @method elasticX
* @memberof dc.coordinateGridMixin
* @instance
* @param {Boolean} [elasticX=false]
* @returns {Boolean|dc.coordinateGridMixin}
*/
_chart.elasticX = function (elasticX) {
if (!arguments.length) {
return _xElasticity;
}
_xElasticity = elasticX;
return _chart;
};
/**
* Set or get x axis padding for the elastic x axis. The padding will be added to both end of the x
* axis if elasticX is turned on; otherwise it is ignored.
*
* Padding can be an integer or percentage in string (e.g. '10%'). Padding can be applied to
* number or date x axes. When padding a date axis, an integer represents number of units being padded
* and a percentage string will be treated the same as an integer. The unit will be determined by the
* xAxisPaddingUnit variable.
* @method xAxisPadding
* @memberof dc.coordinateGridMixin
* @instance
* @param {Number|String} [padding=0]
* @returns {Number|String|dc.coordinateGridMixin}
*/
_chart.xAxisPadding = function (padding) {
if (!arguments.length) {
return _xAxisPadding;
}
_xAxisPadding = padding;
return _chart;
};
/**
* Set or get x axis padding unit for the elastic x axis. The padding unit will determine which unit to
* use when applying xAxis padding if elasticX is turned on and if x-axis uses a time dimension;
* otherwise it is ignored.
*
* Padding unit is a string that will be used when the padding is calculated. Available parameters are
* the available d3 time intervals; see
* {@link https://github.com/d3/d3-3.x-api-reference/blob/master/Time-Intervals.md#interval d3.time.interval}.
* @method xAxisPaddingUnit
* @memberof dc.coordinateGridMixin
* @instance
* @param {String} [unit='days']
* @returns {String|dc.coordinateGridMixin}
*/
_chart.xAxisPaddingUnit = function (unit) {
if (!arguments.length) {
return _xAxisPaddingUnit;
}
_xAxisPaddingUnit = unit;
return _chart;
};
/**
* Returns the number of units displayed on the x axis using the unit measure configured by
* {@link dc.coordinateGridMixin#xUnits xUnits}.
* @method xUnitCount
* @memberof dc.coordinateGridMixin
* @instance
* @returns {Number}
*/
_chart.xUnitCount = function () {
if (_unitCount === undefined) {
var units = _chart.xUnits()(_chart.x().domain()[0], _chart.x().domain()[1], _chart.x().domain());
if (units instanceof Array) {
_unitCount = units.length;
} else {
_unitCount = units;
}
}
return _unitCount;
};
/**
* Gets or sets whether the chart should be drawn with a right axis instead of a left axis. When
* used with a chart in a composite chart, allows both left and right Y axes to be shown on a
* chart.
* @method useRightYAxis
* @memberof dc.coordinateGridMixin
* @instance
* @param {Boolean} [useRightYAxis=false]
* @returns {Boolean|dc.coordinateGridMixin}
*/
_chart.useRightYAxis = function (useRightYAxis) {
if (!arguments.length) {
return _useRightYAxis;
}
_useRightYAxis = useRightYAxis;
return _chart;
};
/**
* Returns true if the chart is using ordinal xUnits ({@link dc.units.ordinal dc.units.ordinal}, or false
* otherwise. Most charts behave differently with ordinal data and use the result of this method to
* trigger the appropriate logic.
* @method isOrdinal
* @memberof dc.coordinateGridMixin
* @instance
* @returns {Boolean}
*/
_chart.isOrdinal = function () {
return _chart.xUnits() === dc.units.ordinal;
};
_chart._useOuterPadding = function () {
return true;
};
_chart._ordinalXDomain = function () {
var groups = _chart._computeOrderedGroups(_chart.data());
return groups.map(_chart.keyAccessor());
};
function compareDomains (d1, d2) {
return !d1 || !d2 || d1.length !== d2.length ||
d1.some(function (elem, i) { return (elem && d2[i]) ? elem.toString() !== d2[i].toString() : elem === d2[i]; });
}
function prepareXAxis (g, render) {
if (!_chart.isOrdinal()) {
if (_chart.elasticX()) {
_x.domain([_chart.xAxisMin(), _chart.xAxisMax()]);
}
} else { // _chart.isOrdinal()
if (_chart.elasticX() || _x.domain().length === 0) {
_x.domain(_chart._ordinalXDomain());
}
}
// has the domain changed?
var xdom = _x.domain();
if (render || compareDomains(_lastXDomain, xdom)) {
_chart.rescale();
}
_lastXDomain = xdom;
// please can't we always use rangeBands for bar charts?
if (_chart.isOrdinal()) {
_x.rangeBands([0, _chart.xAxisLength()], _rangeBandPadding,
_chart._useOuterPadding() ? _outerRangeBandPadding : 0);
} else {
_x.range([0, _chart.xAxisLength()]);
}
_xAxis = _xAxis.scale(_chart.x());
renderVerticalGridLines(g);
}
_chart.renderXAxis = function (g) {
var axisXG = g.select('g.x');
if (axisXG.empty()) {
axisXG = g.append('g')
.attr('class', 'axis x')
.attr('transform', 'translate(' + _chart.margins().left + ',' + _chart._xAxisY() + ')');
}
var axisXLab = g.select('text.' + X_AXIS_LABEL_CLASS);
if (axisXLab.empty() && _chart.xAxisLabel()) {
axisXLab = g.append('text')
.attr('class', X_AXIS_LABEL_CLASS)
.attr('transform', 'translate(' + (_chart.margins().left + _chart.xAxisLength() / 2) + ',' +
(_chart.height() - _xAxisLabelPadding) + ')')
.attr('text-anchor', 'middle');
}
if (_chart.xAxisLabel() && axisXLab.text() !== _chart.xAxisLabel()) {
axisXLab.text(_chart.xAxisLabel());
}
dc.transition(axisXG, _chart.transitionDuration(), _chart.transitionDelay())
.attr('transform', 'translate(' + _chart.margins().left + ',' + _chart._xAxisY() + ')')
.call(_xAxis);
dc.transition(axisXLab, _chart.transitionDuration(), _chart.transitionDelay())
.attr('transform', 'translate(' + (_chart.margins().left + _chart.xAxisLength() / 2) + ',' +
(_chart.height() - _xAxisLabelPadding) + ')');
};
function renderVerticalGridLines (g) {
var gridLineG = g.select('g.' + VERTICAL_CLASS);
if (_renderVerticalGridLine) {
if (gridLineG.empty()) {
gridLineG = g.insert('g', ':first-child')
.attr('class', GRID_LINE_CLASS + ' ' + VERTICAL_CLASS)
.attr('transform', 'translate(' + _chart.margins().left + ',' + _chart.margins().top + ')');
}
var ticks = _xAxis.tickValues() ? _xAxis.tickValues() :
(typeof _x.ticks === 'function' ? _x.ticks(_xAxis.ticks()[0]) : _x.domain());
var lines = gridLineG.selectAll('line')
.data(ticks);
// enter
var linesGEnter = lines.enter()
.append('line')
.attr('x1', function (d) {
return _x(d);
})
.attr('y1', _chart._xAxisY() - _chart.margins().top)
.attr('x2', function (d) {
return _x(d);
})
.attr('y2', 0)
.attr('opacity', 0);
dc.transition(linesGEnter, _chart.transitionDuration(), _chart.transitionDelay())
.attr('opacity', 1);
// update
dc.transition(lines, _chart.transitionDuration(), _chart.transitionDelay())
.attr('x1', function (d) {
return _x(d);
})
.attr('y1', _chart._xAxisY() - _chart.margins().top)
.attr('x2', function (d) {
return _x(d);
})
.attr('y2', 0);
// exit
lines.exit().remove();
} else {
gridLineG.selectAll('line').remove();
}
}
_chart._xAxisY = function () {
return (_chart.height() - _chart.margins().bottom);
};
_chart.xAxisLength = function () {
return _chart.effectiveWidth();
};
/**
* Set or get the x axis label. If setting the label, you may optionally include additional padding to
* the margin to make room for the label. By default the padded is set to 12 to accomodate the text height.
* @method xAxisLabel
* @memberof dc.coordinateGridMixin
* @instance
* @param {String} [labelText]
* @param {Number} [padding=12]
* @returns {String}
*/
_chart.xAxisLabel = function (labelText, padding) {
if (!arguments.length) {
return _xAxisLabel;
}
_xAxisLabel = labelText;
_chart.margins().bottom -= _xAxisLabelPadding;
_xAxisLabelPadding = (padding === undefined) ? DEFAULT_AXIS_LABEL_PADDING : padding;
_chart.margins().bottom += _xAxisLabelPadding;
return _chart;
};
_chart._prepareYAxis = function (g) {
if (_y === undefined || _chart.elasticY()) {
if (_y === undefined) {
_y = d3.scale.linear();
}
var min = _chart.yAxisMin() || 0,
max = _chart.yAxisMax() || 0;
_y.domain([min, max]).rangeRound([_chart.yAxisHeight(), 0]);
}
_y.range([_chart.yAxisHeight(), 0]);
_yAxis = _yAxis.scale(_y);
if (_useRightYAxis) {
_yAxis.orient('right');
}
_chart._renderHorizontalGridLinesForAxis(g, _y, _yAxis);
};
_chart.renderYAxisLabel = function (axisClass, text, rotation, labelXPosition) {
labelXPosition = labelXPosition || _yAxisLabelPadding;
var axisYLab = _chart.g().select('text.' + Y_AXIS_LABEL_CLASS + '.' + axisClass + '-label');
var labelYPosition = (_chart.margins().top + _chart.yAxisHeight() / 2);
if (axisYLab.empty() && text) {
axisYLab = _chart.g().append('text')
.attr('transform', 'translate(' + labelXPosition + ',' + labelYPosition + '),rotate(' + rotation + ')')
.attr('class', Y_AXIS_LABEL_CLASS + ' ' + axisClass + '-label')
.attr('text-anchor', 'middle')
.text(text);
}
if (text && axisYLab.text() !== text) {
axisYLab.text(text);
}
dc.transition(axisYLab, _chart.transitionDuration(), _chart.transitionDelay())
.attr('transform', 'translate(' + labelXPosition + ',' + labelYPosition + '),rotate(' + rotation + ')');
};
_chart.renderYAxisAt = function (axisClass, axis, position) {
var axisYG = _chart.g().select('g.' + axisClass);
if (axisYG.empty()) {
axisYG = _chart.g().append('g')
.attr('class', 'axis ' + axisClass)
.attr('transform', 'translate(' + position + ',' + _chart.margins().top + ')');
}
dc.transition(axisYG, _chart.transitionDuration(), _chart.transitionDelay())
.attr('transform', 'translate(' + position + ',' + _chart.margins().top + ')')
.call(axis);
};
_chart.renderYAxis = function () {
var axisPosition = _useRightYAxis ? (_chart.width() - _chart.margins().right) : _chart._yAxisX();
_chart.renderYAxisAt('y', _yAxis, axisPosition);
var labelPosition = _useRightYAxis ? (_chart.width() - _yAxisLabelPadding) : _yAxisLabelPadding;
var rotation = _useRightYAxis ? 90 : -90;
_chart.renderYAxisLabel('y', _chart.yAxisLabel(), rotation, labelPosition);
};
_chart._renderHorizontalGridLinesForAxis = function (g, scale, axis) {
var gridLineG = g.select('g.' + HORIZONTAL_CLASS);
if (_renderHorizontalGridLine) {
var ticks = axis.tickValues() ? axis.tickValues() : scale.ticks(axis.ticks()[0]);
if (gridLineG.empty()) {
gridLineG = g.insert('g', ':first-child')
.attr('class', GRID_LINE_CLASS + ' ' + HORIZONTAL_CLASS)
.attr('transform', 'translate(' + _chart.margins().left + ',' + _chart.margins().top + ')');
}
var lines = gridLineG.selectAll('line')
.data(ticks);
// enter
var linesGEnter = lines.enter()
.append('line')
.attr('x1', 1)
.attr('y1', function (d) {
return scale(d);
})
.attr('x2', _chart.xAxisLength())
.attr('y2', function (d) {
return scale(d);
})
.attr('opacity', 0);
dc.transition(linesGEnter, _chart.transitionDuration(), _chart.transitionDelay())
.attr('opacity', 1);
// update
dc.transition(lines, _chart.transitionDuration(), _chart.transitionDelay())
.attr('x1', 1)
.attr('y1', function (d) {
return scale(d);
})
.attr('x2', _chart.xAxisLength())
.attr('y2', function (d) {
return scale(d);
});
// exit
lines.exit().remove();
} else {
gridLineG.selectAll('line').remove();
}
};
_chart._yAxisX = function () {
return _chart.useRightYAxis() ? _chart.width() - _chart.margins().right : _chart.margins().left;
};
/**
* Set or get the y axis label. If setting the label, you may optionally include additional padding
* to the margin to make room for the label. By default the padding is set to 12 to accommodate the
* text height.
* @method yAxisLabel
* @memberof dc.coordinateGridMixin
* @instance
* @param {String} [labelText]
* @param {Number} [padding=12]
* @returns {String|dc.coordinateGridMixin}
*/
_chart.yAxisLabel = function (labelText, padding) {
if (!arguments.length) {
return _yAxisLabel;
}
_yAxisLabel = labelText;
_chart.margins().left -= _yAxisLabelPadding;
_yAxisLabelPadding = (padding === undefined) ? DEFAULT_AXIS_LABEL_PADDING : padding;
_chart.margins().left += _yAxisLabelPadding;
return _chart;
};
/**
* Get or set the y scale. The y scale is typically automatically determined by the chart implementation.
* @method y
* @memberof dc.coordinateGridMixin
* @instance
* @see {@link https://github.com/d3/d3-3.x-api-reference/blob/master/Scales.md d3.scale}
* @param {d3.scale} [yScale]
* @returns {d3.scale|dc.coordinateGridMixin}
*/
_chart.y = function (yScale) {
if (!arguments.length) {
return _y;
}
_y = yScale;
_chart.rescale();
return _chart;
};
/**
* Set or get the y axis used by the coordinate grid chart instance. This function is most useful
* when y axis customization is required. The y axis in dc.js is simply an instance of a [d3 axis
* object](https://github.com/d3/d3-3.x-api-reference/blob/master/SVG-Axes.md#axis); therefore it supports any
* valid d3 axis manipulation.
*
* **Caution**: The y axis is usually generated internally by dc; resetting it may cause
* unexpected results. Note also that when used as a getter, this function is not chainable: it
* returns the axis, not the chart,
* {@link https://github.com/dc-js/dc.js/wiki/FAQ#why-does-everything-break-after-a-call-to-xaxis-or-yaxis
* so attempting to call chart functions after calling `.yAxis()` will fail}.
* @method yAxis
* @memberof dc.coordinateGridMixin
* @instance
* @see {@link https://github.com/d3/d3-3.x-api-reference/blob/master/SVG-Axes.md#axis d3.svg.axis}
* @example
* // customize y axis tick format
* chart.yAxis().tickFormat(function(v) {return v + '%';});
* // customize y axis tick values
* chart.yAxis().tickValues([0, 100, 200, 300]);
* @param {d3.svg.axis} [yAxis=d3.svg.axis().orient('left')]
* @returns {d3.svg.axis|dc.coordinateGridMixin}
*/
_chart.yAxis = function (yAxis) {
if (!arguments.length) {
return _yAxis;
}
_yAxis = yAxis;
return _chart;
};
/**
* Turn on/off elastic y axis behavior. If y axis elasticity is turned on, then the grid chart will
* attempt to recalculate the y axis range whenever a redraw event is triggered.
* @method elasticY
* @memberof dc.coordinateGridMixin
* @instance
* @param {Boolean} [elasticY=false]
* @returns {Boolean|dc.coordinateGridMixin}
*/
_chart.elasticY = function (elasticY) {
if (!arguments.length) {
return _yElasticity;
}
_yElasticity = elasticY;
return _chart;
};
/**
* Turn on/off horizontal grid lines.
* @method renderHorizontalGridLines
* @memberof dc.coordinateGridMixin
* @instance
* @param {Boolean} [renderHorizontalGridLines=false]
* @returns {Boolean|dc.coordinateGridMixin}
*/
_chart.renderHorizontalGridLines = function (renderHorizontalGridLines) {
if (!arguments.length) {
return _renderHorizontalGridLine;
}
_renderHorizontalGridLine = renderHorizontalGridLines;
return _chart;
};
/**
* Turn on/off vertical grid lines.
* @method renderVerticalGridLines
* @memberof dc.coordinateGridMixin
* @instance
* @param {Boolean} [renderVerticalGridLines=false]
* @returns {Boolean|dc.coordinateGridMixin}
*/
_chart.renderVerticalGridLines = function (renderVerticalGridLines) {
if (!arguments.length) {
return _renderVerticalGridLine;
}
_renderVerticalGridLine = renderVerticalGridLines;
return _chart;
};
/**
* Calculates the minimum x value to display in the chart. Includes xAxisPadding if set.
* @method xAxisMin
* @memberof dc.coordinateGridMixin
* @instance
* @returns {*}
*/
_chart.xAxisMin = function () {
var min = d3.min(_chart.data(), function (e) {
return _chart.keyAccessor()(e);
});
return dc.utils.subtract(min, _xAxisPadding, _xAxisPaddingUnit);
};
/**
* Calculates the maximum x value to display in the chart. Includes xAxisPadding if set.
* @method xAxisMax
* @memberof dc.coordinateGridMixin
* @instance
* @returns {*}
*/
_chart.xAxisMax = function () {
var max = d3.max(_chart.data(), function (e) {
return _chart.keyAccessor()(e);
});
return dc.utils.add(max, _xAxisPadding, _xAxisPaddingUnit);
};
/**
* Calculates the minimum y value to display in the chart. Includes yAxisPadding if set.
* @method yAxisMin
* @memberof dc.coordinateGridMixin
* @instance
* @returns {*}
*/
_chart.yAxisMin = function () {
var min = d3.min(_chart.data(), function (e) {
return _chart.valueAccessor()(e);
});
return dc.utils.subtract(min, _yAxisPadding);
};
/**
* Calculates the maximum y value to display in the chart. Includes yAxisPadding if set.
* @method yAxisMax
* @memberof dc.coordinateGridMixin
* @instance
* @returns {*}
*/
_chart.yAxisMax = function () {
var max = d3.max(_chart.data(), function (e) {
return _chart.valueAccessor()(e);
});
return dc.utils.add(max, _yAxisPadding);
};
/**
* Set or get y axis padding for the elastic y axis. The padding will be added to the top and
* bottom of the y axis if elasticY is turned on; otherwise it is ignored.
*
* Padding can be an integer or percentage in string (e.g. '10%'). Padding can be applied to
* number or date axes. When padding a date axis, an integer represents number of days being padded
* and a percentage string will be treated the same as an integer.
* @method yAxisPadding
* @memberof dc.coordinateGridMixin
* @instance
* @param {Number|String} [padding=0]
* @returns {Number|dc.coordinateGridMixin}
*/
_chart.yAxisPadding = function (padding) {
if (!arguments.length) {
return _yAxisPadding;
}
_yAxisPadding = padding;
return _chart;
};
_chart.yAxisHeight = function () {
return _chart.effectiveHeight();
};
/**
* Set or get the rounding function used to quantize the selection when brushing is enabled.
* @method round
* @memberof dc.coordinateGridMixin
* @instance
* @example
* // set x unit round to by month, this will make sure range selection brush will
* // select whole months
* chart.round(d3.time.month.round);
* @param {Function} [round]
* @returns {Function|dc.coordinateGridMixin}
*/
_chart.round = function (round) {
if (!arguments.length) {
return _round;
}
_round = round;
return _chart;
};
_chart._rangeBandPadding = function (_) {
if (!arguments.length) {
return _rangeBandPadding;
}
_rangeBandPadding = _;
return _chart;
};
_chart._outerRangeBandPadding = function (_) {
if (!arguments.length) {
return _outerRangeBandPadding;
}
_outerRangeBandPadding = _;
return _chart;
};
dc.override(_chart, 'filter', function (_) {
if (!arguments.length) {
return _chart._filter();
}
_chart._filter(_);
if (_) {
_chart.brush().extent(_);
} else {
_chart.brush().clear();
}
return _chart;
});
_chart.brush = function (_) {
if (!arguments.length) {
return _brush;
}
_brush = _;
return _chart;
};
function brushHeight () {
return _chart._xAxisY() - _chart.margins().top;
}
_chart.renderBrush = function (g) {
if (_brushOn) {
_brush.on('brush', _chart._brushing);
_brush.on('brushstart', _chart._disableMouseZoom);
_brush.on('brushend', configureMouseZoom);
var gBrush = g.append('g')
.attr('class', 'brush')
.attr('transform', 'translate(' + _chart.margins().left + ',' + _chart.margins().top + ')')
.call(_brush.x(_chart.x()));
_chart.setBrushY(gBrush, false);
_chart.setHandlePaths(gBrush);
if (_chart.hasFilter()) {
_chart.redrawBrush(g, false);
}
}
};
_chart.setHandlePaths = function (gBrush) {
gBrush.selectAll('.resize').append('path').attr('d', _chart.resizeHandlePath);
};
_chart.setBrushY = function (gBrush) {
gBrush.selectAll('rect')
.attr('height', brushHeight());
gBrush.selectAll('.resize path')
.attr('d', _chart.resizeHandlePath);
};