在D3js中使用x轴上的自定义刻度标记

时间:2015-04-23 22:22:49

标签: javascript d3.js labels

我在D3js中制作了一张热图,其中x轴表示一年中的一天(整数在1 ... 365之间),y轴表示一天中的时间(整数在1..24之间)。对于一年中的每个小时,点表示温度的颜色。

enter image description here

我试图找到一种方法,通过使用序数量表但没有运气将月份的名称映射到一年中的正确日期。相反,我创建了一个带有月份名称的新xScale变量。问题是数据不再显示 - 现在我只看到x轴和y轴。

enter image description here

我认为这是有道理的,因为x轴基于日期,每个点的x坐标基于整数(1..365)。

所以我的问题是:

  1. 如何将星期日(d.day)转换为日期,是否有效?
  2. 如果我将d.day转换为日期,会如何影响这样的计算:var days = d3.max(dataset, function(d) { return d.day; }) - d3.min(dataset, function(d) { return d.day; });
  3. 这个问题有更好的解决方案吗?
  4. 以下是我的整个代码(抱歉,我不知道在哪里设置限制):

    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);
    
    });
    

1 个答案:

答案 0 :(得分:1)

首先,将一年的第一天确定为参考日期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:尽一切努力使您能够根据需要计算最小值/最大值:

d3.max(dataset, function(d) { return d.date; })

d3.min(dataset, function(d) { return d.date; });

即使d.dateDate对象,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轴的刻度为月(1月,2月等)可能是最合适的。为了获得这些标记,你必须使用时间尺度,因为每月的天数是可变的,并且考虑到没有“时间感知”尺度的那些是很多工作。