/***
|''Name''|TiddlyTagMindMap2|
|''Requires''|excanvas jit-yc|
|''Version''|2.0.0|
!Parameters
See http://mindmaps.tiddlyspace.com#How to use
* fieldType
!StyleSheet
.ttmm {
	overflow: hidden;
	position: relative;
	height: 200px;
}
.ttmm .node {
	cursor: pointer;
	font-size: 0.7em;
}

.nodeMissing {
	color: #ccc;
}

.nodeVisited {
	color: Purple;
}

.depth0 {
	border: solid 1px black;
}
!Todo
* html labels have id of the node. This is really bad and should be fixed in the jit core.
***/
//{{{
(function($){

var tiddler = {title: "TiddlyTagMindMap2Plugin"};
var name = "StyleSheetTiddlyTagMindMap";
config.shadowTiddlers[name] = store.getTiddlerText(tiddler.title +
"##StyleSheet");
store.addNotification(name, refreshStyles);
config.shadowTiddlers.TiddlyTagMindMapNodeTemplate = "<<view title text>>";
var macro = config.macros.TiddlyTagMindMap = {
	
	rootNode: "TiddlyTagMindMapRootNode",
	defaultOptions: {
		id: null,
		rootNode: null,
		field: [ "tags" ], // dictates the parent node
		fieldsAreList: true,
		width: null,
		height: null,
		nodeType: "square",
		nodeColor: '#ddeeff',
		nodeSize: 4,
		centerOn: false,
		edgeColor: '#C17878',
		directed: false,
		edgeType: null,
		edgeWidth: 1.5,
		template: "TiddlyTagMindMapNodeTemplate",
		filter: false,
		maxNodeNameLength: false,
		labelDepth: false,
		exclude: "excludeLists",
		vizType: "radial",
		animate: true, //*
		ignoreLoneNodes: true, //*
		caseSensitive: false, //*
		hideRoot: true,
		valuePrefix: false
	},
	init: function() {
		macro._active = {};
		var _displayTiddler = Story.prototype.displayTiddler;
		Story.prototype.displayTiddler = function(srcElement,tiddler,template,animate,unused,customFields,toggle,animationSrc) {
			var el = _displayTiddler.apply(this, arguments);
			var id = typeof(tiddler) == 'string' ? tiddler : tiddler.title;
			macro.centerAll(id);
			return el;
		};
		
		var _saveTiddler = TiddlyWiki.prototype.saveTiddler;
		TiddlyWiki.prototype.saveTiddler = function(title, newTitle, newBody, modifier,
				modified, tags, fields, clearChangeCount, created, creator) {
			var tid = _saveTiddler.apply(this, arguments);
			macro.updateAllGraphs();
			return tid;
		};
		var _removeTiddler = TiddlyWiki.prototype.removeTiddler;
		TiddlyWiki.prototype.removeTiddler = function(title) {
			var tid = store.getTiddler(title);
			var flag = _removeTiddler.apply(this, arguments);
			macro.updateAllGraphs(title);
			return flag;
		};
	},
	handler: function(place, macroName, params, wikifier, paramString, tiddler) {
		var options = macro.getOptions(place, paramString);
		var data = macro.getData(options);
		var id = options.id || "ttmm2_%0".format([Math.random()]);
		if(macro._active[id]) {
			$(document.getElementById(id)).empty();
		}
		var container = $("<div />").addClass("ttmm").attr("id", id).
			css({width: options.width, height: options.height}).appendTo(place)[0];
		$(container).dblclick(function(ev) {
			ev.stopPropagation();
		});
		macro.renderGraph(container, data, options);
	},
	getNodeLabelId: function(containerId,id) {
		return "node-%0-%1".format([containerId, id])
	},
	updateAllGraphs: function(changed) {
			for(var i in macro._active) {
				var el = document.getElementById(i);
				if(el) {
					var cache = macro._active[i];
					var deleted = document.getElementById(macro.getNodeLabelId(i, changed));
					if(cache) {
						var options = cache.options;
						var viz = cache.viz;
						var data = macro.getData(options);
						macro._refresh(viz, data, options);
						viz.refresh();
						$(deleted).remove();
					}
				} else {
					macro._active[i] = false;
				}
			}
	},
	centerAll: function(id) {
		for(var i in macro._active) {
			var el = document.getElementById(i);
			if(el) {
				var cache = macro._active[i];
				if(cache) {
					var viz = cache.viz;
					if(viz.graph.hasNode(id)) {
						viz.onClick(id);
					}
				}
			} else {
				macro._active[i] = false;
			}
		}
	},
	renderGraph: function(container, data, options) {
		if(options.vizType == "hypertree") {
			macro._renderHypertree(container, data, options);
		} else {
			macro._renderRGraph(container, data, options);
		}
	},
	_getViz: function(type, container, options) {
		var containerId = $(container).attr("id");
		var viz = new $jit[type]({ 
			injectInto: containerId,
			Navigation: {
				enable: true,
				panning: true,
				zooming: 10
			},
			Node: {
				color: options.nodeColor,
				type: options.nodeType,
				overridable: true,
				width: options.nodeSize,
				dim: options.nodeSize,
				height: options.nodeSize
			},
			Edge: {
				color: options.edgeColor,
				lineWidth: options.edgeWidth,
				type: options.edgeType,
				overridable: true,
				dim: 15
			},
			onBeforeCompute: function(node){},
			onAfterCompute: function(){},
			onCreateLabel: function(domElement, node){
				var el = $(domElement).empty();
				if(!options.maxNodeNameLength) {
					var ctx = store.tiddlerExists(node.id) ? store.getTiddler(node.id) : new Tiddler(node.id);
					var template = store.getTiddlerText(options.template);
					template = template ? template : store.getTiddlerText("TiddlyTagMindMapNodeTemplate");
					wikify(template, domElement, null, ctx);
				} else {
					var text = el.text();
					var maxLength = options.maxNodeNameLength;
					if(maxLength) {
						if(text.length > maxLength) {
							var diff = (text.length - maxLength);
							text = "%0...".format([text.slice(0, text.length - diff)]);
						}
					}
					el.text(text);
				}
				el.attr("id", macro.getNodeLabelId(containerId, node.id));
				var rgraph = this;
				var wasClick = false;
				$(domElement).click(function(ev){
					wasClick = true;
					window.setTimeout(function() {
						if(wasClick) {
							var cache = macro._active[containerId];
							if(cache && cache.visitedNodes) {
								cache.visitedNodes[node.id] = true;
							}
							options.centerOn = node.id;
							viz.onClick(node.id);
						}
					}, 300);
				});
				$(domElement).dblclick(function(ev) {
					wasClick = false;
					story.displayTiddler(ev.target, node.id);
					ev.stopPropagation();
					return false;
				});
			},
			onPlaceLabel: function(domElement, node){
				var el = $(domElement);
				var depth = node._depth;
				if(options.labelDepth && depth > options.labelDepth) {
					el.hide();
					return;
				}
				el.show();
				var existingDepth = el.attr("node-depth");
				if(existingDepth) {
					el.removeClass("depth%0".format([existingDepth]));
				}
				el.attr("node-depth", depth);
				el.addClass("depth%0".format([depth]));
				if(options.hideRoot && node.data.root) {
					el.hide();
				}
				if(node.data.missing) {
					el.addClass("nodeMissing");
					if(options.missingColor) {
						el.css({ color: "#ccc" });
					}
				}
				var cache = macro._active[containerId];
				if(cache && cache.visitedNodes && cache.visitedNodes[node.id]) {
					el.addClass("nodeVisited");
				}
			}
		});
		return viz;
	},
	_refresh: function(viz, data, options) {
		viz.labels = false; // hack
		viz.loadJSON(data);
    //compute positions and plot.
    try {
			viz.refresh();
		} catch(e) {
		}
		if(options.centerOn) {
			viz.onClick(options.centerOn);
		}
	},
	_patch: function(viz) {
		// customisations
		var _plotLine = viz.constructor.Plot.prototype.plotLine;
		var _plotNode = viz.constructor.Plot.prototype.plotNode;
		viz.constructor.Plot.prototype.plotLine = function(adj, canvas, animating) {
			if(adj.nodeFrom.id != macro.rootNode && adj.nodeTo.id != macro.rootNode) {
				_plotLine.apply(this, arguments);
			}
		}
		viz.constructor.Plot.prototype.plotNode = function(node, canvas, animating) {
			if(node.id != macro.rootNode) {
				_plotNode.apply(this, arguments);
			}
		};
	},
	_renderHypertree: function(container, data, options) {
		var ht = macro._getViz("Hypertree", container, options);
		macro._patch(ht);
		var id = $(container).attr("id");
		macro._active[id] = {
			data: data,
			viz: ht,
			options: options,
			visitedNodes: {}
		};
		macro._refresh(ht, data, options);
    //end
    ht.controller.onAfterCompute();
	},
	_renderRGraph: function(container, data, options) {
		var rgraph = macro._getViz("RGraph", container, options);
		macro._patch(rgraph);
		//load JSON data
		var id = $(container).attr("id");
		macro._active[id] = {
			data: data,
			viz: rgraph,
			options: options,
			visitedNodes: {}
		};
		macro._refresh(rgraph, data, options);
	},
	refresh: function(container) {
		var id = $(container).attr("id");
		var cache = macro._active[id];
		if(cache) {
			var data = cache.data;
			var options = cache.options;
			var rgraph = cache.viz;
		}
	},
	getData: function(options) {
		var root = options.rootNode || macro.rootNode;
		options = options ? options : macro.defaultOptions;
		options._tiddlers = options.filter ? store.filterTiddlers(options.filter) : store.getTiddlers(null, options.exclude);
		return macro._createNode(root, options);
	},
	getOptions: function(container, paramString) {
		var listOptions = ["field"]; // will be saved as lists
		var numberOptions = ["labelDepth"];
		var args = paramString.parseParams("name", null, true, false, true)[0];
		var options = {};
		merge(options, macro.defaultOptions);
		for(var id in args) {
			if(true) {
				var p = args[id];
				if(listOptions.contains(id)) {
					options[id] = p;
				} else if(numberOptions.contains(id)){
					options[id] = parseFloat(p[0]);
				} else {
					options[id] = p[0];
				}
			}
		}
		options.fieldsAreList = options.fieldType && options.fieldType == "string" ? false : true; 
		options.width = options.width ? parseInt(options.width) : $(container).width();
		options.height = options.height ? parseInt(options.height) : $(container).height();
		options.edgeType = options.directed ? "arrow" : options.edgeType;
		options.hideRoot = options.hideRoot && options.hideRoot == "no" ? false : true;
		// note hyperline doesn't work for RGraph
		var defaultEdgeType = options.vizType == "hypertree" ? "hyperline" : "line";
		if(!options.edgeType) {
			options.edgeType = defaultEdgeType;
		} else {
			options.edgeType = options.edgeType == "hyperline" && options.vizType != "hypertree" ? "line" : options.edgeType;
		}
		return options;
	},
	lookupFieldValue: function(tiddler, options) {
		var allValues = [];
		for(var i = 0; i < options.field.length; i++) {
			var fieldName = options.field[i];
			var values = tiddler[fieldName] ? tiddler[fieldName] : tiddler.fields[fieldName];
			if(options.fieldsAreLists) {
				values = typeof(values) == "string" ? values.readBracketedList() : values;
			} else {
				values = typeof(values) == "string" ? [ values ] : values;
			}
			if(values) {
				for(var j = 0; j < values.length; j++) {
					allValues.pushUnique(values[j]);
				}
			}
		}
		return allValues;
	},
	lookupTiddlers: function(tiddlers, value, options) {
		var matches = [];
		for(var i = 0; i < tiddlers.length; i++) {
			var tiddler = tiddlers[i];
			var values = macro.lookupFieldValue(tiddler, options);
			if(values && values.indexOf(value) > -1) {
				matches.push(tiddler);
			}
		}
		return matches;
	},
	_getChildNodes: function(title, options) {
		var children = [];
		var tiddlers = options._tiddlers;
		if(title == macro.rootNode) { // get all tiddlers with no tags
			var tags = {};
			for(var i = 0; i < tiddlers.length; i++) {
				var tiddler = tiddlers[i];
				var values = macro.lookupFieldValue(tiddler, options);
				if(values.length === 0) {
					var node = macro._createNode(tiddler.title, options);
					node.data.orphan = true;
					if(node) {
						children.push(node);
					}
				} else {
					for(var j = 0; j < values.length; j++) {
						var value = values[j];
						var tiddler = store.getTiddler(value);
						if(!tiddler) {
							var node = macro._createNode(value, options);
							if(node) {
								node.data.orphan = true;
								children.push(node);
							}
						}
					}
				}
			}
		} else {
			var lookup = options.valuePrefix ? "%0%1".format([options.valuePrefix, title]) : title;
			var matches = macro.lookupTiddlers(tiddlers, lookup, options);
			if(matches) {
				// TODO: support strings to array
				for(var i = 0; i < matches.length; i++) {
					var node = macro._createNode(matches[i].title, options);
					if(node) {
						children.push(node);
					}
				}
			}
		}
		return children;
	},
	_createNode: function(title, options) {
		var children = macro._getChildNodes(title, options);
		var node = {
			id: title,
			name: title,
			data: {
				missing: store.tiddlerExists(title) ? false : true,
				visited: false,
				root: title == macro.rootNode,
				weight: children.length
			},
			children: children
		};
		return node;
	}
};
})(jQuery);
//}}}
bag
plugins_public
created
Fri, 01 Oct 2010 14:13:03 GMT
creator
jon
modified
Fri, 01 Oct 2010 14:13:03 GMT
modifier
jon
tags
excludeLists
systemConfig
creator
jon