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