Swaps
Swaps

Reputation: 1548

D3 JS Bar Graph with scaleTime

I am trying to draw a bar graph using d3.js with d3.scaleTime() scale. I tried searching but didn't get any better sample in d3 v4. So decided to build one but I think there's some problem with the domain I am specifying for monthScale which is giving some strange plotting for bars. Any kind of help would be appreciated. Also I am trying to group x-Axis by months at this point(it can be dynamic depending on size of data; group by weeks/days/years).

var temperatures = [{
    temp: 80,
    onDate: '2016-01-01T16:00:49Z'
  },
  {
    temp: 38,
    onDate: '2016-02-01T16:00:49Z'
  },
  {
    temp: 47,
    onDate: '2016-05-01T16:00:49Z'
  },
  {
    temp: 59,
    onDate: '2017-01-01T16:00:49Z'
  },
];

var months = temperatures.map(function(t) {
  return new Date(t.onDate);
});
var maxDate = Math.max(...months);
var minDate = Math.min(...months);

var margin = {
  top: 5,
  right: 5,
  bottom: 50,
  left: 50
};

var fullWidth = 700;
var fullHeight = 200;

var width = fullWidth - margin.left - margin.right;
var height = fullHeight - margin.bottom - margin.top;

var svg = d3.select('.chart')
  .append('svg')
  .attr('width', fullWidth)
  .attr('height', fullHeight)
  .append('g')
  .attr('transform', 'translate(' + margin.left + ',' + margin.top + ')');

// Vertical bars
var monthScale = d3.scaleTime()
  .domain([minDate, maxDate])
  .rangeRound([0, width]);

var tempScale = d3.scaleLinear()
  .domain([0, d3.max(temperatures, function(d) {
    return d.temp;
  })])
  .range([height, 0])
  .nice();

var barHolder = svg.append('g')
  .classed('bar-holder', true);

var bars = barHolder.selectAll('rect.bar')
  .data(temperatures)
  .enter()
  .append('rect')
  .classed('bar', true)
  .attr('x', function(d, i) {
    ndt = new Date(d.onDate)
    return monthScale(ndt.getTime());
  })
  .attr('width', width / months.length)
  .attr('y', function(d) {
    return tempScale(d.temp);
  })
  .attr('height', function(d) {
    return height - tempScale(d.temp);
  });
// Vertical bars end

// scales
var xAxis = d3.axisBottom(monthScale)
  .scale(monthScale)
  .ticks(d3.timeWeeks(minDate, maxDate).length)
  .tickSizeOuter(1);
var yAxis = d3.axisLeft(tempScale)
  .tickSizeOuter(1);

svg.append('g')
  .classed('x axis', true)
  .attr('transform', 'translate(0,' + height + ')')
  .call(xAxis);

var yAxisElement = svg.append('g')
  .classed('y axis', true)
  .call(yAxis);
yAxisElement.append('text')
  .attr('transform', 'rotate(-90) translate(-' + height / 2 + ', 0)')
  .style('text-ancher', 'middle')
  .attr('dy', '-2.5em')
  .text('Farenheit');
.chart {
  background-color: white;
}

.chart rect {
  stroke: white;
  fill: steelblue;
}

.axis {
  shape-rendering: crispEdges;
}

.x.axis line {
  stroke: black;
}

.y.axis line {
  stroke: black;
}

.axis text {
  font-size: 12px;
}
<script src="https://d3js.org/d3.v4.min.js"></script>
<div class="col-lg-12 chart">
</div>

Upvotes: 2

Views: 3736

Answers (1)

Robert Andersson
Robert Andersson

Reputation: 1521

I just want to preface my answer with that you shouldn't normaly use d3.scaleTime for bar charts, but if you absolutely must for one reason or another, here's how i'd do it.

d3 has a built in date parser for your type of date called d3.utcParse("%Y-%m-%dT%H:%M:%SZ");

This simplifies your code by a lot, we use this parser in a forEach loop like this

d.onDate = parseDate(d.onDate);

The following code allows us to padd our xAxis so that we can see more of our bar chart and adjust the rectangles accordingly

var minDate = d3.min(temperatures, function(d) { 
      return d.onDate.getTime(); 
    }),
    maxDate = d3.max(temperatures, function(d) { 
      return d.onDate.getTime(); 
    }),
    padding = (maxDate - minDate) * .1;

Here's all your code with these changes

var parseDate = d3.utcParse("%Y-%m-%dT%H:%M:%SZ");

var temperatures = [{
    temp: 80,
    onDate: '2016-01-01T16:00:49Z'
  },
  {
    temp: 38,
    onDate: '2016-02-01T16:00:49Z'
  },
  {
    temp: 47,
    onDate: '2016-05-01T16:00:49Z'
  },
  {
    temp: 59,
    onDate: '2017-01-01T16:00:49Z'
  },
];

temperatures.forEach(function(d) {
  d.onDate = parseDate(d.onDate);
  console.log(d.onDate)
  return d;
})

var margin = {top: 25, right: 35, bottom: 25, left: 35, padd: 15};

var fullWidth = 600;
var fullHeight = 250;

var width = fullWidth - margin.left - margin.right;
var height = fullHeight - margin.bottom - margin.top;

var svg = d3.select('.chart').append('svg')
    .attr('width', fullWidth)
    .attr('height', fullHeight)
  .append('g')
    .attr('transform', 'translate(' + margin.left + ',' + margin.top + ')');

var minDate = d3.min(temperatures, function(d) { 
      return d.onDate.getTime(); 
    }),
    maxDate = d3.max(temperatures, function(d) { 
      return d.onDate.getTime(); 
    }),
    padding = (maxDate - minDate) * .1;

var x = d3.scaleTime()
      .rangeRound([0, width])
      .domain([minDate - padding, maxDate + padding]);

var y = d3.scaleLinear()
      .range([height, 0])
      .domain([0, d3.max(temperatures, function(d) {
        return d.temp;
      })]);

var xAxis = d3.axisBottom(x)
      .tickSizeOuter(0)
      .tickFormat(d3.timeFormat("%b")),
    yAxis = d3.axisLeft(y);

svg.append("g")
    .attr("class","axis axis--x")
    .attr("transform", "translate(0," + height + ")")
    .call(xAxis);

svg.append("g")
    .attr("class", "axis axis--y")
    .call(yAxis)
  .append("text")
    .attr("transform", "rotate(-90)")
    .attr("y", 6)
    .attr("dy", "0.71em")
    .attr("fill", "#000")
    .text("Farenheit");

var bars = svg.selectAll('.bar')
    .data(temperatures)
    .enter()
  .append('rect')
    .attr("class", "bar")
    .attr('width', (width - margin.padd) / (temperatures.length * temperatures.length))
    .attr('height', function(d) {
        return height - y(d.temp);
    })
    .attr('x', function(d) {
        return x(d.onDate) - margin.padd
    })
    .attr('y', function(d) {
        return y(d.temp);
    })
.chart {
  background-color: white;
}

.chart rect {
  stroke: white;
  fill: steelblue;
}

.axis {
  shape-rendering: crispEdges;
}

.x.axis line {
  stroke: black;
}

.y.axis line {
  stroke: black;
}

.axis text {
  font-size: 12px;
}
<script src="https://d3js.org/d3.v4.min.js"></script>
<div class="col-lg-12 chart">
</div>

Upvotes: 3

Related Questions