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