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