      1 /*
      2     Copyright 2008-2017
      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) { return function() {
    270                             return (txt.Y() >= 0) ? 'bottom' : 'top';
    271                         }; })(text);
    272
    273                     }
    274                 }
    275
    276                 if (Type.isArray(attr.colors)) {
    277                     colors = attr.colors;
    278                     attr.fillcolor = colors[i % colors.length];
    279                 }
    280
    281                 pols[i] = board.create('polygon', p, attr);
    282                 if (Type.exists(attr.labels) && Type.exists(attr.labels[i])) {
    283                     pols[i].text = text;
    284                 }
    285             }
    286
    287             return pols;
    288         },
    289
    290         drawPoints: function (board, x, y, attributes) {
    291             var i,
    292                 points = [],
    293                 infoboxArray = attributes.infoboxarray;
    294
    295             attributes.fixed = true;
    296             attributes.name = '';
    297
    298             for (i = 0; i < x.length; i++) {
    299                 attributes.infoboxtext = infoboxArray ? infoboxArray[i % infoboxArray.length] : false;
    300                 points[i] = board.create('point', [x[i], y[i]], attributes);
    301             }
    302
    303             return points;
    304         },
    305
    306         drawPie: function (board, y, attributes) {
    307             var i, center,
    308                 p = [],
    309                 sector = [],
    310                 s = Statistics.sum(y),
    311                 colorArray = attributes.colors,
    312                 highlightColorArray = attributes.highlightcolors,
    313                 labelArray = attributes.labels,
    314                 r = attributes.radius || 4,
    315                 radius = r,
    316                 cent = attributes.center || [0, 0],
    317                 xc = cent[0],
    318                 yc = cent[1],
    319
    320                 makeRadPointFun = function (j, fun, xc) {
    321                     return function () {
    322                         var s, i, rad,
    323                             t = 0;
    324
    325                         for (i = 0; i <= j; i++) {
    326                             t += parseFloat(Type.evaluate(y[i]));
    327                         }
    328
    329                         s = t;
    330                         for (i = j + 1; i < y.length; i++) {
    331                             s += parseFloat(Type.evaluate(y[i]));
    332                         }
    333                         rad = (s !== 0) ? (2 * Math.PI * t / s) : 0;
    334
    335                         return radius() * Math[fun](rad) + xc;
    336                     };
    337                 },
    338
    339                 highlightHandleLabel = function (f, s) {
    340                     var dx = -this.point1.coords.usrCoords[1] + this.point2.coords.usrCoords[1],
    341                         dy = -this.point1.coords.usrCoords[2] + this.point2.coords.usrCoords[2];
    342
    343                     if (Type.exists(this.label)) {
    344                         this.label.rendNode.style.fontSize = (s * Type.evaluate(this.label.visProp.fontsize)) + 'px';
    345                         this.label.fullUpdate();
    346                     }
    347
    348                     this.point2.coords = new Coords(Const.COORDS_BY_USER, [
    349                         this.point1.coords.usrCoords[1] + dx * f,
    350                         this.point1.coords.usrCoords[2] + dy * f
    351                     ], this.board);
    352                     this.fullUpdate();
    353                 },
    354
    355                 highlightFun = function () {
    356                     if (!this.highlighted) {
    357                         this.highlighted = true;
    358                         this.board.highlightedObjects[this.id] = this;
    359                         this.board.renderer.highlight(this);
    360
    361                         highlightHandleLabel.call(this, 1.1, 2);
    362                     }
    363                 },
    364
    365                 noHighlightFun = function () {
    366                     if (this.highlighted) {
    367                         this.highlighted = false;
    368                         this.board.renderer.noHighlight(this);
    369
    370                         highlightHandleLabel.call(this, 0.90909090, 1);
    371                     }
    372                 },
    373
    374                 hiddenPoint = {
    375                     fixed: true,
    376                     withLabel: false,
    377                     visible: false,
    378                     name: ''
    379                 };
    380
    381             if (!Type.isArray(labelArray)) {
    382                 labelArray = [];
    383                 for (i = 0; i < y.length; i++) {
    384                     labelArray[i] = '';
    385                 }
    386             }
    387
    388             if (!Type.isFunction(r)) {
    389                 radius = function () {
    390                     return r;
    391                 };
    392             }
    393
    394             attributes.highlightonsector = attributes.highlightonsector || false;
    395             attributes.straightfirst = false;
    396             attributes.straightlast = false;
    397
    398             center = board.create('point', [xc, yc], hiddenPoint);
    399             p[0] = board.create('point', [
    400                 function () {
    401                     return radius() + xc;
    402                 },
    403                 function () {
    404                     return yc;
    405                 }
    406             ], hiddenPoint);
    407
    408             for (i = 0; i < y.length; i++) {
    409                 p[i + 1] = board.create('point', [makeRadPointFun(i, 'cos', xc), makeRadPointFun(i, 'sin', yc)], hiddenPoint);
    410
    411                 attributes.name = labelArray[i];
    412                 attributes.withlabel = attributes.name !== '';
    413                 attributes.fillcolor = colorArray && colorArray[i % colorArray.length];
    414                 attributes.labelcolor = colorArray && colorArray[i % colorArray.length];
    415                 attributes.highlightfillcolor = highlightColorArray && highlightColorArray[i % highlightColorArray.length];
    416
    417                 sector[i] = board.create('sector', [center, p[i], p[i + 1]], attributes);
    418
    419                 if (attributes.highlightonsector) {
    420                     // overwrite hasPoint so that the whole sector is used for highlighting
    421                     sector[i].hasPoint = sector[i].hasPointSector;
    422                 }
    423                 if (attributes.highlightbysize) {
    424                     sector[i].highlight = highlightFun;
    425
    426                     sector[i].noHighlight = noHighlightFun;
    427                 }
    428
    429             }
    430
    431             // Not enough! We need points, but this gives an error in setAttribute.
    432             return {sectors: sector, points: p, midpoint: center};
    433         },
    434
    435         /*
    436          * labelArray=[ row1, row2, row3 ]
    437          * paramArray=[ paramx, paramy, paramz ]
    438          * parents=[[x1, y1, z1], [x2, y2, z2], [x3, y3, z3]]
    439          */
    440         drawRadar: function (board, parents, attributes) {
    441             var i, j, paramArray, numofparams, maxes, mins,
    442                 la, pdata, ssa, esa, ssratio, esratio,
    443                 sshifts, eshifts, starts, ends,
    444                 labelArray, colorArray, highlightColorArray, radius, myAtts,
    445                 cent, xc, yc, center, start_angle, rad, p, line, t,
    446                 xcoord, ycoord, polygons, legend_position, circles, lxoff, lyoff,
    447                 cla, clabelArray, ncircles, pcircles, angle, dr, sw, data,
    448                 len = parents.length,
    449
    450                 get_anchor = function () {
    451                     var x1, x2, y1, y2,
    452                         relCoords = Type.evaluate(this.visProp.label.offset).slice(0);
    453
    454                     x1 = this.point1.X();
    455                     x2 = this.point2.X();
    456                     y1 = this.point1.Y();
    457                     y2 = this.point2.Y();
    458                     if (x2 < x1) {
    459                         relCoords[0] = -relCoords[0];
    460                     }
    461
    462                     if (y2 < y1) {
    463                         relCoords[1] = -relCoords[1];
    464                     }
    465
    466                     this.setLabelRelativeCoords(relCoords);
    467
    468                     return new Coords(Const.COORDS_BY_USER, [this.point2.X(), this.point2.Y()], this.board);
    469                 },
    470
    471                 get_transform = function (angle, i) {
    472                     var t, tscale, trot;
    473
    474                     t = board.create('transform', [-(starts[i] - sshifts[i]), 0], {type: 'translate'});
    475                     tscale = board.create('transform', [radius / ((ends[i] + eshifts[i]) - (starts[i] - sshifts[i])), 1], {type: 'scale'});
    476                     t.melt(tscale);
    477                     trot = board.create('transform', [angle], {type: 'rotate'});
    478                     t.melt(trot);
    479
    480                     return t;
    481                 };
    482
    483             if (len <= 0) {
    484                 JXG.debug("No data");
    485                 return;
    486             }
    487             // labels for axes
    488             paramArray = attributes.paramarray;
    489             if (!Type.exists(paramArray)) {
    490                 JXG.debug("Need paramArray attribute");
    491                 return;
    492             }
    493             numofparams = paramArray.length;
    494             if (numofparams <= 1) {
    495                 JXG.debug("Need more than 1 param");
    496                 return;
    497             }
    498
    499             for (i = 0; i < len; i++) {
    500                 if (numofparams !== parents[i].length) {
    501                     JXG.debug("Use data length equal to number of params (" + parents[i].length + " != " + numofparams + ")");
    502                     return;
    503                 }
    504             }
    505
    506             maxes = [];
    507             mins = [];
    508
    509             for (j = 0; j < numofparams; j++) {
    510                 maxes[j] = parents[0][j];
    511                 mins[j] = maxes[j];
    512             }
    513
    514             for (i = 1; i < len; i++) {
    515                 for (j = 0; j < numofparams; j++) {
    516                     if (parents[i][j] > maxes[j]) {
    517                         maxes[j] = parents[i][j];
    518                     }
    519
    520                     if (parents[i][j] < mins[j]) {
    521                         mins[j] = parents[i][j];
    522                     }
    523                 }
    524             }
    525
    526             la = [];
    527             pdata = [];
    528
    529             for (i = 0; i < len; i++) {
    530                 la[i] = '';
    531                 pdata[i] = [];
    532             }
    533
    534             ssa = [];
    535             esa = [];
    536
    537             // 0 <= Offset from chart center <=1
    538             ssratio = attributes.startshiftratio || 0;
    539             // 0 <= Offset from chart radius <=1
    540             esratio = attributes.endshiftratio || 0;
    541
    542             for (i = 0; i < numofparams; i++) {
    543                 ssa[i] = (maxes[i] - mins[i]) * ssratio;
    544                 esa[i] = (maxes[i] - mins[i]) * esratio;
    545             }
    546
    547             // Adjust offsets per each axis
    548             sshifts = attributes.startshiftarray || ssa;
    549             eshifts = attributes.endshiftarray || esa;
    550             // Values for inner circle, minimums by default
    551             starts = attributes.startarray || mins;
    552
    553             if (Type.exists(attributes.start)) {
    554                 for (i = 0; i < numofparams; i++) {
    555                     starts[i] = attributes.start;
    556                 }
    557             }
    558
    559             // Values for outer circle, maximums by default
    560             ends = attributes.endarray || maxes;
    561             if (Type.exists(attributes.end)) {
    562                 for (i = 0; i < numofparams; i++) {
    563                     ends[i] = attributes.end;
    564                 }
    565             }
    566
    567             if (sshifts.length !== numofparams) {
    568                 JXG.debug("Start shifts length is not equal to number of parameters");
    569                 return;
    570             }
    571
    572             if (eshifts.length !== numofparams) {
    573                 JXG.debug("End shifts length is not equal to number of parameters");
    574                 return;
    575             }
    576
    577             if (starts.length !== numofparams) {
    578                 JXG.debug("Starts length is not equal to number of parameters");
    579                 return;
    580             }
    581
    582             if (ends.length !== numofparams) {
    583                 JXG.debug("Ends length is not equal to number of parameters");
    584                 return;
    585             }
    586
    587             // labels for legend
    588             labelArray = attributes.labelarray || la;
    589             colorArray = attributes.colors;
    590             highlightColorArray = attributes.highlightcolors;
    591             radius = attributes.radius || 10;
    592             sw = attributes.strokewidth || 1;
    593
    594             if (!Type.exists(attributes.highlightonsector)) {
    595                 attributes.highlightonsector = false;
    596             }
    597
    598             myAtts = {
    599                 name: attributes.name,
    600                 id: attributes.id,
    601                 strokewidth: sw,
    602                 polystrokewidth: attributes.polystrokewidth || sw,
    603                 strokecolor: attributes.strokecolor || 'black',
    604                 straightfirst: false,
    605                 straightlast: false,
    606                 fillcolor: attributes.fillColor || '#FFFF88',
    607                 fillopacity: attributes.fillOpacity || 0.4,
    608                 highlightfillcolor: attributes.highlightFillColor || '#FF7400',
    609                 highlightstrokecolor: attributes.highlightStrokeColor || 'black',
    610                 gradient: attributes.gradient || 'none'
    611             };
    612
    613             cent = attributes.center || [0, 0];
    614             xc = cent[0];
    615             yc = cent[1];
    616             center = board.create('point', [xc, yc], {name: '', fixed: true, withlabel: false, visible: false});
    617             start_angle = Math.PI / 2 - Math.PI / numofparams;
    618             start_angle = attributes.startangle || 0;
    619             rad = start_angle;
    620             p = [];
    621             line = [];
    622
    623             for (i = 0; i < numofparams; i++) {
    624                 rad += 2 * Math.PI / numofparams;
    625                 xcoord = radius * Math.cos(rad) + xc;
    626                 ycoord = radius * Math.sin(rad) + yc;
    627
    628                 p[i] = board.create('point', [xcoord, ycoord], {name: '', fixed: true, withlabel: false, visible: false});
    629                 line[i] = board.create('line', [center, p[i]], {
    630                     name: paramArray[i],
    631                     strokeColor: myAtts.strokecolor,
    632                     strokeWidth: myAtts.strokewidth,
    633                     strokeOpacity: 1.0,
    634                     straightFirst: false,
    635                     straightLast: false,
    636                     withLabel: true,
    637                     highlightStrokeColor: myAtts.highlightstrokecolor
    638                 });
    639                 line[i].getLabelAnchor = get_anchor;
    640                 t = get_transform(rad, i);
    641
    642                 for (j = 0; j < parents.length; j++) {
    643                     data = parents[j][i];
    644                     pdata[j][i] = board.create('point', [data, 0], {name: '', fixed: true, withlabel: false, visible: false});
    645                     pdata[j][i].addTransform(pdata[j][i], t);
    646                 }
    647             }
    648
    649             polygons = [];
    650             for (i = 0; i < len; i++) {
    651                 myAtts.labelcolor = colorArray && colorArray[i % colorArray.length];
    652                 myAtts.strokecolor = colorArray && colorArray[i % colorArray.length];
    653                 myAtts.fillcolor = colorArray && colorArray[i % colorArray.length];
    654                 polygons[i] = board.create('polygon', pdata[i], {
    655                     withLines: true,
    656                     withLabel: false,
    657                     fillColor: myAtts.fillcolor,
    658                     fillOpacity: myAtts.fillopacity,
    659                     highlightFillColor: myAtts.highlightfillcolor
    660                 });
    661
    662                 for (j = 0; j < numofparams; j++) {
    663                     polygons[i].borders[j].setAttribute('strokecolor:' + colorArray[i % colorArray.length]);
    664                     polygons[i].borders[j].setAttribute('strokewidth:' + myAtts.polystrokewidth);
    665                 }
    666             }
    667
    668             legend_position = attributes.legendposition || 'none';
    669             switch (legend_position) {
    670             case 'right':
    671                 lxoff = attributes.legendleftoffset || 2;
    672                 lyoff = attributes.legendtopoffset || 1;
    673
    674                 this.legend = board.create('legend', [xc + radius + lxoff, yc + radius - lyoff], {
    675                     labels: labelArray,
    676                     colors: colorArray
    677                 });
    678                 break;
    679             case 'none':
    680                 break;
    681             default:
    682                 JXG.debug('Unknown legend position');
    683             }
    684
    685             circles = [];
    686             if (attributes.showcircles) {
    687                 cla = [];
    688                 for (i = 0; i < 6; i++) {
    689                     cla[i] = 20 * i;
    690                 }
    691                 cla[0] = "0";
    692                 clabelArray = attributes.circlelabelarray || cla;
    693                 ncircles = clabelArray.length;
    694
    695                 if (ncircles < 2) {
    696                     JXG.debug("Too less circles");
    697                     return;
    698                 }
    699
    700                 pcircles = [];
    701                 angle = start_angle + Math.PI / numofparams;
    702                 t = get_transform(angle, 0);
    703
    704                 myAtts.fillcolor = 'none';
    705                 myAtts.highlightfillcolor = 'none';
    706                 myAtts.strokecolor = attributes.strokecolor || 'black';
    707                 myAtts.strokewidth = attributes.circlestrokewidth || 0.5;
    708                 myAtts.layer = 0;
    709
    710                 // we have ncircles-1 intervals between ncircles circles
    711                 dr = (ends[0] - starts[0]) / (ncircles - 1);
    712
    713                 for (i = 0; i < ncircles; i++) {
    714                     pcircles[i] = board.create('point', [starts[0] + i * dr, 0], {
    715                         name: clabelArray[i],
    716                         size: 0,
    717                         fixed: true,
    718                         withLabel: true,
    719                         visible: true
    720                     });
    721                     pcircles[i].addTransform(pcircles[i], t);
    722                     circles[i] = board.create('circle', [center, pcircles[i]], myAtts);
    723                 }
    724
    725             }
    726             this.rendNode = polygons[0].rendNode;
    727             return {
    728                 circles: circles,
    729                 lines: line,
    730                 points: pdata,
    731                 midpoint: center,
    732                 polygons: polygons
    733             };
    734         },
    735
    736         /**
    737          * Then, the update function of the renderer
    738          * is called.  Since a chart is only an abstract element,
    739          * containing other elements, this function is empty.
    740          */
    741         updateRenderer: function () {
    742             return this;
    743         },
    744
    745         /**
    746          * Update of the defining points
    747          */
    748         update: function () {
    749             if (this.needsUpdate) {
    750                 this.updateDataArray();
    751             }
    752
    753             return this;
    754         },
    755
    756         /**
    757          * For dynamic charts update
    758          * can be used to compute new entries
    759          * for the arrays this.dataX and
    760          * this.dataY. It is used in @see update.
    761          * Default is an empty method, can be overwritten
    762          * by the user.
    763          */
    764         updateDataArray: function () {}
    765     });
    766
    767     JXG.createChart = function (board, parents, attributes) {
    768         var data, row, i, j, col,
    769             charts = [],
    770             w, x, showRows, attr,
    771             originalWidth, name, strokeColor, fillColor,
    772             hStrokeColor, hFillColor, len,
    773             table = Env.isBrowser ? board.document.getElementById(parents[0]) : null;
    774
    775         if ((parents.length === 1) && (Type.isString(parents[0]))) {
    776             if (Type.exists(table)) {
    777                 // extract the data
    778                 attr = Type.copyAttributes(attributes, board.options, 'chart');
    779
    780                 table = (new DataSource()).loadFromTable(parents[0], attr.withheaders, attr.withheaders);
    781                 data = table.data;
    782                 col = table.columnHeaders;
    783                 row = table.rowHeaders;
    784
    785                 originalWidth = attr.width;
    786                 name = attr.name;
    787                 strokeColor = attr.strokecolor;
    788                 fillColor = attr.fillcolor;
    789                 hStrokeColor = attr.highlightstrokecolor;
    790                 hFillColor = attr.highlightfillcolor;
    791
    792                 board.suspendUpdate();
    793
    794                 len = data.length;
    795                 showRows = [];
    796                 if (attr.rows && Type.isArray(attr.rows)) {
    797                     for (i = 0; i < len; i++) {
    798                         for (j = 0; j < attr.rows.length; j++) {
    799                             if ((attr.rows[j] === i) || (attr.withheaders && attr.rows[j] === row[i])) {
    800                                 showRows.push(data[i]);
    801                                 break;
    802                             }
    803                         }
    804                     }
    805                 } else {
    806                     showRows = data;
    807                 }
    808
    809                 len = showRows.length;
    810
    811                 for (i = 0; i < len; i++) {
    812
    813                     x = [];
    814                     if (attr.chartstyle && attr.chartstyle.indexOf('bar') !== -1) {
    815                         if (originalWidth) {
    816                             w = originalWidth;
    817                         } else {
    818                             w = 0.8;
    819                         }
    820
    821                         x.push(1 - w / 2 + (i + 0.5) * w / len);
    822
    823                         for (j = 1; j < showRows[i].length; j++) {
    824                             x.push(x[j - 1] + 1);
    825                         }
    826
    827                         attr.width = w / len;
    828                     }
    829
    830                     if (name && name.length === len) {
    831                         attr.name = name[i];
    832                     } else if (attr.withheaders) {
    833                         attr.name = col[i];
    834                     }
    835
    836                     if (strokeColor && strokeColor.length === len) {
    837                         attr.strokecolor = strokeColor[i];
    838                     } else {
    839                         attr.strokecolor = Color.hsv2rgb(((i + 1) / len) * 360, 0.9, 0.6);
    840                     }
    841
    842                     if (fillColor && fillColor.length === len) {
    843                         attr.fillcolor = fillColor[i];
    844                     } else {
    845                         attr.fillcolor = Color.hsv2rgb(((i + 1) / len) * 360, 0.9, 1.0);
    846                     }
    847
    848                     if (hStrokeColor && hStrokeColor.length === len) {
    849                         attr.highlightstrokecolor = hStrokeColor[i];
    850                     } else {
    851                         attr.highlightstrokecolor = Color.hsv2rgb(((i + 1) / len) * 360, 0.9, 1.0);
    852                     }
    853
    854                     if (hFillColor && hFillColor.length === len) {
    855                         attr.highlightfillcolor = hFillColor[i];
    856                     } else {
    857                         attr.highlightfillcolor = Color.hsv2rgb(((i + 1) / len) * 360, 0.9, 0.6);
    858                     }
    859
    860                     if (attr.chartstyle && attr.chartstyle.indexOf('bar') !== -1) {
    861                         charts.push(new JXG.Chart(board, [x, showRows[i]], attr));
    862                     } else {
    863                         charts.push(new JXG.Chart(board, [showRows[i]], attr));
    864                     }
    865                 }
    866
    867                 board.unsuspendUpdate();
    868
    869             }
    870             return charts;
    871         }
    872
    873         attr = Type.copyAttributes(attributes, board.options, 'chart');
    874         return new JXG.Chart(board, parents, attr);
    875     };
    876
    877     JXG.registerElement('chart', JXG.createChart);
    878
    879     /**
    880      * Legend for chart
    881      *
    882      **/
    883     JXG.Legend = function (board, coords, attributes) {
    884         var attr;
    885
    886         /* Call the constructor of GeometryElement */
    887         this.constructor();
    888
    889         attr = Type.copyAttributes(attributes, board.options, 'legend');
    890
    891         this.board = board;
    892         this.coords = new Coords(Const.COORDS_BY_USER, coords, this.board);
    893         this.myAtts = {};
    894         this.label_array = attr.labelarray || attr.labels;
    895         this.color_array = attr.colorarray || attr.colors;
    896         this.lines = [];
    897         this.myAtts.strokewidth = attr.strokewidth || 5;
    898         this.myAtts.straightfirst = false;
    899         this.myAtts.straightlast = false;
    900         this.myAtts.withlabel = true;
    901         this.myAtts.fixed = true;
    902         this.style = attr.legendstyle || attr.style;
    903
    904         if (this.style === 'vertical') {
    905             this.drawVerticalLegend(board, attr);
    906         } else {
    907             throw new Error('JSXGraph: Unknown legend style: ' + this.style);
    908         }
    909     };
    910
    911     JXG.Legend.prototype = new GeometryElement();
    912
    913     JXG.Legend.prototype.drawVerticalLegend = function (board, attributes) {
    914         var i,
    915             line_length = attributes.linelength || 1,
    916             offy = (attributes.rowheight || 20) / this.board.unitY,
    917
    918             getLabelAnchor = function () {
    919                 this.setLabelRelativeCoords(this.visProp.label.offset);
    920                 return new Coords(Const.COORDS_BY_USER, [this.point2.X(), this.point2.Y()], this.board);
    921             };
    922
    923         for (i = 0; i < this.label_array.length; i++) {
    924             this.myAtts.strokecolor = this.color_array[i];
    925             this.myAtts.highlightstrokecolor = this.color_array[i];
    926             this.myAtts.name = this.label_array[i];
    927             this.myAtts.label = {
    928                 offset: [10, 0],
    929                 strokeColor: this.color_array[i],
    930                 strokeWidth: this.myAtts.strokewidth
    931             };
    932
    933             this.lines[i] = board.create('line', [
    934                 [this.coords.usrCoords[1], this.coords.usrCoords[2] - i * offy],
    935                 [this.coords.usrCoords[1] + line_length, this.coords.usrCoords[2] - i * offy]],
    936                 this.myAtts);
    937
    938             this.lines[i].getLabelAnchor = getLabelAnchor;
    939
    940         }
    941     };
    942
    943     JXG.createLegend = function (board, parents, attributes) {
    944         //parents are coords of left top point of the legend
    945         var start_from = [0, 0];
    946
    947         if (Type.exists(parents)) {
    948             if (parents.length === 2) {
    949                 start_from = parents;
    950             }
    951         }
    952
    953         return new JXG.Legend(board, start_from, attributes);
    954     };
    955
    956     JXG.registerElement('legend', JXG.createLegend);
    957
    958     return {
    959         Chart: JXG.Chart,
    960         Legend: JXG.Legend,
    961         createChart: JXG.createChart,
    962         createLegend: JXG.createLegend
    963     };
    964 });
    965