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;
});
Related
I tried to figure out the difference between 'd3.event.pageX' & 'd3.mouse(this)[0]'.
I guessed both are same but,
when I console.log both,
the value was different by '8' in my code.
var height=600;
var width=600;
var graphgap=60;
d3.csv('./details.csv').then(function(data){
var svg =d3.select('section').append('svg')
.attr('width',600).attr('height',600)
.on('mousemove',mousemove)
drawrect(data);
})
function drawrect(data){
let bars=d3.select('svg').selectAll('rect').data(data);
bars.enter().append('rect').classed('bargraph',true)
.attr('x',function(d,i){return (i+1)*graphgap})
.attr('y',function(d){return height-(d.Age)*5})
.attr('width',55)
.attr('height',function(d){return (d.Age)*(5)})
}
function mousemove(){
let mouselocation =[];
d3.select('svg').append('text')
.text(d3.event.pageX)
.attr('x',d3.event.pageX)
.attr('y',d3.event.pageY)
console.log(d3.event.pageX)
console.log(d3.mouse(this)[0])
}
So, I think these two are two different things.
Can anyone let me know why it makes a difference?
The reason why I tried to figure this out is because I was re-writing the code below.
<script>
// set the dimensions and margins of the graph
var margin = {top: 10, right: 30, bottom: 30, left: 60},
width = 460 - margin.left - margin.right,
height = 400 - margin.top - margin.bottom;
// append the svg object to the body of the page
var svg = d3.select("#my_dataviz")
.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 + ")");
//Read the data
d3.csv("https://raw.githubusercontent.com/holtzy/D3-graph-gallery/master/DATA/data_IC.csv",function(data) {
// Add X axis --> it is a date format
var x = d3.scaleLinear()
.domain([1,100])
.range([ 0, width ]);
svg.append("g")
.attr("transform", "translate(0," + height + ")")
.call(d3.axisBottom(x));
// Add Y axis
var y = d3.scaleLinear()
.domain([0, 13])
.range([ height, 0 ]);
svg.append("g")
.call(d3.axisLeft(y));
// This allows to find the closest X index of the mouse:
var bisect = d3.bisector(function(d) { return d.x; }).left;
// Create the circle that travels along the curve of chart
var focus = svg
.append('g')
.append('circle')
.style("fill", "none")
.attr("stroke", "black")
.attr('r', 8.5)
.style("opacity", 0)
// Create the text that travels along the curve of chart
var focusText = svg
.append('g')
.append('text')
.style("opacity", 0)
.attr("text-anchor", "left")
.attr("alignment-baseline", "middle")
// Create a rect on top of the svg area: this rectangle recovers mouse position
svg
.append('rect')
.style("fill", "none")
.style("pointer-events", "all")
.attr('width', width)
.attr('height', height)
.on('mouseover', mouseover)
.on('mousemove', mousemove)
.on('mouseout', mouseout);
// Add the line
svg
.append("path")
.datum(data)
.attr("fill", "none")
.attr("stroke", "steelblue")
.attr("stroke-width", 1.5)
.attr("d", d3.line()
.x(function(d) { return x(d.x) })
.y(function(d) { return y(d.y) })
)
// What happens when the mouse move -> show the annotations at the right positions.
function mouseover() {
focus.style("opacity", 1)
focusText.style("opacity",1)
}
function mousemove() {
// recover coordinate we need
var x0 = x.invert(d3.mouse(this)[0]);
var i = bisect(data, x0, 1);
selectedData = data[i]
focus
.attr("cx", x(selectedData.x))
.attr("cy", y(selectedData.y))
focusText
.html("x:" + selectedData.x + " - " + "y:" + selectedData.y)
.attr("x", x(selectedData.x)+15)
.attr("y", y(selectedData.y))
}
function mouseout() {
focus.style("opacity", 0)
focusText.style("opacity", 0)
}
})
</script>
In documentation is written:
While you can use the native event.pageX and event.pageY, it is often
more convenient to transform the event position to the local
coordinate system of the container that received the event using
d3.mouse, d3.touch or d3.touches.
d3.event
d3.mouse - uses local coordinate (without margin (60px))
d3.event.pageX - uses global coordinate (with margin (60px))
But local cordinate start on 68px. I guess 8 pixels is used to describe the y-axis.
I have a d3 graph where I'd like one specific object to remain the same height after zoom is called (so it still extends the height of the chart), but the width zooms in and out accordingly.
Essentially, I just want the zoom to affect it horizontally (x). Other items need to remain a two-dimensional zoom.
Here is the whole project: https://codepen.io/lahesty/pen/XYoyxV?editors=0001 The blue rectangles are what I'm talking about. Lines 295-319
Here is my zoom function:
function zoomed() {
svg.selectAll("path.line")
.attr("transform", d3.event.transform);
svg.selectAll("g.anomrect")
.attr("transform", d3.event.transform);
svg.selectAll("g.dot")
.attr("transform", d3.event.transform);
gX.call(xAxis.scale(d3.event.transform.rescaleX(x)))
gY.call(yAxis.scale(d3.event.transform.rescaleY(y)))}
Calling zoom:
var zoom = d3.zoom()
.scaleExtent([1, 40])
.on("zoom", zoomed);
Object(s) I'm zooming on:
var anamoly = svg.append('g')
.attr('clip-path', 'url(#clipper)')
.selectAll('.anomrect')
.data(anamoly_data, function(d, i) { return d[0];
}).enter().append("g").attr("class", "anomrect");
anamoly.selectAll('.anomrect')
.data(function(d) { return d; }, function(d_, i_) { return i_})
.enter()
.append('rect')
.attr("id", function(d,i){return id_list[i%id_list.length]})
.attr("height", height)
.attr("width", function(d) { return x(d.ended_at)-x(d.started_at); })
.attr("x", function(d) { return x(d.started_at); })
.attr("y", 0)
Should I adjust "height" on the object? Or perhaps my event.transform in my zoom function?
Thank you
For either lines (actually paths), rectangles or circles, use just d3.event.translate.x for translating the x position only, and use d3.event. translate.k also for scaling the x position only:
selection.attr("transform",
"translate(" + d3.event.transform.x + ",0) scale(" + d3.event.transform.k + ",1)");
So, for restricting the zoom to the x coordinate only for all paths, rectangles and circles it will be:
function zoomed() {
svg.selectAll("path.line").attr("transform",
"translate(" + d3.event.transform.x + ",0) scale(" + d3.event.transform.k + ",1)");
svg.selectAll("g.anomrect").attr("transform",
"translate(" + d3.event.transform.x + ",0) scale(" + d3.event.transform.k + ",1)");
svg.selectAll("g.dot").attr("transform",
"translate(" + d3.event.transform.x + ",0) scale(" + d3.event.transform.k + ",1)");
gX.call(xAxis.scale(d3.event.transform.rescaleX(x)));
};
Notice that here I'm removing the y axis call.
As you mentioned in your question, if you want to restrict the zoom to just one selection, change just that respective selection (and keep the y axis call).
Here is the updated CodePen (restricting the zoom for all selections): https://codepen.io/anon/pen/djyyPQ?editors=0011
As you can see here element g http://imgur.com/SZImQNB with different browser zoom levels. I would like to get those values 402x398 and 1608x1582 or whatever the values would be while zooming.
var container - is the svg g that I am looking for dimensions of
var svg = d3.select("body")
.append("svg")
.attr("width", w)
.attr("height", h);
svg.append('rect')
.classed('bg', true)
.attr('stroke', 'transparent')
.attr('fill', 'transparent')
.attr("x", 0)
.attr("y", 0)
.attr('width', w)
.attr('height', h)
.call(d3.behavior.zoom()
.on("zoom", function() {
container.attr("transform", "translate(" + d3.event.translate +
")scale(" + d3.event.scale + ")");
}));
var container = svg.append("g")
.classed("node-area", true)
.attr("id", "test");
I'm using container.node().getBBox().width to get those values and I always receive 70 px width 30 px height no matter what.
console.log("width: "+container.node().getBBox().width+" height: "+container.node().getBBox().height+ "x: "+container.node().getBBox().x+" y: "+container.node().getBBox().y);
I am following d3.js force layout auto zoom/scale after loading TWiStErRob answer
#RobertLongson suggestion does work. In the code, getBoundingClientRect().width has to be used after the transform:
function zoomed() {
container.attr("transform", "translate(" + d3.event.translate
+ ")scale(" + d3.event.scale + ")");
console.log("width: "+container.node().getBoundingClientRect().width);
};
Check the console in this fiddle, while you apply zoom: http://jsfiddle.net/gerardofurtado/tr4kx4af/
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.
As stated in previous questions (for example d3.js Odd Rotation Behavior), when you want to rotate an SVG object on its place you should use
.attr('transform','rotate(45,100,100)')
where the first number is the rotation degrees, and the other two are the coordinates of the rotation origin.
The problem here is that I want to execute the rotation inside a transition:
.transition()
.attr('transform', 'rotate(45,100,100)')
.duration(300)
and I get a strange behaviour, I can see a translation before the expected rotation.
You can see the result here: http://jsfiddle.net/uwM8u/121/
Is there a better way to obtain this?
The D3 way to do this is to use a custom tween function, which in this case is a simple string interpolation:
.attrTween("transform", function() {
return d3.interpolateString(d3.select(this).attr("transform"),
"rotate(" + (rotation+=45) + "," +
(diamond.x+diamond.width/2) + "," +
(diamond.y+diamond.width/2) + ")");
})
Complete demo here.
Add a parent <g> element with the translation so you are actually rotating the shape about the origin.
var svg = d3.select("body")
.append("svg")
.attr("width", 400)
.attr("height", 300);
svg
.append("text")
.text("click the square")
.attr("x", w/2)
.attr("y", w/2)
svg
.append("g")
.attr("transform", "translate(" + diamond.x + "," + diamond.y +")")
.append("rect")
.attr("transform", "rotate(" + rotation + ")")
.attr("x", -diamond.width / 2)
.attr("y", -diamond.width / 2)
.attr("height", diamond.width)
.attr("width", diamond.width)
.attr("fill", "teal")
.on("mousedown", function(){
d3.select(this)
.transition()
.attr("transform", "rotate("+ (rotation+=45) +")")
.duration(300)
});
or as a jsfiddle