Peterson Peterson - 29 days ago 14
Javascript Question

Simple line chart joining top of histograms

In the following example I mapped histograms on letter vs frequency. Now, I want a line chart also for the same data without making much change. This means just a red line joining top of histograms. Can someone help me out?

<html>

<head>

<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/1.12.4/jquery.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/4.3.0/d3.min.js"></script>

</head>

<body>

<svg width="960" height="500"></svg>

<script>

//Our basic data
var data = [
{frequency:0.08, letter:"A"},
{frequency:0.11,letter:"B"},
{frequency:0.13,letter:"C"}
];

var svg = d3.select("svg");
var margin = {top: 40, bottom: 40, left: 40, right: 40};
var width = svg.attr("width") - margin.left - margin.right;
var height = svg.attr("height") - margin.top - margin.bottom;

var x = d3.scaleBand().rangeRound([0, width]).padding(0.6);
var y = d3.scaleLinear().rangeRound([height, 0]);

//defining our main g in svg
var g = svg.append("g").attr("transform", "translate(" + margin.left + "," + margin.top + ")");

//Looping for data bars
data.forEach(function(){

x.domain(data.map(function(d) { return d.letter; }));
y.domain([0, d3.max(data, function(d) { return d.frequency; })]);

g
.append("g")
.attr("transform", "translate(0," + height + ")")
.call(d3.axisBottom(x));

g
.append("g")
.call(d3.axisLeft(y).ticks(10, "%"));

g
.selectAll(".bar")
.data(data)
.enter()
.append("rect")
.attr("x", function(d) { return x(d.letter); })
.attr("y", function(d) { return y(d.frequency); })
.attr("width", x.bandwidth())
.attr("height", function(d) { return height - y(d.frequency); });

});

</script>

</body>

</html>


HISTOGRAM:

enter image description here

Answer

First, get rid of that data.forEach: why do you want to paint everything 3 times?

After that, define your line generator:

var line = d3.line()
    .x(function(d){ return x(d.letter) + x.bandwidth()/2})
    .y(function(d){ return y(d.frequency)})
    .curve(d3.curveCardinal);;

Here, x.bandwidth()/2 will put the line in the middle of the top of each bar. I'm using d3.curveCardinal, but you have other options for the curve.

Then, append the line:

g.append("path")
    .datum(data)
    .attr("d", line);

Here is the demo:

<head> 

<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/1.12.4/jquery.min.js"></script> 
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/4.3.0/d3.min.js"></script> 

</head>

<body> 

<svg width="960" height="500"></svg>

<script>

//Our basic data 
var data = [
    {frequency:0.08, letter:"A"},
    {frequency:0.11,letter:"B"},
    {frequency:0.13,letter:"C"}
];

var svg = d3.select("svg");
var margin = {top: 40, bottom: 40, left: 40, right: 40};
var width = svg.attr("width") - margin.left - margin.right;
var height = svg.attr("height") - margin.top - margin.bottom;

var x = d3.scaleBand().rangeRound([0, width]).padding(0.6);
var y = d3.scaleLinear().rangeRound([height, 0]);

//defining our main g in svg 
var g = svg.append("g").attr("transform", "translate(" + margin.left + "," + margin.top + ")");

//Looping for data bars 


    x.domain(data.map(function(d) { return d.letter; }));
    y.domain([0, d3.max(data, function(d) { return d.frequency; })]);
		
		var line = d3.line()
			.x(function(d){ return x(d.letter) + x.bandwidth()/2})
			.y(function(d){ return y(d.frequency)})
        .curve(d3.curveCardinal);

    g
        .append("g")
        .attr("transform", "translate(0," + height + ")")
        .call(d3.axisBottom(x));

    g
        .append("g")
        .call(d3.axisLeft(y).ticks(10, "%"));

    g
        .selectAll(".bar")
        .data(data)
        .enter()
        .append("rect")
        .attr("x", function(d) { return x(d.letter); })
        .attr("y", function(d) { return y(d.frequency); })
        .attr("width", x.bandwidth())
        .attr("height", function(d) { return height - y(d.frequency); });
				
				g.append("path")
				.datum(data)
				.attr("d", line)
				.attr("stroke", "red")
				.attr("fill", "none");


</script>