I upload an SVG file to a canvas using FabricJS with the function
fabric.loadSVGFromURL (url, function(objects, options){
group = fabric.util.groupSVGElements(objects, options);
canvas.add(group).centerObject(group).renderAll();
});
This works perfectly. However the next step I want do is to ungroup the recently added group. The reason why I need to ungroup is that I want to be able to select the group's child elements by clicking on them since there is no access to these elements if they are grouped.
I found a snippet to perform an ungroup however when I do it with the group created width groupSVGElements the elements lose their original position scrambling the whole svg that I loaded.
Does anyone knows how to ungroup a loaded SVG and still keep the original positions of the elements?
You can still access each of the element using perPixelTargetFind
When set to true, objects are "found" on canvas on per-pixel basis rather than according to
bounding box.
I'm looking for the same solution. Did you find an answer so far?
Looking at the structure of an SVG element, I would image it should be possible to write a recursive method, which gives the children, the properties of the group and places them one level up. If you keep doing this, you should end up with all groups exploded and all properties intact (which are inherited otherwise).
Looking at SVG-EDIT, there is a function which should do this:
Function: ungroupSelectedElement
// Unwraps all the elements in a selected group (g) element. This requires
// significant recalculations to apply group's transforms, etc to its children
this.ungroupSelectedElement = function() {
var g = selectedElements[0];
if (!g) {
return;
}
if ($(g).data('gsvg') || $(g).data('symbol')) {
// Is svg, so actually convert to group
convertToGroup(g);
return;
}
if (g.tagName === 'use') {
// Somehow doesn't have data set, so retrieve
var symbol = svgedit.utilities.getElem(getHref(g).substr(1));
$(g).data('symbol', symbol).data('ref', symbol);
convertToGroup(g);
return;
}
var parents_a = $(g).parents('a');
if (parents_a.length) {
g = parents_a[0];
}
// Look for parent "a"
if (g.tagName === 'g' || g.tagName === 'a') {
var batchCmd = new svgedit.history.BatchCommand('Ungroup Elements');
var cmd = pushGroupProperties(g, true);
if (cmd) {batchCmd.addSubCommand(cmd);}
var parent = g.parentNode;
var anchor = g.nextSibling;
var children = new Array(g.childNodes.length);
var i = 0;
while (g.firstChild) {
var elem = g.firstChild;
var oldNextSibling = elem.nextSibling;
var oldParent = elem.parentNode;
// Remove child title elements
if (elem.tagName === 'title') {
var nextSibling = elem.nextSibling;
batchCmd.addSubCommand(new svgedit.history.RemoveElementCommand(elem, nextSibling, oldParent));
oldParent.removeChild(elem);
continue;
}
children[i++] = elem = parent.insertBefore(elem, anchor);
batchCmd.addSubCommand(new svgedit.history.MoveElementCommand(elem, oldNextSibling, oldParent));
}
// remove the group from the selection
clearSelection();
// delete the group element (but make undo-able)
var gNextSibling = g.nextSibling;
g = parent.removeChild(g);
batchCmd.addSubCommand(new svgedit.history.RemoveElementCommand(g, gNextSibling, parent));
if (!batchCmd.isEmpty()) {addCommandToHistory(batchCmd);}
// update selection
addToSelection(children);
}
};
See also:
https://code.google.com/p/svg-edit/source/browse/trunk/editor/svgcanvas.js
Related
I have a svg document - map of the world, which resides in its own file. I am getting some data from the database which I then loop thru add xlink to the appropriate countries which are appended to the end of the document. However when this happens, the appended xlinks are drawn over other nodes. For example the country of South Africa has Lesotho within its boundaries. South Africa gets a link and then ends up covering Lesotho. How do I make sure Lesotho is accessible or redrawn after the link is created? This is the code I am using.
window.onload = function() {
// Get the Object by ID
var a = document.getElementById("SVGWorldMap");
// Get the SVG document inside the Object tag
var svgDoc = a.contentDocument;
// Get one of the SVG items by ID;
var svgItem = svgDoc.getElementById("ocean");
// Set the colour to something else
svgItem.style.fill = "#5593BB";
for (let i = 0; i < Countries.Countries.length; i++) {
var x = svgDoc.getElementById(Countries.Countries[i].locCountry.toLowerCase());
var link = svgDoc.createElementNS("http://www.w3.org/2000/svg", "a");
link.setAttributeNS('http://www.w3.org/1999/xlink', 'xlink:href', "../Scripts/prodList.aspx?idLocation=" + Countries.Countries[i].idLocation);
link.setAttribute('target', '_top');
svgDoc.documentElement.appendChild(link);
x.style.fill = "#123456";
x.addEventListener('mouseover', mouseOverEffect);
x.addEventListener('mouseout', mouseOutEffect);
link.appendChild(x);
//console.log(Countries.Countries[i].locCountry.toLowerCase());
}
};
Try preserving the order of the elements by replacing each one with a link instead of adding links to the end in Countries.Countries order (assuming the links and paths(?) share a parent).
So instead of this:
svgDoc.documentElement.appendChild(link);
do this:
x.parentNode.replaceChild(link, x);
Background
I have a Google Apps Script that we use to parse the footnote content, wrapped in double parenthesis, in place of the footnote number superscript. The intended result should be:
Before Script
This is my footie index.1 1This is my
footie content with a link and emphasis.
After Script
This is my footie index. (( This is my footie content with a
link and emphasis.)
Problem
Everything works fine, except when I parse the footnotes in double parenthesis, they are losing all the links and formatting:
This is my footie index. (( This is my footie content with a
link and emphasis.)
If anyone can assist me with fixing the code below I would really appreciate the help :)
SOLUTION:
function convertFootNotes () {
var doc = DocumentApp.getActiveDocument()
var copy = generateCopy(doc) // make a copy to avoid damaging the original
var openCopy = doc; //DocumentApp.openById(copy.getId()) // you have to use the App API to copy, but the Doc API to manipulate
performConversion(openCopy); // perform formatting on the copy
}
function performConversion (docu) {
var footnotes = docu.getFootnotes(); // get the footnotes
footnotes.forEach(function (note) {
// Traverse the child elements to get to the `Text` object
// and make a deep copy
var paragraph = note.getParent(); // get the paragraph
var noteIndex = paragraph.getChildIndex(note); // get the footnote's "child index"
insertFootnote(note.getFootnoteContents(),true, paragraph, noteIndex);
note.removeFromParent();
})
}
function insertFootnote(note, recurse, paragraph, noteIndex){
var numC = note.getNumChildren(); //find the # of children
paragraph.insertText(noteIndex," ((");
noteIndex++;
for (var i=0; i<numC; i++){
var C = note.getChild(i).getChild(0).copy();
if (i==0){
var temp = C.getText();
var char1 = temp[0];
var char2 = temp[1];
if (C.getText()[0]==" "){
C = C.deleteText(0,0);
}
}
if (i>0){
paragraph.insertText(noteIndex,"\n");
noteIndex++;
}
paragraph.insertText(noteIndex,C);
noteIndex++;
} //end of looping through children
paragraph.insertText(noteIndex,"))");
}
function generateCopy (doc) {
var name = doc.getName() + ' #PARSED_COPY' // rename copy for easy visibility in Drive
var id = doc.getId()
return DriveApp.getFileById(id).makeCopy(name)
}
Were there any changes to the code other than the added )) to make it not work? Removing the (( & )) still did not have the formatting applied when testing it; getText() returns the element contents as a String, not a rich text object/element which contains the formatting info.
To get to the Text object:
getFootnoteContents().getChild(0) returns the FootnoteSection Paragraph
getChild(0).getChild(0) returns the Text object of that paragraph
copy() returns a detached deep copy of the text object to work with
Note: If there are other child elements in the FootnoteSection or in it's Paragraph child, you'll want to add some kind of type/index checking to get the correct one. However, with basic footnotes - as the above example - this is the correct path.
function performConversion (docu) {
var footnotes = docu.getFootnotes() // get the footnotes
var noteText = footnotes.map(function (note) {
// Traverse the child elements to get to the `Text` object
// and make a deep copy
var note_text_obj = note.getFootnoteContents().getChild(0).getChild(0).copy();
// Add the `((` & `))` to the start and end of the text object
note_text_obj.insertText(0, " ((");
note_text_obj.appendText(")) ");
return note_text_obj // reformat text with parens and save in array
})
...
}
Trying to change a value of an object only for the single instance.
Im trying to make a small library which can a few of the things that jQuery does, just in order to learn the tricks.
I currently have a problem with keeping my elements in the object for usage.
Working example:
Brain('#test').html('a'); // this will change the innerHTML of #test with "a".
Not working example:
var a = Brain('#test');
var b = Brain('#test2');
a.html('a'); // should change content of #test to "a", but changes content of #test2 to "a"
My JS:
// create the brain function to select elements
var Brain = function(selector, context)
{
if (typeof context == 'undefined')
var context = document;
Brain.elements = context.querySelector(selector);
return Brain;
};
// set and get innerHTML of element
Brain.html = function(content)
{
if (typeof content != 'undefined')
Brain.elements.innerHTML = content;
return Brain.elements.innerHTML;
};
// testing
var a = Brain('#test');
var b = Brain('#test2');
a.html('a');
b.html('b');
My HTML
<p id="test">test</p>
<p id="test2">test2</p>
jsfiddle example
Hi i Just gone through your code. the loop is rotating twice and ends at #test2 id. that means Brain contains element of #test2 that's why second element is changing.
Change code as below it is working
var a = Brain('#test').html('a');
var b = Brain('#test2').html('b');
Happy codeing
Lets say I have this arrays:
likes = [4,6,42,72,7,4,42,56,6,24];
time = [1,2,3,4,5,6,7,8,9,10];
And when clicking in one of the bar charts I have, I'm forwarded to the current time, according to the array likes position.
this.click(function () {
var pos = result_likes.indexOf(this.bar.value);
var pos2 = result_time[pos];
setCurTime(pos2);
});
However when I have the same likes values, for instance number 4, there will be two positions of it, and the SetCurTime, will only forward me to the first time when "like=4 appears (timecode[0];)
How can I solve this and be forward to the right time position?
You need to embed meta data in the bar elements to solve this problem. For instance, you can use custom data attributes like such:
<div class="bar" data-index"5">4</div>
You would dynamically generate this of course, to create each bar with the proper index and value.
Then in your click handler would be:
this.click(function () {
var pos = this.bar.dataset.index;
var pos2 = result_time[pos];
setCurTime(pos2);
});
In light of you using the gRaphael library, perhaps you could crawl the bar elements to see which element it is within the chart, like this:
this.click(function(evt) {
var pos = 0;
var child = evt.currentTarget;
var type = child.nodeName;
while( (child = child.previousSibling) != null && type === child.nodeName)
pos++;
var pos2 = result_time[pos];
setCurTime(pos2);
});
Reposting comment here for any others who need a simliar solution:
var pos2 = result_time[this.bar.id];
I have the following code that puts bold style some keywords in a whole google document:
function boldKeywords() {
// Words that will be put in bold:
var keywords = ["end", "proc", "fun"];
var document = DocumentApp.getActiveDocument();
var body = document.getBody();
var Style = {};
Style[DocumentApp.Attribute.BOLD] = true;
for (j in keywords) {
var found = body.findText(keywords[j]);
while(found != null) {
var foundText = found.getElement().asText();
var start = found.getStartOffset();
var end = found.getEndOffsetInclusive();
foundText.setAttributes(start, end, Style)
found = body.findText(keywords[j], found);
}
}
}
But I would like the code to put the keywords in bold only in the selected area of the document, for doing that, I tried using the function getSelection(), but I have the problem that this function returns a Range, but for applying findText I need a Body, somebody knows what could I do?
Modified Script
function boldKeywordsInSelection() {
const keywords = ["end", "proc", "fun"];
const document = DocumentApp.getActiveDocument();
const selection = document.getSelection();
// get a list of all the different range elements
const rangeElements = selection.getRangeElements();
const Style = {};
Style[DocumentApp.Attribute.BOLD] = true;
// forEach used here because for in was giving me trouble...
rangeElements.forEach(rangeElement => {
// Each range element has a corresponding element (e.g. paragraph)
const parentElement = rangeElement.getElement();
// fixing the limits of the bold operations depending on the selection
const startLimit = rangeElement.getStartOffset();
const endLimit = rangeElement.getEndOffsetInclusive();
for (j in keywords) {
let found = parentElement.findText(keywords[j]);
// wrapping in try catch to escape the for loop from within the while loop
try {
while (found != null) {
const foundText = found.getElement().asText();
const start = found.getStartOffset();
// Checking if the start of the word is after the start of the selection
if (start < startLimit) {
// If so, then skip to next word
found = parentElement.findText(keywords[j], found);
continue;
}
// Checking if the start of the word is after the end of the selection
// if so, go to next element
if (start > endLimit) throw "out of selection";
const end = found.getEndOffsetInclusive();
foundText.setAttributes(start, end, Style)
found = parentElement.findText(keywords[j], found);
}
} catch (e) {
Logger.log(e)
continue;
}
}
})
}
See the comments in the code for more details.
A getSelection produces a Range object, which contains within it various instances of RangeElement. Each RangeElement makes reference to a parent element, and the index positions within that parent. The parent is the element that the range element is a part of. For example:
This selection spans 3 elements. Therefore the selection has 3 range elements. You can only use the findText method on the whole element, not the range element.
This means that the flow of the script is generally the same, except that you need to go through each element and find the text within each element. Since this will return elements that are outside the selection, you need to keep track of the index positions of the selection and the found element and make sure the found element is within the selection.
References
Range
RangeElement
getSelection()