running into some issues with ordering
[dygraphs.git] / plugins / axes.js
CommitLineData
f8540c66
DV
1/**
2 * @license
3 * Copyright 2012 Dan Vanderkam (danvdk@gmail.com)
4 * MIT-licensed (http://opensource.org/licenses/MIT)
5 */
6
7Dygraph.Plugins.Axes = (function() {
8
9/*
10
11Bits of jankiness:
12- Direct layout access
13- Direct area access
14- Should include calculation of ticks, not just the drawing.
15
16*/
17
18/**
19 * Draws the axes. This includes the labels on the x- and y-axes, as well
20 * as the tick marks on the axes.
21 * It does _not_ draw the grid lines which span the entire chart.
22 */
23var axes = function() {
24 this.xlabels_ = [];
25 this.ylabels_ = [];
26};
27
28axes.prototype.toString = function() {
29 return "Axes Plugin";
30};
31
32axes.prototype.activate = function(g) {
33 return {
34 layout: this.layout,
35 clearChart: this.clearChart,
36 drawChart: this.drawChart
37 };
38};
39
40axes.prototype.layout = function(e) {
41 var g = e.dygraph;
42
43 if (g.getOption('drawYAxis')) {
44 var w = g.getOption('yAxisLabelWidth') + 2 * g.getOption('axisTickSize');
45 var y_axis_rect = e.reserveSpaceLeft(w);
46 }
47
48 if (g.getOption('drawXAxis')) {
49 var h;
50 if (g.getOption('xAxisHeight')) {
51 h = g.getOption('xAxisHeight');
52 } else {
53 h = g.getOption('axisLabelFontSize') + 2 * g.getOption('axisTickSize');
54 }
55 var x_axis_rect = e.reserveSpaceBottom(h);
56 }
57
58 if (g.numAxes() == 2) {
59 // TODO(danvk): per-axis setting.
60 var w = g.getOption('yAxisLabelWidth') + 2 * g.getOption('axisTickSize');
61 var y2_axis_rect = e.reserveSpaceRight(w);
62 } else if (g.numAxes() > 2) {
63 g.error("Only two y-axes are supported at this time. (Trying " +
64 "to use " + g.numAxes() + ")");
65 }
66};
67
68axes.prototype.detachLabels = function() {
69 function removeArray(ary) {
70 for (var i = 0; i < ary.length; i++) {
71 var el = ary[i];
72 if (el.parentNode) el.parentNode.removeChild(el);
73 }
74 }
75
76 removeArray(this.xlabels_);
77 removeArray(this.ylabels_);
78 this.xlabels_ = [];
79 this.ylabels_ = [];
80};
81
82axes.prototype.clearChart = function(e) {
83 var g = e.dygraph;
84 this.detachLabels();
85}
86
87axes.prototype.drawChart = function(e) {
88 var g = e.dygraph;
89 if (!g.getOption('drawXAxis') && !g.getOption('drawYAxis')) return;
90
91 // Round pixels to half-integer boundaries for crisper drawing.
92 function halfUp(x) { return Math.round(x) + 0.5; }
93 function halfDown(y){ return Math.round(y) - 0.5; }
94
95 var context = e.drawingContext;
96 var containerDiv = e.canvas.parentNode;
beeabac2
DV
97 var canvasWidth = e.canvas.width;
98 var canvasHeight = e.canvas.height;
f8540c66
DV
99
100 var label, x, y, tick, i;
101
102 var labelStyle = {
103 position: "absolute",
104 fontSize: g.getOption('axisLabelFontSize') + "px",
105 zIndex: 10,
106 color: g.getOption('axisLabelColor'),
107 width: g.getOption('axisLabelWidth') + "px",
108 // height: this.attr_('axisLabelFontSize') + 2 + "px",
109 lineHeight: "normal", // Something other than "normal" line-height screws up label positioning.
110 overflow: "hidden"
111 };
112 var makeDiv = function(txt, axis, prec_axis) {
113 var div = document.createElement("div");
114 for (var name in labelStyle) {
115 if (labelStyle.hasOwnProperty(name)) {
116 div.style[name] = labelStyle[name];
117 }
118 }
119 var inner_div = document.createElement("div");
120 inner_div.className = 'dygraph-axis-label' +
121 ' dygraph-axis-label-' + axis +
122 (prec_axis ? ' dygraph-axis-label-' + prec_axis : '');
123 inner_div.innerHTML = txt;
124 div.appendChild(inner_div);
125 return div;
126 };
127
128 // axis lines
129 context.save();
130 context.strokeStyle = g.getOption('axisLineColor');
131 context.lineWidth = g.getOption('axisLineWidth');
132
133 var layout = g.layout_;
134 var area = e.dygraph.plotter_.area;
135
136 if (g.getOption('drawYAxis')) {
137 if (layout.yticks && layout.yticks.length > 0) {
138 var num_axes = g.numAxes();
139 for (i = 0; i < layout.yticks.length; i++) {
140 tick = layout.yticks[i];
141 if (typeof(tick) == "function") return;
142 x = area.x;
143 var sgn = 1;
144 var prec_axis = 'y1';
145 if (tick[0] == 1) { // right-side y-axis
146 x = area.x + area.w;
147 sgn = -1;
148 prec_axis = 'y2';
149 }
150 y = area.y + tick[1] * area.h;
151
152 /* Tick marks are currently clipped, so don't bother drawing them.
153 context.beginPath();
154 context.moveTo(halfUp(x), halfDown(y));
155 context.lineTo(halfUp(x - sgn * this.attr_('axisTickSize')), halfDown(y));
156 context.closePath();
157 context.stroke();
158 */
159
160 label = makeDiv(tick[2], 'y', num_axes == 2 ? prec_axis : null);
161 var top = (y - g.getOption('axisLabelFontSize') / 2);
162 if (top < 0) top = 0;
163
beeabac2 164 if (top + g.getOption('axisLabelFontSize') + 3 > canvasHeight) {
f8540c66
DV
165 label.style.bottom = "0px";
166 } else {
167 label.style.top = top + "px";
168 }
169 if (tick[0] === 0) {
170 label.style.left = (area.x - g.getOption('yAxisLabelWidth') - g.getOption('axisTickSize')) + "px";
171 label.style.textAlign = "right";
172 } else if (tick[0] == 1) {
173 label.style.left = (area.x + area.w +
174 g.getOption('axisTickSize')) + "px";
175 label.style.textAlign = "left";
176 }
177 label.style.width = g.getOption('yAxisLabelWidth') + "px";
178 containerDiv.appendChild(label);
179 this.ylabels_.push(label);
180 }
181
182 // The lowest tick on the y-axis often overlaps with the leftmost
183 // tick on the x-axis. Shift the bottom tick up a little bit to
184 // compensate if necessary.
185 var bottomTick = this.ylabels_[0];
186 var fontSize = g.getOption('axisLabelFontSize');
187 var bottom = parseInt(bottomTick.style.top, 10) + fontSize;
beeabac2 188 if (bottom > canvasHeight - fontSize) {
f8540c66
DV
189 bottomTick.style.top = (parseInt(bottomTick.style.top, 10) -
190 fontSize / 2) + "px";
191 }
192 }
193
194 // draw a vertical line on the left to separate the chart from the labels.
195 var axisX;
196 if (g.getOption('drawAxesAtZero')) {
beeabac2 197 var r = g.toPercentXCoord(0);
f8540c66
DV
198 if (r > 1 || r < 0) r = 0;
199 axisX = halfUp(area.x + r * area.w);
200 } else {
201 axisX = halfUp(area.x);
202 }
203 context.beginPath();
204 context.moveTo(axisX, halfDown(area.y));
205 context.lineTo(axisX, halfDown(area.y + area.h));
206 context.closePath();
207 context.stroke();
208
209 // if there's a secondary y-axis, draw a vertical line for that, too.
210 if (g.numAxes() == 2) {
211 context.beginPath();
212 context.moveTo(halfDown(area.x + area.w), halfDown(area.y));
213 context.lineTo(halfDown(area.x + area.w), halfDown(area.y + area.h));
214 context.closePath();
215 context.stroke();
216 }
217 }
218
219 if (g.getOption('drawXAxis')) {
220 if (layout.xticks) {
221 for (i = 0; i < layout.xticks.length; i++) {
222 tick = layout.xticks[i];
223 x = area.x + tick[0] * area.w;
224 y = area.y + area.h;
225
226 /* Tick marks are currently clipped, so don't bother drawing them.
227 context.beginPath();
228 context.moveTo(halfUp(x), halfDown(y));
229 context.lineTo(halfUp(x), halfDown(y + this.attr_('axisTickSize')));
230 context.closePath();
231 context.stroke();
232 */
233
234 label = makeDiv(tick[1], 'x');
235 label.style.textAlign = "center";
236 label.style.top = (y + g.getOption('axisTickSize')) + 'px';
237
238 var left = (x - g.getOption('axisLabelWidth')/2);
beeabac2
DV
239 if (left + g.getOption('axisLabelWidth') > canvasWidth) {
240 left = canvasWidth - g.getOption('xAxisLabelWidth');
f8540c66
DV
241 label.style.textAlign = "right";
242 }
243 if (left < 0) {
244 left = 0;
245 label.style.textAlign = "left";
246 }
247
248 label.style.left = left + "px";
249 label.style.width = g.getOption('xAxisLabelWidth') + "px";
250 containerDiv.appendChild(label);
251 this.xlabels_.push(label);
252 }
253 }
254
255 context.beginPath();
256 var axisY;
257 if (g.getOption('drawAxesAtZero')) {
258 var r = g.toPercentYCoord(0, 0);
259 if (r > 1 || r < 0) r = 1;
260 axisY = halfDown(area.y + r * area.h);
261 } else {
262 axisY = halfDown(area.y + area.h);
263 }
264 context.moveTo(halfUp(area.x), axisY);
265 context.lineTo(halfUp(area.x + area.w), axisY);
266 context.closePath();
267 context.stroke();
268 }
269
270 context.restore();
271}
272
273return axes;
274})();