Neuron.php.NeuronView.js 29 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823
  1. var React = window.p5VendorJs.React;
  2. var createReactClass = window.p5VendorJs.createReactClass;
  3. var h = window.p5VendorJs.React.createElement;
  4. var ReactDOM = window.p5VendorJs.ReactDOM;
  5. // var AsyncTypeahead = window.p5VendorJs.AsyncTypeahead;
  6. // var swal = window.swal;
  7. if (!HTML_ID) throw "Missing HTML_ID";
  8. if (!MAKE_STORE_FUNCTION_NAME) throw "Missing MAKE_STORE_FUNCTION_NAME";
  9. // if (!d3) throw "Missing d3 (https://d3js.org/d3.v5.min.js)";
  10. var DBG = DBG || false;
  11. var DBG1 = true;
  12. // Receptor is special Neuron which reads input (letters in this case)
  13. // Receptor.ui: { cx, cy, r } // circle
  14. // Neuron: { charge, label, draw, source }
  15. // Neuron.ui: { cx, cy, rx, ry } // eclipse
  16. // Neuron.source: [ { neuron: Neuron | Receptor, atCharge }, ... ]
  17. // Receptor.neighbours: [] list of other receptors with max(receptors.charge)
  18. // makeNeuron cases:
  19. // stream: AAAAA and only A has charge then create Neuron over A and discharge and lower max charge
  20. // stream: AAAAA and receptor B has charge then create Neuron between A and B and discharge (closer to receptor with higher charge)
  21. var DEFAULT_CONFIG = {
  22. ui_output_width: 1000,
  23. ui_output_height: 300,
  24. ui_max_receptor_r: 12,
  25. ui_space_y: 30,
  26. config_anim_speed: 500,
  27. config_read_input_keys_speed: 700,
  28. config_read_speed_multiplier: 4,
  29. config_charge_receptor_at_input: 1,
  30. config_max_receptor_charge: 1,
  31. config_max_neuron_charge: 1.5,
  32. config_discharge_per_tick: 0.1,
  33. config_discharge_max_in_new_neuron_from_one: 1, // 0.7
  34. config_strategy_overcharge: "LEAVE_ALMOST_MAX", // "REMOVE_CHARGE" | "LEAVE_HALF_CHARGE" | "LEAVE_ALMOST_MAX"
  35. }
  36. // var DEFAULT_NEURON = {
  37. // value: '',
  38. // charge: 0,
  39. // maxCharge: DEFAULT_CONFIG.config_max_neuron_charge,
  40. // uiShape: "circle",
  41. // ui: { cx: 0, cy: 0 },
  42. // source: [],
  43. // next: [],
  44. // }
  45. function LOG__render(listLogEntries) {
  46. // console.log("DBG", listLogEntries)
  47. return h('details', { open: false, style: { margin: "12px 0", border: "1px solid #aaa", borderRadius: "6px", padding: "12px", backgroundColor: "#eee", } }, [
  48. h('summary', { style: { cursor: "pointer" } }, "Log"),
  49. h('div', { style: { border: "1px solid #eee", backgroundColor: "#fff" } }, [
  50. h('table', { className: "table table-condensed table-bordered table-hover" }, [
  51. h('thead', {}, LOG__renderTheadLog(listLogEntries)),
  52. h('tbody', {}, LOG__renderTbodyLog(listLogEntries)),
  53. ]),
  54. ]),
  55. ]);
  56. }
  57. function LOG__renderTheadLog(listLogEntries) {
  58. if (!listLogEntries.length) return null;
  59. var lastLog = listLogEntries[listLogEntries.length - 1]
  60. return [
  61. h('tr', {},
  62. [ h('th', {}, "Lp.") ]
  63. .concat(
  64. lastLog.map(function (nodeInfo) {
  65. return h('th', {
  66. style: { color: (nodeInfo.type === 'receptor') ? "orange" : "blue" },
  67. }, "'" + nodeInfo.value + "'");
  68. })
  69. )
  70. )
  71. ];
  72. }
  73. function LOG__renderTbodyLog(listLogEntries) {
  74. if (!listLogEntries.length) return null;
  75. return listLogEntries.map(function (log, lpLog) {
  76. return h('tr', {},
  77. [ h('th', { style: { color: "#ddd" } }, '' + (lpLog + 1) + '.') ]
  78. .concat(
  79. log.map(LOG__renderCellNodeChargeLog)
  80. )
  81. )
  82. })
  83. }
  84. function LOG__renderCellNodeChargeLog(nodeInfo) {
  85. if (null === nodeInfo) return null;
  86. return h('td', {
  87. style: {
  88. color: (nodeInfo.charge > 0.1) ? "#000" : "#aaa",
  89. backgroundColor: (nodeInfo.charge > 0) ? ( (nodeInfo.type === 'receptor') ? "#fa0" : "#00d0ff" ) : "none",
  90. },
  91. }, nodeInfo.charge.toFixed(2));
  92. }
  93. var p5Utils__ArrayDistinctFilter = function (value, idx, self) {
  94. return (idx === self.indexOf(value));
  95. };
  96. var NeuronView = createReactClass({
  97. // _inputNode: null,
  98. // _outputNode: null,
  99. _receptor: [],
  100. _neuron: [],
  101. _store: null,
  102. _log: [],
  103. _readInputLineIntervalID: null,
  104. getInitialState: function () {
  105. return Object.assign({}, DEFAULT_CONFIG, this.getStateFromStore(), {
  106. inputText: (this.props.initialData || []).join(''),
  107. inputType: this.props.inputType || "text",
  108. inputKeysUp: "",
  109. animLastLetter: null,
  110. selectedReceptorIdx: null,
  111. });
  112. },
  113. componentDidMount: function () {
  114. this._unsubscribe = this.props.store.subscribe(this.storeUpdated)
  115. document.addEventListener('keyup', this.handleKeyUp)
  116. document.addEventListener('keydown', this.handleKeyDown)
  117. this.startNewAnim(this.props.initialData);
  118. this._readInputLineIntervalID = setInterval(this.inputReadKeys, this.state.config_read_input_keys_speed)
  119. },
  120. startNewAnim: function (initialData) {
  121. this.log = [];
  122. switch (this.state.inputType) {
  123. case "text": this.props.store.dispatch({ type: 'INIT', input: this.prepareInputFromText(this.state.inputText), config: this.state }); break;
  124. case "key": this.props.store.dispatch({ type: 'INIT', input: initialData || [], config: this.state }); break;
  125. }
  126. },
  127. prepareInputFromText: function (text) {
  128. return text.split('').map(function (char) { return char.split(''); })
  129. },
  130. handleKeyUp: function (event) {
  131. DBG && console.log("DBG:NeuronView:handleKeyUp", { keyCode: event.keyCode })
  132. if (32 === event.keyCode) {
  133. event.preventDefault()
  134. var state = this.props.store.getState()
  135. if (state.doAnim) {
  136. this.props.store.dispatch({ type: 'PAUSE' })
  137. } else {
  138. this.props.store.dispatch({ type: 'PLAY' })
  139. }
  140. }
  141. },
  142. handleKeyDown: function (event) {
  143. if (32 === event.keyCode) event.preventDefault() // avoid page scroll after hit space btn
  144. // [A-Z] = [65-90]
  145. if ("key" === this.state.inputType && event.keyCode >= 65 && event.keyCode <= 90) {
  146. this.setState({
  147. inputKeysUp: this.state.inputKeysUp + String.fromCharCode(event.keyCode)
  148. });
  149. }
  150. },
  151. inputReadKeys: function () {
  152. if ("key" !== this.state.inputType) return;
  153. var inputKeysUp = this.state.inputKeysUp
  154. var uniqInputKeys = inputKeysUp.split('').filter(p5Utils__ArrayDistinctFilter)
  155. this.setState({
  156. inputKeysUp: "",
  157. });
  158. // if (uniqInputKeys.length > 0) {
  159. this.props.store.dispatch({ type: 'INPUT_ADD_LINE', input: [ uniqInputKeys ] })
  160. // }
  161. },
  162. handleToggleInput: function (event) {
  163. // p5_node_id: "input_type-text",
  164. // p5_node_id: "input_type-key",
  165. event.preventDefault()
  166. var targetNodeType = event.target.getAttribute("p5_node_id")
  167. if (!targetNodeType) return;
  168. var toType = targetNodeType.substr("input_type-".length)
  169. DBG && console.log("DBG:handleToggleInput", { target: targetNodeType, toType })
  170. if (toType !== this.state.inputType) {
  171. this.setState({ inputType: toType })
  172. this.startNewAnim()
  173. }
  174. },
  175. componentWillUnmount: function () {
  176. if (this._unsubscribe) this._unsubscribe()
  177. document.removeEventListener('keyup', this.handleKeyUp)
  178. document.removeEventListener('keydown', this.handleKeyDown)
  179. if (this._readInputLineIntervalID) clearInterval(this._readInputLineIntervalID)
  180. },
  181. storeUpdated: function () {
  182. DBG && console.log('DBG:NeuronView:storeUpdated');
  183. this._log.push(this.props.store.getNodesChargeState())
  184. this.setState(this.getStateFromStore())
  185. },
  186. getStateFromStore: function () {
  187. var state = this.props.store.getState();
  188. return {
  189. doAnim: state.doAnim,
  190. animPos: state.animPos,
  191. inputReadPos: state.inputReadPos,
  192. uiOutputHeight: state.uiOutputHeight,
  193. };
  194. },
  195. handleChangeInput: function (event) {
  196. this.setState({
  197. inputText: event.target.value,
  198. })
  199. this.props.store.dispatch({ type: 'INPUT_UPDATED', input: this.prepareInputFromText(event.target.value) })
  200. },
  201. // setInputRet: function (reactEl) { this._inputNode = reactEl; },
  202. // setOutputRet: function (reactEl) { this._outputNode = reactEl; },
  203. handleReset: function (event) {
  204. event.preventDefault();
  205. DBG && console.log('DBG:NeuronView:handleReset...');
  206. this.startNewAnim()
  207. },
  208. handlePause: function (event) {
  209. event.preventDefault();
  210. this.props.store.dispatch({ type: 'PAUSE' })
  211. },
  212. handlePlay: function (event) {
  213. event.preventDefault();
  214. this.props.store.dispatch({ type: 'PLAY' })
  215. },
  216. getReceptor: function (char) {
  217. var receptor = this._receptor.filter(function (receptor) {
  218. return (char == receptor.value);
  219. });
  220. if (!receptor.length) throw "BUG: no input receptor found for char '" + char + "'"; // TODO: create on demand?
  221. return receptor[0];
  222. },
  223. selectReceptor: function (receptorIdx) {
  224. this.setState({ selectedReceptorIdx: receptorIdx })
  225. },
  226. handleClickReceptor: function (event) {
  227. var data_receptor_idx = event.target.getAttribute('data_receptor_idx')
  228. DBG && console.log('DBG:NeuronView:handleClickReceptor', { data_receptor_idx, target: event.target });
  229. this.selectReceptor(data_receptor_idx)
  230. },
  231. // renderConnections: function (neuron, idx) {
  232. // DBG && console.log('DBG:NeuronView:renderConnections', { neuron, p1: neuron.source[0].ui, p2: (neuron.source.length > 1) ? neuron.source[1].ui : null });
  233. // return h('g', {}, [
  234. // h('line', {
  235. // x1: neuron.source[0].ui.cx, y1: neuron.source[0].ui.cy,
  236. // x2: neuron.ui.cx, y2: neuron.ui.cy,
  237. // style: {
  238. // stroke: "#46b8da",
  239. // strokeWidth: "2"
  240. // },
  241. // }),
  242. // (neuron.source.length > 1)
  243. // ? h('line', {
  244. // x1: neuron.source[1].ui.cx, y1: neuron.source[1].ui.cy,
  245. // x2: neuron.ui.cx, y2: neuron.ui.cy,
  246. // style: {
  247. // stroke: "#46b8da",
  248. // strokeWidth: "2"
  249. // },
  250. // })
  251. // : null,
  252. // ]);
  253. // },
  254. renderConnection: function (conn, idx) {
  255. var uiPos = this.getConnectionUIPos(conn)
  256. DBG && console.log('DBG:NeuronView:renderConnection', { conn, uiPos });
  257. return h('g', { p5_node_id: "conn-idx-" + idx }, [
  258. h('line', Object.assign({}, uiPos, {
  259. style: {
  260. stroke: "#46b8da",
  261. strokeWidth: "2"
  262. },
  263. })),
  264. ]);
  265. // fromIdx: 0
  266. // fromType: "receptor"
  267. // timesCreated: 1
  268. // toIdx: 2
  269. },
  270. getConnectionUIPos: function (conn) { // @return { x1, y1, x2, y2 }
  271. // fromIdx: 0
  272. // fromType: "receptor"
  273. // timesCreated: 1
  274. // toIdx: 2
  275. switch (conn.fromType) {
  276. case 'receptor': return this.getConnectionUIPosFromReceptor(conn.from, conn.to);
  277. case 'neuron': return this.getConnectionUIPosFromNeuron(conn.from, conn.to);
  278. default: {
  279. DBG && console.warn("Not implemented render charge type '" + conn.fromType + "'", { conn })
  280. return null;
  281. }
  282. }
  283. },
  284. getConnectionUIPosFromReceptor: function (fromIdx, toIdx) { // @return { x1, y1, x2, y2 }
  285. var sourceNode = this.props.store.getReceptor(fromIdx)
  286. var destNode = this.props.store.getNeuron(toIdx)
  287. DBG && console.log('DBG:NeuronView:getConnectionUIPosFromReceptor', { fromIdx, toIdx, sourceNode: sourceNode, destNode: destNode });
  288. if (!sourceNode || !destNode) {
  289. DBG && console.warn("Missing source or dest node at renderConnection", { fromIdx, toIdx })
  290. return null;
  291. }
  292. return {
  293. x1: sourceNode.ui.cx, y1: sourceNode.ui.cy,
  294. x2: destNode.ui.cx, y2: destNode.ui.cy,
  295. };
  296. },
  297. getConnectionUIPosFromNeuron: function (fromIdx, toIdx) { // @return { x1, y1, x2, y2 }
  298. var sourceNode = this.props.store.getNeuron(fromIdx)
  299. var destNode = this.props.store.getNeuron(toIdx)
  300. DBG && console.log('DBG:NeuronView:getConnectionUIPosFromNeuron', { fromIdx, toIdx, sourceNode: sourceNode, destNode: destNode });
  301. if (!sourceNode || !destNode) {
  302. DBG && console.warn("Missing source or dest node at getConnectionUIPosFromNeuron", { fromIdx, toIdx })
  303. return null;
  304. }
  305. return {
  306. x1: sourceNode.ui.cx, y1: sourceNode.ui.cy,
  307. x2: destNode.ui.cx, y2: destNode.ui.cy,
  308. };
  309. },
  310. renderCharge: function (charge, idx) {
  311. DBG && console.log("DBG:NeuronView:renderCharge", { charge, idx })
  312. switch (charge.nodeType) {
  313. case 'receptor': return this.renderChargeOnReceptor(charge.node, charge.value, idx);
  314. case 'neuron': return this.renderChargeOnNeuron(charge.node, charge.value, idx);
  315. case 'connection': return this.renderChargeOnConnection(charge.node, charge.value, idx);
  316. case 'doubleConn': return this.renderChargeOnDoubleConnection(charge.node, charge.value, idx);
  317. default: {
  318. DBG && console.warn("Not implemented render charge type '" + charge.nodeType + "'", { charge, idx })
  319. return null;
  320. }
  321. }
  322. },
  323. renderChargeOnReceptor: function (idx, value, chargeIdx) {
  324. var sourceNode = this.props.store.getReceptor(idx)
  325. if (!sourceNode) {
  326. DBG && console.warn("Missing source node at renderChargeOnReceptor", { charge: value, idx })
  327. return null;
  328. }
  329. var uiNodePos = sourceNode.ui
  330. var uiPos = {
  331. cx: uiNodePos.cx,
  332. cy: uiNodePos.cy,
  333. }
  334. DBG && console.log("DBG:NeuronView:renderChargeOnReceptor", { uiNodePos, uiPos })
  335. return h('g', { p5_node_id: "charge-idx-" + chargeIdx, p5_node_type: "charge-on-receptor" }, [
  336. h('circle', Object.assign({}, uiPos, {
  337. r: sourceNode.ui.r + 3,
  338. stroke: "#00d0ff",
  339. fill: "none",
  340. strokeWidth: "3px",
  341. }))
  342. ]);
  343. },
  344. renderChargeOnNeuron: function (idx, value, chargeIdx) {
  345. var sourceNode = this.props.store.getNeuron(idx)
  346. if (!sourceNode) {
  347. DBG && console.warn("Missing source node at renderChargeOnNeuron", { charge: value, idx })
  348. return null;
  349. }
  350. var uiNodePos = sourceNode.ui
  351. var uiPos = {
  352. cx: uiNodePos.cx,
  353. cy: uiNodePos.cy,
  354. }
  355. DBG && console.log("DBG:NeuronView:renderChargeOnNeuron", { uiNodePos, uiPos })
  356. return h('g', { p5_node_id: "charge-idx-" + chargeIdx, p5_node_type: "charge-on-neuron" }, [
  357. h('circle', Object.assign({}, uiPos, {
  358. r: sourceNode.ui.ry + 3,
  359. stroke: "#00d0ff",
  360. fill: "none",
  361. strokeWidth: "3px",
  362. }))
  363. ]);
  364. },
  365. renderChargeOnConnection: function (idx, value, chargeIdx) {
  366. var connNode = this.props.store.getConnection(idx)
  367. if (!connNode) {
  368. DBG && console.warn("Missing source node at renderChargeOnConnection", { charge: value, idx })
  369. return null;
  370. }
  371. var uiConnPos = this.getConnectionUIPos(connNode) // @return { x1, y1, x2, y2 }
  372. var uiPos = {
  373. cx: Math.min(uiConnPos.x1, uiConnPos.x2) + Math.abs((uiConnPos.x1 - uiConnPos.x2) / 2),
  374. cy: Math.min(uiConnPos.y1, uiConnPos.y2) + Math.abs((uiConnPos.y1 - uiConnPos.y2) / 2),
  375. }
  376. DBG && console.log("DBG:NeuronView:renderChargeOnConnection", { uiConnPos, uiPos })
  377. // connNode: { fromType: 'receptor', fromIdx: overcharged.idx, toIdx: idxNewNeuron, timesCreated: 1 }
  378. return h('g', { p5_node_id: "charge-idx-" + chargeIdx, p5_node_type: "charge-on-conn" }, [
  379. h('circle', Object.assign({}, uiPos, {
  380. r: 8,
  381. stroke: "#00d0ff",
  382. fill: "#fbfbfb",
  383. strokeWidth: "3px",
  384. })),
  385. h('text', {
  386. x: uiPos.cx, y: uiPos.cy + 1, fill: "#777",
  387. dominantBaseline: "middle", textAnchor: "middle",
  388. style: { fontSize: "8px", cursor: "pointer" },
  389. // data_neuron_idx: idx,
  390. // onClick: this.handleClickNeuron,
  391. }, value.toFixed(1)),
  392. ]);
  393. },
  394. renderChargeOnDoubleConnection: function (idx, value, chargeIdx) {
  395. var doubleConnNode = this.props.store.getDoubleConnection(idx)
  396. var chargeNode = this.props.store.getCharge(chargeIdx)
  397. DBG && console.warn("TODO:NeuronView:renderChargeOnDoubleConnection", { idx, charge: value, chargeIdx, doubleConnNode, chargeNode })
  398. return h(React.Fragment, {}, [
  399. this.renderChargeOnConnection(doubleConnNode.fromConn[0], chargeNode.connCharge[0], '' + chargeIdx + '-0'),
  400. this.renderChargeOnConnection(doubleConnNode.fromConn[1], chargeNode.connCharge[1], '' + chargeIdx + '-1'),
  401. ]);
  402. },
  403. renderNeuron: function (neuron, idx) {
  404. var chargeValue = this.props.store.getNeuronCharge(idx)
  405. // var chargePr = (100 * chargeValue) / this.state.config_max_neuron_charge;
  406. var chargePr = (100 * chargeValue) / neuron.maxCharge;
  407. var fontColor = (chargePr < 30) ? "#000" : "#fff";
  408. return h('g', { p5_node_id: "neuron-idx-" + idx, p5_max_charge: neuron.maxCharge }, [
  409. h(neuron.uiShape, Object.assign({}, neuron.ui, {
  410. stroke: "#46b8da", strokeWidth: 1,
  411. fill: (chargePr > 100) ? "#f00" : "hsl(194, 67%, " + (100 - chargePr) + "%)",
  412. // data_neuron_idx: idx,
  413. // onClick: this.handleClickNeuron,
  414. style: { cursor: "pointer" },
  415. }), [
  416. h('title', {}, this.viewNeuronTitle(neuron.value)),
  417. ]),
  418. h('text', {
  419. x: neuron.ui.cx, y: neuron.ui.cy + 1, fill: fontColor,
  420. dominantBaseline: "middle", textAnchor: "middle",
  421. style: { fontSize: "10px", cursor: "pointer" },
  422. // data_neuron_idx: idx,
  423. // onClick: this.handleClickNeuron,
  424. }, chargeValue.toFixed(1)),
  425. ]);
  426. },
  427. viewNeuronValue: function (value) {
  428. return value
  429. .replace(new RegExp("\n", "g"), "\\n")
  430. .replace(new RegExp("\t", "g"), "\\t")
  431. .replace(new RegExp(" ", "g"), "_")
  432. ;
  433. },
  434. viewNeuronTitle: function (value) {
  435. return this.viewNeuronValue(value);
  436. },
  437. renderReceptor: function (receptor, idx) {
  438. // orange: hsl(40, 100%, 50%) --> hsl(0, 100%, 50%) -> red (overcharged)
  439. // var totalReceptors = this.state.receptor.length;
  440. // var cx = Math.ceil(this.state.ui_output_width / ( totalReceptors + 1 ));
  441. // var r = Math.min(Math.ceil(this.state.ui_output_width / ( totalReceptors + 10 ) / 2), this.state.ui_max_receptor_r);
  442. // DBG && console.log('DBG:NeuronView:renderReceptor', { receptor, idx, totalReceptors, r });
  443. // var xCenter = cx + cx * idx - r;
  444. var charge = this.props.store.getReceptorCharge(idx) // receptor.charge
  445. var chargePr = (100 * charge) / this.state.config_max_receptor_charge
  446. // var shapeColor = (chargePr > 100) ? "#f00" : "hsl(194, 67%, " + (100 - chargePr) + "%)"; // blue -> dark blue -> red
  447. var shapeColor = (chargePr > 100) ? "#f00" : "hsl(" + ( 40 - chargePr * 2 / 5 ).toFixed() + ", 100%, 50%)" // orange -> dark orange -> red
  448. var fontColor = "#fff" // (chargePr < 50) ? "#000" : "#fff";
  449. // this.state.config_max_receptor_charge = 100%
  450. // charge = x% (max 100)
  451. return h('g', { p5_node_id: "receptor-idx-" + idx }, [
  452. h(receptor.uiShape, Object.assign({}, receptor.ui, {
  453. stroke: "#46b8da", strokeWidth: 1,
  454. fill: shapeColor,
  455. data_receptor_idx: idx,
  456. onClick: this.handleClickReceptor,
  457. style: { cursor: "pointer" },
  458. }), [
  459. h('title', {}, this.viewReceptorTitle(receptor.value)),
  460. ]),
  461. h('text', {
  462. x: receptor.ui.cx, y: receptor.ui.cy + 1, fill: fontColor,
  463. dominantBaseline: "middle", textAnchor: "middle",
  464. style: { fontSize: "10px", cursor: "pointer" },
  465. data_receptor_idx: idx,
  466. onClick: this.handleClickReceptor,
  467. }, charge.toFixed(1)),
  468. h('text', {
  469. x: receptor.ui.cx, y: receptor.ui.cy - 20, fill: "#000",
  470. dominantBaseline: "middle", textAnchor: "middle",
  471. style: { fontSize: "10px", cursor: "pointer" },
  472. data_receptor_idx: idx,
  473. onClick: this.handleClickReceptor,
  474. }, this.viewReceptorLetter(receptor.value)),
  475. ]);
  476. },
  477. viewReceptorLetter: function (letter) {
  478. switch (letter) {
  479. case "\t": return "\\t";
  480. case " ": return "_";
  481. case "\n": return "\\n";
  482. default: return letter;
  483. }
  484. },
  485. viewReceptorTitle: function (letter) {
  486. switch (letter) {
  487. case "\t": return "tab";
  488. case " ": return "space";
  489. case "\n": return "new line";
  490. default: return letter;
  491. }
  492. },
  493. renderReceptorInfo: function () {
  494. if (null === this.state.selectedReceptorIdx) return null;
  495. var receptor = this._receptor[this.state.selectedReceptorIdx];
  496. return h('div', {}, [
  497. "Receptor: '" + this.viewReceptorTitle(receptor.value) + "' charge('" + receptor.charge.toFixed(2) + "')",
  498. ]);
  499. },
  500. renderAllReceptorInfo: function () {
  501. var state = this.props.store.getState()
  502. return h('div', {}, state.receptor.map(this.renderAllReceptorInfo_item));
  503. },
  504. renderAllReceptorInfo_item: function (receptor, idx) {
  505. var charge = this.props.store.getReceptorCharge(idx)
  506. return h('div', {}, ["Receptor: '" + this.viewReceptorTitle(receptor.value) + "' charge('" + charge.toFixed(2) + "')"]);
  507. },
  508. renderAllNeuronInfo: function () {
  509. var state = this.props.store.getState()
  510. return h('div', {}, state.neuron.map(this.renderAllNeuronInfo_item));
  511. },
  512. renderAllNeuronInfo_item: function (neuron, idx) {
  513. var charge = this.props.store.getNeuronCharge(idx)
  514. return h('div', {}, ["Neuron: '" + neuron.value + "' charge('" + charge.toFixed(2) + "')"]);
  515. },
  516. renderInput: function () {
  517. return h('details', { open: true, style: { margin: "12px 0", border: "1px solid #aaa", borderRadius: "6px", padding: "12px", backgroundColor: "#eee", } }, [
  518. h('summary', { style: { cursor: "pointer", overflow: "hidden" } }, [
  519. "Input",
  520. h('span', { className: "pull-right" }, [
  521. h('label', { style: { paddingRight: "6px" } }, "type:"),
  522. h('button', {
  523. className: "btn btn-sm btn-default" + ( this.state.inputType === 'text' ? " active" : "" ),
  524. p5_node_id: "input_type-text",
  525. onClick: this.handleToggleInput,
  526. }, "text"),
  527. h('button', {
  528. className: "btn btn-sm btn-default" + ( this.state.inputType === 'key' ? " active" : "" ),
  529. p5_node_id: "input_type-key",
  530. onClick: this.handleToggleInput,
  531. }, "key"),
  532. ])
  533. ]),
  534. (this.state.inputType === 'text') && h('textarea', {
  535. // ref: this.setInputRet,
  536. onChange: this.handleChangeInput,
  537. value: this.state.inputText,
  538. rows: 10,
  539. style: {
  540. width: "100%",
  541. padding: "12px",
  542. marginTop: "6px",
  543. backgroundColor: "#fff",
  544. },
  545. }),
  546. (this.state.inputType === 'key') && h('div', {
  547. style: {
  548. width: "100%",
  549. padding: "12px",
  550. marginTop: "6px",
  551. backgroundColor: "#fff",
  552. border: "1px solid #333",
  553. },
  554. }, [
  555. h('p', {}, [
  556. "Use keyboard keys from A to Z"
  557. ]),
  558. ]),
  559. ]);
  560. },
  561. renderConfig: function () {
  562. DBG && console.log("DBG:NeuronView:renderConfig", { state: this.state })
  563. return h('details', { open: true, style: { margin: "12px 0", border: "1px solid #aaa", borderRadius: "6px", padding: "12px", backgroundColor: "#eee", } }, [
  564. h('summary', { style: { cursor: "pointer" } }, "Config"),
  565. h('div', { style: { padding: "12px", backgroundColor: "#fff", } }, [
  566. h('div', {}, "Anim speed:"),
  567. h('input', {
  568. className: "form-control input-sm",
  569. type: "number",
  570. value: this.state.config_anim_speed,
  571. name: "config_anim_speed",
  572. onChange: this.handleChangeConfig,
  573. }),
  574. h('div', { style: { marginTop: "12px" } }),
  575. h('div', {}, "Read speed (anim speed multiplier):"),
  576. h('input', {
  577. className: "form-control input-sm",
  578. type: "number",
  579. value: this.state.config_read_speed_multiplier,
  580. name: "config_read_speed_multiplier",
  581. onChange: this.handleChangeConfig,
  582. }),
  583. h('div', { style: { marginTop: "12px" } }),
  584. h('div', {}, "Charge receptor at input:"),
  585. h('input', {
  586. className: "form-control input-sm",
  587. type: "number",
  588. value: this.state.config_charge_receptor_at_input,
  589. name: "config_charge_receptor_at_input",
  590. onChange: this.handleChangeConfig,
  591. }),
  592. h('div', { style: { marginTop: "12px" } }),
  593. h('div', {}, "Max receptor charge:"),
  594. h('input', {
  595. className: "form-control input-sm",
  596. type: "number",
  597. value: this.state.config_max_receptor_charge,
  598. name: "config_max_receptor_charge",
  599. onChange: this.handleChangeConfig,
  600. }),
  601. h('div', { style: { marginTop: "12px" } }),
  602. h('div', {}, "Max neuron charge:"),
  603. h('input', {
  604. className: "form-control input-sm",
  605. type: "number",
  606. value: this.state.config_max_neuron_charge,
  607. name: "config_max_neuron_charge",
  608. onChange: this.handleChangeConfig,
  609. }),
  610. h('div', { style: { marginTop: "12px" } }),
  611. h('div', {}, "Discharge per tick:"),
  612. h('input', {
  613. className: "form-control input-sm",
  614. type: "number",
  615. step: "0.1",
  616. value: this.state.config_discharge_per_tick,
  617. name: "config_discharge_per_tick",
  618. onChange: this.handleChangeConfig,
  619. }),
  620. h('div', { style: { marginTop: "12px" } }),
  621. // config_strategy_overcharge: "LEAVE_HALF_CHARGE", // "REMOVE_CHARGE" | "LEAVE_HALF_CHARGE" | "LEAVE_ALMOST_MAX"
  622. h('div', {}, "Overcharge strategy:"),
  623. h('select', {
  624. className: "form-control input-sm",
  625. type: "number",
  626. step: "0.1",
  627. value: this.state.config_strategy_overcharge,
  628. name: "config_strategy_overcharge",
  629. onChange: this.handleChangeConfig,
  630. }, [ "REMOVE_CHARGE", "LEAVE_HALF_CHARGE", "LEAVE_ALMOST_MAX" ].map(function (strategy) {
  631. return h('option', { value: strategy }, strategy);
  632. })),
  633. ]),
  634. ]);
  635. },
  636. handleChangeConfig: function (event) {
  637. var value = event.target.value;
  638. var name = event.target.getAttribute('name');
  639. var state = this.state;
  640. state[name] = value;
  641. this.setState(state);
  642. },
  643. renderLog: function () {
  644. return LOG__render(this._log)
  645. },
  646. handleRefreshLog: function () {
  647. if (!this._log.length) return;
  648. var lastLog = this._log[this._log.length - 1]
  649. console.table(
  650. [
  651. lastLog.receptor.map(function (node) { return "R:'" + node.value + "'"; })
  652. .concat(
  653. lastLog.neuron.map(function (node) { return "N:'" + node.value + "'"; })
  654. )
  655. ].concat(
  656. this._log.map(function (log) {
  657. return log.receptor.map(function (node) { return node.charge })
  658. .concat(
  659. log.neuron.map(function (node) { return node.charge })
  660. )
  661. })
  662. )
  663. )
  664. },
  665. render: function () {
  666. DBG && console.log("DBG:NeuronView:render", {
  667. state: this.state,
  668. receptor: [].concat(this.props.store.getListReceptor()),
  669. neuron: [].concat(this.props.store.getListNeuron()),
  670. connection: [].concat(this.props.store.getListConnection()),
  671. charge: [].concat(this.props.store.getListCharge()),
  672. });
  673. var state = this.props.store.getState()
  674. var input = this.props.store.getInput()
  675. var receptorLabels = state.receptor.map(function (receptor) { return receptor.value; })
  676. return h('div', {}, [
  677. h('table', { className: "table", p5_node_id: "p5-neuron-output-table" }, [
  678. h('tbody', {}, [
  679. h('tr', {}, [
  680. h('td', { style: { width: this.state.ui_output_width + 20 } }, [
  681. h('svg', {
  682. // ref: this.setOutputRet,
  683. height: this.state.uiOutputHeight, width: this.state.ui_output_width,
  684. style: { border: "1px solid #eee" }
  685. }, [
  686. state.connection.map(this.renderConnection),
  687. state.receptor.map(this.renderReceptor),
  688. state.neuron.map(this.renderNeuron),
  689. state.charge.map(this.renderCharge),
  690. ]),
  691. // TODO: more matrix
  692. // h('svg', {
  693. // // ref: this.setOutputRet,
  694. // height: this.state.uiOutputHeight, width: this.state.ui_output_width,
  695. // style: { border: "1px solid #eee" }
  696. // }, [
  697. // state.connection.map(this.renderConnection),
  698. // state.receptor.map(this.renderReceptor),
  699. // state.neuron.map(this.renderNeuron),
  700. // state.charge.map(this.renderCharge),
  701. // ]),
  702. (this.state.inputType === 'text') && h('div', {
  703. style: {
  704. padding: "6px",
  705. border: "1px solid #eee",
  706. }
  707. }, [
  708. h('b', {}, "Input: "),
  709. h('span', {}, this.state.inputText.substr(0, this.state.inputReadPos)),
  710. h('span', { style: { color: "#ddd" } }, this.state.inputText.substr(this.state.inputReadPos)),
  711. ]),
  712. (this.state.inputType === 'key') && h('div', {
  713. style: {
  714. padding: "6px",
  715. border: "1px solid #eee",
  716. }
  717. }, [
  718. h('b', {}, "Input keys: "),
  719. h('span', {}, this.state.inputKeysUp.split('').filter(p5Utils__ArrayDistinctFilter).join('')),
  720. (!this.state.inputKeysUp.length) && h('em', { style: { color: "#aaa" } }, "(Use keyboard keys from A to Z)"),
  721. h('div', {
  722. style: { overflowX: "scroll", marginBottom: "12px" },
  723. }, h('table', { className: "table-bordered" }, [
  724. h('tbody', {}, receptorLabels.map(function (label) {
  725. return h('tr', {}, [
  726. h('th', {
  727. style: {
  728. padding: "0 4px",
  729. color: "#fff",
  730. backgroundColor: "#fa0",
  731. },
  732. }, label + ":"),
  733. ].concat(input.map(function (line, idx) {
  734. var isLabelInLine = (-1 !== line.indexOf(label))
  735. return h('td', {
  736. style: {
  737. padding: "0 2px",
  738. color: isLabelInLine ? "#333" : "#ddd",
  739. backgroundColor: isLabelInLine ? "#333" : "#ddd",
  740. opacity: idx > state.inputReadPos ? "0.7" : "1",
  741. borderRight: idx === state.inputReadPos ? "3px solid #f00" : "1px solid #ddd",
  742. },
  743. }, isLabelInLine ? "x" : "_");
  744. })));
  745. }))
  746. ])),
  747. ]),
  748. ]),
  749. h('td', { style: { verticalAlign: "top" } }, [
  750. // this.renderReceptorInfo(),
  751. this.renderAllReceptorInfo(),
  752. this.renderAllNeuronInfo(),
  753. ]),
  754. ]),
  755. ]),
  756. ]),
  757. h('div', {}, [
  758. h('button', { className: "btn btn-primary", onClick: this.handleReset }, "Uruchom"),
  759. " ",
  760. " (animPos:" + this.state.animPos + ") ",
  761. " ",
  762. state.doAnim
  763. ? h('button', { className: "btn btn-default", onClick: this.handlePause }, [ h('i', { className: "glyphicon glyphicon-pause" }), "Stop" ])
  764. : h('button', { className: "btn btn-default", onClick: this.handlePlay }, [ h('i', { className: "glyphicon glyphicon-play" }), "Start"])
  765. ,
  766. h('button', { className: "btn btn-default", onClick: this.handleRefreshLog }, [h('i', { className: "glyphicon glyphicon-refresh" }), " ", "Log"])
  767. ]),
  768. this.renderLog(),
  769. h('table', { className: "table" }, [
  770. h('tbody', {}, [
  771. h('tr', {}, [
  772. h('td', { style: { width: "50%" } }, [
  773. this.renderInput(),
  774. ]),
  775. h('td', { style: { width: "50%" } }, [
  776. this.renderConfig(),
  777. ]),
  778. ]),
  779. ]),
  780. ]),
  781. h('div', {}, [
  782. "TODO: receptory...",
  783. ]),
  784. ]);
  785. }
  786. });
  787. // export default NeuronView
  788. ReactDOM.render(h(NeuronView, {
  789. store: global[MAKE_STORE_FUNCTION_NAME](),
  790. initialData: INITIAL_DATA || [],
  791. inputType: INPUT_TYPE || "text",
  792. }), document.getElementById(HTML_ID))