瀏覽代碼

U test neuron

Piotr Labudda 7 年之前
父節點
當前提交
b8c28ad13a
共有 1 個文件被更改,包括 478 次插入165 次删除
  1. 478 165
      SE/se-lib/Route/Test/Neuron.php.view.js

+ 478 - 165
SE/se-lib/Route/Test/Neuron.php.view.js

@@ -13,47 +13,336 @@ var DBG1 = true;
 // </svg>
 // </svg>
 
 
 
 
-var p5UI__TestNeuron = createReactClass({
-	_input: null,
-	_output: null,
-	getInitialState: function () {
-		return {
-			inputText: INITIAL_DATA || '',
-			receptor: [],
-			neuron: [],
-			output_width: 300,
-			max_receptor_r: 30,
-			anim_pos: 0,
-			anim_last_letter: null,
-			config_read_speed: 100,
-			config_charge_receptor_at_input: 1,
-			config_max_receptor_charge: 5,
-			config_discharge_per_tick: 0.1,
+var DEFAULT_CONFIG = {
+	ui_output_width: 800,
+	ui_max_receptor_r: 12,
+	ui_space_y: 30,
+	config_read_speed: 500,
+	config_charge_receptor_at_input: 1,
+	config_max_receptor_charge: 1.5,
+	config_discharge_per_tick: 0.1,
+	config_discharge_max_in_new_neuron_from_one: 0.7,
+}
+var DEFAULT_NEURON = {
+	value: '',
+	charge: 0,
+	maxCharge: DEFAULT_CONFIG.config_max_receptor_charge,
+	uiShape: "circle",
+	ui: { cx: 0, cy: 0 },
+	source: [],
+	next: [],
+}
 
 
-			selectedReceptorIdx: null,
-		};
-	},
-	makeReceptor: function (letter, idx, arr) {
+function state__getInitial(state, letters) {
+	function makeReceptor(letter, idx, arr) {
 		var totalReceptors = arr.length;
 		var totalReceptors = arr.length;
-		var cx = Math.ceil(this.state.output_width / (totalReceptors + 1));
-		var r = Math.min(Math.ceil(this.state.output_width / (totalReceptors + 10) / 2), this.state.max_receptor_r);
+		var cx = Math.ceil(state.ui_output_width / (totalReceptors + 1));
+		var r = Math.min(Math.ceil(state.ui_output_width / (totalReceptors + 10) / 2), state.ui_max_receptor_r);
 		var xCenter = cx + cx * idx - r;
 		var xCenter = cx + cx * idx - r;
 
 
-		return {
-			letter: letter,
-			charge: 0,
-			x: xCenter,
-			y: 20,
-			r: r,
+		return Object.assign({}, DEFAULT_NEURON, {
+			value: letter,
+			maxCharge: state.config_max_receptor_charge,
+			uiShape: "circle",
+			ui: {
+				cx: xCenter,
+				cy: 40,
+				r: r,
+			},
+			next: [],
+		})
+	}
+
+	return {
+		receptor: letters.map(makeReceptor),
+	}
+}
+function state__readFromInput(state, char) {
+	// 1. charge receptor with letter `char`
+	var listNeuron = state.neuron;
+	var listReceptor = state.receptor;
+	var mapLetterToReceptor = {}
+	for (var i = 0, l = listReceptor.length; i < l; i++) {
+		mapLetterToReceptor[listReceptor[i].value] = listReceptor[i];
+	}
+	// var toChangeReceptor = getReceptor(listReceptor, char)
+	var toChangeReceptor = mapLetterToReceptor[char]
+	DBG && console.log("DBG:anim", { char, len: state.inputText.length, toChangeReceptor, mapLetterToReceptor: mapLetterToReceptor });
+	toChangeReceptor.charge += state.config_charge_receptor_at_input;
+
+	// 2. check if any receptor is over charged
+	if (toChangeReceptor.charge > state.config_max_receptor_charge) {
+		var closestNodeWithCharge = findReceptorClosestNodeWithCharge(state, toChangeReceptor)
+		DBG1 && console.log("DBG:overcharge.1:", { char, len: state.inputText.length, closestNodeWithCharge, receptor: state.receptor });
+		if (!closestNodeWithCharge) {
+			{ // TODO: export to funtion makeNeuronFromOneNode(state, listReceptor, listNeuron, toChangeReceptor)
+				var neuron = makeNeuronFromOneNode(state, toChangeReceptor)
+				// TODO: neuron may already exists? check neuron.source if already has defined next
+				var alreadyExistNeuron = toChangeReceptor.next.filter(function (nextNeuron) {
+					return (nextNeuron.source.length === 1);
+				})
+				DBG1 && console.log("DBG:overcharge.2:", { alreadyExistNeuron });
+				if (alreadyExistNeuron.length) {
+					alreadyExistNeuron[0].charge += toChangeReceptor.charge
+					toChangeReceptor.charge = 0
+				} else {
+					toChangeReceptor.next.push(neuron)
+					listNeuron.push(neuron)
+					toChangeReceptor.charge = 0
+				}
+			}
+		} else { // (closestNodeWithCharge)
+			{ // TODO: export to funtion makeNeuronFromTwoNodes(state, listReceptor, listNeuron, toChangeReceptor, closestNodeWithCharge)
+				var neuron = makeNeuronFromTwoNodes(state, toChangeReceptor, closestNodeWithCharge)
+				// TODO: neuron may already exists? check neuron.source if already has defined next
+				var alreadyExistNeuron = toChangeReceptor.next.filter(function (nextNeuron) {
+					return (nextNeuron.source.length === 2 && (
+						(nextNeuron.source[0].value === toChangeReceptor.value && nextNeuron.source[1].value === closestNodeWithCharge.value)
+						|| (nextNeuron.source[0].value === closestNodeWithCharge.value && nextNeuron.source[1].value === toChangeReceptor.value)
+					));
+				})
+				DBG1 && console.log("DBG:overcharge.2:", { alreadyExistNeuron });
+				if (alreadyExistNeuron.length) {
+					alreadyExistNeuron[0].charge += toChangeReceptor.charge + closestNodeWithCharge.charge
+					toChangeReceptor.charge = 0
+					closestNodeWithCharge.charge = 0
+				} else {
+					toChangeReceptor.next.push(neuron)
+					closestNodeWithCharge.next.push(neuron)
+					listNeuron.push(neuron)
+					toChangeReceptor.charge = 0
+					closestNodeWithCharge.charge = 0
+				}
+			}
 		}
 		}
-	},
-	dischargeNeuron: function (neuron) {
+
+		// TODO: dont use last letter -- use findReceptorClosestNodeWithCharge
+		// if no closest neuron with charge then create new neuron over receptor
+		// if (null !== state.animLastLetter && state.animLastLetter !== toChangeReceptor.letter) {
+		//     DBG1 && console.log("DBG:check overcharged", { toChangeReceptor, closestNodeWithCharge: closestNodeWithCharge, last_letter: state.animLastLetter });
+		//     listNeuron = makeNueron(listReceptor, listNeuron, toChangeReceptor.letter, state.animLastLetter);
+		//     // discharge receptors to 0
+		//     var toDischarge = [toChangeReceptor.letter, state.animLastLetter];
+		//     listReceptor = listReceptor.map(function (receptor) {
+		//         return (-1 !== toDischarge.indexOf(receptor.letter))
+		//             ? Object.assign({}, receptor, { charge: 0 })
+		//             : receptor
+		//         ;
+		//     })
+		// }
+	}
+
+	// 3. discharge all receptors and nodes by 0.1 (config_discharge_per_tick)
+	var dischargeNode = makeDischargeNodeFun(state);
+	return {
+		receptor: listReceptor.map(dischargeNode),
+		neuron: listNeuron.map(dischargeNode),
+	}
+}
+function state__fromOverchargedNeurons(state, overchargedNeurons) {
+	var listNeuron = state.neuron;
+	var listReceptor = state.receptor;
+
+	overchargedNeurons = overchargedNeurons.sort(sortNeuronByChargeCallback)
+	DBG1 && console.log("DBG:state:overcharged", { overchargedNeurons })
+	if (overchargedNeurons.length === 1) {
+		var toChangeReceptor = overchargedNeurons[0]
+		{ // TODO: export to funtion makeNeuronFromOneNode(state, listReceptor, listNeuron, toChangeReceptor)
+			var neuron = makeNeuronFromOneNode(state, toChangeReceptor)
+			// TODO: neuron may already exists? check neuron.source if already has defined next
+			var alreadyExistNeuron = toChangeReceptor.next.filter(function (nextNeuron) {
+				return (nextNeuron.source.length === 1);
+			})
+			DBG1 && console.log("DBG:overcharge.2:", { alreadyExistNeuron });
+			if (alreadyExistNeuron.length) {
+				alreadyExistNeuron[0].charge += toChangeReceptor.charge
+				toChangeReceptor.charge = 0
+			} else {
+				toChangeReceptor.next.push(neuron)
+				listNeuron.push(neuron)
+				toChangeReceptor.charge = 0
+			}
+		}
+	} else {
+		var toChangeReceptor = overchargedNeurons[0]
+		var closestNodeWithCharge = overchargedNeurons[1]
+		{ // TODO: export to funtion makeNeuronFromTwoNodes(state, listReceptor, listNeuron, toChangeReceptor, closestNodeWithCharge)
+			var neuron = makeNeuronFromTwoNodes(state, toChangeReceptor, closestNodeWithCharge)
+			// TODO: neuron may already exists? check neuron.source if already has defined next
+			var alreadyExistNeuron = toChangeReceptor.next.filter(function (nextNeuron) {
+				return (nextNeuron.source.length === 2 && (
+					(nextNeuron.source[0].value === toChangeReceptor.value && nextNeuron.source[1].value === closestNodeWithCharge.value)
+					|| (nextNeuron.source[0].value === closestNodeWithCharge.value && nextNeuron.source[1].value === toChangeReceptor.value)
+				));
+			})
+			DBG1 && console.log("DBG:overcharge.2:", { alreadyExistNeuron });
+			if (alreadyExistNeuron.length) {
+				alreadyExistNeuron[0].charge += toChangeReceptor.charge + closestNodeWithCharge.charge
+				toChangeReceptor.charge = 0
+				closestNodeWithCharge.charge = 0
+			} else {
+				toChangeReceptor.next.push(neuron)
+				closestNodeWithCharge.next.push(neuron)
+				listNeuron.push(neuron)
+				toChangeReceptor.charge = 0
+				closestNodeWithCharge.charge = 0
+			}
+		}
+	}
+
+	return {
+		receptor: listReceptor,
+		neuron: listNeuron,
+	}
+}
+function sortNeuronByChargeCallback(n1, n2) {
+	if (n1.charge > n2.charge) return 1;
+	if (n1.charge < n2.charge) return -1;
+	return 0;
+}
+function getReceptor(listReceptor, char) {
+	var receptor = listReceptor.filter(function (receptor) {
+		return (char == receptor.value);
+	});
+	if (!receptor.length) throw "BUG: no input receptor found for char '" + char + "'"; // TODO: create on demand?
+	return receptor[0];
+}
+function findReceptorClosestNodeWithCharge(state, node) {
+	var skipSelfValue = node.value;
+	var maxChargedReceptor = state.receptor.reduce(function (ret, receptor) {
+		if (receptor.value === skipSelfValue) return ret;
+		if (0 === receptor.charge) return ret;
+		if (!ret) return receptor;
+		return (receptor.charge > ret.charge) ? receptor : ret;
+	}, null)
+	return maxChargedReceptor;
+}
+function makeNeuronFromOneNode(state, sourceNode) {
+	return Object.assign({}, DEFAULT_NEURON, {
+		value: '' + (state.neuron.length + 1),
+		charge: 0, // sourceNode.charge,
+		maxCharge: sourceNode.maxCharge * state.config_discharge_max_in_new_neuron_from_one, // maybe?: sourceNode.charge
+		uiShape: "ellipse",
+		ui: {
+			cx: sourceNode.ui.cx,
+			cy: sourceNode.ui.cy + 10 + state.ui_space_y,
+			rx: 10,
+			ry: 10,
+		},
+		source: [sourceNode],
+	});
+}
+function makeNeuronFromTwoNodes(state, first, second) {
+	var value = '' + first.value + second.value;
+	return Object.assign({}, DEFAULT_NEURON, {
+		value: value,
+		charge: 0, // (first.charge + second.charge) / 2,
+		maxCharge: ((first.maxCharge + second.maxCharge) / 2) * state.config_discharge_max_in_new_neuron_from_one,
+		uiShape: "ellipse",
+		ui: {
+			cx: (first.ui.cx + second.ui.cx) / 2,
+			cy: first.ui.cy + 10 + state.ui_space_y,
+			rx: 10 * value.length,
+			ry: 10,
+		},
+		source: [first, second],
+	});
+}
+function getNeuron(listNeuron, value) {
+	var neuron = listNeuron.filter(function (neuron) {
+		return (value == neuron.letter);
+	});
+	return (neuron.length) ? neuron[0] : null;
+}
+function makeDischargeNodeFun(state) {
+	return function dischargeNode(neuron) {
 		return (neuron.charge > 0)
 		return (neuron.charge > 0)
-			?	Object.assign({}, neuron, {
-					charge: Math.max(0, neuron.charge - this.state.config_discharge_per_tick),
-				})
-			:	neuron
-		;
+			? Object.assign({}, neuron, {
+				charge: Math.max(0, neuron.charge - state.config_discharge_per_tick),
+			})
+			: neuron
+			;
+	}
+}
+function isNeuronOvercharged(neuron) {
+	return (neuron.charge > neuron.maxCharge);
+}
+
+
+
+
+function LOG__render(listLogEntries) {
+	return h('details', { open: false, style: { margin: "12px 0", border: "1px solid #aaa", borderRadius: "6px", padding: "12px", backgroundColor: "#eee", } }, [
+		h('summary', { style: { cursor: "pointer" } }, "Log"),
+		h('div', { style: { border: "1px solid #eee", backgroundColor: "#fff" } }, [
+			h('table', { className: "table table-border" }, [
+				h('thead', {}, LOG__renderTheadLog(listLogEntries)),
+				h('tbody', {}, LOG__renderTbodyLog(listLogEntries)),
+			]),
+		]),
+	]);
+}
+function LOG__renderTheadLog(listLogEntries) {
+	if (!listLogEntries.length) return null;
+	var lastLog = listLogEntries[listLogEntries.length - 1]
+	var colsR = lastLog.receptor.map(function (node) { return node.value; })
+	var colsN = lastLog.neuron.map(function (node) { return node.value; })
+	return [
+		h('tr', {},
+			[h('th', {}, "Lp.")]
+				.concat(
+					colsR.map(function (label) { return h('th', {}, "R:'" + label + "'"); })
+				)
+				.concat(
+					colsN.map(function (label) { return h('th', {}, "N:'" + label + "'"); })
+				)
+		)
+	];
+}
+function LOG__renderTbodyLog(listLogEntries) {
+	if (!listLogEntries.length) return null;
+	var lastLog = listLogEntries[listLogEntries.length - 1]
+	var colsR = lastLog.receptor.map(function (node) { return node.value; })
+	var colsN = lastLog.neuron.map(function (node) { return node.value; })
+	return listLogEntries.map(function (log, lpLog) {
+		var row = colsR.map(function (label, idx) { return log.receptor[idx].charge; })
+			.concat(
+				colsN.map(function (label, idx) { return (log.neuron[idx]) ? log.neuron[idx].charge : null; })
+			)
+		return h('tr', {}, [h('th', { style: { color: "#ddd" } }, '' + (lpLog + 1) + '.')]
+			.concat(
+				row.map(LOG__renderCellNodeChargeLog)
+			)
+		)
+	})
+}
+function LOG__renderCellNodeChargeLog(charge) {
+	if (null === charge) return null;
+	return h('td', {
+		style: {
+			color: (charge > 0) ? "#000" : "#aaa",
+		},
+	}, charge.toFixed(2));
+}
+
+
+
+var NeuronView = createReactClass({
+	_input: null,
+	_output: null,
+	_animTimoutID: null,
+	_log: [],
+	getInitialState: function () {
+
+		return Object.assign({}, DEFAULT_CONFIG, {
+			inputText: this.props.initialData || '', // TODO: INITIAL_DATA
+			receptor: [],
+			neuron: [],
+			doAnim: false,
+			animPos: 0,
+			animLastLetter: null,
+			selectedReceptorIdx: null,
+		});
 	},
 	},
 
 
 	handleChangeInput: function (event) {
 	handleChangeInput: function (event) {
@@ -63,21 +352,30 @@ var p5UI__TestNeuron = createReactClass({
 	},
 	},
 	setInputRet: function (reactEl) { this._input = reactEl; },
 	setInputRet: function (reactEl) { this._input = reactEl; },
 	setOutputRet: function (reactEl) { this._output = reactEl; },
 	setOutputRet: function (reactEl) { this._output = reactEl; },
-	// d3.selectAll("circle").transition()
-	//     .duration(750)
-	//     .delay(function (d, i) { return i * 10; })
-	//     .attr("r", function (d) { return Math.sqrt(d * scale); });
 	componentDidMount: function () {
 	componentDidMount: function () {
 		this.startAnimation();
 		this.startAnimation();
 	},
 	},
 	handleExec: function (event) {
 	handleExec: function (event) {
 		event.preventDefault();
 		event.preventDefault();
 		DBG1 && console.log('DBG:handleExec...');
 		DBG1 && console.log('DBG:handleExec...');
-		this.startAnimation();
+		if (this._animTimoutID) clearTimeout(this._animTimoutID);
+		this.setState({
+			neuron: [],
+			receptor: [],
+		})
+		setTimeout(this.startAnimation, this.state.config_read_speed * 2)
+	},
+	handlePause: function (event) {
+		event.preventDefault();
+		this.setState({ doAnim: false })
+	},
+	handleStart: function (event) {
+		event.preventDefault();
+		this.forwardAnim(true)
 	},
 	},
 	startAnimation: function (event) {
 	startAnimation: function (event) {
 		var distinct = function (value, idx, self) {
 		var distinct = function (value, idx, self) {
-			return ( idx === self.indexOf(value) );
+			return (idx === self.indexOf(value));
 		};
 		};
 		// var foundLetters = this.state.inputText.split("\n").reduce(function (ret, line) {
 		// var foundLetters = this.state.inputText.split("\n").reduce(function (ret, line) {
 		// 	return ret.concat(
 		// 	return ret.concat(
@@ -85,104 +383,78 @@ var p5UI__TestNeuron = createReactClass({
 		// 	).filter(distinct);
 		// 	).filter(distinct);
 		// }, []);
 		// }, []);
 		var foundLetters = this.state.inputText.split("").filter(distinct);
 		var foundLetters = this.state.inputText.split("").filter(distinct);
-		DBG1 && console.log('DBG:handleExec:foundLetters', { foundLetters });
-		this.setState({
-			anim_pos: 0,
-			receptor: foundLetters.map(this.makeReceptor),
-			neuron: [],
-		});
+		DBG1 && console.warn('DBG:handleExec:foundLetters', { foundLetters });
+
+		this._log = []
+		this.setState(Object.assign(
+			{},
+			state__getInitial(this.state, foundLetters),
+			{
+				animPos: 0,
+				neuron: [],
+				doAnim: true,
+			}
+		), function () {
+			console.warn({
+				receptor: this.state.receptor.map(function (n) { return n }),
+				neuron: this.state.receptor.map(function (n) { return n }),
+			})
+		}.bind(this));
+
 		setTimeout(this.forwardAnim, this.state.config_read_speed)
 		setTimeout(this.forwardAnim, this.state.config_read_speed)
 	},
 	},
 	getReceptor: function (char) {
 	getReceptor: function (char) {
 		var receptor = this.state.receptor.filter(function (receptor) {
 		var receptor = this.state.receptor.filter(function (receptor) {
-			return (char == receptor.letter);
+			return (char == receptor.value);
 		});
 		});
 		if (!receptor.length) throw "BUG: no input receptor found for char '" + char + "'"; // TODO: create on demand?
 		if (!receptor.length) throw "BUG: no input receptor found for char '" + char + "'"; // TODO: create on demand?
 		return receptor[0];
 		return receptor[0];
 	},
 	},
-	getNeuron: function (value) {
-		var neuron = this.state.neuron.filter(function (neuron) {
-			return (value == neuron.letter);
-		});
-		return (neuron.length) ? neuron[0] : null;
-	},
-	makeNueron: function (listNeuron, letter, last_letter) {
-		var receptor = this.getReceptor(letter);
-		var prevReceptor = this.getReceptor(last_letter);
-		var value = '' + letter + last_letter;
-		var findNeuron = this.getNeuron(value);
-		var charge = (receptor.charge + prevReceptor.charge) / 2;
-		if (!findNeuron) {
-			return listNeuron.concat([
-				{
-					value: value,
-					charge: charge,
-					x: (receptor.x + prevReceptor.x) / 2,
-					y: 20 + 20 * value.length,
-					rx: 10 * value.length,
-					ry: 10,
-					source1_x: receptor.x,
-					source1_y: receptor.y,
-					source2_x: prevReceptor.x,
-					source2_y: prevReceptor.y,
-				}
-			])
+	forwardAnim: function (forceUpdateDoAnim) {
+		this._logState(this.state)
+		var doAnim = (arguments.length > 0) ? forceUpdateDoAnim : this.state.doAnim;
+		var animPos = this.state.animPos;
+		var overchargedNeurons = this.state.neuron.filter(isNeuronOvercharged)
+		if (overchargedNeurons.length === 0) {
+			animPos += 1;
 		} else {
 		} else {
-			return listNeuron.map(function (neuron) {
-				(neuron.value === value)
-					?	Object.assign({}, neuron, { charge: neuron.charge + (receptor.charge + prevReceptor.charge) / 2 })
-					:	neuron
-				;
-			})
+			DBG1 && console.log("DBG:anim TODO: neuron overcharged", { overchargedNeurons });
 		}
 		}
-	},
-	forwardAnim: function () {
-		if (this.state.anim_pos + 1 >= this.state.inputText.length) {
-			DBG1 && console.log("DBG:anim STOP", { inputLength: this.state.inputText.length, anim_pos: this.state.anim_pos });
+
+		if (animPos >= this.state.inputText.length) {
+			DBG1 && console.log("DBG:anim STOP - end of input", { inputLength: this.state.inputText.length, animPos: this.state.animPos });
 			return;
 			return;
 		}
 		}
 
 
-		var anim_pos = this.state.anim_pos + 1;
-		var char = this.state.inputText.charAt(anim_pos)
-		DBG1 && console.log("DBG:anim", { anim_pos, char, len: this.state.inputText.length });
-		
-		// 1. charge receptor with letter `char`
-		var neuron = this.state.neuron;
-		var _chargeReceptor = this.state.config_charge_receptor_at_input;
-		var toChangeReceptor = this.getReceptor(char)
-		DBG1 && console.log("DBG:anim", { anim_pos, char, len: this.state.inputText.length, toChangeReceptor });
-		toChangeReceptor.charge += _chargeReceptor;
-		var receptor = this.state.receptor.map(function (receptor) {
-			return (char !== receptor.letter) ? receptor : toChangeReceptor;
-		})
-		// 2. check if any receptor is over charged
-		if (toChangeReceptor.charge > this.state.config_max_receptor_charge) {
-			if (null !== this.state.anim_last_letter && this.state.anim_last_letter !== toChangeReceptor.letter) {
-				DBG1 && console.log("DBG:check overcharged", { toChangeReceptor, last_letter: this.state.anim_last_letter });
-				neuron = this.makeNueron(neuron, toChangeReceptor.letter, this.state.anim_last_letter);
-				// discharge receptors to 0
-				var toDischarge = [toChangeReceptor.letter, this.state.anim_last_letter];
-				receptor = receptor.map(function (receptor) {
-					return (-1 !== toDischarge.indexOf(receptor.letter))
-						?	Object.assign({}, receptor, { charge: 0 })
-						:	receptor
-					;
-				})
+		var char = this.state.inputText.charAt(animPos)
+		DBG && console.log("DBG:anim", { animPos, char, len: this.state.inputText.length, doAnim });
+
+		this.setState(Object.assign(
+			{},
+			(overchargedNeurons.length) ? state__fromOverchargedNeurons(this.state, overchargedNeurons) : state__readFromInput(this.state, char),
+			{
+				animLastLetter: char,
+				animPos: animPos,
+				doAnim: doAnim,
 			}
 			}
-		}
-		// 3. discharge all receptors and nodes by 0.1 (config_discharge_per_tick)
-		receptor = receptor.map(this.dischargeNeuron)
-		neuron = neuron.map(this.dischargeNeuron)
-		// 4. create neuron if needed
+		));
 
 
-		this.setState({
-			anim_last_letter: char,
-			anim_pos: anim_pos,
-			receptor: receptor,
-			neuron: neuron,
-		});
+		if (doAnim) {
+			this._animTimoutID = setTimeout(this.forwardAnim, this.state.config_read_speed)
+		}
+	},
+	_logState: function (state) {
+		function getNodeInfo(node) {
+			return {
+				value: node.value,
+				charge: node.charge,
+			}
+		}
 
 
-		setTimeout(this.forwardAnim, this.state.config_read_speed)
+		this._log.push({
+			receptor: state.receptor.map(getNodeInfo),
+			neuron: state.neuron.map(getNodeInfo),
+		})
 	},
 	},
 	selectReceptor: function (receptorIdx) {
 	selectReceptor: function (receptorIdx) {
 		this.setState({ selectedReceptorIdx: receptorIdx })
 		this.setState({ selectedReceptorIdx: receptorIdx })
@@ -194,45 +466,48 @@ var p5UI__TestNeuron = createReactClass({
 	},
 	},
 
 
 	renderConnections: function (neuron, idx) {
 	renderConnections: function (neuron, idx) {
+		DBG1 && console.log('DBG:renderConnections', { neuron, p1: neuron.source[0].ui, p2: (neuron.source.length > 1) ? neuron.source[1].ui : null });
 		return h('g', {}, [
 		return h('g', {}, [
 			h('line', {
 			h('line', {
-				x1: neuron.source1_x, y1: neuron.source1_y,
-				x2: neuron.x, y2: neuron.y,
-				style: {
-					stroke: "#46b8da",
-					strokeWidth: "2"
-				},
-			}),
-			h('line', {
-				x1: neuron.source2_x, y1: neuron.source2_y,
-				x2: neuron.x, y2: neuron.y,
+				x1: neuron.source[0].ui.cx, y1: neuron.source[0].ui.cy,
+				x2: neuron.ui.cx, y2: neuron.ui.cy,
 				style: {
 				style: {
 					stroke: "#46b8da",
 					stroke: "#46b8da",
 					strokeWidth: "2"
 					strokeWidth: "2"
 				},
 				},
 			}),
 			}),
+			(neuron.source.length > 1)
+				? h('line', {
+					x1: neuron.source[1].ui.cx, y1: neuron.source[1].ui.cy,
+					x2: neuron.ui.cx, y2: neuron.ui.cy,
+					style: {
+						stroke: "#46b8da",
+						strokeWidth: "2"
+					},
+				})
+				: null,
 		]);
 		]);
 	},
 	},
 	renderNeuron: function (neuron, idx) {
 	renderNeuron: function (neuron, idx) {
 		var chargePr = (100 * neuron.charge) / this.state.config_max_receptor_charge;
 		var chargePr = (100 * neuron.charge) / this.state.config_max_receptor_charge;
 		var fontColor = (chargePr < 50) ? "#000" : "#fff";
 		var fontColor = (chargePr < 50) ? "#000" : "#fff";
 		return h('g', {}, [
 		return h('g', {}, [
-			h('ellipse', {
-				cx: neuron.x, cy: neuron.y, rx: neuron.rx, ry: neuron.ry, stroke: "#46b8da", strokeWidth: 1,
+			h(neuron.uiShape, Object.assign({}, neuron.ui, {
+				stroke: "#46b8da", strokeWidth: 1,
 				fill: (chargePr > 100) ? "#f00" : "hsl(194, 67%, " + (100 - chargePr) + "%)",
 				fill: (chargePr > 100) ? "#f00" : "hsl(194, 67%, " + (100 - chargePr) + "%)",
 				// data_neuron_idx: idx,
 				// data_neuron_idx: idx,
 				// onClick: this.handleClickNeuron,
 				// onClick: this.handleClickNeuron,
 				style: { cursor: "pointer" },
 				style: { cursor: "pointer" },
-			}, [
-				h('title', {}, this.viewNeuronTitle(neuron.value)),
-			]),
+			}), [
+					h('title', {}, this.viewNeuronTitle(neuron.value)),
+				]),
 			h('text', {
 			h('text', {
-				x: neuron.x, y: neuron.y + 1, fill: fontColor,
+				x: neuron.ui.cx, y: neuron.ui.cy + 1, fill: fontColor,
 				dominantBaseline: "middle", textAnchor: "middle",
 				dominantBaseline: "middle", textAnchor: "middle",
 				style: { fontSize: "10px", cursor: "pointer" },
 				style: { fontSize: "10px", cursor: "pointer" },
 				// data_neuron_idx: idx,
 				// data_neuron_idx: idx,
 				// onClick: this.handleClickNeuron,
 				// onClick: this.handleClickNeuron,
-			}, this.viewNeuronValue(neuron.value)),
+			}, neuron.charge.toFixed(1)),
 		]);
 		]);
 	},
 	},
 	viewNeuronValue: function (value) {
 	viewNeuronValue: function (value) {
@@ -240,40 +515,48 @@ var p5UI__TestNeuron = createReactClass({
 			.replace(new RegExp("\n", "g"), "\\n")
 			.replace(new RegExp("\n", "g"), "\\n")
 			.replace(new RegExp("\t", "g"), "\\t")
 			.replace(new RegExp("\t", "g"), "\\t")
 			.replace(new RegExp(" ", "g"), "_")
 			.replace(new RegExp(" ", "g"), "_")
-		;
+			;
 	},
 	},
 	viewNeuronTitle: function (value) {
 	viewNeuronTitle: function (value) {
 		return this.viewNeuronValue(value);
 		return this.viewNeuronValue(value);
 	},
 	},
 	renderReceptor: function (receptor, idx) {
 	renderReceptor: function (receptor, idx) {
+		// orange: hsl(40, 100%, 50%) --> hsl(0, 100%, 50%) -> red (overcharged)
 		// var totalReceptors = this.state.receptor.length;
 		// var totalReceptors = this.state.receptor.length;
-		// var cx = Math.ceil(this.state.output_width / ( totalReceptors + 1 ));
-		// var r = Math.min(Math.ceil(this.state.output_width / ( totalReceptors + 10 ) / 2), this.state.max_receptor_r);
+		// var cx = Math.ceil(this.state.ui_output_width / ( totalReceptors + 1 ));
+		// var r = Math.min(Math.ceil(this.state.ui_output_width / ( totalReceptors + 10 ) / 2), this.state.ui_max_receptor_r);
 		// DBG && console.log('DBG:renderReceptor', { receptor, idx, totalReceptors, r });
 		// DBG && console.log('DBG:renderReceptor', { receptor, idx, totalReceptors, r });
 		// var xCenter = cx + cx * idx - r;
 		// var xCenter = cx + cx * idx - r;
 		var chargePr = (100 * receptor.charge) / this.state.config_max_receptor_charge;
 		var chargePr = (100 * receptor.charge) / this.state.config_max_receptor_charge;
-		var fontColor = (chargePr < 50) ? "#000" : "#fff";
+		// var shapeColor = (chargePr > 100) ? "#f00" : "hsl(194, 67%, " + (100 - chargePr) + "%)"; // blue -> dark blue -> red
+		var shapeColor = (chargePr > 100) ? "#f00" : "hsl(" + (40 - chargePr * 2 / 5).toFixed() + ", 100%, 50%)"; // orange -> dark orange -> red
+		var fontColor = "#fff"; // (chargePr < 50) ? "#000" : "#fff";
 		// this.state.config_max_receptor_charge = 100%
 		// this.state.config_max_receptor_charge = 100%
 		// receptor.charge = x% (max 100)
 		// receptor.charge = x% (max 100)
 		return h('g', {}, [
 		return h('g', {}, [
-			h('circle', {
-				cx: receptor.x, cy: receptor.y, r: receptor.r, stroke: "#46b8da", strokeWidth: 1,
-				// fill: "#5bc0de"
-				// (receptor.charge > this.state.config_max_receptor_charge)
-				fill: (chargePr > 100) ? "#f00" : "hsl(194, 67%, " + (100 - chargePr) + "%)",
+			h(receptor.uiShape, Object.assign({}, receptor.ui, {
+				stroke: "#46b8da", strokeWidth: 1,
+				fill: shapeColor,
 				data_receptor_idx: idx,
 				data_receptor_idx: idx,
 				onClick: this.handleClickReceptor,
 				onClick: this.handleClickReceptor,
 				style: { cursor: "pointer" },
 				style: { cursor: "pointer" },
-			}, [
-				h('title', {}, this.viewReceptorTitle(receptor.letter)),
-			]),
+			}), [
+					h('title', {}, this.viewReceptorTitle(receptor.value)),
+				]),
 			h('text', {
 			h('text', {
-				x: receptor.x, y: receptor.y + 1, fill: fontColor,
+				x: receptor.ui.cx, y: receptor.ui.cy + 1, fill: fontColor,
 				dominantBaseline: "middle", textAnchor: "middle",
 				dominantBaseline: "middle", textAnchor: "middle",
 				style: { fontSize: "10px", cursor: "pointer" },
 				style: { fontSize: "10px", cursor: "pointer" },
 				data_receptor_idx: idx,
 				data_receptor_idx: idx,
 				onClick: this.handleClickReceptor,
 				onClick: this.handleClickReceptor,
-			}, this.viewReceptorLetter(receptor.letter)),
+			}, receptor.charge.toFixed(1)),
+			h('text', {
+				x: receptor.ui.cx, y: receptor.ui.cy - 20, fill: "#000",
+				dominantBaseline: "middle", textAnchor: "middle",
+				style: { fontSize: "10px", cursor: "pointer" },
+				data_receptor_idx: idx,
+				onClick: this.handleClickReceptor,
+			}, this.viewReceptorLetter(receptor.value)),
 		]);
 		]);
 	},
 	},
 	viewReceptorLetter: function (letter) {
 	viewReceptorLetter: function (letter) {
@@ -296,14 +579,14 @@ var p5UI__TestNeuron = createReactClass({
 		if (null === this.state.selectedReceptorIdx) return null;
 		if (null === this.state.selectedReceptorIdx) return null;
 		var receptor = this.state.receptor[this.state.selectedReceptorIdx];
 		var receptor = this.state.receptor[this.state.selectedReceptorIdx];
 		return h('div', {}, [
 		return h('div', {}, [
-			"Receptor: '" + this.viewReceptorTitle(receptor.letter) + "' charge('" + receptor.charge.toFixed(2) + "')",
+			"Receptor: '" + this.viewReceptorTitle(receptor.value) + "' charge('" + receptor.charge.toFixed(2) + "')",
 		]);
 		]);
 	},
 	},
 	renderAllReceptorInfo: function () {
 	renderAllReceptorInfo: function () {
 		return h('div', {}, this.state.receptor.map(this.renderAllReceptorInfo_item));
 		return h('div', {}, this.state.receptor.map(this.renderAllReceptorInfo_item));
 	},
 	},
 	renderAllReceptorInfo_item: function (receptor) {
 	renderAllReceptorInfo_item: function (receptor) {
-		return h('div', {}, ["Receptor: '" + this.viewReceptorTitle(receptor.letter) + "' charge('" + receptor.charge.toFixed(2) + "')"]);
+		return h('div', {}, ["Receptor: '" + this.viewReceptorTitle(receptor.value) + "' charge('" + receptor.charge.toFixed(2) + "')"]);
 	},
 	},
 	renderAllNeuronInfo: function () {
 	renderAllNeuronInfo: function () {
 		return h('div', {}, this.state.neuron.map(this.renderAllNeuronInfo_item));
 		return h('div', {}, this.state.neuron.map(this.renderAllNeuronInfo_item));
@@ -378,13 +661,36 @@ var p5UI__TestNeuron = createReactClass({
 		state[name] = value;
 		state[name] = value;
 		this.setState(state);
 		this.setState(state);
 	},
 	},
+	renderLog: function () {
+		return LOG__render(this._log)
+	},
+	handleRefreshLog: function () {
+		if (!this._log.length) return;
+		var lastLog = this._log[this._log.length - 1]
+		console.table(
+			[
+				lastLog.receptor.map(function (node) { return "R:'" + node.value + "'"; })
+					.concat(
+						lastLog.neuron.map(function (node) { return "N:'" + node.value + "'"; })
+					)
+			].concat(
+				this._log.map(function (log) {
+					return log.receptor.map(function (node) { return node.charge })
+						.concat(
+							log.neuron.map(function (node) { return node.charge })
+						)
+				})
+			)
+		)
+	},
 	render: function () {
 	render: function () {
+		DBG1 && console.log("DBG:render", { state: this.state });
 		return h('div', {}, [
 		return h('div', {}, [
-			h('table', { className: "table table-border" }, [
+			h('table', { className: "table table-border", p5_node_id: "p5-neuron-output-table" }, [
 				h('tbody', {}, [
 				h('tbody', {}, [
 					h('tr', {}, [
 					h('tr', {}, [
-						h('td', { style: { width: this.state.output_width + 20 } }, [
-							h('svg', { ref: this.setOutputRet, height: 300, width: this.state.output_width, style: { border: "1px solid #eee" } }, [
+						h('td', { style: { width: this.state.ui_output_width + 20 } }, [
+							h('svg', { ref: this.setOutputRet, height: 300, width: this.state.ui_output_width, style: { border: "1px solid #eee" } }, [
 								this.state.neuron.map(this.renderConnections),
 								this.state.neuron.map(this.renderConnections),
 								this.state.receptor.map(this.renderReceptor),
 								this.state.receptor.map(this.renderReceptor),
 								this.state.neuron.map(this.renderNeuron),
 								this.state.neuron.map(this.renderNeuron),
@@ -392,15 +698,22 @@ var p5UI__TestNeuron = createReactClass({
 						]),
 						]),
 						h('td', { style: { verticalAlign: "top" } }, [
 						h('td', { style: { verticalAlign: "top" } }, [
 							this.renderReceptorInfo(),
 							this.renderReceptorInfo(),
-						]),
-						h('td', { style: { verticalAlign: "top" } }, [
 							this.renderAllReceptorInfo(),
 							this.renderAllReceptorInfo(),
 							this.renderAllNeuronInfo(),
 							this.renderAllNeuronInfo(),
 						]),
 						]),
 					]),
 					]),
 				]),
 				]),
 			]),
 			]),
-			h('button', { className: "btn btn-primary", onClick: this.handleExec }, "Uruchom"),
+			h('div', {}, [
+				h('button', { className: "btn btn-primary", onClick: this.handleExec }, "Uruchom"),
+				" ",
+				this.state.doAnim
+					? h('button', { className: "btn btn-default", onClick: this.handlePause }, [h('i', { className: "glyphicon glyphicon-pause" }), "Stop"])
+					: h('button', { className: "btn btn-default", onClick: this.handleStart }, [h('i', { className: "glyphicon glyphicon-play" }), "Start"])
+				,
+				h('button', { className: "btn btn-default", onClick: this.handleRefreshLog }, [h('i', { className: "glyphicon glyphicon-refresh" }), " ", "Log"])
+			]),
+			this.renderLog(),
 			h('table', { className: "table table-border" }, [
 			h('table', { className: "table table-border" }, [
 				h('tbody', {}, [
 				h('tbody', {}, [
 					h('tr', {}, [
 					h('tr', {}, [
@@ -421,6 +734,6 @@ var p5UI__TestNeuron = createReactClass({
 	}
 	}
 });
 });
 
 
-ReactDOM.render(h(p5UI__TestNeuron
-
-), document.getElementById(HTML_ID))
+ReactDOM.render(h(NeuronView, {
+	initialData: INITIAL_DATA || "",
+}), document.getElementById(HTML_ID))