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