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