D3 Bollinger Band Issue - javascript

I want to generate bollinger band using D3 library. I tried following code but unfortunately its throwing following error -
VM224 d3.v3.js:670 Error: attribute d: Expected number,
"M0,NaNL890,NaN"
What could be the cause of this error?
Code -
var margin = {
top: 20,
right: 20,
bottom: 30,
left: 50
},
width = 960 - margin.left - margin.right,
height = 500 - margin.top - margin.bottom;
var n = 20; // n-period of moving average
var k = 2; // k times n-period standard deviation above/below moving average
var parseDate = d3.time.format("%m/%d/%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")
.tickSize(3, 0);
var line = d3.svg.line()
.x(function(d) {
return x(d.date);
})
.y(function(d) {
return y(d.close);
});
var ma = d3.svg.line()
.x(function(d) {
return x(d.date);
})
.y(function(d) {
return y(d.ma);
});
var lowBand = d3.svg.line()
.x(function(d) {
return x(d.date);
})
.y(function(d) {
return y(d.low);
});
var highBand = d3.svg.line()
.x(function(d) {
return x(d.date);
})
.y(function(d) {
return y(d.high);
});
var bandsArea = d3.svg.area()
.x(function(d) {
return x(d.date);
})
.y0(function(d) {
return y(d.low);
})
.y1(function(d) {
return y(d.high);
});
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 data = [
[
"9/9/2012",
1000
],
[
"9/14/2013",
100
]
];
data.forEach(function(d) {
d.date = parseDate(d[0]);
d.close = parseInt(d[1]);
var bandsData = getBollingerBands(n, k, data);
x.domain(d3.extent(data, function(d) {
return d.date;
}));
y.domain([d3.min(bandsData, function(d) {
return d.low;
}),
d3.max(bandsData, function(d) {
return d.high;
})
]);
svg.append("g")
.attr("class", "x axis")
.attr("transform", "translate(0," + height + ")")
.call(xAxis);
svg.append("g")
.attr("class", "y axis")
.call(yAxis);
svg.append("path")
.datum(bandsData)
.attr("class", "area bands")
.attr("d", bandsArea);
svg.append("path")
.datum(bandsData)
.attr("class", "line bands")
.attr("d", lowBand);
svg.append("path")
.datum(bandsData)
.attr("class", "line bands")
.attr("d", highBand);
svg.append("path")
.datum(bandsData)
.attr("class", "line ma bands")
.attr("d", ma);
svg.append("path")
.datum(data)
.attr("class", "line")
.attr("d", line);
});
function getBollingerBands(n, k, data) {
var bands = []; //{ ma: 0, low: 0, high: 0 }
for (var i = n - 1, len = data.length; i < len; i++) {
var slice = data.slice(i + 1 - n, i);
var mean = d3.mean(slice, function(d) {
return d.close;
});
var stdDev = Math.sqrt(d3.mean(slice.map(function(d) {
return Math.pow(d.close - mean, 2);
})));
bands.push({
date: data[i].date,
ma: mean,
low: mean - (k * stdDev),
high: mean + (k * stdDev)
});
}
return bands;
}
Fiddle

See working jsfiddle
First problem was, this being Bollinger Bands, that it needed more than the 2 data points you supplied it. When passing n=20 to getBollingerBands() it means it'll want to extract a mean value out of 20 consecutive data points. Since there were only 2, it yielded a blank array, which in turn caused NaN values to make it into the <path>.
I updated that data array to be a whole lot longer, as needed.
The other issue was that instead of this:
data.forEach(function(d) {
d.date = parseDate(d[0]);
d.close = parseInt(d[1]);
})
you had the function passed to forEach extend all the way down to include all the rendering code (eg svg.append("path")), which meant it was trying to render the whole thing one per data point (i.e. potentially hundreds of times).

Related

D3 Js Burndown chart have error "attribute d: Expected number", I do not know where the error is throwing up

This error is:
not rendering properly
Error: attribute d: Expected number,"M0,NaNL890,NaN"
I am trying to create a D3js Burndown chart with ideal and actual data. but not able to display on the graph. how can i loop through "actual" and display data in y.domain and chart.append("path").datum(actual)
var margin = { top: 20, right: 20, bottom: 30, left: 50 },
width = 960 - margin.left - margin.right,
height = 500 - margin.top - margin.bottom;
var x = d3.time.scale()
.range([0, width]);
var y = d3.scale.linear()
.range([height, 0]);
ideal = [{ date: new Date(getyear, getmonth, getdate), points: noofhours }, { date: new Date(getyear1, getmonth1, getdate1), points: 0 }];
for (var i = 0; i < range.length - 1; i++) {
var validationItem = new Object();
validationItem["date"] = new Date(dayyear, daymonth, daydate);
validationItem["points"] = hours;
LstArray.push(validationItem);
}
actual = [LstArray];
actual.forEach(function (d1) {
d1.points = d1.points;
d1.date = d1.date;
});
var idealLine = d3.svg.line().x(function (d) { return x(d.date); }).y(function (d) { return y(d.points); });
var actualLine = d3.svg.line()
.x(function (d) {
var ar = [];
d.forEach(function (d1) {
//d.date = d.date;
ar.push(d1.date);
});
console.log('qqqq');
console.log(ar);
return ar;
})
.y(function (d) {
var ar = [];
d.forEach(function (d1) {
//d.date = d.date;
ar.push(d1.points);
});
console.log('ttt');
console.log(ar); return ar;
});
x.domain(d3.extent(ideal, function (d) { return d.date; })); // y.domain(d3.extent(d1.points, function (d) { return d.points; })); y.domain(d3.extent(actual, function (d) { return d1.points; }));
var xAxis = d3.svg.axis().scale(x).orient("bottom").tickFormat(d3.time.format("%b %d"));
var yAxis = d3.svg.axis().scale(y).orient("left");
var chart = d3.select("#divBurnDownChart").append("svg")
.attr("class", "chart")
.attr("width", width + margin.left + margin.right)
.attr("height", height + margin.top + margin.bottom)
.append("g").attr("transform", "translate(" + margin.left + "," + margin.top + ")");
chart.append("g").attr("class", "x axis").attr("transform", "translate(0," + height + ")").call(xAxis);
// Create the y-axis
chart.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("Points");
// Paint the ideal line
chart.append("path")
.datum(ideal)
.attr("class", "line ideal")
.attr("d", idealLine);
// Paint the actual line
chart.append("path")
.datum(actual)
.attr("class", "line actual")
.attr("d", actualLine);

"Error: <path> attribute d: Expected number, "MNaN,NaNLNaN,NaNL…". " Error with D3

I'm importing some data from Quandl's API to make a chart of brent oil prices over the years. I'm serving the data from a HTTP-request made with Angular. Somehow the data being served is not being read as numbers, since i get the following error:
Error: attribute d: Expected number, "MNaN,NaNLNaN,NaNL…".
app.service('oilService', function($http) {
delete $http.defaults.headers.common['X-Requested-With'];
this.getData = function() {
var URL = "https://www.quandl.com/api/v1/datasets/CHRIS/ICE_B1.json";
return $http({
method: 'GET',
url: URL
})
}
})
.directive('oilGraph', function() {
return {
scope: {},
controller: function(oilService) {
var width = 200;
var height = 100;
var parseTime = d3.timeParse("%y-%m-%d");
var x = d3.scaleTime().range([0, width]);
var y = d3.scaleLinear().range([height, 0]);
var valueline = d3.line()
.x(function(d) { return x(d.date); })
.y(function(d) { return y(d.settle); });
var svg = d3.select("#oilgraph").append("svg")
.attr("width", width)
.attr("height", height);
oilService.getData()
.then(function(data) {
var mapped_data = data.data.data.map(function(d) {
return {
date: parseTime(d[0]),
settle: d[4]
};
})
x.domain(d3.extent(data, function(d) { return d.date; }));
y.domain([0, d3.max(data, function(d) { return d.settle; })]);
svg.append("path")
.data([mapped_data])
.attr("class", "line")
.attr("d", valueline);
svg.append("g")
.attr("transform", "translate(0," + height + ")")
.call(d3.axisBottom(x));
svg.append("g")
.call(d3.axisLeft(y));
})
})
}
}
})
Your problem is just a wrong specifier. Since your dates have the century...
"2017-06-16"
... you should use %Y instead of %y. According to the API:
%Y - year with century as a decimal number.
Thus, this should be your specifier:
var parseTime = d3.timeParse("%Y-%m-%d");
Besides that, you have to use mapped_data in the scales, not data.
Here is your code with that changes:
var width = 500;
var height = 200;
var parseTime = d3.timeParse("%Y-%m-%d");
var x = d3.scaleTime().range([0, width]);
var y = d3.scaleLinear().range([height, 0]);
var valueline = d3.line()
.x(function(d) {
return x(d.date);
})
.y(function(d) {
return y(d.settle);
});
var svg = d3.select("body").append("svg")
.attr("width", width)
.attr("height", height);
d3.json("https://www.quandl.com/api/v1/datasets/CHRIS/ICE_B1.json", function(data) {
var mapped_data = data.data.map(function(d) {
return {
date: parseTime(d[0]),
settle: d[4]
};
});
x.domain(d3.extent(mapped_data, function(d) {
return d.date;
}));
y.domain([0, d3.max(mapped_data, function(d) {
return d.settle;
})]);
svg.append("path")
.data([mapped_data])
.attr("class", "line")
.attr("d", valueline);
svg.append("g")
.attr("transform", "translate(0," + height + ")")
.call(d3.axisBottom(x));
svg.append("g")
.call(d3.axisLeft(y));
})
path {
fill: none;
stroke: black;
}
<script src="https://d3js.org/d3.v4.min.js"></script>
As far as i can see, in your code at
svg.append("path")
.data([mapped_data])
.attr("class", "line")
.attr("d", valueline);`
.attr("d", valueline);, "valueline is giving you nan", either you want to use property of value line. i.e x and y property of it you setting it in above of your code.

How to append path from data inside nested structure in d3.js?

I am new using d3.js. I like to represent a multiline chart. In the x axis the years, in the y axis the quantity of patents every country has. Every line should be a different country.
I have a csv file with the following information:
PATENT,GYEAR,GDATE,APPYEAR,COUNTRY
3070801,1963,1096,,"BE"
3070802,1963,1096,,"US"
3070802,1963,1096,,"US"
3070802,1963,1096,,"US"
3070802,1963,1096,,"US"
3070803,1964,1096,,"US"
3070804,1964,1096,,"US"
3070801,1964,1096,,"BE"
3070801,1964,1096,,"BE"
3070801,1964,1096,,"BE"
3070801,1964,1096,,"BE"
3070801,1964,1096,,"BE"
3070801,1964,1096,,"BE"
...
I use a nested structure like the following:
var countries = d3.nest()
.key(function(d) { return d.COUNTRY; })
.key(function(d) { return d.GYEAR; })
.rollup(function(v) { return { "total": v.length} })
.map(data);
This structure give me the information in the following way:
BE: Object
1963: Object
total: 1
1964: Object
total: 6
US: Object
1963: Object
total: 4
1964: Object
total: 2
My script:
<script>
var margin = {top: 20, right: 80, bottom: 30, left: 50},
width = 960 - margin.left - margin.right,
height = 500 - margin.top - margin.bottom;
var parseDate = d3.time.format("%Y").parse;
var x = d3.time.scale()
.range([0, width]);
var y = d3.scale.linear()
.range([height, 0]);
var color = d3.scale.category10();
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) {...}) //not so sure how to obtain the x and y
.y(function(d) {... });
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("data3", function(error, data) {
if (error) throw error;
color.domain(d3.keys(data[0]).filter(function (key) {
return key !== "GYEAR";
}));
data.forEach(function(d) {
d.GYEAR = parseDate(String(d.GYEAR));
});
var countries = d3.nest()
.key(function(d) { return d.COUNTRY; })
.key(function(d) { return d.GYEAR; })
.rollup(function(v) { return { "total": v.length} })
.map(data);
x.domain(d3.extent(data, function (d) {
return d.GYEAR;
}));
y.domain([0,10]);
svg.append("g")
.attr("class", "x axis")
.attr("transform", "translate(0," + height + ")")
.style({'stroke': 'Black', 'fill': 'none', 'stroke-width': '1.5px'})
.call(xAxis);
svg.append("g")
.attr("class", "y axis")
.style({'stroke': 'Black', 'fill': 'none', 'stroke-width': '1.5px'})
.call(yAxis)
.append("text")
.attr("transform", "rotate(-90)")
.attr("y", 6)
.attr("dy", ".71em")
.style("text-anchor", "end")
.text("Cantidad");
var city = svg.selectAll(".country")
.data(countries)
.enter().append("g")
.attr("class", "country");
//How to append path?
city.append("path")
.attr("class", "line")
.attr("d", function(d) {...})
.style("stroke", function(d) {...});
});
</script>
How can I construct my var line? How can I append the path?
Thanks in advance.
For generating line function you can do:
var line = d3.svg.line()
.interpolate("basis")
.x(function(d) {console.log(x(d.year));return x(d.year)}) // x will be year
.y(function(d) {console.log(y(d.total));return y(d.total)}); //y will be total
For rollup function get all the data:
var countries = d3.nest()
.key(function(d) { return d.COUNTRY; })
.key(function(d) { return d.GYEAR.getFullYear(); })
.rollup(function(v){
var pat = 0;
var yr = 0;
var country = ""
v.forEach(function(r){
pat += parseInt(r.PATENT);
yr = r.GYEAR;
country = r.COUNTRY
});
//store all the info like patent year country
return { "total": v.length, patent:pat, year:yr, country: country}
})
.map(data);
For setting values for Line:
var city = svg.selectAll(".country")
.data([countries.BE, countries.US])//this will genrate 2 lines for the two datset.
.enter().append("g")
.attr("class", "country");
//How to append path?
city.append("path")
.attr("class", "line")
.attr("d", function(d) {
var d_array = []
//for all the years make it an array
for (key in d) {
d_array.push(d[key])
}
return line(d_array);
})
.style("stroke", function(d) {
var country = "";
//the first object will tell the country
for (key in d) {
country = d[key].country;
break;
}
return color(country)
});
EDIT
For putting the countries in general form:
Instead of doing this:
.data([countries.BE, countries.US]),
Do this to get the array:
var country_data = [];
for (key in countries) {
country_data.push(countries[key])
}
and later do
.data(country_data),
Working code here
Cleaner code here
Hope this helps!

Ideas for distinct layering of stacked graph in D3.js

i have data for the first and following attempt of seeking asylum in germany for refugees and therefore two graphs which are looking like this
First one
Second one
Transition from one to another shoulnd't be a problem i suppose but what I want is so show both of them in one graph too. So a combination of both datasets for each country. But is it possible to distinguish from one another if I combine countries and the first and following ("erst/folge")? Each country should have on layer divided by two, one is the first attempt the other is the second attempt of this country. One idea to differ those sub-layers is maybe custom coloscale which I try right now. How can I show both data in one graph? Is it even possible to join both datasets and still see the difference?
Here is my html:
<!DOCTYPE html>
<meta charset="utf-8">
<style>
body {
font: 10px sans-serif;
}
.chart {
background: #fff;
}
p {
font: 12px helvetica;
}
.axis path, .axis line {
fill: none;
stroke: #000;
stroke-width: 2px;
shape-rendering: crispEdges;
}
button {
position: absolute;
right: 50px;
top: 10px;
}
</style>
<body>
<script src="http://d3js.org/d3.v2.js"></script>
<div id="option">
<input name="updateButton"
type="button"
value="Show Data for second Attemp"
onclick="updateSecond('folgeantraege_monatlich_2015_mitentscheidungenbisnovember.csv')" />
</div>
<div id="option">
<input name="updateButton"
type="button"
value="Show Data for first Attemp"
onclick="updateFirst('erstantraege_monatlich_2015_mitentscheidungenbisnovember.csv')" />
</div>
<div id="option">
<input name="updateButton"
type="button"
value="Show both"
onclick="updateBoth('erstantraege_monatlich_2015_mitentscheidungenbisnovember.csv', 'folgeantraege_monatlich_2015_mitentscheidungenbisnovember.csv')" />
</div>
<div class="chart">
</div>
<script>
var margin = {top: 20, right: 40, bottom: 30, left: 30};
var width = document.body.clientWidth - margin.left - margin.right;
var height = 400 - margin.top - margin.bottom;
var x = d3.time.scale()
.range([0, width]);
var y = d3.scale.linear()
.range([height-10, 0]);
var dateParser = d3.time.format("%Y-%m-%d").parse;
var stack = d3.layout.stack()
.offset("zero")
.values(function(d) { return d.values; })
.x(function(d) { return d.date; })
.y(function(d) { return d.value; });
var area = d3.svg.area()
.interpolate("cardinal")
.x(function(d) { return x(d.date); })
.y0(function(d) { return y(d.y0); })
.y1(function(d) { return y(d.y0 + d.y); });
var z = d3.scale.category20()
chart("erstantraege_monatlich_2015_mitentscheidungenbisnovember.csv");
var datearray = [];
var colorrange = [];
function chart(csvpath) {
// var dateParser = d3.time.format("%Y-%m-%d").parse;
// var margin = {top: 20, right: 40, bottom: 30, left: 30};
// var width = document.body.clientWidth - margin.left - margin.right;
// var height = 400 - margin.top - margin.bottom;
// var x = d3.time.scale()
// .range([0, width]);
//
// var y = d3.scale.linear()
// .range([height-10, 0]);
// var z = d3.scale.category20()
//var color = d3.scale.category10()
var xAxis = d3.svg.axis()
.scale(x)
.orient("bottom")
.ticks(d3.time.months);
var yAxis = d3.svg.axis()
.scale(y);
var yAxisr = d3.svg.axis()
.scale(y);
// var stack = d3.layout.stack()
// .offset("zero")
// .values(function(d) { return d.values; })
// .x(function(d) { return d.date; })
// .y(function(d) { return d.value; });
var nest = d3.nest()
.key(function(d) { return d.Land});
// var nestFiltered = nest.filter(function(d){
// return d.Land != 'Total';
// })
// var area = d3.svg.area()
// .interpolate("cardinal")
// .x(function(d) { return x(d.date); })
// .y0(function(d) { return y(d.y0); })
// .y1(function(d) { return y(d.y0 + d.y); });
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 + ")");
d3.csv(csvpath, function(data) {
data.forEach(function(d) {
d.date = dateParser(d.Datum);
d.value = +d.ErstanträgeZahl;
});
//onsole.log(data);
var layers = stack(nest.entries(data));
console.log(nest.entries(data));
x.domain(d3.extent(data, function(d) { return d.date; }));
y.domain([0, d3.max(data, function(d) { return d.y0 + d.y; })]);
svg.selectAll(".layer")
.data(layers)
.enter().append("path")
.attr("class", "layer")
.attr("d", function(d) { return area(d.values); })
.style("fill", function(d, i) { return z(i); });
svg.append("g")
.attr("class", "x axis")
.attr("transform", "translate(0," + height + ")")
.call(xAxis);
svg.append("g")
.attr("class", "y axis")
.attr("transform", "translate(" + width + ", 0)")
.call(yAxis.orient("right"));
svg.append("g")
.attr("class", "y axis")
.call(yAxis.orient("left"));
});
}
function updateSecond(csvpath) {
var nest = d3.nest()
.key(function(d) { return d.Land});
d3.csv(csvpath, function(data) {
data.forEach(function(d) {
d.date = dateParser(d.Datum);
d.value = +d.Summe;
console.log(d.date);
console.log(d.value);
});
var layers = stack(nest.entries(data));
x.domain(d3.extent(data, function(d) { return d.date; }));
y.domain([0, d3.max(data, function(d) { return d.y0 + d.y; })]);
d3.selectAll("path")
.data(layers)
.transition()
.duration(750)
.style("fill", function(d, i) { return z(i); })
.attr("d", function(d) { return area(d.values); });
svg.select(".y.axis") // change the y axis
.duration(750)
.call(yAxis);
});
}
function updateFirst(csvpath) {
var nest = d3.nest()
.key(function(d) { return d.Land});
d3.csv(csvpath, function(data) {
data.forEach(function(d) {
d.date = dateParser(d.Datum);
d.value = +d.ErstanträgeZahl;
console.log(d.date);
console.log(d.value);
});
var layers = stack(nest.entries(data));
x.domain(d3.extent(data, function(d) { return d.date; }));
y.domain([0, d3.max(data, function(d) { return d.y0 + d.y; })]);
d3.selectAll("path")
.data(layers)
.transition()
.duration(750)
.style("fill", function(d, i) { return z(i); })
.attr("d", function(d) { return area(d.values); });
svg.select(".y.axis") // change the y axis
.duration(750)
.call(yAxis);
});
}
function updateBoth(csvpathFirst, csvpathSecond){
var nest = d3.nest()
.key(function(d) { return d.Land});
d3.csv(csvpathFirst, function(data1) {
d3.csv(csvpathSecond, function(data2) {
});
});
}
</script>
And my data is here at my repo
EDIT1:
For example csv1 contains
Datum,Land,Summe,Position,Antragsart,EntscheidungenInsgesamt,Asylberechtigt,Flüchtling,GewährungVonSubsidiäremSchutz,Abschiebungsverbot,UnbegrenzteAblehnungen,Ablehnung,sonstigeVerfahrenserledigungen
2015-01-01,Afghanistan,1129,5,Erst,418,0,105,4,58,66,6,179
2015-02-01,Afghanistan,969,5,Erst,849,9,186,16,100,131,10,397
2015-03-01,Afghanistan,885,5,Erst,1376,17,309,58,158,201,11,622
2015-04-01,Afghanistan,1119,6,Erst,1838,21,384,75,202,261,15,880
2015-05-01,Afghanistan,1151,6,Erst,2272,21,499,91,249,303,16,1093
2015-06-01,Afghanistan,2051,6,Erst,2911,23,683,132,313,377,19,1364
2015-07-01,Afghanistan,2104,6,Erst,3340,27,767,160,366,431,21,1568
2015-08-01,Afghanistan,2270,5,Erst,3660,28,922,172,409,453,23,1653
2015-09-01,Afghanistan,2724,4,Erst,4057,36,1049,201,455,475,26,1815
2015-10-01,Afghanistan,3770,4,Erst,4540,37,1188,234,516,538,29,1998
2015-11-01,Afghanistan,4929,0,Erst,5026,46,1340,253,620,623,49,2095
And csv2 contains
Datum,Antragsart,Land,Summe,Position,Datum2,Position,Herkunft,Entscheidungeninsgesamt,Asylberechtigt,Prozent,Flüchtling,Pronzent,GewährungvonsubisdiäremSchutz,Prozent,Abschiebungsverbot,Prozent,UnbegrenzteAblehnungen,Prozent,Ablehnung,Prozent,keinweiteresverfahren,Prozent,sonstigeVerfahrenserledigungen,Prozent
2015-01-01,Folge,Afghanistan,33,10,2015-01-01,10,Afghanistan,29,0,0,5,17.2,2,6.9,8,27.6,0,0,0,0,1,3.4,13,44.8
2015-02-01,Folge,Afghanistan,29,10,2015-02-01,10,Afghanistan,81,0,0,13,16,4,4.9,22,27.2,0,0,0,0,10,12.3,32,39.5
2015-03-01,Folge,Afghanistan,41,9,2015-03-01,9,Afghanistan,135,0,0,21,15.6,10,7.4,37,27.4,1,0.7,0,0,23,17,43,31.9
2015-04-01,Folge,Afghanistan,25,10,2015-04-01,10,Afghanistan,165,0,0,34,20.6,12,7.3,41,24.8,4,2.4,0,0,30,18.2,44,26.7
2015-05-01,Folge,Afghanistan,37,9,2015-05-01,9,Afghanistan,212,0,0,54,25.5,12,5.7,50,23.6,4,1.9,0,0,32,15.1,60,28.3
2015-06-01,Folge,Afghanistan,35,9,2015-06-01,9,Afghanistan,261,0,0,72,27.6,17,6.5,59,22.6,6,2.3,0,0,35,13.4,72,27.6
2015-07-01,Folge,Afghanistan,35,9,2015-07-01,9,Afghanistan,288,0,0,82,28.5,17,5.9,64,22.2,6,2.1,0,0,42,14.6,77,26.7
2015-08-01,Folge,Afghanistan,34,9,2015-08-01,9,Afghanistan,321,0,0,100,31.2,20,6.2,66,20.6,6,1.9,0,0,52,16.2,77,24
2015-09-01,Folge,Afghanistan,27,4,2015-09-01,9,Afghanistan,354,0,0,120,33.9,20,5.6,72,20.3,7,2,0,0,54,15.3,81,22.9
2015-10-01,Folge,Afghanistan,24,9,2015-10-01,9,Afghanistan,389,0,0,136,35,20,5.1,83,21.3,7,1.8,0,0,54,13.9,89,22.9
2015-11-01,Folge,Afghanistan,47,,,,,431,1,0.2,148,34.3,23,5.3,97,22.5,8,1.9,0,0,58,13.5,96,22.3
The values ("Summe") should sum up the values per month of each country (Afghanistan) but also should reflect the values for their stacks on their own (Right now I'm trying to figur out how to use texture.js and custom scales to use textures to distinguish the colors from another because every country should have it's own color in this graph but as I already mentioned they should be different in their sublayers
When I try to put both csv fiels in one file I get not exactly but something similar to this
Can you give me some tips how to archive sub-layers (data structure/algorithm or what i takes to achieve this) so I can proceed and try to implement textures?
Thanks in advance
Final EDIT as answer to Cyrils :
var margin = {top: 20, right: 40, bottom: 30, left: 30};
var width = document.body.clientWidth - margin.left - margin.right;
var height = 400 - margin.top - margin.bottom;
var x = d3.time.scale()
.range([0, width]);
var y = d3.scale.linear()
.range([height-10, 0]);
var dateParser = d3.time.format("%Y-%m-%d").parse;
var stack = d3.layout.stack()
.offset("zero")
.values(function(d) { return d.values; })
.x(function(d) { return d.graphDate; })
.y(function(d) { return d.value; });
var area = d3.svg.area()
.interpolate("cardinal")
.x(function(d) { return x(d.graphDate); })
.y0(function(d) { return y(d.y0); })
.y1(function(d) { return y(d.y0 + d.y); });
var z = d3.scale.category20()
doInit();
updateFirst('data/all.csv');
function doInit(){
//make the svg and axis
xAxis = d3.svg.axis()
.scale(x)
.orient("bottom")
.ticks(d3.time.months);
yAxis = d3.svg.axis()
.scale(y);
yAxisr = d3.svg.axis()
.scale(y);
//make svg
var graph = 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 + ")");
graph.append("g")
.attr("class", "x axis")
.attr("transform", "translate(0," + height + ")")
.call(xAxis);
graph.append("g")
.attr("class", "y axis yright")
.attr("transform", "translate(" + width + ", 0)")
.call(yAxis.orient("right"));
graph.append("g")
.attr("class", "y axis yleft")
.call(yAxis.orient("left"));
}
function updateFirst(csvpath) {
var nest = d3.nest()
.key(function(d) { return d.Land+ "-" + d.Antragsart});
//console.log(nest);
d3.csv(csvpath, function(data) {
data.forEach(function(d) {
//console.log(data);
d.graphDate = dateParser(d.Datum);
d.value = +d.Summe;
d.type= d.Antragsart;
});
var layers = stack(nest.entries(data)).sort(function(a,b){return d3.ascending(a.key, b.key)});
console.log(layers);
x.domain(d3.extent(data, function(d) { return d.graphDate; }));
y.domain([0, d3.max(data, function(d) { return d.y0 + d.y; })]);
var k = d3.select("g .x")
.call(xAxis);
d3.select("g .yright")
.call(yAxis);
d3.select("g .yleft")
.call(yAxis);
d3.selectAll("defs").remove();
d3.select(".chart svg g").selectAll("path").remove();
d3.select(".chart svg g").selectAll("path")
.data(layers).enter().append("path")
//.style("fill", function(d, i) { console.log(d.key);return z(d.key); })
.attr("class", function(d){
var country = d.key.split("-")[0];
var src = d.key.split("-")[1];
return src;
})
.style("fill", function(d){
var country = d.key.split("-")[0];
var src = d.key.split("-")[1];
if (src === "Folge"){
var t = textures.lines().thicker(2).stroke(z(country));
d3.select(".chart").select("svg").call(t);
return t.url();
} else {
return z(country);
}
})
.attr("d", function(d) { return area(d.values); });
});
}
For merging the records I am making use of d3 queue..Read here
The purpose of this is to load 2 CSV via ajax and when both loaded calls the callback.
queue()
.defer(d3.csv, csvpathFirst) //using queue so that callback is called after loading both CSVs
.defer(d3.csv, csvpathSecond)
.await(makeMyChart);
function makeMyChart(error, first, second) {
var data = [];
Make the nested function based on country and csv
var nest = d3.nest()
.key(function(d) {
return d.Land + "-" + d.src; //d.src is first if first csv second if vice versa
});
Next I am merging the records like this:
//iterate first
first.forEach(function(d) {
d.graphDate = dateParser(d.Datum);
d.value = +d.Summe;
d.src = "first"
data.push(d)
});
//iterate second
second.forEach(function(d) {
d.graphDate = dateParser(d.Datum);
d.value = +d.Summe;
d.src = "second"
data.push(d)
});
//sort layers on basis of country
var layers = stack(nest.entries(data)).sort(function(a, b) {
return d3.ascending(a.key, b.key)
});
Regenerate the axis like this:
//regenerate the axis with new domains
var k = d3.select("g .x")
.call(xAxis);
d3.select("g .yright")
.call(yAxis);
d3.select("g .yleft")
.call(yAxis);
Remove all old paths and defs DOM like this:
d3.selectAll("defs").remove();
d3.select(".chart svg g").selectAll("path").remove();
Next based on country and first csv and second csv add style fill.
.style("fill", function(d) {
var country = d.key.split("-")[0];
var src = d.key.split("-")[1];
if (src === "first") {
//use texture.js for pattern
var t = textures.lines().thicker().stroke(z(country));
d3.select(".chart").select("svg").call(t);
return t.url();
} else {
return z(country);
}
})
Working code here
Hope this helps!

Having problems panning and zooming data received by d3.json in a line chart

I'm trying to build this trend component that is able to zoom and pan in data fetched with d3.json. First off, here's my code:
<script>
var margin = { top: 20, right: 80, bottom: 20, left: 50 },
width = $("#trendcontainer").width() - margin.left - margin.right,
height = 500 - margin.top - margin.bottom;
var format = d3.time.format("%Y-%m-%d").parse;
var x = d3.time.scale()
.domain([-width / 2, width / 2])
.range([0, width]);
var y = d3.scale.linear()
.domain([-height / 2, height / 2])
.range([height, 0]);
var color = d3.scale.category10();
var xAxis = d3.svg.axis()
.scale(x)
.orient("bottom")
.tickSize(-height);
var yAxis = d3.svg.axis()
.scale(y)
.orient("left")
.ticks(5)
.tickSize(-width);
var zoom = d3.behavior.zoom()
.x(x)
.y(y)
.scaleExtent([1, 10])
.on("zoom", zoomed);
var line = d3.svg.line()
.interpolate("basis")
.x(function(d) {
return x(d.date);
})
.y(function(d) {
return y(d.value);
});
var svg = d3.select(".panel-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 + ")")
.call(zoom);
d3.json('#Url.Action("DataBlob", "Trend", new {id = Model.Unit.UnitId, runId = 5})', function(error, json) {
$('#processing').hide();
color.domain(d3.keys(json[0]).filter(function(key) {
return key !== "Time" && key !== "Id";
}));
data.forEach(function(d) { var date = new Date(parseInt(d.Time.substr(6))); d.Time = date; });
var instruments = color.domain().map(function(name) {
return {
name: name,
values: data.map(function(d) {
return {
date: d.Time,
value: +d[name]
};
})
};
});
x.domain(d3.extent(data, function(d) { return d.Time; }));
y.domain([
d3.min(instruments, function(c) {
return d3.min(c.values, function(v) {
return v.value;
});
}),
d3.max(instruments, function(c) {
return d3.max(c.values, function(v) {
return v.value;
});
})
]);
svg.append("rect")
.attr("width", width)
.attr("height", height);
svg.append("g")
.attr("class", "x axis")
.attr("transform", "translate(0," + height + ")")
.call(xAxis);
svg.append("g")
.attr("class", "y axis")
.call(yAxis);
var instrument = svg.selectAll(".instrument")
.data(instruments)
.enter().append("g")
.attr("class", "instrument");
instrument.append("path")
.attr("class", "line")
.attr("d", function(d) {
return line(d.values);
})
.style("stroke", function(d) { return color(d.name); });
instrument.append("text")
.datum(function(d) {
return {
name: d.name,
value: d.values[d.values.length - 1]
};
})
.attr("transform", function(d) { return "translate(" + x(d.value.date) + "," + y(d.value.value) + ")"; })
.attr("x", 3)
.attr("dy", ".35em")
.text(function(d) { return d.name; });
});
function zoomed() {
svg.select(".x.axis").call(xAxis);
svg.select(".y.axis").call(yAxis);
svg.select(".x.grid")
.call(make_x_axis()
.tickSize(-height, 0, 0)
.tickFormat(""));
svg.select(".y.grid")
.call(make_y_axis()
.tickSize(-width, 0, 0)
.tickFormat(""));
svg.select(".line")
.attr("class", "line")
.attr("d", line);
};
var make_x_axis = function() {
return d3.svg.axis()
.scale(x)
.orient("bottom")
.ticks(5);
};
var make_y_axis = function() {
return d3.svg.axis()
.scale(y)
.orient("left")
.ticks(5);
};
</script>
Problem here being that the zooming / panning does not interact with my lines. The just stay the same, 'below' the zoomable / pannable grid. Also, one of the lines disappear when trying to zoom / pan and my console says the following:
Error: Problem parsing d="" - referring to the following snippet, last line:
function zoomed() {
svg.select(".x.axis").call(xAxis);
svg.select(".y.axis").call(yAxis);
svg.select(".x.grid")
.call(make_x_axis()
.tickSize(-height, 0, 0)
.tickFormat(""));
svg.select(".y.grid")
.call(make_y_axis()
.tickSize(-width, 0, 0)
.tickFormat(""));
svg.select(".line")
.attr("class", "line")
.attr("d", line);
};
Here's the content of my json result from the controller:
[{"Weight":0.0,"Speed":59.9,"Depth":362.24000,"Time":"2014-04-09T10:01:23","Id":0},{"Weight":10.0,"Speed":59.9,"Depth":394.07000,"Time":"2014-04-09T10:01:56","Id":1},{"Weight":971.0,"Speed":70.1,"Depth":425.84650,"Time":"2014-04-09T10:02:28","Id":2},{"Weight":0.0,"Speed":-29.9,"Depth":422.07465,"Time":"2014-04-09T10:03:00","Id":3},{"Weight":1321.0,"Speed":-21.6,"Depth":406.32840,"Time":"2014-04-09T10:03:32","Id":4},{"Weight":-6.0,"Speed":-30.0,"Depth":390.57880,"Time":"2014-04-09T10:04:04","Id":5},{"Weight":3.0,"Speed":59.9,"Depth":404.50380,"Time":"2014-04-09T10:04:36","Id":6},{"Weight":609.0,"Speed":59.9,"Depth":435.79380,"Time":"2014-04-09T10:05:08","Id":7},{"Weight":1.0,"Speed":59.9,"Depth":467.95280,"Time":"2014-04-09T10:05:40","Id":8},{"Weight":-2149.0,"Speed":34.6,"Depth":498.61060,"Time":"2014-04-09T10:06:12","Id":9},{"Weight":2.0,"Speed":59.9,"Depth":529.83060,"Time":"2014-04-09T10:06:44","Id":10}]
Trend looks like this in my view now, but the actual lines don't zoom or pan. Only the overlaying grid (black lines) does;
For simplicitys sake, I've considered just starting over, following the original example found here, but I really struggle with placing json-loaded data into this.
Hopefully, someone can help me figure this out :)
Thanks to Lars, I was finally able to solve this. Ultimately, I had to do some changes to my controller in addition to using this fiddle which assigns the scales to the zoom behaviour after setting the domains, so it returns a json string to my view like this:
string json = JsonConvert.SerializeObject(Model.Trend.ToArray());
return Json(json, JsonRequestBehavior.AllowGet);
This was due to the fact that some errors appeared when zooming / panning on the array returned without being serialized before returned to the view.
Also had to do the following for it to work:
d3.json('#Url.Action("DataBlob", "Trend", new {id = Model.Unit.UnitId, runId = 5})', function(error, tmparray) {
var json = JSON.parse(tmparray);
...
)};
If this step is skipped, my data will not be displayed properly for some reason, being squeezed to the left side of my graph.

Categories