在 D3js 中的 x 轴上使用自定义刻度标签
Using custom tick lables on x-axis in D3js
我在 D3js 中制作了一个热图,x 轴为一年中的某天(1..365 之间的整数),y 轴为一天中的时间(1..24 之间的整数)。对于一年中的每个小时,一个点通过其颜色指示温度。
我试图找到一种方法,通过使用序数比例将月份的名称映射到一年中的正确日期,但没有成功。相反,我创建了一个带有月份名称的新 xScale
变量。问题是数据不再显示了——现在我只看到 x 轴和 y 轴。
我想这是有道理的,因为 x 轴基于日期,并且每个点的 x 坐标基于整数 (1..365)。
所以我的问题是:
- 如何将年 (
d.day
) 转换为日期,是否有效?
- 如果我将
d.day
转换为日期,这将如何影响这样的计算:var days = d3.max(dataset, function(d) { return d.day; }) - d3.min(dataset, function(d) { return d.day; });
- 这个问题有没有更好的解决办法?
下面是我的全部代码(抱歉我不知道在哪里设置限制):
d3.csv("data-gitignore/temperatures_nyc.csv", function(error, dataset) {
dataset.forEach(function(d) {
d.tOutC = +d.tOutC;
d.day = +d.day;
d.hour = +d.hour;
});
//----------------------------------
// VARIABLES
//----------------------------------
var days = d3.max(dataset, function(d) { return d.day; })
- d3.min(dataset, function(d) { return d.day; });
var hours = d3.max(dataset, function(d) { return d.hour; })
- d3.min(dataset, function(d) { return d.hour; });
var tMin = d3.min(dataset, function(d) { return d.tOutC; }),
tMax = d3.max(dataset, function(d) { return d.tOutC; });
var dotWidth = 1,
dotHeight = 3,
dotSpacing = 0.5;
var margin = {top: 0, right: 25, bottom: 40, left: 25},
width = (dotWidth * 2 + dotSpacing) * days,
height = (dotHeight * 2 + dotSpacing) * hours;//200 - margin.top - margin.bottom;
//----------------------------------
// FUNCTIONS
//----------------------------------
var xScale = d3.time.scale()
.domain([new Date(2015, 0, 1), new Date(2015, 11, 31)])
.range([0, width]);
var yScale = d3.scale.linear()
.domain([1, hours])
.range([(dotHeight * 2 + dotSpacing) * hours, dotHeight * 2 + dotSpacing]);
var colorScale = d3.scale.linear()
.domain([tMin-5, (tMax-tMin)/2, tMax-5])
.range(["#4575b4","#ffffdf", "#d73027"]);
var xAxis = d3.svg.axis()
.scale(xScale)
.orient("bottom")
.ticks(d3.time.months)
.tickFormat(d3.time.format("%b"));
// Define Y axis
var yAxis = d3.svg.axis()
.scale(yScale)
.orient("left")
.ticks(2);
// Zoom behavior
var zoom = d3.behavior.zoom()
.scaleExtent([dotWidth, dotHeight])
.x(xScale)
.on("zoom", zoomHandler);
function zoomHandler() {
var t = zoom.translate(),
tx = t[0],
ty = t[1];
tx = Math.min(tx, 0);
tx = Math.max(tx, width - (dotWidth * 2 + dotSpacing) * days * d3.event.scale);
zoom.translate([tx, ty]);
svg.select(".x.axis").call(xAxis);
svg.selectAll("ellipse")
.attr("cx", function(d) { return xScale(d.day); })
.attr("cy", function(d) { return yScale(d.hour); })
.attr("rx", function(d) { return (dotWidth * d3.event.scale); });
}
// SVG canvas
var svg = d3.select("#chart")
.append("svg")
.attr("width", width + margin.left + margin.right)
.attr("height", height + margin.top + margin.bottom)
.call(zoom)
.append("g")
.attr("transform", "translate(" + margin.left + "," + margin.top + ")");
// Clip path
svg.append("clipPath")
.attr("id", "clip")
.append("rect")
.attr("width", width)
.attr("height", height);
//----------------------------------
// DRAW ELEMENTS ON SVG CANVAS
//----------------------------------
// Heatmap dots
svg.append("g")
.attr("clip-path", "url(#clip)")
.selectAll("ellipse")
.data(dataset)
.enter()
.append("ellipse")
.attr("cx", function(d) { return xScale(d.day); })
.attr("cy", function(d) { return yScale(d.hour); })
.attr("rx", dotWidth)
.attr("ry", dotHeight)
.attr("fill", function(d) { return colorScale(d.tOutC); });
//Create X axis
svg.append("g")
.attr("class", "x axis")
.attr("transform", "translate(0," + yScale(0) + ")")
.call(xAxis)
//Create Y axis
svg.append("g")
.attr("class", "y axis")
.call(yAxis);
});
首先,将一年的第一天设置为参考日期,date0
。如果年份是 2014 年,那么它看起来像这样:
var date0 = Date.UTC(2014, 0);// Midnight on January 1st, 2014
date0
是自1970年以来的毫秒数。要验证这一点,你可以说
console.log(new Date(date0).toUTCString);
//> "Wed, 01 Jan 2014 00:00:00 GMT"
(注意这里使用的是 UTC。最好在任何地方都使用 UTC 日期,因为这样可以确保用户无论在哪个时区都能看到正确的日期)。
鉴于此,您可以像这样轻松地为一年中的任何时间+日期创建日期对象:
// constants
var msPerHour = 3600000,
msPerDay = 24 * 3600000;
var after3days8hours = new Date(date0 + 3 * msPerDay + 8 * msPerHour);
console.log(after3days8hours.toUTCString())
//> "Sat, 04 Jan 2014 08:00:00 GMT"
现在您可以计算数据集中每个 CSV 行的日期:
dataset.forEach(function(d) {
d.tOutC = +d.tOutC;
d.day = +d.day;
d.hour = +d.hour;
d.date = new Date(date0 + d.day * msPerDay + d.hour * msPerDay)
});
这回答了问题 #1。
关于问题 #2:完成所有这些将使您能够根据需要计算 min/max:
d3.max(dataset, function(d) { return d.date; })
和
d3.min(dataset, function(d) { return d.date; });
即使 d.date
是一个 Date
对象,JavaScript 也应该为您将其转换回毫秒数。此外,即使您选择跳过所有这些 Date
对象的实例化,以上所有内容都可以正常工作。如:
// without new Date
d.date = date0 + d.day * msPerDay + d.hour * msPerDay;
但是 d3.time.scale
需要传递日期而不是数字,因此例如 xScale(d.date)
需要是 xScale(new Date(d.date))
如果 d.date
是一个数字。如果您曾经看到 d3 出现控制台错误,请怀疑您可能忘记了这一点并输入了数字而不是日期。
对于 cx
计算,对于任何给定的时间,您都需要找到当天的午夜。您可以像这样使用 d.day
:
.attr("cx", function(d) {
return xScale(new Date(date0 + d.day * msPerDay));
})
或者,您可以像这样从 d.date
算出:
.attr("cx", function(d) {
var msAtMidnight = Math.floor(d.date / msPerDay) * msPerDay
return xScale(new Date(msAtMidnight));
})
要将 d.date
用于 cy
,您可以说:
var msSinceMidnight = d.date % msPerDay
最后,因为您将使用 UTC 日期,所以您需要 UTC 时间标度而不是现有的常规时间标度。 D3 有适合你的体重秤:
var xScale = d3.time.scale.utc()
我认为 yScale
域应该是 [0, 23]
。
关于问题 #3:这可能是最好的解决方案,特别是因为 x 轴的刻度为月份(一月、二月等)可能是最合适的。为了获得这些滴答声,您有点必须使用时间尺度,因为每个月的天数是可变的,并且在没有 "time-aware" 尺度的情况下进行计算需要大量工作。
我在 D3js 中制作了一个热图,x 轴为一年中的某天(1..365 之间的整数),y 轴为一天中的时间(1..24 之间的整数)。对于一年中的每个小时,一个点通过其颜色指示温度。
我试图找到一种方法,通过使用序数比例将月份的名称映射到一年中的正确日期,但没有成功。相反,我创建了一个带有月份名称的新 xScale
变量。问题是数据不再显示了——现在我只看到 x 轴和 y 轴。
我想这是有道理的,因为 x 轴基于日期,并且每个点的 x 坐标基于整数 (1..365)。
所以我的问题是:
- 如何将年 (
d.day
) 转换为日期,是否有效? - 如果我将
d.day
转换为日期,这将如何影响这样的计算:var days = d3.max(dataset, function(d) { return d.day; }) - d3.min(dataset, function(d) { return d.day; });
- 这个问题有没有更好的解决办法?
下面是我的全部代码(抱歉我不知道在哪里设置限制):
d3.csv("data-gitignore/temperatures_nyc.csv", function(error, dataset) {
dataset.forEach(function(d) {
d.tOutC = +d.tOutC;
d.day = +d.day;
d.hour = +d.hour;
});
//----------------------------------
// VARIABLES
//----------------------------------
var days = d3.max(dataset, function(d) { return d.day; })
- d3.min(dataset, function(d) { return d.day; });
var hours = d3.max(dataset, function(d) { return d.hour; })
- d3.min(dataset, function(d) { return d.hour; });
var tMin = d3.min(dataset, function(d) { return d.tOutC; }),
tMax = d3.max(dataset, function(d) { return d.tOutC; });
var dotWidth = 1,
dotHeight = 3,
dotSpacing = 0.5;
var margin = {top: 0, right: 25, bottom: 40, left: 25},
width = (dotWidth * 2 + dotSpacing) * days,
height = (dotHeight * 2 + dotSpacing) * hours;//200 - margin.top - margin.bottom;
//----------------------------------
// FUNCTIONS
//----------------------------------
var xScale = d3.time.scale()
.domain([new Date(2015, 0, 1), new Date(2015, 11, 31)])
.range([0, width]);
var yScale = d3.scale.linear()
.domain([1, hours])
.range([(dotHeight * 2 + dotSpacing) * hours, dotHeight * 2 + dotSpacing]);
var colorScale = d3.scale.linear()
.domain([tMin-5, (tMax-tMin)/2, tMax-5])
.range(["#4575b4","#ffffdf", "#d73027"]);
var xAxis = d3.svg.axis()
.scale(xScale)
.orient("bottom")
.ticks(d3.time.months)
.tickFormat(d3.time.format("%b"));
// Define Y axis
var yAxis = d3.svg.axis()
.scale(yScale)
.orient("left")
.ticks(2);
// Zoom behavior
var zoom = d3.behavior.zoom()
.scaleExtent([dotWidth, dotHeight])
.x(xScale)
.on("zoom", zoomHandler);
function zoomHandler() {
var t = zoom.translate(),
tx = t[0],
ty = t[1];
tx = Math.min(tx, 0);
tx = Math.max(tx, width - (dotWidth * 2 + dotSpacing) * days * d3.event.scale);
zoom.translate([tx, ty]);
svg.select(".x.axis").call(xAxis);
svg.selectAll("ellipse")
.attr("cx", function(d) { return xScale(d.day); })
.attr("cy", function(d) { return yScale(d.hour); })
.attr("rx", function(d) { return (dotWidth * d3.event.scale); });
}
// SVG canvas
var svg = d3.select("#chart")
.append("svg")
.attr("width", width + margin.left + margin.right)
.attr("height", height + margin.top + margin.bottom)
.call(zoom)
.append("g")
.attr("transform", "translate(" + margin.left + "," + margin.top + ")");
// Clip path
svg.append("clipPath")
.attr("id", "clip")
.append("rect")
.attr("width", width)
.attr("height", height);
//----------------------------------
// DRAW ELEMENTS ON SVG CANVAS
//----------------------------------
// Heatmap dots
svg.append("g")
.attr("clip-path", "url(#clip)")
.selectAll("ellipse")
.data(dataset)
.enter()
.append("ellipse")
.attr("cx", function(d) { return xScale(d.day); })
.attr("cy", function(d) { return yScale(d.hour); })
.attr("rx", dotWidth)
.attr("ry", dotHeight)
.attr("fill", function(d) { return colorScale(d.tOutC); });
//Create X axis
svg.append("g")
.attr("class", "x axis")
.attr("transform", "translate(0," + yScale(0) + ")")
.call(xAxis)
//Create Y axis
svg.append("g")
.attr("class", "y axis")
.call(yAxis);
});
首先,将一年的第一天设置为参考日期,date0
。如果年份是 2014 年,那么它看起来像这样:
var date0 = Date.UTC(2014, 0);// Midnight on January 1st, 2014
date0
是自1970年以来的毫秒数。要验证这一点,你可以说
console.log(new Date(date0).toUTCString);
//> "Wed, 01 Jan 2014 00:00:00 GMT"
(注意这里使用的是 UTC。最好在任何地方都使用 UTC 日期,因为这样可以确保用户无论在哪个时区都能看到正确的日期)。
鉴于此,您可以像这样轻松地为一年中的任何时间+日期创建日期对象:
// constants
var msPerHour = 3600000,
msPerDay = 24 * 3600000;
var after3days8hours = new Date(date0 + 3 * msPerDay + 8 * msPerHour);
console.log(after3days8hours.toUTCString())
//> "Sat, 04 Jan 2014 08:00:00 GMT"
现在您可以计算数据集中每个 CSV 行的日期:
dataset.forEach(function(d) {
d.tOutC = +d.tOutC;
d.day = +d.day;
d.hour = +d.hour;
d.date = new Date(date0 + d.day * msPerDay + d.hour * msPerDay)
});
这回答了问题 #1。
关于问题 #2:完成所有这些将使您能够根据需要计算 min/max:
d3.max(dataset, function(d) { return d.date; })
和
d3.min(dataset, function(d) { return d.date; });
即使 d.date
是一个 Date
对象,JavaScript 也应该为您将其转换回毫秒数。此外,即使您选择跳过所有这些 Date
对象的实例化,以上所有内容都可以正常工作。如:
// without new Date
d.date = date0 + d.day * msPerDay + d.hour * msPerDay;
但是 d3.time.scale
需要传递日期而不是数字,因此例如 xScale(d.date)
需要是 xScale(new Date(d.date))
如果 d.date
是一个数字。如果您曾经看到 d3 出现控制台错误,请怀疑您可能忘记了这一点并输入了数字而不是日期。
对于 cx
计算,对于任何给定的时间,您都需要找到当天的午夜。您可以像这样使用 d.day
:
.attr("cx", function(d) {
return xScale(new Date(date0 + d.day * msPerDay));
})
或者,您可以像这样从 d.date
算出:
.attr("cx", function(d) {
var msAtMidnight = Math.floor(d.date / msPerDay) * msPerDay
return xScale(new Date(msAtMidnight));
})
要将 d.date
用于 cy
,您可以说:
var msSinceMidnight = d.date % msPerDay
最后,因为您将使用 UTC 日期,所以您需要 UTC 时间标度而不是现有的常规时间标度。 D3 有适合你的体重秤:
var xScale = d3.time.scale.utc()
我认为 yScale
域应该是 [0, 23]
。
关于问题 #3:这可能是最好的解决方案,特别是因为 x 轴的刻度为月份(一月、二月等)可能是最合适的。为了获得这些滴答声,您有点必须使用时间尺度,因为每个月的天数是可变的,并且在没有 "time-aware" 尺度的情况下进行计算需要大量工作。