I am attempting to rotate my x-axis label for a d3 stacked histogram I created in d3. All of the labels are either displayed as a long string or all on top of each other.
Here's my label code:
var shortNames = ["label1", "label2", "label3", "label4"];
// Add a label per experiment.
var label = svg.selectAll("text")
.data(shortNames)
.enter().append("svg:text")
.attr("x", function(d) { return x(d)+x.rangeBand()/2; })
.attr("y", 6)
.attr("text-anchor", "middle")
.attr("dy", ".71em")
.text(function(d) {return d})
.attr("transform", function(d) { // transform all the text elements
return "rotate(-65)" // rotate them to give a nice slope
});
I played around with the translate function and all of the labels are still treated as one long string. How do I apply translate to each individual label?
I can play around with the margins later, but for now I want to have control over my labels.
I think the issue is the order of transforms: When you rotate the text, you're also rotating its coordinate system. So when you set its x position –– even though you're setting the position earlier than the rotation transform –- you're actually moving it along the 65 degree axis resulting from the rotation.
If I'm correct about this, then inspecting the labels would reveal that they're still made up of multiple text elements (one per label), rather than one text element for all labels.
As a rule, when you introduce a transform attribute as you did for rotate, you should do all your transformation via this attribute. So, you need to use translate instead of using the "x" attribute. Then it would look like this:
var label = svg.selectAll("text")
.data(shortNames)
.enter().append("svg:text")
// REOVE THIS: .attr("x", function(d) { return x(d)+x.rangeBand()/2; })
// AND THIS TOO: .attr("y", 6)
.attr("text-anchor", "middle")
.attr("dy", ".71em")
.text(function(d) {return d})
.attr("transform", function(d) { // transform all the text elements
return "translate(" + // First translate
x(d)+x.rangeBand()/2 + ",6) " + // Translation params same as your existing x & y
"rotate(-65)" // THEN rotate them to give a nice slope
});
Related
Is it possible to add a piece of text in the centre of multiple rectangles in a D3 treemap chart? So what i'm after is something like this:
As you can see by the picture, I want to have text appear once in the middle of each “section” (different colours) on the chart. Is it possible to do this with D3? If so how would I achieve it.
Currently I've managed to have it appear on each individual rectangle, like so:
cell.append("svg:text")
.attr("x", function(d) { return d.dx / 2; })
.attr("y", function(d) { return d.dy / 2; })
.attr("dy", ".35em")
.text("test")
.style("opacity", function(d) { console.log(this.getComputedTextLength());d.w = this.getComputedTextLength(); return d.dx > d.w ? 1 : 0; });
Full code is here: http://jsfiddle.net/noobiecode/9ev9qjt3/74/
Any help will be appreciated.
For appending these texts, we first need to find the corresponding data. Right now, nodes have data regarding all the different depths (parents and children), but for these texts we only need the data for the second depth (depth == 2).
So, we first get the data:
var parents = treemap.nodes(root)
.filter(function(d) { return d.depth == 2 });
It gives us an array with three objects, so we are in the right way. This array (parents) have all we need to position the texts (x, y, dx, dy, name).
Then, we bind this data to the texts:
var parentsText = svg.selectAll(".parentsText")
.data(parents)
.enter()
.append("text");
After that, we append the texts:
parentsText.attr("text-anchor", "middle")
.attr("dominant-baseline", "central")
.attr("x", function(d){ return d.x + d.dx/2})
.attr("y", function(d){ return d.y + d.dy/2})
.text(function(d){ return d.name})
.attr("class", "parentText");
And this is the resulting fiddle: http://jsfiddle.net/gerardofurtado/9ev9qjt3/94/
You can also have the texts for each individual rectangle and for the group (but, in that case, you may want to move away some overlapping texts): http://jsfiddle.net/gerardofurtado/9ev9qjt3/95/
I have a stacked bar chart in d3.js
For every stacked bar i have corresponding text value showing near stack itself.
problem is, some text values displaying are hidden behind bars, where as some are visible over bars. I want all text to visible over my bars. my code looks like,
bar.append("text")
.attr("x", function (d) { return x(d.x); })
.attr("y", function (d) { return y(d.y0 + d.y); })
.attr("dy", ".35em")
.attr('style', 'font-size:13px')
.text(function (d) { if (d.y != 0) { return "$" + d.y; } })
.style('fill', 'black');
Basically the issue related to z-index. But there is no z-index for SVG, so it can be fixed by reordering elements. Details here With JavaScript, can I change the Z index/layer of an SVG <g> element?
The simplest and fastest way:
To add .reverse() to the dataset.
// Create groups for each series, rects for each segment
var groups = svg.selectAll("g.cost")
.data(dataset.reverse())
.enter().append("g")
.attr("class", "cost")
.style("fill", function(d, i) { return colors[i]; });
The better way
To add different containers for bars and labels and put them in the right order in the DOM.
Try it http://jsfiddle.net/kashesandr/z90aywdj/
I'm working with this d3.js example and am looking to change the charts entire orientation to go instead from right to left.
I was able to reverse the x-axis scale:
var x = d3.scale.linear().range([width, 0]);
and the placement of the y axis:
svg.append("g").attr("class", "y axis")
.attr("transform", "translate("+width+", 0)")
.append("line").attr("y1", "100%");
I believe I have to set the transform on each bar to the chart width - the bar width however applying ANY transform to the containing g has no effect.
function bar(d) {
var bar = svg.insert("g", ".y.axis")
.attr("class", "enter")
.attr("transform", "translate(0,5)")
.selectAll("g")
.data(d.children)
.enter().append("g");
//DOESNT WORK
bar.attr("transform", function(n){ return "translate("+ (width - x(n.value)) +", 0)"; })
.style("cursor", function(d) { return !d.children ? null : "pointer"; })
.on("click", down);
bar.append("text")....
bar.append("rect")....
return bar;
}
No transform is set on bar even if I just do a test with a fixed value, the result is translate(0,0) in the resulting page.
Why doesnt the transform not get applied here and is this even the correct way to make the bars right align? I also need the text to be on the right side of the bar instead of on the left and it seems that changing the order of appending makes no difference in this regard.
The problem with this particular example is that the code is a bit confusing -- the positions of the rect elements are set in various non-obvious places. In particular, the transform you are setting is overwritten immediately by other code.
I've modified the example to do what you want here. The key points are that I'm moving the g element containing all bars to the right
var bar = svg.insert("g", ".y.axis")
.attr("class", "enter")
.attr("transform", "translate("+width+",5)")
.selectAll("g")
// etc
and setting the x position of the rect elements to be their negative width
bar.append("rect")
.attr("x", function(d) { return width-x(d.value); })
.attr("width", function(d) { return x(d.value); })
.attr("height", barHeight);
The x position needs to be set in a few other places in the code in this way -- there're almost certainly more elegant ways to do this.
How do I add text labels to axes in d3?
For instance, I have a simple line graph with an x and y axis.
On my x-axis, I have ticks from 1 to 10. I want the word "days" to appear underneath it so people know the x axis is counting days.
Similarly, on the y-axis, I have the numbers 1-10 as ticks, and I want the words "sandwiches eaten" to appear sideways.
Is there a simple way to do this?
Axis labels aren't built-in to D3's axis component, but you can add labels yourself simply by adding an SVG text element. A good example of this is my recreation of Gapminder’s animated bubble chart, The Wealth & Health of Nations. The x-axis label looks like this:
svg.append("text")
.attr("class", "x label")
.attr("text-anchor", "end")
.attr("x", width)
.attr("y", height - 6)
.text("income per capita, inflation-adjusted (dollars)");
And the y-axis label like this:
svg.append("text")
.attr("class", "y label")
.attr("text-anchor", "end")
.attr("y", 6)
.attr("dy", ".75em")
.attr("transform", "rotate(-90)")
.text("life expectancy (years)");
You can also use a stylesheet to style these labels as you like, either together (.label) or individually (.x.label, .y.label).
In the new D3js version (version 3 onwards), when you create a chart axis via d3.svg.axis() function you have access to two methods called tickValues and tickFormat which are built-in inside the function so that you can specifies which values you need the ticks for and in what format you want the text to appear:
var formatAxis = d3.format(" 0");
var axis = d3.svg.axis()
.scale(xScale)
.tickFormat(formatAxis)
.ticks(3)
.tickValues([100, 200, 300]) //specify an array here for values
.orient("bottom");
If you want the y-axis label in the middle of the y-axis like I did:
Rotate text 90 degrees with text-anchor middle
Translate the text by its midpoint
x position: to prevent overlap of y-axis tick labels (-50)
y position: to match the midpoint of the y-axis (chartHeight / 2)
Code sample:
var axisLabelX = -50;
var axisLabelY = chartHeight / 2;
chartArea
.append('g')
.attr('transform', 'translate(' + axisLabelX + ', ' + axisLabelY + ')')
.append('text')
.attr('text-anchor', 'middle')
.attr('transform', 'rotate(-90)')
.text('Y Axis Label')
;
This prevents rotating the whole coordinate system as mentioned by lubar above.
If you work in d3.v4, as suggested, you can use this instance offering everything you need.
You might just want to replace the X-axis data by your "days" but remember to parse string values correctly and not apply concatenate.
parseTime might as well do the trick for days scaling with a date format ?
d3.json("data.json", function(error, data) {
if (error) throw error;
data.forEach(function(d) {
d.year = parseTime(d.year);
d.value = +d.value;
});
x.domain(d3.extent(data, function(d) { return d.year; }));
y.domain([d3.min(data, function(d) { return d.value; }) / 1.005, d3.max(data, function(d) { return d.value; }) * 1.005]);
g.append("g")
.attr("class", "axis axis--x")
.attr("transform", "translate(0," + height + ")")
.call(d3.axisBottom(x));
g.append("g")
.attr("class", "axis axis--y")
.call(d3.axisLeft(y).ticks(6).tickFormat(function(d) { return parseInt(d / 1000) + "k"; }))
.append("text")
.attr("class", "axis-title")
.attr("transform", "rotate(-90)")
.attr("y", 6)
.attr("dy", ".71em")
.style("text-anchor", "end")
.attr("fill", "#5D6971")
.text("Population)");
fiddle with global css / js
D3 provides a pretty low-level set of components that you can use to assemble charts. You are given the building blocks, an axis component, data join, selection and SVG. It's your job to put them together to form a chart!
If you want a conventional chart, i.e. a pair of axes, axis labels, a chart title and a plot area, why not have a look at d3fc? it is an open source set of more high-level D3 components. It includes a cartesian chart component that might be what you need:
var chart = fc.chartSvgCartesian(
d3.scaleLinear(),
d3.scaleLinear()
)
.xLabel('Value')
.yLabel('Sine / Cosine')
.chartLabel('Sine and Cosine')
.yDomain(yExtent(data))
.xDomain(xExtent(data))
.plotArea(multi);
// render
d3.select('#sine')
.datum(data)
.call(chart);
You can see a more complete example here: https://d3fc.io/examples/simple/index.html
chart.xAxis.axisLabel('Label here');
or
xAxis: {
axisLabel: 'Label here'
},
I'm new to d3.js (and stackoverflow) and I'm currently working through the parallel coordinates example. I'm currently using a 2d array named 'row' for the data. Above each vertical axis is the label '0' or '1' or '2', etc. However, I'd like each vertical axis to be labeled with the text in row[0][i]. I believe the numbers 0,1,2 are coming from the datum. Any suggestions on how I may use the labels in row[0][i] instead? I suspect I'm doing something wrong that's pretty basic. Here's the relevant code. Thanks !
// Extract the list of expressions and create a scale for each.
x.domain(dimensions = d3.keys(row[0]).filter(function (d, i) {
return row[0][i] != "name" &&
(y[d] = d3.scale.linear()
.domain(d3.extent(row, function (p) { return +p[d]; }))
.range([height, 0]));
}));
// Add a group element for each dimension.
var g = svg.selectAll(".dimension")
.data(dimensions)
.enter().append("g")
.attr("class", "dimension")
.attr("transform", function (d) { return "translate(" + x(d) + ")"; });
// Add an axis and title.
g.append("g")
.attr("class", "axis")
.each(function (d) { d3.select(this).call(axis.scale(y[d])); })
.append("text")
.attr("text-anchor", "middle")
.attr("y", -9)
.text(String);//.text(String)
If you only have a controlled set of Axis (like three axis), you may just want to set them up, individually, as follows...
svg.append("text").attr("class","First_Axis")
.text("0")
.attr("x", first_x_coordinate)
.attr("y", constant_y_coordinate)
.attr("text-anchor","middle");
svg.append("text").attr("class","Second_Axis")
.text("1")
.attr("x", first_x_coordinate + controlled_offset)
.attr("y", constant_y_coordinate)
.attr("text-anchor","middle");
svg.append("text").attr("class","Third_Axis")
.text("2")
.attr("x", first_x_coordinate + controlled_offset*2)
.attr("y", constant_y_coordinate)
.attr("text-anchor","middle");
However, if you have dynamically placed axis that rely on the data, you may want to place the axis info using a function that holds the y coordinate constant while determining the x coordinate based on a fixed "offset" (i.e. data driven axis placement). For example...
svg.append("text")
.attr("class",function(d, i) {return "Axis_" + i; })
.text(function(d,i) {return i; })
.attr("x", function(d, i) { return (x_root_coordinate + x_offset_value*i); })
.attr("y", constant_y_coordinate)
.attr("text-anchor","middle");
I hope this helps.
Frank