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

标签 javascript d3.js data-visualization dc.js crossfilter

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

gif of the data visualization

看起来有点棘手,但基本上,当我点击折线图中的一个点时,该点对应于杂志的特定问题。等值线更新以反射(reflect)该问题的地理数据,但重要的是,地理数据是针对与该问题相对应的抽样时间段。基本上,1 月至 6 月或 1 月至 6 月之间的任何问题的等值线看起来都相同给定年份的 7 月至 12 月。

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

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

  // 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: ""
    }
  }

问题可能出在其他地方,但我认为这不是交叉过滤器问题(我肯定没有遇到“来自同一维度的两组”问题)并且向 add reducer 添加额外的逻辑可以解决问题甚至更不可预测(可以理解 - 我真的不需要为所有值呈现样本日期。)重点是我完全不知道我逻辑中的缺陷在哪里,我喜欢一些帮助!

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

最佳答案

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

问题

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

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

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

通用解决方案

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

因此,让我们跟踪我们在每个 bin 中看到的日期。当我们开始时,每个箱子都会有所有的日期。一旦选择了一个日期,将只有一个日期(但由于您的双交叉过滤器设置,因此不完全是所选日期)。

我们现在将跟踪一个名为 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 年以来的毫秒数来规范化输入日期。我相信您直接使用字符串日期是安全的,但谁知道可能会有空格或零或其他东西。

你不能用日期对象索引一个对象,你必须把它转换成一个整数。

    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')

可用性说明

从可用性的 Angular 来看,我建议不要在 mouseleave 上filter(null),或者如果您真的想要,则将其设置为超时,当您看到 mouseenter 时该超时会被取消。人们应该能够“擦洗”折线图并查看等值线图中随时间的变化,而不会意外切换回未过滤的颜色。

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

关于javascript - 为什么我的 reducer 在 dc.js 中应用的第一个过滤器和后续过滤器之间表现不同?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/56033942/

相关文章:

javascript - 图表显示不正确

d3.js - 双条形图创建

python-3.x - 影响主要 y 范围的 Bokeh 次要 y 范围

javascript - 如何在 Express 中修复此 ES6 promise 链?

javascript - 使用 Dimple 创建简单的散点图

javascript - 如何根据值的变化修改D3js图表?

javascript - 使用 d3.scale.linear 在 Javascript 中仅缩放对象数组中的一个值

javascript - 如何取消/忽略 redux 中的操作

JavaScript 不会隐藏 asp.net 中的文本区域

javascript - 如何从具有 id 的元素发送文本以在单击时起作用?