summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--.gitignore4
-rw-r--r--css/style.css41
-rw-r--r--index.html39
-rw-r--r--index.js99
-rw-r--r--js/mv/chart.js10
-rw-r--r--js/mv/connect.js156
-rw-r--r--js/mv/heap.js1
-rw-r--r--js/mv/main.js4
-rw-r--r--js/util/trellis-chart.js12
9 files changed, 349 insertions, 17 deletions
diff --git a/.gitignore b/.gitignore
index eb08486..0d1be3a 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,2 +1,4 @@
*~
-map-logs \ No newline at end of file
+map-logs
+node_modules
+manavis.log
diff --git a/css/style.css b/css/style.css
index 0d2ec08..2690b1f 100644
--- a/css/style.css
+++ b/css/style.css
@@ -13,6 +13,19 @@ body {
overflow: hidden;
}
+#mask {
+ position: absolute;
+ width: 100%;
+ height: 100%;
+ background-color: #333;
+ opacity: .8;
+ -moz-transition: opacity 1s linear;
+ -o-transition: opacity 1s linear;
+ -webkit-transition: opacity 1s linear;
+ transition: opacity 1s linear;
+ top: 0;
+}
+
body, #main, #status, .side {
min-height: 40px;
}
@@ -26,6 +39,8 @@ body {
font-size: smaller;
}
+/* Columns */
+
.med {
width: 400px;
}
@@ -34,16 +49,29 @@ body {
width: 250px;
}
-.side {
+#main, .side {
font-size: smaller;
}
+/* Titles */
+h3 {
+ margin: 0.3em 0.8em;
+}
+
/* Loadinfo panel */
#loadinfo {
+ position: absolute;
+ top: 0px;
+ bottom: 0px;
+ left: 0px;
+ right: 0px;
width: 647px;
+ height: 400px;
border: 1px grey solid;
margin: auto;
+ background-color: #fff;
+ padding: 20px;
}
/* Hide charts while loadinfo is shown */
@@ -83,11 +111,22 @@ body {
shape-rendering: crispEdges;
}
+/* User list */
+
+#users-status {
+ padding: 0;
+}
+
+#users-status li.user {
+ list-style: none;
+}
+
/* Utility */
.fader {
-moz-transition: opacity 1s linear;
-o-transition: opacity 1s linear;
-webkit-transition: opacity 1s linear;
+ transition: opacity 1s linear;
}
.help {
diff --git a/index.html b/index.html
index dd93596..baebbb2 100644
--- a/index.html
+++ b/index.html
@@ -7,6 +7,10 @@
<body onload="mv.init();">
<div class="side med">
<div class="vis-hide">
+ <div id="connect-status">
+ <h3>Users</h3>
+ <ul id="users-status"></ul>
+ </div>
<div id="blvl-chart">
<h3>Instance breakdown by Character Base Level <a class="reset" style="display: none;" href="javascript:mv.charts.blvl.filterAll();dc.redrawAll();">clear</a></h3>
</div>
@@ -17,13 +21,13 @@
</div>
<div class="side thin">
<div class="vis-hide">
- <div id="type-chart">
<div id="target-chart">
<h3>Instance breakdown by Target <a class="reset" style="display: none;" href="javascript:mv.charts.target.filterAll();dc.redrawAll();">clear</a></h3>
</div>
<div id="wpn-chart">
<h3>Instance breakdown by Weapon <a class="reset" style="display: none;" href="javascript:mv.charts.wpn.filterAll();dc.redrawAll();">clear</a></h3>
</div>
+ <div id="type-chart">
<h3>Instance breakdown by Type <a class="reset" style="display: none;" href="javascript:mv.charts.type.filterAll();dc.redrawAll();">clear</a></h3>
</div>
<div id="def-chart">
@@ -36,19 +40,6 @@
Manavis
TODO: Load icons et al for when new records need loading?
</div>-->
- <div id="mask"><noscript>Javascript is required for this website.</noscript>
- <div id="loadinfo" class="fader">
- <h3>Select records to load and display</h3>
- <input type="file" id="input" name="records[]" multiple />
- <output id="list"></output>
- <div id="filesbar" class="progressbar fader">
- <div class="percent">0%</div>
- </div>
- <div id="loadbar" class="progressbar fader">
- <div class="percent">0%</div>
- </div>
- </div>
- </div>
<div class="vis-hide">
<div id="map-chart">
<h3>Breakdown by Map <span class="help" title="Bubble size indicates instances of experience gain for that map. X axis position indicates the sum of level experience gain for that map. Y axis position indicates the sum of job experience gain for that map.">[?]</span> <a class="reset" style="display: none;" href="javascript:mv.charts.map.filterAll();dc.redrawAll();">clear</a></h3>
@@ -61,6 +52,23 @@
</div>
</div>
</div>
+ <div id="mask"><noscript><h1>Javascript is required for this website.</h1></noscript>
+ <div id="loadinfo" class="fader">
+ <h1>Manavis</h1>
+ <input id="connect-option" name="connect" type="checkbox" checked></input>
+ <label for="connect">Connect to server</label>
+ <h3>Select records to load and display</h3>
+ <p>You can load any number of files at once.</p>
+ <input type="file" id="input" name="records[]" multiple />
+ <output id="list"></output>
+ <div id="filesbar" class="progressbar fader">
+ <div class="percent">0%</div>
+ </div>
+ <div id="loadbar" class="progressbar fader">
+ <div class="percent">0%</div>
+ </div>
+ </div>
+ </div>
</body>
<!-- Libs -->
@@ -70,6 +78,8 @@
<script src="js/crossfilter/crossfilter.js"></script>
<script src="js/dc/dc.js"></script>
+<script src="/socket.io/socket.io.js"></script>
+
<!-- Components -->
<script src="js/comp/item.js"></script>
<script src="js/comp/map.js"></script>
@@ -78,6 +88,7 @@
<!-- Processing -->
<script src="js/mv/chart.js"></script>
+<script src="js/mv/connect.js"></script>
<script src="js/mv/heap.js"></script>
<script src="js/mv/load.js"></script>
<script src="js/mv/main.js"></script>
diff --git a/index.js b/index.js
new file mode 100644
index 0000000..2c3e894
--- /dev/null
+++ b/index.js
@@ -0,0 +1,99 @@
+/* Deps */
+var http = require('http')
+ , path = require('path')
+ , connect = require('connect')
+ , express = require('express')
+ , app = express()
+ , logger = require('logger').createLogger('manavis.log')
+ , cookieParser = express.cookieParser('your secret sauce')
+ , sessionStore = new connect.middleware.session.MemoryStore()
+ ;
+
+app.configure(function () {
+ app.use(express.bodyParser());
+ app.use(express.methodOverride());
+ app.use(cookieParser);
+ app.use(express.session({ store: sessionStore }));
+ app.use(app.router);
+ app.use(express.static(__dirname));
+});
+
+var server = http.createServer(app)
+ , io = require('socket.io').listen(server)
+ , SessionSockets = require('session.socket.io')
+ , sessionSockets = new SessionSockets(io, sessionStore, cookieParser)
+ ;
+/* End deps */
+
+/* Only one level is logged, and numerical timestamps are easier to compare. */
+logger.format = function(level, date, message) {
+ return (+date) + ":" + message;
+}
+
+/* Number of sessions we've seen. */
+var count = 0;
+/* nid -> { nick, filters, following } */
+var users = {};
+
+sessionSockets.on('connection', function (err, socket, session) {
+ /*
+ * Don't do anything until they send a login message.
+ * Later versions might also check a protocol version here.
+ */
+ socket.on('login', function() {
+ /* Someone new connected. Restore or initialise their session data. */
+ session.nid = session.nid || (++count);
+ session.nick = session.nick || null;
+ session.save();
+ /* New user! */
+ logAction("CONNECT", socket.handshake.address.address);
+ users[session.nid] = { nick: session.nick, filters: {} };
+ /* Let them know of their data. */
+ socket.emit('selflogin', {
+ id: session.nid,
+ nick: session.nick
+ });
+ /* Let everyone else know that someone connected. */
+ socket.broadcast.emit('login', {
+ id: session.nid,
+ nick: session.nick
+ });
+ /* Send the new user the userlist. */
+ socket.emit('users', { users: users });
+ /* Set up various handlers for the new socket. */
+ socket.on('nick', function (d) {
+ /* TODO Collision checking? */
+ users[session.nid].nick = session.nick = d.nick;
+ session.save();
+ logAction("NICK", d.nick);
+ io.sockets.emit('nickset', {
+ id: session.nid,
+ nick: d.nick
+ });
+ });
+ socket.on('filter', function(d) {
+ users[session.nid].filters = d.filters;
+ logAction("FILTER", d.filters);
+ socket.broadcast.emit('filterset', {
+ id: session.nid,
+ filters: d.filters
+ });
+ });
+ socket.on('disconnect', function() {
+ logAction("DISCONNECT");
+ delete users[session.nid];
+ socket.broadcast.emit('logout', {
+ id: session.nid
+ });
+ });
+ });
+ function logAction(action, content) {
+ logger.info(session.nid
+ , action
+ , content
+ );
+ }
+});
+
+logger.info(0, "STARTUP");
+server.listen(3000);
diff --git a/js/mv/chart.js b/js/mv/chart.js
index 3042acd..d06d40e 100644
--- a/js/mv/chart.js
+++ b/js/mv/chart.js
@@ -61,6 +61,16 @@ var mv = function(mv) {
dc.renderlet(function() { mv.charts.stats(); });
dc.renderAll();
}
+ charter.filters = function() {
+ var r = {}, f;
+ for (var k in mv.charts) {
+ f = mv.charts[k].filter();
+ if (f != null) {
+ r[k] = f;
+ }
+ }
+ return r;
+ }
function defLevelVerbose(level) {
switch (level) {
case 0: return "Undefined";
diff --git a/js/mv/connect.js b/js/mv/connect.js
new file mode 100644
index 0000000..aba4c33
--- /dev/null
+++ b/js/mv/connect.js
@@ -0,0 +1,156 @@
+var mv = function(mv) {
+ mv.socket = {
+ connect: connect
+ };
+ /* If we're updating due to something we received, then don't broadcast back. */
+ var netrendering = false;
+ /* Our ID */
+ var id = 0;
+ /* List of all users */
+ var users = {};
+ /* The user status box */
+ var usersStatus = d3.select("#users-status");
+ /* io.socket's socket */
+ var socket;
+ function connect() {
+ socket = io.connect('http://localhost:3000');
+ socket.on("connect", function() { console.log("CONNECT", arguments); });
+ socket.on("disconnect", function() { console.log("DISCONNECT", arguments); });
+ socket.emit('login');
+ /*
+ * Protocol:
+ * selflogin -> id (I)
+ * login -> id (I), nick (S)
+ * nickset -> id (I), nick (S)
+ * users -> { id -> {nick (S), filters ({dim (S) -> filter (*)}) } }
+ * logout -> id (I)
+ */
+ socket.on('selflogin', function(d) {
+ /* Acknowledged that we logged in */
+ /* Take note of our ID. */
+ id = d.id;
+ });
+ socket.on('login', function(d) {
+ /* Someone else logging in */
+ users[d.id] = { nick: d.nick, filters: {} };
+ updateUsersStatus();
+ });
+ socket.on('users', function(d) {
+ /* We've got a list of all users. */
+ /* The server is always right. */
+ users = d.users;
+ updateUsersStatus();
+ });
+ socket.on('nickset', function(d) {
+ /* Someone, possibly us, changed their nick. */
+ users[d.id].nick = d.nick;
+ updateUsersStatus();
+ });
+ socket.on('filterset', function(d) {
+ /* Someone changed their filter */
+ users[d.id].filters = d.filters;
+ /* Use the variable netrendering to denote that we're rendering due to a change received from the network. */
+ netrendering = true;
+ setOwnFilters(d.filters);
+ netrendering = false;
+ });
+ socket.on('logout', function(d) {
+ /* Someone disconnected, take them off the list. */
+ delete users[d.id];
+ updateUsersStatus();
+ });
+ dc.renderlet(function() {
+ /* Hook a listener into dc's rendering routine. If it rerenders, broadcast the change. */
+ if (netrendering) {
+ /* If we rendered due a change we received, don't broadcast it again. That would be A Bad Thing. */
+ return;
+ }
+ socket.emit("filter", { filters: mv.charter.filters() });
+ });
+ }
+ function setOwnFilters(filters) {
+ /* See if there's any difference - if there isn't, don't update. */
+ var change = false;
+ var key;
+ /* Check for keys in the filters to apply which are not in our charts. */
+ for (key in filters) {
+ if (!(key in mv.charts))
+ continue;
+ var filter = mv.charts[key].filter();
+ if (typeof(filter) == "array") {
+ /* Crossfilter uses arrays to filter ranges. Exactly the first two elements are significant. */
+ if (filter[0] == filters[key][0] &&
+ filter[1] == filters[key][1]) {
+ continue;
+ }
+ } else if (filter == filters[key]) {
+ continue;
+ }
+ /* This filter differs. Apply it. */
+ change = true;
+ mv.charts[key].filter(filters[key]);
+ }
+ /* Check for keys in our charts which are not in the filters to apply. */
+ for (key in mv.charts) {
+ if (mv.charts[key].filter() != null) {
+ if (key in filters) {
+ /* This has already been handled above */
+ continue;
+ }
+ /* There is no longer a filter applying here, clear it. */
+ change = true;
+ mv.charts[key].filterAll();
+ }
+ }
+ if (change) {
+ dc.redrawAll();
+ }
+ }
+ function updateUsersStatus() {
+ /* Convert the user list to a form suitable for a d3 selection */
+ var data = [];
+ for (var uid in users) { users[uid].id = uid; data.push(users[uid]); }
+ /* Data join */
+ var userlist = usersStatus.selectAll(".user")
+ .data(data, function(d) { return d.id; });
+ /* Enter */
+ userlist
+ .enter().append("li").attr("class", "user")
+ ;
+ /* Update */
+ userlist
+ .each(function(d,i) {
+ var elm = d3.select(this);
+ console.log("Userlist para appending", d,i);
+ var name = elm.select(".name");
+ var nick = d.nick == null ? "Anonymous User " + d.id : d.nick;
+ if (d.id == id) {
+ /* This is us! We can edit our name. */
+ if (name.empty()) {
+ console.log("Found our entry. id:", id, "datum", d);
+ name = elm.append("input").attr("class", "name")
+ .attr("type", "text")
+ .attr("placeholder", "Enter name here")
+ .on("change", function () {
+ /* A d3 select must be dereferenced twice to access the DOM entity */
+ socket.emit("nick", { nick: name[0][0].value });
+ })
+ ;
+ }
+ name.attr("value", nick);
+ } else {
+ /* This is someone else. We can't edit their name. */
+ if (name.empty()) {
+ name = elm.append("p").attr("class", "name");
+ }
+ name.text(nick);
+ }
+ })
+ ;
+ /* Remove */
+ userlist
+ .exit().remove()
+ ;
+ }
+ return mv;
+}(mv || {});
diff --git a/js/mv/heap.js b/js/mv/heap.js
index a0a69be..03f3c00 100644
--- a/js/mv/heap.js
+++ b/js/mv/heap.js
@@ -1,6 +1,7 @@
var mv = function(mv) {
mv.heap = function() {
var heap = {};
+ var monoGroups = {};
var statGran = 10;
heap.init = function() {
function ea(p, d) { p.e += d.e; p.j += d.j; p.r++; return p; }
diff --git a/js/mv/main.js b/js/mv/main.js
index 4db9973..b7fcb1e 100644
--- a/js/mv/main.js
+++ b/js/mv/main.js
@@ -48,6 +48,10 @@ var mv = function(mv) {
.style("opacity", 1)
;
mv.charter.init();
+ console.log(document.getElementById("connect-option").checked);
+ if (document.getElementById("connect-option").checked) {
+ mv.socket.connect();
+ }
}, 2000);
}
};
diff --git a/js/util/trellis-chart.js b/js/util/trellis-chart.js
index 139d663..65e07a2 100644
--- a/js/util/trellis-chart.js
+++ b/js/util/trellis-chart.js
@@ -10,11 +10,11 @@ function trellisChart(anchor, monoGroups) {
var subChartLength = 57;
var subChartUnpaddedLength = 50;
var subChartPadding = 7;
+ var filler = d3.scale.log().domain([1, 2]).range([0, 255]);
var margin = {top: 10, right: 10, bottom: 20, left: 10};
var anchor = d3.select(anchor);
var g = anchor.select("g");
- var filler = d3.scale.log().domain([1, 2]).range([0, 255]);
var _chart = function() {
if (g.empty()) {
@@ -126,5 +126,15 @@ function trellisChart(anchor, monoGroups) {
;
}
+ _chart.filter = function() {
+ /*
+ * TODO:
+ * This is going to be interesting. As the chart is not charting a single
+ * monogroup, and most code is built around this assumption, this might
+ * well end up being a messy special case.
+ */
+ return null;
+ }
+
return _chart;
}