source: Dev/trunk/src/client/dojox/charting/axis2d/Default.js @ 483

Last change on this file since 483 was 483, checked in by hendrikvanantwerpen, 11 years ago

Added Dojo 1.9.3 release.

File size: 36.2 KB
Line 
1define(["dojo/_base/lang", "dojo/_base/array", "dojo/sniff", "dojo/_base/declare",
2        "dojo/_base/connect", "dojo/dom-geometry", "./Invisible",
3        "../scaler/linear", "./common", "dojox/gfx", "dojox/lang/utils", "dojox/lang/functional",
4        "dojo/has!dojo-bidi?../bidi/axis2d/Default"],
5        function(lang, arr, has, declare, connect, domGeom, Invisible,
6                        lin, acommon, g, du, df, BidiDefault){
7
8        /*=====
9        var __AxisCtorArgs = {
10                // summary:
11                //              Optional arguments used in the definition of an axis.
12                // vertical: Boolean?
13                //              A flag that says whether an axis is vertical (i.e. y axis) or horizontal. Default is false (horizontal).
14                // fixUpper: String?
15                //              Align the greatest value on the axis with the specified tick level. Options are "major", "minor", "micro", or "none".  Defaults to "none".
16                // fixLower: String?
17                //              Align the smallest value on the axis with the specified tick level. Options are "major", "minor", "micro", or "none".  Defaults to "none".
18                // natural: Boolean?
19                //              Ensure tick marks are made on "natural" numbers. Defaults to false.
20                // leftBottom: Boolean?
21                //              The position of a vertical axis; if true, will be placed against the left-bottom corner of the chart.  Defaults to true.
22                // includeZero: Boolean?
23                //              Include 0 on the axis rendering.  Default is false.
24                // fixed: Boolean?
25                //              Force all axis labels to be fixed numbers.  Default is true.
26                // majorLabels: Boolean?
27                //              Flag to draw labels at major ticks. Default is true.
28                // minorTicks: Boolean?
29                //              Flag to draw minor ticks on an axis.  Default is true.
30                // minorLabels: Boolean?
31                //              Flag to labels on minor ticks when there is enough space. Default is true.
32                // microTicks: Boolean?
33                //              Flag to draw micro ticks on an axis. Default is false.
34                // htmlLabels: Boolean?
35                //              Flag to use HTML (as opposed to the native vector graphics engine) to draw labels. Default is true.
36                // min: Number?
37                //              The smallest value on an axis. Default is 0.
38                // max: Number?
39                //              The largest value on an axis. Default is 1.
40                // from: Number?
41                //              Force the chart to render data visible from this value. Default is 0.
42                // to: Number?
43                //              Force the chart to render data visible to this value. Default is 1.
44                // majorTickStep: Number?
45                //              The amount to skip before a major tick is drawn. When not set the major ticks step is computed from
46                //              the data range.
47                // minorTickStep: Number?
48                //              The amount to skip before a minor tick is drawn. When not set the minor ticks step is computed from
49                //              the data range.
50                // microTickStep: Number?
51                //              The amount to skip before a micro tick is drawn. When not set the micro ticks step is computed from
52                // labels: Object[]?
53                //              An array of labels for major ticks, with corresponding numeric values, ordered by value.
54                // labelFunc: Function?
55                //              An optional function to use to compute label text. It takes precedence over
56                //              the default text when available. The function must be of the following form:
57                //      |               function labelFunc(text, value, precision) {}
58                //              `text` is the already pre-formatted text. Pre-formatting is done using `dojo/number` is available, `Date.toFixed` otherwise.
59                //              `value`  is the raw axis value.
60                //              `precision` is the requested precision to be applied.
61                // maxLabelSize: Number?
62                //              The maximum size, in pixels, for a label.  To be used with the optional label function.
63                // stroke: dojox.gfx.Stroke?
64                //              An optional stroke to be used for drawing an axis.
65                // majorTick: Object?
66                //              An object containing a dojox.gfx.Stroke, and a length (number) for a major tick.
67                // minorTick: Object?
68                //              An object containing a dojox.gfx.Stroke, and a length (number) for a minor tick.
69                // microTick: Object?
70                //              An object containing a dojox.gfx.Stroke, and a length (number) for a micro tick.
71                // tick: Object?
72                //              An object containing a dojox.gfx.Stroke, and a length (number) for a tick.
73                // font: String?
74                //              An optional font definition (as used in the CSS font property) for labels.
75                // fontColor: String|dojo.Color?
76                //              An optional color to be used in drawing labels.
77                // titleGap: Number?
78                //              An optional grap between axis title and axis label
79                // titleFont: String?
80                //              An optional font definition for axis title
81                // titleFontColor: String?
82                //              An optional axis title color
83                // titleOrientation: String?
84                //              An optional orientation for axis title. "axis" means the title facing the axis, "away" means facing away.
85                //              If no value is set "axis" is used.
86                // enableCache: Boolean?
87                //              Whether the ticks and labels are cached from one rendering to another. This improves the rendering performance of
88                //              successive rendering but penalize the first rendering. For labels it is only working with gfx labels
89                //              not html ones.  Default false.
90                // dropLabels: Boolean?
91                //              Whether the axis automatically drops labels at regular interval or not to avoid labels overlapping.
92                //              This gives better results but require more computations.  You can disable it to save computation
93                //              time when you know your labels won't overlap. Default is true.
94                // labelSizeChange: Boolean?
95                //              Indicates to the axis whether the axis labels are changing their size on zoom. If false this allows to
96                //              optimize the axis by avoiding recomputing labels maximum size on zoom actions. Default is false.
97        };
98        =====*/
99
100        var centerAnchorLimit = 45;     // in degrees
101
102        var Default = declare(has("dojo-bidi")? "dojox.charting.axis2d.NonBidiDefault" : "dojox.charting.axis2d.Default", Invisible, {
103                // summary:
104                //              The default axis object used in dojox.charting.  See dojox.charting.Chart.addAxis for details.
105
106                // defaultParams: Object
107                //              The default parameters used to define any axis.
108                // optionalParams: Object
109                //              Any optional parameters needed to define an axis.
110
111                /*=====
112                // TODO: the documentation tools need these to be pre-defined in order to pick them up
113                //      correctly, but the code here is partially predicated on whether or not the properties
114                //      actually exist.  For now, we will leave these undocumented but in the code for later. -- TRT
115
116                // opt: Object
117                //              The actual options used to define this axis, created at initialization.
118                // scaler: Object
119                //              The calculated helper object to tell charts how to draw an axis and any data.
120                // ticks: Object
121                //              The calculated tick object that helps a chart draw the scaling on an axis.
122                // dirty: Boolean
123                //              The state of the axis (whether it needs to be redrawn or not)
124                // scale: Number
125                //              The current scale of the axis.
126                // offset: Number
127                //              The current offset of the axis.
128
129                opt: null,
130                scaler: null,
131                ticks: null,
132                dirty: true,
133                scale: 1,
134                offset: 0,
135                =====*/
136                defaultParams: {
137                        vertical:       false,          // true for vertical axis
138                        fixUpper:       "none", // align the upper on ticks: "major", "minor", "micro", "none"
139                        fixLower:       "none", // align the lower on ticks: "major", "minor", "micro", "none"
140                        natural:         false,         // all tick marks should be made on natural numbers
141                        leftBottom:  true,              // position of the axis, used with "vertical"
142                        includeZero: false,             // 0 should be included
143                        fixed:     true,                // all labels are fixed numbers
144                        majorLabels: true,              // draw major labels
145                        minorTicks:  true,              // draw minor ticks
146                        minorLabels: true,              // draw minor labels
147                        microTicks:  false,             // draw micro ticks
148                        rotation:       0,                      // label rotation angle in degrees
149                        htmlLabels:  true,              // use HTML to draw labels
150                        enableCache: false,             // whether we cache or not
151                        dropLabels: true,               // whether we automatically drop overlapping labels or not
152                        labelSizeChange: false // whether the labels size change on zoom
153                },
154                optionalParams: {
155                        min:                    0,      // minimal value on this axis
156                        max:                    1,      // maximal value on this axis
157                        from:                   0,      // visible from this value
158                        to:                             1,      // visible to this value
159                        majorTickStep:  4,      // major tick step
160                        minorTickStep:  2,      // minor tick step
161                        microTickStep:  1,      // micro tick step
162                        labels:                 [],     // array of labels for major ticks
163                        // with corresponding numeric values
164                        // ordered by values
165                        labelFunc:              null, // function to compute label values
166                        maxLabelSize:   0,      // size in px. For use with labelFunc
167                        maxLabelCharCount:      0,      // size in word count.
168                        trailingSymbol: null,
169
170                        // TODO: add support for minRange!
171                        // minRange:            1,      // smallest distance from min allowed on the axis
172
173                        // theme components
174                        stroke:                 {},     // stroke for an axis
175                        majorTick:              {},     // stroke + length for a tick
176                        minorTick:              {},     // stroke + length for a tick
177                        microTick:              {},     // stroke + length for a tick
178                        tick:              {},  // stroke + length for a tick
179                        font:                   "",     // font for labels
180                        fontColor:              "",     // color for labels as a string
181                        title:                           "",    // axis title
182                        titleGap:                        0,             // gap between axis title and axis label
183                        titleFont:                       "",            // axis title font
184                        titleFontColor:          "",            // axis title font color
185                        titleOrientation:        ""             // "axis" means the title facing the axis, "away" means facing away
186                },
187
188                constructor: function(chart, kwArgs){
189                        // summary:
190                        //              The constructor for an axis.
191                        // chart: dojox/charting/Chart
192                        //              The chart the axis belongs to.
193                        // kwArgs: __AxisCtorArgs?
194                        //              Any optional keyword arguments to be used to define this axis.
195                        this.opt = lang.clone(this.defaultParams);
196                        du.updateWithObject(this.opt, kwArgs);
197                        du.updateWithPattern(this.opt, kwArgs, this.optionalParams);
198                        if(this.opt.enableCache){
199                                this._textFreePool = [];
200                                this._lineFreePool = [];
201                                this._textUsePool = [];
202                                this._lineUsePool = [];
203                        }
204                        this._invalidMaxLabelSize = true;
205                },
206                setWindow: function(scale, offset){
207                        // summary:
208                        //              Set the drawing "window" for the axis.
209                        // scale: Number
210                        //              The new scale for the axis.
211                        // offset: Number
212                        //              The new offset for the axis.
213                        // returns: dojox/charting/axis2d/Default
214                        //              The reference to the axis for functional chaining.
215                        if(scale != this.scale){
216                                // if scale changed we need to recompute new max label size
217                                this._invalidMaxLabelSize = true;
218                        }
219                        return this.inherited(arguments);
220                },
221
222                _groupLabelWidth: function(labels, font, wcLimit){
223                        if(!labels.length){
224                                return 0;
225                        }
226                        if(labels.length > 50){
227                                // let's avoid degenerated cases
228                                labels.length = 50;
229                        }
230                        if(lang.isObject(labels[0])){
231                                labels = df.map(labels, function(label){ return label.text; });
232                        }
233                        if(wcLimit){
234                                labels = df.map(labels, function(label){
235                                        return lang.trim(label).length == 0 ? "" : label.substring(0, wcLimit) + this.trailingSymbol;
236                                }, this);
237                        }
238                        var s = labels.join("<br>");
239                        return g._base._getTextBox(s, {font: font}).w || 0;
240                },
241
242                _getMaxLabelSize: function(min, max, span, rotation, font, size){
243                        if(this._maxLabelSize == null && arguments.length == 6){
244                                var o = this.opt;
245                                // everything might have changed, reset the minMinorStep value
246                                this.scaler.minMinorStep = this._prevMinMinorStep = 0;
247                                var ob = lang.clone(o);
248                                delete ob.to;
249                                delete ob.from;
250                                // build all the ticks from min, to max not from to to _but_ using the step
251                                // that would be used if we where just displaying from to to from.
252                                var sb = lin.buildScaler(min, max, span, ob, o.to - o.from);
253                                sb.minMinorStep = 0;
254                                this._majorStart = sb.major.start;
255                                // we build all the ticks not only the ones we need to draw in order to get
256                                // a correct drop rate computation that works for any offset of this scale
257                                var tb = lin.buildTicks(sb, o);
258                                // if there is not tick at all tb is null
259                                if(size && tb){
260                                        var majLabelW = 0, minLabelW = 0; // non rotated versions
261                                        // we first collect all labels when needed
262                                        var tickLabelFunc = function(tick){
263                                                if(tick.label){
264                                                        this.push(tick.label);
265                                                }
266                                        };
267                                        var labels = [];
268                                        if(this.opt.majorLabels){
269                                                arr.forEach(tb.major, tickLabelFunc, labels);
270                                                majLabelW = this._groupLabelWidth(labels, font, ob.maxLabelCharCount);
271                                                if(ob.maxLabelSize){
272                                                        majLabelW = Math.min(ob.maxLabelSize, majLabelW);
273                                                }
274                                        }
275                                        // do the minor labels computation only if dropLabels is set
276                                        labels = [];
277                                        if(this.opt.dropLabels && this.opt.minorLabels){
278                                                arr.forEach(tb.minor, tickLabelFunc, labels);
279                                                minLabelW = this._groupLabelWidth(labels, font, ob.maxLabelCharCount);
280                                                if(ob.maxLabelSize){
281                                                        minLabelW = Math.min(ob.maxLabelSize, minLabelW);
282                                                }
283                                        }
284                                        this._maxLabelSize = {
285                                                majLabelW: majLabelW, minLabelW: minLabelW,
286                                                majLabelH: size, minLabelH: size
287                                        };
288                                }else{
289                                        this._maxLabelSize = null;
290                                }
291                        }
292                        return this._maxLabelSize;
293                },
294
295                calculate: function(min, max, span){
296                        this.inherited(arguments);
297                        // when the scale has not changed there is no reason for minMinorStep to change
298                        this.scaler.minMinorStep = this._prevMinMinorStep;
299                        // we want to recompute the dropping mechanism only when the scale or the size of the axis is changing
300                        // not when for example when we scroll (otherwise effect would be weird)
301                        if((this._invalidMaxLabelSize || span != this._oldSpan) && (min != Infinity && max != -Infinity)){
302                                this._invalidMaxLabelSize = false;
303                                if(this.opt.labelSizeChange){
304                                        this._maxLabelSize = null;
305                                }
306                                this._oldSpan = span;
307                                var o = this.opt;
308                                var ta = this.chart.theme.axis, rotation = o.rotation % 360,
309                                        labelGap = this.chart.theme.axis.tick.labelGap,
310                                        // TODO: we use one font --- of major tick, we need to use major and minor fonts
311                                        font = o.font || (ta.majorTick && ta.majorTick.font) || (ta.tick && ta.tick.font),
312                                        size = font ? g.normalizedLength(g.splitFontString(font).size) : 0,
313                                        // even if we don't drop label we need to compute max size for offsets
314                                        labelW = this._getMaxLabelSize(min, max, span, rotation, font, size);
315                                if(typeof labelGap != "number"){
316                                        labelGap = 4; // in pixels
317                                }
318                                if(labelW && o.dropLabels){
319                                        var cosr = Math.abs(Math.cos(rotation * Math.PI / 180)),
320                                                sinr = Math.abs(Math.sin(rotation * Math.PI / 180));
321                                        var majLabelW, minLabelW;
322                                        if(rotation < 0){
323                                                rotation += 360;
324                                        }
325                                        switch(rotation){
326                                                case 0:
327                                                case 180:
328                                                        // trivial cases: horizontal labels
329                                                        if(this.vertical){
330                                                                majLabelW = minLabelW = size;
331                                                        }else{
332                                                                majLabelW = labelW.majLabelW;
333                                                                minLabelW = labelW.minLabelW;
334                                                        }
335                                                        break;
336                                                case 90:
337                                                case 270:
338                                                        // trivial cases: vertical
339                                                        if(this.vertical){
340                                                                majLabelW = labelW.majLabelW;
341                                                                minLabelW = labelW.minLabelW;
342                                                        }else{
343                                                                majLabelW = minLabelW = size;
344                                                        }
345                                                        break;
346                                                default:
347                                                        // all major labels are parallel they can't collapse except if the two ticks are
348                                                        // closer than the height of the text * cos(90-rotation)
349                                                        majLabelW  = this.vertical ? Math.min(labelW.majLabelW, size / cosr) : Math.min(labelW.majLabelW, size / sinr);
350                                                        // for minor labels we need to rotated them
351                                                        var gap1 = Math.sqrt(labelW.minLabelW * labelW.minLabelW + size * size),
352                                                                gap2 = this.vertical ? size * cosr + labelW.minLabelW * sinr : labelW.minLabelW * cosr + size * sinr;
353                                                        minLabelW = Math.min(gap1, gap2);
354                                                        break;
355                                        }
356                                        // we need to check both minor and major labels fit a minor step
357                                        this.scaler.minMinorStep = this._prevMinMinorStep =  Math.max(majLabelW, minLabelW) + labelGap;
358                                        var canMinorLabel = this.scaler.minMinorStep <= this.scaler.minor.tick * this.scaler.bounds.scale;
359                                        if(!canMinorLabel){
360                                                // we can't place minor labels, let's see if we can place major ones
361                                                // in a major step and if not which skip interval we must follow
362                                                this._skipInterval = Math.floor((majLabelW + labelGap) / (this.scaler.major.tick * this.scaler.bounds.scale));
363                                        }else{
364                                                // everything fit well
365                                                this._skipInterval = 0;
366                                        }
367                                }else{
368                                        // drop label disabled
369                                        this._skipInterval = 0;
370                                }
371                        }
372                        // computes the tick subset we need for that scale/offset
373                        this.ticks = lin.buildTicks(this.scaler, this.opt);
374                        return this;
375                },
376
377                getOffsets: function(){
378                        // summary:
379                        //              Get the physical offset values for this axis (used in drawing data series). This method is not
380                        //              supposed to be called by the users but internally.
381                        // returns: Object
382                        //              The calculated offsets in the form of { l, r, t, b } (left, right, top, bottom).
383                        var s = this.scaler, offsets = { l: 0, r: 0, t: 0, b: 0 };
384                        if(!s){
385                                return offsets;
386                        }
387                        var o = this.opt,
388                                ta = this.chart.theme.axis,
389                                labelGap = this.chart.theme.axis.tick.labelGap,
390                                // TODO: we use one font --- of major tick, we need to use major and minor fonts
391                                taTitleFont = o.titleFont || (ta.title && ta.title.font),
392                                taTitleGap = (o.titleGap==0) ? 0 : o.titleGap || (ta.title && ta.title.gap),
393                                taMajorTick = this.chart.theme.getTick("major", o),
394                                taMinorTick = this.chart.theme.getTick("minor", o),
395                                tsize = taTitleFont ? g.normalizedLength(g.splitFontString(taTitleFont).size) : 0,
396                                rotation = o.rotation % 360, leftBottom = o.leftBottom,
397                                cosr = Math.abs(Math.cos(rotation * Math.PI / 180)),
398                                sinr = Math.abs(Math.sin(rotation * Math.PI / 180));
399                        this.trailingSymbol = (o.trailingSymbol === undefined || o.trailingSymbol === null) ?
400                                this.trailingSymbol : o.trailingSymbol;
401                        if(typeof labelGap != "number"){
402                                labelGap = 4; // in pixels
403                        }
404                        if(rotation < 0){
405                                rotation += 360;
406                        }
407                        var maxLabelSize = this._getMaxLabelSize(); // don't need parameters, calculate has been called before => we use cached value
408                        if(maxLabelSize){
409                                var side;
410                                var labelWidth = Math.ceil(Math.max(maxLabelSize.majLabelW, maxLabelSize.minLabelW)) + 1,
411                                        size = Math.ceil(Math.max(maxLabelSize.majLabelH, maxLabelSize.minLabelH)) + 1;
412                                if(this.vertical){
413                                        side = leftBottom ? "l" : "r";
414                                        switch(rotation){
415                                                case 0:
416                                                case 180:
417                                                        offsets[side] = labelWidth;
418                                                        offsets.t = offsets.b = size / 2;
419                                                        break;
420                                                case 90:
421                                                case 270:
422                                                        offsets[side] = size;
423                                                        offsets.t = offsets.b = labelWidth / 2;
424                                                        break;
425                                                default:
426                                                        if(rotation <= centerAnchorLimit || (180 < rotation && rotation <= (180 + centerAnchorLimit))){
427                                                                offsets[side] = size * sinr / 2 + labelWidth * cosr;
428                                                                offsets[leftBottom ? "t" : "b"] = size * cosr / 2 + labelWidth * sinr;
429                                                                offsets[leftBottom ? "b" : "t"] = size * cosr / 2;
430                                                        }else if(rotation > (360 - centerAnchorLimit) || (180 > rotation && rotation > (180 - centerAnchorLimit))){
431                                                                offsets[side] = size * sinr / 2 + labelWidth * cosr;
432                                                                offsets[leftBottom ? "b" : "t"] = size * cosr / 2 + labelWidth * sinr;
433                                                                offsets[leftBottom ? "t" : "b"] = size * cosr / 2;
434                                                        }else if(rotation < 90 || (180 < rotation && rotation < 270)){
435                                                                offsets[side] = size * sinr + labelWidth * cosr;
436                                                                offsets[leftBottom ? "t" : "b"] = size * cosr + labelWidth * sinr;
437                                                        }else{
438                                                                offsets[side] = size * sinr + labelWidth * cosr;
439                                                                offsets[leftBottom ? "b" : "t"] = size * cosr + labelWidth * sinr;
440                                                        }
441                                                        break;
442                                        }
443                                        offsets[side] += labelGap + Math.max(taMajorTick.length > 0?taMajorTick.length:0,
444                                                                                                                 taMinorTick.length > 0?taMinorTick.length:0) + (o.title ? (tsize + taTitleGap) : 0);
445                                }else{
446                                        side = leftBottom ? "b" : "t";
447                                        switch(rotation){
448                                                case 0:
449                                                case 180:
450                                                        offsets[side] = size;
451                                                        offsets.l = offsets.r = labelWidth / 2;
452                                                        break;
453                                                case 90:
454                                                case 270:
455                                                        offsets[side] = labelWidth;
456                                                        offsets.l = offsets.r = size / 2;
457                                                        break;
458                                                default:
459                                                        if((90 - centerAnchorLimit) <= rotation && rotation <= 90 || (270 - centerAnchorLimit) <= rotation && rotation <= 270){
460                                                                offsets[side] = size * cosr / 2 + labelWidth * sinr;
461                                                                offsets[leftBottom ? "r" : "l"] = size * sinr / 2 + labelWidth * cosr;
462                                                                offsets[leftBottom ? "l" : "r"] = size * sinr / 2;
463                                                        }else if(90 <= rotation && rotation <= (90 + centerAnchorLimit) || 270 <= rotation && rotation <= (270 + centerAnchorLimit)){
464                                                                offsets[side] = size * cosr / 2 + labelWidth * sinr;
465                                                                offsets[leftBottom ? "l" : "r"] = size * sinr / 2 + labelWidth * cosr;
466                                                                offsets[leftBottom ? "r" : "l"] = size * sinr / 2;
467                                                        }else if(rotation < centerAnchorLimit || (180 < rotation && rotation < (180 + centerAnchorLimit))){
468                                                                offsets[side] = size * cosr + labelWidth * sinr;
469                                                                offsets[leftBottom ? "r" : "l"] = size * sinr + labelWidth * cosr;
470                                                        }else{
471                                                                offsets[side] = size * cosr + labelWidth * sinr;
472                                                                offsets[leftBottom ? "l" : "r"] = size * sinr + labelWidth * cosr;
473                                                        }
474                                                        break;
475                                        }
476                                        offsets[side] += labelGap + Math.max(taMajorTick.length > 0?taMajorTick.length:0,
477                                                                                                                 taMinorTick.length > 0?taMinorTick.length:0) + (o.title ? (tsize + taTitleGap) : 0);
478                                }
479                        }
480                        return offsets; //      Object
481                },
482                cleanGroup: function(creator){
483                        if(this.opt.enableCache && this.group){
484                                this._lineFreePool = this._lineFreePool.concat(this._lineUsePool);
485                                this._lineUsePool = [];
486                                this._textFreePool = this._textFreePool.concat(this._textUsePool);
487                                this._textUsePool = [];
488                        }
489                        this.inherited(arguments);
490                },
491                createText: function(labelType, creator, x, y, align, textContent, font, fontColor, labelWidth){
492                        if(!this.opt.enableCache || labelType=="html"){
493                                return acommon.createText[labelType](
494                                                this.chart,
495                                                creator,
496                                                x,
497                                                y,
498                                                align,
499                                                textContent,
500                                                font,
501                                                fontColor,
502                                                labelWidth
503                                        );
504                        }
505                        var text;
506                        if(this._textFreePool.length > 0){
507                                text = this._textFreePool.pop();
508                                text.setShape({x: x, y: y, text: textContent, align: align});
509                                // For now all items share the same font, no need to re-set it
510                                //.setFont(font).setFill(fontColor);
511                                // was cleared, add it back
512                                creator.add(text);
513                        }else{
514                                text = acommon.createText[labelType](
515                                                this.chart,
516                                                creator,
517                                                x,
518                                                y,
519                                                align,
520                                                textContent,
521                                                font,
522                                                fontColor                                               
523                                        );                     
524                        }
525                        this._textUsePool.push(text);
526                        return text;
527                },
528                createLine: function(creator, params){
529                        var line;
530                        if(this.opt.enableCache && this._lineFreePool.length > 0){
531                                line = this._lineFreePool.pop();
532                                line.setShape(params);
533                                // was cleared, add it back
534                                creator.add(line);
535                        }else{
536                                line = creator.createLine(params);
537                        }
538                        if(this.opt.enableCache){
539                                this._lineUsePool.push(line);
540                        }
541                        return line;
542                },
543                render: function(dim, offsets){
544                        // summary:
545                        //              Render/draw the axis.
546                        // dim: Object
547                        //              An object of the form { width, height}.
548                        // offsets: Object
549                        //              An object of the form { l, r, t, b }.
550                        // returns: dojox/charting/axis2d/Default
551                        //              The reference to the axis for functional chaining.
552                       
553                        var isRtl = this._isRtl();      // chart mirroring
554                        if(!this.dirty || !this.scaler){
555                                return this;    //      dojox/charting/axis2d/Default
556                        }
557                        // prepare variable
558                        var o = this.opt, ta = this.chart.theme.axis, leftBottom = o.leftBottom, rotation = o.rotation % 360,
559                                start, stop, titlePos, titleRotation=0, titleOffset, axisVector, tickVector, anchorOffset, labelOffset, labelAlign,
560                                labelGap = this.chart.theme.axis.tick.labelGap,
561                                // TODO: we use one font --- of major tick, we need to use major and minor fonts
562                                taFont = o.font || (ta.majorTick && ta.majorTick.font) || (ta.tick && ta.tick.font),
563                                taTitleFont = o.titleFont || (ta.title && ta.title.font),
564                                // TODO: we use one font color --- we need to use different colors
565                                taFontColor = o.fontColor || (ta.majorTick && ta.majorTick.fontColor) || (ta.tick && ta.tick.fontColor) || "black",
566                                taTitleFontColor = o.titleFontColor || (ta.title && ta.title.fontColor) || "black",
567                                taTitleGap = (o.titleGap==0) ? 0 : o.titleGap || (ta.title && ta.title.gap) || 15,
568                                taTitleOrientation = o.titleOrientation || (ta.title && ta.title.orientation) || "axis",
569                                taMajorTick = this.chart.theme.getTick("major", o),
570                                taMinorTick = this.chart.theme.getTick("minor", o),
571                                taMicroTick = this.chart.theme.getTick("micro", o),
572
573                                taStroke = "stroke" in o ? o.stroke : ta.stroke,
574                                size = taFont ? g.normalizedLength(g.splitFontString(taFont).size) : 0,
575                                cosr = Math.abs(Math.cos(rotation * Math.PI / 180)),
576                                sinr = Math.abs(Math.sin(rotation * Math.PI / 180)),
577                                tsize = taTitleFont ? g.normalizedLength(g.splitFontString(taTitleFont).size) : 0;
578                        if(typeof labelGap != "number"){
579                                labelGap = 4; // in pixels
580                        }
581                        if(rotation < 0){
582                                rotation += 360;
583                        }
584                        var cachedLabelW = this._getMaxLabelSize();
585                        cachedLabelW = cachedLabelW && cachedLabelW.majLabelW;
586                        if(this.vertical){
587                                start = {y: dim.height - offsets.b};
588                                stop  = {y: offsets.t};
589                                titlePos = {y: (dim.height - offsets.b + offsets.t)/2};
590                                titleOffset = size * sinr + (cachedLabelW || 0) * cosr + labelGap + Math.max(taMajorTick.length > 0?taMajorTick.length:0,
591                                                                                                                                                                                         taMinorTick.length > 0?taMinorTick.length:0) +
592                                        tsize + taTitleGap;
593                                axisVector = {x: 0, y: -1};
594                                labelOffset = {x: 0, y: 0};
595                                tickVector = {x: 1, y: 0};
596                                anchorOffset = {x: labelGap, y: 0};
597                                switch(rotation){
598                                        case 0:
599                                                labelAlign = "end";
600                                                labelOffset.y = size * 0.4;
601                                                break;
602                                        case 90:
603                                                labelAlign = "middle";
604                                                labelOffset.x = -size;
605                                                break;
606                                        case 180:
607                                                labelAlign = "start";
608                                                labelOffset.y = -size * 0.4;
609                                                break;
610                                        case 270:
611                                                labelAlign = "middle";
612                                                break;
613                                        default:
614                                                if(rotation < centerAnchorLimit){
615                                                        labelAlign = "end";
616                                                        labelOffset.y = size * 0.4;
617                                                }else if(rotation < 90){
618                                                        labelAlign = "end";
619                                                        labelOffset.y = size * 0.4;
620                                                }else if(rotation < (180 - centerAnchorLimit)){
621                                                        labelAlign = "start";
622                                                }else if(rotation < (180 + centerAnchorLimit)){
623                                                        labelAlign = "start";
624                                                        labelOffset.y = -size * 0.4;
625                                                }else if(rotation < 270){
626                                                        labelAlign = "start";
627                                                        labelOffset.x = leftBottom ? 0 : size * 0.4;
628                                                }else if(rotation < (360 - centerAnchorLimit)){
629                                                        labelAlign = "end";
630                                                        labelOffset.x = leftBottom ? 0 : size * 0.4;
631                                                }else{
632                                                        labelAlign = "end";
633                                                        labelOffset.y = size * 0.4;
634                                                }
635                                }
636                                if(leftBottom){
637                                        start.x = stop.x = offsets.l;
638                                        titleRotation = (taTitleOrientation && taTitleOrientation == "away") ? 90 : 270;
639                                        titlePos.x = offsets.l - titleOffset + (titleRotation == 270 ? tsize : 0);
640                                        tickVector.x = -1;
641                                        anchorOffset.x = -anchorOffset.x;
642                                }else{
643                                        start.x = stop.x = dim.width - offsets.r;
644                                        titleRotation = (taTitleOrientation && taTitleOrientation == "axis") ? 90 : 270;
645                                        titlePos.x = dim.width - offsets.r + titleOffset - (titleRotation == 270 ? 0 : tsize);
646                                        switch(labelAlign){
647                                                case "start":
648                                                        labelAlign = "end";
649                                                        break;
650                                                case "end":
651                                                        labelAlign = "start";
652                                                        break;
653                                                case "middle":
654                                                        labelOffset.x += size;
655                                                        break;
656                                        }
657                                }
658                        }else{
659                                start = {x: offsets.l};
660                                stop  = {x: dim.width - offsets.r};
661                                titlePos = {x: (dim.width - offsets.r + offsets.l)/2};
662                                titleOffset = size * cosr + (cachedLabelW || 0) * sinr + labelGap + Math.max(taMajorTick.length > 0?taMajorTick.length:0,
663                                                                                                                                                                                         taMinorTick.length > 0?taMinorTick.length:0) +
664                                        tsize + taTitleGap;
665                                axisVector = {x: isRtl ? -1 : 1, y: 0};         // chart mirroring
666                                labelOffset = {x: 0, y: 0};
667                                tickVector = {x: 0, y: 1};
668                                anchorOffset = {x: 0, y: labelGap};
669                                switch(rotation){
670                                        case 0:
671                                                labelAlign = "middle";
672                                                labelOffset.y = size;
673                                                break;
674                                        case 90:
675                                                labelAlign = "start";
676                                                labelOffset.x = -size * 0.4;
677                                                break;
678                                        case 180:
679                                                labelAlign = "middle";
680                                                break;
681                                        case 270:
682                                                labelAlign = "end";
683                                                labelOffset.x = size * 0.4;
684                                                break;
685                                        default:
686                                                if(rotation < (90 - centerAnchorLimit)){
687                                                        labelAlign = "start";
688                                                        labelOffset.y = leftBottom ? size : 0;
689                                                }else if(rotation < (90 + centerAnchorLimit)){
690                                                        labelAlign = "start";
691                                                        labelOffset.x = -size * 0.4;
692                                                }else if(rotation < 180){
693                                                        labelAlign = "start";
694                                                        labelOffset.y = leftBottom ? 0 : -size;
695                                                }else if(rotation < (270 - centerAnchorLimit)){
696                                                        labelAlign = "end";
697                                                        labelOffset.y = leftBottom ? 0 : -size;
698                                                }else if(rotation < (270 + centerAnchorLimit)){
699                                                        labelAlign = "end";
700                                                        labelOffset.y = leftBottom ? size * 0.4 : 0;
701                                                }else{
702                                                        labelAlign = "end";
703                                                        labelOffset.y = leftBottom ? size : 0;
704                                                }
705                                }
706                                if(leftBottom){
707                                        start.y = stop.y = dim.height - offsets.b;
708                                        titleRotation = (taTitleOrientation && taTitleOrientation == "axis") ? 180 : 0;
709                                        titlePos.y = dim.height - offsets.b + titleOffset - (titleRotation ? tsize : 0);
710                                }else{
711                                        start.y = stop.y = offsets.t;
712                                        titleRotation = (taTitleOrientation && taTitleOrientation == "away") ? 180 : 0;
713                                        titlePos.y = offsets.t - titleOffset + (titleRotation ? 0 : tsize);
714                                        tickVector.y = -1;
715                                        anchorOffset.y = -anchorOffset.y;
716                                        switch(labelAlign){
717                                                case "start":
718                                                        labelAlign = "end";
719                                                        break;
720                                                case "end":
721                                                        labelAlign = "start";
722                                                        break;
723                                                case "middle":
724                                                        labelOffset.y -= size;
725                                                        break;
726                                        }
727                                }
728                        }
729
730                        // render shapes
731
732                        this.cleanGroup();
733
734                        var s = this.group,
735                                c = this.scaler,
736                                t = this.ticks,
737                                f = lin.getTransformerFromModel(this.scaler),
738                                // GFX Canvas now supports labels, so let's _not_ fallback to HTML anymore on canvas, just use
739                                // HTML labels if explicitly asked + no rotation + no IE + no Opera
740                                labelType = (!o.title || !titleRotation) && !rotation && this.opt.htmlLabels && !has("ie") && !has("opera") ? "html" : "gfx",
741                                dx = tickVector.x * taMajorTick.length,
742                                dy = tickVector.y * taMajorTick.length,
743                                skip = this._skipInterval;
744
745                        s.createLine({
746                                x1: start.x,
747                                y1: start.y,
748                                x2: stop.x,
749                                y2: stop.y
750                        }).setStroke(taStroke);
751
752                        //create axis title
753                        if(o.title){
754                                var axisTitle = acommon.createText[labelType](
755                                        this.chart,
756                                        s,
757                                        titlePos.x,
758                                        titlePos.y,
759                                        "middle",
760                                        o.title,
761                                        taTitleFont,
762                                        taTitleFontColor
763                                );
764                                if(labelType == "html"){
765                                        this.htmlElements.push(axisTitle);
766                                }else{
767                                        //as soon as rotation is provided, labelType won't be "html"
768                                        //rotate gfx labels
769                                        axisTitle.setTransform(g.matrix.rotategAt(titleRotation, titlePos.x, titlePos.y));
770                                }
771                        }
772
773                        // go out nicely instead of try/catch
774                        if(t == null){
775                                this.dirty = false;
776                                return this;
777                        }
778
779                        var rel = (t.major.length > 0)?(t.major[0].value - this._majorStart) / c.major.tick:0;
780                        var canLabel = this.opt.majorLabels;
781                        arr.forEach(t.major, function(tick, i){
782                                var offset = f(tick.value), elem,
783                                        x = (isRtl ? stop.x : start.x) + axisVector.x * offset, // chart mirroring
784                                        y = start.y + axisVector.y * offset;
785                                i += rel;
786                                this.createLine(s, {
787                                        x1: x, y1: y,
788                                        x2: x + dx,
789                                        y2: y + dy
790                                }).setStroke(taMajorTick);
791                                if(tick.label && (!skip || (i - (1 + skip)) % (1 + skip) == 0)){
792                                        var label = o.maxLabelCharCount ? this.getTextWithLimitCharCount(tick.label, taFont, o.maxLabelCharCount) : {
793                                                text: tick.label,
794                                                truncated: false
795                                        };
796                                        label = o.maxLabelSize ? this.getTextWithLimitLength(label.text, taFont, o.maxLabelSize, label.truncated) : label;
797                                        elem = this.createText(labelType,
798                                                s,
799                                                x + (taMajorTick.length > 0 ? dx : 0) + anchorOffset.x + (rotation ? 0 : labelOffset.x),
800                                                y + (taMajorTick.length > 0 ? dy : 0) + anchorOffset.y + (rotation ? 0 : labelOffset.y),
801                                                labelAlign,
802                                                label.text,
803                                                taFont,
804                                                taFontColor
805                                                //cachedLabelW
806                                        );
807                                        // if bidi support was required, the textDir is "auto" and truncation
808                                        // took place, we need to update the dir of the element for cases as:
809                                        // Fool label: 111111W (W for bidi character)
810                                        // truncated label: 11...
811                                        // in this case for auto textDir the dir will be "ltr" which is wrong.
812                                        if(label.truncated){
813                                                this.chart.formatTruncatedLabel(elem, tick.label, labelType);
814                                        }
815                                        label.truncated && this.labelTooltip(elem, this.chart, tick.label, label.text, taFont, labelType);
816                                        if(labelType == "html"){
817                                                this.htmlElements.push(elem);
818                                        }else if(rotation){
819                                                elem.setTransform([
820                                                        {dx: labelOffset.x, dy: labelOffset.y},
821                                                        g.matrix.rotategAt(
822                                                                rotation,
823                                                                x + (taMajorTick.length > 0 ? dx : 0) + anchorOffset.x,
824                                                                y + (taMajorTick.length > 0 ? dy : 0) + anchorOffset.y
825                                                        )
826                                                ]);
827                                        }
828                                }
829                        }, this);
830
831                        dx = tickVector.x * taMinorTick.length;
832                        dy = tickVector.y * taMinorTick.length;
833                        canLabel = this.opt.minorLabels && c.minMinorStep <= c.minor.tick * c.bounds.scale;
834                        arr.forEach(t.minor, function(tick){
835                                var offset = f(tick.value), elem,
836                                        x = (isRtl ? stop.x : start.x)  + axisVector.x * offset,
837                                        y = start.y + axisVector.y * offset; // chart mirroring
838                                this.createLine(s, {
839                                        x1: x, y1: y,
840                                        x2: x + dx,
841                                        y2: y + dy
842                                }).setStroke(taMinorTick);
843                                if(canLabel && tick.label){
844                                        var label = o.maxLabelCharCount ? this.getTextWithLimitCharCount(tick.label, taFont, o.maxLabelCharCount) : {
845                                                text: tick.label,
846                                                truncated: false
847                                        };
848                                        label = o.maxLabelSize ? this.getTextWithLimitLength(label.text, taFont, o.maxLabelSize, label.truncated) : label;
849                                        elem = this.createText(labelType,
850                                                s,
851                                                x + (taMinorTick.length > 0 ? dx : 0) + anchorOffset.x + (rotation ? 0 : labelOffset.x),
852                                                y + (taMinorTick.length  > 0 ? dy : 0) + anchorOffset.y + (rotation ? 0 : labelOffset.y),
853                                                labelAlign,
854                                                label.text,
855                                                taFont,
856                                                taFontColor
857                                                //cachedLabelW
858                                        );
859                                        // if bidi support was required, the textDir is "auto" and truncation
860                                        // took place, we need to update the dir of the element for cases as:
861                                        // Fool label: 111111W (W for bidi character)
862                                        // truncated label: 11...
863                                        // in this case for auto textDir the dir will be "ltr" which is wrong.
864                                        if(label.truncated){
865                                                this.chart.formatTruncatedLabel(elem, tick.label, labelType);
866                                        }
867                                        label.truncated && this.labelTooltip(elem, this.chart, tick.label, label.text, taFont, labelType);
868                                        if(labelType == "html"){
869                                                this.htmlElements.push(elem);
870                                        }else if(rotation){
871                                                elem.setTransform([
872                                                        {dx: labelOffset.x, dy: labelOffset.y},
873                                                        g.matrix.rotategAt(
874                                                                rotation,
875                                                                x + (taMinorTick.length > 0 ? dx : 0) + anchorOffset.x,
876                                                                y + (taMinorTick.length > 0 ? dy : 0) + anchorOffset.y
877                                                        )
878                                                ]);
879                                        }
880                                }
881                        }, this);
882
883                        dx = tickVector.x * taMicroTick.length;
884                        dy = tickVector.y * taMicroTick.length;
885                        arr.forEach(t.micro, function(tick){
886                                var offset = f(tick.value),
887                                        x = start.x + axisVector.x * offset,
888                                        y = start.y + axisVector.y * offset;
889                                        this.createLine(s, {
890                                                x1: x, y1: y,
891                                                x2: x + dx,
892                                                y2: y + dy
893                                        }).setStroke(taMicroTick);
894                        }, this);
895
896                        this.dirty = false;
897                        return this;    //      dojox/charting/axis2d/Default
898                },
899                labelTooltip: function(elem, chart, label, truncatedLabel, font, elemType){
900                        var modules = ["dijit/Tooltip"];
901                        var aroundRect = {type: "rect"}, position = ["above", "below"],
902                                fontWidth = g._base._getTextBox(truncatedLabel, {font: font}).w || 0,
903                                fontHeight = font ? g.normalizedLength(g.splitFontString(font).size) : 0;
904                        if(elemType == "html"){
905                                lang.mixin(aroundRect, domGeom.position(elem.firstChild, true));
906                                aroundRect.width = Math.ceil(fontWidth);
907                                aroundRect.height = Math.ceil(fontHeight);
908                                this._events.push({
909                                        shape:  dojo,
910                                        handle: connect.connect(elem.firstChild, "onmouseover", this, function(e){
911                                                require(modules, function(Tooltip){
912                                                        Tooltip.show(label, aroundRect, position);
913                                                });
914                                        })
915                                });
916                                this._events.push({
917                                        shape:  dojo,
918                                        handle: connect.connect(elem.firstChild, "onmouseout", this, function(e){
919                                                require(modules, function(Tooltip){
920                                                        Tooltip.hide(aroundRect);
921                                                });
922                                        })
923                                });
924                        }else{
925                                var shp = elem.getShape(),
926                                        lt = chart.getCoords();
927                                aroundRect = lang.mixin(aroundRect, {
928                                        x: shp.x - fontWidth / 2,
929                                        y: shp.y
930                                });
931                                aroundRect.x += lt.x;
932                                aroundRect.y += lt.y;
933                                aroundRect.x = Math.round(aroundRect.x);
934                                aroundRect.y = Math.round(aroundRect.y);
935                                aroundRect.width = Math.ceil(fontWidth);
936                                aroundRect.height = Math.ceil(fontHeight);
937                                this._events.push({
938                                        shape:  elem,
939                                        handle: elem.connect("onmouseenter", this, function(e){
940                                                require(modules, function(Tooltip){
941                                                        Tooltip.show(label, aroundRect, position);
942                                                });
943                                        })
944                                });
945                                this._events.push({
946                                        shape:  elem,
947                                        handle: elem.connect("onmouseleave", this, function(e){
948                                                require(modules, function(Tooltip){
949                                                        Tooltip.hide(aroundRect);
950                                                });
951                                        })
952                                });
953                        }
954                },
955                _isRtl: function(){
956                        return false;
957                }
958        });
959        return has("dojo-bidi")? declare("dojox.charting.axis2d.Default", [Default, BidiDefault]) : Default;
960});
Note: See TracBrowser for help on using the repository browser.