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