      1 /*
      2     Copyright 2008-2018
      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 dual licensed under the GNU LGPL or MIT License.
     13
     14     You can redistribute it and/or modify it under the terms of the
     15
     16       * GNU Lesser General Public License as published by
     17         the Free Software Foundation, either version 3 of the License, or
     18         (at your option) any later version
     19       OR
     20       * MIT License: https://github.com/jsxgraph/jsxgraph/blob/master/LICENSE.MIT
     21
     22     JSXGraph is distributed in the hope that it will be useful,
     23     but WITHOUT ANY WARRANTY; without even the implied warranty of
     24     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
     25     GNU Lesser General Public License for more details.
     26
     27     You should have received a copy of the GNU Lesser General Public License and
     28     the MIT License along with JSXGraph. If not, see <http://www.gnu.org/licenses/>
     29     and <http://opensource.org/licenses/MIT/>.
     30  */
     31
     32
     33 /*global JXG: true, define: true, document: true*/
     34 /*jslint nomen: true, plusplus: true*/
     35
     36 /* depends:
     37  jxg
     38  math/numerics
     39  math/statistics
     40  base/constants
     41  base/coords
     42  base/element
     43  parser/datasource
     44  utils/color
     45  utils/type
     46  utils/env
     47   elements:
     48    curve
     49    spline
     50    functiongraph
     51    point
     52    text
     53    polygon
     54    sector
     55    transform
     56    line
     57    legend
     58    circle
     59  */
     60
     61 define([
     62     'jxg', 'math/numerics', 'math/statistics', 'base/constants', 'base/coords', 'base/element', 'parser/datasource',
     63     'utils/color', 'utils/type', 'utils/env', 'base/curve', 'base/point', 'base/text', 'base/polygon', 'element/sector',
     64     'base/transformation', 'base/line', 'base/circle'
     65 ], function (JXG, Numerics, Statistics, Const, Coords, GeometryElement, DataSource, Color, Type, Env, Curve, Point, Text,
     66         Polygon, Sector, Transform, Line, Circle) {
     67
     68     "use strict";
     69
     70     /**
     71      * Chart plotting
     72      */
     73     JXG.Chart = function (board, parents, attributes) {
     74         this.constructor(board, attributes);
     75
     76         var x, y, i, c, style, len;
     77
     78         if (!Type.isArray(parents) || parents.length === 0) {
     79             throw new Error('JSXGraph: Can\'t create a chart without data');
     80         }
     81
     82         /**
     83          * Contains pointers to the various subelements of the chart.
     84          */
     85         this.elements = [];
     86
     87         if (Type.isNumber(parents[0])) {
     88             // parents looks like [a,b,c,..]
     89             // x has to be filled
     90
     91             y = parents;
     92             x = [];
     93             for (i = 0; i < y.length; i++) {
     94                 x[i] = i + 1;
     95             }
     96         } else if (parents.length === 1 && Type.isArray(parents[0])) {
     97             // parents looks like [[a,b,c,..]]
     98             // x has to be filled
     99
    100             y = parents[0];
    101             x = [];
    102
    103             len = Type.evaluate(y).length;
    104             for (i = 0; i < len; i++) {
    105                 x[i] = i + 1;
    106             }
    107         } else if (parents.length === 2) {
    108             // parents looks like [[x0,x1,x2,...],[y1,y2,y3,...]]
    109             len = Math.min(parents[0].length, parents[1].length);
    110             x = parents[0].slice(0, len);
    111             y = parents[1].slice(0, len);
    112         }
    113
    114         if (Type.isArray(y) && y.length === 0) {
    115             throw new Error('JSXGraph: Can\'t create charts without data.');
    116         }
    117
    118         // does this really need to be done here? this should be done in createChart and then
    119         // there should be an extra chart for each chartstyle
    120         style = attributes.chartstyle.replace(/ /g, '').split(',');
    121         for (i = 0; i < style.length; i++) {
    122             switch (style[i]) {
    123             case 'bar':
    124                 c = this.drawBar(board, x, y, attributes);
    125                 break;
    126             case 'line':
    127                 c = this.drawLine(board, x, y, attributes);
    128                 break;
    129             case 'fit':
    130                 c = this.drawFit(board, x, y, attributes);
    131                 break;
    132             case 'spline':
    133                 c = this.drawSpline(board, x, y, attributes);
    134                 break;
    135             case 'pie':
    136                 c = this.drawPie(board, y, attributes);
    137                 break;
    138             case 'point':
    139                 c = this.drawPoints(board, x, y, attributes);
    140                 break;
    141             case 'radar':
    142                 c = this.drawRadar(board, parents, attributes);
    143                 break;
    144             }
    145             this.elements.push(c);
    146         }
    147         this.id = this.board.setId(this, 'Chart');
    148
    149         return this.elements;
    150     };
    151     JXG.Chart.prototype = new GeometryElement();
    152
    153     JXG.extend(JXG.Chart.prototype, /** @lends JXG.Chart.prototype */ {
    154         drawLine: function (board, x, y, attributes) {
    155             // we don't want the line chart to be filled
    156             attributes.fillcolor = 'none';
    157             attributes.highlightfillcolor = 'none';
    158
    159             return board.create('curve', [x, y], attributes);
    160         },
    161
    162         drawSpline: function (board, x, y, attributes) {
    163             // we don't want the spline chart to be filled
    164             attributes.fillColor = 'none';
    165             attributes.highlightfillcolor = 'none';
    166
    167             return board.create('spline', [x, y], attributes);
    168         },
    169
    170         drawFit: function (board, x, y, attributes) {
    171             var deg = attributes.degree;
    172
    173             deg = Math.max(parseInt(deg, 10), 1) || 1;
    174
    175             // never fill
    176             attributes.fillcolor = 'none';
    177             attributes.highlightfillcolor = 'none';
    178
    179             return board.create('functiongraph', [Numerics.regressionPolynomial(deg, x, y)], attributes);
    180         },
    181
    182         drawBar: function (board, x, y, attributes) {
    183             var i, strwidth, text, w, xp0, xp1, xp2, yp, colors,
    184                 pols = [],
    185                 p = [],
    186                 attr, attrSub,
    187
    188                 makeXpFun = function (i, f) {
    189                     return function () {
    190                         return x[i]() - f * w;
    191                     };
    192                 },
    193
    194                 hiddenPoint = {
    195                     fixed: true,
    196                     withLabel: false,
    197                     visible: false,
    198                     name: ''
    199                 };
    200
    201             attr = Type.copyAttributes(attributes, board.options, 'chart');
    202
    203             // Determine the width of the bars
    204             if (attr && attr.width) {  // width given
    205                 w = attr.width;
    206             } else {
    207                 if (x.length <= 1) {
    208                     w = 1;
    209                 } else {
    210                     // Find minimum distance between to bars.
    211                     w = x[1] - x[0];
    212                     for (i = 1; i < x.length - 1; i++) {
    213                         w = (x[i + 1] - x[i] < w) ? x[i + 1] - x[i] : w;
    214                     }
    215                 }
    216                 w *= 0.8;
    217             }
    218
    219             attrSub = Type.copyAttributes(attributes, board.options, 'chart', 'label');
    220
    221             for (i = 0; i < x.length; i++) {
    222                 if (Type.isFunction(x[i])) {
    223                     xp0 = makeXpFun(i, -0.5);
    224                     xp1 = makeXpFun(i, 0);
    225                     xp2 = makeXpFun(i, 0.5);
    226                 } else {
    227                     xp0 = x[i] - w * 0.5;
    228                     xp1 = x[i];
    229                     xp2 = x[i] + w * 0.5;
    230                 }
    231                 if (Type.isFunction(y[i])) {
    232                     yp = y[i]();
    233                 } else {
    234                     yp = y[i];
    235                 }
    236                 yp = y[i];
    237
    238                 if (attr.dir === 'horizontal') {  // horizontal bars
    239                     p[0] = board.create('point', [0, xp0], hiddenPoint);
    240                     p[1] = board.create('point', [yp, xp0], hiddenPoint);
    241                     p[2] = board.create('point', [yp, xp2], hiddenPoint);
    242                     p[3] = board.create('point', [0, xp2], hiddenPoint);
    243
    244                     if (Type.exists(attr.labels) && Type.exists(attr.labels[i])) {
    245                         attrSub.anchorY = 'middle';
    246                         text = board.create('text', [
    247                             yp,
    248                             xp1,
    249                             attr.labels[i]], attrSub);
    250                         text.visProp.anchorx = (function(txt) { return function() {
    251                             return (txt.X() >= 0) ? 'left' : 'right';
    252                         }; })(text);
    253
    254                     }
    255                 } else { // vertical bars
    256                     p[0] = board.create('point', [xp0, 0], hiddenPoint);
    257                     p[1] = board.create('point', [xp0, yp], hiddenPoint);
    258                     p[2] = board.create('point', [xp2, yp], hiddenPoint);
    259                     p[3] = board.create('point', [xp2, 0], hiddenPoint);
    260
    261                     if (Type.exists(attr.labels) && Type.exists(attr.labels[i])) {
    262                         attrSub.anchorX = 'middle';
    263
    264                         text = board.create('text', [
    265                             xp1,
    266                             yp,
    267                             attr.labels[i]], attrSub);
    268
    269                         text.visProp.anchory = (function(txt) {
    270                             return function() {
    271                                     return (txt.Y() >= 0) ? 'bottom' : 'top';
    272                                 };
    273                             })(text);
    274
    275                     }
    276                 }
    277
    278                 if (Type.isArray(attr.colors)) {
    279                     colors = attr.colors;
    280                     attr.fillcolor = colors[i % colors.length];
    281                 }
    282
    283                 pols[i] = board.create('polygon', p, attr);
    284                 if (Type.exists(attr.labels) && Type.exists(attr.labels[i])) {
    285                     pols[i].text = text;
    286                 }
    287             }
    288
    289             return pols;
    290         },
    291
    292         drawPoints: function (board, x, y, attributes) {
    293             var i,
    294                 points = [],
    295                 infoboxArray = attributes.infoboxarray;
    296
    297             attributes.fixed = true;
    298             attributes.name = '';
    299
    300             for (i = 0; i < x.length; i++) {
    301                 attributes.infoboxtext = infoboxArray ? infoboxArray[i % infoboxArray.length] : false;
    302                 points[i] = board.create('point', [x[i], y[i]], attributes);
    303             }
    304
    305             return points;
    306         },
    307
    308         drawPie: function (board, y, attributes) {
    309             var i, center,
    310                 p = [],
    311                 sector = [],
    312                 s = Statistics.sum(y),
    313                 colorArray = attributes.colors,
    314                 highlightColorArray = attributes.highlightcolors,
    315                 labelArray = attributes.labels,
    316                 r = attributes.radius || 4,
    317                 radius = r,
    318                 cent = attributes.center || [0, 0],
    319                 xc = cent[0],
    320                 yc = cent[1],
    321
    322                 makeRadPointFun = function (j, fun, xc) {
    323                     return function () {
    324                         var s, i, rad,
    325                             t = 0;
    326
    327                         for (i = 0; i <= j; i++) {
    328                             t += parseFloat(Type.evaluate(y[i]));
    329                         }
    330
    331                         s = t;
    332                         for (i = j + 1; i < y.length; i++) {
    333                             s += parseFloat(Type.evaluate(y[i]));
    334                         }
    335                         rad = (s !== 0) ? (2 * Math.PI * t / s) : 0;
    336
    337                         return radius() * Math[fun](rad) + xc;
    338                     };
    339                 },
    340
    341                 highlightHandleLabel = function (f, s) {
    342                     var dx = -this.point1.coords.usrCoords[1] + this.point2.coords.usrCoords[1],
    343                         dy = -this.point1.coords.usrCoords[2] + this.point2.coords.usrCoords[2];
    344
    345                     if (Type.exists(this.label)) {
    346                         this.label.rendNode.style.fontSize = (s * Type.evaluate(this.label.visProp.fontsize)) + 'px';
    347                         this.label.fullUpdate();
    348                     }
    349
    350                     this.point2.coords = new Coords(Const.COORDS_BY_USER, [
    351                         this.point1.coords.usrCoords[1] + dx * f,
    352                         this.point1.coords.usrCoords[2] + dy * f
    353                     ], this.board);
    354                     this.fullUpdate();
    355                 },
    356
    357                 highlightFun = function () {
    358                     if (!this.highlighted) {
    359                         this.highlighted = true;
    360                         this.board.highlightedObjects[this.id] = this;
    361                         this.board.renderer.highlight(this);
    362
    363                         highlightHandleLabel.call(this, 1.1, 2);
    364                     }
    365                 },
    366
    367                 noHighlightFun = function () {
    368                     if (this.highlighted) {
    369                         this.highlighted = false;
    370                         this.board.renderer.noHighlight(this);
    371
    372                         highlightHandleLabel.call(this, 0.90909090, 1);
    373                     }
    374                 },
    375
    376                 hiddenPoint = {
    377                     fixed: true,
    378                     withLabel: false,
    379                     visible: false,
    380                     name: ''
    381                 };
    382
    383             if (!Type.isArray(labelArray)) {
    384                 labelArray = [];
    385                 for (i = 0; i < y.length; i++) {
    386                     labelArray[i] = '';
    387                 }
    388             }
    389
    390             if (!Type.isFunction(r)) {
    391                 radius = function () {
    392                     return r;
    393                 };
    394             }
    395
    396             attributes.highlightonsector = attributes.highlightonsector || false;
    397             attributes.straightfirst = false;
    398             attributes.straightlast = false;
    399
    400             center = board.create('point', [xc, yc], hiddenPoint);
    401             p[0] = board.create('point', [
    402                 function () {
    403                     return radius() + xc;
    404                 },
    405                 function () {
    406                     return yc;
    407                 }
    408             ], hiddenPoint);
    409
    410             for (i = 0; i < y.length; i++) {
    411                 p[i + 1] = board.create('point', [makeRadPointFun(i, 'cos', xc), makeRadPointFun(i, 'sin', yc)], hiddenPoint);
    412
    413                 attributes.name = labelArray[i];
    414                 attributes.withlabel = attributes.name !== '';
    415                 attributes.fillcolor = colorArray && colorArray[i % colorArray.length];
    416                 attributes.labelcolor = colorArray && colorArray[i % colorArray.length];
    417                 attributes.highlightfillcolor = highlightColorArray && highlightColorArray[i % highlightColorArray.length];
    418
    419                 sector[i] = board.create('sector', [center, p[i], p[i + 1]], attributes);
    420
    421                 if (attributes.highlightonsector) {
    422                     // overwrite hasPoint so that the whole sector is used for highlighting
    423                     sector[i].hasPoint = sector[i].hasPointSector;
    424                 }
    425                 if (attributes.highlightbysize) {
    426                     sector[i].highlight = highlightFun;
    427
    428                     sector[i].noHighlight = noHighlightFun;
    429                 }
    430
    431             }
    432
    433             // Not enough! We need points, but this gives an error in setAttribute.
    434             return {sectors: sector, points: p, midpoint: center};
    435         },
    436
    437         /*
    438          * labelArray=[ row1, row2, row3 ]
    439          * paramArray=[ paramx, paramy, paramz ]
    440          * parents=[[x1, y1, z1], [x2, y2, z2], [x3, y3, z3]]
    441          */
    442         drawRadar: function (board, parents, attributes) {
    443             var i, j, paramArray, numofparams, maxes, mins,
    444                 la, pdata, ssa, esa, ssratio, esratio,
    445                 sshifts, eshifts, starts, ends,
    446                 labelArray, colorArray, highlightColorArray, radius, myAtts,
    447                 cent, xc, yc, center, start_angle, rad, p, line, t,
    448                 xcoord, ycoord, polygons, legend_position, circles, lxoff, lyoff,
    449                 cla, clabelArray, ncircles, pcircles, angle, dr, sw, data,
    450                 len = parents.length,
    451
    452                 get_anchor = function () {
    453                     var x1, x2, y1, y2,
    454                         relCoords = Type.evaluate(this.visProp.label.offset).slice(0);
    455
    456                     x1 = this.point1.X();
    457                     x2 = this.point2.X();
    458                     y1 = this.point1.Y();
    459                     y2 = this.point2.Y();
    460                     if (x2 < x1) {
    461                         relCoords[0] = -relCoords[0];
    462                     }
    463
    464                     if (y2 < y1) {
    465                         relCoords[1] = -relCoords[1];
    466                     }
    467
    468                     this.setLabelRelativeCoords(relCoords);
    469
    470                     return new Coords(Const.COORDS_BY_USER, [this.point2.X(), this.point2.Y()], this.board);
    471                 },
    472
    473                 get_transform = function (angle, i) {
    474                     var t, tscale, trot;
    475
    476                     t = board.create('transform', [-(starts[i] - sshifts[i]), 0], {type: 'translate'});
    477                     tscale = board.create('transform', [radius / ((ends[i] + eshifts[i]) - (starts[i] - sshifts[i])), 1], {type: 'scale'});
    478                     t.melt(tscale);
    479                     trot = board.create('transform', [angle], {type: 'rotate'});
    480                     t.melt(trot);
    481
    482                     return t;
    483                 };
    484
    485             if (len <= 0) {
    486                 JXG.debug("No data");
    487                 return;
    488             }
    489             // labels for axes
    490             paramArray = attributes.paramarray;
    491             if (!Type.exists(paramArray)) {
    492                 JXG.debug("Need paramArray attribute");
    493                 return;
    494             }
    495             numofparams = paramArray.length;
    496             if (numofparams <= 1) {
    497                 JXG.debug("Need more than 1 param");
    498                 return;
    499             }
    500
    501             for (i = 0; i < len; i++) {
    502                 if (numofparams !== parents[i].length) {
    503                     JXG.debug("Use data length equal to number of params (" + parents[i].length + " != " + numofparams + ")");
    504                     return;
    505                 }
    506             }
    507
    508             maxes = [];
    509             mins = [];
    510
    511             for (j = 0; j < numofparams; j++) {
    512                 maxes[j] = parents[0][j];
    513                 mins[j] = maxes[j];
    514             }
    515
    516             for (i = 1; i < len; i++) {
    517                 for (j = 0; j < numofparams; j++) {
    518                     if (parents[i][j] > maxes[j]) {
    519                         maxes[j] = parents[i][j];
    520                     }
    521
    522                     if (parents[i][j] < mins[j]) {
    523                         mins[j] = parents[i][j];
    524                     }
    525                 }
    526             }
    527
    528             la = [];
    529             pdata = [];
    530
    531             for (i = 0; i < len; i++) {
    532                 la[i] = '';
    533                 pdata[i] = [];
    534             }
    535
    536             ssa = [];
    537             esa = [];
    538
    539             // 0 <= Offset from chart center <=1
    540             ssratio = attributes.startshiftratio || 0;
    541             // 0 <= Offset from chart radius <=1
    542             esratio = attributes.endshiftratio || 0;
    543
    544             for (i = 0; i < numofparams; i++) {
    545                 ssa[i] = (maxes[i] - mins[i]) * ssratio;
    546                 esa[i] = (maxes[i] - mins[i]) * esratio;
    547             }
    548
    549             // Adjust offsets per each axis
    550             sshifts = attributes.startshiftarray || ssa;
    551             eshifts = attributes.endshiftarray || esa;
    552             // Values for inner circle, minimums by default
    553             starts = attributes.startarray || mins;
    554
    555             if (Type.exists(attributes.start)) {
    556                 for (i = 0; i < numofparams; i++) {
    557                     starts[i] = attributes.start;
    558                 }
    559             }
    560
    561             // Values for outer circle, maximums by default
    562             ends = attributes.endarray || maxes;
    563             if (Type.exists(attributes.end)) {
    564                 for (i = 0; i < numofparams; i++) {
    565                     ends[i] = attributes.end;
    566                 }
    567             }
    568
    569             if (sshifts.length !== numofparams) {
    570                 JXG.debug("Start shifts length is not equal to number of parameters");
    571                 return;
    572             }
    573
    574             if (eshifts.length !== numofparams) {
    575                 JXG.debug("End shifts length is not equal to number of parameters");
    576                 return;
    577             }
    578
    579             if (starts.length !== numofparams) {
    580                 JXG.debug("Starts length is not equal to number of parameters");
    581                 return;
    582             }
    583
    584             if (ends.length !== numofparams) {
    585                 JXG.debug("Ends length is not equal to number of parameters");
    586                 return;
    587             }
    588
    589             // labels for legend
    590             labelArray = attributes.labelarray || la;
    591             colorArray = attributes.colors;
    592             highlightColorArray = attributes.highlightcolors;
    593             radius = attributes.radius || 10;
    594             sw = attributes.strokewidth || 1;
    595
    596             if (!Type.exists(attributes.highlightonsector)) {
    597                 attributes.highlightonsector = false;
    598             }
    599
    600             myAtts = {
    601                 name: attributes.name,
    602                 id: attributes.id,
    603                 strokewidth: sw,
    604                 polystrokewidth: attributes.polystrokewidth || sw,
    605                 strokecolor: attributes.strokecolor || 'black',
    606                 straightfirst: false,
    607                 straightlast: false,
    608                 fillcolor: attributes.fillColor || '#FFFF88',
    609                 fillopacity: attributes.fillOpacity || 0.4,
    610                 highlightfillcolor: attributes.highlightFillColor || '#FF7400',
    611                 highlightstrokecolor: attributes.highlightStrokeColor || 'black',
    612                 gradient: attributes.gradient || 'none'
    613             };
    614
    615             cent = attributes.center || [0, 0];
    616             xc = cent[0];
    617             yc = cent[1];
    618             center = board.create('point', [xc, yc], {name: '', fixed: true, withlabel: false, visible: false});
    619             start_angle = Math.PI / 2 - Math.PI / numofparams;
    620             start_angle = attributes.startangle || 0;
    621             rad = start_angle;
    622             p = [];
    623             line = [];
    624
    625             for (i = 0; i < numofparams; i++) {
    626                 rad += 2 * Math.PI / numofparams;
    627                 xcoord = radius * Math.cos(rad) + xc;
    628                 ycoord = radius * Math.sin(rad) + yc;
    629
    630                 p[i] = board.create('point', [xcoord, ycoord], {name: '', fixed: true, withlabel: false, visible: false});
    631                 line[i] = board.create('line', [center, p[i]], {
    632                     name: paramArray[i],
    633                     strokeColor: myAtts.strokecolor,
    634                     strokeWidth: myAtts.strokewidth,
    635                     strokeOpacity: 1.0,
    636                     straightFirst: false,
    637                     straightLast: false,
    638                     withLabel: true,
    639                     highlightStrokeColor: myAtts.highlightstrokecolor
    640                 });
    641                 line[i].getLabelAnchor = get_anchor;
    642                 t = get_transform(rad, i);
    643
    644                 for (j = 0; j < parents.length; j++) {
    645                     data = parents[j][i];
    646                     pdata[j][i] = board.create('point', [data, 0], {name: '', fixed: true, withlabel: false, visible: false});
    647                     pdata[j][i].addTransform(pdata[j][i], t);
    648                 }
    649             }
    650
    651             polygons = [];
    652             for (i = 0; i < len; i++) {
    653                 myAtts.labelcolor = colorArray && colorArray[i % colorArray.length];
    654                 myAtts.strokecolor = colorArray && colorArray[i % colorArray.length];
    655                 myAtts.fillcolor = colorArray && colorArray[i % colorArray.length];
    656                 polygons[i] = board.create('polygon', pdata[i], {
    657                     withLines: true,
    658                     withLabel: false,
    659                     fillColor: myAtts.fillcolor,
    660                     fillOpacity: myAtts.fillopacity,
    661                     highlightFillColor: myAtts.highlightfillcolor
    662                 });
    663
    664                 for (j = 0; j < numofparams; j++) {
    665                     polygons[i].borders[j].setAttribute('strokecolor:' + colorArray[i % colorArray.length]);
    666                     polygons[i].borders[j].setAttribute('strokewidth:' + myAtts.polystrokewidth);
    667                 }
    668             }
    669
    670             legend_position = attributes.legendposition || 'none';
    671             switch (legend_position) {
    672             case 'right':
    673                 lxoff = attributes.legendleftoffset || 2;
    674                 lyoff = attributes.legendtopoffset || 1;
    675
    676                 this.legend = board.create('legend', [xc + radius + lxoff, yc + radius - lyoff], {
    677                     labels: labelArray,
    678                     colors: colorArray
    679                 });
    680                 break;
    681             case 'none':
    682                 break;
    683             default:
    684                 JXG.debug('Unknown legend position');
    685             }
    686
    687             circles = [];
    688             if (attributes.showcircles) {
    689                 cla = [];
    690                 for (i = 0; i < 6; i++) {
    691                     cla[i] = 20 * i;
    692                 }
    693                 cla[0] = "0";
    694                 clabelArray = attributes.circlelabelarray || cla;
    695                 ncircles = clabelArray.length;
    696
    697                 if (ncircles < 2) {
    698                     JXG.debug("Too less circles");
    699                     return;
    700                 }
    701
    702                 pcircles = [];
    703                 angle = start_angle + Math.PI / numofparams;
    704                 t = get_transform(angle, 0);
    705
    706                 myAtts.fillcolor = 'none';
    707                 myAtts.highlightfillcolor = 'none';
    708                 myAtts.strokecolor = attributes.strokecolor || 'black';
    709                 myAtts.strokewidth = attributes.circlestrokewidth || 0.5;
    710                 myAtts.layer = 0;
    711
    712                 // we have ncircles-1 intervals between ncircles circles
    713                 dr = (ends[0] - starts[0]) / (ncircles - 1);
    714
    715                 for (i = 0; i < ncircles; i++) {
    716                     pcircles[i] = board.create('point', [starts[0] + i * dr, 0], {
    717                         name: clabelArray[i],
    718                         size: 0,
    719                         fixed: true,
    720                         withLabel: true,
    721                         visible: true
    722                     });
    723                     pcircles[i].addTransform(pcircles[i], t);
    724                     circles[i] = board.create('circle', [center, pcircles[i]], myAtts);
    725                 }
    726
    727             }
    728             this.rendNode = polygons[0].rendNode;
    729             return {
    730                 circles: circles,
    731                 lines: line,
    732                 points: pdata,
    733                 midpoint: center,
    734                 polygons: polygons
    735             };
    736         },
    737
    738         /**
    739          * Then, the update function of the renderer
    740          * is called.  Since a chart is only an abstract element,
    741          * containing other elements, this function is empty.
    742          */
    743         updateRenderer: function () {
    744             return this;
    745         },
    746
    747         /**
    748          * Update of the defining points
    749          */
    750         update: function () {
    751             if (this.needsUpdate) {
    752                 this.updateDataArray();
    753             }
    754
    755             return this;
    756         },
    757
    758         /**
    759          * For dynamic charts update
    760          * can be used to compute new entries
    761          * for the arrays this.dataX and
    762          * this.dataY. It is used in @see update.
    763          * Default is an empty method, can be overwritten
    764          * by the user.
    765          */
    766         updateDataArray: function () {}
    767     });
    768
    769     JXG.createChart = function (board, parents, attributes) {
    770         var data, row, i, j, col,
    771             charts = [],
    772             w, x, showRows, attr,
    773             originalWidth, name, strokeColor, fillColor,
    774             hStrokeColor, hFillColor, len,
    775             table = Env.isBrowser ? board.document.getElementById(parents[0]) : null;
    776
    777         if ((parents.length === 1) && (Type.isString(parents[0]))) {
    778             if (Type.exists(table)) {
    779                 // extract the data
    780                 attr = Type.copyAttributes(attributes, board.options, 'chart');
    781
    782                 table = (new DataSource()).loadFromTable(parents[0], attr.withheaders, attr.withheaders);
    783                 data = table.data;
    784                 col = table.columnHeaders;
    785                 row = table.rowHeaders;
    786
    787                 originalWidth = attr.width;
    788                 name = attr.name;
    789                 strokeColor = attr.strokecolor;
    790                 fillColor = attr.fillcolor;
    791                 hStrokeColor = attr.highlightstrokecolor;
    792                 hFillColor = attr.highlightfillcolor;
    793
    794                 board.suspendUpdate();
    795
    796                 len = data.length;
    797                 showRows = [];
    798                 if (attr.rows && Type.isArray(attr.rows)) {
    799                     for (i = 0; i < len; i++) {
    800                         for (j = 0; j < attr.rows.length; j++) {
    801                             if ((attr.rows[j] === i) || (attr.withheaders && attr.rows[j] === row[i])) {
    802                                 showRows.push(data[i]);
    803                                 break;
    804                             }
    805                         }
    806                     }
    807                 } else {
    808                     showRows = data;
    809                 }
    810
    811                 len = showRows.length;
    812
    813                 for (i = 0; i < len; i++) {
    814
    815                     x = [];
    816                     if (attr.chartstyle && attr.chartstyle.indexOf('bar') !== -1) {
    817                         if (originalWidth) {
    818                             w = originalWidth;
    819                         } else {
    820                             w = 0.8;
    821                         }
    822
    823                         x.push(1 - w / 2 + (i + 0.5) * w / len);
    824
    825                         for (j = 1; j < showRows[i].length; j++) {
    826                             x.push(x[j - 1] + 1);
    827                         }
    828
    829                         attr.width = w / len;
    830                     }
    831
    832                     if (name && name.length === len) {
    833                         attr.name = name[i];
    834                     } else if (attr.withheaders) {
    835                         attr.name = col[i];
    836                     }
    837
    838                     if (strokeColor && strokeColor.length === len) {
    839                         attr.strokecolor = strokeColor[i];
    840                     } else {
    841                         attr.strokecolor = Color.hsv2rgb(((i + 1) / len) * 360, 0.9, 0.6);
    842                     }
    843
    844                     if (fillColor && fillColor.length === len) {
    845                         attr.fillcolor = fillColor[i];
    846                     } else {
    847                         attr.fillcolor = Color.hsv2rgb(((i + 1) / len) * 360, 0.9, 1.0);
    848                     }
    849
    850                     if (hStrokeColor && hStrokeColor.length === len) {
    851                         attr.highlightstrokecolor = hStrokeColor[i];
    852                     } else {
    853                         attr.highlightstrokecolor = Color.hsv2rgb(((i + 1) / len) * 360, 0.9, 1.0);
    854                     }
    855
    856                     if (hFillColor && hFillColor.length === len) {
    857                         attr.highlightfillcolor = hFillColor[i];
    858                     } else {
    859                         attr.highlightfillcolor = Color.hsv2rgb(((i + 1) / len) * 360, 0.9, 0.6);
    860                     }
    861
    862                     if (attr.chartstyle && attr.chartstyle.indexOf('bar') !== -1) {
    863                         charts.push(new JXG.Chart(board, [x, showRows[i]], attr));
    864                     } else {
    865                         charts.push(new JXG.Chart(board, [showRows[i]], attr));
    866                     }
    867                 }
    868
    869                 board.unsuspendUpdate();
    870
    871             }
    872             return charts;
    873         }
    874
    875         attr = Type.copyAttributes(attributes, board.options, 'chart');
    876         return new JXG.Chart(board, parents, attr);
    877     };
    878
    879     JXG.registerElement('chart', JXG.createChart);
    880
    881     /**
    882      * Legend for chart
    883      *
    884      **/
    885     JXG.Legend = function (board, coords, attributes) {
    886         var attr;
    887
    888         /* Call the constructor of GeometryElement */
    889         this.constructor();
    890
    891         attr = Type.copyAttributes(attributes, board.options, 'legend');
    892
    893         this.board = board;
    894         this.coords = new Coords(Const.COORDS_BY_USER, coords, this.board);
    895         this.myAtts = {};
    896         this.label_array = attr.labelarray || attr.labels;
    897         this.color_array = attr.colorarray || attr.colors;
    898         this.lines = [];
    899         this.myAtts.strokewidth = attr.strokewidth || 5;
    900         this.myAtts.straightfirst = false;
    901         this.myAtts.straightlast = false;
    902         this.myAtts.withlabel = true;
    903         this.myAtts.fixed = true;
    904         this.style = attr.legendstyle || attr.style;
    905
    906         if (this.style === 'vertical') {
    907             this.drawVerticalLegend(board, attr);
    908         } else {
    909             throw new Error('JSXGraph: Unknown legend style: ' + this.style);
    910         }
    911     };
    912
    913     JXG.Legend.prototype = new GeometryElement();
    914
    915     JXG.Legend.prototype.drawVerticalLegend = function (board, attributes) {
    916         var i,
    917             line_length = attributes.linelength || 1,
    918             offy = (attributes.rowheight || 20) / this.board.unitY,
    919
    920             getLabelAnchor = function () {
    921                 this.setLabelRelativeCoords(this.visProp.label.offset);
    922                 return new Coords(Const.COORDS_BY_USER, [this.point2.X(), this.point2.Y()], this.board);
    923             };
    924
    925         for (i = 0; i < this.label_array.length; i++) {
    926             this.myAtts.strokecolor = this.color_array[i];
    927             this.myAtts.highlightstrokecolor = this.color_array[i];
    928             this.myAtts.name = this.label_array[i];
    929             this.myAtts.label = {
    930                 offset: [10, 0],
    931                 strokeColor: this.color_array[i],
    932                 strokeWidth: this.myAtts.strokewidth
    933             };
    934
    935             this.lines[i] = board.create('line', [
    936                 [this.coords.usrCoords[1], this.coords.usrCoords[2] - i * offy],
    937                 [this.coords.usrCoords[1] + line_length, this.coords.usrCoords[2] - i * offy]],
    938                 this.myAtts);
    939
    940             this.lines[i].getLabelAnchor = getLabelAnchor;
    941
    942         }
    943     };
    944
    945     JXG.createLegend = function (board, parents, attributes) {
    946         //parents are coords of left top point of the legend
    947         var start_from = [0, 0];
    948
    949         if (Type.exists(parents)) {
    950             if (parents.length === 2) {
    951                 start_from = parents;
    952             }
    953         }
    954
    955         return new JXG.Legend(board, start_from, attributes);
    956     };
    957
    958     JXG.registerElement('legend', JXG.createLegend);
    959
    960     return {
    961         Chart: JXG.Chart,
    962         Legend: JXG.Legend,
    963         createChart: JXG.createChart,
    964         createLegend: JXG.createLegend
    965     };
    966 });
    967