source: Dev/branches/rest-dojo-ui/client/dojox/charting/Chart.js @ 274

Last change on this file since 274 was 256, checked in by hendrikvanantwerpen, 13 years ago

Reworked project structure based on REST interaction and Dojo library. As
soon as this is stable, the old jQueryUI branch can be removed (it's
kept for reference).

File size: 37.2 KB
Line 
1define(["dojo/_base/lang", "dojo/_base/array","dojo/_base/declare", "dojo/_base/html",
2        "dojo/dom", "dojo/dom-geometry", "dojo/dom-construct","dojo/_base/Color", "dojo/_base/sniff",
3        "./Element", "./Theme", "./Series", "./axis2d/common",
4        "dojox/gfx", "dojox/lang/functional", "dojox/lang/functional/fold", "dojox/lang/functional/reversed"],
5        function(lang, arr, declare, html,
6                         dom, domGeom, domConstruct, Color, has,
7                         Element, Theme, Series, common,
8                         g, func, funcFold, funcReversed){
9        /*=====
10        dojox.charting.__ChartCtorArgs = function(margins, stroke, fill, delayInMs){
11                //      summary:
12                //              The keyword arguments that can be passed in a Chart constructor.
13                //
14                //      margins: Object?
15                //              Optional margins for the chart, in the form of { l, t, r, b}.
16                //      stroke: dojox.gfx.Stroke?
17                //              An optional outline/stroke for the chart.
18                //      fill: dojox.gfx.Fill?
19                //              An optional fill for the chart.
20                //      delayInMs: Number
21                //              Delay in ms for delayedRender(). Default: 200.
22                this.margins = margins;
23                this.stroke = stroke;
24                this.fill = fill;
25                this.delayInMs = delayInMs;
26        }
27         =====*/
28        var dc = dojox.charting,
29                clear = func.lambda("item.clear()"),
30                purge = func.lambda("item.purgeGroup()"),
31                destroy = func.lambda("item.destroy()"),
32                makeClean = func.lambda("item.dirty = false"),
33                makeDirty = func.lambda("item.dirty = true"),
34                getName = func.lambda("item.name");
35
36        declare("dojox.charting.Chart", null, {
37                //      summary:
38                //              The main chart object in dojox.charting.  This will create a two dimensional
39                //              chart based on dojox.gfx.
40                //
41                //      description:
42                //              dojox.charting.Chart is the primary object used for any kind of charts.  It
43                //              is simple to create--just pass it a node reference, which is used as the
44                //              container for the chart--and a set of optional keyword arguments and go.
45                //
46                //              Note that like most of dojox.gfx, most of dojox.charting.Chart's methods are
47                //              designed to return a reference to the chart itself, to allow for functional
48                //              chaining.  This makes defining everything on a Chart very easy to do.
49                //
50                //      example:
51                //              Create an area chart, with smoothing.
52                //      |       new dojox.charting.Chart(node))
53                //      |               .addPlot("default", { type: "Areas", tension: "X" })
54                //      |               .setTheme(dojox.charting.themes.Shrooms)
55                //      |               .addSeries("Series A", [1, 2, 0.5, 1.5, 1, 2.8, 0.4])
56                //      |               .addSeries("Series B", [2.6, 1.8, 2, 1, 1.4, 0.7, 2])
57                //      |               .addSeries("Series C", [6.3, 1.8, 3, 0.5, 4.4, 2.7, 2])
58                //      |               .render();
59                //
60                //      example:
61                //              The form of data in a data series can take a number of forms: a simple array,
62                //              an array of objects {x,y}, or something custom (as determined by the plot).
63                //              Here's an example of a Candlestick chart, which expects an object of
64                //              { open, high, low, close }.
65                //      |       new dojox.charting.Chart(node))
66                //      |               .addPlot("default", {type: "Candlesticks", gap: 1})
67                //      |               .addAxis("x", {fixLower: "major", fixUpper: "major", includeZero: true})
68                //      |               .addAxis("y", {vertical: true, fixLower: "major", fixUpper: "major", natural: true})
69                //      |               .addSeries("Series A", [
70                //      |                               { open: 20, close: 16, high: 22, low: 8 },
71                //      |                               { open: 16, close: 22, high: 26, low: 6, mid: 18 },
72                //      |                               { open: 22, close: 18, high: 22, low: 11, mid: 21 },
73                //      |                               { open: 18, close: 29, high: 32, low: 14, mid: 27 },
74                //      |                               { open: 29, close: 24, high: 29, low: 13, mid: 27 },
75                //      |                               { open: 24, close: 8, high: 24, low: 5 },
76                //      |                               { open: 8, close: 16, high: 22, low: 2 },
77                //      |                               { open: 16, close: 12, high: 19, low: 7 },
78                //      |                               { open: 12, close: 20, high: 22, low: 8 },
79                //      |                               { open: 20, close: 16, high: 22, low: 8 },
80                //      |                               { open: 16, close: 22, high: 26, low: 6, mid: 18 },
81                //      |                               { open: 22, close: 18, high: 22, low: 11, mid: 21 },
82                //      |                               { open: 18, close: 29, high: 32, low: 14, mid: 27 },
83                //      |                               { open: 29, close: 24, high: 29, low: 13, mid: 27 },
84                //      |                               { open: 24, close: 8, high: 24, low: 5 },
85                //      |                               { open: 8, close: 16, high: 22, low: 2 },
86                //      |                               { open: 16, close: 12, high: 19, low: 7 },
87                //      |                               { open: 12, close: 20, high: 22, low: 8 },
88                //      |                               { open: 20, close: 16, high: 22, low: 8 },
89                //      |                               { open: 16, close: 22, high: 26, low: 6 },
90                //      |                               { open: 22, close: 18, high: 22, low: 11 },
91                //      |                               { open: 18, close: 29, high: 32, low: 14 },
92                //      |                               { open: 29, close: 24, high: 29, low: 13 },
93                //      |                               { open: 24, close: 8, high: 24, low: 5 },
94                //      |                               { open: 8, close: 16, high: 22, low: 2 },
95                //      |                               { open: 16, close: 12, high: 19, low: 7 },
96                //      |                               { open: 12, close: 20, high: 22, low: 8 },
97                //      |                               { open: 20, close: 16, high: 22, low: 8 }
98                //      |                       ],
99                //      |                       { stroke: { color: "green" }, fill: "lightgreen" }
100                //      |               )
101                //      |               .render();
102               
103                //      theme: dojox.charting.Theme?
104                //              An optional theme to use for styling the chart.
105                //      axes: dojox.charting.Axis{}?
106                //              A map of axes for use in plotting a chart.
107                //      stack: dojox.charting.plot2d.Base[]
108                //              A stack of plotters.
109                //      plots: dojox.charting.plot2d.Base{}
110                //              A map of plotter indices
111                //      series: dojox.charting.Series[]
112                //              The stack of data runs used to create plots.
113                //      runs: dojox.charting.Series{}
114                //              A map of series indices
115                //      margins: Object?
116                //              The margins around the chart. Default is { l:10, t:10, r:10, b:10 }.
117                //      stroke: dojox.gfx.Stroke?
118                //              The outline of the chart (stroke in vector graphics terms).
119                //      fill: dojox.gfx.Fill?
120                //              The color for the chart.
121                //      node: DOMNode
122                //              The container node passed to the constructor.
123                //      surface: dojox.gfx.Surface
124                //              The main graphics surface upon which a chart is drawn.
125                //      dirty: Boolean
126                //              A boolean flag indicating whether or not the chart needs to be updated/re-rendered.
127                //      coords: Object
128                //              The coordinates on a page of the containing node, as returned from dojo.coords.
129
130                constructor: function(/* DOMNode */node, /* dojox.charting.__ChartCtorArgs? */kwArgs){
131                        //      summary:
132                        //              The constructor for a new Chart.  Initializes all parameters used for a chart.
133                        //      returns: dojox.charting.Chart
134                        //              The newly created chart.
135
136                        // initialize parameters
137                        if(!kwArgs){ kwArgs = {}; }
138                        this.margins   = kwArgs.margins ? kwArgs.margins : {l: 10, t: 10, r: 10, b: 10};
139                        this.stroke    = kwArgs.stroke;
140                        this.fill      = kwArgs.fill;
141                        this.delayInMs = kwArgs.delayInMs || 200;
142                        this.title     = kwArgs.title;
143                        this.titleGap  = kwArgs.titleGap;
144                        this.titlePos  = kwArgs.titlePos;
145                        this.titleFont = kwArgs.titleFont;
146                        this.titleFontColor = kwArgs.titleFontColor;
147                        this.chartTitle = null;
148
149                        // default initialization
150                        this.theme = null;
151                        this.axes = {};         // map of axes
152                        this.stack = [];        // stack of plotters
153                        this.plots = {};        // map of plotter indices
154                        this.series = [];       // stack of data runs
155                        this.runs = {};         // map of data run indices
156                        this.dirty = true;
157                        this.coords = null;
158
159                        // create a surface
160                        this.node = dom.byId(node);
161                        var box = domGeom.getMarginBox(node);
162                        this.surface = g.createSurface(this.node, box.w || 400, box.h || 300);
163                },
164                destroy: function(){
165                        //      summary:
166                        //              Cleanup when a chart is to be destroyed.
167                        //      returns: void
168                        arr.forEach(this.series, destroy);
169                        arr.forEach(this.stack,  destroy);
170                        func.forIn(this.axes, destroy);
171                        if(this.chartTitle && this.chartTitle.tagName){
172                                // destroy title if it is a DOM node
173                                domConstruct.destroy(this.chartTitle);
174                        }
175                        this.surface.destroy();
176                },
177                getCoords: function(){
178                        //      summary:
179                        //              Get the coordinates and dimensions of the containing DOMNode, as
180                        //              returned by dojo.coords.
181                        //      returns: Object
182                        //              The resulting coordinates of the chart.  See dojo.coords for details.
183                        if(!this.coords){
184                                this.coords = html.coords(this.node, true);
185                        }
186                        return this.coords;     //      Object
187                },
188                setTheme: function(theme){
189                        //      summary:
190                        //              Set a theme of the chart.
191                        //      theme: dojox.charting.Theme
192                        //              The theme to be used for visual rendering.
193                        //      returns: dojox.charting.Chart
194                        //              A reference to the current chart for functional chaining.
195                        this.theme = theme.clone();
196                        this.dirty = true;
197                        return this;    //      dojox.charting.Chart
198                },
199                addAxis: function(name, kwArgs){
200                        //      summary:
201                        //              Add an axis to the chart, for rendering.
202                        //      name: String
203                        //              The name of the axis.
204                        //      kwArgs: dojox.charting.axis2d.__AxisCtorArgs?
205                        //              An optional keyword arguments object for use in defining details of an axis.
206                        //      returns: dojox.charting.Chart
207                        //              A reference to the current chart for functional chaining.
208                        var axis, axisType = kwArgs && kwArgs.type || "Default";
209                        if(typeof axisType == "string"){
210                                if(!dc.axis2d || !dc.axis2d[axisType]){
211                                        throw Error("Can't find axis: " + axisType + " - Check " + "require() dependencies.");
212                                }
213                                axis = new dc.axis2d[axisType](this, kwArgs);
214                        }else{
215                                axis = new axisType(this, kwArgs);
216                        }
217                        axis.name = name;
218                        axis.dirty = true;
219                        if(name in this.axes){
220                                this.axes[name].destroy();
221                        }
222                        this.axes[name] = axis;
223                        this.dirty = true;
224                        return this;    //      dojox.charting.Chart
225                },
226                getAxis: function(name){
227                        //      summary:
228                        //              Get the given axis, by name.
229                        //      name: String
230                        //              The name the axis was defined by.
231                        //      returns: dojox.charting.axis2d.Default
232                        //              The axis as stored in the chart's axis map.
233                        return this.axes[name]; //      dojox.charting.axis2d.Default
234                },
235                removeAxis: function(name){
236                        //      summary:
237                        //              Remove the axis that was defined using name.
238                        //      name: String
239                        //              The axis name, as defined in addAxis.
240                        //      returns: dojox.charting.Chart
241                        //              A reference to the current chart for functional chaining.
242                        if(name in this.axes){
243                                // destroy the axis
244                                this.axes[name].destroy();
245                                delete this.axes[name];
246                                // mark the chart as dirty
247                                this.dirty = true;
248                        }
249                        return this;    //      dojox.charting.Chart
250                },
251                addPlot: function(name, kwArgs){
252                        //      summary:
253                        //              Add a new plot to the chart, defined by name and using the optional keyword arguments object.
254                        //              Note that dojox.charting assumes the main plot to be called "default"; if you do not have
255                        //              a plot called "default" and attempt to add data series to the chart without specifying the
256                        //              plot to be rendered on, you WILL get errors.
257                        //      name: String
258                        //              The name of the plot to be added to the chart.  If you only plan on using one plot, call it "default".
259                        //      kwArgs: dojox.charting.plot2d.__PlotCtorArgs
260                        //              An object with optional parameters for the plot in question.
261                        //      returns: dojox.charting.Chart
262                        //              A reference to the current chart for functional chaining.
263                        var plot, plotType = kwArgs && kwArgs.type || "Default";
264                        if(typeof plotType == "string"){
265                                if(!dc.plot2d || !dc.plot2d[plotType]){
266                                        throw Error("Can't find plot: " + plotType + " - didn't you forget to dojo" + ".require() it?");
267                                }
268                                plot = new dc.plot2d[plotType](this, kwArgs);
269                        }else{
270                                plot = new plotType(this, kwArgs);
271                        }
272                        plot.name = name;
273                        plot.dirty = true;
274                        if(name in this.plots){
275                                this.stack[this.plots[name]].destroy();
276                                this.stack[this.plots[name]] = plot;
277                        }else{
278                                this.plots[name] = this.stack.length;
279                                this.stack.push(plot);
280                        }
281                        this.dirty = true;
282                        return this;    //      dojox.charting.Chart
283                },
284                getPlot: function(name){
285                        //      summary:
286                        //              Get the given plot, by name.
287                        //      name: String
288                        //              The name the plot was defined by.
289                        //      returns: dojox.charting.plot2d.Base
290                        //              The plot.
291                        return this.stack[this.plots[name]];
292                },
293                removePlot: function(name){
294                        //      summary:
295                        //              Remove the plot defined using name from the chart's plot stack.
296                        //      name: String
297                        //              The name of the plot as defined using addPlot.
298                        //      returns: dojox.charting.Chart
299                        //              A reference to the current chart for functional chaining.
300                        if(name in this.plots){
301                                // get the index and remove the name
302                                var index = this.plots[name];
303                                delete this.plots[name];
304                                // destroy the plot
305                                this.stack[index].destroy();
306                                // remove the plot from the stack
307                                this.stack.splice(index, 1);
308                                // update indices to reflect the shift
309                                func.forIn(this.plots, function(idx, name, plots){
310                                        if(idx > index){
311                                                plots[name] = idx - 1;
312                                        }
313                                });
314                                // remove all related series
315                                var ns = arr.filter(this.series, function(run){ return run.plot != name; });
316                                if(ns.length < this.series.length){
317                                        // kill all removed series
318                                        arr.forEach(this.series, function(run){
319                                                if(run.plot == name){
320                                                        run.destroy();
321                                                }
322                                        });
323                                        // rebuild all necessary data structures
324                                        this.runs = {};
325                                        arr.forEach(ns, function(run, index){
326                                                this.runs[run.plot] = index;
327                                        }, this);
328                                        this.series = ns;
329                                }
330                                // mark the chart as dirty
331                                this.dirty = true;
332                        }
333                        return this;    //      dojox.charting.Chart
334                },
335                getPlotOrder: function(){
336                        //      summary:
337                        //              Returns an array of plot names in the current order
338                        //              (the top-most plot is the first).
339                        //      returns: Array
340                        return func.map(this.stack, getName); // Array
341                },
342                setPlotOrder: function(newOrder){
343                        //      summary:
344                        //              Sets new order of plots. newOrder cannot add or remove
345                        //              plots. Wrong names, or dups are ignored.
346                        //      newOrder: Array:
347                        //              Array of plot names compatible with getPlotOrder().
348                        //      returns: dojox.charting.Chart
349                        //              A reference to the current chart for functional chaining.
350                        var names = {},
351                                order = func.filter(newOrder, function(name){
352                                        if(!(name in this.plots) || (name in names)){
353                                                return false;
354                                        }
355                                        names[name] = 1;
356                                        return true;
357                                }, this);
358                        if(order.length < this.stack.length){
359                                func.forEach(this.stack, function(plot){
360                                        var name = plot.name;
361                                        if(!(name in names)){
362                                                order.push(name);
363                                        }
364                                });
365                        }
366                        var newStack = func.map(order, function(name){
367                                        return this.stack[this.plots[name]];
368                                }, this);
369                        func.forEach(newStack, function(plot, i){
370                                this.plots[plot.name] = i;
371                        }, this);
372                        this.stack = newStack;
373                        this.dirty = true;
374                        return this;    //      dojox.charting.Chart
375                },
376                movePlotToFront: function(name){
377                        //      summary:
378                        //              Moves a given plot to front.
379                        //      name: String:
380                        //              Plot's name to move.
381                        //      returns: dojox.charting.Chart
382                        //              A reference to the current chart for functional chaining.
383                        if(name in this.plots){
384                                var index = this.plots[name];
385                                if(index){
386                                        var newOrder = this.getPlotOrder();
387                                        newOrder.splice(index, 1);
388                                        newOrder.unshift(name);
389                                        return this.setPlotOrder(newOrder);     //      dojox.charting.Chart
390                                }
391                        }
392                        return this;    //      dojox.charting.Chart
393                },
394                movePlotToBack: function(name){
395                        //      summary:
396                        //              Moves a given plot to back.
397                        //      name: String:
398                        //              Plot's name to move.
399                        //      returns: dojox.charting.Chart
400                        //              A reference to the current chart for functional chaining.
401                        if(name in this.plots){
402                                var index = this.plots[name];
403                                if(index < this.stack.length - 1){
404                                        var newOrder = this.getPlotOrder();
405                                        newOrder.splice(index, 1);
406                                        newOrder.push(name);
407                                        return this.setPlotOrder(newOrder);     //      dojox.charting.Chart
408                                }
409                        }
410                        return this;    //      dojox.charting.Chart
411                },
412                addSeries: function(name, data, kwArgs){
413                        //      summary:
414                        //              Add a data series to the chart for rendering.
415                        //      name: String:
416                        //              The name of the data series to be plotted.
417                        //      data: Array|Object:
418                        //              The array of data points (either numbers or objects) that
419                        //              represents the data to be drawn. Or it can be an object. In
420                        //              the latter case, it should have a property "data" (an array),
421                        //              destroy(), and setSeriesObject().
422                        //      kwArgs: dojox.charting.__SeriesCtorArgs?:
423                        //              An optional keyword arguments object that will be mixed into
424                        //              the resultant series object.
425                        //      returns: dojox.charting.Chart:
426                        //              A reference to the current chart for functional chaining.
427                        var run = new Series(this, data, kwArgs);
428                        run.name = name;
429                        if(name in this.runs){
430                                this.series[this.runs[name]].destroy();
431                                this.series[this.runs[name]] = run;
432                        }else{
433                                this.runs[name] = this.series.length;
434                                this.series.push(run);
435                        }
436                        this.dirty = true;
437                        // fix min/max
438                        if(!("ymin" in run) && "min" in run){ run.ymin = run.min; }
439                        if(!("ymax" in run) && "max" in run){ run.ymax = run.max; }
440                        return this;    //      dojox.charting.Chart
441                },
442                getSeries: function(name){
443                        //      summary:
444                        //              Get the given series, by name.
445                        //      name: String
446                        //              The name the series was defined by.
447                        //      returns: dojox.charting.Series
448                        //              The series.
449                        return this.series[this.runs[name]];
450                },
451                removeSeries: function(name){
452                        //      summary:
453                        //              Remove the series defined by name from the chart.
454                        //      name: String
455                        //              The name of the series as defined by addSeries.
456                        //      returns: dojox.charting.Chart
457                        //              A reference to the current chart for functional chaining.
458                        if(name in this.runs){
459                                // get the index and remove the name
460                                var index = this.runs[name];
461                                delete this.runs[name];
462                                // destroy the run
463                                this.series[index].destroy();
464                                // remove the run from the stack of series
465                                this.series.splice(index, 1);
466                                // update indices to reflect the shift
467                                func.forIn(this.runs, function(idx, name, runs){
468                                        if(idx > index){
469                                                runs[name] = idx - 1;
470                                        }
471                                });
472                                this.dirty = true;
473                        }
474                        return this;    //      dojox.charting.Chart
475                },
476                updateSeries: function(name, data){
477                        //      summary:
478                        //              Update the given series with a new set of data points.
479                        //      name: String
480                        //              The name of the series as defined in addSeries.
481                        //      data: Array|Object:
482                        //              The array of data points (either numbers or objects) that
483                        //              represents the data to be drawn. Or it can be an object. In
484                        //              the latter case, it should have a property "data" (an array),
485                        //              destroy(), and setSeriesObject().
486                        //      returns: dojox.charting.Chart
487                        //              A reference to the current chart for functional chaining.
488                        if(name in this.runs){
489                                var run = this.series[this.runs[name]];
490                                run.update(data);
491                                this._invalidateDependentPlots(run.plot, false);
492                                this._invalidateDependentPlots(run.plot, true);
493                        }
494                        return this;    //      dojox.charting.Chart
495                },
496                getSeriesOrder: function(plotName){
497                        //      summary:
498                        //              Returns an array of series names in the current order
499                        //              (the top-most series is the first) within a plot.
500                        //      plotName: String:
501                        //              Plot's name.
502                        //      returns: Array
503                        return func.map(func.filter(this.series, function(run){
504                                        return run.plot == plotName;
505                                }), getName);
506                },
507                setSeriesOrder: function(newOrder){
508                        //      summary:
509                        //              Sets new order of series within a plot. newOrder cannot add
510                        //              or remove series. Wrong names, or dups are ignored.
511                        //      newOrder: Array:
512                        //              Array of series names compatible with getPlotOrder(). All
513                        //              series should belong to the same plot.
514                        //      returns: dojox.charting.Chart
515                        //              A reference to the current chart for functional chaining.
516                        var plotName, names = {},
517                                order = func.filter(newOrder, function(name){
518                                        if(!(name in this.runs) || (name in names)){
519                                                return false;
520                                        }
521                                        var run = this.series[this.runs[name]];
522                                        if(plotName){
523                                                if(run.plot != plotName){
524                                                        return false;
525                                                }
526                                        }else{
527                                                plotName = run.plot;
528                                        }
529                                        names[name] = 1;
530                                        return true;
531                                }, this);
532                        func.forEach(this.series, function(run){
533                                var name = run.name;
534                                if(!(name in names) && run.plot == plotName){
535                                        order.push(name);
536                                }
537                        });
538                        var newSeries = func.map(order, function(name){
539                                        return this.series[this.runs[name]];
540                                }, this);
541                        this.series = newSeries.concat(func.filter(this.series, function(run){
542                                return run.plot != plotName;
543                        }));
544                        func.forEach(this.series, function(run, i){
545                                this.runs[run.name] = i;
546                        }, this);
547                        this.dirty = true;
548                        return this;    //      dojox.charting.Chart
549                },
550                moveSeriesToFront: function(name){
551                        //      summary:
552                        //              Moves a given series to front of a plot.
553                        //      name: String:
554                        //              Series' name to move.
555                        //      returns: dojox.charting.Chart
556                        //              A reference to the current chart for functional chaining.
557                        if(name in this.runs){
558                                var index = this.runs[name],
559                                        newOrder = this.getSeriesOrder(this.series[index].plot);
560                                if(name != newOrder[0]){
561                                        newOrder.splice(index, 1);
562                                        newOrder.unshift(name);
563                                        return this.setSeriesOrder(newOrder);   //      dojox.charting.Chart
564                                }
565                        }
566                        return this;    //      dojox.charting.Chart
567                },
568                moveSeriesToBack: function(name){
569                        //      summary:
570                        //              Moves a given series to back of a plot.
571                        //      name: String:
572                        //              Series' name to move.
573                        //      returns: dojox.charting.Chart
574                        //              A reference to the current chart for functional chaining.
575                        if(name in this.runs){
576                                var index = this.runs[name],
577                                        newOrder = this.getSeriesOrder(this.series[index].plot);
578                                if(name != newOrder[newOrder.length - 1]){
579                                        newOrder.splice(index, 1);
580                                        newOrder.push(name);
581                                        return this.setSeriesOrder(newOrder);   //      dojox.charting.Chart
582                                }
583                        }
584                        return this;    //      dojox.charting.Chart
585                },
586                resize: function(width, height){
587                        //      summary:
588                        //              Resize the chart to the dimensions of width and height.
589                        //      description:
590                        //              Resize the chart and its surface to the width and height dimensions.
591                        //              If no width/height or box is provided, resize the surface to the marginBox of the chart.
592                        //      width: Number
593                        //              The new width of the chart.
594                        //      height: Number
595                        //              The new height of the chart.
596                        //      returns: dojox.charting.Chart
597                        //              A reference to the current chart for functional chaining.
598                        var box;
599                        switch(arguments.length){
600                                // case 0, do not resize the div, just the surface
601                                case 1:
602                                        // argument, override node box
603                                        box = lang.mixin({}, width);
604                                        domGeom.setMarginBox(this.node, box);
605                                        break;
606                                case 2:
607                                        box = {w: width, h: height};
608                                        // argument, override node box
609                                        domGeom.setMarginBox(this.node, box);
610                                        break;
611                        }
612                        // in all cases take back the computed box
613                        box = domGeom.getMarginBox(this.node);
614                        var d = this.surface.getDimensions();
615                        if(d.width != box.w || d.height != box.h){
616                                // and set it on the surface
617                                this.surface.setDimensions(box.w, box.h);
618                                this.dirty = true;
619                                this.coords = null;
620                                return this.render();   //      dojox.charting.Chart                           
621                        }else{
622                                return this;
623                        }
624                },
625                getGeometry: function(){
626                        //      summary:
627                        //              Returns a map of information about all axes in a chart and what they represent
628                        //              in terms of scaling (see dojox.charting.axis2d.Default.getScaler).
629                        //      returns: Object
630                        //              An map of geometry objects, a one-to-one mapping of axes.
631                        var ret = {};
632                        func.forIn(this.axes, function(axis){
633                                if(axis.initialized()){
634                                        ret[axis.name] = {
635                                                name:           axis.name,
636                                                vertical:       axis.vertical,
637                                                scaler:         axis.scaler,
638                                                ticks:          axis.ticks
639                                        };
640                                }
641                        });
642                        return ret;     //      Object
643                },
644                setAxisWindow: function(name, scale, offset, zoom){
645                        //      summary:
646                        //              Zooms an axis and all dependent plots. Can be used to zoom in 1D.
647                        //      name: String
648                        //              The name of the axis as defined by addAxis.
649                        //      scale: Number
650                        //              The scale on the target axis.
651                        //      offset: Number
652                        //              Any offest, as measured by axis tick
653                        //      zoom: Boolean|Object?
654                        //              The chart zooming animation trigger.  This is null by default,
655                        //              e.g. {duration: 1200}, or just set true.
656                        //      returns: dojox.charting.Chart
657                        //              A reference to the current chart for functional chaining.
658                        var axis = this.axes[name];
659                        if(axis){
660                                axis.setWindow(scale, offset);
661                                arr.forEach(this.stack,function(plot){
662                                        if(plot.hAxis == name || plot.vAxis == name){
663                                                plot.zoom = zoom;
664                                        }
665                                });
666                        }
667                        return this;    //      dojox.charting.Chart
668                },
669                setWindow: function(sx, sy, dx, dy, zoom){
670                        //      summary:
671                        //              Zooms in or out any plots in two dimensions.
672                        //      sx: Number
673                        //              The scale for the x axis.
674                        //      sy: Number
675                        //              The scale for the y axis.
676                        //      dx: Number
677                        //              The pixel offset on the x axis.
678                        //      dy: Number
679                        //              The pixel offset on the y axis.
680                        //      zoom: Boolean|Object?
681                        //              The chart zooming animation trigger.  This is null by default,
682                        //              e.g. {duration: 1200}, or just set true.
683                        //      returns: dojox.charting.Chart
684                        //              A reference to the current chart for functional chaining.
685                        if(!("plotArea" in this)){
686                                this.calculateGeometry();
687                        }
688                        func.forIn(this.axes, function(axis){
689                                var scale, offset, bounds = axis.getScaler().bounds,
690                                        s = bounds.span / (bounds.upper - bounds.lower);
691                                if(axis.vertical){
692                                        scale  = sy;
693                                        offset = dy / s / scale;
694                                }else{
695                                        scale  = sx;
696                                        offset = dx / s / scale;
697                                }
698                                axis.setWindow(scale, offset);
699                        });
700                        arr.forEach(this.stack, function(plot){ plot.zoom = zoom; });
701                        return this;    //      dojox.charting.Chart
702                },
703                zoomIn: function(name, range){
704                        //      summary:
705                        //              Zoom the chart to a specific range on one axis.  This calls render()
706                        //              directly as a convenience method.
707                        //      name: String
708                        //              The name of the axis as defined by addAxis.
709                        //      range: Array
710                        //              The end points of the zoom range, measured in axis ticks.
711                        var axis = this.axes[name];
712                        if(axis){
713                                var scale, offset, bounds = axis.getScaler().bounds;
714                                var lower = Math.min(range[0],range[1]);
715                                var upper = Math.max(range[0],range[1]);
716                                lower = range[0] < bounds.lower ? bounds.lower : lower;
717                                upper = range[1] > bounds.upper ? bounds.upper : upper;
718                                scale = (bounds.upper - bounds.lower) / (upper - lower);
719                                offset = lower - bounds.lower;
720                                this.setAxisWindow(name, scale, offset);
721                                this.render();
722                        }
723                },
724                calculateGeometry: function(){
725                        //      summary:
726                        //              Calculate the geometry of the chart based on the defined axes of
727                        //              a chart.
728                        //      returns: dojox.charting.Chart
729                        //              A reference to the current chart for functional chaining.
730                        if(this.dirty){
731                                return this.fullGeometry();
732                        }
733
734                        // calculate geometry
735                        var dirty = arr.filter(this.stack, function(plot){
736                                        return plot.dirty ||
737                                                (plot.hAxis && this.axes[plot.hAxis].dirty) ||
738                                                (plot.vAxis && this.axes[plot.vAxis].dirty);
739                                }, this);
740                        calculateAxes(dirty, this.plotArea);
741
742                        return this;    //      dojox.charting.Chart
743                },
744                fullGeometry: function(){
745                        //      summary:
746                        //              Calculate the full geometry of the chart.  This includes passing
747                        //              over all major elements of a chart (plots, axes, series, container)
748                        //              in order to ensure proper rendering.
749                        //      returns: dojox.charting.Chart
750                        //              A reference to the current chart for functional chaining.
751                        this._makeDirty();
752
753                        // clear old values
754                        arr.forEach(this.stack, clear);
755
756                        // rebuild new connections, and add defaults
757
758                        // set up a theme
759                        if(!this.theme){
760                                this.setTheme(new Theme(dojox.charting._def));
761                        }
762
763                        // assign series
764                        arr.forEach(this.series, function(run){
765                                if(!(run.plot in this.plots)){
766                                        if(!dc.plot2d || !dc.plot2d.Default){
767                                                throw Error("Can't find plot: Default - didn't you forget to dojo" + ".require() it?");
768                                        }
769                                        var plot = new dc.plot2d.Default(this, {});
770                                        plot.name = run.plot;
771                                        this.plots[run.plot] = this.stack.length;
772                                        this.stack.push(plot);
773                                }
774                                this.stack[this.plots[run.plot]].addSeries(run);
775                        }, this);
776                        // assign axes
777                        arr.forEach(this.stack, function(plot){
778                                if(plot.hAxis){
779                                        plot.setAxis(this.axes[plot.hAxis]);
780                                }
781                                if(plot.vAxis){
782                                        plot.setAxis(this.axes[plot.vAxis]);
783                                }
784                        }, this);
785
786                        // calculate geometry
787
788                        // 1st pass
789                        var dim = this.dim = this.surface.getDimensions();
790                        dim.width  = g.normalizedLength(dim.width);
791                        dim.height = g.normalizedLength(dim.height);
792                        func.forIn(this.axes, clear);
793                        calculateAxes(this.stack, dim);
794
795                        // assumption: we don't have stacked axes yet
796                        var offsets = this.offsets = { l: 0, r: 0, t: 0, b: 0 };
797                        func.forIn(this.axes, function(axis){
798                                func.forIn(axis.getOffsets(), function(o, i){ offsets[i] += o; });
799                        });
800                        // add title area
801                        if(this.title){
802                                this.titleGap = (this.titleGap==0) ? 0 : this.titleGap || this.theme.chart.titleGap || 20;
803                                this.titlePos = this.titlePos || this.theme.chart.titlePos || "top";
804                                this.titleFont = this.titleFont || this.theme.chart.titleFont;
805                                this.titleFontColor = this.titleFontColor || this.theme.chart.titleFontColor || "black";
806                                var tsize = g.normalizedLength(g.splitFontString(this.titleFont).size);
807                                offsets[this.titlePos=="top" ? "t":"b"] += (tsize + this.titleGap);
808                        }
809                        // add margins
810                        func.forIn(this.margins, function(o, i){ offsets[i] += o; });
811
812                        // 2nd pass with realistic dimensions
813                        this.plotArea = {
814                                width: dim.width - offsets.l - offsets.r,
815                                height: dim.height - offsets.t - offsets.b
816                        };
817                        func.forIn(this.axes, clear);
818                        calculateAxes(this.stack, this.plotArea);
819
820                        return this;    //      dojox.charting.Chart
821                },
822                render: function(){
823                        //      summary:
824                        //              Render the chart according to the current information defined.  This should
825                        //              be the last call made when defining/creating a chart, or if data within the
826                        //              chart has been changed.
827                        //      returns: dojox.charting.Chart
828                        //              A reference to the current chart for functional chaining.
829                        if(this.theme){
830                                this.theme.clear();
831                        }
832
833                        if(this.dirty){
834                                return this.fullRender();
835                        }
836
837                        this.calculateGeometry();
838
839                        // go over the stack backwards
840                        func.forEachRev(this.stack, function(plot){ plot.render(this.dim, this.offsets); }, this);
841
842                        // go over axes
843                        func.forIn(this.axes, function(axis){ axis.render(this.dim, this.offsets); }, this);
844
845                        this._makeClean();
846
847                        // BEGIN FOR HTML CANVAS
848                        if(this.surface.render){ this.surface.render(); };
849                        // END FOR HTML CANVAS
850
851                        return this;    //      dojox.charting.Chart
852                },
853                fullRender: function(){
854                        //      summary:
855                        //              Force a full rendering of the chart, including full resets on the chart itself.
856                        //              You should not call this method directly unless absolutely necessary.
857                        //      returns: dojox.charting.Chart
858                        //              A reference to the current chart for functional chaining.
859
860                        // calculate geometry
861                        this.fullGeometry();
862                        var offsets = this.offsets, dim = this.dim, rect;
863
864                        // get required colors
865                        //var requiredColors = func.foldl(this.stack, "z + plot.getRequiredColors()", 0);
866                        //this.theme.defineColors({num: requiredColors, cache: false});
867
868                        // clear old shapes
869                        arr.forEach(this.series, purge);
870                        func.forIn(this.axes, purge);
871                        arr.forEach(this.stack,  purge);
872                        if(this.chartTitle && this.chartTitle.tagName){
873                                // destroy title if it is a DOM node
874                            domConstruct.destroy(this.chartTitle);
875            }
876                        this.surface.clear();
877                        this.chartTitle = null;
878
879                        // generate shapes
880
881                        // draw a plot background
882                        var t = this.theme,
883                                fill   = t.plotarea && t.plotarea.fill,
884                                stroke = t.plotarea && t.plotarea.stroke,
885                                // size might be neg if offsets are bigger that chart size this happens quite often at
886                                // initialization time if the chart widget is used in a BorderContainer
887                                // this will fail on IE/VML
888                                w = Math.max(0, dim.width  - offsets.l - offsets.r),
889                                h = Math.max(0, dim.height - offsets.t - offsets.b),
890                                rect = {
891                                        x: offsets.l - 1, y: offsets.t - 1,
892                                        width:  w + 2,
893                                        height: h + 2
894                                };
895                        if(fill){
896                                fill = Element.prototype._shapeFill(Element.prototype._plotFill(fill, dim, offsets), rect);
897                                this.surface.createRect(rect).setFill(fill);
898                        }
899                        if(stroke){
900                                this.surface.createRect({
901                                        x: offsets.l, y: offsets.t,
902                                        width:  w + 1,
903                                        height: h + 1
904                                }).setStroke(stroke);
905                        }
906
907                        // go over the stack backwards
908                        func.foldr(this.stack, function(z, plot){ return plot.render(dim, offsets), 0; }, 0);
909
910                        // pseudo-clipping: matting
911                        fill   = this.fill   !== undefined ? this.fill   : (t.chart && t.chart.fill);
912                        stroke = this.stroke !== undefined ? this.stroke : (t.chart && t.chart.stroke);
913
914                        //      TRT: support for "inherit" as a named value in a theme.
915                        if(fill == "inherit"){
916                                //      find the background color of the nearest ancestor node, and use that explicitly.
917                                var node = this.node, fill = new Color(html.style(node, "backgroundColor"));
918                                while(fill.a==0 && node!=document.documentElement){
919                                        fill = new Color(html.style(node, "backgroundColor"));
920                                        node = node.parentNode;
921                                }
922                        }
923
924                        if(fill){
925                                fill = Element.prototype._plotFill(fill, dim, offsets);
926                                if(offsets.l){  // left
927                                        rect = {
928                                                width:  offsets.l,
929                                                height: dim.height + 1
930                                        };
931                                        this.surface.createRect(rect).setFill(Element.prototype._shapeFill(fill, rect));
932                                }
933                                if(offsets.r){  // right
934                                        rect = {
935                                                x: dim.width - offsets.r,
936                                                width:  offsets.r + 1,
937                                                height: dim.height + 2
938                                        };
939                                        this.surface.createRect(rect).setFill(Element.prototype._shapeFill(fill, rect));
940                                }
941                                if(offsets.t){  // top
942                                        rect = {
943                                                width:  dim.width + 1,
944                                                height: offsets.t
945                                        };
946                                        this.surface.createRect(rect).setFill(Element.prototype._shapeFill(fill, rect));
947                                }
948                                if(offsets.b){  // bottom
949                                        rect = {
950                                                y: dim.height - offsets.b,
951                                                width:  dim.width + 1,
952                                                height: offsets.b + 2
953                                        };
954                                        this.surface.createRect(rect).setFill(Element.prototype._shapeFill(fill, rect));
955                                }
956                        }
957                        if(stroke){
958                                this.surface.createRect({
959                                        width:  dim.width - 1,
960                                        height: dim.height - 1
961                                }).setStroke(stroke);
962                        }
963
964                        //create title: Whether to make chart title as a widget which extends dojox.charting.Element?
965                        if(this.title){
966                                var forceHtmlLabels = (g.renderer == "canvas"),
967                                        labelType = forceHtmlLabels || !has("ie") && !has("opera") ? "html" : "gfx",
968                                        tsize = g.normalizedLength(g.splitFontString(this.titleFont).size);
969                                this.chartTitle = common.createText[labelType](
970                                        this,
971                                        this.surface,
972                                        dim.width/2,
973                                        this.titlePos=="top" ? tsize + this.margins.t : dim.height - this.margins.b,
974                                        "middle",
975                                        this.title,
976                                        this.titleFont,
977                                        this.titleFontColor
978                                );
979                        }
980
981                        // go over axes
982                        func.forIn(this.axes, function(axis){ axis.render(dim, offsets); });
983
984                        this._makeClean();
985
986                        // BEGIN FOR HTML CANVAS
987                        if(this.surface.render){ this.surface.render(); };
988                        // END FOR HTML CANVAS
989
990                        return this;    //      dojox.charting.Chart
991                },
992                delayedRender: function(){
993                        //      summary:
994                        //              Delayed render, which is used to collect multiple updates
995                        //              within a delayInMs time window.
996                        //      returns: dojox.charting.Chart
997                        //              A reference to the current chart for functional chaining.
998
999                        if(!this._delayedRenderHandle){
1000                                this._delayedRenderHandle = setTimeout(
1001                                        lang.hitch(this, function(){
1002                                                clearTimeout(this._delayedRenderHandle);
1003                                                this._delayedRenderHandle = null;
1004                                                this.render();
1005                                        }),
1006                                        this.delayInMs
1007                                );
1008                        }
1009
1010                        return this;    //      dojox.charting.Chart
1011                },
1012                connectToPlot: function(name, object, method){
1013                        //      summary:
1014                        //              A convenience method to connect a function to a plot.
1015                        //      name: String
1016                        //              The name of the plot as defined by addPlot.
1017                        //      object: Object
1018                        //              The object to be connected.
1019                        //      method: Function
1020                        //              The function to be executed.
1021                        //      returns: Array
1022                        //              A handle to the connection, as defined by dojo.connect (see dojo.connect).
1023                        return name in this.plots ? this.stack[this.plots[name]].connect(object, method) : null;        //      Array
1024                },
1025                fireEvent: function(seriesName, eventName, index){
1026                        //      summary:
1027                        //              Fires a synthetic event for a series item.
1028                        //      seriesName: String:
1029                        //              Series name.
1030                        //      eventName: String:
1031                        //              Event name to simulate: onmouseover, onmouseout, onclick.
1032                        //      index: Number:
1033                        //              Valid data value index for the event.
1034                        //      returns: dojox.charting.Chart
1035                        //              A reference to the current chart for functional chaining.
1036                        if(seriesName in this.runs){
1037                                var plotName = this.series[this.runs[seriesName]].plot;
1038                                if(plotName in this.plots){
1039                                        var plot = this.stack[this.plots[plotName]];
1040                                        if(plot){
1041                                                plot.fireEvent(seriesName, eventName, index);
1042                                        }
1043                                }
1044                        }
1045                        return this;    //      dojox.charting.Chart
1046                },
1047                _makeClean: function(){
1048                        // reset dirty flags
1049                        arr.forEach(this.axes,   makeClean);
1050                        arr.forEach(this.stack,  makeClean);
1051                        arr.forEach(this.series, makeClean);
1052                        this.dirty = false;
1053                },
1054                _makeDirty: function(){
1055                        // reset dirty flags
1056                        arr.forEach(this.axes,   makeDirty);
1057                        arr.forEach(this.stack,  makeDirty);
1058                        arr.forEach(this.series, makeDirty);
1059                        this.dirty = true;
1060                },
1061                _invalidateDependentPlots: function(plotName, /* Boolean */ verticalAxis){
1062                        if(plotName in this.plots){
1063                                var plot = this.stack[this.plots[plotName]], axis,
1064                                        axisName = verticalAxis ? "vAxis" : "hAxis";
1065                                if(plot[axisName]){
1066                                        axis = this.axes[plot[axisName]];
1067                                        if(axis && axis.dependOnData()){
1068                                                axis.dirty = true;
1069                                                // find all plots and mark them dirty
1070                                                arr.forEach(this.stack, function(p){
1071                                                        if(p[axisName] && p[axisName] == plot[axisName]){
1072                                                                p.dirty = true;
1073                                                        }
1074                                                });
1075                                        }
1076                                }else{
1077                                        plot.dirty = true;
1078                                }
1079                        }
1080                }
1081        });
1082
1083        function hSection(stats){
1084                return {min: stats.hmin, max: stats.hmax};
1085        }
1086
1087        function vSection(stats){
1088                return {min: stats.vmin, max: stats.vmax};
1089        }
1090
1091        function hReplace(stats, h){
1092                stats.hmin = h.min;
1093                stats.hmax = h.max;
1094        }
1095
1096        function vReplace(stats, v){
1097                stats.vmin = v.min;
1098                stats.vmax = v.max;
1099        }
1100
1101        function combineStats(target, source){
1102                if(target && source){
1103                        target.min = Math.min(target.min, source.min);
1104                        target.max = Math.max(target.max, source.max);
1105                }
1106                return target || source;
1107        }
1108
1109        function calculateAxes(stack, plotArea){
1110                var plots = {}, axes = {};
1111                arr.forEach(stack, function(plot){
1112                        var stats = plots[plot.name] = plot.getSeriesStats();
1113                        if(plot.hAxis){
1114                                axes[plot.hAxis] = combineStats(axes[plot.hAxis], hSection(stats));
1115                        }
1116                        if(plot.vAxis){
1117                                axes[plot.vAxis] = combineStats(axes[plot.vAxis], vSection(stats));
1118                        }
1119                });
1120                arr.forEach(stack, function(plot){
1121                        var stats = plots[plot.name];
1122                        if(plot.hAxis){
1123                                hReplace(stats, axes[plot.hAxis]);
1124                        }
1125                        if(plot.vAxis){
1126                                vReplace(stats, axes[plot.vAxis]);
1127                        }
1128                        plot.initializeScalers(plotArea, stats);
1129                });
1130        }
1131       
1132        return dojox.charting.Chart;
1133});
Note: See TracBrowser for help on using the repository browser.