Incorrect Positioning of Voronoi on Leaflet Map - javascript

i'm struggling with positioning and scaling of a voronoi diagram on a leaflet map.
the voronoi polygons are correctly displayed after they're appended on the map, but
after resizing they don't scale and translate correctly. i tried to reset the path after
panning and zooming to the feature element. but it looks like the new values are passed to their parent's element. If i set the path on feature.selectAll('path).attr('d',path) the scaling and translation is absolutly correct, but it show's the voronoi means instead the voronoi polygons. Any Idea?
Best regards,
Flo
this.id = p_id;
this.data = p_data;
d3.select('#'+this.id).remove();
var svg = d3.select(map.getPanes().overlayPane).append("svg").attr('id', this.id);
var g = svg.append("g").attr("class", "leaflet-zoom-hide").attr("id", "cells");
var pointdata = this.data.features;
var positions = [];
pointdata.forEach(function(d){
positions.push(project(d.geometry.coordinates));
});
var polygons = d3.geom.voronoi(positions);
var bounds = d3.geo.bounds(this.data),
path = d3.geo.path().projection(project);
var feature = g.selectAll('g').data(pointdata).enter().append('g');
feature.append('path')
.attr('class', 'cell')
.attr({
"d":function(d, i) { return "M" + polygons[i].join("L") + "Z"},
stroke:"#43676b",
fill: "rgba(255,140,10,0.3)"
});
map.on("viewreset", reset);
reset();
function reset() {
scale = Math.pow(2, map.getZoom() - map.options.zoom);
var padding = 25;
var bottomLeft = project(bounds[0]),
topRight = project(bounds[1]);
bottomLeft = [bottomLeft[0]-padding, bottomLeft[1]+padding]
topRight = [topRight[0]+padding, topRight[1]-padding]
console.log(polygons);
svg.attr("width", topRight[0] - bottomLeft[0]).attr("height", bottomLeft[1] - topRight[1]).style("margin-left", bottomLeft[0] + "px").style("margin-top", topRight[1] + "px");
g.attr("transform", "translate(" + -bottomLeft[0] + "," + -topRight[1] + ")");
feature.attr('d', path);
}
function project(x) {
var point = map.latLngToLayerPoint(new L.LatLng(x[1], x[0]));
return [point.x, point.y];
}

Related

Problem with select a rect element with d3

I have a problem with a panel development. The panel generates a bar graph for the current reading and draws lines for the lower and upper limits. This works great. Now I want to draw a sparkline to it. I first draw this sparkline in a separate div element below the actual graph.
In the graph I have two rect elements, one for the background and one for the actual bar. My goal now is to determine the size of the background rect element, then assign this size to my div for the sparkline and then place the sparkline div over the graph.
But the problem is that I can't access the rect element (it's just not found).
Hopefully my question is understandable. What am I doing wrong?
Here is the code snippet:
...
var panelID = "dd-multistat-panel-" + id;
var tooltipDivID = "dd-multistat-panel-tooltip-" + id;
var RectBackID = "dd-multistat-panel-back-" + id;
var RectBackClass = "dd-multistat-panel-back-" + id;
var RectBarID = "dd-multistat-panel-bar-" + id;
...
// draw the background
svg
.append("g")
//.attr("id", RectBackID)
//.attr("class", RectBackClass)
.selectAll("rect")
.data(stripedata)
.enter()
.append("rect")
.attr("id", RectBackID)
.attr("class", RectBackClass)
.attr("width", w)
.attr("height", stripeScale.step())
.attr("x", left)
.attr("y", function (d) {
return stripeScale(d);
})
.attr("fill", "rgba(0,0,0,0)")
.attr("stroke", OutlineColor)
.on("mouseover", function (d) {
if (showTooltips || Links.length /* && i < data.length*/)
tooltipShow(d);
})
.on("mouseleave", function () {
if (!isInTooltip) {
tooltipHide(false);
}
});
...
//my seach code
var BarClassID = "." + panelID;
var PanelBackID = "#" + RectBackID;
var PanelBackClass = "." + RectBackClass;
console.log("var findBar = d3.select(" + BarClassID + ")");
var findBar = d3.select(BarClassID);
console.log(findBar.nodes()); // --> finds 1 svg
console.log("var findRect = findBar.selectAll(" + PanelBackID + ")");
var findRect = findBar.selectAll(PanelBackID);
console.log(findRect.nodes()); // --> finds 0 elements
console.log("var findRect2 = d3.selectAll(" + PanelBackID + ")");
var findRect2 = d3.selectAll(PanelBackID);
console.log(findRect2.nodes()); // --> finds 0 elements
console.log("var findRect3 = d3.selectAll(" + PanelBackClass + ")");
var findRect3 = d3.selectAll(PanelBackClass);
console.log(findRect3.nodes()); // --> finds 0 elements
console.log("var findRect4 = d3.selectAll(svg)");
var findRect4 = d3.selectAll("svg");
console.log(findRect4.nodes()); // --> finds 55 svg
console.log("var findRect5 = d3.selectAll(g)");
var findRect5 = d3.selectAll("g");
console.log(findRect5.nodes()); // --> finds 0 elements
console.log("var findRect6 = d3.selectAll(rect)");
var findRect6 = d3.selectAll("rect");
console.log(findRect6.nodes()); // --> finds 0 elements
Instead of creating multiple svgs, you should create one svg and append your bar graph and spakrline to that.
In case you stick to same logic, try using d3-selection to select your rect.
import * as d3Select from 'd3-selection';
d3Select.select('#your-rect-id');
Here are some helpful links -
ngx-charts
Simple Bar Graph
Graph Gallery

avoid zoom transform from resetting after svg is clicked d3

I have a svg element ; the nodes, links, labels etc. are appended to it. I got the zoom-to-particular-node-by-name functionality running but the issue is after zooming automatically to the respective node , whenever I try to pan svg (by clicking and dragging it around), it resets the zoom and the coordinates to how it was before I zoomed to a particular node. I think it has to do with the way d3.event.transform works but I am not able to fix it. I want to be able to continue panning and zooming from the node I zoomed to without resetting any values.
(Also, from a bit of debugging , I observed that the cx and cy coordinates for the nodes did not change by zooming and panning from the code, but If I were to zoom and pan to a node manually , then it would. I guess that is the problem)
var svg1 = d3.select("svg");
var width = +screen.width;
var height = +screen.height - 500;
svg1.attr("width", width).attr("height", height);
var zoom = d3.zoom();
var svg = svg1
.call(
zoom.on("zoom", function() {
svg.attr("transform", d3.event.transform);
})
)
.on("dblclick.zoom", null)
.append("g");
function highlightNode() {
var userInput = document.getElementById("targetNode");
theNode = d3.select("#" + userInput.value);
const isEmpty = theNode.empty();
if (isEmpty) {
document.getElementById("output").innerHTML = "Given node doesn't exist";
} else {
document.getElementById("output").innerHTML = "";
}
svg
.transition()
.duration(750)
.attr(
"transform",
"translate(" +
-(theNode.attr("cx") - screen.width / 2) +
"," +
-(theNode.attr("cy") - screen.height / 4) +
")"
// This works correctly
);
}

D3 v4 Animation: How to loop over multiple circles over multiple paths?

Inspired by https://www.nytimes.com/interactive/2018/03/19/upshot/race-class-white-and-black-men.html
I am trying to create a animation that will have markers move from one point to multiple levels in another point in the y axis. Thanks to detailed pages by Amelia and Mike Bostock in both bl.ocks.org and stackoverflow. I have got so far to get the circles and animate it. But, I am not able to make each marker loop over by the pathlevel and do the transition
Pathlevel, here, indicates whether they are high, middle or low (1,2,3).
The entire code in using d3.v4 has been pasted below. what I am missing?
Thanks for your help.
<!DOCTYPE html>
<meta charset="utf-8">
<title>SANKEY Experiment</title>
<style>
</style>
<body>
<script type="text/javascript" src="d3.v4.js"></script>
<script>
//ref: very important for path animation: https://stackoverflow.com/questions/21226523/trace-path-with-dom-object/21228701#21228701
//ref: clustered force layout: https://bl.ocks.org/mbostock/7881887
//ref: data manipulation: http://learnjsdata.com/iterate_data.html
var margin = {top: 30, right: 10, bottom: 30, left: 20},
width = 500 - margin.left - margin.right,
height = 500 - margin.top - margin.bottom;
var series = [{x:5,y:10},{x:150,y:10}],
series2 = [{x:5,y:10},{x:50,y:15},{x:100,y:30},{x:150,y:30}],
series3 = [{x:5,y:10},{x:50,y:22},{x:100,y:50},{x:150,y:50}];
var data = [{"dim":"a","pos":"high","pathlevel":1,"x1":1,"y1":10,"x2":150,"y2":8},
{"dim":"b","pos":"high","pathlevel":1,"x1":1,"y1":10,"x2":150,"y2":8},
{"dim":"a","pos":"mid","pathlevel":2,"x1":1,"y1":10,"x2":150,"y2":28},
{"dim":"b","pos":"mid","pathlevel":2,"x1":1,"y1":10,"x2":150,"y2":28},
{"dim":"a","pos":"low","pathlevel":3,"x1":1,"y1":10,"x2":150,"y2":48},
{"dim":"b","pos":"low","pathlevel":3,"x1":1,"y1":10,"x2":150,"y2":48}]
var x = d3.scaleLinear()
.domain([5,150])
.range([0,width]);
var y = d3.scaleLinear()
.domain([10,50])
.range([0,height]);
var line = d3.line()
.curve(d3.curveCardinal)
.x(function(d) { return x(d.x); })
.y(function(d) { return y(d.y); });
var svg = d3.select("body").append("svg").attr("height",600).attr("width",600);
var chart = svg.append("g").attr("transform","translate("+margin.left+","+margin.top+")");
var update = function(series,k){
chart.append("path")
.attr("class","route"+k)
.attr("fill","none")
.attr("stroke","blue")
.attr("d",line(series));
}
update(series,1);
update(series2,2);
update(series3,3);
///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
//create transistions along the path //////////////////////////////////////////////////////////////////////////////////////////////////////////////
///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
var colorScale = d3.scaleOrdinal()
.domain(['a','b'])
.range(['orange','darkblue']);
//Get path start point for placing marker
var path = d3.select('.route1')
var startPoint = path.attr("d").split(",")[1];
//path example["M12.885906040268456", "84.48979591836735C12.885906040268456", "84.48979591836735", "241.07382550335572", "84.48979591836735",
//"318.9261744966443", "84.48979591836735C396.7785234899329", "84.48979591836735", "480", "84.48979591836735", "480", "84.48979591836735"]
//selecting class route which represents the path. d represents the path that is held by the path object. in that we split by comma and take the first
console.log(startPoint);
var glider = function(data,p){//p for path level
var marker = chart.selectAll(".marker")
.data(data)
.enter().append('circle')
.attr("class","marker")
.attr("fill",function(d){ return colorScale(d.dim);})
//.attr("x",function(d,i){return x(d.x1)+i*10;})
//.attr("y",function(d,i){return y(d.y1)+i*10;})
.attr("r",5);
//.attr("width",10)
//.attr("height",10);
var simulation = d3.forceSimulation()
.force("x",d3.forceX().strength(0.05))
.force("y",d3.forceY().strength(0.01))
.force('charge', d3.forceManyBody().strength(20))
.force("collide",d3.forceCollide(function(d){return y(d.y1)+4;}))
.alphaTarget(.03)
.restart();
simulation.nodes(data)
.on('tick',ticked);
function ticked(){
marker
.attr("cx",function(d){ return d.x;})
.attr("cy",function(d){ return d.y;})
}//end of ticked
//marker.transition().duration(3000).delay(200)
// .attr("x",function(d,i){return x(d.x2)+i*10;});
function translateAlong(path) {
var l = path.getTotalLength();
return function (d) {
return function (t) {
var p = path.getPointAtLength(t * l);
return "translate(" + p.x + "," + p.y + ")";//Move marker
}
}
}//end of translateAlong
console.log(marker);
function transition(){
var path2 = d3.select('.route'+p);
marker.attr("transform", "translate(" + startPoint + ")").transition().duration(3000).delay(function(d,i) { return i * 100; })
.attrTween("transform",translateAlong(path2.node()));
//.attr("x",function(d,i){return x(d.x2)+i*10;});
}//end of transition
transition();
}
/*var check = d3.map(data, function(d){return d.pathlevel;}).keys(); //for getting unique values from a column
check.forEach(function(i){
datapoints = data.filter(function(d){return d.pathlevel==i});
console.log(i);
glider(datapoints,i);
});*/
data1 = data.filter(function(d){return d.pathlevel==1});
data2 = data.filter(function(d){return d.pathlevel==2});
data3 = data.filter(function(d){return d.pathlevel==3});
//glider(data1,1);
//glider(data2,2);
glider(data3,3);
//glider(data,2);
//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
/*function createPathTween(d, i, a) {
var path = this.parentNode.getElementsByTagName("path")[1];
//i.e., go from this <circle> -> parent <g> -> array of child <path> elements
//-> first (and only) element in that array
//console.log(path);
var l = path.getTotalLength();
return function(t) {
var p = path.getPointAtLength(t * l);
console.log(p);
return "translate(" + p.x + "," + p.y + ")";
};
}//end of tweenpath*/
</script>
</body>

D3: keeping position of SVG's relative while panning and zooming

I'm trying to do a proof of concept of a SVG floorplan that pans and zooms and also have the ability to place markers on top. When zooming/panning happens the marker doesn't stay in position. I understand why this happens but not sure about the best way to keep the marker in position when panning/zooming.
Heres the code:
var svg = d3.select(".floorplan")
.attr("width", "100%")
.attr("height", "100%")
.call(d3.zoom().on("zoom", zoomed))
.select("g")
var marker = d3.selectAll(".marker")
.call(d3.drag()
.on("start", dragstarted)
.on("drag", dragged)
.on("end", dragended)
)
function zoomed() {
svg.attr("transform", d3.event.transform);
}
function dragstarted(d) {
console.log('dragstarted');
}
function dragged(d) {
var x = d3.event.x;
var y = d3.event.y;
d3.select(this).attr("transform", "translate(" + x + "," + y + ")");
}
function dragended(d) {
console.log('drag ended: marker:'+ d3.select(this).attr('data-id') + ' position: ' + d3.event.x +', ' + d3.event.y);
}
Theres also a codepen to visually see here: https://codepen.io/danielhoff/pen/WzQbRr
I have an additional constraints that the marker element shouldn't be contained inside the floorplan svg.
Here is a modified version of your codepen which fixes the movements of the marker during drag events while keeping the marker outside the floorplan svg container:
https://codepen.io/xavierguihot/pen/OvyRPY?editors=0010
To bring back into context, an easy solution would have been to include the marker element inside the floorplan container (in order for the marker to get the same zoom events as the floorplan), but here we want the marker to be in its own svg container.
And it is not trivial!
Appart from including ids in html tags (in order to select these elements from the html), only the javascript part has ben modified.
Let's dig a little bit on the steps necessary to get to this point:
First: Let's modify the zoomed function to apply to the marker as well:
Initially this was the zoom function:
function zoomed() {
svg.attr("transform", d3.event.transform);
}
And the modified version:
function zoomed() {
// Before zooming the floor, let's find the previous scale of the floor:
var curFloor = document.getElementById('floorplan');
var curFloorScale = 1;
if (curFloor.getAttribute("transform")) {
var curFloorTransf = getTransformation(curFloor.getAttribute("transform"));
curFloorScale = curFloorTransf.scaleX;
}
// Let's apply the zoom
svg.attr("transform", d3.event.transform);
// And let's now find the new scale of the floor:
var newFloorTransf = getTransformation(curFloor.getAttribute("transform"));
var newFloorScale = newFloorTransf.scaleX;
// This way we get the diff of scale applied to the floor, which we'll apply to the marker:
var dscale = newFloorScale - curFloorScale;
// Then let's find the current x, y coordinates of the marker:
var marker = document.getElementById('Layer_1');
var currentTransf = getTransformation(marker.getAttribute("transform"));
var currentx = currentTransf.translateX;
var currenty = currentTransf.translateY;
// And the position of the mouse:
var center = d3.mouse(marker);
// In order to find out the distance between the mouse and the marker:
// (43 is based on the size of the marker)
var dx = currentx - center[0] + 43;
var dy = currenty - center[1];
// Which allows us to find out the exact place of the new x, y coordinates of the marker after the zoom:
// 38.5 and 39.8 comes from the ratio between the size of the floor container and the marker container.
// "/2" comes (I think) from the fact that the floor container is initially translated at the center of the screen:
var newx = currentx + dx * dscale / (38.5/2);
var newy = currenty + dy * dscale / (39.8/2);
// And we can finally apply the translation/scale of the marker!:
d3.selectAll(".marker").attr("transform", "translate(" + newx + "," + newy + ") scale(" + d3.event.transform.k + ")");
}
This heavily uses the getTransformation function which allows to retrieve the current transform details of an element.
Then: But now, after having zoomed, when we drag the marker, it takes back its original size:
This means we have to tweak the marker's dragg function to keep its current scale when applying the drag transform:
Here was the initial drag function:
function dragged(d) {
var x = d3.event.x;
var y = d3.event.y;
d3.select(this).attr("transform", "translate(" + x + "," + y + ")");
}
And its modified version:
function draggedMarker(d) {
var x = d3.event.x;
var y = d3.event.y;
// As we want to keep the same current scale of the marker during the transform, let's find out the current scale of the marker:
var marker = document.getElementById('Layer_1');
var curScale = 1;
if (marker.getAttribute("transform")) {
curScale = getTransformation(marker.getAttribute("transform")).scaleX;
}
// We can thus apply the translate And keep the current scale:
d3.select(this).attr("transform", "translate(" + x + "," + y + "), scale(" + curScale + ")");
}
Finally: When dragging the floor we also have to drag the marker accordingly:
We thus have to override the default dragging of the floor in order to include the same dragg event to the marker.
Here is the drag function applied to the floor:
function draggedFloor(d) {
// Overriding the floor drag to do the exact same thing as the default drag behaviour^^:
var dx = d3.event.dx;
var dy = d3.event.dy;
var curFloor = document.getElementById('svg-floor');
var curScale = 1;
var curx = 0;
var cury = 0;
if (curFloor.getAttribute("transform")) {
curScale = getTransformation(curFloor.getAttribute("transform")).scaleX;
curx = getTransformation(curFloor.getAttribute("transform")).translateX;
cury = getTransformation(curFloor.getAttribute("transform")).translateY;
}
d3.select(this).attr("transform", "translate(" + (curx + dx) + "," + (cury + dy) + ")");
// We had to override the floor drag in order to include in the same method the drag of the marker:
var marker = document.getElementById('Layer_1');
var currentTransf = getTransformation(marker.getAttribute("transform"));
var currentx = currentTransf.translateX;
var currenty = currentTransf.translateY;
var currentScale = currentTransf.scaleX;
d3.selectAll(".marker").attr("transform", "translate(" + (currentx + dx) + "," + (currenty + dy) + ") scale(" + currentScale + ")");
}

d3.js limit panning in force layout

I am using d3.js with a force layout to visualize a large number of nodes. I would like to implement a limitation to the panning option of the zoom.
JSFiddle : https://jsfiddle.net/40z5tw8h/24/
The above fiddle contains a simple version of what I am working on.
Because I would potentially have to visualize a very large dataset, I use a function to scale down the group holding element ('g') after forces are done. In that way i always have the full visualization visible afterwards.
I would like to limit the panning - when the graph is fully visible, to only be able to move it within the viewport.
In case the layout is zoomed, I would like to limit the panning as follows:
The group holding element should not be able to go:
down more than 20 px from the top of the svg.
right more than 20 px from the left side of the svg.
up more than 20 px from the bottom of the svg.
left more than 20 px from the right side of the svg.
I think all the implementation should be within the zoom function, which for now is:
function zoomed(){
if (d3.event.sourceEvent == null){ //when fitFullGraph uses the zoom
g.attr("transform", "translate(" + d3.event.translate + ")scale(" + d3.event.scale + ")");
}
else{
var gElementBounds = g.node().getBoundingClientRect();
var g_bottom = gElementBounds.bottom;
var g_top = gElementBounds.top;
var g_left = gElementBounds.left;
var g_right = gElementBounds.right;
var g_height = gElementBounds.height;
var g_width = gElementBounds.width;
var svg = g.node().parentElement;
var svgElementBounds = svg.getBoundingClientRect();
var svg_bottom = svgElementBounds.bottom;
var svg_top = svgElementBounds.top;
var svg_left = svgElementBounds.left;
var svg_right = svgElementBounds.right;
var svg_height = svgElementBounds.height;
var svg_width = svgElementBounds.width;
var t = d3.event.translate;
var margin = 20;
if(d3.event.sourceEvent.type == 'wheel'){//event is zoom
g.attr("transform", "translate(" + d3.event.translate + ")scale(" + d3.event.scale + ")");
}
else{//event is pan
// if(t[0] < svg_left + margin) t[0]= svg_left + margin;
//else if(t[0] > svg_width-g_width - margin) t[0] = svg_width-g_width - margin;
// if(t[1] < g_height +margin) t[1] = g_height + margin;
//else if (t[1] > svg_height - margin) t[1] = svg_height - margin;
//.attr("transform", "translate(" + t+ ")scale(" + d3.event.scale + ")");
//3.event.translate = t;
g.attr("transform", "translate(" + d3.event.translate + ")scale(" + d3.event.scale + ")");
}
}
}
The limitations I tried to implement are commented out, because they do not work properly.
Does anyone have a solution?
This is not the complete answer to your question.
I used for block panning to left side translate X scale
var translate = d3.event.translate;
var translateX = translate[0];
var translateY = translate[1];
var scale = d3.event.scale;
var tX = translateX * scale;
var tY = translateY * scale;
console.log('tx', tX, 'ty', tY);
// Do not pan more to left
if (tX> 0) {
g.attr("transform", "translate(" + d3.event.translate + ") scale(" + d3.event.scale + ")");
} else {
translate[0] = 0;
g.attr("transform", "translate(" + translate + ") scale(" + d3.event.scale + ")");
}
Which cancels the translation to left but internally it continues. Your user probably stops dragging to the left. Panning to the right gets weird when starting to pan as internally the event has panned far to the left.

Categories

Resources