dc.js使用两个没有简单尺寸和第二分组阶段的化约器

时间:2018-08-03 18:00:00

标签: javascript d3.js dc.js

在我对这篇文章的回应之后,我提出了一个简短的问题: dc.js Box plot reducer using two groups 只是想尽一切办法去了解减速器以及如何过滤和收集数据,所以我将首先逐步理解。

数据格式:

{
    "SSID": "eduroam",
    "identifier": "Client",
    "latitude": 52.4505,
    "longitude": -1.9361,
    "mac": "dc:d9:16:##:##:##",
    "packet": "PR-REQ",
    "timestamp": "2018-07-10 12:25:26",
    "vendor": "Huawei Technologies Co.Ltd"
}

(1)使用以下命令应得到键值对的输出数组(键MAC地址和所连接网络的值计数):

var MacCountsGroup = mac.group().reduce(
    function (p, v) {
        p[v.mac] = (p[v.mac] || 0) + v.counter;
        return p;
    },
    function (p, v) {
        p[v.mac] -= v.counter;
        return p;
    },
    function () {
        return {}; // KV Pair of MAC -> Count
    }
);

(2)然后,为了使用该对象,必须将其展平,以便可以按如下所示将其传递到图表:

function flatten_object_group(group) {
    return {
        all: function () {
            return group.all().map(function (kv) {
                return {
                    key: kv.key,
                    value: Object.values(kv.value).filter(function (v) {
                        return v > 0;
                    })
                };
            });
        }
    };
}

var connectionsGroup = flatten_object_group(MacCountsGroup);

(3)然后,我将mac作为饼图维传递,将connectionsGroup作为组传递。这会根据我的数据集向图表返回大约50,000个切片的图表。

var packetPie = dc.pieChart("#packetPie");
packetPie
    .height(495)
    .width(350)
    .radius(180)
    .renderLabel(true)
    .transitionDuration(1000)
    .dimension(mac)
    .ordinalColors(['#07453E', '#145C54', '#36847B'])
    .group(connectionsGroup); 

enter image description here

这很正常,我会继续进行。

(4)现在,我想按第一个reducer给出的值进行分组,即我想将所有Mac地址与1个网络连接,2个网络连接等组合在一起作为切片。

如何将其作为“网络连接”的维度?如何生成源数据中不存在的,由mac生成的汇总数据?

或者这是否需要在第一个化简器和展平之间使用一个中间函数来合并第一个化简器的所有值?

1 个答案:

答案 0 :(得分:2)

您无需完成所有操作即可获取mac地址的饼图。

在第1-3点中有一些错误的理解,我想我将首先解决。看来您从上一个问题复制并粘贴了代码,因此我不确定是否有帮助。

(1)如果您具有一维mac地址,则将其减小将不会有任何进一步的影响。最初的想法是按供应商进行维度/分组,然后减少每个 mac地址的数量。这种减少将按 mac address 分组,然后进一步计算每个bin中每个mac地址的实例,因此它只是一个带有一个键的对象。它将生成键值对的映射,例如

{key: 'MAC-123', value: {'MAC-123': 12}}

(2)这将使对象在值内变平,放下键并仅产生一个计数数组

{key: 'MAC-123', value: [12]}

(3)由于饼图期望简单的键/值对,其值是一个数字,因此对于获取数组[12]之类的值可能不满意。这些值可能被强制为NaN

(4)好的,这是真正的问题,它实际上不像您先前的问题那么容易。我们对箱形图很轻松,因为数据中存在“维度”(用交叉过滤器来说,就是您过滤和分组的键)。

让我们忘记以上1-3点中的错误线索,并从第一原则开始。

无法查看数据的单个行并确定它是否属于“具有1个连接”,“具有2个连接”等类别,而无需查看其他内容。假设您希望成为能够单击饼图中的切片并过滤所有数据,我们将不得不找到另一种实现方法。

但是首先让我们看一下如何生成“网络连接数”饼图。这样做稍微容易一些,但是据我所知,它确实需要真正的“双重减少”。

如果我们在mac维度上使用默认的缩减,我们将得到一个键/值对数组,其中键是mac地址,值是该地址的连接数:

[
  {
    "key": "1c:b7:2c:48",
    "value": 8
  },
  {
    "key": "1c:b7:be:ef",
    "value": 3
  },
  {
    "key": "6c:17:79:03",
    "value": 2
  },
  ...

我们现在如何生成键/值数组,其中键是连接数,而值是该连接数的mac地址数组?

听起来像是鲜为人知的Array.reduce的工作。此函数可能是交叉过滤器group.reduce()的灵感来源,但它有点简单:它只是遍历一个数组,将每个值与最后一个的结果组合在一起。从数组生成对象非常有用:

var value_keys = macPacketGroup.all().reduce(function(p, kv) {
  if(!p[kv.value])
    p[kv.value] = [];
  p[kv.value].push(kv.key);
  return p;
}, {});

太好了:

{
  "1": [
    "b8:1d:ab:d1",
    "dc:d9:16:3a",
    "dc:d9:16:3b"
  ],
  "2": [
    "6c:17:79:03",
    "6c:27:79:04",
    "b8:1d:aa:d1",
    "b8:1d:aa:d2",
    "dc:da:16:3d"
  ],

但是我们想要一个键/值对数组,而不是对象!

var key_count_value_macs = Object.keys(value_keys)
    .map(k => ({key: k, value: value_keys[k]}));

太好了,看起来就像一个“真实小组”会产生什么:

[
  {
    "key": "1",
    "value": [
      "b8:1d:ab:d1",
      "dc:d9:16:3a",
      "dc:d9:16:3b"
    ]
  },
  {
    "key": "2",
    "value": [
      "6c:17:79:03",
      "6c:27:79:04",
      "b8:1d:aa:d1",
      "b8:1d:aa:d2",
      "dc:da:16:3d"
    ]
  },
  ...

将所有内容包装在“伪造的组”中,当要求产生.all()时,将查询原始组并进行上述转换:

function value_keys_group(group) {
  return {
    all: function() {
      var value_keys = group.all().reduce(function(p, kv) {
        if(!p[kv.value])
          p[kv.value] = [];
        p[kv.value].push(kv.key);
        return p;
      }, {});
      return Object.keys(value_keys)
        .map(k => ({key: k, value: value_keys[k]}));
    }
  }
}

现在我们可以绘制饼图了!唯一奇怪的是,值访问器应查看每个值的数组长度(而不是假设值只是一个数字):

packetPie
    // ...
    .group(value_keys_group(macPacketGroup))
    .valueAccessor(kv => kv.value.length);

packet pie

Demo fiddle

但是,单击切片将不起作用。一分钟后,我会再说一遍-只想先单击“保存”!

第2部分:基于计数的过滤

正如我在开始时所说,无法创建将根据连接数进行过滤的交叉过滤器维度。这是因为交叉过滤器始终需要查看每一行,并仅根据该行中的信息确定它是否属于组或过滤器。

如果此时添加另一个图表,然后尝试单击切片everything in the other charts will disappear。这是因为密钥现在是计数,并且计数是无效的mac地址,因此我们要告诉它过滤到不存在的密钥。

但是,我们显然可以按mac地址进行过滤,而且我们也知道每个计数的mac地址!所以这还不错。它只需要一个filterHandler

尽管,嗯,在制作假组时,我们似乎忘记了value_keys。它隐藏在函数内部,然后放开。

这有点难看,但是我们可以解决这个问题:

function value_keys_group(group) {
  var saved_value_keys;
  return {
    all: function() {
      var value_keys = group.all().reduce(function(p, kv) {
        if(!p[kv.value])
          p[kv.value] = [];
        p[kv.value].push(kv.key);
        return p;
      }, {});
      saved_value_keys = value_keys;
      return Object.keys(value_keys)
        .map(k => ({key: k, value: value_keys[k]}));
    },
    value_keys: function() {
      return saved_value_keys;
    }
  }
}

现在,每次调用.all()时(每次绘制饼图时),假组将隐藏value_keys对象。这不是一个好习惯(如果您在.value_keys()之前调用undefined会返回.all()),但是基于dc.js的工作方式是安全的。

通过这种方式,饼图的filterHandler相对简单:

packetPie.filterHandler(function(dimension, filters) {
  if(filters.length === 0)
    dimension.filter(null);
  else {
    var value_keys = packetPie.group().value_keys();
    var all_macs = filters.reduce(
      (p, v) => p.concat(value_keys[v]), []);
    dimension.filterFunction(k => all_macs.indexOf(k) !== -1);
  }
  return filters;
});

这里有趣的一行是对Array.reduce的另一个调用。此功能对于从另一个数组生成一个数组也很有用,这里我们仅用它来连接所有选定切片(连接计数)中的所有值(mac地址)。

现在,我们有了一个有效的过滤器。将其与上一个问题的箱形图结合起来并没有太大意义,但是the new fiddle证明了基于连接数的过滤确实有效。

第3部分:零呢?

通常会出现这种情况,crossfilter认为值零的bin仍然存在,因此我们需要"remove the empty bins"。但是,在这种情况下,我们为第一个假组添加了非标准方法,以便进行过滤。 (我们本可以在那里使用全局变量,但是全局变量是凌乱的。)

因此,我们需要“通过” value_keys方法:

function remove_empty_bins_pt(source_group) {
    return {
        all:function () {
            return source_group.all().filter(function(d) {
                return d.key !== '0';
            });
        },
        value_keys: function() {
            return source_group.value_keys();
        }
    };
}
packetPie
  .group(remove_empty_bins_pt(value_keys_group(macPacketGroup)))

另一个奇怪的是,我们正在过滤 key 零,而这是一个字符串!

Demo fiddle!

或者,这是一个更好的解决方案!在传递到value_keys_group之前进行bin过滤,然后我们可以使用普通的remove_empty_bins

function remove_empty_bins(source_group) {
    return {
        all:function () {
            return source_group.all().filter(function(d) {
                //return Math.abs(d.value) > 0.00001; // if using floating-point numbers
                return d.value !== 0; // if integers only
            });
        }
    };
}
packetPie
    .group(value_keys_group(remove_empty_bins(macPacketGroup)))

Yet another demo fiddle!!