3 * Copyright 2012 Dan Vanderkam (danvdk@gmail.com)
4 * MIT-licensed (http://opensource.org/licenses/MIT)
7 /*global Dygraph:false */
13 - Direct layout access
15 - Should include calculation of ticks, not just the drawing.
17 Options left to make axis-friendly.
23 * Draws the axes. This includes the labels on the x- and y-axes, as well
24 * as the tick marks on the axes.
25 * It does _not_ draw the grid lines which span the entire chart.
27 var axes
= function() {
32 axes
.prototype.toString
= function() {
36 axes
.prototype.activate
= function(g
) {
39 clearChart
: this.clearChart
,
40 willDrawChart
: this.willDrawChart
44 axes
.prototype.layout
= function(e
) {
47 if (g
.getOptionForAxis('drawAxis', 'y')) {
48 var w
= g
.getOptionForAxis('axisLabelWidth', 'y') + 2 * g
.getOptionForAxis('axisTickSize', 'y');
49 e
.reserveSpaceLeft(w
);
52 if (g
.getOptionForAxis('drawAxis', 'x')) {
54 // NOTE: I think this is probably broken now, since g.getOption() now
55 // hits the dictionary. (That is, g.getOption('xAxisHeight') now always
57 if (g
.getOption('xAxisHeight')) {
58 h
= g
.getOption('xAxisHeight');
60 h
= g
.getOptionForAxis('axisLabelFontSize', 'x') + 2 * g
.getOptionForAxis('axisTickSize', 'x');
62 e
.reserveSpaceBottom(h
);
65 if (g
.numAxes() == 2) {
66 if (g
.getOptionForAxis('drawAxis', 'y2')) {
67 var w
= g
.getOptionForAxis('axisLabelWidth', 'y2') + 2 * g
.getOptionForAxis('axisTickSize', 'y2');
68 e
.reserveSpaceRight(w
);
70 } else if (g
.numAxes() > 2) {
71 g
.error('Only two y-axes are supported at this time. (Trying ' +
72 'to use ' + g
.numAxes() + ')');
76 axes
.prototype.detachLabels
= function() {
77 function removeArray(ary
) {
78 for (var i
= 0; i
< ary
.length
; i
++) {
80 if (el
.parentNode
) el
.parentNode
.removeChild(el
);
84 removeArray(this.xlabels_
);
85 removeArray(this.ylabels_
);
90 axes
.prototype.clearChart
= function(e
) {
94 axes
.prototype.willDrawChart
= function(e
) {
97 if (!g
.getOptionForAxis('drawAxis', 'x') &&
98 !g
.getOptionForAxis('drawAxis', 'y') &&
99 !g
.getOptionForAxis('drawAxis', 'y2')) {
103 // Round pixels to half-integer boundaries for crisper drawing.
104 function halfUp(x
) { return Math
.round(x
) + 0.5; }
105 function halfDown(y
){ return Math
.round(y
) - 0.5; }
107 var context
= e
.drawingContext
;
108 var containerDiv
= e
.canvas
.parentNode
;
109 var canvasWidth
= g
.width_
; // e.canvas.width is affected by pixel ratio.
110 var canvasHeight
= g
.height_
;
112 var label
, x
, y
, tick
, i
;
114 var makeLabelStyle
= function(axis
) {
116 position
: 'absolute',
117 fontSize
: g
.getOptionForAxis('axisLabelFontSize', axis
) + 'px',
119 color
: g
.getOptionForAxis('axisLabelColor', axis
),
120 width
: g
.getOptionForAxis('axisLabelWidth', axis
) + 'px',
121 // height: g.getOptionForAxis('axisLabelFontSize', 'x') + 2 + "px",
122 lineHeight
: 'normal', // Something other than "normal" line-height screws up label positioning.
128 x
: makeLabelStyle('x'),
129 y
: makeLabelStyle('y'),
130 y2
: makeLabelStyle('y2')
133 var makeDiv
= function(txt
, axis
, prec_axis
) {
135 * This seems to be called with the following three sets of axis/prec_axis:
140 var div
= document
.createElement('div');
141 var labelStyle
= labelStyles
[prec_axis
== 'y2' ? 'y2' : axis
];
142 for (var name
in labelStyle
) {
143 if (labelStyle
.hasOwnProperty(name
)) {
144 div
.style
[name
] = labelStyle
[name
];
147 var inner_div
= document
.createElement('div');
148 inner_div
.className
= 'dygraph-axis-label' +
149 ' dygraph-axis-label-' + axis
+
150 (prec_axis
? ' dygraph-axis-label-' + prec_axis
: '');
151 inner_div
.innerHTML
= txt
;
152 div
.appendChild(inner_div
);
159 var layout
= g
.layout_
;
160 var area
= e
.dygraph
.plotter_
.area
;
162 // Helper for repeated axis-option accesses.
163 var makeOptionGetter
= function(axis
) {
164 return function(option
) {
165 return g
.getOptionForAxis(option
, axis
);
169 if (g
.getOptionForAxis('drawAxis', 'y')) {
170 if (layout
.yticks
&& layout
.yticks
.length
> 0) {
171 var num_axes
= g
.numAxes();
172 var getOptions
= [makeOptionGetter('y'), makeOptionGetter('y2')];
173 for (i
= 0; i
< layout
.yticks
.length
; i
++) {
174 tick
= layout
.yticks
[i
];
175 if (typeof(tick
) == 'function') return; // <-- when would this happen?
178 var prec_axis
= 'y1';
179 var getAxisOption
= getOptions
[0];
180 if (tick
[0] == 1) { // right-side y-axis
184 getAxisOption
= getOptions
[1];
186 var fontSize
= getAxisOption('axisLabelFontSize');
187 y
= area
.y
+ tick
[1] * area
.h
;
189 /* Tick marks are currently clipped, so don't bother drawing them.
191 context.moveTo(halfUp(x), halfDown(y));
192 context.lineTo(halfUp(x - sgn * this.attr_('axisTickSize')), halfDown(y));
197 label
= makeDiv(tick
[2], 'y', num_axes
== 2 ? prec_axis
: null);
198 var top
= (y
- fontSize
/ 2);
199 if (top
< 0) top
= 0;
201 if (top
+ fontSize
+ 3 > canvasHeight
) {
202 label
.style
.bottom
= '0';
204 label
.style
.top
= top
+ 'px';
207 label
.style
.left
= (area
.x
- getAxisOption('axisLabelWidth') - getAxisOption('axisTickSize')) + 'px';
208 label
.style
.textAlign
= 'right';
209 } else if (tick
[0] == 1) {
210 label
.style
.left
= (area
.x
+ area
.w
+
211 getAxisOption('axisTickSize')) + 'px';
212 label
.style
.textAlign
= 'left';
214 label
.style
.width
= getAxisOption('axisLabelWidth') + 'px';
215 containerDiv
.appendChild(label
);
216 this.ylabels_
.push(label
);
219 // The lowest tick on the y-axis often overlaps with the leftmost
220 // tick on the x-axis. Shift the bottom tick up a little bit to
221 // compensate if necessary.
222 var bottomTick
= this.ylabels_
[0];
223 // Interested in the y2 axis also?
224 var fontSize
= g
.getOptionForAxis('axisLabelFontSize', 'y');
225 var bottom
= parseInt(bottomTick
.style
.top
, 10) + fontSize
;
226 if (bottom
> canvasHeight
- fontSize
) {
227 bottomTick
.style
.top
= (parseInt(bottomTick
.style
.top
, 10) -
228 fontSize
/ 2) + 'px';
232 // draw a vertical line on the left to separate the chart from the labels.
234 if (g
.getOption('drawAxesAtZero')) {
235 var r
= g
.toPercentXCoord(0);
236 if (r
> 1 || r
< 0 || isNaN(r
)) r
= 0;
237 axisX
= halfUp(area
.x
+ r
* area
.w
);
239 axisX
= halfUp(area
.x
);
242 context
.strokeStyle
= g
.getOptionForAxis('axisLineColor', 'y');
243 context
.lineWidth
= g
.getOptionForAxis('axisLineWidth', 'y');
246 context
.moveTo(axisX
, halfDown(area
.y
));
247 context
.lineTo(axisX
, halfDown(area
.y
+ area
.h
));
251 // if there's a secondary y-axis, draw a vertical line for that, too.
252 if (g
.numAxes() == 2) {
253 context
.strokeStyle
= g
.getOptionForAxis('axisLineColor', 'y2');
254 context
.lineWidth
= g
.getOptionForAxis('axisLineWidth', 'y2');
256 context
.moveTo(halfDown(area
.x
+ area
.w
), halfDown(area
.y
));
257 context
.lineTo(halfDown(area
.x
+ area
.w
), halfDown(area
.y
+ area
.h
));
263 if (g
.getOptionForAxis('drawAxis', 'x')) {
265 var getAxisOption
= makeOptionGetter('x');
266 for (i
= 0; i
< layout
.xticks
.length
; i
++) {
267 tick
= layout
.xticks
[i
];
268 x
= area
.x
+ tick
[0] * area
.w
;
271 /* Tick marks are currently clipped, so don't bother drawing them.
273 context.moveTo(halfUp(x), halfDown(y));
274 context.lineTo(halfUp(x), halfDown(y + this.attr_('axisTickSize')));
279 label
= makeDiv(tick
[1], 'x');
280 label
.style
.textAlign
= 'center';
281 label
.style
.top
= (y
+ getAxisOption('axisTickSize')) + 'px';
283 var left
= (x
- getAxisOption('axisLabelWidth')/2);
284 if (left
+ getAxisOption('axisLabelWidth') > canvasWidth
) {
285 left
= canvasWidth
- getAxisOption('axisLabelWidth');
286 label
.style
.textAlign
= 'right';
290 label
.style
.textAlign
= 'left';
293 label
.style
.left
= left
+ 'px';
294 label
.style
.width
= getAxisOption('axisLabelWidth') + 'px';
295 containerDiv
.appendChild(label
);
296 this.xlabels_
.push(label
);
300 context
.strokeStyle
= g
.getOptionForAxis('axisLineColor', 'x');
301 context
.lineWidth
= g
.getOptionForAxis('axisLineWidth', 'x');
304 if (g
.getOption('drawAxesAtZero')) {
305 var r
= g
.toPercentYCoord(0, 0);
306 if (r
> 1 || r
< 0) r
= 1;
307 axisY
= halfDown(area
.y
+ r
* area
.h
);
309 axisY
= halfDown(area
.y
+ area
.h
);
311 context
.moveTo(halfUp(area
.x
), axisY
);
312 context
.lineTo(halfUp(area
.x
+ area
.w
), axisY
);