So I am using svg-pan-zoom to display a dynamically loaded SVG element. The code for loading the element is similar to the example here: https://ariutta.github.io/svg-pan-zoom/demo/dynamic-load.html (to see what I mean, view the source).
What I am trying to do is search the SVG document for text tags that match a specific query. I found an example here which seems like the solution to that part, but I can't find anything on how to access the SVG content inside svg-pan-zoom.
I'm afraid I don't have any code... I've been doing trial-and-error for quite a while now. Basically I'm just trying to figure out how to access the SVG content so I can search it.
Thanks!
I think this answer can be useful: Pan to specific X and Y coordinates and center image svg-pan-zoom
For example, let's suppose you want to look for a string that is contained inside a tspan (since you are not giving more details), then you can have a search box, and when it changes, the following function is called:
function searchTerm() {
let term = this.value;
var tspans = document.getElementsByTagName("tspan");
var found;
for (var i = 0; i < tspans.length; i++) {
if (tspans[i].innerHTML.includes(term)) {
found = tspans[i];
break;
}
}
let position = found.parentNode.parentNode.getAttribute("transform").split("translate(")[1].split(")")[0];
let posX = position.split(",")[0];
let posY = position.split(",")[1];
panZoom.zoom(1);
panZoom.pan({x:0,y:0});
var realZoom= panZoom.getSizes().realZoom;
panZoom.pan
({
x: -(posX*realZoom)+(panZoom.getSizes().width/2),
y: -(posY*realZoom)+(panZoom.getSizes().height/2)
});
}
As you can see, this code has been prepared for a specific situation, but you can get the position depending on your needs.
Related
I am pretty new to the whole basiljs world. So this might be a very basic question. I wasn't able to figure it out on my own however…Â
I am trying to create a simple script that swaps two Items that are selected on the same page.
I was able to get the image frame to swap, however it leaves the frames content in the same position. Here is waht it looks like:
#includepath "~/Documents/;%USERPROFILE%Documents";
#include "basiljs/bundle/basil.js";
function draw() {
var selItems = b.selections();
var selItems0x = b.itemX(selItems[0]);
var selItems1x = b.itemX(selItems[1]);
var selItems0y = b.itemY(selItems[0]);
var selItems1y = b.itemY(selItems[1]);
b.itemX(selItems[0], selItems1x);
b.itemX(selItems[1], selItems0x);
b.itemY(selItems[0], selItems1y);
b.itemY(selItems[1], selItems0y);
}
b.go();
Now my question is: How can I call on the frames content. Obviously I want that one the move the same with the frame.
Thanks for your help, I am eager to learn more!
Raphy
Even though it is not the "basiliy" way I suggest using InDesign build in functions. You can mix them with Basil Code. Basil doesn't care. There is the possibility to fit elements into its frame or center them.
Try this snippet:
#includepath "~/Documents/;%USERPROFILE%Documents";
#include "basiljs/bundle/basil.js";
function setup(){
var sel = b.selections();
var gb0 = sel[0].geometricBounds;
var gb1 = sel[1].geometricBounds;
// swap them
sel[0].geometricBounds = gb1;
sel[1].geometricBounds = gb0;
// see the different fit options
// http://yearbook.github.io/esdocs/#/InDesign/FitOptions
sel[0].fit(FitOptions.CENTER_CONTENT);
sel[0].fit(FitOptions.PROPORTIONALLY);
}
b.go();
The problem is that in InDesign a (image) graphic and its containing frame are treated as two separate objects, which means, if you only move the frame, the graphic in it does not move along.
In basil.js you have the method b.transformImage() to move a frame along with its graphic, but it is rather cumbersome to use, as you need to pass along the position as well as the scale of the image.
Alternatively you could move the graphic in a second step. Make sure first that the item actually contains a graphic (instead of being a simple oval etc.) and if that's the case move it to the same position as its parent frame. You can access a frames graphic by using frame.graphics[0].
The script would look like this then:
#includepath "~/Documents/;%USERPROFILE%Documents";
#include "basiljs/bundle/basil.js";
function draw() {
var selItems = b.selections();
var selItems0x = b.itemX(selItems[0]);
var selItems1x = b.itemX(selItems[1]);
var selItems0y = b.itemY(selItems[0]);
var selItems1y = b.itemY(selItems[1]);
b.itemX(selItems[0], selItems1x);
b.itemY(selItems[0], selItems1y);
if(selItems[0].graphics.length > 0) {
b.itemX(selItems[0].graphics[0], selItems1x);
b.itemY(selItems[0].graphics[0], selItems1y);
}
b.itemX(selItems[1], selItems0x);
b.itemY(selItems[1], selItems0y);
if(selItems[1].graphics.length > 0) {
b.itemX(selItems[1].graphics[0], selItems0x);
b.itemY(selItems[1].graphics[0], selItems0y);
}
}
b.go();
Note that this will not work if the top left corner of the image is cropped by the frame. In this case you would need to figure out where the top left corner of the graphic actually is and then offset the graphic accordingly after moving it.
Btw., the basil.js team is aware that the transforming of images is somewhat overly complicated and there is a plan to simplify this process in the future.
In the page I have time and right of it some pictures. I wanna to create a button to jump to current time using Tampermonkey. Current time is a text in <h1>. You can see a screenshot below.
How to jump to some position. For example 07:39.
If you want to suggest external library, please show me how to use it in Tampermonkey.
Finding H1 by XPath
If the document tree is stable (the order of elements is always the same) you can get the XPath and get the H1 element by xPath. That would be the fastest (in the mean of CPU time) solution.
Finding H1 with time in it
Otherwise, I'd use regular expression to detect time format in H1 element.
var headers = document.getElementsByTagName("h1");
//Regexp: Any amount of whitespace; One or two numbers; :; Two numbers; any ammount of whitespace
var check = /\s*[0-9]{1,2}:[0-9]{2}\s*/;
//Position
var pos = 0;
for(var i=0, l=headers.length;i<l;i++)
{
if(check.test(headers[i].textContent))
{
pos = headers[i].offsetTop;
//After finding the element, do not loop any more.
break;
}
}
//Scroll to pixel position [0; pos]
window.scrollTo(0,pos);
Getting specific time and finding it
If you want to jump to as specific time (eg. current time at any moment), go for the Date object. With that, you can do as follows:
var date = new Date();
var time_string = zeroFill(date.getHours())+":"+zeroFill(date.getMinutes());
//Procceed with the loop
Using the zeroFill function.
Seems my javascript experience grows :)
var myList = document.getElementsByTagName("h1");
var time = "07:39";
for(var i=0;i<myList.length;i++)
{
if(myList[i].textContent == time)
{
var pos = myList[i].offsetTop;
}
}
window.scrollTo(0,pos);
I'm using the SVG located at http://upload.wikimedia.org/wikipedia/commons/3/32/Blank_US_Map.svg in a project and interacting with it with d3.js. I'd like to create a click to zoom effect like http://bl.ocks.org/2206590, however that example relies on path data stored in a JSON object to calculate the centroid. Is there any way to load path data in d3 from an existing SVG to get the centroid?
My (hackish) attempt so far:
function get_centroid(sel){
var coords = d3.select(sel).attr('d');
coords = coords.replace(/ *[LC] */g,'],[').replace(/ *M */g,'[[[').replace(/ *z */g,']]]').replace(/ /g,'],[');
return d3.geo.path().centroid({
"type":"Feature",
"geometry":{"type":"Polygon","coordinates":JSON.parse(coords)}
});
}
This seems to work on some states, such as Missouri, but others like Washington fail because my SVG data parsing is so rudimentary. Does d3 support something like this natively?
The D3 functions all seem to assume you're starting with GeoJSON. However, I don't actually think you need the centroid for this - what you really need is the bounding box, and fortunately this is available directly from the SVG DOM interface:
function getBoundingBoxCenter (selection) {
// get the DOM element from a D3 selection
// you could also use "this" inside .each()
var element = selection.node();
// use the native SVG interface to get the bounding box
var bbox = element.getBBox();
// return the center of the bounding box
return [bbox.x + bbox.width/2, bbox.y + bbox.height/2];
}
This is actually slightly better than the true centroid for the purpose of zooming, as it avoids some projection issues you might otherwise run into.
The accepted answer was working great for me until I tested in Edge. I can't comment since I don't have enough karma or whatever but was using this solution and found an issue with Microsoft Edge, which does not use x or y, just top/left/bottom/right, etc.
So the above code should be:
function getBoundingBoxCenter (selection) {
// get the DOM element from a D3 selection
// you could also use "this" inside .each()
var element = selection.node();
// use the native SVG interface to get the bounding box
var bbox = element.getBBox();
// return the center of the bounding box
return [bbox.left + bbox.width/2, bbox.top + bbox.height/2];
}
From here
The solution is to use the .datum() method on the selection.
var element = d3.select("#element");
var centroid = path.centroid(element.datum());
I'm working with a database that has X and Y points per group, it's being used to draw outlines of images.
Right now in my web side this code is what I use to get the points:
var Drawing = $(XML).find('DrawingXML');
alert($(Drawing[1]).text());
Result:
<DrawingPoints>
<Point><X>1</X><Y>2</Y></Point>
<Point><X>2</X><Y>4</Y></Point>
<Point><X>3</X><Y>5</Y></Point>
<Point><X>2</X><Y>2</Y></Point>
<Point><X>0</X><Y>4</Y></Point>
</DrawingPoints>
Using the .replace() call only changes one item so it's usable for something like this:
.replace("</DrawingPoints>","");
but if I want to replace all 'Point' tags I'm out of luck.
My goal is to use the canvas feature to draw the points out so I want it to be parsed like this:
ctx.beginPath();
ctx.moveTo(1,2);
ctx.lineTo(2,4);
ctx.lineTo(3,5);
ctx.lineTo(2,2);
ctx.lineTo(0,4);
ctx.stroke();
I'm not going to use this with IE browsers just Safari/Chrome, if that helps out.
In this case you'll probably save an awful lot of brainache by using a library instead of writing your own code.
I reckon d3 does what you need:
d3.xml
d3.geo.path
Check out this question/answer. It's not Prototype specific and should help you here.
How to parse XML string with Prototype?
Get all your X and Y values at once:
var points = {};
points.X = Array();
points.Y = Array();
var ix = 0;
$(XML).find('DrawingXML DrawingPoints Point X').each(function()
{
points.X[ix++] = $(this).text();
});
$(XML).find('DrawingXML DrawingPoints Point Y').each(function()
{
points.Y[ix++] = $(this).text();
});
This might not be exact, I didn't test it and my Javascript is a bit rusty, but you get the idea.
I'm storing click coordinates in my db and then reloading them later and showing them on the site where the click happened, how do I make sure it loads in the same place?
Storing the click coordinates is obviously the simple step, but once I have them if the user comes back and their window is smaller or larger the coordinates are wrong. Am I going about this in the wrong way, should I also store an element id/dom reference or something of that nature.
Also, this script will be run over many different websites with more than one layout. Is there a way to do this where the layout is independent of how the coordinates are stored?
Yeah, there are many, many ways a page's layout can alter between loads. Different window sizes, different font sizes, different font availability, different browser/settings (even a small change in layout or font preference can throw out the wrapping). Storing page-relative co-ordinates is unlikely to be that useful unless your page is almost entirely fixed-size images.
You could try looking up the ancestors of the clicked element to find the nearest easily-identifiable one, then make a plot from that element down to the element you want based on which child number it is.
Example using simple XPath syntax:
document.onclick= function(event) {
if (event===undefined) event= window.event; // IE hack
var target= 'target' in event? event.target : event.srcElement; // another IE hack
var root= document.compatMode==='CSS1Compat'? document.documentElement : document.body;
var mxy= [event.clientX+root.scrollLeft, event.clientY+root.scrollTop];
var path= getPathTo(target);
var txy= getPageXY(target);
alert('Clicked element '+path+' offset '+(mxy[0]-txy[0])+', '+(mxy[1]-txy[1]));
}
function getPathTo(element) {
if (element.id!=='')
return 'id("'+element.id+'")';
if (element===document.body)
return element.tagName;
var ix= 0;
var siblings= element.parentNode.childNodes;
for (var i= 0; i<siblings.length; i++) {
var sibling= siblings[i];
if (sibling===element)
return getPathTo(element.parentNode)+'/'+element.tagName+'['+(ix+1)+']';
if (sibling.nodeType===1 && sibling.tagName===element.tagName)
ix++;
}
}
function getPageXY(element) {
var x= 0, y= 0;
while (element) {
x+= element.offsetLeft;
y+= element.offsetTop;
element= element.offsetParent;
}
return [x, y];
}
You can see it in action using this JSFiddle: http://jsfiddle.net/luisperezphd/L8pXL/
I prefer not using the id selector and just going recursive.
function getPathTo(element) {
if (element.tagName == 'HTML')
return '/HTML[1]';
if (element===document.body)
return '/HTML[1]/BODY[1]';
var ix= 0;
var siblings= element.parentNode.childNodes;
for (var i= 0; i<siblings.length; i++) {
var sibling= siblings[i];
if (sibling===element)
return getPathTo(element.parentNode)+'/'+element.tagName+'['+(ix+1)+']';
if (sibling.nodeType===1 && sibling.tagName===element.tagName)
ix++;
}
}
Your coordinates should be relative to the page contents, not the window. Use the upper-left of your HTML as the origin.
You will need to do a calculation to determine this at the time the data is recorded.
It probably depands on the meaning of the click. That is, are you concerned about which elements of the page that you user clicked on? If that is the case then I would store the coordinates relative to the element.
So the user clicked on element X. Do you need the precise location in element X? Then store that coordinate with the origin at top left of the element itself. This way, when the element moves relative to other content on the page then the position within the element remains valid.
I was hoping that someone had a much more brilliant solution to this problem, but my original thoughts must be the only way to effectively do this.
Each website must have a base setup described (e.g. [Centered layout, 960px] or [Fluid layout, Col1: 25%, Col2: 60%, Col3: 15%]
Click coordiantes must be recorded in relation to the screen:x/scroll:y along with screen coordinates.
On return the click coords will look at the stored layout, current screen size and calculate based on that.
I'm doing something similar here where I need to record where an element was drag and dropped on the page. I can store some data of the drop location in a database, in order to pull it out and place the element back where it was dropped. The only requirement is that I want the dropped element to be as close as possible to the element on which it was dropped, on all screen sizes.
Due to the responsive nature of the modern web, elements can move to completely different locations depending on screen size.
My solution is to ignore all DOM selectors, and instead simply record where the element is in the DOM tree by recording a child index on every 'layer' of the DOM, all the way down to to the element in question.
I do this by traversing up the DOM tree from the event.target element, looking at currentNode.parentNode.children to find which child index my currentNode inhabits. I record that in an array, which I can then use to index all the way back down the DOM tree to find my element. I also save the dropped elements offset as a percentage, in case the dropzone element has changed pixel size.
Here's my cut down code:
var rect = mouseEvent.target.getBoundingClientRect()
// get position of mouseEvent in target as a percentage so we can be ok if it changes size
var xpos = Math.floor(mouseEvent.offsetX / rect.width * 100)
var ypos = Math.floor(mouseEvent.offsetY / rect.height * 100)
// traverse backwards up the dom tree, recording this 'branch' of it as we go:
var curEl = mouseEvent.target
var tree = []
while(curEl.parentNode){
for( var i = 0; i < curEl.parentNode.children.length; i ++ ){
var curChild = curEl.parentNode.children[i]
if( curChild === curEl ){ // i is our child index
tree.unshift(i) // unshift to push to the front of the array
break
}
}
curEl = curEl.parentNode
}
And then in order to find my node again, I simply traverse back down the dom:
var curEl = document
for(var i = 0; i < tree.length; i ++){
curEl = curEl.children[tree[i]]
}
All I save to the database is the tree array (a flat array of integers - how can you get smaller?) and my x and y offsets!