Trying to create a styled tooltip for an inline SVG - javascript

New to Js I am building an interactive pronunciation guide through an inline svg.
You can view it here: https://codepen.io/r-pg/pen/OJgoXWQ
I am currently trying to add basic styled tooltips which will display an associated sentence. I have bound its coordinates relative to the cursor position.
The js I am using for this is here:
function tooldef() {
let t1 = document.querySelector('text[data-tooltip]');
let t2 = document.querySelector('text:nth-of-type(2)');
t1.addEventListener('onmousemove', showToolTip(evt));
t2.addEventListener('onmousemove', showToolTip(evt));
}
function showToolTip(evt) {
let t = evt.currentTarget;
let phrase = t.getAttribute('data-tooltip');
document.getElementById('tooltip').innerHTML = phrase;
tooltip.style.display = "block";
tooltip.style.left = evt.offsetX + 0 + 'px';
tooltip.style.top = evt.offsetY + 0 + 'px';
/* console.log(tooltip);*/
console.log(evt.offsetX);
console.log(evt.offsetY);
}
function hideToolTip(evt) {
tooltip.style.display = "none";
}
This functions perfectly fine when loaded in through codepen or locally however when I apply this to Wordpress it pushes the tooltip far to the right ad I cannot Identify why. Is there an issue with the code or is this an internal problem within WP?

Your code for positioning the tooltip assumes a couple of things:
That your SVG is at 100% scale.
That your SVG is at the top left of the page
If you are embedding the SVG inside other content, then one or both of those will not be true any more.
You are attaching the mousemove events to SVG <text> elements. So the coordinates returned in the event will be in SVG coordinate space. If the SVG is scaled to any size other than 1070 x 900, then your event coordinates won't match up with page coordinates.
If you are always displaying the SVG at 1070 x 900, then your fix might be as simple as just getting the page offset of the SVG, then add that X and Y position to the position returned in the event.
However if you are scaling the SVG, then you will need to convert the coordinates from SVG coordinates to page coordinates. There are many other questions on here about that topic. For example, this one: https://stackoverflow.com/a/48354404/1292848

Related

Zoom image given x and y

I have an image which as a "ruler" (made of basic divs positioned absolute on top of the image) that are use to measure the ends of the image. Now the idea is that if you long press one of the ruler ends (the dots at the end of the line which are draggable), the image in the background would zoom in that point, and follow the dot if the user moves it. I am able to detect the long press but I cannot get the image to zoom and follow the dot once detected. The code below is where I have done the detection and now I should apply the styling to move the image. I thought of using the transition property but couldn't get it to zoom on the dot. Any help is appreciated...
Here's a codesandbox with how the ruler works: Link
Meaningful code:
const x = get('x', varToUse); //This just gives the x coordinate of the ruler end
const y = get('y', varToUse); //This just gives the y coordinate of the ruler end
const image = ruler.current.parentElement.parentElement.childNodes[1].childNodes[1];
if (zoom) {
image.style.transform = `translate(${x * 2}px, ${y * 2}px) scale(2.0)`;
} else {
image.style.transform = `scale(1.0)`;
}
This is what the ruler looks like just to get an understanding:
You can make the image a div with background-image.
.image {
background-image: url({image_url});
}
so this way you can update the image size and position easily with this properties
.image {
background-size: x y;
background-position x y;
}
I think this way is easier to do the image resizing and zoom abilities.
another way is to use a canvas library that can help you a lot they have lots of built in functions.
I think trying it without library is better for now but as it grows try to move to a canvas library
The first reason is that in the code you provided, the DOM element that is being manipulated is a div id='root'. The image should be selected.

How to trace / track user input on touch screen to match image on screen

So I want to have an image, let's say a square and would want the user to 'trace' the image with their finger, I need to track this and know / understand if the user has done it correctly.
The use case is educational software where users trace shapes to learn how to draw them.
My thinking was an SVG object and then mouse hold events but because SVG has beginning and end points I am not sure if they could then be tracked all the way over the image etc.
Also how? If I have interaction on the SVG is it a matter of if statements and some kind of variance on the line and if user gets to far away from the original line then stop / break?
Sorry if this is explained badly also I couldn't find almost anything so I'm also sorry if this is a duplicate.
Found this article: https://www.smashingmagazine.com/2018/05/svg-interaction-pointer-events-property/
And: https://gist.github.com/elidupuis/11325438 / http://bl.ocks.org/elidupuis/11325438
So could maybe cobble something together but yes any direction would be appreciated.
var xmlns = "http://www.w3.org/2000/svg";
function setMouseCoordinates(event) {
// contains the size of element having id 'image svg' and its position relative to the viewport, svg width/height equal to image height and width
var boundary = document.getElementById('<id_of_image_svg>').getBoundingClientRect();
// sets the x position of the mouse co-ordinate
auth_mouseX = event.clientX - boundary.left;
// sets the y position of the mouse co-ordinate
auth_mouseY = event.clientY - boundary.top;
return [auth_mouseX, auth_mouseY];
}
// add this in mousedown or respective touch event
function drawPath(event) {
var {
auth_mouseX,
auth_mouseY
} = setMouseCoordinates(event);
// Creates an element with the specified namespace URI and qualified name.
scribble = document.createElementNS(xmlns, 'path');
// sets the stroke width and color of the drawing drawn by scribble drawing tool
scribble.style.stroke = 'red';
// sets the stroke width of the scribble drawing
scribble.style.strokeWidth = '2';
scribble.style.fill = 'none';
scribble.setAttributeNS(null, 'd', 'M' + auth_mouseX + ' ' + auth_mouseY);
}
Now you can add this function in the touchdown event and create logic accordingly. You have to check that the user has start or stopped the drawing by a varible and changing its value accordingly.
For evaluation of task whether it is in a given area or not you have to use some shape detection api or AI (You can search on google there are many of them like AWS rekognition and many others I don't remember the name)
Hope it helps anyhow. :)

How do I translate mouse movement distances to SVG coordinate space?

I have an SVG visualization of the distribution of CSS4 color keywords in HSL space here: https://meyerweb.com/eric/css/colors/hsl-dist.html
I recently added zooming via the mouse wheel, and panning via mouse clack-and-drag. I’m able to convert a point from screen space to SVG coordinate space using matrixTransform, .getScreenCTM(), and .inverse() thanks to example code I found online, but how do I convert mouse movements during dragging? Right now I’m just shifting the viewBox coordinates by the X and Y values from event, which means the image drag is faster than the mouse movement when zoomed in.
As an example, suppose I’m zoomed in on the image and am dragging to pan, and I jerk the mouse leftwards and slightly downwards. event.movementX returns -37 and event.movementY returns 6. How do I determine how far that equates to in SVG coordinates, so that the viewBox coordinates are shifted properly?
(Note: I’m aware that there are libraries for this sort of thing, but I’m intentionally writing vanilla JS code in order to learn more about both SVG and JS. So please, don’t post “lol just use library X” and leave it at that. Thanks!)
Edited to add: I was asked to post code. Posting the entire JS seems overlong, but this is the function that fires on mousemove events:
function dragger(event) {
var target = document.getElementById('color-wheel');
var coords = parseViewBox(target);
coords.x -= event.movementX;
coords.y -= event.movementY;
changeViewBox(target,coords);
}
If more is needed, then view source on the linked page; all the JS is at the top of the page. Nothing is external except for a file that just contains all the HSL values and color names for the visualization.
My recommendation:
Don't worry about the movementX/Y properties on the event.
Just worry about where the mouse started and where it is now.
(This has the additional benefit that you get the same result even if you miss some events: maybe because the mouse moved out of the window, or maybe because you want to group events so you only run the code once per animation frame.)
For where the mouse started, you measure that on the mousedown event.
Convert it to a position in the SVG coordinates, using the method you were using,
with .getScreenCTM().inverse() and .matrixTransform().
After this conversion, you don't care where on the screen this point is. You only care about where it is in the picture. That's the point in the picture that you're always going to move to be underneath the mouse.
On the mousemove events, you use that same conversion method to find out where the mouse currently is within the current SVG coordinate system. Then you figure out how far that is from the point (again, in SVG coordinates) that you want underneath the mouse. That's the amount that you use to transform the graphic. I've followed your example and am doing the transform by shifting the x and y parts of the viewBox:
function move(e) {
var targetPoint = svgCoords(event, svg);
shiftViewBox(anchorPoint.x - targetPoint.x,
anchorPoint.y - targetPoint.y);
}
You can also shift the graphic around with a transform on a group (<g> element) within the SVG; just be sure to use that same group element for the getScreenCTM() call that converts from the clientX/Y event coordinates.
Full demo for the drag to pan. I've skipped all your drawing code and the zooming effect.
But the zoom should still work, because the only position you're saving in global values is already converted into SVG coordinates.
var svg = document.querySelector("svg");
var anchorPoint;
function shiftViewBox(deltaX, deltaY) {
svg.viewBox.baseVal.x += deltaX;
svg.viewBox.baseVal.y += deltaY;
}
function svgCoords(event,elem) {
var ctm = elem.getScreenCTM();
var pt = svg.createSVGPoint();
// Note: rest of method could work with another element,
// if you don't want to listen to drags on the entire svg.
// But createSVGPoint only exists on <svg> elements.
pt.x = event.clientX;
pt.y = event.clientY;
return pt.matrixTransform(ctm.inverse());
}
svg.addEventListener("mousedown", function(e) {
anchorPoint = svgCoords(event, svg);
window.addEventListener("mousemove", move);
window.addEventListener("mouseup", cancelMove);
});
function cancelMove(e) {
window.removeEventListener("mousemove", move);
window.removeEventListener("mouseup", cancelMove);
anchorPoint = undefined;
}
function move(e) {
var targetPoint = svgCoords(event, svg);
shiftViewBox(anchorPoint.x - targetPoint.x,
anchorPoint.y - targetPoint.y);
}
body {
display: grid;
margin: 0;
min-height: 100vh;
}
svg {
margin: auto;
width: 70vmin;
height: 70vmin;
border: thin solid gray;
cursor: move;
}
<svg viewBox="-40 -40 80 80">
<polygon fill="skyBlue"
points="0 -40, 40 0, 0 40 -40 0" />
</svg>
So the script needs something so that the vectors moved by the SVG are coordinated against the vectors moved by the mouse on screen. Despite the event being on your target, your SVG, the MouseEvent properties relate to your screen alone.
The movementX read-only property of the MouseEvent interface provides the difference in the X coordinate of the mouse pointer between the given event and the previous mousemove event. In other words, the value of the property is computed like this: currentEvent.movementX = currentEvent.screenX - previousEvent.screenX.
From https://developer.mozilla.org/en-US/docs/Web/API/MouseEvent/movementX
The screenX read-only property of the MouseEvent interface provides the horizontal coordinate (offset) of the mouse pointer in global (screen) coordinates.
So what you're measuring, and to the best of my knowledge the only thing you can measure direcly without additional libraries or complication, is the movement of the pointer in pixel terms across the screen. The only way to make this work in terms of vector for movement of your SVG is to translate the on screen movement to the dimensions that are relevant to your scaled SVG.
My initial thinking was that you would be able to work out the scaling of the SVG object, using some combination of its viewbox and its actual width on the screen. Naturally what would initially appear sensible is not. This approach won't work, if it appears to it would be purely by chance.
But it turns out that the solution is essentially to use the same type of code you've used in your scaling when you approach your mouse movements. The .getScreenCTM() and .inverse() functions are exactly what you'll need again. But instead of trying to find a single point on the SVG to work from, you need to find out what the on-screen distance translates to in the SVG by comparing two points on the SVG instead.
What I provide here isn't necessarily the most optimal solution but hopefully helps explain and gives you something to work further from...
function dragger(event) {
var target = document.getElementById('color-wheel');
var coords = parseViewBox(target);
//Get an initial point in the SVG to start measuring from
var start_pt = target.createSVGPoint();
start_pt.x = 0;
start_pt.y = 0;
var svgcoord = start_pt.matrixTransform(target.getScreenCTM().inverse());
//Create a point within the same SVG that is equivalent to
//the px movement by the pointer
var comparison_pt = target.createSVGPoint();
comparison_pt.x = event.movementX;
comparison_pt.y = event.movementY;
var svgcoord_plus_movement = comparison_pt.matrixTransform(target.getScreenCTM().inverse());
//Use the two SVG points created from screen position values to determine
//the in-SVG distance to change coordinates
coords.x -= (svgcoord_plus_movement.x - svgcoord.x);
//Repeat the above, but for the Y axis
coords.y -= (svgcoord_plus_movement.y - svgcoord.y);
//Deliver the changes to the SVG to update the view
changeViewBox(target,coords);
}
Sorry for the long winded answer, but hopefully it explains it from the beginning enough that anyone else looking to find an answer can get the whole picture if they've not come as far as you have in this script.
From MouseEvent, we have clientX and movememntX. Taken together, we can deduce our last location. We can then take the transform of our current location and subtract it from the transform of our last location:
element.onpointermove = e => {
const { clientX, clientY, movementX, movementY } = e;
const DOM_pt = svg.createSVGPoint();
DOM_pt.x = clientX;
DOM_pt.y = clientY;
const { x, y } = DOM_pt.matrixTransform(svgs[i].getScreenCTM().inverse());
DOM_pt.x += movementX;
DOM_pt.y += movementY;
const { x: last_x, y: last_y } = DOM_pt.matrixTransform(svgs[i].getScreenCTM().inverse());
const dx = last_x - x;
const dy = last_y - y;
// TODO: use dx & dy
}

svg.js / svg-pan-zoom - doubleclick interactions

I am using Ariutta's svg-pan-zoom with svg.js. I have disabled the native Arriuta doubleclick functionality and have tried adding my own, which ultimately consists of a pan adjustment and an animation.
Usually this works fine, but sometimes when I load my page the doubleclick function acts strangely. According to my debugging, it looks like sometimes when my app loads, the doubleclick function I wrote is called twice for each doubleclick. This causes the animation to behave strangely, and there seems to be no consistent basis for when this issue arises. Restarting my server sometimes works, and sometimes doesn't. I pretty much just have to continue reloading my page until the issue goes away.
My initial thoughts are that maybe there is something off in the load order of my files, and sometimes things load out of order. Currently investigating this. My other thought was that maybe this has something to do with the svg.js animation library or my trying to replace the native double-click function in arriuta's plugin. Thoughts?
myApp.service('AnimatorService', function(){
this.dblClickBehavior = function(svgCanvas, zoom){
$('.node').dblclick(function(){
// get pan
pan = zoom.getPan();
// get screen width and height
var sizes = zoom.getSizes();
var centerX = (sizes.width)/2;
var centerY = (sizes.height)/2;
// get center position of svg object double clicked
var x0 = this.instance.first().attr('cx');
var y0 = this.instance.first().attr('cy');
//determine the correct pan value necessary to center the svg
panAdjustX = centerX - x0*sizes.realZoom;
panAdjustY = centerY - y0*sizes.realZoom;
//center the svg object by adjust pan
zoom.pan({'x' :centerX - x0*sizes.realZoom, 'y' : centerY - y0*sizes.realZoom});
//simple animation on the object
this.instance.first().animate().radius(centerX);
});
}
});
When it behaves correctly, the svg image centers and then grows. When it behaves incorrectly, it centers and then shrinks into nothingness.
You tagged svg.js so I will give an svg.js answer. There is a plugin svg.panZoom.js now which can be used to easily implement the functionality you want:
var canvas = SVG('container').viewbox(0,0,1000,1000)
canvas.image('https://www.zooroyal.de/magazin/wp-content/uploads/2017/05/cat-2783601_1920.jpg', 1000, 1000)
.attr('preserveAspectRatio', 'none')
canvas.on('dblclick', function(e) {
var p = this.point(e.pageX, e.pageY)
var zoomLvl = 3
// zoom into point p to zoomLvl
canvas.animate().zoom(zoomLvl, p)
})
Here is a fiddle: https://jsfiddle.net/Fuzzy/95t6oL8y/5/
When you want to be able to panZoom your svg, too. Just add a call to canvas.panZoom()

Trigger event from JavaScript

Let me try and be as much clear as possible although it won't be easy. OK, I have a circle inside div, and I know the x and y coordinates of the circle inside this div.
How could I create JavaScript or HTML tooltip to point at the spot where I clicked (in this case the circle)?
Here is the image so you can better understand what I'm trying to do:
Here is the picture
Now I know some of you will think: why not just use the id of circle and get the tooltip to point to that direction? Well because the circle is dynamically created as a JavaScript object and is not part of the HTML.
Because drawing saves time: where can I find a tooltip script pointing from above? Something like this, here goes another:
Screen shot
If you're wondering about positioning the tooltip, use styling. Give the parent element relative position and the child absolute position in a style sheet:
#canvas {
position: relative;
}
.tooltip {
position: absolute;
}
When you create the tooltip, give it the 'tooltip' class and set its style.left and style.top properties. Try something like:
function click(evt) {
if (! tooltip) {
tooltip = document.createElement('div'); // or what-have-you
tooltip.className += ' tooltip';
...
tooltip.style.left = evt.offsetX + 'px';
tooltip.style.left = evt.offsetY + 'px';
You can capture the mouse position: http://www.codelifter.com/main/javascript/capturemouseposition1.html
Get the x and y coordinates when the mouse is clicked. Position the tooltip[probably will be a div] at the specified x and y coordinates.
To get the coordinates see
event.clientX and event.clientY

Categories

Resources