1 // moreyon.js 2 // Oreyon モドキ 3 4 /** 5 * @fileOverview Oreyon ライクにスレッドを可視化する Moreyon(仮称) 6 */ 7 8 /** 9 * Moreyon を管理します。 10 * @static 11 * @property {Boolean} enabled 有効/無効(表示/非表示)の切り替え 12 */ 13 var Moreyon = { 14 PI2: Math.PI * 2, 15 /** SVGCanvas オブジェクト 16 * @type SVGCanvas 17 */ 18 SVGCanvas: null, 19 /** SVGLayer を格納する配列 20 * @type Array 21 */ 22 SVGLayers: [], 23 /** 円の半径 24 * @type Number 25 */ 26 r: 12, 27 /** 一行に表示するアイテム数 28 * @type Number 29 */ 30 itemCountPerLine: 10, 31 _enabled: false, 32 get enabled() { 33 return this._enabled; 34 }, 35 set enabled(value) { 36 this._enabled = value; 37 if (this._enabled) { 38 this.show(); 39 } else { 40 this.hide(); 41 } 42 }, 43 /** 正しい値を返す cos 関数です。 44 * @param {Number} x 数値 45 * @return {Number} 結果 46 */ 47 cos: function(x) { 48 return Math.round(Math.cos(x) * 1000000000000000) / 1000000000000000; 49 }, 50 /** HSL で指定された色の文字列を RGB に変換します。HSL が指定されていない場合は、ランダムに色を生成します。 51 * @param {String} [hsl] で指定された色の文字列 52 * @return {Array} R, G, B の順に値が格納された配列 53 */ 54 getRGBfromHSL: function(hsl) { 55 var rgb = []; 56 var re = /(\d+?),(\d+?)%,(\d+?)%/; 57 if (hsl) if (hsl.match(re)) { 58 rgb[0] = Math.floor(parseInt(RegExp.$1) * 255 / 360 * 1.3); 59 rgb[1] = Math.floor(parseInt(RegExp.$2) * 255 / 100 * 1.3); 60 rgb[2] = Math.floor(parseInt(RegExp.$3) * 255 / 100 * 1.3); 61 return rgb; 62 } 63 rgb[0] = Math.floor(Math.random() * 128); 64 rgb[1] = Math.floor(Math.random() * 128); 65 rgb[2] = Math.floor(Math.random() * 128); 66 return rgb; 67 }, 68 /** RGB の明度を半分にします。 69 * @param {Array} rgbArray RGB の配列 70 * @return {Array} 結果 71 */ 72 rgbDarker: function(rgbArray) { 73 var rgb = []; 74 rgb[0] = Math.floor(rgbArray[0] / 2); 75 rgb[1] = Math.floor(rgbArray[1] / 2); 76 rgb[2] = Math.floor(rgbArray[2] / 2); 77 return rgb; 78 }, 79 /** ランダムな HSL 値を生成します。 80 * @return {String} HSL 値 81 */ 82 hslRandom: function() { 83 return "hsl(" + Math.floor(Math.random() * 360) + "," + Math.floor(Math.random() * 100) + "%," + Math.floor(Math.random() * 100) + "%)" 84 }, 85 /** HSL 値を適切な色に調節します。 86 * @param {String} hsl HSL 値 87 * @return {String} 結果 88 */ 89 hslColor: function(hsl) { 90 var rgb = []; 91 var re = /(\d+?)%\)/; 92 if (hsl) if (hsl.match(re)) { 93 return hsl.replace(RegExp.$1 + "%)", Math.min(85, 100-parseInt(RegExp.$1)) + "%)"); 94 } 95 }, 96 /** HSL 値の明度を半分にします。 97 * @param {String} hsl HSL 値 98 * @return {String} 結果 99 */ 100 hslDarker: function(hsl) { 101 var rgb = []; 102 var re = /(\d+?)%\)/; 103 if (hsl) if (hsl.match(re)) { 104 return hsl.replace(RegExp.$1 + "%)", (parseInt(RegExp.$1) / 2) + "%)"); 105 } 106 }, 107 /** アイテムのラベルを描画します。 108 * @param {SVGLayer} layer レイヤー 109 * @param {Object} circle アイテム 110 */ 111 drawLabel: function(layer, circle) { 112 layer.strokeColor = "none"; 113 layer.fillColor = "black"; 114 layer.fillOpacity = 1; 115 var text = layer.text(circle.index, circle.x, circle.y + (this.r * 2)); 116 text.element.setAttributeNS(null, "text-anchor", "middle"); 117 text.element.setAttributeNS(null, "font-size", "12"); 118 119 //if (circle.isAA) 120 // //console.log("AA"); 121 122 //this.addLabel(circle); 123 }, 124 /** アイテムの円を描画します。 125 * @param {SVGLayer} layer レイヤー 126 * @param {Object} circle アイテム 127 */ 128 drawCircle: function(layer, circle) { 129 layer.strokeColor = "none"; 130 layer.fillColor = "black"; 131 layer.fillOpacity = 0.25; 132 for (var i = 2; i > 0; i--) { 133 layer.circle(circle.x + i, circle.y + i, circle.r + (circle.strokeWidth / 2)); 134 } 135 layer.fillColor = circle.fillColor; 136 layer.strokeColor = circle.strokeColor; 137 layer.strokeWidth = circle.strokeWidth; 138 layer.fillOpacity = 1; 139 layer.circle(circle.x, circle.y, circle.r); 140 141 //if (circle.isAA) 142 // //console.log("AA"); 143 144 //this.addLabel(circle); 145 }, 146 /** 二つのアイテム間に矢印を描画します。 147 * @param {SVGLayer} layer レイヤー 148 * @param {Object} circleFrom 始点のアイテム 149 * @param {Object} circleTo 終点のアイテム 150 */ 151 drawArrow: function(layer, circleFrom, circleTo) { 152 layer.fillColor = circleFrom.fillColor; 153 layer.strokeColor = circleFrom.strokeColor; 154 var x1 = circleFrom.x; 155 var y1 = circleFrom.y; 156 var x2 = circleTo.x; 157 var y2 = circleTo.y; 158 var r = Math.atan2(y2 - y1, x2 - x1); 159 x2 -= circleTo.r * Math.cos(r); 160 y2 -= circleTo.r * Math.sin(r); 161 162 layer.strokeWidth = 1; 163 var lx = (-6) * Math.cos(r) - (6) * Math.sin(r); 164 var ly = (-6) * Math.sin(r) + (6) * Math.cos(r); 165 var rx = (-6) * Math.cos(r) - (-6) * Math.sin(r); 166 var ry = (-6) * Math.sin(r) + (-6) * Math.cos(r); 167 var points = [[x1,y1],[x2 + (lx + rx) / 2, y2 + (ly + ry) / 2]]; 168 points.push([x2 + lx, y2 + ly]); 169 points.push([x2, y2]); 170 points.push([x2 + rx, y2 + ry]); 171 points.push(points[1]); 172 /* 173 var points = [[x1,y1],[x2,y2]]; 174 layer.strokeWidth = 1; 175 var lx = (-6) * Math.cos(r) - (6) * Math.sin(r); 176 var ly = (-6) * Math.sin(r) + (6) * Math.cos(r); 177 var rx = (-6) * Math.cos(r) - (-6) * Math.sin(r); 178 var ry = (-6) * Math.sin(r) + (-6) * Math.cos(r); 179 points.push([x2 + lx, y2 + ly]); 180 points.push([x2 + rx, y2 + ry]); 181 points.push([x2, y2]); 182 */ 183 layer.polyline(points); 184 }, 185 /** Moreyon を描画/表示します。*/ 186 show: function() { 187 var resItems = ResNodes.getContainers(); 188 this.width = this.itemCountPerLine * this.r * 2; //document.body.offsetWidth; 189 this.height = (Math.ceil(resItems.length / this.itemCountPerLine)+1) * this.r * 4 * 1.75; //window.innerHeight - (Nodes.header.offsetTop + Nodes.header.offsetHeight); 190 this.SVGCanvas = new SVGCanvas(this.width, this.height); 191 this.SVGLayers[0] = new SVGLayer(""); 192 this.SVGLayers[1] = new SVGLayer(""); 193 this.SVGLayers[2] = new SVGLayer(""); 194 this.SVGLayers[3] = new SVGLayer(""); 195 // 怪しい曲線(たぶん 0 <= y <= 1) 196 var curve = []; 197 for (var i = 1; i <= this.itemCountPerLine; i++) { 198 //curve.push((-this.cos(((Math.PI * 0.75 * i) / this.itemCountPerLine) - (Math.PI / 2.75)) + 1) / 0.66); 199 //curve.push((-Math.cos(((Math.PI * 1.5 * i) / this.itemCountPerLine) - (Math.PI / 2)) + 1) / 2); 200 curve.push((Math.cos((i * 8 / this.itemCountPerLine - Math.PI * 4) / 3)) + 1); 201 } 202 203 Trackback.traverse(); 204 var r = this.r; 205 var r2 = r * 2; 206 var circles = []; 207 var indexTable = []; 208 var idTable = []; 209 210 var bodyItems = ResNodes.getBodies(); 211 var idItems = ResNodes.getIDs(); 212 for (var i = 0; i < resItems.length; i++ ) { 213 var resItem = resItems.items(i); 214 215 var x = i % this.itemCountPerLine + 1; 216 var y = Math.floor(i / this.itemCountPerLine); 217 circles.push([]); 218 circles[i].r = (this.r / 4) + Math.floor(resItem.textContent.length / 50); 219 if (circles[i].r > this.r) circles[i].r = this.r; 220 circles[i].index = Nodes.getIndexFromRes(resItem); 221 222 var hsl = resItem.getAttribute("rel"); 223 circles[i].fillColor = hsl == "inherit" ? this.hslRandom() : this.hslColor(hsl); 224 circles[i].strokeColor = this.hslDarker(circles[i].fillColor); 225 226 circles[i].strokeWidth = r / 4; 227 circles[i].x = (x * r2) - r; 228 //circles[i].y = (r2 * curve[x - 1] * 3) + r + (y * r2 * 2); 229 circles[i].y = (r2 * curve[x - 1] * 3) + r + (y * r2 * 3.5); 230 231 var childNode = bodyItems.items(i).childNodes[0]; 232 if (childNode.className == "aaRes") circles[i].isAA = true; 233 if (idItems.items(i)) { 234 var id = idItems.items(i).getAttribute("rel"); 235 if (idTable[id]) circles[i].prev = circles[idTable[id]]; //if (idTable[id]) circles[i].prev = idTable[id]; 236 idTable[id] = i; //idTable[id] = circles[i]; 237 238 // -- 239 indexTable["#" + circles[i].index] = i; 240 } 241 } 242 243 244 for (var i = 0; i < circles.length; i++) { 245 this.drawLabel(this.SVGLayers[3], circles[i]); 246 } 247 248 249 250 //console.time("draw circles"); 251 this.SVGLayers[1].strokeOpacity = 1; 252 for (var i = 0; i < circles.length; i++) { 253 this.drawCircle(this.SVGLayers[1], circles[i]); 254 } 255 //console.timeEnd("draw circles"); 256 257 this.SVGLayers[2].strokeOpacity = 0.5; 258 for (var i = 0; i < circles.length; i++) { 259 this 260 if (circles[i].prev) { 261 this.SVGLayers[2].strokeColor = circles[i].fillColor; 262 this.SVGLayers[2].strokeWidth = 4; 263 this.SVGLayers[2].line(circles[i].x, circles[i].y, circles[i].prev.x, circles[i].prev.y); 264 } 265 } 266 267 //console.time("draw arrows"); 268 this.SVGLayers[0].strokeOpacity = 0.75; 269 for (var i = 0; i < circles.length; i++) { 270 if (Trackback.items[circles[i].index]) { 271 for (var j = 0; j < Trackback.items[circles[i].index].length; j++) { 272 var c = circles[indexTable["#" + Trackback.items[circles[i].index][j]]]; 273 this.SVGLayers[0].strokeOpacity = 1 - (Math.abs(c.index - circles[i].index) / 400); 274 this.SVGLayers[0].fillOpacity = this.SVGLayers[0].strokeOpacity; 275 this.drawArrow(this.SVGLayers[0], c, circles[i]); 276 } 277 } 278 } 279 //console.timeEnd("draw arrows"); 280 281 this.SVGCanvas.add(this.SVGLayers[3]); 282 this.SVGCanvas.add(this.SVGLayers[2]); 283 this.SVGCanvas.add(this.SVGLayers[1]); 284 this.SVGCanvas.add(this.SVGLayers[0]); 285 this.reposition(); 286 this.SVGCanvas.appendTo(Nodes.scrollView); 287 Nodes.scrollView.display = "block"; 288 }, 289 /** Moreyon を非表示にします。*/ 290 hide: function() { 291 Nodes.sidePanel.style.paddingLeft = 0; 292 Nodes.scrollView.removeChild(this.SVGCanvas.svg); 293 Nodes.scrollView.display = "none"; 294 }, 295 /** Moreyon の位置を調節します。 296 * @ignore 297 */ 298 reposition: function() { 299 300 Nodes.sidePanel.style.paddingLeft = this.width; 301 Nodes.scrollView.style.left = 0; 302 Nodes.scrollView.style.width = this.width; 303 }, 304 /** scroll イベントを処理します。 305 * @ignore 306 */ 307 onScroll: function(e) { 308 //console.log(e); 309 this.reposition(); 310 } 311 } 312 /* 313 Moreyon.show(); 314 Graph.addLabel("test"); 315 */ 316 317 //window.addEventListener("load", Moreyon.show.bind(Moreyon), false); 318 /