diff options
-rw-r--r-- | public/js/mv/chart.js | 44 | ||||
-rw-r--r-- | public/js/mv/connect.js | 26 | ||||
-rw-r--r-- | public/js/util/trellis-chart.js | 71 |
3 files changed, 111 insertions, 30 deletions
diff --git a/public/js/mv/chart.js b/public/js/mv/chart.js index d2eedd4..ebb62da 100644 --- a/public/js/mv/chart.js +++ b/public/js/mv/chart.js @@ -14,6 +14,21 @@ var mv = function(mv) { .xUnits(d3.time.hours) .xAxisPadding(2) ; + mv.charts.date.filterCompare = function(l, r) { + return ((l instanceof Date) ? l : new Date(l)) + == ((r instanceof Date) ? r : new Date(r)); + }; + // Remember the old date filter + // FIXME: Find a more elegant way to do this + var innerDateFilter = mv.charts.date.filter; + mv.charts.date.filter = function(_) { + if (!arguments.length) return innerDateFilter(); + if (!_) return innerDateFilter(_); + _ = _ instanceof Array + ? _.map(function(d) { return d instanceof Date ? d : new Date(d); }) + : (_ instanceof Date ? _ : new Date(_)); + return innerDateFilter(_); + } /* dc's default date format is M/D/Y, which is confusing and not ISO 8901 */ dc.dateFormat = d3.time.format("%Y-%m-%d %H:%M"); mv.charts.blvl = bar(monoGroup(med(dc.barChart("#blvl-chart")), "blvl")) @@ -52,7 +67,34 @@ var mv = function(mv) { .title(function(d) { return "Map " + d.key + ":" + d.value.r; }) .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]; })); + var attrs = ["str", "agi", "vit", "dex", "int", "luk"]; + mv.charts.stats = trellisChart("#stat-chart", attrs.map(function(d) { mv.heap[d].name = d; return mv.heap[d]; })); + mv.charts.stats.filterCompare = function(l, r) { + /* Compare each attribute in turn. FIXME: Duplicated code with connect.js */ + if (l == null && r == null) + return true; + if (l == null || r == null) + return false; + for (var key in attrs) { + var attr = attrs[key]; + if (attr in l && attr in r) { + if (l[attr] instanceof Array) { + /* Range filter */ + if (!(r[attr] instanceof Array) + || l[attr][0] != r[attr][0] + || l[attr][1] != r[attr][1]) { + return false; + } + } else if ((r[attr] instanceof Array) || l[attr] != r[attr]) { + /* Exact filter */ + return false; + } + } else if (attr in l || attr in r) { + return false; + } + } + return true; + } mv.charts.type.filter("KILLXP"); var killxpShown = true; var killxpCharts = d3.select("#killxp-charts"); diff --git a/public/js/mv/connect.js b/public/js/mv/connect.js index 6f3101f..a17febb 100644 --- a/public/js/mv/connect.js +++ b/public/js/mv/connect.js @@ -105,30 +105,30 @@ var mv = function(mv) { /* See if there's any difference - if there isn't, don't update. */ var change = false; var key; + function filterCompare(key, l, r) { + return ("filterCompare" in mv.charts[key]) ? mv.charts[key].filterCompare(l, r) : l == r; + } /* Check for keys in the filters to apply which are not in our charts. */ for (key in filters) { - if (key == "date") { - /* - * Special case! FIXME: Find a more elegant way to handle this - */ - filters[key][0] = new Date(filters[key][0]); - filters[key][1] = new Date(filters[key][1]); - } if (!(key in mv.charts)) continue; - var filter = mv.charts[key].filter(); - if (typeof(filter) == "array") { + /* The two filters to compare. */ + var l = mv.charts[key].filter(); + var r = filters[key]; + if (l instanceof 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]) { + if ((r instanceof Array) + && filterCompare(key, l[0], r[0]) + && filterCompare(key, l[1], r[1])) { continue; } - } else if (filter == filters[key]) { + } else if (!(r instanceof Array) && filterCompare(key, l, r)) { + /* Exact filter */ continue; } /* This filter differs. Apply it. */ change = true; - mv.charts[key].filter(filters[key]); + mv.charts[key].filter(r); } /* Check for keys in our charts which are not in the filters to apply. */ for (key in mv.charts) { diff --git a/public/js/util/trellis-chart.js b/public/js/util/trellis-chart.js index 8b92344..473d7d3 100644 --- a/public/js/util/trellis-chart.js +++ b/public/js/util/trellis-chart.js @@ -31,14 +31,15 @@ function trellisChart(anchor, monoGroups) { var g = anchor.select("g"); /* Main columns, one for each attr */ - var columns; var colBodies; + var brushContainers; var _chart = function() { if (g.empty()) { renderBase(); } colBodies.each(redrawCells); + brushContainers.each(redrawBrush); } function renderBase() { @@ -157,19 +158,32 @@ function trellisChart(anchor, monoGroups) { ; }) ; - dimLabelsG.selectAll("g.brush-container") + brushContainers = dimLabelsG.selectAll("g.brush-container") .data(monoGroups) .enter().append("g").attr("class", "brush-container") .attr("transform", function(d, i) { return "translate(" + (i * subChartLength) + ",0)"; }) .each(function (d, i) { d.id = i; d.brush = d3.svg.brush(); + d.brushRedrawNeeded = false; d.filter = function(_) { if (!arguments.length) return d._filter; - d.dim.filter(_); - d._filter = _; + if (_ != null) { + d._filter = _; + d.brush.extent(_); + d.dim.filter(_); + d.brushRedrawNeeded = true; + } else { + d.filterAll(); + } return d; }; + d.filterAll = function() { + d._filter = null; + d.brush.clear(); + d.dim.filterAll(); + d.brushRedrawNeeded = true; + } }) .each(renderBrush) ; @@ -179,7 +193,7 @@ function trellisChart(anchor, 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 } */ - columns = g.selectAll(".column") + var columns = g.selectAll(".column") .data(monoGroups); var colE = columns.enter().append("g").attr("class", "column") @@ -253,7 +267,8 @@ function trellisChart(anchor, monoGroups) { } function renderBrush(d, i) { - var columnG = d3.select(this); + var columnGDOM = this; + var columnG = d3.select(columnGDOM); var brushG = columnG.select(".brush"); d.brush @@ -261,7 +276,7 @@ function trellisChart(anchor, monoGroups) { .on("brush", function () { var extent = extendBrush(d, brushG); - redrawBrush(d, i, brushG); + redrawBrush.call(columnGDOM, d, i); if (brushIsEmpty(extent, d.brush)) { dc.events.trigger(function () { @@ -287,13 +302,20 @@ function trellisChart(anchor, monoGroups) { } } - function redrawBrush(d, i, brushG) { - if (d.filter() && d.brush.empty()) + function redrawBrush(d, i) { + if (!d.brushRedrawNeeded) { + return; + } + if (d.filter() && d.brush.empty()) { d.brush.extent(d.filter()); + } + var brushG = d3.select(this).select(".brush"); brushG.call(d.brush.x(_scale)); brushG.selectAll("rect").attr("height", chartLen); + d.brushRedrawNeeded = false; + // TODO: fade the deselected area } @@ -326,18 +348,35 @@ function trellisChart(anchor, monoGroups) { + "V" + (2 * y - 8); } + /* + * Interface assumptions: + * - filter with one argument uses this argument as a filter in a manner defined by the chart. + * - filter with no arguments returns a description of the chart's active filter(s) suitable for applying to a filter() function of the same chart type. + * + * Most chart's filter() uses the data type of crossfilter's filter(). The trellis chart, using multiple dimensions, cannot. + * Therefore, it uses the format { [name] -> filter } where filter is the format used by crossfilter's filter. + */ _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. - */ if (!arguments.length) { - return null; + var ret = null; + for (var key in monoGroups) { + if (monoGroups[key].filter()) { + (ret || (ret = {}))[attrs[key]] = monoGroups[key].filter(); + } + } + return ret; + } + for (key in _) { + monoGroups[attrsIdByName[key]].filter(_[key]); } return _chart; } + _chart.filterAll = function(_) { + for (var key in monoGroups) { + monoGroups[key].filterAll(); + } + } + return _chart; } |