Related
I have the following dataset in csv format:
Month,S40201,S40202,S40203
JAN,79,0,70
FEB,58,26,70
MAR,48,47,46
APR,64,98,77
MAY,79,71,64
JUN,86,103,116
JUL,95,75,95
AUG,0,40,3,5
SEP,60,82,79
OCT,98,101,79
NOV,60,81,75
DEC,7,30,46
The D3.js bar chart should display the sum of each column "S40201", "S40202", "S40203" as bar with the corresponding label on the X-axis. Label should be the column name (first row).
<script>
// Set the margins
var margin = {top: 60, right: 100, bottom: 20, left: 80},
width = 850 - margin.left - margin.right,
height = 370 - margin.top - margin.bottom;
// Parse the month variable
var parseMonth = d3.timeParse("%b");
var formatMonth = d3.timeFormat("%b");
// Set the ranges
var x = d3.scaleBand().rangeRound([0, width]).padding(0.1)
var y = d3.scaleLinear().range([height, 0]);
// Create the svg canvas in the "graph" div
var svg = d3.select("#graph")
.append("svg")
.style("width", width + margin.left + margin.right + "px")
.style("height", height + margin.top + margin.bottom + "px")
.attr("width", width + margin.left + margin.right)
.attr("height", height + margin.top + margin.bottom)
.append("g")
.attr("transform","translate(" + margin.left + "," + margin.top + ")")
.attr("class", "svg");
// Import the CSV data
d3.csv("data.csv", function(error, data) {
if (error) throw error;
// Format the data
data.forEach(function(d) {
d.Month = parseMonth(d.Month);
d.S40201 = +d.S40201;
d.S40202 = +d.S40202;
d.S40203 = +d.S40203;
});
var nest = d3.nest()
.key(function(d){
return d.S40201,d.S40202,d.S40203;
})
.sortKeys(d3.ascending)
.rollup(function(leaves){
return d3.sum(leaves, function(d) {return (d.S40201,d.S40203,d.S40203)});
})
.entries(data)
console.log(nest)
// Scale the range of the data
x.domain(nest.map(function(d) { return d.key; }));
y.domain([0, d3.max(nest, function(d) { return d.value; })]);
// Set up the x axis
var xaxis = svg.append("g")
.attr("transform", "translate(0," + height + ")")
.attr("class", "x axis")
.call(d3.axisBottom(x)
//.ticks(d3.timeMonth)
.tickSize(0, 0)
//.tickFormat(d3.timeFormat("%B"))
.tickSizeInner(0)
.tickPadding(10));
// Add the Y Axis
var yaxis = svg.append("g")
.attr("class", "y axis")
.call(d3.axisLeft(y)
.ticks(5)
.tickSizeInner(0)
.tickPadding(6)
.tickSize(0, 0));
// yaxis.select(".domain").style("display","none")
// Add a label to the y axis
svg.append("text")
.attr("transform", "rotate(-90)")
.attr("y", 0 - 60)
.attr("x", 0 - (height / 2))
.attr("dy", "1em")
.style("text-anchor", "middle")
.text("Annual Sales")
.attr("class", "y axis label");
// Draw the bars
svg.selectAll(".rect")
.data(nest)
.enter()
.append("rect")
.attr("class", "bar")
.attr("x", function(d) { return x(d.key); })
.attr("y", function(d) { return y(d.value); })
.attr("width", x.bandwidth())
.attr("height", function(d) { return height - y(d.value); });
})
</script>
With just one column it works fine, but If I want to add more than one column it doesn´t work correctly.
Welcome to StackOverflow. The problem you face is your nested data is not the way you like. If you console log your nested data the way you have it, the key and the value are both the same and not the headers.
Instead if you manually summarize the data the way you like it would be easier. For example:
var nest = [];//create empty array
var keys = Object.keys(data[0]); //get the headers for your data
//for each header push the sum as an object
keys.forEach(function (d, i) {
if (i == 0) return;//ignore first column as that is month
//get the sumfrom your data for all the values of this key i.e. d
var sum = d3.sum (data, function(e){ return e[d] });
//create an object with this key value pair
var obj = {
key: d, //column name
value: sum //sum for the column
}
nest.push(obj); //push this as an object in the nest array
})
Here is a block with the correct code showing the headers as the labels on the x-axis and the sum values.
https://bl.ocks.org/akulmehta/724d63f0108304ede84a14fc145aad28
Please feel free to comment if you need more explanation /guidance and please remember to mark it as an answer if this answers your question.
Update 1- For selected columns only
Based on the comments below, if you want selected columns only simply replace the var keys = Object.keys(data[0]); with an array of headers like var keys = ['S40201','S40202','S40203'] and also remove the line if (i == 0) return;//ignore first column as that is month as we do not have the month's column anymore. Final code for this part should look like this:
var nest = [];//create empty array
var keys = ['S40201','S40202','S40203']; //the headers for your data
//for each header push the sum as an object
keys.forEach(function (d, i) {
//get the sumfrom your data for all the values of this key i.e. d
var sum = d3.sum (data, function(e){ return e[d] });
//create an object with this key value pair
var obj = {
key: d, //column name
value: sum //sum for the column
}
nest.push(obj); //push this as an object in the nest array
})
And here is the block to demonstrate it works. Notice the CSV file has an additional column but it the graph ignores this. https://bl.ocks.org/akulmehta/724d63f0108304ede84a14fc145aad28
I am trying to create a simple histogram with values stored in a csv (that I will be modifying through the time).
The code I am using now is: (edited code!)
var values = []
d3.csv('../static/CSV/Chart_data/histogram_sub.csv?rnd='+(new Date).getTime(),function(data){
values = Object.keys(data).map(function(k){ return data[k]['Calculus I']});
var color = "steelblue";
// Generate a 1000 data points using normal distribution with mean=20, deviation=5
// A formatter for counts.
var formatCount = d3.format(",.0f");
var margin = {top: 20, right: 30, bottom: 30, left: 30},
width = 800 - margin.left - margin.right,
height = 400 - margin.top - margin.bottom;
var max = d3.max(values);
var min = d3.min(values);
var x = d3.scale.linear()
.domain([min, max])
.range([0, width]);
// Generate a histogram using twenty uniformly-spaced bins.
var data = d3.layout.histogram()
.bins(x.ticks(20))
(values);
var yMax = d3.max(data, function(d){return d.length});
var yMin = d3.min(data, function(d){return d.length});
var colorScale = d3.scale.linear()
.domain([yMin, yMax])
.range([d3.rgb(color).brighter(), d3.rgb(color).darker()]);
var y = d3.scale.linear()
.domain([0, yMax])
.range([height, 0]);
var xAxis = d3.svg.axis()
.scale(x)
.orient("bottom");
var svg = d3.select("#Histogram2").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 bar = svg.selectAll(".bar")
.data(data)
.enter().append("g")
.attr("class", "bar")
.attr("transform", function(d) { return "translate(" + x(d.x) + "," + y(d.y) + ")"; });
bar.append("rect")
.attr("x", 1)
.attr("width", (x(data[0].dx) - x(0)) - 1)
.attr("height", function(d) { return height - y(d.y); })
.attr("fill", function(d) { return colorScale(d.y) });
bar.append("text")
.attr("dy", ".75em")
.attr("y", -12)
.attr("x", (x(data[0].dx) - x(0)) / 2)
.attr("text-anchor", "middle")
.text(function(d) { return formatCount(d.y); });
svg.append("g")
.attr("class", "x axis")
.attr("transform", "translate(0," + height + ")")
.call(xAxis);
});
And my csv file looks like this:
Calculus I
5.0
5.1
5.7
...
And I am getting errors that I think refer to data[0]:
Uncaught TypeError: Cannot read property 'dx' of undefined
Any help? Thanks in advance!
Here's a plunkr using d3.csv and fetching data from the file:
http://plnkr.co/edit/2xCvrwiXWzrS6gtbmIU7?p=preview
And please go through the docs for d3.csv
Using the same, here are the relevant changes to the code:
Added a new file test.csv with the content.
Fetched the file using d3.csv:
d3.csv("test.csv", parse, function(error, data) {
console.log(data);
});
The parse that you see above is a accessor function that receives every row from the csv and I'm using it to parse the integer value.
function parse(row) {
row['Calculus I'] = +row['Calculus I'];
return row;
}
And as you were assuming values to be array of integers, I'm mapping the fetched data in the same format as desired using map
values = data.map(function(d) { return d['Calculus I']; });
Hope this helps.
What you need to do is first read the csv using d3.csv and then convert it to an array of values
var values = []
d3.csv("**csv file path**",function(data){
//This will internally convert csv to a json and then we can extract all values and transform it into an array
values = Object.keys(data).map(function(k){ return data[k]['Calculus I']});
//If the above code is too complex for you
//for(i in data){
// values.push(data[i]['Calculus I']
//}
});
//Rest of the chart rendering code goes here
I'm tring to create a multi-line chart using D3.js. Here is my sample csv data:
A,B,C,D,E,F,G,date
53831,72169.87,54219,72555,63466,115312,126390,4/26/16
53031,70901.11,5976,5111,62388,111626,123198,7/10/16
51834,69917.12,5449,4902,62990,114296,124833,4/24/16
54637,73016.92,58535,77379,63090,113216,125261,6/14/16
54801,73072.4,57997,75674,63090,113216,125261,6/27/16
53578,71718.19,51085,69152,63370,115061,125949,5/3/16
51679,68897.14,6021,5421,61514,110330,121972,7/24/16
Here is my code snippet. However I keep seeing the error like d is not an expected number(as title shows). Can anyone please point me out?
Also I feel like the way I'm parsing data is ugly (two for loop). Any suggestions are welcome.
// Set the dimensions of the canvas / graph
var margin = {
top: 30,
right: 20,
bottom: 30,
left: 50
},
width = 800 - margin.left - margin.right,
height = 500 - margin.top - margin.bottom;
// Parse the date / time
var parseDate = d3.time.format("%b %Y").parse;
// Set the ranges
var x = d3.time.scale().range([0, width]);
var y = d3.scale.linear().range([height, 0]);
// Define the axes
var xAxis = d3.svg.axis().scale(x)
.orient("bottom");
var yAxis = d3.svg.axis().scale(y)
.orient("left");
var line = d3.svg.line()
.interpolate("basis")
.x(function (d) {
return x(d.date);
})
.y(function (d) {
return y(d.value);
});
// Adds the svg canvas
var svg = d3.select("#d3-line-chart")
.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 + ")");
//get the data
d3.csv("test.csv", function (error, data) {
var res = [];
var cols = d3.keys(data[0])
.filter(function (key) {
return key;
});
for (var j = 0; j < cols.length - 1; j++) {
var col = cols[j];
var row = [];
for (var i = 0; i < data.length; i++) {
row.push({
symbol: col,
date: data[i]["date"],
value: +data[i][col]
});
}
res.push(row);
}
// Scale the range of the data
x.domain(d3.extent(res, function (d) {
return d.date;
}));
y.domain([0, d3.max(res, function (d) {
return d.value;
})]);
svg.selectAll(".line")
.data(res)
.enter().append("path")
.attr("class", "line")
.attr("d", line);
// Add the X Axis
svg.append("g")
.attr("class", "x axis")
.attr("transform", "translate(0," + height + ")")
.call(xAxis);
// Add the Y Axis
svg.append("g")
.attr("class", "y axis")
.call(yAxis);
});
Firstly, provide sorted data in the CSV on the basis of date so:
Instead of this:
A,B,C,D,E,F,G,date
53831,72169.87,54219,72555,63466,115312,126390,4/26/16
53031,70901.11,5976,5111,62388,111626,123198,7/10/16
51834,69917.12,5449,4902,62990,114296,124833,4/24/16
54637,73016.92,58535,77379,63090,113216,125261,6/14/16
54801,73072.4,57997,75674,63090,113216,125261,6/27/16
53578,71718.19,51085,69152,63370,115061,125949,5/3/16
51679,68897.14,6021,5421,61514,110330,121972,7/24/16
Provide sorted CSV:
A,B,C,D,E,F,G,date
51834,69917.12,5449,4902,62990,114296,124833,4/24/16
53831,72169.87,54219,72555,63466,115312,126390,4/26/16
53578,71718.19,51085,69152,63370,115061,125949,5/3/16
54637,73016.92,58535,77379,63090,113216,125261,6/14/16
54801,73072.4,57997,75674,63090,113216,125261,6/27/16
53031,70901.11,5976,5111,62388,111626,123198,7/10/16
51679,68897.14,6021,5421,61514,110330,121972,7/24/16
Secondly:
The date parser you providing is incorrect:
var parseDate = d3.time.format("%b %Y").parse;
Should have been this:
var parseDate = d3.time.format("%m/%d/%Y").parse;
Because your date is in the format ,4/26/16.
Thirdly,
The way you are calculating the x and y domain extent is wrong:
So instead of this:
// Scale the range of the data
x.domain(d3.extent(res, function (d) {
return d.date;
}));
y.domain([0, d3.max(res, function (d) {
return d.value;
})]);
It should have been:
x.domain(d3.extent(data, function (d) {
return parseDate(d.date);
}));
y.domain([0, d3.max(res, function (d) {
return d3.max(d, function(d2){console.log(d2);return d2.value;});
})]);
Reason: the res array you creating is an array inside an array so need that handling in here.
Working code here
I have a new set of data that I want to have updated within the SVG but I'm not really sure how to correctly grab the elements I have replace the data while smoothly transitioning.
http://codepen.io/jacob_johnson/pen/jAkmPG
All relevant code is in the CodePen above; however, I will post some of it here.
// Array to supply graph data
var FOR_PROFIT = [10,80,10];
var NONPROFIT = [60,10,30];
var PUBLIC = [40,40,20];
var data = [
{
"key":"PUBLIC",
"pop1":PUBLIC[0],
"pop2":PUBLIC[1],
"pop3":PUBLIC[2]
},
{
"key":"NONPROFIT",
"pop1":NONPROFIT[0],
"pop2":NONPROFIT[1],
"pop3":NONPROFIT[2]
},
{
"key":"FORPROFIT",
"pop1":FOR_PROFIT[0],
"pop2":FOR_PROFIT[1],
"pop3":FOR_PROFIT[2]
}
];
I have two data arrays (one called data and another called data2 with modified information). I essentially want to transition the created graph into the new data. I understand enough to rewrite the graph over the old graph but I am not getting any transitions and am obviously just printing new data on top of the old instead of modifying what I have.
var n = 3, // Number of layers
m = data.length, // Number of samples per layer
stack = d3.layout.stack(),
labels = data.map(function(d) { return d.key; }),
layers = stack(d3.range(n).map(function(d)
{
var a = [];
for (var i = 0; i < m; ++i)
{
a[i] = { x: i, y: data[i]['pop' + (d+1)] };
}
return a;
})),
// The largest single layer
yGroupMax = d3.max(layers, function(layer) { return d3.max(layer, function(d) { return d.y; }); }),
// The largest stack
yStackMax = d3.max(layers, function(layer) { return d3.max(layer, function(d) { return d.y0 + d.y; }); });
var margin = {top: 40, right: 10, bottom: 20, left: 150},
width = 677 - margin.left - margin.right,
height = 212 - margin.top - margin.bottom;
var y = d3.scale.ordinal()
.domain(d3.range(m))
.rangeRoundBands([2, height], .08);
var x = d3.scale.linear()
.domain([0, yStackMax])
.range([0, width]);
var color = d3.scale.linear()
.domain([0, n - 1])
.range(["#aad", "#556"]);
var svg = d3.select("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 layer = svg.selectAll(".layer")
.data(layers)
.enter().append("g")
.attr("class", "layer")
.style("fill", function(d, i) {
return color(i);
});
layer.selectAll("rect")
.data(function(d) {
return d;
})
.enter().append("rect")
.attr("y", function(d) {
return y(d.x);
})
.attr("x", function(d) {
return x(d.y0);
})
.attr("height", y.rangeBand())
.attr("width", function(d) {
return x(d.y);
});
This is what is handling the creation of my graph with its layers being the individual bars. Now I've seen this: https://bl.ocks.org/mbostock/1134768 but I still don't understand as its not very well commented.
Any guidance, links, or help in this would be amazing. Thanks.
Your Solution: http://codepen.io/typhon/pen/bZLoZW
Hope this helps!!
You can slow down the speed of transition by increasing parameter passed to duration(500) at line 200.(and vice versa)
Read update, enter and exit selections. They are handy almost all the time while working with D3. Take this for reference.
I have a json object that I am trying to visualize with D3.js. I want the x axis to represent the date in the json object which is stored as a string and the y axis to represent sales projections which is also a number in a string i.e "85,000.00"
example of my json object:
[{"Num":78689,"Client":"Health Services" ,"TotalEstSales":"85,000,000.00","Date ":"2/15/2015","RFP Receipt Date":null,"Exp. Proposal Due Date":"3/6/2015","Proposal Submission Date":null,"estAwardDate":"4/15/2015","Procurement Type":"New - Incumbent","Bid Type":"Standalone Contract"}]
and my d3 code:
// Various accessors that specify the four dimensions of data to visualize.
function x(d) { return d.date; }
function y(d) { return d.TotalEstSales; }
function radius(d) { return parseFloat(d.TotalEstSales);}
function color(d) { return d.region; }
function key(d) { return d.Title;}
// Chart dimensions.
var margin = {top: 19.5, right: 19.5, bottom: 19.5, left: 39.5},
width = 960 - margin.right,
height = 500 - margin.top - margin.bottom;
// Various scales. These domains make assumptions of data, naturally.
var xScale = d3.scale.log().domain([300, 1e5]).range([0, width]),
yScale = d3.scale.linear().domain([10000, 85000000]).range([height, 0]),
radiusScale = d3.scale.sqrt().domain([0, 5e8]).range([0, 40]),
colorScale = d3.scale.category10();
// The x & y axes.
var xAxis = d3.svg.axis().orient("bottom").scale(xScale).ticks(12, d3.format(",d")),
yAxis = d3.svg.axis().scale(yScale).orient("left");
// Create the SVG container and set the origin.
var svg = d3.select("#chart").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 + ")");
// Add the x-axis.
svg.append("g")
.attr("class", "x axis")
.attr("transform", "translate(0," + height + ")")
.call(xAxis);
// Add the y-axis.
svg.append("g")
.attr("class", "y axis")
.call(yAxis);
// Add an x-axis label.
svg.append("text")
.attr("class", "x label")
.attr("text-anchor", "end")
.attr("x", width)
.attr("y", height - 6)
.text("Data of RFP");
// Add a y-axis label.
svg.append("text")
.attr("class", "y label")
.attr("text-anchor", "end")
.attr("y", 6)
.attr("dy", ".75em")
.attr("transform", "rotate(-90)")
.text("Award amount");
// Add the year label; the value is set on transition.
var label = svg.append("text")
.attr("class", "year label")
.attr("text-anchor", "end")
.attr("y", height - 24)
.attr("x", width)
.text(2015);
// Load the data.
d3.json("rfpdata.json", function(data) {
// A bisector since many nation's data is sparsely-defined.
// var bisect = d3.bisector(function(d) { return d[0]; });
// Add a dot per nation. Initialize the data at 1800, and set the colors.
var dot = svg.append("g")
.attr("class", "dots")
.selectAll(".dot")
.data(data)
.enter().append("circle")
.attr("class", "dot")
.style("fill", function(d) { return colorScale(color(d)); })
.call(position)
.sort(order);
// Add a title.
dot.append("title")
.text(function(d) { return d.Client; })
// Positions the dots based on data.
function position(dot) {
dot .attr("cx", function(d) { return xScale(x(d)); })
// .attr("cy", function(d) { return yScale(y(d)); })
.attr("r", function(d) { return radiusScale(radius(d)); });
}
// Defines a sort order so that the smallest dots are drawn on top.
function order(a, b) {
return radius(b) - radius(a);
}
// After the transition finishes, you can mouseover to change the year.
function enableInteraction() {
var yearScale = d3.scale.linear()
.domain([1800, 2009])
.range([box.x + 10, box.x + box.width - 10])
.clamp(true);
// Cancel the current transition, if any.
function mouseover() {
label.classed("active", true);
}
function mouseout() {
label.classed("active", false);
}
function mousemove() {
displayYear(yearScale.invert(d3.mouse(this)[0]));
}
}
// this is the function needed to bring in data
// Interpolates the dataset for the given (fractional) year.
function interpolateData(date) {
return data.map(function(d) {
return {
title: d.Title,
client: d.Client,
sales: parseFloat(d.TotalEstSales),
sales: interpolateValues(d.TotalEstSales, date),
};
});
}
// Finds (and possibly interpolates) the value for the specified year.
function interpolateValues(values, date) {
var i = bisect.left(values, date, 0, values.length - 1),
a = values[i];
if (i > 0) {
var b = values[i - 1],
t = (date - a[0]) / (b[0] - a[0]);
return a[1] * (1 - t) + b[1] * t;
}
return a[1];
}
});
I am not sure what I am doing wrong but the data is not displaying? Am i properly parsing the date string? This was a graph available on the d3 site. I want a bubble graph where the radius changes depending on the size of the sale and the date is on the x axis.
#all Update:
I was able to make the proper adjustment for date on the xaxis here:
var xAxis = d3.svg.axis().orient("bottom").scale(xScale).tickFormat(d3.time.format("%m/%d")),
yAxis = d3.svg.axis().scale(yScale).orient("left").ticks(23, d3.format(" ,d"));
d3.time.format was what I was looking for. Once data was loaded I needed to parse the date:
month = data.Date;
parseDate = d3.time.format("%m/%d/%Y").parse;
data.forEach(function(d) {
d.Date = parseDate(d.Date);
});
// update Dates here when new report comes in monthly
xScale.domain([parseDate("1/1/2015"),parseDate("6/1/2015")]);
obviously, using "Date" as a name column in the excel file was not idea for "Date" in js(because it is an oject).