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