为什么我的减速器在第一个过滤器和 dc.js 中应用的后续过滤器之间表现不同?

Why is my reducer behaving differently between the first filter and subsequent filters applied in dc.js?

我正在研究 a data visualization,它有一个奇怪的小错误:

看起来有点棘手,但本质上,当我单击折线图中的一个点时,该点对应于杂志的特定问题。等值线更新以反映该问题的地理数据,但重要的是,地理数据适用于 与该问题相对应的采样周期。 本质上,对于 January-June 或 July-December 给定年份。

如您所见,我有一个名为采样发行日期(用于地理数据)的键,该值应该是地理数据所基于的发行日期(基本上,他们会得到一个地理分布具体问题并将其称为六个月内所有数据的代表。)然而,当我最初单击某个问题时,我总是在我的数据中获取最后一个采样日期。所有的地理数据都是正确的,令人恼火的是,所有后续的点击都会显示正确的信息。所以只有第一次点击(在刷新页面或解决问题后)我遇到了问题。

老实说,我的代码现在是一场噩梦,因为我专注于调试,但你可以 see my reducer for the remove function on GitHub 下面 copy/pasted:

  // Reducer function for raw geodata
  function geoReducerAdd(p, v) {
    // console.log(p.sampled_issue_date, v.sampled_issue_date, state.periodEnding, state.periodStart)
    ++p.count
    p.sampled_mail_subscriptions += v.sampled_mail_subscriptions
    p.sampled_single_copy_sales += v.sampled_single_copy_sales
    p.sampled_total_sales += v.sampled_total_sales
    p.state_population = v.state_population // only valid for population viz
    p.sampled_issue_date = v.sampled_issue_date
    return p
  }

  function geoReducerRemove(p, v) {
    const currDate = new Date(v.sampled_issue_date)
    // if(currDate.getFullYear() === 1921) {
    //   console.log(currDate)
    // }
    currDate <= state.periodEnding && currDate >= state.periodStart ? console.log(v.sampled_issue_date, p.sampled_issue_date) : null
    const dateToRender = currDate <= state.periodEnding && currDate >= state.periodStart ? v.sampled_issue_date : p.sampled_issue_date
    --p.count
    p.sampled_mail_subscriptions -= v.sampled_mail_subscriptions
    p.sampled_single_copy_sales -= v.sampled_single_copy_sales
    p.sampled_total_sales -= v.sampled_total_sales
    p.state_population = v.state_population // only valid for population viz
    p.sampled_issue_date = dateToRender
    return p
  }

  // generic georeducer
  function geoReducerDefault() {
    return {
      count: 0,
      sampled_mail_subscriptions: 0,
      sampled_single_copy_sales: 0,
      sampled_total_sales: 0,
      state_population: 0,
      sampled_issue_date: ""
    }
  }

问题可能出在其他地方,但我不认为这是一个交叉过滤器问题(我肯定没有 运行 进入 "two groups from the same dimension" 问题)并向添加添加额外的逻辑reducer 使事情变得更不可预测(可以理解 - 我真的不需要为所有值呈现样本日期。)关键是我完全不知道我的逻辑中的缺陷在哪里,我'我希望得到一些帮助!

编辑:请注意,reducers 用于 dc.js 维度上的 reduce 方法,而不是原生 javascript reducer! :D

两个交叉过滤器!看到它总是很有趣......但它可能很棘手,因为 dc.js 中没有任何东西直接支持它,除了图表注册表。您需要自己在不同的图表组之间进行过滤,并且在具有不同时间分辨率的数据集之间进行映射可能会很棘手等等。

问题

据我了解您的应用程序,当在折线图中选择一个日期时,等值线图和随附的文本应该恰好有一行来自每个州选择的地理数据集。

关键问题是 Crossfilter 不能很好地告诉您哪些行在任何给定的 bin 中。所以即使只选择了一行,你也不知道它是什么!

这个问题让 minimum, maximum, and median reductions 异常复杂。您通常最终会构建新的数据结构来捕获 crossfilter 以效率的名义丢弃的内容。

通用解决方案

我将提供一个通用的解决方案,它比您更需要,但在类似情况下可能会有帮助。我知道的唯一选择是完全脱离 crossfilter 并查看原始数据集。这也很好,也许更有效率。但它可能有问题,在系统内工作很好。

所以让我们跟踪我们在每个垃圾箱中看到的日期。当我们开始时,每个箱子都会有所有的日期。一旦选择了一个日期,将只有一个日期(但由于您的 two-crossfilter 设置,不会完全是所选日期)。

我们现在将跟踪一个名为 date_counts 的对象,而不是 sampled_issue_date 对象:

  // Reducer function for raw geodata
  function geoReducerAdd(p, v) {
    // ...
    const canonDate = new Date(v.sampled_issue_date).getTime()
    p.date_counts[canonDate] = (p.date_counts[canonDate] || 0) + 1
    return p
  }
  function geoReducerRemove(p, v) {
    // ...
    const canonDate = new Date(v.sampled_issue_date).getTime()
    if(!--p.date_counts[canonDate])
      delete p.date_counts[canonDate]
    return p
  }
  // generic georeducer
  function geoReducerDefault() {
    return {
    // ...
      date_counts: {}
    }
  }

它有什么作用?

逐行

    const canonDate = new Date(v.sampled_issue_date).getTime()

也许这是偏执狂,但这通过将输入日期转换为自 1970 年以来的毫秒数来规范化输入日期。我相信您直接使用字符串日期是安全的,但谁知道可能会有 space 或零或其他东西。

不能用日期对象索引对象,必须将其转换为整数。

    p.date_counts[canonDate] = (p.date_counts[canonDate] || 0) + 1

当我们添加一行时,我们将检查当前是否有该行日期的计数。如果是这样,我们将使用我们拥有的计数。否则我们将默认为零。那我们就加一个。

    if(!--p.date_counts[canonDate])
      delete p.date_counts[canonDate]

当我们删除一行时,我们知道我们有该行的日期计数(因为 crossfilter 不会告诉我们它正在删除该行,除非它是早些时候添加的)。所以我们可以继续减少计数。然后,如果它达到零,我们可以删除该条目。

正如我所说,这太过分了。在你的情况下,计数只会变为 1,然后下降到 0。但这并不比保持

贵多少

正在渲染侧面板

当我们渲染侧面板时,date_counts 中应该只为该选定项目留下一个日期。

console.assert(Object.keys(date_counts).length === 1) // only one entry
console.assert(Object.entries(date_counts)[0][1] === 1) // with count 1
document.getElementById('geo-issue-date').textContent = new Date(+Object.keys(date_counts)[0]).format('mmm dd, yyyy')

可用性说明

从可用性的角度来看,我建议不要 filter(null) 在 mouseleave 上,或者如果你真的想要,然后将其置于超时状态,当你看到 mouseenter 时它会被取消。人们应该能够 "scrub" 在折线图上查看等值线图随时间的变化,而不会意外切换回未过滤的颜色。

我还注意到 (and filed) 一个问题,因为我注意到鼠标指针右侧显示的点很难单击。原因是这些点是重叠的,所以只有一小部分新月形可以悬停。至少在我的触控板上,点击会使指针向左移动。 (我可以在工具提示中看到日期倒退一周,然后 return。)当你放大时,这不是什么大问题。