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