I have added a datetime X-axis to my rickshaw graph:
var x_axis = new Rickshaw.Graph.Axis.Time({
graph: graph,
timeFixture: new Rickshaw.Fixtures.Time(),
});
However, it doesn't generally give me the format I want. Can I give it a specifier so the datetimes are always in a specified format (i.e. something like d3.time.format(specifier) )?
Based on Lars' linked example I have done the following:
var format = function(d) {
d = new Date(d)
return d3.time.format("%c")(d)
}
var x_axis = new Rickshaw.Graph.Axis.X({
graph: graph,
tickFormat: format,
});
Which seems to work, so now I just have to find a way to make the spacing come out okay....
The formatter will only format the string, it will not determine the spacing.
In order to control spacing and formatting, you could write your own 'Fixture', e.g. take a look at https://github.com/shutterstock/rickshaw/blob/master/src/js/Rickshaw.Fixtures.Time.js for an example.
The fixture provides two things: the spacing (e.g. year, month, day, hour) and the formatting of each.
Create a similar fixture, space and format to your needs and set it on the x-axis:
var xAxis = new Rickshaw.Graph.Axis.Time( {
graph: graph,
//timeFixture: new Rickshaw.Fixtures.Time()
timeFixture: new MyOwnTimeFixture()
} );
try this one it will work
var xAxis = new Rickshaw.Graph.Axis.Time({
graph: graph,
tickFormat: function(x){
return new Date(x).toLocaleString();
},
ticks: 4
});
Related
Good Evening Everyone,
I'm trying to take the data from a database full of hour reports (name, timestamp, hours worked, etc.) and create a plot using dc.js to visualize the data. I would like the timestamp to be on the x-axis, the sum of hours for the particular timestamp on the y-axis, and a new bar graph for each unique name all on the same chart.
It appears based on my objectives that using crossfilter.js the timestamp should be my 'dimension' and then the sum of hours should be my 'group'.
Question 1, how would I then use the dimension and group to further split the data based on the person's name and then create a bar graph to add to my composite graph? I would like for the crossfilter.js functionality to remain intact so that if I add a date range tool or some other user controllable filter, everything updates accordingly.
Question 2, my timestamps are in MySQL datetime format: YYYY-mm-dd HH:MM:SS so how would I go about dropping precision? For instance, if I want to combine all entries from the same day into one entry (day precision) or combine all entries in one month into a single entry (month precision).
Thanks in advance!
---- Added on 2017/01/28 16:06
To further clarify, I'm referencing the Crossfilter & DC APIs alongside the DC NASDAQ and Composite examples. The Composite example has shown me how to place multiple line/bar charts on a single graph. On the composite chart I've created, each of the bar charts I've added a dimension based off of the timestamps in the data-set. Now I'm trying to figure out how to define the groups for each. I want each bar chart to represent the total time worked per timestamp.
For example, I have five people in my database, so I want there to be five bar charts within the single composite chart. Today all five submitted reports saying they worked 8 hours, so now all five bar charts should show a mark at 01/28/2017 on the x-axis and 8 hours on the y-axis.
var parseDate = d3.time.format('%Y-%m-%d %H:%M:%S').parse;
data.forEach(function(d) {
d.timestamp = parseDate(d.timestamp);
});
var ndx = crossfilter(data);
var writtenDimension = ndx.dimension(function(d) {
return d.timestamp;
});
var hoursSumGroup = writtenDimension.group().reduceSum(function(d) {
return d.time_total;
});
var minDate = parseDate('2017-01-01 00:00:00');
var maxDate = parseDate('2017-01-31 23:59:59');
var mybarChart = dc.compositeChart("#my_chart");
mybarChart
.width(window.innerWidth)
.height(480)
.x(d3.time.scale().domain([minDate,maxDate]))
.brushOn(false)
.clipPadding(10)
.yAxisLabel("This is the Y Axis!")
.compose([
dc.barChart(mybarChart)
.dimension(writtenDimension)
.colors('red')
.group(hoursSumGroup, "Top Line")
]);
So based on what I have right now and the example I've provided, in the compose section I should have 5 charts because there are 5 people (obviously this needs to be dynamic in the end) and each of those charts should only show the timestamp: total_time data for that person.
At this point I don't know how to further breakup the group hoursSumGroup based on each person and this is where my Question #1 comes in and I need help figuring out.
Question #2 above is that I want to make sure that the code is both dynamic (more people can be handled without code change), when minDate and maxDate are later tied to user input fields, the charts update automatically (I assume through adjusting the dimension variable in some way), and if I add a names filter that if I unselect names that the chart will update by removing the data for that person.
A Question #3 that I'm now realizing I'll want to figure out is how to get the person's name to show up in the pointer tooltip (the title) along with timestamp and total_time values.
There are a number of ways to go about this, but I think the easiest thing to do is to create a custom reduction which reduces each person into a sub-bin.
First off, addressing question #2, you'll want to set up your dimension based on the time interval you're interested in. For instance, if you're looking at days:
var writtenDimension = ndx.dimension(function(d) {
return d3.time.hour(d.timestamp);
});
chart.xUnits(d3.time.hours);
This will cause each timestamp to be rounded down to the nearest hour, and tell the chart to calculate the bar width accordingly.
Next, here's a custom reduction (from the FAQ) which will create an object for each reduced value, with values for each person's name:
var hoursSumGroup = writtenDimension.group().reduce(
function(p, v) { // add
p[v.name] = (p[v.name] || 0) + d.time_total;
return p;
},
function(p, v) { // remove
p[v.name] -= d.time_total;
return p;
},
function() { // init
return {};
});
I did not go with the series example I mentioned in the comments, because I think composite keys can be difficult to deal with. That's another option, and I'll expand my answer if that's necessary.
Next, we can feed the composite line charts with value accessors that can fetch the value by name.
Assume we have an array names.
compositeChart.shareTitle(false);
compositeChart.compose(
names.map(function(name) {
return dc.lineChart(compositeChart)
.dimension(writtenDimension)
.colors('red')
.group(hoursSumGroup)
.valueAccessor(function(kv) {
return kv.value[name];
})
.title(function(kv) {
return name + ' ' + kv.key + ': ' + kv.value;
});
}));
Again, it wouldn't make sense to use bar charts here, because they would obscure each other.
If you filter a name elsewhere, it will cause the line for the name to drop to zero. Having the line disappear entirely would probably not be so simple.
The above shareTitle(false) ensures that the child charts will draw their own titles; the title functions just add the current name to those titles (which would usually just be key:value).
I try to dynamically change the number of ticks on an axis via an input field
// updated by an input field
var nNumberOfTicks;
function updatenumberofticks(nValue) { nNumberOfTicks = nValue; }
// definition of the axis
var xAxis = d3.svg.axis()
.orient('bottom')
.ticks(nNumberOfTicks)
.scale(xScale);
but it doesn't work. See here: https://jsfiddle.net/stefanooo/cn2xo56w/3/.
This example works perfectly when changing xmin. What do I have to change to make it work for the number of ticks also?
You need to give the updated number of ticks to the axis component after changing it, e.g.
d3.select("#numberofticks").on("change", function() {
updatenumberofticks(+this.value);
vis.select('.xaxis').transition().call(xAxis.ticks(+this.value));
});
Complete example here.
I'm using code similar to that in the dc.js annotated example:
var ndx = crossfilter(data);
...
var dayName=["0.Sun","1.Mon","2.Tue","3.Wed","4.Thu","5.Fri","6.Sat"];
var dayOfWeek = ndx.dimension(function (d) {
var day = d.dd.getDay();
return dayName[day];
});
var dayOfWeekGroup = dayOfWeek.group();
var dayOfWeekChart = dc.rowChart("#day-of-week-chart");
dayOfWeekChart.width(180)
.height(180)
.group(dayOfWeekGroup)
.label(function(d){return d.key.substr(2);})
.dimension(dayOfWeek);
The issue I've got is that only days of the week present in the data are displayed in my rowChart, and there's no guarantee every day will be represented in all of my data sets.
This is desirable behaviour for many types of categories, but it's a bit disconcerting to omit them for short and well-known lists like day and month names and I'd rather an empty row was included instead.
For a barChart, I can use .xUnits(dc.units.ordinal) and something like .x(d3.scale.ordinal.domain(dayName)).
Is there some way to do the same thing for a rowChart so that all days of the week are displayed, whether present in data or not?
From my understanding of the crossfilter library, I need to do this at the chart level, and the dimension is OK as is. I've been digging around in the dc.js 1.6.0 api reference, and the d3 scales documentation but haven't had any luck finding what I'm looking for.
Solution
Based on #Gordon's answer, I've added the following function:
function ordinal_groups(keys, group) {
return {
all: function () {
var values = {};
group.all().forEach(function(d, i) {
values[d.key] = d.value;
});
var g = [];
keys.forEach(function(key) {
g.push({key: key,
value: values[key] || 0});
});
return g;
}
};
}
Calling this as follows will fill in any missing rows with 0s:
.group(ordinal_groups(dayNames, dayOfWeekGroup))
Actually, I think you are better off making sure that the groups exist before passing them off to dc.js.
One way to do this is the "fake group" pattern described here:
https://github.com/dc-js/dc.js/wiki/FAQ#filter-the-data-before-its-charted
This way you can make sure the extra entries are created every time the data changes.
Are you saying that you tried adding the extra entries to the ordinal domain and they still weren't represented in the row chart, whereas this did work for bar charts? That sounds like a bug to me. Specifically, it looks like support for ordinal domains needs to be added to the row chart.
I am trying to display a time based d3 area graph. There is a very strange 'tail' at the start of thie graph and I'm not sure what is causing the issue.
Here is the JSfiddle http://jsfiddle.net/qxkRJ/
I convert the date to the correct format but am wondering if the issue arises because of the data isn't ordered by the datetime value.
data.forEach(function (d) {
var newDate = new Date(d.daterun/1000000);
d.daterun = newDate;
d.intvalue = +d.intvalue;
});
All you need to do is sort the data:
data.sort(function(a, b) { return a.daterun - b.daterun; });
Complete jsfiddle here.
I have the following dataset:
var data = [
{
"air_used": 0.660985,
"datestr": "2012-12-01 00:00:00",
"energy_used": 0.106402
},
{
"air_used": 0.824746,
"datestr": "2013-01-01 00:00:00",
"energy_used": 0.250462
} ...
]
And I want to draw a bar graph (for air_used) and line graph (for energy_used) that look like this:
My problem is that at the moment, with the x-scale I'm using, the graph looks like this - basically the bars are in the wrong position, and the last bar is falling off the chart:
Here is a JSFiddle with full code and working graph: http://jsfiddle.net/aWJtJ/4/
To achieve what I want, I think I need to amend the x-scale so that there is extra width before the first data point and after the last data point, and so that the bars are all shifted to the left by half the width of each bar.
Can anyone help me figure out what I need to do with the x-scale?
I've tried adding an extra month to the domain - that stops the last bar falling off the end of the graph, but it also adds an extra tick that I don't want, and it doesn't fix the position of the line graph and ticks.
If possible I want to continue to a time scale for the x-axis, rather than an ordinal scale, because I want to use D3's clever time-based tick formatters and date parsers, e.g. xAxis.ticks(d3.time.weeks, 2).
Expand your domain to be +1 and -1 month from the actual extent of your data. That will pad the graph with the extra months on either side and then update the bar width to add 2 to the count of data elements.
var barRawWidth = width / (data.length + 2);
See this fiddle: http://jsfiddle.net/reblace/aWJtJ/6/
If you want to hide the lower and upper boundary months, you can hack it like this: http://jsfiddle.net/reblace/aWJtJ/7/ by just adding and subtracting 20 days instead of a whole month, but there are probably more elegant ways to do it.
var xExtent = d3.extent(data, function(d) { return d.date; });
var nxExtent = [d3.time.day.offset(xExtent[0], -20), d3.time.day.offset(xExtent[1], 20)];
x.domain(nxExtent);
As pointed out in the comments, I think the best approach is to use d3.scale.ordinal. Note that using it doesn't prevent you from using d3.time parsers, but you need to take into account the bar width to align the line with the bars.
An example solution is here:
http://jsfiddle.net/jcollado/N8tuR/
Relevant code from the solution above is as follows:
// Map data set to dates to provide the whole domain information
var x = d3.scale.ordinal()
.domain(data.map(function(d) {
return d.date;
}))
.rangeRoundBands([0, width], 0.1);
...
// Use x.rangeBand() to align line with bars
var line = d3.svg.line()
.x(function(d) { return x(d.date) + x.rangeBand() / 2; })
.y(function(d) { return y(d.energy_used); });
...
// Use x.rangeBand() to set bar width
bars.enter().append("rect")
.attr("class", "air_used")
.attr("width", x.rangeBand())
...
Note that date parsing code has been moved up to have d.date available when creating the x scale. Aside from that, d3.time statements have not been modified at all.