D3.js Dynamic Transitioning with Live Data - json

Long time lurker, enthusiastic about learning D3! Anywhoo, I'm working on following Mr. Bostock's paths examples.
Now, I have a fiddle that is working just wonderfully - it is smooth, updates quickly, scales the x-axis and y-axis quite nicely.
This is a time series chart I'm trying to tackle. Basically, I'm taking sensor integers that go into a JSON array. My target is to receive this data in realtime; Sensor sends a temp, and I get a smooth reflection of that data on the chart in near realtime. Here's the rub. As soon as I add in the JSON fetching code, everything goes from smooth as butter to choppy and crazy. Here's what I'm working with... Please be understand that I am very new to D3.
Here is the nicely randomized code which displays the graph in the way I want:
var n = 40,
duration = 750,
now = new Date(Date.now() - duration),
random = d3.random.normal(0, 0.2),
count = 0,
data = d3.range(n).map(random);
var margin = {
top: 20,
right: 20,
bottom: 20,
left: 40
},
width = 500 - margin.left - margin.right,
height = 300 - margin.top - margin.bottom;
var x = d3.time.scale()
.domain([now - (n - 2) * duration, now - duration])
.range([0, width]);
var y = d3.scale.linear()
.domain([0, d3.max(data)])
.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 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("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(0," + height + ")")
.call(x.axis = d3.svg.axis().scale(x).orient("bottom"));
var yAx = svg.append("g")
.attr("class", "y axis")
.call(y.axis = d3.svg.axis().scale(y).orient("left"));
var path = svg.append("g")
.attr("clip-path", "url(#clip)")
.append("path")
.datum(data)
.attr("class", "line");
var transition = d3.select({}).transition()
.duration(750)
.ease("linear");
tick();
function tick() {
transition = transition.each(function () {
// update the domains
now = new Date();
x.domain([now - (n - 2) * duration, now - duration]);
y.domain([0, d3.max(data) + 10]);
// push a new data point onto the back
data.push(20 + Math.random() * 100);
// redraw the line, and slide it to the left
path.attr("d", line)
.attr("transform", null);
// slide the x-axis left, rescale the y-axis
axis.call(x.axis);
yAx.call(y.axis);
// slide the line left
path.transition()
.attr("transform", "translate(" + x(now - (n - 1) * duration) + ")");
// pop the old data point off the front
data.shift();
}).transition().each("start", tick);
}
svg {
font: 10px sans-serif;
}
.line {
fill: none;
stroke: orange;
stroke-width: 1.5px;
}
.axis path, .axis line {
fill: none;
stroke: #000;
shape-rendering: crispEdges;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/3.4.11/d3.min.js"></script>
Now, I realize that my poor understanding of transitions is probably getting me stuck. That being said, I need help! My trouble starts when I add the following:
First, I add var JSONdata = []; as a global, then right after function tick() {
transition = transition.each(function () { I add my JSON code:
d3.json("tempdata.php", function(error, data){
JSONdata = data;
data.forEach(function(d) {
JSONdata.temp = +d.temp;
})
I finish by wrapping up the end of the whole tick block with the final })
So it works... but it's slow and choppy! Methinks there be dragons in my transitions...? I'd like to have as near real time as possible, but even up to 5 seconds as a buffered delay would be acceptable.
Can anyone lend a hand or some enlightenment?
I'd like the graph to display a constant line with the last updated value, so even if the sensor goes offline, I'll see the last value. Granted, eventually I need to work out a round robin database type deal and actually face the issue of better timestamp handling. But, that can be for another day.
My JSFiddle with just the randomized data: http://jsfiddle.net/6z9qe46e/7/

Related

d3 : coloring dots on a scatterplot using column in dataset

I've copied some code to make a basic scatterplot and I'm trying to color the dots based on one of the columns of the data.
I've tried modifying the dataset to have a column named "Color" with values between 0 and 1 but when I assign the color function (i.e. d3.interpolateRdGy(d[2]) ) the scatterplot has no dots on it.
I'm very, very new to d3 (I have experience only with ggplot2 in R).
<!DOCTYPE html>
<meta charset="utf-8">
<!-- Load d3.js -->
<script src="https://d3js.org/d3.v4.js"></script>
<!-- Create a div where the graph will take place -->
<div id="my_dataviz"></div>
<script>
// set the dimensions and margins of the graph
var margin = {top: 10, right: 30, bottom: 30, left: 60},
width = 460 - margin.left - margin.right,
height = 400 - margin.top - margin.bottom;
// append the svg object to the body of the page
var svg = d3.select("#my_dataviz")
.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 + ")");
//Read the data
d3.csv("https://raw.githubusercontent.com/holtzy/data_to_viz/master/Example_dataset/2_TwoNum.csv", function(data) {
// Add X axis
var x = d3.scaleLinear()
.domain([0, 4000])
.range([ 0, width ]);
svg.append("g")
.attr("transform", "translate(0," + height + ")")
.call(d3.axisBottom(x));
// Add Y axis
var y = d3.scaleLinear()
.domain([0, 500000])
.range([ height, 0]);
svg.append("g")
.call(d3.axisLeft(y));
// Add dots
svg.append('g')
.selectAll("dot")
.data(data)
.enter()
.append("circle")
.attr("cx", function (d) { return x(d.GrLivArea); } )
.attr("cy", function (d) { return y(d.SalePrice); } )
.attr("r", 1.5)
.style("fill", d3.interpolateRdGy(d[1]))
})
</script>
Typically with D3, you would create a color scale. If you want to map a quantitative value to a color, then you could do
const color = d3.scaleSequential()
.domain(d3.extent(data, d => d.Color))
.interpolator(d3.interpolateBlues);
d3.extent(data, d => d.Color) returns an array that contains the min and max values of the "Color" column in your dataset. This approach will work even when the values are not between 0 and 1, unlike directly calling the interpolator.
You can find other color schemes in the d3-scale-chromatic docs. For diverging color schemes, you can use a diverging scale.
Then to use the color scale, you would do
.attr('fill', d => color(d.Color))

JSON data for d3 line and bar graphs

I am new to the d3 graphing library and all the tutorials I've seen for loading JSON data for a graph have been using a url and a callback function. I already have the JSON data in a JSON object in the same function in which I am looking to graph (the JSON data is stored in a variable in the same file as the graphing function). How do I use that JSON object in my graphs? This is my code. The JSON object is called 'data'.
function showBargraph() {
var calories = 0;
var protein = 0;
var carbohydrates = 0;
var fat = 0;
for (var i in graphdata) {
calories = calories + parseInt(graphdata[i].calories);
protein = protein + parseInt(graphdata[i].protein);
carbohydrates = carbohydrates + parseInt(graphdata[i].carbohydrates);
fat = fat + parseInt(graphdata[i].fat);
}
//THIS IS THE JSON OBJECT!!!!!!!!!!!!!!!!!!!!!!!!!!
var data = {
'calories': calories,
'protein': protein,
'carbohydrates': carbohydrates,
'fat': fat
}
// set the dimensions and margins of the graph
var margin = {top: 30, right: 30, bottom: 70, left: 60},
width = 460 - margin.left - margin.right,
height = 400 - margin.top - margin.bottom;
// append the svg object to the body of the page
var svg = d3.select("#myBarChart")
.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 + ")");
//THIS IS WHERE I NEED HELP!!!!!!!!!!!!!!!!!!!!!!!!
d3.json(data, function(d) {
console.log(d);
});
// X axis
var x = d3.scaleBand()
.range([ 0, width ])
.domain(data.map(function(d) { return d.Country; }))
.padding(0.2);
svg.append("g")
.attr("transform", "translate(0," + height + ")")
.call(d3.axisBottom(x))
.selectAll("text")
.attr("transform", "translate(-10,0)rotate(-45)")
.style("text-anchor", "end");
// Add Y axis
var y = d3.scaleLinear()
.domain([0, 13000])
.range([ height, 0]);
svg.append("g")
.call(d3.axisLeft(y));
var trace1 = {
x: ['Calories', 'Protein', 'Carbohydrates', 'Fat'],
y: [calories, protein, carbohydrates, fat],
type: 'bar'
};

Adding transition Image

I am trying to make a d3 visual through block builder.
I want the picture of tourdefrance to appear first in the whole container and then fade away and then the bars to appear.
I know I have made it a little complicated by copying from various sources.
Would be great if someone could help out and also explain the problem.
<!DOCTYPE html>
<meta charset="utf-8">
<style>
.bar {
fill: steelblue;
}
.bar:hover {
fill: gold;
}
.axis {
font: 10px sans-serif;
}
.axis path,
.axis line {
fill: none;
stroke: #000;
shape-rendering: crispEdges;
}
.x.axis path {
display: none;
}
</style>
<body>
<script src="http://d3js.org/d3.v3.min.js"></script>
<script src="https://d3js.org/d3.v4.min.js"></script>
<script>
// Mike Bostock "margin conventions"
var margin = {top: 10, right: 20, bottom: -500, left: 40},
width = 960 - margin.left - margin.right,
height = 500 - margin.top - margin.bottom;
// D3 scales = just math
// x is a function that transforms from "domain" (data) into "range" (usual pixels)
// domain gets set after the data loads
var x = d3.scale.ordinal()
.rangeRoundBands([0, width], .1);
var y = d3.scale.linear()
.range([height, 0]);
// D3 Axis - renders a d3 scale in SVG
var xAxis = d3.svg.axis()
.scale(x)
.orient("bottom");
var yAxis = d3.svg.axis()
.scale(y)
.orient("left")
.ticks(10, "%");
// create an SVG element (appended to body)
// set size
// add a "g" element (think "group")
// annoying d3 gotcha - the 'svg' variable here is a 'g' element
// the final line sets the transform on <g>, not on <svg>
body = d3.select("body");
show_image("https://upload.wikimedia.org/wikipedia/en/e/eb/Tour_de_France_logo.svg")
function show_image(source) {
var img = body.append("img").attr("src", source).style("opacity", 0)
; img.transition().duration(5000).ease(d3.easeLinear).style("opacity", 1)
}
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")
.attr("class", "x axis")
.attr("transform", "translate(0," + height + ")")
svg.append("g")
.attr("class", "y axis")
.append("text") // just for the title (ticks are automatic)
.attr("transform", "rotate(-90)") // rotate the text!
.attr("y", 6)
.attr("dy", ".71em")
.style("text-anchor", "end")
.text("Frequency");
// d3.tsv is a wrapper around XMLHTTPRequest, returns array of arrays (?) for a TSV file
// type function transforms strings to numbers, dates, etc.
d3.csv("https://gist.githubusercontent.com/vaibhavjaitly/1418c9beb1c71f6acffdaf21669e1956/raw/45af8ebcc87d6641eee8b4a60b7bafb8326607a0/data.csv", type, function(error, data) {
replay(data);
});
function type(d) {
// + coerces to a Number from a String (or anything)
d.Frequency = +d.Frequency;
return d;
}
function replay(data) {
var slices = [];
for (var i = 0; i < data.length; i++) {
slices.push(data.slice(0, i+1));
}
slices.forEach(function(slice, index){
setTimeout(function(){
draw(slice);
}, index * 300);
});
}
function draw(data) {
// measure the domain (for x, unique letters) (for y [0,maxFrequency])
// now the scales are finished and usable
x.domain(data.map(function(d) { return d.Letter; }));
y.domain([0, d3.max(data, function(d) { return d.Frequency; })]);
// another g element, this time to move the origin to the bottom of the svg element
// someSelection.call(thing) is roughly equivalent to thing(someSelection[i])
// for everything in the selection\
// the end result is g populated with text and lines!
svg.select(".x.axis").transition().duration(300).call(xAxis);
// same for yAxis but with more transform and a title
svg.select(".y.axis").transition().duration(300).call(yAxis)
// THIS IS THE ACTUAL WORK!
var bars = svg.selectAll(".bar").data(data, function(d) { return d.Letter; }) // (data) is an array/iterable thing, second argument is an ID generator function
bars.exit()
.transition()
.duration(300)
.attr("y", y(0))
.attr("height", height - y(0))
.style('fill-opacity', 0.00001024)
.remove();
// data that needs DOM = enter() (a set/selection, not an event!)
bars.enter().append("rect")
.attr("class", "bar")
.attr("y", y(0))
.attr("height", height - y(0));
// the "UPDATE" set:
bars.transition().duration(300).attr("x", function(d) { return x(d.Letter); }) // (d) is one item from the data array, x is the scale object from above
.attr("width", x.rangeBand()) // constant, so no callback function(d) here
.attr("y", function(d) { return y(d.Frequency); })
.attr("height", function(d) { return height - y(d.Frequency); }); // flip the height, because y's domain is bottom up, but SVG renders top down
}

Line graph is not working in d3.js

I am trying to create line graph from this data. Year should be in x-axis and value should be in y-axis
[{"year":2000,"value":1},{"year":2001,"value":2},
{"year":2002,"value":3},
{"year":2003,"value":4},{"year":2004,"value":5},
{"year":2005,"value":6}];
I don't understand why it is not working.
Code is in jsfiddle.
https://jsfiddle.net/u58x99vh/1/
Lots of issues here:
You are assigned "text" to a g element, it does not have such a thing.
Your x-axis is rotated back vertical, it shouldn't be. Also it's not placed at the bottom of the graph.
Finally, you are creating the path incorrectly. And you have no declaration of CSS class viiva.
Putting this all together:
let dataSorc = [{"vuosi":2000,"arvo":1},{"vuosi":2001,"arvo":2},{"vuosi":2002,"arvo":3},
{"vuosi":2003,"arvo":4},{"vuosi":2004,"arvo":5},{"vuosi":2005,"arvo":6}];
// 2. Use the margin convention practice
let margin = {top: 50, right: 50, bottom: 50, left: 50}
, width = window.innerWidth - margin.left - margin.right // Use the window's width
, height = window.innerHeight - margin.top - margin.bottom; // Use the window's height
// The number of datapoints
//let kokoelma = d3.values(dataSorc);
//console.log(kokoelma);
// 5. X scale will use the index of our data
let xScale = d3.scaleLinear()
.domain(d3.extent(dataSorc, d => d.vuosi)) // input
.range([0, width]); // output
// 6. Y
let yScale = d3.scaleLinear()
.domain(d3.extent(dataSorc, d => d.arvo))
// input
.range([height, 0]); // output
//console.log("yScale");
//console.log(yScale);
//console.log("xScale");
//console.log(xScale);
// 7. d3's line generator
let line = d3.line(dataSorc)
.x( d => xScale(d.vuosi)) // set the x values for the line generator
.y( d => yScale(d.arvo)); // set the y values for the line generator
console.log("line");
console.log(line);
let svg = d3.select("#taulu").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 + ")");
//let g = svg.append("g").
//console.log("svg");
//console.log(svg);
// 3. Call the x axis in a group tag
svg.append("g")
.attr("transform", "translate(0," + height + ")")
.call(d3.axisBottom(xScale))
.attr("class", "x_axis")
//.text("x_axis");
// Create an axis component with d3.axisBottom
// 4. Call the y axis in a group tag
svg.append("g")
.call(d3.axisLeft(yScale))
.attr("class", "y_axis")
//.text("y_axis");
// Create an axis component with d3.axisLeft
// 12. Appends a line for each datapoint
svg.append("path")
.datum(dataSorc)
//.enter().append("path") // Uses the enter().append() method
.attr("class", "viiva")
.attr("d", line); // Assign a class for styling
//console.log("data tulostetaan");
//console.log(dataSorc);
//console.log("svg kaikki data on laitettu");
//console.log(svg);
//console.log("testaa path");
//console.log(d3.selectAll("path"));
.viiva {
stroke: steelblue;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/4.13.0/d3.min.js"></script>
<p>
testi
</p>
<div id="taulu">
</div>
I reworked your fiddle, check it. There is a diff between your code and my.
You should parse your date this way:
const parseTime = d3.timeParse("%Y");
dataSorc.forEach(item => {
item.vuosi = parseTime(item.vuosi);
item.arvo = item.arvo;
});
And use scaleTime for x axis instead of scaleLinear:
let xScale = d3.scaleTime()
.domain(d3.extent(dataSorc, d => d.vuosi)) // input
.range([0, width]); // output
You have only one line, so you can draw it like this:
svg.append("path")
.attr("class", "viiva")
.attr("stroke", "#000")
.attr("d",item => line(dataSorc));

How to make a scatter plot from a CSV file using d3.js?

I am quite new to d3.js. I am trying to make a scatter plot using the data present in a csv file. From the csv file, I am utilizing data from two columns.
d3.csv("test.csv",function (data) {
var margin = {top: 30, right: 10, bottom: 50, left: 60},
width = 960 - margin.left - margin.right,
height = 500 - margin.top - margin.bottom;
var xMax = d3.max(data, function(d) { return +d.Survivaltime; }),
xMin = 0,
yMax = d3.max(data, function(d) { return +d.Year; }),
yMin = 1950;
//Define scales
var x = d3.scale.linear()
.domain([xMin, xMax])
.range([0, width]);
var y = d3.scale.linear()
.domain([yMin, yMax])
.range([height, 0]);
});
// the chart object, includes all margins
var chart = d3.select('body')
.append('svg:svg')
.attr('width', width + margin.right + margin.left)
.attr('height', height + margin.top + margin.bottom)
.attr('class', 'chart')
// the main object where the chart and axis will be drawn
var main = chart.append('g')
.attr('transform', 'translate(' + margin.left + ',' + margin.top + ')')
.attr('width', width)
.attr('height', height)
.attr('class', 'main')
// draw the x axis
var xAxis = d3.svg.axis()
.scale(x)
.orient('bottom');
.tickSize(-height)
.tickFormat(d3.format("s"));
main.append('g')
.attr('transform', 'translate(0,' + height + ')')
.attr('class', 'main axis date')
.call(xAxis);
// draw the y axis
var yAxis = d3.svg.axis()
.scale(y)
.orient('left');
.ticks(5)
.tickSize(-width)
.tickFormat(d3.format("s"));
main.append('g')
.attr('transform', 'translate(0,0)')
.attr('class', 'main axis date')
.call(yAxis);
// draw the graph object
var svg = main.append("svg:g");
g.selectAll("scatter-dots")
.data(d.Year) // using the values in the ydata array
.enter().append("svg:circle") // create a new circle for each value
.attr("cy", function (d) { return y(d.Year); } ) // translate y value to a pixel
.attr("cx", function (d) { return x(d.Survivaltime); } ) // translate x value
.attr("r", 10) // radius of circle
.style("opacity", 0.6); // opacity of circle
here'sa link to the csv file:
http://bit.ly/14oLdml
Please help me.
Looks good for the most part. The only problems I could see (although there may be others that I'm missing) are near the end:
// draw the graph object
//var svg = main.append("svg:g");// <---- this is causing a bug. Should be:
var g = main.append("svg:g");
Then fix:
g.selectAll(".scatter-dots"); // <---- added a period before scatter-dots
The other thing, which is not breaking your code, but should be fixed:
After you .append("svg:circle"), you should call .attr("class", "scatter-dots") on it.