1 // ========================================================================== 2 // Papercube.CircleViewHTML 3 // 4 // License: PaperCube is open source software released under 5 // the MIT License (see license.js) 6 // ========================================================================== 7 8 require('core'); 9 10 /** @class 11 12 This is CircleView, a view for navigating a paper's references. 13 14 @extends SC.View 15 @extends NodeGraph.DragPanMixin 16 @author Peter Bergstrom 17 @version 1.0 18 @copyright 2008-2009 Peter Bergström. 19 */ 20 21 Papercube.CircleViewHTML = SC.View.extend( 22 /** @scope Papercube.CircleViewHTML.prototype */ NodeGraph.DragPanMixin, { 23 24 /** 25 Bind the display properties from the canvasController. 26 27 @property {Array} 28 @binding "Papercube.canvasController.displayProperties" 29 */ 30 displayPropertiesBinding: "Papercube.canvasController.displayProperties", 31 32 /** 33 Bind the view direction from the viewController. 34 35 @property {String} 36 @binding "Papercube.viewController.viewDirection" 37 */ 38 viewDirectionBinding: "Papercube.viewController.viewDirection", 39 40 /** 41 Bind the cite threshold binding. Don't show papers with less cites. 42 43 @property {Integer} 44 @binding "Papercube.circleViewController.citeThreshold" 45 */ 46 citeThresholdBinding: "Papercube.circleViewController.citeThreshold", 47 48 /** 49 Cite threshold cached value. 50 51 @property {Integer} 52 @default 1 53 */ 54 _cached_citeThreshold: 1, 55 56 /** 57 Bind the ref threshold binding. Don't show papers with less refs. 58 59 @property {Integer} 60 @binding "Papercube.circleViewController.refThreshold" 61 */ 62 refThresholdBinding: "Papercube.circleViewController.refThreshold", 63 64 /** 65 Ref threshold cached value. 66 67 @property {Integer} 68 @default 1 69 */ 70 _cached_refThreshold: 1, 71 72 /** 73 Display references or citations. 74 75 @property {Boolean} 76 @default YES 77 */ 78 _displayRefs: YES, 79 80 /** 81 boolean that determines if the meta data box is showing or not. 82 83 @property {Boolean} 84 @default NO 85 */ 86 _showingMetaData: NO, 87 88 /** 89 Cached width of the view's canvas, set by the displayProperties. 90 91 @property {Integer} 92 @default 800px 93 */ 94 _canvasWidth: 800, 95 96 /** 97 Cached height of the view's canvas, set by the displayProperties. 98 99 @property {Integer} 100 @default 600px 101 */ 102 _canvasHeight: 600, 103 104 /** 105 Cached height property from displayProperties. 106 107 @property {Integer} 108 @default 0px 109 */ 110 _h: 0, 111 112 /** 113 Cached width property from displayProperties. 114 115 @property {Integer} 116 @default 0px 117 */ 118 _w: 0, 119 120 /** 121 Cached x-axis offset property from displayProperties. 122 123 @property {Integer} 124 @default 0px 125 */ 126 _x: 0, 127 128 /** 129 Cached y-axis offset property from displayProperties. 130 131 @property {Integer} 132 @default 0px 133 */ 134 _y: 0, 135 136 /** 137 Stored value of the number of first level references. 138 139 @property {Integer} 140 @default 0 141 */ 142 _numberOfFirstRefs: 0, 143 144 /** 145 Stored value of the number of first level citations. 146 147 @property {Integer} 148 @default 0 149 */ 150 _numberOfFirstCites: 0, 151 152 /** 153 Stored value of the number of first level circles to be displayed. 154 155 @property {Integer} 156 @default 0 157 */ 158 _numberOfFirstCircles: 0, 159 160 /** 161 boolean specifying that the visualization is rotating left. 162 163 @property {Boolean} 164 @default NO 165 */ 166 _rotatingLeft: NO, 167 168 /** 169 boolean specifying that the visualization is rotating right. 170 171 @property {Boolean} 172 @default NO 173 */ 174 _rotatingRight: NO, 175 176 /** 177 The local x-axis center of the view. 178 179 @property {Integer} 180 @default 0px 181 */ 182 _xC: 0, 183 184 /** 185 The local y-axis center of the view. 186 187 @property {Integer} 188 @default 0px 189 */ 190 _yC: 0, 191 192 /** 193 The angle of the detail paper. 194 195 @property {Integer} 196 @default -25 degrees 197 */ 198 _sAngle: -25, 199 200 /** 201 The rotation speed. 202 203 Default is 1x depending on the angle. It slows down when it is about to stop. 204 205 @property {Integer} 206 @default 1px 207 */ 208 _rotationSpeed: 1, 209 210 /** 211 The number of degrees per frame. 212 213 @property {Integer} 214 */ 215 _tAIncr: null, 216 217 /** 218 boolean specifying if the view is animating. 219 220 @property {Boolean} 221 @default NO 222 */ 223 _isAnimating: NO, 224 225 /** 226 The guid of the detail paper. 227 228 @property {String} 229 */ 230 _detailGUID: null, 231 232 /** 233 Destination guid during rotation. 234 235 @property {String} 236 */ 237 _rotateToGUID: null, 238 239 /** 240 Departure guid during rotation. 241 242 @property {String} 243 */ 244 _rotateFromGUID: null, 245 246 /** 247 The minimum number of references in the visualization. 248 249 @property {Integer} 250 @default 0 251 */ 252 _minRefs: 0, 253 254 /** 255 The minimum number of citations in the visualization. 256 257 @property {Integer} 258 @default 0 259 */ 260 _minCites: 0, 261 262 /** 263 The maximum number of references in the visualization. 264 265 @property {Integer} 266 @default 0 267 */ 268 _maxRefs: 0, 269 270 /** 271 The maximum number of citations in the visualization. 272 273 @property {Integer} 274 @default 0 275 */ 276 _maxCites: 0, 277 278 /** 279 The guid of the last center paper, to avoid unnecessary loading of data. 280 281 @property {String} 282 */ 283 _lastContentGUID: null, 284 285 /** 286 The cached content object. 287 288 @property {Paper} 289 */ 290 _cachedContent: null, 291 292 /** 293 Is YES when you're animating to the middle. 294 295 @property {Boolean} 296 @default NO 297 */ 298 _isAnimatingToMiddle: NO, 299 300 /** 301 Source circle diameter for animating a paper to the middle. 302 303 @property {Integer} 304 @default 0px 305 */ 306 _animateSrcD: 0, 307 308 /** 309 Source x position for animating a paper to the middle. 310 311 @property {Integer} 312 @default 0px 313 */ 314 _animateSrcX: 0, 315 316 /** 317 Source y position for animating a paper to the middle. 318 319 @property {Integer} 320 @default 0px 321 */ 322 _animateSrcY: 0, 323 324 /** 325 Source circle diameter frame delta for animating a paper to the middle. 326 327 @property {Integer} 328 @default 0px 329 */ 330 _animatePosDeltaD: 0, 331 332 /** 333 Source x position frame delta for animating a paper to the middle. 334 335 @property {Integer} 336 @default 0px 337 */ 338 _animatePosDeltaX: 0, 339 340 /** 341 Source y position frame delta for animating a paper to the middle. 342 343 @property {Integer} 344 @default 0px 345 */ 346 _animatePosDeltaY: 0, 347 348 /** 349 The element that is being animated to the the middle. 350 351 @property {DOM Element} 352 */ 353 _animateElm: null, 354 355 /** 356 The opacity of the rest of the visualization while animating a paper to the middle. 357 358 @property {Integer} 359 @default 100 360 */ 361 _animateOpacity: 100, 362 363 /** 364 2*PI saved so that we don't have to do it all over the place. 365 366 @property {Integer} 367 */ 368 _twoPI: Math.PI*2, 369 370 /** 371 The guid of the last clicked paper or author. 372 373 @property {String} 374 */ 375 _mouseDownGUID: null, 376 377 /** 378 The DOM element of the last clicked paper. 379 380 @property {DOM Element} 381 */ 382 _mouseDownCircle: null, 383 384 /** 385 The cache of guids. 386 387 @property {Object} 388 */ 389 _guidCache: {}, 390 391 /** 392 The cache of HTML. 393 394 @property {Object} 395 */ 396 _htmlCache: {}, 397 398 /** 399 Color cache for the top level lines. 400 401 @property {Object} 402 */ 403 _lineColorCache: {}, 404 405 /** 406 The currently selected guid. 407 408 @property {String} 409 */ 410 _selection: null, 411 412 /** 413 Filtered reference relations. 414 415 @property {Object} 416 */ 417 _filteredRefs: {}, 418 419 /** 420 Filtered citation relations. 421 422 @property {Object} 423 */ 424 _filteredCites: {}, 425 426 /** 427 Cached stylesheet reference. 428 429 @property {DOM Element} 430 */ 431 _styleSheet: null, 432 433 /** 434 Outlets for CircleView. 435 436 [ 437 "canvas", 438 "divCanvas", 439 "fan", 440 "rotateLeftButton", 441 "rotateRightButton", 442 "metaDataView" 443 ] 444 */ 445 outlets: ["canvas", "divCanvas", "fan", "rotateLeftButton", "rotateRightButton", "metaDataView"], 446 447 /** 448 Canvas tag DOM element. Contains the lines. Bound to the '.canvas?' element. 449 450 @outlet {DOM Element} '.canvas?' 451 */ 452 canvas: ".canvas?", 453 454 /** 455 HTML canvas DOM element. Contains the text and circles. Bound to the '.div-canvas?' element. 456 457 @outlet {DOM Element} '.div-canvas?' 458 */ 459 divCanvas: ".div-canvas?", 460 461 /** 462 The DOM element that contains the meta data for an item. Bound to the '.meta-data?' element. 463 464 @outlet {DOM Element} '.meta-data?' 465 */ 466 metaDataView: ".meta-data?", 467 468 /** 469 "rotateLeftButton" for button to initiate rotation to the left. 470 471 @outlet {SC.ButtonView} '.rotate-left?' 472 */ 473 rotateLeftButton: SC.ButtonView.extend({ 474 475 /** 476 Rotate to the left. 477 */ 478 action: function() 479 { 480 this.owner._rotateFromGUID = this.owner._detailGUID; 481 482 // Calc the rotation speed. 483 this.owner._cRotationSpeed(); 484 this.owner.rotateLeft(NO); 485 } 486 }).outletFor('.rotate-left?'), 487 488 /** 489 "rotateRightButton" for button to initiate rotation to the left. 490 491 @outlet {SC.ButtonView} '.rotate-right?' 492 */ 493 rotateRightButton: SC.ButtonView.extend({ 494 495 /** 496 Rotate to the right. 497 */ 498 action: function() 499 { 500 this.owner._rotateFromGUID = this.owner._detailGUID; 501 502 // Calc the rotation speed. 503 this.owner._cRotationSpeed(); 504 this.owner.rotateRight(NO); 505 } 506 }).outletFor('.rotate-right?'), 507 508 /** 509 Determine the speed of the rotation depending on the distance from paper A to paper B if passed in. 510 511 @param dist {Integer} If specified, it is the distance from A to B. 512 */ 513 _cRotationSpeed: function(dist) 514 { 515 var rel = (this._displayRefs) ? this._numberOfFirstRefs : this._numberOfFirstCites; 516 var a = 360/rel; 517 if(dist) 518 a = a*(dist/2); 519 520 // Set the rotation parameters. 521 this._stopRotation = NO; 522 this._rotationSpeed = Math.max(2,Math.min(4,a/20)); 523 }, 524 525 /** 526 Initiate an animation to the middle. Basically move from detail paper to main paper. 527 528 @param guid {string} The guid of the paper to animate. 529 530 @returns {Boolean} Returns NO if there is no element. 531 */ 532 animateToMiddle: function(guid) 533 { 534 var elm = this._mouseDownCircle; 535 if(!elm) return NO; 536 537 // Get the x and y parameters. 538 var x = parseInt(elm.style.left,0); 539 var y = parseInt(elm.style.top,0); 540 541 // Get the radius. 542 var r = 0; 543 this._animateElm = elm; 544 if(Element.hasClassName(elm, 'small') || Element.hasClassName(elm, 's')) 545 { 546 r = this._smallCRadius; 547 } 548 else if(Element.hasClassName(elm, 'detail') || Element.hasClassName(elm, 'd')) 549 { 550 r = this._detailCRadius; 551 } 552 else if(Element.hasClassName(elm, 'medium') || Element.hasClassName(elm, 'm')) 553 { 554 r = this._mediumCRadius; 555 } 556 557 // Clear the lines. 558 this._ctx.clearRect(0,0,this._canvasWidth, this._canvasHeight); // Second option, just clear it. 559 560 // Set the source x, y, and diameter. 561 this._animateSrcX = x; 562 this._animateSrcY = y; 563 this._animateSrcD = r*2; 564 565 // Calculate the deltas. 566 this._animatePosDeltaD = (this._largeCRadius*2-r*2)/30; 567 var lx = (this._xC-this._largeCRadius); 568 var ly = (this._yC-this._largeCRadius); 569 this._animatePosDeltaX = (lx-x)/30; 570 this._animatePosDeltaY = (ly-y)/30; 571 572 // Reset the opacity. Will fade to 0. 573 this._animateOpacity = 100; 574 575 elm.innerHTML = ''; 576 577 // Start the animation. 578 setTimeout(this.doAnimateToMiddle.bind(this,0),100); 579 }, 580 581 /** 582 Perform the animation from detail paper to main paper. 583 584 @param frame {Integer} The current frame. 585 */ 586 doAnimateToMiddle: function(frame) 587 { 588 // If you're at the last frame, then set the new content and start form a clean slate. 589 if(frame == 31) 590 { 591 this.divCanvas.innerHTML = ''; 592 var content = Papercube.Paper.find(this._selection); 593 Papercube.viewController.setContentToView(content); 594 this._isAnimatingToMiddle = NO; 595 this._render(); 596 return; 597 } 598 599 600 // If there is opacity to subtract, subtract it. 601 if(this._animateOpacity > 5) 602 { 603 this._animateOpacity -= 15; 604 } 605 606 // Set the opacity of all the circles. 607 var opacity = parseFloat(this._animateOpacity/100, 0); 608 for(var key in this._htmlCache) 609 { 610 if(key != this._mouseDownGUID) 611 { 612 this._htmlCache[key].style.opacity = opacity; 613 } 614 } 615 this._animateElm.style.opacity = '1.0'; 616 617 618 // Change the circle that is being animated. 619 this._animateSrcX += this._animatePosDeltaX; 620 this._animateSrcY += this._animatePosDeltaY; 621 this._animateSrcD += this._animatePosDeltaD; 622 this._animateElm.style.left = this._animateSrcX + "px"; 623 this._animateElm.style.top = this._animateSrcY + "px"; 624 this._animateElm.style.width = this._animateSrcD + "px"; 625 this._animateElm.style.height = this._animateSrcD + "px"; 626 this._animateElm.style['-webkit-border-radius'] = (Math.floor(this._animateSrcD/2))+"px"; 627 this._animateElm.className = 'large sel'; 628 629 // One more frame. 630 frame++; 631 632 // Animate anothe frame in 15 milliseconds. 633 setTimeout(this.doAnimateToMiddle.bind(this,frame), 15); 634 }, 635 636 /** 637 There are various operations that happend on mouseDown. If it is a detail paper, 638 then animate it to the middle. If it is a first level reference, make it the detail paper. 639 If it is a second level reference, refocus to that immediately. 640 641 @param {DOM Event} evt The mouseDown event. 642 643 @returns {Boolean} Returns NO if the view is not visible. 644 */ 645 mouseDown: function(evt) 646 { 647 if(!this.get('isVisible')) return NO; 648 649 // If there is no guid, capture info for panning. 650 var guid = evt.target.getAttribute('objguid'); 651 if(!guid) 652 { 653 return this.handleMouseDownDrag(evt); 654 } 655 656 657 // Get the guid. 658 this._mouseDownGUID = guid; 659 660 // Check for the type. 661 var type = 'large'; 662 if(Element.hasClassName(evt.target, 'small') || Element.hasClassName(evt.target, 's')) 663 { 664 type = 'small'; 665 } 666 else if(Element.hasClassName(evt.target, 'detail') || Element.hasClassName(evt.target, 'd') || (Element.hasClassName(evt.target, 'm') && (Element.hasClassName(evt.target.parentNode, 'detail') ||Element.hasClassName(evt.target.parentNode.parentNode, 'detail'))) ) 667 { 668 type = 'detail'; 669 } 670 else if(Element.hasClassName(evt.target, 'medium') || Element.hasClassName(evt.target, 'm') || (Element.hasClassName(evt.target, 'm') && Element.hasClassName(evt.target.parentNode, 'medium')) ) 671 { 672 type = 'medium'; 673 } 674 675 if(Element.hasClassName(evt.target, 'd') || Element.hasClassName(evt.target, 's') || Element.hasClassName(evt.target, 'm')) 676 { 677 if(Element.hasClassName(evt.target, 'title') || Element.hasClassName(evt.target, 'authors') || Element.hasClassName(evt.target, 'rels')) 678 { 679 this._mouseDownCircle = evt.target.parentNode.parentNode; 680 } 681 else 682 { 683 this._mouseDownCircle = evt.target.parentNode; 684 } 685 } 686 else 687 { 688 this._mouseDownCircle = evt.target; 689 } 690 691 // Show the fan. 692 Papercube.canvasController.showFan(Event.pointerX(evt), Event.pointerY(evt), 'circleview', (type+"Fan")); 693 return YES; 694 }, 695 696 697 /** 698 If the mouse is moved on top of the view, see if there is any meta data to show. 699 700 @param {DOM Event} evt The mouseMoved event. 701 702 @returns {Boolean} Returns NO if there is no guid of if the view is not visible. 703 */ 704 mouseMoved: function(evt) 705 { 706 if(!this.get('isVisible')) return; 707 708 // If there is no guid, don't do anything. 709 var guid = evt.target.getAttribute('objguid'); 710 if(!guid) return NO; 711 712 if(guid == this._selection) return; 713 714 // Get rid of current selection. 715 var cache = this._htmlCache[this._selection]; 716 if(cache) 717 { 718 var cacheLen = cache.length; 719 for(var i=0; i<cacheLen; i++) 720 { 721 Element.removeClassName(cache[i], 'sel'); 722 } 723 } 724 725 // Add new selection. 726 var cache = this._htmlCache[guid]; 727 if(cache) 728 { 729 var cacheLen = cache.length; 730 for(var i=0; i<cacheLen; i++) 731 { 732 Element.addClassName(cache[i], 'sel'); 733 } 734 } 735 this._selection = guid; 736 737 // Update the UI. 738 this._render(); 739 740 // Show the meta data! 741 this._showMetaData(); 742 }, 743 744 /** 745 Hide meta data box. 746 747 */ 748 _hideMetaData: function() 749 { 750 this._showingMetaData = NO; 751 752 // Hide meta data view. 753 this.metaDataView.removeClassName("show-meta-data"); 754 }, 755 756 /** 757 Show meta data for paper! 758 759 @returns {Boolean} Returns NO if there is no content. 760 */ 761 _showMetaData: function() 762 { 763 764 this._showingMetaData = YES; 765 766 // Get the paper content. 767 var content = Papercube.Paper.find(this._selection); 768 769 if(!content) return NO; 770 771 // Set the title 772 this.metaDataView.childNodes[0].innerHTML = content.get("title"); 773 774 // Set the authors 775 var authors = content.get('authorNames'); 776 777 var authorstring = authors.join(', '); 778 this.metaDataView.childNodes[1].innerHTML = authorstring; 779 780 // Set the abstract 781 this.metaDataView.childNodes[2].innerHTML = "<em>"+content.get("abstract").substring(0,200)+"</em>"; 782 783 // Set the date 784 this.metaDataView.childNodes[3].innerHTML = "<strong>Publication Date: </strong> " + content.get("year"); 785 786 this.metaDataView.childNodes[4].innerHTML = Papercube.pluralizeString(" reference", content.get('refCount')); 787 this.metaDataView.childNodes[5].innerHTML = Papercube.pluralizeString(" citation", content.get('citeCount')); 788 789 790 // Show the meta data view. 791 this.metaDataView.addClassName("show-meta-data"); 792 }, 793 794 /** 795 This is the rendering driver. 796 797 798 @returns {Boolean} Returns NO if there the view is not visible. 799 */ 800 _render: function() 801 { 802 if(!this.get('isVisible')) return NO; 803 804 this._ctx.lineWidth = this._lineWidth; 805 806 // Clear the rect. Mostly important when animating. Now drawing white with 95% opacity for some trails. 807 this._ctx.clearRect(0,0,this._canvasWidth, this._canvasHeight); // Second option, just clear it. 808 809 // Render first and second level references. 810 this._renderOneDepth(); 811 812 // Render the main paper. 813 this._renderZeroDepth(); 814 }, 815 816 /** 817 Pick a line color. This is a gradient. 818 819 820 @param count {Integer} The relation count. 821 @param isRef {boolean} True if it is a reference, NO if it is a citation. 822 823 @returns {Integer} Returns the calculated hex color. 824 */ 825 _lineColor: function(count, isRef) 826 { 827 var colorNumber = Math.min(255,parseInt(((count/(isRef ? this._maxRefs : this._maxCites))*180 +75), 0)).toString(16); 828 return "#"+ ((colorNumber.length == 1) ? "0" : "") + colorNumber +"2d2d"; 829 }, 830 831 /** 832 Generate a circle DIV element. 833 834 835 @param {DOM Element} childNode A child node to append to the circle. This is usually a text element. 836 @param guid {string} The guid of the paper. 837 @param cacheStr {string} The guid cache string. 838 @param lineColor {string} The line color 839 840 @returns {Array} {DOM Element} Returns the newly generated circle DIV element. 841 */ 842 _genCircle: function(childNode, guid, cacheStr, lineColor) 843 { 844 var div = document.createElement('div'); 845 div.setAttribute('objGuid', guid); 846 div.style.borderColor = lineColor; 847 848 if(childNode) 849 { 850 div.appendChild(childNode); 851 } 852 this.divCanvas.appendChild(div); 853 854 // Add to the cache. 855 if(!this._guidCache[guid]) 856 this._guidCache[guid] = []; 857 this._guidCache[guid].push(div); 858 this._htmlCache[cacheStr] = div; 859 860 return div; 861 }, 862 863 /** 864 Set the class name and style attributes for a circle DIV element. 865 866 867 @param x {Integer} x-position in pixels. 868 @param y {Integer} y-position in pixels. 869 @param r {Integer} radius in pixels. 870 @param className {string} The class name for the div. 871 @param lineWidth {Integer} The width of the border line. 872 @param guid {string} The guid of the paper. 873 */ 874 _setProperties: function(x,y,r,className, lineWidth, obj) 875 { 876 obj.className = className; 877 obj.style.top = (y-r)+'px'; 878 obj.style.left = (x-r)+'px'; 879 obj.style.borderWidth = (lineWidth*this._z) + "px"; 880 obj.style.borderStyle= "solid"; 881 }, 882 883 /** 884 Render the main paper. 885 886 @returns {Boolean} Returns NO if there is no content. 887 */ 888 _renderZeroDepth: function() 889 { 890 var content = this._cachedContent; 891 if(!content) return NO; 892 893 var guid = content.get('guid'); 894 895 var cacheStr = guid+guid; 896 897 // Generate the main circle. 898 if(!this._htmlCache[cacheStr]) 899 { 900 if(this._displayRefs) 901 { 902 var lineColor = this._lineColor(content.get("references").length, YES); 903 } 904 else 905 { 906 var lineColor = this._lineColor(content.get("citations").length, NO); 907 } 908 this._genCircle(this._createLargeText(content, 'large'), guid, cacheStr, lineColor); 909 } 910 this._setProperties(this._xC, this._yC, this._largeCRadius, ((this._selection == guid) ? 'large sel' : 'large'), 1,this._htmlCache[cacheStr]); 911 }, 912 913 /** 914 Filters based on the cite and ref threshold and then caches the values. Cache is invalidated when thresholds change. 915 916 917 @param content {Paper} The content object. 918 @param forceCites {boolean} If YES, always return citations no matter of the value of the _displayRefs. 919 920 @returns {Array} Returns an array containing relations for a given content object. 921 */ 922 _filterByRels: function(content, forceCites) 923 { 924 if(!content) return []; 925 926 var guid = content._attributes.guid; 927 var filterRefs = (this._displayRefs && !forceCites); 928 929 // If the thresholds are above 0, then calculate what relations to return. 930 if(this._cached_citeThreshold > 0 || this._cached_refThreshold > 0) 931 { 932 933 // If there is a cache hit, return from cache. 934 if(filterRefs && this._filteredRefs[guid]) 935 { 936 return this._filteredRefs[guid]; 937 } 938 else if(this._filteredCites[guid]) 939 { 940 return this._filteredCites[guid]; 941 } 942 943 // If not, filter to get the relations based on the thresholds. 944 var rels = (filterRefs) ? content._attributes.references : content._attributes.citations; 945 var childCount = rels.length; 946 var newRels = []; 947 for(var i=0; i<childCount; i++) 948 { 949 var relGuid = rels[i]; 950 var child = Papercube.Paper.find(relGuid); 951 if(child) 952 { 953 if(this._cached_citeThreshold <= child.get('citeCount') && 954 this._cached_refThreshold <= child.get('refCount')) 955 { 956 newRels.push(relGuid); 957 } 958 } 959 } 960 if(filterRefs) 961 { 962 this._filteredRefs[guid] = newRels; 963 } 964 else 965 { 966 this._filteredCites[guid] = newRels; 967 } 968 return newRels; 969 } 970 return (filterRefs) ? content._attributes.references : content._attributes.citations; 971 }, 972 973 /** 974 Render the first and second level papers. 975 976 @returns {Boolean} Returns NO if there is no content. 977 */ 978 _renderOneDepth: function() 979 { 980 981 var content = this._cachedContent; 982 if(!content) return NO; 983 var guid= content._attributes.guid; 984 var displayRefs = this._displayRefs; 985 986 var rel = this._filterByRels(content); 987 988 // Get the angle between references. 989 var aIncr = (1/rel.length); 990 this._tAIncr = aIncr; 991 992 // Commonly used values. 993 var mr35 = this._mR*3.5; 994 995 // Get the angle... 996 var tA = this._sAngle/360; 997 998 // Draw references. 999 var rLen = rel.length; 1000 for(var i=0; i<rLen; i++) 1001 { 1002 // Defaults. 1003 var isDetail = NO; 1004 var inDetailArea = NO; 1005 var lx = 0; 1006 var ly = 0; 1007 var rGuid = rel[i]; 1008 var th = this._twoPI*tA; 1009 // Calculate the current angle. 1010 var a = parseInt((tA*360)%360); 1011 1012 // Detail paper hot zone to hard stop. 1013 var drawCase = 0; 1014 if((a <= -24 && a >= -26) || 1015 (a >= 334 && a <= 336) && (!this._rotateTo || (this._rotateTo && rGuid == this._rotateToGUID))) 1016 { 1017 isDetail = YES; 1018 inDetailArea =YES; 1019 this._detailGUID = rGuid; 1020 drawCase = 1; 1021 } 1022 1023 var className = (this._selection == rGuid) ? 'sel ' : ''; 1024 1025 // Detail paper hot zone to hard stop. 1026 if(drawCase == 1) 1027 { 1028 this._stopRotation = YES; 1029 className += 'detail'; 1030 lx = this._xC + (mr35)*Math.cos(th); 1031 ly = this._yC + (mr35)*Math.sin(th); 1032 } 1033 1034 // Otherwise, you're in the normal case. No slow down or stop. 1035 else 1036 { 1037 className += 'medium'; 1038 1039 if(rLen <= 30) 1040 { 1041 var extra = 0; 1042 } 1043 else if(rLen > 30 &&rLen < 50) 1044 { 1045 var extra = (i%2 == 0) ? this._mR/4 : 0; 1046 } 1047 else if(rLen > 50 && rLen < 70) 1048 { 1049 var extra = (i%4 == 0) ? this._mR/2 : (i%4 == 1 || i%4 == 3) ? this._mR/4 : 0; 1050 } 1051 else 1052 { 1053 var extra = (i%6 == 0) ? this._mR/3 : (i%6 == 1 || i%6 == 5) ? this._mR/6 : (i%6 == 2 || i%6 == 4) ? this._mR/9 : 0; 1054 } 1055 var offsetLen = this._mR+this._mR+extra; 1056 lx = this._xC + (offsetLen)*Math.cos(th); 1057 ly = this._yC + (offsetLen)*Math.sin(th); 1058 } 1059 1060 1061 // Get the papers references OR citations. 1062 var relPaper = Papercube.Paper.find(rGuid); 1063 1064 if(!relPaper) 1065 { 1066 var relPaperRefs = []; 1067 var relPaperCites = []; 1068 } 1069 else 1070 { 1071 var relPaperRefs = relPaper._attributes.references; 1072 var relPaperCites = relPaper._attributes.citations; 1073 } 1074 1075 // Draw the line to the circle based on the number of citations. 1076 1077 if(!this._lineColorCache[rGuid]) 1078 { 1079 if(displayRefs) 1080 { 1081 this._lineColorCache[rGuid] = this._lineColor(relPaperCites.length, NO); 1082 } 1083 else 1084 { 1085 this._lineColorCache[rGuid] = this._lineColor(relPaperRefs.length, YES); 1086 } 1087 } 1088 this._ctx.strokeStyle = this._lineColorCache[rGuid]; 1089 this._ctx.beginPath(); 1090 this._ctx.moveTo(this._xC,this._yC); 1091 this._ctx.lineTo(lx,ly); 1092 this._ctx.stroke(); 1093 1094 var cacheStr = guid+rGuid; 1095 1096 if(!this._htmlCache[cacheStr]) 1097 { 1098 var lineWidth = 1; 1099 if(displayRefs) 1100 { 1101 // if(relPaperRefs.length > 10) lineWidth = 4; 1102 var lineColor = this._lineColor(relPaperRefs.length, YES); 1103 } 1104 else 1105 { 1106 // if(relPaperCites.length > 10) lineWidth = 4; 1107 var lineColor = this._lineColor(relPaperCites.length, NO); 1108 } 1109 1110 this._genCircle(this._createLargeText(relPaper, ((isDetail) ? 'detail' : 'medium')), rGuid, cacheStr, lineColor); 1111 } 1112 this._setProperties(lx,ly, ((isDetail) ? this._detailCRadius : this._mediumCRadius), className, lineWidth, this._htmlCache[cacheStr]); 1113 1114 1115 var parentRad = (isDetail) ? this._detailCRadius : this._mediumCRadius; 1116 parentRad += (this._z*7); 1117 1118 // Draw the second level references or cites if you are not animating. 1119 var rels = this._filterByRels(relPaper); 1120 1121 if(rels && (rel.length < 20 || isDetail && !this._isAnimating) && (!this._isAnimating || (rels.length < 20 && !isDetail && !this._isAnimating) || isDetail)) 1122 { 1123 var rel_aIncr = (1/rels.length); 1124 var rel_tA = tA; 1125 var relsLength = rels.length; 1126 1127 // Iterate through the references or citations for the first level paper and draw circles for the second level. 1128 for(var j=0; j<relsLength; j++) 1129 { 1130 // If there are references or citations, then get the details. 1131 var theta = this._twoPI*rel_tA; 1132 var rel_lx = lx + parentRad*Math.cos(theta); 1133 var rel_ly = ly + parentRad*Math.sin(theta); 1134 rel_tA += rel_aIncr; 1135 var rrGuid = rels[j]; 1136 1137 var cacheStr = rGuid+rrGuid; 1138 1139 if(!this._htmlCache[cacheStr]) 1140 { 1141 var lineWidth = 1; 1142 if(displayRefs) 1143 { 1144 // if(relPaperRefs.length > 10) lineWidth = 4; 1145 var lineColor = this._lineColor(relPaperRefs.length, YES); 1146 } 1147 else 1148 { 1149 // if(relPaperCites.length > 10) lineWidth = 4; 1150 var lineColor = this._lineColor(relPaperCites.length, NO); 1151 } 1152 this._genCircle(this._createSmallText(Papercube.Paper.find(rrGuid)), rrGuid, cacheStr, lineColor); 1153 } 1154 1155 var className = (this._selection == rrGuid) ? 'small sel ' : 'small'; 1156 this._setProperties(rel_lx,rel_ly,this._smallCRadius, className, lineWidth, this._htmlCache[cacheStr]); 1157 1158 } 1159 } 1160 else 1161 { 1162 this._setPaperRefVisibilty(NO, rGuid); 1163 } 1164 1165 // Increment the angle for the next one. 1166 tA += aIncr; 1167 1168 } 1169 }, 1170 1171 /** 1172 Given a record, create the text DIV for a small circle. 1173 1174 @param record {Paper} The content object. 1175 1176 @returns {Boolean} Returns NO if there is no content. 1177 */ 1178 _createSmallText: function(record) 1179 { 1180 if(!record) return; 1181 1182 1183 // Print the authors in the standard abbreviated form. 1184 var authors = record.get('authorNames'); 1185 var string = ''; 1186 var aLen = authors.length; 1187 for(var i=0; i<aLen;i++) 1188 { 1189 var authorArray = authors[i].split(' '); 1190 string += authorArray[authorArray.length-1].substr(0,1); 1191 } 1192 if(string.length > 2) 1193 { 1194 string = string.substr(0,2)+"+"; 1195 } 1196 var div = document.createElement('div'); 1197 div.setAttribute('objGuid', record._attributes.guid); 1198 div.className = 's'; 1199 div.innerHTML = string; 1200 return div; 1201 }, 1202 1203 /** 1204 Given a record, create the text DIV for a large circle. Prints Title, authors, references, and citations. 1205 1206 @param record {Paper} The content object. 1207 @param type {string} The type of circle. Can be 'large', 'detail', or 'medium'. 1208 1209 @returns {Boolean} Returns NO if there is no content. 1210 */ 1211 _createLargeText: function(record, type) 1212 { 1213 if(!record) return NO; 1214 1215 var guid = record._attributes.guid; 1216 1217 var div = document.createElement('div'); 1218 div.setAttribute('objGuid', guid); 1219 div.className = type.substr(0,1); 1220 1221 var titleDiv = document.createElement('div'); 1222 titleDiv.setAttribute('objGuid', guid); 1223 titleDiv.setAttribute('type', type); 1224 titleDiv.className = 'title ' + type.substr(0,1); 1225 titleDiv.innerHTML = record.get("title"); 1226 1227 var authorsDiv = document.createElement('div'); 1228 authorsDiv.setAttribute('objGuid', guid); 1229 authorsDiv.className = 'authors ' + type.substr(0,1); 1230 1231 var authors = record.get("authorNames"); 1232 var aLen = authors.length; 1233 for(var i=0; i<aLen;i++) 1234 { 1235 authorsDiv.innerHTML +=authors[i]+'<br/>'; 1236 } 1237 1238 var relDiv = document.createElement('div'); 1239 relDiv.setAttribute('objGuid', guid); 1240 relDiv.className = 'rels ' + type.substr(0,1); 1241 relDiv.innerHTML =record.get("formattedReferenceCount") + '<br/>'+ record.get("formattedCitationCount"); 1242 1243 div.appendChild(titleDiv); 1244 div.appendChild(authorsDiv); 1245 div.appendChild(relDiv); 1246 return div; 1247 }, 1248 1249 /** 1250 Given a guid, find the shortest path and rotate the circle to it in that direction. 1251 1252 @param guid {string} The guid to rotate to. 1253 */ 1254 rotateTo: function(guid) 1255 { 1256 this._setSecondLevelVisibility(NO); 1257 this._rotateTo = YES; 1258 this._isAnimating = YES; 1259 this._rotateToGUID = guid; 1260 var rel = this._filterByRels(this._cachedContent); 1261 var relLen = rel.length; 1262 var detailIdX = rel.indexOf(this._detailGUID); 1263 var guidIdX = rel.indexOf(guid); 1264 1265 // Pick the closest direction to rotate. You don't want to waste too much time. 1266 var left = NO; 1267 var right = NO; 1268 var dist = 0; 1269 if(guidIdX > detailIdX) 1270 { 1271 if(guidIdX-detailIdX >= (relLen-guidIdX)+detailIdX) 1272 { 1273 dist = (relLen-guidIdX)+detailIdX; 1274 right = YES; 1275 } 1276 else 1277 { 1278 dist = guidIdX-detailIdX; 1279 left = YES; 1280 } 1281 } 1282 else 1283 { 1284 if(detailIdX-guidIdX >= (relLen-detailIdX)+guidIdX) 1285 { 1286 dist =(relLen-detailIdX)+guidIdX; 1287 left = YES; 1288 } 1289 else 1290 { 1291 dist = detailIdX-guidIdX; 1292 right = YES; 1293 } 1294 } 1295 1296 // Set the rotation speed. 1297 this._cRotationSpeed(dist); 1298 1299 // Rotate! 1300 if(left) 1301 { 1302 this.rotateLeft(YES); 1303 } 1304 else 1305 { 1306 this.rotateRight(YES); 1307 } 1308 }, 1309 1310 /** 1311 Hide or show the second level circles. 1312 1313 @param show {boolean} If YES, show circles, otherwise hide. 1314 1315 @returns {Boolean} Returns NO if there is no content. 1316 */ 1317 _setSecondLevelVisibility: function(show) 1318 { 1319 if(!this._cachedContent) return; 1320 var rel = this._filterByRels(this._cachedContent); 1321 var relLen = rel.length; 1322 for(var i=0; i<relLen; i++) 1323 { 1324 var paper = Papercube.Paper.find(rel[i]); 1325 if(paper) 1326 { 1327 var relGuid = rel[i]; 1328 var rels = this._filterByRels(paper); //(this._displayRefs) ? paper._attributes.references : paper._attributes.citations; 1329 var relsLen = rels.length; 1330 for(var j=0; j<relsLen; j++) 1331 { 1332 var cacheStr = relGuid+rels[j]; 1333 if(this._htmlCache[cacheStr]) 1334 { 1335 this._htmlCache[cacheStr].style.display = (show) ? '' : 'none'; 1336 } 1337 } 1338 } 1339 } 1340 }, 1341 1342 /** 1343 Given a paper guid, show or hide it. 1344 1345 @param show {boolean} If YES, show circle, otherwise hide. 1346 @param guid {string} The guid of the paper. 1347 1348 @returns {Boolean} Returns NO if there is no content. 1349 */ 1350 _setPaperRefVisibilty: function(show, guid) 1351 { 1352 var paper = Papercube.Paper.find(guid); 1353 if(!paper) return; 1354 var rel = this._filterByRels(paper); //(this._displayRefs) ? paper._attributes.references : paper._attributes.citations; 1355 var relLen = rel.length; 1356 for(var i=0; i<relLen; i++) 1357 { 1358 var cacheStr = guid+rel[i]; 1359 if(this._htmlCache[cacheStr]) 1360 { 1361 this._htmlCache[cacheStr].style.display = (show) ? '' : 'none'; 1362 } 1363 } 1364 }, 1365 1366 /** 1367 Rotate to the left. If the hasGUID property is YES, rotate to a destination guid. Otherwise, rotate to the next one. 1368 1369 @param hasGUID {boolean} If YES, that means that there is a guid to rotate to. 1370 */ 1371 rotateLeft: function(hasGUID) 1372 { 1373 if(!this._stopRotation) 1374 { 1375 if(!hasGUID) 1376 { 1377 var rel =this._filterByRels(this._cachedContent); //(this._displayRefs) ? this._cachedContent._attributes.references : this._cachedContent._attributes.citations; 1378 var idx = rel.indexOf(this._detailGUID); 1379 var guid = ''; 1380 if(idx == rel.length-1) 1381 { 1382 guid = rel[0]; 1383 } 1384 else 1385 { 1386 guid = rel[idx+1]; 1387 } 1388 1389 this.rotateTo(guid); 1390 } 1391 else 1392 { 1393 if(this._rotatingRight) 1394 { 1395 this.stopRotation(); 1396 } 1397 this._rotatingLeft = YES; 1398 this._isAnimating = YES; 1399 this._sAngle-=this._rotationSpeed; 1400 this._sAngle=this._sAngle%360; 1401 this._render(); 1402 this._rotationTimeout = setTimeout(this.rotateLeft.bind(this, YES), 30); 1403 } 1404 } 1405 else 1406 { 1407 this.stopRotation(); 1408 this._stopRotation = NO; 1409 this._rotateTo = NO; 1410 this._isAnimating = NO; 1411 } 1412 }, 1413 1414 /** 1415 Rotate to the right. If the hasGUID property is YES, rotate to a destination guid. Otherwise, rotate to the next one. 1416 1417 @param hasGUID {boolean} If YES, that means that there is a guid to rotate to. 1418 */ 1419 rotateRight: function(hasGUID) 1420 { 1421 if(!this._stopRotation) 1422 { 1423 if(!hasGUID) 1424 { 1425 var rel = this._filterByRels(this._cachedContent); 1426 var idx = rel.indexOf(this._detailGUID); 1427 var guid = ''; 1428 if(idx == 0) 1429 { 1430 guid = rel[rel.length-1]; 1431 } 1432 else 1433 { 1434 guid = rel[idx-1]; 1435 } 1436 this.rotateTo(guid); 1437 } 1438 else 1439 { 1440 if(this._rotatingLeft) 1441 { 1442 this.stopRotation(); 1443 } 1444 this._rotatingRight = YES; 1445 this._isAnimating = YES; 1446 1447 this._sAngle+=(this._slowRotation == YES) ? this._slowRotationSpeed : this._rotationSpeed; 1448 this._sAngle=this._sAngle%360; 1449 this._render(); 1450 this._rotationTimeout = setTimeout(this.rotateRight.bind(this, YES), 30); 1451 } 1452 } 1453 else 1454 { 1455 this.stopRotation(); 1456 this._stopRotation = NO; 1457 this._rotateTo = NO; 1458 this._isAnimating = NO; 1459 } 1460 }, 1461 1462 /** 1463 Stop the rotation. Then, if desired, render the view. 1464 1465 @param doNotRender {boolean} If YES, do not re-render the visualization. 1466 */ 1467 stopRotation: function(doNotRender) 1468 { 1469 this._setSecondLevelVisibility(YES); 1470 this._rotatingLeft = NO; 1471 this._rotatingRight = NO; 1472 this._isAnimating = NO; 1473 clearTimeout(this._rotationTimeout); 1474 if(!doNotRender) this._render(); 1475 }, 1476 1477 /** 1478 When the displayProperities binding changes, update the view appropriately. 1479 1480 @observes displayProperties 1481 1482 @param force {boolean} If YES, force a redraw. 1483 1484 @returns {Boolean} Returns NO if there is no guid of if the view is not visible. 1485 */ 1486 displayPropertiesDidChange: function(force) 1487 { 1488 1489 if(!this.get("isVisible")) return NO; 1490 1491 // Save display properties locally. 1492 var h = this.displayProperties.height; 1493 var w = this.displayProperties.width; 1494 var x = this.displayProperties.left; 1495 var y = this.displayProperties.top; 1496 var z = this.displayProperties.zoomValue; 1497 1498 var bail = NO; 1499 if(h == this._h && w == this._w && this._z == z) 1500 { 1501 if((x != this._x || y != this._y)) 1502 { 1503 bail = YES; 1504 } 1505 } 1506 1507 if(force) bail = NO; 1508 1509 this._h = h; 1510 this._w = w; 1511 this._x = x; 1512 this._y = y; 1513 this._z = z; 1514 1515 // Set the style and frame of the view. This replaces set('frame', {...}) due to performance reasons. 1516 this.setStyle({height: h+"px", width: w+'px', left: x+'px', top: y+'px'}); 1517 this._frame = {height: h, width: w, x: x, y: y}; 1518 1519 // Set the canvas dimensions. 1520 this.canvas.style.left = x+'px'; 1521 this.canvas.style.top = y+'px'; 1522 this.divCanvas.style.left = x+'px'; 1523 this.divCanvas.style.top = y+'px'; 1524 1525 // Save the properties as needed. 1526 //this.resizeWithOldParentSize(); 1527 1528 // Stop any rotation. 1529 this.stopRotation(YES); 1530 1531 if(bail) return; 1532 1533 // console.log(this._z +" and " + z); 1534 this.divCanvas.style.height = h+'px'; 1535 this.divCanvas.style.width = w+'px'; 1536 this.canvas.height = h; 1537 this.canvas.width = w; 1538 this._canvasHeight = h; 1539 this._canvasWidth = w; 1540 this._lineColorCache = {}; 1541 1542 var styleSheet = this._styleSheet; 1543 1544 // Set the default angle back. 1545 this._sAngle = -25; 1546 1547 // Calculate defaults for the positioning of the circle. 1548 this._xC = (this._canvasWidth/2)-(this._canvasWidth/8); 1549 this._yC = (this._canvasHeight/2);//*this._z; 1550 this._mR = Math.min((this._canvasWidth/9)/*this._z*/, (this._canvasHeight/6)); 1551 1552 1553 this._largeCRadius = this._mR*1.2; 1554 this._detailCRadius = this._mR*1.1; 1555 this._mediumCRadius = this._mR/(2+.08*this._numberOfFirstCircles); 1556 this._smallCRadius = this._mR/(8+.12*this._numberOfFirstCircles); 1557 this._lineWidth = 3*this._z; 1558 1559 1560 if(!SC.isChrome && SC.isSafari()) 1561 { 1562 // Set shadow. 1563 var shadow1 = (2*this._z); 1564 var shadow2 = (2*this._z); 1565 var shadowStr = shadow1+'px '+shadow1+'px '+shadow2+'px #222'; 1566 // this._styleSheet.cssRules[0].style['-webkit-box-shadow'] = shadowStr; 1567 styleSheet.cssRules[1].style['-webkit-box-shadow'] = shadowStr; 1568 styleSheet.cssRules[2].style['-webkit-box-shadow'] = shadowStr; 1569 styleSheet.cssRules[3].style['-webkit-box-shadow'] = shadowStr; 1570 } 1571 1572 // Calculate the diameter. 1573 var smallDiameter = (this._smallCRadius*2)+"px"; 1574 var mediumDiameter = (this._mediumCRadius*2)+"px"; 1575 var largeDiameter = (this._largeCRadius*2)+"px"; 1576 var detailDiameter = (this._detailCRadius*2)+"px"; 1577 1578 // For the large circles. 1579 var fontSize = (10*this._z)+"px"; 1580 1581 styleSheet.cssRules[10].style.fontSize = fontSize; 1582 styleSheet.cssRules[10].style.lineHeight = fontSize; 1583 1584 // Set diameters and border radius. 1585 styleSheet.cssRules[0].style.width = smallDiameter; 1586 styleSheet.cssRules[0].style.height = smallDiameter; 1587 styleSheet.cssRules[0].style['-webkit-border-radius'] = (this._smallCRadius)+"px"; 1588 1589 styleSheet.cssRules[1].style.width = mediumDiameter; 1590 styleSheet.cssRules[1].style.height = mediumDiameter; 1591 styleSheet.cssRules[1].style['-webkit-border-radius'] = (this._mediumCRadius)+"px"; 1592 1593 styleSheet.cssRules[2].style.width = largeDiameter; 1594 styleSheet.cssRules[2].style.height = largeDiameter; 1595 styleSheet.cssRules[2].style['-webkit-border-radius'] = (this._largeCRadius)+"px"; 1596 1597 styleSheet.cssRules[3].style.width = detailDiameter; 1598 styleSheet.cssRules[3].style.height = detailDiameter; 1599 styleSheet.cssRules[3].style['-webkit-border-radius'] =(this._detailCRadius)+"px"; 1600 1601 1602 var fontSize = this._smallCRadius-(4*this._z)+"px"; 1603 1604 // Set font size for the smaller circles. 1605 styleSheet.cssRules[4].style.fontSize = fontSize; 1606 styleSheet.cssRules[4].style.lineHeight = fontSize; 1607 1608 // Set small circle offset. 1609 styleSheet.cssRules[5].style.marginTop = (this._smallCRadius/2)+(2*this._z)+"px"; 1610 1611 // Set medium margins for title. 1612 styleSheet.cssRules[6].style.marginTop = (this._mediumCRadius-15*this._z)+"px"; 1613 styleSheet.cssRules[6].style.marginLeft = (5*this._z)+"px"; 1614 styleSheet.cssRules[6].style.marginRight = (5*this._z)+"px"; 1615 1616 // Set margin for all rels. 1617 styleSheet.cssRules[7].style.marginTop = (10*this._z)+"px"; 1618 1619 // Set large margins for title. 1620 styleSheet.cssRules[8].style.marginTop = (this._detailCRadius*.7)+"px"; 1621 styleSheet.cssRules[8].style.marginLeft = (5*this._z)+"px"; 1622 styleSheet.cssRules[8].style.marginRight = (5*this._z)+"px"; 1623 1624 // Set large margins for authors. 1625 styleSheet.cssRules[9].style.marginTop = (20*this._z)+"px"; 1626 styleSheet.cssRules[9].style.marginLeft = (5*this._z)+"px"; 1627 styleSheet.cssRules[9].style.marginRight = (5*this._z)+"px"; 1628 styleSheet.cssRules[9].style.fontSize = fontSize; 1629 1630 // Position the buttons. 1631 var dYC = this._yC + (this._mR*3)*Math.sin(this._twoPI*(-25/360)); 1632 this.rotateLeftButton.setStyle({left:(this._xC+this._mR*4.5) +"px", top: (dYC-20-this._mR)+"px"}); 1633 this.rotateRightButton.setStyle({left:(this._xC+this._mR*4.5) +"px", top: (dYC-20+this._mR/2)+"px"}); 1634 1635 // Render if you're not animating. 1636 if(!this._isAnimatingToMiddle) 1637 { 1638 this._render(); 1639 } 1640 1641 }.observes('displayProperties'), 1642 1643 /** 1644 Redraw based on 'content', 'isVisible', 'viewDirection', 'refThreshold', 'citeThreshold' binding changes. 1645 1646 @observes content 1647 @observes isVisible 1648 @observes viewDirection 1649 @observes refThreshold 1650 @observes citeThreshold 1651 1652 @returns {Boolean} Returns NO if there is no guid of if the view is not visible. 1653 */ 1654 redrawParamsDidChange: function() 1655 { 1656 var content = this.get('content'); 1657 1658 // If there is no content or if you're not visible, bail. 1659 if(!this.get("isVisible") || !content) return NO; 1660 1661 // Set the view direction. 1662 var displayRefChange = (this._displayRefs != (this.get("viewDirection") == "References")); 1663 this._displayRefs = (this.get("viewDirection") == "References"); 1664 1665 // Grab the cite/ref threshold. 1666 var citeThreshold = this.get('citeThreshold'); 1667 var refThreshold = this.get('refThreshold'); 1668 1669 if(this._cached_citeThreshold != citeThreshold || this._cached_refThreshold != refThreshold || displayRefChange) 1670 { 1671 this._filteredRefs = {}; 1672 this._filteredCites = {}; 1673 this._selection = []; 1674 } 1675 1676 this._cached_citeThreshold = citeThreshold; 1677 this._cached_refThreshold = refThreshold; 1678 1679 Papercube.canvasController.set("zoomValueMax", 2); 1680 Papercube.canvasController.set("zoomStep", .5); 1681 1682 1683 // Hide meta data view. 1684 this._hideMetaData(); 1685 1686 this.divCanvas.innerHTML = ''; 1687 this._guidCache = {}; 1688 this._htmlCache = {}; 1689 1690 1691 // Retrieve papers if needed. 1692 var rel = this._filterByRels(content); 1693 1694 if((this._displayRefs && content.get("maxRefLevel") < 2) || (!this._displayRefs && content.get("maxCiteLevel") < 2)) 1695 { 1696 Papercube.searchController.set('showRequestSpinner', YES); 1697 this._retrieveRecords(content, rel); 1698 } 1699 else 1700 { 1701 Papercube.searchController.set('showRequestSpinner', NO); 1702 } 1703 1704 this._minRefs = 0; 1705 this._minCites = 0; 1706 this._maxRefs = 0; 1707 this._maxCites = 0; 1708 this._lastContentGUID = content.get('guid'); 1709 1710 1711 var len = rel.length; 1712 for(var i=0; i<len; i++) 1713 { 1714 var c = Papercube.Paper.find(rel[i]); 1715 if(c) 1716 { 1717 var refs = this._filterByRels(c).length; 1718 var cites = this._filterByRels(c, YES).length; 1719 if(refs < this._minRefs) this._minRefs = refs; 1720 if(refs > this._maxRefs) this._maxRefs = refs; 1721 if(cites < this._minCites) this._minCites = cites; 1722 if(cites > this._maxCites) this._maxCites = cites; 1723 } 1724 } 1725 1726 // Save the number of references. 1727 this._numberOfFirstRefs = this._filterByRels(content).length; 1728 this._numberOfFirstCites = this._filterByRels(content, YES).length; 1729 1730 this._numberOfFirstCircles = (this._displayRefs) ? this._numberOfFirstRefs : this._numberOfFirstCites; 1731 1732 // Show the left of right button if there are references. 1733 if((this._displayRefs && this._numberOfFirstRefs > 0) || (!this._displayRefs && this._numberOfFirstCites > 0)) 1734 { 1735 this.rotateLeftButton.set("isVisible", YES); 1736 this.rotateRightButton.set("isVisible", YES); 1737 } 1738 if(!this._cachedContent || this._cachedContent.get('guid') != content.get('guid')) 1739 { 1740 Papercube.circleViewController.setDefaults(); 1741 this._cachedContent = content; 1742 } 1743 1744 // Stop any rotation. 1745 this.stopRotation(YES); 1746 1747 // Set the default angle back. 1748 this._sAngle = -25; 1749 1750 this.displayPropertiesDidChange(YES); 1751 1752 }.observes('content', 'isVisible', 'viewDirection', 'refThreshold', 'citeThreshold'), 1753 1754 /** 1755 When a level is retrieved from the server, redraw the view. 1756 1757 @returns {Boolean} Returns NO if there is no content. 1758 */ 1759 retrieveLevelDone: function() 1760 { 1761 var content = this.get('content'); 1762 if(!content) return NO; 1763 var level = content.get((this._displayRefs ? "maxRefLevel" : "maxCiteLevel")); 1764 content.set((this._displayRefs ? "maxRefLevel" : "maxCiteLevel"), level++); 1765 this.redrawParamsDidChange(); 1766 Papercube.searchController.set('showRequestSpinner', NO); 1767 }, 1768 1769 /** 1770 Retrieve the records for the view. Only load what is needed. 1771 1772 @private 1773 1774 @param content {Paper} The content object. 1775 @param rel {Array} Array of relation guids. 1776 */ 1777 _retrieveRecords: function(content, rel) 1778 { 1779 var firstLvl = Papercube.viewController.collectNeededPaperGUIDs(rel, []); 1780 var callBack = function() { this.retrieveLevelDone(); }.bind(this); 1781 1782 // If there are guids that need to be retrieved, retrieve them now. 1783 if(firstLvl.guids.length > 0) 1784 { 1785 // Call the server if needed. 1786 Papercube.adaptor.getPaperDetails(firstLvl.guids, callBack); 1787 } 1788 1789 // Otherwise iterate through the references that exist can get those papers. 1790 else 1791 { 1792 var papers = firstLvl.papers; 1793 var guids = []; 1794 1795 // Go through all papers and retrieve them. 1796 for(var i=0; i<papers.length; i++) 1797 { 1798 var secondLvl = Papercube.viewController.collectNeededPaperGUIDs((this._displayRefs) ? papers[i].get('references') : papers[i].get('citations'), guids); 1799 guids = secondLvl.guids; 1800 } 1801 1802 // If there are guids that need to be retrieved, retrieve them now. 1803 if(guids.length > 0) 1804 { 1805 // Call the server if needed. 1806 Papercube.adaptor.getPaperDetails(guids, callBack); 1807 } 1808 } 1809 }, 1810 1811 /** 1812 Initalization function. 1813 1814 Set up the canvas and rotation buttons. 1815 1816 Set up the fan menu actions: 1817 1818 smallFan: { 1819 CiteSeer: citeseerFunc, 1820 Save: saveFunc, 1821 Refocus: refocusFunc, 1822 "Zoom +": zoomInFunc, 1823 "Zoom -": zoomOutFunc 1824 }, 1825 mediumFan: { 1826 CiteSeer: citeseerFunc, 1827 Save: saveFunc, 1828 Refocus: refocusFunc, 1829 Detail: detailFunc, 1830 "Zoom +": zoomInFunc, 1831 "Zoom -": zoomOutFunc 1832 }, 1833 largeFan: { 1834 CiteSeer: citeseerFunc, 1835 Save: saveFunc, 1836 "Zoom +": zoomInFunc, 1837 "Zoom -": zoomOutFunc 1838 }, 1839 detailFan: { 1840 CiteSeer: citeseerFunc, 1841 Save: saveFunc, 1842 Refocus: refocusDetailFunc, 1843 "Zoom +": zoomInFunc, 1844 "Zoom -": zoomOutFunc 1845 } 1846 1847 */ 1848 init: function() 1849 { 1850 arguments.callee.base.apply(this,arguments); 1851 1852 // Get the default canvas context. 1853 this._ctx = this.canvas.getContext('2d'); 1854 1855 // Hide the rotation buttons until they are needed. 1856 this.rotateLeftButton.set("isVisible", NO); 1857 this.rotateRightButton.set("isVisible", NO); 1858 1859 var refocusDetailFunc = function(evt) 1860 { 1861 this.animateToMiddle(this._mouseDownGUID); 1862 }.bind(this); 1863 1864 var refocusFunc = function(evt) 1865 { 1866 this.animateToMiddle(this._mouseDownGUID); 1867 }.bind(this); 1868 1869 var citeseerFunc = function(evt) 1870 { 1871 window.open(Papercube.Paper.find(this._mouseDownGUID).get('url')); 1872 }.bind(this); 1873 1874 var saveFunc = function(evt) 1875 { 1876 Papercube.viewController.saveObject(this._mouseDownGUID, 'Paper'); 1877 }.bind(this); 1878 1879 var zoomOutFunc = function(evt) 1880 { 1881 Papercube.canvasController.setZoomToPointerLocation(Event.pointerX(evt), Event.pointerY(evt), NO); 1882 }.bind(this); 1883 var zoomInFunc = function(evt) 1884 { 1885 Papercube.canvasController.setZoomToPointerLocation(Event.pointerX(evt), Event.pointerY(evt), YES); 1886 }.bind(this); 1887 1888 var detailFunc = function(evt) 1889 { 1890 this._rotateFromGUID = this._detailGUID; 1891 this.rotateTo(this._mouseDownGUID); 1892 }.bind(this); 1893 1894 Papercube.canvasController.registerFans('circleview', 1895 { 1896 smallFan: { 1897 CiteSeer: citeseerFunc, 1898 Save: saveFunc, 1899 Refocus: refocusFunc, 1900 "Zoom +": zoomInFunc, 1901 "Zoom -": zoomOutFunc 1902 }, 1903 mediumFan: { 1904 CiteSeer: citeseerFunc, 1905 Save: saveFunc, 1906 Refocus: refocusFunc, 1907 Detail: detailFunc, 1908 "Zoom +": zoomInFunc, 1909 "Zoom -": zoomOutFunc 1910 }, 1911 largeFan: { 1912 CiteSeer: citeseerFunc, 1913 Save: saveFunc, 1914 "Zoom +": zoomInFunc, 1915 "Zoom -": zoomOutFunc 1916 }, 1917 detailFan: { 1918 CiteSeer: citeseerFunc, 1919 Save: saveFunc, 1920 Refocus: refocusDetailFunc, 1921 "Zoom +": zoomInFunc, 1922 "Zoom -": zoomOutFunc 1923 } 1924 }); 1925 1926 // make a new stylesheet 1927 var ns = document.createElement('style'); 1928 document.getElementsByTagName('head')[0].appendChild(ns); 1929 1930 // Safari does not see the new stylesheet unless you append something. 1931 // However! IE will blow chunks, so ... filter it thusly: 1932 if (!window.createPopup) { 1933 ns.appendChild(document.createTextNode('')); 1934 } 1935 var s = this._styleSheet = document.styleSheets[document.styleSheets.length - 1]; 1936 1937 var rules = { 1938 ".circle_view_tab .small":"{position: absolute;background-color: #eee;-moz-border-radius: 100px;overflow: hidden;}", 1939 ".circle_view_tab .medium":"{position: absolute;background-color: lightgray;-moz-border-radius: 10000px;overflow: hidden;}", 1940 ".circle_view_tab .large":"{position: absolute;background-color: lightblue;-moz-border-radius: 10000px;overflow: hidden;}", 1941 ".circle_view_tab .detail":"{position: absolute;background-color: lightgray;-moz-border-radius: 10000px;overflow: hidden;}", 1942 ".circle_view_tab .div-canvas div":"{text-align: center;cursor: default;}", 1943 ".circle_view_tab .small div ":"{}", 1944 ".circle_view_tab .medium div.title ":"{}", 1945 ".circle_view_tab div.rels ":"{}", 1946 ".circle_view_tab .large div.title, .circle_view_tab .detail div.title ":"{}", 1947 ".circle_view_tab .large div.authors, .circle_view_tab .detail div.authors ":"{}", 1948 ".circle_view_tab .div-canvas .large div, .circle_view_tab .div-canvas .detail div ":"{}" 1949 }; 1950 1951 // loop through and insert 1952 for (selector in rules) { 1953 if (s.insertRule) { 1954 // it's an IE browser 1955 try { 1956 s.insertRule(selector + rules[selector], s.cssRules.length); 1957 } catch(e) {} 1958 } else { 1959 // it's a W3C browser 1960 try { 1961 s.addRule(selector, rules[selector]); 1962 } catch(e) {} 1963 } 1964 } 1965 } 1966 }); 1967