Piotr Labudda 7 лет назад
Родитель
Сommit
c598f0ce95
2 измененных файлов с 456 добавлено и 0 удалено
  1. 30 0
      SE/se-lib/Route/Test/Neuron.php
  2. 426 0
      SE/se-lib/Route/Test/Neuron.php.view.js

+ 30 - 0
SE/se-lib/Route/Test/Neuron.php

@@ -0,0 +1,30 @@
+<?php
+
+Lib::loadClass('RouteBase');
+Lib::loadClass('UI');
+Lib::loadClass('Response');
+
+class Route_Test_Neuron extends RouteBase {// TODO: UrlActionBase @see Route_UrlAction
+
+    function defaultAction() { UI::layout([ $this, 'defaultView' ]); }
+    function defaultView() {
+        echo UI::h('div', [ 'id' => "p5widget-test-neuron" ]);
+        // echo UI::h('script', ['src'=>"https://d3js.org/d3.v5.min.js", 'type'=>"text/javascript"]);
+        echo UI::h('script', ['src'=>"static/vendor.js?v=71baa97d", 'type'=>"text/javascript"]);
+        UI::inlineJS(__FILE__ . '.view.js', [
+            'HTML_ID' => "p5widget-test-neuron",
+            'INITIAL_DATA' => implode("\n", [
+                "A",
+                "AAA",
+                "ABC",
+                "AAB",
+                "ACCCB",
+                "CCC",
+                "ABCDE",
+                "AA",
+                "BB",
+            ]),
+        ]);
+        throw new Exception("TODO");
+    }
+}

+ 426 - 0
SE/se-lib/Route/Test/Neuron.php.view.js

@@ -0,0 +1,426 @@
+var createReactClass = window.p5VendorJs.createReactClass;
+var h = window.p5VendorJs.React.createElement;
+var ReactDOM = window.p5VendorJs.ReactDOM;
+// var AsyncTypeahead = window.p5VendorJs.AsyncTypeahead;
+// var swal = window.swal;
+if (!HTML_ID) throw "Missing HTML_ID";
+// if (!d3) throw "Missing d3 (https://d3js.org/d3.v5.min.js)";
+var DBG = DBG || false;
+var DBG1 = true;
+
+// <svg height="100" width="100">
+//     <circle cx="50" cy="50" r="40" stroke="black" stroke-width="3" fill="red" />
+// </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,
+
+			selectedReceptorIdx: null,
+		};
+	},
+	makeReceptor: function (letter, idx, arr) {
+		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 xCenter = cx + cx * idx - r;
+
+		return {
+			letter: letter,
+			charge: 0,
+			x: xCenter,
+			y: 20,
+			r: r,
+		}
+	},
+	dischargeNeuron: function (neuron) {
+		return (neuron.charge > 0)
+			?	Object.assign({}, neuron, {
+					charge: Math.max(0, neuron.charge - this.state.config_discharge_per_tick),
+				})
+			:	neuron
+		;
+	},
+
+	handleChangeInput: function (event) {
+		this.setState({
+			inputText: event.target.value,
+		})
+	},
+	setInputRet: function (reactEl) { this._input = 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 () {
+		this.startAnimation();
+	},
+	handleExec: function (event) {
+		event.preventDefault();
+		DBG1 && console.log('DBG:handleExec...');
+		this.startAnimation();
+	},
+	startAnimation: function (event) {
+		var distinct = function (value, idx, self) {
+			return ( idx === self.indexOf(value) );
+		};
+		// var foundLetters = this.state.inputText.split("\n").reduce(function (ret, line) {
+		// 	return ret.concat(
+		// 		line.split('').filter(distinct)
+		// 	).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: [],
+		});
+		setTimeout(this.forwardAnim, this.state.config_read_speed)
+	},
+	getReceptor: function (char) {
+		var receptor = this.state.receptor.filter(function (receptor) {
+			return (char == receptor.letter);
+		});
+		if (!receptor.length) throw "BUG: no input receptor found for char '" + char + "'"; // TODO: create on demand?
+		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: 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,
+				}
+			])
+		} else {
+			return listNeuron.map(function (neuron) {
+				(neuron.value === value)
+					?	Object.assign({}, neuron, { charge: neuron.charge + (receptor.charge + prevReceptor.charge) / 2 })
+					:	neuron
+				;
+			})
+		}
+	},
+	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 });
+			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
+					;
+				})
+			}
+		}
+		// 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,
+		});
+
+		setTimeout(this.forwardAnim, this.state.config_read_speed)
+	},
+	selectReceptor: function (receptorIdx) {
+		this.setState({ selectedReceptorIdx: receptorIdx })
+	},
+	handleClickReceptor: function (event) {
+		var data_receptor_idx = event.target.getAttribute('data_receptor_idx')
+		DBG1 && console.log('DBG:handleClickReceptor', { data_receptor_idx, target: event.target });
+		this.selectReceptor(data_receptor_idx)
+	},
+
+	renderConnections: function (neuron, idx) {
+		return h('g', {}, [
+			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,
+				style: {
+					stroke: "#46b8da",
+					strokeWidth: "2"
+				},
+			}),
+		]);
+	},
+	renderNeuron: function (neuron, idx) {
+		var chargePr = (100 * neuron.charge) / this.state.config_max_receptor_charge;
+		var fontColor = (chargePr < 50) ? "#000" : "#fff";
+		return h('g', {}, [
+			h('ellipse', {
+				cx: neuron.x, cy: neuron.y, rx: neuron.rx, ry: neuron.ry, stroke: "#46b8da", strokeWidth: 1,
+				fill: (chargePr > 100) ? "#f00" : "hsl(194, 67%, " + (100 - chargePr) + "%)",
+				// data_neuron_idx: idx,
+				// onClick: this.handleClickNeuron,
+				style: { cursor: "pointer" },
+			}, [
+				h('title', {}, this.viewNeuronTitle(neuron.value)),
+			]),
+			h('text', {
+				x: neuron.x, y: neuron.y + 1, fill: fontColor,
+				dominantBaseline: "middle", textAnchor: "middle",
+				style: { fontSize: "10px", cursor: "pointer" },
+				// data_neuron_idx: idx,
+				// onClick: this.handleClickNeuron,
+			}, this.viewNeuronValue(neuron.value)),
+		]);
+	},
+	viewNeuronValue: function (value) {
+		return value
+			.replace(new RegExp("\n", "g"), "\\n")
+			.replace(new RegExp("\t", "g"), "\\t")
+			.replace(new RegExp(" ", "g"), "_")
+		;
+	},
+	viewNeuronTitle: function (value) {
+		return this.viewNeuronValue(value);
+	},
+	renderReceptor: function (receptor, idx) {
+		// 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);
+		// DBG && console.log('DBG:renderReceptor', { receptor, idx, totalReceptors, r });
+		// var xCenter = cx + cx * idx - r;
+		var chargePr = (100 * receptor.charge) / this.state.config_max_receptor_charge;
+		var fontColor = (chargePr < 50) ? "#000" : "#fff";
+		// this.state.config_max_receptor_charge = 100%
+		// receptor.charge = x% (max 100)
+		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) + "%)",
+				data_receptor_idx: idx,
+				onClick: this.handleClickReceptor,
+				style: { cursor: "pointer" },
+			}, [
+				h('title', {}, this.viewReceptorTitle(receptor.letter)),
+			]),
+			h('text', {
+				x: receptor.x, y: receptor.y + 1, fill: fontColor,
+				dominantBaseline: "middle", textAnchor: "middle",
+				style: { fontSize: "10px", cursor: "pointer" },
+				data_receptor_idx: idx,
+				onClick: this.handleClickReceptor,
+			}, this.viewReceptorLetter(receptor.letter)),
+		]);
+	},
+	viewReceptorLetter: function (letter) {
+		switch (letter) {
+			case "\t": return "\\t";
+			case " ": return "_";
+			case "\n": return "\\n";
+			default: return letter;
+		}
+	},
+	viewReceptorTitle: function (letter) {
+		switch (letter) {
+			case "\t": return "tab";
+			case " ": return "space";
+			case "\n": return "new line";
+			default: return letter;
+		}
+	},
+	renderReceptorInfo: function () {
+		if (null === this.state.selectedReceptorIdx) return null;
+		var receptor = this.state.receptor[this.state.selectedReceptorIdx];
+		return h('div', {}, [
+			"Receptor: '" + this.viewReceptorTitle(receptor.letter) + "' charge('" + receptor.charge.toFixed(2) + "')",
+		]);
+	},
+	renderAllReceptorInfo: function () {
+		return h('div', {}, this.state.receptor.map(this.renderAllReceptorInfo_item));
+	},
+	renderAllReceptorInfo_item: function (receptor) {
+		return h('div', {}, ["Receptor: '" + this.viewReceptorTitle(receptor.letter) + "' charge('" + receptor.charge.toFixed(2) + "')"]);
+	},
+	renderAllNeuronInfo: function () {
+		return h('div', {}, this.state.neuron.map(this.renderAllNeuronInfo_item));
+	},
+	renderAllNeuronInfo_item: function (neuron) {
+		return h('div', {}, ["Neuron: '" + neuron.value + "' charge('" + neuron.charge.toFixed(2) + "')"]);
+	},
+	renderInput: function () {
+		return h('details', { open: true, style: { margin: "12px 0", border: "1px solid #aaa", borderRadius: "6px", padding: "12px", backgroundColor: "#eee", } }, [
+			h('summary', { style: { cursor: "pointer" } }, "Input"),
+			h('textarea', {
+				ref: this.setInputRet,
+				onChange: this.handleChangeInput,
+				value: this.state.inputText,
+				rows: 10,
+				style: {
+					width: "100%",
+					padding: "12px",
+					marginTop: "6px",
+					backgroundColor: "#fff",
+				},
+			}),
+		]);
+	},
+	renderConfig: function () {
+		return h('details', { open: true, style: { margin: "12px 0", border: "1px solid #aaa", borderRadius: "6px", padding: "12px", backgroundColor: "#eee", } }, [
+			h('summary', { style: { cursor: "pointer" } }, "Config"),
+			h('div', { style: { padding: "12px", backgroundColor: "#fff", } }, [
+				h('div', {}, "Read speed:"),
+				h('input', {
+					className: "form-control input-sm",
+					type: "number",
+					value: this.state.config_read_speed,
+					name: "config_read_speed",
+					onChange: this.handleChangeConfig,
+				}),
+				h('div', { style: { marginTop: "12px" } }),
+				h('div', {}, "Charge receptor at input:"),
+				h('input', {
+					className: "form-control input-sm",
+					type: "number",
+					value: this.state.config_charge_receptor_at_input,
+					name: "config_charge_receptor_at_input",
+					onChange: this.handleChangeConfig,
+				}),
+				h('div', { style: { marginTop: "12px" } }),
+				h('div', {}, "Max receptor charge:"),
+				h('input', {
+					className: "form-control input-sm",
+					type: "number",
+					value: this.state.config_max_receptor_charge,
+					name: "config_max_receptor_charge",
+					onChange: this.handleChangeConfig,
+				}),
+				h('div', { style: { marginTop: "12px" } }),
+				h('div', {}, "Discharge per tick:"),
+				h('input', {
+					className: "form-control input-sm",
+					type: "number",
+					step: "0.1",
+					value: this.state.config_discharge_per_tick,
+					name: "config_discharge_per_tick",
+					onChange: this.handleChangeConfig,
+				}),
+			]),
+		]);
+	},
+	handleChangeConfig: function (event) {
+		var value = event.target.value;
+		var name = event.target.getAttribute('name');
+		var state = this.state;
+		state[name] = value;
+		this.setState(state);
+	},
+	render: function () {
+		return h('div', {}, [
+			h('table', { className: "table table-border" }, [
+				h('tbody', {}, [
+					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" } }, [
+								this.state.neuron.map(this.renderConnections),
+								this.state.receptor.map(this.renderReceptor),
+								this.state.neuron.map(this.renderNeuron),
+							]),
+						]),
+						h('td', { style: { verticalAlign: "top" } }, [
+							this.renderReceptorInfo(),
+						]),
+						h('td', { style: { verticalAlign: "top" } }, [
+							this.renderAllReceptorInfo(),
+							this.renderAllNeuronInfo(),
+						]),
+					]),
+				]),
+			]),
+			h('button', { className: "btn btn-primary", onClick: this.handleExec }, "Uruchom"),
+			h('table', { className: "table table-border" }, [
+				h('tbody', {}, [
+					h('tr', {}, [
+						h('td', { style: { width: "50%" } }, [
+							this.renderInput(),
+						]),
+						h('td', { style: { width: "50%" } }, [
+							this.renderConfig(),
+						]),
+					]),
+				]),
+			]),
+
+			h('div', {}, [
+				"TODO: receptory...",
+			]),
+		]);
+	}
+});
+
+ReactDOM.render(h(p5UI__TestNeuron
+
+), document.getElementById(HTML_ID))