Working with SVG polygon elements - javascript

I'm trying to work with an SVG polygon and javascript. I create a polygon and set its initial point list like this:
var polygon = document.createElementNS('http://www.w3.org/2000/svg','polygon');
polygon.setAttribute("points", "0,0 100,100 200,200");
now what do I do if I want to modify the 2nd point (100,100)? Right now I'm basically reconstructing the whole string again. But can we address "polygon.points" as an array somehow, or is it really just a plain simple string? This can work ok for very simple polygons, but if my polygon eventually has hundreds of point pairs, I'd hate to reconstruct the entire "points" attribute as a string every time I want to modify a single element.
Thanks

You can access the individual point values using the SVG DOM:
var p = polygon.points.getItem(1);
p.x = 150;
p.y = 300;
(Assuming that your UA implements this interface.) See SVGPolygonElement, SVGAnimatedPoints, SVGPointList and SVGPoint.
I find though that using these SVG DOM interfaces (at least for me in Batik, in which I do most of my SVG stuff) is often not faster than just updating the attribute with string manipulation.

No way around it I'm afraid. You have to reconstruct the string again. But it's not difficult to wrap the whole thing in an object, something like:
function Polygon () {
var pointList = [];
this.node = document.createElementNS('http://www.w3.org/2000/svg','polygon');
function build (arg) {
var res = [];
for (var i=0,l=arg.length;i<l;i++) {
res.push(arg[i].join(','));
}
return res.join(' ');
}
this.attribute = function (key,val) {
if (val === undefined) return node.getAttribute(key);
node.setAttribute(key,val);
}
this.getPoint = function (i) {return pointList[i]}
this.setPoint = function (i,x,y) {
pointList[i] = [x,y];
this.attribute('points',build(pointList));
}
this.points = function () {
for (var i=0,l=arguments.length;i<l;i+=2) {
pointList.push([arguments[i],arguments[i+1]]);
}
this.attribute('points',build(pointList));
}
// initialize 'points':
this.points.apply(this,arguments);
}
var polygon = new Polygon(0,0, 100,100, 200,200);
polygon.setPoint(0, 50,10); // set point and automatically re-build points
polygon.points(50,50, 50,100, 200,100); // set everything
polygon.node; // refer to the actual SVG element
* not the best implementation but you get the idea.

You need to use setAttributeNS. You'll probably want to cache that namespace in a variable somewhere so you don't have to keep typing it.

You need to set all points at once, the performance is pretty solid, what you may want to do is manage the array outside and merge it on the setAttribute calls

Related

Split single line string into multiline string in JavaScript/p5.js

I have a .csv file that I'm calling in JavaScript through a p5.js sketch. One of the fields contains sentences that range from 103 char to 328 char. My script calls the data and displays in randomly on the canvas. Because some of the sentences are very long, they aren't fitting on the canvas properly, so I'd like to split them into 2- or 3-line strings.
I've read up on Template Literals and RegExp in the JavaScript documentation, but all of the examples use string variables written out as a variable. So, for example, something like this in the case of my data:
var myString = `We can lift up people and places who've been left out,
from our inner cities to Appalachia,
in every manufacturing town hollowed out when the factory closed,
every community scarred by substance abuse,
every home where a child goes to bed hungry.`
That Template Literal would print to the canvas as a multiline object. But what I need to do is have JavaScript create a multiline object from the statements array in my data.
I have a constructor and a prototype that format the color, size, x/y placement, and motion of the sentences.
// Function to align statements, categories, and polarity
function Statement(category, polarity, statement) {
this.category = category;
this.statement = statement;
this.polarity = polarity;
this.x = random(width/2);
this.y = random(height);
this.dx = random(-speed, speed);
this.dy = random(-speed, speed);
}
// Attach pseudo-class methods to prototype;
// Maps polarity to color and x,y to random placement on canvas
Statement.prototype.display = function() {
this.x += this.dx;
this.y += this.dy;
if(this.x > width+10){
this.x = -10
}
if(this.y > height+10) {
this.y = -10
}
if(this.polarity == -1){
fill(205, 38, 38);
}
else if(this.polarity == 1){
fill(0, 145, 205);
}
else{
fill(148, 0, 211);
}
textSize(14);
text(this.statement, this.x, this.y);
}
So I suppose what I'm wondering is whether I need to create a RegExp, like String.split("[\\r\\n]+") and add \r\n into the data, and if so, where would I put it in my script. I tried in in the Statement.display.prototype, but it just seemed to break the whole script as the statements wouldn't load.
EDIT: I am adding this edit with some trepidation, as I got nailed for not producing a Minimal, Complete, Verifiable example, with "minimal" being the part I got nailed on. That said, here is the top part of my code.
var clContext;
var speed = 0.8;
var statements = [];
var category = [];
var canvas;
//load the table of Clinton's words and frequencies
function preload() {
clContext = loadTable("cl_context_rev.csv", "header");
}
function setup() {
canvas = createCanvas(680, 420);
canvas.mousePressed(inWidth);
background(51);
// Calling noStroke once here to avoid unecessary repeated function calls
noStroke();
// iterate over the table rows
for (var i = 0; i < clContext.getRowCount(); i++) {
var category = clContext.get(i, "category");
var statement = clContext.get(i, "statement");
var polarity = clContext.get(i, "polarity");
statements[i] = new Statement(category, polarity, statement);
}
}
function draw() {
if (mouseIsPressed) {
background(51);
for (var i = 0; i < statements.length; i++) {
statements[i].display();
}
}
}
I've added that only to provide context for the data type I'm trying to split. There seems to be two points at which I could do the split: the statement array created in setup, or the statements array from the constructor. Meaning that if I go into my data file and add \n where I want to split, which is easy enough as there are only 20 statements, how and where is it best to construct a RegExp that will split those lines?
I dunno if I understand exactly that you want, but you can use this to get an array from template
var myString = `We can lift up people and places who've been left out,
from our inner cities to Appalachia,
in every manufacturing town hollowed out when the factory closed,
every community scarred by substance abuse,
every home where a child goes to bed hungry.`
var array = myString.replace(/,/gi, "").split("\n").map(x => x.trim());
console.log(array);
Basically I removed all the commas of your example with replace(/,/gi, ""), then split for \n, and finally trim it.
I think the other answer overthinks it a bit.
P5.js already has a handy split() function that you should be using. You can read about it in the reference here.
But basically, all you really need to do is change your statement variable into an array instead of a single string value.
function Statement(category, polarity, statement) {
this.statement = split(statement, "//");
//rest of your code
This code assumes that you've inserted a // into your .csv file wherever you want a line break. You could use any delimiter you want though.
You would then have to modify your display() function to use the statement variable as an array instead of a single value. How you do that is up to you, but the basic syntax would look like this:
text(this.statement[0], this.x, this.y);
text(this.statement[1], this.x, this.y+25);

How to avoid using filter twice against the same set of elements

I'm trying to separate two types of inputs into their own jQuery wrapped sets as they need to be processed differently depending on whether the id contain '-add-new-' or not. I know I could do this using filter twice as follows:
var seriesTabInputs = $msSeriesTabs.find('input').filter(function() {
return $(this).attr('id').indexOf('-add-new-') == -1;
});
var addNewTabInputs = $msSeriesTabs.find('input').filter(function() {
return $(this).attr('id').indexOf('-add-new-') >= 0;
});
However filtering twice seems inefficient to me as I know it will require a second loop. Is there a way to avoid this?
Try like below:
var addNewTabInputs = $msSeriesTabs.find('input[id*="-add-new-"]');
var seriesTabInputs = $msSeriesTabs.find('input[id]:not([id*="-add-new-"])');
OR
var addNewTabInputs = $msSeriesTabs.find('input[id*="-add-new-"]');
var seriesTabInputs = $msSeriesTabs.find('input[id]').not(addNewTabInputs);
Just to offer an alternative to using specific selectors, you could iterate through the jQuery set and build the two collections as you go. I don't know that this would be any faster due to the different operations applied to the collections.
var $inputs = $msSeriesTabs.find('input');
var seriesTabInputs = [];
var addNewTabInputs = [];
for (var i = 0; i < $inputs.length ; i += 1)
{
var input = $($inputs[i]);
if ( $(input).attr('id').indexOf('-add-new-') >= 0 )
{ addNewTabInputs.push(input); }
else
{ seriesTabInputs.push(input); }
}
seriesTabInputs = $(seriesTabInputs);
addNewTabInputs = $(addNewTabInputs);
Avoiding filtering twice may not be so crucial unless you are dealing with an enormous amount of elements. Furthermore there is something to be said for the consistency of the code when you filter twice.
That being said there is a way to avoid filtering twice and it may even be instructional; below is some code that can be used to achieve this.
First, we create an empty wrapped set that can be added to, this is achieved by var seriesTabInputs = $(false); Please see this write-up for more information.
Then inside of the filter, we conditionally add to seriesTabInputs but note the way in which we do it: we continually re-assign with seriesTabInputs = seriesTabInputs.add($(this)); If instead you merely call seriesTabInputs.add($(this)) without assigning to seriesTabInput you will wind up with an empty array in the end. Please see the jQuery docs for .add() which gives a similar incorrect example and states that such usage "will not save the added elements, because the .add() method creates a new set".
var seriesTabInputs = $(false);
var addNewTabInputs = $msSeriesTabs.find('input').filter(function() {
if ($(this).attr('id').indexOf('-add-new') >= 0) {
return true;
}
else {
seriesTabInputs = seriesTabInputs.add($(this));
}
});

Eval vs IF statements (many IF statements)

This fiddle pretty much explains what I'm looking for. I'm trying to find the simplest way to go about coding something WITHOUT using eval. I can do it without eval but I think I will have to write 1000s of IF statements. Or is there another way?
http://jsfiddle.net/243rz8eq/9/
HTML
Eval way...<br>
Window-A<br>
Window-B<br>
Window-C<br><br>
Non-Eval way. Requires if statement for each window (there will be thousands). Or is there a simpler way I'm not seeing?<br>
Window-A<br>
Window-B<br>
Window-C<br><br>
JavaScript
window.core = {
launch: function(obj_string) {
//we init a blank dhtmlxwindow and do some other things here, then...
var x = eval("new " + obj_string); //fill dhtmlxwindow with proper content
},
launch_no_eval: function(id) {
//we init a blank dhtmlxwindow and do some other things here, then...
if (id==="window_a") var x = wins.a({x:1}); //fill dhtmlxwindow with proper content
else if (id==="window_b") var x = wins.b({x:1}); //fill dhtmlxwindow with proper content
else if (id==="window_c") var x = wins.c({x:1}); //fill dhtmlxwindow with proper content
//and so on for literally thousands of items.
}
};
window.wins = {
a: function(args) {
//this.myName = 'wins.a'; is used for the help topic.
//DB contains columns: [item] / [helpurl]
//Example Data: [wins.a] / [/help/gettingstarted.html]
//That is why in this previous post (http://stackoverflow.com/q/28096922/3112803)
//I was wanting to know if a function could know it's own name so I wouldn't
//have to type this line below for 1000s of items.
this.myName = 'wins.a';
console.log('Window-A is now displayed. Use "'+this.myName+'" to make link to help topic.');
},
b: function(args) {
this.myName = 'wins.b';
console.log('Window-B is now displayed. Use "'+this.myName+'" to make link to help topic.');
},
c: function(args) {
this.myName = 'wins.c';
console.log('Window-C is now displayed. Use "'+this.myName+'" to make link to help topic.');
}
};
Another approach would be to re-write the current usage of accessing object properties via the dot notation and use the bracket notation instead.
launch_no_eval:function(id) {
//we init a blank dhtmlxwindow and do some other things here, then...
if (id==="window_a") var x = wins.a({x:1}); //fill dhtmlxwindow with proper content
else if (id==="window_b") var x = wins.b({x:1}); //fill dhtmlxwindow with proper content
else if (id==="window_c") var x = wins.c({x:1}); //fill dhtmlxwindow with proper content
//and so on for literally thousands of items.
}
could be re-written as follows,
launch_no_eval:function(id) {
var x = wins[id]({x:1});
}
This would mean your HTML markup can change from,
Window-A<br>
Window-B<br>
Window-C<br><br>
to,
Window-A<br>
Window-B<br>
Window-C<br><br>
As Daniel commented below, assuming you have no control over the HTML markup and must use the window_# value being passed in then you could perform the following,
launch_no_eval:function(id) {
// replace 'window_' with empty string to retrieve unique id
var x = wins[id.replace('window_', '')]({x:1});
}

Create reusable document fragment from the DOM

I would like to have a document fragment/element on the shelf to which I've connected a bunch of other elements. Then whenever I want to add one of these element-systems to the DOM, I copy the fragment, add the unique DOM ID and attach it.
So, for example:
var doc = document,
prototype = doc.createElement(), // or fragment
ra = doc.createElement("div"),
rp = doc.createElement("div"),
rp1 = doc.createElement("a"),
rp2 = doc.createElement("a"),
rp3 = doc.createElement("a");
ra.appendChild(rp);
rp.appendChild(rp1);
rp.appendChild(rp2);
rp.appendChild(rp3);
rp1.className = "rp1";
rp2.className = "rp2";
rp3.className = "rp3";
prototype.appendChild(ra);
This creates the prototype. Then I want to be able to copy the prototype, add an id, and attach. Like so:
var fr = doc.createDocumentFragment(),
to_use = prototype; // This step is illegal, but what I want!
// I want prototype to remain to be copied again.
to_use.id = "unique_id75";
fr.appendChild(to_use);
doc.getElementById("container").appendChild(fr);
I know it's not legal as it stands. I've done fiddles and researched and so on, but it ain't working. One SO post suggested el = doc.appendChild(el); returns el, but that didn't get me far.
So... is it possible? Can you create an on-the-shelf element which can be reused? Or do you have to build the DOM structure you want to add from scratch each time?
Essentially I'm looking for a performance boost 'cos I'm creating thousands of these suckers :)
Thanks.
Use Node.cloneNode:
var container = document.getElementById('container');
var prototype = document.createElement('div');
prototype.innerHTML = "<p>Adding some <strong>arbitrary</strong> HTML in"
+" here just to illustrate.</p> <p>Some <span>nesting</span> too.</p>"
+"<p>CloneNode doesn't care how the initial nodes are created.</p>";
var prototype_copy = prototype.cloneNode(true);
prototype_copy.id = 'whatever'; //note--must be an Element!
container.appendChild(prototype_copy);
Speed Tips
There are three operations you want to minimize:
String Parsing
This occurs when you use innerHTML. innerHTML is fast when you use it in isolation. It's often faster than the equivalent manual-DOM construction because of the overhead of all those DOM method calls. However, you want to keep innerHTML out of inner loops and you don't want to use it for appending. element.innerHTML += 'more html' in particular has catastrophic run-time behavior as the element's contents get bigger and bigger. It also destroys any event or data binding because all those nodes are destroyed and recreated.
So use innerHTML to create your "prototype" nodes for convenience, but for inner loops use DOM manipulation. To clone your prototypes, use prototype.cloneNode(true) which does not invoke the parser. (Be careful with id attributes in cloned prototypes--you need to make sure yourself that they are unique when you append them to the document!)
Document tree modification (repeated appendChild calls)
Every time you modify the document tree you might trigger a repaint of the document window and update the document DOM node relationships, which can be slow. Instead, batch your appends up into a DocumentFragment and append that to the document DOM only once.
Node lookup
If you already have an in-memory prototype object and want to modify pieces of it, you will need to navigate the DOM to find and modify those pieces whether you use DOM traversal, getElement*, or querySelector*.
Keep these searches out of your inner loops by keeping a reference to the nodes you want to modify when you create the prototype. Then whenever you want to clone a near-identical copy of the prototype, modify the nodes you have references to already and then clone the modified prototype.
Sample Template object
For the heck of it, here is a basic (and probably fast) template object illustrating the use of cloneNode and cached node references (reducing the use of string parsing and Node lookups).
Supply it with a "prototype" node (or string) with class names and data-attr="slotname attributename" attributes. The class names become "slots" for text-content replacement; the elements with data-attr become slots for attribute name setting/replacement. You can then supply an object to the render() method with new values for the slots you have defined, and you will get back a clone of the node with the replacements done.
Example usage is at the bottom.
function Template(proto) {
if (typeof proto === 'string') {
this.proto = this.fromString(proto);
} else {
this.proto = proto.cloneNode(true);
}
this.slots = this.findSlots(this.proto);
}
Template.prototype.fromString = function(str) {
var d = document.createDocumentFragment();
var temp = document.createElement('div');
temp.innerHTML = str;
while (temp.firstChild) {
d.appendChild(temp.firstChild);
}
return d;
};
Template.prototype.findSlots = function(proto) {
// textContent slots
var slots = {};
var tokens = /^\s*(\w+)\s+(\w+)\s*$/;
var classes = proto.querySelectorAll('[class]');
Array.prototype.forEach.call(classes, function(e) {
var command = ['setText', e];
Array.prototype.forEach.call(e.classList, function(c) {
slots[c] = command;
});
});
var attributes = proto.querySelectorAll('[data-attr]');
Array.prototype.forEach.call(attributes, function(e) {
var matches = e.getAttribute('data-attr').match(tokens);
if (matches) {
slots[matches[1]] = ['setAttr', e, matches[2]];
}
e.removeAttribute('data-attr');
});
return slots;
};
Template.prototype.render = function(data) {
Object.getOwnPropertyNames(data).forEach(function(name) {
var cmd = this.slots[name];
if (cmd) {
this[cmd[0]].apply(this, cmd.slice(1).concat(data[name]));
}
}, this);
return this.proto.cloneNode(true);
};
Template.prototype.setText = (function() {
var d = document.createElement('div');
var txtprop = (d.textContent === '') ? 'textContent' : 'innerText';
d = null;
return function(elem, val) {
elem[txtprop] = val;
};
}());
Template.prototype.setAttr = function(elem, attrname, val) {
elem.setAttribute(attrname, val);
};
var tpl = new Template('<p data-attr="cloneid id">This is clone number <span class="clonenumber">one</span>!</p>');
var tpl_data = {
cloneid: 0,
clonenumber: 0
};
var df = document.createDocumentFragment();
for (var i = 0; i < 100; i++) {
tpl_data.cloneid = 'id' + i;
tpl_data.clonenumber = i;
df.appendChild(tpl.render(tpl_data));
}
document.body.appendChild(df);
I'd be shocked if innerHTML wasn't faster. Pre-compiled templates such as those provided by lo-dash or doT seem like a great way to go!
Check out this simple example:
http://jsperf.com/lodash-template
It shows you can get 300,000 ops/sec for a fairly complex template with a loop using lo-dash's pre-compiled templates. Seems pretty fast to me and way cleaner JS.
Obviously, this is only one part of the problem. This generates the HTML, actually inserting the HTML is another problem, but once again, innerHTML seems to win over cloneNode and other DOM-based approaches and generally the code is way cleaner.
http://jsperf.com/clonenode-vs-innerhtml-redo/2
Obviously you can take these benchmarks worth a grain of salt. What really matters is your actual app. But I'd recommend giving multiple approaches a try and benchmarking them yourself before making up your mind.
Note: A lot of the benchmarks about templates on JSPerf are doing it wrong. They're re-compiling the template on every iteration, which is obviously going to be way slow.

Avoiding having to write the same word over and over again

I'm very new to javascript so this question might sound stupid. But what is the correct syntax of replacing certain words inside variables and functions. For example, I have this function:
function posTelegram(p){
var data = telegramData;
$("#hotspotTelegram").css("left", xposTelegram[p] +"px");
if (p < data[0] || p > data[1]) {
$("#hotspotTelegram").hide()
} else {
$("#hotspotTelegram").show()
}
};
There is the word "telegram" repeating a lot and every time I make a new hotspot I'm manually inserting the word to replace "telegram" in each line. What would be a smarter way of writing that code so that I only need to write "telegram" once?
Group similar / related data in to data structures instead of having a variable for each bit.
Cache results of calling jQuery
Use an argument
function posGeneral(p, word){
// Don't have a variable for each of these, make them properties of an object
var data = generalDataThing[word].data;
// Don't search the DOM for the same thing over and over, use a variable
var hotspot = $("#hotspot" + word);
hotspot.css("left", generalDataThing[word].xpos[p] +"px");
if (p < data[0] || p > data[1]) {
hotspot.hide()
} else {
hotspot.show()
}
};
You can't always avoid this kind of repetition (this is general to all programing languages).
Sometimes, you can make generic functions or generic classes, for example a class which would embed all your data :
Thing = function(key, xpos) {
this.$element = $('#hotspot'+key);
this.xpos = xpos;
};
Thing.prototype.pos = function (p, data) {
this.$element.css("left", this.xpos[p] +"px");
if (p < this.data[0] || p > this.data[1]) {
this.$element.hide()
} else {
this.$element.show()
}
};
And we could imagine that this could be called like this :
var telegramThing = new Thing('telegram', xposTelegram);
...
telegramThing.pos(p, data);
But it's really hard to make a more concrete proposition without more information regarding your exact problem.
I recommend you read a little about OOP and javascript, as it may help you make complex programs more clear, simple, and easier to maintain.
For example, using a Thing class here would enable
not defining more than once the "#hotspotTelegram" string in your code
reusing the logic and avoid making the same code with another thing than "telegram"
not having the Thing logic in your main application logic (usually in another Thing.js file)
But don't abstract too much, it would have the opposite effects. And if you don't use objects, try to keep meaningful variable names.
var t = "Telegram";
var $_tg = $('#hotspotTelegram');
$_tg.css("left", "xpos"+t[p] + "px"); // not sure about this line, lol
$_tg.hide();
$_tg.show();
etc.
you can create a selector as variable, something like this
function posTelegram(p){
var data = telegramData;
var $sel = $("#hotspotTelegram");
$sel.css("left", xposTelegram[p] +"px");
if (p < data[0] || p > data[1]) {
$sel.hide()
} else {
$sel.show()
}
};

Categories

Resources