NetLogo Global Climate model and JavaScript grapher
This example displays a NetLogo model sending data to a JavaScript grapher
Java Applets need to be enabled in browser for this to run.
NetLogo Global Climate model and JavaScript grapher
This example displays a NetLogo model sending data to a JavaScript grapher
Java Applets need to be enabled in browser for this to run.
| /* | |
| * Minimal classList shim for IE 9 | |
| * By Devon Govett | |
| * MIT LICENSE | |
| */ | |
| if (!("classList" in document.documentElement) && Object.defineProperty && typeof HTMLElement !== 'undefined') { | |
| Object.defineProperty(HTMLElement.prototype, 'classList', { | |
| get: function() { | |
| var self = this; | |
| function update(fn) { | |
| return function(value) { | |
| var classes = self.className.split(/\s+/), | |
| index = classes.indexOf(value); | |
| fn(classes, index, value); | |
| self.className = classes.join(" "); | |
| } | |
| } | |
| var ret = { | |
| add: update(function(classes, index, value) { | |
| ~index || classes.push(value); | |
| }), | |
| remove: update(function(classes, index) { | |
| ~index && classes.splice(index, 1); | |
| }), | |
| toggle: update(function(classes, index, value) { | |
| ~index ? classes.splice(index, 1) : classes.push(value); | |
| }), | |
| contains: function(value) { | |
| return !!~self.className.split(/\s+/).indexOf(value); | |
| }, | |
| item: function(i) { | |
| return self.className.split(/\s+/)[i] || null; | |
| } | |
| }; | |
| Object.defineProperty(ret, 'length', { | |
| get: function() { | |
| return self.className.split(/\s+/).length; | |
| } | |
| }); | |
| return ret; | |
| } | |
| }); | |
| } |
| // [KCPT] | |
| // The dgApi object contains everything needed to communicate back to the DG application. | |
| dgApi = {}; | |
| // For now we go through 'DG.currGameController.doCommand'. | |
| // Very shortly a development branch will be merged to the trunk | |
| // which simplifies that to 'DG.doCommand'. | |
| dgApi.gameController = window.parent.DG.currGameController; | |
| dgApi.doCommand = dgApi.gameController && dgApi.gameController.doCommand; | |
| // Mainly for debugging -- turns off creation of cases in DG | |
| dgApi.isEnabled = true; | |
| // Cases can be grouped into "Runs". Feel free to change to a better name. | |
| // I'm not sure where the best place to end a run is -- starting and stopping | |
| // the simulation doesn't seem like an appropriate place to end a run for instance. | |
| dgApi.runCount = 0; | |
| /** | |
| Initializes the communication between the model and DG. | |
| Passes information about the names of collection, names and types of attributes, etc. | |
| Quotes follow DG convention of using double quotes for user-focused strings and | |
| single quotes for strings used by the code. In general, changing a double-quoted string | |
| will affect what the user sees but not affect the operation of the program, whereas | |
| changing a single-quoted string can change the behavior of the program. Thus, changing | |
| the name of the game from "Simple Atoms Model" won't break anything, but changing | |
| 'initGame' to something else will prevent the proper initialization from occurring. | |
| */ | |
| dgApi.initGame = function() { | |
| if( !dgApi.doCommand) return; | |
| dgApi.doCommand.call( dgApi.gameController, { | |
| action: 'initGame', | |
| args: { | |
| name: "NetLogo GCC Data", | |
| dimensions: { width: 525, height: 625 }, | |
| collections: [ | |
| { // parent collection -- Runs | |
| name: "Runs", | |
| attrs: [ { name: "run", type: 'numeric', description: "The run number", precision: 0 } | |
| ], | |
| childAttrName: "run" | |
| }, | |
| { // Child collection -- Steps | |
| name: "Ticks", | |
| attrs: [ { name: "ticks", type: 'numeric', description: "Current tick", precision: 0 }, | |
| { name: "temperature", type: 'numeric', description: "Global Climate Temperature", precision: 2 } | |
| ], | |
| // default attributes for initial plot | |
| defaults: { | |
| xAttr: "ticks", | |
| yAttr: "temperature" | |
| } | |
| } | |
| ] | |
| } | |
| }); | |
| }; | |
| /** | |
| Begins a "Run", i.e. the collection of a set of related cases (Steps). | |
| Client should call endRun() when the Run is complete. | |
| */ | |
| dgApi.beginRun = function() { | |
| if( !dgApi.doCommand) return; | |
| var result = dgApi.doCommand.call( dgApi.gameController, { | |
| action: 'openCase', | |
| args: { | |
| collection: "Runs", | |
| // increment the runCount when we start a run | |
| values: [ ++dgApi.runCount ] | |
| } | |
| }); | |
| // Returns the ID of the parent case, which should be passed | |
| // in subsequent calls to create the child cases. | |
| if( result.success) | |
| dgApi.openRunID = result.caseID; | |
| return result; | |
| }; | |
| /** | |
| Ends a "Run". This is not currently called as I wasn't sure when to do so. | |
| */ | |
| dgApi.endRun = function() { | |
| if( !dgApi.doCommand) return; | |
| dgApi.doCommand.call( dgApi.gameController, { | |
| action: 'closeCase', | |
| args: { | |
| collection: "Runs", | |
| // Passes the case ID returned by the call to 'openCase' | |
| caseID: dgApi.openRunID | |
| } | |
| }); | |
| // Null out the openRunID so it can't get used inadvertently. | |
| dgApi.openRunID = null; | |
| }; | |
| /** | |
| Creates a DG case corresponding to the current state of the model. | |
| The model state supported currently is the five values returned by model.getStats(), | |
| but additional properties could be added as well. To do so, the corresponding | |
| attribute descriptions should be added to the 'initGame' call, and the code should | |
| be changed here to pass the corresponding value in the array passed to doCommand. | |
| @param {Object} iModel -- The current model state | |
| */ | |
| dgApi.addTick = function( data) { | |
| if( !dgApi.isEnabled || !dgApi.doCommand) return; | |
| dgApi.doCommand.call( dgApi.gameController, { | |
| action: 'createCase', | |
| args: { | |
| collection: "Ticks", | |
| parent: dgApi.openRunID, | |
| values: [ data.length, data[data.length-1] ] | |
| } | |
| }); | |
| }; | |
| // [/KCPT] |
| <!DOCTYPE html> | |
| <html> | |
| <head> | |
| <title>NetLogo Applet</title> | |
| <link href="styles.css" rel="stylesheet" type="text/css"> | |
| <script src="http://ajax.googleapis.com/ajax/libs/jquery/1.7.2/jquery.min.js"></script> | |
| <script src="http://mbostock.github.com/d3/d3.v2.js?2.8.1"></script> | |
| <script src="classlist-shim-for-ie.js" type="text/javascript"></script> | |
| <script src="dgapi.js" type="text/javascript"></script> | |
| </head> | |
| <body> | |
| <h1>Simple Global Climate Change NetLogo Applet</h1> | |
| <ul class="hlist"> | |
| <li> | |
| <div id="appletwrapper"> | |
| <ul class="hlist"> | |
| <li><button id="run-button" class="nlogo">Run</button></li> | |
| <li><button id="reset-button" class="nlogo">Reset</button></li> | |
| <li><button id="watch-sunray-button" class="nlogo">Watch Sunray</button></li> | |
| </ul> | |
| <applet id="applet" code="org.nlogo.lite.Applet" archive="http://stepheneb.github.com/netlogo-gcc/NetLogoLite.jar" width="590" height="430"> | |
| <param name="DefaultModel" value="http://stepheneb.github.com/netlogo-gcc/GCCModel.v3.nlogo"> | |
| </applet> | |
| </div> | |
| </li> | |
| <li> | |
| <div id="chart" class="chart"></div> | |
| </li> | |
| </ul> | |
| <script src="netlogo.js" type="text/javascript"></script> | |
| </body> | |
| </html> |
| /*global browser:true */ | |
| var applet = document.getElementById("applet"), | |
| run_button = document.getElementById("run-button"), | |
| reset_button = document.getElementById("reset-button"), | |
| watch_sunray_button = document.getElementById("watch-sunray-button"), | |
| nl_obj_panel, // org.nlogo.lite.Applet object | |
| nl_obj_workspace, // org.nlogo.lite.LiteWorkspace | |
| nl_obj_world, // org.nlogo.agent.World | |
| nl_obj_program, // org.nlogo.api.Program | |
| nl_obj_state, | |
| nl_obj_observer, | |
| nl_obj_globals, | |
| sw, | |
| pw, | |
| nlogo_elements, | |
| globals = [], i, | |
| data_array = [], | |
| graph; | |
| window.onload=function() { | |
| disable_nlogo_elements(); | |
| dgApi.initGame(); | |
| dgApi.beginRun(); | |
| // | |
| // NetLogo Applet Loading Handler | |
| // | |
| // Wait until the applet is loaded and initialized before enabling buttons | |
| // and creating JavaScript variables for Java objects in the applet. | |
| // | |
| applet.ready = false; | |
| applet.checked_more_than_once = false; | |
| window.setTimeout (function() { isAppletReady(); }, 250); | |
| function isAppletReady() { | |
| try { | |
| applet.ready = applet.panel(); | |
| } catch (e) { | |
| // Do nothing--we'll try again in the next timer interval. | |
| } | |
| if(applet.ready) { | |
| nl_setup_objects(); | |
| nl_obj_panel.commandLater("set done true"); | |
| sw = new applet.Packages.java.io.StringWriter(); | |
| pw = new applet.Packages.java.io.PrintWriter(sw); | |
| enable_nlogo_elements(); | |
| if(applet.checked_more_than_once) { | |
| clearInterval(applet.checked_more_than_once); | |
| applet.checked_more_than_once = false; | |
| } | |
| } else { | |
| if(!applet.checked_more_than_once) { | |
| applet.checked_more_than_once = window.setInterval(function() { isAppletReady(); }, 250); | |
| } | |
| } | |
| } | |
| // | |
| // Create these JavaScript objects to provide access to the | |
| // corresponding Java objects in NetLogo. | |
| // | |
| function nl_setup_objects() { | |
| nl_obj_panel = applet.panel(); | |
| nl_obj_workspace = nl_obj_panel.workspace(); | |
| nl_obj_world = nl_obj_workspace.org$nlogo$lite$LiteWorkspace$$world; | |
| nl_obj_program = nl_obj_world.program(); | |
| nl_obj_observer = nl_obj_world.observer(); | |
| nl_obj_globals = nl_obj_program.globals(); | |
| } | |
| // | |
| // NetLogo command interface | |
| // | |
| function nl_cmd_start() { | |
| nl_obj_panel.commandLater("set done false while [not done] [ execute ]"); | |
| } | |
| function nl_cmd_stop() { | |
| nl_obj_panel.commandLater("set done true"); | |
| } | |
| function nl_cmd_execute(cmd) { | |
| nl_obj_panel.commandLater(cmd); | |
| } | |
| function nl_cmd_save_state() { | |
| nl_obj_world.exportWorld(pw, true); | |
| nl_obj_state = sw.toString(); | |
| } | |
| function nl_cmd_restore_state() { | |
| if (nl_obj_state) { | |
| var sr = new applet.Packages.java.io.StringReader(nl_obj_state); | |
| nl_obj_workspace.importWorld(sr); | |
| nl_obj_panel.commandLater("display"); | |
| } | |
| } | |
| function nl_cmd_reset() { | |
| nl_cmd_stop(); | |
| nl_obj_panel.commandLater("startup"); | |
| } | |
| // | |
| // Managing the NetLogo data polling system | |
| // | |
| function startNLDataPoller() { | |
| applet.data_poller = window.setInterval(function() { nlDataPoller(); }, 200); | |
| } | |
| function stopNLDataPoller() { | |
| if (applet.data_poller) { | |
| clearInterval(applet.data_poller); | |
| applet.data_poller = false; | |
| } | |
| } | |
| function nlDataPoller() { | |
| data_array.push(nl_obj_observer.getVariable(3)); | |
| dgApi.addTick(data_array); | |
| } | |
| // | |
| // button handlers | |
| // | |
| run_button.onclick = function() { | |
| if (run_button.textContent == "Run") { | |
| run_button_run(); | |
| } else { | |
| run_button_stop(); | |
| } | |
| }; | |
| reset_button.onclick = function() { | |
| run_button_stop(); | |
| nl_cmd_reset(); | |
| data_array.length = 0; | |
| dgApi.endRun(); | |
| dgApi.beginRun(); | |
| }; | |
| watch_sunray_button.onclick = function() { | |
| nl_cmd_execute("watch one-of sunrays with [ycor > (max-pycor / 2 ) and heading > 90 ]"); | |
| }; | |
| // | |
| // button/view helpers | |
| // | |
| function run_button_run() { | |
| nl_cmd_start(); | |
| startNLDataPoller(); | |
| run_button.textContent = "Stop"; | |
| } | |
| function run_button_stop() { | |
| nl_cmd_stop(); | |
| stopNLDataPoller(); | |
| run_button.textContent = "Run"; | |
| } | |
| // | |
| // add the css class "inactive" to all dom elements that include the class "nlogo" | |
| // | |
| function disable_nlogo_elements() { | |
| nlogo_elements = document.getElementsByClassName("nlogo"); | |
| for (i=0; i < nlogo_elements.length; i++) { | |
| nlogo_elements[i].classList.add("inactive"); | |
| } | |
| } | |
| // | |
| // add the css class "active" to all dom elements that include the class "nlogo" | |
| // | |
| function enable_nlogo_elements() { | |
| nlogo_elements = document.getElementsByClassName("nlogo"); | |
| for (i=0; i < nlogo_elements.length; i++) { | |
| nlogo_elements[i].classList.remove("inactive"); | |
| nlogo_elements[i].classList.add("active"); | |
| } | |
| } | |
| }; |
| body { font: 12px Verdana, Arial, Helvetica, sans-serif; | |
| margin: 1.0em 2.0em; | |
| background-color: white;} | |
| h1 { font-size: 1.6em; | |
| font-weight: bold; } | |
| h2 { font-size: 1.4em; | |
| font-weight: bold; } | |
| h1 { font-size: 1.2em; | |
| font-weight: bold; } | |
| hr { margin: 2em 0em; } | |
| p { max-width: 600px; } | |
| #appletwrapper { border: 1px solid white; | |
| padding: 10px; | |
| width: 600px; | |
| height: 490px; | |
| background-color: white; } | |
| applet { padding: 0px; | |
| background-color: white; } | |
| table { border: 1px solid gray; | |
| border-spacing: 0px; | |
| border-collapse: collapse; | |
| font: 10px/24px Verdana, Arial, Helvetica, sans-serif; } | |
| table th { | |
| border: 1px solid gray; | |
| padding: 0em 1em; | |
| text-align: center; } | |
| table td { | |
| font-size: 0.9em; | |
| border: 1px solid gray; | |
| padding: 0em 1em; | |
| text-align: right; | |
| width: 14em; } | |
| table td.left { | |
| padding: 0em 1em 0em 2em; | |
| text-align: left; } | |
| ul { | |
| list-style-type: none; | |
| padding: 0em 0em; | |
| margin: 0.5em 0em 0em 0.5em; | |
| width: 100%; } | |
| ul li { | |
| margin: 0em; | |
| padding: 0em 1em; } | |
| ul.hlist li { | |
| display: table-cell; | |
| vertical-align: top; | |
| margin: 0em; | |
| padding: 0em 0.5em; } | |
| button.active { | |
| font-weight: bold; | |
| font-color: black; | |
| border-bottom: 2px solid black; } | |
| button { | |
| font-weight: normal; | |
| font-color: gray; | |
| border-bottom: 0px solid black; } | |
| pre { font-size: 0.8em; | |
| border: 1px solid gray; | |
| padding: 1em; | |
| background-color: #F0F0F0; | |
| width: 80em; | |
| height: 40em; | |
| overflow: scroll; } | |
| .chart { | |
| border: 1px solid white; | |
| background-color: #FAFAFA; | |
| margin-top: 20px; | |
| width: 580px; | |
| height: 465px; } | |
| circle, .line { | |
| fill: none; | |
| stroke: steelblue; | |
| stroke-width: 1px; } | |
| text.title { font-size: 1.2em; | |
| font-weight: bold; } | |
| text.axis { font-size: 1.0em; | |
| font-weight: normal; } | |
| circle { | |
| fill: white; | |
| fill-opacity: 0.2; | |
| cursor: move; } | |
| circle.selected { | |
| fill: #ff7f0e; | |
| stroke: #ff7f0e; } | |
| circle:hover { | |
| fill: #ff7f0e; | |
| stroke: #707f0e; } | |
| circle.selected:hover { | |
| fill: #ff7f0e; | |
| stroke: #ff7f0e; } |