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