1 /* 2 Copyright 2008-2025 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, text, w, 230 xp0, xp1, xp2, yp, 231 colors, 232 pols = [], 233 p = [], 234 attr, 235 attrSub, 236 makeXpFun = function (i, f) { 237 return function () { 238 return x[i]() - f * w; 239 }; 240 }, 241 hiddenPoint = { 242 fixed: true, 243 withLabel: false, 244 visible: false, 245 name: "" 246 }; 247 248 attr = Type.copyAttributes(attributes, board.options, "chart"); 249 250 // Determine the width of the bars 251 if (attr && attr.width) { 252 // width given 253 w = attr.width; 254 } else { 255 if (x.length <= 1) { 256 w = 1; 257 } else { 258 // Find minimum distance between to bars. 259 w = x[1] - x[0]; 260 for (i = 1; i < x.length - 1; i++) { 261 w = x[i + 1] - x[i] < w ? x[i + 1] - x[i] : w; 262 } 263 } 264 w *= 0.8; 265 } 266 267 attrSub = Type.copyAttributes(attributes, board.options, "chart", "label"); 268 269 for (i = 0; i < x.length; i++) { 270 if (Type.isFunction(x[i])) { 271 xp0 = makeXpFun(i, -0.5); 272 xp1 = makeXpFun(i, 0); 273 xp2 = makeXpFun(i, 0.5); 274 } else { 275 xp0 = x[i] - w * 0.5; 276 xp1 = x[i]; 277 xp2 = x[i] + w * 0.5; 278 } 279 if (Type.isFunction(y[i])) { 280 yp = y[i](); 281 } else { 282 yp = y[i]; 283 } 284 yp = y[i]; 285 286 if (attr.dir === "horizontal") { 287 // horizontal bars 288 p[0] = board.create("point", [0, xp0], hiddenPoint); 289 p[1] = board.create("point", [yp, xp0], hiddenPoint); 290 p[2] = board.create("point", [yp, xp2], hiddenPoint); 291 p[3] = board.create("point", [0, xp2], hiddenPoint); 292 293 if (Type.exists(attr.labels) && Type.exists(attr.labels[i])) { 294 attrSub.anchorX = function (self) { 295 return self.X() >= 0 ? "left" : "right"; 296 }; 297 attrSub.anchorY = "middle"; 298 text = board.create("text", [yp, xp1, attr.labels[i]], attrSub); 299 } 300 } else { 301 // vertical bars 302 p[0] = board.create("point", [xp0, 0], hiddenPoint); 303 p[1] = board.create("point", [xp0, yp], hiddenPoint); 304 p[2] = board.create("point", [xp2, yp], hiddenPoint); 305 p[3] = board.create("point", [xp2, 0], hiddenPoint); 306 307 if (Type.exists(attr.labels) && Type.exists(attr.labels[i])) { 308 attrSub.anchorX = "middle"; 309 attrSub.anchorY = function (self) { 310 return self.Y() >= 0 ? "bottom" : "top"; 311 }; 312 text = board.create("text", [xp1, yp, attr.labels[i]], attrSub); 313 } 314 } 315 316 if (Type.isArray(attr.colors)) { 317 colors = attr.colors; 318 attr.fillcolor = colors[i % colors.length]; 319 } 320 321 pols[i] = board.create("polygon", p, attr); 322 if (Type.exists(attr.labels) && Type.exists(attr.labels[i])) { 323 pols[i].text = text; 324 pols[i].addChild(text); 325 } 326 } 327 328 return pols; 329 }, 330 331 /** 332 * Create chart consisting of JSXGraph points. 333 * Attributes to change the layout of the point chart are: 334 * <ul> 335 * <li> fixed (Boolean) 336 * <li> infoboxArray (Array): Texts for the infobox 337 * </ul> 338 * 339 * @param {String|JXG.Board} board The board the chart is drawn on 340 * @param {Array} x Array of x-coordinates 341 * @param {Array} y Array of y-coordinates 342 * @param {Object} attributes Javascript object containing attributes like colors 343 * @returns {Array} Array of JSXGraph points 344 */ 345 drawPoints: function (board, x, y, attributes) { 346 var i, 347 points = [], 348 infoboxArray = attributes.infoboxarray; 349 350 attributes.fixed = true; 351 attributes.name = ""; 352 353 for (i = 0; i < x.length; i++) { 354 attributes.infoboxtext = infoboxArray 355 ? infoboxArray[i % infoboxArray.length] 356 : false; 357 points[i] = board.create("point", [x[i], y[i]], attributes); 358 } 359 360 return points; 361 }, 362 363 /** 364 * Create pie chart. 365 * Attributes to change the layout of the pie chart are: 366 * <ul> 367 * <li> labels: array of labels 368 * <li> colors: (Array) 369 * <li> highlightColors (Array) 370 * <li> radius 371 * <li> center (coordinate array) 372 * <li> highlightOnSector (Boolean) 373 * </ul> 374 * 375 * @param {String|JXG.Board} board The board the chart is drawn on 376 * @param {Array} y Array of x-coordinates 377 * @param {Object} attributes Javascript object containing attributes like colors 378 * @returns {Object} with keys: "{sectors, points, midpoint}" 379 */ 380 drawPie: function (board, y, attributes) { 381 var i, 382 center, 383 p = [], 384 sector = [], 385 // s = Statistics.sum(y), 386 colorArray = attributes.colors, 387 highlightColorArray = attributes.highlightcolors, 388 labelArray = attributes.labels, 389 r = attributes.radius || 4, 390 radius = r, 391 cent = attributes.center || [0, 0], 392 xc = cent[0], 393 yc = cent[1], 394 makeRadPointFun = function (j, fun, xc) { 395 return function () { 396 var s, 397 i, 398 rad, 399 t = 0; 400 401 for (i = 0; i <= j; i++) { 402 t += parseFloat(Type.evaluate(y[i])); 403 } 404 405 s = t; 406 for (i = j + 1; i < y.length; i++) { 407 s += parseFloat(Type.evaluate(y[i])); 408 } 409 rad = s !== 0 ? (2 * Math.PI * t) / s : 0; 410 411 return radius() * Math[fun](rad) + xc; 412 }; 413 }, 414 highlightHandleLabel = function (f, s) { 415 var dx = -this.point1.coords.usrCoords[1] + this.point2.coords.usrCoords[1], 416 dy = -this.point1.coords.usrCoords[2] + this.point2.coords.usrCoords[2]; 417 418 if (Type.exists(this.label)) { 419 this.label.rendNode.style.fontSize = 420 s * this.label.evalVisProp('fontsize') + "px"; 421 this.label.fullUpdate(); 422 } 423 424 this.point2.coords = new Coords( 425 Const.COORDS_BY_USER, 426 [ 427 this.point1.coords.usrCoords[1] + dx * f, 428 this.point1.coords.usrCoords[2] + dy * f 429 ], 430 this.board 431 ); 432 this.fullUpdate(); 433 }, 434 highlightFun = function () { 435 if (!this.highlighted) { 436 this.highlighted = true; 437 this.board.highlightedObjects[this.id] = this; 438 this.board.renderer.highlight(this); 439 440 highlightHandleLabel.call(this, 1.1, 2); 441 } 442 }, 443 noHighlightFun = function () { 444 if (this.highlighted) { 445 this.highlighted = false; 446 this.board.renderer.noHighlight(this); 447 448 highlightHandleLabel.call(this, 0.9090909, 1); 449 } 450 }, 451 hiddenPoint = { 452 fixed: true, 453 withLabel: false, 454 visible: false, 455 name: "" 456 }; 457 458 if (!Type.isArray(labelArray)) { 459 labelArray = []; 460 for (i = 0; i < y.length; i++) { 461 labelArray[i] = ""; 462 } 463 } 464 465 if (!Type.isFunction(r)) { 466 radius = function () { 467 return r; 468 }; 469 } 470 471 attributes.highlightonsector = attributes.highlightonsector || false; 472 attributes.straightfirst = false; 473 attributes.straightlast = false; 474 475 center = board.create("point", [xc, yc], hiddenPoint); 476 p[0] = board.create( 477 "point", 478 [ 479 function () { 480 return radius() + xc; 481 }, 482 function () { 483 return yc; 484 } 485 ], 486 hiddenPoint 487 ); 488 489 for (i = 0; i < y.length; i++) { 490 p[i + 1] = board.create( 491 "point", 492 [makeRadPointFun(i, "cos", xc), makeRadPointFun(i, "sin", yc)], 493 hiddenPoint 494 ); 495 496 attributes.name = labelArray[i]; 497 attributes.withlabel = attributes.name !== ""; 498 attributes.fillcolor = colorArray && colorArray[i % colorArray.length]; 499 attributes.labelcolor = colorArray && colorArray[i % colorArray.length]; 500 attributes.highlightfillcolor = 501 highlightColorArray && highlightColorArray[i % highlightColorArray.length]; 502 503 sector[i] = board.create("sector", [center, p[i], p[i + 1]], attributes); 504 505 if (attributes.highlightonsector) { 506 // overwrite hasPoint so that the whole sector is used for highlighting 507 sector[i].hasPoint = sector[i].hasPointSector; 508 } 509 if (attributes.highlightbysize) { 510 sector[i].highlight = highlightFun; 511 512 sector[i].noHighlight = noHighlightFun; 513 } 514 } 515 516 // Not enough! We need points, but this gives an error in setAttribute. 517 return { sectors: sector, points: p, midpoint: center }; 518 }, 519 520 /** 521 * Create radar chart. 522 * Attributes to change the layout of the pie chart are: 523 * <ul> 524 * <li> paramArray: labels for axes, [ paramx, paramy, paramz ] 525 * <li> startShiftRatio: 0 <= offset from chart center <=1 526 * <li> endShiftRatio: 0 <= offset from chart radius <=1 527 * <li> startShiftArray: Adjust offsets per each axis 528 * <li> endShiftArray: Adjust offsets per each axis 529 * <li> startArray: Values for inner circle. Default values: minimums 530 * <li> start: one value to overwrite all startArray values 531 * <li> endArray: Values for outer circle, maximums by default 532 * <li> end: one value to overwrite all endArray values 533 * <li> labelArray 534 * <li> polyStrokeWidth 535 * <li> colors 536 * <li> highlightcolors 537 * <li> labelArray: [ row1, row2, row3 ] 538 * <li> radius 539 * <li> legendPosition 540 * <li> showCircles 541 * <li> circleLabelArray 542 * <li> circleStrokeWidth 543 * </ul> 544 * 545 * @param {String|JXG.Board} board The board the chart is drawn on 546 * @param {Array} parents Array of coordinates, e.g. [[x1, y1, z1], [x2, y2, z2], [x3, y3, z3]] 547 * @param {Object} attributes Javascript object containing attributes like colors 548 * @returns {Object} with keys "{circles, lines, points, midpoint, polygons}" 549 */ 550 drawRadar: function (board, parents, attributes) { 551 var i, 552 j, 553 paramArray, 554 numofparams, 555 maxes, 556 mins, 557 la, 558 pdata, 559 ssa, 560 esa, 561 ssratio, 562 esratio, 563 sshifts, 564 eshifts, 565 starts, 566 ends, 567 labelArray, 568 colorArray, 569 // highlightColorArray, 570 radius, 571 myAtts, 572 cent, 573 xc, 574 yc, 575 center, 576 start_angle, 577 rad, 578 p, 579 line, 580 t, 581 xcoord, 582 ycoord, 583 polygons, 584 legend_position, 585 circles, 586 lxoff, 587 lyoff, 588 cla, 589 clabelArray, 590 ncircles, 591 pcircles, 592 angle, 593 dr, 594 sw, 595 data, 596 len = parents.length, 597 get_anchor = function () { 598 var x1, x2, y1, y2, 599 relCoords = this.evalVisProp('label.offset).slice(0'); 600 601 x1 = this.point1.X(); 602 x2 = this.point2.X(); 603 y1 = this.point1.Y(); 604 y2 = this.point2.Y(); 605 if (x2 < x1) { 606 relCoords[0] = -relCoords[0]; 607 } 608 609 if (y2 < y1) { 610 relCoords[1] = -relCoords[1]; 611 } 612 613 this.setLabelRelativeCoords(relCoords); 614 615 return new Coords( 616 Const.COORDS_BY_USER, 617 [this.point2.X(), this.point2.Y()], 618 this.board 619 ); 620 }, 621 get_transform = function (angle, i) { 622 var t, tscale, trot; 623 624 t = board.create("transform", [-(starts[i] - sshifts[i]), 0], { 625 type: "translate" 626 }); 627 tscale = board.create( 628 "transform", 629 [radius / (ends[i] + eshifts[i] - (starts[i] - sshifts[i])), 1], 630 { type: "scale" } 631 ); 632 t.melt(tscale); 633 trot = board.create("transform", [angle], { type: "rotate" }); 634 t.melt(trot); 635 636 return t; 637 }; 638 639 if (len <= 0) { 640 throw new Error("JSXGraph radar chart: no data"); 641 } 642 // labels for axes 643 paramArray = attributes.paramarray; 644 if (!Type.exists(paramArray)) { 645 throw new Error("JSXGraph radar chart: need paramArray attribute"); 646 } 647 numofparams = paramArray.length; 648 if (numofparams <= 1) { 649 throw new Error("JSXGraph radar chart: need more than one param in paramArray"); 650 } 651 652 for (i = 0; i < len; i++) { 653 if (numofparams !== parents[i].length) { 654 throw new Error( 655 "JSXGraph radar chart: use data length equal to number of params (" + 656 parents[i].length + 657 " != " + 658 numofparams + 659 ")" 660 ); 661 } 662 } 663 664 maxes = []; 665 mins = []; 666 667 for (j = 0; j < numofparams; j++) { 668 maxes[j] = parents[0][j]; 669 mins[j] = maxes[j]; 670 } 671 672 for (i = 1; i < len; i++) { 673 for (j = 0; j < numofparams; j++) { 674 if (parents[i][j] > maxes[j]) { 675 maxes[j] = parents[i][j]; 676 } 677 678 if (parents[i][j] < mins[j]) { 679 mins[j] = parents[i][j]; 680 } 681 } 682 } 683 684 la = []; 685 pdata = []; 686 687 for (i = 0; i < len; i++) { 688 la[i] = ""; 689 pdata[i] = []; 690 } 691 692 ssa = []; 693 esa = []; 694 695 // 0 <= Offset from chart center <=1 696 ssratio = attributes.startshiftratio || 0; 697 // 0 <= Offset from chart radius <=1 698 esratio = attributes.endshiftratio || 0; 699 700 for (i = 0; i < numofparams; i++) { 701 ssa[i] = (maxes[i] - mins[i]) * ssratio; 702 esa[i] = (maxes[i] - mins[i]) * esratio; 703 } 704 705 // Adjust offsets per each axis 706 sshifts = attributes.startshiftarray || ssa; 707 eshifts = attributes.endshiftarray || esa; 708 // Values for inner circle, minimums by default 709 starts = attributes.startarray || mins; 710 711 if (Type.exists(attributes.start)) { 712 for (i = 0; i < numofparams; i++) { 713 starts[i] = attributes.start; 714 } 715 } 716 717 // Values for outer circle, maximums by default 718 ends = attributes.endarray || maxes; 719 if (Type.exists(attributes.end)) { 720 for (i = 0; i < numofparams; i++) { 721 ends[i] = attributes.end; 722 } 723 } 724 725 if (sshifts.length !== numofparams) { 726 throw new Error( 727 "JSXGraph radar chart: start shifts length is not equal to number of parameters" 728 ); 729 } 730 731 if (eshifts.length !== numofparams) { 732 throw new Error( 733 "JSXGraph radar chart: end shifts length is not equal to number of parameters" 734 ); 735 } 736 737 if (starts.length !== numofparams) { 738 throw new Error( 739 "JSXGraph radar chart: starts length is not equal to number of parameters" 740 ); 741 } 742 743 if (ends.length !== numofparams) { 744 throw new Error( 745 "JSXGraph radar chart: snds length is not equal to number of parameters" 746 ); 747 } 748 749 // labels for legend 750 labelArray = attributes.labelarray || la; 751 colorArray = attributes.colors; 752 // highlightColorArray = attributes.highlightcolors; 753 radius = attributes.radius || 10; 754 sw = attributes.strokewidth || 1; 755 756 if (!Type.exists(attributes.highlightonsector)) { 757 attributes.highlightonsector = false; 758 } 759 760 myAtts = { 761 name: attributes.name, 762 id: attributes.id, 763 strokewidth: sw, 764 polystrokewidth: attributes.polystrokewidth || sw, 765 strokecolor: attributes.strokecolor || "black", 766 straightfirst: false, 767 straightlast: false, 768 fillcolor: attributes.fillColor || "#FFFF88", 769 fillopacity: attributes.fillOpacity || 0.4, 770 highlightfillcolor: attributes.highlightFillColor || "#FF7400", 771 highlightstrokecolor: attributes.highlightStrokeColor || "black", 772 gradient: attributes.gradient || "none" 773 }; 774 775 cent = attributes.center || [0, 0]; 776 xc = cent[0]; 777 yc = cent[1]; 778 center = board.create("point", [xc, yc], { 779 name: "", 780 fixed: true, 781 withlabel: false, 782 visible: false 783 }); 784 start_angle = Math.PI / 2 - Math.PI / numofparams; 785 start_angle = attributes.startangle || 0; 786 rad = start_angle; 787 p = []; 788 line = []; 789 790 for (i = 0; i < numofparams; i++) { 791 rad += (2 * Math.PI) / numofparams; 792 xcoord = radius * Math.cos(rad) + xc; 793 ycoord = radius * Math.sin(rad) + yc; 794 795 p[i] = board.create("point", [xcoord, ycoord], { 796 name: "", 797 fixed: true, 798 withlabel: false, 799 visible: false 800 }); 801 line[i] = board.create("line", [center, p[i]], { 802 name: paramArray[i], 803 strokeColor: myAtts.strokecolor, 804 strokeWidth: myAtts.strokewidth, 805 strokeOpacity: 1.0, 806 straightFirst: false, 807 straightLast: false, 808 withLabel: true, 809 highlightStrokeColor: myAtts.highlightstrokecolor 810 }); 811 line[i].getLabelAnchor = get_anchor; 812 t = get_transform(rad, i); 813 814 for (j = 0; j < parents.length; j++) { 815 data = parents[j][i]; 816 pdata[j][i] = board.create("point", [data, 0], { 817 name: "", 818 fixed: true, 819 withlabel: false, 820 visible: false 821 }); 822 pdata[j][i].addTransform(pdata[j][i], t); 823 } 824 } 825 826 polygons = []; 827 for (i = 0; i < len; i++) { 828 myAtts.labelcolor = colorArray && colorArray[i % colorArray.length]; 829 myAtts.strokecolor = colorArray && colorArray[i % colorArray.length]; 830 myAtts.fillcolor = colorArray && colorArray[i % colorArray.length]; 831 polygons[i] = board.create("polygon", pdata[i], { 832 withLines: true, 833 withLabel: false, 834 fillColor: myAtts.fillcolor, 835 fillOpacity: myAtts.fillopacity, 836 highlightFillColor: myAtts.highlightfillcolor 837 }); 838 839 for (j = 0; j < numofparams; j++) { 840 polygons[i].borders[j].setAttribute( 841 "strokecolor:" + colorArray[i % colorArray.length] 842 ); 843 polygons[i].borders[j].setAttribute( 844 "strokewidth:" + myAtts.polystrokewidth 845 ); 846 } 847 } 848 849 legend_position = attributes.legendposition || "none"; 850 switch (legend_position) { 851 case "right": 852 lxoff = attributes.legendleftoffset || 2; 853 lyoff = attributes.legendtopoffset || 1; 854 855 this.legend = board.create( 856 "legend", 857 [xc + radius + lxoff, yc + radius - lyoff], 858 { 859 labels: labelArray, 860 colors: colorArray 861 } 862 ); 863 break; 864 case "none": 865 break; 866 default: 867 JXG.debug("Unknown legend position"); 868 } 869 870 circles = []; 871 if (attributes.showcircles) { 872 cla = []; 873 for (i = 0; i < 6; i++) { 874 cla[i] = 20 * i; 875 } 876 cla[0] = "0"; 877 clabelArray = attributes.circlelabelarray || cla; 878 ncircles = clabelArray.length; 879 880 if (ncircles < 2) { 881 throw new Error( 882 "JSXGraph radar chart: too less circles in circleLabelArray" 883 ); 884 } 885 886 pcircles = []; 887 angle = start_angle + Math.PI / numofparams; 888 t = get_transform(angle, 0); 889 890 myAtts.fillcolor = "none"; 891 myAtts.highlightfillcolor = "none"; 892 myAtts.strokecolor = attributes.strokecolor || "black"; 893 myAtts.strokewidth = attributes.circlestrokewidth || 0.5; 894 myAtts.layer = 0; 895 896 // we have ncircles-1 intervals between ncircles circles 897 dr = (ends[0] - starts[0]) / (ncircles - 1); 898 899 for (i = 0; i < ncircles; i++) { 900 pcircles[i] = board.create("point", [starts[0] + i * dr, 0], { 901 name: clabelArray[i], 902 size: 0, 903 fixed: true, 904 withLabel: true, 905 visible: true 906 }); 907 pcircles[i].addTransform(pcircles[i], t); 908 circles[i] = board.create("circle", [center, pcircles[i]], myAtts); 909 } 910 } 911 this.rendNode = polygons[0].rendNode; 912 return { 913 circles: circles, 914 lines: line, 915 points: pdata, 916 midpoint: center, 917 polygons: polygons 918 }; 919 }, 920 921 /** 922 * Uses the boards renderer to update the chart. 923 * @private 924 */ 925 updateRenderer: function () { 926 return this; 927 }, 928 929 // documented in base/element 930 update: function () { 931 if (this.needsUpdate) { 932 this.updateDataArray(); 933 } 934 935 return this; 936 }, 937 938 /** 939 * Template for dynamic charts update. 940 * This method is used to compute new entries 941 * for the arrays this.dataX and 942 * this.dataY. It is used in update. 943 * Default is an empty method, can be overwritten 944 * by the user. 945 * 946 * @returns {JXG.Chart} Reference to this chart object. 947 */ 948 updateDataArray: function () { 949 return this; 950 } 951 } 952 ); 953 954 /** 955 * @class Various types of charts for data visualization. 956 * @pseudo 957 * @name Chart 958 * @augments JXG.Chart 959 * @constructor 960 * @type JXG.Chart 961 * @throws {Exception} If the element cannot be constructed with the given parent objects an exception is thrown. 962 * @param {Array} x Array of x-coordinates (default case, see below for alternatives) 963 * @param {Array} y Array of y-coordinates (default case, see below for alternatives) 964 * <p> 965 * The parent array may be of one of the following forms: 966 * <ol> 967 * <li> Parents array looks like [number, number, number, ...]. It is interpreted as array of y-coordinates. 968 * The x coordinates are automatically set to [1, 2, ...] 969 * <li> Parents array looks like [[number, number, number, ...]]. The content is interpreted as array of y-coordinates. 970 * The x coordinates are automatically set to [1, 2, ...]x coordinates are automatically set to [1, 2, ...] 971 * Default case: [[x0,x1,x2,...],[y1,y2,y3,...]] 972 * </ol> 973 * 974 * The attribute value for the key 'chartStyle' determines the type(s) of the chart. 'chartStyle' is a comma 975 * separated list of strings of the possible chart types 976 * 'bar', 'fit', 'line', 'pie', 'point', 'radar', 'spline'. 977 * 978 * @see JXG.Chart#drawBar 979 * @see JXG.Chart#drawFit 980 * @see JXG.Chart#drawLine 981 * @see JXG.Chart#drawPie 982 * @see JXG.Chart#drawPoints 983 * @see JXG.Chart#drawRadar 984 * @see JXG.Chart#drawSpline 985 * 986 * @example 987 * board = JXG.JSXGraph.initBoard('jxgbox', {boundingbox:[-0.5,8,9,-2],axis:true}); 988 * 989 * var f = [4, 2, -1, 3, 6, 7, 2]; 990 * var chart = board.create('chart', f, 991 * {chartStyle:'bar', 992 * width:0.8, 993 * labels:f, 994 * colorArray:['#8E1B77','#BE1679','#DC1765','#DA2130','#DB311B','#DF4917','#E36317','#E87F1A', 995 * '#F1B112','#FCF302','#C1E212'], 996 * label: {fontSize:30, display:'internal', anchorX:'left', rotate:90} 997 * }); 998 * 999 * </pre><div id="JXG1528c395-9fa4-4210-ada6-7fc5652ed920" class="jxgbox" style="width: 300px; height: 300px;"></div> 1000 * <script type="text/javascript"> 1001 * (function() { 1002 * var board = JXG.JSXGraph.initBoard('JXG1528c395-9fa4-4210-ada6-7fc5652ed920', 1003 * {boundingbox: [-0.5,8,9,-2], axis: true, showcopyright: false, shownavigation: false}); 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 * })(); 1015 * 1016 * </script><pre> 1017 * 1018 * @example 1019 * board = JXG.JSXGraph.initBoard('jxgbox', {boundingbox: [-1, 9, 13, -3], axis:true}); 1020 * 1021 * var s = board.create('slider', [[4,7],[8,7],[1,1,1.5]], {name:'S', strokeColor:'black', fillColor:'white'}); 1022 * var f = [function(){return (s.Value()*4.5).toFixed(2);}, 1023 * function(){return (s.Value()*(-1)).toFixed(2);}, 1024 * function(){return (s.Value()*3).toFixed(2);}, 1025 * function(){return (s.Value()*2).toFixed(2);}, 1026 * function(){return (s.Value()*(-0.5)).toFixed(2);}, 1027 * function(){return (s.Value()*5.5).toFixed(2);}, 1028 * function(){return (s.Value()*2.5).toFixed(2);}, 1029 * function(){return (s.Value()*(-0.75)).toFixed(2);}, 1030 * function(){return (s.Value()*3.5).toFixed(2);}, 1031 * function(){return (s.Value()*2).toFixed(2);}, 1032 * function(){return (s.Value()*(-1.25)).toFixed(2);} 1033 * ]; 1034 * var chart = board.create('chart', [f], 1035 * {chartStyle:'bar',width:0.8,labels:f, 1036 * colorArray:['#8E1B77','#BE1679','#DC1765','#DA2130','#DB311B','#DF4917','#E36317','#E87F1A', 1037 * '#F1B112','#FCF302','#C1E212']}); 1038 * 1039 * var dataArr = [4,1,3,2,5,6.5,1.5,2,0.5,1.5,-1]; 1040 * var chart2 = board.create('chart', dataArr, {chartStyle:'line,point'}); 1041 * chart2[0].setAttribute('strokeColor:black','strokeWidth:2pt'); 1042 * for(var i=0; i<11;i++) { 1043 * chart2[1][i].setAttribute({strokeColor:'black',fillColor:'white',face:'[]', size:4, strokeWidth:'2pt'}); 1044 * } 1045 * board.unsuspendUpdate(); 1046 * 1047 * </pre><div id="JXG22deb158-48c6-41c3-8157-b88b4b968a55" class="jxgbox" style="width: 300px; height: 300px;"></div> 1048 * <script type="text/javascript"> 1049 * (function() { 1050 * var board = JXG.JSXGraph.initBoard('JXG22deb158-48c6-41c3-8157-b88b4b968a55', 1051 * {boundingbox: [-1, 9, 13, -3], axis: true, showcopyright: false, shownavigation: false}); 1052 * var s = board.create('slider', [[4,7],[8,7],[1,1,1.5]], {name:'S', strokeColor:'black', fillColor:'white'}); 1053 * var f = [function(){return (s.Value()*4.5).toFixed(2);}, 1054 * function(){return (s.Value()*(-1)).toFixed(2);}, 1055 * function(){return (s.Value()*3).toFixed(2);}, 1056 * function(){return (s.Value()*2).toFixed(2);}, 1057 * function(){return (s.Value()*(-0.5)).toFixed(2);}, 1058 * function(){return (s.Value()*5.5).toFixed(2);}, 1059 * function(){return (s.Value()*2.5).toFixed(2);}, 1060 * function(){return (s.Value()*(-0.75)).toFixed(2);}, 1061 * function(){return (s.Value()*3.5).toFixed(2);}, 1062 * function(){return (s.Value()*2).toFixed(2);}, 1063 * function(){return (s.Value()*(-1.25)).toFixed(2);} 1064 * ]; 1065 * var chart = board.create('chart', [f], 1066 * {chartStyle:'bar',width:0.8,labels:f, 1067 * colorArray:['#8E1B77','#BE1679','#DC1765','#DA2130','#DB311B','#DF4917','#E36317','#E87F1A', 1068 * '#F1B112','#FCF302','#C1E212']}); 1069 * 1070 * var dataArr = [4,1,3,2,5,6.5,1.5,2,0.5,1.5,-1]; 1071 * var chart2 = board.create('chart', dataArr, {chartStyle:'line,point'}); 1072 * chart2[0].setAttribute('strokeColor:black','strokeWidth:2pt'); 1073 * for(var i=0; i<11;i++) { 1074 * chart2[1][i].setAttribute({strokeColor:'black',fillColor:'white',face:'[]', size:4, strokeWidth:'2pt'}); 1075 * } 1076 * board.unsuspendUpdate(); 1077 * 1078 * })(); 1079 * 1080 * </script><pre> 1081 * 1082 * @example 1083 * var dataArr = [4, 1.2, 3, 7, 5, 4, 1.54, function () { return 2; }]; 1084 * var a = board.create('chart', dataArr, { 1085 * chartStyle:'pie', colors:['#B02B2C','#3F4C6B','#C79810','#D15600'], 1086 * fillOpacity:0.9, 1087 * center:[5,2], 1088 * strokeColor:'#ffffff', 1089 * strokeWidth:6, 1090 * highlightBySize:true, 1091 * highlightOnSector:true 1092 * }); 1093 * 1094 * </pre><div id="JXG1180b7dd-b048-436a-a5ad-87ffa82d5aff" class="jxgbox" style="width: 300px; height: 300px;"></div> 1095 * <script type="text/javascript"> 1096 * (function() { 1097 * var board = JXG.JSXGraph.initBoard('JXG1180b7dd-b048-436a-a5ad-87ffa82d5aff', 1098 * {boundingbox: [0, 8, 12, -4], axis: true, showcopyright: false, shownavigation: false}); 1099 * var dataArr = [4, 1.2, 3, 7, 5, 4, 1.54, function () { return 2; }]; 1100 * var a = board.create('chart', dataArr, { 1101 * chartStyle:'pie', colors:['#B02B2C','#3F4C6B','#C79810','#D15600'], 1102 * fillOpacity:0.9, 1103 * center:[5,2], 1104 * strokeColor:'#ffffff', 1105 * strokeWidth:6, 1106 * highlightBySize:true, 1107 * highlightOnSector:true 1108 * }); 1109 * 1110 * })(); 1111 * 1112 * </script><pre> 1113 * 1114 * @example 1115 * board = JXG.JSXGraph.initBoard('jxgbox', {boundingbox: [-12, 12, 20, -12], axis: false}); 1116 * board.suspendUpdate(); 1117 * // See labelArray and paramArray 1118 * var dataArr = [[23, 14, 15.0], [60, 8, 25.0], [0, 11.0, 25.0], [10, 15, 20.0]]; 1119 * 1120 * var a = board.create('chart', dataArr, { 1121 * chartStyle:'radar', 1122 * colorArray:['#0F408D','#6F1B75','#CA147A','#DA2228','#E8801B','#FCF302','#8DC922','#15993C','#87CCEE','#0092CE'], 1123 * //fillOpacity:0.5, 1124 * //strokeColor:'black', 1125 * //strokeWidth:1, 1126 * //polyStrokeWidth:1, 1127 * paramArray:['Speed','Flexibility', 'Costs'], 1128 * labelArray:['Ruby','JavaScript', 'PHP', 'Python'], 1129 * //startAngle:Math.PI/4, 1130 * legendPosition:'right', 1131 * //"startShiftRatio": 0.1, 1132 * //endShiftRatio:0.1, 1133 * //startShiftArray:[0,0,0], 1134 * //endShiftArray:[0.5,0.5,0.5], 1135 * start:0 1136 * //end:70, 1137 * //startArray:[0,0,0], 1138 * //endArray:[7,7,7], 1139 * //radius:3, 1140 * //showCircles:true, 1141 * //circleLabelArray:[1,2,3,4,5], 1142 * //highlightColorArray:['#E46F6A','#F9DF82','#F7FA7B','#B0D990','#69BF8E','#BDDDE4','#92C2DF','#637CB0','#AB91BC','#EB8EBF'], 1143 * }); 1144 * board.unsuspendUpdate(); 1145 * 1146 * </pre><div id="JXG985fbbe6-0488-4073-b73b-cb3ebaea488a" class="jxgbox" style="width: 300px; height: 300px;"></div> 1147 * <script type="text/javascript"> 1148 * (function() { 1149 * var board = JXG.JSXGraph.initBoard('JXG985fbbe6-0488-4073-b73b-cb3ebaea488a', 1150 * {boundingbox: [-12, 12, 20, -12], axis: false, showcopyright: false, shownavigation: false}); 1151 * board.suspendUpdate(); 1152 * // See labelArray and paramArray 1153 * var dataArr = [[23, 14, 15.0], [60, 8, 25.0], [0, 11.0, 25.0], [10, 15, 20.0]]; 1154 * 1155 * var a = board.create('chart', dataArr, { 1156 * chartStyle:'radar', 1157 * colorArray:['#0F408D','#6F1B75','#CA147A','#DA2228','#E8801B','#FCF302','#8DC922','#15993C','#87CCEE','#0092CE'], 1158 * //fillOpacity:0.5, 1159 * //strokeColor:'black', 1160 * //strokeWidth:1, 1161 * //polyStrokeWidth:1, 1162 * paramArray:['Speed','Flexibility', 'Costs'], 1163 * labelArray:['Ruby','JavaScript', 'PHP', 'Python'], 1164 * //startAngle:Math.PI/4, 1165 * legendPosition:'right', 1166 * //"startShiftRatio": 0.1, 1167 * //endShiftRatio:0.1, 1168 * //startShiftArray:[0,0,0], 1169 * //endShiftArray:[0.5,0.5,0.5], 1170 * start:0 1171 * //end:70, 1172 * //startArray:[0,0,0], 1173 * //endArray:[7,7,7], 1174 * //radius:3, 1175 * //showCircles:true, 1176 * //circleLabelArray:[1,2,3,4,5], 1177 * //highlightColorArray:['#E46F6A','#F9DF82','#F7FA7B','#B0D990','#69BF8E','#BDDDE4','#92C2DF','#637CB0','#AB91BC','#EB8EBF'], 1178 * }); 1179 * board.unsuspendUpdate(); 1180 * 1181 * })(); 1182 * 1183 * </script><pre> 1184 * 1185 * For more examples see 1186 * <ul> 1187 * <li><a href="https://jsxgraph.org/wiki/index.php/Charts_from_HTML_tables_-_tutorial">JSXgraph wiki: Charts from HTML tables - tutorial</a> 1188 * <li><a href="https://jsxgraph.org/wiki/index.php/Pie_chart">JSXgraph wiki: Pie chart</a> 1189 * <li><a href="https://jsxgraph.org/wiki/index.php/Different_chart_styles">JSXgraph wiki: Various chart styles</a> 1190 * <li><a href="https://jsxgraph.org/wiki/index.php/Dynamic_bar_chart">JSXgraph wiki: Dynamic bar chart</a> 1191 * </ul> 1192 */ 1193 JXG.createChart = function (board, parents, attributes) { 1194 var data, 1195 row, 1196 i, 1197 j, 1198 col, 1199 charts = [], 1200 w, 1201 x, 1202 showRows, 1203 attr, 1204 originalWidth, 1205 name, 1206 strokeColor, 1207 fillColor, 1208 hStrokeColor, 1209 hFillColor, 1210 len, 1211 table = Env.isBrowser ? board.document.getElementById(parents[0]) : null; 1212 1213 if (parents.length === 1 && Type.isString(parents[0])) { 1214 if (Type.exists(table)) { 1215 // extract the data 1216 attr = Type.copyAttributes(attributes, board.options, "chart"); 1217 1218 table = new DataSource().loadFromTable( 1219 parents[0], 1220 attr.withheaders, 1221 attr.withheaders 1222 ); 1223 data = table.data; 1224 col = table.columnHeaders; 1225 row = table.rowHeaders; 1226 1227 originalWidth = attr.width; 1228 name = attr.name; 1229 strokeColor = attr.strokecolor; 1230 fillColor = attr.fillcolor; 1231 hStrokeColor = attr.highlightstrokecolor; 1232 hFillColor = attr.highlightfillcolor; 1233 1234 board.suspendUpdate(); 1235 1236 len = data.length; 1237 showRows = []; 1238 if (attr.rows && Type.isArray(attr.rows)) { 1239 for (i = 0; i < len; i++) { 1240 for (j = 0; j < attr.rows.length; j++) { 1241 if ( 1242 attr.rows[j] === i || 1243 (attr.withheaders && attr.rows[j] === row[i]) 1244 ) { 1245 showRows.push(data[i]); 1246 break; 1247 } 1248 } 1249 } 1250 } else { 1251 showRows = data; 1252 } 1253 1254 len = showRows.length; 1255 1256 for (i = 0; i < len; i++) { 1257 x = []; 1258 if (attr.chartstyle && attr.chartstyle.indexOf("bar") !== -1) { 1259 if (originalWidth) { 1260 w = originalWidth; 1261 } else { 1262 w = 0.8; 1263 } 1264 1265 x.push(1 - w / 2 + ((i + 0.5) * w) / len); 1266 1267 for (j = 1; j < showRows[i].length; j++) { 1268 x.push(x[j - 1] + 1); 1269 } 1270 1271 attr.width = w / len; 1272 } 1273 1274 if (name && name.length === len) { 1275 attr.name = name[i]; 1276 } else if (attr.withheaders) { 1277 attr.name = col[i]; 1278 } 1279 1280 if (strokeColor && strokeColor.length === len) { 1281 attr.strokecolor = strokeColor[i]; 1282 } else { 1283 attr.strokecolor = Color.hsv2rgb(((i + 1) / len) * 360, 0.9, 0.6); 1284 } 1285 1286 if (fillColor && fillColor.length === len) { 1287 attr.fillcolor = fillColor[i]; 1288 } else { 1289 attr.fillcolor = Color.hsv2rgb(((i + 1) / len) * 360, 0.9, 1.0); 1290 } 1291 1292 if (hStrokeColor && hStrokeColor.length === len) { 1293 attr.highlightstrokecolor = hStrokeColor[i]; 1294 } else { 1295 attr.highlightstrokecolor = Color.hsv2rgb(((i + 1) / len) * 360, 0.9, 1.0); 1296 } 1297 1298 if (hFillColor && hFillColor.length === len) { 1299 attr.highlightfillcolor = hFillColor[i]; 1300 } else { 1301 attr.highlightfillcolor = Color.hsv2rgb(((i + 1) / len) * 360, 0.9, 0.6); 1302 } 1303 1304 if (attr.chartstyle && attr.chartstyle.indexOf("bar") !== -1) { 1305 charts.push(new JXG.Chart(board, [x, showRows[i]], attr)); 1306 } else { 1307 charts.push(new JXG.Chart(board, [showRows[i]], attr)); 1308 } 1309 } 1310 1311 board.unsuspendUpdate(); 1312 } 1313 return charts; 1314 } 1315 1316 attr = Type.copyAttributes(attributes, board.options, "chart"); 1317 return new JXG.Chart(board, parents, attr); 1318 }; 1319 1320 JXG.registerElement("chart", JXG.createChart); 1321 1322 /** 1323 * Legend for chart 1324 * 1325 * The Legend class is a basic class for legends. 1326 * @class Creates a new Legend object. Do not use this constructor to create a legend. 1327 * Use {@link JXG.Board#create} with type {@link Legend} instead. 1328 * <p> 1329 * The legend object consists of segements with labels. These lines can be 1330 * accessed with the property "lines" of the element. 1331 * @constructor 1332 * @augments JXG.GeometryElement 1333 * @param {String|JXG.Board} board The board the new legend is drawn on. 1334 * @param {Array} coords Coordinates of the left top point of the legend. 1335 * @param {Object} attributes Attributes of the legend 1336 */ 1337 JXG.Legend = function (board, coords, attributes) { 1338 var attr; 1339 1340 /* Call the constructor of GeometryElement */ 1341 this.constructor(); 1342 1343 attr = Type.copyAttributes(attributes, board.options, "legend"); 1344 1345 this.board = board; 1346 this.coords = new Coords(Const.COORDS_BY_USER, coords, this.board); 1347 this.myAtts = {}; 1348 this.label_array = attr.labelarray || attr.labels; 1349 this.color_array = attr.colorarray || attr.colors; 1350 this.opacy_array = attr.strokeopacity || [1]; 1351 this.lines = []; 1352 this.myAtts.strokewidth = attr.strokewidth || 5; 1353 this.myAtts.straightfirst = false; 1354 this.myAtts.straightlast = false; 1355 this.myAtts.withlabel = true; 1356 this.myAtts.fixed = true; 1357 this.myAtts.frozen = attr.frozen || false ; 1358 this.style = attr.legendstyle || attr.style; 1359 1360 if (this.style === "vertical") { 1361 this.drawVerticalLegend(board, attr); 1362 } else { 1363 throw new Error("JSXGraph: Unknown legend style: " + this.style); 1364 } 1365 1366 this.id = this.board.setId(this, "Leg"); 1367 1368 }; 1369 1370 JXG.Legend.prototype = new GeometryElement(); 1371 1372 /** 1373 * Draw a vertical legend. 1374 * 1375 * @private 1376 * @param {String|JXG.Board} board The board the legend is drawn on 1377 * @param {Object} attributes Attributes of the legend 1378 */ 1379 JXG.Legend.prototype.drawVerticalLegend = function (board, attributes) { 1380 var i, 1381 line_length = attributes.linelength || 1, 1382 offy = (attributes.rowheight || 20) / this.board.unitY, 1383 getLabelAnchor = function () { 1384 this.setLabelRelativeCoords(this.visProp.label.offset); 1385 return new Coords( 1386 Const.COORDS_BY_USER, 1387 [this.point2.X(), this.point2.Y()], 1388 this.board 1389 ); 1390 }; 1391 1392 for (i = 0; i < this.label_array.length; i++) { 1393 this.myAtts.name = this.label_array[i]; 1394 this.myAtts.strokecolor = this.color_array[i % this.color_array.length]; 1395 this.myAtts.highlightstrokecolor = this.color_array[i % this.color_array.length]; 1396 this.myAtts.strokeopacity = this.opacy_array[i % this.opacy_array.length]; 1397 this.myAtts.highlightstrokeopacity = this.opacy_array[i % this.opacy_array.length]; 1398 this.myAtts.label = { 1399 offset: [10, 0], 1400 strokeColor: this.color_array[i % this.color_array.length], 1401 strokeWidth: this.myAtts.strokewidth 1402 }; 1403 1404 this.lines[i] = board.create( 1405 "line", 1406 [ 1407 [this.coords.usrCoords[1], this.coords.usrCoords[2] - i * offy], 1408 [this.coords.usrCoords[1] + line_length, this.coords.usrCoords[2] - i * offy] 1409 ], 1410 this.myAtts 1411 ); 1412 1413 if (this.myAtts.frozen){ 1414 this.lines[i].setAttribute({ point1: { frozen: true }, point2: { frozen: true } }); 1415 } 1416 1417 this.lines[i].getLabelAnchor = getLabelAnchor; 1418 this.lines[i] 1419 .prepareUpdate() 1420 .update() 1421 .updateVisibility(this.lines[i].evalVisProp('visible')) 1422 .updateRenderer(); 1423 1424 this.addChild(this.lines[i]); 1425 } 1426 }; 1427 1428 /** 1429 * @class Creates a legend for a chart element. 1430 * Parameter is a pair of coordinates. The label names and the label colors are 1431 * supplied in the attributes: 1432 * <ul> 1433 * <li> labels (Array): array of strings containing label names 1434 * <li> labelArray (Array): alternative array for label names (has precedence over 'labels') 1435 * <li> colors (Array): array of color values 1436 * <li> colorArray (Array): alternative array for color values (has precedence over 'colors') 1437 * <li> opacities (Array): opacity of a line in the legend 1438 * <li> legendStyle or style: at the time being only 'vertical' is supported. 1439 * <li> rowHeight: height of an entry in the legend (in px) 1440 * <li> linelenght: length of a line in the legend (measured in the coordinate system) 1441 * <li> frozen (Boolean, false): 1442 * </ul> 1443 * 1444 * @pseudo 1445 * @name Legend 1446 * @augments JXG.Legend 1447 * @constructor 1448 * @type JXG.Legend 1449 * @throws {Exception} If the element cannot be constructed with the given parent objects an exception is thrown. 1450 * @param {Number} x Horizontal coordinate of the left top point of the legend 1451 * @param {Number} y Vertical coordinate of the left top point of the legend 1452 * 1453 * @example 1454 * var board = JXG.JSXGraph.initBoard('jxgbox', {axis:true,boundingbox:[-4,48.3,12.0,-2.3]}); 1455 * var x = [-3,-2,-1,0,1,2,3,4,5,6,7,8]; 1456 * var dataArr = [4,7,7,27,33,37,46,22,11,4,1,0]; 1457 * 1458 * colors = ['green', 'yellow', 'red', 'blue']; 1459 * board.create('chart', [x,dataArr], {chartStyle:'bar', width:1.0, labels:dataArr, colors: colors} ); 1460 * board.create('legend', [8, 45], {labels:dataArr, colors: colors, strokeWidth:5} ); 1461 * 1462 * </pre><div id="JXGeeb588d9-a4fd-41bf-93f4-cd6f7a016682" class="jxgbox" style="width: 300px; height: 300px;"></div> 1463 * <script type="text/javascript"> 1464 * (function() { 1465 * var board = JXG.JSXGraph.initBoard('JXGeeb588d9-a4fd-41bf-93f4-cd6f7a016682', 1466 * {boundingbox: [-4,48.3,12.0,-2.3], axis: true, showcopyright: false, shownavigation: false}); 1467 * var x = [-3,-2,-1,0,1,2,3,4,5,6,7,8]; 1468 * var dataArr = [4,7,7,27,33,37,46,22,11,4,1,0]; 1469 * 1470 * colors = ['green', 'yellow', 'red', 'blue']; 1471 * board.create('chart', [x,dataArr], {chartStyle:'bar', width:1.0, labels:dataArr, colors: colors} ); 1472 * board.create('legend', [8, 45], {labels:dataArr, colors: colors, strokeWidth:5} ); 1473 * 1474 * })(); 1475 * 1476 * </script><pre> 1477 * 1478 * @example 1479 * var inputFun, cf = [], cf2 = [], niveaunum, 1480 * niveauline = [], niveauopac = [],legend; 1481 * 1482 * inputFun = "x^2/2-2*x*y+y^2/2"; 1483 * niveauline = [-3,-2,-1,-0.5, 0, 1,2,3]; 1484 * niveaunum = niveauline.length; 1485 * for (let i = 0; JXG.Math.lt(i, niveaunum); i++) { 1486 * let niveaui = niveauline[i]; 1487 * niveauopac.push(((i + 1) / (niveaunum + 1))); 1488 * cf.push(board.create("implicitcurve", [ 1489 * inputFun + "-(" + niveaui.toFixed(2) + ")", [-2, 2], [-2, 2]], { 1490 * strokeWidth: 2, 1491 * strokeColor: JXG.palette.red, 1492 * strokeOpacity: niveauopac[i], 1493 * needsRegularUpdate: false, 1494 * name: "niveau"+i, 1495 * visible: true 1496 * })); 1497 * } 1498 * legend = board.create('legend', [-1.75, 1.75], { 1499 * labels: niveauline, 1500 * colors: [cf[0].visProp.strokecolor], 1501 * strokeOpacity: niveauopac, 1502 * linelength: 0.2, 1503 * frozen:true 1504 * } 1505 * ); 1506 * 1507 * 1508 * </pre><div id="JXG079fce93-07b9-426f-a267-ab9c1253e435" class="jxgbox" style="width: 300px; height: 300px;"></div> 1509 * <script type="text/javascript"> 1510 * (function() { 1511 * var board = JXG.JSXGraph.initBoard('JXG079fce93-07b9-426f-a267-ab9c1253e435', 1512 * {boundingbox: [-2, 2, 2, -2], axis: true, showcopyright: false, shownavigation: false}); 1513 * var board, inputFun, cf = [], cf2 = [], niveaunum, 1514 * niveauline = [], niveauopac = [],legend; 1515 * 1516 * inputFun = "x^2/2-2*x*y+y^2/2"; 1517 * niveauline = [-3,-2,-1,-0.5, 0, 1,2,3]; 1518 * niveaunum = niveauline.length; 1519 * for (let i = 0; JXG.Math.lt(i, niveaunum); i++) { 1520 * let niveaui = niveauline[i]; 1521 * niveauopac.push(((i + 1) / (niveaunum + 1))); 1522 * cf.push(board.create("implicitcurve", [ 1523 * inputFun + "-(" + niveaui.toFixed(2) + ")", [-2, 2], [-2, 2]], { 1524 * strokeWidth: 2, 1525 * strokeColor: JXG.palette.red, 1526 * strokeOpacity: niveauopac[i], 1527 * needsRegularUpdate: false, 1528 * name: "niveau"+i, 1529 * visible: true 1530 * })); 1531 * } 1532 * legend = board.create('legend', [-1.75, 1.75], { 1533 * labels: niveauline, 1534 * colors: [cf[0].visProp.strokecolor], 1535 * strokeOpacity: niveauopac, 1536 * linelength: 0.2, 1537 * frozen:true 1538 * } 1539 * ); 1540 * 1541 * 1542 * })(); 1543 * 1544 * </script> 1545 * 1546 * 1547 */ 1548 JXG.createLegend = function (board, parents, attributes) { 1549 //parents are coords of left top point of the legend 1550 var start_from = [0, 0]; 1551 1552 if (Type.exists(parents) && parents.length === 2) { 1553 start_from = parents; 1554 } else { 1555 throw new Error("JSXGraph: Legend element needs two numbers as parameters"); 1556 } 1557 1558 return new JXG.Legend(board, start_from, attributes); 1559 }; 1560 1561 JXG.registerElement("legend", JXG.createLegend); 1562 1563 export default { 1564 Chart: JXG.Chart, 1565 Legend: JXG.Legend 1566 // createChart: JXG.createChart, 1567 // createLegend: JXG.createLegend 1568 }; 1569