d3.js: Align text labels between ticks on the axis - d3.js

Is there an easy way to align text labels between ticks?
Here is my time axis with labels above the ticks:
I would like to place these labels like here:

I ended up with one of the Lars Kotthoff's advices.
Every time when I call(axis) I also adjust text labels.
Here is simplified code:
function renderAxis() {
axisContainer
.transition().duration(300)
.call(axis) // draw the standart d3 axis
.call(adjustTextLabels); // adjusts text labels on the axis
}
function adjustTextLabels(selection) {
selection.selectAll('.major text')
.attr('transform', 'translate(' + daysToPixels(1) / 2 + ',0)');
}
// calculate the width of the days in the timeScale
function daysToPixels(days, timeScale) {
var d1 = new Date();
timeScale || (timeScale = Global.timeScale);
return timeScale(d3.time.day.offset(d1, days)) - timeScale(d1);
}
Update:
BTW, here is a calendar demo with I ended up: http://bl.ocks.org/oluckyman/6199145

There is no easy (i.e. built-in) way of doing this, but you can still achieve it. There are a few options. The most straightforward one is probably to use the tickFormat function to specify a format with a suitable number of spaces in front/after the numbers. This would need to be hand-tuned for each application though.
Alternatively, you could select the label elements after they have been drawn and add a suitable transform attribute that shifts them accordingly. Again, this would have to be hand-tuned.
Your third option is to have two different axes, one for the ticks and one for the labels. The idea is that the axis that provides the ticks has no labels and the other one no ticks. You would need to set the tick values appropriately, but at least you wouldn't have to guess the right offset.

You might want to consider using D3FC, which has a drop-in replacement for the D3 axis component that supports this feature.
Here's an example which substitutes the D3 axis d3.axisBottom, for the D3FC equivalent fc.axisBottom:
const axis = fc.axisBottom(linear)
.tickCenterLabel(true);
The tickCenterLabel centres the axis labels as requested.
Here's what the axis looks like with tickCenterLabel = false:
And here with the tickCenterLabel = true:
Full disclosure - I'm a maintainer and contributor to D3FC

You can do this by using axis.tickSize(major[[,minor],end]) and .tickSubdivide(). Your ticks are set to line up with the major ticks, but if you set the height of these ticks to 0, and set some height for minor ticks, and specify that there is one minor tick between each pair of major ticks, you will end up with tick labels between your ticks. Your code would look like this:
var myAxis = d3.svg.axis()
.ticks(15)
.tickSubdivide(1)
.tickSize(0, 6, 0);
Note that you need to explicitly set an end size. If you only provide two numbers, they will be interpreted as major and end and minor will default to 0.
Here's a fiddle.

I often do this by stacking multiple axes, each with a custom .tickFormat().
If I'm placing labels in between dates, I'll often do something like this:
#timeDaysAxisLabels = d3.svg.axis()
.scale(#timescale)
.orient('bottom')
.ticks(d3.time.hour.utc, 12) # I want ticks at noon, easiest to just get them ever 12 hours
.tickFormat((d) =>
# only draw labels at noon, between the date boundaries
if d.getUTCHours() == 12
# draw the label!
formatter = d3.time.format.utc('%a %d %b') # "Mon 12 Apr"
return formatter(d)
else
# not noon, don't draw anything
return null)
.tickSize(0)
.tickPadding(30)
I'll also create a separate axis with no labels at all, and a non-zero .tickSize() to actually draw ticks, but this block above positions date labels in the center of the "column".

Already a few good replies but just to add one more. Note the use of text-anchor.
Same idea: After your call, select the text, reposition.
.call(xAxis)
.selectAll(".tick text")
.style("text-anchor", "start")
.attr("x", axisTextAdjust)

svg.append("g")
.attr("class", "axis axis-years")
.attr("transform", "translate(0," + (height + 1) + ")")
.call(xAxis)
.selectAll("text")
.attr("x", "-1.8em")
.attr("y", ".00em")
.attr("transform", function (d) {
return "rotate(-90)"});

Related

Add text / label to each point / circle in nvd3 scatter plot?

I've followed the nvd3 scatter plot example to create a scatter plot: http://nvd3.org/examples/scatter.html
What i'd like to do is display the "size" property (from the data) beneath each circle. I've been trying various combinations of trying to selectAll of the "g.nv-group" elements and then appending "text", but nothing is working.
Any thoughts?
If the data has a label property and
chart.showLabels(true)
then this discussion on a closed (not merged) PR suggests it should work. Works on lineCharts.
I tweaked code from the link from the comment above, in my case, this worked
d3.selectAll(".nv-group path")[0].forEach(function(d){
var tf = d3.select(d).attr("transform")
t = d3.transform(tf).translate;
t[0] = t[0] +10;//moving the translate x by 5 pixel.
console.log(d3.select(d).data()[0])//data associated with the point
d3.select(d.parentNode)
.append("text")
.attr("class", "label")
.text("data: "+ d3.select(d).data()[0][0].size)//putting data
.attr("transform", "translate("+t[0]+","+t[1]+")");
});

DimpleJS barchart styling columns

I'm basically using a modified version of : http://dimplejs.org/advanced_examples_viewer.html?id=advanced_bar_labels .
I'd like to be able to add for each value a border on the left as high as the value (with a specific color for that border).
I'm not really sure where to start for adding that.
Any ideas?
Thanks.
More details : This is what I'd like to obtain : https://dl.dropboxusercontent.com/u/2227188/Image%202.png - the border on the left is the issue. (jsfiddle.net/mkzTk/5/ this what I currently have which is pretty much what's in the example - I don't know where to start really for adding a border)
You could append a rectangle after drawing for each element of the series as follows:
mySeries.afterDraw = function (s, d) {
var shape = d3.select(s);
svg.append("rect")
.attr("x", shape.attr("x"))
.attr("y", shape.attr("y"))
.attr("height", shape.attr("height"))
.attr("width", "10px")
.style("fill", shape.style("stroke"))
.style("pointer-events", "none");
};
The example you mention already uses the afterDraw function so just add the contents above to the existing method for labelling.
It looks nice, here's an example:
http://jsbin.com/lorin/9/edit?js,output#J:L20
I would set up each bar + edge pair as its own group based on a certain data point, and then append two rect elements to that group. Differences in color can be used to give them their distinctive colors.
Your code would look something like this:
var monthBars = d3.selectAll('.monthBar') //These will be for each chart
.data(allMyData, idFunction) //Assign and key your data
.enter()
.append('g')
.classed('monthBar', true);
.each(function(d){
var taskGroups = d3.select(this).selectAll('.taskGroup')
.data(d.dataForThisMonth, taskIdFn)
.enter()
.append('g')
.classed('.taskGroup', true);
.attr('transform', ...) //Define the x and y positioning for the group
taskGroups.append('rect')
//Make this the 'body' rect with the text in it
taskGroups.append('rect')
//Make this the edge rect
})

dimple.js: get co-ordinates of point/position on axis

I have a Dimple.JS scatter plot with a time-based (in years) X-axis. I'd like (in a similar manner to this D3 question) to be able to shade in an arbitrary area (ideally the start and end positions wouldn't necessarily be data points in the series).
Is there an existing function that will let me supply a year and give me the X co-ordinate the correct position on the scale in the SVG, which I can then use the construct my rectangle (I tried to look at the source code to figure out how dimple does it's positioning...)?
Alternatively, if it's more practical to use points already plotted on the chart, what's the correct way to use d3.select with dimple to access a specific one? My series has a date field (dd/mm/yyyy) so I have SVG elements like this:
<circle id="All_Wed Mar 18 1931 00:00:00 GMT+0000 (GMT)__" class="series0 bubble All Wed_Mar_18_1931_00:00:00_GMT+0000_(GMT) " cx="465.0000000006503" cy="362.1714285714286" r="2" opacity="0.8" fill="#e90e0e" stroke="#c20b0b"></circle>
… my guess was I should use mySeries.shapes.select(id) to access that, but for:
mySeries.shapes.select("#All_Wed Mar 18 1931 00:00:00 GMT+0000 (GMT)__");
or (if I escape it, unless there's a silly syntax error):
mySeries.shapes.select("#All_Wed Mar\ 18\ 1931\ 00:00:00\ GMT+0000\ (GMT)__");
I get "Not a valid selector".
(Thanks)
You need to use a non-public method of the axes to do this, so it may not work this way in future versions (>1.1.5) however between you and me, I don't think the scale method of the axis is going to be disappearing any time soon.
The _scale method is the raw d3 scale method added once the draw method of the chart is called so it can convert the values for you. I've created a fiddle to illustrate the solution. This will need a little tweaking if you are dealing with negative values or log axes:
// Draw a simple chart
var svg = dimple.newSvg("body", 800, 600);
var data = [
{ "a":300, "b":2000, "c":"a" },
{ "a":400, "b":3000, "c":"b" },
{ "a":340, "b":2200, "c":"c" },
{ "a":300, "b":5000, "c":"d" }
];
var chart = new dimple.chart(svg, data);
var x = chart.addMeasureAxis("x", "a");
var y = chart.addMeasureAxis("y", "b");
chart.addSeries("c", dimple.plot.bubble);
chart.draw();
// Draw a grey region using the following co-ordinates
var fromX = x._scale(210),
toX = x._scale(320),
fromY = y._scale(2200),
toY = y._scale(3100)
svg.append("rect")
.attr("x", fromX)
.attr("y", toY)
.attr("width", toX - fromX)
.attr("height", fromY - toY)
.style("fill", "grey")
.style("opacity", 0.2);
Here's the fiddle: http://jsfiddle.net/T6ZDL/7/

Add a custom tick / marker for d3.time.scale() in a dc.js seriesChart

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.

D3.js: calculate x-axis time scale for bar graph?

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.

Resources