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 /