1 /* 2 * Copyright 2010 Scriptoid s.r.l 3 * 4 * TODO: different methods for image patterns, either whole bg, with the figure as a mask, or shrunk to size 5 * if shrunk to size, keep ratios? 6 * A small library of drawing primitives. 7 * All primitives should have the following methods implemented: 8 * Like a Shape interface: 9 * -paint:void 10 * -tranform(matrix):void 11 * -contains(x, y):boolean 12 * -equals(object):boolean 13 * -toString():String 14 * -clone():object - this is a deep clone 15 * -getBounds():[number, number, number, number] 16 * -near(distance):boolean - This should be used to test if a click is close to a primitive/figure 17 * -getPoints():Array - returns an array of points, so that a figure can implement contains 18 * -getStyleInfo():Style - returns the different styles that can be used by any shape 19 */ 20 21 22 23 24 25 /** 26 * Creates an instance of Point 27 * 28 * 29 * @constructor 30 * @this {Point} 31 * @param {Number} x The x coordinate of point. 32 * @param {Number} y The y coordinate of point. 33 * @author Alex Gheorghiu <alex@scriptoid.com> 34 * Note: Even if it is named Point this class should be named Dot as Dot is closer 35 * then Point from math perspective. 36 **/ 37 function Point(x, y){ 38 /**The x coordinate of point*/ 39 this.x = x; 40 41 /**The y coordinate of point*/ 42 this.y = y; 43 44 /**The {@link Style} of the Point*/ 45 this.style = new Style(); 46 47 /**Serialization type*/ 48 this.oType = 'Point'; //object type used for JSON deserialization 49 } 50 51 /**Creates a {Point} out of JSON parsed object 52 *@param {JSONObject} o - the JSON parsed object 53 *@return {Point} a newly constructed Point 54 *@author Alex Gheorghiu <alex@scriptoid.com> 55 **/ 56 Point.load = function(o){ 57 var newPoint = new Point(); 58 newPoint.x = o.x; 59 newPoint.y = o.y; 60 newPoint.style = Style.load(o.style); 61 return newPoint; 62 } 63 64 65 /**Creates an array of points from an array of {JSONObject}s 66 *@param {Array} v - the array of JSONObjects 67 *@return an {Array} of {Point}s 68 **/ 69 Point.loadArray = function(v){ 70 var newPoints = []; 71 for(var i=0; i< v.length; i++){ 72 newPoints.push(Point.load(v[i])); 73 } 74 return newPoints; 75 } 76 77 78 /**Clones an array of points 79 *@param {Array} v - the array of {Point}s 80 *@return an {Array} of {Point}s 81 **/ 82 Point.cloneArray = function(v){ 83 var newPoints = []; 84 for(var i=0; i< v.length; i++){ 85 newPoints.push(v[i].clone()); 86 } 87 return newPoints; 88 } 89 90 Point.prototype = { 91 /*@param matrix is a 3x3 matrix*/ 92 transform:function(matrix){ 93 if(this.style!=null){ 94 this.style.transform(matrix); 95 } 96 var oldX = this.x; 97 var oldY = this.y; 98 this.x = matrix[0][0] * oldX + matrix[0][1] * oldY + matrix[0][2]; 99 this.y = matrix[1][0] * oldX + matrix[1][1] * oldY + matrix[1][2]; 100 }, 101 102 /**Paint current {Point} withing a context 103 *If you want to use a different style then the default one change the style 104 **/ 105 paint:function(context){ 106 if(this.style != null){ 107 this.style.setupContext(context); 108 } 109 if(this.style.strokeStyle != ""){ 110 context.fillStyle = this.style.strokeStyle; 111 context.beginPath(); 112 var width = 1; 113 if(this.style.lineWidth != null){ 114 width = parseInt(this.style.lineWidth); 115 } 116 context.arc(this.x, this.y, width, 0,Math.PI/180*360,false); 117 context.fill(); 118 } 119 }, 120 121 122 /**Tests if this point is similar to other point 123 *@param {Point} anotherPoint - the other point 124 **/ 125 equals:function(anotherPoint){ 126 if(! (anotherPoint instanceof Point) ){ 127 return false; 128 } 129 return (this.x == anotherPoint.x) 130 && (this.y == anotherPoint.y) 131 && this.style.equals(anotherPoint.style); 132 }, 133 134 /**Clone current Point 135 **/ 136 clone: function(){ 137 var newPoint = new Point(this.x, this.y); 138 newPoint.style = this.style.clone(); 139 return newPoint; 140 }, 141 142 /**Tests to see if a point (x, y) is within a range of current Point 143 *@param {Numeric} x - the x coordinate of tested point 144 *@param {Numeric} y - the x coordinate of tested point 145 *@param {Numeric} radius - the radius of the vicinity 146 *@author Alex Gheorghiu <alex@scriptoid.com> 147 **/ 148 near:function(x, y, radius){ 149 var distance = Math.sqrt(Math.pow(this.x - x, 2) + Math.pow(this.y - y, 2)); 150 151 return (distance <= radius); 152 }, 153 154 contains: function(x,y){ 155 return this.x == x && this.y == y; 156 }, 157 158 toString:function(){ 159 return 'point(' + this.x + ',' + this.y + ')'; 160 }, 161 162 getPoints:function(){ 163 return [this]; 164 }, 165 166 getBounds:function(){ 167 return Util.getBounds(this.getPoints()); 168 }, 169 170 /** 171 *We will draw a point a circle. The "visual" color and thicknes of the point will 172 *be created by the SVG's element style 173 * 174 *@see <a href="http://tutorials.jenkov.com/svg/circle-element.html">http://tutorials.jenkov.com/svg/circle-element.html</a> 175 * 176 *Example: 177 *<circle cx="40" cy="40" r="1" style="stroke:#006600; fill:#00cc00"/> 178 **/ 179 toSVG: function(){ 180 var r = ''; 181 182 r += '<circle cx="' + this.x + '" cy="' + this.y + '" r="' + 1 + '"' ; 183 r += this.style.toSVG(); 184 r += '/>' 185 186 return r; 187 } 188 189 190 } 191 192 193 194 195 /** 196 * Creates an instance of a Line 197 * 198 * @constructor 199 * @this {Line} 200 * @param {Point} startPoint - starting point of the line 201 * @param {Point} endPoint - the ending point of the line 202 * @author Alex Gheorghiu <alex@scriptoid.com> 203 **/ 204 function Line(startPoint, endPoint){ 205 /**Starting {@link Point} of the line*/ 206 this.startPoint = startPoint; 207 208 /**Ending {@link Point} of the line*/ 209 this.endPoint = endPoint; 210 211 /**The {@link Style} of the line*/ 212 this.style = new Style(); 213 214 /**Serialization type*/ 215 this.oType = 'Line'; //object type used for JSON deserialization 216 } 217 218 /**Creates a {Line} out of JSON parsed object 219 *@param {JSONObject} o - the JSON parsed object 220 *@return {Line} a newly constructed Line 221 *@author Alex Gheorghiu <alex@scriptoid.com> 222 **/ 223 Line.load = function(o){ 224 var newLine = new Line(); 225 newLine.startPoint = Point.load(o.startPoint); 226 newLine.endPoint = Point.load(o.endPoint); 227 newLine.style = Style.load(o.style); 228 return newLine; 229 } 230 231 Line.prototype = { 232 transform:function(matrix){ 233 this.startPoint.transform(matrix); 234 this.endPoint.transform(matrix); 235 if(this.style!=null){ 236 this.style.transform(matrix); 237 } 238 239 }, 240 241 paint:function(context){ 242 if(this.style != null){ 243 this.style.setupContext(context); 244 } 245 context.moveTo(this.startPoint.x, this.startPoint.y); 246 if(this.style.dashLength==0){ 247 context.lineTo(this.endPoint.x, this.endPoint.y); 248 } 249 else{ 250 251 //get the length of the line 252 var lineLength=Math.sqrt(Math.pow(this.startPoint.x-this.endPoint.x,2)+Math.pow(this.startPoint.y-this.endPoint.y,2)); 253 254 //get the angle 255 var angle=Util.getAngle(this.startPoint,this.endPoint); 256 257 //draw a dotted line 258 var move=false; 259 for(var i=0; i<lineLength; i+=(this.style.dashLength)){ 260 var p=this.startPoint.clone(); 261 262 //translate to origin of start 263 p.transform(Matrix.translationMatrix(-this.startPoint.x,-this.startPoint.y)) 264 265 //move it north by incremental dashlengths 266 p.transform(Matrix.translationMatrix(0, -i)); 267 268 //rotate to correct location 269 p.transform(Matrix.rotationMatrix(angle)); 270 271 //translate back 272 p.transform(Matrix.translationMatrix(this.startPoint.x,this.startPoint.y)) 273 274 if (move==false){ 275 context.lineTo(p.x,p.y); 276 move=true; 277 } 278 else{ 279 context.moveTo(p.x,p.y); 280 move=false; 281 } 282 } 283 } 284 285 if(this.style.strokeStyle != null && this.style.strokeStyle != ""){ 286 context.stroke(); 287 } 288 }, 289 290 clone:function(){ 291 var ret = new Line(this.startPoint.clone(), this.endPoint.clone()); 292 ret.style = this.style.clone(); 293 return ret; 294 }, 295 296 equals:function(anotherLine){ 297 if(!anotherLine instanceof Line){ 298 return false; 299 } 300 return this.startPoint.equals(anotherLine.startPoint) 301 && this.endPoint.equals(anotherLine.endPoint) 302 && this.style.equals(anotherLine.style); 303 }, 304 305 /** Tests to see if a point belongs to this line (not as infinite line but more like a segment) 306 * Algorithm: Compute line's equation and see if (x, y) verifies it. 307 * @param {Number} x - the X coordinates 308 * @param {Number} y - the Y coordinates 309 * @author Alex Gheorghiu <alex@scriptoid.com> 310 **/ 311 contains: function(x, y){ 312 // if the point is inside rectangle bounds of the segment 313 if (Math.min(this.startPoint.x, this.endPoint.x) <= x 314 && x <= Math.max(this.startPoint.x, this.endPoint.x) 315 && Math.min(this.startPoint.y, this.endPoint.y) <= y 316 && y <= Math.max(this.startPoint.y, this.endPoint.y)) { 317 318 // check for vertical line 319 if (this.startPoint.x == this.endPoint.x) { 320 return x == this.startPoint.x; 321 } else { // usual (not vertical) line can be represented as y = a * x + b 322 var a = (this.endPoint.y - this.startPoint.y) / 323 (this.endPoint.x - this.startPoint.x); 324 var b = this.startPoint.y - a * this.startPoint.x; 325 return y == a * x + b; 326 } 327 } else { 328 return false; 329 } 330 }, 331 332 /* 333 *See if we are near a {Line} by a certain radius 334 *@param {Number} x - the x coordinates 335 *@param {Number} y - the y coordinates 336 *@param {Number} radius - the radius to search for 337 *@author Zack Newsham <zack_newsham@yahoo.co.uk> 338 *@see <a href="http://mathworld.wolfram.com/Point-LineDistance2-Dimensional.html">http://mathworld.wolfram.com/Point-LineDistance2-Dimensional.html</a> 339 *@see <a href="http://www.worsleyschool.net/science/files/linepoint/distance.html">http://www.worsleyschool.net/science/files/linepoint/distance.html </a> (see method 5) 340 *TODO: review not made 341 **/ 342 near:function(x,y,radius){ 343 with(this){ 344 //get the slope of the line 345 var m; 346 if(endPoint.x==startPoint.x){ 347 return ( (startPoint.y-radius<=y && endPoint.y+radius>=y) || (endPoint.y-radius<=y && startPoint.y+radius>=y)) 348 && x>startPoint.x-radius && x<startPoint.x+radius ; 349 } 350 if(startPoint.y==endPoint.y){ 351 return ( (startPoint.x-radius<=x && endPoint.x+radius>=x) || (endPoint.x-radius<=x && startPoint.x+radius>=x)) 352 && y>startPoint.y-radius && y<startPoint.y+radius ; 353 } 354 355 356 startX = Math.min(endPoint.x,startPoint.x); 357 startY = Math.min(endPoint.y,startPoint.y); 358 endX = Math.max(endPoint.x,startPoint.x); 359 endY = Math.max(endPoint.y,startPoint.y); 360 361 m = (endPoint.y-startPoint.y)/(endPoint.x-startPoint.x); 362 b = -1; 363 //get the intercept 364 var c = startPoint.y-m*startPoint.x; 365 366 //get the radius 367 var d = (m*x+(b*y)+c)/Math.sqrt(Math.pow(m,2)+Math.pow(b,2)); 368 if(d < 0){ 369 d = 0 - d; 370 } 371 return (d<=radius && endX>=x && x>=startX && endY>=y && y>=startY) 372 || startPoint.near(x,y,radius) || endPoint.near(x,y,radius); 373 } 374 375 }, 376 377 /**we need to create a new array each time, or we will affect the actual shape*/ 378 getPoints:function(){ 379 points=[]; 380 points.push(this.startPoint); 381 points.push(this.endPoint); 382 return points; 383 }, 384 385 /** 386 *Get bounds for this line 387 *@author Alex Gheorghiu <alex@scriptoid.com> 388 **/ 389 getBounds:function(){ 390 return Util.getBounds(this.getPoints()); 391 }, 392 393 /**String representation*/ 394 toString:function(){ 395 return 'line(' + this.startPoint + ',' + this.endPoint + ')'; 396 }, 397 398 /**Render the SVG fragment for this primitive*/ 399 toSVG:function(){ 400 //<line x1="0" y1="0" x2="300" y2="300" style="stroke:rgb(99,99,99);stroke-width:2"/> 401 var result = '<line x1="' + this.startPoint.x + '" y1="' + this.startPoint.y + '" x2="' + this.endPoint.x + '" y2="' + this.endPoint.y + '"'; 402 result += this.style.toSVG(); 403 result += " />" 404 return result; 405 } 406 } 407 408 409 410 /** 411 * Creates an instance of a Polyline 412 * 413 * @constructor 414 * @this {Polyline} 415 * @author Alex Gheorghiu <alex@scriptoid.com> 416 **/ 417 function Polyline(){ 418 /**An {Array} of {@link Point}s*/ 419 this.points = [] 420 421 /**The {@link Style} of the polyline*/ 422 this.style = new Style(); 423 424 /**The starting {@link Point}. 425 * Required for path, we could use getPoints(), but this existed first. 426 * Also its a lot simpler. Each other element used in path already has a startPoint 427 **/ 428 this.startPoint = null; 429 430 /**Serialization type*/ 431 this.oType = 'Polyline'; //object type used for JSON deserialization 432 } 433 434 /**Creates a {Polyline} out of JSON parsed object 435 *@param {JSONObject} o - the JSON parsed object 436 *@return {Polyline} a newly constructed Polyline 437 *@author Alex Gheorghiu <alex@scriptoid.com> 438 **/ 439 Polyline.load = function(o){ 440 var newPolyline = new Polyline(); 441 newPolyline.points = Point.loadArray(o.points); 442 newPolyline.style = Style.load(o.style); 443 newPolyline.startPoint = Point.load(o.startPoint); 444 return newPolyline; 445 } 446 447 Polyline.prototype = { 448 addPoint:function(point){ 449 if(this.points.length==0){ 450 this.startPoint=point; 451 } 452 this.points.push(point); 453 }, 454 transform:function(matrix){ 455 with(this){ 456 if(style!=null){ 457 style.transform(matrix); 458 } 459 for(var i=0; i<points.length; i++){ 460 points[i].transform(matrix); 461 } 462 } 463 }, 464 getPoints:function(){ 465 var p=[]; 466 for (var i=0; i<this.points.length; i++){ 467 p.push(this.points[i]); 468 } 469 return p; 470 }, 471 472 getBounds:function(){ 473 return Util.getBounds(this.getPoints()); 474 }, 475 476 clone:function(){ 477 var ret=new Polyline(); 478 with(this){ 479 for(var i=0; i<points.length; i++){ 480 ret.addPoint(points[i].clone()); 481 } 482 } 483 ret.style=this.style.clone(); 484 return ret; 485 }, 486 487 488 equals:function(anotherPolyline){ 489 if(!anotherPolyline instanceof Polyline){ 490 return false; 491 } 492 if(anotherPolyline.points.length == this.points.length){ 493 for(var i=0; i<this.points.length; i++){ 494 if(!this.points[i].equals(anotherPolyline.points[i])){ 495 return false; 496 } 497 } 498 } 499 else{ 500 return false; 501 } 502 503 if(!this.style.equals(anotherPolyline.style)){ 504 return false; 505 } 506 507 if(!this.startPoint.equals(anotherPolyline.startPoint)){ 508 return false; 509 } 510 511 512 513 return true; 514 }, 515 paint:function(context){ 516 with(this){ 517 if(style!=null){ 518 style.setupContext(context); 519 } 520 context.moveTo(points[0].x,points[0].y); 521 for(var i=1; i<points.length; i++){ 522 context.lineTo(points[i].x,points[i].y); 523 } 524 if(style.strokeStyle!=null && this.style.strokeStyle!=""){ 525 context.stroke(); 526 } 527 if(style.fillStyle!=null && this.style.fillStyle!=""){ 528 context.fill(); 529 } 530 } 531 }, 532 533 contains:function(x, y){ 534 return Util.isPointInside(new Point(x, y), this.getPoints()) 535 }, 536 537 near:function(x,y,radius){ 538 with(this){ 539 for(var i=0; i< points.length-1; i++){ 540 var l=new Line(points[i],points[i+1]); 541 if(l.near(x,y)){ 542 return true; 543 } 544 } 545 return false; 546 } 547 548 }, 549 550 toString:function(){ 551 var result = 'polyline('; 552 with(this){ 553 for(var i=0; i < points.length; i++){ 554 result += points[i].toString() + ' '; 555 } 556 } 557 result += ')'; 558 return result; 559 }, 560 561 /**Render the SVG fragment for this primitive*/ 562 toSVG:function(){ 563 //<polyline points="0,0 0,20 20,20 20,40 40,40 40,60" style="fill:white;stroke:red;stroke-width:2"/> 564 var result = '<polyline points="'; 565 for(var i=0; i < this.points.length; i++){ 566 result += this.points[i].x + ',' + this.points[i].y + ' '; 567 } 568 result += '"'; 569 result += this.style.toSVG(); 570 result += '/>'; 571 572 return result; 573 } 574 } 575 576 577 /** 578 * Creates an instance of a Polygon 579 * 580 * @constructor 581 * @this {Polygon} 582 * @author Alex Gheorghiu <alex@scriptoid.com> 583 **/ 584 function Polygon(){ 585 /**An {Array} of {@link Point}s*/ 586 this.points = [] 587 588 /**The {@link Style} of the polygon*/ 589 this.style = new Style(); 590 591 /**Serialization type*/ 592 this.oType = 'Polygon'; //object type used for JSON deserialization 593 } 594 595 /**Creates a {Polygon} out of JSON parsed object 596 *@param {JSONObject} o - the JSON parsed object 597 *@return {Polygon} a newly constructed Polygon 598 *@author Alex Gheorghiu <alex@scriptoid.com> 599 **/ 600 Polygon.load = function(o){ 601 var newPolygon = new Polygon(); 602 newPolygon.points = Point.loadArray(o.points); 603 newPolygon.style = Style.load(o.style); 604 return newPolygon; 605 } 606 607 608 Polygon.prototype = { 609 addPoint:function(point){ 610 this.points.push(point); 611 }, 612 613 614 getPosition:function(){ 615 return [this.points[0].x,[this.points[0].y]]; 616 }, 617 618 619 paint:function(context){ 620 with(context){ 621 beginPath(); 622 if(this.style!=null){ 623 this.style.setupContext(context); 624 } 625 if(this.points.length > 1){ 626 moveTo(this.points[0].x,this.points[0].y); 627 for(var i=1; i<this.points.length; i++){ 628 lineTo(this.points[i].x, this.points[i].y) 629 } 630 } 631 closePath(); 632 633 //first fill 634 if(this.style.fillStyle!=null && this.style.fillStyle!=""){ 635 fill(); 636 } 637 638 //then stroke 639 if(this.style.strokeStyle!=null && this.style.strokeStyle!=""){ 640 stroke(); 641 } 642 } 643 }, 644 645 getPoints:function(){ 646 return this.points; 647 }, 648 649 /** 650 *@return {Array<Number>} - returns [minX, minY, maxX, maxY] - bounds, where 651 * all points are in the bounds.*/ 652 getBounds:function(){ 653 return Util.getBounds(this.getPoints()); 654 }, 655 656 fill:function(context,color){ 657 with(this){ 658 context.fillStyle=color; 659 context.beginPath(); 660 context.moveTo(points[0].x,points[0].y); 661 for(var i=1; i<points.length; i++){ 662 context.lineTo(points[i].x,points[i].y); 663 } 664 context.lineTo(points[0].x,points[0].y); 665 context.closePath(); 666 context.fill(); 667 } 668 }, 669 670 near:function(x,y,radius){ 671 with(this){ 672 var i=0; 673 for(i=0; i< points.length-1; i++){ 674 var l=new Line(points[i],points[i+1]); 675 if(l.near(x,y,radius)){ 676 return true; 677 } 678 } 679 l=new Line(points[i],points[0]); 680 if(l.near(x,y,radius)){ 681 return true; 682 } 683 return false; 684 } 685 }, 686 equals:function(anotherPolygon){ 687 if(!anotherPolygon instanceof Polygon){ 688 return false; 689 } 690 with(this){ 691 if(anotherPolygon.points.length == points.length){ 692 for(var i=0; i<points.length; i++){ 693 if(!points[i].equals(anotherPolygon.points[i])){ 694 return false; 695 } 696 } 697 } 698 //TODO: test for all Polygon members 699 } 700 return true; 701 }, 702 703 clone:function(){ 704 var ret=new Polygon(); 705 with(this){ 706 for(var i=0; i<points.length; i++){ 707 ret.addPoint(points[i].clone()); 708 } 709 } 710 ret.style=this.style.clone(); 711 return ret; 712 }, 713 714 contains:function(x, y){ 715 var inPath = false; 716 return Util.isPointInside(new Point(x,y),this.points); 717 }, 718 719 transform:function(matrix){ 720 with(this){ 721 if(style!=null){ 722 style.transform(matrix); 723 } 724 for(var i=0; i < points.length; i++){ 725 points[i].transform(matrix); 726 } 727 } 728 }, 729 730 toString:function(){ 731 var result = 'polygon('; 732 with(this){ 733 for(var i=0; i < points.length; i++){ 734 result += points[i].toString() + ' '; 735 } 736 } 737 result += ')'; 738 return result; 739 }, 740 741 /**Render the SVG fragment for this primitive*/ 742 toSVG:function(){ 743 //<polygon points="220,100 300,210 170,250" style="fill:#cccccc; stroke:#000000;stroke-width:1"/> 744 var result = '<polygon points="'; 745 for(var i=0; i < this.points.length; i++){ 746 result += this.points[i].x + ',' + this.points[i].y + ' '; 747 } 748 result += '" ' 749 //+ 'style="fill:#cccccc;stroke:#000000;stroke-width:1"' 750 + this.style.toSVG() 751 + ' />'; 752 return result; 753 } 754 } 755 756 757 /** 758 * Creates an instance of a quad curve. 759 * A curved line determined by 2 normal points (startPoint and endPoint) and 1 control point (controlPoint) 760 * 761 * @constructor 762 * @this {QuadCurve} 763 * @param {Point} startPoint - starting point of the line 764 * @param {Point} controlPoint - the control point of the line 765 * @param {Point} endPoint - the ending point of the line 766 * @see <a href="http://en.wikipedia.org/wiki/B%C3%A9zier_curve#Quadratic_B.C3.A9zier_curves">http://en.wikipedia.org/wiki/B%C3%A9zier_curve#Quadratic_B.C3.A9zier_curves</a> 767 **/ 768 function QuadCurve(startPoint, controlPoint, endPoint){ 769 /**The start {@link Point}*/ 770 this.startPoint = startPoint; 771 772 /**The controll {@link Point}*/ 773 this.controlPoint = controlPoint; 774 775 /**The end {@link Point}*/ 776 this.endPoint = endPoint; 777 778 /**The {@link Style} of the quad*/ 779 this.style = new Style(); 780 781 /**Serialization type*/ 782 this.oType = 'QuadCurve'; //object type used for JSON deserialization 783 } 784 785 /**Creates a {QuadCurve} out of JSON parsed object 786 *@param {JSONObject} o - the JSON parsed object 787 *@return {QuadCurve} a newly constructed QuadCurve 788 *@author Alex Gheorghiu <alex@scriptoid.com> 789 **/ 790 QuadCurve.load = function(o){ 791 var newQuad = new QuadCurve(); 792 newQuad.startPoint = Point.load(o.startPoint); 793 newQuad.controlPoint = Point.load(o.controlPoint); 794 newQuad.endPoint = Point.load(o.endPoint); 795 newQuad.style = Style.load(o.style); 796 return newQuad; 797 } 798 799 /**Creates an {Array} of {QuadCurve} out of JSON parsed object 800 *@param {JSONObject} v - the JSON parsed object (actually an {Array} of {JSONObject}s 801 *@return {Array} of {QuadCurve}s 802 *@author Alex Gheorghiu <alex@scriptoid.com> 803 **/ 804 QuadCurve.loadArray = function(v){ 805 var quads = []; 806 807 for(var i=0; i<v.length; i++){ 808 quads.push(QuadCurve.load(v[i])); 809 } 810 811 return quads; 812 } 813 814 QuadCurve.prototype = { 815 transform:function(matrix){ 816 if(this.style!=null){ 817 this.style.transform(matrix); 818 } 819 this.startPoint.transform(matrix); 820 this.controlPoint.transform(matrix); 821 this.endPoint.transform(matrix); 822 }, 823 824 getPoints:function(){ 825 //should this return a number of points along the line? 826 return [this.startPoint,this.controlPoint,this.endPoint]; 827 }, 828 829 /*We could use an interpolation algorightm t=0,1 and pick 10 points to iterate on ...but for now it's fine 830 **/ 831 getBounds:function(){ 832 return Util.getBounds(this.getPoints()); 833 }, 834 835 836 paint:function(context){ 837 with(context){ 838 if(this.style!=null){ 839 this.style.setupContext(context); 840 } 841 with(this){ 842 moveTo(startPoint.x,startPoint.y); 843 quadraticCurveTo(controlPoint.x, controlPoint.y, endPoint.x, endPoint.y); 844 //first fill 845 if(style.fillStyle!=null && this.style.fillStyle!=""){ 846 context.fill(); 847 } 848 849 //then stroke 850 if(style.strokeStyle!=null && this.style.strokeStyle!=""){ 851 stroke(); 852 } 853 854 } 855 } 856 }, 857 858 /* 859 *TODO: algorithm not clear and maybe we can find the math formula to determine if we have an intersection 860 *@see <a href="http://rosettacode.org/wiki/Bitmap/B%C3%A9zier_curves/Quadratic">http://rosettacode.org/wiki/Bitmap/B%C3%A9zier_curves/Quadratic</a> 861 */ 862 near:function(x,y,radius){ 863 with(this){ 864 var polls=100; 865 if(!Util.isPointInside(new Point(x,y), [startPoint,controlPoint,endPoint]) && !startPoint.near(x,y,radius) && ! endPoint.near(x,y,radius)){ 866 return false;//not inside the control points, so can't be near the line 867 } 868 var low=0; 869 var high=polls; 870 var i=(high-low)/2; 871 while(i >= low && i <= high && high-low>0.01){//high-low indicates>0.01 stops us from taking increasingly tiny steps 872 i=low+(high-low)/2 //we want the mid point 873 874 //dont fully understand this 875 var t = i / polls; 876 var fromEnd = Math.pow((1.0 - t), 2); //get how far from end we are and square it 877 var a = 2.0 * t * (1.0 - t); 878 var fromStart = Math.pow(t, 2); //get how far from start we are and square it 879 var newX = fromEnd * startPoint.x + a * controlPoint.x + fromStart * endPoint.x;//? 880 var newY = fromEnd * startPoint.y + a * controlPoint.y + fromStart * endPoint.y;//? 881 p=new Point(newX,newY); 882 if(p.near(x,y,radius)){ 883 return true; 884 } 885 886 //get distance between start and the point we are looking for, and the current point on line 887 pToStart=Math.sqrt(Math.pow(startPoint.x-p.x,2)+Math.pow(startPoint.y-p.y,2)); 888 myToStart=Math.sqrt(Math.pow(startPoint.x-x,2)+Math.pow(startPoint.y-y,2)); 889 890 //if our point is closer to start, we know that our cursor must be between start and where we are 891 if(myToStart<pToStart){ 892 high=i; 893 } 894 else if(myToStart!=pToStart){ 895 low=i; 896 } 897 else{ 898 return false;//their distance is the same but the point is not near, return false. 899 } 900 return startPoint.near(x,y,radius)|| endPoint.near(x,y,radius); 901 } 902 } 903 }, 904 905 clone:function(){ 906 ret=new QuadCurve(this.startPoint.clone(),this.controlPoint.clone(),this.endPoint.clone()); 907 ret.style=this.style.clone(); 908 return ret; 909 }, 910 911 equals:function(anotherQuadCurve){ 912 if(!anotherQuadCurve instanceof QuadCurve){ 913 return false; 914 } 915 916 return this.startPoint.equals(anotherQuadCurve.startPoint) 917 && this.controlPoint.equals(anotherQuadCurve.controlPoint) 918 && this.endPoint.equals(anotherQuadCurve.endPoint) 919 && this.style.equals(anotherQuadCurve.style); 920 }, 921 922 /** 923 *@deprecated 924 **/ 925 deprecated_contains:function(x, y){ 926 return this.near(x,y,3); 927 points=[this.startPoint,this.controlPoint,this.endPoint]; 928 return Util.isPointInside(new Point(x,y),points); 929 }, 930 931 /** 932 * @see sources for <a href="http://grepcode.com/file/repository.grepcode.com/java/root/jdk/openjdk/6-b14/java/awt/geom/QuadCurve2D.java">java.awt.geom.QuadCurve2D</a> 933 * @author (just converted to JavaScript) alex@scriptoid.com 934 */ 935 contains:function(x,y) { 936 937 var x1 = this.startPoint.x; 938 var y1 = this.startPoint.y; 939 var xc = this.controlPoint.x; 940 var yc = this.controlPoint.y; 941 var x2 = this.endPoint.x; 942 var y2 = this.endPoint.y; 943 944 /* 945 * We have a convex shape bounded by quad curve Pc(t) 946 * and ine Pl(t). 947 * 948 * P1 = (x1, y1) - start point of curve 949 * P2 = (x2, y2) - end point of curve 950 * Pc = (xc, yc) - control point 951 * 952 * Pq(t) = P1*(1 - t)^2 + 2*Pc*t*(1 - t) + P2*t^2 = 953 * = (P1 - 2*Pc + P2)*t^2 + 2*(Pc - P1)*t + P1 954 * Pl(t) = P1*(1 - t) + P2*t 955 * t = [0:1] 956 * 957 * P = (x, y) - point of interest 958 * 959 * Let's look at second derivative of quad curve equation: 960 * 961 * Pq''(t) = 2 * (P1 - 2 * Pc + P2) = Pq'' 962 * It's constant vector. 963 * 964 * Let's draw a line through P to be parallel to this 965 * vector and find the intersection of the quad curve 966 * and the line. 967 * 968 * Pq(t) is point of intersection if system of equations 969 * below has the solution. 970 * 971 * L(s) = P + Pq''*s == Pq(t) 972 * Pq''*s + (P - Pq(t)) == 0 973 * 974 * | xq''*s + (x - xq(t)) == 0 975 * | yq''*s + (y - yq(t)) == 0 976 * 977 * This system has the solution if rank of its matrix equals to 1. 978 * That is, determinant of the matrix should be zero. 979 * 980 * (y - yq(t))*xq'' == (x - xq(t))*yq'' 981 * 982 * Let's solve this equation with 't' variable. 983 * Also let kx = x1 - 2*xc + x2 984 * ky = y1 - 2*yc + y2 985 * 986 * t0q = (1/2)*((x - x1)*ky - (y - y1)*kx) / 987 * ((xc - x1)*ky - (yc - y1)*kx) 988 * 989 * Let's do the same for our line Pl(t): 990 * 991 * t0l = ((x - x1)*ky - (y - y1)*kx) / 992 * ((x2 - x1)*ky - (y2 - y1)*kx) 993 * 994 * It's easy to check that t0q == t0l. This fact means 995 * we can compute t0 only one time. 996 * 997 * In case t0 < 0 or t0 > 1, we have an intersections outside 998 * of shape bounds. So, P is definitely out of shape. 999 * 1000 * In case t0 is inside [0:1], we should calculate Pq(t0) 1001 * and Pl(t0). We have three points for now, and all of them 1002 * lie on one line. So, we just need to detect, is our point 1003 * of interest between points of intersections or not. 1004 * 1005 * If the denominator in the t0q and t0l equations is 1006 * zero, then the points must be collinear and so the 1007 * curve is degenerate and encloses no area. Thus the 1008 * result is false. 1009 */ 1010 var kx = x1 - 2 * xc + x2; 1011 var ky = y1 - 2 * yc + y2; 1012 var dx = x - x1; 1013 var dy = y - y1; 1014 var dxl = x2 - x1; 1015 var dyl = y2 - y1; 1016 1017 var t0 = (dx * ky - dy * kx) / (dxl * ky - dyl * kx); 1018 if (t0 < 0 || t0 > 1 || t0 != t0) { 1019 return false; 1020 } 1021 1022 var xb = kx * t0 * t0 + 2 * (xc - x1) * t0 + x1; 1023 var yb = ky * t0 * t0 + 2 * (yc - y1) * t0 + y1; 1024 var xl = dxl * t0 + x1; 1025 var yl = dyl * t0 + y1; 1026 1027 return (x >= xb && x < xl) || 1028 (x >= xl && x < xb) || 1029 (y >= yb && y < yl) || 1030 (y >= yl && y < yb); 1031 }, 1032 1033 toString:function(){ 1034 return 'quad(' + this.startPoint + ',' + this.controlPoint + ',' + this.endPoint + ')'; 1035 }, 1036 1037 /**Render the SVG fragment for this primitive 1038 *@see <a href="http://www.w3.org/TR/SVG/paths.html#PathDataQuadraticBezierCommands">http://www.w3.org/TR/SVG/paths.html#PathDataQuadraticBezierCommands</a> 1039 **/ 1040 toSVG:function(){ 1041 //<path d="M200,300 Q400,50 600,300 T1000,300" fill="none" stroke="red" stroke-width="5" /> 1042 1043 var result = '<path d="M'; 1044 result += this.startPoint.x + ',' + this.endPoint.y; 1045 result += ' Q' + this.controlPoint.x + ',' + this.controlPoint.y; 1046 result += ' ' + this.endPoint.x + ',' + this.endPoint.y; 1047 1048 result += '" ' 1049 + this.style.toSVG() 1050 + ' />'; 1051 1052 return result; 1053 } 1054 } 1055 1056 1057 /** 1058 * Creates an instance of a cubic curve. 1059 * A curved line determined by 2 normal points (startPoint and endPoint) and 2 control points (controlPoint1, controlPoint2) 1060 * 1061 * @constructor 1062 * @this {CubicCurve} 1063 * @param {Point} startPoint - starting point of the line 1064 * @param {Point} controlPoint1 - 1st control point of the line 1065 * @param {Point} controlPoint2 - 2nd control point of the line 1066 * @param {Point} endPoint - the ending point of the line 1067 * @see <a href="http://en.wikipedia.org/wiki/B%C3%A9zier_curve#Cubic_B.C3.A9zier_curves">http://en.wikipedia.org/wiki/B%C3%A9zier_curve#Cubic_B.C3.A9zier_curves</a> 1068 **/ 1069 function CubicCurve(startPoint, controlPoint1, controlPoint2, endPoint){ 1070 /**The start {@link Point}*/ 1071 this.startPoint = startPoint; 1072 1073 /**The first controll {@link Point}*/ 1074 this.controlPoint1 = controlPoint1; 1075 1076 /**The second controll {@link Point}*/ 1077 this.controlPoint2 = controlPoint2; 1078 1079 /**The end {@link Point}*/ 1080 this.endPoint = endPoint; 1081 1082 /**The {@link Style} of the quad*/ 1083 this.style = new Style(); 1084 1085 /**Object type used for JSON deserialization*/ 1086 this.oType = 'CubicCurve'; 1087 } 1088 1089 /**Creates a {CubicCurve} out of JSON parsed object 1090 *@param {JSONObject} o - the JSON parsed object 1091 *@return {CubicCurve} a newly constructed CubicCurve 1092 *@author Alex Gheorghiu <alex@scriptoid.com> 1093 **/ 1094 CubicCurve.load = function(o){ 1095 var newCubic = new CubicCurve(); 1096 1097 newCubic.startPoint = Point.load(o.startPoint); 1098 newCubic.controlPoint1 = Point.load(o.controlPoint1); 1099 newCubic.controlPoint2 = Point.load(o.controlPoint2); 1100 newCubic.endPoint = Point.load(o.endPoint); 1101 1102 newCubic.style = Style.load(o.style); 1103 return newCubic; 1104 } 1105 1106 1107 CubicCurve.prototype = { 1108 transform:function(matrix){ 1109 if(this.style!=null){ 1110 this.style.transform(matrix); 1111 } 1112 this.startPoint.transform(matrix); 1113 this.controlPoint1.transform(matrix); 1114 this.controlPoint2.transform(matrix); 1115 this.endPoint.transform(matrix); 1116 }, 1117 paint:function(context){ 1118 with(this){ 1119 if(style!=null){ 1120 style.setupContext(context); 1121 } 1122 context.moveTo(startPoint.x,startPoint.y); 1123 context.bezierCurveTo(controlPoint1.x, controlPoint1.y, controlPoint2.x, controlPoint2.y, endPoint.x, endPoint.y) 1124 1125 1126 if(style.fillStyle!=null && this.style.fillStyle!=""){ 1127 context.fill(); 1128 } 1129 1130 if(style.strokeStyle!=null && this.style.strokeStyle!=""){ 1131 context.stroke(); 1132 } 1133 } 1134 }, 1135 1136 clone:function(){ 1137 ret = new CubicCurve(this.startPoint.clone(),this.controlPoint1.clone(), this.controlPoint2.clone(),this.endPoint.clone()); 1138 ret.style=this.style.clone(); 1139 return ret; 1140 }, 1141 1142 equals:function(anotherCubicCurve){ 1143 if(!anotherCubicCurve instanceof CubicCurve){ 1144 return false; 1145 } 1146 return this.startPoint.equals(anotherCubicCurve.startPoint) 1147 && this.controlPoint1.equals(anotherCubicCurve.controlPoint1) 1148 && this.controlPoint2.equals(anotherCubicCurve.controlPoint2) 1149 && this.endPoint.equals(anotherCubicCurve.endPoint); 1150 }, 1151 1152 /**@deprecated*/ 1153 deprecated_contains:function(x,y){ 1154 with(this){ 1155 var q1=new QuadCurve(startPoint,controlPoint1,controlPoint2); 1156 var q2=new QuadCurve(controlPoint1,controlPoint2,endPoint); 1157 return q1.contains(x,y) || q2.contains(x,y); 1158 //restore(); 1159 } 1160 1161 }, 1162 1163 /** 1164 * Inspired by java.awt.geom.CubicCurve2D 1165 */ 1166 contains:function(x, y) { 1167 if (!(x * 0.0 + y * 0.0 == 0.0)) { 1168 /* Either x or y was infinite or NaN. 1169 * A NaN always produces a negative response to any test 1170 * and Infinity values cannot be "inside" any path so 1171 * they should return false as well. 1172 */ 1173 return false; 1174 } 1175 // We count the "Y" crossings to determine if the point is 1176 // inside the curve bounded by its closing line. 1177 var x1 = this.startPoint.x//getX1(); 1178 var y1 = this.startPoint.y//getY1(); 1179 var x2 = this.endPoint.x//getX2(); 1180 var y2 = this.endPoint.y//getY2(); 1181 var crossings = 1182 (Util.pointCrossingsForLine(x, y, x1, y1, x2, y2) + 1183 Util.pointCrossingsForCubic(x, y, 1184 x1, y1, 1185 this.controlPoint1.x, this.controlPoint1.y, 1186 this.controlPoint2.x, this.controlPoint2.y, 1187 x2, y2, 0)); 1188 return ((crossings & 1) == 1); 1189 }, 1190 1191 1192 /** 1193 *TODO: algorithm not clear and maybe we can find the math formula to determine if we have an intersection 1194 *@see <a href="http://rosettacode.org/wiki/Bitmap/B%C3%A9zier_curves/Cubic">http://rosettacode.org/wiki/Bitmap/B%C3%A9zier_curves/Cubic</a> 1195 */ 1196 near:function(x,y,radius){ 1197 var polls=100; 1198 with(this){ 1199 for(i=0; i<=polls; ++i){ 1200 var t=i/polls; 1201 var fromEnd=Math.pow((1 - t), 3); 1202 var fromStart=Math.pow(t, 3); 1203 var b = 3 * t * Math.pow((1 - t), 2); 1204 var c = 3 * Math.pow(t, 2) * (1 - t); 1205 var newX = fromEnd * startPoint.x + b * controlPoint1.x + c * controlPoint2.x + fromStart * endPoint.x; 1206 var newY = fromEnd * startPoint.y + b * controlPoint1.y + c * controlPoint2.y + fromStart * endPoint.y; 1207 var p=new Point(newX,newY); 1208 if(p.near(x,y,radius)){ 1209 return true; 1210 } 1211 } 1212 } 1213 return false 1214 }, 1215 1216 getPoints:function(){ 1217 return [this.startPoint,this.controlPoint1,this.controlPoint2,this.endPoint]; 1218 }, 1219 1220 getBounds:function(){ 1221 return Util.getBounds(this.getPoints()); 1222 }, 1223 1224 toString:function(){ 1225 return 'quad(' + this.startPoint + ',' + this.controlPoint1 + ',' + this.controlPoint2 + ',' + this.endPoint + ')'; 1226 }, 1227 1228 /**Render the SVG fragment for this primitive 1229 *@see <a href="http://www.w3.org/TR/SVG/paths.html#PathDataCubicBezierCommands">http://www.w3.org/TR/SVG/paths.html#PathDataCubicBezierCommands</a> 1230 **/ 1231 toSVG:function(){ 1232 //<path d="M100,200 C100,100 250,100 250,200" /> 1233 1234 1235 var result = '<path d="M'; 1236 result += this.startPoint.x + ',' + this.endPoint.y; 1237 result += ' C' + this.controlPoint1.x + ',' + this.controlPoint1.y; 1238 result += ' ' + this.controlPoint2.x + ',' + this.controlPoint2.y; 1239 result += ' ' + this.endPoint.x + ',' + this.endPoint.y; 1240 1241 result += '" style="' + this.style.toSVG() + '" />'; 1242 return result; 1243 } 1244 } 1245 1246 1247 /** 1248 * Draws an arc. 1249 * To allow easy transformations of the arc we will simulate the arc by a series of curves 1250 * 1251 * @constructor 1252 * @this {Arc} 1253 * @param {Number} x - the X coordinates of the "imaginary" circle of which the arc is part of 1254 * @param {Number} y - the Y coordinates of the "imaginary" circle of which the arc is part of 1255 * @param {Number} radius - the radius of the arc 1256 * @param {Number} startAngle - the degrees (not radians as in Canvas'specs) of the starting angle for this arc 1257 * @param {Number} endAngle - the degrees (not radians as in Canvas'specs) of the starting angle for this arc 1258 * @param {Number} direction - the direction of drawing. For now it's from startAngle to endAngle (anticlockwise). Not really used 1259 * @param {Number} styleFlag (optional) - 1260 * 1: close path between start of arc and end, 1261 * 2: draw pie slice, line to center point, line to start point 1262 * default: empty/0/anything else: just draw the arc 1263 * TODO: make it a class constant 1264 * @see <a href="http://stackoverflow.com/questions/2688808/drawing-quadratic-bezier-circles-with-a-given-radius-how-to-determine-control-po">http://stackoverflow.com/questions/2688808/drawing-quadratic-bezier-circles-with-a-given-radius-how-to-determine-control-po</a> 1265 **/ 1266 1267 function Arc(x, y, radius, startAngle, endAngle, direction, styleFlag){ 1268 /**End angle. Required for dashedArc*/ 1269 this.endAngle = endAngle; 1270 1271 /**Start angle. required for dashedArc*/ 1272 this.startAngle = startAngle; 1273 1274 /**The center {@link Point} of the circle*/ 1275 this.middle = new Point(x,y); 1276 1277 /**The radius of the circle*/ 1278 this.radius = radius 1279 1280 /**An {Array} of {@link QuadCurve}s used to draw the arc*/ 1281 this.curves = []; 1282 1283 /**Accuracy. It tells the story of how many QuadCurves we will use*/ 1284 var numControlPoints = 8; 1285 1286 /**The start {@link Point}*/ 1287 this.startPoint = null; 1288 1289 /**The end {@link Point}*/ 1290 this.endPoint = null; 1291 1292 /**The start angle, in radians*/ 1293 this.startAngleRadians = 0; 1294 1295 /**The end angle, in radians*/ 1296 this.endAngleRadians = 0; 1297 1298 //code shamelessly stollen from the above site. 1299 var start = Math.PI/180 * startAngle; //convert the angles back to radians 1300 this.startAngleRadians = start; 1301 this.endAngleRadians = Math.PI/180 * endAngle; 1302 var arcLength = (Math.PI/180*(endAngle-startAngle))/ numControlPoints; 1303 for (var i = 0; i < numControlPoints; i++) { 1304 if (i < 1) 1305 { 1306 this.startPoint = new Point(x + radius * Math.cos(arcLength * i),y + radius * Math.sin(arcLength * i)) 1307 } 1308 var startPoint=new Point(x + radius * Math.cos(arcLength * i),y + radius * Math.sin(arcLength * i)) 1309 1310 //control radius formula 1311 //where does it come from, why does it work? 1312 var controlRadius = radius / Math.cos(arcLength * .5); 1313 1314 //the control point is plotted halfway between the arcLength and uses the control radius 1315 var controlPoint=new Point(x + controlRadius * Math.cos(arcLength * (i + 1) - arcLength * .5),y + controlRadius * Math.sin(arcLength * (i + 1) - arcLength * .5)) 1316 if (i == (numControlPoints - 1)) 1317 { 1318 this.endPoint = new Point(x + radius * Math.cos(arcLength * (i + 1)),y + radius * Math.sin(arcLength * (i + 1))); 1319 } 1320 var endPoint=new Point(x + radius * Math.cos(arcLength * (i + 1)),y + radius * Math.sin(arcLength * (i + 1))); 1321 1322 1323 //if we arent starting at 0, rotate it to where it needs to be 1324 1325 //move to origin (O) 1326 startPoint.transform(Matrix.translationMatrix(-x,-y)); 1327 controlPoint.transform(Matrix.translationMatrix(-x,-y)); 1328 endPoint.transform(Matrix.translationMatrix(-x,-y)); 1329 1330 //rotate by angle (start) 1331 startPoint.transform(Matrix.rotationMatrix(start)); 1332 controlPoint.transform(Matrix.rotationMatrix(start)); 1333 endPoint.transform(Matrix.rotationMatrix(start)); 1334 1335 //move it back to where it was 1336 startPoint.transform(Matrix.translationMatrix(x,y)); 1337 controlPoint.transform(Matrix.translationMatrix(x,y)); 1338 endPoint.transform(Matrix.translationMatrix(x,y)); 1339 1340 this.curves.push(new QuadCurve(startPoint,controlPoint,endPoint)); 1341 } 1342 1343 /**The style flag - see contructor's arguments*/ 1344 this.styleFlag = styleFlag; 1345 1346 /**The {@link Style} of the arc*/ 1347 this.style = new Style(); 1348 1349 /**Adding a reference to the end point makes the transform code hugely cleaner*/ 1350 this.direction = direction; 1351 1352 /**Object type used for JSON deserialization*/ 1353 this.oType = 'Arc'; 1354 } 1355 1356 1357 /**Creates a {Arc} out of JSON parsed object 1358 *@param {JSONObject} o - the JSON parsed object 1359 *@return {Arc} a newly constructed Arc 1360 *@author Alex Gheorghiu <alex@scriptoid.com> 1361 **/ 1362 Arc.load = function(o){ 1363 var newArc = new Arc(); 1364 1365 newArc.endAngle = o.endAngle; 1366 newArc.startAngle = o.startAngle; 1367 newArc.middle = Point.load(o.middle); 1368 newArc.radius = o.radius 1369 newArc.curves = QuadCurve.loadArray(o.curves); 1370 1371 /*we need to load these 'computed' values as they are computed only in constructor :( 1372 *TODO: maybe make a new function setUp() that deal with this*/ 1373 newArc.startPoint = Point.load(o.startPoint); 1374 newArc.endPoint = Point.load(o.endPoint); 1375 newArc.startAngleRadians = o.startAngleRadians; 1376 newArc.endAngleRadians = o.endAngleRadians; 1377 1378 newArc.styleFlag = o.styleFlag; 1379 newArc.style = Style.load(o.style); 1380 1381 return newArc; 1382 } 1383 1384 /**Creates a {Arc} out of an Array of JSON parsed object 1385 *@param {Array} v - an {Array} of JSON parsed objects 1386 *@return {Array}of newly constructed Arcs 1387 *@author Alex Gheorghiu <alex@scriptoid.com> 1388 **/ 1389 Arc.loadArray = function(v){ 1390 var newArcs = []; 1391 1392 for(var i=0; i<v.length; i++){ 1393 newArcs.push(Arc.load(v[i])); 1394 } 1395 1396 return newArcs; 1397 } 1398 1399 1400 1401 Arc.prototype = { 1402 transform:function(matrix){ 1403 /* rotations - ok 1404 * translations - ok 1405 * scale - ok 1406 * skew - NOT ok (i do not know how to preserve points, angles...etc- maybe a Cola :) 1407 **/ 1408 with(this){ 1409 //transform the style 1410 if(style!=null){ 1411 style.transform(matrix); 1412 } 1413 1414 //transform the center of the circle 1415 middle.transform(matrix); 1416 1417 //transform each curve 1418 for(var i=0; i<curves.length; i++){ 1419 curves[i].transform(matrix); 1420 } 1421 } 1422 }, 1423 1424 1425 paint:function(context){ 1426 with(this){ 1427 if(style!=null){ 1428 style.setupContext(context); 1429 } 1430 context.lineWidth = style.lineWidth; 1431 //context.arc(x,y,radius,(Math.PI/180)*startAngle,(Math.PI/180)*endAngle,direction); 1432 context.moveTo(curves[0].startPoint.x,curves[0].startPoint.y); 1433 for(var i=0; i<curves.length; i++){ 1434 context.quadraticCurveTo(curves[i].controlPoint.x,curves[i].controlPoint.y,curves[i].endPoint.x,curves[i].endPoint.y) 1435 //curves[i].paint(context); 1436 } 1437 1438 if(styleFlag == 1){ 1439 context.closePath(); 1440 } 1441 else if(styleFlag == 2){ 1442 context.lineTo(this.middle.x,this.middle.y); 1443 context.closePath(); 1444 } 1445 1446 //first fill 1447 if(style.fillStyle!=null && this.style.fillStyle!=""){ 1448 context.fill(); 1449 } 1450 1451 //then stroke 1452 if(style.strokeStyle!=null && this.style.strokeStyle!=""){ 1453 context.stroke(); 1454 } 1455 1456 } 1457 }, 1458 1459 clone:function(){ 1460 with(this){ 1461 var ret = new Arc(middle.x,middle.y,radius,startAngle,endAngle,direction,styleFlag); 1462 for (var i=0; i< this.curves.length; i++){ 1463 ret.curves[i]=this.curves[i].clone(); 1464 } 1465 ret.style=this.style.clone(); 1466 return ret; 1467 } 1468 }, 1469 1470 equals:function(anotherArc){ 1471 if(!anotherArc instanceof Arc){ 1472 return false; 1473 } 1474 1475 //check curves 1476 for(var i = 0 ; i < this.curves.lenght; i++){ 1477 if(!this.curves[i].equals(anotherArc.curves[i])){ 1478 return false; 1479 } 1480 } 1481 1482 return this.startAngle == anotherArc.startAngle 1483 && this.endAngle == anotherArc.endAngle 1484 && this.middle.equals(anotherArc.middle) 1485 && this.radius == anotherArc.radius 1486 && this.numControlPoints == anotherArc.numControlPoints 1487 && this.startPoint.equals(anotherArc.startPoint) 1488 && this.endPoint.equals(anotherArc.endPoint) 1489 && this.startAngleRadians == anotherArc.startAngleRadians 1490 && this.endAngleRadians == anotherArc.endAngleRadians 1491 ; 1492 }, 1493 1494 near:function(thex,they,theradius){ 1495 with(this){ 1496 for(var i=0; i<curves.length; i++){ 1497 if(curves[i].near(thex,they,radius)){ 1498 return true; 1499 } 1500 } 1501 //return (distance && angle) || finishLine || startLine || new Point(x,y).near(thex,they,theradius); 1502 } 1503 1504 return false; 1505 }, 1506 1507 contains: function(thex,they){ 1508 with(this){ 1509 var p = getPoints(); 1510 return Util.isPointInside((new Point(thex,they)), p); 1511 } 1512 1513 }, 1514 1515 /**Get the points of the Arc 1516 *@return {Array} of {Point}s 1517 *@author Zack 1518 **/ 1519 getPoints:function(){ 1520 var p = []; 1521 if(this.styleFlag ==2){ 1522 p.push(this.middle); 1523 } 1524 for(var i=0; i<this.curves.length; i++){ 1525 var c = this.curves[i].getPoints(); 1526 for(var a=0; a<c.length; a++){ 1527 p.push(c[a]); 1528 } 1529 } 1530 return p; 1531 }, 1532 1533 getBounds:function(){ 1534 return Util.getBounds(this.getPoints()); 1535 }, 1536 1537 toString:function(){ 1538 return 'arc(' + new Point(this.x,this.y) + ',' + this.radius + ',' + this.startAngle + ',' + this.endAngle + ',' + this.direction + ')'; 1539 }, 1540 1541 1542 /** 1543 *As we simulate an arc by {QuadCurve}s so we will collect all of them 1544 *and add it to a <path/> element. 1545 *Note: We might use the SVG's arc command but what if the arc got distorted by a transformation? 1546 *@see <a href="http://www.w3.org/TR/SVG/paths.html#PathDataEllipticalArcCommands">http://www.w3.org/TR/SVG/paths.html#PathDataEllipticalArcCommands</a> 1547 *@see <a href="http://tutorials.jenkov.com/svg/path-element.html">http://tutorials.jenkov.com/svg/path-element.html</a> 1548 *@author Alex <alex@scriptoid.com> 1549 **/ 1550 toSVG: function(){ 1551 var r = '<path d="'; 1552 r += ' M' + this.curves[0].startPoint.x + ',' + this.curves[0].startPoint.y 1553 for(var i=0; i<this.curves.length; i++){ 1554 r += ' Q' + this.curves[i].controlPoint.x + ',' + this.curves[i].controlPoint.y 1555 + ' ' + this.curves[i].endPoint.x + ',' + this.curves[i].endPoint.y; 1556 } 1557 r += '" '; 1558 r += this.style.toSVG(); 1559 r += '/>'; 1560 return r; 1561 } 1562 } 1563 1564 1565 1566 /** 1567 * Approximate an ellipse through 4 bezier curves, one for each quadrant 1568 * 1569 * @constructor 1570 * @this {Ellipse} 1571 * @param {Point} centerPoint - the center point of the ellipse 1572 * @param {Number} width - the width of the ellipse 1573 * @param {Number} height - the height of the ellipse 1574 1575 * @see <a href="http://www.codeguru.com/cpp/g-m/gdi/article.php/c131">http://www.codeguru.com/cpp/g-m/gdi/article.php/c131</a> 1576 * @see <a href="http://www.tinaja.com/glib/ellipse4.pdf">http://www.tinaja.com/glib/ellipse4.pdf</a> 1577 * @author Zack Newsham <zack_newsham@yahoo.co.uk> 1578 **/ 1579 function Ellipse(centerPoint, width, height) { 1580 /**"THE" constant*/ 1581 var EToBConst = 0.2761423749154; 1582 1583 /**Width offset*/ 1584 var offsetWidth = width * 2 * EToBConst; 1585 1586 /**Height offset*/ 1587 var offsetHeight = height * 2 * EToBConst; 1588 1589 /**The center {@link Point}*/ 1590 this.centerPoint = centerPoint; 1591 1592 /**Top left {@link CubicCurve}*/ 1593 this.topLeftCurve = new CubicCurve(new Point(centerPoint.x-width,centerPoint.y),new Point(centerPoint.x-width,centerPoint.y-offsetHeight),new Point(centerPoint.x-offsetWidth,centerPoint.y-height),new Point(centerPoint.x,centerPoint.y-height)); 1594 1595 /**Top right {@link CubicCurve}*/ 1596 this.topRightCurve = new CubicCurve(new Point(centerPoint.x,centerPoint.y-height),new Point(centerPoint.x+offsetWidth,centerPoint.y-height),new Point(centerPoint.x+width,centerPoint.y-offsetHeight),new Point(centerPoint.x+width,centerPoint.y)); 1597 1598 /**Bottom right {@link CubicCurve}*/ 1599 this.bottomRightCurve = new CubicCurve(new Point(centerPoint.x+width,centerPoint.y),new Point(centerPoint.x+width,centerPoint.y+offsetHeight),new Point(centerPoint.x+offsetWidth,centerPoint.y+height),new Point(centerPoint.x,centerPoint.y+height)); 1600 1601 /**Bottom left {@link CubicCurve}*/ 1602 this.bottomLeftCurve = new CubicCurve(new Point(centerPoint.x,centerPoint.y+height),new Point(centerPoint.x-offsetWidth,centerPoint.y+height),new Point(centerPoint.x-width,centerPoint.y+offsetHeight),new Point(centerPoint.x-width,centerPoint.y)); 1603 1604 /**The matrix array*/ 1605 this.matrix = null; //TODO: do we really need this? 1606 1607 /**The {@link Style} used*/ 1608 this.style = new Style(); 1609 1610 /**Oject type used for JSON deserialization*/ 1611 this.oType = 'Ellipse'; 1612 } 1613 1614 /**Creates a new {Ellipse} out of JSON parsed object 1615 *@param {JSONObject} o - the JSON parsed object 1616 *@return {Ellipse} a newly constructed Ellipse 1617 *@author Alex Gheorghiu <alex@scriptoid.com> 1618 **/ 1619 Ellipse.load = function(o){ 1620 var newEllipse= new Ellipse(new Point(0,0), 0, 0); //fake ellipse (if we use a null centerPoint we got errors) 1621 1622 newEllipse.offsetWidth = o.offsetWidth; 1623 newEllipse.offsetHeight = o.offsetHeight; 1624 newEllipse.centerPoint = Point.load(o.centerPoint); 1625 newEllipse.topLeftCurve = CubicCurve.load(o.topLeftCurve); 1626 newEllipse.topRightCurve = CubicCurve.load(o.topRightCurve); 1627 newEllipse.bottomRightCurve = CubicCurve.load(o.bottomRightCurve); 1628 newEllipse.bottomLeftCurve = CubicCurve.load(o.bottomLeftCurve); 1629 this.matrix = Matrix.clone(o.matrix); 1630 newEllipse.style = Style.load(o.style); 1631 1632 return newEllipse; 1633 } 1634 1635 1636 Ellipse.prototype = { 1637 transform:function(matrix){ 1638 this.topLeftCurve.transform(matrix); 1639 this.topRightCurve.transform(matrix); 1640 this.bottomLeftCurve.transform(matrix); 1641 this.bottomRightCurve.transform(matrix); 1642 this.centerPoint.transform(matrix); 1643 if(this.style){ 1644 this.style.transform(matrix); 1645 } 1646 }, 1647 1648 paint:function(context){ 1649 with(context){ 1650 with(this){ 1651 if(style!=null){ 1652 style.setupContext(context); 1653 } 1654 beginPath(); 1655 moveTo(topLeftCurve.startPoint.x,topLeftCurve.startPoint.y); 1656 bezierCurveTo(topLeftCurve.controlPoint1.x,topLeftCurve.controlPoint1.y,topLeftCurve.controlPoint2.x,topLeftCurve.controlPoint2.y,topLeftCurve.endPoint.x,topLeftCurve.endPoint.y); 1657 bezierCurveTo(topRightCurve.controlPoint1.x,topRightCurve.controlPoint1.y,topRightCurve.controlPoint2.x,topRightCurve.controlPoint2.y,topRightCurve.endPoint.x,topRightCurve.endPoint.y); 1658 bezierCurveTo(bottomRightCurve.controlPoint1.x,bottomRightCurve.controlPoint1.y,bottomRightCurve.controlPoint2.x,bottomRightCurve.controlPoint2.y,bottomRightCurve.endPoint.x,bottomRightCurve.endPoint.y); 1659 bezierCurveTo(bottomLeftCurve.controlPoint1.x,bottomLeftCurve.controlPoint1.y,bottomLeftCurve.controlPoint2.x,bottomLeftCurve.controlPoint2.y,bottomLeftCurve.endPoint.x,bottomLeftCurve.endPoint.y); 1660 //first fill 1661 if(style.fillStyle!=null && this.style.fillStyle!=""){ 1662 fill(); 1663 } 1664 1665 //then stroke 1666 if(style.strokeStyle!=null && this.style.strokeStyle!=""){ 1667 stroke(); 1668 } 1669 1670 } 1671 } 1672 }, 1673 1674 contains:function(x,y){ 1675 with(this){ 1676 var points = topLeftCurve.getPoints(); 1677 curves = [topRightCurve, bottomRightCurve, bottomLeftCurve]; 1678 for(var i=0; i<curves.length; i++){ 1679 curPoints = curves[i].getPoints(); 1680 1681 for(var a=0; a<curPoints.length; a++){ 1682 points.push(curPoints[a]); 1683 } 1684 } 1685 return Util.isPointInside(new Point(x,y), points); 1686 } 1687 1688 return false; 1689 }, 1690 1691 near:function(x,y,radius){ 1692 return this.topLeftCurve.near(x,y,radius) || this.topRightCurve.near(x,y,radius) || this.bottomLeftCurve.near(x,y,radius) || this.bottomRightCurve.near(x,y,radius); 1693 }, 1694 1695 equals:function(anotherEllipse){ 1696 if(!anotherEllipse instanceof Ellipse){ 1697 return false; 1698 } 1699 1700 return this.offsetWidth == anotherEllipse.offsetWidth 1701 && this.offsetHeight == anotherEllipse.offsetHeight 1702 && this.centerPoint.equals(anotherEllipse.centerPoint) 1703 && this.topLeftCurve.equals(anotherEllipse.topLeftCurve) 1704 && this.topRightCurve.equals(anotherEllipse.topRightCurve) 1705 && this.bottomRightCurve.equals(anotherEllipse.bottomRightCurve) 1706 && this.bottomLeftCurve.equals(anotherEllipse.bottomLeftCurve); 1707 //TODO: add this && this.matrix.equals(anotherEllipse.bottomLeftCurve) 1708 //TODO: add this && this.style.equals(anotherEllipse.bottomLeftCurve) 1709 }, 1710 1711 clone:function(){ 1712 var ret=new Ellipse(this.centerPoint.clone(),10,10); 1713 ret.topLeftCurve=this.topLeftCurve.clone(); 1714 ret.topRightCurve=this.topRightCurve.clone(); 1715 ret.bottomLeftCurve=this.bottomLeftCurve.clone(); 1716 ret.bottomRightCurve=this.bottomRightCurve.clone(); 1717 ret.style=this.style.clone(); 1718 return ret; 1719 }, 1720 1721 toString:function(){ 1722 return 'ellipse('+this.centerPoint+","+this.xRadius+","+this.yRadius+")"; 1723 }, 1724 1725 /** 1726 *@see <a href="http://www.w3.org/TR/SVG/paths.html#PathDataCubicBezierCommands">http://www.w3.org/TR/SVG/paths.html#PathDataCubicBezierCommands</a> 1727 *@author Alex Gheorghiu <scriptoid.com> 1728 **/ 1729 toSVG: function(){ 1730 var result = '<path d="M'; 1731 result += this.topLeftCurve.startPoint.x + ',' + this.topLeftCurve.startPoint.y; 1732 1733 //top left curve 1734 result += ' C' + this.topLeftCurve.controlPoint1.x + ',' + this.topLeftCurve.controlPoint1.y; 1735 result += ' ' + this.topLeftCurve.controlPoint2.x + ',' + this.topLeftCurve.controlPoint2.y; 1736 result += ' ' + this.topLeftCurve.endPoint.x + ',' + this.topLeftCurve.endPoint.y; 1737 1738 //top right curve 1739 result += ' C' + this.topRightCurve.controlPoint1.x + ',' + this.topRightCurve.controlPoint1.y; 1740 result += ' ' + this.topRightCurve.controlPoint2.x + ',' + this.topRightCurve.controlPoint2.y; 1741 result += ' ' + this.topRightCurve.endPoint.x + ',' + this.topRightCurve.endPoint.y; 1742 1743 //bottom right curve 1744 result += ' C' + this.bottomRightCurve.controlPoint1.x + ',' + this.bottomRightCurve.controlPoint1.y; 1745 result += ' ' + this.bottomRightCurve.controlPoint2.x + ',' + this.bottomRightCurve.controlPoint2.y; 1746 result += ' ' + this.bottomRightCurve.endPoint.x + ',' + this.bottomRightCurve.endPoint.y; 1747 1748 //bottom left curve 1749 result += ' C' + this.bottomLeftCurve.controlPoint1.x + ',' + this.bottomLeftCurve.controlPoint1.y; 1750 result += ' ' + this.bottomLeftCurve.controlPoint2.x + ',' + this.bottomLeftCurve.controlPoint2.y; 1751 result += ' ' + this.bottomLeftCurve.endPoint.x + ',' + this.bottomLeftCurve.endPoint.y; 1752 1753 result += '" ' + this.style.toSVG() + ' />'; 1754 return result; 1755 1756 }, 1757 1758 getPoints:function(){ 1759 var points = []; 1760 var curves = [this.topLeftCurve, this.topRightCurve,this.bottomRightCurve,this.bottomLeftCurve]; 1761 1762 for(var i=0; i<curves.length; i++){ 1763 var curPoints = curves[i].getPoints(); 1764 for(var a=0; a<curPoints.length; a++){ 1765 points.push(curPoints[a]); 1766 } 1767 } 1768 return points; 1769 }, 1770 1771 1772 getBounds:function(){ 1773 return Util.getBounds(this.getPoints()); 1774 } 1775 } 1776 1777 1778 1779 /** 1780 * Approximate an ellipse through 4 bezier curves, one for each quadrant 1781 * 1782 * @constructor 1783 * @this {DashedArc} 1784 * @param {Number} x - x coodinated of the center of the "invisible" circle 1785 * @param {Number} y - y coodinated of the center of the "invisible" circle 1786 * @param {Number} radius - the radius of the "invisible" circle 1787 * @param {Number} startAngle - the angle the arc will start from 1788 * @param {Number} endAngle - the angle the arc will end into 1789 * @param {Number} direction - direction of drawing (clockwise or anti-clock wise) 1790 * @param {Style} styleFlag - the style of the arc 1791 * @param {Number} dashGap - how big the gap between the lines will be 1792 1793 * @see <a href="http://www.codeguru.com/cpp/g-m/gdi/article.php/c131">http://www.codeguru.com/cpp/g-m/gdi/article.php/c131</a> 1794 * @see <a href="http://www.tinaja.com/glib/ellipse4.pdf">http://www.tinaja.com/glib/ellipse4.pdf</a> 1795 * @author Zack Newsham <zack_newsham@yahoo.co.uk> 1796 **/ 1797 function DashedArc(x, y, radius, startAngle, endAngle, direction, styleFlag, dashGap){ 1798 /**The "under the hood" {@link Arc}*/ 1799 this.arc = new Arc(x, y, radius, startAngle, endAngle, direction, styleFlag); 1800 1801 /*The {@link Style} used**/ 1802 this.style = this.arc.style; 1803 1804 /**The gap between dashes*/ 1805 this.dashWidth = dashGap; 1806 1807 /**An {Array} or {@link Arc}s*/ 1808 this.lines = []; //an {Array} of {Arc}s 1809 1810 //init the parts 1811 for(var i=0; i<100; i += this.dashWidth){ 1812 var a = new Arc(x, y, radius+this.style.lineWidth/2, (endAngle-startAngle)/100*i, (endAngle-startAngle)/100*(i+1), false); 1813 a.style.strokeStyle = this.style.strokeStyle; 1814 this.lines.push(a); 1815 } 1816 1817 /**Object type used for JSON deserialization*/ 1818 this.oType = 'DashedArc'; 1819 } 1820 1821 /**Creates a new {Ellipse} out of JSON parsed object 1822 *@param {JSONObject} o - the JSON parsed object 1823 *@return {DashedArc} a newly constructed DashedArc 1824 *@author Alex Gheorghiu <alex@scriptoid.com> 1825 **/ 1826 DashedArc.load = function(o){ 1827 var newDashedArc = new DashedArc(100,100,30,0,360,false,0,6); //fake dashed (if we do not use it we got errors - endless loop) 1828 newDashedArc.style.fillStyle="#ffffff" 1829 1830 newDashedArc.arc = Arc.load(o.arc); 1831 newDashedArc.style = newDashedArc.arc.style; //strange but... 1832 newDashedArc.dashWidth = o.dashWidth; 1833 newDashedArc.lines = Arc.loadArray(o.lines); 1834 1835 1836 return newDashedArc; 1837 } 1838 1839 1840 DashedArc.prototype = { 1841 transform:function(matrix){ 1842 this.arc.transform(matrix); 1843 for (var i=0; i<this.lines.length; i++){ 1844 this.lines[i].transform(matrix); 1845 } 1846 }, 1847 1848 getBounds:function(){ 1849 return this.arc.getBounds(); 1850 }, 1851 1852 getPoints:function(){ 1853 return this.arc.getPoints(); 1854 }, 1855 1856 contains:function(x,y){ 1857 return this.arc.contains(x,y); 1858 }, 1859 1860 near:function(x,y,radius){ 1861 return this.arc.near(x,y,radius); 1862 }, 1863 1864 1865 toString:function(){ 1866 return this.arc.toString(); 1867 }, 1868 1869 1870 toSVG: function(){ 1871 throw 'Arc:toSVG() - not implemented'; 1872 }, 1873 1874 /***/ 1875 equals:function(anotherDashedArc){ 1876 if(!anotherDashedArc instanceof DashedArc){ 1877 return false; 1878 } 1879 1880 1881 if(this.lines.length != anotherDashedArc.lines.length){ 1882 return false; 1883 } 1884 else{ 1885 for(var i in this.lines){ 1886 if(!this.lines[i].equals(anotherDashedArc.lines[i])){ 1887 return false; 1888 } 1889 } 1890 } 1891 1892 return this.arc.equals(anotherDashedArc.arc) 1893 && this.style.equals(anotherDashedArc.style) 1894 && this.dashWidth == anotherDashedArc.dashWidth; 1895 }, 1896 1897 clone:function(){ 1898 return this.arc.clone(); 1899 }, 1900 1901 paint:function(context){ 1902 this.style.setupContext(context); 1903 context.lineCap="round"//this.style.lineCap; 1904 for(var i=0; i<this.lines.length; i++){ 1905 context.beginPath(); 1906 this.lines[i].paint(context); 1907 context.stroke(); 1908 } 1909 this.style.strokeStyle=null; 1910 this.arc.paint(context) 1911 } 1912 1913 } 1914 1915 1916 /** 1917 * A path contains a number of elements (like shape) but they are drawn as one, i.e. 1918 * 1919 * @example 1920 * begin path 1921 * loop to draw shapes 1922 * draw shape 1923 * close loop 1924 * close path 1925 * 1926 * 1927 * @constructor 1928 * @this {Path} 1929 **/ 1930 function Path() { 1931 /**An {Array} that will store all the basic primitives: {@link Point}s, {@link Line}s, {@link CubicCurve}s, etc that make the path*/ 1932 this.primitives = []; 1933 1934 /**The {@link Style} used for drawing*/ 1935 this.style = new Style(); 1936 1937 /**Object type used for JSON deserialization*/ 1938 this.oType = 'Path'; 1939 } 1940 1941 /**Creates a new {Path} out of JSON parsed object 1942 *@param {JSONObject} o - the JSON parsed object 1943 *@return {Path} a newly constructed {Path} 1944 *@author Alex Gheorghiu <alex@scriptoid.com> 1945 **/ 1946 Path.load = function(o){ 1947 var newPath = new Path(); //fake path 1948 1949 newPath.style = Style.load(o.style); 1950 1951 for(var i=0; i< o.primitives.length; i++){ 1952 /**We can not use instanceof Point construction as 1953 *the JSON objects are typeless... so JSONObject are simply objects */ 1954 if(o.primitives[i].oType == 'Point'){ 1955 newPath.primitives.push(Point.load(o.primitives[i])) 1956 } 1957 else if(o.primitives[i].oType == 'Line'){ 1958 newPath.primitives.push(Line.load(o.primitives[i])) 1959 } 1960 else if(o.primitives[i].oType == 'Polyline'){ 1961 newPath.primitives.push(Polyline.load(o.primitives[i])) 1962 } 1963 else if(o.primitives[i].oType == 'Polygon'){ 1964 newPath.primitives.push(Polygon.load(o.primitives[i])) 1965 } 1966 else if(o.primitives[i].oType == 'QuadCurve'){ 1967 newPath.primitives.push(QuadCurve.load(o.primitives[i])) 1968 } 1969 else if(o.primitives[i].oType == 'CubicCurve'){ 1970 newPath.primitives.push(CubicCurve.load(o.primitives[i])) 1971 } 1972 else if(o.primitives[i].oType == 'Arc'){ 1973 newPath.primitives.push(Arc.load(o.primitives[i])) 1974 } 1975 else if(o.primitives[i].oType == 'Ellipse'){ 1976 newPath.primitives.push(Ellipse.load(o.primitives[i])) 1977 } 1978 else if(o.primitives[i].oType == 'DashedArc'){ 1979 newPath.primitives.push(DashedArc.load(o.primitives[i])) 1980 } 1981 else if(o.primitives[i].oType == 'Path'){ 1982 newPath.primitives.push(Path.load(o.primitives[i])) 1983 } 1984 1985 } 1986 1987 return newPath; 1988 } 1989 1990 1991 Path.prototype = { 1992 transform:function(matrix){ 1993 for(var i = 0; i<this.primitives.length; i++ ){ 1994 this.primitives[i].transform(matrix); 1995 } 1996 }, 1997 1998 addPrimitive:function(primitive){ 1999 this.primitives.push(primitive); 2000 }, 2001 2002 contains:function(x,y){ 2003 with(this){ 2004 var points=[]; 2005 for(var i=0; i<primitives.length; i++){ 2006 if(primitives[i].contains(x,y)){ 2007 return true; 2008 } 2009 var curPoints=primitives[i].getPoints(); 2010 for(var a=0; a<curPoints.length; a++){ 2011 points.push(curPoints[a]); 2012 } 2013 } 2014 } 2015 return Util.isPointInside(new Point(x,y),points); 2016 }, 2017 2018 near: function(x,y,radius){ 2019 with(this){ 2020 var points=[]; 2021 for(var i=0; i<primitives.length; i++){ 2022 if(primitives[i].near(x,y,radius)){ 2023 return true; 2024 } 2025 } 2026 } 2027 return false; 2028 }, 2029 2030 getPoints:function(){ 2031 var points = []; 2032 for (var i=0; i<this.primitives.length; i++){ 2033 points = points.concat(this.primitives[i].getPoints()); 2034 } 2035 return points; 2036 }, 2037 getBounds:function(){ 2038 var points = []; 2039 for (var i in this.primitives) { 2040 var bounds = this.primitives[i].getBounds(); 2041 points.push(new Point(bounds[0], bounds[1])); 2042 points.push(new Point(bounds[2], bounds[3])); 2043 } 2044 return Util.getBounds(points); 2045 }, 2046 2047 clone:function(){ 2048 var ret = new Path(); 2049 for (var i=0; i<this.primitives.length; i++){ 2050 ret.addPrimitive(this.primitives[i].clone()); 2051 if(this.primitives[i].parentFigure){ 2052 ret.primitives[i].parentFigure=ret; 2053 } 2054 } 2055 ret.style=this.style 2056 return ret; 2057 }, 2058 2059 /**@author: Alex Gheorghiu <alex@scriptoid.com>*/ 2060 equals : function(anotherPath){ 2061 if(!anotherPath instanceof Path){ 2062 return false; 2063 } 2064 2065 for(var i=0; i<this.primitives.length; i++){ 2066 if(!this.primitives[i].equals(anotherPath.primitives[i])){ 2067 return false; 2068 } 2069 } 2070 return true; 2071 }, 2072 2073 paint:function(context){ 2074 context.save(); 2075 2076 if(this.style != null){ 2077 this.style.setupContext(context); 2078 } 2079 2080 //PAINT FILL 2081 //this loop is the one for the fill, we keep the reference. 2082 //if you try to put these two together, you will get a line that is the same colour, 2083 //even if you define different colours for each part of the line (i.e. fig 19) 2084 //not allowing multiple colours in a single path will clean this code up hugely. 2085 // 2086 if(this.style.fillStyle != null && this.style.fillStyle != "" ){ 2087 context.beginPath(); 2088 context.moveTo(this.primitives[0].startPoint.x,this.primitives[0].startPoint.y); 2089 for(var i = 0; i<this.primitives.length; i++ ){ 2090 var primitive = this.primitives[i]; 2091 if(primitive instanceof Line){ 2092 context.lineTo(primitive.endPoint.x,primitive.endPoint.y); 2093 } 2094 else if(primitive instanceof Polyline){ 2095 for(var a=0; a<primitive.points.length; a++){ 2096 context.lineTo(primitive.points[a].x,primitive.points[a].y); 2097 } 2098 } 2099 else if(primitive instanceof QuadCurve){ 2100 context.quadraticCurveTo(primitive.controlPoint.x, primitive.controlPoint.y, primitive.endPoint.x, primitive.endPoint.y); 2101 } 2102 else if(primitive instanceof CubicCurve){ 2103 context.bezierCurveTo(primitive.controlPoint1.x, primitive.controlPoint1.y, primitive.controlPoint2.x, primitive.controlPoint2.y, primitive.endPoint.x, primitive.endPoint.y) 2104 } 2105 } 2106 context.fill(); 2107 } 2108 2109 //PAINT STROKE 2110 //This loop draws the lines of each individual shape. Each part might have a different strokeStyle ! 2111 if(this.style.strokeStyle != null && this.style.strokeStyle != "" ){ 2112 for(var i = 0; i<this.primitives.length; i++ ){ 2113 var primitive = this.primitives[i]; 2114 2115 context.save(); 2116 context.beginPath(); 2117 2118 //TODO: what if a primitive does not have a start point? 2119 context.moveTo(primitive.startPoint.x,primitive.startPoint.y); 2120 2121 if(primitive instanceof Line){ 2122 context.lineTo(primitive.endPoint.x,primitive.endPoint.y); 2123 //Log.info("line"); 2124 } 2125 else if(primitive instanceof Polyline){ 2126 for(var a=0; a<primitive.points.length; a++){ 2127 context.lineTo(primitive.points[a].x,primitive.points[a].y); 2128 //Log.info("polyline"); 2129 } 2130 } 2131 else if(primitive instanceof QuadCurve){ 2132 context.quadraticCurveTo(primitive.controlPoint.x, primitive.controlPoint.y, primitive.endPoint.x, primitive.endPoint.y); 2133 //Log.info("quadcurve"); 2134 } 2135 else if(primitive instanceof CubicCurve){ 2136 context.bezierCurveTo(primitive.controlPoint1.x, primitive.controlPoint1.y, primitive.controlPoint2.x, primitive.controlPoint2.y, primitive.endPoint.x, primitive.endPoint.y) 2137 //Log.info("cubiccurve"); 2138 } 2139 else if(primitive instanceof Arc){ 2140 context.arc(primitive.startPoint.x, primitive.startPoint.y, primitive.radius, primitive.startAngleRadians, primitive.endAngleRadians, true) 2141 //Log.info("arc" + primitive.startPoint.x + " " + primitive.startPoint.y); 2142 } 2143 else 2144 { 2145 //Log.info("unknown primitive"); 2146 } 2147 2148 //save primitive's old style 2149 var oldStyle = primitive.style.clone(); 2150 2151 //update primitive's style 2152 if(primitive.style == null){ 2153 primitive.style = this.style; 2154 } 2155 else{ 2156 primitive.style.merge(this.style); 2157 } 2158 2159 //use primitive's style 2160 primitive.style.setupContext(context); 2161 2162 //stroke it 2163 context.stroke(); 2164 2165 //change primitive' style back to original one 2166 primitive.style = oldStyle; 2167 2168 context.restore(); 2169 } 2170 } 2171 2172 context.restore(); 2173 2174 }, 2175 2176 2177 /** 2178 *Export this path to SVG 2179 *@see <a href="http://tutorials.jenkov.com/svg/path-element.html">http://tutorials.jenkov.com/svg/path-element.html</a> 2180 *@example 2181 * <path d="M50,50 2182 * A30,30 0 0,1 35,20 2183 * L100,100 2184 * M110,110 2185 * L100,0" 2186 * style="stroke:#660000; fill:none;"/> 2187 */ 2188 toSVG: function(){ 2189 2190 var result = '<path d="'; 2191 var previousPrimitive = null; 2192 for(var i=0; i<this.primitives.length; i++){ 2193 2194 var primitive = this.primitives[i]; 2195 2196 if(primitive instanceof Point){ 2197 //TODO: implement me. Should we allow points? 2198 /**Here is a big problem as if we implement the point as a line with 1px width 2199 *upon scaling it will became obvious it's a line. 2200 *Alternative solutions: 2201 *1 - draw it as a SVG arc 2202 *2 - draw it as an independent SVG circle*/ 2203 throw 'Path:toSVG()->Point - not implemented'; 2204 } 2205 if(primitive instanceof Line){ 2206 /*If you want the Path to be a continuous contour we check if 2207 *the M is unecessary - maybe we are already comming from that spot*/ 2208 if(previousPrimitive == null || previousPrimitive.endPoint.x != primitive.startPoint.x || previousPrimitive.endPoint.y != primitive.startPoint.y){ 2209 result += ' M' + primitive.startPoint.x + ',' + primitive.startPoint.y; 2210 } 2211 result += ' L' + primitive.endPoint.x + ',' + primitive.endPoint.y; 2212 } 2213 else if(primitive instanceof Polyline){ 2214 for(var a=0; a<primitive.points.length; a++){ 2215 result += ' L' + primitive.points[a].x + ',' + primitive.points[a].y; 2216 //Log.info("polyline"); 2217 } 2218 } 2219 else if(primitive instanceof QuadCurve){ 2220 result += ' Q' + primitive.controlPoint.x + ',' + primitive.controlPoint.y + ',' + primitive.endPoint.x + ',' + primitive.endPoint.y ; 2221 } 2222 else if(primitive instanceof CubicCurve){ 2223 result += ' C' + primitive.controlPoint1.x + ',' + primitive.controlPoint1.y + ',' + primitive.controlPoint2.x + ',' + primitive.controlPoint2.y + ',' + primitive.endPoint.x + ',' + primitive.endPoint.y; 2224 } 2225 else if(primitive instanceof Arc){ 2226 //TODO: implement me 2227 //<path d="M100,100 A25 25 0 0 0 150 100" stroke="lightgreen" stroke-width="4" fill="none" /> 2228 throw 'Path:toSVG()->Arc - not implemented'; 2229 } 2230 else if(primitive instanceof Polyline){ 2231 //TODO: implement me 2232 throw 'Path:toSVGPolylineArc - not implemented'; 2233 } 2234 else{ 2235 throw 'Path:toSVG()->unknown primitive rendering not implemented'; 2236 } 2237 2238 previousPrimitive = primitive; 2239 }//end for 2240 result += '" '; //end of primitive shapes 2241 // result += ' fill="none" stroke="#0F0F00" /> '; //end of path 2242 result += this.style.toSVG(); //end of path 2243 result += ' />'; //end of path 2244 2245 return result; 2246 } 2247 2248 } 2249 2250 2251 2252 /**A figure is simply a collection of basic primitives: Points, Lines, Arcs, Curves and Paths 2253 * A figure should not care about grouping primitives into Paths, each shape should draw itself. 2254 * The Figure only delegate the painting to the composing shape. 2255 * 2256 * @constructor 2257 * @this {DashedArc} 2258 * @param {String} name - the name of the figure 2259 * 2260 **/ 2261 function Figure(name) { 2262 /**Each Figure will have an unique Id on canvas*/ 2263 this.id = stack.generateId(); 2264 2265 /**Figure's name*/ 2266 this.name = name; 2267 2268 /**An {Array} of primitives that make the figure*/ 2269 this.primitives = []; 2270 2271 /**the Group'id to which this figure belongs to*/ 2272 this.groupId = -1; 2273 2274 /*Keeps track of all the handles for a figure*/ 2275 //this.handles = []; 2276 //this.handleSelectedIndex=-1; 2277 //this.builder = new Builder(this.id); 2278 2279 /**An {Array} of {@link BuilderProperty} objects*/ 2280 this.properties = []; 2281 2282 /**The {@link Style} use to draw tis figure*/ 2283 this.style = new Style(); 2284 2285 /**We keep the figure position by having different points 2286 *[central point of the figure, the middle of upper edge] 2287 * An {Array} or {@link Point}s 2288 **/ 2289 this.rotationCoords = []; 2290 2291 /**Object type used for JSON deserialization*/ 2292 this.oType = 'Figure'; 2293 } 2294 2295 /**Creates a new {Figure} out of JSON parsed object 2296 *@param {JSONObject} o - the JSON parsed object 2297 *@return {Figure} a newly constructed Figure 2298 *@author Alex Gheorghiu <alex@scriptoid.com> 2299 **/ 2300 Figure.load = function(o){ 2301 var newFigure = new Figure(); //fake dashed (if we do not use it we got errors - endless loop) 2302 2303 newFigure.id = o.id; 2304 newFigure.name = o.name; 2305 for(var i=0; i< o.primitives.length; i++){ 2306 /**We can not use instanceof Point construction as 2307 *the JSON objects are typeless... so JSONObject are simply objects */ 2308 if(o.primitives[i].oType == 'Point'){ 2309 newFigure.primitives.push(Point.load(o.primitives[i])) 2310 } 2311 else if(o.primitives[i].oType == 'Line'){ 2312 newFigure.primitives.push(Line.load(o.primitives[i])) 2313 } 2314 else if(o.primitives[i].oType == 'Polyline'){ 2315 newFigure.primitives.push(Polyline.load(o.primitives[i])) 2316 } 2317 else if(o.primitives[i].oType == 'Polygon'){ 2318 newFigure.primitives.push(Polygon.load(o.primitives[i])) 2319 } 2320 else if(o.primitives[i].oType == 'QuadCurve'){ 2321 newFigure.primitives.push(QuadCurve.load(o.primitives[i])) 2322 } 2323 else if(o.primitives[i].oType == 'CubicCurve'){ 2324 newFigure.primitives.push(CubicCurve.load(o.primitives[i])) 2325 } 2326 else if(o.primitives[i].oType == 'Arc'){ 2327 newFigure.primitives.push(Arc.load(o.primitives[i])) 2328 } 2329 else if(o.primitives[i].oType == 'Ellipse'){ 2330 newFigure.primitives.push(Ellipse.load(o.primitives[i])) 2331 } 2332 else if(o.primitives[i].oType == 'DashedArc'){ 2333 newFigure.primitives.push(DashedArc.load(o.primitives[i])) 2334 } 2335 else if(o.primitives[i].oType == 'Text'){ 2336 newFigure.primitives.push(Text.load(o.primitives[i])) 2337 } 2338 else if(o.primitives[i].oType == 'Path'){ 2339 newFigure.primitives.push(Path.load(o.primitives[i])) 2340 } 2341 else if(o.primitives[i].oType == 'Figure'){ 2342 newFigure.primitives.push(Figure.load(o.primitives[i])) //kinda recursevly 2343 } 2344 else if(o.primitives[i].oType == 'RasterImage'){ 2345 newFigure.primitives.push(RasterImage.load(o.primitives[i])) //kinda recursevly 2346 } 2347 }//end for 2348 2349 newFigure.groupId = o.groupId; 2350 newFigure.properties = BuilderProperty.loadArray(o.properties); 2351 newFigure.style = Style.load(o.style); 2352 newFigure.rotationCoords = Point.loadArray(o.rotationCoords); 2353 2354 2355 return newFigure ; 2356 } 2357 2358 /**Creates a new {Array} of {Figure}s out of JSON parsed object 2359 *@param {JSONObject} v - the JSON parsed object 2360 *@return {Array} of newly constructed {Figure}s 2361 *@author Alex Gheorghiu <alex@scriptoid.com> 2362 **/ 2363 Figure.loadArray = function(v){ 2364 var newFigures = []; 2365 2366 for(var i=0; i<v.length; i++){ 2367 newFigures.push(Figure.load(v[i])); 2368 } 2369 2370 return newFigures; 2371 } 2372 2373 Figure.prototype = { 2374 /* used by the edit panel 2375 * @return {Text} the text item 2376 */ 2377 getText:function(){ 2378 for(var i=0; i<this.primitives.length; i++){ 2379 if(this.primitives[i] instanceof Text){ 2380 return this.primitives[i]; 2381 } 2382 } 2383 2384 return ''; 2385 }, 2386 2387 /*set the text from edit panel 2388 *@param{Text} text - text object 2389 */ 2390 setText:function(text){ 2391 for(var i=0; i<this.primitives.length; i++){ 2392 if(this.primitives[i] instanceof Text){ 2393 this.primitives[i]=text; 2394 } 2395 } 2396 }, 2397 2398 //@param{bool} transformConnector - should we transform the connector? Used when we transform a figure, 2399 //without redrawing it. 2400 transform:function(matrix, transformConnector){ 2401 if(transformConnector == "undefined" || transformConnector == undefined){ 2402 transformConnector = true; 2403 } 2404 //transform all composing primitives 2405 for(var i = 0; i<this.primitives.length; i++ ){ 2406 this.primitives[i].transform(matrix); 2407 } 2408 2409 //transform the style 2410 this.style.transform(matrix); 2411 2412 //cascade transform to the connection point 2413 //Log.info('Figure: transform()'); 2414 if(transformConnector){ 2415 CONNECTOR_MANAGER.connectionPointTransform(this.id,matrix); 2416 } 2417 2418 //some figures dont have rotation coords, i.e. those that arent "real" figures, such as the highlight rectangle 2419 if(this.rotationCoords.length!=0){ 2420 this.rotationCoords[0].transform(matrix); 2421 this.rotationCoords[1].transform(matrix); 2422 } 2423 }, 2424 2425 getPoints:function(){ 2426 var points = []; 2427 for (var i=0; i<this.primitives.length; i++){ 2428 points = points.concat(this.primitives[i].getPoints()); //add all primitive's points in a single pass 2429 } 2430 return points; 2431 }, 2432 2433 addPrimitive:function(primitive){ 2434 this.primitives.push(primitive); 2435 }, 2436 2437 //no more points to add, so create the handles and selectRect 2438 finalise:function(){ 2439 var bounds = this.getBounds(); 2440 2441 //central point of the figure 2442 this.rotationCoords[0] = new Point(bounds[0] + (bounds[2] - bounds[0]) / 2, 2443 bounds[1] + (bounds[3] - bounds[1]) / 2); 2444 2445 //the middle of upper edge 2446 this.rotationCoords[1] = new Point(this.rotationCoords[0].x, bounds[1]); 2447 }, 2448 2449 /**TODO: this clone is not gonna be equal with the original 2450 *as it will have a different id 2451 **/ 2452 clone:function(){ 2453 var ret = new Figure(); 2454 for (var i=0; i<this.primitives.length; i++){ 2455 ret.addPrimitive(this.primitives[i].clone()); 2456 } 2457 ret.style = this.style 2458 ret.rotationCoords[0]=this.rotationCoords[0].clone(); 2459 ret.rotationCoords[1]=this.rotationCoords[1].clone(); 2460 return ret; 2461 }, 2462 2463 2464 contains:function(x,y){ 2465 var points=[]; 2466 for(var i=0; i<this.primitives.length; i++){ 2467 if(this.primitives[i].contains(x,y)){ 2468 return true; 2469 } 2470 points = points.concat(this.primitives[i].getPoints()); 2471 } 2472 return Util.isPointInside(new Point(x,y),points); 2473 }, 2474 2475 getBounds: function(){ 2476 var points = []; 2477 for (var i = 0; i < this.primitives.length; i++) { 2478 var bounds = this.primitives[i].getBounds(); 2479 points.push(new Point(bounds[0], bounds[1])); 2480 points.push(new Point(bounds[2], bounds[3])); 2481 } 2482 return Util.getBounds(points); 2483 }, 2484 2485 2486 paint:function(context){ 2487 if(this.style){ 2488 this.style.setupContext(context); 2489 } 2490 for(var i = 0; i<this.primitives.length; i++ ){ 2491 var primitive = this.primitives[i]; 2492 context.save(); 2493 2494 var oldStyle = null; 2495 if(primitive.style){ //save primitive's style 2496 oldStyle = primitive.style.clone(); 2497 } 2498 2499 if(primitive.style == null){ //if primitive does not have a style use Figure's one 2500 primitive.style = this.style; 2501 } 2502 else{ //if primitive has a style merge it 2503 primitive.style.merge(this.style); 2504 } 2505 2506 context.beginPath(); 2507 primitive.paint(context); 2508 primitive.style = oldStyle; 2509 if(this.style.image != null){ //TODO: should a figure has a Style cann't just delegate all to primitives? 2510 //clip required for background images, there were two methods, this was the second I tried 2511 //neither work in IE 2512 context.clip(); 2513 context.save(); 2514 if(this.rotationCoords.length != 0){ 2515 var angle=Util.getAngle(this.rotationCoords[0], this.rotationCoords[1]); 2516 if(IE && angle==0){ 2517 angle=0.00000001;//stupid excanves, without this it puts all images down and right of the correct location 2518 //and by an amount relative to the distane from the top left corner 2519 } 2520 2521 //if we perform a rotation on the actual rotationCoords[0] (centerPoint), when we try to translate it back, 2522 //rotationCoords[0] will = 0,0, so we create a clone that does not get changed 2523 var rotPoint = this.rotationCoords[0].clone(); 2524 2525 //move to origin, make a rotation, move back in place 2526 this.transform(Matrix.translationMatrix(-rotPoint.x, -rotPoint.y)) 2527 this.transform(Matrix.rotationMatrix(-angle)); 2528 this.transform(Matrix.translationMatrix(rotPoint.x, rotPoint.y)) 2529 2530 //TODO: these are not used...so why the whole acrobatics ? 2531 //this was the second method that is also not supported by IE, get the image, place it in 2532 //the correct place, then shrink it, so its still an 'image mask' but it is only a small image 2533 //context.scale below is also part of this 2534 //var shrinkBounds = this.getBounds(); 2535 2536 //move back to origin, 'undo' the rotation, move back in place 2537 this.transform(Matrix.translationMatrix(-rotPoint.x, -rotPoint.y)) 2538 this.transform(Matrix.rotationMatrix(angle)); 2539 this.transform(Matrix.translationMatrix(rotPoint.x, rotPoint.y)) 2540 2541 //rotate current canvas to prepare it to draw the image (you can not roate the image...:D) 2542 context.translate(rotPoint.x,rotPoint.y); 2543 context.rotate(angle); 2544 //context.scale(0.01,0.01)//1/getCanvas().width*shrinkBounds[0]+(shrinkBounds[2]-shrinkBounds[0])/2,1/getCanvas().width*shrinkBounds[1]+(shrinkBounds[3]-shrinkBounds[1])/2) 2545 context.translate(-rotPoint.x,-rotPoint.y); 2546 } 2547 //draw image 2548 /*context.fill(); 2549 context.beginPath(); 2550 context.globalCompositeOperation = "source-atop" 2551 clip works best,but this works too, neither will work in IE*/ 2552 //context.fill(); 2553 context.drawImage(this.style.image,this.rotationCoords[0].x-this.style.image.width/2,this.rotationCoords[0].y-this.style.image.height/2,this.style.image.width,this.style.image.height) 2554 2555 context.restore(); 2556 } 2557 else if (this.style.image!=null){ 2558 context.fill(); 2559 } 2560 2561 context.restore(); 2562 } 2563 }, 2564 2565 equals:function(anotherFigure){ 2566 if(!anotherFigure instanceof Figure){ 2567 Log.info("Figure:equals() 0"); 2568 return false; 2569 } 2570 2571 2572 if(this.primitives.length == anotherFigure.primitives.length){ 2573 for(var i=0; i<this.primitives.length; i++){ 2574 if(!this.primitives[i].equals(anotherFigure.primitives[i])){ 2575 Log.info("Figure:equals() 1"); 2576 return false; 2577 } 2578 } 2579 } 2580 else{ 2581 Log.info("Figure:equals() 2"); 2582 return false; 2583 } 2584 //test group 2585 if(this.groupId != anotherFigure.groupId){ 2586 return false; 2587 } 2588 2589 //test rotation coords 2590 if(this.rotationCoords.length == anotherFigure.rotationCoords.length){ 2591 for(var i in this.rotationCoords){ 2592 if(!this.rotationCoords[i].equals(anotherFigure.rotationCoords[i])){ 2593 return false; 2594 } 2595 } 2596 } 2597 else{ 2598 return false; 2599 } 2600 2601 //test style 2602 if(!this.style.equals(anotherFigure.style)){ 2603 return false; 2604 } 2605 2606 return true; 2607 }, 2608 2609 near:function(x,y,radius){ 2610 for(var i=0; i<this.primitives.length; i++){ 2611 if(this.primitives[i].near(x,y,radius)){ 2612 return true; 2613 } 2614 } 2615 return false; 2616 }, 2617 toString:function(){ 2618 var result = this.name + ' [id: ' + this.id + '] ('; 2619 for(var i = 0; i<this.primitives.length; i++ ){ 2620 result += this.primitives[i].toString(); 2621 } 2622 result += ')'; 2623 return result; 2624 }, 2625 2626 2627 toSVG: function(){ 2628 var tempSVG = ''; 2629 for(var i = 0; i<this.primitives.length; i++ ){ 2630 var primitive = this.primitives[i]; 2631 2632 var oldStyle = null; 2633 if(primitive.style){ //save primitive's style 2634 oldStyle = primitive.style.clone(); 2635 } 2636 2637 if(primitive.style == null){ //if primitive does not have a style use Figure's one 2638 primitive.style = this.style; 2639 } 2640 else{ //if primitive has a style merge it 2641 primitive.style.merge(this.style); 2642 } 2643 2644 tempSVG += this.primitives[i].toSVG(); 2645 2646 //restore primitives style 2647 primitive.style = oldStyle; 2648 } 2649 2650 return tempSVG; 2651 } 2652 } 2653 2654 2655 2656 2657 /** 2658 * A predefined matrix of a 90 degree clockwise rotation 2659 * 2660 *@see <a href="http://en.wikipedia.org/wiki/Rotation_matrix">http://en.wikipedia.org/wiki/Rotation_matrix</a> 2661 */ 2662 R90 = [ 2663 [Math.cos(0.0872664626),-Math.sin(0.0872664626), 0], 2664 [Math.sin(0.0872664626), Math.cos(0.0872664626), 0], 2665 [0, 0, 1] 2666 ]; 2667 2668 /** 2669 * A predefined matrix of a 90 degree anti-clockwise rotation 2670 * 2671 *@see <a href="http://en.wikipedia.org/wiki/Rotation_matrix">http://en.wikipedia.org/wiki/Rotation_matrix</a> 2672 */ 2673 R90A = [ 2674 [Math.cos(0.0872664626), Math.sin(0.0872664626), 0], 2675 [-Math.sin(0.0872664626), Math.cos(0.0872664626), 0], 2676 [0, 0, 1] 2677 ]; 2678 2679 /** 2680 * The identity matrix 2681 */ 2682 2683 IDENTITY=[[1,0,1],[0,1,0],[0,0,1]]; 2684 2685 2686 if(typeof(document) == 'undefined'){ //test only from console 2687 print("\n--==Point==--\n"); 2688 p = new Point(10, 10); 2689 print(p); 2690 print("\n"); 2691 p.transform(R90); 2692 print(p) 2693 2694 print("\n--==Line==--\n"); 2695 l = new Line(new Point(10, 23), new Point(34, 50)); 2696 print(l); 2697 print("\n"); 2698 2699 2700 print("\n--==Polyline==--\n"); 2701 polyline = new Polyline(); 2702 for(var i=0;i<5; i++){ 2703 polyline.addPoint(new Point(i, i*i)); 2704 } 2705 print(polyline); 2706 print("\n"); 2707 2708 2709 2710 2711 print("\n--==Quad curve==--\n"); 2712 q = new QuadCurve(new Point(75,25), new Point(25,25), new Point(25,62)) 2713 print(q) 2714 2715 print("\n"); 2716 q.transform(R90); 2717 print(q) 2718 2719 print("\n--==Cubic curve==--\n"); 2720 q = new CubicCurve(new Point(75,40), new Point(75,37), new Point(70,25), new Point(50,25)) 2721 print(q) 2722 2723 print("\n"); 2724 q.transform(R90); 2725 print(q) 2726 2727 print("\n--==Figure==--\n"); 2728 f = new Figure(); 2729 f.addPrimitive(p); 2730 f.addPrimitive(q); 2731 print(f); 2732 2733 f.transform(R90); 2734 print("\n"); 2735 print(f); 2736 print("\n"); 2737 //f.draw(); 2738 } 2739