jma
jma

Reputation: 3779

D3 axes: aligning dates (x), displaying time (y)

I have data that is an array of (date, start, stop) tuples, where start and stop are minutes in the day [0, 1440], so 1 a.m. is represented as 60. I would like to plot rectangles, where each rectangle is horizontally aligned to the date and the height is the start and stop times. This is what I've got, which is close but has a couple errors (which errors are my question):

this is what I get, which is close to what I want

  1. I want my rectangles to be as wide as a date column, but that's not quite what I'm seeing. In particular, my call to x_scale.rangeBand() is giving me something wider than the column. (Note the horizontal overlap.) The - .5 * x_scale.rangeBand()) is meant to center the rectangles.

  2. I'd like to indicate times rather than minutes on the y-axis, but the commented calls to Ticks() and TickValues() do not do it at all.

Pointers much appreciated.

This is the code that generates this:

<!DOCTYPE html>
<html>
<head>
    <meta charset="utf-8">
    <title>D3 Page Template</title>
    <script type="text/javascript" src="d3.js"></script>
    <style>
    .d3-canvas { background-color: #F1EEFF; }
    .icongray { fill: #5B5852; }
    .axis path,
    .axis line {
        fill: none;
        stroke: black;
        shape-rendering: crispEdges;
    }
    </style>
</head>
<body>
    <script type="text/javascript">
     var data = [{"date": new Date("2015-01-01"), "start": 0, "end": 60},
         {"date": new Date("2015-01-02"), "start": 720, "end": 780},
         {"date": new Date("2015-01-03"), "start": 1300, "end": 1440}]

     var do_the_plot = function(width, height) {
         margin = {"left": 45, "right": 25, "top": 25, "bottom": 25};
         image_width = width - margin.left - margin.right;
         image_height = height - margin.top - margin.bottom;
         svg = setup_svg(width, height);
         draw_it(svg);
     }

     log_int = function(label, the_int) {
         console.log(label, the_int);
         return the_int;
     }

     minutes_in_day = 1440;

     // Set up an svg element for us.  Return it.
     setup_svg = function(width, height) {
     num_days = 3
     canvas = d3.select("body")
            .append("svg")
            .attr({"width": width,
               "height": height,
               "class": "d3-canvas",
            });
     inside = canvas.append("g")
            .attr({"transform": "translate(" + margin.left + "," + margin.top + ")"
            })
         return inside;
     }

     // Draw some rectangles representing reading sessions.
     draw_it = function(svg, reading_sessions) {
     // Assume data is a list of {day, start, stop}, day an int
     // (zero-based, days since start), start and stop minutes (int).
     x_scale = d3.scale.ordinal()
         //.domain([0, num_days])
             .domain([new Date("2015-01-01"), new Date("2015-01-03")]) // Use from data.
             .rangeRoundBands([0, image_width]);
     y_scale = d3.scale.linear()
             .domain([0, minutes_in_day])
             .range([image_height, 0]);
     h_scale = d3.scale.linear()
             .domain([0, minutes_in_day])
             .range([0, image_height]);
     console.log("about to rect");
     svg.selectAll("rect")
        .data(data)
        .enter()
        .append("rect")
        .attr({
            "width": function(d) { return log_int("w", x_scale.rangeBand()); },
            "height": function(d) { return log_int("h", h_scale(d.end - d.start)); },
            "x": function(d) { return log_int("x", x_scale(d.date) - .5 * x_scale.rangeBand()); },
            "y": function(d) { return log_int("y", y_scale(d.end)); },
            "class": "icongray"
        });
     console.log("got rects");
     var x_axis = d3.svg.axis()
            .scale(x_scale)
            .orient("bottom")
            .tickFormat(d3.time.format("%d-%m"));
     svg.append("g")
        .attr({"class": "axis",
           "transform": "translate(0, " + image_height + ")",
        })
        .call(x_axis);
     var y_axis = d3.svg.axis()
            .scale(y_scale)
         //.ticks([0, 180, 360, 540, 720, 900, 1080, 1260, 1440])
         //.tickValues(["midnight", "3:00", "6:00", "9:00", "noon", "15:00", "18:00", "21:00", "midnight"])
            .orient("left");
     svg.append("g")
        .attr({"class": "axis"})
        .call(y_axis);
     }

     do_the_plot(300, 300);
    </script>
</body>
</html>

Upvotes: 0

Views: 216

Answers (1)

Cyril Cherian
Cyril Cherian

Reputation: 32327

  1. I want my rectangles to be as wide as a date column ....The problem was because you had given domain as if it were range. domain will have all the values. Your code on x axis:

.domain([new Date("2015-01-01"), new Date("2015-01-03")]) 
// should have been this.. it will give an array of all dates in data
.domain(data.map(function (d) {return d.date;}))

  1. I'd like to indicate times rather than minutes on the y-axis .... tick values will not help tick format will help. So on your Y axis do:

      //y tick values
        var y_values = [0, 180, 360, 540, 720, 900, 1080, 1260, 1440]
        //y tick display for its corresponding values 
        var y_display = ["midnight", "3:00", "6:00", "9:00", "noon", "15:00", "18:00", "21:00", "midnight"]

        .tickValues(y_values)
        .tickFormat(function (d) {
            var index = y_values.indexOf(d)
            return y_display[index];
        })

Full corrected code here: http://jsfiddle.net/cyril123/q4zy96zu/1/

Upvotes: 2

Related Questions