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