Skip to content

Instantly share code, notes, and snippets.

@stepheneb
Last active January 7, 2020 23:31
Show Gist options
  • Save stepheneb/2763712 to your computer and use it in GitHub Desktop.
Save stepheneb/2763712 to your computer and use it in GitHub Desktop.
NetLogo Global Climate model and JavaScript grapher (Java Applet).

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; }
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment