Get translated SVG Point with svgpanzoom - javascript

In trying to get the SVG point from the mouse in my Blazor application, im doing this in razor page
public async Task SvgElementOnMouseMove(MouseEventArgs e)
{
var point = jsInProcess3.Invoke<string>("getSVG_XY", refSvgElement, e.ClientX, e.ClientY);
}
And in the javascript
window.getSVG_XY = (svg, x, y) => {
var pt = svg.createSVGPoint();
pt.x = x;
pt.y = y;
pt = pt.matrixTransform(svg.getScreenCTM().inverse());
return pt.x + " " + pt.y;
}
but using svg-pan-zoom library, im having trouble getting the correct SVG point, I have tried different variations of
var pan = window.zoomCanvas.getPan();
var zoom = window.zoomCanvas.getZoom();
var matrix = window.zoomCanvas.viewport.getCTM();
svgPoint = svgPoint.matrixTransform(window.CurrentCTM.inverse());
window.zoomCanvas.onUpdatedCTM: function (ctm) { window.CurrentCTM = ctm; }
but the point is always way out of bounds, how can I get get correct point when user is panning and zooming? My SVG has a viewport set and works fine with pan and zoom just returning incorrect point
I have also tried this, and it's alot closer but still off by some margin (getting CTM of viewport group)
var mainGroup = svg.getElementById("viewport");
pt = pt.matrixTransform(mainGroup.getCTM().inverse());

Related

Problems fitting a map in a container

In a previous question, a user informed me of a great function to center a map and adapt its size to the container.
"There is this nice gist from nrabinowitz, which provides a function which scales and translate a projection to fit a given box.
It goes through each of the geodata points (data parameter), projects it (projection parameter), and incrementally update the necessary scale and translation to fit all points in the container (box parameter) while maximizing the scale:
function fitProjection(projection, data, box, center) {
...
return projection.scale(scale).translate([transX, transY])
}
I love this function but for now I would not mind using something that solves my problem. This works for any map, but specifically for the one in Colombia it does not work for me.
I'm trying to center the map to the container so that it fits the center and the size is the right one to the container. but I can not get it to adapt. I have also tried with a .translate and it does not work for me. Is something wrong?
Here is my code:
function fitProjection(projection, data, box, center) {
// get the bounding box for the data - might be more efficient approaches
var left = Infinity,
bottom = -Infinity,
right = -Infinity,
top = Infinity;
// reset projection
projection
.scale(1)
.translate([0, 0]);
data.features.forEach(function(feature) {
d3.geo.bounds(feature).forEach(function(coords) {
coords = projection(coords);
var x = coords[0],
y = coords[1];
if (x < left) left = x;
if (x > right) right = x;
if (y > bottom) bottom = y;
if (y < top) top = y;
});
});
// project the bounding box, find aspect ratio
function width(bb) {
return (bb[1][0] - bb[0][0])
}
function height(bb) {
return (bb[1][1] - bb[0][1]);
}
function aspect(bb) {
return width(bb) / height(bb);
}
var startbox = [[left, top], [right, bottom]],
a1 = aspect(startbox),
a2 = aspect(box),
widthDetermined = a1 > a2,
scale = widthDetermined ?
// scale determined by width
width(box) / width(startbox) :
// scale determined by height
height(box) / height(startbox),
// set x translation
transX = box[0][0] - startbox[0][0] * scale,
// set y translation
transY = box[0][1] - startbox[0][1] * scale;
// center if requested
if (center) {
if (widthDetermined) {
transY = transY - (transY + startbox[1][1] * scale - box[1][1])/2;
} else {
transX = transX - (transX + startbox[1][0] * scale - box[1][0])/2;
}
}
return projection.scale(scale).translate([transX, transY])
}
var width = document.getElementById('statesvg').offsetWidth;
var height =document.getElementById('statesvg').offsetHeight;
/*// Define path generator
var path = d3.geo.path() // path generator that will convert GeoJSON to SVG paths
.projection(projection); // tell path generator to use albersUsa projection
*/
//remove svg
d3.select("#statesvg svg").remove();
var svg = d3.select("#statesvg")
.append("svg")
.attr("width", width+"px")
.attr("height", height+"px");
d3.json("https://rawgit.com/john-guerra/43c7656821069d00dcbc/raw/be6a6e239cd5b5b803c6e7c2ec405b793a9064dd/Colombia.geo.json", function(data) {
var features = data.features;
var projection=fitProjection(d3.geo.mercator(), data, [[0, 0], [width, height]], true)
var path = d3.geo.path()
.projection(projection);
svg.selectAll('path')
.data(features)
.enter().append('path')
.classed('map-layer', true)
.attr('d', path)
.attr('vector-effect', 'non-scaling-stroke')
});
http://plnkr.co/edit/JWL6L7NnhOpwkJeTfO6h?p=preview
You said that the function...
works for any map, but specifically for the one in Colombia it does not work for me.
This makes no sense: what makes you think that the function has personal issues with Colombia?
The problem is just those islands at the top left corner, the Archipelago of San Andrés, Providencia and Santa Catalina. Let's remove them:
data.features = data.features.filter(function(d){
return d.properties.DPTO !== "88"
});
Here is the result in my browser:
Here is your updated Plunker: http://plnkr.co/edit/1G0xY7CCCoJv070pdcx4?p=preview

d3.js - scaling canvas points to overlay on svg map during zoom

I am rendering a raster map using d3.geo.tile, like in this example here (the raster images don't work on that page, so I cloned it over here). Unlike this example I have thousands of points to show, so I am rendering them using HTML5 Canvas instead of SVG. I positioned the canvas directly over the map.
The points are rendering correctly, and I am able to pan the map. However, if I zoom in the points are not translated to the proper coordinates.
Here is the function that draws these points:
function set_scales() {
var translate = zoom.sub_regions.translate(),
scale = zoom.sub_regions.scale(),
width = sub_region.get('width'),
height = sub_region.get('height');
var x1 = -translate[0]+width/2;
var y1 = -translate[1]+height/2;
var x2 = width + x1;
var y2 = height + y1;
var x = d3.scale.linear().domain([x1, x2]).range([0, width]);
var y = d3.scale.linear().domain([y1, y2]).range([0, height ]);
draw_canvas();
function draw_canvas() {
sub_region.var.context.clearRect(0, 0, width, height);
var data = sub_region.get('points');
if (!data) {
return;
}
var i = -1, n = data.length, d, cx, cy;
var canvas = sub_region.get('context');
canvas.fillStyle = '#0A00FF';
canvas.beginPath();
while (++i < n) {
d = data[i];
cx = x(d[0]);
cy = y(d[1]);
canvas.moveTo(cx, cy);
canvas.arc(cx, cy, 1, 0, 2 * Math.PI);
}
canvas.fill()
}
}
I know that I should be using the scale variable in there somehow, but I can't figure out how. The scale at which the points are positioned properly is 4096. I tried making a variable called zoom_factor setting it to 4096/scale, and multiplying the x1, x2, y1, and y2 coordinates by it, but that didn't work. Perhaps I didn't do it correctly.
I took some screenshots:
This is before the zoom (points render correctly):
This is after the zoom (points translated to incorrect coordinates):
If you want to see it in all its broken glory go here and click on the United States, then select "Stations."
I could really use some help on this one; been running in circles for days!

Why are the click coordinates not what I expect?

Why when I click at the beginning or end of a Raphael path element are the coordinates not what I expect? See example (http://jsfiddle.net/gharabed/prh2J/1/)
In the example, I have a path drawn from (10,10) to (100,100). If I click at the very tip of the path near 100,100, I get a click event coordinate of something like (107,108). I can't be more than 4 or 5 pixels away from the end of the line. In fact it looks like I am only 2 (maybe 3 at the most) pixels away. It seems like the coordinates are a little off. Am I not considering something here?
function sendAlert(e) {
alert("Clicked at: " + e.x + "," + e.y + " I would expect it to be near the coordinates in the path defined (10,10) or (100,100)");
}
var paper = Raphael(document.getElementById('myid'),200,200);
var line = paper.path('M10,10L100,100');
line.attr({"stroke":"red","stroke-width":4});
line.click(sendAlert);
alert("Click as close as you can to the end of either end of the line segment.")
i agree what Vasil has said. i have made a small utility function which will gives you exact coordinates. just pass the mousedown event to this function. ( call this function in your sendalert function)
(i am using javascript).
var mainsvg = document.getElementsByTagName('svg')[0];
function getSvgCordinates(event) {
var m = mainsvg.getScreenCTM();
var p = mainsvg.createSVGPoint();
var x, y;
x = event.pageX;
y = event.pageY;
p.x = x;
p.y = y;
p = p.matrixTransform(m.inverse());
x = p.x;
y = p.y;
x = parseFloat(x.toFixed(3));
y = parseFloat(y.toFixed(3));
return {x: x, y: y};
}

Why is Raphael.js animating to incorrect coordinates?

I'm trying to do a simple animation in Raphael.js in which a paper.text object is moved from its current position to another position. Here is some of my code (there is far too much to post all of it):
songPos = getSongPosition(this, charIndex);
letter.path.animate({x: songPos.x, y: songPos.y, "font-size": this.correctFontSize}, 500, onAnimationComplete);
Here is the Letter object being referenced in the above code:
function Letter(args)
{
this.letter = args.letter || "A";
this.correct = args.correct || false;
this.transformation = args.transformation || "";
this.initX = args.x || 0;
this.initY = args.y || 0;
this.path = null;
}
Letter.prototype.buildPath = function()
{
this.path = paper.text(this.initX, this.initY, this.letter);
if(this.transformation)
{
this.path.transform(this.transformation);
}
return this;
};
The problem is I'm printing the x and y values returned by getSongPosition, and they're correct, but the animate method is sending my text object somewhere off-screen. I've also tried just setting the animation attributes to {x: 0, y: 0}, but I still get the same results. I can post more of the code if it is necessary.
Any help would be greatly appreciated.
UPDATE 1:
Part of my program requires I be able move objects to specific coordinates. Some of the objects will be rotated and others will not, so I wrote this:
Letter.prototype.getMoveTransform = function(x, y)
{
var bbox = this.path.getBBox(true);
var dx = x - bbox.x;
var dy = y - bbox.y;
return "T" + dx + "," + dy + (this.transformation == null ? "" : this.transformation);
};
I believe this is the root of my problem. This function is supposed to generate the transformation required to move a rotated object to (x, y). I'm not sure why I have to re-rotate the object on every animation.
UPDATE 2:
I have somehow solved my problem. I would post my solution, but I don't understand why any of this works/didn't work in the first place anymore.
this.path.getBBox(true); should be this.path.getBBox() or this.path.getBBox(false);
you need get transformed position every time to calculate the dx and dy

transform matrix

I have an svg map with a g element container.
inside the g element I have items with x, y positions.
I am trying to implement a mouse wheel zoom that pans the g element so that the object under the mouse is always under the mouse. similar to the way Google maps pans the map when zooming via the mouse wheel so that you zoom to the mouse position.
I have exhausted all searches and tried many different ways to calculate out the mouse position verses the g element position.
I've tried:
var xPan = (mouse.x - (matrix.scale * mouse.x)) - matrix.panX;
var yPan = (mouse.y - (matrix.scale * mouse.y)) - matrix.panY;
pan(xPan, yPan);
I had an similar problem some time ago, with the difference that I am using canvas but because I use svg to save my transform matrix it may help you, if I post the necessary part of my code:
window.transform = svg.createSVGMatrix();
window.pt = svg.createSVGPoint();
transformedPoint = function (x, y) {
window.pt.x = x; window.pt.y = y;
return pt.matrixTransform(window.transform.inverse());
}
translate = function(dx, dy) {
window.transform = window.transform.translate(dx, dy);
}
scale = function (scaleX, scaleY) {
window.transform = window.transform.scaleNonUniform(scaleX, scaleY);
};
zoom = function (scaleX, scaleY, x, y) { //use real x and y i.e. mouseposition on element
var p = transformedPoint(x, y);
translate(x, y);
scale(scaleX, scaleY);
translate(-x, -y);
}
I hope you can use some of this code and get it to work for you
Credits going to Phrogz and his outstanding example here: https://stackoverflow.com/a/5527449/1293849

Categories

Resources