D3.js:计算随范围变化的时间尺度上的柱线宽度
D3.js: calculate width of bars in time scale with changing range?
我正在构建一个 D3 条形图,在 x 轴上有一个时间刻度。x 轴的范围可能会有所不同。
如何为条形图上的条形指定正确的宽度?我见过人们使用rangeBands
作为序数尺度,但我不确定如何使用时间尺度来做到这一点。
这是我的代码:
var x = d3.time.scale().range([0, width]);
var xAxis = d3.svg.axis().scale(x).orient("bottom");
[...]
// After new data has been fetched, update the domain of x...
x.domain(d3.extent(data, function(d) { return d.date; }));
d3.select(".x.axis").call(xAxis);
// ... and draw bars for chart
var bars = svg.selectAll(".air_used")
.data(data, function(d) { return d.date; });
bars.attr("y", function(d) { return y(d.air_used); })
.attr("height", function(d) { return height - y(d.air_used); })
.attr("x", function(d) { return x(d.date); })
.attr("width", 20) // ??
我应该在最后一行放什么,代替 20?
更新:JSFiddle在这里:http://jsfiddle.net/aWJtJ/2/
没有获取宽度的函数,但您可以非常轻松地计算它:
.attr("width", width/data.length);
如果您不希望条形接触,您可能希望从中减去少量。您还需要相应地调整x
位置,例如
.attr("x", function(d) { return x(d.date) - (width/data.length)/2; })
.attr("width", width/data.length);
要使刻度正确对齐,您还需要调整 x 轴刻度的范围,因为第一个刻度将放置在第一个值处:
var x = d3.time.scale().range([width/data.length/2, width-width/data.length/2]);
在这里完成 jsfiddle。
尽管我同意 Scott 的观点,即条形图旨在与 x 轴上的有序或分类数据一起使用,但我想问题更多的是关于绘制时间轴的便利性。由于d3.time.scale
在绘制各种持续时间(小时、天、月等)的时间轴及其刻度d3.time.format.multi
的帮助下做得很好,因此将d3.time.scale
与轴组合在一起,d3.scale.ordinal
用于带宽计算可能是个好主意。
下面的片段的灵感来自D3 Google群组中关于该主题的讨论。序数刻度的单位是一天。
function prepare(data)
{
var dateParse = d3.time.format('%Y-%m-%d');
data.forEach(function(v)
{
v.date = dateParse.parse(v.date);
});
var dateValueMap = data.reduce(function(r, v)
{
r[v.date.toISOString()] = v.value;
return r;
}, {});
var dateExtent = d3.extent(data.map(function(v)
{
return v.date;
}));
// make data have each date within the extent
var fullDayRange = d3.time.day.range(
dateExtent[0],
d3.time.day.offset(dateExtent[1], 1)
);
fullDayRange.forEach(function(date)
{
if(!(date.toISOString() in dateValueMap))
{
data.push({
'date' : date,
'value' : 0
});
}
});
data = data.sort(function(a, b)
{
return a.date - b.date;
});
return data;
}
function draw(data)
{
var margin = {
'top' : 10,
'right' : 20,
'bottom' : 20,
'left' : 60
};
var size = {
'width' : 600 - margin.left - margin.right,
'height' : 180 - margin.top - margin.bottom
};
var svg = d3.select('#chart').append('svg')
.attr('width', '100%')
.attr('height', '100%')
.append('g')
.attr('transform', 'translate(' + margin.left + ',' + margin.top + ')');
var dates = data.map(function(v)
{
return v.date;
});
var x = d3.time.scale()
.range([0, size.width])
.domain(d3.extent(dates));
var y = d3.scale.linear()
.range([size.height, 0])
.domain([0, d3.max(data.map(function(v)
{
return v.value;
}))]);
var xAxis = d3.svg.axis()
.scale(x)
.orient('bottom');
var yAxis = d3.svg.axis()
.scale(y)
.orient('left');
var barWidth = d3.scale.ordinal()
.domain(dates)
.rangeRoundBands(x.range(), 0.1)
.rangeBand();
svg.append('g')
.attr('class', 'x axis')
.attr('transform', 'translate(' + barWidth / 2 + ',' + size.height + ')')
.call(xAxis);
svg.append('g')
.attr('class', 'y axis')
.call(yAxis)
.append('text')
.attr('transform', 'rotate(-90)')
.attr('y', 6)
.attr('dy', '.71em')
.style('text-anchor', 'end')
.text('Amount');
svg.selectAll('.bar')
.data(data)
.enter()
.append('rect')
.attr('class', 'bar')
.attr('x', function(d)
{
return x(d.date);
})
.attr('width', barWidth)
.attr('y', function(d)
{
return y(d.value);
})
.attr('height', function(d)
{
return size.height - y(d.value);
});
}
function getData()
{
return [
{'date': '2014-01-31', 'value': 5261.38},
{'date': '2014-02-02', 'value': 7460.23},
{'date': '2014-02-03', 'value': 8553.39},
{'date': '2014-02-04', 'value': 3897.18},
{'date': '2014-02-05', 'value': 2822.22},
{'date': '2014-02-06', 'value': 6762.49},
{'date': '2014-02-07', 'value': 8624.56},
{'date': '2014-02-08', 'value': 7870.35},
{'date': '2014-02-09', 'value': 7991.43},
{'date': '2014-02-10', 'value': 9947.14},
{'date': '2014-02-11', 'value': 6539.75},
{'date': '2014-02-12', 'value': 2487.3},
{'date': '2014-02-15', 'value': 3517.38},
{'date': '2014-02-16', 'value': 1919.08},
{'date': '2014-02-19', 'value': 1764.8},
{'date': '2014-02-20', 'value': 5607.57},
{'date': '2014-02-21', 'value': 7148.87},
{'date': '2014-02-22', 'value': 5496.45},
{'date': '2014-02-23', 'value': 296.89},
{'date': '2014-02-24', 'value': 1578.59},
{'date': '2014-02-26', 'value': 1763.16},
{'date': '2014-02-27', 'value': 8622.26},
{'date': '2014-02-28', 'value': 7298.99},
{'date': '2014-03-01', 'value': 3014.06},
{'date': '2014-03-05', 'value': 6971.12},
{'date': '2014-03-06', 'value': 2949.03},
{'date': '2014-03-07', 'value': 8512.96},
{'date': '2014-03-09', 'value': 7734.72},
{'date': '2014-03-10', 'value': 6703.21},
{'date': '2014-03-11', 'value': 9798.07},
{'date': '2014-03-12', 'value': 6541.8},
{'date': '2014-03-13', 'value': 915.44},
{'date': '2014-03-14', 'value': 9570.82},
{'date': '2014-03-16', 'value': 6459.17},
{'date': '2014-03-17', 'value': 9389.62},
{'date': '2014-03-18', 'value': 6216.9},
{'date': '2014-03-19', 'value': 4433.5},
{'date': '2014-03-20', 'value': 9017.23},
{'date': '2014-03-23', 'value': 2828.45},
{'date': '2014-03-24', 'value': 63.29},
{'date': '2014-03-25', 'value': 3855.02},
{'date': '2014-03-26', 'value': 4203.06},
{'date': '2014-03-27', 'value': 3132.32}
];
}
draw(prepare(getData()));
#chart {
width : 600px;
height : 180px;
}
.bar {
fill : steelblue;
}
.axis {
font : 10px sans-serif;
}
.axis path,
.axis line {
fill : none;
stroke : #000;
shape-rendering : crispEdges;
}
.x.axis path {
display : none;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/3.4.11/d3.min.js"></script>
<div id='chart'></div>
在使用基于时间序列的 x 轴更改条形图上的缩放级别时,我遇到了这个问题,并找到了两种解决方案。
1) 创建一个伴随的序数刻度来帮助计算宽度。(痛苦)
2)时间序列x轴是你的朋友 - 用它来计算宽度。
如果您希望您的栏始终为"月"宽,无论缩放级别如何,您都可以执行以下操作。我假设 d.date 在数据中可用。
svg.selectAll(".air_used").attr("width", function(d.date) {
var next = d3.time.month.offset(d.date, 1);
return (x(next)- x(d));
});
这很好用,因为它适用于每个缩放比例,您只需要在 on "zoom" 处理程序的末尾调用此函数,即 .on("zoom",zoomed);此时通常会调整 x 轴。
由于时间尺度是连续的,因此对于什么是"正确"条形宽度可以有很多想法。 如果数据点非常精细且分布不均匀,则可能需要使用固定宽度的细条来最大程度地减少重叠。 如果您提前了解预期的数据值,并且它们是统一的粒度,则可以执行类似@LarsKotthoff操作以均匀间隔它们。
要考虑的一件事是,你是否真的想要一个时间尺度。 条形通常用于表示分类值,而不是连续刻度上的点。 也许实际上您想要从日期范围派生域的序号刻度。 在这种情况下,您可以根据原始帖子使用范围波段。
不是说做你正在做的事情是错误的。 只是值得深思。
- 节点JS:时间机器坏了——timekeeper.travel不做时间旅行
- 如何使用自定义功能覆盖时间轴对象.js时间轴对象的_repaintMinorText
- D3 JS时间格式
- CreateJS:吐温JS时间轴补间多次更改属性而不链接
- d3.js 时间序列图表上的标签
- 如何在这个js时间脚本上添加day
- D3.js时间刻度刻度-仅限年和月-自定义时间格式
- D3.js时间格式
- 如何将瞬间JS时间插入MySQL
- D3.js时间序列无限滚动
- D3 JS-时间轴上的刻度间距被月份边界搞砸了
- Node.js时间模块时区列表
- JS时间选择器设置和谐的最小和最大时间
- Moment.js时间格式错误
- Moment js时间选择器
- 在JS时间轴上以JSON文件显示时间
- 在d3.js时间序列图上插入分钟和小时
- JS时间计算-一个日期在两个日期之间发生了多少次
- 将yyyy-mm-dd转换为JS时间戳
- D3.JS时间序列折线图,具有实时数据、平移和缩放功能