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

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);

Related

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});
}

How to add new line character for each node text in arbor js

I have a network,with 14 nodes, and each node has a label, and also edges for connecting these nodes to each other. I tried to enter a long label for one of these nodes, and unfortunatly it seems that arborjs show labels on nodes just horizontally, so I tried to put a new line character in a label text "\n" and it will render it as an space, so I was wondering if anybody knows how to have multiple line label for a node in arbor js?
Here is the code :
<script language="javascript" type="text/javascript">
var sys = arbor.ParticleSystem(1000, 700,0.01);
sys.parameters({gravity:false});
sys.renderer = Renderer("#viewport") ;
var data = {
nodes:{
STRUCTURE:{'color':'black','shape':'rect','label':'STRUCTURE', },
Engineering:{'color':'salmon','shape':'rect','label':'Engineering'},
Architecture:{'color':'salmon','shape':'rect','label':'Architecture'},
ArtsSciences:{'color':'salmon','shape':'rect','label':'Arts & Sciences'},
EarthEnergy:{'color':'salmon','shape':'rect','label':'Earth & Energy'},
SustainableDesign:s{'color':'lightskyblue','shape':'rect','label':'Sustainable Design'},
sutabledesignleaf1:{'color':'lawngreen','shape':'rect','label':'Earthen Structures'},
MaterialsStructures:{'color':'lightskyblue','shape':'rect','label':'Materials & Structures'},
MaterialsStructuresleaf1:{'color':'lawngreen','shape':'rect','label':'AEROSPACE/MECH. ENGINEERING'},
LithosphereDynamics:{'color':'lightskyblue','shape':'rect','label':'Lithosphere Dynamics'},
Energy:{'color':'lightskyblue','shape':'rect','label':'Energy'},
LithosphereDynamicsleaf1:{'color':'lawngreen','shape':'rect','label':'Structure/Tectonophy'},
Energyleaf1:{'color':'lawngreen','shape':'rect','label':'Structural Control on Reservoirs'},
ArtsSciencesleaf1:{'color':'lawngreen','shape':'rect','label':'VARIOUS THEMES Market Structure'},
},
edges:{
STRUCTURE:{ Engineering:{}, Architecture:{} , ArtsSciences:{}, EarthEnergy:{}},
ArtsSciences:{ArtsSciencesleaf1:{}},
EarthEnergy:{Energy:{},LithosphereDynamics:{}},
Energy:{Energyleaf1:{}},
LithosphereDynamics:{LithosphereDynamicsleaf1:{}},
Engineering:{MaterialsStructures:{}},
MaterialsStructures:{MaterialsStructuresleaf1:{}},
Architecture:{SustainableDesign:{}},
SustainableDesign:{sutabledesignleaf1:{}}
}
};
sys.graft(data);
var canvas = document.selectElementById('viewport') ;
var context = canvas.getContext('2d');
context.font = '40pt Calibri';
context.fillStyle = 'blue';
</script>
suppose in this code, for STRUCTURE node's label we have "STRUCTURE" but when I want to have like a long text lie "kaskdjhkjahdkjhaskjdhjkahskjdhakjshdkjahdkjhaskjdhkjahsdkjhakjsdhkjashdkjhasdkjhkajshdkjhakjdhkajshdk" it doesn't break it to two lines, it will show up as one line, and even if I put a new line character, it will consider it as a space and it will show that again in one line, any help is appreciated.
Thanks in advance
Thank you for your answer, last night, I figured out that unfortunately there is no way on canvas fillText() to draw text in multiple line, so I had to write my wrapper for that, here is the function that I wrote, and it's working right now,
function wrap_text(var rawstring,var line_width)
{
var strarray = new Array() ;
var temp_str = new Array() ;
var j = 0 ;var i = 0 ;
strarray = text.split(' ');
var temp = strarray[j] ;
j++;
while(j < strarray.length)
{
while(temp.length <30)
{
temp = temp+" "+strarray[j];
j++;
}
temp_str[i]=temp ; i++;
var temp = "" ;
}
return temp_str;
}
var wrap_result = wrap_text('kjakjhkjashd kajasd asdmbdmnad nmauhiqwe kbawem mnbasdm',20);
for (var i = 0; i <wrap_result.length ; i--)
{
console.log(wrap_result[i]);
}
I know this post is 2 year too late, though I was still having the same issue and unable to find the solution anywhere. The main problem is the canvas fillText does not support multiple lines. So you have to add multiple lines to the label. The trick is to split out the returns into an array and loop over the array for each new line.
To keep the code clean, I prefer to not to directly edit the function in the files. To get the multiple lines to work add to your custom JS the below function which will override the Cytoscape internal label rendering. Works for both cytoscape.js & cytoscape.min.js
//function to override the default label rending to support multiple lines
;(function($$){ 'use strict';
var CanvasRenderer = $$('renderer', 'canvas');
CanvasRenderer.prototype.drawText = function(context, element, textX, textY) {
var style = element._private.style;
var parentOpacity = element.effectiveOpacity();
if( parentOpacity === 0 ){ return; }
var text = this.setupTextStyle( context, element );
if ( text != null && !isNaN(textX) && !isNaN(textY) ) {
var lineWidth = 2 * style['text-outline-width'].value; // *2 b/c the stroke is drawn centred on the middle
if (lineWidth > 0) {
context.lineWidth = lineWidth;
context.strokeText(text, textX, textY);
}
//START NEW CODE
//remove old label rendinging
//context.fillText(text, textX, textY);
//explode the text into an array split up by line returns
var lines = text.split("\n");
//loop through each of the lines
for (var index = 0; index < lines.length; ++index) {
//render the multiple lines
context.fillText(lines[index], textX, (textY + (style['font-size'].pxValue * 1.2 * index)));
}
//END NEW CODE
}
};
})( cytoscape );
This question really concerns canvas.fillText(), not arbor.js, see this question. In short, this is a limitation of the fillText function.
That said, you can solve this problem relatively simply. The following all assumes you are basing your rendering on the "atlas" example, but you can figure it out from the idea anyway I think.
// draw the text
if (label){
ctx.font = "bold 11px Arial"
ctx.textAlign = "center"
// if (node.data.region) ctx.fillStyle = palette[node.data.region]
// else ctx.fillStyle = "#888888"
ctx.fillStyle = "#888888"
// ctx.fillText(label||"", pt.x, pt.y+4)
ctx.fillText(label||"", pt.x, pt.y+4)
}
Check out the code above to see how the text is rendered. We can replace the line ctx.fillText(label||"", pt.x, pt.y+4) with a loop based on multiple lines you want to render. Make your label value something like line 1\nline 2\nline 3, then use String.split() to make it into an array like so:
var lines = label.split("\n");
Now, loop through lines and render a ctx.fillText() for each one. If you are basing your project closely on the examples you may need to adjust the layout to make it look right, but hopefully this has put you on the right track.

canvas class javascript

I'm having trouble with my javascript code. I'm trying to create a moving set of circles where each circle has their own attributes. So far I've managed to input all the needed values into an array, but I can't figure out how to use them properly for drawing on canvas.
Here's the javascript:
var radius = 10;
var step = x = y = 0;
var r = g = b = 255;
var circleHolder = [];
var loop = setInterval(function(){update();}, 30);
function Circle(x, y, radius, r, g, b)
{
this.x = x;
this.y = y;
this.radius = radius;
this.r = r;
this.g = g;
this.b = b;
circleHolder.push(this);
}
Circle.prototype.draw = function()
{
Circle.prototype.ctx = document.getElementById("MyCanvas").getContext("2d");
Circle.prototype.ctx.clearRect(0,0,720,720); // clear canvas
Circle.prototype.ctx.beginPath();
Circle.prototype.ctx.strokeStyle = "rgb("+ this.r +", "+ this.g +", "+ this.b +")";
Circle.prototype.ctx.arc(this.x, this.y, this.radius, 0, 2 * Math.PI);
Circle.prototype.ctx.stroke();
}
Circle.prototype.update = function ()
{
step += .02;
step %= 2 * Math.PI;
this.x = parseInt((Math.sin(step)) * 150) + 360;
this.y = parseInt((Math.cos(step)) * 150) + 360;
this.radius += 16;
if (this.radius > 200)
{
for (i in circleHolder)
{
if (circleHolder[i]==this)
{
circleHolder.splice(i, 1);
}
}
}
}
function update()
{
var ci = new Circle(x, y, radius, r, g, b);
for (i in circleHolder)
{
ci = circleHolder[i];
ci.update();
ci.draw();
}
}
I'm pretty sure my problem lies within update() {} but I can't figure out how to do it properly.
EDIT: Okay, I've got it working with some changes! Check this Fiddle! I'm getting "ci not defined" error in the console though, and it has a strange bug: Changing the "if (this.radius > 128)" to higher integer it will make the circles spin faster, I don't know why. If you want you can try to change it to 256 and see what happens.
for (var i=0; i < allCircles; i++)
{
ci = circleHolder[i]; <----- This is causing the error
ci.update();
ci.draw();
}
it's not 100% clear to me what you're trying to do, but I tried to fix the main problem
One problem is your for loop.. you shouldn't use for in for arrays, do this instead:
for (var i=0 ; i<circleHolder.length ; i++)
{
ci = circleHolder[i];
ci.update();
ci.draw();
}
see this fiddle
Also I moved your get context and other things that should happen only once into the constructor, instead of having it in the update function.
You're also clearing the canvas before each draw, so the it will only show the last drawn circle per frame. (if you remove the clearRect it looks like one of those old spirographs).
You were also drawing the circles with (255,255,255)(white) so it wasn't showing until the color was changed.
Edit:
Really there are a few problems with this code:
The context shouldn't be inside a circle class if you plan on having many of them.
You should have some object which contains the canvas/context and an array of all circles.
Then have that object manage the updating/drawing.
For starters, unless there's something else going on, outside of this code:
You are using for ... in ... on an array, for-in is for objects, when used on arrays, most browsers will include methods like .splice and .forEach, and not just the numeric 0...n index.
function splice () {}.draw(); doesn't end well.
Also, what is the colour of your page's background? You're setting the rgb colour of each circle to 100% white. You're also clearing the canvas... ...which might well mean that the whole thing is transparent. So if you've got a transparent canvas, white circles and a white background, chances are great you're not going to be seeing anything at all, if this is even working without spitting out an error.
It might make a lot more sense to move your logic around in a way that lets you follow what's going on.
If you make a circle constructor, don't have it do anything but make a new circle.
Inside of your update, create a circle.
THEN put it inside of your circle collection (not in the circle constructor).
In a large application, you will typically call update on ALL objects, and then call draw on ALL objects, rather than updating and drawing one at a time.
Imagine a game that didn't bother to check if you had been hit by a bullet before drawing you and letting you move, for instance.
So inside of your loop, you should have an update and a draw.
Inside of the update, create your circles add them to the list and update the positions of them.
Inside of the draw, draw the circles.
In the future, this will give you the benefit of having things like collision-detection, without having to redraw everything, multiple times per frame.
Also, don't do DOM-access inside of a function that's going to be called many, many times (Circle.draw).
That will demolish your framerate in the future.
Instead, pass the function a dependency (the canvas).
// inside of the main game's scope
var screen = document.getElementById(...).getContext("2d");
// inside of your new draw function, in the animation/game loop
var i = 0, allCircles = circleHolder.length, currentCircle;
for (; i < allCircles; i += 1) {
currentCircle = circleHolder[i];
currentCircle.draw(screen);
}

Simple javascript game, hide / show random square

I'm working on a simple game and I need some help to improve my code
So here's the game:
Some square show and hide randomely for a few seconds and you have to clic on them.
I use RaphaelJS to draw the square and a few of JQuery ($.each() function)
I work in a div, that's how I draw my squares (6 squares), x y are random numbers.
var rec1 = paper.rect(x, y, 50, 50).attr({
fill: "blue",});
Can I use for() to build my squares with a different var name for each one ?
I try with var = varName+i but it didn't work.
To hide and show the square I use two functions call with two setTimeout:
function box1() {rec1.show();}
function hidebox1() {rec1.hide();}
var time1 = setTimeout(box1, 1000);
var time1 = setTimeout(hidebox1, 2000);
I know it looks crappy...
I'm sure there is a way to use a toggle, or something more fancy to do that if you could help me finding it :) Because right now I have to do that for every square...
Thanks a lot for your help.
Your instinct to try to use varName plus some i to identify which varName you want is spot on, and JavaScript (like most languages) has that idea built in through what's called an array.
A simple one looks something like this:
var foo = [1, 5, 198, 309];
With that array, you can access foo[0] which is 1, or foo[3] which is 309.
Note two things: First, we identify which element of the array we want using square brackets. Second, we start counting at 0, not 1.
You can create an empty array like var varName = []; and then add new elements to it using varName.push( newValueToPutIn );
With those tools, you can now get at what you wanted. Now you can do something like:
var recs = [];
for(var i = 0; i < 100; i++) {
var rec = paper.rect(x, y, 50, 50).attr({fill: 'blue'});
recs.push(rec);
}
And recs[0] and recs[1] and so forth will refer to your various boxes.
For the first question, an array is the way to go.
For the second part, you could encapsulate the square and its show/hide stuff into a new anonymous object, like this:
var recs = [];
var numberOfRecs = 6;
for (var i = 0; i < numberOfRecs; i++) {
//determine x and y?
recs.push({
box: paper.rect(x, y, 50, 50).attr({ fill: "blue" }),
showBriefly: function(timeFromNow, duration) {
window.setTimeout(this.box.show, timeFromNow);
window.setTimeout(this.box.hide, timeFromNow + duration);
}
});
}
//show the 3rd box 1000 ms from now, for a duration of 1000 ms
recs[2].showBriefly(1000, 1000);

Working with SVG polygon elements

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

Categories

Resources