使用 dc.js 的可编辑散点图

editable scatter chart using dc.js

什么是制作散点图的好方法,可以通过单击操作在散点图中编辑数据?

我们的想法是发现图中数据中的异常值并过滤图中的值,而不是必须更改源数据。

更好的方法是从交叉过滤器中删除数据,但仅过滤的解决方案是可以接受的。

我想出了一个使用当前 dc.js (beta 32) 的解决方案。

它不支持画笔(需要 .brushOn(false))- 我会在增强请求中解释为什么这需要对 dc.js 进行一些更改。

但它确实支持点击点来切换它们,以及重置 link。 (点击背景重置也是可以的,这里没有实现。)

我们要做的是使用标准 dc.js 过滤器签名定义我们自己的 ExcludePointsFilter

function compare_point(p1, p2) {
  return p1[0] === p2[0] && p1[1] === p2[1];
}
function has_point(points, point) {
  return points.some(function(p) {
    return compare_point(point, p);
  });
}
function ExcludePointsFilter(points) {
  var points2 = points.slice(0);
  points2.filterType = 'ExcludePointsFilter';
  points2.isFiltered = function(k) {
    return !has_point(points2, k);
  };
  return points2;
}

每次单击一个点时,我们都会计算一组新的点,并替换过滤器:

scatterPlot.on('pretransition.exclude-dots', function() { #1
  // toggle exclusion on click
  scatterPlot.selectAll('path.symbol') #2
      .style('cursor', 'pointer') // #3
      .on('click.exclude-dots', function(d) { // #4
    var p = [d.key[0],d.key[1]];
    // rebuild the filter #5
    var points = scatterPlot.filter() || [];
    if(has_point(points, p))
      points = points.filter(function(p2) {
        return !compare_point(p2, p);
      });
    else
      points.push(p);
    // bypass scatterPlot.filter, which will try to change
    // it into a RangedTwoDimensionalFilter #6
    scatterPlot.__filter(null)
      .__filter(ExcludePointsFilter(points));
    scatterPlot.redrawGroup();
  });
});

解释:

  1. 每次渲染或重绘图表时,我们都会在任何转换开始之前对其进行注释
  2. Select所有的点,也就是path个元素加上symbol class
  3. 设置合适的光标(指针手可能不理想,但有aren't too many to choose from
  4. 为每个点设置一个点击处理程序 - 使用 exclude-dots 事件命名空间以确保我们不会干扰其他任何人。
  5. 获取当前过滤器或启动一个新过滤器。查看当前点击的点(传递为 d)是否在该数组中,并根据需要添加或删除它。
  6. 替换散点图的当前过滤器。由于散点图与 RangedTwoDimensionalFilter 紧密结合,我们需要绕过它的 filter 覆盖(以及 coordinateGridMixin 覆盖!)并一直走到 baseMixin.filter()。是的,这很奇怪。

为了更好的衡量,我们还将更换过滤器打印机,它通常不知道如何处理点数组:

scatterPlot.filterPrinter(function(filters) {
  // filters will contain 1 or 0 elements (top map/join is just for safety)
  return filters.map(function(filter) {
    // filter is itself an array of points
    return filter.map(function(p) {
      return '[' + p.map(dc.utils.printSingleValue).join(',') + ']';
    }).join(',');
  }).join(',');
});

这里是 fiddle 中的一个工作示例:http://jsfiddle.net/gordonwoodhull/3y72o0g8/16/

请注意,如果您随后想对排除的点执行某些操作,您可以从 scatterPlot.filter() 中读取它们 - 过滤器是带有一些注释的点数组。您甚至可以反转过滤器,然后调用 crossfilter.remove(),但我会把它留作练习。