I'm trying to append four rectto my svg. I can see them appended in chrome's dev tools. However, they're never rendered because it seems I have an issue with passing on the width value.
Besides, in version 3 of D3 I receive the following error message in the browser:
d3.v3.min.js:1 Error: attribute width: Expected length, "NaN".
There's no error message in version 4.
Here's my code:
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>Test</title>
<script src="https://d3js.org/d3.v3.min.js"></script>
</head>
<body>
<script type="text/javascript">
var data = [20,3,60,800];
var width = 500,
height = 500;
var widthScale = d3.scale.linear()
.domain([0, 80])
.range(0, width);
var svg = d3.select("body")
.append("svg")
.attr("width", width)
.attr("height", height);
var bars = svg.selectAll("rect")
.data(data)
.enter()
.append("rect")
.attr("width", function (d) { return widthScale(d); } )
.attr("height", 50)
.attr("fill", "steelblue")
.attr("y", function (d, i) { return i * 100});
</script>
</body>
</html>
The only difference between v3 and v4 is this line (scaleLinear):
var widthScale = d3.scaleLinear().domain([0, 80]).range(0, width);
Any help highly appreciated.
range has to be an array. The documentation is clear:
linear.range([values]):
If values is specified, sets the scale's output range to the specified array of values
So, instead of:
.range(0, width);
It should be:
.range([0, width]);
If you use .range(0, width) D3 v3.x returns a NaN, and you see in the console:
Error: attribute width: Expected length, "NaN".
However, D3 v4.x returns undefined, and you see no error message in the console.
Here is your working code:
var data = [20,3,60,800];
var width = 500,
height = 500;
var widthScale = d3.scale.linear()
.domain([0, 80])
.range([0, width]);
var svg = d3.select("body")
.append("svg")
.attr("width", width)
.attr("height", height);
var bars = svg.selectAll("rect")
.data(data)
.enter()
.append("rect")
.attr("width", function (d) { return widthScale(d); } )
.attr("height", 50)
.attr("fill", "steelblue")
.attr("y", function (d, i) { return i * 100});
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/3.4.11/d3.min.js"></script>
Related
Can anyone tell me how to provide gaps between the bars that are getting stacked at one particular place?
Here is the code:
P.s: Sorry i tried but coukd not post it as a code snippet due to some error "Please add some explanation"
Part 1
Part 2
Output
#Samrat- When appending rect to your svg, change this line in your code .attr("x"), function(d,i){return x(i)}) to .attr("x", (d, i) => i * (svgWidth / info.length)). This code divides the entire svg width by the length of your dataset and multiplies each value by its index, which distances bars from each other.
Hope this helps.
Fiddle for your reference: https://jsfiddle.net/zfjcLopw/1/
Note: Going forward while posting questions on SO, follow the guidelines listed in this article https://stackoverflow.com/help/how-to-ask. This helps in getting answers promptly.
Here is the updated code:
<!DOCTYPE html>
<html>
<head>
<title>test</title>
<script src="https://d3js.org/d3.v5.min.js"></script>
</head>
<body>
<script>
const width = 600;
const height = 500;
const margin = { top: 100, right: 100, bottom: 100, left: 120 };
const innerWidth = width - margin.left - margin.right;
const innerHeight = height - margin.top - margin.bottom;
const barPadding = 2;
const svg = d3
.select("body")
.append("svg")
.attr("width", width)
.attr("height", height);
const tag = ["mango", "banana", "apple", "lemon", "coconut"];
const info = [23, 54, 34, 51, 22];
const xScale = d3
.scaleBand()
.domain(tag.map(d => d))
.rangeRound([0, innerWidth]);
const yScale = d3
.scaleLinear()
.domain([0, d3.max(info, d => d)])
.range([innerHeight, 0]);
const mainG = svg
.append("g")
.attr("transform", `translate(${margin.left}, ${margin.top})`);
const g = mainG
.selectAll("g")
.data(info)
.enter()
.append("g")
.attr("transform", `translate(0,0)`);
g.append("rect")
.attr("x", (d, i) => i * (innerWidth / info.length))
// .attr("x", (d, i) => xScale(i)) - This line will stack bars on top of each other
.attr("y", d => yScale(d))
.attr("width", innerWidth / info.length - barPadding)
.attr("height", d => innerHeight - yScale(d))
.attr("fill", "blue");
mainG.append("g").call(d3.axisLeft(yScale));
mainG
.append("g")
.call(d3.axisBottom(xScale))
.attr("transform", `translate(0, ${innerHeight})`);
</script>
</body>
</html>
X scaleBand domain is set to tag values, but you call it with i index. Try x(tag[i])
I'm trying to link my .csv stored in github to the my d3 code.
Does anybody know if there is anything that I'm missing? I was able to do it with LeafLet not with D3. Any help would be highly appreciated. Thanks!
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>D3!!</title>
</head>
<body>
<script src="https://d3js.org/d3.v4.min.js"></script>
<script
src="https://cdnjs.cloudflare.com/ajax/libs/leaflet/1.0.3/leaflet.js">
</script>
<script src="https://d3js.org/d3-selection-multi.v0.4.min.js"></script>
<script>
var outerWidth=500;
var outerheight=250;
var margin={left:-50, top:0, right:-50, bottom:0};
var xColumn="longitude";
var yColumn="latitude";
var rColumn="population";
var peoplePerPixel=1000000;
var innerWidth=outerWidth - margin.left - margin.right;
var innerHeight=outerheight - margin.top - margin.bottom;
var svg=d3.select("body").append("svg")
.attr("width", outerWidth)
.attr("height", outerheight);
var g= svg.append("g")
.attr("transform", "translate (" + margin.left + "," +margin.top +")");
var xScale= d3.scaleLog()
.range([0,innerWidth]);
var yScale= d3.scaleLog()
.range([innerHeight,0]);
var rScale= d3.scaleSqrt();
function render (data){
xScale.domain(d3.extent(data, function (d){return d[xColumn]; }));
yScale.domain(d3.extent(data, function (d){return d[yColumn]; }));
rScale.domain([0, d3.max(data, function (d){return d[xColumn]; })]);
var circles= svg.selectAll("circle").data(data);
circles.enter().append("circle");
circles
.attr("cx", function(d){ return xScale(d[xColumn]);})
.attr("cy", function(d){ return yScale(d[yColumn]);})
.attr("r", function(d){ return rScale(d[rColumn]);});
circles.exit().remove();
}
function type(d) {
d.latitude=+d.latitude;
d.longitude=+d.longitude;
d.population=+d.population;
return d;
}
var data =
d3.csv(
"https://raw.githubusercontent.com/Pre60/myTest/master/map_cities.csv",
type, render)
</script>
</body>
</html>
You have some problems here:
You're setting the attributes to an "update" selection. This will not work (unless you call the function twice). It has to be:
circles.enter()
.append("circle")
.attr("cx", function(d) {
//etc...
because of that, there were no circles in your SVG. However, changing that point #1 shows you two additional problems:
You're using a scaleLog with a domain that crosses zero. There is no log of zero (actually, it is minus infinity). As the API clearly says:
As log(0) = -∞, a log scale domain must be strictly-positive or strictly-negative; the domain must not include or cross zero.
So, use a linear scale instead.
You are using the wrong property for the radii. It should be rColumn.
You forgot to set the range of the rScale.
All together, this is your (almost) working code:
<script src="https://d3js.org/d3.v4.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/leaflet/1.0.3/leaflet.js">
</script>
<script src="https://d3js.org/d3-selection-multi.v0.4.min.js"></script>
<script>
var outerWidth = 500;
var outerheight = 250;
var margin = {
left: -50,
top: 0,
right: -50,
bottom: 0
};
var xColumn = "longitude";
var yColumn = "latitude";
var rColumn = "population";
var peoplePerPixel = 1000000;
var innerWidth = outerWidth - margin.left - margin.right;
var innerHeight = outerheight - margin.top - margin.bottom;
var svg = d3.select("body").append("svg")
.attr("width", outerWidth)
.attr("height", outerheight);
var g = svg.append("g")
.attr("transform", "translate (" + margin.left + "," + margin.top + ")");
var xScale = d3.scaleLinear()
.range([0, innerWidth]);
var yScale = d3.scaleLinear()
.range([innerHeight, 0]);
var rScale = d3.scaleSqrt().range([1, 5]);
function render(data) {
xScale.domain(d3.extent(data, function(d) {
return d[xColumn];
}));
yScale.domain(d3.extent(data, function(d) {
return d[yColumn];
}));
rScale.domain([0, d3.max(data, function(d) {
return d[rColumn];
})]);
var circles = svg.selectAll("circle").data(data);
circles.enter().append("circle").attr("cx", function(d) {
return xScale(d[xColumn]);
})
.attr("cy", function(d) {
return yScale(d[yColumn]);
})
.attr("r", function(d) {
return rScale(d[rColumn]);
});
circles.exit().remove();
}
function type(d) {
d.latitude = +d.latitude;
d.longitude = +d.longitude;
d.population = +d.population;
return d;
}
var data = d3.csv(
"https://raw.githubusercontent.com/Pre60/myTest/master/map_cities.csv",
type, render)
</script>
PS: There is no difference in writing var data = d3.csv(url, callback), since d3.csv doesn't return anything (actually, it returns an object related to the request). So, just drop that var data.
I have the the d3.js code which is pasted here.
I am trying to display more than one graphs in the same page. Though the d3.js code is same. Say one from data1.json and the other from data2.json. Following is the snippet which is bothering me.
<svg width="960" height="960"></svg>
<script src="https://d3js.org/d3.v4.min.js"></script>
<script>
var svg2 = d3.select("svg"),
margin = 20,
diameter = +svg2.attr("width"),
g = svg2.append("g").attr("transform", "translate(" + diameter / 2 + "," + diameter / 2 + ")");
As per different answers in SO here, here, here, here or here, the solution seems to be one of the following:
Use different variable name to hold svgs such as svg1, svg2.. etc..
which I have done.
Use a method as described here.
var chart1 = d3.select("#area1")
.append("svg")
Method two is not working for me, as it shows blank page.
How to resolve this. I am sure that I am not getting the syntax correctly.
There's no problem at all using multiple SVGs on the same page. Here's an example:
var svg1 = d3.select("#svg1");
svg1.append("circle")
.attr("cx",100)
.attr("cy", 100)
.attr("r", 90)
.attr("fill", "red");
var svg2 = d3.select("#svg2");
svg2.append("circle")
.attr("cx",100)
.attr("cy", 100)
.attr("r", 90)
.attr("fill", "blue");
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/3.4.11/d3.min.js"></script>
<svg width="200" height="200" id="svg1"></svg>
<svg width="200" height="200" id="svg2"></svg>
There is no need for repeating all the code, as you're doing right now. Don't repeat yourself.
An easy alternative is wrapping all your D3 code in a function that has two parameters, selector and url:
function draw(selector, url){
//code here
};
Then, inside that function draw, you set the position of your SVG:
var svg = d3.select(selector).append("svg")...
And the URL you get the data:
d3.json(ulr, function(error, root) {...
After that, just call the draw function twice, with different arguments:
draw(selector1, url1);
draw(selector2, url2);
Here is a demo, read it carefully to see how it works:
draw("#svg1", "#data1");
draw("#svg2", "#data2");
function draw(selector, url){
var data = d3.csvParse(d3.select(url).text())
var width = 500,
height = 150;
var svg = d3.select(selector)
.append("svg")
.attr("width", width)
.attr("height", height);
var xScale = d3.scalePoint()
.domain(data.map(function(d) {
return d.name
}))
.range([50, width - 50])
.padding(0.5);
var yScale = d3.scaleLinear()
.domain([0, d3.max(data, function(d) {
return d.value
}) * 1.1])
.range([height - 20, 6]);
var line = d3.line()
.x(function(d){ return xScale(d.name)})
.y(function(d){ return yScale(d.value)});
svg.append("path")
.attr("d", line(data))
.attr("stroke", "teal")
.attr("stroke-width", "2")
.attr("fill", "none");
var xAxis = d3.axisBottom(xScale);
var yAxis = d3.axisLeft(yScale);
svg.append("g").attr("transform", "translate(0,130)")
.attr("class", "xAxis")
.call(xAxis);
svg.append("g")
.attr("transform", "translate(50,0)")
.attr("class", "yAxis")
.call(yAxis);
}
pre {
display: none;
}
<script src="https://d3js.org/d3.v4.min.js"></script>
<div>First SVG</div>
<div id="svg1"></div>
<div>Second SVG</div>
<div id="svg2"></div>
<pre id="data1">name,value
foo,8
bar,1
baz,7
foobar,9
foobaz,4</pre>
<pre id="data2">name,value
foo,1
bar,2
baz,3
foobar,9
foobaz,8</pre>
If the two charts use the same code, I think the most d3-like way to go about it would be
var width = 960,
height = 960,
margin = 30;
var svgs = d3.select('#area1')
.selectAll('svg')
.data([json1, json2])
.enter()
.append('svg')
.attr('width', width)
.attr('height', height)
svgs.append("g")
.attr("transform", "translate(" + width / 2 + "," + height / 2 + ")")
.each(function(d) {console.log(d)}) // will log json1, then json2
You'll then have json1 and json2 bound to each of the newly appended svgs, and all code that follows will be done to both.
var width = 200,
height = 100,
margin = 30;
var svgs = d3.select('#area1')
.selectAll('svg')
.data([{text:'thing1'}, {text:'thing2'}])
.enter()
.append('svg')
.attr('width', width)
.attr('height', height);
svgs.append("text")
.attr("transform", "translate(" + width / 2 + "," + height / 2 + ")")
.text(function(d) {return d.text});
<script src="https://d3js.org/d3.v4.js"></script>
<div id='area1'></div>
I made a bar chart from data from a .csv file. I am struggling to make the height of the bar chart. I would like the height to be taken from the data values of a specific column, in this case, the "NO OF RECORDS STOLEN" column in the file.
I have tried things like:
.attr("height", function(d) {return d["NO OF RECORDS STOLEN"];}
but it does not work.
This is my HTML:
<!DOCTYPE HTML>
<html>
<head>
<meta charset="utf-8">
<title>Bar Chart | Crime File</title>
<script type="text/javascript" src="https://d3js.org/d3.v4.min.js"></script>
</head>
<body>
<script type="text/javascript">
var dataset = "data_breaches.csv";
var w = 960;
var h = 500;
var barPadding = 1;
var barWidth = w / dataset.length - barPadding;
// create canvas
var svg = d3.select("body")
.append("svg")
.attr("width", w)
.attr("height", h);
// create bar chart
svg.selectAll("rect")
.data(dataset)
.enter()
.append("rect")
.attr("x", function (d, i) {
return i * (barWidth + barPadding);
})
.attr("y", 0)
.attr("width", barWidth)
.attr("height", 100) // WORKING ON THIS
.attr("fill", function (d) {
return "rgb(200, 50, 50)";
});
// get data
d3.csv(dataset, function (data) {
// convert type from string to integer
data.forEach(function typeConv(d) {
// type conversion from string to number
d["YEAR"] = +d["YEAR"]; // for names with spaces
d["DATA SENSITIVITY"] = +d["DATA SENSITIVITY"];
d["NO OF RECORDS STOLEN"] = +d["NO OF RECORDS STOLEN"];
return d;
});
var arrayLength = data.length;
// fixed, should have been <, not <= bc n-1, not n
for (var i = 0; i < arrayLength; i++) {
var breachesData = data[i];
console.log(breachesData);
}
});
</script>
</body>
</html>
As mentioned in the comment at your question, you need to append the rectangles after the data is loaded. Also I reviewed your code and removed unnecessary parts for clarity. Pay attention to the comments that I've added and let us know if you have any questions. Good luck!
var dataset = "data_breaches.csv";
var w = 960;
var h = 500;
var barPadding = 1;
var barWidth = w / dataset.length - barPadding;
// create canvas
var svg = d3.select("body")
.append("svg")
.attr("width", w)
.attr("height", h);
// get data
d3.csv(dataset, function (data) {
// You need to create a "scale" to convert from your data values to pixels in the screen
var heightBy = "NO OF RECORDS STOLEN"
var scale = d3.scaleLinear()
.domain([0, d3.max(d => d[heightBy])])
.range([0, h])
// create bar chart
svg.selectAll("rect")
.data(data) // "dataset" is the filepath, "data" is the loaded file content
.enter()
.append("rect")
.attr("x", (d, i) => i * (barWidth + barPadding))
.attr("y", d => h - scale(d[heightBy])) // Remember that on SVG y=0 is at the bottom and the rect height grows down
.attr("width", barWidth)
.attr("height", d => scale(d[heightBy]))
.attr("fill", "rgb(200, 50, 50)");
});
I'm trying to create a bar chart using the json url. With respect to impressions and time. I don't think I'm referencing the data correctly at .data(data) how do I access the impressions field from json file in d3?
var url = "https://script.google.com/macros/s/AKfycbwy56QiQwyfkkaLFWZ33QHVieAHhtLJYNa_AzKcCBr-J7Catgv2/exec?id=1vQsWQPUET20KcgeRKgs5NOOBngqLeUuNTHI1bWi5Et8&sheet=Sheet1";
d3.json(url, function(data) {
console.log(data.Sheet1[0]);
var canvas = d3.select("body").append("svg")
.attr("width", 500)
.attr("height", 500)
canvas.selectAll("rect")
.data(data)
.enter()
.append("rect")
.attr("width", function(d) {
return d.Impressions
})
.attr("height", 45)
.attr("y", function(d, i) {
return i * 50
})
.attr("fill", "blue")
});
You have to pass an array to the function data(). Thus, since data is an object:
Object {Sheet1: Array[71]}
Your data should be data.Sheet1 instead. Check the demo:
var url = "https://script.google.com/macros/s/AKfycbwy56QiQwyfkkaLFWZ33QHVieAHhtLJYNa_AzKcCBr-J7Catgv2/exec?id=1vQsWQPUET20KcgeRKgs5NOOBngqLeUuNTHI1bWi5Et8&sheet=Sheet1";
d3.json(url, function (data) {
var scale = d3.scale.linear()
.domain([0, d3.max(data.Sheet1, function(d){ return d.Impressions})])
.range([0, 500]);
var canvas = d3.select("body").append("svg")
.attr("width",500)
.attr("height",500)
canvas.selectAll("rect")
.data(data.Sheet1)
.enter()
.append("rect")
.attr("width",function(d){return scale(d.Impressions)})
.attr("height", 6)
.attr("y",function(d,i){return i*7})
.attr("fill","blue")
});
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/3.4.11/d3.min.js"></script>
PS: I added a scale to your code.