Hey so I'm having difficulty with the positioning of my stacked bar chart. It's showing up, I'm just having difficulty declaring it's x axis. Here is the jsfiddle: http://jsfiddle.net/E2HST/
var xTimeScale = d3.time.scale().
domain([new Date(data[0].date), d3.time.day.offset(new Date(data[data.length - 1].date), 1)])
.range([0, width]);
is obviously part of the problem, I pulled code and have unfortunately fallen into the trap of not fully understanding it.
var bars = svg.selectAll(".bar")
.data(data).enter()
.append("g")
.attr("class","bar")
.attr("transform", function(d){
return "translate("+xTimeScale(d.date)+",0)"
})
I've tried swapping in d.year for d.date seeing as there is no more d.date but it's throwing up errors. Any help would be greatly appreciated.
The simple answer is that the objects in your data array do not have a date key, so your xTimeScale domain is undefined. An easy way to correct this would be to create this key for each data item:
data.forEach( function(d) { d.date = new Date(d.year, 0, 1); });
Which creates the date as January 1st of the year variable. This basically solves your x-axis problem.
I would, however, suggest that your data would be better suited to using an ordinal scale, since you only have yearly data.
A couple of other small things:
Your x-axis definition has way too many ticks, consider changing this
Consider adding css styles for .axis elements, to improve readability
An updated fiddle with these slight changes is at http://jsfiddle.net/E2HST/1/
Related
I draw a line chart using D3 with some data from our database, I got some data for the entire year to calculate what would be our trendline (taking some values for 8am, 12m, 4pm and 9pm), I'm drawing this in the chart with path and values for each X (time).
Now my problem is the domain of the trendline is of course bigger than our current values (lets say its 2 pm and my trendline will always go to 9 pm). The closes I got was setting the trendline's domain to my current data domain, which returns this:
Test1
xTrend.domain(d3.extent(trendData, function (d) { return d.date; }));
How can I cut it so it doesn't go beyond the SVGs width? I tried setting the width attribute and it doesn't work, so my guess is it has something to do with the domain.
If I set the trendline's domain to its data, I get this:
Test2
xTrend.domain(d3.extent(data, function (d) { return d.date; }));
PS: While we are on this, if anyone can point me on how I could see if my line is above-below my trendline it would be great ;)
Update:
Thanks to #lhoworko
I added this
svg.append("defs").append("clipPath")
.attr("id", "clip")
.append("rect")
.attr("width", width)
.attr("height", height);
And this to my line path
.attr("clip-path", "url(#clip)")
Take a look at d3 clip paths. It will make sure the line beyond the end of the chart isn't displayed.
Take a look here.
https://developer.mozilla.org/en-US/docs/Web/SVG/Element/clipPath
I would like to add a marker for a specific date for a date axis. Please see the line chart below.
My xAxis is drawn by this function, where dateMin and dateMax can be set by the user through the front end (or a brush, etc.).
d3.time.scale.utc().domain([dateMin,dateMax]);
This means the tickValues are calculated automatically.
Now, there is a certain fixed date for our data where there is a cutoff. For example, we may have consolidated figures up to Jan. 31st 2014, and then projected data from February 1st 2014 onwards.
What I need to do is make it visually clear at what date the cutoff point is. I have manually drawn a red vertical line at the date into the JPG below. But how do I do this programatically with d3?
One caveat is that the user might choose a date range (using the brush, etc.) which does not include the cutoff date (say, Jan 1st 2014 to Jan 20th, 2014). In this case, no line should be drawn.
If possible, it would be even better if the actual lines of the line chart would look different from the cutoff date onwards. They could be dotted instead of solid, or their colours could be less saturated (.brighter ?), to make visually clear that the underlying data is not consolidated yet.
Thanks for any hints you can give me.
Sorry I can't post images to StackOverflow yet, hence I uploaded the example here:
Trying out code from the answers
Using the code below, the line and label get drawn, but not at the given x value (cutoffDate), but too "early" on the time scale, approximately on the evening of 2014-01-29.
var cutoffDate = new Date("2014-02-01T00:00:00Z");
seriesChart.svg().append("svg:line")
.attr("x1", xScale(cutoffDate))
.attr("x2", xScale(cutoffDate))
.attr("y1", yScale.range()[1])
.attr("y2", yScale.range()[0])
.style("stroke", "rgb(225,0,0)")
.style("stroke-width", "1");
seriesChart.svg()
.append("text")
.attr("text-anchor", "start")
.attr("x", xScale(cutoffDate))
.attr("y", 80)
.text("Projected data");
See the result here:
http://i.imgur.com/0PXKFup.jpg
In my original question, I didn't mention I am using seriesChart from dc.js:
seriesChart API docs
I suppose this does something with the xScale when it composes the seriesChart so setting a value on the xScale later on will result in a shifted display. Will investigate further.
Update: x position fixed
The correct way to append svg elements to a dc.js chart is not to use
chart.svg().append()
but
chart.chartBodyG().append()
This fixes the position offset for custom elements added to the chart. Using this in combination with Lars' answer works.
This would be difficult to achieve with one axis, but easy with separate axes. First, for the dividing line, you can use code like this:
svg.append("line")
.attr("x1", xScale(cutoffDate))
.attr("x2", xScale(cutoffDate))
.attr("y1", yScale.range()[0])
.attr("y2", yScale.range()[1]);
svg.append("text").attr("x", xScale(cutoffDate) + 10).attr("y", yCoord)
.text("projected");
To have different styles, use two different axes:
var xScale1 = d3.time.scale().domain([..., cutoffDate]).range([0, 100]),
xScale2 = d3.time.scale().domain([cutoffDate, ...]).range([100, 200]);
svg.append("g").attr("class", "before")
.call(d3.svg.axis().scale(xScale1));
svg.append("g").attr("class", "after")
.attr("transform", "translate(" + xScale1.range()[1] + ",0)")
.call(d3.svg.axis().scale(xScale2));
Once appended, you can style the axes using the classes or by selecting the individual DOM elements. This means you end up with two scales -- to make using them for computing coordinates easier, you could wrap them:
var xScale = function(x) { return x < cutoffDate ? xScale1(x) : xScale2(x); };
The exact best way to implement this will depend on your specific application, but the above should give you a rough guide how to do it.
I've created a pretty simple scatterplot showing a person's weightlifting progress over time. The x-axis is a time scale in days (there's one circle for each day) and the y-axis is the number of pounds lifted that day.
The scatterplot works in Chrome, but not Firefox. It has something to do with using a date object as the x-position of the circles.
Take a look at the chart in Chrome
Here are two rows of my dataset, to show how it is formatted (the date is the first item of each row):
["2011-09-16",150,"Cottage cheese and 1 apple",170,16,"4 Chicken breast strips",130,17,"Hibachi grill: onion soup, salad, 2 shrimp pieces, vegetables. 12 oz chicken, rice",880,99,"Small hard frozen yogurt",300,6,"Cottage cheese, greek yogurt, a bunch of icebreaker sours",230,26,1710,164,175,"Back/biceps, 31 pushups",135,0,0],
["2011-09-17",150,"15 peanuts",80,4,"Dim Sum",1000,40,"Azn salad, 2 serv chicken breast",490,57,"8.8 oz mixx",264,9,"(No Data)",0,0,1833.6875,109.55,174.2," ",135,0,0],
Here's how I draw the circles, and some relevant functions/variables I used to format the dates:
//Width and height
var w = 4200;
var h = 200;
var padding = 35;
var mindate = new Date("2011-9-15");
var maxdate = new Date("2012-5-29");
//Create scale functions
var xScale = d3.time.scale()
.domain([mindate, maxdate])
.range([padding, w - padding * 2]);
var format = d3.time.format("%Y-%m-%d");
var dateFn = function(d) {return format.parse(d[0])};
//Create circles
svg.selectAll("circle")
.data(dataset)
.enter()
.append("circle")
.attr("cx", function(d) {
return xScale(dateFn(d));
})
.attr("cy", function(d) {
return yScale(d[1]);
})
.attr("r", 6)
.attr("fill", "gray")
.attr("opacity", .5)
Like I said, it works exactly how I want in Chrome, but in Firefox, all the circles are on top of one another with cx=0. Any ideas would be appreciated. I asked my local d3 expert, and he showed me a project he did which also fails in Firefox due to drawing circles using date objects. He just gave up. a link to his project
The problem isn't the way you're parsing the dates, but the way you're setting up the scale.
var mindate = new Date("2011-9-15");
var maxdate = new Date("2012-5-29");
This is the code that works properly only in Chrome because you're relying on its date parsing by using the constructor rather than parsing explicitly like you're doing for the rest of the dates.
The fix is simple -- just parse the dates you're using to set the scale domain as well:
var format = d3.time.format("%Y-%m-%d"),
mindate = format.parse("2011-09-15"),
maxdate = format.parse("2012-05-29");
I am trying to build a sortable chart based off of Mike Bostock's recent health care chart on NYT: http://www.nytimes.com/interactive/2013/09/24/us/health-care-premiums.html
My issue is that my some of the columns in the chart require a custom scale, rather than a single scale for the entire key. I am wondering if there is a technique for assigning a custom scale for each column or set thereof?
Mike's table has a single scale for all the columns within the key:
var x = d3.scale.linear()
.domain([0, 11000])
.range([0, 320]);
....
row.transition()
.delay(function(d, i) { return i * 8; })
.selectAll(".g-table-bar")
.each("start", function(d) { this.style.width = x(d) + "px"; });
Ideally, I would like to assign a automatic variable scale based on each individual variable/column. Is this possible? It seems that d3.min and d3.max can only be used for an individual variable but it's likely I'm wrong.
Thanks. Ian
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.