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