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.
Related
I'm new to dc.js and trying to implement a something like the "Monthly Index Abs Move" graph in the demo at https://dc-js.github.io/dc.js/
(see document source at https://dc-js.github.io/dc.js/docs/stock.html).
ie. I'm trying to implement a line chart for "zoom in" view with a bar chart for the "zoomed out" view (rangeChart).
My problem is that when I filter a date range (eg. by using the "brushOn" the bar chart) then the bars that are filtered out disappear
The demo has this working correctly - the bars outside the date range are gray and those within the date range are blue - see screenshots.
I'm using the css file used in the demo, and I'm using very similar code (see code below), so I'm not sure why this difference.
var maxDate = new Date(1985, 0, 1);
var minDate = new Date(2200, 12, 31);
events.forEach(function (d) {
d.created = new Date(d.created);
//d.last_modified = new Date(d.last_modified);
d.hour = d3.time.hour(d.created); // precaclculate for performance
d.day = d3.time.day(d.created);
if (d.created > maxDate) {
maxDate = d.created;
}
if (d.created < minDate) {
minDate = d.created;
}
});
var ndx = crossfilter(events);
var dateDimension = ndx.dimension(dc.pluck('created'));
var chatHourDim = ndx.dimension(dc.pluck('hour'));
var chatDayDim = ndx.dimension(dc.pluck('day'));
var chatsPerHourGroup = chatHourDim.group().reduceCount();
var chatsPerDayGroup = chatDayDim.group().reduceCount();
visitorsPerHour /* dc.lineChart('#visitors-count', 'chartGroup'); */
.renderArea(true)
.width(900)
.height(200)
.transitionDuration(10)
.margins({top: 30, right: 40, bottom: 25, left: 40})
.dimension(chatHourDim)
.mouseZoomable(true)
// Specify a “range chart” to link its brush extent with the zoom of the current “focus chart”.
.rangeChart(visitorsPerDay)
.x(d3.time.scale().domain([minDate, maxDate]))
.round(d3.time.hour.round)
.xUnits(d3.time.hours)
.elasticY(true)
.renderHorizontalGridLines(true)
.legend(dc.legend().x(650).y(10).itemHeight(13).gap(5))
.brushOn(false)
.group(chatsPerHourGroup, 'Chat events per hour')
.title(function (d) {
var value = d.value;
if (isNaN(value)) {
value = 0;
}
return dateFormat(d.key) + '\n' + value + " chat events";
});
// dc.barChart("visitors-count-per-day", 'chartGroup');
visitorsPerDay.width(900)
.height(40)
.margins({top: 0, right: 50, bottom: 20, left: 40})
.dimension(chatDayDim)
.group(chatsPerDayGroup)
// .centerBar(true)
.gap(1)
.brushOn(true)
.x(d3.time.scale().domain([minDate, maxDate]))
.round(d3.time.day.round)
.alwaysUseRounding(true)
.xUnits(d3.time.days);
The way dc.js and crossfilter ordinarily support this functionality is that a crossfilter group does not observe its own dimension's filters.
The range chart example in the stock example uses the same dimension for both charts (moveMonths). So, when the focus chart is zoomed to the selected range in the range chart, it does filter the data for all the other charts (which you want), but it does not filter the range chart.
If you want to use different dimensions for the two charts, I can see a couple ways to get around this.
Using a fake group
Perhaps the easiest thing to do is snapshot the data and disconnect the range chart from later filters, using a fake group:
function snapshot_group(group) {
// will get evaluated immediately when the charts are initializing
var _all = group.all().map(function(kv) {
// don't just copy the array, copy the objects inside, because they may change
return {key: kv.key, value: kv.value};
});
return {
all: function() { return _all; }
};
}
visitorsPerDay
.group(snapshot_group(chatsPerDayGroup))
However, the range chart also won't respond to filters on other charts, and you probably want it to.
Same dimension, different groups
So arguably the more correct thing is to use only one time dimension for both the focus and range charts, although it kills the optimization you were trying to do on binning. A group optionally takes its own accessor, which takes the dimension key and produces its own key, which must preserve the ordering.
Seems like it was probably designed for exactly this purpose:
var dateDimension = ndx.dimension(dc.pluck('created'));
var chatsPerHourGroup = dateDimension.group(function(d) {
return d3.time.hour(d);
}).reduceCount();
var chatsPerDayGroup = dateDimension.group(function(d) {
return d3.time.day(d);
}).reduceCount();
visitorsPerHour /* dc.lineChart('#visitors-count', 'chartGroup'); */
.dimension(dateDimension)
.group(chatsPerHourGroup, 'Chat events per hour')
visitorsPerDay.width(900)
.dimension(dateDimension)
.group(chatsPerDayGroup)
I don't know if you'll notice a slowdown. Yes, JavaScript date objects are slow, but this shouldn't be an issue unless you are converting tens or hundreds of thousands of dates. It's usually DOM elements that are the bottleneck in d3/dc, not anything on the JavaScript side.
I'm trying for days now to get Highstock working with an external CSV file. The issue was first that the imported file was sorted in "descending" order whereas Highcharts requires the data to be sorted in "ascending" order. Once I found a JSFiddle/Codepen close to my problem, I managed to display the data correctly.
Now the problem is that on the x-axis the dates are displayed as something like 00:00:00.500 whereas it should be looking like this 2016-03-11.
I have created a codepen since it may be easier for you to respond to it than copy/pasting here a lot of code: http://codepen.io/bauhausweb/pen/aNpbxg
Thanks for looking into my issue!
For your example, there seems to at least be the problem of 2016-03-11 simply being a string and not a timestamp in milliseconds, which causes it to chose the defaults of 0, 1, 2, ... as x-values instead.
Below I've provided an example of how you can use the data modules csv attribute to achieve a similar result, with the help of the complete function:
$(function () {
$.get("https://www.example.com/my.csv", function (csv) {
$('#container').highcharts('StockChart', {
data: {
complete: function(o) {
o.series[0].data.reverse();
},
csv: csv
}
});
});
});
Or look at this JSFiddle demonstration.
Try to do something like this:
$.each(lines, function (lineNo, line) {
if (lineNo > 0 && lineNo < 557) {
var items = line.split(',');
// var seriesname = String(items[0]); // this is the area name
var seriesname = "Gold"; // this is the area name
var price = parseFloat(items[1]);
var f_date = items[0];
var format = String(f_date.replace(/-/g,','));
var date_items = format.split(',');
var d = Date.UTC(date_items[0],date_items[1],date_items[2]);
console.log(d);
var date = d;
// this will be the id of the drilldown
// var shift_one_value = parseFloat(items[3]); // drilldown shift1 value
// var shift_two_value = parseFloat(items[4]); // drilldown shift2 value
series.data.push({
name: seriesname,
y: price,
x: date
});
}
});
The problem is the date it would be formated in UTC
Your code has a lot of oddities. Your xAxis is set to datetime which is good. With a point interval of one day - also good. But, if you look at your series.data you are sending in data formatted like:
{
name: "Gold",
x: "1233.6",
y: 1233.6
}
You are setting the y here:
var date = String(items[1]);
You should be using items[0]. Now, you also have to parse this string into javascript time. Something like this can work:
var arr = String(items[0]).split("-");
var date = Date.UTC(arr[0], arr[1], arr[2]);
However, now your chart throws error that date is not sorted. See updated pen here.
I'm trying to create a number of interactive pie charts using dc.js to represent a basketball team's stats. One pie chart would have each player's points, another their salary, etc., represented as a portion of the team's total.
I don't really understand how to use the crossfilter .dimension() and .group() functions. And does reduceSum() really need to be in there? All I'm getting is an empty pie chart. Other examples I've seen have differenty structured data and don't apply to this situation.
Here is a bit of the relevant code for a salary pie chart:
var data = [
{"player":"boomhauer", "rebound_avg":12.1, "salary":4000, "point_avg":15},
{"player":"bill", "rebound_avg":4.2, "salary":3000, "point_avg":20},
{"player":"hank", "rebound_avg":12.1, "salary":4000, "point_avg":15},
{"player":"dale", "rebound_avg":16.1, "salary":6000, "point_avg":4},
];
var ndx = crossfilter(data);
var playerDim = ndx.dimension(function(d) {return d.player;});
var salaryPerPlayer = playerDim.group().reduceSum(function(d) {return d.salary;});
var salaryGroupPerPlayer = salaryPerPlayer.group(function(d) {return d.player; });
var salaryRingChart = dc.pieChart("#chart-ring-salary");
salaryRingChart
.width(200)
.height(200)
.slicesCap(17)
.innerRadius(10)
.dimension(salaryPerPlayer)
.group(salaryGroupPerPlayer)
.renderLabel(true);
dc.renderAll();
All I'm getting is an empty pie chart.
Thanks for your help!
I think just lose the salaryGroupPerPlayer:
var data = [
{"player":"boomhauer", "rebound_avg":12.1, "salary":4000, "point_avg":15},
{"player":"bill", "rebound_avg":4.2, "salary":3000, "point_avg":20},
{"player":"hank", "rebound_avg":12.1, "salary":4000, "point_avg":15},
{"player":"dale", "rebound_avg":16.1, "salary":6000, "point_avg":4},
];
var ndx = crossfilter(data);
var playerDim = ndx.dimension(function(d) {return d.player;});
var salaryPerPlayer = playerDim.group().reduceSum(function(d) {return d.salary;});
var salaryRingChart = dc.pieChart("#chart-ring-salary");
salaryRingChart
.width(200)
.height(200)
.slicesCap(17)
.innerRadius(10)
.dimension(salaryPerPlayer)
.group(salaryPerPlayer)
.renderLabel(true);
dc.renderAll();
Here's a good Crossfilter tutorial: http://blog.rusty.io/2012/09/17/crossfilter-tutorial/
The reduceSum is necessary because the default aggregation on a group in Crossfilter is a count. This would just count the number of records for each player, which is 1. So, not very interesting!
I would question why you are using Crossfilter at all though. The data is all pre-aggregated and you are only going to have 1 dimension (player). Filtering on these charts won't really make sense. Crossfilter is designed more for scenarios in which you have dis-aggregated data (maybe 1 record for each player in each game, o even play-by-play data) and you want to aggregate the data and filter dynamically on different dimensions.
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...
I am using this library: Dimensional Charting to build some relatively standard charts that need CrossFilter functionality.
I have been following the examples but they aren't working for me.
Here is my code:
var dashData = crossfilter(data.report),
dataByHour = dashData.dimension(function(d){ return d3.time.hour(new Date(d.timestamp))}),
totalByHour = dataByHour.group().reduceSum(function(d) { return d.amount }),
dc.barChart("#graphTimeOverview")
.width(990) // (optional) define chart width, :default = 200
.height(250) // (optional) define chart height, :default = 200
.transitionDuration(500) // (optional) define chart transition duration, :default = 500
.margins({top: 10, right: 50, bottom: 30, left: 40})
.dimension(dataByHour) // set dimension
.group(totalByHour) // set group
.elasticY(true)
.centerBar(true)
.gap(1)
.x(d3.time.scale().domain([new Date(data.report[0].timestamp), new Date(data.report[(data.report.length - 1)].timestamp)]))
.round(d3.time.hour.round)
.xUnits(d3.time.hours)
.renderHorizontalGridLines(true);
dc.renderAll();
I know the crossfilter data is working correctly, here is a sample of the group:
totalByHour:
[ {key:(new Date(1361746800000)), value:6170.17},
{key:(new Date(1361678400000)), value:3003},
{key:(new Date(1361581200000)), value:2350.42},
{key:(new Date(1361667600000)), value:1636.19},
etc...
]
Unfortunately all this gets me is an empty graph, it seems to compute the y-axis correctly, so it would seem to me that it can read the data, however I never see any bar values:
Maybe the data.report array is not sorted by timestamp (the sample provided is unsorted). In your code, you assume that those values are sorted. You can try using
// Compute the timestamp extent
var timeExtent = d3.extent(data.report, function(d) { return d.timestamp; });
dc.barChart("#graphTimeOverview")
// more settings here
.x(d3.time.scale().domain(timeExtent.map(function(d) { return new Date(d); })))
.round(d3.time.hour.round)
.xUnits(d3.time.hours)
.renderHorizontalGridLines(true);
It would be easier to tell what is the problem if you provide a jsFiddle.
I started using dc.js few days ago, so I don't know for sure. But, I think your code should go in
d3.csv("data.csv", function(data) { //your-code };
or
d3.json("data.json", function(data) {//your-code};
or
jQuery.getJson("data.json", function(data){//your-code});