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.offsets.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 {labelArray:labelArray, 604 colorArray: colorArray 605 }); 606 break; 607 case 'none': 608 break; 609 default: 610 JXG.debug('Unknown legend position'); 611 } 612 613 circles = []; 614 if (attributes['showcircles'] != false) { 615 cla = []; 616 for(i=0;i<6;i++) 617 cla[i]=20*i; 618 cla[0] = "0"; 619 clabelArray = attributes['circlelabelarray'] || cla; 620 ncircles = clabelArray.length; 621 if (ncircles<2) {alert("Too less circles"); return; } 622 pcircles = []; 623 angle=start_angle + Math.PI/numofparams; 624 t = get_transform(angle,0); 625 myAtts['fillcolor'] = 'none'; 626 myAtts['highlightfillcolor'] = 'none'; 627 myAtts['strokecolor'] = attributes['strokecolor'] || 'black'; 628 myAtts['strokewidth'] = attributes['circlestrokewidth'] || 0.5; 629 myAtts['layer'] = 0; 630 631 // we have ncircles-1 intervals between ncircles circles 632 dr = (ends[0]-starts[0])/(ncircles-1); 633 for(i=0;i<ncircles;i++) { 634 pcircles[i] = board.create('point', [starts[0]+i*dr,0], 635 {name:clabelArray[i], size:0, fixed:true, withLabel:true, visible:true} 636 ); 637 pcircles[i].addTransform(pcircles[i],t); 638 circles[i] = board.create('circle', [center,pcircles[i]], myAtts); 639 } 640 641 } 642 this.rendNode = polygons[0].rendNode; 643 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. 644 }, 645 646 /** 647 * Then, the update function of the renderer 648 * is called. Since a chart is only an abstract element, 649 * containing other elements, this function is empty. 650 */ 651 updateRenderer: function () { return this; }, 652 653 /** 654 * Update of the defining points 655 */ 656 update: function () { 657 if (this.needsUpdate) { 658 this.updateDataArray(); 659 } 660 return this; 661 }, 662 663 /** 664 * For dynamic charts update 665 * can be used to compute new entries 666 * for the arrays this.dataX and 667 * this.dataY. It is used in @see update. 668 * Default is an empty method, can be overwritten 669 * by the user. 670 */ 671 updateDataArray: function () {} 672 }); 673 674 JXG.createChart = function(board, parents, attributes) { 675 676 if((parents.length == 1) && (typeof parents[0] == 'string')) { 677 // todo: don't use document in here. let the user provide the dom node directly 678 var table = document.getElementById(parents[0]), 679 data, row, i, j, col, charts = [], w, x, showRows, 680 originalWidth, name, strokeColor, fillColor, hStrokeColor, hFillColor, len, attr; 681 if(JXG.exists(table)) { 682 // extract the data 683 attr = JXG.copyAttributes(attributes, board.options, 'chart'); 684 685 table = (new JXG.DataSource()).loadFromTable(parents[0], attr.withheaders, attr.withheaders); 686 data = table.data; 687 col = table.columnHeaders; 688 row = table.rowHeaders; 689 690 originalWidth = attr.width; 691 name = attr.name; 692 strokeColor = attr.strokecolor; 693 fillColor = attr.fillcolor; 694 hStrokeColor = attr.highlightstrokecolor; 695 hFillColor = attr.highlightfillcolor; 696 697 board.suspendUpdate(); 698 699 len = data.length; 700 showRows = []; 701 if (attr.rows && JXG.isArray(attr.rows)) { 702 for(i=0; i<len; i++) { 703 for(j=0; j<attr.rows.length; j++) { 704 if((attr.rows[j] == i) || (attr.withheaders && attr.rows[j] == row[i])) { 705 showRows.push(data[i]); 706 break; 707 } 708 } 709 } 710 } else { 711 showRows = data; 712 } 713 714 len = showRows.length; 715 716 for(i=0; i<len; i++) { 717 718 x = []; 719 if(attr.chartstyle && attr.chartstyle.indexOf('bar') != -1) { 720 if(originalWidth) { 721 w = originalWidth; 722 } else { 723 w = 0.8; 724 } 725 x.push(1 - w/2. + (i+0.5)*w/(1.0*len)); 726 for(j=1; j<showRows[i].length; j++) { 727 x.push(x[j-1] + 1); 728 } 729 attr.width = w/(1.0*len); 730 } 731 732 if(name && name.length == len) 733 attr.name = name[i]; 734 else if(attr.withheaders) 735 attr.name = col[i]; 736 737 if(strokeColor && strokeColor.length == len) 738 attr.strokecolor = strokeColor[i]; 739 else 740 attr.strokecolor = JXG.hsv2rgb(((i+1)/(1.0*len))*360,0.9,0.6); 741 742 if(fillColor && fillColor.length == len) 743 attr.fillcolor = fillColor[i]; 744 else 745 attr.fillcolor = JXG.hsv2rgb(((i+1)/(1.0*len))*360,0.9,1.0); 746 747 if(hStrokeColor && hStrokeColor.length == len) 748 attr.highlightstrokecolor = hStrokeColor[i]; 749 else 750 attr.highlightstrokecolor = JXG.hsv2rgb(((i+1)/(1.0*len))*360,0.9,1.0); 751 752 if(hFillColor && hFillColor.length == len) 753 attr.highlightfillcolor = hFillColor[i]; 754 else 755 attr.highlightfillcolor = JXG.hsv2rgb(((i+1)/(1.0*len))*360,0.9,0.6); 756 757 if(attr.chartstyle && attr.chartstyle.indexOf('bar') != -1) { 758 charts.push(new JXG.Chart(board, [x, showRows[i]], attr)); 759 } else 760 charts.push(new JXG.Chart(board, [showRows[i]], attr)); 761 } 762 763 board.unsuspendUpdate(); 764 765 } 766 return charts; 767 } else { 768 attr = JXG.copyAttributes(attributes, board.options, 'chart'); 769 return new JXG.Chart(board, parents, attr); 770 } 771 }; 772 773 JXG.JSXGraph.registerElement('chart', JXG.createChart); 774 775 /** 776 * Legend for chart 777 * 778 **/ 779 JXG.Legend = function(board, coords, attributes) { 780 var attr; 781 782 /* Call the constructor of GeometryElement */ 783 this.constructor(); 784 785 attr = JXG.copyAttributes(attributes, board.options, 'legend'); 786 787 this.board = board; 788 this.coords = new JXG.Coords(JXG.COORDS_BY_USER, coords, this.board); 789 this.myAtts = {}; 790 this.label_array = attr['labelarray'] || attr.labels; 791 this.color_array = attr['colorarray'] || attr.colors; 792 this.lines = []; 793 this.myAtts['strokewidth'] = attr['strokewidth'] || 5; 794 this.myAtts['straightfirst'] = false; 795 this.myAtts['straightlast'] = false; 796 this.myAtts['withlabel'] = true; 797 this.myAtts['fixed'] = true; 798 this.style = attr['legendstyle'] || attr.style; 799 800 switch(this.style) { 801 case 'vertical': 802 this.drawVerticalLegend(board, attr); 803 break; 804 default: 805 throw new Error('JSXGraph: Unknown legend style: ' + this.style); 806 break; 807 } 808 }; 809 JXG.Legend.prototype = new JXG.GeometryElement; 810 811 JXG.Legend.prototype.drawVerticalLegend = function(board, attributes) { 812 var line_length = attributes['linelength'] || 1, 813 offy = (attributes['rowheight'] || 20)/this.board.unitY, 814 i; 815 816 for(i=0;i<this.label_array.length;i++) { 817 this.myAtts['strokecolor'] = this.color_array[i]; 818 this.myAtts['highlightstrokecolor'] = this.color_array[i]; 819 this.myAtts['name'] = this.label_array[i]; 820 this.myAtts['label'] = {offsets:[10, 0]}; 821 822 this.lines[i] = board.create('line', 823 [[this.coords.usrCoords[1],this.coords.usrCoords[2] - i*offy], 824 [this.coords.usrCoords[1] + line_length,this.coords.usrCoords[2] - i*offy]], 825 this.myAtts 826 ); 827 this.lines[i].getLabelAnchor = function() { 828 this.setLabelRelativeCoords(this.visProp.label.offsets); 829 return new JXG.Coords(JXG.COORDS_BY_USER, [this.point2.X(),this.point2.Y()],this.board); 830 } 831 } 832 }; 833 834 JXG.createLegend = function(board, parents, attributes) { 835 //parents are coords of left top point of the legend 836 var start_from = [0,0]; 837 if(JXG.exists(parents)) 838 if(parents.length == 2) { 839 start_from = parents; 840 } 841 return new JXG.Legend(board, start_from, attributes); 842 }; 843 JXG.JSXGraph.registerElement('legend', JXG.createLegend); 844