      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  */
     29 JXG.Chart = function(board, parents, attributes) {
     30     this.constructor(board, attributes);
     31
     32     var x, y, i, c, style, len;
     33
     34     if (!JXG.isArray(parents) || parents.length === 0) {
     35         throw new Error('JSXGraph: Can\'t create a chart without data');
     36     }
     37
     38     /**
     39      * Contains pointers to the various subelements of the chart.
     40      */
     41     this.elements = [];
     42
     43     if (JXG.isNumber(parents[0])) {
     44         // parents looks like [a,b,c,..]
     45         // x has to be filled
     46
     47         y = parents;
     48         x = [];
     49         for (i=0;i<y.length;i++) {
     50             x[i] = i+1;
     51         }
     52     } else if (parents.length === 1 && JXG.isArray(parents[0])) {
     53         // parents looks like [[a,b,c,..]]
     54         // x has to be filled
     55
     56         y = parents[0];
     57         x = [];
     58
     59         len = JXG.evaluate(y).length;
     60         for (i = 0; i < len; i++) {
     61             x[i] = i+1;
     62         }
     63     } else if (parents.length === 2) {
     64         // parents looks like [[x0,x1,x2,...],[y1,y2,y3,...]]
     65         len = Math.min(parents[0].length, parents[1].length);
     66         x = parents[0].slice(0, len);
     67         y = parents[1].slice(0, len);
     68     }
     69
     70     if (JXG.isArray(y) && y.length === 0) {
     71         throw new Error('JSXGraph: Can\'t create charts without data.');
     72     }
     73
     74     // does this really need to be done here? this should be done in createChart and then
     75     // there should be an extra chart for each chartstyle
     76     style = attributes.chartstyle.replace(/ /g,'').split(',');
     77     for (i = 0; i < style.length; i++) {
     78         switch (style[i]) {
     79             case 'bar':
     80                 c = this.drawBar(board, x, y, attributes);
     81                 break;
     82             case 'line':
     83                 c = this.drawLine(board, x, y, attributes);
     84                 break;
     85             case 'fit':
     86                 c = this.drawFit(board, x, y, attributes);
     87                 break;
     88             case 'spline':
     89                 c = this.drawSpline(board, x, y, attributes);
     90                 break;
     91             case 'pie':
     92                 c = this.drawPie(board, y, attributes);
     93                 break;
     94             case 'point':
     95                 c = this.drawPoints(board, x, y, attributes);
     96                 break;
     97             case 'radar':
     98                 c = this.drawRadar(board, parents, attributes);
     99                 break;
    100         }
    101         this.elements.push(c);
    102     }
    103     this.id = this.board.setId(this, 'Chart');
    104
    105     return this.elements;
    106 };
    107 JXG.Chart.prototype = new JXG.GeometryElement;
    108
    109 JXG.extend(JXG.Chart.prototype, /** @lends JXG.Chart.prototype */ {
    110
    111     drawLine: function(board, x, y, attributes) {
    112         // we don't want the line chart to be filled
    113         attributes.fillcolor = 'none';
    114         attributes.highlightfillcolor = 'none';
    115
    116         return board.create('curve', [x, y], attributes);
    117     },
    118
    119     drawSpline: function(board, x, y, attributes) {
    120         // we don't want the spline chart to be filled
    121         attributes.fillColor = 'none';
    122         attributes.highlightfillcolor = 'none';
    123
    124         return board.create('spline', [x, y], attributes);
    125     },
    126
    127     drawFit: function(board, x, y, attributes) {
    128         var deg = attributes.degree;
    129
    130         deg = (!JXG.exists(deg) || parseInt(deg) == NaN || parseInt(deg) < 1) ? 1 : parseInt(deg),
    131
    132         // never fill
    133         attributes.fillcolor = 'none';
    134         attributes.highlightfillcolor = 'none';
    135
    136         return board.create('functiongraph', [JXG.Math.Numerics.regressionPolynomial(deg, x, y)], attributes);
    137     },
    138
    139     drawBar: function(board, x, y, attributes) {
    140         var i,pols = [], strwidth, fill, fs, text,
    141             w, xp0, xp1, xp2, yp, colors, p = [],
    142             hiddenPoint = {
    143                 fixed: true,
    144                 withLabel: false,
    145                 visible: false,
    146                 name: ''
    147             };
    148
    149         if (!JXG.exists(attributes.fillopacity)) {
    150             attributes.fillopacity = 0.6;
    151         }
    152
    153         // Determine the width of the bars
    154         if (attributes && attributes.width) {  // width given
    155             w = attributes.width;
    156         } else {
    157             if (x.length <= 1) {
    158                 w = 1;
    159             } else {
    160                 // Find minimum distance between to bars.
    161                 w = x[1]-x[0];
    162                 for (i = 1; i < x.length-1; i++) {
    163                     w = (x[i+1] - x[i] < w) ? x[i+1] - x[i] : w;
    164                 }
    165             }
    166             w *=0.8;
    167         }
    168
    169         fill = attributes.fillcolor;
    170         fs = parseFloat(board.options.text.fontSize);                 // TODO: handle fontSize attribute
    171         for (i = 0; i < x.length; i++) {
    172             if (isNaN(JXG.evaluate(x[i])) || isNaN(y[i])) {
    173                 //continue;
    174             }
    175             if (JXG.isFunction(x[i])) {
    176                 xp0 = function() { return x[i]()-w*0.5; };
    177                 xp1 = function() { return x[i](); };
    178                 xp2 = function() { return x[i]()+w*0.5; };
    179             } else {
    180                 xp0 = x[i]-w*0.5;
    181                 xp1 = x[i];
    182                 xp2 = x[i]+w*0.5;
    183             }
    184             yp = y[i];
    185             if (attributes.dir == 'horizontal') {  // horizontal bars
    186                 p[0] = board.create('point',[0,xp0], hiddenPoint);
    187                 p[1] = board.create('point',[yp,xp0], hiddenPoint);
    188                 p[2] = board.create('point',[yp,xp2], hiddenPoint);
    189                 p[3] = board.create('point',[0,xp2], hiddenPoint);
    190
    191                 if ( JXG.exists(attributes.labels) && JXG.exists(attributes.labels[i]) ) {
    192                     strwidth = attributes.labels[i].toString().length;
    193                     strwidth = 2.0*strwidth*fs/board.unitX;
    194                     if (yp>=0) {
    195                         yp += fs*0.5/board.unitX;   // Static offset for label
    196                     } else {
    197                         yp -= fs*strwidth/board.unitX;   // Static offset for label
    198                     }
    199                     xp1 -= fs*0.2/board.unitY;
    200                     text = board.create('text',[yp,xp1,attributes.labels[i]],attributes);
    201                 }
    202             } else { // vertical bars
    203                 p[0] = board.create('point',[xp0,0], hiddenPoint);
    204                 p[1] = board.create('point',[xp0,yp], hiddenPoint);
    205                 p[2] = board.create('point',[xp2,yp], hiddenPoint);
    206                 p[3] = board.create('point',[xp2,0], hiddenPoint);
    207                 if ( JXG.exists(attributes.labels) && JXG.exists(attributes.labels[i]) ) {
    208                     strwidth = attributes.labels[i].toString().length;
    209                     strwidth = 0.6*strwidth*fs/board.unitX;
    210                     if (yp>=0) {
    211                         yp += fs*0.5/board.unitY;   // Static offset for label
    212                     } else {
    213                         yp -= fs*1.0/board.unitY;   // Static offset for label
    214                     }
    215                     text = board.create('text',[xp1-strwidth*0.5, yp, attributes['labels'][i]],attributes);
    216                 }
    217             }
    218
    219             attributes.withlines = false;
    220             if(JXG.exists(attributes.colors) && JXG.isArray(attributes.colors)) {
    221                 colors = attributes.colors;
    222                 attributes.fillcolor = colors[i%colors.length];
    223             }
    224             pols[i] = board.create('polygon', p, attributes);
    225             if (JXG.exists(attributes.labels) && JXG.exists(attributes.labels[i])) {
    226                 pols[i].text = text;
    227             }
    228         }
    229
    230         return pols;
    231     },
    232
    233     drawPoints: function(board, x, y, attributes) {
    234         var i,
    235             points = [],
    236             infoboxArray = attributes.infoboxarray;
    237
    238         attributes.fixed = true;
    239         attributes.name = '';
    240
    241         for (i = 0; i < x.length; i++) {
    242             attributes.infoboxtext = infoboxArray ? infoboxArray[i%infoboxArray.length] : false;
    243             points[i] = board.create('point', [x[i], y[i]], attributes);
    244         }
    245
    246         return points;
    247     },
    248
    249     drawPie: function(board, y, attributes) {
    250         var i,
    251             p = [],
    252             sector = [],
    253             s = JXG.Math.Statistics.sum(y),
    254             colorArray = attributes.colors,
    255             highlightColorArray = attributes.highlightcolors,
    256             labelArray = attributes.labels,
    257             r = attributes.radius || 4,
    258             radius = r,
    259             cent = attributes.center || [0,0],
    260             xc = cent[0],
    261             yc = cent[1],
    262             center,
    263             hiddenPoint = {
    264                 fixed: true,
    265                 withLabel: false,
    266                 visible: false,
    267                 name: ''
    268             };
    269
    270         if (!JXG.isArray(labelArray)) {
    271             labelArray = [];
    272             for(i = 0; i < y.length; i++) {
    273                 labelArray[i] = '';
    274             }
    275         }
    276
    277         if (!JXG.isFunction(r)) {
    278             radius = function(){ return r; }
    279         }
    280
    281         attributes.highlightonsector = attributes.highlightonsector || false;
    282         attributes.straightfirst = false;
    283         attributes.straightlast = false;
    284
    285         center = board.create('point',[xc, yc], hiddenPoint);
    286         p[0] = board.create('point', [
    287             function () {
    288                 return radius() + xc;
    289             },
    290             function () {
    291                 return yc;
    292             }
    293         ], hiddenPoint);
    294
    295         for (i = 0; i < y.length; i++) {
    296             p[i+1] = board.create('point', [
    297                 (function(j) {
    298                     return function() {
    299                         var s, t = 0, i, rad;
    300
    301                         for (i = 0; i <= j; i++) {
    302                             t += parseFloat(JXG.evaluate(y[i]));
    303                         }
    304                         s = t;
    305                         for (i = j + 1; i < y.length; i++) {
    306                             s += parseFloat(JXG.evaluate(y[i]));
    307                         }
    308                         rad = (s != 0) ? (2*Math.PI*t/s) : 0;
    309
    310                         return radius()*Math.cos(rad)+xc;
    311                     };
    312                 })(i),
    313                 (function(j){
    314                     return function() {
    315                         var s, t = 0.0, i, rad;
    316                         for (i = 0; i <= j ;i++) {
    317                             t += parseFloat(JXG.evaluate(y[i]));
    318                         }
    319                         s = t;
    320                         for (i = j + 1; i < y.length; i++) {
    321                             s += parseFloat(JXG.evaluate(y[i]));
    322                         }
    323                         rad = (s != 0) ? (2*Math.PI*t/s) : 0;
    324
    325                         return radius()*Math.sin(rad)+yc;
    326                     };
    327                 })(i)
    328             ], hiddenPoint);
    329
    330             attributes.name = labelArray[i];
    331             attributes.withlabel = attributes.name != '';
    332             attributes.fillcolor = colorArray && colorArray[i%colorArray.length];
    333             attributes.labelcolor = colorArray && colorArray[i%colorArray.length];
    334             attributes.highlightfillcolor = highlightColorArray && highlightColorArray[i%highlightColorArray.length];
    335
    336             sector[i] = board.create('sector',[center,p[i],p[i+1]], attributes);
    337
    338             if(attributes.highlightonsector) {
    339                 sector[i].hasPoint = sector[i].hasPointSector; // overwrite hasPoint so that the whole sector is used for highlighting
    340             }
    341             if(attributes.highlightbysize) {
    342                 sector[i].highlight = function() {
    343                     if (!this.highlighted) {
    344                         this.highlighted = true;
    345
    346                         this.board.renderer.highlight(this);
    347                         var dx = - this.point1.coords.usrCoords[1] + this.point2.coords.usrCoords[1],
    348                             dy = - this.point1.coords.usrCoords[2] + this.point2.coords.usrCoords[2];
    349
    350                         if(this.label.content != null) {
    351                             this.label.content.rendNode.style.fontSize = (2*this.label.content.visProp.fontsize) + 'px';
    352                             this.label.content.prepareUpdate().update().updateRenderer();
    353                         }
    354
    355                         this.point2.coords = new JXG.Coords(JXG.COORDS_BY_USER, [
    356                             this.point1.coords.usrCoords[1]+dx*1.1,
    357                             this.point1.coords.usrCoords[2]+dy*1.1
    358                         ], this.board);
    359                         this.prepareUpdate().update().updateRenderer();
    360                     }
    361                 };
    362
    363                 sector[i].noHighlight = function() {
    364                     if (this.highlighted) {
    365                         this.highlighted = false;
    366                         this.board.renderer.noHighlight(this);
    367
    368                         var dx = -this.point1.coords.usrCoords[1] + this.point2.coords.usrCoords[1],
    369                             dy = -this.point1.coords.usrCoords[2] + this.point2.coords.usrCoords[2];
    370
    371                         if(this.label.content != null) {
    372                             this.label.content.rendNode.style.fontSize = (this.label.content.visProp.fontsize*2) + 'px';
    373                             this.label.content.prepareUpdate().update().updateRenderer();
    374                         }
    375
    376                         this.point2.coords = new JXG.Coords(JXG.COORDS_BY_USER, [
    377                             this.point1.coords.usrCoords[1]+dx/1.1,
    378                             this.point1.coords.usrCoords[2]+dy/1.1
    379                         ], this.board);
    380                         this.prepareUpdate().update().updateRenderer();
    381                     }
    382                 };
    383             }
    384
    385         }
    386         return {sectors:sector, points:p, midpoint:center}; //[0];  // Not enough! We need points, but this gives an error in board.setProperty.
    387     },
    388
    389     /*
    390      * labelArray=[ row1, row2, row3 ]
    391      * paramArray=[ paramx, paramy, paramz ]
    392      * parents=[[x1, y1, z1], [x2, y2, z2], [x3, y3, z3]]
    393      */
    394     drawRadar: function(board, parents, attributes) {
    395         var i, j, paramArray, numofparams, maxes, mins,
    396             len = parents.length,
    397             la, pdata, ssa, esa, ssratio, esratio,
    398             sshifts, eshifts, starts, ends,
    399             labelArray, colorArray, highlightColorArray, radius, myAtts,
    400             cent, xc, yc, center, start_angle, rad, p, line, t,
    401             xcoord, ycoord, polygons, legend_position, circles,
    402             cla, clabelArray, ncircles, pcircles, angle, dr;
    403
    404         if (len<=0) {
    405             JXG.debug("No data");
    406             return;
    407         }
    408         // labels for axes
    409         paramArray = attributes['paramarray'];
    410         if (!JXG.exists(paramArray)) {
    411             JXG.debug("Need paramArray attribute");
    412             return;
    413         }
    414         numofparams=paramArray.length;
    415         if (numofparams<=1) {
    416             JXG.debug("Need more than 1 param");
    417             return;
    418         }
    419
    420         for(i=0; i<len; i++) {
    421             if (numofparams!=parents[i].length) {
    422                 JXG.debug("Use data length equal to number of params (" + parents[i].length + " != " + numofparams + ")");
    423                 return;
    424             }
    425         }
    426         maxes=new Array(numofparams);
    427         mins=new Array(numofparams);
    428         for(j=0; j<numofparams; j++) {
    429             maxes[j] = parents[0][j];
    430             mins[j] = maxes[j];
    431         }
    432         for(i=1; i<len; i++) {
    433             for(j=0;j<numofparams; j++) {
    434                 if (parents[i][j]>maxes[j])
    435                     maxes[j] = parents[i][j];
    436                 if (parents[i][j]<mins[j])
    437                     mins[j] = parents[i][j];
    438             }
    439         }
    440
    441         la = new Array(len);
    442         pdata = new Array(len);
    443         for(i=0; i<len; i++) {
    444             la[i] = '';
    445             pdata[i] = [];
    446         }
    447
    448         ssa = new Array(numofparams);
    449         esa = new Array(numofparams);
    450
    451         // 0 <= Offset from chart center <=1
    452         ssratio = attributes.startshiftratio || 0.0;
    453         // 0 <= Offset from chart radius <=1
    454         esratio = attributes.endshiftratio || 0.0;
    455
    456         for(i=0; i<numofparams; i++) {
    457             ssa[i] = (maxes[i]-mins[i])*ssratio;
    458             esa[i] = (maxes[i]-mins[i])*esratio;
    459         }
    460
    461         // Adjust offsets per each axis
    462         sshifts = attributes.startshiftarray || ssa;
    463         eshifts = attributes.endshiftarray || esa;
    464         // Values for inner circle, minimums by default
    465         starts = attributes.startarray || mins;
    466
    467         if (JXG.exists(attributes.start))
    468             for(i=0; i<numofparams; i++) starts[i] = attributes.start;
    469         // Values for outer circle, maximums by default
    470         ends = attributes.endarray || maxes;
    471         if (JXG.exists(attributes.end))
    472             for(i=0; i<numofparams; i++) ends[i] = attributes.end;
    473
    474         if(sshifts.length != numofparams) {
    475             JXG.debug("Start shifts length is not equal to number of parameters");
    476             return;
    477         }
    478         if(eshifts.length != numofparams) {
    479             JXG.debug("End shifts length is not equal to number of parameters");
    480             return;
    481         }
    482         if(starts.length != numofparams) {
    483             JXG.debug("Starts length is not equal to number of parameters");
    484             return;
    485         }
    486         if(ends.length != numofparams) {
    487             JXG.debug("Ends length is not equal to number of parameters");
    488             return;
    489         }
    490
    491         // labels for legend
    492         labelArray = attributes.labelarray || la;
    493         colorArray = attributes.colors;
    494         highlightColorArray = attributes.highlightcolors;
    495         radius = attributes.radius || 10;
    496         myAtts = {};
    497         if (!JXG.exists(attributes.highlightonsector)) {
    498             attributes.highlightonsector = false;
    499         }
    500
    501         myAtts['name'] = attributes['name'];
    502         myAtts['id'] = attributes['id'];
    503         myAtts['strokewidth'] = attributes['strokewidth'] || 1;
    504         myAtts['polystrokewidth'] = attributes['polystrokewidth'] || 2*myAtts['strokewidth'];
    505         myAtts['strokecolor'] = attributes['strokecolor'] || 'black';
    506         myAtts['straightfirst'] = false;
    507         myAtts['straightlast'] = false;
    508         myAtts['fillcolor'] = attributes['fillColor'] || '#FFFF88';
    509         myAtts['fillopacity'] = attributes['fillOpacity'] || 0.4;
    510         myAtts['highlightfillcolor'] = attributes['highlightFillColor'] || '#FF7400';
    511         myAtts['highlightstrokecolor'] = attributes['highlightStrokeColor'] || 'black';
    512         myAtts['gradient'] = attributes['gradient'] || 'none';
    513
    514         cent = attributes['center'] || [0,0];
    515         xc = cent[0];
    516         yc = cent[1];
    517
    518         center = board.create('point',[xc,yc], {name:'', fixed:true, withlabel:false, visible:false});
    519         start_angle = Math.PI/2 - Math.PI/numofparams;
    520         if(attributes['startangle'] || attributes['startangle'] === 0)
    521             start_angle = attributes['startangle'];
    522
    523         rad = start_angle;
    524         p = [];
    525         line = [];
    526         var get_anchor =  function() {
    527             var x1, x2, y1, y2,
    528                 relCoords = this.visProp.label.offsets.slice(0);
    529
    530             x1 = this.point1.X();
    531             x2 = this.point2.X();
    532             y1 = this.point1.Y();
    533             y2 = this.point2.Y();
    534             if(x2<x1)
    535                 relCoords[0] = -relCoords[0];
    536             if(y2<y1)
    537                 relCoords[1] = -relCoords[1];
    538
    539             this.setLabelRelativeCoords(relCoords);
    540             return new JXG.Coords(JXG.COORDS_BY_USER, [this.point2.X(),this.point2.Y()],this.board);
    541         };
    542
    543         var get_transform = function(angle,i) {
    544             var t;
    545             var tscale;
    546             var trot;
    547             t = board.create('transform', [-(starts[i]-sshifts[i]), 0],{type:'translate'});
    548             tscale = board.create('transform', [radius/((ends[i]+eshifts[i])-(starts[i]-sshifts[i])), 1],{type:'scale'});
    549             t.melt(tscale);
    550             trot = board.create('transform', [angle],{type:'rotate'});
    551             t.melt(trot);
    552             return t;
    553         };
    554
    555         for (i=0;i<numofparams;i++) {
    556             rad += 2*Math.PI/numofparams;
    557             xcoord = radius*Math.cos(rad)+xc;
    558             ycoord = radius*Math.sin(rad)+yc;
    559
    560             p[i] = board.create('point',[xcoord,ycoord], {name:'',fixed:true,withlabel:false,visible:false});
    561             line[i] = board.create('line',[center,p[i]],
    562             {name:paramArray[i],
    563                 strokeColor:myAtts['strokecolor'],
    564                 strokeWidth:myAtts['strokewidth'],
    565                 strokeOpacity:1.0,
    566                 straightFirst:false, straightLast:false, withLabel:true,
    567                 highlightStrokeColor:myAtts['highlightstrokecolor']
    568             });
    569             line[i].getLabelAnchor = get_anchor;
    570             t = get_transform(rad,i);
    571
    572             for(j=0; j<parents.length; j++) {
    573                 var data=parents[j][i];
    574                 pdata[j][i] = board.create('point',[data,0], {name:'',fixed:true,withlabel:false,visible:false});
    575                 pdata[j][i].addTransform(pdata[j][i], t);
    576             }
    577         }
    578
    579         polygons = new Array(len);
    580         for(i=0;i<len;i++) {
    581             myAtts['labelcolor'] = colorArray && colorArray[i%colorArray.length];
    582             myAtts['strokecolor'] = colorArray && colorArray[i%colorArray.length];
    583             myAtts['fillcolor'] = colorArray && colorArray[i%colorArray.length];
    584             polygons[i] = board.create('polygon',pdata[i], {
    585                 withLines:true,
    586                 withLabel:false,
    587                 fillColor:myAtts['fillcolor'],
    588                 fillOpacity:myAtts['fillopacity'],
    589                 highlightFillColor:myAtts['highlightfillcolor']
    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                 JXG.debug('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             myAtts['layer'] = 0;
    630
    631             // we have ncircles-1 intervals between ncircles circles
    632             dr = (ends[0]-starts[0])/(ncircles-1);
    633             for(i=0;i<ncircles;i++) {
    634                 pcircles[i] = board.create('point', [starts[0]+i*dr,0],
    635                     {name:clabelArray[i], size:0, fixed:true, withLabel:true, visible:true}
    636                     );
    637                 pcircles[i].addTransform(pcircles[i],t);
    638                 circles[i] = board.create('circle', [center,pcircles[i]], myAtts);
    639             }
    640
    641         }
    642         this.rendNode = polygons[0].rendNode;
    643         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.
    644     },
    645
    646     /**
    647      * Then, the update function of the renderer
    648      * is called.  Since a chart is only an abstract element,
    649      * containing other elements, this function is empty.
    650      */
    651     updateRenderer: function () { return this; },
    652
    653     /**
    654      * Update of the defining points
    655      */
    656     update: function () {
    657         if (this.needsUpdate) {
    658             this.updateDataArray();
    659         }
    660         return this;
    661     },
    662
    663     /**
    664      * For dynamic charts update
    665      * can be used to compute new entries
    666      * for the arrays this.dataX and
    667      * this.dataY. It is used in @see update.
    668      * Default is an empty method, can be overwritten
    669      * by the user.
    670      */
    671     updateDataArray: function () {}
    672 });
    673
    674 JXG.createChart = function(board, parents, attributes) {
    675
    676     if((parents.length == 1) && (typeof parents[0] == 'string')) {
    677         // todo: don't use document in here. let the user provide the dom node directly
    678         var table = document.getElementById(parents[0]),
    679             data, row, i, j, col, charts = [], w, x, showRows,
    680             originalWidth, name, strokeColor, fillColor, hStrokeColor, hFillColor, len, attr;
    681         if(JXG.exists(table)) {
    682             // extract the data
    683             attr = JXG.copyAttributes(attributes, board.options, 'chart');
    684
    685             table = (new JXG.DataSource()).loadFromTable(parents[0], attr.withheaders, attr.withheaders);
    686             data = table.data;
    687             col = table.columnHeaders;
    688             row = table.rowHeaders;
    689
    690             originalWidth = attr.width;
    691             name = attr.name;
    692             strokeColor = attr.strokecolor;
    693             fillColor = attr.fillcolor;
    694             hStrokeColor = attr.highlightstrokecolor;
    695             hFillColor = attr.highlightfillcolor;
    696
    697             board.suspendUpdate();
    698
    699             len = data.length;
    700             showRows = [];
    701             if (attr.rows && JXG.isArray(attr.rows)) {
    702                 for(i=0; i<len; i++) {
    703                     for(j=0; j<attr.rows.length; j++) {
    704                         if((attr.rows[j] == i) || (attr.withheaders && attr.rows[j] == row[i])) {
    705                             showRows.push(data[i]);
    706                             break;
    707                         }
    708                     }
    709                 }
    710             } else {
    711                 showRows = data;
    712             }
    713
    714             len = showRows.length;
    715
    716             for(i=0; i<len; i++) {
    717
    718                 x = [];
    719                 if(attr.chartstyle && attr.chartstyle.indexOf('bar') != -1) {
    720                     if(originalWidth) {
    721                         w = originalWidth;
    722                     } else {
    723                         w = 0.8;
    724                     }
    725                     x.push(1 - w/2. + (i+0.5)*w/(1.0*len));
    726                     for(j=1; j<showRows[i].length; j++) {
    727                         x.push(x[j-1] + 1);
    728                     }
    729                     attr.width = w/(1.0*len);
    730                 }
    731
    732                 if(name && name.length == len)
    733                     attr.name = name[i];
    734                 else if(attr.withheaders)
    735                     attr.name = col[i];
    736
    737                 if(strokeColor && strokeColor.length == len)
    738                     attr.strokecolor = strokeColor[i];
    739                 else
    740                     attr.strokecolor = JXG.hsv2rgb(((i+1)/(1.0*len))*360,0.9,0.6);
    741
    742                 if(fillColor && fillColor.length == len)
    743                     attr.fillcolor = fillColor[i];
    744                 else
    745                     attr.fillcolor = JXG.hsv2rgb(((i+1)/(1.0*len))*360,0.9,1.0);
    746
    747                 if(hStrokeColor && hStrokeColor.length == len)
    748                     attr.highlightstrokecolor = hStrokeColor[i];
    749                 else
    750                     attr.highlightstrokecolor = JXG.hsv2rgb(((i+1)/(1.0*len))*360,0.9,1.0);
    751
    752                 if(hFillColor && hFillColor.length == len)
    753                     attr.highlightfillcolor = hFillColor[i];
    754                 else
    755                     attr.highlightfillcolor = JXG.hsv2rgb(((i+1)/(1.0*len))*360,0.9,0.6);
    756
    757                 if(attr.chartstyle && attr.chartstyle.indexOf('bar') != -1) {
    758                     charts.push(new JXG.Chart(board, [x, showRows[i]], attr));
    759                 } else
    760                     charts.push(new JXG.Chart(board, [showRows[i]], attr));
    761             }
    762
    763             board.unsuspendUpdate();
    764
    765         }
    766         return charts;
    767     } else {
    768         attr = JXG.copyAttributes(attributes, board.options, 'chart');
    769         return new JXG.Chart(board, parents, attr);
    770     }
    771 };
    772
    773 JXG.JSXGraph.registerElement('chart', JXG.createChart);
    774
    775 /**
    776  * Legend for chart
    777  *
    778  **/
    779 JXG.Legend = function(board, coords, attributes) {
    780     var attr;
    781
    782     /* Call the constructor of GeometryElement */
    783     this.constructor();
    784
    785     attr = JXG.copyAttributes(attributes, board.options, 'legend');
    786
    787     this.board = board;
    788     this.coords = new JXG.Coords(JXG.COORDS_BY_USER, coords, this.board);
    789     this.myAtts = {};
    790     this.label_array = attr['labelarray'] || attr.labels;
    791     this.color_array = attr['colorarray'] || attr.colors;
    792     this.lines = [];
    793     this.myAtts['strokewidth'] = attr['strokewidth'] || 5;
    794     this.myAtts['straightfirst'] = false;
    795     this.myAtts['straightlast'] = false;
    796     this.myAtts['withlabel'] = true;
    797     this.myAtts['fixed'] = true;
    798     this.style = attr['legendstyle'] || attr.style;
    799
    800     switch(this.style) {
    801         case 'vertical':
    802             this.drawVerticalLegend(board, attr);
    803             break;
    804         default:
    805             throw new Error('JSXGraph: Unknown legend style: ' + this.style);
    806             break;
    807     }
    808 };
    809 JXG.Legend.prototype = new JXG.GeometryElement;
    810
    811 JXG.Legend.prototype.drawVerticalLegend = function(board, attributes) {
    812     var line_length = attributes['linelength'] || 1,
    813         offy = (attributes['rowheight'] || 20)/this.board.unitY,
    814         i;
    815
    816     for(i=0;i<this.label_array.length;i++) {
    817         this.myAtts['strokecolor'] = this.color_array[i];
    818         this.myAtts['highlightstrokecolor'] = this.color_array[i];
    819         this.myAtts['name'] = this.label_array[i];
    820         this.myAtts['label'] = {offsets:[10, 0]};
    821
    822         this.lines[i] = board.create('line',
    823                 [[this.coords.usrCoords[1],this.coords.usrCoords[2] - i*offy],
    824                 [this.coords.usrCoords[1] + line_length,this.coords.usrCoords[2] - i*offy]],
    825                 this.myAtts
    826                 );
    827         this.lines[i].getLabelAnchor = function() {
    828             this.setLabelRelativeCoords(this.visProp.label.offsets);
    829             return new JXG.Coords(JXG.COORDS_BY_USER, [this.point2.X(),this.point2.Y()],this.board);
    830         }
    831     }
    832 };
    833
    834 JXG.createLegend = function(board, parents, attributes) {
    835     //parents are coords of left top point of the legend
    836     var start_from = [0,0];
    837     if(JXG.exists(parents))
    838         if(parents.length == 2) {
    839             start_from = parents;
    840         }
    841     return new JXG.Legend(board, start_from, attributes);
    842 };
    843 JXG.JSXGraph.registerElement('legend', JXG.createLegend);
    844