      1 /*
      2     Copyright 2008-2022
      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       *
     72       * The Chart class is a basic class for the chart object.
     73       * @class Creates a new basic chart object. Do not use this constructor to create a chart.
     74       * Use {@link JXG.Board#create} with type {@link Chart} instead.
     75       * @constructor
     76       * @augments JXG.GeometryElement
     77       * @param {String,JXG.Board} board The board the new chart is drawn on.
     78       * @param {Array} parent data arrays for the chart
     79       * @param {Object} attributes Javascript object containing attributes like name, id and colors.
     80       *
     81       */
     82     JXG.Chart = function (board, parents, attributes) {
     83         this.constructor(board, attributes);
     84
     85         var x, y, i, c, style, len;
     86
     87         if (!Type.isArray(parents) || parents.length === 0) {
     88             throw new Error('JSXGraph: Can\'t create a chart without data');
     89         }
     90
     91         /**
     92          * Contains pointers to the various subelements of the chart.
     93          */
     94         this.elements = [];
     95
     96         if (Type.isNumber(parents[0])) {
     97             // parents looks like [a,b,c,..]
     98             // x has to be filled
     99
    100             y = parents;
    101             x = [];
    102             for (i = 0; i < y.length; i++) {
    103                 x[i] = i + 1;
    104             }
    105         } else if (parents.length === 1 && Type.isArray(parents[0])) {
    106             // parents looks like [[a,b,c,..]]
    107             // x has to be filled
    108
    109             y = parents[0];
    110             x = [];
    111
    112             len = Type.evaluate(y).length;
    113             for (i = 0; i < len; i++) {
    114                 x[i] = i + 1;
    115             }
    116         } else if (parents.length === 2) {
    117             // parents looks like [[x0,x1,x2,...],[y1,y2,y3,...]]
    118             len = Math.min(parents[0].length, parents[1].length);
    119             x = parents[0].slice(0, len);
    120             y = parents[1].slice(0, len);
    121         }
    122
    123         if (Type.isArray(y) && y.length === 0) {
    124             throw new Error('JSXGraph: Can\'t create charts without data.');
    125         }
    126
    127         // does this really need to be done here? this should be done in createChart and then
    128         // there should be an extra chart for each chartstyle
    129         style = attributes.chartstyle.replace(/ /g, '').split(',');
    130         for (i = 0; i < style.length; i++) {
    131             switch (style[i]) {
    132             case 'bar':
    133                 c = this.drawBar(board, x, y, attributes);
    134                 break;
    135             case 'line':
    136                 c = this.drawLine(board, x, y, attributes);
    137                 break;
    138             case 'fit':
    139                 c = this.drawFit(board, x, y, attributes);
    140                 break;
    141             case 'spline':
    142                 c = this.drawSpline(board, x, y, attributes);
    143                 break;
    144             case 'pie':
    145                 c = this.drawPie(board, y, attributes);
    146                 break;
    147             case 'point':
    148                 c = this.drawPoints(board, x, y, attributes);
    149                 break;
    150             case 'radar':
    151                 c = this.drawRadar(board, parents, attributes);
    152                 break;
    153             }
    154             this.elements.push(c);
    155         }
    156         this.id = this.board.setId(this, 'Chart');
    157
    158         return this.elements;
    159     };
    160
    161     JXG.Chart.prototype = new GeometryElement();
    162
    163     JXG.extend(JXG.Chart.prototype, /** @lends JXG.Chart.prototype */ {
    164         /**
    165          * Create line chart defined by two data arrays.
    166          *
    167          * @param  {String,JXG.Board} board      The board the chart is drawn on
    168          * @param  {Array} x          Array of x-coordinates
    169          * @param  {Array} y          Array of y-coordinates
    170          * @param  {Object} attributes  Javascript object containing attributes like colors
    171          * @returns {JXG.Curve}       JSXGraph curve
    172          */
    173         drawLine: function (board, x, y, attributes) {
    174             // we don't want the line chart to be filled
    175             attributes.fillcolor = 'none';
    176             attributes.highlightfillcolor = 'none';
    177
    178             return board.create('curve', [x, y], attributes);
    179         },
    180
    181         /**
    182          * Create line chart that consists of a natural spline curve
    183          * defined by two data arrays.
    184          *
    185          * @param  {String,JXG.Board} board      The board the chart is drawn on
    186          * @param  {Array} x          Array of x-coordinates
    187          * @param  {Array} y          Array of y-coordinates
    188          * @param  {Object} attributes Javascript object containing attributes like colors
    189          * @returns {JXG.Curve}       JSXGraph (natural) spline curve
    190          */
    191         drawSpline: function (board, x, y, attributes) {
    192             // we don't want the spline chart to be filled
    193             attributes.fillColor = 'none';
    194             attributes.highlightfillcolor = 'none';
    195
    196             return board.create('spline', [x, y], attributes);
    197         },
    198
    199         /**
    200          * Create line chart where the curve is given by a regression polynomial
    201          * defined by two data arrays. The degree of the polynomial is supplied
    202          * through the attribute "degree" in attributes.
    203          *
    204          * @param  {String,JXG.Board} board      The board the chart is drawn on
    205          * @param  {Array} x          Array of x-coordinates
    206          * @param  {Array} y          Array of y-coordinates
    207          * @param  {Object} attributes Javascript object containing attributes like colors
    208          * @returns {JXG.Curve}    JSXGraph function graph object
    209          */
    210         drawFit: function (board, x, y, attributes) {
    211             var deg = attributes.degree;
    212
    213             deg = Math.max(parseInt(deg, 10), 1) || 1;
    214
    215             // never fill
    216             attributes.fillcolor = 'none';
    217             attributes.highlightfillcolor = 'none';
    218
    219             return board.create('functiongraph', [Numerics.regressionPolynomial(deg, x, y)], attributes);
    220         },
    221
    222         /**
    223          * Create bar chart defined by two data arrays.
    224          * Attributes to change the layout of the bar chart are:
    225          * <ul>
    226          * <li> width (optional)
    227          * <li> dir: 'horizontal' or 'vertical'
    228          * <li> colors: array of colors
    229          * <li> labels: array of labels
    230          * </ul>
    231          *
    232          * @param  {String,JXG.Board} board      The board the chart is drawn on
    233          * @param  {Array} x          Array of x-coordinates
    234          * @param  {Array} y          Array of y-coordinates
    235          * @param  {Object} attributes Javascript object containing attributes like colors
    236          * @returns {Array}    Array of JXG polygons defining the bars
    237          */
    238         drawBar: function (board, x, y, attributes) {
    239             var i, strwidth, text, w, xp0, xp1, xp2, yp, colors,
    240                 pols = [],
    241                 p = [],
    242                 attr, attrSub,
    243                 makeXpFun = function (i, f) {
    244                     return function () {
    245                         return x[i]() - f * w;
    246                     };
    247                 },
    248                 hiddenPoint = {
    249                     fixed: true,
    250                     withLabel: false,
    251                     visible: false,
    252                     name: ''
    253                 };
    254
    255             attr = Type.copyAttributes(attributes, board.options, 'chart');
    256
    257             // Determine the width of the bars
    258             if (attr && attr.width) {  // width given
    259                 w = attr.width;
    260             } else {
    261                 if (x.length <= 1) {
    262                     w = 1;
    263                 } else {
    264                     // Find minimum distance between to bars.
    265                     w = x[1] - x[0];
    266                     for (i = 1; i < x.length - 1; i++) {
    267                         w = (x[i + 1] - x[i] < w) ? x[i + 1] - x[i] : w;
    268                     }
    269                 }
    270                 w *= 0.8;
    271             }
    272
    273             attrSub = Type.copyAttributes(attributes, board.options, 'chart', 'label');
    274
    275             for (i = 0; i < x.length; i++) {
    276                 if (Type.isFunction(x[i])) {
    277                     xp0 = makeXpFun(i, -0.5);
    278                     xp1 = makeXpFun(i, 0);
    279                     xp2 = makeXpFun(i, 0.5);
    280                 } else {
    281                     xp0 = x[i] - w * 0.5;
    282                     xp1 = x[i];
    283                     xp2 = x[i] + w * 0.5;
    284                 }
    285                 if (Type.isFunction(y[i])) {
    286                     yp = y[i]();
    287                 } else {
    288                     yp = y[i];
    289                 }
    290                 yp = y[i];
    291
    292                 if (attr.dir === 'horizontal') {  // horizontal bars
    293                     p[0] = board.create('point', [0, xp0], hiddenPoint);
    294                     p[1] = board.create('point', [yp, xp0], hiddenPoint);
    295                     p[2] = board.create('point', [yp, xp2], hiddenPoint);
    296                     p[3] = board.create('point', [0, xp2], hiddenPoint);
    297
    298                     if (Type.exists(attr.labels) && Type.exists(attr.labels[i])) {
    299                         attrSub.anchorY = 'middle';
    300                         text = board.create('text', [
    301                             yp,
    302                             xp1,
    303                             attr.labels[i]], attrSub);
    304                         text.visProp.anchorx = (function(txt) { return function() {
    305                             return (txt.X() >= 0) ? 'left' : 'right';
    306                         }; })(text);
    307
    308                     }
    309                 } else { // vertical bars
    310                     p[0] = board.create('point', [xp0, 0], hiddenPoint);
    311                     p[1] = board.create('point', [xp0, yp], hiddenPoint);
    312                     p[2] = board.create('point', [xp2, yp], hiddenPoint);
    313                     p[3] = board.create('point', [xp2, 0], hiddenPoint);
    314
    315                     if (Type.exists(attr.labels) && Type.exists(attr.labels[i])) {
    316                         attrSub.anchorX = 'middle';
    317
    318                         text = board.create('text', [
    319                             xp1,
    320                             yp,
    321                             attr.labels[i]], attrSub);
    322
    323                         text.visProp.anchory = (function(txt) {
    324                             return function() {
    325                                     return (txt.Y() >= 0) ? 'bottom' : 'top';
    326                                 };
    327                             })(text);
    328
    329                     }
    330                 }
    331
    332                 if (Type.isArray(attr.colors)) {
    333                     colors = attr.colors;
    334                     attr.fillcolor = colors[i % colors.length];
    335                 }
    336
    337                 pols[i] = board.create('polygon', p, attr);
    338                 if (Type.exists(attr.labels) && Type.exists(attr.labels[i])) {
    339                     pols[i].text = text;
    340                 }
    341             }
    342
    343             return pols;
    344         },
    345
    346         /**
    347          * Create chart consisting of JSXGraph points.
    348          * Attributes to change the layout of the point chart are:
    349          * <ul>
    350          * <li> fixed (Boolean)
    351          * <li> infoboxArray (Array): Texts for the infobox
    352          * </ul>
    353          *
    354          * @param  {String,JXG.Board} board      The board the chart is drawn on
    355          * @param  {Array} x          Array of x-coordinates
    356          * @param  {Array} y          Array of y-coordinates
    357          * @param  {Object} attributes Javascript object containing attributes like colors
    358          * @returns {Array} Array of JSXGraph points
    359          */
    360         drawPoints: function (board, x, y, attributes) {
    361             var i,
    362                 points = [],
    363                 infoboxArray = attributes.infoboxarray;
    364
    365             attributes.fixed = true;
    366             attributes.name = '';
    367
    368             for (i = 0; i < x.length; i++) {
    369                 attributes.infoboxtext = infoboxArray ? infoboxArray[i % infoboxArray.length] : false;
    370                 points[i] = board.create('point', [x[i], y[i]], attributes);
    371             }
    372
    373             return points;
    374         },
    375
    376         /**
    377          * Create pie chart.
    378          * Attributes to change the layout of the pie chart are:
    379          * <ul>
    380          * <li> labels: array of labels
    381          * <li> colors: (Array)
    382          * <li> highlightColors (Array)
    383          * <li> radius
    384          * <li> center (coordinate array)
    385          * <li> highlightOnSector (Boolean)
    386          * </ul>
    387          *
    388          * @param  {String,JXG.Board} board      The board the chart is drawn on
    389          * @param  {Array} y          Array of x-coordinates
    390          * @param  {Object} attributes Javascript object containing attributes like colors
    391          * @returns {Object}  with keys: "{sectors, points, midpoint}"
    392          */
    393         drawPie: function (board, y, attributes) {
    394             var i, center,
    395                 p = [],
    396                 sector = [],
    397                 s = Statistics.sum(y),
    398                 colorArray = attributes.colors,
    399                 highlightColorArray = attributes.highlightcolors,
    400                 labelArray = attributes.labels,
    401                 r = attributes.radius || 4,
    402                 radius = r,
    403                 cent = attributes.center || [0, 0],
    404                 xc = cent[0],
    405                 yc = cent[1],
    406
    407                 makeRadPointFun = function (j, fun, xc) {
    408                     return function () {
    409                         var s, i, rad,
    410                             t = 0;
    411
    412                         for (i = 0; i <= j; i++) {
    413                             t += parseFloat(Type.evaluate(y[i]));
    414                         }
    415
    416                         s = t;
    417                         for (i = j + 1; i < y.length; i++) {
    418                             s += parseFloat(Type.evaluate(y[i]));
    419                         }
    420                         rad = (s !== 0) ? (2 * Math.PI * t / s) : 0;
    421
    422                         return radius() * Math[fun](rad) + xc;
    423                     };
    424                 },
    425
    426                 highlightHandleLabel = function (f, s) {
    427                     var dx = -this.point1.coords.usrCoords[1] + this.point2.coords.usrCoords[1],
    428                         dy = -this.point1.coords.usrCoords[2] + this.point2.coords.usrCoords[2];
    429
    430                     if (Type.exists(this.label)) {
    431                         this.label.rendNode.style.fontSize = (s * Type.evaluate(this.label.visProp.fontsize)) + 'px';
    432                         this.label.fullUpdate();
    433                     }
    434
    435                     this.point2.coords = new Coords(Const.COORDS_BY_USER, [
    436                         this.point1.coords.usrCoords[1] + dx * f,
    437                         this.point1.coords.usrCoords[2] + dy * f
    438                     ], this.board);
    439                     this.fullUpdate();
    440                 },
    441
    442                 highlightFun = function () {
    443                     if (!this.highlighted) {
    444                         this.highlighted = true;
    445                         this.board.highlightedObjects[this.id] = this;
    446                         this.board.renderer.highlight(this);
    447
    448                         highlightHandleLabel.call(this, 1.1, 2);
    449                     }
    450                 },
    451
    452                 noHighlightFun = function () {
    453                     if (this.highlighted) {
    454                         this.highlighted = false;
    455                         this.board.renderer.noHighlight(this);
    456
    457                         highlightHandleLabel.call(this, 0.90909090, 1);
    458                     }
    459                 },
    460
    461                 hiddenPoint = {
    462                     fixed: true,
    463                     withLabel: false,
    464                     visible: false,
    465                     name: ''
    466                 };
    467
    468             if (!Type.isArray(labelArray)) {
    469                 labelArray = [];
    470                 for (i = 0; i < y.length; i++) {
    471                     labelArray[i] = '';
    472                 }
    473             }
    474
    475             if (!Type.isFunction(r)) {
    476                 radius = function () {
    477                     return r;
    478                 };
    479             }
    480
    481             attributes.highlightonsector = attributes.highlightonsector || false;
    482             attributes.straightfirst = false;
    483             attributes.straightlast = false;
    484
    485             center = board.create('point', [xc, yc], hiddenPoint);
    486             p[0] = board.create('point', [
    487                 function () {
    488                     return radius() + xc;
    489                 },
    490                 function () {
    491                     return yc;
    492                 }
    493             ], hiddenPoint);
    494
    495             for (i = 0; i < y.length; i++) {
    496                 p[i + 1] = board.create('point', [makeRadPointFun(i, 'cos', xc), makeRadPointFun(i, 'sin', yc)], hiddenPoint);
    497
    498                 attributes.name = labelArray[i];
    499                 attributes.withlabel = attributes.name !== '';
    500                 attributes.fillcolor = colorArray && colorArray[i % colorArray.length];
    501                 attributes.labelcolor = colorArray && colorArray[i % colorArray.length];
    502                 attributes.highlightfillcolor = highlightColorArray && highlightColorArray[i % highlightColorArray.length];
    503
    504                 sector[i] = board.create('sector', [center, p[i], p[i + 1]], attributes);
    505
    506                 if (attributes.highlightonsector) {
    507                     // overwrite hasPoint so that the whole sector is used for highlighting
    508                     sector[i].hasPoint = sector[i].hasPointSector;
    509                 }
    510                 if (attributes.highlightbysize) {
    511                     sector[i].highlight = highlightFun;
    512
    513                     sector[i].noHighlight = noHighlightFun;
    514                 }
    515
    516             }
    517
    518             // Not enough! We need points, but this gives an error in setAttribute.
    519             return {sectors: sector, points: p, midpoint: center};
    520         },
    521
    522         /**
    523          * Create radar chart.
    524          * Attributes to change the layout of the pie chart are:
    525          * <ul>
    526          * <li> paramArray: labels for axes, [ paramx, paramy, paramz ]
    527          * <li> startShiftRatio: 0 <= offset from chart center <=1
    528          * <li> endShiftRatio:  0 <= offset from chart radius <=1
    529          * <li> startShiftArray: Adjust offsets per each axis
    530          * <li> endShiftArray: Adjust offsets per each axis
    531          * <li> startArray: Values for inner circle. Default values: minimums
    532          * <li> start: one value to overwrite all startArray values
    533          * <li> endArray: Values for outer circle, maximums by default
    534          * <li> end: one value to overwrite all endArray values
    535          * <li> labelArray
    536          * <li> polyStrokeWidth
    537          * <li> colors
    538          * <li> highlightcolors
    539          * <li> labelArray: [ row1, row2, row3 ]
    540          * <li> radius
    541          * <li> legendPosition
    542          * <li> showCircles
    543          * <li> circleLabelArray
    544          * <li> circleStrokeWidth
    545          * </ul>
    546          *
    547          * @param  {String,JXG.Board} board      The board the chart is drawn on
    548          * @param  {Array} parents    Array of coordinates, e.g. [[x1, y1, z1], [x2, y2, z2], [x3, y3, z3]]
    549          * @param  {Object} attributes Javascript object containing attributes like colors
    550          * @returns {Object} with keys "{circles, lines, points, midpoint, polygons}"
    551          */
    552         drawRadar: function (board, parents, attributes) {
    553             var i, j, paramArray, numofparams, maxes, mins,
    554                 la, pdata, ssa, esa, ssratio, esratio,
    555                 sshifts, eshifts, starts, ends,
    556                 labelArray, colorArray, highlightColorArray, radius, myAtts,
    557                 cent, xc, yc, center, start_angle, rad, p, line, t,
    558                 xcoord, ycoord, polygons, legend_position, circles, lxoff, lyoff,
    559                 cla, clabelArray, ncircles, pcircles, angle, dr, sw, data,
    560                 len = parents.length,
    561
    562                 get_anchor = function () {
    563                     var x1, x2, y1, y2,
    564                         relCoords = Type.evaluate(this.visProp.label.offset).slice(0);
    565
    566                     x1 = this.point1.X();
    567                     x2 = this.point2.X();
    568                     y1 = this.point1.Y();
    569                     y2 = this.point2.Y();
    570                     if (x2 < x1) {
    571                         relCoords[0] = -relCoords[0];
    572                     }
    573
    574                     if (y2 < y1) {
    575                         relCoords[1] = -relCoords[1];
    576                     }
    577
    578                     this.setLabelRelativeCoords(relCoords);
    579
    580                     return new Coords(Const.COORDS_BY_USER, [this.point2.X(), this.point2.Y()], this.board);
    581                 },
    582
    583                 get_transform = function (angle, i) {
    584                     var t, tscale, trot;
    585
    586                     t = board.create('transform', [-(starts[i] - sshifts[i]), 0], {type: 'translate'});
    587                     tscale = board.create('transform', [radius / ((ends[i] + eshifts[i]) - (starts[i] - sshifts[i])), 1], {type: 'scale'});
    588                     t.melt(tscale);
    589                     trot = board.create('transform', [angle], {type: 'rotate'});
    590                     t.melt(trot);
    591
    592                     return t;
    593                 };
    594
    595             if (len <= 0) {
    596                 throw new Error('JSXGraph radar chart: no data');
    597             }
    598             // labels for axes
    599             paramArray = attributes.paramarray;
    600             if (!Type.exists(paramArray)) {
    601                 throw new Error('JSXGraph radar chart: need paramArray attribute');
    602             }
    603             numofparams = paramArray.length;
    604             if (numofparams <= 1) {
    605                 throw new Error('JSXGraph radar chart: need more than one param in paramArray');
    606             }
    607
    608             for (i = 0; i < len; i++) {
    609                 if (numofparams !== parents[i].length) {
    610                     throw new Error('JSXGraph radar chart: use data length equal to number of params (' + parents[i].length + ' != ' + numofparams + ')');
    611                 }
    612             }
    613
    614             maxes = [];
    615             mins = [];
    616
    617             for (j = 0; j < numofparams; j++) {
    618                 maxes[j] = parents[0][j];
    619                 mins[j] = maxes[j];
    620             }
    621
    622             for (i = 1; i < len; i++) {
    623                 for (j = 0; j < numofparams; j++) {
    624                     if (parents[i][j] > maxes[j]) {
    625                         maxes[j] = parents[i][j];
    626                     }
    627
    628                     if (parents[i][j] < mins[j]) {
    629                         mins[j] = parents[i][j];
    630                     }
    631                 }
    632             }
    633
    634             la = [];
    635             pdata = [];
    636
    637             for (i = 0; i < len; i++) {
    638                 la[i] = '';
    639                 pdata[i] = [];
    640             }
    641
    642             ssa = [];
    643             esa = [];
    644
    645             // 0 <= Offset from chart center <=1
    646             ssratio = attributes.startshiftratio || 0;
    647             // 0 <= Offset from chart radius <=1
    648             esratio = attributes.endshiftratio || 0;
    649
    650             for (i = 0; i < numofparams; i++) {
    651                 ssa[i] = (maxes[i] - mins[i]) * ssratio;
    652                 esa[i] = (maxes[i] - mins[i]) * esratio;
    653             }
    654
    655             // Adjust offsets per each axis
    656             sshifts = attributes.startshiftarray || ssa;
    657             eshifts = attributes.endshiftarray || esa;
    658             // Values for inner circle, minimums by default
    659             starts = attributes.startarray || mins;
    660
    661             if (Type.exists(attributes.start)) {
    662                 for (i = 0; i < numofparams; i++) {
    663                     starts[i] = attributes.start;
    664                 }
    665             }
    666
    667             // Values for outer circle, maximums by default
    668             ends = attributes.endarray || maxes;
    669             if (Type.exists(attributes.end)) {
    670                 for (i = 0; i < numofparams; i++) {
    671                     ends[i] = attributes.end;
    672                 }
    673             }
    674
    675             if (sshifts.length !== numofparams) {
    676                 throw new Error('JSXGraph radar chart: start shifts length is not equal to number of parameters');
    677             }
    678
    679             if (eshifts.length !== numofparams) {
    680                 throw new Error('JSXGraph radar chart: end shifts length is not equal to number of parameters');
    681             }
    682
    683             if (starts.length !== numofparams) {
    684                 throw new Error('JSXGraph radar chart: starts length is not equal to number of parameters');
    685             }
    686
    687             if (ends.length !== numofparams) {
    688                 throw new Error('JSXGraph radar chart: snds length is not equal to number of parameters');
    689             }
    690
    691             // labels for legend
    692             labelArray = attributes.labelarray || la;
    693             colorArray = attributes.colors;
    694             highlightColorArray = attributes.highlightcolors;
    695             radius = attributes.radius || 10;
    696             sw = attributes.strokewidth || 1;
    697
    698             if (!Type.exists(attributes.highlightonsector)) {
    699                 attributes.highlightonsector = false;
    700             }
    701
    702             myAtts = {
    703                 name: attributes.name,
    704                 id: attributes.id,
    705                 strokewidth: sw,
    706                 polystrokewidth: attributes.polystrokewidth || sw,
    707                 strokecolor: attributes.strokecolor || 'black',
    708                 straightfirst: false,
    709                 straightlast: false,
    710                 fillcolor: attributes.fillColor || '#FFFF88',
    711                 fillopacity: attributes.fillOpacity || 0.4,
    712                 highlightfillcolor: attributes.highlightFillColor || '#FF7400',
    713                 highlightstrokecolor: attributes.highlightStrokeColor || 'black',
    714                 gradient: attributes.gradient || 'none'
    715             };
    716
    717             cent = attributes.center || [0, 0];
    718             xc = cent[0];
    719             yc = cent[1];
    720             center = board.create('point', [xc, yc], {name: '', fixed: true, withlabel: false, visible: false});
    721             start_angle = Math.PI / 2 - Math.PI / numofparams;
    722             start_angle = attributes.startangle || 0;
    723             rad = start_angle;
    724             p = [];
    725             line = [];
    726
    727             for (i = 0; i < numofparams; i++) {
    728                 rad += 2 * Math.PI / numofparams;
    729                 xcoord = radius * Math.cos(rad) + xc;
    730                 ycoord = radius * Math.sin(rad) + yc;
    731
    732                 p[i] = board.create('point', [xcoord, ycoord], {name: '', fixed: true, withlabel: false, visible: false});
    733                 line[i] = board.create('line', [center, p[i]], {
    734                     name: paramArray[i],
    735                     strokeColor: myAtts.strokecolor,
    736                     strokeWidth: myAtts.strokewidth,
    737                     strokeOpacity: 1.0,
    738                     straightFirst: false,
    739                     straightLast: false,
    740                     withLabel: true,
    741                     highlightStrokeColor: myAtts.highlightstrokecolor
    742                 });
    743                 line[i].getLabelAnchor = get_anchor;
    744                 t = get_transform(rad, i);
    745
    746                 for (j = 0; j < parents.length; j++) {
    747                     data = parents[j][i];
    748                     pdata[j][i] = board.create('point', [data, 0], {name: '', fixed: true, withlabel: false, visible: false});
    749                     pdata[j][i].addTransform(pdata[j][i], t);
    750                 }
    751             }
    752
    753             polygons = [];
    754             for (i = 0; i < len; i++) {
    755                 myAtts.labelcolor = colorArray && colorArray[i % colorArray.length];
    756                 myAtts.strokecolor = colorArray && colorArray[i % colorArray.length];
    757                 myAtts.fillcolor = colorArray && colorArray[i % colorArray.length];
    758                 polygons[i] = board.create('polygon', pdata[i], {
    759                     withLines: true,
    760                     withLabel: false,
    761                     fillColor: myAtts.fillcolor,
    762                     fillOpacity: myAtts.fillopacity,
    763                     highlightFillColor: myAtts.highlightfillcolor
    764                 });
    765
    766                 for (j = 0; j < numofparams; j++) {
    767                     polygons[i].borders[j].setAttribute('strokecolor:' + colorArray[i % colorArray.length]);
    768                     polygons[i].borders[j].setAttribute('strokewidth:' + myAtts.polystrokewidth);
    769                 }
    770             }
    771
    772             legend_position = attributes.legendposition || 'none';
    773             switch (legend_position) {
    774             case 'right':
    775                 lxoff = attributes.legendleftoffset || 2;
    776                 lyoff = attributes.legendtopoffset || 1;
    777
    778                 this.legend = board.create('legend', [xc + radius + lxoff, yc + radius - lyoff], {
    779                     labels: labelArray,
    780                     colors: colorArray
    781                 });
    782                 break;
    783             case 'none':
    784                 break;
    785             default:
    786                 JXG.debug('Unknown legend position');
    787             }
    788
    789             circles = [];
    790             if (attributes.showcircles) {
    791                 cla = [];
    792                 for (i = 0; i < 6; i++) {
    793                     cla[i] = 20 * i;
    794                 }
    795                 cla[0] = "0";
    796                 clabelArray = attributes.circlelabelarray || cla;
    797                 ncircles = clabelArray.length;
    798
    799                 if (ncircles < 2) {
    800                     throw new Error('JSXGraph radar chart: too less circles in circleLabelArray');
    801                 }
    802
    803                 pcircles = [];
    804                 angle = start_angle + Math.PI / numofparams;
    805                 t = get_transform(angle, 0);
    806
    807                 myAtts.fillcolor = 'none';
    808                 myAtts.highlightfillcolor = 'none';
    809                 myAtts.strokecolor = attributes.strokecolor || 'black';
    810                 myAtts.strokewidth = attributes.circlestrokewidth || 0.5;
    811                 myAtts.layer = 0;
    812
    813                 // we have ncircles-1 intervals between ncircles circles
    814                 dr = (ends[0] - starts[0]) / (ncircles - 1);
    815
    816                 for (i = 0; i < ncircles; i++) {
    817                     pcircles[i] = board.create('point', [starts[0] + i * dr, 0], {
    818                         name: clabelArray[i],
    819                         size: 0,
    820                         fixed: true,
    821                         withLabel: true,
    822                         visible: true
    823                     });
    824                     pcircles[i].addTransform(pcircles[i], t);
    825                     circles[i] = board.create('circle', [center, pcircles[i]], myAtts);
    826                 }
    827
    828             }
    829             this.rendNode = polygons[0].rendNode;
    830             return {
    831                 circles: circles,
    832                 lines: line,
    833                 points: pdata,
    834                 midpoint: center,
    835                 polygons: polygons
    836             };
    837         },
    838
    839          /**
    840           * Uses the boards renderer to update the chart.
    841           * @private
    842           */
    843         updateRenderer: function () {
    844             return this;
    845         },
    846
    847          // documented in base/element
    848         update: function () {
    849             if (this.needsUpdate) {
    850                 this.updateDataArray();
    851             }
    852
    853             return this;
    854         },
    855
    856         /**
    857          * Template for dynamic charts update.
    858          * This method is used to compute new entries
    859          * for the arrays this.dataX and
    860          * this.dataY. It is used in update.
    861          * Default is an empty method, can be overwritten
    862          * by the user.
    863          *
    864          * @returns {JXG.Chart} Reference to this chart object.
    865          */
    866         updateDataArray: function () { return this; }
    867     });
    868
    869     /**
    870      * @class Constructor for a chart.
    871      * @pseudo
    872      * @description
    873      * @name Chart
    874      * @augments JXG.Chart
    875      * @constructor
    876      * @type JXG.Chart
    877      * @throws {Exception} If the element cannot be constructed with the given parent objects an exception is thrown.
    878      * @param {Array} x Array of x-coordinates (default case, see below for alternatives)
    879      * @param {Array} y Array of y-coordinates (default case, see below for alternatives)
    880      * <p>
    881      * The parent array may be of one of the following forms:
    882      * <ol>
    883      * <li> Parents array looks like [number, number, number, ...]. It is interpreted as array of y-coordinates.
    884      * The x coordinates are automatically set to [1, 2, ...]
    885      * <li> Parents array looks like [[number, number, number, ...]]. The content is interpreted as array of y-coordinates.
    886      * The x coordinates are automatically set to [1, 2, ...]x coordinates are automatically set to [1, 2, ...]
    887      * Default case: [[x0,x1,x2,...],[y1,y2,y3,...]]
    888      * </ol>
    889      *
    890      * The attribute value for the key 'chartStyle' determines the type(s) of the chart. 'chartStyle' is a comma
    891      * separated list of strings of the possible chart types
    892      * 'bar', 'fit', 'line',  'pie', 'point', 'radar', 'spline'.
    893      *
    894      * @see JXG.Chart#drawBar
    895      * @see JXG.Chart#drawFit
    896      * @see JXG.Chart#drawLine
    897      * @see JXG.Chart#drawPie
    898      * @see JXG.Chart#drawPoints
    899      * @see JXG.Chart#drawRadar
    900      * @see JXG.Chart#drawSpline
    901      *
    902      * @example
    903      *   board = JXG.JSXGraph.initBoard('jxgbox', {boundingbox:[-0.5,8,9,-2],axis:true});
    904      *
    905      *   var f = [4, 2, -1, 3, 6, 7, 2];
    906      *   var chart = board.create('chart', f,
    907      *                 {chartStyle:'bar',
    908      *                  width:0.8,
    909      *                  labels:f,
    910      *                  colorArray:['#8E1B77','#BE1679','#DC1765','#DA2130','#DB311B','#DF4917','#E36317','#E87F1A',
    911      *                              '#F1B112','#FCF302','#C1E212'],
    912      *                  label: {fontSize:30, display:'internal', anchorX:'left', rotate:90}
    913      *             });
    914      *
    915      * </pre><div id="JXG1528c395-9fa4-4210-ada6-7fc5652ed920" class="jxgbox" style="width: 300px; height: 300px;"></div>
    916      * <script type="text/javascript">
    917      *     (function() {
    918      *         var board = JXG.JSXGraph.initBoard('JXG1528c395-9fa4-4210-ada6-7fc5652ed920',
    919      *             {boundingbox: [-0.5,8,9,-2], axis: true, showcopyright: false, shownavigation: false});
    920      *                 var f = [4,2,-1,3,6,7,2];
    921      *                 var chart = board.create('chart', f,
    922      *                     {chartStyle:'bar',
    923      *                      width:0.8,
    924      *                      labels:f,
    925      *                      colorArray:['#8E1B77','#BE1679','#DC1765','#DA2130','#DB311B','#DF4917','#E36317','#E87F1A',
    926      *                                  '#F1B112','#FCF302','#C1E212'],
    927      *                      label: {fontSize:30, display:'internal', anchorX:'left', rotate:90}
    928      *                 });
    929      *
    930      *     })();
    931      *
    932      * </script><pre>
    933      *
    934      * @example
    935      *   board = JXG.JSXGraph.initBoard('jxgbox', {boundingbox: [-1, 9, 13, -3], axis:true});
    936      *
    937      *   var s = board.create('slider', [[4,7],[8,7],[1,1,1.5]], {name:'S', strokeColor:'black', fillColor:'white'});
    938      *   var f = [function(){return (s.Value()*4.5).toFixed(2);},
    939      *                      function(){return (s.Value()*(-1)).toFixed(2);},
    940      *                      function(){return (s.Value()*3).toFixed(2);},
    941      *                      function(){return (s.Value()*2).toFixed(2);},
    942      *                      function(){return (s.Value()*(-0.5)).toFixed(2);},
    943      *                      function(){return (s.Value()*5.5).toFixed(2);},
    944      *                      function(){return (s.Value()*2.5).toFixed(2);},
    945      *                      function(){return (s.Value()*(-0.75)).toFixed(2);},
    946      *                      function(){return (s.Value()*3.5).toFixed(2);},
    947      *                      function(){return (s.Value()*2).toFixed(2);},
    948      *                      function(){return (s.Value()*(-1.25)).toFixed(2);}
    949      *                      ];
    950      *   var chart = board.create('chart', [f],
    951      *                                             {chartStyle:'bar',width:0.8,labels:f,
    952      *                                              colorArray:['#8E1B77','#BE1679','#DC1765','#DA2130','#DB311B','#DF4917','#E36317','#E87F1A',
    953      *                                                          '#F1B112','#FCF302','#C1E212']});
    954      *
    955      *   var dataArr = [4,1,3,2,5,6.5,1.5,2,0.5,1.5,-1];
    956      *   var chart2 = board.create('chart', dataArr, {chartStyle:'line,point'});
    957      *   chart2[0].setAttribute('strokeColor:black','strokeWidth:2pt');
    958      *   for(var i=0; i<11;i++) {
    959      *            chart2[1][i].setAttribute({strokeColor:'black',fillColor:'white',face:'[]', size:4, strokeWidth:'2pt'});
    960      *   }
    961      *   board.unsuspendUpdate();
    962      *
    963      * </pre><div id="JXG22deb158-48c6-41c3-8157-b88b4b968a55" class="jxgbox" style="width: 300px; height: 300px;"></div>
    964      * <script type="text/javascript">
    965      *     (function() {
    966      *         var board = JXG.JSXGraph.initBoard('JXG22deb158-48c6-41c3-8157-b88b4b968a55',
    967      *             {boundingbox: [-1, 9, 13, -3], axis: true, showcopyright: false, shownavigation: false});
    968      *                 var s = board.create('slider', [[4,7],[8,7],[1,1,1.5]], {name:'S', strokeColor:'black', fillColor:'white'});
    969      *                 var f = [function(){return (s.Value()*4.5).toFixed(2);},
    970      *                          function(){return (s.Value()*(-1)).toFixed(2);},
    971      *                          function(){return (s.Value()*3).toFixed(2);},
    972      *                          function(){return (s.Value()*2).toFixed(2);},
    973      *                          function(){return (s.Value()*(-0.5)).toFixed(2);},
    974      *                          function(){return (s.Value()*5.5).toFixed(2);},
    975      *                          function(){return (s.Value()*2.5).toFixed(2);},
    976      *                          function(){return (s.Value()*(-0.75)).toFixed(2);},
    977      *                          function(){return (s.Value()*3.5).toFixed(2);},
    978      *                          function(){return (s.Value()*2).toFixed(2);},
    979      *                          function(){return (s.Value()*(-1.25)).toFixed(2);}
    980      *                          ];
    981      *                 var chart = board.create('chart', [f],
    982      *                                                 {chartStyle:'bar',width:0.8,labels:f,
    983      *                                                  colorArray:['#8E1B77','#BE1679','#DC1765','#DA2130','#DB311B','#DF4917','#E36317','#E87F1A',
    984      *                                                              '#F1B112','#FCF302','#C1E212']});
    985      *
    986      *                 var dataArr = [4,1,3,2,5,6.5,1.5,2,0.5,1.5,-1];
    987      *                 var chart2 = board.create('chart', dataArr, {chartStyle:'line,point'});
    988      *                 chart2[0].setAttribute('strokeColor:black','strokeWidth:2pt');
    989      *                 for(var i=0; i<11;i++) {
    990      *                     chart2[1][i].setAttribute({strokeColor:'black',fillColor:'white',face:'[]', size:4, strokeWidth:'2pt'});
    991      *                 }
    992      *                 board.unsuspendUpdate();
    993      *
    994      *     })();
    995      *
    996      * </script><pre>
    997      *
    998       * @example
    999      *         var dataArr = [4, 1.2, 3, 7, 5, 4, 1.54, function () { return 2; }];
    1000      *         var a = board.create('chart', dataArr, {
    1001      *                 chartStyle:'pie', colors:['#B02B2C','#3F4C6B','#C79810','#D15600'],
    1002      *                 fillOpacity:0.9,
    1003      *                 center:[5,2],
    1004      *                 strokeColor:'#ffffff',
    1005      *                 strokeWidth:6,
    1006      *                 highlightBySize:true,
    1007      *                 highlightOnSector:true
    1008      *             });
    1009      *
    1010      * </pre><div id="JXG1180b7dd-b048-436a-a5ad-87ffa82d5aff" class="jxgbox" style="width: 300px; height: 300px;"></div>
    1011      * <script type="text/javascript">
    1012      *     (function() {
    1013      *         var board = JXG.JSXGraph.initBoard('JXG1180b7dd-b048-436a-a5ad-87ffa82d5aff',
    1014      *             {boundingbox: [0, 8, 12, -4], axis: true, showcopyright: false, shownavigation: false});
    1015      *             var dataArr = [4, 1.2, 3, 7, 5, 4, 1.54, function () { return 2; }];
    1016      *             var a = board.create('chart', dataArr, {
    1017      *                     chartStyle:'pie', colors:['#B02B2C','#3F4C6B','#C79810','#D15600'],
    1018      *                     fillOpacity:0.9,
    1019      *                     center:[5,2],
    1020      *                     strokeColor:'#ffffff',
    1021      *                     strokeWidth:6,
    1022      *                     highlightBySize:true,
    1023      *                     highlightOnSector:true
    1024      *                 });
    1025      *
    1026      *     })();
    1027      *
    1028      * </script><pre>
    1029      *
    1030      * @example
    1031      *             board = JXG.JSXGraph.initBoard('jxgbox', {boundingbox: [-12, 12, 20, -12], axis: false});
    1032      *             board.suspendUpdate();
    1033      *             // See labelArray and paramArray
    1034      *             var dataArr = [[23, 14, 15.0], [60, 8, 25.0], [0, 11.0, 25.0], [10, 15, 20.0]];
    1035      *
    1036      *             var a = board.create('chart', dataArr, {
    1037      *                 chartStyle:'radar',
    1038      *                 colorArray:['#0F408D','#6F1B75','#CA147A','#DA2228','#E8801B','#FCF302','#8DC922','#15993C','#87CCEE','#0092CE'],
    1039      *                 //fillOpacity:0.5,
    1040      *                 //strokeColor:'black',
    1041      *                 //strokeWidth:1,
    1042      *                 //polyStrokeWidth:1,
    1043      *                 paramArray:['Speed','Flexibility', 'Costs'],
    1044      *                 labelArray:['Ruby','JavaScript', 'PHP', 'Python'],
    1045      *                 //startAngle:Math.PI/4,
    1046      *                 legendPosition:'right',
    1047      *                 //"startShiftRatio": 0.1,
    1048      *                 //endShiftRatio:0.1,
    1049      *                 //startShiftArray:[0,0,0],
    1050      *                 //endShiftArray:[0.5,0.5,0.5],
    1051      *                 start:0
    1052      *                 //end:70,
    1053      *                 //startArray:[0,0,0],
    1054      *                 //endArray:[7,7,7],
    1055      *                 //radius:3,
    1056      *                 //showCircles:true,
    1057      *                 //circleLabelArray:[1,2,3,4,5],
    1058      *                 //highlightColorArray:['#E46F6A','#F9DF82','#F7FA7B','#B0D990','#69BF8E','#BDDDE4','#92C2DF','#637CB0','#AB91BC','#EB8EBF'],
    1059      *             });
    1060      *             board.unsuspendUpdate();
    1061      *
    1062      * </pre><div id="JXG985fbbe6-0488-4073-b73b-cb3ebaea488a" class="jxgbox" style="width: 300px; height: 300px;"></div>
    1063      * <script type="text/javascript">
    1064      *     (function() {
    1065      *         var board = JXG.JSXGraph.initBoard('JXG985fbbe6-0488-4073-b73b-cb3ebaea488a',
    1066      *             {boundingbox: [-12, 12, 20, -12], axis: false, showcopyright: false, shownavigation: false});
    1067      *                 board.suspendUpdate();
    1068      *                 // See labelArray and paramArray
    1069      *                 var dataArr = [[23, 14, 15.0], [60, 8, 25.0], [0, 11.0, 25.0], [10, 15, 20.0]];
    1070      *
    1071      *                 var a = board.create('chart', dataArr, {
    1072      *                     chartStyle:'radar',
    1073      *                     colorArray:['#0F408D','#6F1B75','#CA147A','#DA2228','#E8801B','#FCF302','#8DC922','#15993C','#87CCEE','#0092CE'],
    1074      *                     //fillOpacity:0.5,
    1075      *                     //strokeColor:'black',
    1076      *                     //strokeWidth:1,
    1077      *                     //polyStrokeWidth:1,
    1078      *                     paramArray:['Speed','Flexibility', 'Costs'],
    1079      *                     labelArray:['Ruby','JavaScript', 'PHP', 'Python'],
    1080      *                     //startAngle:Math.PI/4,
    1081      *                     legendPosition:'right',
    1082      *                     //"startShiftRatio": 0.1,
    1083      *                     //endShiftRatio:0.1,
    1084      *                     //startShiftArray:[0,0,0],
    1085      *                     //endShiftArray:[0.5,0.5,0.5],
    1086      *                     start:0
    1087      *                     //end:70,
    1088      *                     //startArray:[0,0,0],
    1089      *                     //endArray:[7,7,7],
    1090      *                     //radius:3,
    1091      *                     //showCircles:true,
    1092      *                     //circleLabelArray:[1,2,3,4,5],
    1093      *                     //highlightColorArray:['#E46F6A','#F9DF82','#F7FA7B','#B0D990','#69BF8E','#BDDDE4','#92C2DF','#637CB0','#AB91BC','#EB8EBF'],
    1094      *                 });
    1095      *                 board.unsuspendUpdate();
    1096      *
    1097      *     })();
    1098      *
    1099      * </script><pre>
    1100      *
    1101      * For more examples see
    1102      * <ul>
    1103      * <li><a href="https://jsxgraph.org/wiki/index.php/Charts_from_HTML_tables_-_tutorial">JSXgraph wiki: Charts from HTML tables - tutorial</a>
    1104      * <li><a href="https://jsxgraph.org/wiki/index.php/Pie_chart">JSXgraph wiki: Pie chart</a>
    1105      * <li><a href="https://jsxgraph.org/wiki/index.php/Different_chart_styles">JSXgraph wiki: Various chart styles</a>
    1106      * <li><a href="https://jsxgraph.org/wiki/index.php/Dynamic_bar_chart">JSXgraph wiki: Dynamic bar chart</a>
    1107      * </ul>
    1108      */
    1109      JXG.createChart = function (board, parents, attributes) {
    1110          var data, row, i, j, col,
    1111              charts = [],
    1112              w, x, showRows, attr,
    1113              originalWidth, name, strokeColor, fillColor,
    1114              hStrokeColor, hFillColor, len,
    1115              table = Env.isBrowser ? board.document.getElementById(parents[0]) : null;
    1116
    1117         if ((parents.length === 1) && (Type.isString(parents[0]))) {
    1118             if (Type.exists(table)) {
    1119                 // extract the data
    1120                 attr = Type.copyAttributes(attributes, board.options, 'chart');
    1121
    1122                 table = (new DataSource()).loadFromTable(parents[0], attr.withheaders, attr.withheaders);
    1123                 data = table.data;
    1124                 col = table.columnHeaders;
    1125                 row = table.rowHeaders;
    1126
    1127                 originalWidth = attr.width;
    1128                 name = attr.name;
    1129                 strokeColor = attr.strokecolor;
    1130                 fillColor = attr.fillcolor;
    1131                 hStrokeColor = attr.highlightstrokecolor;
    1132                 hFillColor = attr.highlightfillcolor;
    1133
    1134                 board.suspendUpdate();
    1135
    1136                 len = data.length;
    1137                 showRows = [];
    1138                 if (attr.rows && Type.isArray(attr.rows)) {
    1139                     for (i = 0; i < len; i++) {
    1140                         for (j = 0; j < attr.rows.length; j++) {
    1141                             if ((attr.rows[j] === i) || (attr.withheaders && attr.rows[j] === row[i])) {
    1142                                 showRows.push(data[i]);
    1143                                 break;
    1144                             }
    1145                         }
    1146                     }
    1147                 } else {
    1148                     showRows = data;
    1149                 }
    1150
    1151                 len = showRows.length;
    1152
    1153                 for (i = 0; i < len; i++) {
    1154
    1155                     x = [];
    1156                     if (attr.chartstyle && attr.chartstyle.indexOf('bar') !== -1) {
    1157                         if (originalWidth) {
    1158                             w = originalWidth;
    1159                         } else {
    1160                             w = 0.8;
    1161                         }
    1162
    1163                         x.push(1 - w / 2 + (i + 0.5) * w / len);
    1164
    1165                         for (j = 1; j < showRows[i].length; j++) {
    1166                             x.push(x[j - 1] + 1);
    1167                         }
    1168
    1169                         attr.width = w / len;
    1170                     }
    1171
    1172                     if (name && name.length === len) {
    1173                         attr.name = name[i];
    1174                     } else if (attr.withheaders) {
    1175                         attr.name = col[i];
    1176                     }
    1177
    1178                     if (strokeColor && strokeColor.length === len) {
    1179                         attr.strokecolor = strokeColor[i];
    1180                     } else {
    1181                         attr.strokecolor = Color.hsv2rgb(((i + 1) / len) * 360, 0.9, 0.6);
    1182                     }
    1183
    1184                     if (fillColor && fillColor.length === len) {
    1185                         attr.fillcolor = fillColor[i];
    1186                     } else {
    1187                         attr.fillcolor = Color.hsv2rgb(((i + 1) / len) * 360, 0.9, 1.0);
    1188                     }
    1189
    1190                     if (hStrokeColor && hStrokeColor.length === len) {
    1191                         attr.highlightstrokecolor = hStrokeColor[i];
    1192                     } else {
    1193                         attr.highlightstrokecolor = Color.hsv2rgb(((i + 1) / len) * 360, 0.9, 1.0);
    1194                     }
    1195
    1196                     if (hFillColor && hFillColor.length === len) {
    1197                         attr.highlightfillcolor = hFillColor[i];
    1198                     } else {
    1199                         attr.highlightfillcolor = Color.hsv2rgb(((i + 1) / len) * 360, 0.9, 0.6);
    1200                     }
    1201
    1202                     if (attr.chartstyle && attr.chartstyle.indexOf('bar') !== -1) {
    1203                         charts.push(new JXG.Chart(board, [x, showRows[i]], attr));
    1204                     } else {
    1205                         charts.push(new JXG.Chart(board, [showRows[i]], attr));
    1206                     }
    1207                 }
    1208
    1209                 board.unsuspendUpdate();
    1210
    1211             }
    1212             return charts;
    1213         }
    1214
    1215         attr = Type.copyAttributes(attributes, board.options, 'chart');
    1216         return new JXG.Chart(board, parents, attr);
    1217     };
    1218
    1219     JXG.registerElement('chart', JXG.createChart);
    1220
    1221     /**
    1222      * Legend for chart
    1223      * TODO
    1224      *
    1225      * The Legend class is a basic class for legends.
    1226      * @class Creates a new Lgend object. Do not use this constructor to create a legend.
    1227      * Use {@link JXG.Board#create} with type {@link Legend} instead.
    1228      * <p>
    1229      * The legend object consists of segements with labels. These lines can be
    1230      * access with the property "lines" of the element.
    1231      * @constructor
    1232      * @augments JXG.GeometryElement
    1233      * @param {String,JXG.Board} board The board the new legend is drawn on.
    1234      * @param {Array} coords Coordinates of the left top point of the legend.
    1235      * @param  {Object} attributes Attributes of the legend
    1236      */
    1237     JXG.Legend = function (board, coords, attributes) {
    1238         var attr;
    1239
    1240         /* Call the constructor of GeometryElement */
    1241         this.constructor();
    1242
    1243         attr = Type.copyAttributes(attributes, board.options, 'legend');
    1244
    1245         this.board = board;
    1246         this.coords = new Coords(Const.COORDS_BY_USER, coords, this.board);
    1247         this.myAtts = {};
    1248         this.label_array = attr.labelarray || attr.labels;
    1249         this.color_array = attr.colorarray || attr.colors;
    1250         this.lines = [];
    1251         this.myAtts.strokewidth = attr.strokewidth || 5;
    1252         this.myAtts.straightfirst = false;
    1253         this.myAtts.straightlast = false;
    1254         this.myAtts.withlabel = true;
    1255         this.myAtts.fixed = true;
    1256         this.style = attr.legendstyle || attr.style;
    1257
    1258         if (this.style === 'vertical') {
    1259             this.drawVerticalLegend(board, attr);
    1260         } else {
    1261             throw new Error('JSXGraph: Unknown legend style: ' + this.style);
    1262         }
    1263     };
    1264
    1265     JXG.Legend.prototype = new GeometryElement();
    1266
    1267     /**
    1268      * Draw a vertical legend.
    1269      *
    1270      * @private
    1271      * @param  {String,JXG.Board} board      The board the legend is drawn on
    1272      * @param  {Object} attributes Attributes of the legend
    1273      */
    1274     JXG.Legend.prototype.drawVerticalLegend = function (board, attributes) {
    1275         var i,
    1276             line_length = attributes.linelength || 1,
    1277             offy = (attributes.rowheight || 20) / this.board.unitY,
    1278
    1279             getLabelAnchor = function () {
    1280                 this.setLabelRelativeCoords(this.visProp.label.offset);
    1281                 return new Coords(Const.COORDS_BY_USER, [this.point2.X(), this.point2.Y()], this.board);
    1282             };
    1283
    1284         for (i = 0; i < this.label_array.length; i++) {
    1285             this.myAtts.name = this.label_array[i];
    1286             this.myAtts.strokecolor = this.color_array[i % this.color_array.length];
    1287             this.myAtts.highlightstrokecolor = this.color_array[i % this.color_array.length];
    1288             this.myAtts.label = {
    1289                 offset: [10, 0],
    1290                 strokeColor: this.color_array[i % this.color_array.length ],
    1291                 strokeWidth: this.myAtts.strokewidth
    1292             };
    1293
    1294             this.lines[i] = board.create('line', [
    1295                 [this.coords.usrCoords[1], this.coords.usrCoords[2] - i * offy],
    1296                 [this.coords.usrCoords[1] + line_length, this.coords.usrCoords[2] - i * offy]],
    1297                 this.myAtts);
    1298
    1299             this.lines[i].getLabelAnchor = getLabelAnchor;
    1300             this.lines[i].prepareUpdate().update().updateVisibility(Type.evaluate(this.lines[i].visProp.visible)).updateRenderer();
    1301         }
    1302     };
    1303
    1304     /**
    1305      * @class This element is used to provide a constructor for a chart legend.
    1306      * Parameter is a pair of coordinates. The label names and  the label colors are
    1307      * supplied in the attributes:
    1308      * <ul>
    1309      * <li> labels (Array): array of strings containing label names
    1310      * <li> labelArray (Array): alternative array for label names (has precedence over 'labels')
    1311      * <li> colors (Array): array of color values
    1312      * <li> colorArray (Array): alternative array for color values (has precedence over 'colors')
    1313      * <li> legendStyle or style: at the time being only 'vertical' is supported.
    1314      * <li> rowHeight.
    1315      * </ul>
    1316      *
    1317      * @pseudo
    1318      * @description
    1319      * @name Legend
    1320      * @augments JXG.Legend
    1321      * @constructor
    1322      * @type JXG.Legend
    1323      * @throws {Exception} If the element cannot be constructed with the given parent objects an exception is thrown.
    1324      * @param {Number} x Horizontal coordinate of the left top point of the legend
    1325      * @param {Number} y Vertical coordinate of the left top point of the legend
    1326      *
    1327      * @example
    1328      * var board = JXG.JSXGraph.initBoard('jxgbox', {axis:true,boundingbox:[-4,48.3,12.0,-2.3]});
    1329      * var x       = [-3,-2,-1,0,1,2,3,4,5,6,7,8];
    1330      * var dataArr = [4,7,7,27,33,37,46,22,11,4,1,0];
    1331      *
    1332      * colors = ['green', 'yellow', 'red', 'blue'];
    1333      * board.create('chart', [x,dataArr], {chartStyle:'bar', width:1.0, labels:dataArr, colors: colors} );
    1334      * board.create('legend', [8, 45], {labels:dataArr, colors: colors, strokeWidth:5} );
    1335      *
    1336      * </pre><div id="JXGeeb588d9-a4fd-41bf-93f4-cd6f7a016682" class="jxgbox" style="width: 300px; height: 300px;"></div>
    1337      * <script type="text/javascript">
    1338      *     (function() {
    1339      *         var board = JXG.JSXGraph.initBoard('JXGeeb588d9-a4fd-41bf-93f4-cd6f7a016682',
    1340      *             {boundingbox: [-4,48.3,12.0,-2.3], axis: true, showcopyright: false, shownavigation: false});
    1341      *     var x       = [-3,-2,-1,0,1,2,3,4,5,6,7,8];
    1342      *     var dataArr = [4,7,7,27,33,37,46,22,11,4,1,0];
    1343      *
    1344      *     colors = ['green', 'yellow', 'red', 'blue'];
    1345      *     board.create('chart', [x,dataArr], {chartStyle:'bar', width:1.0, labels:dataArr, colors: colors} );
    1346      *     board.create('legend', [8, 45], {labels:dataArr, colors: colors, strokeWidth:5} );
    1347      *
    1348      *     })();
    1349      *
    1350      * </script><pre>
    1351      *
    1352      *
    1353      */
    1354     JXG.createLegend = function (board, parents, attributes) {
    1355         //parents are coords of left top point of the legend
    1356         var start_from = [0, 0];
    1357
    1358         if (Type.exists(parents) && parents.length === 2) {
    1359                 start_from = parents;
    1360         } else {
    1361             throw new Error('JSXGraph: Legend element needs two numbers as parameters');
    1362         }
    1363
    1364         return new JXG.Legend(board, start_from, attributes);
    1365     };
    1366
    1367     JXG.registerElement('legend', JXG.createLegend);
    1368
    1369     return {
    1370         Chart: JXG.Chart,
    1371         Legend: JXG.Legend,
    1372         createChart: JXG.createChart,
    1373         createLegend: JXG.createLegend
    1374     };
    1375 });
    1376