3 * Copyright 2012 Dan Vanderkam (danvdk@gmail.com)
4 * MIT-licensed (http://opensource.org/licenses/MIT)
7 /*global Dygraph:false */
9 Dygraph
.Plugins
.Axes
= (function() {
15 - Direct layout access
17 - Should include calculation of ticks, not just the drawing.
19 Options left to make axis-friendly.
24 These too. What is the difference between axisLablelWidth and {x,y}AxisLabelWidth?
31 * Draws the axes. This includes the labels on the x- and y-axes, as well
32 * as the tick marks on the axes.
33 * It does _not_ draw the grid lines which span the entire chart.
35 var axes
= function() {
40 axes
.prototype.toString
= function() {
44 axes
.prototype.activate
= function(g
) {
47 clearChart
: this.clearChart
,
48 willDrawChart
: this.willDrawChart
52 axes
.prototype.layout
= function(e
) {
55 if (g
.getOptionForAxis('drawAxis', 'y')) {
56 var w
= g
.getOption('yAxisLabelWidth') + 2 * g
.getOption('axisTickSize');
57 e
.reserveSpaceLeft(w
);
60 if (g
.getOptionForAxis('drawAxis', 'x')) {
62 // NOTE: I think this is probably broken now, since g.getOption() now
63 // hits the dictionary. (That is, g.getOption('xAxisHeight') now always
65 if (g
.getOption('xAxisHeight')) {
66 h
= g
.getOption('xAxisHeight');
68 h
= g
.getOptionForAxis('axisLabelFontSize', 'x') + 2 * g
.getOption('axisTickSize');
70 e
.reserveSpaceBottom(h
);
73 if (g
.numAxes() == 2) {
74 // TODO(danvk): introduce a 'drawAxis' per-axis property.
75 if (g
.getOptionForAxis('drawAxis', 'y')) {
76 // TODO(danvk): per-axis setting.
77 var w
= g
.getOption('yAxisLabelWidth') + 2 * g
.getOption('axisTickSize');
78 e
.reserveSpaceRight(w
);
80 } else if (g
.numAxes() > 2) {
81 g
.error("Only two y-axes are supported at this time. (Trying " +
82 "to use " + g
.numAxes() + ")");
86 axes
.prototype.detachLabels
= function() {
87 function removeArray(ary
) {
88 for (var i
= 0; i
< ary
.length
; i
++) {
90 if (el
.parentNode
) el
.parentNode
.removeChild(el
);
94 removeArray(this.xlabels_
);
95 removeArray(this.ylabels_
);
100 axes
.prototype.clearChart
= function(e
) {
104 axes
.prototype.willDrawChart
= function(e
) {
107 if (!g
.getOptionForAxis('drawAxis', 'x') && !g
.getOptionForAxis('drawAxis', 'y')) return;
109 // Round pixels to half-integer boundaries for crisper drawing.
110 function halfUp(x
) { return Math
.round(x
) + 0.5; }
111 function halfDown(y
){ return Math
.round(y
) - 0.5; }
113 var context
= e
.drawingContext
;
114 var containerDiv
= e
.canvas
.parentNode
;
115 var canvasWidth
= e
.canvas
.width
;
116 var canvasHeight
= e
.canvas
.height
;
118 var label
, x
, y
, tick
, i
;
120 var makeLabelStyle
= function(axis
) {
122 position
: "absolute",
123 fontSize
: g
.getOptionForAxis('axisLabelFontSize', axis
) + "px",
125 color
: g
.getOptionForAxis('axisLabelColor', axis
),
126 width
: g
.getOption('axisLabelWidth') + "px",
127 // height: g.getOptionForAxis('axisLabelFontSize', 'x') + 2 + "px",
128 lineHeight
: "normal", // Something other than "normal" line-height screws up label positioning.
134 x
: makeLabelStyle('x'),
135 y
: makeLabelStyle('y'),
136 y2
: makeLabelStyle('y2')
139 var makeDiv
= function(txt
, axis
, prec_axis
) {
141 * This seems to be called with the following three sets of axis/prec_axis:
146 var div
= document
.createElement("div");
147 var labelStyle
= labelStyles
[prec_axis
== 'y2' ? 'y2' : axis
];
148 for (var name
in labelStyle
) {
149 if (labelStyle
.hasOwnProperty(name
)) {
150 div
.style
[name
] = labelStyle
[name
];
153 var inner_div
= document
.createElement("div");
154 inner_div
.className
= 'dygraph-axis-label' +
155 ' dygraph-axis-label-' + axis
+
156 (prec_axis
? ' dygraph-axis-label-' + prec_axis
: '');
157 inner_div
.innerHTML
= txt
;
158 div
.appendChild(inner_div
);
165 var layout
= g
.layout_
;
166 var area
= e
.dygraph
.plotter_
.area
;
168 if (g
.getOptionForAxis('drawAxis', 'y')) {
169 if (layout
.yticks
&& layout
.yticks
.length
> 0) {
170 var num_axes
= g
.numAxes();
171 for (i
= 0; i
< layout
.yticks
.length
; i
++) {
172 tick
= layout
.yticks
[i
];
173 if (typeof(tick
) == "function") return;
176 var prec_axis
= 'y1';
177 if (tick
[0] == 1) { // right-side y-axis
182 var fontSize
= g
.getOptionForAxis('axisLabelFontSize', prec_axis
);
183 y
= area
.y
+ tick
[1] * area
.h
;
185 /* Tick marks are currently clipped, so don't bother drawing them.
187 context.moveTo(halfUp(x), halfDown(y));
188 context.lineTo(halfUp(x - sgn * this.attr_('axisTickSize')), halfDown(y));
193 label
= makeDiv(tick
[2], 'y', num_axes
== 2 ? prec_axis
: null);
194 var top
= (y
- fontSize
/ 2);
195 if (top
< 0) top
= 0;
197 if (top
+ fontSize
+ 3 > canvasHeight
) {
198 label
.style
.bottom
= "0px";
200 label
.style
.top
= top
+ "px";
203 label
.style
.left
= (area
.x
- g
.getOption('yAxisLabelWidth') - g
.getOption('axisTickSize')) + "px";
204 label
.style
.textAlign
= "right";
205 } else if (tick
[0] == 1) {
206 label
.style
.left
= (area
.x
+ area
.w
+
207 g
.getOption('axisTickSize')) + "px";
208 label
.style
.textAlign
= "left";
210 label
.style
.width
= g
.getOption('yAxisLabelWidth') + "px";
211 containerDiv
.appendChild(label
);
212 this.ylabels_
.push(label
);
215 // The lowest tick on the y-axis often overlaps with the leftmost
216 // tick on the x-axis. Shift the bottom tick up a little bit to
217 // compensate if necessary.
218 var bottomTick
= this.ylabels_
[0];
219 // Interested in the y2 axis also?
220 var fontSize
= g
.getOptionForAxis('axisLabelFontSize', "y");
221 var bottom
= parseInt(bottomTick
.style
.top
, 10) + fontSize
;
222 if (bottom
> canvasHeight
- fontSize
) {
223 bottomTick
.style
.top
= (parseInt(bottomTick
.style
.top
, 10) -
224 fontSize
/ 2) + "px";
228 // draw a vertical line on the left to separate the chart from the labels.
230 if (g
.getOption('drawAxesAtZero')) {
231 var r
= g
.toPercentXCoord(0);
232 if (r
> 1 || r
< 0 || isNaN(r
)) r
= 0;
233 axisX
= halfUp(area
.x
+ r
* area
.w
);
235 axisX
= halfUp(area
.x
);
238 context
.strokeStyle
= g
.getOptionForAxis('axisLineColor', 'y');
239 context
.lineWidth
= g
.getOptionForAxis('axisLineWidth', 'y');
242 context
.moveTo(axisX
, halfDown(area
.y
));
243 context
.lineTo(axisX
, halfDown(area
.y
+ area
.h
));
247 // if there's a secondary y-axis, draw a vertical line for that, too.
248 if (g
.numAxes() == 2) {
249 context
.strokeStyle
= g
.getOptionForAxis('axisLineColor', 'y2');
250 context
.lineWidth
= g
.getOptionForAxis('axisLineWidth', 'y2');
252 context
.moveTo(halfDown(area
.x
+ area
.w
), halfDown(area
.y
));
253 context
.lineTo(halfDown(area
.x
+ area
.w
), halfDown(area
.y
+ area
.h
));
259 if (g
.getOptionForAxis('drawAxis', 'x')) {
261 for (i
= 0; i
< layout
.xticks
.length
; i
++) {
262 tick
= layout
.xticks
[i
];
263 x
= area
.x
+ tick
[0] * area
.w
;
266 /* Tick marks are currently clipped, so don't bother drawing them.
268 context.moveTo(halfUp(x), halfDown(y));
269 context.lineTo(halfUp(x), halfDown(y + this.attr_('axisTickSize')));
274 label
= makeDiv(tick
[1], 'x');
275 label
.style
.textAlign
= "center";
276 label
.style
.top
= (y
+ g
.getOption('axisTickSize')) + 'px';
278 var left
= (x
- g
.getOption('axisLabelWidth')/2);
279 if (left
+ g
.getOption('axisLabelWidth') > canvasWidth
) {
280 left
= canvasWidth
- g
.getOption('xAxisLabelWidth');
281 label
.style
.textAlign
= "right";
285 label
.style
.textAlign
= "left";
288 label
.style
.left
= left
+ "px";
289 label
.style
.width
= g
.getOption('xAxisLabelWidth') + "px";
290 containerDiv
.appendChild(label
);
291 this.xlabels_
.push(label
);
295 context
.strokeStyle
= g
.getOptionForAxis('axisLineColor', 'x');
296 context
.lineWidth
= g
.getOptionForAxis('axisLineWidth', 'x');
299 if (g
.getOption('drawAxesAtZero')) {
300 var r
= g
.toPercentYCoord(0, 0);
301 if (r
> 1 || r
< 0) r
= 1;
302 axisY
= halfDown(area
.y
+ r
* area
.h
);
304 axisY
= halfDown(area
.y
+ area
.h
);
306 context
.moveTo(halfUp(area
.x
), axisY
);
307 context
.lineTo(halfUp(area
.x
+ area
.w
), axisY
);