src/converter/ArtistooWriter.js

  1. import Writer from "./Writer.js"
  2.  
  3. class ArtistooWriter extends Writer {
  4.  
  5. constructor( model, config ){
  6. super( model, config )
  7.  
  8. this.mode = config.mode || "html"
  9. this.out = ""
  10. this.modelconfig = {}
  11. this.custommethods = {}
  12. this.methodDeclarations = ""
  13.  
  14.  
  15.  
  16. this.FPSMeterPath = config.FPSMeterPath || "https://artistoo.net/examples/fpsmeter.min.js"
  17. this.browserLibrary = config.browserLibrary || ".artistoo.js" // "https://artistoo.net/examples/artistoo.js"
  18. this.nodeLibrary = config.nodeLibrary || "https://raw.githubusercontent.com/ingewortel/artistoo/master/build/artistoo-cjs.js"
  19. this.styleSheet = config.styleSheet || "./modelStyle.css"
  20.  
  21. this.logString = "Hi there! Converting " + this.model.from + " to Artistoo...\n\n"
  22.  
  23. }
  24.  
  25. write(){
  26. if( this.mode === "html" ){
  27. //console.log( this.writeHTML() )
  28. this.target.innerHTML = this.writeHTML()
  29. }
  30. this.writeLog()
  31. }
  32.  
  33.  
  34. writeHTML(){
  35. return this.writeHTMLHead() +
  36. this.writeConfig() +
  37. this.setInitialisation() +
  38. this.customMethodsString() +
  39. this.writeBasicScript() +
  40. this.writeHTMLBody()
  41. }
  42.  
  43. writeNode(){
  44. return "let CPM = require(\"../../build/artistoo-cjs.js\")" +
  45. this.writeConfig() +
  46. this.writeBasicScript()
  47. }
  48.  
  49. writeHTMLHead( ){
  50.  
  51. let string = "<html lang=\"en\"><head><meta http-equiv=\"Content-Type\" " +
  52. "content=\"text/html; charset=UTF-8\">\n" +
  53. "\t<title>" + this.model.modelInfo.title + "</title>\n"+
  54. "\t<link rel=\"stylesheet\" href=\"" + this.styleSheet + "\" />" +
  55. /*"\t<style type=\"text/css\">\n" +
  56. "\t\t body{\n"+
  57. "\t\t\t font-family: \"HelveticaNeue-Light\", \"Helvetica Neue Light\", \"Helvetica Neue\", " +
  58. "Helvetica, Arial, \"Lucida Grande\", sans-serif; \n" +
  59. "\t\t\t padding : 15px; \n" +
  60. "\t\t} \n" +
  61. "\t\t td { \n" +
  62. "\t\t\t padding: 10px; \n" +
  63. "\t\t\t vertical-align: top; \n" +
  64. "\t\t } \n" +
  65. "\t </style> \n" +*/
  66. "\t" + "<script src=\"" + this.browserLibrary + "\"></script> \n" + //'\t <script src="https://artistoo.net/examples/artistoo.js"></script> \n' +
  67. "\t" + "<script src=\"" + this.FPSMeterPath + "\"></script> \n\n" +
  68. "<script> \n\n\n" +
  69. "\"use strict\" \n" +
  70. "var sim, meter \n\n"
  71.  
  72. return(string)
  73.  
  74. }
  75.  
  76. writeHTMLBody(){
  77.  
  78. let modelDesc = this.model.modelInfo.desc
  79. modelDesc = this.htmlNewLine( modelDesc )
  80.  
  81. return "</script> \n\n" +
  82. "</head>\n" +
  83. "<body onload=\"initialize();parent.window.model = sim\"> \n" +
  84. "<h1>"+this.model.modelInfo.title + "</h1> \n"+
  85. "<p>\n\t" + modelDesc + "\n" +
  86. "</p>\n" +
  87. "</body> \n" +
  88. "</html>"
  89.  
  90. }
  91.  
  92. writeConfig(){
  93. this.setModelConfig()
  94. return "let config = " + this.objToString( this.modelconfig ) + "\n\n"
  95. }
  96.  
  97. customMethodsString(){
  98. let string = "let custommethods = {\n"
  99. for( let m of Object.keys( this.custommethods ) ){
  100. string += "\t" + m + " : " + m + ",\n"
  101. }
  102. return string + "}"
  103. }
  104.  
  105. /* TO DO */
  106. setModelConfig(){
  107.  
  108. // Initialize structure
  109. let config = {
  110. conf : {},
  111. simsettings : {
  112. zoom : 2,
  113. CANVASCOLOR : "EEEEEE"
  114. }
  115. }
  116.  
  117. // Time information; warn if start time != 0
  118. if( this.model.timeInfo.start !== 0 ){
  119. this.conversionWarnings.time.push(
  120. "Morpheus model time starts at t = " + this.model.timeInfo.start +
  121. ". Interpreting time before that as a burnin time, but in Artistoo " +
  122. " time will restart at t = 0 after this burnin."
  123. )
  124. }
  125. config.simsettings.BURNIN = parseInt( this.model.timeInfo.start )
  126. config.simsettings.RUNTIME = parseInt( this.model.timeInfo.duration )
  127. config.simsettings.RUNTIME_BROWSER = parseInt( this.model.timeInfo.duration )
  128.  
  129. // Grid information, warn if grid has to be converted.
  130. config.ndim = this.model.grid.ndim
  131. config.field_size = this.model.grid.extents
  132. if( this.model.grid.geometry === "hexagonal" ){
  133. this.conversionWarnings.grid.push(
  134. "Grid of type 'hexagonal' is not yet supported in Artistoo. " +
  135. "Converting to square 2D lattice instead. You may have to adjust some parameters, " +
  136. "especially where neighborhood sizes matter (eg PerimeterConstraint, Adhesion)."
  137. )
  138. }
  139. config.torus = []
  140. const dimNames = ["x","y","z"]
  141. for( let d = 0; d < config.ndim; d++ ){
  142. const bound = this.model.grid.boundaries[d]
  143. switch( bound ){
  144. case "periodic" :
  145. config.torus.push( true )
  146. break
  147. case "noflux" :
  148. config.torus.push( false )
  149. break
  150. default :
  151. config.torus.push( true )
  152. this.conversionWarnings.grid.push(
  153. "unknown boundary condition in " + dimNames[d] + "-dimension: " +
  154. bound + "; reverting to default periodic boundary."
  155. )
  156. }
  157.  
  158. // special case for "linear" geometry, which in Artistoo is just
  159. // a 2D grid with a field_size [x,1] and torus = [x, false].
  160. if( this.model.grid.geometry === "linear" ){
  161. config.torus[1] = false
  162. }
  163. }
  164. if( !isNaN( this.model.grid.neighborhood.distance ) ){
  165. this.conversionWarnings.grid.push(
  166. "You are trying to set a neighborhood with distance = " +
  167. this.model.grid.neighborhood.distance + ", " +
  168. "but this is currently not supported in Artistoo. Reverting to" +
  169. "default (Moore) neighborhood; behaviour may change."
  170. )
  171. }
  172. if( !isNaN( this.model.grid.neighborhood.order ) && this.model.grid.neighborhood.order !== 2 ){
  173. this.conversionWarnings.grid.push(
  174. "You are trying to set a neighborhood with order = " +
  175. this.model.grid.neighborhood.order + ", " +
  176. "but this is currently not supported in Artistoo. Reverting to" +
  177. "default (Moore) neighborhood; behaviour may change."
  178. )
  179. }
  180.  
  181. // CPM kinetics
  182. config.conf.T = this.model.kinetics.T
  183. config.conf.seed = this.model.kinetics.seed
  184.  
  185. this.modelconfig = config
  186.  
  187. // CellKinds
  188. config.simsettings.NRCELLS = this.model.initCellKindVector( 0, false )
  189. config.simsettings.SHOWBORDERS = this.model.initCellKindVector( true, false )
  190. config.simsettings.CELLCOLOR = this.model.initCellKindVector( "333333", false )
  191. for( let k = 1; k < this.model.cellKinds.count - 1; k++ ){
  192. // Overwrite cellcolors for all kinds except the first with a
  193. // randomly generated color.
  194. config.simsettings.CELLCOLOR[k] =
  195. Math.floor(Math.random()*16777215).toString(16).toUpperCase()
  196. }
  197.  
  198. // Constraints
  199. // First constraints that can go in the main conf object (via auto-adder)
  200. let constraintString = ""
  201. for( let cName of Object.keys( this.model.constraints.constraints ) ){
  202. const constraintArray = this.model.constraints.constraints[cName]
  203. for( let ci = 0; ci < constraintArray.length; ci++ ){
  204. const constraintConf = constraintArray[ci]
  205. constraintString += this.addConstraintToConfig( cName, constraintConf )
  206. }
  207. }
  208. if( constraintString !== "" ){
  209. this.addCustomMethod( "addConstraints", "", constraintString )
  210. }
  211. }
  212.  
  213. addCustomMethod( methodName, args, contentString ){
  214. if( this.custommethods.hasOwnProperty( methodName ) ){
  215. throw( "Cannot add two custom methods of the same name!" )
  216. }
  217. this.custommethods[methodName] = methodName
  218. this.methodDeclarations += "function " + methodName + "( " + args + "){\n\n\t" +
  219. contentString + "\n}\n\n"
  220. }
  221.  
  222. addConstraintToConfig( cName, cConf ){
  223.  
  224. const autoAdded = {
  225. ActivityConstraint : true,
  226. Adhesion : true,
  227. VolumeConstraint : true,
  228. PerimeterConstraint : true,
  229. BarrierConstraint : true
  230. }
  231.  
  232. // Constraints that can be directly added to config.conf:
  233. if( autoAdded.hasOwnProperty(cName) ) {
  234. for (let parameter of Object.keys(cConf)) {
  235. this.modelconfig.conf[parameter] = cConf[parameter]
  236. }
  237.  
  238. // ActivityConstraint special case; set ACTCOLOR
  239. if (cName === "ActivityConstraint") {
  240. // check which kinds have activity; skip background
  241. let hasAct = []
  242. for (let k = 1; k < this.model.cellKinds.count; k++) {
  243. if (cConf.LAMBDA_ACT[k] > 0 && cConf.MAX_ACT[k] > 0) {
  244. hasAct.push(true)
  245. } else {
  246. hasAct.push(false)
  247. }
  248. }
  249. this.modelconfig.simsettings.ACTCOLOR = hasAct
  250. }
  251.  
  252. // Another special case for the PerimeterConstraint, which may
  253. // have to be converted depending on 'mode' and the 'ShapeSurface'.
  254. else if (cName === "PerimeterConstraint") {
  255. switch (cConf.mode) {
  256. case "surface" :
  257. // do nothing
  258. break
  259. case "aspherity" : {
  260. // correct the 'target' perimeter.
  261. let P = cConf.P
  262. const volume = this.modelconfig.conf.V
  263. for (let i = 0; i < P.length; i++) {
  264. if (this.modelconfig.ndim === 2) {
  265. P[i] = 4 * P[i] * 2 * Math.sqrt(volume[i] * Math.PI)
  266. } else if (this.modelconfig.ndim === 3) {
  267. P[i] = P[i] * 4 * Math.PI * Math.pow((3 / 4) * volume[i] / Math.PI, 2 / 3)
  268. }
  269. P[i] = parseFloat(P[i])
  270. }
  271. this.conversionWarnings.constraints.push(
  272. "Artistoo does not support the 'aspherity' mode of the Morpheus <SurfaceConstraint>." +
  273. "Adding a regular PerimeterConstraint (mode 'surface') instead. I am converting the " +
  274. "target perimeter with an educated guess, but behaviour may be slightly different; " +
  275. "please check parameters."
  276. )
  277.  
  278. this.modelconfig.conf.P = P
  279. break
  280. }
  281.  
  282. }
  283. delete this.modelconfig.conf.mode
  284. }
  285.  
  286. return ""
  287. }
  288.  
  289. // For the other constraints, add them by overwriting the
  290. // Simulation.addConstraints() method. Return the string of code
  291. // to add in this method.
  292. const otherSupportedConstraints = {
  293. LocalConnectivityConstraint : true,
  294. PersistenceConstraint : true,
  295. PreferredDirectionConstraint : true,
  296. ChemotaxisConstraint : true
  297. }
  298.  
  299. if( !otherSupportedConstraints[cName] ){
  300. this.conversionWarnings.constraints.push(
  301. "Ignoring unknown constraint of type " + cName + ". Behaviour may change.")
  302. }
  303.  
  304. // Special case for the PersistenceConstraint: warn if
  305. // protrusion/retraction setting does not correspond.
  306. if (cName === "PersistenceConstraint" || cName === "PreferredDirectionConstraint" ){
  307. const protrude = cConf.PROTRUDE
  308. const retract = cConf.RETRACT
  309. const lambda = cConf.LAMBDA_DIR
  310.  
  311. let warn = false
  312. for( let k = 0; k < protrude.length; k++ ){
  313.  
  314. if( lambda[k] > 0 ) {
  315.  
  316. if (!protrude[k]) {
  317. warn = true
  318. }
  319. if (retract[k]) {
  320. warn = true
  321. }
  322. if (warn) {
  323. this.conversionWarnings.constraints.push(
  324. "You are trying to set a PersistenceConstraint for cellkind " +
  325. this.model.getKindName(k) + " with protrusion = " +
  326. protrude[k] + " and retraction = " + retract[k] + ", but Artistoo " +
  327. "only supports protrusion = true and retraction = false. " +
  328. "Reverting to these settings. Behaviour may change slightly; " +
  329. "if this is important, consider implementing your own constraint."
  330. )
  331. }
  332. }
  333. }
  334. delete cConf.PROTRUDE
  335. delete cConf.RETRACT
  336. }
  337.  
  338. return "this.C.add( new CPM." + cName + "( " + this.objToString( cConf, 1 ) + ") )\n\n\t"
  339.  
  340. }
  341.  
  342. writeBasicScript(){
  343.  
  344. return "\n\n" +
  345. "function initialize(){ \n" +
  346. "\t" + "sim = new CPM.Simulation( config, custommethods ) \n" +
  347. "\t" + "meter = new FPSMeter({left:\"auto\", right:\"5px\"}) \n\n"+
  348. "\t" + "step() \n" +
  349. "} \n\n" +
  350. "function step(){ \n\t" +
  351. "sim.step() \n\t" +
  352. "meter.tick() \n\n\t" +
  353. "if( sim.conf[\"RUNTIME_BROWSER\"] == \"Inf\" | sim.time+1 < sim.conf[\"RUNTIME_BROWSER\"] ){ \n\t" +
  354. "\t\t" + "requestAnimationFrame( step ) \n" +
  355. "\t} \n}\n\n" + this.methodDeclarations
  356.  
  357. }
  358.  
  359. setInitialisation(){
  360. // Initializers
  361. let initString = ""
  362. for( let initConf of this.model.setup.init ){
  363. initString += this.addInitializer( initConf )
  364. }
  365. if( initString !== "" ){
  366. initString = "" + "this.addGridManipulator()\n\n" + initString
  367. this.addCustomMethod( "initializeGrid", "", initString )
  368. }
  369. return ""
  370. }
  371.  
  372. addInitializer( conf ){
  373.  
  374. const kindIndex = conf.kind
  375. switch( conf.setter ){
  376.  
  377. case "circleObject" :
  378. return "\t" + "this.gm.assignCellPixels( this.gm.makeCircle( [" +
  379. conf.center.toString() + "], " + conf.radius + ") , " + kindIndex + " )\n"
  380.  
  381. case "boxObject" :
  382. return "\t" + "this.gm.assignCellPixels( this.gm.makeBox( [" +
  383. conf.bottomLeft.toString() + "], [" + conf.boxSize.toString() +
  384. "] ) , " + kindIndex + " )\n"
  385.  
  386. case "cellCircle" :
  387. return "\t" + "this.gm.seedCellsInCircle( " + kindIndex + ", " +
  388. conf.nCells + ", [" + conf.center.toString() + "], " +
  389. conf.radius + " )\n"
  390.  
  391. case "pixelSet" : {
  392. let out = "[\n\t\t"
  393. for (let i = 0; i < conf.pixels.length; i++ ) {
  394. const p = conf.pixels[i]
  395. out += "[" + p.toString() + "]"
  396. if( i < conf.pixels.length - 1 ){ out += "," }
  397. }
  398. return "\t" + "this.gm.assignCellPixels( " + out +
  399. " ], " + kindIndex + ")\n"
  400.  
  401. }
  402.  
  403. default :
  404. this.conversionWarnings.init.push( "Unknown initializer "
  405. + conf.setter + "; ignoring." )
  406. }
  407.  
  408. }
  409.  
  410. }
  411.  
  412. export default ArtistooWriter