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