summaryrefslogtreecommitdiff
path: root/js
diff options
context:
space:
mode:
authorFreeyorp <TheFreeYorp@NOSPAM.G.m.a.i.l.replace>2013-04-23 15:26:48 +1200
committerFreeyorp <TheFreeYorp@NOSPAM.G.m.a.i.l.replace>2013-04-23 15:26:48 +1200
commita7da1fafe2ca8c9115ce76f99a903e1b3da692a3 (patch)
treeee4465cbebc54cd937f69ede32b2b99513dd76a0 /js
parentf12d953786784ca8e8b8e9db146ce494b30f6647 (diff)
downloadmanavis-a7da1fafe2ca8c9115ce76f99a903e1b3da692a3.tar.gz
manavis-a7da1fafe2ca8c9115ce76f99a903e1b3da692a3.tar.bz2
manavis-a7da1fafe2ca8c9115ce76f99a903e1b3da692a3.tar.xz
manavis-a7da1fafe2ca8c9115ce76f99a903e1b3da692a3.zip
Add initial stat trellis chart
This should now be implemented efficiently enough for everything else to still work. This currently does not allow filtering, but the dimensions are prepared in a manner that makes this a simple addition.
Diffstat (limited to 'js')
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
5 files changed, 182 insertions, 9 deletions
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;
+}