I am new to D3 and I am trying to make a simple bar chart following an example in the "Interactive Data Visualization for the Web".
My data in csv format looks like this:
category,number
one,20
two,60
three,5
I am doing the following:
<script type="text/javascript">
//Width and height
var w = 500;
var h = 100;
var barPadding = 1;
var rowConverter = function(d) {
return {
category: d.category,
number: parseFloat(d.number)
};
}
var dataset;
d3.csv("barcharttest.csv", rowConverter).then (function(data) {
console.log(data);
dataset=data;
});
//Create SVG element
var svg = d3.select("body")
.append("svg")
.attr("width", w)
.attr("height", h);
svg.selectAll("rect")
.data(dataset)
.enter()
.append("rect")
.attr("x", function(d, i) {
return i * (w / dataset.number.length);
})
.attr("y", function(d) {
return h - (d.number * 4);
})
.attr("width", w / dataset.length - barPadding)
.attr("height", function(d) {
return d.number * 4;
})
.attr("fill", "teal");
</script>
</body>
</html>
However, this fails to produce a bar chart.
Error on the console: bookcode.html:41 Uncaught TypeError: svg.selectAll(...).data(...).enter is not a function
I would greatly appreciate any suggestions.
Related
var w = 300;
var h = 150;
var padding = 2;
var dataset =[5, 10, 15, 20, 25];
var svg = d3.select("body")
.append("svg")
.attr("width", w)
.attr("height", h);
function colorPicker(v){
if (v<=20) { return "#666666"; }
else if (v>20) { return "#FF0033"; }
}
svg.selectAll("rect")
.data(dataset)
.enter()
.append("rect")
.attr("x", function(d, i) { return (i*(w/dataset.length)); })
.attr("y", function(d) { return h-(d*4); })
.attr("width", w/dataset.length-padding)
.attr("height", function(d) { return d*4;})
.attr("fill", function(d){
return colorPicker(d);
});
svg.selectAll("text")
.data(dataset)
.enter()
.append("text")
.text(function(d) {return d; })
.attr({"text-anchor": "middle"})
.attr({
x: function(d, i) {return i* (w / dataset.length);},
y: function(d) {return h - (d*4); }
});
I am following a D3.js tutorial and I'm trying to get the text-anchor to work, but it won't append. No text appears, can anyone shed any light into what I'm doing wrong?
It should display the number above every rectangle
In the new (not so new, actually) V4.x version, you cannot use objects to set the attr() method.
Besides that you have another problem, which will avoid the texts to be rendered: there is no value property in your dataset (which is just an array of numbers). Thus, it should be:
.text(function(d){return d})
Here is your code with the necessary changes:
var w = 300;
var h = 150;
var padding = 2;
var dataset = [5, 10, 15, 20, 25];
var svg = d3.select("body")
.append("svg")
.attr("width", w)
.attr("height", h);
function colorPicker(v) {
if (v <= 20) {
return "#666666";
} else if (v > 20) {
return "#FF0033";
}
}
svg.selectAll("rect")
.data(dataset)
.enter()
.append("rect")
.attr("x", function(d, i) {
return (i * (w / dataset.length));
})
.attr("y", function(d) {
return h - (d * 4);
})
.attr("width", w / dataset.length - padding)
.attr("height", function(d) {
return d * 4;
})
.attr("fill", function(d) {
return colorPicker(d);
});
svg.selectAll("text")
.data(dataset)
.enter()
.append("text")
.attr("text-anchor", "middle")
.text(function(d) {
return d;
})
.attr("x", function(d, i) {
return i * (w / dataset.length) + ((w / dataset.length - padding) / 2);
})
.attr("y", function(d) {
return h - (d * 4);
});
<script src="https://d3js.org/d3.v4.min.js"></script>
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 have a relatively simple barchart. I want to .transition() between datasets with an .on("click") event. What I'm getting is a complete redraw of an additional chart appended to the DOM id, instead of removing the original chart and transitioning or replacing it. I think I'm misunderstanding how to correctly .remove().
d3.json("data/cfilt-steps.json", function(d) {
d.forEach(function(d) {
parseDate = d3.time.format("%Y-%m-%d").parse;
d.date = parseDate(d.date); d.value = +d.value;
});
margin = {top:5, right:5, bottom: 40, left:5},
height = 150 - margin.top - margin.bottom,
width = 500 - margin.left - margin.right,
barPadding = 1;
steps = crossfilter(d),
monthdim = steps.dimension(function(d){ thisDate = new Date(d.date); return thisDate.getMonth(); }),
monthgrp = monthdim.group().reduceSum(function(d){ return d.value; });
daydim = steps.dimension(function(d){ thisDate = new Date(d.date); return thisDate.getDay(); }),
daygrp = daydim.group().reduceSum( function(d) { return d.value; });
stepColor = d3.scale.threshold()
.domain([100, 150, 200, 250, 300, 400, 500])
.range(["#E3E3E3", "#D0DFD2", "#C3DABC", "#BDCB87", "#CAB44E", "#E29517", "#FF6600"]);
d3.select("#monthly-steps-previous-selector")
.on("click", function(d) {reDraw(monthgrp.all()) })
d3.select("#monthly-steps-next-selector")
.on("click", function(d) {reDraw(daygrp.all()); })
function reDraw(data) {
xScale = d3.scale.ordinal().domain(monthdim).range(0, width);
yScale = d3.scale.linear().domain([0, d3.max(data, function(d){return d.value;})]).range([height, 5]);
var stepbars = d3.select("#steps-bar")
.append("svg:svg")
.attr("width", width)
.attr("height", height)
stepbars.selectAll("rect")
.data(data)
.enter().append("rect")
.attr("x", function(d,i){ return i * width/data.length; })
.attr("y", function(d){ return yScale(d.value); })
.attr("width", width/data.length - barPadding)
.attr("height", function(d) { return height-yScale(d.value); })
.attr("fill", function(d){ return stepColor(d.value/2000); })
}
reDraw(monthgrp.all());
});
Can someone show me what this is supposed to look like, or tell me what I'm doing wrong?
Your reDraw function appends the svg, this means every time you call redraw a new svg is appended, hence the double chart. I would suggest to put the lines
var stepbars = d3.select("#steps-bar")
.append("svg:svg")
.attr("width", width)
.attr("height", height)
above the reDraw function.
Furthermore, your redraw function does not call remove. I would do something like:
//Select and bind to data
var selection = stepbars.selectAll("rect")
.data(data);
//Enter and create new rectangles
selection.enter()
.append("rect");
//Update all rectangles
selection.attr("x", function(d,i){ return i * width/data.length; })
.attr("y", function(d){ return yScale(d.value); })
.attr("width", width/data.length - barPadding)
.attr("height", function(d) { return height-yScale(d.value); })
.attr("fill", function(d){ return stepColor(d.value/2000); });
//Remove unused rectangles
selection.exit().remove();
I am trying to create a bar chart with the dataset below. I am stuck on the part where the height[score] of the bar[country] is determined. How do I loop through the dataset to pull each score for a different country?
Any help would be greatly appreciated :)
var w = 500;
var h = 100;
var barPadding = 1;
var dataset = [
{"country":"Hong Kong","score":8.98},
{"country":"Singapore","score":8.54},
{"country":"New Zealand","score":8.19},
{"country":"Switzerland","score":8.09},
{"country":"Mauritius","score":8.98},
{"country":"United Arab Emirates","score":8.05},
{"country":"Canada","score":8.00},
{"country":"Australia","score":7.87},
{"country":"Jordan","score":7.86},
{"country":"Chile","score":7.84},
];
//Create SVG element
var svg = d3.select("body")
.append("svg")
.attr("width", w)
.attr("height", h);
svg.selectAll("rect")
.data(dataset)
.enter()
.append("rect")
.attr("x", function(d, i) {
return i * (w / dataset.length);
})
.attr("y", function(d) {
return h - (d * 4);
})
.attr("width", w / dataset.length - barPadding)
.attr("height", function(d) {
return d * 4;
});
In D3, once you load the data through the .data(dataset) command, you can now access each record of the data by inserting the anonymous function function(d, i) { } as you have done in a few of your attributes.
Since your dataset is:
var dataset = [
{"country":"Hong Kong","score":8.98},
{"country":"Singapore","score":8.54},
{"country":"New Zealand","score":8.19},
{"country":"Switzerland","score":8.09},
{"country":"Mauritius","score":8.98},
{"country":"United Arab Emirates","score":8.05},
{"country":"Canada","score":8.00},
{"country":"Australia","score":7.87},
{"country":"Jordan","score":7.86},
{"country":"Chile","score":7.84},
];
each d is a object record e.g. {"country":"Singapore","score":8.54}, while i refers to the index of the object d returned e.g. 1 for our example of d used above.
To access the score of the object record d, this becomes simple Javscript object notation i.e. d.score.
Hence your .attr call should look like:
.attr("height", function(d) {
return d.score * 4;
});
Similarly, you can extract the other fields e.g. country with d.country if you intend to use it in .attr("text", function(d) { return d.country; });
This is the real beauty and power of D3. If you ever want to expand your visualization with more features that is obtained through your data, then all you have to make sure is that your dataset data contains more data attributes, and you can call them later as you iterate through the anonymous functions. And D3 is in the spirit of its name, truly being "data-driven"! :)
You will need to fix d to d.score.
If you want to show country text, write svg.selectAll("text") after svg.selectAll("rect").
Like this:
var w = 500;
var h = 100;
var barPadding = 1;
var dataset = [
{"country":"Hong Kong","score":8.98},
{"country":"Singapore","score":8.54},
{"country":"New Zealand","score":8.19},
{"country":"Switzerland","score":8.09},
{"country":"Mauritius","score":8.98},
{"country":"United Arab Emirates","score":8.05},
{"country":"Canada","score":8.00},
{"country":"Australia","score":7.87},
{"country":"Jordan","score":7.86},
{"country":"Chile","score":7.84},
];
//Create SVG element
var svg = d3.select("body")
.append("svg")
.attr("width", w)
.attr("height", h);
svg.selectAll("rect")
.data(dataset)
.enter()
.append("rect")
.attr("x", function(d, i) {
return i * (w / dataset.length);
})
.attr("y", function(d) {
return h - (d.score * 4);
})
.attr("width", w / dataset.length - barPadding)
.attr("height", function(d) {
return d.score * 4;
});
svg.selectAll("text")
.data(dataset)
.enter()
.append("text")
.text(function(d) {
return d.country;
})
.attr("transform", function(d, i) {
var barW = w / dataset.length;
return "translate(" +
( barW * i + barW / 2 + barPadding ) + "," +
( h - 5 ) +
")rotate(-90)";
})
.attr("font-size", "8pt")
.attr("fill", "white");
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/3.4.11/d3.min.js"></script>
Something like
For( var i =0; i<dataset.length; i++){
// Dataset[i].country
// dataset[i].score
}
You have an array of objects
i have donut chart with legend specification. I have 2 values in dataset. But here with this code i'm getting only the first value, "Unresolved".
var dataset = {
Unresolved: [3],
Resolved:[7]
};
var keyValue=[];
for(key in dataset){
keyValue.push(key);
}
var width = 260,
height = 300,
radius = Math.min(width, height) / 2;
var color = ["#9F134C", "#ccc"];
var pie = d3.layout.pie()
.sort(null);
var arc = d3.svg.arc()
.innerRadius(radius - 90)
.outerRadius(radius - 80);
var svg = d3.select("body").append("svg")
.attr("width", width)
.attr("height", height)
.append("g")
.attr("transform", "translate(" + width / 2 + "," + height / 2 + ")");
var gs = svg.selectAll("g").data(d3.values(dataset)).enter().append("g");
var path = gs.selectAll("path")
.data(function(d,i) { return pie(d); })
.enter().append("path")
.attr("fill", function(d, i) { console.log("log", keyValue[i]);return color[i]; }) //Here i'm getting only the 1st value "unresolved".
.attr("d", arc);
var legendCircle = d3.select("body").append("svg").selectAll("g").data(keyValue).enter().append("g")
.attr("class","legend")
.attr("width", radius)
.attr("height", radius * 2)
.attr("transform", function(d, i) { return "translate(0," + i * 20 + ")"; });
legendCircle.append("rect")
.attr("width", 18)
.attr("height", 10)
.style("fill", function(d, i) { return color[i];});
legendCircle.append("text")
.attr("x", 24)
.attr("y", 5)
.attr("dy", ".35em")
.text(function(d) { return d; });
The output i'm getting is,
Can anyone help on this? Thanks.
It looks like you're doing a nested selection in your code, which you would usually only need for nested data. Your data is not nested however -- there's a single level with 2 values. What's happening is that, by using a nested selection, you're descending into the value arrays, each of which contains only a single value.
It works fine if you do away with the nested selection and pass your original data to the pie layout.
var gs = svg.selectAll("g").data(pie(d3.values(dataset))).enter().append("g");
var path = gs.append("path")
.attr("fill", function(d, i) { return color[i]; })
.attr("d", arc);
Complete example here.