summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--css/style.css9
-rw-r--r--index.html22
m---------js/dc0
-rw-r--r--js/mv/chart.js3
-rw-r--r--js/mv/heap.js40
-rw-r--r--js/mv/parse.js18
-rw-r--r--js/util/trellis-chart.js130
7 files changed, 206 insertions, 16 deletions
diff --git a/css/style.css b/css/style.css
index 0efc85b..8571fa9 100644
--- a/css/style.css
+++ b/css/style.css
@@ -68,6 +68,15 @@ body {
white-space: nowrap;
}
+/* Stat chart */
+
+#stat-chart svg g g.column .border-line {
+ fill: none;
+ stroke: #ccc;
+ opacity: .5;
+ shape-rendering: crispEdges;
+}
+
/* Utility */
.fader {
-moz-transition: opacity 1s linear;
diff --git a/index.html b/index.html
index 9a45e27..c621789 100644
--- a/index.html
+++ b/index.html
@@ -8,16 +8,23 @@
<div class="side">
<div class="vis-hide">
<div id="blvl-chart">
- <h3>Experience gain instances by Character Base Level <a class="reset" style="display: none;" href="javascript:mv.charts.blvl.filterAll();dc.redrawAll();">clear</a></h3>
+ <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>
+ <div id="stat-chart">
+ <h3>Instance breakdown by Stat allocation</h3>
+ </div>
+ </div>
+ </div>
+ <div class="side">
+ <div class="vis-hide">
<div id="type-chart">
- <h3>Experience gain instances by Type <a class="reset" style="display: none;" href="javascript:mv.charts.type.filterAll();dc.redrawAll();">clear</a></h3>
+ <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="target-chart">
- <h3>Experience gain instances by Target <a class="reset" style="display: none;" href="javascript:mv.charts.target.filterAll();dc.redrawAll();">clear</a></h3>
+ <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>Experience gain instances by Weapon <a class="reset" style="display: none;" href="javascript:mv.charts.wpn.filterAll();dc.redrawAll();">clear</a></h3>
+ <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="def-chart">
<h3>Definedness of records <span class="help" title="If logs are missing between server boot and the logs provided, not all information will be available for all records. Definedness of records falls into three categories. Records with undefined data; records with well defined data, but mixed in with records having undefined data, limiting validity of inferences; and records after any uncertain times, which are fully well defined. It is highly recommended that you filter results to only include records which are in fully well defined times; however, this may not always be possible.">[?]</span> <a class="reset" style="display: none;" href="javascript:mv.charts.def.filterAll();dc.redrawAll();">clear</a></h3>
@@ -44,13 +51,13 @@
</div>
<div class="vis-hide">
<div id="map-chart">
- <h3>Experience gain 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>
+ <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>
</div>
<div id="date-chart">
- <h3>Experience gain instances by Date <a class="reset" style="display: none;" href="javascript:mv.charts.date.filterAll();dc.redrawAll();">clear</a></h3>
+ <h3>Instance breakdown by Date <a class="reset" style="display: none;" href="javascript:mv.charts.date.filterAll();dc.redrawAll();">clear</a></h3>
</div>
<div id="player-chart">
- <h3>Experience gain instances by Character ID <span class="help" title="Older to newer characters appear left to right, respectively.">[?]</span> <a class="reset" style="display: none;" href="javascript:mv.charts.pc.filterAll();dc.redrawAll();">clear</a></h3>
+ <h3>Instance breakdown by Character ID <span class="help" title="Older to newer characters appear left to right, respectively.">[?]</span> <a class="reset" style="display: none;" href="javascript:mv.charts.pc.filterAll();dc.redrawAll();">clear</a></h3>
</div>
</div>
</div>
@@ -73,3 +80,4 @@
<script src="js/mv/load.js"></script>
<script src="js/mv/main.js"></script>
<script src="js/mv/parse.js"></script>
+<script src="js/util/trellis-chart.js"></script>
diff --git a/js/dc b/js/dc
-Subproject 9a243a5d684aafc3239ed06dc5a9bb15ac4aa94
+Subproject c8eab4e79abd32c4f7b42a20b16a18ba4af2967
diff --git a/js/mv/chart.js b/js/mv/chart.js
index 882caee..74d7b85 100644
--- a/js/mv/chart.js
+++ b/js/mv/chart.js
@@ -32,6 +32,7 @@ var mv = function(mv) {
case 2: return "#6baed6";
default: throw "Definition chart: Color access key out of range!";
}})
+ .filter(2)
;
mv.charts.map = monoGroup(margined(wide(dc.bubbleChart("#map-chart"))), "map")
.height(500)
@@ -51,6 +52,8 @@ var mv = function(mv) {
.title(function(d) { return "Map " + d.key; })
.renderTitle(true)
;
+ mv.charts.stats = trellisChart("#stat-chart", ["str", "agi", "vit", "dex", "int", "luk"].map(function(d) { mv.heap[d].name = d; return mv.heap[d]; }));
+ dc.renderlet(function() { mv.charts.stats(); });
dc.renderAll();
}
function defLevelVerbose(level) {
diff --git a/js/mv/heap.js b/js/mv/heap.js
index bd1d583..a0a69be 100644
--- a/js/mv/heap.js
+++ b/js/mv/heap.js
@@ -1,19 +1,47 @@
var mv = function(mv) {
mv.heap = function() {
var heap = {};
+ var statGran = 10;
heap.init = function() {
- function a(p, d) { return { e: p.e + d.e, j: p.j + d.j, r: p.r + 1 }; }
- function s(p, d) { return { e: p.e - d.e, j: p.j - d.j, r: p.r - 1 }; }
- function z(p, d) { return { e: 0, j: 0, r: 0 }; }
+ function ea(p, d) { p.e += d.e; p.j += d.j; p.r++; return p; }
+ function es(p, d) { p.e -= d.e; p.j -= d.j; p.r--; return p; }
+ function ez(p, d) { return { e: 0, j: 0, r: 0 }; }
heap.cfdata = crossfilter(mv.parser.records);
- heap.all = heap.cfdata.groupAll().reduce(a, s, z);
+ heap.all = heap.cfdata.groupAll().reduce(ea, es, ez);
monoGroup("date", function(d) { return d3.time.hour.round(d.date); });
monoGroup("pc", function(d) { return d.pc; });
- monoGroup("map", function(d) { return d.map; }).reduce(a, s, z);
+ monoGroup("map", function(d) { return d.map; }).reduce(ea, es, ez);
monoGroup("blvl", function(d) { return d.pcstat ? d.pcstat.blvl : 0; });
monoGroup("type", function(d) { return d.type; });
monoGroup("target", function(d) { return d.target; });
- monoGroup("wpn", function(d) { return d.wpn; })
+ monoGroup("wpn", function(d) { return d.wpn; });
+ function sa(p, d) {
+ if (!d.pcstat) return p;
+ p.str[d.pcstat.str]++ || (p.str[d.pcstat.str] = 1);
+ p.agi[d.pcstat.agi]++ || (p.agi[d.pcstat.agi] = 1);
+ p.vit[d.pcstat.vit]++ || (p.vit[d.pcstat.vit] = 1);
+ p.dex[d.pcstat.dex]++ || (p.dex[d.pcstat.dex] = 1);
+ p.int[d.pcstat.int]++ || (p.int[d.pcstat.int] = 1);
+ p.luk[d.pcstat.luk]++ || (p.luk[d.pcstat.luk] = 1);
+ return p;
+ }
+ function ss(p, d) {
+ if (!d.pcstat) return p;
+ --p.str[d.pcstat.str] || (p.str[d.pcstat.str] = undefined);
+ --p.agi[d.pcstat.agi] || (p.agi[d.pcstat.agi] = undefined);
+ --p.vit[d.pcstat.vit] || (p.vit[d.pcstat.vit] = undefined);
+ --p.dex[d.pcstat.dex] || (p.dex[d.pcstat.dex] = undefined);
+ --p.int[d.pcstat.int] || (p.int[d.pcstat.int] = undefined);
+ --p.luk[d.pcstat.luk] || (p.luk[d.pcstat.luk] = undefined);
+ return p;
+ }
+ function sz(p, d) { return { str: [], agi: [], vit: [], dex: [], int: [], luk: [] }; }
+ monoGroup("str", function(d) { return d.pcstat ? d.pcstat.str : 0; }).reduce(sa, ss, sz);
+ monoGroup("agi", function(d) { return d.pcstat ? d.pcstat.agi : 0; }).reduce(sa, ss, sz);
+ monoGroup("vit", function(d) { return d.pcstat ? d.pcstat.vit : 0; }).reduce(sa, ss, sz);
+ monoGroup("dex", function(d) { return d.pcstat ? d.pcstat.dex : 0; }).reduce(sa, ss, sz);
+ monoGroup("int", function(d) { return d.pcstat ? d.pcstat.int : 0; }).reduce(sa, ss, sz);
+ monoGroup("luk", function(d) { return d.pcstat ? d.pcstat.luk : 0; }).reduce(sa, ss, sz);
/* Debugging group */
/*
* How well defined a record is.
diff --git a/js/mv/parse.js b/js/mv/parse.js
index 23a0f79..c30a874 100644
--- a/js/mv/parse.js
+++ b/js/mv/parse.js
@@ -24,9 +24,9 @@ var mv = function(mv) {
j: parseInt(d[7]),
type: d[8],
pcstat: pcstat[d[2]],
- target: 0,
- dmg: 0,
- wpn: 0
+ target: -1010,
+ dmg: -1010,
+ wpn: -1010
};
if (pcstat[d[2]] == undefined && (!fullyDefinedCutoff || ts > fullyDefinedCutoff)) {
fullyDefinedCutoff = ts;
@@ -42,8 +42,11 @@ var mv = function(mv) {
softAssert(mID == parseInt(d[6]), "Integrity error: MOB ID mismatch!");
// softAssert(rec.pc == parseInt(d[2]), "Integrity error: PC ID mismatch!");
rec.target = parseInt(d[7]);
+ softAssert(rec.target, "Unknown target!")
rec.dmg = parseInt(d[8]);
rec.wpn = parseInt(d[9]);
+ } else {
+// console.error("No match (deathblow):", spl[i - 2]);
}
} else {
d = spl[i - 1].match(/^(\d+\.\d+) PC(\d+) (\d+):(\d+),(\d+) GAINXP (\d+) (\d+) (\w+)/);
@@ -51,8 +54,11 @@ var mv = function(mv) {
var clone = parser.records[parser.records.length - 1];
softAssert(rec.map == clone.map, "Integrity error: MAP ID mismatch!");
rec.target = clone.target;
+ softAssert(rec.target, "Unknown (cloned) target!");
rec.dmg = clone.dmg; /* FIXME: Take into account actual assist damage */
rec.wpn = clone.wpn;
+ } else {
+// console.error("No match (clone):", spl[i - 1]);
}
}
}
@@ -70,6 +76,12 @@ var mv = function(mv) {
luk: parseInt(d[7])
};
s.blvl = stat.minLevelForStats(s.str, s.agi, s.vit, s.int, s.dex, s.luk);
+ s.str = Math.floor(s.str / 10);
+ s.agi = Math.floor(s.agi / 10);
+ s.vit = Math.floor(s.vit / 10);
+ s.int = Math.floor(s.int / 10);
+ s.dex = Math.floor(s.dex / 10);
+ s.luk = Math.floor(s.luk / 10);
pcstat[d[1]] = s;
return;
}
diff --git a/js/util/trellis-chart.js b/js/util/trellis-chart.js
new file mode 100644
index 0000000..139d663
--- /dev/null
+++ b/js/util/trellis-chart.js
@@ -0,0 +1,130 @@
+function trellisChart(anchor, monoGroups) {
+ /* attr -> {dim, group} key -> str amount, value -> { str, agi, vit, dex, int, luk } */
+
+ var attrs = monoGroups.map(function(d) { return d.name; });
+ var attrsIdByName = {};
+ monoGroups.forEach(function(d, i) { attrsIdByName[d.name] = i; });
+
+ var cellWidth = 5;
+ var radius = cellWidth / 2;
+ var subChartLength = 57;
+ var subChartUnpaddedLength = 50;
+ var subChartPadding = 7;
+
+ 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()) {
+ /* Make stuff! */
+ var svg = anchor.append("svg");
+ g = svg
+ .append("g");
+ attrs.forEach(function(d, i) {
+ g
+ .append("text")
+ .attr("transform", function(d) { return "translate(0," + ((attrs.length - i) * subChartLength + 10 - subChartLength / 2) + ")"; })
+ .text(d)
+ ;
+ g
+ .append("text")
+ .attr("transform", function(d) { return "translate(" + (i * subChartLength + 25 + 22) + "," + (attrs.length * subChartLength + 18) + ")"; })
+ .text(d)
+ ;
+ })
+ g = svg
+ .append("g")
+ .attr("transform", "translate(" + (margin.left + 25) + "," + (margin.top) + ")");
+ }
+ /* Group first into columns for each stat. We have one column for each of the stat monoGroups. */
+ /*
+ * monoGroups is an array of each stat dimension. We can consider each column to have data in the following format:
+ * { group: function, dim: function, name: stat }
+ */
+ var columns = g.selectAll(".column")
+ .data(monoGroups);
+ var colE = columns
+ .enter().append("g")
+ .attr("class", "column")
+ .attr("transform", function(d) { return "translate(" + (attrsIdByName[d.name] * subChartLength) + ",0)"; })
+ ;
+ colE
+ .append("line")
+ .attr("x1", -cellWidth)
+ .attr("x2", -cellWidth)
+ .attr("y1", -cellWidth)
+ .attr("y2", subChartLength * attrs.length - subChartPadding)
+ .attr("class", "border-line")
+ ;
+ colE
+ .append("line")
+ .attr("x1", subChartUnpaddedLength)
+ .attr("x2", subChartUnpaddedLength)
+ .attr("y1", -cellWidth)
+ .attr("y2", subChartLength * attrs.length - subChartPadding)
+ .attr("class", "border-line")
+ ;
+ /* Each stat has an array for its value. Group these to find the x position. */
+ /*
+ * The function transforms the data to take the grouping. We can consider each x position grouping to have data in the following format:
+ * { key: position, value: [{[stat] -> [y pos] -> count}] }
+ */
+ var colposg = columns.selectAll(".colpos")
+ .data(function(d, i) {
+// console.log("Incoming colposg format:", d, i, "Transformed to:", d.group.all().map(function(d2) { d2.name = d.name; return d2; }));
+ return d.group.all().map(function(d2) { d2.name = d.name; return d2; });
+ }, function(d) { return d.key; });
+ colposg
+ .enter().append("g")
+ .attr("class", "colpos")
+ .attr("transform", function(d) { return "translate(" + (d.key * cellWidth) + ",0)"; })
+ ;
+ /* Next, split up each x position grouping into its y stat grouping. */
+ /*
+ * We can consider each y stat grouping to have data in the following format:
+ * v[y pos] -> count; v.name -> name
+ */
+ var rows = colposg.selectAll(".row")
+ .data(function(d, i) {
+// console.log("Incoming row format:", d, i, "Transformed to:", attrs.map(function(d2) { return { name: d2, data: d.value[d2] }; }));
+ return attrs.map(function(d2) { return { name: d2, data: d.value[d2] }; });
+ });
+ rows
+ .enter().append("g")
+ .attr("class", "row")
+ .attr("transform", function(d) { return "translate(0," + ((attrs.length - attrsIdByName[d.name] - 1) * subChartLength) + ")"; })
+ ;
+ /* Finally, split up each y stat grouping into x. */
+ var vmax = 0;
+ var cells = rows.selectAll(".cell")
+ .data(function(d, i) {
+// console.log("Incoming cells format:", d, i, "Transformed to:", d.data);
+ return d.data;
+ });
+ cells
+ .enter().append("circle")
+ .attr("class", "cell")
+ .attr("r", radius)
+ ;
+ cells
+ .each(function(d) {
+ if (d > vmax) vmax = d;
+ })
+ ;
+ filler.domain([1, vmax + 1]);
+ cells
+ .attr("fill", function(d) {
+ return d ? d3.rgb(255 - filler(d + 1), 255 - filler(d + 1) / 2, 255 - filler(d + 1) / 3) : "white";
+ })
+ .attr("transform", function(d, i) { return "translate(0," + ((10-i) * cellWidth) + ")"; })
+ ;
+ cells
+ .exit()
+ .remove()
+ ;
+ }
+
+ return _chart;
+}