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