1 // ==========================================================================
  2 // Papercube.AuthorStatView
  3 //
  4 // License:  PaperCube is open source software released under 
  5 //           the MIT License (see license.js)
  6 // ==========================================================================
  7 
  8 require('core');
  9 require('views/paperstatlist');
 10 
 11 /** @class
 12 
 13   This is a more classical web-based view on an author's citation and references.
 14 
 15   @extends SC.View
 16   @author Peter Bergstrom
 17   @version 1.0
 18   @copyright 2008-2009 Peter Bergström.
 19 */
 20 Papercube.AuthorStatView = SC.View.extend(
 21 /** @scope Papercube.AuthorStatView.prototype */ {
 22 
 23   /**
 24     Collaborator sort order binding.
 25   
 26     @property {String}
 27     @binding 'Papercube.authorStatController.collaboratorSortOrder'
 28   */
 29   collaboratorSortOrderBinding: 'Papercube.authorStatController.collaboratorSortOrder',
 30 
 31   /**
 32     Paper sort order binding.
 33   
 34     @property {String}
 35     @binding 'Papercube.authorStatController.paperSortOrder'
 36   */
 37   paperSortOrderBinding: 'Papercube.authorStatController.paperSortOrder',
 38 
 39   /**
 40     Reference author sort order binding.
 41   
 42     @property {String}
 43     @binding 'Papercube.authorStatController.refAuthorSortOrder'
 44   */
 45   refAuthorSortOrderBinding: 'Papercube.authorStatController.refAuthorSortOrder',
 46 
 47   /**
 48     Citation author sort order binding.
 49   
 50     @property {String}
 51     @binding 'Papercube.authorStatController.citeAuthorSortOrder'
 52   */
 53   citeAuthorSortOrderBinding: 'Papercube.authorStatController.citeAuthorSortOrder',
 54 
 55 
 56   /**
 57     Outlets for author stat view.
 58   
 59     [
 60       'nameLabel',
 61       'collaboratorCount', 
 62       'paperCount', 
 63       'refAuthorCount', 
 64       'citeAuthorCount',
 65       'collaboratorList', 
 66       'paperList',
 67       'refAuthorList', 
 68       'citeAuthorList',
 69       'papersPerYear', 
 70       'graph', 
 71       'stats'
 72     ]
 73 
 74   */
 75   outlets:  [
 76               'nameLabel',
 77               'collaboratorCount', 
 78               'paperCount', 
 79               'refAuthorCount', 
 80               'citeAuthorCount',
 81               'collaboratorList', 
 82               'paperList',
 83               'refAuthorList', 
 84               'citeAuthorList',
 85               'papersPerYear', 
 86               'graph', 
 87               'stats'
 88             ],
 89 
 90   /**
 91     Name label. Bound to the '.name?' element.
 92 
 93     @outlet {DOM Element} '.name?'
 94   */
 95   nameLabel: '.name?',
 96 
 97   /**
 98     Collaborator count. Bound to the '.collaborator-count?' element.
 99 
100     @outlet {DOM Element} '.collaborator-count?' 
101   */
102   collaboratorCount: '.collaborator-count?',
103 
104   /**
105     Paper count. Bound to the '.paper-count?' element.
106 
107     @outlet {DOM Element} '.paper-count?'
108   */
109   paperCount: '.paper-count?',
110 
111   /**
112     refAuthor count. Bound to the '.refauthor-count?' element.
113 
114     @outlet {DOM Element} '.refauthor-count?'
115   */
116   refAuthorCount: '.refauthor-count?',
117 
118   /**
119     refAuthor count. Bound to the '.citeauthor-count?' element.
120 
121     @outlet {DOM Element} '.citeauthor-count?'
122   */
123   citeAuthorCount: '.citeauthor-count?',
124 
125   /**
126     papersPerYear graph box. Bound to the '.papersperyear?' element.
127 
128     @outlet {DOM Element} '.papersperyear?'
129   */
130   papersPerYear: '.papersperyear?',
131 
132   /**
133     papersPerYear graph itself. Bound to the '.data?' element.
134 
135     @outlet {DOM Element} '.data?'
136   */
137   graph: '.data?',
138 
139   /**
140     The container for the stat view boxes. Is set to display none by default so that the boxes don't show when there isn't any content. 
141     Bound to the '.stats?' element.
142 
143     @outlet {DOM Element} '.stats?'
144   */
145   stats: '.stats?',
146   
147   /**
148     paperList view. Shows the author's papers. Bound to the '.paper-list?' element.
149 
150     @outlet {Papercube.PaperstatListView} '.paper-list?'
151   */
152   paperList: Papercube.PaperstatListView.outletFor('.paper-list?'),
153 
154   /**
155     collaboratorList view. Shows the author's collaborators. Bound to the '.collaborator-list?' element.
156 
157     @outlet {Papercube.PaperstatListView} '.collaborator-list?'
158   */
159   collaboratorList: Papercube.PaperstatListView.extend({isAuthorList: YES}).outletFor('.collaborator-list?'),
160 
161   /**
162     refAuthorList view. Shows the authors that the author has referenced. Bound to the '.refauthor-list?' element.
163 
164     @outlet {Papercube.PaperstatListView} '.refauthor-list?'
165   */
166   refAuthorList: Papercube.PaperstatListView.extend({isAuthorList: YES}).outletFor('.refauthor-list?'),
167 
168   /**
169     citeAuthorList view. Shows the authors that have cited the author. Bound to the '.refauthor-list?' element.
170 
171     @outlet {Papercube.PaperstatListView} '.citeauthor-list?'
172   */
173   citeAuthorList: Papercube.PaperstatListView.extend({isAuthorList: YES}).outletFor('.citeauthor-list?'),
174   
175   /**
176     The guid of the last clicked paper or author.
177     
178     @property {String}
179   */
180   _mouseDownGUID: null,
181 
182 
183   /**
184     Reset when new content is set. Is set to YES once the call for the server for additional author data is initiated.
185     
186     @property {Boolean}
187     @default NO
188   */
189   _calledForAuthors: NO,
190 
191   /**
192     Reset when new content is set. Is set to YES once the call for the server for additional paper data is initiated.
193         
194     @property {Boolean}
195     @default NO
196   */
197   _calledForPapers: NO,
198   
199   /**
200     Re-render the view when the content, sort order changes, or view is set to be visible.
201 
202     @observes content
203     @observes isVisible
204     @observes paperSortOrder
205     @observes collaboratorSortOrder
206     @observes refAuthorSortOrder
207     @observes citeAuthorSortOrder
208   */
209   contentDidChange: function()
210   {
211     
212     var content = this.get('content');
213 
214     // If there is no content or if you're not visible, bail.
215     if(!this.get("isVisible") || !content) return;
216         
217     // Show the stats div.    
218     this.stats.style.display = '';
219 
220     // If there is a new content, reset the server call variables, cache the content, and clear the graph.
221     if(!this._cachedContent || this._cachedContent.get('guid') != content.get('guid'))
222     {
223       this.papersPerYear.className = 'papersperyear';
224       this.papersPerYear.className = 'papersperyear show';
225       this._cachedContent = content;
226       this._calledForAuthors = NO;
227       this._calledForPapers = NO;
228       this._clearGraph();
229     }
230     
231     var callBack = function() { this.contentDidChange(); }.bind(this);
232     
233     // Get authors for collabs, refs, and cites.
234     if(!this._calledForAuthors)
235     {
236       var authGuids = [];
237       authGuids = authGuids.concat(this._collectAuthors(content.get('collaborators')));
238       authGuids = authGuids.concat(this._collectAuthors(content.get('refAuthors')));
239       authGuids = authGuids.concat(this._collectAuthors(content.get('citeAuthors')));
240     
241       if(authGuids.length > 0)
242       {
243         Papercube.adaptor.getAuthorDetails(authGuids, callBack);
244       }
245       this._calledForAuthors = YES;
246     }
247     
248     // Render the lists.
249     this._renderList('collaborators');
250     this._renderList('refAuthors');
251     this._renderList('citeAuthors');
252 
253     // Get papers for the author.
254     var shouldLoadPapers = NO;
255     if(!this._calledForPapers)
256     {
257       //console.log('called for papers');
258       
259       var paperGuids = [];
260       var papers = content.get('papers');
261       for(var i=0; i<papers.length; i++)
262       {
263         var p = Papercube.Paper.find(papers[i]);
264         if(!p)
265         {
266           paperGuids.push(papers[i]);
267         }
268       }
269     
270       var shouldLoadPapers = (paperGuids.length > 0);
271       
272       if(shouldLoadPapers)
273       {
274         Papercube.adaptor.getPaperDetails(paperGuids, callBack);
275       }
276 
277       this._calledForPapers = YES;
278     }
279 
280     if(!shouldLoadPapers)  
281       this._renderGraph();
282     
283     // Render paper list.
284     this._renderList('papers');
285     
286     // Reset zoom value.
287     Papercube.canvasController.set("zoomValueMax", 1);
288     Papercube.canvasController.set("zoomStep", 1);
289     
290     // Set the title and year.
291     this.nameLabel.innerHTML = content.get('name').escapeHTML();
292     
293   }.observes('content', 'isVisible', 'paperSortOrder', 'collaboratorSortOrder', 'refAuthorSortOrder', 'citeAuthorSortOrder'),
294 
295   /**
296     Create the request and call the server. 
297   
298     @param auths {Array} The author relation array.
299     
300     @returns {Array} Returns a list of guids that are not in the data store.
301   */
302   _collectAuthors: function(auths)
303   {
304     var authGuids = [];
305     for(var i=0; i<auths.length; i++)
306     {
307       var a = Papercube.Author.find(auths[i][0]);
308       if(!a)
309       {
310         authGuids.push(auths[i][0]);
311       }
312     }
313     return authGuids;
314   },
315 
316   /**
317     Render a list of papers or authors.
318   
319     @param type {string} The type of the list shown. Either 'collaborators', 'papers', 'refAuthors', or 'citeAuthors'.
320     
321     @returns {Boolean} Returns NO if there is no content.
322   */
323   _renderList: function(type)
324   {
325     var content = this.get('content');
326     if(!content)
327     {
328       if(type == 'collaborators')
329       {
330          this.collaboratorList.set("content", []);
331       }
332       else if(type == "papers")
333       {
334         this.paperList.set("content", []);
335       }
336       else if(type == "refAuthors")
337       {
338         this.refAuthorList.set("content", []);
339       }
340       else if(type == "citeAuthors")
341       {
342         this.citeAuthorList.set("content", []);
343       }
344       return NO;
345     }
346     
347     // If the type is papers, get the papers, sort them, then set the content.
348     if(type == 'papers')
349     {
350     
351       // Get the papers to sort.
352       var rel = content.get('papers');
353     
354       var papers = [];
355       for(var i=0; i<rel.length; i++)
356       {
357         var paper = Papercube.Paper.find(rel[i]);
358         if(paper)
359         {
360           papers.push(paper);
361         }
362       }
363       
364       var sortType = this.get('paperSortOrder');
365 
366       // Sort the array.
367       // 'Title', 'Year', 'References', 'Citations', 'Num Authors'
368       switch(sortType)
369       {
370         case 'Title':
371           papers.sort(function(a, b) 
372           { 
373             var aTitle = a._attributes.title.toLowerCase();
374             var bTitle = b._attributes.title.toLowerCase();
375             if(aTitle == bTitle) return 0;
376             if(aTitle < bTitle) return -1;
377             return 1;
378           });
379           break;
380         case 'Date':
381           papers.sort(function(a, b) 
382           { 
383             var aDate = a.get('year'); 
384             var bDate = b.get('year');
385             if(aDate == bDate) 
386               return a._attributes.title.toLowerCase() < b._attributes.title.toLowerCase();        
387             if(aDate > bDate) return -1;
388             return 1;
389           });
390           break;
391         case 'Num Refs':
392         case 'Num Cites':
393         case 'Num Authors':
394           var sortKey = (sortType == 'Num Refs') ? 'refCount' : (sortType == 'Num Cites') ? 'citeCount' : 'authorCount';
395           papers.sort(function(a, b) 
396           { 
397             var aCnt = a.get(sortKey);
398             var bCnt = b.get(sortKey);
399             if(aCnt == bCnt) return 0;
400             if(aCnt > bCnt) return -1;
401             return 1;
402           });
403           break;
404       }
405  
406       this.paperCount.innerHTML = ' (' + papers.length+')';
407       this.paperList.set("content", papers);
408       this.paperList.setClassName('show', (papers.length != 0));
409     }
410 
411     // If the type is authors, get the authors, sort them, then set the content.
412     else
413     {
414       
415       // Get the authors to be sorted.
416       var auths = content.get(type);
417     
418       var authors = [];
419       for(var i=0; i<auths.length; i++)
420       {
421         var author = Papercube.Author.find(auths[i][0]);
422         if(author)
423         {
424           authors.push(author);
425         }
426       }
427       
428       // Pick the sort type to read from.
429       var sortType = (type == "collaborators") ? this.get('collaboratorSortOrder') : (type == "refAuthors") ? this.get('refAuthorSortOrder') : this.get("citeAuthorSortOrder");
430       
431       // Sort the array.
432       // 'Title', 'Year', 'References', 'Citations', 'Num Authors'
433       switch(sortType)
434       {
435         case 'Name':
436           authors.sort(function(a, b) 
437           { 
438             var aName = a._attributes.name.toLowerCase();
439             var bName = b._attributes.name.toLowerCase();
440             if(aName == bName) return 0;
441             if(aName < bName) return -1;
442             return 1;
443           });
444           break;
445         case 'Num Papers':
446           authors.sort(function(a, b) 
447           { 
448             var aCnt = a.get('paperCount');
449             var bCnt = b.get('paperCount');
450             if(aCnt == bCnt) return 0;
451             if(aCnt > bCnt) return -1;
452             return 1;
453           });
454           break;
455         case 'Num Collabs':
456         case 'Cite Count':
457           var sortKey = (sortType == 'Num Collabs') ? 'collaborators' : 'citeAuthors';
458           authors.sort(function(a, b) 
459           { 
460             var aCnt = a.get(sortKey).length;
461             var bCnt = b.get(sortKey).length;
462             if(aCnt == bCnt) return 0;
463             if(aCnt > bCnt) return -1;
464             return 1;
465           });
466           break;
467       }
468       
469       // Set the content to the appropriate list and count label.
470       if(type == "collaborators")
471       {
472         this.collaboratorCount.innerHTML = ' (' + authors.length+')';
473         this.collaboratorList.set("content", authors);
474         this.collaboratorList.setClassName('show', (authors.length != 0));
475         
476       }
477       else if(type == "refAuthors")
478       {
479         this.refAuthorCount.innerHTML = ' (' + authors.length+')';
480         this.refAuthorList.set("content", authors);
481         this.refAuthorList.setClassName('show', (authors.length != 0));
482         
483       }
484       else
485       {
486         this.citeAuthorCount.innerHTML = ' (' + authors.length+')';
487         this.citeAuthorList.set("content", authors);
488         this.citeAuthorList.setClassName('show', (authors.length != 0));
489       }
490     }
491   },
492   
493   /**
494     Render the graph.
495   
496   */
497   _renderGraph: function()
498   {
499     // Clear the graph.
500     this._clearGraph();
501     var rel = this._cachedContent.get('papers');
502     var perYearCountHash = {};
503     var numYears = 0;
504     var maxPapers = 0;
505 
506     // Loop through the relations and collect the items by year.
507     for(var i=0; i<rel.length; i++)
508     {
509       var paper = Papercube.Paper.find(rel[i]);
510       var yr = '?';
511       if(paper)
512         yr = paper.get('year');
513         
514       if(!perYearCountHash[yr])
515       {
516         numYears ++;
517         perYearCountHash[yr] = 0;
518       }
519       perYearCountHash[yr]++;
520       if(perYearCountHash[yr] > maxPapers)
521       {
522         maxPapers = perYearCountHash[yr];
523       }
524     }
525 
526     // Flatten to an array.
527     var years = [];
528     for(var key in perYearCountHash)
529     {
530       years.push([key, perYearCountHash[key]]);
531     }
532     
533     // Sort the years.
534     years.sort(function(a,b){ if(a[0]<b[0]) return 1; return -1;});
535     
536     var html = '';
537 
538     // Calculate the graph dimensions.
539     var height = 135;
540     var width = 500;
541     var rowHeight = height/numYears;
542     var rowWidth = width/maxPapers;
543     var barHeight = (rowHeight-2*rowHeight/3);
544     var halfBarHeight = ((barHeight/2)-12/2);
545 
546     // Loop through the year array and render the HTML.
547     for(var i=0; i<years.length; i++)
548     {
549       var count = years[i][1];
550       var str = (count*rowWidth > 50) ? Papercube.pluralizeString(' Paper', count)+' ' : '';
551       html+= '<div class="row" style="height:'+rowHeight+'px;"><div class="year" style="margin-top:'+((rowHeight/2)-11/2)+'px; height:'+(rowHeight-2*rowHeight/3)+'px;">'+years[i][0]+
552              '</div><div class="bar" style="padding-top: '+halfBarHeight+'px; margin-top:'+(rowHeight-2*rowHeight/3)+'px; width:'+(count*rowWidth)+'px; height:'+barHeight+'px;">'+str+'</div></div>';
553     }
554     
555     // Set the HTML.
556     this.graph.innerHTML = html;
557   },
558   
559   /**
560     Clear the graph innerHTML.
561 
562   */
563   _clearGraph: function()
564   {
565     this.graph.innerHTML = '';
566   },
567   
568   /** 
569     Save the GUID of the element and then show the fan.
570     
571     @param {DOM Event} evt The mouseDown event.
572   */
573   mouseDown: function(evt)
574   {
575     var guid = evt.target.getAttribute('guid');
576     var type = evt.target.getAttribute('fanType');
577     if(guid)
578     {
579       this._mouseDownGUID = guid;
580       Papercube.canvasController.showFan(Event.pointerX(evt), Event.pointerY(evt), 'paperstat', (type+"Fan"));
581       return YES;
582     }
583     return NO;
584   }, 
585 
586   /**
587     Initalization function.
588     
589     Set up the fan menu actions:
590       
591     authorFan: {
592       Save: saveFuncAuth,
593       Refocus: refocusFuncAuth
594     }, 
595     paperFan: {
596       CiteSeer: citeseerFunc,
597       Save: saveFunc,
598       Refocus: refocusFunc
599     }     
600 
601   */
602   init: function()
603   {
604     sc_super();
605     
606     var refocusFunc = function(evt)
607     { 
608       Papercube.viewController.setContentToViewFromGUID(this._mouseDownGUID, 'Paper', NO);
609     }.bind(this); 
610 
611     var refocusFuncAuth = function(evt)
612     { 
613       Papercube.viewController.viewAuthor(this._mouseDownGUID);
614     }.bind(this); 
615 
616     var citeseerFunc = function(evt)
617     { 
618       window.open(Papercube.Paper.find(this._mouseDownGUID).get('url'));
619     }.bind(this); 
620 
621     var saveFunc = function(evt)
622     { 
623       Papercube.viewController.saveObject(this._mouseDownGUID, 'Paper');
624     }.bind(this); 
625 
626 
627     var saveFuncAuth = function(evt)
628     { 
629       Papercube.viewController.saveObject(this._mouseDownGUID, 'Author');
630     }.bind(this);
631 
632 
633     // Register two fans: authors and papers.
634     Papercube.canvasController.registerFans('authorstat',
635     {
636         authorFan: {
637           Save: saveFuncAuth,
638           Refocus: refocusFuncAuth
639         }, 
640         paperFan: {
641           CiteSeer: citeseerFunc,
642           Save: saveFunc,
643           Refocus: refocusFunc
644         }     
645     });
646   }
647 }) ;
648