dc.js 超过 8k 个项目的折线图性能问题
dc.js lineChart performance issue with 8k+ items
这是我关于 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 毫秒;
- groups 聚合大约需要 8 毫秒;
- dc.lineChart 大约需要 16 毫秒;
- dc.barChart 大约需要 8 毫秒;
- 渲染 大约需要 700 毫秒(折线图需要 450 毫秒);
- 数据交互不是特别顺畅,但也足够了
- 有 8000 条记录:
- make_graphs 大约需要 6 秒;
- 维度 聚合大约需要 80 毫秒;
- groups 聚合大约需要 55 毫秒;
- dc.lineChart 大约需要 25 毫秒;
- dc.barChart 大约需要 15 毫秒;
- 渲染 大约需要 5.3 秒(折线图需要 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 install flask
运行 仪表板:
python3 dashboard.py
然后使用浏览器访问:
localhost:5000
如果不尝试很难判断,但可能发生的情况是唯一日期太多,因此您最终会得到大量 DOM 个对象。请记住,JavaScript 很快,但 DOM 很慢 - 因此处理多达 0.5 GB 的数据应该没问题,但在 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 或其他任何地方尝试它们。如果这不能解释它,那么添加屏幕截图可能也很有帮助。)
编辑:我还注意到您的数据是整数,但您的比例是 time-based。也许这会导致对象一直被构建。请尝试:
records.forEach(function(d) {
d.date = new Date(+d.date);
});
这是我关于 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 毫秒;
- groups 聚合大约需要 8 毫秒;
- dc.lineChart 大约需要 16 毫秒;
- dc.barChart 大约需要 8 毫秒;
- 渲染 大约需要 700 毫秒(折线图需要 450 毫秒);
- 数据交互不是特别顺畅,但也足够了
- 有 8000 条记录:
- make_graphs 大约需要 6 秒;
- 维度 聚合大约需要 80 毫秒;
- groups 聚合大约需要 55 毫秒;
- dc.lineChart 大约需要 25 毫秒;
- dc.barChart 大约需要 15 毫秒;
- 渲染 大约需要 5.3 秒(折线图需要 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 install flask
运行 仪表板:
python3 dashboard.py
然后使用浏览器访问:
localhost:5000
如果不尝试很难判断,但可能发生的情况是唯一日期太多,因此您最终会得到大量 DOM 个对象。请记住,JavaScript 很快,但 DOM 很慢 - 因此处理多达 0.5 GB 的数据应该没问题,但在 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 或其他任何地方尝试它们。如果这不能解释它,那么添加屏幕截图可能也很有帮助。)
编辑:我还注意到您的数据是整数,但您的比例是 time-based。也许这会导致对象一直被构建。请尝试:
records.forEach(function(d) {
d.date = new Date(+d.date);
});