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