d3 如果没有数据则折线图
d3 break line graph if no data
我正在使用 dc 创建一个折线图,其中容量在 y 轴上,周在 x 轴上。对于周,范围是 1-52,但没有第 2-40 周的数据。我只有第 1 周和第 41-52 周的数据,但当没有数据时,我的折线图仍在创建一条线:
如何获取它以便在没有值时折线图会中断?所以它不会是一条连接线。这是我的代码供参考
let chart = dc.lineChart("#chart");
let ndx = crossfilter(results);
let weekDimension = ndx.dimension(function (d) {
return d.week = +d.week;
});
function reduceAdd(p, v) {
++p.count;
p.total += v.capacity;
p.average = p.total / p.count;
return p;
}
function reduceRemove(p, v) {
--p.count;
p.total -= v.capacity;
p.average = p.count ? p.total / p.count : 0;
return p;
}
function reduceInitial() {
return { count: 0, total: 0, average: 0 };
}
let capacityGroup = weekDimension.group().reduce(reduceAdd, reduceRemove, reduceInitial);
chart.width(360)
.height(200)
.margins({ top: 20, right: 20, bottom: 50, left: 30 })
.mouseZoomable(false)
.x(d3.scale.linear().domain([1, 52]))
.renderHorizontalGridLines(true)
.brushOn(false)
.dimension(weekDimension)
.valueAccessor(function (d) {
return d.value.average;
})
.group(capacityGroup);
dc.renderAll('chart');
这就是 results
的样子
{month : "1", capacity: "48"}
{month : "1", capacity: "60"}
{month : "42", capacity: "67"}
{month : "42", capacity: "60"}
{month : "43", capacity: "66"}
{month : "44", capacity: "52"}
{month : "45", capacity: "63"}
{month : "46", capacity: "67"}
{month : "47", capacity: "80"}
{month : "48", capacity: "61"}
{month : "48", capacity: "66"}
{month : "49", capacity: "54"}
{month : "50", capacity: "69"}
我已经尝试添加 .defined(d => { return d.y != null; });
和 .defined(d => !isNaN(d.value));
但没有做任何事情...任何帮助将不胜感激
正如我们在评论中讨论的那样,重要的问题是 dc.js 只会绘制它接收到的数据。它不知道数据是否丢失,所以我们需要填写 null
以在行中绘制空白。
我链接到 , where the data is timestamps. The answer there uses a d3 time interval 以生成丢失的时间戳。
但是,您的数据使用整数作为键(即使它代表周),因此我们需要稍微更改一下函数:
function fill_ints(group, fillval, stride = 1) { // 1
return {
all: function() {
var orig = group.all();
var target = d3.range(orig[0].key, orig[orig.length-1].key, stride); // 2
var result = [];
for(var oi = 0, ti = 0; oi < orig.length && ti < target.length;) {
if(orig[oi].key <= target[ti]) {
result.push(orig[oi]);
if(orig[oi++].key === target[ti])
++ti;
} else {
result.push({key: target[ti], value: fillval});
++ti;
}
} // 3
if(oi<orig.length) // 4
Array.prototype.push.apply(result, orig.slice(oi));
if(ti<target.length) // 5
result = [...result, ...target.slice(ti).map(t => ({key: t, value: fillval}))];
return result;
}
};
}
- 此函数采用一个组、要填充的值和一个跨度,即条目之间所需的间隙。
- 它读取当前数据,并使用d3.range生成所需的密钥。
- 它遍历两个数组,将任何缺失的条目添加到组数据的副本中。
- 如果原始组数据中有任何剩余条目,则会附加它。
- 如果有任何剩余目标,它会生成这些目标。
现在我们使用这个函数包装我们原来的组,创建一个 "fake group":
const filledGroup = fill_ints(capacityGroup, {average: null});
并将其传递给图表:
.group(filledGroup);
使用 LineChart.defined(), and the underlying d3.line.defined 的一个弱点是需要两个点才能形成一条线。如果您有孤立点,因为第 1 周在您的原始数据中是孤立的,那么它根本不会显示。
在 this demo fiddle 中,我通过添加第 2 周的数据避免了这个问题。
但是孤立点呢?
我很好奇如何解决“孤立点问题”,所以我尝试显示通常用于鼠标悬停效果的内置点:
chart.on('pretransition', chart => {
const all = chart.group().all();
isolated = all.reduce((p, kv, i) => {
return (kv.value.average !== null &&
(i==0 || all[i-1].value.average == null) &&
((i==all.length-1 || all[i+1].value.average == null))) ?
{...p, [kv.key]: true} : p;
}, {});
chart.g().selectAll('circle.dot')
.filter(d => isolated[d.data.key])
.style('fill-opacity', 0.75)
.on('mousemove mouseout', null)
})
This works 但它目前依赖于禁用这些点的交互性,这样它们就不会消失。
我正在使用 dc 创建一个折线图,其中容量在 y 轴上,周在 x 轴上。对于周,范围是 1-52,但没有第 2-40 周的数据。我只有第 1 周和第 41-52 周的数据,但当没有数据时,我的折线图仍在创建一条线:
如何获取它以便在没有值时折线图会中断?所以它不会是一条连接线。这是我的代码供参考
let chart = dc.lineChart("#chart");
let ndx = crossfilter(results);
let weekDimension = ndx.dimension(function (d) {
return d.week = +d.week;
});
function reduceAdd(p, v) {
++p.count;
p.total += v.capacity;
p.average = p.total / p.count;
return p;
}
function reduceRemove(p, v) {
--p.count;
p.total -= v.capacity;
p.average = p.count ? p.total / p.count : 0;
return p;
}
function reduceInitial() {
return { count: 0, total: 0, average: 0 };
}
let capacityGroup = weekDimension.group().reduce(reduceAdd, reduceRemove, reduceInitial);
chart.width(360)
.height(200)
.margins({ top: 20, right: 20, bottom: 50, left: 30 })
.mouseZoomable(false)
.x(d3.scale.linear().domain([1, 52]))
.renderHorizontalGridLines(true)
.brushOn(false)
.dimension(weekDimension)
.valueAccessor(function (d) {
return d.value.average;
})
.group(capacityGroup);
dc.renderAll('chart');
这就是 results
的样子
{month : "1", capacity: "48"}
{month : "1", capacity: "60"}
{month : "42", capacity: "67"}
{month : "42", capacity: "60"}
{month : "43", capacity: "66"}
{month : "44", capacity: "52"}
{month : "45", capacity: "63"}
{month : "46", capacity: "67"}
{month : "47", capacity: "80"}
{month : "48", capacity: "61"}
{month : "48", capacity: "66"}
{month : "49", capacity: "54"}
{month : "50", capacity: "69"}
我已经尝试添加 .defined(d => { return d.y != null; });
和 .defined(d => !isNaN(d.value));
但没有做任何事情...任何帮助将不胜感激
正如我们在评论中讨论的那样,重要的问题是 dc.js 只会绘制它接收到的数据。它不知道数据是否丢失,所以我们需要填写 null
以在行中绘制空白。
我链接到
但是,您的数据使用整数作为键(即使它代表周),因此我们需要稍微更改一下函数:
function fill_ints(group, fillval, stride = 1) { // 1
return {
all: function() {
var orig = group.all();
var target = d3.range(orig[0].key, orig[orig.length-1].key, stride); // 2
var result = [];
for(var oi = 0, ti = 0; oi < orig.length && ti < target.length;) {
if(orig[oi].key <= target[ti]) {
result.push(orig[oi]);
if(orig[oi++].key === target[ti])
++ti;
} else {
result.push({key: target[ti], value: fillval});
++ti;
}
} // 3
if(oi<orig.length) // 4
Array.prototype.push.apply(result, orig.slice(oi));
if(ti<target.length) // 5
result = [...result, ...target.slice(ti).map(t => ({key: t, value: fillval}))];
return result;
}
};
}
- 此函数采用一个组、要填充的值和一个跨度,即条目之间所需的间隙。
- 它读取当前数据,并使用d3.range生成所需的密钥。
- 它遍历两个数组,将任何缺失的条目添加到组数据的副本中。
- 如果原始组数据中有任何剩余条目,则会附加它。
- 如果有任何剩余目标,它会生成这些目标。
现在我们使用这个函数包装我们原来的组,创建一个 "fake group":
const filledGroup = fill_ints(capacityGroup, {average: null});
并将其传递给图表:
.group(filledGroup);
使用 LineChart.defined(), and the underlying d3.line.defined 的一个弱点是需要两个点才能形成一条线。如果您有孤立点,因为第 1 周在您的原始数据中是孤立的,那么它根本不会显示。
在 this demo fiddle 中,我通过添加第 2 周的数据避免了这个问题。
但是孤立点呢?
我很好奇如何解决“孤立点问题”,所以我尝试显示通常用于鼠标悬停效果的内置点:
chart.on('pretransition', chart => {
const all = chart.group().all();
isolated = all.reduce((p, kv, i) => {
return (kv.value.average !== null &&
(i==0 || all[i-1].value.average == null) &&
((i==all.length-1 || all[i+1].value.average == null))) ?
{...p, [kv.key]: true} : p;
}, {});
chart.g().selectAll('circle.dot')
.filter(d => isolated[d.data.key])
.style('fill-opacity', 0.75)
.on('mousemove mouseout', null)
})
This works 但它目前依赖于禁用这些点的交互性,这样它们就不会消失。