I'm trying this graph:
with my data.
I wanted to rotate the x-axis labels.
Here's the code :
var timeLabels = svg.selectAll(".timeLabel")
.data(times)
.enter().append("text")
.text(function(d) { return d; })
.attr("x", function(d, i) { return i * gridSize; })
.attr("y", 0)
.style("text-anchor", "end")
.attr("transform", "translate(" + gridSize / 2 + ", -6)rotate(-90)")
.attr("class", function(d, i) { return ((i >= 8 && i <= 16) ? "timeLabel mono axis axis-worktime" : "timeLabel mono axis"); });
But the result I get is. Entire labels get rotated to -90 deg in a single straight line. Instead I wanted each label to be rotated to -90 deg. Like this:
I even tried using .attr("transform", function(d) {return "translate(" + gridSize / 2 + ", -6)rotate(-90)"}) but it didn't help.
Result :
Help will be very much pleased.
The problem is that when you rotate an element, it gets rotated around the origin point, (0,0), of the local coordinate system. If the element you're rotating isn't right next to the origin, it can end up moved quite a large distance, and possibly moved outside the chart altogether.
There are two ways you can fix it:
Position the label with a "transform" attribute instead of with x and y attributes. That way, the label's coordinate system -- including the (0,0) point of rotation -- will be positioned with it, and the rotation will happen where you expect. The code #Manoj gave follows that system, but assumes you are using the default xAxis function. For your custom axis label code, your labels are given a y value of 0, and an x value determined from a function. You just need to move those values into the transform attribute, paying careful attention that the transformation of the overall position comes before the rotation and adjustment:
var timeLabels = svg.selectAll(".timeLabel")
.data(times)
.enter().append("text")
.text(function(d) { return d; })
.attr("transform", function(d, i) {
return "translate(" + ( i * gridSize) + ",0)"
+ "translate(" + gridSize / 2 + ", -6)rotate(-90)";
} )
.style("text-anchor", "end")
.attr("class", function(d, i) { return ((i >= 8 && i <= 16) ?
"timeLabel mono axis axis-worktime" :
"timeLabel mono axis");
});
Of course, you could combine those two translation statements, as:
.attr("transform", function(d, i) {
return "translate(" + ( (i + 0.5) * gridSize) + ",-6)"
+ "rotate(-90)")
Alternately, you can specify a center of rotation in the rotate statement, by including the x and y values of the point you want it to rotate around:
var timeLabels = svg.selectAll(".timeLabel")
.data(times)
.enter().append("text")
.text(function(d) { return d; })
.attr("x", function(d, i) { return i * gridSize; })
.attr("y", 0)
.attr("transform", function(d, i) {
return "translate(" + gridSize / 2 + ", -6)" +
"rotate(-90 "+ ((i + 0.5) * gridSize) + " " + (-6) +")";
} )
.style("text-anchor", "end")
.attr("class", function(d, i) { return ((i >= 8 && i <= 16) ?
"timeLabel mono axis axis-worktime" :
"timeLabel mono axis");
});
As you can see, this version is rather repetitive, as we have to calculate the position of the label twice -- once to position it, and once to set the center of rotation. You can see why most examples (and the d3 axis functions) position the labels with translations, so they can just be rotated in place.
Try this code:
Fiddle:
svg.append("g") // Add the X Axis
.attr("class", "x axis")
.attr("id", "x")
.attr("transform", "translate(0," + (h) + ")")
.call(xAxis)
.selectAll("text")
.style("text-anchor", "end")
.attr("dx", "-.8em")
.attr("dy", ".15em")
.attr("transform", function (d) {
return "rotate(-90)";
});
Related
I'm newbie in D3 and I'm trying to set a symbol on the left of the text of a legend. The legend is on the right of the graphic and all the texts of the legend are correctly located but I cannot be able to located on their left the symbol which corresponds with the legend.
The function which locate the legend and try to do the same with the symbols are:
setLegend(canvas, symbols, width, offset_right, height) {
canvas
.selectAll("legends")
.data(symbols)
.enter()
.append("text")
.attr("transform", function(d, i) {
let x = width - offset_right + 50;
let y = height / 2 - 100 + i * 24;
return "translate( " + x + "," + y + ")";
})
.attr(
"d",
d3
.symbol()
.type(function(d) {
return d.symbol;
})
.size("75")
)
.style("text-anchor", "left")
.text(d => {
return d.stats;
})
.attr("fill", "#FFFFFF")
.style("font-size", "10pt")
.style("font-weight", "bold"); }
You can check in this screen cap how the legend is correctly located but there are no any symbol on its left.
You can check all the code of the development in codesanbox:
What am I doing wrong?
Right now you're setting an attribute called d to text elements, which has no effect on those texts (only paths have the d attribute). On top of that, you're not appending any path.
A simple and common fix is appending groups in the enter selection, to which you append the paths and texts. Here is an example (I'm setting the x and y positions of the texts so they don't start right over the symbols):
setLegend(canvas, symbols, width, offset_right, height) {
const groups = canvas
.selectAll("legends")
.data(symbols)
.enter()
.append("g")
.attr("transform", function(d, i) {
let x = width - offset_right + 50;
let y = height / 2 - 100 + i * 24;
return "translate( " + x + "," + y + ")";
});
groups.append("path")
.attr("d", d3.symbol().type(function(d) {
return d.symbol;
}).size("75"))
.attr("fill", function(d) {
return d.color;
});
groups.append("text")
.attr("x", 10)
.attr("y", 5)
.style("text-anchor", "left")
.text(d => {
return d.stats;
})
.attr("fill", function(d) {
return d.color;
})
.style("font-size", "10pt")
.style("font-weight", "bold");
}
I would like to reduce the number of plots in a D3 scatterplot matrix to just 4. I am using https://bl.ocks.org/mbostock/4063663 as my initial template. I have my data set up in http://plnkr.co/edit/lySDnd58vUlelRKmk20S?p=preview. Ideally I want only the plots on the top, setting up only one Y-Axis Label (Homicides per 1000) with the x-axis changing to each subsequent plot. Something similar to this quick mockup in paint. https://postimg.org/image/7638jvln3/
I was sure it would have been controlled somewhere in here, but I realized I was a bit lost. If anyone is able to help me set this up, it will be greatly appreaciated!
svg.selectAll(".x.axis")
.data(traits)
.enter().append("g")
.attr("class", "x axis")
.attr("transform", function(d, i) { return "translate(" + (n - i - 1) * size + ",0)"; })
.each(function(d) { x.domain(domainByTrait[d]); d3.select(this).call(xAxis); });
svg.selectAll(".y.axis")
.data(traits)
.enter().append("g")
.attr("class", "y axis")
.attr("transform", function(d, i) { return "translate(0," + i * size + ")"; })
.each(function(d) { y.domain(domainByTrait[d]);d3.select(this).call(yAxis); });
var cell = svg.selectAll(".cell")
.data(cross(traits, traits))
.enter().append("g")
.attr("class", "cell")
.attr("transform", function(d) { return "translate(" + (n - d.i - 1) * size + "," + d.j * size + ")"; })
.each(plot);
One last question, is it possible to get a y-axis title to appear. When I add the code below, everything rescaled and the data disappeared.
svg.append("text")
.attr("transform", "rotate(-90)")
.attr("y", 0 - margin.left)
.attr("x",0 - (height / 2))
.attr("dy", "1em")
.style("text-anchor", "middle")
.style("font-size", "12px")
.text("Homicides per 1,000");
It is indeed possible using the same code. A little strange, but possible:
// keep similar calucations
var domainByTrait = {},
traits = d3.keys(data[0]).filter(function(d) { return d !== "name"; }),
n = traits.length - 1;
traits.forEach(function(trait) {
domainByTrait[trait] = d3.extent(data, function(d) { return d[trait]; });
});
// reset traits to ones you care about
var traits = ["% Holding Only HS Diploma", "Unemployement Rate", "Median Household Value ($10k)", "% African American"];
// chane xAxis ticks to 1, not n
xAxis.tickSize(size * 1);
yAxis.tickSize(-size * n);
...
// modify y axis to only Homicides
svg.selectAll(".y.axis")
.data(["Homicides per 1000 people"])
.enter().append("g")
...
// modify cross-product
var cell = svg.selectAll(".cell")
.data(cross(traits, ["Homicides per 1000 people"]))
.enter().append("g")
...
// remove filter on text titltes
cell
//.filter(function(d) { return d.i === d.j; })
.append("text")
...
Update plunker.
How do I position the legend above and out of the chart?
I am working in this d3 example Grouped Bar Chart
Here is my PLUNKER but the legend can overlap the graph. Ideally I would like the legend above and out of the chart.
This is my code that I have to change. I don't understand why the 0 refers to the current position.
var legend = svg.selectAll(".legend")
.data(ageNames.slice().reverse())
.enter().append("g")
.attr("class", "legend")
.attr("transform", function(d, i) { return "translate(0," + i * 20 + ")"; });
I can move the legend as follows: PLUNKER
.attr("transform", function(d, i) { return "translate(" + "-500," + i * 20 + ")"; }); which moves imore to the center.
I can then have the legend read from left to right as follows:
.attr("transform", function(d, i) { return "translate(" + (-700+i*100) + "," + 0 + ")"; }); I would be great if I could move this above and outside the chart as it still overlaps some of the graph.
EDIT1 PLUNKER
tks to an answer belwo. This is my attempt, which is above the chart as I would expect, but I would like the different series in the legend to appear closer together (there is too much white space). So how do I have the coloured rect and then the text beside it, but without the whitespace?
## the below is close but I am just guessing
var legendHolder = svg.append('g')
// translate the holder to the right side of the graph
.attr('transform', "translate(" + (-width) + "," + (-margin.top) + ")")
.attr('class','legendHolder')
var legend = legendHolder.selectAll(".legend")
.data(ageNames.slice().reverse())
.enter().append("g")
.attr("class", "legend")
legend.append("rect")
.attr("x", function(d,i){return (width +(150*i))})
.attr("width", 36)
.attr("height", 18)
//.style("text-anchor", "end")
.style("fill", color);
legend.append("text")
//.attr("x", width - 24)
.attr("x", function(d,i){return (width +(140*i))})
.attr("y", 9)
.attr("dy", ".35em")
//.style("text-anchor", "end")
.text(function(d) { return d; });
EDIT2 PLUNKER
This is the best I can do, but I fell I am jsut guessing, maybe I will revisit but in the meant time if anyone can beautifully explain it to me that would be greatly appreciated
var legendHolder = svg.append('g')
// translate the holder to the right side of the graph
.attr('transform', "translate(" + (-width) + "," + (-margin.top) + ")")
.attr('class','legendHolder')
var legend = legendHolder.selectAll(".legend")
.data(ageNames.slice().reverse())
.enter().append("g")
.attr("class", "legend")
.attr('transform', function(d, i) { return "translate(" + -40*i + "," + 0 + ")"; })
.attr("width", 36)
legend.append("rect")
.attr("x", function(d,i){return (width +(150*i))})
.attr("width", 18)
.attr("height", 18)
//.style("text-anchor", "end") //"startOffset"="100%
//.style("startOffset","100%") //"startOffset"="100%
.style("fill", color);
legend.append("text")
//.attr("x", width - 24)
.attr("x", function(d,i){return (width +(150*i)+20)})
.attr("y", 9)
.attr("dy", ".35em")
//.style("text-anchor", "end")
.text(function(d) { return d; });
If you want the legend to be located outside of the graph, you just need to increase the size of the margin where you want it to be placed and translate it into position.
Right now you are positioning the individual parts of your legend based on the size of the <svg>. You can simplify this by creating a <g> that contains all of your legend elements and translating that to its desired position in the graph.
You'll need to play around with the values to get exactly what you want, but below are the values that would allow you to place the legend in the right margin.
var margin = {top: 20, right: 100, bottom: 30, left: 40};
var legendHolder = svg.append('g')
// translate the holder to the right side of the graph
.attr('transform', "translate(" + (margin.left + width) + ",0)")
var legend = legendHolder.selectAll(".legend")
.data(ageNames.slice().reverse())
.enter().append("g")
.attr("class", "legend")
.attr("transform", function(d, i) { return "translate(0," + i * 20 + ")"; });
legend.append("rect")
.attr("x", 0)
.attr("width", 18)
.attr("height", 18)
.style("fill", color);
legend.append("text")
.attr("x", 0)
.attr("y", 9)
.attr("dy", ".35em")
.style("text-anchor", "end")
.text(function(d) { return d; });
The legend in the example appears on the right hand side, despite a transform of zero because the elements in the group have an x attribute of nearly the width of the frame (minus a small offset), pushing them to the right:
legend.append("rect")
.attr("x", width - 18)
.attr("width", 18)
.attr("height", 18)
.style("fill", color);
legend.append("text")
.attr("x", width - 24)
.attr("y", 9)
.attr("dy", ".35em")
.style("text-anchor", "end")
.text(function(d) { return d; });
So an x transform of -500, about half your width, pulls it to the middle, as noted. Using a smaller x attribute for the legend elements might help make it clearer for setting up your legend (this is seen in the other answer), though as your comment notes, it isn't too hard to make it work as it is (just more confusing than needed).
I am still very new to d3.js and have added a Focus + Context via Brushing to my Scatter plot that has a logarithmic y-Axis and ordinal x-Axis scale, but it isn't working as intended. Brushing seems to be working at an offset on the X-Axis but doesn't only show the item/items selected.
Below is my brushed function:
// Brush Function
function brushed() {
var extent = brush.extent();
var d = xScale2.domain(),
r = xScale2.range();
extent = extent.map(function(e) { return d[d3.bisect(r, e) - 1]; });
xScale.domain(brush.empty() ? d : extent);
focus.select(".x.axis")
.call(xAxis)
.selectAll("text")
.style("text-anchor", "end")
.attr("dx", "-.8em")
.attr("dy", ".15em")
.attr("transform", "rotate (-65)");
focus.selectAll(".point")
.attr("d", d3.svg.symbol().type(function(d) { return symbols[d.TYPE_CODE]; }))
.attr("transform", function(d) { return "translate(" + xScale(dateFn(d)) + "," + yScale(d.TYPE_VALUE) + ")"; })
.style("fill", function(d) { return colors[d.TYPE_CODE]; });
}
If anyone could help out with this (especially all the places I have to change and how), I would love to get this working and learn what I am doing wrong.
Full Code at: http://jsfiddle.net/brebuck/LL42b4L7/
Figured out my own solution to the problem.
Changed the Brush Function to:
// Brush Function
function brushed() {
var extent = brush.extent();
var d = xScale2.domain();
// Find out what is selected between the extent on the Ordinal Axis.
var SymbolInside = data.filter(function(d) {
return extent[0] <= xScale2(dateFn(d)) && xScale2(dateFn(d)) <= extent[1];
});
// Convert the Array of objects to return a single array of dates used for the ordinal axis.
SymbolInside = SymbolInside.map(function (d) { return dateFn(d); });
xScale.domain(brush.empty() ? d : SymbolInside);
focus.select(".x.axis")
.call(xAxis)
.selectAll("text")
.style("text-anchor", "end")
.attr("dx", "-.8em")
.attr("dy", ".15em")
.attr("transform", "rotate (-65)");
focus.selectAll(".point")
.attr("d", d3.svg.symbol().type(function(d) { return symbols[d.TYPE_CODE]; }))
.attr("transform", function(d) { return "translate(" + xScale(dateFn(d)) + "," + yScale(d.TYPE_VALUE) + ")"; })
.style("fill", function(d) { return colors[d.TYPE_CODE]; });
}
Updated Code at: http://jsfiddle.net/brebuck/LL42b4L7/
I'm using D3 to present some data as a horizontal bar chart. Values will typically range between -10 and +10 on 8 different scales. I have the bars rendering as I want, but I can't work out how to add lables for each of the extreems of the axes.
so far I have:
but I want to achieve something like:
In other words a label for each extreme of each scale.
I have found lots of examples that add data labels to the bars them selves (e.g. the value), but I want to some how force the array of strings to be rendered at the extremes of the container.
At the moment, I am rendering the data from an array, and I have the labels stored in 2 other arrays e.g.
var data = [10, 5, -5, -10, 2, -2, 8, -8];
var leftLabels = ["label 1","label 2", ...];
var rightLabels = ["label 1", "label 2", ...];
Any ideas or links to examples most welcome.
I am not an expert in d3.js, but I think this can be easily done. There are different ways to go about it. I have created a pen for your use case.
I will paste the important part of the code below. In your chart, you will have to certainly make some adjustments to suit your needs. Feel free to play around with the values until you feel they are stable.
// Your array containing labels for left and right values
var leftSideData = ["left1", "left2", "left3", "left4", "left5", "left6", "left7", "left8"];
var rightSideData = ["right1", "right2", "right3", "right4", "right5", "right6", "right7", "right8"];
var left = svg.selectAll(".leftData")
.data(leftSideData)
.enter().append("g")
.attr("class", "leftVal")
.attr("transform", function(d, i) {
return "translate(0," + i * 57 + ")";
});
left.append("text")
.attr("x", 0)
.attr("y", 9)
.attr("dy", ".35em")
.style("text-anchor", "end")
.text(function(d) {
return d;
});
var right = svg.selectAll(".rightData")
.data(rightSideData)
.enter().append("g")
.attr("class", "rightVal")
.attr("transform", function(d, i) {
return "translate(0," + i * 57 + ")";
});
right.append("text")
.attr("x", width + 30)
.attr("y", 9)
.attr("dy", ".35em")
.style("text-anchor", "end")
.text(function(d) {
return d;
});
I won't say this is perfect, but I hope you get an idea about how to approach it. All the best!!
It's funny, just by asking the q on SE I find it helps me reformulate the problem.. and then some time later a new try yields a result. Anyone else find that?
I managed to make it work by changing the way the SVG was created. So I now have the following structure:
<SVG>
><g> (one for each bar)
>><text>
>><rect>
>><text>
><other stuff like axies>
It turns out that <text> elements cannot be added to <rect> elements (well they can, be added but they won't render).
the code is:
var data = [10,2,4,-10,...etc...];
var leftLabels = ["left 1","left 1", ...etc...];
var rightLabels = ["right 1","right 2", ...etc...];
//chart dimentions
var margin = { top: 20, right: 30, bottom: 40, left: 30 },
width = 600 - margin.left - margin.right,
barHeight = 30,
height = barHeight * data.length;
//chart bar scaling
var x = d3.scale.linear()
.range([100, width-100]);
var y = d3.scale.ordinal()
.rangeRoundBands([0, height], 0.1);
var chart = d3.select(".chartsvg")
.attr("width", width + margin.left + margin.right)
.attr("height", barHeight * data.length + margin.top + margin.bottom)
.append("g")
.attr("transform", "translate(" + margin.left + "," + margin.top + ")");
x.domain([d3.min(data), d3.max(data)]);
//append a g for each data item
var bar = chart.selectAll(".bar")
.data(data)
.enter()
.append("g");
//in each bar add a rect for the bar chart bar
bar.append("rect")
.attr("class", function (d) { return "bar--" + (d < 0 ? "negative" : "positive"); })
.attr("x", function (d) { return x(Math.min(0, d)); })
.attr("y", function (d, i) { return i* barHeight; })
.attr("width", function (d) { return Math.abs(x(d) - x(0)); })
.attr("height", barHeight-1);
//append the labels to each g using the label data
bar.append("text")
.data(rightLabels)
.attr("x", width)
.attr("y", function (d, i) { return (i * barHeight)+barHeight/2; })
.attr("dy", ".5em")
.attr("fill","steelblue")
.attr("text-anchor","end")
.text(function (d) { return d; });
bar.append("text")
.data(leftLabels)
.attr("x", 0)
.attr("y", function (d, i) { return (i * barHeight) + barHeight / 2; })
.attr("dy", ".5em")
.attr("fill","darkorange")
.attr("text-anchor", "start")
.text(function (d) { return d; });
//then append axis etc...
Formatting: something else to note. It turns out that to color the text in the label you need to use "stroke" and "fill" attributes. These are broadly equiv to the HTML "color" attribute on text.