1 /* 2 Copyright 2008-2018 3 Matthias Ehmann, 4 Michael Gerhaeuser, 5 Carsten Miller, 6 Bianca Valentin, 7 Alfred Wassermann, 8 Peter Wilfahrt 9 10 This file is part of JSXGraph. 11 12 JSXGraph is free software dual licensed under the GNU LGPL or MIT License. 13 14 You can redistribute it and/or modify it under the terms of the 15 16 * GNU Lesser General Public License as published by 17 the Free Software Foundation, either version 3 of the License, or 18 (at your option) any later version 19 OR 20 * MIT License: https://github.com/jsxgraph/jsxgraph/blob/master/LICENSE.MIT 21 22 JSXGraph is distributed in the hope that it will be useful, 23 but WITHOUT ANY WARRANTY; without even the implied warranty of 24 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 25 GNU Lesser General Public License for more details. 26 27 You should have received a copy of the GNU Lesser General Public License and 28 the MIT License along with JSXGraph. If not, see <http://www.gnu.org/licenses/> 29 and <http://opensource.org/licenses/MIT/>. 30 */ 31 32 33 /*global JXG: true, define: true, document: true*/ 34 /*jslint nomen: true, plusplus: true*/ 35 36 /* depends: 37 jxg 38 math/numerics 39 math/statistics 40 base/constants 41 base/coords 42 base/element 43 parser/datasource 44 utils/color 45 utils/type 46 utils/env 47 elements: 48 curve 49 spline 50 functiongraph 51 point 52 text 53 polygon 54 sector 55 transform 56 line 57 legend 58 circle 59 */ 60 61 define([ 62 'jxg', 'math/numerics', 'math/statistics', 'base/constants', 'base/coords', 'base/element', 'parser/datasource', 63 'utils/color', 'utils/type', 'utils/env', 'base/curve', 'base/point', 'base/text', 'base/polygon', 'element/sector', 64 'base/transformation', 'base/line', 'base/circle' 65 ], function (JXG, Numerics, Statistics, Const, Coords, GeometryElement, DataSource, Color, Type, Env, Curve, Point, Text, 66 Polygon, Sector, Transform, Line, Circle) { 67 68 "use strict"; 69 70 /** 71 * Chart plotting 72 */ 73 JXG.Chart = function (board, parents, attributes) { 74 this.constructor(board, attributes); 75 76 var x, y, i, c, style, len; 77 78 if (!Type.isArray(parents) || parents.length === 0) { 79 throw new Error('JSXGraph: Can\'t create a chart without data'); 80 } 81 82 /** 83 * Contains pointers to the various subelements of the chart. 84 */ 85 this.elements = []; 86 87 if (Type.isNumber(parents[0])) { 88 // parents looks like [a,b,c,..] 89 // x has to be filled 90 91 y = parents; 92 x = []; 93 for (i = 0; i < y.length; i++) { 94 x[i] = i + 1; 95 } 96 } else if (parents.length === 1 && Type.isArray(parents[0])) { 97 // parents looks like [[a,b,c,..]] 98 // x has to be filled 99 100 y = parents[0]; 101 x = []; 102 103 len = Type.evaluate(y).length; 104 for (i = 0; i < len; i++) { 105 x[i] = i + 1; 106 } 107 } else if (parents.length === 2) { 108 // parents looks like [[x0,x1,x2,...],[y1,y2,y3,...]] 109 len = Math.min(parents[0].length, parents[1].length); 110 x = parents[0].slice(0, len); 111 y = parents[1].slice(0, len); 112 } 113 114 if (Type.isArray(y) && y.length === 0) { 115 throw new Error('JSXGraph: Can\'t create charts without data.'); 116 } 117 118 // does this really need to be done here? this should be done in createChart and then 119 // there should be an extra chart for each chartstyle 120 style = attributes.chartstyle.replace(/ /g, '').split(','); 121 for (i = 0; i < style.length; i++) { 122 switch (style[i]) { 123 case 'bar': 124 c = this.drawBar(board, x, y, attributes); 125 break; 126 case 'line': 127 c = this.drawLine(board, x, y, attributes); 128 break; 129 case 'fit': 130 c = this.drawFit(board, x, y, attributes); 131 break; 132 case 'spline': 133 c = this.drawSpline(board, x, y, attributes); 134 break; 135 case 'pie': 136 c = this.drawPie(board, y, attributes); 137 break; 138 case 'point': 139 c = this.drawPoints(board, x, y, attributes); 140 break; 141 case 'radar': 142 c = this.drawRadar(board, parents, attributes); 143 break; 144 } 145 this.elements.push(c); 146 } 147 this.id = this.board.setId(this, 'Chart'); 148 149 return this.elements; 150 }; 151 JXG.Chart.prototype = new GeometryElement(); 152 153 JXG.extend(JXG.Chart.prototype, /** @lends JXG.Chart.prototype */ { 154 drawLine: function (board, x, y, attributes) { 155 // we don't want the line chart to be filled 156 attributes.fillcolor = 'none'; 157 attributes.highlightfillcolor = 'none'; 158 159 return board.create('curve', [x, y], attributes); 160 }, 161 162 drawSpline: function (board, x, y, attributes) { 163 // we don't want the spline chart to be filled 164 attributes.fillColor = 'none'; 165 attributes.highlightfillcolor = 'none'; 166 167 return board.create('spline', [x, y], attributes); 168 }, 169 170 drawFit: function (board, x, y, attributes) { 171 var deg = attributes.degree; 172 173 deg = Math.max(parseInt(deg, 10), 1) || 1; 174 175 // never fill 176 attributes.fillcolor = 'none'; 177 attributes.highlightfillcolor = 'none'; 178 179 return board.create('functiongraph', [Numerics.regressionPolynomial(deg, x, y)], attributes); 180 }, 181 182 drawBar: function (board, x, y, attributes) { 183 var i, strwidth, text, w, xp0, xp1, xp2, yp, colors, 184 pols = [], 185 p = [], 186 attr, attrSub, 187 188 makeXpFun = function (i, f) { 189 return function () { 190 return x[i]() - f * w; 191 }; 192 }, 193 194 hiddenPoint = { 195 fixed: true, 196 withLabel: false, 197 visible: false, 198 name: '' 199 }; 200 201 attr = Type.copyAttributes(attributes, board.options, 'chart'); 202 203 // Determine the width of the bars 204 if (attr && attr.width) { // width given 205 w = attr.width; 206 } else { 207 if (x.length <= 1) { 208 w = 1; 209 } else { 210 // Find minimum distance between to bars. 211 w = x[1] - x[0]; 212 for (i = 1; i < x.length - 1; i++) { 213 w = (x[i + 1] - x[i] < w) ? x[i + 1] - x[i] : w; 214 } 215 } 216 w *= 0.8; 217 } 218 219 attrSub = Type.copyAttributes(attributes, board.options, 'chart', 'label'); 220 221 for (i = 0; i < x.length; i++) { 222 if (Type.isFunction(x[i])) { 223 xp0 = makeXpFun(i, -0.5); 224 xp1 = makeXpFun(i, 0); 225 xp2 = makeXpFun(i, 0.5); 226 } else { 227 xp0 = x[i] - w * 0.5; 228 xp1 = x[i]; 229 xp2 = x[i] + w * 0.5; 230 } 231 if (Type.isFunction(y[i])) { 232 yp = y[i](); 233 } else { 234 yp = y[i]; 235 } 236 yp = y[i]; 237 238 if (attr.dir === 'horizontal') { // horizontal bars 239 p[0] = board.create('point', [0, xp0], hiddenPoint); 240 p[1] = board.create('point', [yp, xp0], hiddenPoint); 241 p[2] = board.create('point', [yp, xp2], hiddenPoint); 242 p[3] = board.create('point', [0, xp2], hiddenPoint); 243 244 if (Type.exists(attr.labels) && Type.exists(attr.labels[i])) { 245 attrSub.anchorY = 'middle'; 246 text = board.create('text', [ 247 yp, 248 xp1, 249 attr.labels[i]], attrSub); 250 text.visProp.anchorx = (function(txt) { return function() { 251 return (txt.X() >= 0) ? 'left' : 'right'; 252 }; })(text); 253 254 } 255 } else { // vertical bars 256 p[0] = board.create('point', [xp0, 0], hiddenPoint); 257 p[1] = board.create('point', [xp0, yp], hiddenPoint); 258 p[2] = board.create('point', [xp2, yp], hiddenPoint); 259 p[3] = board.create('point', [xp2, 0], hiddenPoint); 260 261 if (Type.exists(attr.labels) && Type.exists(attr.labels[i])) { 262 attrSub.anchorX = 'middle'; 263 264 text = board.create('text', [ 265 xp1, 266 yp, 267 attr.labels[i]], attrSub); 268 269 text.visProp.anchory = (function(txt) { 270 return function() { 271 return (txt.Y() >= 0) ? 'bottom' : 'top'; 272 }; 273 })(text); 274 275 } 276 } 277 278 if (Type.isArray(attr.colors)) { 279 colors = attr.colors; 280 attr.fillcolor = colors[i % colors.length]; 281 } 282 283 pols[i] = board.create('polygon', p, attr); 284 if (Type.exists(attr.labels) && Type.exists(attr.labels[i])) { 285 pols[i].text = text; 286 } 287 } 288 289 return pols; 290 }, 291 292 drawPoints: function (board, x, y, attributes) { 293 var i, 294 points = [], 295 infoboxArray = attributes.infoboxarray; 296 297 attributes.fixed = true; 298 attributes.name = ''; 299 300 for (i = 0; i < x.length; i++) { 301 attributes.infoboxtext = infoboxArray ? infoboxArray[i % infoboxArray.length] : false; 302 points[i] = board.create('point', [x[i], y[i]], attributes); 303 } 304 305 return points; 306 }, 307 308 drawPie: function (board, y, attributes) { 309 var i, center, 310 p = [], 311 sector = [], 312 s = Statistics.sum(y), 313 colorArray = attributes.colors, 314 highlightColorArray = attributes.highlightcolors, 315 labelArray = attributes.labels, 316 r = attributes.radius || 4, 317 radius = r, 318 cent = attributes.center || [0, 0], 319 xc = cent[0], 320 yc = cent[1], 321 322 makeRadPointFun = function (j, fun, xc) { 323 return function () { 324 var s, i, rad, 325 t = 0; 326 327 for (i = 0; i <= j; i++) { 328 t += parseFloat(Type.evaluate(y[i])); 329 } 330 331 s = t; 332 for (i = j + 1; i < y.length; i++) { 333 s += parseFloat(Type.evaluate(y[i])); 334 } 335 rad = (s !== 0) ? (2 * Math.PI * t / s) : 0; 336 337 return radius() * Math[fun](rad) + xc; 338 }; 339 }, 340 341 highlightHandleLabel = function (f, s) { 342 var dx = -this.point1.coords.usrCoords[1] + this.point2.coords.usrCoords[1], 343 dy = -this.point1.coords.usrCoords[2] + this.point2.coords.usrCoords[2]; 344 345 if (Type.exists(this.label)) { 346 this.label.rendNode.style.fontSize = (s * Type.evaluate(this.label.visProp.fontsize)) + 'px'; 347 this.label.fullUpdate(); 348 } 349 350 this.point2.coords = new Coords(Const.COORDS_BY_USER, [ 351 this.point1.coords.usrCoords[1] + dx * f, 352 this.point1.coords.usrCoords[2] + dy * f 353 ], this.board); 354 this.fullUpdate(); 355 }, 356 357 highlightFun = function () { 358 if (!this.highlighted) { 359 this.highlighted = true; 360 this.board.highlightedObjects[this.id] = this; 361 this.board.renderer.highlight(this); 362 363 highlightHandleLabel.call(this, 1.1, 2); 364 } 365 }, 366 367 noHighlightFun = function () { 368 if (this.highlighted) { 369 this.highlighted = false; 370 this.board.renderer.noHighlight(this); 371 372 highlightHandleLabel.call(this, 0.90909090, 1); 373 } 374 }, 375 376 hiddenPoint = { 377 fixed: true, 378 withLabel: false, 379 visible: false, 380 name: '' 381 }; 382 383 if (!Type.isArray(labelArray)) { 384 labelArray = []; 385 for (i = 0; i < y.length; i++) { 386 labelArray[i] = ''; 387 } 388 } 389 390 if (!Type.isFunction(r)) { 391 radius = function () { 392 return r; 393 }; 394 } 395 396 attributes.highlightonsector = attributes.highlightonsector || false; 397 attributes.straightfirst = false; 398 attributes.straightlast = false; 399 400 center = board.create('point', [xc, yc], hiddenPoint); 401 p[0] = board.create('point', [ 402 function () { 403 return radius() + xc; 404 }, 405 function () { 406 return yc; 407 } 408 ], hiddenPoint); 409 410 for (i = 0; i < y.length; i++) { 411 p[i + 1] = board.create('point', [makeRadPointFun(i, 'cos', xc), makeRadPointFun(i, 'sin', yc)], hiddenPoint); 412 413 attributes.name = labelArray[i]; 414 attributes.withlabel = attributes.name !== ''; 415 attributes.fillcolor = colorArray && colorArray[i % colorArray.length]; 416 attributes.labelcolor = colorArray && colorArray[i % colorArray.length]; 417 attributes.highlightfillcolor = highlightColorArray && highlightColorArray[i % highlightColorArray.length]; 418 419 sector[i] = board.create('sector', [center, p[i], p[i + 1]], attributes); 420 421 if (attributes.highlightonsector) { 422 // overwrite hasPoint so that the whole sector is used for highlighting 423 sector[i].hasPoint = sector[i].hasPointSector; 424 } 425 if (attributes.highlightbysize) { 426 sector[i].highlight = highlightFun; 427 428 sector[i].noHighlight = noHighlightFun; 429 } 430 431 } 432 433 // Not enough! We need points, but this gives an error in setAttribute. 434 return {sectors: sector, points: p, midpoint: center}; 435 }, 436 437 /* 438 * labelArray=[ row1, row2, row3 ] 439 * paramArray=[ paramx, paramy, paramz ] 440 * parents=[[x1, y1, z1], [x2, y2, z2], [x3, y3, z3]] 441 */ 442 drawRadar: function (board, parents, attributes) { 443 var i, j, paramArray, numofparams, maxes, mins, 444 la, pdata, ssa, esa, ssratio, esratio, 445 sshifts, eshifts, starts, ends, 446 labelArray, colorArray, highlightColorArray, radius, myAtts, 447 cent, xc, yc, center, start_angle, rad, p, line, t, 448 xcoord, ycoord, polygons, legend_position, circles, lxoff, lyoff, 449 cla, clabelArray, ncircles, pcircles, angle, dr, sw, data, 450 len = parents.length, 451 452 get_anchor = function () { 453 var x1, x2, y1, y2, 454 relCoords = Type.evaluate(this.visProp.label.offset).slice(0); 455 456 x1 = this.point1.X(); 457 x2 = this.point2.X(); 458 y1 = this.point1.Y(); 459 y2 = this.point2.Y(); 460 if (x2 < x1) { 461 relCoords[0] = -relCoords[0]; 462 } 463 464 if (y2 < y1) { 465 relCoords[1] = -relCoords[1]; 466 } 467 468 this.setLabelRelativeCoords(relCoords); 469 470 return new Coords(Const.COORDS_BY_USER, [this.point2.X(), this.point2.Y()], this.board); 471 }, 472 473 get_transform = function (angle, i) { 474 var t, tscale, trot; 475 476 t = board.create('transform', [-(starts[i] - sshifts[i]), 0], {type: 'translate'}); 477 tscale = board.create('transform', [radius / ((ends[i] + eshifts[i]) - (starts[i] - sshifts[i])), 1], {type: 'scale'}); 478 t.melt(tscale); 479 trot = board.create('transform', [angle], {type: 'rotate'}); 480 t.melt(trot); 481 482 return t; 483 }; 484 485 if (len <= 0) { 486 JXG.debug("No data"); 487 return; 488 } 489 // labels for axes 490 paramArray = attributes.paramarray; 491 if (!Type.exists(paramArray)) { 492 JXG.debug("Need paramArray attribute"); 493 return; 494 } 495 numofparams = paramArray.length; 496 if (numofparams <= 1) { 497 JXG.debug("Need more than 1 param"); 498 return; 499 } 500 501 for (i = 0; i < len; i++) { 502 if (numofparams !== parents[i].length) { 503 JXG.debug("Use data length equal to number of params (" + parents[i].length + " != " + numofparams + ")"); 504 return; 505 } 506 } 507 508 maxes = []; 509 mins = []; 510 511 for (j = 0; j < numofparams; j++) { 512 maxes[j] = parents[0][j]; 513 mins[j] = maxes[j]; 514 } 515 516 for (i = 1; i < len; i++) { 517 for (j = 0; j < numofparams; j++) { 518 if (parents[i][j] > maxes[j]) { 519 maxes[j] = parents[i][j]; 520 } 521 522 if (parents[i][j] < mins[j]) { 523 mins[j] = parents[i][j]; 524 } 525 } 526 } 527 528 la = []; 529 pdata = []; 530 531 for (i = 0; i < len; i++) { 532 la[i] = ''; 533 pdata[i] = []; 534 } 535 536 ssa = []; 537 esa = []; 538 539 // 0 <= Offset from chart center <=1 540 ssratio = attributes.startshiftratio || 0; 541 // 0 <= Offset from chart radius <=1 542 esratio = attributes.endshiftratio || 0; 543 544 for (i = 0; i < numofparams; i++) { 545 ssa[i] = (maxes[i] - mins[i]) * ssratio; 546 esa[i] = (maxes[i] - mins[i]) * esratio; 547 } 548 549 // Adjust offsets per each axis 550 sshifts = attributes.startshiftarray || ssa; 551 eshifts = attributes.endshiftarray || esa; 552 // Values for inner circle, minimums by default 553 starts = attributes.startarray || mins; 554 555 if (Type.exists(attributes.start)) { 556 for (i = 0; i < numofparams; i++) { 557 starts[i] = attributes.start; 558 } 559 } 560 561 // Values for outer circle, maximums by default 562 ends = attributes.endarray || maxes; 563 if (Type.exists(attributes.end)) { 564 for (i = 0; i < numofparams; i++) { 565 ends[i] = attributes.end; 566 } 567 } 568 569 if (sshifts.length !== numofparams) { 570 JXG.debug("Start shifts length is not equal to number of parameters"); 571 return; 572 } 573 574 if (eshifts.length !== numofparams) { 575 JXG.debug("End shifts length is not equal to number of parameters"); 576 return; 577 } 578 579 if (starts.length !== numofparams) { 580 JXG.debug("Starts length is not equal to number of parameters"); 581 return; 582 } 583 584 if (ends.length !== numofparams) { 585 JXG.debug("Ends length is not equal to number of parameters"); 586 return; 587 } 588 589 // labels for legend 590 labelArray = attributes.labelarray || la; 591 colorArray = attributes.colors; 592 highlightColorArray = attributes.highlightcolors; 593 radius = attributes.radius || 10; 594 sw = attributes.strokewidth || 1; 595 596 if (!Type.exists(attributes.highlightonsector)) { 597 attributes.highlightonsector = false; 598 } 599 600 myAtts = { 601 name: attributes.name, 602 id: attributes.id, 603 strokewidth: sw, 604 polystrokewidth: attributes.polystrokewidth || sw, 605 strokecolor: attributes.strokecolor || 'black', 606 straightfirst: false, 607 straightlast: false, 608 fillcolor: attributes.fillColor || '#FFFF88', 609 fillopacity: attributes.fillOpacity || 0.4, 610 highlightfillcolor: attributes.highlightFillColor || '#FF7400', 611 highlightstrokecolor: attributes.highlightStrokeColor || 'black', 612 gradient: attributes.gradient || 'none' 613 }; 614 615 cent = attributes.center || [0, 0]; 616 xc = cent[0]; 617 yc = cent[1]; 618 center = board.create('point', [xc, yc], {name: '', fixed: true, withlabel: false, visible: false}); 619 start_angle = Math.PI / 2 - Math.PI / numofparams; 620 start_angle = attributes.startangle || 0; 621 rad = start_angle; 622 p = []; 623 line = []; 624 625 for (i = 0; i < numofparams; i++) { 626 rad += 2 * Math.PI / numofparams; 627 xcoord = radius * Math.cos(rad) + xc; 628 ycoord = radius * Math.sin(rad) + yc; 629 630 p[i] = board.create('point', [xcoord, ycoord], {name: '', fixed: true, withlabel: false, visible: false}); 631 line[i] = board.create('line', [center, p[i]], { 632 name: paramArray[i], 633 strokeColor: myAtts.strokecolor, 634 strokeWidth: myAtts.strokewidth, 635 strokeOpacity: 1.0, 636 straightFirst: false, 637 straightLast: false, 638 withLabel: true, 639 highlightStrokeColor: myAtts.highlightstrokecolor 640 }); 641 line[i].getLabelAnchor = get_anchor; 642 t = get_transform(rad, i); 643 644 for (j = 0; j < parents.length; j++) { 645 data = parents[j][i]; 646 pdata[j][i] = board.create('point', [data, 0], {name: '', fixed: true, withlabel: false, visible: false}); 647 pdata[j][i].addTransform(pdata[j][i], t); 648 } 649 } 650 651 polygons = []; 652 for (i = 0; i < len; i++) { 653 myAtts.labelcolor = colorArray && colorArray[i % colorArray.length]; 654 myAtts.strokecolor = colorArray && colorArray[i % colorArray.length]; 655 myAtts.fillcolor = colorArray && colorArray[i % colorArray.length]; 656 polygons[i] = board.create('polygon', pdata[i], { 657 withLines: true, 658 withLabel: false, 659 fillColor: myAtts.fillcolor, 660 fillOpacity: myAtts.fillopacity, 661 highlightFillColor: myAtts.highlightfillcolor 662 }); 663 664 for (j = 0; j < numofparams; j++) { 665 polygons[i].borders[j].setAttribute('strokecolor:' + colorArray[i % colorArray.length]); 666 polygons[i].borders[j].setAttribute('strokewidth:' + myAtts.polystrokewidth); 667 } 668 } 669 670 legend_position = attributes.legendposition || 'none'; 671 switch (legend_position) { 672 case 'right': 673 lxoff = attributes.legendleftoffset || 2; 674 lyoff = attributes.legendtopoffset || 1; 675 676 this.legend = board.create('legend', [xc + radius + lxoff, yc + radius - lyoff], { 677 labels: labelArray, 678 colors: colorArray 679 }); 680 break; 681 case 'none': 682 break; 683 default: 684 JXG.debug('Unknown legend position'); 685 } 686 687 circles = []; 688 if (attributes.showcircles) { 689 cla = []; 690 for (i = 0; i < 6; i++) { 691 cla[i] = 20 * i; 692 } 693 cla[0] = "0"; 694 clabelArray = attributes.circlelabelarray || cla; 695 ncircles = clabelArray.length; 696 697 if (ncircles < 2) { 698 JXG.debug("Too less circles"); 699 return; 700 } 701 702 pcircles = []; 703 angle = start_angle + Math.PI / numofparams; 704 t = get_transform(angle, 0); 705 706 myAtts.fillcolor = 'none'; 707 myAtts.highlightfillcolor = 'none'; 708 myAtts.strokecolor = attributes.strokecolor || 'black'; 709 myAtts.strokewidth = attributes.circlestrokewidth || 0.5; 710 myAtts.layer = 0; 711 712 // we have ncircles-1 intervals between ncircles circles 713 dr = (ends[0] - starts[0]) / (ncircles - 1); 714 715 for (i = 0; i < ncircles; i++) { 716 pcircles[i] = board.create('point', [starts[0] + i * dr, 0], { 717 name: clabelArray[i], 718 size: 0, 719 fixed: true, 720 withLabel: true, 721 visible: true 722 }); 723 pcircles[i].addTransform(pcircles[i], t); 724 circles[i] = board.create('circle', [center, pcircles[i]], myAtts); 725 } 726 727 } 728 this.rendNode = polygons[0].rendNode; 729 return { 730 circles: circles, 731 lines: line, 732 points: pdata, 733 midpoint: center, 734 polygons: polygons 735 }; 736 }, 737 738 /** 739 * Then, the update function of the renderer 740 * is called. Since a chart is only an abstract element, 741 * containing other elements, this function is empty. 742 */ 743 updateRenderer: function () { 744 return this; 745 }, 746 747 /** 748 * Update of the defining points 749 */ 750 update: function () { 751 if (this.needsUpdate) { 752 this.updateDataArray(); 753 } 754 755 return this; 756 }, 757 758 /** 759 * For dynamic charts update 760 * can be used to compute new entries 761 * for the arrays this.dataX and 762 * this.dataY. It is used in @see update. 763 * Default is an empty method, can be overwritten 764 * by the user. 765 */ 766 updateDataArray: function () {} 767 }); 768 769 JXG.createChart = function (board, parents, attributes) { 770 var data, row, i, j, col, 771 charts = [], 772 w, x, showRows, attr, 773 originalWidth, name, strokeColor, fillColor, 774 hStrokeColor, hFillColor, len, 775 table = Env.isBrowser ? board.document.getElementById(parents[0]) : null; 776 777 if ((parents.length === 1) && (Type.isString(parents[0]))) { 778 if (Type.exists(table)) { 779 // extract the data 780 attr = Type.copyAttributes(attributes, board.options, 'chart'); 781 782 table = (new DataSource()).loadFromTable(parents[0], attr.withheaders, attr.withheaders); 783 data = table.data; 784 col = table.columnHeaders; 785 row = table.rowHeaders; 786 787 originalWidth = attr.width; 788 name = attr.name; 789 strokeColor = attr.strokecolor; 790 fillColor = attr.fillcolor; 791 hStrokeColor = attr.highlightstrokecolor; 792 hFillColor = attr.highlightfillcolor; 793 794 board.suspendUpdate(); 795 796 len = data.length; 797 showRows = []; 798 if (attr.rows && Type.isArray(attr.rows)) { 799 for (i = 0; i < len; i++) { 800 for (j = 0; j < attr.rows.length; j++) { 801 if ((attr.rows[j] === i) || (attr.withheaders && attr.rows[j] === row[i])) { 802 showRows.push(data[i]); 803 break; 804 } 805 } 806 } 807 } else { 808 showRows = data; 809 } 810 811 len = showRows.length; 812 813 for (i = 0; i < len; i++) { 814 815 x = []; 816 if (attr.chartstyle && attr.chartstyle.indexOf('bar') !== -1) { 817 if (originalWidth) { 818 w = originalWidth; 819 } else { 820 w = 0.8; 821 } 822 823 x.push(1 - w / 2 + (i + 0.5) * w / len); 824 825 for (j = 1; j < showRows[i].length; j++) { 826 x.push(x[j - 1] + 1); 827 } 828 829 attr.width = w / len; 830 } 831 832 if (name && name.length === len) { 833 attr.name = name[i]; 834 } else if (attr.withheaders) { 835 attr.name = col[i]; 836 } 837 838 if (strokeColor && strokeColor.length === len) { 839 attr.strokecolor = strokeColor[i]; 840 } else { 841 attr.strokecolor = Color.hsv2rgb(((i + 1) / len) * 360, 0.9, 0.6); 842 } 843 844 if (fillColor && fillColor.length === len) { 845 attr.fillcolor = fillColor[i]; 846 } else { 847 attr.fillcolor = Color.hsv2rgb(((i + 1) / len) * 360, 0.9, 1.0); 848 } 849 850 if (hStrokeColor && hStrokeColor.length === len) { 851 attr.highlightstrokecolor = hStrokeColor[i]; 852 } else { 853 attr.highlightstrokecolor = Color.hsv2rgb(((i + 1) / len) * 360, 0.9, 1.0); 854 } 855 856 if (hFillColor && hFillColor.length === len) { 857 attr.highlightfillcolor = hFillColor[i]; 858 } else { 859 attr.highlightfillcolor = Color.hsv2rgb(((i + 1) / len) * 360, 0.9, 0.6); 860 } 861 862 if (attr.chartstyle && attr.chartstyle.indexOf('bar') !== -1) { 863 charts.push(new JXG.Chart(board, [x, showRows[i]], attr)); 864 } else { 865 charts.push(new JXG.Chart(board, [showRows[i]], attr)); 866 } 867 } 868 869 board.unsuspendUpdate(); 870 871 } 872 return charts; 873 } 874 875 attr = Type.copyAttributes(attributes, board.options, 'chart'); 876 return new JXG.Chart(board, parents, attr); 877 }; 878 879 JXG.registerElement('chart', JXG.createChart); 880 881 /** 882 * Legend for chart 883 * 884 **/ 885 JXG.Legend = function (board, coords, attributes) { 886 var attr; 887 888 /* Call the constructor of GeometryElement */ 889 this.constructor(); 890 891 attr = Type.copyAttributes(attributes, board.options, 'legend'); 892 893 this.board = board; 894 this.coords = new Coords(Const.COORDS_BY_USER, coords, this.board); 895 this.myAtts = {}; 896 this.label_array = attr.labelarray || attr.labels; 897 this.color_array = attr.colorarray || attr.colors; 898 this.lines = []; 899 this.myAtts.strokewidth = attr.strokewidth || 5; 900 this.myAtts.straightfirst = false; 901 this.myAtts.straightlast = false; 902 this.myAtts.withlabel = true; 903 this.myAtts.fixed = true; 904 this.style = attr.legendstyle || attr.style; 905 906 if (this.style === 'vertical') { 907 this.drawVerticalLegend(board, attr); 908 } else { 909 throw new Error('JSXGraph: Unknown legend style: ' + this.style); 910 } 911 }; 912 913 JXG.Legend.prototype = new GeometryElement(); 914 915 JXG.Legend.prototype.drawVerticalLegend = function (board, attributes) { 916 var i, 917 line_length = attributes.linelength || 1, 918 offy = (attributes.rowheight || 20) / this.board.unitY, 919 920 getLabelAnchor = function () { 921 this.setLabelRelativeCoords(this.visProp.label.offset); 922 return new Coords(Const.COORDS_BY_USER, [this.point2.X(), this.point2.Y()], this.board); 923 }; 924 925 for (i = 0; i < this.label_array.length; i++) { 926 this.myAtts.strokecolor = this.color_array[i]; 927 this.myAtts.highlightstrokecolor = this.color_array[i]; 928 this.myAtts.name = this.label_array[i]; 929 this.myAtts.label = { 930 offset: [10, 0], 931 strokeColor: this.color_array[i], 932 strokeWidth: this.myAtts.strokewidth 933 }; 934 935 this.lines[i] = board.create('line', [ 936 [this.coords.usrCoords[1], this.coords.usrCoords[2] - i * offy], 937 [this.coords.usrCoords[1] + line_length, this.coords.usrCoords[2] - i * offy]], 938 this.myAtts); 939 940 this.lines[i].getLabelAnchor = getLabelAnchor; 941 942 } 943 }; 944 945 JXG.createLegend = function (board, parents, attributes) { 946 //parents are coords of left top point of the legend 947 var start_from = [0, 0]; 948 949 if (Type.exists(parents)) { 950 if (parents.length === 2) { 951 start_from = parents; 952 } 953 } 954 955 return new JXG.Legend(board, start_from, attributes); 956 }; 957 958 JXG.registerElement('legend', JXG.createLegend); 959 960 return { 961 Chart: JXG.Chart, 962 Legend: JXG.Legend, 963 createChart: JXG.createChart, 964 createLegend: JXG.createLegend 965 }; 966 }); 967