Lines not showing in d3.js multi-line-chart with time axis - javascript

Hello nice people of SO,
I'm pretty new to javascript, JSON and D3.js and I'm trying to build a multi-line time series chart. My data comes as JSON from my server like this:
{
"t": ["2014-08-16T16:15:00", "2014-08-16T16:20:00", "2014-08-16T16:25:00", ...],
"todd": [0,0,1,2,3,2,1,0,0, ...],
"pete": [3,2,1,0,4,4,0,0,0, ...]
}
This I store in an array called 'dataset'. The datetimes are subsequently parsed. 't', 'todd' and 'pete' arrays are of equal length.
Ultimately I want the time (t) values to be used for the x-axis, the other two as y values over time. Parsing the data works, however I can't get it to display properly. What works so far is:
A: display my values one after the other without a reference to time, so I get two nice, colored lines but the x-axis holds no information, as it just displays 0 to N.
OR
B: have the x-axis show as a timeline based on the "t"-values (this tells me that the parsing works as it should). However, I can't get my two lines to show.
In Variant A I was simply returning the index as x value like so:
var line = d3.svg.
.x(function(d,i) {
return i;
})
...
Now I'm trying to return the datetime from my "t"-array:
var line = d3.svg.
.x(function(d,i) {
return dataset.t[i];
})
...
However, nothing is displayed. I get no errors at all in the console. I tried console logging the value of 'dataset.t[i]' but nothing shows. It's as if the code isn't even running so here is the for loop that calls the above code:
for (var key in dataset) {
if (dataset.hasOwnProperty(key) && key!="t") {
data=dataset[key];
svg.append("svg:path").attr("d", line(data))
.style("stroke", color(key))
.style("stroke-width", 2);
//BELOW IS IRRELEVANT TO MY QUESTION, JUST FOR COMPLETENESS...
svg.append("rect")
.attr("x", w - 65)
.attr("y", n*25)
.attr("width", 10)
.attr("height", 10)
.style("fill", color(key));
svg.append("text")
.attr("x", w - 50)
.attr("y", n * 25 + 10)
.attr("height",30)
.attr("width",100)
.style("fill", color(key))
.text(key);
n+=1; // counter up
}
};
I can tell this is running as the 'rect' and 'text' elements are added to the chart.
The full code is here: http://pastebin.com/tbS7rD7p
By now I can't see straight anymore so I'm really looking forward to your ideas. Many thanks in advance!
Cheers, Ben

Thanks, in code veritas, for your input. I'm in the process of optimizing the code in the way you suggested. However, it seems you do not need the .data() method with lines as you allocate the data via .x() and .y() methods for these (if I understand correctly).
Anyway, in the end the solution was to change:
.x(function(d,i) {return dataset.t[i];})
to
.x(function(d,i) {return x(dataset.t[i]);})
which is mentioned in pretty much every tutorial but which I, wisely ^^, chose to ignore because it seemed pointless to me. If anybody can comment on that and enlighten me, I'd be grateful.
Cheers!

Related

d3 data manipulation before plotting

Good evening everybody,
I am working for the first time on a visualization using D3. Basically I want to plot a bar chart that plots means of groups.
For example:
d3.csv("sample.csv", function(d) {
return {
"currency" : +d["year"],
"month" : +d.month,
"spendings" : +d.spendings
}
})
.then(function(data) {
chart_group.selectAll(".bar")
.data(sample.filter(function(d) { return d["currency"] === temp_curr; }))
.enter()
.append("rect")
.attr("class", "bar")
It is easily possible to create a new object containing the dynamic data i want. Group by curreny and mean the spending.
Is it possible to replace the .data(sample) by another object I created. Somehow the plot is always empty changing it. If it is not. How can I get the mean as y value?
.attr("y", function(d) { return y(d.spendings); })
I tried many things including nesting and d3.mean but there are always errors messages complaining about lists instead of single values. I totally get that but don't know how to do the trick.
Can someone help me?
Try .data(data.filter since data is the var name provided to the function containing your d3 code

Getting color as a JSON input to a d3 bullet chart

I am following the following D3 bullet chart example, trying to modify it a bit so that the different colors of the ranges are also included in the JSON: http://www.d3noob.org/2013/07/introduction-to-bullet-charts-in-d3js.html. The reason for the change is that I need the colors to be dynamic and depend on various things.
This exists one other place in the forum, but old and unsolved. I should add that I am a total newbie to d3, and don't have a lot of JavaScript experience in general.
Here is the JSON I use. "rangecolor" will in the future be an array of different colors, as there are several ranges, but for simplicity I attempt only with one color to begin with.
{
"title":"Memory Used",
"subtitle":"MBytes",
"ranges":[256,512,1024],
"rangecolor": "red",
"measures":[768],
"markers":[900]
}
Now, getting an idea of how to use it, I looked at the working example for title:
var title = svg.append("g")
.style("text-anchor", "end")
.attr("transform", "translate(-6," + height / 2 + ")");
title.append("text")
.attr("class", "title")
.text(function(d) { return d.title; });
The problem is that I cannot get the following to work:
d3.selectAll(".bullet .range.s0")
.style("fill", function(d) { return d.rangecolor; });
The following does work:
d3.selectAll(".bullet .range.s0")
.style("fill", function(d) { return "red"; });
And I can also extract the rangecolor value to the title:
title.append("text")
.attr("class", "title")
.text(function(d) { return d.rangecolor; }); //works - title is now "red"
My approach might be misguided, so any help on how to best include color ranges to the JSON and using it would be much appreciated.
The problem is that when you select all bullets, there is no data bound to them so d is undefined here:
d3.selectAll(".bullet .range.s0")
.style("fill", function(d) { return d.rangecolor; });
Why? You did not perform a data join like this:
d3.selectAll('.something').data(somethingData)
.style('fill', function (d) { // d is defined });
You should wonder why it works for the title on the contrary. This is because when you do this:
var title = svg.append("g");
title inherits data from the svg selection. See Mike Bostock explanation. In fact I was myself not aware of this behaviour, I prefer performing data joins explicitly.
I don't know the overall structure of your code, but you might apply you rangecolor properties with data inheritance (as for title) or refactor to use explicit data joins.
I have talked with some experienced developers that state that the tutorial I am following is not a good one. It is a bit messy, and I am trying to find something cleaner. Troubleshooting has been difficult in this regard, and
The exact issue presented here was solved by using d3.selectAll(".bullet .range.s0").data(data).

Node.Js Map example, adding speed and on-click data?

I've been attempting to learn better visualization with node.js and the mapbox library.
Using this example here: Running Map Example
I'd like to add a graph of speed, and allow a user to click on a node, and see data about that position in a little popup - For today, I just want to get speed working.
It seems to be a recursive algorithm, so I need to implement variables to store the previous position and time, but I've ran into three problems:
I don't know how to use this date format: "2015-01-19T21:24:20Z" or Chroniton's parsing of it to generate a subtractable number to get the difference in time.
I don't know how to get the distance between two points using the code given, I could simply do sqrt((.x(point1) - .x(point2)) + (.y(point1) + .y(point2)), but I'm not sure how coordinates are stored or parsed in this example.
I don't know where to calculate the speed. It seems like the coordinates are only defined after the graphs are displayed, since the coordinates aren't used in the graphics. I am probably wrong, but I need some direction.
Here is what I have now:
Using the elevation display as my template, I think I have made it able to display the line by adding in the following three snippets:
Setting the scale:
var speed = d3.scale.linear()
.range([height, 0])
.domain([0, d3.max(dataRet, function(d) {
return d[1][2];
})]);
Adding in the line, with data:
var SpeedLine = d3.svg.line()
.x(function (d) { return x(d[0]); })
.y(function (d) { return speed(d[2])})
Displaying the line:
svg.append('path')
.datum(dataRet)
.attr('class', 'speed-line')
.attr('d', speedLine);
I know I have to add in a speed function similar to this psudocode:
var dt = chroniton.domain(Time1, Time2)
var speed[i] = LongLat(previousPoint).distanceto(currentPoint)/dt
And on the popup box:
dt.format(something to do with time formatting)
Note 1:, I changed the name of the function datePlaceHeart to dataRet since I'll be adding new things to do it, and datePlaceHeartSpeedStuffAndThings was getting a bit long ;)
Note 2: I haven't been able to start the pop-up because I haven't figured out how to calculate speed using the given data, and well, it seems kinda silly to do the easy one first. (With my luck, its actually not easy)
Please help? Here is my edited code in full (Edited index.js):
Code

D3.js not updating data

I am updating a D3 stacked bar graph in combination with AngularJS. In this, all data is initially visible, and updates filter out undesired data. In the past it has worked without issue using this model:
data = [{
name: John Doe,
splits: [{distance: 1, time: 1234},]
},...]
Now I am attempting to to add one more bar to each stack using this model:
data = [{
name: John Doe
time: 12345,
splits: [{distance: 1, time: 1234},]
},...]
My issue is updating the data. My calculated values are recalculated correctly, such as the domain for scaling. A line for time update still only recognizes the data values from before the update (code snippet heavily truncated for brevity):
// Set ranges of values in axes
x.domain(data.map(function(d) { return d.name}));
y.domain([ min , max]);
// Y Axis is updated to the correct time range
chart.append('g').attr('class', 'y axis').call(yAxis).selectAll('text').style('font-family','Open Sans').style('font-size', '.9rem');
// data join / Select Athlete
var athlete = chart.selectAll(".athlete").data(data),
athdata = athlete.enter(),
console.log(data) // data is correct here
// add container for each athlete
athplace = athdata.append("g")
.attr("class", "athlete")
.attr("transform", function(d) { return "translate(" + x(d.name) + ",0)"; })
.text(function(d) { return d.name}),
// ENTER time
athplace.append('rect')
.attr('class', "time")
.attr("width", x.rangeBand())
.attr("y", function(d) { return y(d.time.time); })
.attr("height", 0).transition().duration(300)
.attr("height", function(d) { return height-y(d.time); });
... enter splits & labels
// exit splits & athlete
splitlabels.exit().transition().duration(300).attr('opacity', 0).remove();
splits.exit().transition().duration(300).attr('height', 0).attr('opacity', 0).remove();
athlete.exit().transition().duration(300).attr('width',0).attr('opacity', 0).remove();
console.log(athlete) // data is still correct
// UPDATE time, this chain has the problem with data not being updated. "d" is equal to previous values
athlete.selectAll('rect.time')
.attr("width", x.rangeBand())
.attr("y", function(d) { return y(d.time.time); })
.attr("height", function(d) { return height-y(d.time); });
Due to these errors, the updated columns represent the wrong data and produce the wrong visualization. I have been staring/testing at this all day trying to isolate the issue to what I have now. Could someone more experienced with D3 give me some insight?
Note: for those interested, this is all in an Angular directive where I $watch for changes to the data values, although I am 100% sure that that is not the issue.
Edit
Here is a JSFiddle that illustrates the same error as in my script. All of the code was extracted directly from the script exhibiting the issue. The interval update at the bottom imitates the swapping of data that would normally happen.
I played with your example for a little bit and made a few changes that may be of use.
First, I re-factored your global var's up top to inside your update function. By doing this, we are not "double-appending" where our x and y axis left off. This was seemingly drawing our chart on top of our old chart.
However, fixing this then gave me the new chart overlaying on top of our old. To fix this, I call d3.selectAll("svg > *").remove(); within update to remove all attached SVG elements and groupings, giving us a "clean slate" to render our new chart on. This solves the overlaying issue.
You mention this is shortened for brevity, so I hope this example can help with your actual application
Updated Fiddle

d3 enter().append() not appending all elements of my array

I'm working on my first mid-scale d3 project right now after having run through the tutorials. I understand scales, enter, update, and exit, so I'm pretty confused about a problem I'm running into.
I have an array of JSON objects with two fields, year and number. I am creating a bar chart with this array with the following code:
var bar = chart.selectAll('g')
.data(yearData)
.enter().append('g')
.attr('transform', function(d, i) {
console.log(i);
return 'translate(' + i * barWidth + ',0)'; });
My confusion stems from the fact that the console.log statement in this code block outputs 27 as its first value. In other words, d3 is skipping elements 0 - 26 of my array. Why could this be??
Thanks for your help.
This is most likely because you already have g elements on your page (e.g. from adding an axis). These are selected and matched with data, so the enter selection doesn't contain everything you expect.
One solution is to assign a class to these elements and select accordingly:
var bar = chart.selectAll('g.bar')
.data(yearData)
.enter().append('g')
.attr("class", "bar")
// ...
Much more detail on this in the second half of this tutorial.

Categories