updating a line graph in d3 is not working - javascript

i am trying to update a line graph and it is not throwing any error but it is also not updating the graph.
i am deleting a point and adding a new one with an incremented rate and incremented created_at date by a second(trying to follow http://bl.ocks.org/benjchristensen/1148374)
function redrawWithoutAnimation() {
for (var i in chart_data) {
linedata = chart_data[i];
//delete first element of array
linedata.points.reverse().shift();
//create a new point
rate = linedata.points[0].rate + 1;
created_at = linedata.points[0].created_at + 6000;
new_point = {};
new_point.rate = rate;
new_point.created_at = created_at;
linedata.points.push(new_point);
console.log(linedata);
}
// static update without animation
svg.selectAll("path")
.data([linedata.points]); // set the new data
line(linedata.points); // apply the new data values
}
redrawWithoutAnimation();
setInterval(function () {
redrawWithoutAnimation();
}, 8000);
here is my code
http://jsfiddle.net/yr2Nw/8/

Working fiddle: http://jsfiddle.net/reblace/GsaGb/1
There's a few issues here...
First, you were updating all the chart_data in the for loop, but outside the loop, you were only trying to update the line still stored in the linedata variable after loop execution. You should try to avoid having variables with greater scope than they need. It can lead to bugs like this one:
svg.selectAll("path").data([linedata.points]);
line(linedata.points);
You should instead use D3's data joining to rejoin the new data to all the paths at once declaratively like so:
linesGroup.selectAll("path")
.data(chart_data)
.attr("d", function(d){ return line(d.points); });
What that code's doing is it's selecting the paths and then joining each of them to the chart_data elements and then binding the appropriate line generator to the "d" attribute for the appropriate path.
Then, you need to update your x axis and y axis otherwise the plot will just shoot off the drawn area. This code is updating the domains and then rebinding the axes to the dom elements so they redraw:
xAxis.scale().domain([
d3.min(chart_data, function (c) { return d3.min(c.points, function (v) { return v.created_at; }); }),
d3.max(chart_data, function (c) { return d3.max(c.points, function (v) { return v.created_at; }); })
]);
yAxis.scale().domain([
0,
d3.max(chart_data, function (c) { return d3.max(c.points, function (v) { return v.rate; }); })
]);
svg.select(".x.axis").call(xAxis);
svg.select(".y.axis").call(yAxis);
There were a few other bugs I fixed them in the Fiddle. For example, you need to calculate the time for the new point based on the last element in the array, not the first, otherwise the line can't interpolate properly since its no longer a continuous function... and this is a bit more concise way to do your line updates:
for (var i=0; i<chart_data.length; i++) {
linedata = chart_data[i];
//delete first element of array
var removedPoint = linedata.points.shift();
//create a new point
var lastpoint = linedata.points[linedata.points.length-1];
var new_point = {
rate: removedPoint.rate,
created_at: lastpoint.created_at + 6000
};
linedata.points.push(new_point);
}
Also note that you shouldn't use the for(var in) loop for Arrays, that's for iterating over the properties in an object.
There's still some issues, but I think this should help get you over the hurdle you were stuck on. Anyways, it looks cool in action!

Fine fenac.. You facing so many problems since your data is not in good format for your requirements..
as per http://bl.ocks.org/benjchristensen/1148374 The x-axis data must be (data[] (data array))
Your data is something like this
[objects,object,object] where each object holds one element of xaxis value.. so the pushing and shifting is not possible..
try to change the format of the data (linedata.points) to an array (data[]) and try it out sure it works..
You just need to put all the values in linedata.points into an array data[] and use this data[] to animate your line..
Since yours the multiline.. you need to create 2D array and must pass them accordingly...
Cheers..
I updated your jsfiddle
setInterval(function () {
console.log(linedata.points);
var v = linedata.points.shift(); // remove the first element of the array
linedata.points.push(v); // add a new element to the array (we're just taking the number we just shifted off the front and appending to the end)
redrawWithoutAnimation();
}, 3000);
http://jsfiddle.net/yr2Nw/9/
But still it wont works till you do that work...
Personal Suggestion: First Try with single line graph then go with looping for multiline...

Related

Picking quartile value on each point

I'm plotting sentiment value of tweet over last 10 years.
The csv file has the three columns like below.
I plotted each value by date successfully.
However, when I tried to generate an area graph,
I encountered a problem which is,
each date has multiple values.
That's because each data point is from one single tweets that
one x point ended up with having multiple y values.
So I tried to pick quartile value of each date or pick largest or least y value.
For clarity, please see the example below.
January 8 has multiple y values (textblob)
I want to draw area graph by picking the largest value or 2nd quartile value of each point.
How do I only pick the point?
I would like to feed the point in the following code as a
x/y coordinate for line or area greaph.
function* vlinedrawing(data){
for(let i;i<data.length;i++){
if( i%500==0) yield svg.node();
let px = margin+xscale(data[i].date)
let py = height-margin-yscale(data[i].vader)
paths.append('path')
.attr('x',px)
.attr('y',py)
}
yield svg.node()
}
The entire code is in the following link.
https://jsfiddle.net/soonk/uh5djax4/2/
Thank you in advance.
( The reason why it is a generator is that I'm going to visualize the graph in animated way)
For getting the 2nd quartile you can use d3.quantile like this:
d3.quantile(dataArray, 0.5);
Of course, since the 2nd quartile is the median, you can also just use:
d3.median(dataArray);
But d3.quantile is a bit more versatile, you can just change the p value for any quartile you want.
Using your data, without parsing the dates (so we can use a Set for unique values`), here is a possible solution:
const aggregatedData = [...new Set(data.map(function(d) {
return d.date
}))].map(function(d) {
return {
date: parser(d),
textblob: d3.quantile(data.filter(function(e) {
return e.date === d
}).map(function(e) {
return e.textblob
}), 0.5)
}
});
This is just a quick answer for showing you the way: that's not a optimised code, because there are several loops within loops. You can try to optimise it.
Here is the demo:
var parser = d3.timeParse("%m/%d/%y");
d3.csv('https://raw.githubusercontent.com/jotnajoa/Javascript/master/tweetdata.csv', row).then(function(data) {
const aggregatedData = [...new Set(data.map(function(d) {
return d.date
}))].map(function(d) {
return {
date: parser(d),
textblob: d3.quantile(data.filter(function(e) {
return e.date === d
}).map(function(e) {
return e.textblob
}), 0.5)
}
});
console.log(aggregatedData)
});
function row(d) {
d.vader = +d.vader;
d.textblob = +d.textblob;
return d;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/5.7.0/d3.min.js"></script>

crossfilter.js - Histogram with custom reduce function fails in filtering data

following situation: I have two plots, one scatterplot and one histogram for the x-values in this scatterplot. I wrote a custom reduce function that looks similar to this:
let grouping = this._cf_dimensions[attribute].group().reduce(
function(elements, item) {
elements.items.push(item);
elements.count++;
return elements;
},
function(elements, item) {
// console.log("item.id = " + item.id);
let match = false;
let values = [];
for (let i = 0; i < elements.items.length && !match; i++) {
// Compare hyperparameter signature.
if (item.id === elements.items[i].id) {
match = true;
elements.items.splice(i, 1);
elements.count--;
}
}
}
return elements;
},
function() {
return {items: [], count: 0};
}
);
The problem: When I select points in my scatterplot, the correlating histogram does not update properly. I traced it back to the remove function, i. e. the second of the three functions above, being called for only one of my five groups (I checked by comparison of the length of elements with the original group size). That means that the items to be removed won't be necessarily found.
In other words, the scatterplot selects the correct set of datapoints, but the remove function in the barchart grouping shown above, while registering the incoming filter update, is not called for all groups of this grouping (equivalently: not called for all bars in the bar chart).
I'm a bit at a loss, since I seem to remember successfully implementing dashboards with dc.js and crossfilter.js and the past exactly like this. Do I misunderstand something about the custom reduce concept or is there something obvious I'm overlooking?
Thanks!

D3.js animating update of stacked bar graph

I am trying to update a stacked bar chart with transitions as the underlying data is changed. It calls the same "render" function each time and works well when no transitions are involved. However, I would like to animate the changes in values, transitioning from its current state to the next.
I have somewhat solved the problem, but feel like my solution is clunky - hoping there is a better way to do this for stacked bar charts.
My approach has been to do the following:
Load the data
Load the initial conditions (req. for transitions)
Load the final conditions (within a transition)
Copy the current data into another array: prevData
Reload data after interval
Using the above approach, if prevData has values, then use these to set the initial conditions. My problems is that finding and setting the initial conditions feels really clunky:
if (prevData.length > 0) {
//get the parent key so we know who's data we are now updating
var devKey = d3.select(this.parentNode).datum().key;
//find the data associated with its PREVIOUS value
var seriesData = seriesPrevData.find(function (s) { return (s.key == devKey); })
if (seriesData != null) {
//now find the date we are currently looking at
var day = seriesData.find(function (element) { return (element.data.Date.getTime() == d.data.Date.getTime()); });
if (day != null) {
//now set the value appropriately
//console.debug("prev height:" + devKey + ":" + day[1]);
return (y(day[0]) - y(day[1]));
}
}
}
All I'm doing, is finding the correct key array (created by d3.stack()), then trying to find the appropriate previous data entry (if it exists). However, searching parent nodes, and searching through arrays to find the required key and the appropriate data element feels very long-winded.
So, my question is, is there a better way to do this? or parts of this?
Find the previously bound data values associated with this element or the current values before it is changed within a function.
Better way to find the current key being updated rather than using: d3.select(this.parentNode)... ? I've tried passing key values but don't seem to be getting it right. The best I have achieved, is passing a key function to the parent, and looking for it the way described above.
Sorry for the long post, I just spent a whole day working out my solution, frustrated by the fact that all I really needed, was the previous values of an item. Having to do all these "gymnastics" to get what I needed seems very "un" D3.js like :-)
Thanks
Following is a simple example for an animated bar chart. It'll iterate over two different versions of the dataset to show how one can handle changes in the underlying data very easily with d3. There is no need (in this example) for any manual data preparation for the transition/animation.
var data = [
[1, 2, 3, 4, 5],
[1, 6, 5, 3]
];
var c = d3.select('#canvas');
var currentDataIndex = -1;
function updateData() {
// change the current data
currentDataIndex = ++currentDataIndex % data.length;
console.info('updating data, index:', currentDataIndex);
var currentData = data[currentDataIndex];
// get our elements and bind the current data to it
var rects = c.selectAll('div.rect').data(currentData);
// remove old items
rects.exit()
.transition()
.style('opacity', 0)
.remove();
// add new items and define their appearance
rects.enter()
.append('div')
.attr('class', 'rect')
.style('width', '0px');
// change new and existing items
rects
// will transition from the previous width to the current one
// for new items, they will transition from 0px to the current value
.transition()
.duration('1000')
.ease('circle')
.style('width', function (d) { return d * 50 + 'px'; });
}
// initially set the data
updateData();
// keep changing the data every 2 seconds
window.setInterval(updateData, 2000);
div.rect {
height: 40px;
background-color: red;
}
div#canvas {
padding: 20px;
border: 1px solid #ccc;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/3.4.11/d3.min.js"></script>
<div id="canvas">
</div>

Crossfilter - Double Dimensions (second value linked to daily max)

Quite an oddly specific question here but something I've been having a lot of trouble with over the past day or so. Broadly, I'm trying to calculate the maximum of an array using crossfilter and then use this value to find a maximum.
For example, I have a series of Timestamps with an associated X Value and a Y Value. I want to aggregate the Timestamps by day and find the maximum X Value and then report the Y Value associated with this Timestamp. In essence this is a double dimension as I understand it.
I'm able to do the first stage simply to find the maximum values. But am having a lot of difficulty getting through to the second value.
Working code for the first, (using Crossfilter and Reductio). Assuming that each row has the following four values.
[(Timestamp, Date, XValue, YValue),
(2015-05-15 16:00:00, 2015-05-15, 30, 15),
(2015-05-15 16:45:00, 2015-05-15, 25, 33)
... (many thousand of rows)]
First Dimension
ndx = crossfilter(data);
dailyDimension = ndx.dimension(function(d) { return d.date; });
Get the max of the X Value using reductio
maxXValue = reductio().max(function(d) { return d.XValue;});
XValues = maxXValue(dailyDimension.group())
XValues now contains all of the maximum X Values on a Daily Basis.
I would now like to use these X Values to identify the corresponding Y Values on a date basis.
Using the same data above the appropriate value returned would be:
[(date, YValue),
('2015-05-15', 15)]
// Note, that it is 15 as it is the max X Value we find, not the max Y Value.
In Python/Pandas I would set the index of a DataFrame to X and then do an index match to find the Y Values
(Note, it can safely be assumed that the X Values are unique in this case but in reality we should really identify the Timestamp linked to this period and then match on that as they are strictly guaranteed to be unique, not loosely).
I believe this can be accomplished by modifying the reductio maximum code which I don't fully understand properly Source Code is from here
var reductio_max = {
add: function (prior, path) {
return function (p, v) {
if(prior) prior(p, v);
path(p).max = path(p).valueList[path(p).valueList.length - 1];
return p;
};
},
remove: function (prior, path) {
return function (p, v) {
if(prior) prior(p, v);
// Check for undefined.
if(path(p).valueList.length === 0) {
path(p).max = undefined;
return p;
}
path(p).max = path(p).valueList[path(p).valueList.length - 1];
return p;
};
},
initial: function (prior, path) {
return function (p) {
p = prior(p);
path(p).max = undefined;
return p;
};
}
};
Perhaps this can be modified so that there is a second valueList of Y Values which maps 1:1 with the X Values associated in the max function. In that case it would be the same index look up of both in the functions and could be assigned simply.
My apologies that I don't have any more working code.
An alternative approach would be to use some form of Filtering Function to remove entries which don't satisfy the X Criteria and then group by day (there should only be one value in this setting so a simple reduceSum for example will still return the correct value).
// Pseudo non working code
dailyDimension.filter(function(p) {return p.XValue === XValues;})
dailyDimension.group().reduceSum(function(d) {return d.YValue;})
Eventual results will be plotted in dc.js
Not sure if this will work, but maybe give it a try:
maxXValue = reductio()
.valueList(function(d) {
return ("0000000000" + d.XValue).slice(-10) + ',' + d.YValue;
})
.aliasProp({
max: function(g) {
return +(g.valueList[g.valueList.length - 1].split(',')[0]);
},
yValue: function(g) {
return +(g.valueList[g.valueList.length - 1].split(',')[1]);
}
});
XValues = maxXValue(dailyDimension.group())
This is kind of a less efficient and less safe re-implementation of the maximum calculation using the aliasProp option, which let's you do pretty much whatever you want to to a group on every record addition and removal.
My untested assumption here is that the undocumented valueList function that is used internally in max/min/median will properly order. Might be easier/better to write a Crossfilter maximum aggregation and then modify it to also add the y-value to the group.
If you want to work through this with Reductio, I'm happy to do that with you here, but it will be easier if we have a working example on something like JSFiddle.

dc.js: extending line and area to end of chart

I'm trying to draw an area chart using dc.js, and the end date (i.e. far right) of the chart is based on the current date, not the last date in the dataset. In cases where there's a date gap between data points, I want the area to extend from one point to the next, not draw at 0.
Given this data:
var data = [
{domain: "foo.com", project: "pdp", repo: "myrepo", commit_date: "6/1/2014", lines_added: 100, lines_deleted: 50},
{domain: "foo.com", project: "pdp", repo: "myrepo", commit_date: "7/1/2014", lines_added: 100, lines_deleted: 50}
];
var ndx = crossfilter(data);
The chart's line/area currently ends at the "7/1/2014" data point, but I want it to stretch the entire length of the chart.
The relevant code for drawing the chart is:
var dateDim = ndx.dimension(function(d) {return d.commit_date;});
var minDate = dateDim.bottom(1)[0].commit_date;
var maxDate = new Date();
var domainGroup = dateDim.group().reduceSum(function(d) {return d.cumulative_lines;});
unshippedlineChart
.width(500).height(200)
.dimension(dateDim)
.group(domainGroup)
.renderArea(true)
.x(d3.time.scale().domain([minDate,maxDate]))
.brushOn(false)
.interpolate('step-after')
.yAxisLabel("Unshipped Value");
Full example is at http://jsfiddle.net/xayhkcvn/1/
You didn't actually ask a question :-), but I think you may be looking for ways to prefilter your data so that it gets extended to today, and to remove any zeros.
This stuff isn't built into dc.js, but there is some example code in the FAQ which may help. Specifically, there is a function remove_empty_bins which adapts a group to remove any zeros.
You could similarly define a function to add a final point (untested):
function duplicate_final_bin(source_group, key) {
return {
all:function () {
var ret = Array.prototype.slice.call(source_group.all()); // copy array
if(!ret.length) return ret;
ret.push({key: key, value: ret[ret.length-1].value});
return ret;
}
};
}
You can compose this with remove_empty_bins:
var super_group = duplicate_final_bin(remove_empty_bins(domainGroup), maxDate);
The idea is to create a wrapper object which dynamically adds or remove stuff from the (always changing) source_group.all() on demand. dc.js will call group.all() whenever it is redrawing, and these wrappers intercept that call and adapt the data the crossfilter group returns.

Categories