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  * @description
972  * @name Chart
973  * @augments JXG.Chart
974  * @constructor
975  * @type JXG.Chart
976  * @throws {Exception} If the element cannot be constructed with the given parent objects an exception is thrown.
977  * @param {Array} x Array of x-coordinates (default case, see below for alternatives)
978  * @param {Array} y Array of y-coordinates (default case, see below for alternatives)
979  * <p>
980  * The parent array may be of one of the following forms:
981  * <ol>
982  * <li> Parents array looks like [number, number, number, ...]. It is interpreted as array of y-coordinates.
983  * The x coordinates are automatically set to [1, 2, ...]
984  * <li> Parents array looks like [[number, number, number, ...]]. The content is interpreted as array of y-coordinates.
985  * The x coordinates are automatically set to [1, 2, ...]x coordinates are automatically set to [1, 2, ...]
986  * Default case: [[x0,x1,x2,...],[y1,y2,y3,...]]
987  * </ol>
988  *
989  * The attribute value for the key 'chartStyle' determines the type(s) of the chart. 'chartStyle' is a comma
990  * separated list of strings of the possible chart types
991  * 'bar', 'fit', 'line',  'pie', 'point', 'radar', 'spline'.
992  *
993  * @see JXG.Chart#drawBar
994  * @see JXG.Chart#drawFit
995  * @see JXG.Chart#drawLine
996  * @see JXG.Chart#drawPie
997  * @see JXG.Chart#drawPoints
998  * @see JXG.Chart#drawRadar
999  * @see JXG.Chart#drawSpline
1000  *
1001  * @example
1002  *   board = JXG.JSXGraph.initBoard('jxgbox', {boundingbox:[-0.5,8,9,-2],axis:true});
1003  *
1004  *   var f = [4, 2, -1, 3, 6, 7, 2];
1005  *   var chart = board.create('chart', f,
1006  *                 {chartStyle:'bar',
1007  *                  width:0.8,
1008  *                  labels:f,
1009  *                  colorArray:['#8E1B77','#BE1679','#DC1765','#DA2130','#DB311B','#DF4917','#E36317','#E87F1A',
1010  *                              '#F1B112','#FCF302','#C1E212'],
1011  *                  label: {fontSize:30, display:'internal', anchorX:'left', rotate:90}
1012  *             });
1013  *
1014  * </pre><div id="JXG1528c395-9fa4-4210-ada6-7fc5652ed920" class="jxgbox" style="width: 300px; height: 300px;"></div>
1015  * <script type="text/javascript">
1016  *     (function() {
1017  *         var board = JXG.JSXGraph.initBoard('JXG1528c395-9fa4-4210-ada6-7fc5652ed920',
1018  *             {boundingbox: [-0.5,8,9,-2], axis: true, showcopyright: false, shownavigation: false});
1019  *                 var f = [4,2,-1,3,6,7,2];
1020  *                 var chart = board.create('chart', f,
1021  *                     {chartStyle:'bar',
1022  *                      width:0.8,
1023  *                      labels:f,
1024  *                      colorArray:['#8E1B77','#BE1679','#DC1765','#DA2130','#DB311B','#DF4917','#E36317','#E87F1A',
1025  *                                  '#F1B112','#FCF302','#C1E212'],
1026  *                      label: {fontSize:30, display:'internal', anchorX:'left', rotate:90}
1027  *                 });
1028  *
1029  *     })();
1030  *
1031  * </script><pre>
1032  *
1033  * @example
1034  *   board = JXG.JSXGraph.initBoard('jxgbox', {boundingbox: [-1, 9, 13, -3], axis:true});
1035  *
1036  *   var s = board.create('slider', [[4,7],[8,7],[1,1,1.5]], {name:'S', strokeColor:'black', fillColor:'white'});
1037  *   var f = [function(){return (s.Value()*4.5).toFixed(2);},
1038  *                      function(){return (s.Value()*(-1)).toFixed(2);},
1039  *                      function(){return (s.Value()*3).toFixed(2);},
1040  *                      function(){return (s.Value()*2).toFixed(2);},
1041  *                      function(){return (s.Value()*(-0.5)).toFixed(2);},
1042  *                      function(){return (s.Value()*5.5).toFixed(2);},
1043  *                      function(){return (s.Value()*2.5).toFixed(2);},
1044  *                      function(){return (s.Value()*(-0.75)).toFixed(2);},
1045  *                      function(){return (s.Value()*3.5).toFixed(2);},
1046  *                      function(){return (s.Value()*2).toFixed(2);},
1047  *                      function(){return (s.Value()*(-1.25)).toFixed(2);}
1048  *                      ];
1049  *   var chart = board.create('chart', [f],
1050  *                                             {chartStyle:'bar',width:0.8,labels:f,
1051  *                                              colorArray:['#8E1B77','#BE1679','#DC1765','#DA2130','#DB311B','#DF4917','#E36317','#E87F1A',
1052  *                                                          '#F1B112','#FCF302','#C1E212']});
1053  *
1054  *   var dataArr = [4,1,3,2,5,6.5,1.5,2,0.5,1.5,-1];
1055  *   var chart2 = board.create('chart', dataArr, {chartStyle:'line,point'});
1056  *   chart2[0].setAttribute('strokeColor:black','strokeWidth:2pt');
1057  *   for(var i=0; i<11;i++) {
1058  *            chart2[1][i].setAttribute({strokeColor:'black',fillColor:'white',face:'[]', size:4, strokeWidth:'2pt'});
1059  *   }
1060  *   board.unsuspendUpdate();
1061  *
1062  * </pre><div id="JXG22deb158-48c6-41c3-8157-b88b4b968a55" class="jxgbox" style="width: 300px; height: 300px;"></div>
1063  * <script type="text/javascript">
1064  *     (function() {
1065  *         var board = JXG.JSXGraph.initBoard('JXG22deb158-48c6-41c3-8157-b88b4b968a55',
1066  *             {boundingbox: [-1, 9, 13, -3], axis: true, showcopyright: false, shownavigation: false});
1067  *                 var s = board.create('slider', [[4,7],[8,7],[1,1,1.5]], {name:'S', strokeColor:'black', fillColor:'white'});
1068  *                 var f = [function(){return (s.Value()*4.5).toFixed(2);},
1069  *                          function(){return (s.Value()*(-1)).toFixed(2);},
1070  *                          function(){return (s.Value()*3).toFixed(2);},
1071  *                          function(){return (s.Value()*2).toFixed(2);},
1072  *                          function(){return (s.Value()*(-0.5)).toFixed(2);},
1073  *                          function(){return (s.Value()*5.5).toFixed(2);},
1074  *                          function(){return (s.Value()*2.5).toFixed(2);},
1075  *                          function(){return (s.Value()*(-0.75)).toFixed(2);},
1076  *                          function(){return (s.Value()*3.5).toFixed(2);},
1077  *                          function(){return (s.Value()*2).toFixed(2);},
1078  *                          function(){return (s.Value()*(-1.25)).toFixed(2);}
1079  *                          ];
1080  *                 var chart = board.create('chart', [f],
1081  *                                                 {chartStyle:'bar',width:0.8,labels:f,
1082  *                                                  colorArray:['#8E1B77','#BE1679','#DC1765','#DA2130','#DB311B','#DF4917','#E36317','#E87F1A',
1083  *                                                              '#F1B112','#FCF302','#C1E212']});
1084  *
1085  *                 var dataArr = [4,1,3,2,5,6.5,1.5,2,0.5,1.5,-1];
1086  *                 var chart2 = board.create('chart', dataArr, {chartStyle:'line,point'});
1087  *                 chart2[0].setAttribute('strokeColor:black','strokeWidth:2pt');
1088  *                 for(var i=0; i<11;i++) {
1089  *                     chart2[1][i].setAttribute({strokeColor:'black',fillColor:'white',face:'[]', size:4, strokeWidth:'2pt'});
1090  *                 }
1091  *                 board.unsuspendUpdate();
1092  *
1093  *     })();
1094  *
1095  * </script><pre>
1096  *
1097  * @example
1098  *         var dataArr = [4, 1.2, 3, 7, 5, 4, 1.54, function () { return 2; }];
1099  *         var a = board.create('chart', dataArr, {
1100  *                 chartStyle:'pie', colors:['#B02B2C','#3F4C6B','#C79810','#D15600'],
1101  *                 fillOpacity:0.9,
1102  *                 center:[5,2],
1103  *                 strokeColor:'#ffffff',
1104  *                 strokeWidth:6,
1105  *                 highlightBySize:true,
1106  *                 highlightOnSector:true
1107  *             });
1108  *
1109  * </pre><div id="JXG1180b7dd-b048-436a-a5ad-87ffa82d5aff" class="jxgbox" style="width: 300px; height: 300px;"></div>
1110  * <script type="text/javascript">
1111  *     (function() {
1112  *         var board = JXG.JSXGraph.initBoard('JXG1180b7dd-b048-436a-a5ad-87ffa82d5aff',
1113  *             {boundingbox: [0, 8, 12, -4], axis: true, showcopyright: false, shownavigation: false});
1114  *             var dataArr = [4, 1.2, 3, 7, 5, 4, 1.54, function () { return 2; }];
1115  *             var a = board.create('chart', dataArr, {
1116  *                     chartStyle:'pie', colors:['#B02B2C','#3F4C6B','#C79810','#D15600'],
1117  *                     fillOpacity:0.9,
1118  *                     center:[5,2],
1119  *                     strokeColor:'#ffffff',
1120  *                     strokeWidth:6,
1121  *                     highlightBySize:true,
1122  *                     highlightOnSector:true
1123  *                 });
1124  *
1125  *     })();
1126  *
1127  * </script><pre>
1128  *
1129  * @example
1130  *             board = JXG.JSXGraph.initBoard('jxgbox', {boundingbox: [-12, 12, 20, -12], axis: false});
1131  *             board.suspendUpdate();
1132  *             // See labelArray and paramArray
1133  *             var dataArr = [[23, 14, 15.0], [60, 8, 25.0], [0, 11.0, 25.0], [10, 15, 20.0]];
1134  *
1135  *             var a = board.create('chart', dataArr, {
1136  *                 chartStyle:'radar',
1137  *                 colorArray:['#0F408D','#6F1B75','#CA147A','#DA2228','#E8801B','#FCF302','#8DC922','#15993C','#87CCEE','#0092CE'],
1138  *                 //fillOpacity:0.5,
1139  *                 //strokeColor:'black',
1140  *                 //strokeWidth:1,
1141  *                 //polyStrokeWidth:1,
1142  *                 paramArray:['Speed','Flexibility', 'Costs'],
1143  *                 labelArray:['Ruby','JavaScript', 'PHP', 'Python'],
1144  *                 //startAngle:Math.PI/4,
1145  *                 legendPosition:'right',
1146  *                 //"startShiftRatio": 0.1,
1147  *                 //endShiftRatio:0.1,
1148  *                 //startShiftArray:[0,0,0],
1149  *                 //endShiftArray:[0.5,0.5,0.5],
1150  *                 start:0
1151  *                 //end:70,
1152  *                 //startArray:[0,0,0],
1153  *                 //endArray:[7,7,7],
1154  *                 //radius:3,
1155  *                 //showCircles:true,
1156  *                 //circleLabelArray:[1,2,3,4,5],
1157  *                 //highlightColorArray:['#E46F6A','#F9DF82','#F7FA7B','#B0D990','#69BF8E','#BDDDE4','#92C2DF','#637CB0','#AB91BC','#EB8EBF'],
1158  *             });
1159  *             board.unsuspendUpdate();
1160  *
1161  * </pre><div id="JXG985fbbe6-0488-4073-b73b-cb3ebaea488a" class="jxgbox" style="width: 300px; height: 300px;"></div>
1162  * <script type="text/javascript">
1163  *     (function() {
1164  *         var board = JXG.JSXGraph.initBoard('JXG985fbbe6-0488-4073-b73b-cb3ebaea488a',
1165  *             {boundingbox: [-12, 12, 20, -12], axis: false, showcopyright: false, shownavigation: false});
1166  *                 board.suspendUpdate();
1167  *                 // See labelArray and paramArray
1168  *                 var dataArr = [[23, 14, 15.0], [60, 8, 25.0], [0, 11.0, 25.0], [10, 15, 20.0]];
1169  *
1170  *                 var a = board.create('chart', dataArr, {
1171  *                     chartStyle:'radar',
1172  *                     colorArray:['#0F408D','#6F1B75','#CA147A','#DA2228','#E8801B','#FCF302','#8DC922','#15993C','#87CCEE','#0092CE'],
1173  *                     //fillOpacity:0.5,
1174  *                     //strokeColor:'black',
1175  *                     //strokeWidth:1,
1176  *                     //polyStrokeWidth:1,
1177  *                     paramArray:['Speed','Flexibility', 'Costs'],
1178  *                     labelArray:['Ruby','JavaScript', 'PHP', 'Python'],
1179  *                     //startAngle:Math.PI/4,
1180  *                     legendPosition:'right',
1181  *                     //"startShiftRatio": 0.1,
1182  *                     //endShiftRatio:0.1,
1183  *                     //startShiftArray:[0,0,0],
1184  *                     //endShiftArray:[0.5,0.5,0.5],
1185  *                     start:0
1186  *                     //end:70,
1187  *                     //startArray:[0,0,0],
1188  *                     //endArray:[7,7,7],
1189  *                     //radius:3,
1190  *                     //showCircles:true,
1191  *                     //circleLabelArray:[1,2,3,4,5],
1192  *                     //highlightColorArray:['#E46F6A','#F9DF82','#F7FA7B','#B0D990','#69BF8E','#BDDDE4','#92C2DF','#637CB0','#AB91BC','#EB8EBF'],
1193  *                 });
1194  *                 board.unsuspendUpdate();
1195  *
1196  *     })();
1197  *
1198  * </script><pre>
1199  *
1200  * For more examples see
1201  * <ul>
1202  * <li><a href="https://jsxgraph.org/wiki/index.php/Charts_from_HTML_tables_-_tutorial">JSXgraph wiki: Charts from HTML tables - tutorial</a>
1203  * <li><a href="https://jsxgraph.org/wiki/index.php/Pie_chart">JSXgraph wiki: Pie chart</a>
1204  * <li><a href="https://jsxgraph.org/wiki/index.php/Different_chart_styles">JSXgraph wiki: Various chart styles</a>
1205  * <li><a href="https://jsxgraph.org/wiki/index.php/Dynamic_bar_chart">JSXgraph wiki: Dynamic bar chart</a>
1206  * </ul>
1207  */
1208 JXG.createChart = function (board, parents, attributes) {
1209     var data,
1210         row,
1211         i,
1212         j,
1213         col,
1214         charts = [],
1215         w,
1216         x,
1217         showRows,
1218         attr,
1219         originalWidth,
1220         name,
1221         strokeColor,
1222         fillColor,
1223         hStrokeColor,
1224         hFillColor,
1225         len,
1226         table = Env.isBrowser ? board.document.getElementById(parents[0]) : null;
1227 
1228     if (parents.length === 1 && Type.isString(parents[0])) {
1229         if (Type.exists(table)) {
1230             // extract the data
1231             attr = Type.copyAttributes(attributes, board.options, "chart");
1232 
1233             table = new DataSource().loadFromTable(
1234                 parents[0],
1235                 attr.withheaders,
1236                 attr.withheaders
1237             );
1238             data = table.data;
1239             col = table.columnHeaders;
1240             row = table.rowHeaders;
1241 
1242             originalWidth = attr.width;
1243             name = attr.name;
1244             strokeColor = attr.strokecolor;
1245             fillColor = attr.fillcolor;
1246             hStrokeColor = attr.highlightstrokecolor;
1247             hFillColor = attr.highlightfillcolor;
1248 
1249             board.suspendUpdate();
1250 
1251             len = data.length;
1252             showRows = [];
1253             if (attr.rows && Type.isArray(attr.rows)) {
1254                 for (i = 0; i < len; i++) {
1255                     for (j = 0; j < attr.rows.length; j++) {
1256                         if (
1257                             attr.rows[j] === i ||
1258                             (attr.withheaders && attr.rows[j] === row[i])
1259                         ) {
1260                             showRows.push(data[i]);
1261                             break;
1262                         }
1263                     }
1264                 }
1265             } else {
1266                 showRows = data;
1267             }
1268 
1269             len = showRows.length;
1270 
1271             for (i = 0; i < len; i++) {
1272                 x = [];
1273                 if (attr.chartstyle && attr.chartstyle.indexOf("bar") !== -1) {
1274                     if (originalWidth) {
1275                         w = originalWidth;
1276                     } else {
1277                         w = 0.8;
1278                     }
1279 
1280                     x.push(1 - w / 2 + ((i + 0.5) * w) / len);
1281 
1282                     for (j = 1; j < showRows[i].length; j++) {
1283                         x.push(x[j - 1] + 1);
1284                     }
1285 
1286                     attr.width = w / len;
1287                 }
1288 
1289                 if (name && name.length === len) {
1290                     attr.name = name[i];
1291                 } else if (attr.withheaders) {
1292                     attr.name = col[i];
1293                 }
1294 
1295                 if (strokeColor && strokeColor.length === len) {
1296                     attr.strokecolor = strokeColor[i];
1297                 } else {
1298                     attr.strokecolor = Color.hsv2rgb(((i + 1) / len) * 360, 0.9, 0.6);
1299                 }
1300 
1301                 if (fillColor && fillColor.length === len) {
1302                     attr.fillcolor = fillColor[i];
1303                 } else {
1304                     attr.fillcolor = Color.hsv2rgb(((i + 1) / len) * 360, 0.9, 1.0);
1305                 }
1306 
1307                 if (hStrokeColor && hStrokeColor.length === len) {
1308                     attr.highlightstrokecolor = hStrokeColor[i];
1309                 } else {
1310                     attr.highlightstrokecolor = Color.hsv2rgb(((i + 1) / len) * 360, 0.9, 1.0);
1311                 }
1312 
1313                 if (hFillColor && hFillColor.length === len) {
1314                     attr.highlightfillcolor = hFillColor[i];
1315                 } else {
1316                     attr.highlightfillcolor = Color.hsv2rgb(((i + 1) / len) * 360, 0.9, 0.6);
1317                 }
1318 
1319                 if (attr.chartstyle && attr.chartstyle.indexOf("bar") !== -1) {
1320                     charts.push(new JXG.Chart(board, [x, showRows[i]], attr));
1321                 } else {
1322                     charts.push(new JXG.Chart(board, [showRows[i]], attr));
1323                 }
1324             }
1325 
1326             board.unsuspendUpdate();
1327         }
1328         return charts;
1329     }
1330 
1331     attr = Type.copyAttributes(attributes, board.options, "chart");
1332     return new JXG.Chart(board, parents, attr);
1333 };
1334 
1335 JXG.registerElement("chart", JXG.createChart);
1336 
1337 /**
1338  * Legend for chart
1339  * TODO
1340  *
1341  * The Legend class is a basic class for legends.
1342  * @class Creates a new Lgend object. Do not use this constructor to create a legend.
1343  * Use {@link JXG.Board#create} with type {@link Legend} instead.
1344  * <p>
1345  * The legend object consists of segements with labels. These lines can be
1346  * access with the property "lines" of the element.
1347  * @constructor
1348  * @augments JXG.GeometryElement
1349  * @param {String,JXG.Board} board The board the new legend is drawn on.
1350  * @param {Array} coords Coordinates of the left top point of the legend.
1351  * @param  {Object} attributes Attributes of the legend
1352  */
1353 JXG.Legend = function (board, coords, attributes) {
1354     var attr;
1355 
1356     /* Call the constructor of GeometryElement */
1357     this.constructor();
1358 
1359     attr = Type.copyAttributes(attributes, board.options, "legend");
1360 
1361     this.board = board;
1362     this.coords = new Coords(Const.COORDS_BY_USER, coords, this.board);
1363     this.myAtts = {};
1364     this.label_array = attr.labelarray || attr.labels;
1365     this.color_array = attr.colorarray || attr.colors;
1366     this.lines = [];
1367     this.myAtts.strokewidth = attr.strokewidth || 5;
1368     this.myAtts.straightfirst = false;
1369     this.myAtts.straightlast = false;
1370     this.myAtts.withlabel = true;
1371     this.myAtts.fixed = true;
1372     this.style = attr.legendstyle || attr.style;
1373 
1374     if (this.style === "vertical") {
1375         this.drawVerticalLegend(board, attr);
1376     } else {
1377         throw new Error("JSXGraph: Unknown legend style: " + this.style);
1378     }
1379 };
1380 
1381 JXG.Legend.prototype = new GeometryElement();
1382 
1383 /**
1384  * Draw a vertical legend.
1385  *
1386  * @private
1387  * @param  {String,JXG.Board} board      The board the legend is drawn on
1388  * @param  {Object} attributes Attributes of the legend
1389  */
1390 JXG.Legend.prototype.drawVerticalLegend = function (board, attributes) {
1391     var i,
1392         line_length = attributes.linelength || 1,
1393         offy = (attributes.rowheight || 20) / this.board.unitY,
1394         getLabelAnchor = function () {
1395             this.setLabelRelativeCoords(this.visProp.label.offset);
1396             return new Coords(
1397                 Const.COORDS_BY_USER,
1398                 [this.point2.X(), this.point2.Y()],
1399                 this.board
1400             );
1401         };
1402 
1403     for (i = 0; i < this.label_array.length; i++) {
1404         this.myAtts.name = this.label_array[i];
1405         this.myAtts.strokecolor = this.color_array[i % this.color_array.length];
1406         this.myAtts.highlightstrokecolor = this.color_array[i % this.color_array.length];
1407         this.myAtts.label = {
1408             offset: [10, 0],
1409             strokeColor: this.color_array[i % this.color_array.length],
1410             strokeWidth: this.myAtts.strokewidth
1411         };
1412 
1413         this.lines[i] = board.create(
1414             "line",
1415             [
1416                 [this.coords.usrCoords[1], this.coords.usrCoords[2] - i * offy],
1417                 [this.coords.usrCoords[1] + line_length, this.coords.usrCoords[2] - i * offy]
1418             ],
1419             this.myAtts
1420         );
1421 
1422         this.lines[i].getLabelAnchor = getLabelAnchor;
1423         this.lines[i]
1424             .prepareUpdate()
1425             .update()
1426             .updateVisibility(Type.evaluate(this.lines[i].visProp.visible))
1427             .updateRenderer();
1428     }
1429 };
1430 
1431 /**
1432  * @class This element is used to provide a constructor for a chart legend.
1433  * Parameter is a pair of coordinates. The label names and  the label colors are
1434  * supplied in the attributes:
1435  * <ul>
1436  * <li> labels (Array): array of strings containing label names
1437  * <li> labelArray (Array): alternative array for label names (has precedence over 'labels')
1438  * <li> colors (Array): array of color values
1439  * <li> colorArray (Array): alternative array for color values (has precedence over 'colors')
1440  * <li> legendStyle or style: at the time being only 'vertical' is supported.
1441  * <li> rowHeight.
1442  * </ul>
1443  *
1444  * @pseudo
1445  * @description
1446  * @name Legend
1447  * @augments JXG.Legend
1448  * @constructor
1449  * @type JXG.Legend
1450  * @throws {Exception} If the element cannot be constructed with the given parent objects an exception is thrown.
1451  * @param {Number} x Horizontal coordinate of the left top point of the legend
1452  * @param {Number} y Vertical coordinate of the left top point of the legend
1453  *
1454  * @example
1455  * var board = JXG.JSXGraph.initBoard('jxgbox', {axis:true,boundingbox:[-4,48.3,12.0,-2.3]});
1456  * var x       = [-3,-2,-1,0,1,2,3,4,5,6,7,8];
1457  * var dataArr = [4,7,7,27,33,37,46,22,11,4,1,0];
1458  *
1459  * colors = ['green', 'yellow', 'red', 'blue'];
1460  * board.create('chart', [x,dataArr], {chartStyle:'bar', width:1.0, labels:dataArr, colors: colors} );
1461  * board.create('legend', [8, 45], {labels:dataArr, colors: colors, strokeWidth:5} );
1462  *
1463  * </pre><div id="JXGeeb588d9-a4fd-41bf-93f4-cd6f7a016682" class="jxgbox" style="width: 300px; height: 300px;"></div>
1464  * <script type="text/javascript">
1465  *     (function() {
1466  *         var board = JXG.JSXGraph.initBoard('JXGeeb588d9-a4fd-41bf-93f4-cd6f7a016682',
1467  *             {boundingbox: [-4,48.3,12.0,-2.3], axis: true, showcopyright: false, shownavigation: false});
1468  *     var x       = [-3,-2,-1,0,1,2,3,4,5,6,7,8];
1469  *     var dataArr = [4,7,7,27,33,37,46,22,11,4,1,0];
1470  *
1471  *     colors = ['green', 'yellow', 'red', 'blue'];
1472  *     board.create('chart', [x,dataArr], {chartStyle:'bar', width:1.0, labels:dataArr, colors: colors} );
1473  *     board.create('legend', [8, 45], {labels:dataArr, colors: colors, strokeWidth:5} );
1474  *
1475  *     })();
1476  *
1477  * </script><pre>
1478  *
1479  *
1480  */
1481 JXG.createLegend = function (board, parents, attributes) {
1482     //parents are coords of left top point of the legend
1483     var start_from = [0, 0];
1484 
1485     if (Type.exists(parents) && parents.length === 2) {
1486         start_from = parents;
1487     } else {
1488         throw new Error("JSXGraph: Legend element needs two numbers as parameters");
1489     }
1490 
1491     return new JXG.Legend(board, start_from, attributes);
1492 };
1493 
1494 JXG.registerElement("legend", JXG.createLegend);
1495 
1496 export default {
1497     Chart: JXG.Chart,
1498     Legend: JXG.Legend
1499     // createChart: JXG.createChart,
1500     // createLegend: JXG.createLegend
1501 };
1502