D3 x axis Formatting - d3.js

I want to format the x axis text with more padding and space and I am not able to achieve this. The actual text is around 6-7 characters long, but it is only showing 3. Here is the extract of the code and a screenshot:
svg.append("g")
.attr("class", "x axis")
.attr("transform", "translate(0," + height + ")")
.call(xAxis)
.selectAll("text")
.attr("transform", "rotate(90)");
d3 bar chart

When rotating the text in the ticks, you have to set the x and y. Also, it's a good idea changing the text-anchor. Something like this:
svg.append("g")
.attr("class", "x axis")
.attr("transform", "translate(0," + height + ")")
.call(xAxis)
.selectAll("text")
.attr("y", 0)//tweak this value
.attr("x", 10)//tweak this value
.attr("dy", ".35em")//tweak this value
.attr("transform", "rotate(90)")
.style("text-anchor", "start");
Check the demo:
var svg = d3.select("body")
.append("svg")
.attr("width", 400)
.attr("height", 100);
var data = ["foo", "bar", "baz", "foobar", "foobaz"];
var scale = d3.scaleBand()
.range([0, 400])
.domain(data);
var axis = d3.axisBottom(scale);
var gX = svg.append("g")
.attr("class", "x axis")
.attr("transform", "translate(0,30)")
.call(axis)
.selectAll("text")
.attr("y", 0)//tweak this value
.attr("x", 10)//tweak this value
.attr("dy", ".35em")//tweak this value
.attr("transform", "rotate(90)")
.attr("font-size", 14)
.style("text-anchor", "start");
<script src="https://d3js.org/d3.v4.min.js"></script>

Related

Behavior of chaining syntax in axes generation

I'm writing d3.js (d3 v4) code to generate a simple bar chart, which I managed to do, but I am confused about the behavior of the code in generating axes.
So basically, I encapsulated the chart code into a function that contains basic accessors.
Here is some code showing the core of my chart function.
function chart(selection){
selection.each(function(data){
var x = d3.scale.ordinal().rangeRoundBands([0, width], 0.5);
var y = d3.scale.linear().range([height, 0]);
var xAxis = d3.svg.axis()
.scale(x)
.orient("bottom");
var yAxis = d3.svg.axis()
.scale(y)
.orient("left")
.ticks(10);
x.domain(data.map(function(d) { return d.name; }));
y.domain([0, d3.max(data, function(d) { return d.value; })]);
var div = d3.select(this)
var svg = div.selectAll("svg")
.data([data]).enter()
.append("svg")
.attr("width", width + margin.left + margin.right)
.attr("height", height + margin.top + margin.bottom)
.append("g")
.attr("transform", "translate(" + margin.left + "," + margin.top + ")");
svg.append("g")
.attr("class", "x axis")
.attr("transform", "translate(0," + height + ")")
.call(xAxis)
.selectAll("text")
.style("text-anchor", "end")
.attr("dx", "-.8em")
.attr("dy", "-.55em")
.attr("transform", "rotate(-90)" );
svg.append("g")
.attr("class", "y axis")
.call(yAxis)
.append("text")
.attr("transform", "rotate(-90)")
.attr("y", 5)
.attr("dy", ".71em")
.style("text-anchor", "end")
.text("Frequency");
svg.selectAll(".bar")
.data(data)
.enter().append("rect")
.attr("class", "bar")
.attr("fill", "#1CE6FF")
.attr("x", function(d) { return x(d.name); })
.attr("width", x.rangeBand())
.attr("y", function(d) { return y(d.value); })
.attr("height", function(d) { return height - y(d.value); });
});
So I have two questions: 1) Is it okay to use multiple code blocks invoking svg.append("g") like shown above in the code?
2) When I replace the following code :
var svg = div.selectAll("svg")
.data([data]).enter()
.append("svg")
.attr("width", width + margin.left + margin.right)
.attr("height", height + margin.top + margin.bottom)
.append("g")
.attr("transform", "translate(" + margin.left + "," + margin.top + ")");
svg.append("g")
.attr("class", "x axis")
.attr("transform", "translate(0," + height + ")")
.call(xAxis)
.selectAll("text")
.style("text-anchor", "end")
.attr("dx", "-.8em")
.attr("dy", "-.55em")
.attr("transform", "rotate(-90)" );
With this (which is basically merging the two blocks in a single chain code chunk):
var svg = div.selectAll("svg")
.data([data]).enter()
.append("svg")
.attr("width", width + margin.left + margin.right)
.attr("height", height + margin.top + margin.bottom)
.append("g")
.attr("transform", "translate(" + margin.left + "," + margin.top + ")").append("g")
.attr("class", "x axis")
.attr("transform", "translate(0," + height + ")")
.call(xAxis)
.selectAll("text")
.style("text-anchor", "end")
.attr("dx", "-.8em")
.attr("dy", "-.55em")
.attr("transform", "rotate(-90)" );
...why is my y axis not showing at the right place? The <g class="y axis">...</g> tag appears nested into of a <text> tag itself nested in the x axis tag. Why the 2-block code vs single block code not behaving the same?
The structure of the code defines the structure of the resulting SVG. In the function chart() the variable svg holds the reference to the lastly appended <g> not to the previously appended <svg> itself. Note, how all statements after its first creation append to that same outer group of svg. That's one perfectly fine way of doing this.
If you concatenate the statements, however, like you did in your last snippet, the value of svg changes because the right hand side, i.e. the return value of the expression, changes. Since you last selected all text elements of the appended x-axis svg contains exactly those texts. Given that, it is pretty easy to understand that this breaks the rest of the code which will now append other contents to the text elements. Even if it was syntactically valid, which it is not, it would most likely still break the layout.
If you want to rearrange the code structure you have to make sure to have the correct references at the time they are needed.

extend x and y axis length in scatterplot, d3js

I am trying to extend the x and y axis length and add the arrow at the end. Here is my code and plunker. I need x axis to extend beyond 4.6 and y-axis beyond "AS" and add an arrow at the end. Kindly help.
https://plnkr.co/edit/tA6oyKQCCmhNadbARR89?p=preview
var margin = {top: 20, right: 20, bottom: 20, left: 40},
width = 420 - margin.left - margin.right,
height = 500 - margin.top - margin.bottom;
var x = d3.scale.linear()
.range([0, width]);
var y = d3.scale.ordinal().rangePoints([height, 0])
var color = d3.scale.category10();
var xAxis = d3.svg.axis()
.scale(x)
.orient("bottom").outerTickSize(0);
var yAxis = d3.svg.axis()
.scale(y).tickSize(-width).outerTickSize(0)
.orient("left");
var svg = d3.select("body").append("svg")
.attr("width", width + margin.left + margin.right)
.attr("height", height + margin.top + margin.bottom)
.append("g")
.attr("transform", "translate(" + margin.left + "," + margin.top + ")");
d3.tsv("data.tsv", function(error, data) {
if (error) throw error;
data.forEach(function(d) {
d.sepalWidth = +d.sepalWidth;
});
x.domain(d3.extent(data, function(d) { return d.sepalWidth; })).nice();
y.domain(data.map(function(d) { return d.sepalLength;}));
svg.append("g")
.attr("class", "x axis")
.attr("transform", "translate(0," + (height) + ")")
.call(xAxis);
svg.append("g")
.attr("class", "y axis")
.call(yAxis);
svg.selectAll(".dot")
.data(data)
.enter().append("circle")
.attr("class", "dot")
.attr("r", 3.5)
.attr("cx", function(d) { return x(d.sepalWidth); })
.attr("cy", function(d) { return y(d.sepalLength); })
.style("fill", function(d) { return color(d.species); });
var legend = svg.selectAll(".legend")
.data(color.domain())
.enter().append("g")
.attr("class", "legend")
.attr("transform", function(d, i) { return "translate(0," + i * 20 + ")"; });
legend.append("text")
.attr("x", width - 24)
.attr("y", 9)
.attr("dy", ".35em")
.style("text-anchor", "end")
.text(function(d) { return d; });
});
Easiest way is to just tack on a custom path to the end of the axis:
svg.append("g")
.attr("class", "x axis")
.attr("transform", "translate(0," + (height) + ")")
.call(xAxis)
.append("path")
// add extension arrow
.attr("d", "M" + width + ",0L" + (width + 20) + ",0L" + (width + 20) + ",-5L" + (width + 30) + ",0L" + (width + 20) + ",5L" + (width + 20) + ",0L");
svg.append("g")
.attr("class", "y axis")
.call(yAxis)
.append("path")
// add extension arrow
.attr("d", "M0,0L0,-20L-5,-20L0,-30L5,-20,L0,-20Z");
Updated plunker.

D3 time scales being split onto two lines

I have a time-series d3.js chart which I can't seem to get lined up properly. Initially I create the x-axis with:
var x_domain = d3.extent(active_data, function(d) { return d.date; })
var x = d3.time.scale()
.domain(x_domain)
.range([bar_width/2, width-bar_width/2]); // stop bars going outside chart
var date_format = d3.time.format("%b %y");
var xAxis = d3.svg.axis()
.orient("bottom")
.scale(x)
.ticks(8)
.tickFormat(date_format);
// create the xAxis
svg.append("g")
.attr("class", "x axis")
.attr("transform", "translate(0," + height + ")")
.call(xAxis)
.selectAll("text")
.style("text-anchor", "end")
.attr("dx", "-.8em")
.attr("dy", "-.55em")
.attr("transform", "rotate(-90)");
svg.selectAll(".x text") // some legacy code I had lying around
.attr("transform", function(d) {
return "translate(" + this.getBBox().height*3 + "," + this.getBBox().height + ")rotate(0)";
and then I update later on when user selects a date range. "active_data" is updated with the actual values/dates that will show from my total data before this update.
function update_chart(start){
/*...update active_data and other stuff...*/
x.domain(d3.extent(active_data, function(d){return d.date;}))
.range([bar_width/2, width-bar_width/2]);
/*...*/
var svg = d3.select("body").transition().duration(500);
svg.select(".x.axis").call(xAxis);
}
There's a lot more going on, but those are the related bits that I'm having trouble with. I've tried everything I can think of, searched online and I just don't understand what I'm doing wrong. It produces the following sort of problem:
d3 time scale some ticks on second line
You can see the dates go onto the second line. On some charts, when I update to the lowest or the maximum date, it draws it correctly, then draws all of them correctly afterwards. On another chart, it does this:
ticks all over each other
On this second one, I don't change the .ticks(8), so I don't know why it does that. Any insight would be greatly appreciated.
Ok I figured it out, and there were a couple bugs.
Then, the following was giving some dates a special x location, but not all of them:
// create the xAxis
svg.append("g")
.attr("class", "x axis")
.attr("transform", "translate(0," + height + ")")
.call(xAxis)
.selectAll("text")
.style("text-anchor", "end")
.attr("dx", "-.8em")
.attr("dy", "-.55em")
.attr("transform", "rotate(-90)");
I changed it to:
// create the xAxis
svg.append("g")
.attr("class", "x axis")
.attr("transform", "translate(0," + height + ")")
.call(xAxis);
and let the update do all the work (let D3 figure it out). The date was now in the right spot, except it was vertical instead of horizontal. I fixed that by removing this:
svg.selectAll(".x text") // some legacy code I had lying around
.attr("transform", function(d) {
return "translate(" + this.getBBox().height*3 + "," + this.getBBox().height + ")rotate(0)";
And it works properly!

d3.js - how to delete a group element

I want to delete a particular group element from the svg.
The code is:
var margin = {top: 20, right: 20, bottom: 20, left: 60};
var width=800, height=450;
svg = d3.select("#svgchart").append("g").attr("id","groupid").attr("transform", "translate(" + margin.left + "," + margin.top + ")");
svg.append("g").attr("id", "groupid2")
.attr("class", "x axis")
.attr("transform", "translate(1," + (height + 1) +")")
.call(xAxis)
.moveToBack();
//add the y-axis
svg.append("g").attr("id", "groupid3")
.attr("class", "y axis")
.attr("transform", "translate(1, 0)")
.call(yAxis)
.moveToBack();
//add an x-axis label
svg.append("text")
.attr("class", "x label axis")
.attr("text-anchor", "end")
.attr("x", width)
.attr("y", height - 6)
.text(varxaxis);
svg.append("text")
.attr("class", "y label axis")
.attr("text-anchor", "end")
.attr("y", 6)
.attr("dy", "3.75em")
.attr("transform", "rotate(-90)")
.text(yaxistext);
.....................................
.....................................
.....................................
To delete a particular group from svg:
svg.select("groupid").remove();
The group is not getting deleted.How do i remove a group?
A selection of select("groupid") will select the first element called "groupid". If you want to select the first element with an ID of "groupid", you must use: select("#groupid"). For classes, replace the "#" with a period.
In your case, "groupid" isn't an element. It is an ID you have provided to a 'g' element.
So, make sure you use select("#groupid").remove(). Don't forget the "#".

Inconsistency in append 'rect' with text

Any thoughts on why the text is missing for the first rectangle yet others are all fine?
http://jsfiddle.net/sjp700/bhwpu/
vis = d3.select("#chart")
.append('svg')
.attr('width', width + margin.right + margin.left)
.attr('height', height + margin.top + margin.bottom)
.style("fill", "pink")
.attr('class', 'chart');
vis.append("g") // container element
.attr("class", "x axis") // so we can style it with CSS
.attr("transform", "translate(0," + height + ")") // move into position
.call(xAxis); // add to the visualisation
//add in the y axis
vis.append("g") // container element
.attr("class", "y axis") // so we can style it with CSS
.call(yAxis); // add to the visualisation

Resources