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