Hello I have this visual: https://plnkr.co/edit/H6M1xoS9cZv5dKCTIyid?p=preview
I'm attempting to rotate the labels of the x-axis and have tried amending the code in lines 299-317 to no avail - so I feel code such as .attr("transform", "rotate(-65)" ); needs adding to the following?
// Add x labels to chart
var xLabels = svg
.append("g")
.attr("transform", "translate(" + margin.left + "," + (margin.top + height) + ")");
xLabels.selectAll("text.xAxis")
.data(BarData)
.enter()
.append("text")
.text(function(d) {
return d.dt;
})
.attr("text-anchor", "middle")
// Set x position to the left edge of each bar plus half the bar width
.attr("x", function(d, i) {
return (i * (width / BarData.length)) + ((width / BarData.length - barPadding) / 2);
})
.attr("y", 15)
.attr("class", "xAxis")
Your issue is that you are using the .attr("x" to position the axis text. You should be using translate to do this otherwise your rotation will rotate all the elements from the bottom left.
the code should look like this, the translate and the rotation should occur within the same transform function:
.attr("transform", function (d, i) {
return "translate("
+ ((i * (width / BarData.length)) + ((width / BarData.length - barPadding) / 2))
+ ", 0) rotate(-65)";
})
Here is a working version of your plunk: https://plnkr.co/edit/UqwtLqTn6iJ2XS012Vr4?p=preview
Hope this helps.
With links help I eventually used the following code:
xLabels.selectAll("text.xAxis")
.data(BarData)
.enter()
.append("text")
.text(function(d) {
return d.dt;
})
.attr({
'text-anchor': "middle",
transform: function(d, i) {
var x = (i * (width / BarData.length)) + ((width / BarData.length - barPadding) / 2);
var y = 20;
return 'translate(' + x + ',' + y + ')rotate(-90)';
},
dy: "0.35em",
'class': "xAxis"
})
Working example here: https://plnkr.co/edit/3d5UhM?p=preview
I also added in the attribute dy: "0.35em" to fully centre the labels to each bar.
Related
First of all, I apologize for such a long post and my absolute beginner knowledge about D3 framework.
I have managed to draw the following chart using D3 framework.
However, I am not been able to accomplish the following things:
1) Cannot draw Y-Axis vertical sideline
2) Cannot make the Y-Axis ticks to align centered. At the moment they all are left-aligned
3) Cannot make the color of X-Axis baseline same as Y-Axis gridlines
4) Cannot make the label "Growth Target (7)" center-aligned Right now it is left-aligned
1) Y-Axis Vertical Sideline:
The text Growth Target (7) is within the chart. In order to make the gridlines to stop before this, I have used this (approach 1)
// ******************* << Custom Y axis grid lines Begins
var yGrid = svg.selectAll(".hgrid")
.data(yTicks)
.enter()
.append("g")
.attr("transform", function(d){
return "translate(0, " + y(d) + ")"
});
yGrid.append("line")
.attr("class", "hgrid")
.attr("x1", 0)
// shorten growth target line width to give room for the label
.attr("x2", width - growth_target_width_adjusted);
yGrid.append("text")
.text(function(d){return d; })
.attr("class", "hgrid-label")
// update X and Y positions of the label
.attr("x", -15)
.attr("y", "0.3em");
d3.selectAll(".hgrid-label")
.each(function (d, i) {
d3.select(this).style("font-size", 13);
});
// ******************* Custom Y axis grid lines Ends >>
Rather than this (approach 2):
// gridlines in y axis function
function draw_yAxis_gridlines() {
return d3.axisLeft(y)
.tickValues(yTicks);
}
// add the Y grid lines
svg.append("g")
.attr("class", "grid axis yAxis")
.call(draw_yAxis_gridlines()
.tickSize(-width)
);
The above approach 2, however, creates the Y-Axis baseline but the gridlines are using the entire chart area and overlapping the text "Growth Target (7)".
Is there any way that I can control the width of each Y-Axis line using approach 2? If not, how can I draw the Y-Axis baseline using approach 1?
2) Y-Axis ticks to align center
Currently, the Y-Axis tick values (0, 5, 10...) are left-aligned. Is there a way to make them centered?
3) Color of X-Axis baseline same as Y-Axis gridlines
In my chart above, the X-Axis color is black while the gridline colors are light gray. I need to make the X-Axis color light gray as well. What is the right way to do this?
4) Center aligning the label "Growth Target (7)"
What is the right way to make the two lines of the label centered-aligned? Right now the lines are aligned left to each other.
The desired output needs to be like this:
Here is the complete chart script that I have now:
function barColor(data_month, current_month) {
if( parseInt(data_month) >= current_month)
return "#008600";
else
return "#c4c4c4";
}
function draw_gp_chart(data_str) {
var chart_container = jQuery("#ecbg_unitary");
jQuery(chart_container).html("");
var w = parseInt(jQuery(chart_container).width());
var h = 375;
var barPadding = 2;
// set the dimensions and margins of the graph
var margin = {top: 30, right: 20, bottom: 30, left: 40};
var width = w - margin.left - margin.right;
var height = h - margin.top - margin.bottom;
var growth_target_width_adjusted = margin.left + margin.right;
var svg = d3.select("#ecbg_unitary")
.append("svg")
.attr("width", width + margin.left + margin.right)
.attr("height", height + margin.top + margin.bottom)
.append("g")
.attr("transform", "translate(" + margin.left + ", 10)");
var data = data_str;
// set the ranges (less width of chart to allow room for Growth Target label)
var x = d3.scaleBand().range([0, width - growth_target_width_adjusted]).padding(0.2);
var y = d3.scaleLinear().range([height, 0]);
// Scale the range of the data in the domains
x.domain(data.map(function (d) {
return d.month;
}));
var y_domain_upperBound = d3.max(data, function (d) {
return d.points;
});
y_domain_upperBound = Math.round(y_domain_upperBound / 10) * 10 + 10;
y.domain([0, y_domain_upperBound]);
// Create Y-Axis tick array to draw grid lines
var yTicks = [];
var tickInterval = 5;
for (var i = 0; i <= y_domain_upperBound; i = i + tickInterval) {
yTicks.push(i);
}
// gridlines in y axis function
function draw_yAxis_gridlines() {
return d3.axisLeft(y)
.tickValues(yTicks);
}
// ******************* << Growth Target line Begins
var targetGoalArr = [parseInt(jQuery("#ecbg_unitary_growth_target").val())];
var target = svg.selectAll(".targetgoal")
.data(targetGoalArr)
.enter()
.append("g")
.attr("transform", function(d){
return "translate(0, " + y(d) + ")"
});
target.append("line")
.attr("class", "targetgoal")
.attr("x1", 0)
// shorten growth target line width to give room for the label
.attr("x2", width - growth_target_width_adjusted);
// Adding two SVG text elements since SVG text element does not support text-wrapping.
// ref: https://stackoverflow.com/a/13254862/1496518
// Option 2: may be tried later - https://bl.ocks.org/mbostock/7555321
// Text element 1
target.append("text")
.text(function(d){return "Growth " })
.attr("class", "tglebal")
// update X position of the label
.attr("x", width - growth_target_width_adjusted + 5)
.attr("y", "-0.3em");
// Text element 2
target.append("text")
.text(function(d){return "Target (" + d + ")" })
.attr("class", "tglebal")
// update X position of the label
.attr("x", width - growth_target_width_adjusted + 5)
.attr("y", "1em");
d3.selectAll(".tglebal")
.each(function (d, i) {
d3.select(this).style("font-size", 12);
});
// ******************* Growth Target line Ends >>
// ******************* << Custom Y axis grid lines Begins
var yGrid = svg.selectAll(".hgrid")
.data(yTicks)
.enter()
.append("g")
.attr("transform", function(d){
return "translate(0, " + y(d) + ")"
});
yGrid.append("line")
.attr("class", "hgrid")
.attr("x1", 0)
// shorten growth target line width to give room for the label
.attr("x2", width - growth_target_width_adjusted);
yGrid.append("text")
.text(function(d){return d; })
.attr("class", "hgrid-label")
// update X and Y positions of the label
.attr("x", -15)
.attr("y", "0.3em");
d3.selectAll(".hgrid-label")
.each(function (d, i) {
d3.select(this).style("font-size", 13);
});
// ******************* Custom Y axis grid lines Ends >>
// append the rectangles for the bar chart
svg.selectAll("rect")
.data(data)
.enter().append("rect")
.attr("x", function (d) {
return x(d.month);
})
.attr("width", x.bandwidth())
.attr("y", function (d) {
return y(d.points);
})
.attr("height", function (d) {
return height - y(d.points);
})
//.attr("height", function(d) {return d.points;})
.attr("fill", function (d) {
return barColor(d.data_month_number, d.current_month_number)
});
// column labels
svg.selectAll(".colvalue")
.data(data)
.enter()
.append("text")
.attr("class", "colvalue")
.text(function (d) {
return d.points;
})
.attr("text-anchor", "middle")
.attr("x", function (d, i) {
return x(d.month) + x.bandwidth() / 2;
})
//.attr("x", function(d) { return x(d.month); })
.attr("y", function (d) {
//return h - (d.points * 4) - 10;
return y(d.points) - 10;
})
.attr("font-family", "Roboto")
.attr("font-size", "13px")
.attr("font-weight", "bold")
.attr("fill", "#606668");
// add the x Axis
svg.append("g")
.attr("class", "axis")
.attr("transform", "translate(0," + height + ")")
.call(d3.axisBottom(x));
// text label (axis name) for the x axis
var xAxisNameHeightFromTop = height + margin.bottom + 20;
svg.append("g")
.attr("class", "x-axis-name")
.append("text")
.attr("transform", "translate(" + width / 2 + ", " + xAxisNameHeightFromTop + ")")
.style("text-anchor", "middle")
.text("MONTH");
// text label for the y axis
svg.append("g")
.attr("class", "y-axis-name")
.append("text")
.attr("transform", "rotate(-90)")
.attr("y", 0 - margin.left)
.attr("x", 0 - (height / 2))
.attr("dy", "1em")
.style("text-anchor", "middle")
.text("SALES UNITS");
// add the Y grid lines
/*svg.append("g")
.attr("class", "grid axis yAxis")
.call(draw_yAxis_gridlines()
.tickSize(-width)
);*/
d3.selectAll(".yAxis>.tick>text")
.each(function (d, i) {
d3.select(this).style("font-size", 13);
});
}
I'm pretty new in D3.js (I'm a R programmer) and I want to make a simple bar chart based on Aravind's example with a start transition and add a tooltip.
The problem is when the tooltip works, the transition doesn't (and vice versa).
Here's is what I've tried so far (the jsfiddle):
var easeOutBounce = function(pos) {
if ((pos) < (1 / 2.75)) {
return (7.5625 * pos * pos);
}
if (pos < (2 / 2.75)) {
return (7.5625 * (pos -= (1.5 / 2.75)) * pos + 0.75);
}
if (pos < (2.5 / 2.75)) {
return (7.5625 * (pos -= (2.25 / 2.75)) * pos + 0.9375);
}
return (7.5625 * (pos -= (2.625 / 2.75)) * pos + 0.984375);
};
Math.easeOutBounce = easeOutBounce;
var t = d3.transition()
.duration(1000)
.ease(easeOutBounce)
var svg = d3.select("svg"),
margin = {
top: 20,
right: 50,
bottom: 30,
left: 10
},
width = +svg.attr("width") - margin.left - margin.right,
height = +svg.attr("height") - margin.top - margin.bottom,
g = svg.append("g").attr("transform", "translate(" + margin.left + "," + margin.top + ")");
// set x axis margin
var x = d3.scaleBand()
.range([30, width])
.padding(0.1);
// set y axis margin
var y = d3.scaleLinear()
.rangeRound([height, 0]);
var div = d3.select("body").append("div")
.attr("class", "tooltip")
.style("display", "none");
d3.tsv("https://raw.githubusercontent.com/patarol/d3_sketch/master/data/data.tsv").then(function(data) {
x.domain(data.map(function(d) {
return d.name;
}));
y.domain([0, d3.max(data, function(d) {
return Number(d.value);
})]);
// draw x axis
svg.append("g")
.attr("class", "x axis")
.attr("transform", "translate(0," + height + ")")
.call(d3.axisBottom(x));
// draw y axis
svg.append("g")
.attr("class", "y axis")
.attr("transform", "translate(0" + 30 + ")")
.call(d3.axisLeft(y)
.ticks(5)
.tickFormat(d3.format(".0s")))
.style("font-size", "10px")
.append("text")
.attr("fill", "#000")
.attr("transform", "rotate(-90)")
.attr("y", 10)
.attr("dy", "0.71em")
.attr("text-anchor", "end")
.text("Value")
.style("font-size", "12px");
svg.selectAll(".bar")
.data(data)
.enter().append("rect")
.attr("class", "bar")
.attr("x", function(d) {
return x(d.name);
})
.attr("y", height)
.transition()
.duration(1000)
.ease(easeOutBounce)
.attr("y", function(d) {
return y(Number(d.value));
})
.attr("width", x.bandwidth())
.attr("height", function(d) {
return height - y(Number(d.value));
});
});
Any help is appreciated!
I am trying to disable the D3 zoom on a particular element. This element happens to be the PNG background to a circle.
Right now this is not working. I have tried to offset the scale parameter in the zoom, but the background PNG still 'grows' with the circle. Here is my jsfiddle.
This is how I try to offset the zoom:
d3.selectAll("#grump_avatar").attr("transform", "scale(" + 1/d3.event.scale + ")");
I know there are similar questions on SO, but please note none of them have received a satisfactory response thus far. Better luck here, hopefully.
Lots of issues with this code:
Matching by id is an exact match.
Your ids are on def attributes, which aren't the objects, you don't want to scale (those would be the circles).
To match multiple objects, you should be using a class on the circles.
You apply the zoom directly to the svg, you should be wrapping everything in a g. SVG handles the events, g is the zoomable "canvas".
Once you apply the zoom correctly you are going to lose your circle placement because you overwrite the transform without reapplying the translate.
You've made no use of d3 data-binding, so you can't persist your data correctly.
All this in mind, here is how I would refactor your code:
var config = {
"avatar_size": 100
}
var body = d3.select("body");
var svg = body.append("svg")
.attr("width", 500)
.attr("height", 500);
var g = svg.append("g");
var defs = svg.append('svg:defs');
data = [{
posx: 100,
posy: 100,
img: "https://cdn0.iconfinder.com/data/icons/flat-round-system/512/android-128.png",
}, {
posx: 200,
posy: 200,
img: "https://cdn1.iconfinder.com/data/icons/social-media-set/24/Reverbnation-128.png"
}, {
posx: 300,
posy: 300,
img: "https://cdn1.iconfinder.com/data/icons/user-pictures/100/male3-128.png"
}];
defs.selectAll("pattern")
.data(data)
.enter()
.append("pattern")
.attr("id", (d, i) => "grump_avatar" + i)
.attr("width", config.avatar_size)
.attr("height", config.avatar_size)
.attr("patternUnits", "userSpaceOnUse")
.append("svg:image")
.attr("xlink:href", (d) => d.img)
.attr("width", config.avatar_size)
.attr("height", config.avatar_size)
.attr("x", 0)
.attr("y", 0);
g.selectAll(".grump_avatar")
.data(data)
.enter()
.append("circle")
.attr("class", "grump_avatar")
.attr("transform", (d) => "translate(" + d.posx + "," + d.posy + ")")
.attr("cx", config.avatar_size / 2)
.attr("cy", config.avatar_size / 2)
.attr("r", config.avatar_size / 2)
.style("fill", "white")
.style("fill", (d, i) => "url(#grump_avatar" + i + ")");
var zoom = d3.behavior.zoom()
.on("zoom", function() {
g.attr('transform', 'translate(' + d3.event.translate + ') scale(' + d3.event.scale + ')');
d3.selectAll(".grump_avatar").attr("transform", (d) => {
return "scale(" + 1 / d3.event.scale + ")" + "translate(" + (d.posx - d3.event.translate[0]) + "," + (d.posy - d3.event.translate[1]) + ")";
});
});
svg.call(zoom);
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/3.4.11/d3.min.js"></script>
EDITS based on comments:
To scale the circles opposite the zoom and position them, the key is:
d3.selectAll("circle")
.attr("transform", function(d){
return 'scale(' + 1 / d3.event.scale + ')'; // inverse of scale for size
})
.attr("cx", function(d){
return d.x * d3.event.scale; // change position based on scale, d.x is the original unscaled position
})
.attr("cy", function(d){
return d.y * d3.event.scale;
});
I'm trying to apply d3.time.format("%b-%Y") to the dates used on the x-axis.
Here is the code which adds the axis and labels
var xLabels = svg
.append("g")
.attr("transform", "translate(" + margin.left + "," + (margin.top + height + 12) + ")");
var formatDateOutputX = d3.time.format("%b-%Y");
xLabels.selectAll("text.xAxis")
.data(BarData)
.enter()
.append("text")
.text(function(d) {
return d.dt; //<< returns dates
//return formatDateOutputX(d.dt); //<< NOTHING RETURNED
})
.attr({
'text-anchor': "middle",
transform: function(d, i) {
var x = (i * (width / BarData.length)) + ((width / BarData.length - barPadding) / 2);
var y = 20;
return 'translate(' + x + ',' + y + ')rotate(-90)';
},
dy: "0.35em", //dx: "-1.05em",
'class': "xAxis"
});
The above relates to the lines of code 285-309 of this visualisation: https://plnkr.co/edit/3d5UhM?p=preview
Hoping someone can help as this will be a fairly common manipulation that I will want to apply. What am I doing wrong?
You are not passing a valid date to formatDateOutputX so it doesn't know how to process it. You have to parse the value appropriately and you can do it in many ways. Since d3 provides a parse method to time.format that is probably the best way to follow.
In your case something like this will work:
var formatDateOutputX = d3.time.format("%b-%Y");
var readDate = d3.time.format("%d/%m/%Y").parse;
xLabels.selectAll("text.xAxis")
.data(BarData)
.enter()
.append("text")
.text(function(d) {
return formatDateOutputX(readDate(d.dt));
})
Check the Plunkr.
I am trying to add labels in a d3 pie as displayed at http://bl.ocks.org/dbuezas/9306799 but it is always displays the labels inside the slices.
var dataset = ${pieList};
var width = 700,
height = 700,
outerRadius = Math.min(width, height) / 2,
innerRadius = outerRadius * .999,
innerRadiusFinal = outerRadius * .5,
innerRadiusFinal3 = outerRadius * .45,
color = d3.scale.category20()
;
var vis = d3.select("#pieChart")
.append("svg:svg")
.data([dataset])
.attr("width", width)
.attr("height", height)
.append("svg:g")
.attr("transform", "translate(" + outerRadius + "," + outerRadius + ")")
;
var arc = d3.svg.arc()
.outerRadius(outerRadius).innerRadius(innerRadius);
var arcFinal = d3.svg.arc().innerRadius(innerRadiusFinal).outerRadius(outerRadius);
var arcFinal3 = d3.svg.arc().innerRadius(innerRadiusFinal3).outerRadius(outerRadius);
var pie = d3.layout.pie()
.value(function (d) {
return d.measure;
});
var arcs = vis.selectAll("g.slice")
.data(pie)
.enter()
.append("svg:g")
.attr("class", "slice")
.on("mouseover", mouseover)
.on("mouseout", mouseout)
.on("click", up)
;
arcs.append("svg:path")
.attr("fill", function (d, i) {
return color(i);
})
.attr("d", arc)
.append("svg:title")
.text(function (d) {
return d.data.category + ": " + formatAsPercentage(d.data.measure);
});
d3.selectAll("g.slice").selectAll("path").transition()
.duration(750)
.delay(10)
.attr("d", arcFinal)
;
arcs.filter(function (d) {
return d.endAngle - d.startAngle > .2;
})
.append("svg:text")
.attr("dy", ".35em")
.attr("text-anchor", "middle")
.attr("transform", function (d) {
return "translate(" + arcFinal.centroid(d) + ")rotate(" + angle(d) + ")";
})
.text(function (d) {
return d.data.category;
})
;
.attr("transform", function (d) {
return "translate(" + arcFinal.centroid(d)
+ ")rotate(" + angle(d)
+ ")translate(" + 700 + ",0)";
})
function angle(d) {
var a = (d.startAngle + d.endAngle) * 90 / Math.PI - 90;
return a > 90 ? a - 180 : a;
}
// Pie chart title
vis.append("svg:text")
.attr("dy", ".35em")
.attr("text-anchor", "middle")
.text("Attendance report 2015")
.attr("class", "title")
;
function mouseover() {
d3.select(this).select("path").transition()
.duration(750)
//.attr("stroke","red")
//.attr("stroke-width", 1.5)
.attr("d", arcFinal3)
;
}
function mouseout() {
d3.select(this).select("path").transition()
.duration(750)
//.attr("stroke","blue")
//.attr("stroke-width", 1.5)
.attr("d", arcFinal)
;
}
function up(d, i) {
/* update bar chart when user selects piece of the pie chart */
//updateBarChart(dataset[i].category);
updateBarChart(d.data.category, color(i));
updateLineChart(d.data.category, color(i));
}
I want to display the labels outside of the pie slices but it always display inside the slices.
One option would be to translate your text outward by the radius of the pie, which might be easiest to do after the rotate. So, something like:
.attr("transform", function (d) {
return "translate(" + arcFinal.centroid(d)
+ ")rotate(" + angle(d)
+ ")translate(" + radius + ",0)";
})
Where radius is the radius of your pie chart. Sometimes you might want to translate a few pixels further, so that you have a margin between the chart and the label.
This is certainly not the only answer, here are some more issues to watch out for though:
Rotating your text can end up with it being upside down on one side of the graph, which is not very readable.
After fixing the rotation issue, you might find that your label's textanchor causes long labels to go over the graph anyway.
If your pie slices are thin, you can end up writing labels over one another.