1 /* 2 Copyright 2008-2017 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) { return function() { 270 return (txt.Y() >= 0) ? 'bottom' : 'top'; 271 }; })(text); 272 273 } 274 } 275 276 if (Type.isArray(attr.colors)) { 277 colors = attr.colors; 278 attr.fillcolor = colors[i % colors.length]; 279 } 280 281 pols[i] = board.create('polygon', p, attr); 282 if (Type.exists(attr.labels) && Type.exists(attr.labels[i])) { 283 pols[i].text = text; 284 } 285 } 286 287 return pols; 288 }, 289 290 drawPoints: function (board, x, y, attributes) { 291 var i, 292 points = [], 293 infoboxArray = attributes.infoboxarray; 294 295 attributes.fixed = true; 296 attributes.name = ''; 297 298 for (i = 0; i < x.length; i++) { 299 attributes.infoboxtext = infoboxArray ? infoboxArray[i % infoboxArray.length] : false; 300 points[i] = board.create('point', [x[i], y[i]], attributes); 301 } 302 303 return points; 304 }, 305 306 drawPie: function (board, y, attributes) { 307 var i, center, 308 p = [], 309 sector = [], 310 s = Statistics.sum(y), 311 colorArray = attributes.colors, 312 highlightColorArray = attributes.highlightcolors, 313 labelArray = attributes.labels, 314 r = attributes.radius || 4, 315 radius = r, 316 cent = attributes.center || [0, 0], 317 xc = cent[0], 318 yc = cent[1], 319 320 makeRadPointFun = function (j, fun, xc) { 321 return function () { 322 var s, i, rad, 323 t = 0; 324 325 for (i = 0; i <= j; i++) { 326 t += parseFloat(Type.evaluate(y[i])); 327 } 328 329 s = t; 330 for (i = j + 1; i < y.length; i++) { 331 s += parseFloat(Type.evaluate(y[i])); 332 } 333 rad = (s !== 0) ? (2 * Math.PI * t / s) : 0; 334 335 return radius() * Math[fun](rad) + xc; 336 }; 337 }, 338 339 highlightHandleLabel = function (f, s) { 340 var dx = -this.point1.coords.usrCoords[1] + this.point2.coords.usrCoords[1], 341 dy = -this.point1.coords.usrCoords[2] + this.point2.coords.usrCoords[2]; 342 343 if (Type.exists(this.label)) { 344 this.label.rendNode.style.fontSize = (s * Type.evaluate(this.label.visProp.fontsize)) + 'px'; 345 this.label.fullUpdate(); 346 } 347 348 this.point2.coords = new Coords(Const.COORDS_BY_USER, [ 349 this.point1.coords.usrCoords[1] + dx * f, 350 this.point1.coords.usrCoords[2] + dy * f 351 ], this.board); 352 this.fullUpdate(); 353 }, 354 355 highlightFun = function () { 356 if (!this.highlighted) { 357 this.highlighted = true; 358 this.board.highlightedObjects[this.id] = this; 359 this.board.renderer.highlight(this); 360 361 highlightHandleLabel.call(this, 1.1, 2); 362 } 363 }, 364 365 noHighlightFun = function () { 366 if (this.highlighted) { 367 this.highlighted = false; 368 this.board.renderer.noHighlight(this); 369 370 highlightHandleLabel.call(this, 0.90909090, 1); 371 } 372 }, 373 374 hiddenPoint = { 375 fixed: true, 376 withLabel: false, 377 visible: false, 378 name: '' 379 }; 380 381 if (!Type.isArray(labelArray)) { 382 labelArray = []; 383 for (i = 0; i < y.length; i++) { 384 labelArray[i] = ''; 385 } 386 } 387 388 if (!Type.isFunction(r)) { 389 radius = function () { 390 return r; 391 }; 392 } 393 394 attributes.highlightonsector = attributes.highlightonsector || false; 395 attributes.straightfirst = false; 396 attributes.straightlast = false; 397 398 center = board.create('point', [xc, yc], hiddenPoint); 399 p[0] = board.create('point', [ 400 function () { 401 return radius() + xc; 402 }, 403 function () { 404 return yc; 405 } 406 ], hiddenPoint); 407 408 for (i = 0; i < y.length; i++) { 409 p[i + 1] = board.create('point', [makeRadPointFun(i, 'cos', xc), makeRadPointFun(i, 'sin', yc)], hiddenPoint); 410 411 attributes.name = labelArray[i]; 412 attributes.withlabel = attributes.name !== ''; 413 attributes.fillcolor = colorArray && colorArray[i % colorArray.length]; 414 attributes.labelcolor = colorArray && colorArray[i % colorArray.length]; 415 attributes.highlightfillcolor = highlightColorArray && highlightColorArray[i % highlightColorArray.length]; 416 417 sector[i] = board.create('sector', [center, p[i], p[i + 1]], attributes); 418 419 if (attributes.highlightonsector) { 420 // overwrite hasPoint so that the whole sector is used for highlighting 421 sector[i].hasPoint = sector[i].hasPointSector; 422 } 423 if (attributes.highlightbysize) { 424 sector[i].highlight = highlightFun; 425 426 sector[i].noHighlight = noHighlightFun; 427 } 428 429 } 430 431 // Not enough! We need points, but this gives an error in setAttribute. 432 return {sectors: sector, points: p, midpoint: center}; 433 }, 434 435 /* 436 * labelArray=[ row1, row2, row3 ] 437 * paramArray=[ paramx, paramy, paramz ] 438 * parents=[[x1, y1, z1], [x2, y2, z2], [x3, y3, z3]] 439 */ 440 drawRadar: function (board, parents, attributes) { 441 var i, j, paramArray, numofparams, maxes, mins, 442 la, pdata, ssa, esa, ssratio, esratio, 443 sshifts, eshifts, starts, ends, 444 labelArray, colorArray, highlightColorArray, radius, myAtts, 445 cent, xc, yc, center, start_angle, rad, p, line, t, 446 xcoord, ycoord, polygons, legend_position, circles, lxoff, lyoff, 447 cla, clabelArray, ncircles, pcircles, angle, dr, sw, data, 448 len = parents.length, 449 450 get_anchor = function () { 451 var x1, x2, y1, y2, 452 relCoords = Type.evaluate(this.visProp.label.offset).slice(0); 453 454 x1 = this.point1.X(); 455 x2 = this.point2.X(); 456 y1 = this.point1.Y(); 457 y2 = this.point2.Y(); 458 if (x2 < x1) { 459 relCoords[0] = -relCoords[0]; 460 } 461 462 if (y2 < y1) { 463 relCoords[1] = -relCoords[1]; 464 } 465 466 this.setLabelRelativeCoords(relCoords); 467 468 return new Coords(Const.COORDS_BY_USER, [this.point2.X(), this.point2.Y()], this.board); 469 }, 470 471 get_transform = function (angle, i) { 472 var t, tscale, trot; 473 474 t = board.create('transform', [-(starts[i] - sshifts[i]), 0], {type: 'translate'}); 475 tscale = board.create('transform', [radius / ((ends[i] + eshifts[i]) - (starts[i] - sshifts[i])), 1], {type: 'scale'}); 476 t.melt(tscale); 477 trot = board.create('transform', [angle], {type: 'rotate'}); 478 t.melt(trot); 479 480 return t; 481 }; 482 483 if (len <= 0) { 484 JXG.debug("No data"); 485 return; 486 } 487 // labels for axes 488 paramArray = attributes.paramarray; 489 if (!Type.exists(paramArray)) { 490 JXG.debug("Need paramArray attribute"); 491 return; 492 } 493 numofparams = paramArray.length; 494 if (numofparams <= 1) { 495 JXG.debug("Need more than 1 param"); 496 return; 497 } 498 499 for (i = 0; i < len; i++) { 500 if (numofparams !== parents[i].length) { 501 JXG.debug("Use data length equal to number of params (" + parents[i].length + " != " + numofparams + ")"); 502 return; 503 } 504 } 505 506 maxes = []; 507 mins = []; 508 509 for (j = 0; j < numofparams; j++) { 510 maxes[j] = parents[0][j]; 511 mins[j] = maxes[j]; 512 } 513 514 for (i = 1; i < len; i++) { 515 for (j = 0; j < numofparams; j++) { 516 if (parents[i][j] > maxes[j]) { 517 maxes[j] = parents[i][j]; 518 } 519 520 if (parents[i][j] < mins[j]) { 521 mins[j] = parents[i][j]; 522 } 523 } 524 } 525 526 la = []; 527 pdata = []; 528 529 for (i = 0; i < len; i++) { 530 la[i] = ''; 531 pdata[i] = []; 532 } 533 534 ssa = []; 535 esa = []; 536 537 // 0 <= Offset from chart center <=1 538 ssratio = attributes.startshiftratio || 0; 539 // 0 <= Offset from chart radius <=1 540 esratio = attributes.endshiftratio || 0; 541 542 for (i = 0; i < numofparams; i++) { 543 ssa[i] = (maxes[i] - mins[i]) * ssratio; 544 esa[i] = (maxes[i] - mins[i]) * esratio; 545 } 546 547 // Adjust offsets per each axis 548 sshifts = attributes.startshiftarray || ssa; 549 eshifts = attributes.endshiftarray || esa; 550 // Values for inner circle, minimums by default 551 starts = attributes.startarray || mins; 552 553 if (Type.exists(attributes.start)) { 554 for (i = 0; i < numofparams; i++) { 555 starts[i] = attributes.start; 556 } 557 } 558 559 // Values for outer circle, maximums by default 560 ends = attributes.endarray || maxes; 561 if (Type.exists(attributes.end)) { 562 for (i = 0; i < numofparams; i++) { 563 ends[i] = attributes.end; 564 } 565 } 566 567 if (sshifts.length !== numofparams) { 568 JXG.debug("Start shifts length is not equal to number of parameters"); 569 return; 570 } 571 572 if (eshifts.length !== numofparams) { 573 JXG.debug("End shifts length is not equal to number of parameters"); 574 return; 575 } 576 577 if (starts.length !== numofparams) { 578 JXG.debug("Starts length is not equal to number of parameters"); 579 return; 580 } 581 582 if (ends.length !== numofparams) { 583 JXG.debug("Ends length is not equal to number of parameters"); 584 return; 585 } 586 587 // labels for legend 588 labelArray = attributes.labelarray || la; 589 colorArray = attributes.colors; 590 highlightColorArray = attributes.highlightcolors; 591 radius = attributes.radius || 10; 592 sw = attributes.strokewidth || 1; 593 594 if (!Type.exists(attributes.highlightonsector)) { 595 attributes.highlightonsector = false; 596 } 597 598 myAtts = { 599 name: attributes.name, 600 id: attributes.id, 601 strokewidth: sw, 602 polystrokewidth: attributes.polystrokewidth || sw, 603 strokecolor: attributes.strokecolor || 'black', 604 straightfirst: false, 605 straightlast: false, 606 fillcolor: attributes.fillColor || '#FFFF88', 607 fillopacity: attributes.fillOpacity || 0.4, 608 highlightfillcolor: attributes.highlightFillColor || '#FF7400', 609 highlightstrokecolor: attributes.highlightStrokeColor || 'black', 610 gradient: attributes.gradient || 'none' 611 }; 612 613 cent = attributes.center || [0, 0]; 614 xc = cent[0]; 615 yc = cent[1]; 616 center = board.create('point', [xc, yc], {name: '', fixed: true, withlabel: false, visible: false}); 617 start_angle = Math.PI / 2 - Math.PI / numofparams; 618 start_angle = attributes.startangle || 0; 619 rad = start_angle; 620 p = []; 621 line = []; 622 623 for (i = 0; i < numofparams; i++) { 624 rad += 2 * Math.PI / numofparams; 625 xcoord = radius * Math.cos(rad) + xc; 626 ycoord = radius * Math.sin(rad) + yc; 627 628 p[i] = board.create('point', [xcoord, ycoord], {name: '', fixed: true, withlabel: false, visible: false}); 629 line[i] = board.create('line', [center, p[i]], { 630 name: paramArray[i], 631 strokeColor: myAtts.strokecolor, 632 strokeWidth: myAtts.strokewidth, 633 strokeOpacity: 1.0, 634 straightFirst: false, 635 straightLast: false, 636 withLabel: true, 637 highlightStrokeColor: myAtts.highlightstrokecolor 638 }); 639 line[i].getLabelAnchor = get_anchor; 640 t = get_transform(rad, i); 641 642 for (j = 0; j < parents.length; j++) { 643 data = parents[j][i]; 644 pdata[j][i] = board.create('point', [data, 0], {name: '', fixed: true, withlabel: false, visible: false}); 645 pdata[j][i].addTransform(pdata[j][i], t); 646 } 647 } 648 649 polygons = []; 650 for (i = 0; i < len; i++) { 651 myAtts.labelcolor = colorArray && colorArray[i % colorArray.length]; 652 myAtts.strokecolor = colorArray && colorArray[i % colorArray.length]; 653 myAtts.fillcolor = colorArray && colorArray[i % colorArray.length]; 654 polygons[i] = board.create('polygon', pdata[i], { 655 withLines: true, 656 withLabel: false, 657 fillColor: myAtts.fillcolor, 658 fillOpacity: myAtts.fillopacity, 659 highlightFillColor: myAtts.highlightfillcolor 660 }); 661 662 for (j = 0; j < numofparams; j++) { 663 polygons[i].borders[j].setAttribute('strokecolor:' + colorArray[i % colorArray.length]); 664 polygons[i].borders[j].setAttribute('strokewidth:' + myAtts.polystrokewidth); 665 } 666 } 667 668 legend_position = attributes.legendposition || 'none'; 669 switch (legend_position) { 670 case 'right': 671 lxoff = attributes.legendleftoffset || 2; 672 lyoff = attributes.legendtopoffset || 1; 673 674 this.legend = board.create('legend', [xc + radius + lxoff, yc + radius - lyoff], { 675 labels: labelArray, 676 colors: colorArray 677 }); 678 break; 679 case 'none': 680 break; 681 default: 682 JXG.debug('Unknown legend position'); 683 } 684 685 circles = []; 686 if (attributes.showcircles) { 687 cla = []; 688 for (i = 0; i < 6; i++) { 689 cla[i] = 20 * i; 690 } 691 cla[0] = "0"; 692 clabelArray = attributes.circlelabelarray || cla; 693 ncircles = clabelArray.length; 694 695 if (ncircles < 2) { 696 JXG.debug("Too less circles"); 697 return; 698 } 699 700 pcircles = []; 701 angle = start_angle + Math.PI / numofparams; 702 t = get_transform(angle, 0); 703 704 myAtts.fillcolor = 'none'; 705 myAtts.highlightfillcolor = 'none'; 706 myAtts.strokecolor = attributes.strokecolor || 'black'; 707 myAtts.strokewidth = attributes.circlestrokewidth || 0.5; 708 myAtts.layer = 0; 709 710 // we have ncircles-1 intervals between ncircles circles 711 dr = (ends[0] - starts[0]) / (ncircles - 1); 712 713 for (i = 0; i < ncircles; i++) { 714 pcircles[i] = board.create('point', [starts[0] + i * dr, 0], { 715 name: clabelArray[i], 716 size: 0, 717 fixed: true, 718 withLabel: true, 719 visible: true 720 }); 721 pcircles[i].addTransform(pcircles[i], t); 722 circles[i] = board.create('circle', [center, pcircles[i]], myAtts); 723 } 724 725 } 726 this.rendNode = polygons[0].rendNode; 727 return { 728 circles: circles, 729 lines: line, 730 points: pdata, 731 midpoint: center, 732 polygons: polygons 733 }; 734 }, 735 736 /** 737 * Then, the update function of the renderer 738 * is called. Since a chart is only an abstract element, 739 * containing other elements, this function is empty. 740 */ 741 updateRenderer: function () { 742 return this; 743 }, 744 745 /** 746 * Update of the defining points 747 */ 748 update: function () { 749 if (this.needsUpdate) { 750 this.updateDataArray(); 751 } 752 753 return this; 754 }, 755 756 /** 757 * For dynamic charts update 758 * can be used to compute new entries 759 * for the arrays this.dataX and 760 * this.dataY. It is used in @see update. 761 * Default is an empty method, can be overwritten 762 * by the user. 763 */ 764 updateDataArray: function () {} 765 }); 766 767 JXG.createChart = function (board, parents, attributes) { 768 var data, row, i, j, col, 769 charts = [], 770 w, x, showRows, attr, 771 originalWidth, name, strokeColor, fillColor, 772 hStrokeColor, hFillColor, len, 773 table = Env.isBrowser ? board.document.getElementById(parents[0]) : null; 774 775 if ((parents.length === 1) && (Type.isString(parents[0]))) { 776 if (Type.exists(table)) { 777 // extract the data 778 attr = Type.copyAttributes(attributes, board.options, 'chart'); 779 780 table = (new DataSource()).loadFromTable(parents[0], attr.withheaders, attr.withheaders); 781 data = table.data; 782 col = table.columnHeaders; 783 row = table.rowHeaders; 784 785 originalWidth = attr.width; 786 name = attr.name; 787 strokeColor = attr.strokecolor; 788 fillColor = attr.fillcolor; 789 hStrokeColor = attr.highlightstrokecolor; 790 hFillColor = attr.highlightfillcolor; 791 792 board.suspendUpdate(); 793 794 len = data.length; 795 showRows = []; 796 if (attr.rows && Type.isArray(attr.rows)) { 797 for (i = 0; i < len; i++) { 798 for (j = 0; j < attr.rows.length; j++) { 799 if ((attr.rows[j] === i) || (attr.withheaders && attr.rows[j] === row[i])) { 800 showRows.push(data[i]); 801 break; 802 } 803 } 804 } 805 } else { 806 showRows = data; 807 } 808 809 len = showRows.length; 810 811 for (i = 0; i < len; i++) { 812 813 x = []; 814 if (attr.chartstyle && attr.chartstyle.indexOf('bar') !== -1) { 815 if (originalWidth) { 816 w = originalWidth; 817 } else { 818 w = 0.8; 819 } 820 821 x.push(1 - w / 2 + (i + 0.5) * w / len); 822 823 for (j = 1; j < showRows[i].length; j++) { 824 x.push(x[j - 1] + 1); 825 } 826 827 attr.width = w / len; 828 } 829 830 if (name && name.length === len) { 831 attr.name = name[i]; 832 } else if (attr.withheaders) { 833 attr.name = col[i]; 834 } 835 836 if (strokeColor && strokeColor.length === len) { 837 attr.strokecolor = strokeColor[i]; 838 } else { 839 attr.strokecolor = Color.hsv2rgb(((i + 1) / len) * 360, 0.9, 0.6); 840 } 841 842 if (fillColor && fillColor.length === len) { 843 attr.fillcolor = fillColor[i]; 844 } else { 845 attr.fillcolor = Color.hsv2rgb(((i + 1) / len) * 360, 0.9, 1.0); 846 } 847 848 if (hStrokeColor && hStrokeColor.length === len) { 849 attr.highlightstrokecolor = hStrokeColor[i]; 850 } else { 851 attr.highlightstrokecolor = Color.hsv2rgb(((i + 1) / len) * 360, 0.9, 1.0); 852 } 853 854 if (hFillColor && hFillColor.length === len) { 855 attr.highlightfillcolor = hFillColor[i]; 856 } else { 857 attr.highlightfillcolor = Color.hsv2rgb(((i + 1) / len) * 360, 0.9, 0.6); 858 } 859 860 if (attr.chartstyle && attr.chartstyle.indexOf('bar') !== -1) { 861 charts.push(new JXG.Chart(board, [x, showRows[i]], attr)); 862 } else { 863 charts.push(new JXG.Chart(board, [showRows[i]], attr)); 864 } 865 } 866 867 board.unsuspendUpdate(); 868 869 } 870 return charts; 871 } 872 873 attr = Type.copyAttributes(attributes, board.options, 'chart'); 874 return new JXG.Chart(board, parents, attr); 875 }; 876 877 JXG.registerElement('chart', JXG.createChart); 878 879 /** 880 * Legend for chart 881 * 882 **/ 883 JXG.Legend = function (board, coords, attributes) { 884 var attr; 885 886 /* Call the constructor of GeometryElement */ 887 this.constructor(); 888 889 attr = Type.copyAttributes(attributes, board.options, 'legend'); 890 891 this.board = board; 892 this.coords = new Coords(Const.COORDS_BY_USER, coords, this.board); 893 this.myAtts = {}; 894 this.label_array = attr.labelarray || attr.labels; 895 this.color_array = attr.colorarray || attr.colors; 896 this.lines = []; 897 this.myAtts.strokewidth = attr.strokewidth || 5; 898 this.myAtts.straightfirst = false; 899 this.myAtts.straightlast = false; 900 this.myAtts.withlabel = true; 901 this.myAtts.fixed = true; 902 this.style = attr.legendstyle || attr.style; 903 904 if (this.style === 'vertical') { 905 this.drawVerticalLegend(board, attr); 906 } else { 907 throw new Error('JSXGraph: Unknown legend style: ' + this.style); 908 } 909 }; 910 911 JXG.Legend.prototype = new GeometryElement(); 912 913 JXG.Legend.prototype.drawVerticalLegend = function (board, attributes) { 914 var i, 915 line_length = attributes.linelength || 1, 916 offy = (attributes.rowheight || 20) / this.board.unitY, 917 918 getLabelAnchor = function () { 919 this.setLabelRelativeCoords(this.visProp.label.offset); 920 return new Coords(Const.COORDS_BY_USER, [this.point2.X(), this.point2.Y()], this.board); 921 }; 922 923 for (i = 0; i < this.label_array.length; i++) { 924 this.myAtts.strokecolor = this.color_array[i]; 925 this.myAtts.highlightstrokecolor = this.color_array[i]; 926 this.myAtts.name = this.label_array[i]; 927 this.myAtts.label = { 928 offset: [10, 0], 929 strokeColor: this.color_array[i], 930 strokeWidth: this.myAtts.strokewidth 931 }; 932 933 this.lines[i] = board.create('line', [ 934 [this.coords.usrCoords[1], this.coords.usrCoords[2] - i * offy], 935 [this.coords.usrCoords[1] + line_length, this.coords.usrCoords[2] - i * offy]], 936 this.myAtts); 937 938 this.lines[i].getLabelAnchor = getLabelAnchor; 939 940 } 941 }; 942 943 JXG.createLegend = function (board, parents, attributes) { 944 //parents are coords of left top point of the legend 945 var start_from = [0, 0]; 946 947 if (Type.exists(parents)) { 948 if (parents.length === 2) { 949 start_from = parents; 950 } 951 } 952 953 return new JXG.Legend(board, start_from, attributes); 954 }; 955 956 JXG.registerElement('legend', JXG.createLegend); 957 958 return { 959 Chart: JXG.Chart, 960 Legend: JXG.Legend, 961 createChart: JXG.createChart, 962 createLegend: JXG.createLegend 963 }; 964 }); 965