2150 |
mathias |
1 |
if(!dojo._hasResource["dojox.charting.axis2d.Default"]){ //_hasResource checks added by build. Do not use _hasResource directly in your code.
|
|
|
2 |
dojo._hasResource["dojox.charting.axis2d.Default"] = true;
|
|
|
3 |
dojo.provide("dojox.charting.axis2d.Default");
|
|
|
4 |
|
|
|
5 |
dojo.require("dojox.charting.scaler");
|
|
|
6 |
dojo.require("dojox.charting.axis2d.common");
|
|
|
7 |
dojo.require("dojox.charting.axis2d.Base");
|
|
|
8 |
|
|
|
9 |
dojo.require("dojo.colors");
|
|
|
10 |
dojo.require("dojox.gfx");
|
|
|
11 |
dojo.require("dojox.lang.functional");
|
|
|
12 |
dojo.require("dojox.lang.utils");
|
|
|
13 |
|
|
|
14 |
(function(){
|
|
|
15 |
var dc = dojox.charting,
|
|
|
16 |
df = dojox.lang.functional,
|
|
|
17 |
du = dojox.lang.utils,
|
|
|
18 |
g = dojox.gfx,
|
|
|
19 |
labelGap = 4, // in pixels
|
|
|
20 |
labelFudgeFactor = 0.8; // in percents (to convert font's heigth to label width)
|
|
|
21 |
|
|
|
22 |
var eq = function(/* Number */ a, /* Number */ b){
|
|
|
23 |
// summary: compare two FP numbers for equality
|
|
|
24 |
return Math.abs(a - b) <= 1e-6 * (Math.abs(a) + Math.abs(b)); // Boolean
|
|
|
25 |
};
|
|
|
26 |
|
|
|
27 |
dojo.declare("dojox.charting.axis2d.Default", dojox.charting.axis2d.Base, {
|
|
|
28 |
defaultParams: {
|
|
|
29 |
vertical: false, // true for vertical axis
|
|
|
30 |
fixUpper: "none", // align the upper on ticks: "major", "minor", "micro", "none"
|
|
|
31 |
fixLower: "none", // align the lower on ticks: "major", "minor", "micro", "none"
|
|
|
32 |
natural: false, // all tick marks should be made on natural numbers
|
|
|
33 |
leftBottom: true, // position of the axis, used with "vertical"
|
|
|
34 |
includeZero: false, // 0 should be included
|
|
|
35 |
fixed: true, // all labels are fixed numbers
|
|
|
36 |
majorLabels: true, // draw major labels
|
|
|
37 |
minorTicks: true, // draw minor ticks
|
|
|
38 |
minorLabels: true, // draw minor labels
|
|
|
39 |
microTicks: false, // draw micro ticks
|
|
|
40 |
htmlLabels: true // use HTML to draw labels
|
|
|
41 |
},
|
|
|
42 |
optionalParams: {
|
|
|
43 |
"min": 0, // minimal value on this axis
|
|
|
44 |
"max": 1, // maximal value on this axis
|
|
|
45 |
"majorTickStep": 4, // major tick step
|
|
|
46 |
"minorTickStep": 2, // minor tick step
|
|
|
47 |
"microTickStep": 1, // micro tick step
|
|
|
48 |
"labels": [], // array of labels for major ticks
|
|
|
49 |
// with corresponding numeric values
|
|
|
50 |
// ordered by values
|
|
|
51 |
// theme components
|
|
|
52 |
"stroke": {}, // stroke for an axis
|
|
|
53 |
"majorTick": {}, // stroke + length for a tick
|
|
|
54 |
"minorTick": {}, // stroke + length for a tick
|
|
|
55 |
"font": "", // font for labels
|
|
|
56 |
"fontColor": "" // color for labels as a string
|
|
|
57 |
},
|
|
|
58 |
|
|
|
59 |
constructor: function(chart, kwArgs){
|
|
|
60 |
this.opt = dojo.clone(this.defaultParams);
|
|
|
61 |
du.updateWithObject(this.opt, kwArgs);
|
|
|
62 |
du.updateWithPattern(this.opt, kwArgs, this.optionalParams);
|
|
|
63 |
},
|
|
|
64 |
dependOnData: function(){
|
|
|
65 |
return !("min" in this.opt) || !("max" in this.opt);
|
|
|
66 |
},
|
|
|
67 |
clear: function(){
|
|
|
68 |
delete this.scaler;
|
|
|
69 |
this.dirty = true;
|
|
|
70 |
return this;
|
|
|
71 |
},
|
|
|
72 |
initialized: function(){
|
|
|
73 |
return "scaler" in this;
|
|
|
74 |
},
|
|
|
75 |
calculate: function(min, max, span, labels){
|
|
|
76 |
if(this.initialized()){ return this; }
|
|
|
77 |
this.labels = "labels" in this.opt ? this.opt.labels : labels;
|
|
|
78 |
if("min" in this.opt){ min = this.opt.min; }
|
|
|
79 |
if("max" in this.opt){ max = this.opt.max; }
|
|
|
80 |
if(this.opt.includeZero){
|
|
|
81 |
if(min > 0){ min = 0; }
|
|
|
82 |
if(max < 0){ max = 0; }
|
|
|
83 |
}
|
|
|
84 |
var minMinorStep = 0, ta = this.chart.theme.axis,
|
|
|
85 |
taFont = "font" in this.opt ? this.opt.font : ta.font,
|
|
|
86 |
size = taFont ? g.normalizedLength(g.splitFontString(taFont).size) : 0;
|
|
|
87 |
if(this.vertical){
|
|
|
88 |
if(size){
|
|
|
89 |
minMinorStep = size + labelGap;
|
|
|
90 |
}
|
|
|
91 |
}else{
|
|
|
92 |
if(size){
|
|
|
93 |
var labelLength = Math.ceil(Math.log(Math.max(Math.abs(min), Math.abs(max))) / Math.LN10);
|
|
|
94 |
if(min < 0 || max < 0){ ++labelLength; }
|
|
|
95 |
var precision = Math.floor(Math.log(max - min) / Math.LN10);
|
|
|
96 |
if(precision > 0){ labelLength += precision; }
|
|
|
97 |
if(this.labels){
|
|
|
98 |
labelLength = df.foldl(df.map(this.labels, "x.text.length"), "Math.max(a, b)", labelLength);
|
|
|
99 |
}
|
|
|
100 |
minMinorStep = Math.floor(size * labelLength * labelFudgeFactor) + labelGap;
|
|
|
101 |
}
|
|
|
102 |
}
|
|
|
103 |
var kwArgs = {
|
|
|
104 |
fixUpper: this.opt.fixUpper,
|
|
|
105 |
fixLower: this.opt.fixLower,
|
|
|
106 |
natural: this.opt.natural
|
|
|
107 |
};
|
|
|
108 |
if("majorTickStep" in this.opt){ kwArgs.majorTick = this.opt.majorTickStep; }
|
|
|
109 |
if("minorTickStep" in this.opt){ kwArgs.minorTick = this.opt.minorTickStep; }
|
|
|
110 |
if("microTickStep" in this.opt){ kwArgs.microTick = this.opt.microTickStep; }
|
|
|
111 |
this.scaler = dojox.charting.scaler(min, max, span, kwArgs);
|
|
|
112 |
this.scaler.minMinorStep = minMinorStep;
|
|
|
113 |
return this;
|
|
|
114 |
},
|
|
|
115 |
getScaler: function(){
|
|
|
116 |
return this.scaler;
|
|
|
117 |
},
|
|
|
118 |
getOffsets: function(){
|
|
|
119 |
var offsets = {l: 0, r: 0, t: 0, b: 0};
|
|
|
120 |
var offset = 0, ta = this.chart.theme.axis,
|
|
|
121 |
taFont = "font" in this.opt ? this.opt.font : ta.font,
|
|
|
122 |
taMajorTick = "majorTick" in this.opt ? this.opt.majorTick : ta.majorTick,
|
|
|
123 |
taMinorTick = "minorTick" in this.opt ? this.opt.minorTick : ta.minorTick,
|
|
|
124 |
size = taFont ? g.normalizedLength(g.splitFontString(taFont).size) : 0;
|
|
|
125 |
if(this.vertical){
|
|
|
126 |
if(size){
|
|
|
127 |
var s = this.scaler,
|
|
|
128 |
a = this._getLabel(s.major.start, s.major.prec).length,
|
|
|
129 |
b = this._getLabel(s.major.start + s.major.count * s.major.tick, s.major.prec).length,
|
|
|
130 |
c = this._getLabel(s.minor.start, s.minor.prec).length,
|
|
|
131 |
d = this._getLabel(s.minor.start + s.minor.count * s.minor.tick, s.minor.prec).length,
|
|
|
132 |
labelLength = Math.max(a, b, c, d);
|
|
|
133 |
if(this.labels){
|
|
|
134 |
labelLength = df.foldl(df.map(this.labels, "x.text.length"), "Math.max(a, b)", labelLength);
|
|
|
135 |
}
|
|
|
136 |
offset = Math.floor(size * labelLength * labelFudgeFactor) + labelGap;
|
|
|
137 |
}
|
|
|
138 |
offset += labelGap + Math.max(taMajorTick.length, taMinorTick.length);
|
|
|
139 |
offsets[this.opt.leftBottom ? "l" : "r"] = offset;
|
|
|
140 |
offsets.t = offsets.b = size / 2;
|
|
|
141 |
}else{
|
|
|
142 |
if(size){
|
|
|
143 |
offset = size + labelGap;
|
|
|
144 |
}
|
|
|
145 |
offset += labelGap + Math.max(taMajorTick.length, taMinorTick.length);
|
|
|
146 |
offsets[this.opt.leftBottom ? "b" : "t"] = offset;
|
|
|
147 |
if(size){
|
|
|
148 |
var s = this.scaler,
|
|
|
149 |
a = this._getLabel(s.major.start, s.major.prec).length,
|
|
|
150 |
b = this._getLabel(s.major.start + s.major.count * s.major.tick, s.major.prec).length,
|
|
|
151 |
c = this._getLabel(s.minor.start, s.minor.prec).length,
|
|
|
152 |
d = this._getLabel(s.minor.start + s.minor.count * s.minor.tick, s.minor.prec).length,
|
|
|
153 |
labelLength = Math.max(a, b, c, d);
|
|
|
154 |
if(this.labels){
|
|
|
155 |
labelLength = df.foldl(df.map(this.labels, "x.text.length"), "Math.max(a, b)", labelLength);
|
|
|
156 |
}
|
|
|
157 |
offsets.l = offsets.r = Math.floor(size * labelLength * labelFudgeFactor) / 2;
|
|
|
158 |
}
|
|
|
159 |
}
|
|
|
160 |
return offsets;
|
|
|
161 |
},
|
|
|
162 |
render: function(dim, offsets){
|
|
|
163 |
if(!this.dirty){ return this; }
|
|
|
164 |
// prepare variable
|
|
|
165 |
var start, stop, axisVector, tickVector, labelOffset, labelAlign,
|
|
|
166 |
ta = this.chart.theme.axis,
|
|
|
167 |
taStroke = "stroke" in this.opt ? this.opt.stroke : ta.stroke,
|
|
|
168 |
taMajorTick = "majorTick" in this.opt ? this.opt.majorTick : ta.majorTick,
|
|
|
169 |
taMinorTick = "minorTick" in this.opt ? this.opt.minorTick : ta.minorTick,
|
|
|
170 |
taFont = "font" in this.opt ? this.opt.font : ta.font,
|
|
|
171 |
taFontColor = "fontColor" in this.opt ? this.opt.fontColor : ta.fontColor,
|
|
|
172 |
tickSize = Math.max(taMajorTick.length, taMinorTick.length),
|
|
|
173 |
size = taFont ? g.normalizedLength(g.splitFontString(taFont).size) : 0;
|
|
|
174 |
if(this.vertical){
|
|
|
175 |
start = {y: dim.height - offsets.b};
|
|
|
176 |
stop = {y: offsets.t};
|
|
|
177 |
axisVector = {x: 0, y: -1};
|
|
|
178 |
if(this.opt.leftBottom){
|
|
|
179 |
start.x = stop.x = offsets.l;
|
|
|
180 |
tickVector = {x: -1, y: 0};
|
|
|
181 |
labelAlign = "end";
|
|
|
182 |
}else{
|
|
|
183 |
start.x = stop.x = dim.width - offsets.r;
|
|
|
184 |
tickVector = {x: 1, y: 0};
|
|
|
185 |
labelAlign = "start";
|
|
|
186 |
}
|
|
|
187 |
labelOffset = {x: tickVector.x * (tickSize + labelGap), y: size * 0.4};
|
|
|
188 |
}else{
|
|
|
189 |
start = {x: offsets.l};
|
|
|
190 |
stop = {x: dim.width - offsets.r};
|
|
|
191 |
axisVector = {x: 1, y: 0};
|
|
|
192 |
labelAlign = "middle";
|
|
|
193 |
if(this.opt.leftBottom){
|
|
|
194 |
start.y = stop.y = dim.height - offsets.b;
|
|
|
195 |
tickVector = {x: 0, y: 1};
|
|
|
196 |
labelOffset = {y: tickSize + labelGap + size};
|
|
|
197 |
}else{
|
|
|
198 |
start.y = stop.y = offsets.t;
|
|
|
199 |
tickVector = {x: 0, y: -1};
|
|
|
200 |
labelOffset = {y: -tickSize - labelGap};
|
|
|
201 |
}
|
|
|
202 |
labelOffset.x = 0;
|
|
|
203 |
}
|
|
|
204 |
|
|
|
205 |
// render shapes
|
|
|
206 |
this.cleanGroup();
|
|
|
207 |
var s = this.group, c = this.scaler, step, next,
|
|
|
208 |
nextMajor = c.major.start, nextMinor = c.minor.start, nextMicro = c.micro.start;
|
|
|
209 |
s.createLine({x1: start.x, y1: start.y, x2: stop.x, y2: stop.y}).setStroke(taStroke);
|
|
|
210 |
if(this.opt.microTicks && c.micro.tick){
|
|
|
211 |
step = c.micro.tick, next = nextMicro;
|
|
|
212 |
}else if(this.opt.minorTicks && c.minor.tick){
|
|
|
213 |
step = c.minor.tick, next = nextMinor;
|
|
|
214 |
}else if(c.major.tick){
|
|
|
215 |
step = c.major.tick, next = nextMajor;
|
|
|
216 |
}else{
|
|
|
217 |
// don't draw anything
|
|
|
218 |
return this;
|
|
|
219 |
}
|
|
|
220 |
while(next <= c.bounds.upper + 1/c.scale){
|
|
|
221 |
var offset = (next - c.bounds.lower) * c.scale,
|
|
|
222 |
x = start.x + axisVector.x * offset,
|
|
|
223 |
y = start.y + axisVector.y * offset;
|
|
|
224 |
if(Math.abs(nextMajor - next) < step / 2){
|
|
|
225 |
// major tick
|
|
|
226 |
s.createLine({
|
|
|
227 |
x1: x, y1: y,
|
|
|
228 |
x2: x + tickVector.x * taMajorTick.length,
|
|
|
229 |
y2: y + tickVector.y * taMajorTick.length
|
|
|
230 |
}).setStroke(taMajorTick);
|
|
|
231 |
if(this.opt.majorLabels){
|
|
|
232 |
var elem = dc.axis2d.common.createText[this.opt.htmlLabels ? "html" : "gfx"]
|
|
|
233 |
(this.chart, s, x + labelOffset.x, y + labelOffset.y, labelAlign,
|
|
|
234 |
this._getLabel(nextMajor, c.major.prec), taFont, taFontColor);
|
|
|
235 |
if(this.opt.htmlLabels){ this.htmlElements.push(elem); }
|
|
|
236 |
}
|
|
|
237 |
nextMajor += c.major.tick;
|
|
|
238 |
nextMinor += c.minor.tick;
|
|
|
239 |
nextMicro += c.micro.tick;
|
|
|
240 |
}else if(Math.abs(nextMinor - next) < step / 2){
|
|
|
241 |
// minor tick
|
|
|
242 |
if(this.opt.minorTicks){
|
|
|
243 |
s.createLine({
|
|
|
244 |
x1: x, y1: y,
|
|
|
245 |
x2: x + tickVector.x * taMinorTick.length,
|
|
|
246 |
y2: y + tickVector.y * taMinorTick.length
|
|
|
247 |
}).setStroke(taMinorTick);
|
|
|
248 |
if(this.opt.minorLabels && (c.minMinorStep <= c.minor.tick * c.scale)){
|
|
|
249 |
var elem = dc.axis2d.common.createText[this.opt.htmlLabels ? "html" : "gfx"]
|
|
|
250 |
(this.chart, s, x + labelOffset.x, y + labelOffset.y, labelAlign,
|
|
|
251 |
this._getLabel(nextMinor, c.minor.prec), taFont, taFontColor);
|
|
|
252 |
if(this.opt.htmlLabels){ this.htmlElements.push(elem); }
|
|
|
253 |
}
|
|
|
254 |
}
|
|
|
255 |
nextMinor += c.minor.tick;
|
|
|
256 |
nextMicro += c.micro.tick;
|
|
|
257 |
}else{
|
|
|
258 |
// micro tick
|
|
|
259 |
if(this.opt.microTicks){
|
|
|
260 |
s.createLine({
|
|
|
261 |
x1: x, y1: y,
|
|
|
262 |
// use minor ticks for now
|
|
|
263 |
x2: x + tickVector.x * taMinorTick.length,
|
|
|
264 |
y2: y + tickVector.y * taMinorTick.length
|
|
|
265 |
}).setStroke(taMinorTick);
|
|
|
266 |
}
|
|
|
267 |
nextMicro += c.micro.tick;
|
|
|
268 |
}
|
|
|
269 |
next += step;
|
|
|
270 |
}
|
|
|
271 |
this.dirty = false;
|
|
|
272 |
return this;
|
|
|
273 |
},
|
|
|
274 |
|
|
|
275 |
// utilities
|
|
|
276 |
_getLabel: function(number, precision){
|
|
|
277 |
if(this.opt.labels){
|
|
|
278 |
// classic binary search
|
|
|
279 |
var l = this.opt.labels, lo = 0, hi = l.length;
|
|
|
280 |
while(lo < hi){
|
|
|
281 |
var mid = Math.floor((lo + hi) / 2), val = l[mid].value;
|
|
|
282 |
if(val < number){
|
|
|
283 |
lo = mid + 1;
|
|
|
284 |
}else{
|
|
|
285 |
hi = mid;
|
|
|
286 |
}
|
|
|
287 |
}
|
|
|
288 |
// lets take into account FP errors
|
|
|
289 |
if(lo < l.length && eq(l[lo].value, number)){
|
|
|
290 |
return l[lo].text;
|
|
|
291 |
}
|
|
|
292 |
--lo;
|
|
|
293 |
if(lo < l.length && eq(l[lo].value, number)){
|
|
|
294 |
return l[lo].text;
|
|
|
295 |
}
|
|
|
296 |
lo += 2;
|
|
|
297 |
if(lo < l.length && eq(l[lo].value, number)){
|
|
|
298 |
return l[lo].text;
|
|
|
299 |
}
|
|
|
300 |
// otherwise we will produce a number
|
|
|
301 |
}
|
|
|
302 |
return this.opt.fixed ? number.toFixed(precision < 0 ? -precision : 0) : number.toString();
|
|
|
303 |
}
|
|
|
304 |
});
|
|
|
305 |
})();
|
|
|
306 |
|
|
|
307 |
}
|