Drawing multiple real time lines - javascript

I want to draw multiple real time lines using JSON files. I am basically retrieving the JSON file from a website, getting the time data (duration in seconds), converting them into minutes and pushing them into the data array. This code checks the JSON file for every second.
I want to add as many line as possible. For example, I want to add the average of the elements in data array (average duration) and plot it on the same plane. I tried to add another "line" and "path" variable, however I wasn't able to plot it at the same time.
The data array is an empty array with 44 elements in the beginning, and everytime the code checks the JSON file it replaces those zeroes with the retrieved duration data.
Here is my code to draw only one line.
function graph() {
var n = 43,
duration = 1000,
now = new Date(Date.now() - duration),
count = 0,
data = d3.range(n).map(function() { return 0; });
var margin = {top: 10, right: 20, bottom: 30, left: 60},
width = 1200 - margin.left-margin.right,
height = 460 - margin.top - margin.bottom;
var x = d3.time.scale()
.domain([now - (n - 2) * duration, now - duration])
.range([0, width]);
var y = d3.scale.linear()
.range([height, 0]);
var line = d3.svg.line()
.interpolate("basis")
.x(function(d, i) { return x(now - (n - 1 - i) * duration); })
.y(function(d, i) { return y(d); });
var line2 = d3.svg.line()
.interpolate("basis")
.x(function(d, i) { return x(now - (n - 1 - i) * duration); })
.y(function(d, i) { return y(d); });
var svg = d3.select("body").append("p").append("svg")
.attr("width", width + margin.left + margin.right)
.attr("height", height + margin.top + margin.bottom)
.style("margin-left", -margin.left + "px")
.append("g")
.attr("transform", "translate(" + margin.left + "," + margin.top + ")");
svg.append("defs").append("clipPath")
.attr("id", "clip")
.append("rect")
.attr("width", width)
.attr("height", height);
var axis = svg.append("g")
.attr("class", "x axis")
.attr("transform", "translate( "+margin.left+"," + height + ")")
.call(x.axis = d3.svg.axis().scale(x).orient("bottom"));
var yaxis = svg.append("g")
.attr("class", "y axis")
.attr("transform", "translate(" + margin.left + ",0)")
.call(y.axis = d3.svg.axis().scale(y).orient("left"));
d3.select(".y.axis")
.append("text")
.text("Travel Time (min)")
.attr("text-anchor", "middle")
.attr("transform","rotate( -90, 200, 0)")
.attr("y",-250);
var path = svg.append("g")
.attr("clip-path", "url(#clip)")
.attr("transform", "translate(" + margin.left + ",0)")
.append("path")
.data([data])
.attr("class", "line");
tick();
function tick() {
d3.json("route.json",function(barzo){
var tempdata = barzo.route;
var len = tempdata.realTime;
var lastdata = parseInt(len)/60; //this is the time variable I use.
// update the domains
now = new Date();
x.domain([now - (n - 2) * duration, now - duration]);
y.domain([0, d3.max(data)+5]);
// push the time into the data
data.push(count);
count = lastdata;
// redraw the line
svg.select(".line")
.attr("d", line)
.attr("transform", null);
// slide the x-axis left
axis.transition()
.duration(duration)
.ease("linear")
.call(x.axis);
yaxis.transition()
.duration(duration/10)
.ease("linear")
.call(y.axis);
// slide the line left
path.transition()
.duration(duration)
.ease("linear")
.attr("transform", "translate(" + x(now - (n - 1) * duration) + ")")
.each("end", tick);
// pop the old data point off the front
data.shift();
});
}
};

First, I included another another data array (data2) to push the new data points for the new path:
var n = 43,
duration = 1000,
now = new Date(Date.now() - duration),
count = 0,
data = d3.range(n).map(function() { return 0; });
data2 = d3.range(n).map(function() { return 0; });
Then, I defined another path for the line that uses the points of data2 array.
var path2 = svg.append("g")
.attr("clip-path", "url(#clip)")
.attr("transform", "translate(" + margin.left + ",0)")
.append("path")
.data([data2])
.attr("class", "line2")
In the tick function, I needed to select both of those lines to update them (You can write a function to do the same thing for these steps instead of repeating the same code twice).
// redraw the line
svg.select(".line")
.attr("d", line)
.attr("transform", null);
svg.select(".line2")
.attr("d", line2)
.attr("transform", null);
The same thing for transition and data shift as well
// slide the line left
path.transition()
.duration(duration)
.ease("linear")
.attr("transform", "translate(" + x(now - (n - 1) * duration) + ")");
path2.transition()
.duration(duration)
.ease("linear")
.attr("transform", "translate(" + x(now - (n - 1) * duration) + ")")
.each("end", tick);
// pop the old data point off the front
data.shift();
data2.shift();

Related

D3js Donut chart legend

I want to add a legend to my D3js donut chart, like this post, its supposed to be kind of simple but I can't get it and I don't know what I'm doing wrong, also the console is not throwing any errors, anyone can see the error?
my data comes from a csv and looks like this:
data = [{
value: 30,
key: "Alta"
}, {
value: 37,
key: "Media"
}, {
value: 15,
key: "Moderada"
}, {
value: 8,
key: "Baja"
},
{
value: 13,
key: "Muy baja"
},
]
and this is the part that adds the data to the chart:
var margin = {top: 20, right: 20, bottom: 20, left: 20},
width = 500 - margin.right - margin.left,
height = 500 - margin.top - margin.bottom,
radius = width/2;
var color = d3.scaleOrdinal()
.range(["#B4DC70", "#FEFE2B", "#FE8E2B", "#FE2B2B", "#2B5EFE"]);
// arc generator
var arc = d3.arc()
.outerRadius(radius - 10)
.innerRadius(radius - 70);
// generate pie chart and donut chart
var pie = d3.pie()
.sort(null)
.value(function(d) { return d.value; });
// define the svg for pie chart
var svg = d3.select("body").append("svg")
.attr("width", width)
.attr("height", height)
.append("g")
.attr("transform", "translate(" + width / 2 + "," + height / 2 + ")");
d3.csv("alertas.csv", function(error, data) {
if (error) throw error;
var amenazasCount = d3.nest()
.key(function(d) { return d.TEXTO_AMENAZA; })
.rollup(function(v) { return v.length; })
.entries(data);
amenazasCount.forEach(function(d) {
d.value = +d.value;
});
var g = svg.selectAll(".arc")
.data(pie(amenazasCount))
.enter().append("g")
.attr("class", "arc");
// append path
g.append("path")
.attr("d", arc)
.style("fill", function(d) { return color(d.data.key); });
var legendG = svg.selectAll(".legend")
.data(pie(amenazasCount))
.enter().append("g")
.attr("transform", function(d,i){
return "translate(" + (width - 110) + "," + (i * 15 + 20) + ")";
})
.attr("class", "legend");
legendG.append("rect")
.attr("width", 10)
.attr("height", 10)
.attr("fill", function(d) { return color(d.data.key); });
legendG.append("text")
.text(function(d){
return d.value + " " + d.data.key;
})
.style("font-size", 12)
.attr("y", 10)
.attr("x", 11);
});
The SVG and G elements are not sized correctly or consistently with the margins you had defined, so that legend was positioned too far to the right, and outside of the SVG view.
If you set up your SVG and g elements like this then it will work:
// set a width and height inclusive of the margins
var svg = d3.select("body").append("svg")
.attr("width", width + margin.right + margin.left)
.attr("height", height + margin.top + margin.bottom)
// create a parent g element for everything to be included within
var g = svg.append("g")
.attr("transform", "translate(" + margin.left + "," + margin.top + ")");
// position the pie half of the width and height
var pieG = svg.selectAll(".arc")
.data(pie(data))
.enter().append("g")
.attr("transform", "translate(" + width/2 + "," + height/2 + ")")
.attr("class", "arc");
And then append the legendG to the "g" element:
var legendG = g.selectAll(".legend")
.data(legendData)
.enter()
.append("g")
.attr("transform", function(d,i){
return "translate(" + (width - 60) + "," + (i * 15 + 20) + ")";
})
.attr("class", "legend");

Update selection not working

I'm new to D3 and I am trying to display a simple d3 bar chart that changes which data attribute it is visualizing based on a dropdown menu - the data remains the same and I am displaying the same labels (x-axis) with each dropdown selection, just the labels should transition/change their ordering and the bar values should transition/change based on which attribute they are showing.
When the dropdown menu changes however, the transition (update) selection isn't getting called - it is only called when the chart loads for the first time. Therefore, based on the code, the y-Axis is changing its numerical values, but the heights always remain the same as they are initiated so the bars don't animate at all despite the labels changing.
updateChart(menuSelection) { // called when dropdown selection changes, and initially upon page load with default menuSelection
// I sort the data by the attribute of the dropdown item selected
this.myData.sort(function(a,b){
if(menuSelection == 1) return b.count - a.count;
else if(menuSelection == 2) return b.positiveCount - a.positiveCount;
else return b.negativeCount - a.negativeCount;
});
var m = this.myData.length;
var svg = d3.select(".chart"),
margin = {top: 40, right: 25, bottom: 40, left: 25},
width = +svg.attr("width") - margin.left - margin.right,
height = +svg.attr("height") - margin.top - margin.bottom;
var g = svg.select("g.rectGroup").attr("transform", "translate(" + margin.left + "," + margin.top + ")").attr("class", "rectGroup");
if(g.empty()) {
g = svg.append("g").attr("transform", "translate(" + margin.left + "," + margin.top + ")").attr("class", "rectGroup");
}
var x = d3.scaleBand()
.domain(myData.map(function(d) { return d._id; }))
.range([0, width])
.padding(0.08);
var yMax = d3.max(this.myData, function(d) {
if(this.menuSelection == 1) return d.count;
else if(this.menuSelection == 2) return d.positiveCount;
else return d.negativeCount;
});
var y = d3.scaleLinear()
.domain([0, yMax])
.range([height, 0]);
var xAxis = d3.axisBottom()
.scale(x);
var yAxis = d3.axisLeft()
.scale(y);
var yz = getHeights(m, menuSelection, this.myData); // ARRAY OF HEIGHTS DEPENDING ON MENU DROP DOWN SELECTION
var bars = g.selectAll(".bar")
.data(this.myData, function(d) {
return d._id; // KEY IS ITEM NAME FOR OBJECT CONSTANCY; ALL ITEMS ARE DISPLAYED REGARDLESS OF ATTRIBUTE SELECTED, BUT ORDER/VALUES CHANGE FOR EACH ITEM
})
.enter().append("rect")
.attr("class", "bar")
.attr("height", 0)
.attr("y", height);
bars.transition().duration(700)
.attr("x", function(d) { return x(d._id); })
.attr("width", x.bandwidth())
.attr("y", function(d, i) { return y(yz[i])})
.attr("height", function(d, i) {
return height - y(yz[i])
});
bars.exit().remove();
svg.selectAll(".axis").remove();
var height_to_use = +svg.attr("height") - margin.bottom
var xAxis_g = svg.append("g")
.attr("class", "x axis")
.attr("transform", "translate(" + margin.left + "," + height_to_use + ")")
.call(xAxis)
.selectAll(".tick text")
.call(wrap, x.bandwidth());
svg.append("g")
.attr("class", "y axis")
.attr("transform", "translate(" + margin.left + "," + margin.top + ")")
.call(yAxis);
function getHeights(m, menuSelection, data) {
var values = [];
for (var i = 0; i < m; ++i) {
if(menuSelection == 1) {
values[i] = data[i].count;
} else if(menuSelection == 2) {
values[i] = data[i].positiveCount;
} else {
values[i] = data[i].negativeCount;
}
}
return values;
}
}
Actually, you don't have an update selection in your code.
For having an update selection, break up your "enter" selection, like this:
//this is the update selection
var bars = g.selectAll(".bar")
.data(data, function(d) {
return d._id;
});
//and the remainder is the enter selection
bars.enter().append("rect")
.attr("class", "bar")
.attr("height", 0)
.attr("y", height);
Also, it's worth mentioning that, since you don't have an update selection in your code, this...
bars.exit().remove();
... is useless.

D3 v4 line chart with JSON dates

I've worked on this for a few days now, and I cant really get my line drawn and got some date formating problems I can't crack.
Using this D3 v3 fiddle as inspiration: http://jsfiddle.net/vmvp0zja/ I tried to convert it to D3 v4, but I can't really get my data drawn properly.
I am trying to draw several lines, but I cant even draw one..
Could you take a look and see what I am missing here? Thanks! :)
// JSON data:
var data = [
{"Date":"\/Date(1475272800000)\/","Open":0,"Closed":0},
{"Date":"\/Date(1475359200000)\/","Open":0,"Closed":0},
{"Date":"\/Date(1475445600000)\/","Open":0,"Closed":0},
{"Date":"\/Date(1475532000000)\/","Open":0,"Closed":0},
{"Date":"\/Date(1475618400000)\/","Open":0,"Closed":0},
{"Date":"\/Date(1475704800000)\/","Open":0,"Closed":0},
{"Date":"\/Date(1475791200000)\/","Open":0,"Closed":0},
{"Date":"\/Date(1475877600000)\/","Open":0,"Closed":0},
{"Date":"\/Date(1475964000000)\/","Open":0,"Closed":0},
{"Date":"\/Date(1476050400000)\/","Open":0,"Closed":0},
{"Date":"\/Date(1476136800000)\/","Open":0,"Closed":0},
{"Date":"\/Date(1476223200000)\/","Open":0,"Closed":0},
{"Date":"\/Date(1476309600000)\/","Open":0,"Closed":0},
{"Date":"\/Date(1476396000000)\/","Open":0,"Closed":0},
{"Date":"\/Date(1475445600000)\/","Open":1,"Closed":0},
{"Date":"\/Date(1475532000000)\/","Open":1,"Closed":0},
{"Date":"\/Date(1475618400000)\/","Open":2,"Closed":0},
{"Date":"\/Date(1475791200000)\/","Open":9,"Closed":0},
{"Date":"\/Date(1475964000000)\/","Open":1,"Closed":0},
{"Date":"\/Date(1475445600000)\/","Open":0,"Closed":1},
{"Date":"\/Date(1475532000000)\/","Open":0,"Closed":1},
{"Date":"\/Date(1475618400000)\/","Open":0,"Closed":1},
{"Date":"\/Date(1475964000000)\/","Open":0,"Closed":1}]
This is my D3 mess:
// linechart.js
var formatTime = d3.timeFormat("%Y-%m-%d");
data.forEach(function (d) {
var unixToISO = new Date(d.Date.match(/\d+/)[0]*1);
d.Date = formatTime(unixToISO);
d.Open = +d.Open;
d.Closed = +d.Closed;
console.log(d.Date);
return d;
});
var margin = {top: 30, right: 40, bottom: 30, left: 50 },
width = 600 - margin.left - margin.right,
height = 270 - margin.top - margin.bottom;
var x = d3.scaleTime()
.range([0, width]);
var y0 = d3.scaleLinear()
.range([height, 0]);
// Scale the range of the data
x.domain(d3.extent(data, function (d) { return d.Date; }));
y0.domain([
d3.min(data, function (d) { return Math.min(d.Open); }),
d3.max(data, function (d) { return Math.max(d.Open); })]);
var valueline1 = d3.line()
.x(function (d) { console.log(x(d.Date)); return x(d.Date); })
.y(function (d) { return y(d.Open); });
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 + ")");
svg.append("g") // Add the X Axis
.attr("class", "x axis")
.attr("transform", "translate(0," + height + ")")
.call(d3.axisBottom(x));
svg.append("g")
.attr("class", "y axis")
.style("fill", "steelblue")
.call(d3.axisLeft(y0));
svg.append("path") // Add the valueline path.
.data(data)
.attr("class", "line")
.attr("d", valueline1);
Help much appreciated in advance. I am probably overlooking something obvious.
Thank you!
Changes:
data.forEach(function(d) {
var unixToISO = new Date(d.Date.match(/\d+/)[0] * 1);
d.Date = unixToISO; // here
d.Open = +d.Open;
// (...)
var valueline1 = d3.line()
.x(function(d) {
console.log(x(d.Date));
return x(d.Date);
})
.y(function(d) {
return y0(d.Open); // here
});
// (...)
svg.append("path")
.data([data]) // here
.attr("class", "line")
.attr("d", valueline1);
and added x-axis labels with
svg.append("text")
.attr("transform", "translate(" + (width / 2) + " ," +
(height + margin.top + 20) + ")")
.style("text-anchor", "middle")
.text("Date");
BTW, your data is unsorted.
JSFiddle Demo
Docs reference

D3 update data on the wrong bars

I want to update data on a click but the bars that are changing are not the right ones. There is something I cant quite fix with the select. On click the grey bars, which should be bar2 are updating. It should be bar.
Example: https://jsfiddle.net/Monduiz/kaqv37gu/
D3 chart:
var values = feature.properties;
var data = [
{name:"Employment rate",value:values["ERate15P"]},
{name:"Participation rate",value:values["PR15P"]},
{name:"Unemployment rate",value:values["URate15P"]}
];
var margin = {top: 70, right: 50, bottom: 20, left: 50},
width = 400 - margin.left - margin.right,
height = 270 - margin.top - margin.bottom,
barHeight = height / data.length;
// Scale for X axis
var x = d3.scale.linear()
.domain([0, 100]) //set input to a scale of 0 - 1. The index has a score scale of 0 to 1. makes the bars more accurate for comparison.
.range([0, width]);
var y = d3.scale.ordinal()
.domain(["Employment rate", "Participation rate", "Unemployment rate"])
.rangeRoundBands([0, height], 0.2);
var svg = d3.select(div).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 + ")")
.classed("chartInd", true);
var bar2 = svg.selectAll("g.bar")
.data(data)
.enter()
.append("g")
.attr("transform", function(d, i) { return "translate(0," + i * barHeight + ")"; });
var bar = svg.selectAll("g.bar")
.data(data)
.enter()
.append("g")
.attr("transform", function(d, i) { return "translate(0," + i * barHeight + ")"; });
bar2.append("rect")
.attr("height", y.rangeBand()-15)
.attr("fill", "#EDEDED")
.attr("width", 300);
bar.append("rect")
.attr("height", y.rangeBand()-15)
.attr("fill", "#B44978")
.attr("width", function(d){return x(d.value);});
bar.append("text")
.attr("class", "text")
.attr("x", 298)
.attr("y", y.rangeBand() - 50)
.text(function(d) { return d.value + " %"; })
.attr("fill", "black")
.attr("text-anchor", "end");
bar.append("text")
.attr("class", "text")
.attr("x", function(d) { return x(d.name) -5 ; })
.attr("y", y.rangeBand()-50)
//.attr("dy", ".35em")
.text(function(d) { return d.name; });
d3.select("p")
.on("click", function() {
//New values for dataset
var values = feature.properties;
var dataset = [
{name:"Employment rate",value:values["ERate15_24"]},
{name:"Participation rate",value:values["PR15_24"]},
{name:"Unemployment rate",value:values["URate15_24"]}
];
//Update all rects
var bar = svg.selectAll("rect")
.data(dataset)
.attr("x", function(d){return x(d.value);})
.attr("width", function(d){return x(d.value);})
});
}
var bar2 = svg.selectAll("g.bar")
.data(data)
.enter()
.append("g")
.attr("transform", function(d, i) { return "translate(0," + i * barHeight + ")"; });
var bar = svg.selectAll("g.bar")
.data(data)
.enter()
.append("g")
.attr("transform", function(d, i) { return "translate(0," + i * barHeight + ")"; });
'bar2' above generates 3 new g elements (one for each datum)
Since you don't set attr("class","bar") for these then 'bar' will also generate 3 new g elements - (if you had set the class attribute bar would return empty as no new elements would be generated and you'd see missing stuff)
Further on you add rects to all these g elements for six rectangles in total and in the click function you select all these rectangles and re-attach 3 fresh bits of data
Since bar2 was added first the rectangles in its g elements are hoovering up the new data
You need to select and set different classes on the g elements, .selectAll("g.bar") and .attr("class", "bar") for bar, and .selectAll("g.bar2") and .attr("class", "bar2") for bar2 (use the same name to keep it simple)
then in the new data you need select only the rects belonging to g elements of the bar class: svg.selectAll(".bar rect")
Another way would be to have only one set of g elements and add two types of rectangle (differentiated by class attribute)

Time Series not plotting

I have some pretty simple code to plot a time series line graph. The x-scale is rendered correctly, however the line is not being rendered.
I am pretty sure that the issue we are facing here is with my function for calculating the x axis value, but I can't see what I might be doing wrong
My code for the graph is similar to this:
var data = getData();
var graph;
var line;
function getData() {
var theDate = new Date(2012, 01, 01, 0, 0, 0, 0);
var arr = [];
for (var x = 0; x < 30; x++) {
arr.push([theDate.getTime(), rand(100)]);
theDate.setDate(theDate.getDate() + 1);
}
return arr;
}
function rand(max) {
return Math.floor(Math.random() * (max + 1))
}
var m = [80, 80, 80, 80]; // margins
var w = 1000 - m[1] - m[3]; // width
var h = 400 - m[0] - m[2]; // height
// pretty sure the issue is which this line
var x = d3.time.scale().domain([new Date(data[0][0]), new Date(data[data.length - 1][0])]).range([0, w]);
var y = d3.scale.linear().domain([0, 100]).range([h, 0]);
line = d3.svg.line()
.x(function (d, i) { return x(new Date(d[0])); })
.y(function (d) { return y(d[1]); })
.interpolate("linear")
graph = d3.select("#graph").append("svg:svg")
.attr("width", w + m[1] + m[3])
.attr("height", h + m[0] + m[2])
.append("svg:g")
.attr("transform", "translate(" + m[3] + "," + m[0] + ")");
var xAxis = d3.svg.axis().scale(x).ticks(d3.time.days, 1).tickFormat(d3.time.format("%d"));
graph.append("svg:g")
.attr("class", "x axis")
.attr("transform", "translate(0," + h + ")")
.call(xAxis);
var yAxisLeft = d3.svg.axis().scale(y).ticks(10).orient("left");
graph.append("svg:g")
.attr("class", "y axis")
.attr("transform", "translate(0,0)") //positon of the y axis
.call(yAxisLeft);
graph.append("svg:path")
.data([data])
.attr("class", "line")
.attr("transform", "translate(" + x(1) + ")")
.attr("d", line)
.transition()
.duration(1500)
.ease("linear")
.attr("transform", "translate(" + x(0) + ")");
I figured it out, changing
graph.append("svg:path")
.data([data])
.attr("class", "line")
.attr("transform", "translate(" + x(1) + ")")
.attr("d", line)
.transition()
.duration(1500)
.ease("linear")
.attr("transform", "translate(" + x(0) + ")");
to
graph.append("svg:path")
.data([data])
.attr("class", "line")
worked.
Obviously my transition for the x axis is incorrect

Categories