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; } |