|
|
@@ -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))
|