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