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