HistogramHeatGraph_html5.user.js

ニコニコ動画のコメントをグラフで表示(html5版)※コメントをリロードすることでグラフを再描画します

Fra 04.12.2017. Se den seneste versjonen.

  1. // ==UserScript==
  2. // @name HistogramHeatGraph_html5.user.js
  3. // @namespace sotoba
  4. // @version 1.1.1.20171204
  5. // @description ニコニコ動画のコメントをグラフで表示(html5版)※コメントをリロードすることでグラフを再描画します
  6. // @match http://www.nicovideo.jp/watch/*
  7. // @include http://www.nicovideo.jp/watch/*
  8. // @require https://code.jquery.com/jquery-3.2.1.min.js
  9. // @grant none
  10. // ==/UserScript==
  11.  
  12. (function () {
  13. 'use strict';
  14. // default settings
  15. var NicoHeatGraph = function(){
  16. this.MINIMUMBARNUM=50;
  17. this.DEFAULTINTERBAL=10;
  18. this.MAXCOMMENTNUM=30;
  19. this.GRAPHHEIGHT = 30;
  20. this.GRAPHDEFWIDTH=856;
  21. this.BUTTONSIZE=15;
  22. this.barIndexNum=0;
  23. this.$canvas=null;
  24.  
  25. this.$commentgraph=$('<div>').attr('id', 'comment-graph');
  26. this.$commentlist=$('<div>').attr('id', 'comment-list');
  27.  
  28. };
  29. //draw background of graph
  30. NicoHeatGraph.prototype.drawCoordinate = function(){
  31. const $commentgraph = this.$commentgraph;
  32. const $commentlist = this.$commentlist;
  33. this.$canvas=$("#CommentRenderer").children('canvas').eq(0);
  34. $('.PlayerContainer').eq(0).append($commentgraph);
  35. $('.MainContainer').eq(0).append($commentlist);
  36. const styleString = `
  37. #comment-graph :hover{
  38. -webkit-filter: hue-rotate(180deg);
  39. filter: hue-rotate(180deg);
  40. }
  41. #comment-list:empty {
  42. display: none;
  43. }
  44. `;
  45. const style = document.createElement('style');
  46. style.appendChild(document.createTextNode(styleString));
  47. document.body.appendChild(style);
  48. var playerWidth =parseFloat(this.$canvas.css("width"))|this.GRAPHDEFWIDTH;
  49. $commentgraph.height(this.GRAPHHEIGHT);
  50. $commentgraph.width( playerWidth );
  51. $commentgraph.css({
  52. background:'repeating-linear-gradient(to top, #000, #111 5px)',
  53. border: '1px solid #000',
  54. borderTo: 0,
  55. float: 'left',
  56. fontSize: 0,
  57. whiteSpace: 'nowrap',
  58. });
  59. $commentlist.css({
  60. background: '#000',
  61. color: '#fff',
  62. fontSize: '12px',
  63. lineHeight: 1.25,
  64. padding: '4px 4px 0',
  65. pointerEvents:' none',
  66. position: 'absolute',
  67. zIndex: 9999,
  68. });
  69. };
  70. function getCommentData() {
  71. var ApiJsonData=JSON.parse(document.getElementById('js-initial-watch-data').getAttribute('data-api-data'));
  72. var thread_id;
  73. var video_id;
  74. var user_id;
  75. if(ApiJsonData.video.dmcInfo !== null){
  76. thread_id=ApiJsonData.video.dmcInfo.thread.thread_id;
  77. video_id=ApiJsonData.video.dmcInfo.video.video_id;
  78. user_id=ApiJsonData.video.dmcInfo.user.user_id;
  79. }else{
  80. thread_id=ApiJsonData.thread.ids.default;
  81. video_id=ApiJsonData.video.id;
  82. user_id=ApiJsonData.viewer.id;
  83. }
  84.  
  85. if(video_id.startsWith('sm')||video_id.startsWith('nm')){
  86. return $.ajax({
  87. url:'http://nmsg.nicovideo.jp/api/thread?thread='+thread_id+'&version=20061206&res_from=-1000&scores=1',
  88. type:'GET',
  89. dataType:'xml'
  90. });
  91. }else{
  92. return $.ajax({
  93. url:'http://flapi.nicovideo.jp/api/getthreadkey?thread='+thread_id,
  94. type:'GET',
  95. }).then(function(response){
  96. return $.ajax({
  97. url:'http://nmsg.nicovideo.jp/api/thread?thread='+thread_id+'&version=20061206&res_from=-1000&scores=1&user='+user_id+'&'+response,
  98. type:'GET',
  99. dataType:'xml'
  100. });
  101. });
  102. }
  103. }
  104. //draw bars make comment list
  105. NicoHeatGraph.prototype.drowgraph = function(commentData,$canvas){
  106. const $commentgraph = this.$commentgraph;
  107. const $commentlist = this.$commentlist;
  108. var ApiJsonData=JSON.parse(document.getElementById('js-initial-watch-data').getAttribute('data-api-data'));
  109. var playerWidth =parseFloat($canvas.css("width"));
  110. var videoTotalTime = ApiJsonData.video.dmcInfo !== null ? ApiJsonData.video.dmcInfo.video.length_seconds : ApiJsonData.video.duration;
  111. var barTimeInterval;
  112.  
  113. //TODO 非常に長い(2,3時間以上)動画の処理
  114. //長い動画
  115. if(videoTotalTime > this.MINIMUMBARNUM*this.DEFAULTINTERBAL){
  116. barTimeInterval=this.DEFAULTINTERBAL;
  117. this.barIndexNum=Math.ceil(videoTotalTime / barTimeInterval);
  118. //普通の動画
  119. }else if(videoTotalTime>this.MINIMUMBARNUM){
  120. this.barIndexNum=this.MINIMUMBARNUM;
  121. barTimeInterval=videoTotalTime/this.MINIMUMBARNUM;
  122. }else{
  123. //MINIMUMBARNUM秒以下の短い動画
  124. this.barIndexNum=Math.floor(videoTotalTime);
  125. barTimeInterval=1;
  126. }
  127.  
  128. $commentgraph.width( playerWidth );
  129. const barColors = [
  130. '003165', '00458f', '0058b5','005fc4', '006adb',
  131. '0072ec', '007cff', '55a7ff','3d9bff'
  132. ];
  133. var listCounts = (new Array(this.barIndexNum+1)).fill(0);
  134. var listMessages = (new Array(this.barIndexNum+1)).fill("");
  135. var listTimes = (new Array(this.barIndexNum+1)).fill("");
  136. var lastBarTimeIntervalGap = Math.floor(videoTotalTime- (this.barIndexNum * barTimeInterval));
  137. var barWidth = playerWidth / this.barIndexNum;
  138. var barTimePoint = 0;
  139.  
  140. const MAXCOMMENTNUM=this.MAXCOMMENTNUM;
  141.  
  142. $(commentData).find('chat').each(function(index){
  143. let vpos = $(this).attr('vpos')/100;
  144. //動画長を超えた時間のpostがある
  145. if (videoTotalTime<=vpos){
  146. vpos=videoTotalTime;
  147. }
  148. let section=Math.floor(vpos/barTimeInterval);
  149. listCounts[section]++;
  150. if(listCounts[section]<= MAXCOMMENTNUM){
  151. let comment=$(this).text().replace(/"|<|&lt;/g, ' ').replace(/\n/g, '<br>');
  152. listMessages[section]+=comment+'<br>';
  153. }
  154. });
  155.  
  156. let starttime=0;
  157. let nexttime=0;
  158. for (var i = 0; i < this.barIndexNum; i++) {
  159. starttime=nexttime;
  160. nexttime+=barTimeInterval;
  161. if(i==this.barIndexNum-1){
  162. nexttime+=lastBarTimeIntervalGap;
  163. }
  164. let startmin=Math.floor(starttime/60);
  165. let startsec=Math.floor(starttime-startmin*60);
  166. let endmin=Math.floor(nexttime/60);
  167. let endsec=Math.ceil(nexttime-endmin*60);
  168. if(59 < endsec){
  169. endmin+=1;
  170. endsec-=60;
  171. }
  172. listTimes[i] += `${("0"+startmin).slice(-2)}:${("0"+startsec).slice(-2)}-${("0"+endmin).slice(-2)}:${("0"+endsec).slice(-2)}`;
  173. }
  174.  
  175. // TODO なぜかthis.barIndexNum以上の配列ができる
  176. listCounts=listCounts.slice(0, this.barIndexNum);
  177. var listCountMax = Math.max.apply(null,listCounts);
  178. const barColorRatio = (barColors.length - 1) / listCountMax;
  179.  
  180. $commentgraph.empty();
  181. $commentgraph.height(this.GRAPHHEIGHT);
  182. var barColor;
  183. var barBackground;
  184. for (i = 0; i < this.barIndexNum; i++) {
  185. barColor = barColors[Math.floor(listCounts[i] * barColorRatio)];
  186. barBackground = `linear-gradient(to top, #${barColor}, #${barColor} ` +
  187. `${listCounts[i]}px, transparent ${listCounts[i]}px, transparent)`;
  188. var barText = listCounts[i] ?
  189. `${listMessages[i]}<br><br>${listTimes[i]} コメ ${listCounts[i]}` : '';
  190. $('<div>')
  191. .css('background-image', barBackground)
  192. .css('float','left')
  193. .data('text', barText)
  194. .height(this.GRAPHHEIGHT)
  195. .width(barWidth)
  196. .addClass("commentbar")
  197. .appendTo($commentgraph);
  198. }
  199. };
  200. // set mouse functions
  201. NicoHeatGraph.prototype.addMousefunc = function($canvas){
  202. let $commentgraph = this.$commentgraph;
  203. let $commentlist = this.$commentlist;
  204. function mouseOverFunc() {
  205. $commentlist.css({
  206. 'left': $(this).offset().left,
  207. 'top': $commentgraph.offset().top - $commentlist.height() - 10
  208. })
  209. .html($(this).data('text'));
  210. }
  211. function mouseOutFunc() {
  212. $commentlist.empty();
  213. }
  214.  
  215. $commentgraph.children().on({
  216. 'mouseenter': function(val) {
  217. $commentlist.css({
  218. 'left': $(this).offset().left,
  219. 'top': $commentgraph.offset().top - $commentlist.height() - 10
  220. })
  221. .html($(this).data('text'));
  222. },
  223. 'mousemove': function(val) {
  224. $commentlist.offset({
  225. 'left': $(this).offset().left,
  226. 'top': $commentgraph.offset().top - $commentlist.height() - 10
  227. });
  228. },
  229. 'mouseleave': function() {
  230. $commentlist.empty();
  231. }
  232. });
  233.  
  234. /* 1 Dom Style Watcher本体 監視する側*/
  235. var domStyleWatcher = {
  236. Start: function(tgt, styleobj){
  237. function eventHappen(data1, data2){
  238. var throwval = tgt.css(styleobj);
  239. tgt.trigger('domStyleChange', [throwval]);
  240. }
  241. var tge = tgt[0];
  242. var filter = ['style'];
  243. var options = {
  244. attributes: true,
  245. attributeFilter: filter
  246. };
  247. var mutOb = new MutationObserver(eventHappen);
  248. mutOb.observe(tge, options);
  249. return mutOb;
  250. },
  251. Stop: function(mo){
  252. mo.disconnect();
  253. }
  254. };
  255. function catchEvent(event, value){
  256. var playerWidth=parseFloat(value);
  257. var barIndexNum=$('.commentbar').length;
  258. $commentgraph.width(playerWidth);
  259. $('.commentbar').width(playerWidth /barIndexNum);
  260. }
  261. var target = $canvas;
  262. var styleobj = 'width';
  263. target.on('domStyleChange', catchEvent);//イベントを登録
  264. var dsw = domStyleWatcher.Start(target, styleobj);//監視開始
  265. //domStyleWatcher.Stop(dsw);//監視終了
  266. };
  267.  
  268. NicoHeatGraph.prototype.load = function(){
  269. let self=this;
  270. getCommentData().done(function(data, textStatus, jqXHR){
  271. this.canvas=$("#CommentRenderer").children('canvas').eq(0);
  272. self.drowgraph(data,this.canvas);
  273. self.addMousefunc(this.canvas);
  274. }).fail(function(jqXHR, textStatus, errorThrown){
  275. //TODO
  276. console.log("failed");
  277. });
  278. };
  279. NicoHeatGraph.prototype.reload = function(){
  280. this.load();
  281. };
  282.  
  283. // Main
  284. var heatgraph = new NicoHeatGraph();
  285. heatgraph.drawCoordinate();
  286. heatgraph.load();
  287. //リロード
  288. let $reloadbutton=$('.ActionButton.ReloadButton').eq(0);
  289. $reloadbutton.click(function(e) {
  290. heatgraph.reload();
  291. });
  292. let $startbutton=$('.VideoStartButtonContainer').eq(0);
  293. $startbutton.click(function(e) {
  294. heatgraph.reload();
  295. });
  296.  
  297. })();