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
Related
Looking at this Histogram chart using d3 example I plugged in my data but it had some strange side effects e.g. after refreshing to a new dataset, some information from the previous dataset i.e. x-axis scale was retained. I tried deleting and appending a new x-axis etc but nothing worked.
This happened due to the fact that my datasets had completely different x-axis ranges and scales. The only way I found to make it work was to select the whole svg element, remove it and re-append everything anew. However, this doesn't make a pleasant transition for the user so I was wondering how can this be improved to make it refreshable using transitions as in the original example even when having datasets with different x-scales and ranges.
This was my last approach which is a bit harsh to the eye:
// delete old
d3.select("#" + divId).select("svg").remove();
// then recreate all new
And this was my refresh attempt (integrated with AngularJS). Note how it has some common initialization and then if the SVG doesn't exist appends everything new otherwise tries to update it. I went bit by bit but can't see why the refresh doesn't remove all the previous dataset information of the x-axis scale:
var divId = $scope.histogramData.divId;
var color = $scope.histogramData.color;
var values = $scope.histogramData.data[$scope.histogramData.selected];
var svg = $scope.histogramData.svg;
// plot common initialization
var margin = {top: 40, right: 20, bottom: 20, left: 20},
width = 450 - margin.left - margin.right,
height = 370 - 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(10))
(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");
// ===================================================================
// If the SVG doesn't exist then adds everything new
// ===================================================================
if (svg === undefined) {
var svg = d3.select("#" + divId)
.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 + ")");
$scope.histogramData.svg = svg;
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); });
var gTitle = svg.append("text")
.attr("x", 0)
.attr("y", 0 - (margin.top / 2))
.attr("text-anchor", "left")
.classed("label", true)
.text($scope.histogramData.spec[selected]);
$scope.histogramData.gTitle = gTitle;
var gAxis = svg.append("g")
.attr("class", "x axis")
.attr("transform", "translate(0," + height + ")")
.call(xAxis);
$scope.histogramData.gAxis = gAxis;
} else {
// ===================================================================
// If the SVG does exist then tries refreshing
// ===================================================================
var bar = svg.selectAll(".bar").data(data);
// remove object with data
bar.exit().remove();
bar.transition()
.duration(1000)
.attr("transform", function(d) { return "translate(" + x(d.x) + "," + y(d.y) + ")"; });
bar.select("rect")
.transition()
.duration(1000)
.attr("height", function(d) { return height - y(d.y); })
.attr("fill", function(d) { return colorScale(d.y) });
bar.select("text")
.transition()
.duration(1000)
.text(function(d) { return formatCount(d.y); });
var gTitle = $scope.histogramData.gTitle;
gTitle.transition()
.duration(1000)
.text($scope.histogramData.spec[selected]);
var gAxis = $scope.histogramData.gAxis;
gAxis.transition()
.duration(1000)
.call(xAxis);
}
I would suggest to keep this d3 code inside one angularJS directive and keep a watch on the json which you are using to plot that graph. As soon as values are changing the directive will be called again and the graph will be plotted. Hope it helps.
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'm very new to d3 and in order to learn I'm trying to manipulate the d3.js line example, the code is below. I'm trying to modify this to use model data that I already have on hand. This data is passed down as a json object. The problem is that I don't know how to manipulate the data to fit what d3 expects. Most of the d3 examples use key-value arrays. I want to use a key array + a value array. For example my data is structured per the example below:
// my data. A name property, with array values and a value property with array values.
// data is the json object returned from the server
var tl = new Object;
tl.date = data[0].fields.date;
tl.close = data[0].fields.close;
console.log(tl);
Here is the structure visually (yes it time format for now):
Now this is different from the data.tsv call which results in key-value pairs in the code below.
The goal is to use my data as is, without having to iterate over my array to preprocess it.
Questions:
1) Are there any built in's to d3 to deal with this situation? For example, if key-values are absolutely necessary in python we could use the zip function to quickly generate a key-value list.
2) Can I use my data as is, or does it have to be turned into key-value pairs?
Below is the line example code.
// javascript/d3 (LINE EXAMPLE)
var margin = {top: 20, right: 20, bottom: 30, left: 50},
width = 640 - margin.left - margin.right,
height = 480 - margin.top - margin.bottom;
var parseDate = d3.time.format("%d-%b-%y").parse;
var x = d3.time.scale()
.range([0, width]);
var y = d3.scale.linear()
.range([height, 0]);
var xAxis = d3.svg.axis()
.scale(x)
.orient("bottom");
var yAxis = d3.svg.axis()
.scale(y)
.orient("left");
var line = d3.svg.line()
.x(function(d) { return x(d.date); })
.y(function(d) { return y(d.close); });
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.tsv("/data.tsv", function(error, data) {
data.forEach(function(d) {
d.date = parseDate(d.date);
d.close = +d.close;
});
x.domain(d3.extent(data, function(d) { return d.date; }));
y.domain(d3.extent(data, function(d) { return d.close; }));
svg.append("g")
.attr("class", "x axis")
.attr("transform", "translate(0," + height + ")")
.call(xAxis);
svg.append("g")
.attr("class", "y axis")
.call(yAxis)
.append("text")
.attr("transform", "rotate(-90)")
.attr("y", 6)
.attr("dy", ".71em")
.style("text-anchor", "end")
.text("Price ($)");
svg.append("path")
.datum(data)
.attr("class", "line")
.attr("d", line);
});
Check out d3's array functions, zip is among them.
Here's a commented version of the original gist working with your data: http://bl.ocks.org/patrickberkeley/9162034
The core of it is this:
// 1) Zip the close value with their corresponding date/time
// Results in an array of arrays:
//
// [[582.13, "02:30:00"], [583.98, "02:45:00"], ...]
//
data = d3.zip(data.close, data.date).map(function(d) {
// 2) Format each close and date/time value so d3 understands what each represents.
close = +d[0];
// If your data source can't be changed at all, I'd rename `date` to `time` here.
date = parseTime(d[1]);
// 3) Return an object for each close and date/time pair.
return {close: close, date: date};
});
You can pass one of your arrays to .data() and use the index to get the respective element from the other:
var line = d3.svg.line()
.x(function(d) { return x(d); })
.y(function(d, i) { return y(tl.close[i]); });
svg.selectAll("path")
.data([tl.date])
.enter().append("path")
.attr("d", line);
You just have to remember to set the domains of the scales you're using with the correct arrays.
I am using D3 for the first time and I have followed the instructions on their site to get to this point. I cannot seem to get the chart to display although there are no exceptions in the JS console in Chrome.
Here's the JS in the header of my page:
<script>
var margin = {
top: 0,
right: 0,
bottom: 10,
left: 0
},
width = 838 - margin.left - margin.right,
height = 300 - margin.top - margin.bottom;
var x = d3.scale.ordinal()
.rangeRoundBands([0, width], .1);
var y = d3.scale.linear()
.range([height, 0]);
var xAxis = d3.svg.axis()
.scale(x)
.orient("bottom");
var yAxis = d3.svg.axis()
.scale(y)
.orient("left");
var chart = d3.select(".day_chart")
.attr("width", width + margin.left + margin.right)
.attr("height", height + margin.top + margin.bottom)
.append("g")
.attr("transform", "translate(" + margin.left + "," + margin.top + ")");
d3.json("http://solarmonitoringaustralia.com.au/myphp/inverterdata.php?var=TEST1&id=C120031", function (error, data) {
data.forEach(function (d) {
d.hour = +d.hour; // coerce to number
d.max_energy = +d.max_energy;
alert(d.hour + " -- " + d.max_energy);
});
x.domain(data.map(function (d) {
return d.hour;
}));
y.domain([0, d3.max(data, function (d) {
return d.max_energy;
})]);
chart.append("g")
.attr("class", "x axis")
.attr("transform", "translate(0," + height + ")")
.call(xAxis);
chart.append("g")
.attr("class", "y axis")
.call(yAxis);
chart.selectAll(".bar")
.data(data)
.enter().append("rect")
.attr("class", "bar")
.attr("x", function (d) {
return x(d.hour);
})
.attr("y", function (d) {
return y(d.max_energy);
})
.attr("height", function (d) {
return height - y(d.max_energy);
})
.attr("width", x.rangeBand());
});
</script>
The code calls in a JSON request to a server side script. Here's the data it returns to the browser when executing the query:
[{"hour":"1","max_energy":"15.969"},{"hour":"2","max_energy":"19.065"},{"hour":"3","max_energy":"21.191"},{"hour":"4","max_energy":"23.151"},{"hour":"5","max_energy":"24.403"},{"hour":"6","max_energy":"25.082"},{"hour":"7","max_energy":"25.494"},{"hour":"8","max_energy":"25.499"},{"hour":"9","max_energy":"4.685"},{"hour":"10","max_energy":"7.309"},{"hour":"11","max_energy":"10.051"},{"hour":"12","max_energy":"13.119"}]
My HTML contains this tag where I want the chart to appear:
<sgv class="day_chart"></svg>
I can see the data passing into the JS above via the "alert" function in the data.forEach(function (d), so the data comes back.
The page loads but it loads with a blank area where the chart would go, and no JS errors in the console. Thanks for any advice!
Well, this one's going to be a face-palm moment.
The only thing wrong was a typo.
<sgv class="day_chart"></svg>
Should of course be
<svg class="day_chart"></svg>
I've got a working version here
http://jsfiddle.net/cZxey/11/
(I also set the library to load D3, and inserted hard-coded data. For working with the real data, just comment that out. Oh, and increase your bottom padding so your tick mark labels don't get cut off.)
--ABR
Is it possible to put D3 code into a function and then call the function?
For example, I am interested in using this histogram code
http://bl.ocks.org/3048450
If I put code in a function and call like
function hist(bin, data) {
//the D3 histogram plotting code
// Generate an Irwin–Hall distribution of 10 random variables.
var values = d3.range(1000).map(d3.random.irwinHall(10));
// A formatter for counts.
var formatCount = d3.format(",.0f");
var margin = {top: 10, right: 30, bottom: 30, left: 30},
width = 960 - margin.left - margin.right,
height = 500 - margin.top - margin.bottom;
var x = d3.scale.linear()
.domain([0, 1])
.range([0, width]);
// Generate a histogram using twenty uniformly-spaced bins.
var data = d3.layout.histogram()
.bins(x.ticks(20))
(values);
var y = d3.scale.linear()
.domain([0, d3.max(data, function(d) { return d.y; })])
.range([height, 0]);
var xAxis = d3.svg.axis()
.scale(x)
.orient("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 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) - 1)
.attr("height", function(d) { return height - y(d.y); });
bar.append("text")
.attr("dy", ".75em")
.attr("y", 6)
.attr("x", x(data[0].dx) / 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);
}
hist(...); //call the function
it doesn't plot. Why is that?
I found the cause of the bug. I misspell "function" as "function"
Where are you including and running that code? If you're including and runningit in the <head> element, then when the script executes the browser will not be aware of the <body> element. d3.select("body") will return an empty selection, and therefore there will be nothing to which to append and svg element.
Try putting the script within the <body> or use a library like jQuery (i.e., $(document).ready()) to ensure that the document has been loaded before executing your script.