1 /* 2 Copyright 2010 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 JXG.CanvasRenderer = function(container) { 27 var i; 28 this.constructor(); 29 30 /* 31 Enable easy test which renderer is used. 32 */ 33 this.type = 'canvas'; 34 35 this.canvasRoot = null; 36 this.suspendHandle = null; 37 this.canvasId = JXG.Util.genUUID(); 38 39 this.canvasNamespace = null; 40 41 this.container = container; 42 this.container.style.MozUserSelect = 'none'; 43 44 this.container.style.overflow = 'hidden'; 45 if (this.container.style.position=='') { 46 this.container.style.position = 'relative'; 47 } 48 49 //this.canvasRoot = this.container.ownerDocument.createElementNS(this.canvasNamespace, "canvas"); 50 this.container.innerHTML = '<canvas id="'+this.canvasId+'" width="'+this.container.style.width+'" height="'+this.container.style.height+'"></canvas>'; 51 //this.canvasRoot.setAttribute('id', 'canvasel'); 52 //this.canvasRoot.style.overflow = 'hidden'; 53 //this.canvasRoot.style.width = this.container.style.width; 54 //this.canvasRoot.style.height = this.container.style.height; 55 //this.container.appendChild(this.canvasRoot); 56 this.canvasRoot = document.getElementById(this.canvasId); 57 this.context = this.canvasRoot.getContext('2d'); 58 59 this.dashArray = [[2, 2], [5, 5], [10, 10], [20, 20], [20, 10, 10, 10], [20, 5, 10, 5]]; 60 }; 61 62 JXG.CanvasRenderer.prototype = JXG.AbstractRenderer(); 63 64 /* 65 * I suggest to use this.fill() and this.stroke() instead of this.updateStencilBuffer() A.W. 66 */ 67 JXG.CanvasRenderer.prototype.updateStencilBuffer = function(el) { 68 var highlight; 69 70 // this highlight thing doesn't work by now ... :/ 71 if(typeof el.board.highlightedObjects[el.id] != 'undefined' && el.board.highlightedObjects[el.id] != null) { 72 if (el.visProp.strokeColor!='none') this.context.strokeStyle = el.visProp.highlightStrokeColor; 73 if (el.visProp.fillColor!='none') this.context.fillStyle = el.visProp.highlightFillColor; 74 this.context.lineWidth = parseFloat(el.visProp.strokeWidth); 75 76 // we can only set ONE globalAlpha value in here, so we set it to the elements fill-alpha. 77 // but we can use the stroke alpha in the other methods by ourselves. 78 this.context.globalAlpha = el.visProp.highlightFillOpacity; 79 highlight = true; 80 } else { 81 if (el.visProp.strokeColor!='none') this.context.strokeStyle = el.visProp.strokeColor; 82 if (el.visProp.fillColor!='none') this.context.fillStyle = el.visProp.fillColor; 83 this.context.lineWidth = parseFloat(el.visProp.strokeWidth); 84 85 // we can only set ONE globalAlpha value in here, so we set it to the elements fill-alpha. 86 // but we can use the stroke alpha in the other methods by ourselves. 87 this.context.globalAlpha = el.visProp.fillOpacity; 88 highlight = false; 89 } 90 91 return highlight; 92 }; 93 94 /* 95 * Sets color and opacity for filling and stroking 96 */ 97 JXG.CanvasRenderer.prototype.setColor = function(el, type) { 98 var hasColor = true, isTrace = false; 99 if (!JXG.exists(el.board)||!JXG.exists(el.board.highlightedObjects)) { 100 // This case handles trace elements. 101 // To make them work, we simply neglect highlighting. 102 isTrace = true; 103 } 104 if (type=='fill') { 105 if(!isTrace && typeof el.board.highlightedObjects[el.id] != 'undefined' && el.board.highlightedObjects[el.id] != null) { 106 if (el.visProp.highlightFillColor!='none') { 107 this.context.globalAlpha = el.visProp.highlightFillOpacity; 108 this.context.fillStyle = el.visProp.highlightFillColor; 109 } else { 110 hasColor = false; 111 } 112 } else { 113 if (el.visProp.fillColor!='none') { 114 this.context.globalAlpha = el.visProp.fillOpacity; 115 this.context.fillStyle = el.visProp.fillColor; 116 } else { 117 hasColor = false; 118 } 119 } 120 } else { 121 if(!isTrace && typeof el.board.highlightedObjects[el.id] != 'undefined' && el.board.highlightedObjects[el.id] != null) { 122 if (el.visProp.highlightStrokeColor!='none') { 123 this.context.globalAlpha = el.visProp.highlightStrokeOpacity; 124 this.context.strokeStyle = el.visProp.highlightStrokeColor; 125 } else { 126 hasColor = false; 127 } 128 } else { 129 if (el.visProp.strokeColor!='none') { 130 this.context.globalAlpha = el.visProp.strokeOpacity; 131 this.context.strokeStyle = el.visProp.strokeColor; 132 } else { 133 hasColor = false; 134 } 135 } 136 this.context.lineWidth = parseFloat(el.visProp.strokeWidth); 137 } 138 return hasColor; 139 }; 140 141 /* 142 * Sets color and opacity for filling 143 * and does the filling 144 */ 145 JXG.CanvasRenderer.prototype.fill = function(el) { 146 this.context.save(); 147 if (this.setColor(el, 'fill')) this.context.fill(); 148 this.context.restore(); 149 }; 150 151 /* 152 * Sets color and opacity for drawing paths and lines 153 * and draws the paths and lines. 154 */ 155 JXG.CanvasRenderer.prototype.stroke = function(el) { 156 this.context.save(); 157 if(el.visProp['dash']>0) { 158 // doesnt work by now 159 // this.context.lineDashArray = this.dashArray[el.visProp['dash']-1]; 160 } else { 161 this.context.lineDashArray = []; 162 } 163 if (this.setColor(el, 'stroke')) this.context.stroke(); 164 this.context.restore(); 165 }; 166 167 JXG.CanvasRenderer.prototype.setShadow = function(el) { 168 if (el.visPropOld['shadow']==el.visProp['shadow']) { 169 return; 170 } 171 172 // not implemented yet 173 // we simply have to redraw the element 174 // probably the best way to do so would be to call el.updateRenderer(), i think. 175 176 el.visPropOld['shadow']=el.visProp['shadow']; 177 }; 178 179 JXG.CanvasRenderer.prototype.setGradient = function(el) { 180 var fillNode = el.rendNode, col, op, 181 node, node2, node3, x1, x2, y1, y2; 182 183 if (typeof el.visProp['fillOpacity']=='function') { 184 op = el.visProp['fillOpacity'](); 185 } else { 186 op = el.visProp['fillOpacity']; 187 } 188 op = (op>0)?op:0; 189 if (typeof el.visProp['fillColor']=='function') { 190 col = el.visProp['fillColor'](); 191 } else { 192 col = el.visProp['fillColor']; 193 } 194 195 // not implemented yet. this should be done in the draw methods for the 196 // elements and here we just call the updateRenderer of the given element, 197 // resp. the JXG.Board.update(). 198 /* 199 if(el.visProp['gradient'] == 'linear') { 200 } 201 else if (el.visProp['gradient'] == 'radial') { 202 } 203 else { 204 } 205 */ 206 }; 207 208 JXG.CanvasRenderer.prototype.updateGradient = function(el) { 209 // see drawGradient 210 }; 211 212 JXG.CanvasRenderer.prototype.displayCopyright = function(str, fontSize) { 213 // this should be called on EVERY update, otherwise it won't be shown after the first update 214 this.context.save(); 215 this.context.font = fontSize+'px Arial'; 216 this.context.fillStyle = '#aaa'; 217 this.context.lineWidth = 0.5; 218 this.context.fillText(str, 10, 2+fontSize); 219 this.context.restore(); 220 }; 221 222 JXG.CanvasRenderer.prototype.drawInternalText = function(el) { 223 var fs, ctx = this.context; 224 //this.updateStencilBuffer(el); 225 226 ctx.save(); 227 if (this.setColor(el,'stroke')) { 228 if(typeof el.board.highlightedObjects[el.id] != 'undefined' && el.board.highlightedObjects[el.id] != null) { 229 ctx.fillStyle = el.visProp.highlightStrokeColor; 230 } else { 231 ctx.fillStyle = el.visProp.strokeColor; 232 } 233 if (el.visProp['fontSize']) { 234 if (typeof el.visProp['fontSize'] == 'function') { 235 fs = el.visProp['fontSize'](); 236 ctx.font = (fs > 0 ? fs : 0)+'px Arial'; 237 } else { 238 ctx.font = (el.visProp['fontSize'])+'px Arial'; 239 } 240 } 241 //ctx.font = el.board.options.text.fontSize+'px Arial'; 242 this.transformImage(el,el.transformations,ctx); 243 ctx.fillText(el.plaintextStr, el.coords.scrCoords[1], el.coords.scrCoords[2]); 244 } 245 ctx.restore(); 246 247 return null; 248 }; 249 250 JXG.CanvasRenderer.prototype.updateInternalText = function(/** JXG.Text */ el) { 251 this.drawInternalText(el); 252 }; 253 254 JXG.CanvasRenderer.prototype.updateTextStyle = function(el) { 255 }; 256 257 JXG.CanvasRenderer.prototype.drawTicks = function(axis) { 258 // this function is supposed to initialize the svg/vml nodes in the SVG/VMLRenderer. 259 // but in canvas there are no such nodes, hence we just do nothing and wait until 260 // updateTicks is called. 261 }; 262 263 JXG.CanvasRenderer.prototype.updateTicks = function(axis,dxMaj,dyMaj,dxMin,dyMin) { 264 var i, c, len = axis.ticks.length; 265 //this.context.globalAlpha = axis.visProp[(this.updateStencilBuffer(axis) ? 'highlightS' : 's' ) + 'trokeOpacity']; 266 267 this.context.beginPath(); 268 for (i=0; i<len; i++) { 269 c = axis.ticks[i].scrCoords; 270 if (axis.ticks[i].major) { 271 if ((axis.board.needsFullUpdate||axis.needsRegularUpdate||axis.labels[i].display=='internal') 272 && axis.labels[i].visProp['visible']) { 273 this.drawText(axis.labels[i]); 274 } 275 this.context.moveTo(c[1]+dxMaj, c[2]-dyMaj); 276 this.context.lineTo(c[1]-dxMaj, c[2]+dyMaj); 277 } 278 else { 279 this.context.moveTo(c[1]+dxMin, c[2]-dyMin); 280 this.context.lineTo(c[1]-dxMin, c[2]+dyMin); 281 } 282 } 283 //this.context.stroke(); 284 this.stroke(axis); 285 }; 286 287 JXG.CanvasRenderer.prototype.drawImage = function(el) { 288 el.rendNode = new Image(); 289 // Store the file name of the image. 290 // Before, this was done in el.rendNode.src 291 // But there, the file name is expanded to 292 // the full url. This may be different from 293 // the url computed in updateImageURL(). 294 el._src = ''; 295 this.updateImage(el); 296 }; 297 298 JXG.CanvasRenderer.prototype.updateImageURL = function(el) { 299 var url; 300 if (JXG.isFunction(el.url)) { 301 url = el.url(); 302 } else { 303 url = el.url; 304 } 305 if (el._src!=url) { 306 el.imgIsLoaded = false; 307 el.rendNode.src = url; 308 el._src = url; 309 return true; 310 } else { 311 return false; 312 } 313 }; 314 315 JXG.CanvasRenderer.prototype.updateImage = function(/** Image */ el) { 316 var ctx = this.context, 317 o = this.evaluate(el.visProp.fillOpacity), 318 paintImg = JXG.bind(function(){ 319 el.imgIsLoaded = true; 320 if (el.size[0]<=0 || el.size[1]<=0) return; 321 ctx.save(); 322 ctx.globalAlpha = o; 323 // If det(el.transformations)=0, FireFox 3.6. breaks down. 324 // This is tested in transformImage 325 this.transformImage(el,el.transformations,ctx); 326 ctx.drawImage(el.rendNode, 327 el.coords.scrCoords[1], 328 el.coords.scrCoords[2]-el.size[1], 329 el.size[0], 330 el.size[1]); 331 ctx.restore(); 332 }, this); 333 334 if (this.updateImageURL(el)) { 335 el.rendNode.onload = paintImg; 336 } else { 337 if (el.imgIsLoaded) paintImg(); 338 } 339 }; 340 341 JXG.CanvasRenderer.prototype.transformImage = function(el,t,ctx) { 342 var m, len = t.length; 343 if (len>0) { 344 m = this.joinTransforms(el,t); 345 if (Math.abs(JXG.Math.Numerics.det(m))>=JXG.Math.eps) 346 ctx.transform(m[1][1],m[2][1],m[1][2],m[2][2],m[1][0],m[2][0]); 347 } 348 }; 349 350 JXG.CanvasRenderer.prototype.setArrowAtts = function(node, c, o) { 351 // this isn't of any use in a canvas based renderer, 352 // because the arrows have to be redrawn on every update. 353 }; 354 355 JXG.CanvasRenderer.prototype.setObjectStrokeColor = function(el, color, opacity) { 356 // this is not required in a canvas based renderer 357 //if (JXG.exists(el.board)) 358 // el.board.updateRenderer(); 359 }; 360 361 JXG.CanvasRenderer.prototype.setObjectFillColor = function(el, color, opacity) { 362 // useless 363 //el.board.updateRenderer(); 364 }; 365 366 JXG.CanvasRenderer.prototype.setObjectStrokeWidth = function(el, width) { 367 // useless 368 //el.board.updateRenderer(); 369 }; 370 371 JXG.CanvasRenderer.prototype.hide = function(el) { 372 // useless beside HTML texts 373 if (JXG.exists(el.rendNode)) 374 el.rendNode.style.visibility = "hidden"; 375 //el.board.updateRenderer(); 376 }; 377 378 JXG.CanvasRenderer.prototype.show = function(el) { 379 // useless beside HTML texts 380 if (JXG.exists(el.rendNode)) 381 el.rendNode.style.visibility = "inherit"; 382 //el.board.updateRenderer(); 383 }; 384 385 JXG.CanvasRenderer.prototype.remove = function(shape) { 386 // useless with the exception of HTML texts 387 if(shape!=null && shape.parentNode != null) 388 shape.parentNode.removeChild(shape); 389 }; 390 391 JXG.CanvasRenderer.prototype.suspendRedraw = function() { 392 this.context.save(); 393 this.context.clearRect(0, 0, this.canvasRoot.width, this.canvasRoot.height); 394 this.displayCopyright(JXG.JSXGraph.licenseText, 12); 395 }; 396 397 JXG.CanvasRenderer.prototype.unsuspendRedraw = function() { 398 this.context.restore(); 399 }; 400 401 JXG.CanvasRenderer.prototype.setDashStyle = function(el,visProp) { 402 }; 403 404 JXG.CanvasRenderer.prototype.setGridDash = function(id) { 405 // useless 406 }; 407 408 JXG.CanvasRenderer.prototype.createPrim = function(type,id) { 409 // <canvas> is not node-based like svg, hence this is useless 410 }; 411 412 JXG.CanvasRenderer.prototype.createArrowHead = function(el,idAppendix) { 413 // we have to draw arrow heads directly in the draw line methods 414 }; 415 416 /* 417 // seems to be unused 418 JXG.CanvasRenderer.prototype.makeArrow = function(node,el,idAppendix) { 419 // we have to draw arrow heads directly in the draw line methods 420 }; 421 */ 422 423 /* 424 * _drawFilledPolygon, _translateShape, _rotateShape 425 * are necessary for drawing arrow heads. 426 */ 427 JXG.CanvasRenderer.prototype._drawFilledPolygon = function(shape) { 428 var i, len = shape.length; 429 if (len<=0) return; 430 this.context.beginPath(); 431 this.context.moveTo(shape[0][0],shape[0][1]); 432 for(i=0;i<len;i++) { 433 if (i > 0) this.context.lineTo(shape[i][0],shape[i][1]); 434 } 435 this.context.lineTo(shape[0][0],shape[0][1]); 436 this.context.fill(); 437 }; 438 439 JXG.CanvasRenderer.prototype._translateShape = function(shape,x,y) { 440 var i, rv = [], len = shape.length; 441 if (len<=0) return shape; 442 for(i=0;i<len;i++) { 443 rv.push([ shape[i][0] + x, shape[i][1] + y ]); 444 } 445 return rv; 446 }; 447 448 JXG.CanvasRenderer.prototype._rotateShape = function(shape,ang) { 449 var i, rv = [], len = shape.length; 450 if (len<=0) return shape; 451 452 for(i=0;i<len;i++) { 453 rv.push(this._rotatePoint(ang,shape[i][0],shape[i][1])); 454 } 455 return rv; 456 }; 457 458 JXG.CanvasRenderer.prototype._rotatePoint = function(ang,x,y) { 459 return [ 460 (x * Math.cos(ang)) - (y * Math.sin(ang)), 461 (x * Math.sin(ang)) + (y * Math.cos(ang)) 462 ]; 463 }; 464 465 JXG.CanvasRenderer.prototype.makeArrows = function(el, scr1, scr2) { 466 var ang; 467 468 // not done yet for curves and arcs. 469 var arrowHead = [ 470 [ 2, 0 ], 471 [ -10, -4 ], 472 [ -10, 4] 473 ], 474 arrowTail = [ 475 [ -2, 0 ], 476 [ 10, -4 ], 477 [ 10, 4] 478 ], 479 x1, y1, x2, y2, ang; 480 481 if (el.visProp['strokeColor']!='none' && (el.visProp['lastArrow']||el.visProp['firstArrow'])) { 482 if (el.elementClass==JXG.OBJECT_CLASS_LINE) { 483 x1 = scr1.scrCoords[1]; 484 y1 = scr1.scrCoords[2]; 485 x2 = scr2.scrCoords[1]; 486 y2 = scr2.scrCoords[2]; 487 } else { 488 return; 489 } 490 491 this.context.save(); 492 if (this.setColor(el,'stroke')) { 493 if(typeof el.board.highlightedObjects[el.id] != 'undefined' && el.board.highlightedObjects[el.id] != null) { 494 this.context.fillStyle = el.visProp.highlightStrokeColor; 495 } else { 496 this.context.fillStyle = el.visProp.strokeColor; 497 } 498 var ang = Math.atan2(y2-y1,x2-x1); 499 if (el.visProp['lastArrow']) 500 this._drawFilledPolygon(this._translateShape(this._rotateShape(arrowHead,ang),x2,y2)); 501 if (el.visProp['firstArrow']) 502 this._drawFilledPolygon(this._translateShape(this._rotateShape(arrowTail,ang),x1,y1)); 503 } 504 this.context.restore(); 505 } 506 }; 507 508 JXG.CanvasRenderer.prototype.updateLinePrim = function(node,p1x,p1y,p2x,p2y) { 509 // not required 510 }; 511 512 JXG.CanvasRenderer.prototype.updateCirclePrim = function(node,x,y,r) { 513 // not required 514 }; 515 516 JXG.CanvasRenderer.prototype.updateEllipsePrim = function(node,x,y,rx,ry) { 517 // not required 518 }; 519 520 JXG.CanvasRenderer.prototype.updateRectPrim = function(node,x,y,w,h) { 521 // not required 522 }; 523 524 JXG.CanvasRenderer.prototype.updatePathPrim = function(node, pointString, board) { 525 // not required 526 }; 527 528 JXG.CanvasRenderer.prototype.updatePathStringPrim = function(el) { 529 var symbm = 'M', 530 symbl = 'L', 531 nextSymb = symbm, 532 maxSize = 5000.0, 533 pStr = '', 534 //h = 3*el.board.canvasHeight, 535 //w = 100*el.board.canvasWidth, 536 i, scr, 537 isNoPlot = (el.curveType!='plot'), 538 //isFunctionGraph = (el.curveType=='functiongraph'), 539 len; 540 541 if (el.numberPoints<=0) { return ''; } 542 543 if (isNoPlot && el.board.options.curve.RDPsmoothing) { 544 el.points = this.RamenDouglasPeuker(el.points,0.5); 545 } 546 len = Math.min(el.points.length,el.numberPoints); 547 548 this.context.beginPath(); 549 for (i=0; i<len; i++) { 550 scr = el.points[i].scrCoords; 551 //if (isNaN(scr[1]) || isNaN(scr[2]) /*|| Math.abs(scr[1])>w || (isFunctionGraph && (scr[2]>h || scr[2]<-0.5*h))*/ ) { // PenUp 552 if (isNaN(scr[1]) || isNaN(scr[2])) { // PenUp 553 nextSymb = symbm; 554 } else { 555 // Chrome has problems with values being too far away. 556 if (scr[1]>maxSize) { scr[1] = maxSize; } 557 else if (scr[1]<-maxSize) { scr[1] = -maxSize; } 558 if (scr[2]>maxSize) { scr[2] = maxSize; } 559 else if (scr[2]<-maxSize) { scr[2] = -maxSize; } 560 561 if(nextSymb == 'M') 562 this.context.moveTo(scr[1], scr[2]); 563 else 564 this.context.lineTo(scr[1], scr[2]); 565 //pStr += [nextSymb,scr[1],' ',scr[2]].join(''); // Attention: first coordinate may be inaccurate if far way 566 nextSymb = symbl; 567 } 568 } 569 //this.context.closePath(); 570 this.fill(el); 571 this.stroke(el); 572 return null; 573 }; 574 575 JXG.CanvasRenderer.prototype.appendChildPrim = function(node,level) { 576 577 }; 578 579 JXG.CanvasRenderer.prototype.setPropertyPrim = function(node,key,val) { 580 if (key=='stroked') { 581 } 582 //node.setAttributeNS(null, key, val); 583 }; 584 585 JXG.CanvasRenderer.prototype.drawVerticalGrid = function(topLeft, bottomRight, gx, board) { 586 var node = this.createPrim('path', 'gridx'), 587 gridArr = ''; 588 589 while(topLeft.scrCoords[1] < bottomRight.scrCoords[1] + gx - 1) { 590 gridArr += ' M ' + topLeft.scrCoords[1] + ' ' + 0 + ' L ' + topLeft.scrCoords[1] + ' ' + board.canvasHeight+' '; 591 topLeft.setCoordinates(JXG.COORDS_BY_SCREEN, [topLeft.scrCoords[1] + gx, topLeft.scrCoords[2]]); 592 } 593 this.updatePathPrim(node, gridArr, board); 594 return node; 595 }; 596 597 JXG.CanvasRenderer.prototype.drawHorizontalGrid = function(topLeft, bottomRight, gy, board) { 598 var node = this.createPrim('path', 'gridy'), 599 gridArr = ''; 600 601 while(topLeft.scrCoords[2] <= bottomRight.scrCoords[2] + gy - 1) { 602 gridArr += ' M ' + 0 + ' ' + topLeft.scrCoords[2] + ' L ' + board.canvasWidth + ' ' + topLeft.scrCoords[2]+' '; 603 topLeft.setCoordinates(JXG.COORDS_BY_SCREEN, [topLeft.scrCoords[1], topLeft.scrCoords[2] + gy]); 604 } 605 this.updatePathPrim(node, gridArr, board); 606 return node; 607 }; 608 609 JXG.CanvasRenderer.prototype.appendNodesToElement = function(element, type) { 610 // not used, 611 }; 612 613 // we need to overwrite some AbstractRenderer methods which are only useful for vector based renderers 614 JXG.CanvasRenderer.prototype.drawPoint = function(/** Point */ el) { 615 var f = el.visProp['face'], 616 size = el.visProp['size'], 617 scr = el.coords.scrCoords, 618 sqrt32 = size*Math.sqrt(3)*0.5, 619 s05 = size*0.5, 620 stroke05 = parseFloat(el.visProp.strokeWidth)/2.0; 621 622 if (size<=0) return; 623 // determine how the point looks like 624 switch(f) { 625 case 'cross': // x 626 case 'x': 627 //this.context.globalAlpha = el.visProp[(highlight ? 'highlightS' : 's' ) + 'trokeOpacity']; 628 this.context.beginPath(); 629 this.context.moveTo(scr[1]-size, scr[2]-size); 630 this.context.lineTo(scr[1]+size, scr[2]+size); 631 this.context.moveTo(scr[1]+size, scr[2]-size); 632 this.context.lineTo(scr[1]-size, scr[2]+size); 633 this.context.closePath(); 634 this.stroke(el); 635 break; 636 case 'circle': // dot 637 case 'o': 638 this.context.beginPath(); 639 this.context.arc(scr[1], scr[2], size+1+stroke05, 0, 2*Math.PI, false); 640 this.context.closePath(); 641 //this.context.fill(); 642 this.fill(el); 643 this.stroke(el); 644 break; 645 case 'square': // rectangle 646 case '[]': 647 if (size<=0) break; 648 this.context.save(); 649 if (this.setColor(el,'stroke')) { 650 if(typeof el.board.highlightedObjects[el.id] != 'undefined' && el.board.highlightedObjects[el.id] != null) { 651 this.context.fillStyle = el.visProp.highlightStrokeColor; 652 } else { 653 this.context.fillStyle = el.visProp.strokeColor; 654 } 655 this.context.fillRect(scr[1]-size-stroke05, scr[2]-size-stroke05, size*2+3*stroke05, size*2+3*stroke05); 656 } 657 this.context.restore(); 658 this.context.save(); 659 this.setColor(el,'fill'); 660 this.context.fillRect(scr[1]-size+stroke05, scr[2]-size+stroke05, size*2-stroke05, size*2-stroke05); 661 this.context.restore(); 662 break; 663 case 'plus': // + 664 case '+': 665 //this.context.globalAlpha = el.visProp[(highlight ? 'highlightS' : 's' ) + 'trokeOpacity']; 666 this.context.beginPath(); 667 this.context.moveTo(scr[1]-size, scr[2]); 668 this.context.lineTo(scr[1]+size, scr[2]); 669 this.context.moveTo(scr[1], scr[2]-size); 670 this.context.lineTo(scr[1], scr[2]+size); 671 this.context.closePath(); 672 this.stroke(el); 673 break; 674 case 'diamond': // <> 675 case '<>': 676 this.context.beginPath(); 677 this.context.moveTo(scr[1]-size, scr[2]); 678 this.context.lineTo(scr[1], scr[2]+size); 679 this.context.lineTo(scr[1]+size, scr[2]); 680 this.context.lineTo(scr[1], scr[2]-size); 681 this.context.closePath(); 682 this.fill(el); 683 this.stroke(el); 684 break; 685 case 'triangleup': 686 case 'a': 687 case '^': 688 this.context.beginPath(); 689 this.context.moveTo(scr[1], scr[2]-size); 690 this.context.lineTo(scr[1]-sqrt32, scr[2]+s05); 691 this.context.lineTo(scr[1]+sqrt32, scr[2]+s05); 692 this.context.closePath(); 693 this.fill(el); 694 this.stroke(el); 695 break; 696 case 'triangledown': 697 case 'v': 698 this.context.beginPath(); 699 this.context.moveTo(scr[1], scr[2]+size); 700 this.context.lineTo(scr[1]-sqrt32, scr[2]-s05); 701 this.context.lineTo(scr[1]+sqrt32, scr[2]-s05); 702 this.context.closePath(); 703 this.fill(el); 704 this.stroke(el); 705 break; 706 case 'triangleleft': 707 case '<': 708 this.context.beginPath(); 709 this.context.moveTo(scr[1]-size, scr[2]); 710 this.context.lineTo(scr[1]+s05, scr[2]-sqrt32); 711 this.context.lineTo(scr[1]+s05, scr[2]+sqrt32); 712 this.context.closePath(); 713 this.fill(el); 714 this.stroke(el); 715 break; 716 case 'triangleright': 717 case '>': 718 this.context.beginPath(); 719 this.context.moveTo(scr[1]+size, scr[2]); 720 this.context.lineTo(scr[1]-s05, scr[2]-sqrt32); 721 this.context.lineTo(scr[1]-s05, scr[2]+sqrt32); 722 this.context.closePath(); 723 this.fill(el); 724 this.stroke(el); 725 break; 726 } 727 }; 728 729 JXG.CanvasRenderer.prototype.updatePoint = function(el) { 730 this.drawPoint(el); 731 }; 732 733 JXG.CanvasRenderer.prototype.changePointStyle = function(/** Point */el) { 734 this.drawPoint(el); 735 }; 736 737 JXG.CanvasRenderer.prototype.drawText = function(/** Text */ el) { 738 var node; 739 if (el.display=='html') { 740 node = this.container.ownerDocument.createElement('div'); 741 node.style.position = 'absolute'; 742 node.style.color = el.visProp['strokeColor']; 743 node.className = 'JXGtext'; 744 node.style.zIndex = '10'; 745 this.container.appendChild(node); 746 node.setAttribute('id', this.container.id+'_'+el.id); 747 node.style.fontSize = el.board.options.text.fontSize + 'px'; 748 } else { 749 node = this.drawInternalText(el); 750 } 751 el.rendNode = node; 752 el.htmlStr = ''; 753 this.updateText(el); 754 }; 755 756 JXG.CanvasRenderer.prototype.updateText = function(/** JXG.Text */ el) { 757 // Update only objects that are visible. 758 if (el.visProp['visible'] === false) return; 759 if (isNaN(el.coords.scrCoords[1]+el.coords.scrCoords[2])) return; 760 this.updateTextStyle(el); 761 if (el.display=='html') { 762 el.rendNode.style.left = (el.coords.scrCoords[1])+'px'; 763 el.rendNode.style.top = (el.coords.scrCoords[2] - this.vOffsetText)+'px'; 764 el.updateText(); 765 if (el.htmlStr!= el.plaintextStr) { 766 el.rendNode.innerHTML = el.plaintextStr; 767 if (el.board.options.text.useASCIIMathML) { 768 AMprocessNode(el.rendNode,false); 769 } 770 el.htmlStr = el.plaintextStr; 771 } 772 } else { 773 this.updateInternalText(el); 774 } 775 }; 776 777 JXG.CanvasRenderer.prototype.drawLine = function(/** Line */ el) { 778 var scr1 = new JXG.Coords(JXG.COORDS_BY_USER, el.point1.coords.usrCoords, el.board), 779 scr2 = new JXG.Coords(JXG.COORDS_BY_USER, el.point2.coords.usrCoords, el.board), 780 ax, ay, bx, by, beta, sgn, x, y, m; 781 this.calcStraight(el,scr1,scr2); 782 783 this.context.beginPath(); 784 this.context.moveTo(scr1.scrCoords[1],scr1.scrCoords[2]); 785 this.context.lineTo(scr2.scrCoords[1],scr2.scrCoords[2]); 786 //this.context.stroke(); 787 //this.context.closePath(); 788 this.stroke(el); 789 // if this line has arrows attached, update them, too. 790 this.makeArrows(el,scr1,scr2); 791 }; 792 793 JXG.CanvasRenderer.prototype.updateLine = function(/** Line */ el) { 794 this.drawLine(el); 795 }; 796 797 JXG.CanvasRenderer.prototype.drawCurve = function(/** Curve */ el) { 798 this.updatePathStringPrim(el); 799 }; 800 801 JXG.CanvasRenderer.prototype.updateCurve = function(/** Curve */ el) { 802 this.drawCurve(el); 803 }; 804 805 JXG.CanvasRenderer.prototype.drawEllipse = function(el, m1, m2, sX, sY, rX, rY) { 806 var aWidth = rX*sX, 807 aHeight = rY*sY, 808 aX = m1 - aWidth/2, 809 aY = m2 - aHeight/2, 810 hB = (aWidth / 2) * .5522848, 811 vB = (aHeight / 2) * .5522848, 812 eX = aX + aWidth, 813 eY = aY + aHeight, 814 mX = aX + aWidth / 2, 815 mY = aY + aHeight / 2; 816 817 //this.context.globalAlpha = el.visProp[(this.updateStencilBuffer(el) ? 'highlightS' : 's' ) + 'trokeOpacity']; 818 if (rX>0.0 && rY>0.0 && !isNaN(m1+m2) ) { 819 this.context.beginPath(); 820 this.context.moveTo(aX, mY); 821 this.context.bezierCurveTo(aX, mY - vB, mX - hB, aY, mX, aY); 822 this.context.bezierCurveTo(mX + hB, aY, eX, mY - vB, eX, mY); 823 this.context.bezierCurveTo(eX, mY + vB, mX + hB, eY, mX, eY); 824 this.context.bezierCurveTo(mX - hB, eY, aX, mY + vB, aX, mY); 825 this.context.closePath(); 826 this.fill(el); 827 this.stroke(el); 828 } 829 }; 830 831 JXG.CanvasRenderer.prototype.drawCircle = function(/** Circle */ el) { 832 this.drawEllipse(el, el.midpoint.coords.scrCoords[1], 833 el.midpoint.coords.scrCoords[2], 834 el.board.stretchX, el.board.stretchY, 835 2*el.Radius(), 2*el.Radius()); 836 }; 837 838 JXG.CanvasRenderer.prototype.updateCircle = function(/** Circle */ el) { 839 this.drawCircle(el); 840 }; 841 842 JXG.CanvasRenderer.prototype.drawPolygon = function(/** Polygon */ el) { 843 }; 844 845 JXG.CanvasRenderer.prototype.updatePolygonPrim = function(node, el) { 846 var pStr = '', 847 scrCoords, i, 848 len = el.vertices.length; 849 850 if (len<=0) return; 851 this.context.beginPath(); 852 scrCoords = el.vertices[0].coords.scrCoords; 853 this.context.moveTo(scrCoords[1],scrCoords[2]); 854 for (i=1; i<len; i++) { 855 scrCoords = el.vertices[i].coords.scrCoords; 856 this.context.lineTo(scrCoords[1],scrCoords[2]); 857 } 858 this.context.closePath(); 859 860 this.fill(el); // The edges of a polygon are displayed separately (as segments). 861 }; 862 863 /* 864 * Highlighting in CanvasRenderer means we have to render again 865 */ 866 JXG.CanvasRenderer.prototype.highlight = function(/** JXG.GeometryElement */ obj) { 867 obj.board.prepareUpdate(); 868 obj.board.renderer.suspendRedraw(); 869 obj.board.updateRenderer(); 870 obj.board.renderer.unsuspendRedraw(); 871 return this; 872 }; 873 874 /* 875 * Dehighlighting in CanvasRenderer means we have to render again 876 */ 877 JXG.CanvasRenderer.prototype.noHighlight = function(/** JXG.GeometryElement */ obj) { 878 obj.board.prepareUpdate(); 879 obj.board.renderer.suspendRedraw(); 880 obj.board.updateRenderer(); 881 obj.board.renderer.unsuspendRedraw(); 882 return this; 883 }; 884