I have an issue restoring transform parameters and applying it to D3 view. When I apply previously saved transform parameters to view. Everything looks nice. But as soon as you try to drag or zoom view the view jumps.
Here what I mean:
https://blockchaingraph.org/#ipfs-Qmdv3dEd5YUsiLv8GJ4ZEKYcBwPU6vKosqCXRWvR7LmcuE
After graph loads, try to drag the view and you will see it jumps.
Here's my code:
svg = container.append('svg')
.attr('width', '100%')
.attr('height', '100%')
.attr('class', 'graphd3-graph')
.call(d3.zoom().on('zoom', function () {
var scale = d3.event.transform.k;
var translate = [d3.event.transform.x, d3.event.transform.y];
svg.attr('transform', 'translate(' + translate[0] + ', ' + translate[1] + ') scale(' + scale + ')');
}))
.on('dblclick.zoom', null)
.append('g')
.attr('width', '100%')
.attr('height', '100%');
.....
var c = container.select(".graphd3-graph");
svg.attr('transform', data.transform);
d3.zoom().scaleTo(c, tra.k);
d3.zoom().translateTo(c, tra.x, tra.y);
The question is how to properly apply transform on draggable/zoomable D3 view?
d3.zoom().scaleTo() seems to be working, but d3.zoom().translateTo() completely ignored.
I'm using d3/5.7.0/d3.min.js
Related
I've built a D3 force graph largely based on these really helpful examples.
I wanted to add pan and zoom functionality, which I tried to do using another example (looks like I can only include two links, but Google "d3 force zoom eyaler" to find it).
Unfortunately, when I zoom out on a graph that is larger than the initial SVG, I get something like this:
Result of dragging and dropping
Here's the relevant code:
var svg = d3.select("body").append("svg")
.attr("width", width)
.attr("height", height)
.call(d3.behavior.zoom().scaleExtent([0.5,2]).on("zoom", redraw));
function redraw() {
svg.attr("transform",
"translate(" + d3.event.translate + ")"
+ " scale(" + d3.event.scale + ")");
}
How can I change the pan and zoom behaviour so that it scrolls and makes it possible to see the rest of the graph, rather than just allowing me to move the square that was originally visible?
OK, looks like I worked it out... you need to perform the transform on a <g> rather than on the SVG itself. So:
var svg = d3.select("body").append("svg")
.attr("width", width)
.attr("height", height)
.call(d3.behavior.zoom().scaleExtent([0.5,2]).on("zoom", redraw));
var g = svg.append("g"); // add <g>
function redraw() {
g.attr("transform", // perform transform on g, not svg
"translate(" + d3.event.translate + ")"
+ " scale(" + d3.event.scale + ")");
}
Just putting this here in case anyone else made the same mistake!
I have a rect holding a clip path, which I'm applying to a group (holding a tree). I have a zoom function bound to the rect which transforms the group, which works fine. I've applied the clip path to the group, and when it first renders it looks like it should. However, after panning or zooming, the drawn tree extends beyond the bounds of the clip path while maintaining its previously-clipped appearance.
var svg = d3.select(this.$.chart);
var svg2 = svg.select("svg");
var main = svg2.append("g")
.attr("class","main")
.attr("transform", "translate(" + margin.left + "," + margin.top + ")");
var treeContainer = svg2.append('g')
.attr("transform", "translate(" + margin2.left + "," + margin2.top + ")")
var treeBaseRect = treeContainer.append("rect") // the rectangle which holds the clip path and zoom actions for the tree.
.attr("width", width + margin2.right)
.attr("height", height2)
.style("fill", "#eee")
.style("pointer-events", "all")
.call(d3.zoom().scaleExtent([0.1, 3]).on("zoom", function () {
svgGroup.attr("transform", d3.event.transform)
}));
treeContainer.append('defs').append("clipPath")
.attr("id", "clip")
.append("rect")
.attr("width", width + margin2.right)
.attr("height", height2);
var svgGroup = treeContainer.append("g")
.attr("clip-path","url(#clip)");
Here's some screenshots. First one shows the initial render, which is fine (the clip area is the darker grey rectangle):
Then after doing a scroll zoom or pan, note how the tree is still 'originally' clipped, and not being clipped properly outside of the gray area:
And this is my clip path rect in the DOM structure:
You can tell that the clip rect is still where it's meant to be, but the tree is completely ignoring it. No idea why.
Apparently I hadn't added enough groups. The easy solution was to add another append("g") to the svgGroup I was creating, for a last line looking like this:
var svgGroup = treeContainer.append("g")
.attr("clip-path","url(#clip)")
.append("g");
D3.js panning appears to be slower and more choppy than zooming when the svg has many elements. I made an example on JSFiddle http://jsfiddle.net/cornhundred/cfeu1ws2/10/ and the code is also shown below
var num_rect = 3000;
var zoom = d3.behavior.zoom()
.on("zoom", zoomed);
function zoomed() {
console.log('zooming or panning');
d3.select('svg')
.select('#rect_group')
.attr("transform", "translate(" + d3.event.translate + ")scale(" + d3.event.scale + ")");
}
svg = d3.select("body")
.append("svg:svg").attr("width", 800).attr("height", 800)
.call(zoom);
var rect_group = d3.select('svg')
.append('g')
.attr('id', 'rect_group');
var data = _.range(num_rect);
var color = d3.scale.linear().domain([0, num_rect]).range(['red', 'blue']);
rect_group.selectAll('rect')
.data(data)
.enter()
.append('rect')
.attr('height', 175)
.attr('width', 5)
.attr('x', function (d) {return 50 + d / (0.003 * num_rect);})
.attr('y', 50)
.style('fill', function (d) {return color(d);});
In this example I'm appending num_rects rectangles onto the svg side-by-side and adding some coloring so its clear there are many rectangles. I'm also making a console log whenever the zoomed function is called.
Increasing num_rect above ~3000 caused panning to be choppy while zooming remained smooth. This can ben seen in the behavior of the visualization or from the frequency of the console logs - 'zooming or panning'. This is odd since I would expect panning to be as cpu intensive than zooming.
This behavior also appears to be browser-specific - I'm only seeing this in chrome (which is also odd since Chrome is usually the best at rendering D3.js visualizations).
The d3.geo.path has a null projection because the TopoJSON is already projected, so it can be displayed as-is. I'm trying to plot data in the form of [longitude, latitude] on a map.
Below is what my code basically looks like:
var width, height, path, svg;
width = 960;
height = 600;
path = d3.geo.path().projection(null);
svg = d3.select('.viz').append('svg')
.attr('width', width)
.attr('height', height);
d3.json("counties.json", function(error, us) {
svg.append('path')
.datum(topojson.mesh(us))
.attr('d', path);
});
svg.selectAll('.pin')
.data(ds) // i.e. ds = {[12.521, 15.312], [616.122,-31.160]}
.enter().append('circle', '.pin')
.attr('r', 3)
.attr('transform', function (d) {
return 'translate(' + path([
d.longitude,
d.latitude
]) + ')';
});
I debugged through this, and I do get the data fine. But, I am getting an error that "path([d.longitude, d.latitude])" is undefined. "d" exists with longitude and latitude values. "path" also exists. I think it's because the projection is null.
What can I do to make this work?
------- EDIT -------
I followed the recommendation by Ben Lyall to remove the use of "path" in the selectAll statement as well as moving the selectAll statement inside the .json(). I also noticed I put in an incorrect example for ds, so I modified the comment. Here is my updated code.
This displays the map fine with no console errors, but I still don't see any circles in the map itself.
var width, height, path, svg;
width = 960;
height = 600;
path = d3.geo.path().projection(null);
svg = d3.select('.viz').append('svg')
.attr('width', width)
.attr('height', height);
d3.json("counties.json", function(error, us) {
svg.append('path')
.datum(topojson.mesh(us))
.attr('d', path);
svg.selectAll('.pin')
.data(ds) // i.e. ds = [{longitude: 12.521, latitude: 15.312}, {longitude: 616.122, latitude: -31.160}]
.enter().append('circle', '.pin')
.attr('r', 3)
.attr('transform', function (d) {
return 'translate(' +
d.longitude + ',' + d.latitude +
')';
});
});
------- EDIT -------
The solution was a combination of Ben Lyall's proposed solution as well as taking into account the projection already done on the map for the .pins. Since the projection in code is null, I had to create a new one that matches the projection for the map and then use that when performing the transform on the .pin's.
Below is the solution:
var width, height, path, projection, svg;
width = 960;
height = 600;
path = d3.geo.path().projection(null);
projection = d3.geo.albersUsa().scale(1280).translate([width/2, height/2]);
svg = d3.select('.viz').append('svg')
.attr('width', width)
.attr('height', height);
d3.json("counties.json", function(error, us) {
svg.append('path')
.datum(topojson.mesh(us))
.attr('d', path);
svg.selectAll('.pin')
.data(ds)
.enter().append('circle', '.pin')
.attr('r', 3)
.attr('transform', function (d) {
return 'translate(' +
projection([d.longitude, d.latitude]) +
')';
});
});
When you're positioning your .pin elements, why are you using path in your translate? You don't want to create a path, and the d.latitude and d.longitude values should already be in pixel co-ordinates, since they're projected already, so you should be able to use them directly.
Note: you probably also want that part of your code inside the d3.json handler, rather than outside, since it'll run asynchronously before your data is set to anything (this is probably the actual issue with your code, rather than using path incorrectly).
It's a little hard to confirm without an example to fork, but try this:
var width, height, path, svg;
width = 960;
height = 600;
path = d3.geo.path().projection(null);
svg = d3.select('.viz').append('svg')
.attr('width', width)
.attr('height', height);
d3.json("counties.json", function(error, us) {
svg.append('path')
.datum(topojson.mesh(us))
.attr('d', path);
svg.selectAll('.pin')
.data(ds) // i.e. ds = {[12.521, 15.312], [616.122, -31.160]}
.enter().append('circle', '.pin')
.attr('r', 3)
.attr('transform', function (d) {
return 'translate(' + d.longitude + "," + d.latitude + ')';
});
});
I have a bubble force chart application, and I've created various instances of it. However I can not seem to scale the bubbles in relation to the height/width of the svg.
http://jsfiddle.net/pPMqQ/138/
I've placed an inverse on the heights/widths to alter the viewport - but it doesn't feel like the right approach to me. Does anyone have any experience as to how to correct this?
var inverseHeight = 1/h * 100000;
var inverseWidth = 1/w * 100000;
var svg = d3.select(selector)
.append("svg")
.attr("class", "bubblechart")
.attr("width", parseInt(w + padding,10))
.attr("height", parseInt(h + padding,10))
.attr('viewBox', "0 0 "+parseInt(inverseWidth,10)+" "+parseInt(inverseHeight,10))
.attr('perserveAspectRatio', "xMinYMid")
.append("g")
.attr("transform", "translate(" + (w/4) + "," + (h/4) + ")");
I've tried to scale the actual bubbles
http://jsfiddle.net/pPMqQ/143/
var scale = methods.width*.005;
Its producing an ideal effect - but the chart is not always central.