The following snippet is basically this example https://bl.ocks.org/mbostock/4248145 but with custom data points. No matter how I scale or modify my points array, the hexagons are always at the upper left corner, though it seems that the distribution is displayed correctly.
How can I fix this?
var svg = d3.select("svg"),
margin = {top: 20, right: 20, bottom: 30, left: 40},
width = +svg.attr("width") - margin.left - margin.right,
height = +svg.attr("height") - margin.top - margin.bottom,
g = svg.append("g").attr("transform", "translate(" + margin.left + "," + margin.top + ")");
var points = [[1,1]]
var color = d3.scaleSequential(d3.interpolateLab("white", "#5B85AA"))
.domain([0, 3]);
var hexbin = d3.hexbin()
.radius(20)
.size([0, 3]);
var x = d3.scaleLinear()
.domain([1, 4])
.range([0, width]);
var y = d3.scaleLinear()
.domain([1, 4])
.range([height, 0]);
g.append("clipPath")
.attr("id", "clip")
.append("rect")
.attr("width", width)
.attr("height", height);
g.append("g")
.attr("class", "hexagon")
.attr("clip-path", "url(#clip)")
.selectAll("path")
.data(hexbin(points))
.enter().append("path")
.attr("d", hexbin.hexagon())
.attr("transform", function(d) { return "translate(" + 0 + "," + height + ")"; })
.transition()
.duration(1000)
.delay(function (d, i) {
return i * 10;
})
.attr("transform", function(d) { return "translate(" + d.x + "," + d.y + ")"; })
.attr("fill", function(d) { return color(d.length); });
<script src="https://d3js.org/d3.v4.min.js"></script>
<script src="https://d3js.org/d3-hexbin.v0.2.min.js"></script>
<svg width="960" height="500"></svg>
That's the expected result, since your data is just:
[1, 1]
Which is a single data point next to the origin. For instance, using the same code but creating 1000 random data points from 0 to the width...
var points = d3.range(1000).map(d=>([Math.random()*width, Math.random()*width]));
... will have a different result:
var svg = d3.select("svg"),
margin = {top: 20, right: 20, bottom: 30, left: 40},
width = +svg.attr("width") - margin.left - margin.right,
height = +svg.attr("height") - margin.top - margin.bottom,
g = svg.append("g").attr("transform", "translate(" + margin.left + "," + margin.top + ")");
var points = d3.range(1000).map(d=>([Math.random()*width, Math.random()*width]));
var color = d3.scaleSequential(d3.interpolateLab("white", "#5B85AA"))
.domain([0, 3]);
var hexbin = d3.hexbin()
.radius(20)
.size([0, 3]);
var x = d3.scaleLinear()
.domain([1, 4])
.range([0, width]);
var y = d3.scaleLinear()
.domain([1, 4])
.range([height, 0]);
g.append("clipPath")
.attr("id", "clip")
.append("rect")
.attr("width", width)
.attr("height", height);
g.append("g")
.attr("class", "hexagon")
.attr("clip-path", "url(#clip)")
.selectAll("path")
.data(hexbin(points))
.enter().append("path")
.attr("d", hexbin.hexagon())
.attr("transform", function(d) { return "translate(" + 0 + "," + height + ")"; })
.transition()
.duration(1000)
.delay(function (d, i) {
return i * 10;
})
.attr("transform", function(d) { return "translate(" + d.x + "," + d.y + ")"; })
.attr("fill", function(d) { return color(d.length); });
<script src="https://d3js.org/d3.v4.min.js"></script>
<script src="https://d3js.org/d3-hexbin.v0.2.min.js"></script>
<svg width="960" height="500"></svg>
Besides that, what you said:
No matter how I scale or modify my points array, the hexagons are always at the upper left corner.
Is not accurate. For instance, this is the same code, but using [[100,100]]. You can see the hexagon further down and to the right:
var svg = d3.select("svg"),
margin = {top: 20, right: 20, bottom: 30, left: 40},
width = +svg.attr("width") - margin.left - margin.right,
height = +svg.attr("height") - margin.top - margin.bottom,
g = svg.append("g").attr("transform", "translate(" + margin.left + "," + margin.top + ")");
var points = [[100,100]];
var color = d3.scaleSequential(d3.interpolateLab("white", "#5B85AA"))
.domain([0, 3]);
var hexbin = d3.hexbin()
.radius(20)
.size([0, 3]);
var x = d3.scaleLinear()
.domain([1, 4])
.range([0, width]);
var y = d3.scaleLinear()
.domain([1, 4])
.range([height, 0]);
g.append("clipPath")
.attr("id", "clip")
.append("rect")
.attr("width", width)
.attr("height", height);
g.append("g")
.attr("class", "hexagon")
.attr("clip-path", "url(#clip)")
.selectAll("path")
.data(hexbin(points))
.enter().append("path")
.attr("d", hexbin.hexagon())
.attr("transform", function(d) { return "translate(" + 0 + "," + height + ")"; })
.transition()
.duration(1000)
.delay(function (d, i) {
return i * 10;
})
.attr("transform", function(d) { return "translate(" + d.x + "," + d.y + ")"; })
.attr("fill", function(d) { return color(d.length); });
<script src="https://d3js.org/d3.v4.min.js"></script>
<script src="https://d3js.org/d3-hexbin.v0.2.min.js"></script>
<svg width="960" height="500"></svg>
Related
This is my expected result
Please Click to see Image
This is the data I am using: -
year,co2,ghg
2000,15.34,34.86
2001,15.54,34.86
2002,16.53,34.06
2003,17.03,29.74
2004,17.48,31.97
2005,16.98,29.66
2006,17.62,31.52
2007,19.82,30.91
2008,17.24,29.93
2009,17.66,29.14
2010,17.31,27.13
2011,17.43,28.61
2012,17.91,28.08
2013,16.67,23.88
2014,16.8,24
2015,16.6,23.67
2016,16.04,23.8
2017,15.78,25.34
2018,15.2,24.87
When I use it in HTML like this It works completely fine
<!DOCTYPE html>
<html lang="en">
<head>
<link rel="stylesheet" href="main.css">
<title>Stacked Bar Graph</title>
<meta charset="utf-8">
<script src="https://d3js.org/d3.v4.js"></script>
</head>
<body>
<script>
var margin = {top: 10, right: 30, bottom: 20, left: 50},
width = 1000 - margin.left - margin.right,
height = 400 - margin.top - margin.bottom;
// append the svg object to the body of the page
var svg = d3.select("body")
.append("svg")
.attr("width", width + margin.left + margin.right)
.attr("height", height + margin.top + margin.bottom)
.append("g")
.attr("transform",
"translate(" + margin.left + "," + margin.top + ")");
d3.csv("data.csv", function(data) {
var subgroups = data.columns.slice(1);
var groups = d3.map(data, function(d){return(d.year)}).keys()
var x = d3.scaleBand()
.domain(groups)
.range([0, width])
.padding([0.2])
svg.append("g")
.attr("transform", "translate(0," + height + ")")
.call(d3.axisBottom(x).tickSize(5));
var y = d3.scaleLinear()
.domain([0, 40])
.range([ height, 0 ]);
svg.append("g")
.call(d3.axisLeft(y).ticks(8));
var xSubgroup = d3.scaleBand()
.domain(subgroups)
.range([0, x.bandwidth()])
.padding([0.05])
var color = d3.scaleOrdinal()
.domain(subgroups)
.range(['#e41a1c','#377eb8','#4daf4a'])
svg.append("g")
.selectAll("g")
.data(data)
.enter()
.append("g")
.attr("transform", function(d) { return "translate(" + x(d.year) + ",0)"; })
.selectAll("rect")
.data(function(d) { return subgroups.map(function(key) { return {key: key, value: d[key]}; }); })
.enter().append("rect")
.attr("x", function(d) { return xSubgroup(d.key); })
.attr("y", function(d) { return y(d.value); })
.attr("width", xSubgroup.bandwidth())
.attr("height", function(d) { return height - y(d.value); })
.attr("fill", function(d) { return color(d.key); });
})
</script>
</body>
</html>
But when I use it in a separate Js file like this
function GroupBar() {
// set the dimensions and margins of the graph
var margin = {top: 10, right: 30, bottom: 20, left: 50},
width = 1000 - margin.left - margin.right,
height = 400 - margin.top - margin.bottom;
// append the svg object to the body of the page
var svg = d3.select("body")
.append("svg")
.attr("width", width + margin.left + margin.right)
.attr("height", height + margin.top + margin.bottom)
.append("g")
.attr("transform",
"translate(" + margin.left + "," + margin.top + ")");
// Parse the Data
d3.csv("src/data/co2_vs_ghg.csv", function(data) {
// List of subgroups = header of the csv files = soil condition here
var subgroups = data.columns.slice(1);
// List of groups = species here = value of the first column called group -> I show them on the X axis
var groups = d3.map(data, function(d){return(d.year)}).keys()
// Add X axis
var x = d3.scaleBand()
.domain(groups)
.range([0, width])
.padding([0.2])
svg.append("g")
.attr("transform", "translate(0," + height + ")")
.call(d3.axisBottom(x).tickSize(5));
// Add Y axis
var y = d3.scaleLinear()
.domain([0, 40])
.range([ height, 0 ]);
svg.append("g")
.call(d3.axisLeft(y).ticks(8));
// Another scale for subgroup position?
var xSubgroup = d3.scaleBand()
.domain(subgroups)
.range([0, x.bandwidth()])
.padding([0.05])
// color palette = one color per subgroup
var color = d3.scaleOrdinal()
.domain(subgroups)
.range(['#e41a1c','#377eb8','#4daf4a'])
// Show the bars
svg.append("g")
.selectAll("g")
// Enter in data = loop group per group
.data(data)
.enter()
.append("g")
.attr("transform", function(d) { return "translate(" + x(d.year) + ",0)"; })
.selectAll("rect")
.data(function(d) { return subgroups.map(function(key) { return {key: key, value: d[key]}; }); })
.enter().append("rect")
.attr("x", function(d) { return xSubgroup(d.key); })
.attr("y", function(d) { return y(d.value); })
.attr("width", xSubgroup.bandwidth())
.attr("height", function(d) { return height - y(d.value); })
.attr("fill", function(d) { return color(d.key); });
});
}
window.addEventListener("load", GroupBar);
It gives me an error
shown here
I have tried some alternate methods online but none of them work I think I am missing a minute detail but cannot figure out what if anyone can double check this it would be great
I am using scaleBand() for both x and y axes for a bar chart. For some reason, the height of the bars are in between the ticks of the y axis. I would appreciate any help. Here is my code:
var margin_ = { top: 20, right: 20, bottom: 30, left: 40 },
width_ = 960 - margin_.left - margin_.right,
height_ = 500 - margin_.top - margin_.bottom;
var svg_ = d3.select("body").append("svg")
.attr("width", width_ + margin_.left + margin_.right)
.attr("height", height_ + margin_.top + margin_.bottom)
.append("g")
.attr("transform",
"translate(" + margin_.left + "," + margin_.top + ")");
var x = d3.scaleBand()
.range([0, width_])
.padding(0.2)
var y = d3.scaleBand()
.range([height_, 0]);
x.domain(satisfactScaleKeyValues);
y.domain(graphYvalues);
svg_.selectAll(".bar")
.data(datas)
.enter().append("rect")
.attr("class", "bar__")
.attr("x", function (d) { return x(d.variable); })
.attr("width", x.bandwidth())
.attr("y", function (d) { return y(d.satisLevel); })
.attr("height", function (d) { return height_ - y(d.satisLevel); });
// add the x Axis
svg_.append("g")
.attr("transform", "translate(0," + height_ + ")")
.call(d3.axisBottom(x));
// add the y Axis
svg_.append("g")
.call(d3.axisLeft(y))
You should use d3.scalePoint, which would provide a better translation from an ordinal domain to linear points on a range:
let datas = [{variable: 1, satisLevel: "Neutral"}]
var margin_ = { top: 20, right: 50, bottom: 30, left: 75 },
width_ = 960 - margin_.left - margin_.right,
height_ = 500 - margin_.top - margin_.bottom;
var svg_ = d3.select("body").append("svg")
.attr("width", width_ + margin_.left + margin_.right)
.attr("height", height_ + margin_.top + margin_.bottom)
var g = svg_.append("g")
.attr("transform",
"translate(" + margin_.left + "," + margin_.top + ")");
var x = d3.scaleBand()
.range([0, width_])
.padding(0.2)
var y = d3.scalePoint()
.range([height_, 0])
.padding(0.2)
x.domain([1]);
y.domain(["Not satisfied", "Neutral", "Satisfied"]);
g.selectAll(".bar")
.data(datas)
.enter().append("rect")
.attr("class", "bar__")
.attr("x", function (d) { return x(d.variable); })
.attr("width", x.bandwidth())
.attr("y", function (d) { return y(d.satisLevel); })
.attr("height", function (d) { return height_ - y(d.satisLevel); });
// add the x Axis
g.append("g")
.attr("transform", "translate(0," + height_ + ")")
.call(d3.axisBottom(x));
// add the y Axis
g.append("g")
.call(d3.axisLeft(y))
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/5.7.0/d3.min.js"></script>
If you want the height of the lowest Satisfaction Level to be more than zero, you can add padding to the scalePoint, like in the example.
How should one define values range for X axis?
I've took example which used decimal values in range 0 to 1, and this clearly doesn't work for greater numbers.
<!DOCTYPE html>
<meta charset="utf-8">
<style>
.bar rect {
fill: steelblue;
}
.bar text {
fill: #fff;
font: 10px sans-serif;
}
</style>
<svg width="960" height="500"></svg>
<script src="https://d3js.org/d3.v4.min.js"></script>
<script>
// var data = d3.range(1000).map(d3.randomBates(10));
var data = [1321017167, 1421017167, 1421017167, 1421017167, 1521017167, 1521017167];
var formatCount = d3.format(",.0f");
var svg = d3.select("svg"),
margin = {top: 10, right: 30, bottom: 30, left: 30},
width = +svg.attr("width") - margin.left - margin.right,
height = +svg.attr("height") - margin.top - margin.bottom,
g = svg.append("g").attr("transform", "translate(" + margin.left + "," + margin.top + ")");
var x = d3.scaleLinear()
.rangeRound([0, width]);
var bins = d3.histogram()
.domain(x.domain())
.thresholds(x.ticks(20))
(data);
var y = d3.scaleLinear()
.domain([0, d3.max(bins, function(d) { return d.length; })])
.range([height, 0]);
var bar = g.selectAll(".bar")
.data(bins)
.enter().append("g")
.attr("class", "bar")
.attr("transform", function(d) { return "translate(" + x(d.x0) + "," + y(d.length) + ")"; });
bar.append("rect")
.attr("x", 1)
.attr("width", x(bins[0].x1) - x(bins[0].x0) - 1)
.attr("height", function(d) { return height - y(d.length); });
bar.append("text")
.attr("dy", ".75em")
.attr("y", 6)
.attr("x", (x(bins[0].x1) - x(bins[0].x0)) / 2)
.attr("text-anchor", "middle")
.text(function(d) { return formatCount(d.length); });
g.append("g")
.attr("class", "axis axis--x")
.attr("transform", "translate(0," + height + ")")
.call(d3.axisBottom(x));
</script>
By default, the domain of a linear scale is [0, 1]. You just copied that code from Mike Bostock without changing the domain. In his original code the domain is, coincidentally, the default domain, but in your code you have to define it:
var x = d3.scaleLinear()
.rangeRound([0, width])
.domain(d3.extent(data))//domain here
Here I'm using d3.extent, but you can use any other array you want.
Here is your code with that change only:
<!DOCTYPE html>
<meta charset="utf-8">
<style>
.bar rect {
fill: steelblue;
}
.bar text {
fill: #fff;
font: 10px sans-serif;
}
</style>
<svg width="960" height="500"></svg>
<script src="https://d3js.org/d3.v4.min.js"></script>
<script>
// var data = d3.range(1000).map(d3.randomBates(10));
var data = [1321017167, 1421017167, 1421017167, 1421017167, 1521017167, 1521017167];
var formatCount = d3.format(",.0f");
var svg = d3.select("svg"),
margin = {top: 10, right: 30, bottom: 30, left: 30},
width = +svg.attr("width") - margin.left - margin.right,
height = +svg.attr("height") - margin.top - margin.bottom,
g = svg.append("g").attr("transform", "translate(" + margin.left + "," + margin.top + ")");
var x = d3.scaleLinear()
.rangeRound([0, width])
.domain(d3.extent(data))
var bins = d3.histogram()
.domain(x.domain())
.thresholds(x.ticks(20))
(data);
var y = d3.scaleLinear()
.domain([0, d3.max(bins, function(d) { return d.length; })])
.range([height, 0]);
var bar = g.selectAll(".bar")
.data(bins)
.enter().append("g")
.attr("class", "bar")
.attr("transform", function(d) { return "translate(" + x(d.x0) + "," + y(d.length) + ")"; });
bar.append("rect")
.attr("x", 1)
.attr("width", x(bins[0].x1) - x(bins[0].x0) - 1)
.attr("height", function(d) { return height - y(d.length); });
bar.append("text")
.attr("dy", ".75em")
.attr("y", 6)
.attr("x", (x(bins[0].x1) - x(bins[0].x0)) / 2)
.attr("text-anchor", "middle")
.text(function(d) { return formatCount(d.length); });
g.append("g")
.attr("class", "axis axis--x")
.attr("transform", "translate(0," + height + ")")
.call(d3.axisBottom(x));
</script>
PS: You'll have to adjust the horizontal position of the rectangles.
I have been following the block that uses hexagonal binning of random points with the normal distribution but instead trying to tailor it to the exponential distribution.
The code runs, but the output seems to show a mirror along the x-axis. That is, the points are all clustered along the upper-left instead of lower-left. I've been playing with the transform function but can't quite get it. What am I missing? JSFiddle
<!DOCTYPE html>
<style>
.hexagon {
stroke: #000;
stroke-width: 0.5px;
}
</style>
<svg width="500" height="200"></svg>
<script src="https://d3js.org/d3.v4.min.js"></script>
<script src="https://d3js.org/d3-hexbin.v0.2.min.js"></script>
<script>
var svg = d3.select("svg"),
margin = {top: 20, right: 20, bottom: 30, left: 40},
width = +svg.attr("width") - margin.left - margin.right,
height = +svg.attr("height") - margin.top - margin.bottom,
g = svg.append("g").attr("transform", "translate(" + margin.left + "," + margin.top + ")");
var randomX = d3.randomExponential(1 / 30),
randomY = d3.randomExponential(1 / 30),
points = d3.range(2000).map(function() { return [randomX(), randomY()]; });
var color = d3.scaleSequential(d3.interpolateLab("white", "steelblue"))
.domain([0, 20]);
var hexbin = d3.hexbin()
.radius(5)
.extent([[0, 0], [width, height]]);
var x = d3.scaleLinear()
.domain([0, width])
.range([0, width]);
var y = d3.scaleLinear()
.domain([0, height])
.range([height, 0]);
g.append("clipPath")
.attr("id", "clip")
.append("rect")
.attr("width", width)
.attr("height", height);
g.append("g")
.attr("class", "hexagon")
.attr("clip-path", "url(#clip)")
.selectAll("path")
.data(hexbin(points))
.enter().append("path")
.attr("d", hexbin.hexagon())
.attr("transform", function(d) { return "translate(" + d.x + "," + d.y + ")"; })
.attr("fill", function(d) { return color(d.length); });
</script>
You set your scales, but you never use them:
.attr("transform", function(d) {
return "translate(" + d.x + "," + d.y + ")";
})
Solution: use your scales:
.attr("transform", function(d) {
return "translate(" + x(d.x) + "," + y(d.y) + ")";
//scales here --------^--------------^
})
Here is your code with that change:
var svg = d3.select("svg"),
margin = {
top: 20,
right: 20,
bottom: 30,
left: 40
},
width = +svg.attr("width") - margin.left - margin.right,
height = +svg.attr("height") - margin.top - margin.bottom,
g = svg.append("g").attr("transform", "translate(" + margin.left + "," + margin.top + ")");
var randomX = d3.randomExponential(1 / 30),
randomY = d3.randomExponential(1 / 30),
points = d3.range(2000).map(function() {
return [randomX(), randomY()];
});
var color = d3.scaleSequential(d3.interpolateLab("white", "steelblue"))
.domain([0, 20]);
var hexbin = d3.hexbin()
.radius(5)
.extent([
[0, 0],
[width, height]
]);
var x = d3.scaleLinear()
.domain([0, width])
.range([0, width]);
var y = d3.scaleLinear()
.domain([0, height])
.range([height, 0]);
g.append("clipPath")
.attr("id", "clip")
.append("rect")
.attr("width", width)
.attr("height", height);
g.append("g")
.attr("class", "hexagon")
.attr("clip-path", "url(#clip)")
.selectAll("path")
.data(hexbin(points))
.enter().append("path")
.attr("d", hexbin.hexagon())
.attr("transform", function(d) {
return "translate(" + x(d.x) + "," + y(d.y) + ")";
})
.attr("fill", function(d) {
return color(d.length);
});
.hexagon {
stroke: #000;
stroke-width: 0.5px;
}
<svg width="500" height="200"></svg>
<script src="https://d3js.org/d3.v4.min.js"></script>
<script src="https://d3js.org/d3-hexbin.v0.2.min.js"></script>
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:
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>