【问题标题】:dc.js lineChart performance issue with 8k+ itemsdc.js lineChart 8k+ 项目的性能问题
【发布时间】:2017-05-29 20:32:35
【问题描述】:

这是我关于 dc.js/d3.js/crossfilter.js 主题的第二个问题。我正在尝试实现一个基本的个人仪表板,我首先创建了一个非常简单的 lineChart(与 rangeChart 相关联),它随着时间的推移输出指标。

数据我保存为 json (它将在稍后阶段存储在 mongoDb 实例中,所以现在我使用 JSON 也保持日期时间格式) 看起来像这样:

[
{"date":1374451200000,"prodPow":0.0,"consPow":0.52,"toGridPow":0.0,"fromGridPow":0.52,"prodEn":0.0,"consEn":0.0,"toGridEn":0.0,"fromGridEn":0.0},
{"date":1374451500000,"prodPow":0.0,"consPow":0.34,"toGridPow":0.0,"fromGridPow":0.34,"prodEn":0.0,"consEn":0.0,"toGridEn":0.0,"fromGridEn":0.0},
{"date":1374451800000,"prodPow":0.0,"consPow":0.42,"toGridPow":0.0,"fromGridPow":0.42,"prodEn":0.0,"consEn":0.0,"toGridEn":0.0,"fromGridEn":0.0},
...
]

我有大约 22000 个这样的条目,打开仪表板时遇到很多性能问题。即使我尝试将数据切片为一组 8000 条记录,性能仍然很差(但至少渲染在一段时间后完成),并且与数据的交互也很糟糕。 我猜我的代码存在一些缺陷,导致它表现不佳,因为我预计 dc.js 和 crossfilter.js 会与 100k+ 条目和不止一个维度作斗争!

尽管如此,使用 chrome 进行分析和在线阅读并没有太大帮助(有关我稍后尝试更改的更多详细信息)。

这是我的 graph.js 代码:

queue()
    .defer(d3.json, "/data")
    .await(makeGraphs);

function makeGraphs(error, recordsJson) {

    // Clean data
    var records = recordsJson;

    // Slice data to avoid browser deadlock
    records = records.slice(0, 8000);

    // Crossfilter instance
    ndx = crossfilter(records);

    // Define Dimensions
    var dateDim = ndx.dimension(function(d) { return d.date; });

    // Define Groups
    var consPowByDate = dateDim.group().reduceSum(function (d) { return d.consPow; });
    var prodPowByDate = dateDim.group().reduceSum(function (d) { return d.prodPow; });

    // Min and max dates to be used in the charts
    var minDate = dateDim.bottom(1)[0]["date"];
    var maxDate = dateDim.top(1)[0]["date"];

    // Charts instance
    var chart = dc.lineChart("#chart");
    var volumeChart = dc.barChart('#volume-chart');

    chart
        .renderArea(true)
        /* Make the chart as big as the bootstrap grid by not setting ".width(x)" */
        .height(350)
        .transitionDuration(1000)
        .margins({top: 30, right: 50, bottom: 25, left: 40})
        .dimension(dateDim)
        /* Grouped data to represent and label to use in the legend */
        .group(consPowByDate, "Consumed")
        /* Function to access grouped-data values in the chart */
        .valueAccessor(function (d) {
            return d.value;
        })
        /* x-axis range */
        .x(d3.time.scale().domain([minDate, maxDate]))
        /* Auto-adjust y-axis */
        .elasticY(true)
        .renderHorizontalGridLines(true)
        .legend(dc.legend().x(80).y(10).itemHeight(13).gap(5))
        /* When on, you can't visualize values, when off you can filter data */
        .brushOn(false)
        /* Add another line to the chart; pass (i) group, (ii) legend label and (iii) value accessor */
        .stack(prodPowByDate, "Produced", function(d) { return d.value; })
        /* Range chart to link the brush extent of the range with the zoom focus of the current chart. */
        .rangeChart(volumeChart)
        ;

    volumeChart
        .height(60)
        .margins({top: 0, right: 50, bottom: 20, left: 40})
        .dimension(dateDim)
        .group(consPowByDate)
        .centerBar(true)
        .gap(1)
        .x(d3.time.scale().domain([minDate, maxDate]))
        .alwaysUseRounding(true)
        ;

    // Render all graphs
    dc.renderAll();
};

我使用 chrome 开发工具进行了一些 CPU 分析,总结如下:

  • 顶部的 d3_json 解析大约需要 70 毫秒(独立于#records)
  • 有 2000 条记录:
    • make_graphs 耗时略低于 1 秒;
    • 维度聚合大约需要 11 毫秒;
    • 聚合大约需要 8 毫秒;
    • dc.lineChart 大约需要 16 毫秒;
    • dc.barChart 大约需要 8 毫秒;
    • 渲染大约需要 700 毫秒(lineChart 需要 450 毫秒);
    • 数据交互不是很流畅,但也够用了。
  • 有 8000 条记录:
    • make_graphs 大约需要 6 秒;
    • 维度聚合大约需要 80 毫秒;
    • 聚合大约需要 55 毫秒;
    • dc.lineChart 大约需要 25 毫秒;
    • dc.barChart 大约需要 15 毫秒;
    • 渲染大约需要 5.3 秒(lineChart 需要 3 秒);
    • 数据交互很糟糕,过滤需要很多时间。
  • 浏览器停止所有记录,我需要停止脚本。

阅读此thread 后,我认为这可能是日期的问题,因此我尝试修改代码以使用数字而不是日期。这是我修改的内容(我将只写下更改):

// Added before creating the crossfilter to coerce a number date
records.forEach(function(d) {
    d.date = +d.date;
});

// In both the lineChart and barChart I used a numeric range
.x(d3.scale.linear().domain([minDate, maxDate]))

不幸的是,性能方面没有任何明显的变化。 我不知道如何解决这个问题,实际上我想向仪表板添加更多组、维度和图表...


编辑: 如果你想自己测试我的代码,这里是github link

我使用python3和flask作为服务器端,所以你只需要安装flask:

pip3 安装烧瓶

运行仪表板:

python3 仪表盘.py

然后用你的浏览器去:

本地主机:5000

【问题讨论】:

  • 当然,我在帖子中添加了链接。提前感谢您的宝贵时间。

标签: javascript d3.js google-chrome-devtools dc.js crossfilter


【解决方案1】:

如果不尝试就很难判断,但可能发生的情况是有太多唯一日期,因此您最终会得到大量 DOM 对象。请记住,JavaScript 很快,但 DOM 很慢——因此处理最多 5GB 的数据应该没问题,但在浏览器阻塞之前您只能拥有几千个 DOM 对象。

这正是 crossfilter 的设计初衷!您需要做的就是聚合。您将无法看到 1000 点;他们只会迷路,因为您的图表(可能)只有几百像素宽。

因此,根据时间尺度,您可以按小时汇总:

var consPowByHour = dateDim.group(function(d) {
    return d3.time.hour(d);
}).reduceSum(function (d) { return d.consPow; });

chart.group(consPowByHour)
    .xUnits(d3.time.hours)

或类似的分钟、天、年等等。它可能比您需要的更复杂,但this example 显示了如何在时间间隔之间切换。

(我不打算安装整个堆栈来尝试这个 - 大多数示例都是 JS,所以很容易在 jsfiddle 或其他任何东西中尝试它们。如果这不能解释它,那么添加屏幕截图也可能是有帮助。)

编辑:我还注意到您的数据是整数,但您的比例是基于时间的。也许这会导致对象一直被构建。请尝试:

records.forEach(function(d) {
    d.date = new Date(+d.date); 
});

【讨论】:

  • 感谢您的建议。我尝试了您的解决方案,但我只能注意到一个非常轻微的改进。我什至尝试按天汇总,但没有任何变化。我认为您为我指明了正确的方向(js 很快,DOM 不是)但我可能应该找到一种不同的修复方法。
  • 嗯,这个尺寸数据应该永远不会有问题。我在上面添加了另一个提示。
  • 哇,这让我的性能得到了巨大的提升!现在,所有 24k 项目按小时分组,浏览器可以快速加载,并且非常具有交互性(不是超级流畅,但已经足够好了)。切片速度非常快。我仍然不确定我是否很好地解决了这个问题,我还注意到使用 previous solution 我得到了这个错误:TypeError: date.getFullYear is not a function...我认为 JSON 在 d3.json(...) 之后保持日期时间格式我的日期将是 javascript Dates 而不是数字......另外,为什么“对象总是被构建”?
  • 太棒了!我不认为 json 有日期格式,只有字符串、整数和布尔值。绝对 +d.date 将强制转换为整数(该建议可能适用于 d3.csv,其中所有内容都作为字符串读取)。我认为数据作为整数存储在交叉过滤器中,但每当它被 d3.time.* 读取时,都会导致创建一个 Date 对象。众所周知,日期对象的创建速度很慢。
  • 如果交叉过滤器现在足够快,您可以尝试dc.constants.EVENT_DELAY = 0; 来消除 dc.js 的延迟以避免陷入困境。延迟刷牙的方式需要权衡取舍 - 有关详细信息,请参阅 this issuethis other issue
猜你喜欢
  • 2014-06-24
  • 1970-01-01
  • 1970-01-01
  • 2017-06-16
  • 1970-01-01
  • 1970-01-01
  • 2013-03-18
  • 1970-01-01
  • 2017-06-05
相关资源
最近更新 更多