Zoom into group of elements with d3.js - d3.js

I have an svg with multiple objects in it. On selecting a checkbox i would like to zoom into the appropriate objects. I've come to something like the code below but zooming and positions are still not accurate. Can some point me out how i could accomplish this?
d3.selectAll('g.active')
.each(function(d, i) {
var bounds = this.getBoundingClientRect();
boundsLeft.push(bounds.left);
boundsRight.push(bounds.right);
boundsTop.push(bounds.top);
boundsBottom.push(bounds.bottom);
});
var top = Math.min.apply(Math, boundsTop);
var bottom = Math.max.apply(Math, boundsBottom) ;
var right = Math.max.apply(Math, boundsRight) ;
var left = Math.min.apply(Math, boundsLeft);
var bounds = [[left, top], [right, bottom]],
dx = bounds[1][0] - bounds[0][0],
dy = bounds[1][1] - bounds[0][1],
x = (bounds[0][0] + bounds[1][0]) / 2,
y = (bounds[0][1] + bounds[1][1]) / 2,
scale = Math.max(0.6, Math.min(8, 0.9 / Math.max(dx / width, dy / height))),
translate = [(width / 2) - (scale * x), (height / 2) - (scale * y)];
vis.transition()
.duration(750)
.call(zoom.translate([((dx * -scale) + (width / 2)), ((dy * -scale) + height / 2)]).scale(scale).event);

Related

Godot: Animation only plays when screen is being resized

My game starts with a splash screen of a starfield flying towards the player. The first frame draws fine, but subsequent frames only draw while the window is actively being resized.
I just migrated from Godot 3.5.1 to 4.0.beta5 and had to do a bit of code refactoring just to get it semi-functional.
extends Node2D
const WIDTH = 1920
const HEIGHT = 1080
const STAR_COUNT = 400
const PLANET_COUNT = 5
var splash_stars = []
var theta
var offsetz
var x
var dx
var y
var dy
var radius
var alpha
var planet_scale
var color
var rng
func _ready():
rng = RandomNumberGenerator.new()
rng.randomize()
# initialize the stars animation
var Star = load("res://stars.gd")
for _i in range(STAR_COUNT):
theta = rng.randi()%360
offsetz = rng.randi()%(HEIGHT -1) + 1
x = offsetz * cos(theta) + WIDTH / 2.0
dx = 2.5 * cos(theta)
y = offsetz * sin(theta) + HEIGHT / 2.0
dy = 2.5 * sin(theta)
radius = 100 / offsetz
alpha = 0
var star = Star.new(theta, offsetz, x, dx, y, dy, radius, alpha)
splash_stars.append(star)
func update_star_coords():
for splash_star in splash_stars:
splash_star._dx = splash_star._dx * 1.005
splash_star._dy = splash_star._dy * 1.005
splash_star._x += splash_star._dx
splash_star._y += splash_star._dy
splash_star._radius += .025
splash_star._alpha += .005
if 0 > splash_star._x or splash_star._x > WIDTH or 0 > splash_star._y or splash_star._y > HEIGHT or splash_star._radius > 8:
splash_star._theta = rng.randi()%360
splash_star._theta = deg_to_rad(splash_star._theta)
splash_star._offset = rng.randi()%(HEIGHT - 1) + 1
splash_star._x = splash_star._offset * cos(splash_star._theta) + WIDTH / 2.0
splash_star._dx = 2.5 * cos(splash_star._theta)
splash_star._y = splash_star._offset * sin(splash_star._theta) + HEIGHT / 2.0
splash_star._dy = 2.5 * sin(splash_star._theta)
splash_star._radius = 100 / splash_star._offset
if splash_star._radius < 1:
splash_star._radius = 1
splash_star._alpha = 0
func _draw():
for splash_star in splash_stars:
var center = Vector2(splash_star._x, splash_star._y)
color = Color(1, 1, 0, splash_star._alpha)
draw_circle(center, splash_star._radius, color)
func _process(delta):
update_star_coords()
And the 'stars' class:
extends Node2D
var _theta
var _offset
var _x
var _dx
var _y
var _dy
var _radius
var _alpha
func _init(theta,offset,x,dx,y,dy,radius,alpha):
self._theta = theta
self._offset = offset
self._x = x
self._dx = dx
self._y = y
self._dy = dy
self._radius = radius
self._alpha = alpha
You need to call update (queue_redraw in Godot 4.0) to tell Godot that it needs to call _draw again.
You can read about it in Custom drawing in 2D.

pgraphics element won't center on mobile devices | drawingContext.drawImage causing mobile offset

I’m working on some of Tim Rodenbrökers code involving a copy() function (https://timrodenbroeker.de/processing-tutorial-kinetic-typography-1/), expanding it and making it ready for web.
This involves replacing the copy() function with drawingContext.drawImage() for performance increase (found here: https://discourse.processing.org/t/p5-js-copy-function-slow-performance-since-version-0-10-0/30007).
Doing this works great for desktop; on mobile, however, the pgraphics element (centered on the canvas, usually), moves position.
Using the regular copy() function centeres it correctly.
The positioning varies according to mobile screen size, I can’t seem to figure out the exact behavior to fix. It's not the font size, I've tried adapting the position to screen.size and document.documentElement.clientWidth, no luck.
let font;
let pg;
function setup() {
font = loadFont("./assets/FGrotesk-Regular.otf");
createCanvas(innerWidth, innerHeight);
pg = createGraphics(innerWidth, innerHeight, P2D);
frameRate(60);
pixelDensity(1);
}
function draw() {
background(0);
pg.background(0);
pg.fill(255);
pg.textFont(font);
pg.textSize(380);
pg.push();
pg.translate(innerWidth / 2, innerHeight / 2);
pg.textAlign(CENTER, CENTER);
pg.text("Enrico", 0, -50);
pg.text("Gisana", 0, 50);
pg.pop();
let tilesX = 400;
let tilesY = 20;
let tileW = int(width / tilesX);
let tileH = int(height / tilesY);
for (y = 0; y < tilesY; y++) {
for (x = 0; x < tilesX; x++) {
// WARP
let wave_x = int(sin(frameCount * 0.02 + (x * y) * 0.07) * 100) - (mouseY / 2);
let wave_y = int(sin(frameCount * 0.02 + (x * y) * 0.07) * 100) - (mouseY / 2);
if (mouseX - (width / 2) >= 0) {
wave_x = int(sin(frameCount * 0.02 + ((x / 0.8) * (y/0.2)) * 0.04) * (-1 * (mouseX - (width / 2)) / 30));
} else {
wave_x = int(sin(frameCount * 0.02 + ((x / 0.8) * (y/0.2)) * 0.04) * (-1 * (mouseX - (width / 2)) / 30));
}
if (mouseY - (height / 2) >= 0) {
wave_y = int(sin(frameCount * 0.02 + ((x / 0.2) * (y/0.8)) * 0.04) * ((mouseY - (height / 2)) / 30));
} else {
wave_y = int(sin(frameCount * 0.02 + ((x / 0.2) * (y/0.8)) * 0.04) * ((mouseY - (height / 2)) / 30));
}
// SOURCE
let sx = x * tileW + wave_x;
// + wave should be added here
let sy = y * tileH - wave_y;
let sw = tileW;
let sh = tileH;
// DESTINATION
let dx = x * tileW;
let dy = y * tileH;
let dw = tileW;
let dh = tileH;
drawingContext.drawImage(pg.elt, sx, sy, sw, sh, dx, dy, dw, dh);
}
}
}

D3JS v5 Zoom - The transform value changes between zoom on button and mouse wheel

I'm working on a project using D3js V5 that takes the input of a CSV file and returns a Workflow diagram, everything is working fine besides on it's own.
My problem is when I fit the workflow, through a Fit Button, and after that I use the mouse wheel to zoom in or out it just jumps to the previous zoom and position settings.
After some research I've found that after D3v4+ that "Zoom behaviours no longer store the active zoom transform (i.e., the visible region; the scale and translate) internally. The zoom transform is now stored on any elements to which the zoom behaviour has been applied.(link)"
That being said I cannot make it work and that's why I'm asking for your help.
The code is has follows:
document.getElementById('fitBTN').addEventListener('click', zoomFit);
function zoomFit() {
let bounds = document.getElementById('svgContent').getBBox();
let parent = document.getElementById('svgContent').parentElement;
let fullWidth = parent.clientWidth,
fullHeight = parent.clientHeight;
let width = bounds.width,
height = bounds.height;
let midX = bounds.x + width / 2,
midY = bounds.y + height / 2;
if (width == 0 || height == 0) return; // nothing to fit
let scale = 0.95 / Math.max(width / fullWidth, height / fullHeight);
let translate = [
fullWidth / 2 - scale * midX,
fullHeight / 2 - scale * midY
];
document
.getElementById('svgContent')
.setAttribute(
'transform',
'translate(' +
translate[0] +
',' +
translate[1] +
') scale(' +
scale +
')'
);
}
svg
.call(
zoom()
.on('zoom', () => {
document
.getElementById('svgContent')
.setAttribute(
'transform',
'translate(' +
currentEvent.transform.x * 0.125 +
',' +
currentEvent.transform.y * 0.125 +
') scale(' +
currentEvent.transform.k +
')'
);
})
.scaleExtent([0.1, 1])
)
.on('dblclick.zoom', false)
.on('wheel', function() {
currentEvent.preventDefault();
})
.on('wheel', false);
I believe that I have to had some variable that is able to read the state of the transform translate, but how?
So the answer provided by #AndrewReid has helped me resolve my issue and the corrected version of the code is here:
function zoomFit() {
let bounds = document.getElementById('svgContent').getBBox();
let parent = document.getElementById('svgContent').parentElement;
let fullWidth = parent.clientWidth,
fullHeight = parent.clientHeight;
let width = bounds.width,
height = bounds.height;
let midX = bounds.x + width / 2,
midY = bounds.y + height / 2;
if (width == 0 || height == 0) return; // nothing to fit
let scale = 0.95 / Math.max(width / fullWidth, height / fullHeight);
let translate = [
fullWidth / 2 - scale * midX,
fullHeight / 2 - scale * midY
];
document
.getElementById('svgContent')
.setAttribute(
'transform',
'translate(' +
translate[0] +
',' +
translate[1] +
') scale(' +
scale +
')'
);
// Create a zoom transform from d3.zoomIdentity
var transform = zoomIdentity
.scale(scale)
.translate(-translate[0], -translate[1]);
// Apply the zoom and trigger a zoom event:
svg.call(zoom().transform, transform);
}
var zooming = zoom()
.scaleExtent([0.1, 1])
.translateExtent([[0, 0], [840, 740]])
.on('zoom', zoomed);
svg.call(zooming);
function zoomed() {
svg
.attr('transform', currentEvent.transform)
.on('dblclick.zoom', false)
.on('wheel', function() {
currentEvent.preventDefault();
});
}
But another issue got in the way which is the panning function was lost.
I'll have to figure that one out and I'll update this answer has soon has the solutions is found.
Best regards,

Add padding to sunburst layout d3 v4

Following this example I am creating a sunburst diagram. I would like to add a bit of padding between each node. Here is the code I have so far:
var sunburstLayout = d3.partition();
var radius = 100;
sunburstLayout.size([2*Math.PI, radius]);
//sunburstLayout.padding(2);
var arc= d3.arc()
.startAngle( function(d) { return d.x0 })
.endAngle( function(d) { return d.x1 })
.innerRadius(function(d) { return d.y0 })
.outerRadius(function(d) { return d.y1 })
root.sum(d => d.value);
...
which produces this:
Now when adding:
sunburstLayout.padding(2);
The layout is messed up.
sunburstLayout.padding(1);
Any suggestions on how to keep the layout correct while being able to add padding to each node?
It seems to me that you just found a limitation of this padding method. If you look at the source code...
function positionNode(dy, n) {
return function(node) {
if (node.children) {
treemapDice(node, node.x0, dy * (node.depth + 1) / n, node.x1, dy * (node.depth + 2) / n);
}
var x0 = node.x0,
y0 = node.y0,
x1 = node.x1 - padding,
y1 = node.y1 - padding;
if (x1 < x0) x0 = x1 = (x0 + x1) / 2;
if (y1 < y0) y0 = y1 = (y0 + y1) / 2;
node.x0 = x0;
node.y0 = y0;
node.x1 = x1;
node.y1 = y1;
};
}
... you'll see that it works for linear distances, but it doesn't work for radial layouts, where x and y don't represent a cartesian coordinate. Therefore, it can be used for icicles, but nor for sunbursts.
A quick and dirty workaround is changing the arc generator instead, using padAngle and adding the linear value in both innerRadius and outerRadius (in innerRadius you add the value, and in outerRadius you subtract the value). That gives us:
This is the updated CodePen: https://codepen.io/anon/pen/QBWMKm?editors=0010

D3 Map disable zoom on city label

i'm stuck in this problem for a few days, and i hope someone can help me.
Basically this is the problem:
I've a UK map , with city label and marker. I've enabled zoom and pan on this, but when i zoom in even marker and label grow in dimension.
I'd like to preserve marker poisiton during zoom and pan (London is always in its place) but don't scale the dimension of marker and label.
This is my zoom function
var zoomBehav= function(){
var t = d3.event.translate,
s = d3.event.scale;
t[0] = Math.min(width / 2 * (s - 1), Math.max(width / 2 * (1 - s), t[0]));
t[1] = Math.min(height / 2 * (s - 1) + 230 * s, Math.max(height / 2 * (1 - s) - 230 * s, t[1]));
zoom.translate(t);
g.style("stroke-width", 1 / s).attr("transform", "translate(" + t + ") scale(" + s + ")");
};
Anyone can help me?
ADDITIONAL INFO
I think i should write more info.
My svg is defined as
this.svg = d3.select("#"+this.config.mapContainer).append("svg")
.attr("id","svgMap")
.attr("width",width)
.attr("height",height)
.style("background-color", "lightblue")
.append("g").attr('id','first_g')
.attr("transform", "translate(" + width / 2 + "," + height / 2 + ")")
//Adding Zoom Rect to svg
this.svg.append("rect")
.attr("id", "d3e_zoomArea")
.attr("class", "d3e_zoomArea")
.attr("x", -width / 2)
.attr("y", -height / 2)
.attr("width", width)
.attr("height", height)
.attr('onmousedown', 'return false');
//Insert g elementi in svg
g = this.svg.append("g").attr('id','g_pathContainer');
//Set Actual Map Right Projection
/ create a first guess for the projection
subunits = topojson.feature(map, map.objects[level]);
center = d3.geo.centroid(subunits);
projection = d3.geo.mercator()
.scale(width / 2 / Math.PI) //Crea la scala di visualizzazione
.center(center) //Centra la posizione sulla mappa
.translate([0, 0]);
path = d3.geo.path().projection(projection);
// using the path determine the bounds of the current map and use
// these to determine better values for the scale and translation
var bounds = path.bounds(subunits);
var hscale = scale*width / (bounds[1][0] - bounds[0][0]);
var vscale = scale*height / (bounds[1][1] - bounds[0][1]);
scale = (hscale < vscale) ? hscale : vscale;
// new projection
projection = d3.geo.mercator().center(center)
.scale(scale).translate([0, 0]);
path = path.projection(projection);
zoom = d3.behavior.zoom()
.translate(projection.translate())
.scale(projection.scale())
.scaleExtent([this.config.scaleRange.scaleMin , this.config.scaleRange.scaleMax])
.on("zoom", zoomed);
d3.select("#first_g").call(zoom);
JSFIDDLE added
I've created a jsfiddle (http://jsfiddle.net/z9S7y/) to reproduce my problem. I've applied some initial transformation to base projection in order to center and scale the map in relation to actual country.

Resources