Time series done right with NVD3

Learn how to: 1) set the x scale to a time scale so default ticks are round values, 2) format the tooltip, 3) put tick marks on the x axis for usability, 4) use bars instead of line so any gaps are visible, 5) use multi-format so that time labels are short and fit nicely.

by David McClelland

HTML

<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/3.5.2/d3.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/nvd3/1.7.0/nv.d3.min.js"></script>
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/nvd3/1.7.0/nv.d3.min.css">
<div>Try resizing the panel to see the various types of time labels.</div>
<br>
<div id="chart">
  <svg></svg>
</div>

CSS

text {
            font: 12px sans-serif;
        }
        svg {
            display: block;
        }
        html, body, #chart1, svg {
            margin: 0px;
            padding: 0px;
            height: 300px;
            width: 100%;
        }

        .nvd3 line.nv-guideline { // Hide the guideline because it is ugly. Ideally, we would make the guideline as wide as the bars and semi-transparent, instead of hiding it.
            display: none;
            stroke-width: 0.0;
        }

        .nvd3 .nv-bars rect { // fix the hover guideline to be snappy
          transition: fill-opacity 0ms linear;
          -moz-transition: fill-opacity 0ms linear;
          -webkit-transition: fill-opacity 0ms linear;
        }

        .nvd3 .nv-x .nv-axis .x-axis-tick-mark {
            stroke: #000; // black tick marks
        }

        .nvd3 .nv-y .nv-axis .tick.zero line {
            stroke: #000; // black line for the x axis
        }

JavaScript

var data;
    data = [
        {
            values : [
            ],
        },
    ];

    var i, x;
    var prevVal = 3000;
    var tickCount = 2000;
    for (i = 0; i < tickCount; i++) {
        x = 1425096000 + i * 10 * 60; // data points every ten minutes
        if (Math.random() < 0.8) { // add some gaps
            prevVal += (Math.random() - 0.5) * 500;
            if (prevVal <= 0) {
                prevVal = Math.random() * 100;
            }
            data[0].values.push({x: x * 1000, y: prevVal});
        }
    }

    var chart;

    nv.addGraph(function() {
        chart = nv.models.historicalBarChart();
        chart
            // .x(function(d) { return d[0]; })
            // .y(function(d) { return d[1]; })
            .xScale(d3.time.scale()) // use a time scale instead of plain numbers in order to get nice round default values in the axis
            .color(['#68c'])
            .useInteractiveGuideline(true) // check out the css that turns the guideline into this nice thing
            // .tooltips(true)
            // .tooltipContent(function (key, x, y, graph) {
            //     var content = '<h3 style="background-color: ' + y.color + '">' + d3.time.format('%b %-d, %Y %I:%M%p')(new Date(graph.point.x)) + '</h3>';
            //     content += '<p>' +  y + '</p>';
            //     return content;
            // })
            .margin({"left": 80, "right": 50, "top": 20, "bottom": 30,})
            .noData("There is no data to display.")
            .duration(0)
            ;

        var tickMultiFormat = d3.time.format.multi([
            ["%-I:%M%p", function(d) { return d.getMinutes(); }], // not the beginning of the hour
            ["%-I%p", function(d) { return d.getHours(); }], // not midnight
            ["%b %-d", function(d) { return d.getDate() != 1; }], // not the first of the month
            ["%b %-d", function(d) { return d.getMonth(); }], // not Jan 1st
            ["%Y", function() { return true; }]
        ]);
  ...