1 /*
  2     Copyright 2008,2009
  3         Matthias Ehmann,
  4         Michael Gerhaeuser,
  5         Carsten Miller,
  6         Bianca Valentin,
  7         Alfred Wassermann,
  8         Peter Wilfahrt
  9 
 10     This file is part of JSXGraph.
 11 
 12     JSXGraph is free software: you can redistribute it and/or modify
 13     it under the terms of the GNU Lesser General Public License as published by
 14     the Free Software Foundation, either version 3 of the License, or
 15     (at your option) any later version.
 16 
 17     JSXGraph is distributed in the hope that it will be useful,
 18     but WITHOUT ANY WARRANTY; without even the implied warranty of
 19     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 20     GNU Lesser General Public License for more details.
 21 
 22     You should have received a copy of the GNU Lesser General Public License
 23     along with JSXGraph.  If not, see <http://www.gnu.org/licenses/>.
 24 */
 25 
 26 /**
 27  * Chart plotting
 28  * input array: 
 29  * 
 30  **/
 31  
 32 JXG.Chart = function(board, parents, attributes) {
 33     this.constructor();
 34     if (parents.length==0) { return; }  // No input data in parentArr
 35     
 36     /**
 37      * Contains lpointers to the various displays.
 38      */
 39     this.elements = [];
 40     
 41     var id = attributes['id'] || '';
 42     var name = attributes['name'] || '';
 43     this.init(board, id, name);
 44     
 45     var x,y,i;
 46     if (parents.length>0 && (typeof parents[0]=='number')) { // parents looks like [a,b,c,..]
 47                                                                 // x has to be filled
 48         y = parents;
 49         x = [];
 50         for (i=0;i<y.length;i++) {
 51             x[i] = i+1;
 52         }
 53     } else {
 54         if (parents.length==1) { // parents looks like [[a,b,c,..]]
 55                                 // x has to be filled
 56             y = parents[0];
 57             x = [];
 58             var len;
 59             if (JXG.isFunction(y)) {
 60                 len = y().length;
 61             } else {
 62                 len = y.length;
 63             }
 64             for (i=0;i<len;i++) {
 65                 x[i] = i+1;
 66             }
 67         }
 68         if (parents.length==2) { // parents looks like [[x0,x1,x2,...],[y1,y2,y3,...]]
 69             x = parents[0];
 70             y = parents[1];
 71         }
 72     }
 73     if (!JXG.exists(attributes)) attributes = {};
 74     var style = attributes['chartStyle'] || 'line';
 75     style = style.replace(/ /g,'');
 76     style = style.split(',');
 77     var c;
 78     for (i=0;i<style.length;i++) {
 79         switch (style[i]) {
 80             case 'bar':
 81                 c = this.drawBar(board,[x,y],attributes);
 82                 break;
 83             case 'line':
 84                 c = this.drawLine(board, [x, y], attributes);
 85                 break;
 86             case 'fit':
 87                 c = this.drawFit(board, [x, y], attributes);
 88                 break;
 89             case 'spline':
 90                 c = this.drawSpline(board, [x, y], attributes);
 91                 break;
 92             case 'pie':
 93                 c = this.drawPie(board,[y],attributes);
 94                 break;
 95             case 'point':
 96                 c = this.drawPoints(board,[x,y],attributes);
 97                 break;
 98             case 'radar':
 99                 c = this.drawRadar(board,parents,attributes);
100                 break;
101         }
102         this.elements.push(c);
103     }
104     this.id = this.board.setId(this,'Chart');
105     
106     return this.elements;
107 };
108 JXG.Chart.prototype = new JXG.GeometryElement;
109 
110 JXG.Chart.prototype.drawLine = function(board, parents, attributes) {
111     var _x = parents[0],
112         _y = parents[1];
113 
114     /*
115     var Fy = function(x) {
116         var i, j = 0;
117 
118         for(i=0; i<_x.length; i++) {
119             if(_x[i] == x) {
120                 j = i;
121                 break;
122             }
123         }
124 
125         if(typeof _y[j] == 'function')
126             return _y[j]();
127         else
128             return _y[j];
129     }
130     */
131     
132     // not needed
133     attributes['fillColor'] = 'none';
134     attributes['highlightFillColor'] = 'none';
135 
136     var c = board.create('curve', [_x, _y], attributes);
137     this.rendNode = c.rendNode;  // This is needed in setProperty
138     return c;
139 };
140 
141 JXG.Chart.prototype.drawSpline = function(board, parents, attributes) {
142     var x = parents[0],
143         y = parents[1],
144         i;
145 
146     // not needed
147     attributes['fillColor'] = 'none';
148     attributes['highlightFillColor'] = 'none';
149 
150     var c = board.create('spline', [x, y], attributes);
151     this.rendNode = c.rendNode;  // This is needed in setProperty
152     return c;
153 };
154 
155 JXG.Chart.prototype.drawFit = function(board, parents, attributes) {
156     var x = parents[0],
157         y = parents[1],
158         deg = (((typeof attributes.degree == 'undefined') || (parseInt(attributes.degree) == NaN)|| (parseInt(attributes.degree) < 1)) ? 1 : parseInt(attributes.degree));
159 
160     // not needed
161     attributes['fillColor'] = 'none';
162     attributes['highlightFillColor'] = 'none';
163 
164     var regression = JXG.Math.Numerics.regressionPolynomial(deg, x, y);
165     var c = board.create('functiongraph', [regression], attributes);
166     this.rendNode = c.rendNode;  // This is needed in setProperty
167     return c;
168 };
169 
170 JXG.Chart.prototype.drawBar = function(board, parents, attributes) {
171     var i, pols = [], x = parents[0], y = parents[1], w, xp0,xp1,xp2, yp, ypL, colorArray, p = [], fill, strwidth, fs;
172     if (!JXG.exists(attributes['fillOpacity'])) {
173         attributes['fillOpacity'] = 0.6;
174     }
175     
176     // Determine the width of the bars
177     if (attributes && attributes['width']) {  // width given
178         w = attributes['width'];
179     } else {
180         if (x.length<=1) {
181             w = 1;
182         } else {
183             // Find minimum distance between to bars.
184             w = x[1]-x[0];
185             for (i=1;i<x.length-1;i++) {  
186                 w = (x[i+1]-x[i]<w)?(x[i+1]-x[i]):w;
187             }
188         }
189         w *=0.8;
190     }
191 
192     fill = attributes['fillColor']
193     fs = parseFloat(board.options.text.fontSize);                 // TODO: handle fontSize attribute
194     for (i=0;i<x.length;i++) {        
195         if (JXG.isFunction(x[i])) {  // Not yet
196             xp0 = function() { return x[i]()-w*0.5; };
197             xp1 = function() { return x[i](); };
198             xp2 = function() { return x[i]()+w*0.5; };
199         } else {
200             xp0 = x[i]-w*0.5;
201             xp1 = x[i];
202             xp2 = x[i]+w*0.5;
203         }
204         if (JXG.isFunction(y[i])) {  // Not yet
205             ypL = yp; //function() { return y[i]()*1.1; };
206         } else {
207             ypL = y[i]+0.2;
208         }
209         yp = y[i];
210        
211         if (attributes['dir']=='horizontal') {  // horizontal bars
212             p[0] = board.create('point',[0,xp0], {name:'',fixed:true,visible:false});
213             p[1] = board.create('point',[yp,xp0], {name:'',fixed:true,visible:false});
214             p[2] = board.create('point',[yp,xp2], {name:'',fixed:true,visible:false});
215             p[3] = board.create('point',[0,xp2], {name:'',fixed:true,visible:false});
216             if ( JXG.exists(attributes['labels']) && JXG.exists(attributes['labels'][i]) ) {
217                 strwidth = attributes['labels'][i].toString().length;
218                 strwidth = 2.0*strwidth*fs/board.stretchX;
219                 if (yp>=0) {
220                     yp += fs*0.5/board.stretchX;   // Static offset for label
221                 } else {
222                     yp -= fs*strwidth/board.stretchX;   // Static offset for label
223                 }
224                 xp1 -= fs*0.2/board.stretchY;
225                 board.create('text',[yp,xp1,attributes['labels'][i]],attributes);
226             }
227         } else { // vertical bars
228             p[0] = board.create('point',[xp0,0], {name:'',fixed:true,visible:false});
229             p[1] = board.create('point',[xp0,yp], {name:'',fixed:true,visible:false});
230             p[2] = board.create('point',[xp2,yp], {name:'',fixed:true,visible:false});
231             p[3] = board.create('point',[xp2,0], {name:'',fixed:true,visible:false});
232             if ( JXG.exists(attributes['labels']) && JXG.exists(attributes['labels'][i]) ) {
233                 strwidth = attributes['labels'][i].toString().length;
234                 strwidth = 0.6*strwidth*fs/board.stretchX;
235                 if (yp>=0) {
236                     yp += fs*0.5/board.stretchY;   // Static offset for label
237                 } else {
238                     yp -= fs*1.0/board.stretchY;   // Static offset for label
239                 }
240                 board.create('text',[xp1-strwidth*0.5, yp, attributes['labels'][i]],attributes);
241             }
242         }
243         attributes['withLines'] = false;
244 
245         if(!fill) {
246             colorArray = attributes['colorArray'] || ['#B02B2C','#3F4C6B','#C79810','#D15600','#FFFF88','#C3D9FF','#4096EE','#008C00'];
247             attributes['fillColor'] = colorArray[i%colorArray.length];
248         }
249         pols[i] = board.create('polygon',p,attributes);
250     }
251     this.rendNode = pols[0].rendNode;  // This is needed in setProperty
252 
253     return pols; //[0];  // Not enough! We need pols, but this gives an error in board.setProperty.
254 };
255 
256 JXG.Chart.prototype.drawPoints = function(board, parents, attributes) {
257     var i;
258     var points = [];
259     attributes['fixed'] = true;
260     attributes['name'] = '';
261     var infoboxArray = JXG.isArray(attributes['infoboxArray']) ? attributes['infoboxArray'] || false : false;
262     var x = parents[0];
263     var y = parents[1];
264     
265     for (i=0;i<x.length;i++) {
266         attributes['infoboxtext'] = infoboxArray ? infoboxArray[i%infoboxArray.length] : false;
267         points[i] = board.create('point',[x[i],y[i]], attributes);
268     }
269     this.rendNode = points[0].rendNode;
270     return points; //[0];  // Not enough! We need points, but this gives an error in board.setProperty.
271 };
272 
273 JXG.Chart.prototype.drawPie = function(board, parents, attributes) {  // Only 1 array possible as argument 
274     var y = parents[0];
275     if (y.length<=0) { return; }
276     //if (typeof y[0] == 'function') { return; } // functions not yet possible
277 
278     var i;
279     var p = [];
280     var arc = [];
281     var s = JXG.Math.Statistics.sum(y);
282     var colorArray = attributes['colorArray'] || ['#B02B2C','#3F4C6B','#C79810','#D15600','#FFFF88','#C3D9FF','#4096EE','#008C00'];
283     var highlightColorArray = attributes['highlightColorArray'] || ['#FF7400'];
284     var la = new Array(y.length);
285     for(i=0; i<y.length; i++) {
286         la[i] = '';
287     }
288     var labelArray = attributes['labelArray'] || la;
289     var r = attributes['radius'] || 4;
290     var radius;
291     if (!JXG.isFunction(r)) {
292         radius = function(){ return r; }
293     } else {
294         radius = r;
295     }
296     var myAtts = {};
297     if (!JXG.exists(attributes['highlightOnSector'])) {
298         attributes['highlightOnSector'] = false;
299     }    
300     myAtts['name'] = attributes['name'];
301     myAtts['id'] = attributes['id'];
302     myAtts['strokeWidth'] = attributes['strokeWidth'] || 1;
303     myAtts['strokeColor'] = attributes['strokeColor'] || 'none';
304     myAtts['straightFirst'] = false;
305     myAtts['straightLast'] = false;
306     myAtts['fillColor'] = attributes['fillColor'] || '#FFFF88';
307     myAtts['fillOpacity'] = attributes['fillOpacity'] || 0.6;
308     myAtts['highlightFillColor'] = attributes['highlightFillColor'] || '#FF7400';
309     myAtts['highlightStrokeColor'] = attributes['highlightStrokeColor'] || '#FFFFFF';
310     myAtts['gradient'] = attributes['gradient'] || 'none';
311     myAtts['gradientSecondColor'] = attributes['gradientSecondColor'] || 'black';
312     var cent = attributes['center'] || [0,0];
313     var xc = cent[0];
314     var yc = cent[1];
315 
316     var center = board.create('point',[xc,yc], {name:'',fixed:true,visible:false});
317     p[0] = board.create('point',[function(){ return radius()+xc;},function(){ return 0+yc;}], {name:'',fixed:true,visible:false});
318     var rad = 0.0;
319     for (i=0;i<y.length;i++) {
320             p[i+1] = board.create('point',
321                 [(function(j){ return function() {
322                 var s = 0.0, t = 0.0, i;
323                 for (i=0; i<=j ;i++) { 
324                     if  (JXG.isFunction(y[i])) {
325                         t += parseFloat(y[i]()); 
326                     } else {
327                         t += parseFloat(y[i]); 
328                     }
329                 }
330                 s = t;
331                 for (i=j+1; i<y.length ;i++) { 
332                     if  (JXG.isFunction(y[i])) {
333                         s += parseFloat(y[i]()); 
334                     } else {
335                         s += parseFloat(y[i]); 
336                     }
337                 }
338                 var rad = (s!=0)?(2*Math.PI*t/s):0;
339                 return radius()*Math.cos(rad)+xc;
340             };})(i),
341                 (function(j){ return function() {
342                 var s = 0.0, t = 0.0, i;
343                 for (i=0; i<=j ;i++) { 
344                     if  (JXG.isFunction(y[i])) {
345                         t += parseFloat(y[i]()); 
346                     } else {
347                         t += parseFloat(y[i]); 
348                     }
349                 }
350                 s = t;
351                 for (i=j+1; i<y.length ;i++) { 
352                     if  (JXG.isFunction(y[i])) {
353                         s += parseFloat(y[i]()); 
354                     } else {
355                         s += parseFloat(y[i]); 
356                     }
357                 }
358                 var rad = (s!=0)?(2*Math.PI*t/s):0;
359                 return radius()*Math.sin(rad)+yc;
360             };})(i)
361                 ], 
362                 {name:'',fixed:false,visible:false,withLabel:false});
363         myAtts['fillColor'] = colorArray[i%colorArray.length];
364         myAtts['name'] = labelArray[i];
365         myAtts['withLabel'] = myAtts['name'] != '';
366         myAtts['labelColor'] = colorArray[i%colorArray.length];
367         myAtts['highlightfillColor'] = highlightColorArray[i%highlightColorArray.length];
368         arc[i] = board.create('sector',[center,p[i],p[i+1]], myAtts);
369         
370         if(attributes['highlightOnSector']) {
371             arc[i].hasPoint = arc[i].hasPointSector; // overwrite hasPoint so that the whole sector is used for highlighting
372         }
373         if(attributes['highlightBySize']) {
374             arc[i].highlight = function() {
375                 this.board.renderer.highlight(this);
376                 if(this.label.content != null) {
377                     this.label.content.rendNode.style.fontSize = (2*this.board.options.text.fontSize) + 'px';
378                 }
379             
380                 var dx = - this.midpoint.coords.usrCoords[1] + this.point2.coords.usrCoords[1];
381                 var dy = - this.midpoint.coords.usrCoords[2] + this.point2.coords.usrCoords[2];
382                 /*
383                 var ddx = 20/(this.board.stretchX);
384                 var ddy = 20/(this.board.stretchY);
385                 var z = Math.sqrt(dx*dx+dy*dy);
386                 */
387                 this.point2.coords = new JXG.Coords(JXG.COORDS_BY_USER, 
388                                                 [this.midpoint.coords.usrCoords[1]+dx*1.1, //*(z+ddx)/z,
389                                                  this.midpoint.coords.usrCoords[2]+dy*1.1], //(z+ddy)/z],
390                                                 this.board);
391                 this.prepareUpdate().update().updateRenderer();      
392             };
393           
394             arc[i].noHighlight = function() {
395                 this.board.renderer.noHighlight(this);
396                 if(this.label.content != null) {
397                     this.label.content.rendNode.style.fontSize = (this.board.options.text.fontSize) + 'px';
398                 }
399             
400                 var dx = -this.midpoint.coords.usrCoords[1] + this.point2.coords.usrCoords[1];
401                 var dy = -this.midpoint.coords.usrCoords[2] + this.point2.coords.usrCoords[2];
402                 /*
403                 var ddx = 20/(this.board.stretchX);
404                 var ddy = 20/(this.board.stretchY);
405                 var z = Math.sqrt(dx*dx+dy*dy);
406                 */           
407                 this.point2.coords = new JXG.Coords(JXG.COORDS_BY_USER, 
408                                                 [this.midpoint.coords.usrCoords[1]+dx/1.1, //*(z-ddx)/z,
409                                                  this.midpoint.coords.usrCoords[2]+dy/1.1], //*(z-ddy)/z],
410                                                 this.board);
411                 this.prepareUpdate().update().updateRenderer();      
412             }; 
413         }
414 
415     }
416     this.rendNode = arc[0].rendNode;
417     return {arcs:arc, points:p, midpoint:center}; //[0];  // Not enough! We need points, but this gives an error in board.setProperty.
418 };
419 
420 /*
421  * labelArray=[ row1, row2, row3 ]
422  * paramArray=[ paramx, paramy, paramz ]
423  * parents=[[x1, y1, z1], [x2, y2, z2], [x3, y3, z3]]
424  */
425 JXG.Chart.prototype.drawRadar = function(board, parents, attributes) { 
426     var i, j, paramArray, numofparams, maxes, mins,
427         len = parents.length,
428         la, pdata, ssa, esa, ssratio, esratio,
429         sshifts, eshifts, starts, ends,
430         labelArray, colorArray, highlightColorArray, radius, myAtts,
431         cent, xc, yc, center, start_angle, rad, p, line, t,
432         xcoord, ycoord, polygons, legend_position, circles,
433         cla, clabelArray, ncircles, pcircles, angle, dr;
434     
435     if (len<=0) { alert("No data"); return; } 
436     // labels for axes
437     paramArray = attributes['paramArray'];
438     if (!JXG.exists(paramArray)){ alert("Need paramArray attribute"); return; }
439     numofparams=paramArray.length;
440     if (numofparams<=1) { alert("Need more than 1 param"); return; }
441 
442     for(i=0; i<len; i++) {
443         if (numofparams!=parents[i].length) { alert("Use data length equal to number of params (" + parents[i].length + " != " + numofparams + ")"); return; }
444     }
445     maxes=new Array(numofparams);
446     mins=new Array(numofparams);
447     for(j=0; j<numofparams; j++) {
448         maxes[j] = parents[0][j];
449         mins[j] = maxes[j];
450     }
451     for(i=1; i<len; i++) {
452         for(j=0;j<numofparams; j++) {
453             if (parents[i][j]>maxes[j])
454                 maxes[j] = parents[i][j];
455             if (parents[i][j]<mins[j])
456                 mins[j] = parents[i][j];
457         }
458     }
459 
460     la = new Array(len);
461     pdata = new Array(len);
462     for(i=0; i<len; i++) {
463         la[i] = '';
464         pdata[i] = [];
465     }
466 
467     ssa = new Array(numofparams);
468     esa = new Array(numofparams);
469     
470     // 0 <= Offset from chart center <=1
471     ssratio = attributes['startShiftRatio'] || 0.0;
472     // 0 <= Offset from chart radius <=1
473     esratio = attributes['endShiftRatio'] || 0.0;
474     
475     for(i=0; i<numofparams; i++) {
476         ssa[i] = (maxes[i]-mins[i])*ssratio;
477         esa[i] = (maxes[i]-mins[i])*esratio;
478     }
479     
480     // Adjust offsets per each axis
481     sshifts = attributes['startShiftArray'] || ssa;
482     eshifts = attributes['endShiftArray'] || esa;
483     // Values for inner circle, minimums by default
484     starts = attributes['startArray'] || mins;
485     
486     if (JXG.exists(attributes['start']))
487         for(i=0; i<numofparams; i++) starts[i] = attributes['start'];
488     // Values for outer circle, maximums by default
489     ends = attributes['endArray'] || maxes;
490     if (JXG.exists(attributes['end']))
491         for(i=0; i<numofparams; i++) ends[i] = attributes['end'];
492 
493     if(sshifts.length != numofparams) { alert("Start shifts length is not equal to number of parameters"); return; }
494     if(eshifts.length != numofparams) { alert("End shifts length is not equal to number of parameters"); return; }
495     if(starts.length != numofparams) { alert("Starts length is not equal to number of parameters"); return; }
496     if(ends.length != numofparams) { alert("Ends length is not equal to number of parameters"); return; }
497 
498     // labels for legend
499     labelArray = attributes['labelArray'] || la;
500     colorArray = attributes['colorArray'] || ['#B02B2C','#3F4C6B','#C79810','#D15600','#FFFF88','#C3D9FF','#4096EE','#008C00'];
501     highlightColorArray = attributes['highlightColorArray'] || ['#FF7400'];
502     radius = attributes['radius'] || 10;
503     myAtts = {};
504     if (!JXG.exists(attributes['highlightOnSector'])) {
505         attributes['highlightOnSector'] = false;
506     }    
507     myAtts['name'] = attributes['name'];
508     myAtts['id'] = attributes['id'];
509     myAtts['strokeWidth'] = attributes['strokeWidth'] || 1;
510     myAtts['polyStrokeWidth'] = attributes['polyStrokeWidth'] || 2*myAtts['strokeWidth'];
511     myAtts['strokeColor'] = attributes['strokeColor'] || 'black';
512     myAtts['straightFirst'] = false;
513     myAtts['straightLast'] = false;
514     myAtts['fillColor'] = attributes['fillColor'] || '#FFFF88';
515     myAtts['fillOpacity'] = attributes['fillOpacity'] || 0.4;
516     myAtts['highlightFillColor'] = attributes['highlightFillColor'] || '#FF7400';
517     myAtts['highlightStrokeColor'] = attributes['highlightStrokeColor'] || 'black';
518     myAtts['gradient'] = attributes['gradient'] || 'none';
519 
520     cent = attributes['center'] || [0,0];
521     xc = cent[0];
522     yc = cent[1];
523 
524     center = board.create('point',[xc,yc], {name:'',fixed:true, withlabel:false,visible:false});
525     start_angle = Math.PI/2 - Math.PI/numofparams;
526     if(attributes['startAngle'] || attributes['startAngle'] === 0) 
527         start_angle = attributes['startAngle'];
528     
529     rad = start_angle;
530     p = [];
531     line = [];
532     var get_anchor =  function() {
533         var x1,x2,y1,y2,relCoords = [].concat(this.labelOffsets);
534         x1 = this.point1.X();
535         x2 = this.point2.X();
536         y1 = this.point1.Y();
537         y2 = this.point2.Y();
538         if(x2<x1)
539             relCoords[0] = -relCoords[0];
540         if(y2<y1)
541             relCoords[1] = -relCoords[1];
542 
543         this.setLabelRelativeCoords(relCoords);
544         return new JXG.Coords(JXG.COORDS_BY_USER, [this.point2.X(),this.point2.Y()],this.board);
545     };
546 
547     var get_transform = function(angle,i) {
548         var t;
549         var tscale;
550         var trot;
551         t = board.create('transform', [-(starts[i]-sshifts[i]), 0],{type:'translate'}); 
552         tscale = board.create('transform', [radius/((ends[i]+eshifts[i])-(starts[i]-sshifts[i])), 1],{type:'scale'}); 
553         t.melt(tscale);
554         trot = board.create('transform', [angle],{type:'rotate'}); 
555         t.melt(trot);
556         return t;
557     };
558     
559     for (i=0;i<numofparams;i++) {
560         rad += 2*Math.PI/numofparams;
561         xcoord = radius*Math.cos(rad)+xc;
562         ycoord = radius*Math.sin(rad)+yc;
563 
564         p[i] = board.create('point',[xcoord,ycoord], {name:'',fixed:true,withlabel:false,visible:false});
565         line[i] = board.create('line',[center,p[i]], 
566             {name:paramArray[i], 
567             strokeColor:myAtts['strokeColor'], strokeWidth:myAtts['strokeWidth'], strokeOpacity:1.0,
568             straightFirst:false, straightLast:false, withLabel:true,
569             highlightStrokeColor:myAtts['highlightStrokeColor']
570         });
571         line[i].getLabelAnchor = get_anchor;
572         t = get_transform(rad,i);
573 
574         for(j=0; j<parents.length; j++) {
575             var data=parents[j][i];
576             pdata[j][i] = board.create('point',[data,0], {name:'',fixed:true,withlabel:false,visible:false});
577             pdata[j][i].addTransform(pdata[j][i], t);
578         }
579     }
580     polygons = new Array(len);
581     for(i=0;i<len;i++) {
582         myAtts['labelColor'] = colorArray[i%colorArray.length];
583         myAtts['strokeColor'] = colorArray[i%colorArray.length];
584         myAtts['fillColor'] = colorArray[i%colorArray.length];
585         polygons[i] = board.create('polygon',pdata[i], 
586                 {withLines:true,
587                 withLabel:false,
588                 fillColor:myAtts['fillColor'],
589                 fillOpacity:myAtts['fillOpacity']
590                 });
591         for(j=0;j<numofparams;j++) {
592             polygons[i].borders[j].setProperty('strokeColor:' + colorArray[i%colorArray.length]);
593             polygons[i].borders[j].setProperty('strokeWidth:' + myAtts['polyStrokeWidth']);
594         }
595     }
596 
597     legend_position = attributes['legendPosition'] || 'none';
598     switch(legend_position) {
599         case 'right':
600         var lxoff = attributes['legendLeftOffset'] || 2;
601             var lyoff = attributes['legendTopOffset'] || 1;
602             this.legend = board.create('legend', [xc+radius+lxoff,yc+radius-lyoff],
603             {labelArray:labelArray,
604             colorArray: colorArray
605             });
606             break;
607         case 'none':
608             break;
609         default:
610             alert('Unknown legend position');
611     }
612 
613     circles = [];
614     if (attributes['showCircles'] != false) {
615         cla = [];
616         for(i=0;i<6;i++)
617             cla[i]=20*i;
618         cla[0] = "0";
619         clabelArray = attributes['circleLabelArray'] || cla;
620         ncircles = clabelArray.length;
621         if (ncircles<2) {alert("Too less circles"); return; }
622         pcircles = [];
623         angle=start_angle + Math.PI/numofparams;
624         t = get_transform(angle,0);
625         myAtts['fillColor'] = 'none';
626         myAtts['highlightFillColor'] = 'none';
627         myAtts['strokeColor'] = attributes['strokeColor'] || 'black';
628         myAtts['strokeWidth'] = attributes['circleStrokeWidth'] || 0.5;
629         // we have ncircles-1 intervals between ncircles circles
630         dr = (ends[0]-starts[0])/(ncircles-1);
631         for(i=0;i<ncircles;i++) {
632             pcircles[i] = board.create('point', [starts[0]+i*dr,0],{name:clabelArray[i], size:0, withLabel:true, visible:true});
633             pcircles[i].addTransform(pcircles[i],t);
634             circles[i] = board.create('circle', [center,pcircles[i]], myAtts);
635         }
636 
637     }
638     this.rendNode = polygons[0].rendNode;
639     return {circles:circles, lines:line, points:pdata, midpoint:center,polygons:polygons}; //[0];  // Not enough! We need points, but this gives an error in board.setProperty.
640 };
641 
642 /**
643  * Then, the update function of the renderer
644  * is called.  Since a chart is only an abstract element,
645  * containing other elements, this function is empty.
646  */
647 JXG.Chart.prototype.updateRenderer = function () {};
648 
649 /**
650  * Update of the defining points
651  */
652 JXG.Chart.prototype.update = function () {
653     if (this.needsUpdate) {
654         this.updateDataArray();
655     }
656 };
657 
658 /**
659   * For dynamic charts update
660   * can be used to compute new entries
661   * for the arrays this.dataX and
662   * this.dataY. It is used in @see update.
663   * Default is an empty method, can be overwritten
664   * by the user.
665   */
666 JXG.Chart.prototype.updateDataArray = function () {};
667 
668 
669 JXG.createChart = function(board, parents, attributes) {
670     if((parents.length == 1) && (typeof parents[0] == 'string')) {
671         var table = document.getElementById(parents[0]),
672             data, row, i, j, col, cell, charts = [], w, x, showRows,
673             originalWidth, name, strokeColor, fillColor, hStrokeColor, hFillColor, len;
674         if(JXG.exists(table)) {
675             // extract the data
676             attributes = JXG.checkAttributes(attributes,{withHeader:true});
677             
678             table = (new JXG.DataSource()).loadFromTable(parents[0], attributes['withHeader'], attributes['withHeader']);
679             data = table.data;
680             col = table.columnHeader;
681             row = table.rowHeader;
682 
683             originalWidth = attributes['width'];
684             name = attributes['name'];
685             strokeColor = attributes['strokeColor'];
686             fillColor = attributes['fillColor'];
687             hStrokeColor = attributes['highlightStrokeColor'];
688             hFillColor = attributes['highlightFillColor'];
689 
690             board.suspendUpdate();
691 
692             len = data.length;
693             showRows = [];
694             if (attributes['rows'] && JXG.isArray(attributes['rows'])) {
695                 for(i=0; i<len; i++) {
696                     for(j=0; j<attributes['rows'].length; j++) {
697                         if((attributes['rows'][j] == i) || (attributes['withHeaders'] && attributes['rows'][j] == row[i])) {
698                             showRows.push(data[i]);
699                             break;
700                         }
701                     }
702                 }
703             } else {
704                 showRows = data;
705             }
706 
707             len = showRows.length;
708 
709             for(i=0; i<len; i++) {
710 
711                 x = [];
712                 if(attributes['chartStyle'] && attributes['chartStyle'].indexOf('bar') != -1) {
713                     if(originalWidth) {
714                         w = originalWidth;
715                     } else {
716                         w = 0.8;
717                     }
718                     x.push(1 - w/2. + (i+0.5)*w/(1.0*len));
719                     for(j=1; j<showRows[i].length; j++) {
720                         x.push(x[j-1] + 1);
721                     }
722                     attributes['width'] = w/(1.0*len);
723                 }
724                 
725                 if(name && name.length == len)
726                     attributes['name'] = name[i];
727                 else if(attributes['withHeaders'])
728                     attributes['name'] = col[i];
729                 
730                 if(strokeColor && strokeColor.length == len)
731                     attributes['strokeColor'] = strokeColor[i];
732                 else
733                     attributes['strokeColor'] = JXG.hsv2rgb(((i+1)/(1.0*len))*360,0.9,0.6);
734                 
735                 if(fillColor && fillColor.length == len)
736                     attributes['fillColor'] = fillColor[i];
737                 else
738                     attributes['fillColor'] = JXG.hsv2rgb(((i+1)/(1.0*len))*360,0.9,1.0);
739                 
740                 if(hStrokeColor && hStrokeColor.length == len)
741                     attributes['highlightStrokeColor'] = hStrokeColor[i];
742                 else
743                     attributes['highlightStrokeColor'] = JXG.hsv2rgb(((i+1)/(1.0*len))*360,0.9,1.0);
744                 
745                 if(hFillColor && hFillColor.length == len)
746                     attributes['highlightFillColor'] = hFillColor[i];
747                 else
748                     attributes['highlightFillColor'] = JXG.hsv2rgb(((i+1)/(1.0*len))*360,0.9,0.6);
749                 
750                 if(attributes['chartStyle'] && attributes['chartStyle'].indexOf('bar') != -1) {
751                     charts.push(new JXG.Chart(board, [x, showRows[i]], attributes));
752                 } else
753                     charts.push(new JXG.Chart(board, [showRows[i]], attributes));
754             }
755 
756             board.unsuspendUpdate();
757 
758         }
759         return charts;
760     } else     
761         return new JXG.Chart(board, parents, attributes);
762 };    
763 
764 JXG.JSXGraph.registerElement('chart', JXG.createChart);
765 
766 /**
767  * Legend for chart
768  * 
769  **/
770 JXG.Legend = function(board, coords, attributes) {
771     /* Call the constructor of GeometryElement */
772     this.constructor();
773     this.board = board;
774     this.coords = new JXG.Coords(JXG.COORDS_BY_USER, coords, this.board);
775     this.myAtts = {};
776     this.label_array = attributes['labelArray'] || ['1','2','3','4','5','6','7','8'];
777     this.color_array = attributes['colorArray'] || ['#B02B2C','#3F4C6B','#C79810','#D15600','#FFFF88','#C3D9FF','#4096EE','#008C00'];
778     var i;
779     this.lines = [];
780     this.myAtts['strokeWidth'] = attributes['strokeWidth'] || 5;
781     this.myAtts['straightFirst'] = false;
782     this.myAtts['straightLast'] = false;
783     this.myAtts['withLabel'] = true;
784     this.style = attributes['legendStyle'] || 'vertical';
785 
786     switch(this.style) {
787         case 'vertical':
788             this.drawVerticalLegend(attributes); 
789             break;
790         default:
791             alert('Unknown legend style' + this.style);
792             break;
793     }
794 }
795 JXG.Legend.prototype = new JXG.GeometryElement;
796 
797 JXG.Legend.prototype.drawVerticalLegend = function(attributes) {
798     var line_length = attributes['lineLength'] || 1,
799         offy = (attributes['rowHeight'] || 20)/this.board.stretchY,
800         i;
801 
802     for(i=0;i<this.label_array.length;i++) {
803         this.myAtts['strokeColor'] = this.color_array[i];
804         this.myAtts['highlightStrokeColor'] = this.color_array[i];
805         this.myAtts['name'] = this.label_array[i];
806         this.myAtts['labelOffsets'] = [10, 0];
807         this.lines[i] = board.create('line', 
808                 [[this.coords.usrCoords[1],this.coords.usrCoords[2] - i*offy],
809                 [this.coords.usrCoords[1] + line_length,this.coords.usrCoords[2] - i*offy]],
810                 this.myAtts
811                 );
812         this.lines[i].getLabelAnchor = function() {
813             this.setLabelRelativeCoords(this.labelOffsets);
814             return new JXG.Coords(JXG.COORDS_BY_USER, [this.point2.X(),this.point2.Y()],this.board);
815         }
816     }
817 }
818 
819 JXG.createLegend = function(board, parents, attributes) {
820     //parents are coords of left top point of the legend
821     var start_from = [0,0];
822     if(JXG.exists(parents))
823         if(parents.length == 2) {
824             start_from = parents;
825         }
826     return new JXG.Legend(board, start_from, attributes);
827 }
828 JXG.JSXGraph.registerElement('legend', JXG.createLegend);
829 // vim: et ts=4
830