1 /*
  2     Copyright 2008-2017
  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      * Chart plotting
 72      */
 73     JXG.Chart = function (board, parents, attributes) {
 74         this.constructor(board, attributes);
 75 
 76         var x, y, i, c, style, len;
 77 
 78         if (!Type.isArray(parents) || parents.length === 0) {
 79             throw new Error('JSXGraph: Can\'t create a chart without data');
 80         }
 81 
 82         /**
 83          * Contains pointers to the various subelements of the chart.
 84          */
 85         this.elements = [];
 86 
 87         if (Type.isNumber(parents[0])) {
 88             // parents looks like [a,b,c,..]
 89             // x has to be filled
 90 
 91             y = parents;
 92             x = [];
 93             for (i = 0; i < y.length; i++) {
 94                 x[i] = i + 1;
 95             }
 96         } else if (parents.length === 1 && Type.isArray(parents[0])) {
 97             // parents looks like [[a,b,c,..]]
 98             // x has to be filled
 99 
100             y = parents[0];
101             x = [];
102 
103             len = Type.evaluate(y).length;
104             for (i = 0; i < len; i++) {
105                 x[i] = i + 1;
106             }
107         } else if (parents.length === 2) {
108             // parents looks like [[x0,x1,x2,...],[y1,y2,y3,...]]
109             len = Math.min(parents[0].length, parents[1].length);
110             x = parents[0].slice(0, len);
111             y = parents[1].slice(0, len);
112         }
113 
114         if (Type.isArray(y) && y.length === 0) {
115             throw new Error('JSXGraph: Can\'t create charts without data.');
116         }
117 
118         // does this really need to be done here? this should be done in createChart and then
119         // there should be an extra chart for each chartstyle
120         style = attributes.chartstyle.replace(/ /g, '').split(',');
121         for (i = 0; i < style.length; i++) {
122             switch (style[i]) {
123             case 'bar':
124                 c = this.drawBar(board, x, y, attributes);
125                 break;
126             case 'line':
127                 c = this.drawLine(board, x, y, attributes);
128                 break;
129             case 'fit':
130                 c = this.drawFit(board, x, y, attributes);
131                 break;
132             case 'spline':
133                 c = this.drawSpline(board, x, y, attributes);
134                 break;
135             case 'pie':
136                 c = this.drawPie(board, y, attributes);
137                 break;
138             case 'point':
139                 c = this.drawPoints(board, x, y, attributes);
140                 break;
141             case 'radar':
142                 c = this.drawRadar(board, parents, attributes);
143                 break;
144             }
145             this.elements.push(c);
146         }
147         this.id = this.board.setId(this, 'Chart');
148 
149         return this.elements;
150     };
151     JXG.Chart.prototype = new GeometryElement();
152 
153     JXG.extend(JXG.Chart.prototype, /** @lends JXG.Chart.prototype */ {
154         drawLine: function (board, x, y, attributes) {
155             // we don't want the line chart to be filled
156             attributes.fillcolor = 'none';
157             attributes.highlightfillcolor = 'none';
158 
159             return board.create('curve', [x, y], attributes);
160         },
161 
162         drawSpline: function (board, x, y, attributes) {
163             // we don't want the spline chart to be filled
164             attributes.fillColor = 'none';
165             attributes.highlightfillcolor = 'none';
166 
167             return board.create('spline', [x, y], attributes);
168         },
169 
170         drawFit: function (board, x, y, attributes) {
171             var deg = attributes.degree;
172 
173             deg = Math.max(parseInt(deg, 10), 1) || 1;
174 
175             // never fill
176             attributes.fillcolor = 'none';
177             attributes.highlightfillcolor = 'none';
178 
179             return board.create('functiongraph', [Numerics.regressionPolynomial(deg, x, y)], attributes);
180         },
181 
182         drawBar: function (board, x, y, attributes) {
183             var i, strwidth, text, w, xp0, xp1, xp2, yp, colors,
184                 pols = [],
185                 p = [],
186                 attr, attrSub,
187 
188                 makeXpFun = function (i, f) {
189                     return function () {
190                         return x[i]() - f * w;
191                     };
192                 },
193 
194                 hiddenPoint = {
195                     fixed: true,
196                     withLabel: false,
197                     visible: false,
198                     name: ''
199                 };
200 
201             attr = Type.copyAttributes(attributes, board.options, 'chart');
202 
203             // Determine the width of the bars
204             if (attr && attr.width) {  // width given
205                 w = attr.width;
206             } else {
207                 if (x.length <= 1) {
208                     w = 1;
209                 } else {
210                     // Find minimum distance between to bars.
211                     w = x[1] - x[0];
212                     for (i = 1; i < x.length - 1; i++) {
213                         w = (x[i + 1] - x[i] < w) ? x[i + 1] - x[i] : w;
214                     }
215                 }
216                 w *= 0.8;
217             }
218 
219             attrSub = Type.copyAttributes(attributes, board.options, 'chart', 'label');
220 
221             for (i = 0; i < x.length; i++) {
222                 if (Type.isFunction(x[i])) {
223                     xp0 = makeXpFun(i, -0.5);
224                     xp1 = makeXpFun(i, 0);
225                     xp2 = makeXpFun(i, 0.5);
226                 } else {
227                     xp0 = x[i] - w * 0.5;
228                     xp1 = x[i];
229                     xp2 = x[i] + w * 0.5;
230                 }
231                 if (Type.isFunction(y[i])) {
232                     yp = y[i]();
233                 } else {
234                     yp = y[i];
235                 }
236                 yp = y[i];
237 
238                 if (attr.dir === 'horizontal') {  // horizontal bars
239                     p[0] = board.create('point', [0, xp0], hiddenPoint);
240                     p[1] = board.create('point', [yp, xp0], hiddenPoint);
241                     p[2] = board.create('point', [yp, xp2], hiddenPoint);
242                     p[3] = board.create('point', [0, xp2], hiddenPoint);
243 
244                     if (Type.exists(attr.labels) && Type.exists(attr.labels[i])) {
245                         attrSub.anchorY = 'middle';
246                         text = board.create('text', [
247                             yp,
248                             xp1,
249                             attr.labels[i]], attrSub);
250                         text.visProp.anchorx = (function(txt) { return function() {
251                             return (txt.X() >= 0) ? 'left' : 'right';
252                         }; })(text);
253 
254                     }
255                 } else { // vertical bars
256                     p[0] = board.create('point', [xp0, 0], hiddenPoint);
257                     p[1] = board.create('point', [xp0, yp], hiddenPoint);
258                     p[2] = board.create('point', [xp2, yp], hiddenPoint);
259                     p[3] = board.create('point', [xp2, 0], hiddenPoint);
260 
261                     if (Type.exists(attr.labels) && Type.exists(attr.labels[i])) {
262                         attrSub.anchorX = 'middle';
263 
264                         text = board.create('text', [
265                             xp1,
266                             yp,
267                             attr.labels[i]], attrSub);
268 
269                         text.visProp.anchory = (function(txt) { return function() {
270                             return (txt.Y() >= 0) ? 'bottom' : 'top';
271                         }; })(text);
272 
273                     }
274                 }
275 
276                 if (Type.isArray(attr.colors)) {
277                     colors = attr.colors;
278                     attr.fillcolor = colors[i % colors.length];
279                 }
280 
281                 pols[i] = board.create('polygon', p, attr);
282                 if (Type.exists(attr.labels) && Type.exists(attr.labels[i])) {
283                     pols[i].text = text;
284                 }
285             }
286 
287             return pols;
288         },
289 
290         drawPoints: function (board, x, y, attributes) {
291             var i,
292                 points = [],
293                 infoboxArray = attributes.infoboxarray;
294 
295             attributes.fixed = true;
296             attributes.name = '';
297 
298             for (i = 0; i < x.length; i++) {
299                 attributes.infoboxtext = infoboxArray ? infoboxArray[i % infoboxArray.length] : false;
300                 points[i] = board.create('point', [x[i], y[i]], attributes);
301             }
302 
303             return points;
304         },
305 
306         drawPie: function (board, y, attributes) {
307             var i, center,
308                 p = [],
309                 sector = [],
310                 s = Statistics.sum(y),
311                 colorArray = attributes.colors,
312                 highlightColorArray = attributes.highlightcolors,
313                 labelArray = attributes.labels,
314                 r = attributes.radius || 4,
315                 radius = r,
316                 cent = attributes.center || [0, 0],
317                 xc = cent[0],
318                 yc = cent[1],
319 
320                 makeRadPointFun = function (j, fun, xc) {
321                     return function () {
322                         var s, i, rad,
323                             t = 0;
324 
325                         for (i = 0; i <= j; i++) {
326                             t += parseFloat(Type.evaluate(y[i]));
327                         }
328 
329                         s = t;
330                         for (i = j + 1; i < y.length; i++) {
331                             s += parseFloat(Type.evaluate(y[i]));
332                         }
333                         rad = (s !== 0) ? (2 * Math.PI * t / s) : 0;
334 
335                         return radius() * Math[fun](rad) + xc;
336                     };
337                 },
338 
339                 highlightHandleLabel = function (f, s) {
340                     var dx = -this.point1.coords.usrCoords[1] + this.point2.coords.usrCoords[1],
341                         dy = -this.point1.coords.usrCoords[2] + this.point2.coords.usrCoords[2];
342 
343                     if (Type.exists(this.label)) {
344                         this.label.rendNode.style.fontSize = (s * Type.evaluate(this.label.visProp.fontsize)) + 'px';
345                         this.label.fullUpdate();
346                     }
347 
348                     this.point2.coords = new Coords(Const.COORDS_BY_USER, [
349                         this.point1.coords.usrCoords[1] + dx * f,
350                         this.point1.coords.usrCoords[2] + dy * f
351                     ], this.board);
352                     this.fullUpdate();
353                 },
354 
355                 highlightFun = function () {
356                     if (!this.highlighted) {
357                         this.highlighted = true;
358                         this.board.highlightedObjects[this.id] = this;
359                         this.board.renderer.highlight(this);
360 
361                         highlightHandleLabel.call(this, 1.1, 2);
362                     }
363                 },
364 
365                 noHighlightFun = function () {
366                     if (this.highlighted) {
367                         this.highlighted = false;
368                         this.board.renderer.noHighlight(this);
369 
370                         highlightHandleLabel.call(this, 0.90909090, 1);
371                     }
372                 },
373 
374                 hiddenPoint = {
375                     fixed: true,
376                     withLabel: false,
377                     visible: false,
378                     name: ''
379                 };
380 
381             if (!Type.isArray(labelArray)) {
382                 labelArray = [];
383                 for (i = 0; i < y.length; i++) {
384                     labelArray[i] = '';
385                 }
386             }
387 
388             if (!Type.isFunction(r)) {
389                 radius = function () {
390                     return r;
391                 };
392             }
393 
394             attributes.highlightonsector = attributes.highlightonsector || false;
395             attributes.straightfirst = false;
396             attributes.straightlast = false;
397 
398             center = board.create('point', [xc, yc], hiddenPoint);
399             p[0] = board.create('point', [
400                 function () {
401                     return radius() + xc;
402                 },
403                 function () {
404                     return yc;
405                 }
406             ], hiddenPoint);
407 
408             for (i = 0; i < y.length; i++) {
409                 p[i + 1] = board.create('point', [makeRadPointFun(i, 'cos', xc), makeRadPointFun(i, 'sin', yc)], hiddenPoint);
410 
411                 attributes.name = labelArray[i];
412                 attributes.withlabel = attributes.name !== '';
413                 attributes.fillcolor = colorArray && colorArray[i % colorArray.length];
414                 attributes.labelcolor = colorArray && colorArray[i % colorArray.length];
415                 attributes.highlightfillcolor = highlightColorArray && highlightColorArray[i % highlightColorArray.length];
416 
417                 sector[i] = board.create('sector', [center, p[i], p[i + 1]], attributes);
418 
419                 if (attributes.highlightonsector) {
420                     // overwrite hasPoint so that the whole sector is used for highlighting
421                     sector[i].hasPoint = sector[i].hasPointSector;
422                 }
423                 if (attributes.highlightbysize) {
424                     sector[i].highlight = highlightFun;
425 
426                     sector[i].noHighlight = noHighlightFun;
427                 }
428 
429             }
430 
431             // Not enough! We need points, but this gives an error in setAttribute.
432             return {sectors: sector, points: p, midpoint: center};
433         },
434 
435         /*
436          * labelArray=[ row1, row2, row3 ]
437          * paramArray=[ paramx, paramy, paramz ]
438          * parents=[[x1, y1, z1], [x2, y2, z2], [x3, y3, z3]]
439          */
440         drawRadar: function (board, parents, attributes) {
441             var i, j, paramArray, numofparams, maxes, mins,
442                 la, pdata, ssa, esa, ssratio, esratio,
443                 sshifts, eshifts, starts, ends,
444                 labelArray, colorArray, highlightColorArray, radius, myAtts,
445                 cent, xc, yc, center, start_angle, rad, p, line, t,
446                 xcoord, ycoord, polygons, legend_position, circles, lxoff, lyoff,
447                 cla, clabelArray, ncircles, pcircles, angle, dr, sw, data,
448                 len = parents.length,
449 
450                 get_anchor = function () {
451                     var x1, x2, y1, y2,
452                         relCoords = Type.evaluate(this.visProp.label.offset).slice(0);
453 
454                     x1 = this.point1.X();
455                     x2 = this.point2.X();
456                     y1 = this.point1.Y();
457                     y2 = this.point2.Y();
458                     if (x2 < x1) {
459                         relCoords[0] = -relCoords[0];
460                     }
461 
462                     if (y2 < y1) {
463                         relCoords[1] = -relCoords[1];
464                     }
465 
466                     this.setLabelRelativeCoords(relCoords);
467 
468                     return new Coords(Const.COORDS_BY_USER, [this.point2.X(), this.point2.Y()], this.board);
469                 },
470 
471                 get_transform = function (angle, i) {
472                     var t, tscale, trot;
473 
474                     t = board.create('transform', [-(starts[i] - sshifts[i]), 0], {type: 'translate'});
475                     tscale = board.create('transform', [radius / ((ends[i] + eshifts[i]) - (starts[i] - sshifts[i])), 1], {type: 'scale'});
476                     t.melt(tscale);
477                     trot = board.create('transform', [angle], {type: 'rotate'});
478                     t.melt(trot);
479 
480                     return t;
481                 };
482 
483             if (len <= 0) {
484                 JXG.debug("No data");
485                 return;
486             }
487             // labels for axes
488             paramArray = attributes.paramarray;
489             if (!Type.exists(paramArray)) {
490                 JXG.debug("Need paramArray attribute");
491                 return;
492             }
493             numofparams = paramArray.length;
494             if (numofparams <= 1) {
495                 JXG.debug("Need more than 1 param");
496                 return;
497             }
498 
499             for (i = 0; i < len; i++) {
500                 if (numofparams !== parents[i].length) {
501                     JXG.debug("Use data length equal to number of params (" + parents[i].length + " != " + numofparams + ")");
502                     return;
503                 }
504             }
505 
506             maxes = [];
507             mins = [];
508 
509             for (j = 0; j < numofparams; j++) {
510                 maxes[j] = parents[0][j];
511                 mins[j] = maxes[j];
512             }
513 
514             for (i = 1; i < len; i++) {
515                 for (j = 0; j < numofparams; j++) {
516                     if (parents[i][j] > maxes[j]) {
517                         maxes[j] = parents[i][j];
518                     }
519 
520                     if (parents[i][j] < mins[j]) {
521                         mins[j] = parents[i][j];
522                     }
523                 }
524             }
525 
526             la = [];
527             pdata = [];
528 
529             for (i = 0; i < len; i++) {
530                 la[i] = '';
531                 pdata[i] = [];
532             }
533 
534             ssa = [];
535             esa = [];
536 
537             // 0 <= Offset from chart center <=1
538             ssratio = attributes.startshiftratio || 0;
539             // 0 <= Offset from chart radius <=1
540             esratio = attributes.endshiftratio || 0;
541 
542             for (i = 0; i < numofparams; i++) {
543                 ssa[i] = (maxes[i] - mins[i]) * ssratio;
544                 esa[i] = (maxes[i] - mins[i]) * esratio;
545             }
546 
547             // Adjust offsets per each axis
548             sshifts = attributes.startshiftarray || ssa;
549             eshifts = attributes.endshiftarray || esa;
550             // Values for inner circle, minimums by default
551             starts = attributes.startarray || mins;
552 
553             if (Type.exists(attributes.start)) {
554                 for (i = 0; i < numofparams; i++) {
555                     starts[i] = attributes.start;
556                 }
557             }
558 
559             // Values for outer circle, maximums by default
560             ends = attributes.endarray || maxes;
561             if (Type.exists(attributes.end)) {
562                 for (i = 0; i < numofparams; i++) {
563                     ends[i] = attributes.end;
564                 }
565             }
566 
567             if (sshifts.length !== numofparams) {
568                 JXG.debug("Start shifts length is not equal to number of parameters");
569                 return;
570             }
571 
572             if (eshifts.length !== numofparams) {
573                 JXG.debug("End shifts length is not equal to number of parameters");
574                 return;
575             }
576 
577             if (starts.length !== numofparams) {
578                 JXG.debug("Starts length is not equal to number of parameters");
579                 return;
580             }
581 
582             if (ends.length !== numofparams) {
583                 JXG.debug("Ends length is not equal to number of parameters");
584                 return;
585             }
586 
587             // labels for legend
588             labelArray = attributes.labelarray || la;
589             colorArray = attributes.colors;
590             highlightColorArray = attributes.highlightcolors;
591             radius = attributes.radius || 10;
592             sw = attributes.strokewidth || 1;
593 
594             if (!Type.exists(attributes.highlightonsector)) {
595                 attributes.highlightonsector = false;
596             }
597 
598             myAtts = {
599                 name: attributes.name,
600                 id: attributes.id,
601                 strokewidth: sw,
602                 polystrokewidth: attributes.polystrokewidth || sw,
603                 strokecolor: attributes.strokecolor || 'black',
604                 straightfirst: false,
605                 straightlast: false,
606                 fillcolor: attributes.fillColor || '#FFFF88',
607                 fillopacity: attributes.fillOpacity || 0.4,
608                 highlightfillcolor: attributes.highlightFillColor || '#FF7400',
609                 highlightstrokecolor: attributes.highlightStrokeColor || 'black',
610                 gradient: attributes.gradient || 'none'
611             };
612 
613             cent = attributes.center || [0, 0];
614             xc = cent[0];
615             yc = cent[1];
616             center = board.create('point', [xc, yc], {name: '', fixed: true, withlabel: false, visible: false});
617             start_angle = Math.PI / 2 - Math.PI / numofparams;
618             start_angle = attributes.startangle || 0;
619             rad = start_angle;
620             p = [];
621             line = [];
622 
623             for (i = 0; i < numofparams; i++) {
624                 rad += 2 * Math.PI / numofparams;
625                 xcoord = radius * Math.cos(rad) + xc;
626                 ycoord = radius * Math.sin(rad) + yc;
627 
628                 p[i] = board.create('point', [xcoord, ycoord], {name: '', fixed: true, withlabel: false, visible: false});
629                 line[i] = board.create('line', [center, p[i]], {
630                     name: paramArray[i],
631                     strokeColor: myAtts.strokecolor,
632                     strokeWidth: myAtts.strokewidth,
633                     strokeOpacity: 1.0,
634                     straightFirst: false,
635                     straightLast: false,
636                     withLabel: true,
637                     highlightStrokeColor: myAtts.highlightstrokecolor
638                 });
639                 line[i].getLabelAnchor = get_anchor;
640                 t = get_transform(rad, i);
641 
642                 for (j = 0; j < parents.length; j++) {
643                     data = parents[j][i];
644                     pdata[j][i] = board.create('point', [data, 0], {name: '', fixed: true, withlabel: false, visible: false});
645                     pdata[j][i].addTransform(pdata[j][i], t);
646                 }
647             }
648 
649             polygons = [];
650             for (i = 0; i < len; i++) {
651                 myAtts.labelcolor = colorArray && colorArray[i % colorArray.length];
652                 myAtts.strokecolor = colorArray && colorArray[i % colorArray.length];
653                 myAtts.fillcolor = colorArray && colorArray[i % colorArray.length];
654                 polygons[i] = board.create('polygon', pdata[i], {
655                     withLines: true,
656                     withLabel: false,
657                     fillColor: myAtts.fillcolor,
658                     fillOpacity: myAtts.fillopacity,
659                     highlightFillColor: myAtts.highlightfillcolor
660                 });
661 
662                 for (j = 0; j < numofparams; j++) {
663                     polygons[i].borders[j].setAttribute('strokecolor:' + colorArray[i % colorArray.length]);
664                     polygons[i].borders[j].setAttribute('strokewidth:' + myAtts.polystrokewidth);
665                 }
666             }
667 
668             legend_position = attributes.legendposition || 'none';
669             switch (legend_position) {
670             case 'right':
671                 lxoff = attributes.legendleftoffset || 2;
672                 lyoff = attributes.legendtopoffset || 1;
673 
674                 this.legend = board.create('legend', [xc + radius + lxoff, yc + radius - lyoff], {
675                     labels: labelArray,
676                     colors: colorArray
677                 });
678                 break;
679             case 'none':
680                 break;
681             default:
682                 JXG.debug('Unknown legend position');
683             }
684 
685             circles = [];
686             if (attributes.showcircles) {
687                 cla = [];
688                 for (i = 0; i < 6; i++) {
689                     cla[i] = 20 * i;
690                 }
691                 cla[0] = "0";
692                 clabelArray = attributes.circlelabelarray || cla;
693                 ncircles = clabelArray.length;
694 
695                 if (ncircles < 2) {
696                     JXG.debug("Too less circles");
697                     return;
698                 }
699 
700                 pcircles = [];
701                 angle = start_angle + Math.PI / numofparams;
702                 t = get_transform(angle, 0);
703 
704                 myAtts.fillcolor = 'none';
705                 myAtts.highlightfillcolor = 'none';
706                 myAtts.strokecolor = attributes.strokecolor || 'black';
707                 myAtts.strokewidth = attributes.circlestrokewidth || 0.5;
708                 myAtts.layer = 0;
709 
710                 // we have ncircles-1 intervals between ncircles circles
711                 dr = (ends[0] - starts[0]) / (ncircles - 1);
712 
713                 for (i = 0; i < ncircles; i++) {
714                     pcircles[i] = board.create('point', [starts[0] + i * dr, 0], {
715                         name: clabelArray[i],
716                         size: 0,
717                         fixed: true,
718                         withLabel: true,
719                         visible: true
720                     });
721                     pcircles[i].addTransform(pcircles[i], t);
722                     circles[i] = board.create('circle', [center, pcircles[i]], myAtts);
723                 }
724 
725             }
726             this.rendNode = polygons[0].rendNode;
727             return {
728                 circles: circles,
729                 lines: line,
730                 points: pdata,
731                 midpoint: center,
732                 polygons: polygons
733             };
734         },
735 
736         /**
737          * Then, the update function of the renderer
738          * is called.  Since a chart is only an abstract element,
739          * containing other elements, this function is empty.
740          */
741         updateRenderer: function () {
742             return this;
743         },
744 
745         /**
746          * Update of the defining points
747          */
748         update: function () {
749             if (this.needsUpdate) {
750                 this.updateDataArray();
751             }
752 
753             return this;
754         },
755 
756         /**
757          * For dynamic charts update
758          * can be used to compute new entries
759          * for the arrays this.dataX and
760          * this.dataY. It is used in @see update.
761          * Default is an empty method, can be overwritten
762          * by the user.
763          */
764         updateDataArray: function () {}
765     });
766 
767     JXG.createChart = function (board, parents, attributes) {
768         var data, row, i, j, col,
769             charts = [],
770             w, x, showRows, attr,
771             originalWidth, name, strokeColor, fillColor,
772             hStrokeColor, hFillColor, len,
773             table = Env.isBrowser ? board.document.getElementById(parents[0]) : null;
774 
775         if ((parents.length === 1) && (Type.isString(parents[0]))) {
776             if (Type.exists(table)) {
777                 // extract the data
778                 attr = Type.copyAttributes(attributes, board.options, 'chart');
779 
780                 table = (new DataSource()).loadFromTable(parents[0], attr.withheaders, attr.withheaders);
781                 data = table.data;
782                 col = table.columnHeaders;
783                 row = table.rowHeaders;
784 
785                 originalWidth = attr.width;
786                 name = attr.name;
787                 strokeColor = attr.strokecolor;
788                 fillColor = attr.fillcolor;
789                 hStrokeColor = attr.highlightstrokecolor;
790                 hFillColor = attr.highlightfillcolor;
791 
792                 board.suspendUpdate();
793 
794                 len = data.length;
795                 showRows = [];
796                 if (attr.rows && Type.isArray(attr.rows)) {
797                     for (i = 0; i < len; i++) {
798                         for (j = 0; j < attr.rows.length; j++) {
799                             if ((attr.rows[j] === i) || (attr.withheaders && attr.rows[j] === row[i])) {
800                                 showRows.push(data[i]);
801                                 break;
802                             }
803                         }
804                     }
805                 } else {
806                     showRows = data;
807                 }
808 
809                 len = showRows.length;
810 
811                 for (i = 0; i < len; i++) {
812 
813                     x = [];
814                     if (attr.chartstyle && attr.chartstyle.indexOf('bar') !== -1) {
815                         if (originalWidth) {
816                             w = originalWidth;
817                         } else {
818                             w = 0.8;
819                         }
820 
821                         x.push(1 - w / 2 + (i + 0.5) * w / len);
822 
823                         for (j = 1; j < showRows[i].length; j++) {
824                             x.push(x[j - 1] + 1);
825                         }
826 
827                         attr.width = w / len;
828                     }
829 
830                     if (name && name.length === len) {
831                         attr.name = name[i];
832                     } else if (attr.withheaders) {
833                         attr.name = col[i];
834                     }
835 
836                     if (strokeColor && strokeColor.length === len) {
837                         attr.strokecolor = strokeColor[i];
838                     } else {
839                         attr.strokecolor = Color.hsv2rgb(((i + 1) / len) * 360, 0.9, 0.6);
840                     }
841 
842                     if (fillColor && fillColor.length === len) {
843                         attr.fillcolor = fillColor[i];
844                     } else {
845                         attr.fillcolor = Color.hsv2rgb(((i + 1) / len) * 360, 0.9, 1.0);
846                     }
847 
848                     if (hStrokeColor && hStrokeColor.length === len) {
849                         attr.highlightstrokecolor = hStrokeColor[i];
850                     } else {
851                         attr.highlightstrokecolor = Color.hsv2rgb(((i + 1) / len) * 360, 0.9, 1.0);
852                     }
853 
854                     if (hFillColor && hFillColor.length === len) {
855                         attr.highlightfillcolor = hFillColor[i];
856                     } else {
857                         attr.highlightfillcolor = Color.hsv2rgb(((i + 1) / len) * 360, 0.9, 0.6);
858                     }
859 
860                     if (attr.chartstyle && attr.chartstyle.indexOf('bar') !== -1) {
861                         charts.push(new JXG.Chart(board, [x, showRows[i]], attr));
862                     } else {
863                         charts.push(new JXG.Chart(board, [showRows[i]], attr));
864                     }
865                 }
866 
867                 board.unsuspendUpdate();
868 
869             }
870             return charts;
871         }
872 
873         attr = Type.copyAttributes(attributes, board.options, 'chart');
874         return new JXG.Chart(board, parents, attr);
875     };
876 
877     JXG.registerElement('chart', JXG.createChart);
878 
879     /**
880      * Legend for chart
881      *
882      **/
883     JXG.Legend = function (board, coords, attributes) {
884         var attr;
885 
886         /* Call the constructor of GeometryElement */
887         this.constructor();
888 
889         attr = Type.copyAttributes(attributes, board.options, 'legend');
890 
891         this.board = board;
892         this.coords = new Coords(Const.COORDS_BY_USER, coords, this.board);
893         this.myAtts = {};
894         this.label_array = attr.labelarray || attr.labels;
895         this.color_array = attr.colorarray || attr.colors;
896         this.lines = [];
897         this.myAtts.strokewidth = attr.strokewidth || 5;
898         this.myAtts.straightfirst = false;
899         this.myAtts.straightlast = false;
900         this.myAtts.withlabel = true;
901         this.myAtts.fixed = true;
902         this.style = attr.legendstyle || attr.style;
903 
904         if (this.style === 'vertical') {
905             this.drawVerticalLegend(board, attr);
906         } else {
907             throw new Error('JSXGraph: Unknown legend style: ' + this.style);
908         }
909     };
910 
911     JXG.Legend.prototype = new GeometryElement();
912 
913     JXG.Legend.prototype.drawVerticalLegend = function (board, attributes) {
914         var i,
915             line_length = attributes.linelength || 1,
916             offy = (attributes.rowheight || 20) / this.board.unitY,
917 
918             getLabelAnchor = function () {
919                 this.setLabelRelativeCoords(this.visProp.label.offset);
920                 return new Coords(Const.COORDS_BY_USER, [this.point2.X(), this.point2.Y()], this.board);
921             };
922 
923         for (i = 0; i < this.label_array.length; i++) {
924             this.myAtts.strokecolor = this.color_array[i];
925             this.myAtts.highlightstrokecolor = this.color_array[i];
926             this.myAtts.name = this.label_array[i];
927             this.myAtts.label = {
928                 offset: [10, 0],
929                 strokeColor: this.color_array[i],
930                 strokeWidth: this.myAtts.strokewidth
931             };
932 
933             this.lines[i] = board.create('line', [
934                 [this.coords.usrCoords[1], this.coords.usrCoords[2] - i * offy],
935                 [this.coords.usrCoords[1] + line_length, this.coords.usrCoords[2] - i * offy]],
936                 this.myAtts);
937 
938             this.lines[i].getLabelAnchor = getLabelAnchor;
939 
940         }
941     };
942 
943     JXG.createLegend = function (board, parents, attributes) {
944         //parents are coords of left top point of the legend
945         var start_from = [0, 0];
946 
947         if (Type.exists(parents)) {
948             if (parents.length === 2) {
949                 start_from = parents;
950             }
951         }
952 
953         return new JXG.Legend(board, start_from, attributes);
954     };
955 
956     JXG.registerElement('legend', JXG.createLegend);
957 
958     return {
959         Chart: JXG.Chart,
960         Legend: JXG.Legend,
961         createChart: JXG.createChart,
962         createLegend: JXG.createLegend
963     };
964 });
965