Behavior of chaining syntax in axes generation - d3.js

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.

Related

D3 Scatterplot legend overlapping

I have a scatterplot that works fine, but the legend I add to it is overlapping the chart. My current approach is to make the chart DIV be 70% of the width and have the legend take up the remaining 30%. For some reason, the legend isn't showing up on the screen, even though the HTML is there.
This is the link to my initial problem: http://jsfiddle.net/chp5a09e/373/
Here is the link to what I'm currently trying: http://jsfiddle.net/chp5a09e/372/
var legend = d3.select("#legend").append("svg")
.attr("width", $("#legend").width())
.attr("height", height + margin.top + margin.bottom)
.append("g")
.attr("transform", "translate(" + margin.left + "," + margin.top + ")");
legend.selectAll(".legend")
.data(color.domain())
.enter().append("g")
.attr("class", "legend")
.attr("transform", function(d, i) {
return "translate(0," + i * 20 + ")";
});
legend.append("rect")
.attr("x", width - 12)
.attr("width", 12)
.attr("height", 12)
.style("fill", function(d) {
return color(d);
})
.on("click", function(d) {
d3.selectAll(".symbol").style("opacity", 1)
if (clicked !== d) {
d3.selectAll(".symbol")
.filter(function(e) {
return e.items[columns.indexOf("Channel")] !== d;
})
.style("opacity", 0.1)
clicked = d
} else {
clicked = ""
}
});
legend.append("text")
.attr("x", width - 16)
.attr("y", 6)
.attr("dy", ".35em")
.style("text-anchor", "end")
.text(function(d) {
return d;
});
HTML is there
Only group (g) elements are there, and they are never visible themselves. In your original code
var legend = svg.selectAll(".legend")
.data(color.domain())
.enter().append("g")
...
legend.append("rect")
the legend here is a selection of multiple g.legend elements, and thus a rect gets appended to each of them, as well as gets access to the datum bound to the parent g. However in you new code
var legend = d3.select("#legend").append("svg")
...
.append("g")
.attr("transform", "translate(" + margin.left + "," + margin.top + ")");
legend.selectAll(".legend")
.data(color.domain())
.enter().append("g")
...;
legend.append("rect")
The legend here refers only to the single g element that contains your whole legend. Your legend.selectAll(".legend") isn't saved into variable, so while inside the chain you set class and transform attributes properly, you don't use it to get rect appended to it -- again, legend at that point refers to the outter single g container.
Potential solution:
var legendCnt = d3.select("#legend").append("svg")
.attr("width", $("#legend").width())
.attr("height", height + margin.top + margin.bottom)
.append("g")
.attr("transform", "translate(" + margin.left + "," + margin.top + ")");
var legend = legendCnt.selectAll(".legend")
.data(color.domain())
...
You'll notice you'll need to remove or lower x attribute for text and rect, since 0 is now at the beginning of the legend instead of chart

D3 x axis Formatting

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>

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.

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

How to fix shifted columns on histogram?

I am building a histogram with number of days as the x axis, but the columns are shifted. I want to bin the data on months, or more exactly, 30-day intervals. For some reason, when generating this graph the columns are not the right size, and the bars end up shifting a lot, as shown in the picture:
The bins are hardcoded like this:
var data = d3.layout.histogram()
.bins(20)
(values);
var y = d3.scale.linear()
.domain([0, d3.max(data, function(d) { return d.y; })])
.range([height, 0]);
var xAxis = d3.svg.axis()
.scale(x)
.tickValues([0,30,60,90,120,150,180,210,240,270,300,330,360,390,420,450,480,510,540,570,600])
.orient("bottom");
var svg = d3.select("#graph").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 + ")");
var bar = svg.selectAll(".bar")
.data(data)
.enter().append("g")
.attr("class", "bar")
.attr("transform", function(d) { return "translate(" + x(d.x) + "," + y(d.y) + ")"; });
bar.append("rect")
.attr("x", 1)
.attr("width", x(data[0].dx) - 1)
.attr("height", function(d) { return height - y(d.y); });
bar.append("text")
.attr("dy", ".75em")
.attr("y", 6)
.attr("x", x(data[0].dx) / 2)
.attr("text-anchor", "middle")
.text(function(d) { return formatCount(d.y); });
svg.append("g")
.attr("class", "x axis")
.attr("transform", "translate(0," + height + ")")
.call(xAxis);
What is the correct way to solve this issue?

Resources