Dynamically add SVG circle in a SVG group? - javascript

I am using SnapSVG library. I am trying to add a dynamically created SVG circle into a SVG group. A new circle is added every time I click a link (Add Composite). Then I try to push that newly created circle to an array and pass the array to the group (drag) function. I basically want to drag all the circles and rectangle as a group. But its not working. Here is my code ...
<!DOCTYPE html>
<html>
<head>
<script data-require="jquery#*" data-semver="2.1.4" src="http://code.jquery.com/jquery-2.1.4.min.js"></script>
<link rel="stylesheet" href="style.css" />
<script src="snap.svg-min.js"></script>
</head>
<body>
<svg id="svg"></svg>
<div id="addComp">Add Composite</div>
<script>
// JavaScript Document
(function() {
var s = Snap("#svg");
// Construct Composite
function composite(y, i) {
var CirArray = [];
$("svg g").remove();
$("svg rect").remove();
$("svg circle").remove();
var square = s.rect(30, 40, y, 40);
console.log("Square:" + y);
square.attr({
fill: 'lightblue',
stroke: 'lightblue',
//strokeOpacity: .3,
//strokeWidth: 10
});
CirArray.push(square);
var k = 0;
for (z = 1; z <= i; z++) {
k = k + 45;
console.log("Circle:" + k);
var mycircle = s.circle(k, 120, 20);
mycircle.attr({
fill: 'coral',
stroke: 'coral',
strokeOpacity: .3,
strokeWidth: 10
});
CirArray.push(mycircle);
} // For loop end
drag.apply(null, CirArray);
} // Construct Composite End
// Group
function drag(CirArray) {
var tableset = s.group(CirArray);
for (p = 0; p < CirArray.length; p++) {
console.log(CirArray[p])
}
console.log(CirArray.length)
// Drag
var move = function(dx, dy) {
this.attr({
transform: this.data('origTransform') + (this.data('origTransform') ? "T" : "t") + [dx, dy]
});
}
var start = function() {
this.data('origTransform', this.transform().local);
}
var stop = function() {
console.log('finished dragging');
}
tableset.drag(move, start, stop);
} //Drag End
// Add Composite
var x = 0;
var clct = 0;
$("#addComp").click(function() {
x = x + 45;
clct = clct + 1;
composite(x, clct);
}); // Add Composite End
}()); // Iffe End
</script>
</body>
</html>
Visit this site and click on 'Add composite' link a couple of times to see it in action
Any help is very appreciated ....
Thanks

Basically adding the array on the .group(CirArray) does not seem to add all the array items into a group (Inspect the SVG output) the group tag is empty.
Instead adding them in the for-loop below. Made the following changes and it seems to work as expected and moving all squares and circles together.
var tableset = s.group();
for (p = 0; p < CirArray.length; p++) {
tableset.add(CirArray[p]);
console.log(CirArray[p])
}
In the drag function also calling it simply drag(cirArray) (as suggested by #Paul-lebeau ie. drag(CirArray);).
Forked Plunker

The second argument to the function.apply() method is supposed to be an array of parameters.
But you are passing in an array of Snap elements. So in your drag() function, the CirArray parameter is getting set to the first element of your original CirArray, not the whole array.
Either change the call to
drag.apply(null, [CirArray]);
or just call it normally
drag(CirArray);

Related

Linear animation with multiple x,y coordinates

The below code is doing the animation from 2 points , Point (0,0) to Point (100,50) , which is working fine . But i need a function that takes an array of x,y coordinates and keep on running the same below script to draw path one by one . How can this be achieved .
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>Chain</title>
<script src="https://cdnjs.cloudflare.com/ajax/libs/paper.js/0.12.15/paper-full.js" integrity="sha512-XV5MGZ7Tv+G60rzU8P7tPUlaf0yz7SJ/ uI9CLAwyLcZKl9kzxJQFs3nsBNZVNVAwNOkBLqrzMvjGMEycDccqiA==" crossorigin="anonymous" referrerpolicy="no-referrer"></script> <script type="text/paperscript" canvas="canvas">
var path = new Path();
path.strokeColor = 'white';
path.add(new Point(0, 0));
path.add(new Point(100, 50));
var time = 0;
// On each frame...
function onFrame() {
console.log('Running frame ');
if (time <= 1) {
time += 0.01;
drawTmpPath(time);
}
}
var tmpPath;
function drawTmpPath(t) {
// Make sure that t is never over 1.
t = Math.min(t, 1);
// Remove the previously drawn temporary path.
if (tmpPath) {
tmpPath.remove();
}
// Draw the new temporary path from the reference one.
tmpPath = path.clone().set({
selected: false,
strokeColor: 'orange',
strokeWidth: 5
});
// Split it at the appropriate location.
var remainingPath = tmpPath.splitAt(tmpPath.length * t);
// Remove the eventual remaining part.
if (remainingPath) {
remainingPath.remove();
}
}
</script>
</head>
<body>
<canvas id="canvas" resize></canvas>
</body>
</html>

Vertices with indexArray doesn't connect triangles properly

I'm trying to create a sort of "polygon drawer" in p5.js.
My intented behaviour is that a random triangle out of three vertices gets ctreated at start and the user can use his mouse to add triangles, which automatically connect to the two closest vertices from the mouseCursor.
This allw orks fine, but sometimes when I start adding the new vertice by clicking (the current vector at the mouse get's pushed to the array of vertices and the triangle / index array get' updated), a triangle get's drawn which uses a different vertice, not the closest, second closest and the mouse cursor vector.
I have two arrays for the vertices and triangles (the later functions as an index array):
let vertices = [];
let triangles = [];
Globally I track the closest and second closest index of the vertices array:
let closest;
let secondClosest;
I start by creating a random triangle. I push each vertex' index to the triangle array and push the first one once again to complete the triangle:
function createRandomTriangle(){
for(i = 0; i < 3; i++){
let vert = createVector(random(0, width),random(0, height));
vertices.push(vert);
triangles.push(i);
}
triangles.push(0);
}
In my draw function I first draw every triangle by going through the triangles array and drawing lines from their vector equivalents in the vertices array.
I then calculate which two vertices are closest to the mouse cursor and update my globals.
As the last step I draw the trianlge from the mouse cursor as some sort of "guide".
function draw() {
background(255);
for(i = 0; i < triangles.length-1; i++){
line(vertices[triangles[i]].x, vertices[triangles[i]].y,vertices[triangles[i+1]].x, vertices[triangles[i+1]].y);
}
let mouseVector = createVector(mouseX, mouseY);
let distances = [];
for(i = 0; i < vertices.length; i++){
distances.push(mouseVector.dist(vertices[i]));
}
closest = distances.indexOf(Math.min( ...distances ));
distances[closest] = width + height;
secondClosest = distances.indexOf(Math.min( ...distances ));
line(mouseVector.x, mouseVector.y, vertices[closest].x, vertices[closest].y);
line(mouseVector.x, mouseVector.y, vertices[secondClosest].x, vertices[secondClosest].y);
line(vertices[closest].x, vertices[closest].y, vertices[secondClosest].x, vertices[secondClosest].y);
}
Now this function is probably the one causing harm, but I can't figure out why.
Once the user clicks, the mouse cursor vector get's pushed to the array of vertices, his index is pushed first to the triangles array, then the closest index, then the second closest index, then the mouse cursor index again.
function mouseClicked() {
let latestIndex = vertices.push(createVector(mouseX, mouseY)) - 1;
triangles.push(latestIndex);
triangles.push(closest);
triangles.push(secondClosest);
triangles.push(latestIndex);
}
Sometimes this method works fine and sometimes a new line suddenly appears.
I can't comprehend exactly why.
You can test thje p5.js sketch here: https://editor.p5js.org/etlam/sketches/4SAhIydAC
Here is a very basic example of how you might go about drawing a polygon. I really recommend creating a StateMaker, so you can keep track of everything, but that's more than I'm willing to go into right now.
//<![CDATA[
/* js/external.js */
let doc, htm, bod, nav, M, I, mobile, S, Q;
addEventListener('load', ()=>{
doc = document; htm = doc.documentElement; bod = doc.body; nav = navigator; M = tag=>doc.createElement(tag); I = id=>doc.getElementById(id);
mobile = nav.userAgent.match(/Mobi/i) ? true : false;
S = (selector, within)=>{
let w = within || doc;
return w.querySelector(selector);
}
Q = (selector, within)=>{
let w = within || doc;
return w.querySelectorAll(selector);
}
rand = (min, max)=>{
let mn = min, mx = max;
if(mx === undefined){
mx = mn; mn = 0;
}
return mn+Math.floor(Math.random()*(mx-mn+1));
}
// tiny library above magic below - can put on another page using a load Event (besides // end load line)
const can = I('can'), canB = can.getBoundingClientRect();
const canW = can.width = canB.width, canH = can.height = canB.height;
const ctx = can.getContext('2d'), points = [];
ctx.lineWidth = '1px'; ctx.lineStyle = '#000'; ctx.fillStyle = '#700';
function polyFun(e){
let b = can.getBoundingClientRect();
ctx.clearRect(0, 0, canW, canH);
points.push([e.clientX-b.left, e.clientY-b.top]);
ctx.beginPath(); ctx.moveTo(...points[0]);
for(let i=1,l=points.length; i<l; i++){
ctx.lineTo(...points[i]);
}
ctx.fill(); ctx.stroke(); ctx.closePath();
}
if(mobile){
ontouchstart = e=>{
polyFun(e.touches[0]);
}
}
else{
onmousedown = polyFun;
}
}); // end load
//]]>
/* css/external.css */
*{
box-sizing:border-box; font-size:0; padding:0; margin:0;
}
html,body,#can{
width:100%; height:100%;
}
#can{
cursor:pointer;
}
<!DOCTYPE html>
<html lang='en'>
<head>
<meta charset='UTF-8' /><meta name='viewport' content='width=device-width, height=device-height, initial-scale:1, user-scalable=no' />
<title>Title Here</title>
<link type='text/css' rel='stylesheet' href='css/external.css' />
<script src='js/external.js'></script>
</head>
<body>
<canvas id='can'></canvas>
</body>
</html>

Javascript - How to prevent blocking during nested For loops?

I have a set of nested For loops in my script (vanilla javascript, no jquery) that could go on for quite a while (30+ seconds). During this time, the browser is obviously unresponsive and the DOM isn't updating because the loop is blocking - just as one would expect.
Inside the nested For loops, I'm calling another function and generating a SVG circle with the parameters being passed and then adding the circles to an svg element and returning to the loop.
Question: what strategy can be used to update the DOM and actually show some of the SVG circles being created?
There are a lot of questions on S/O asking for similar solutions, but most of the examples use setTimeout(function, 0) and although I have tried using this, the DOM doesn't update probably because I'm not sure which function I should set the Timeout for?
I've also found some examples of webworkers, but can't quite wrap my head around how I could actually use those in this project.
Using this basic example - could someone show me how I can update the DOM while the loop is processing?
<button onclick="randomCircles()">
make circles
</button>
<svg
id="cont" version="1.1"
xmlns="http://www.w3.org/2000/svg"
xmlns:xlink="http://www.w3.org/1999/xlink"
x="0" y="0" viewBox="0, 0, 900, 600">
<style>svg { background-color: black;}</style>
</svg>
<script>
function randomCircles(){
var x_end = 900;
var y_end = 675;
for(x=0; x<=x_end; x += 5){
for(y=0; y<=y_end; y += 5){
var new_circle = Math.random();
drawCircle(x,y,new_circle);
}
}
}
function drawCircle(x,y,circle_radius){
var svgns = "http://www.w3.org/2000/svg";
var container = document.getElementById('cont');
var circle = document.createElementNS(svgns, 'circle');
var circle_to_draw = circle_radius * 2;
circle.setAttributeNS(null, 'cx', x);
circle.setAttributeNS(null, 'cy', y);
circle.setAttributeNS(null, 'r', circle_to_draw);
circle.setAttributeNS(null, 'style', 'fill: white; stroke: none;' );
container.appendChild(circle);
return;
}
</script>
Very basic example (fiddle: https://jsfiddle.net/wx67uyzv/2/)
Here is an updated fiddle with the solution, thanks to Joel!
https://jsfiddle.net/198znrc0/
You're going to need to use setInterval().
// sample values
var loopSpeed = 25; // How often the window will refresh, in milliseconds
var loopEnd = 75;
var loopStart = 5;
var loopIncrement = 5;
var i = loopStart;
var intervalId = setInterval(() => {
if (i === loopEnd) {
// wrap up
clearInterval(intervalId);
return;
}
// do loop stuff
i += loopIncrement;
}, loopSpeed);
You need to split your code into multiple chunks which you can run without blocking browser for long time. Here is simple example of how it can be done, tried to keep it as close as possible to your original code. Basically you draw certain number of circles circlesCount per cycle and then use setTimeout to let browser update DOM.
function randomCircles() {
var x_end = 900;
var y_end = 675;
var x = 0;
var y = 0;
var circlesCount = 20;
function randomCirclesIteration() {
var drawnCircles = 0;
for(; x<=x_end; x += 5){
for(; y<=y_end; y += 5){
new_circle = Math.random();
drawCircle(x, y, new_circle);
drawnCircles++;
if (drawnCircles == circlesCount) {
setTimeout(randomCirclesIteration, 0);
return;
}
}
y = 0;
}
}
randomCirclesIteration();
}
function drawCircle(x, y, circle_radius){
var svgns = "http://www.w3.org/2000/svg";
var container = document.getElementById('cont');
var circle = document.createElementNS(svgns, 'circle');
var new_circle = (circle_radius * 2).toFixed(2);
circle.setAttributeNS(null, 'cx', x);
circle.setAttributeNS(null, 'cy', y);
circle.setAttributeNS(null, 'r', parseFloat(new_circle));
circle.setAttributeNS(null, 'style', 'fill: white; stroke: none;' );
container.appendChild(circle);
}

Errors working with bokeh in javascript model

I'm trying to work with Bokeh JS implementation i.e. trying to use the JS API of bokeh to display glyphs. I followed the instructions provided in bokeh documentation, but the page error's out. For example var plt = Bokeh. Plotting is undefined. What's going wrong?
I have included all the CSS/JS files as indicated in the documentation. Below is the code copied from the documentation - trying to get it working
Can someone with bokehjs experience help?
$(function() {
var plt = Bokeh.Plotting;
console.log(Bokeh.Plotting);
// set up some data
var M = 100;
var xx = [];
var yy = [];
var colors = [];
var radii = [];
for (var y = 0; y <= M; y += 4) {
for (var x = 0; x <= M; x += 4) {
xx.push(x);
yy.push(y);
colors.push(plt.color(50 + 2 * x, 30 + 2 * y, 150));
radii.push(Math.random() * 0.4 + 1.7)
}
}
// create a data source
var source = new Bokeh.ColumnDataSource({
data: {
x: xx,
y: yy,
radius: radii,
colors: colors
}
});
// make the plot and add some tools
var tools = "pan,crosshair,wheel_zoom,box_zoom,reset,save";
var p = plt.figure({
title: "Colorful Scatter",
tools: tools
});
// call the circle glyph method to add some circle glyphs
var circles = p.circle({
field: "x"
}, {
field: "y"
}, {
source: source,
radius: radii,
fill_color: colors,
fill_alpha: 0.6,
line_color: null
});
// add the plot to a document and display it
// var doc = new Bokeh.Document();
// doc.add_root(plt);
// var div = $("#plot");
// cosole.log(div);
// Bokeh.embed.add_document_standalone(doc, div);
plt.show(p);
});
<script src="https://ajax.googleapis.com/ajax/libs/jquery/3.1.1/jquery.min.js"></script>
<script src="https://cdn.bokeh.org/bokeh/release/bokeh-0.12.4.min.js"></script>
<script src="https://cdn.bokeh.org/bokeh/release/bokeh-widgets-0.12.4.min.js"></script>
<link rel="stylesheet" type="text/css" href="https://cdn.bokeh.org/bokeh/release/bokeh-0.12.4.min.css">
<link rel="stylesheet" type="text/css" href="https://cdn.bokeh.org/bokeh/release/bokeh-widgets-0.12.4.min.css">
<div id="plot">
</div>
Turns out, Bokeh has changed the JS APIs since 0.12.1 So the above code works for <=0.12.1 but for more recent releases the JS code breaks. The documentation has not changed though. Hoping the documentation is updated soon
Finally, I was able to get BokehJS 0.12.4 to work. The release notes of 0.12.2 contain a small note that the BokehJS API has been split into its own bundle. So when migrating from pre-0.12.2 to a recent version, it is necessary to add the following javascript
https://cdn.bokeh.org/bokeh/release/bokeh-api-0.12.4.min.js
in addition to the existing bokeh-0.12.4.min.js and bokeh-widgets-0.12.4.min.js. After adding this file to your example it works. Unfortunately, this information didn't make it into the official BokehJS documentation yet.
$(function() {
var plt = Bokeh.Plotting;
// set up some data
var M = 100;
var xx = [];
var yy = [];
var colors = [];
var radii = [];
for (var y = 0; y <= M; y += 4) {
for (var x = 0; x <= M; x += 4) {
xx.push(x);
yy.push(y);
colors.push(plt.color(50 + 2 * x, 30 + 2 * y, 150));
radii.push(Math.random() * 0.4 + 1.7)
}
}
// create a data source
var source = new Bokeh.ColumnDataSource({
data: {
x: xx,
y: yy,
radius: radii,
colors: colors
}
});
// make the plot and add some tools
var tools = "pan,crosshair,wheel_zoom,box_zoom,reset,save";
var p = plt.figure({
title: "Colorful Scatter",
tools: tools
});
// call the circle glyph method to add some circle glyphs
var circles = p.circle({
field: "x"
}, {
field: "y"
}, {
source: source,
radius: radii,
fill_color: colors,
fill_alpha: 0.6,
line_color: null
});
plt.show(p);
});
<script src="https://ajax.googleapis.com/ajax/libs/jquery/3.1.1/jquery.min.js"></script>
<script src="https://cdn.bokeh.org/bokeh/release/bokeh-0.12.4.min.js"></script>
<script src="https://cdn.bokeh.org/bokeh/release/bokeh-api-0.12.4.min.js"></script>
<script src="https://cdn.bokeh.org/bokeh/release/bokeh-widgets-0.12.4.min.js"></script>
<link rel="stylesheet" type="text/css" href="https://cdn.bokeh.org/bokeh/release/bokeh-0.12.4.min.css">
<link rel="stylesheet" type="text/css" href="https://cdn.bokeh.org/bokeh/release/bokeh-widgets-0.12.4.min.css">
<div id="plot"></div>

Animating SVG polygons

I wrote a code to drawing polygons:
var svg = document.createElementNS('http://www.w3.org/2000/svg', 'svg');
svg.setAttribute('width', '100%');
svg.setAttribute('height', window.innerHeight);
document.querySelector('#bg').appendChild(svg);
for(var x = 0; x < polygons.length; x++){
var polygon = document.createElementNS(svg.namespaceURI, 'polygon');
polygon.setAttribute('points', polygons[0 + x]);
polygon.setAttribute('fill', polygons[0 + x][1]);
svg.appendChild(polygon);
}
My full code with polygon points:
http://codepen.io/anon/pen/WrqrbB
I would like to animate this polygons similar to this animation:
http://codepen.io/zessx/pen/ZGBMXZ
How to animate my polygons?
You can
call an animation function to manipulate your coordinate values as desired,
convert them to a string, e.g. using .join(),
send the resulting string back to the polygon as its points attribute value, redrawing the shape (as you were already doing when you initially created your shapes), and
have the animation function, when it is finished, call itself again at a reasonable built-in time-delay using requestAnimationFrame.
The following snippet gives a basic idea of what can be done.
(Note that I've redefined the array polygons in my example so that it is different from what you had, but that was done for the sake of simplicity in this example.)
var svg = document.getElementsByTagName("svg")[0];
var polygons = [], numSteps = 100, stepNum = 0;
var coords = [
[40, 20, 80, 20, 80, 60, 40, 60],
[140, 20, 180, 20, 160, 50]
];
for (var x = 0; x < coords.length; x++) {
polygons[x] = document.createElementNS(svg.namespaceURI, 'polygon');
polygons[x].setAttribute('points', coords[x].join());
svg.appendChild(polygons[x]);
}
function anim() {
for (var x = 0; x < coords.length; x++) {
coords[x] = coords[x].map(function(coord) {
return coord + 4 * (Math.random() - 0.5);
});
polygons[x].setAttribute('points', coords[x].join());
stepNum += 1;
}
if (stepNum < numSteps) requestAnimationFrame(anim);
}
anim();
<svg></svg>
UPDATE The above snippet shows generally how to animate a polygon. In your case, however, there is a further issue. On your codepen demo, it is clear that you have hard-coded the point coordinates for each polygon separately. Thus, if you want to move one point, you're going to have to update coordinates in at least 2 if not more places, for every polygon that touches that point.
A better approach would be to create a separate array of all points and then define each polygon with respect to that array. (This is similar to how things are sometimes done in 3D graphics, e.g. WebGL.) The following code snippet demonstrates this approach.
var svg = document.getElementsByTagName("svg")[0];
var polyElems = [], numSteps = 100, stepNum = 0;
var pts = [[120,20], [160,20], [200,20], [240,20], [100,50], [140,50], [180,50], [220,50], [260,50], [120,80], [160,80], [200,80], [240,80]];
var polyPts = [[0,1,5], [1,2,6], [2,3,7], [0,4,5], [1,5,6], [2,6,7], [3,7,8], [4,5,9], [5,6,10], [6,7,11], [7,8,12], [5,9,10], [6,10,11], [7,11,12]];
for (var x = 0; x < polyPts.length; x++) {
polyElems[x] = document.createElementNS(svg.namespaceURI, 'polygon');
polyElems[x].setAttribute('fill', '#'+Math.floor(Math.random()*16777215).toString(16));
// random hex color routine from http://www.paulirish.com/2009/random-hex-color-code-snippets/
drawPolygon(x);
}
function anim() {
pts = pts.map(function(pt) {
return pt.map(function(coord) {
return coord + 3 * (Math.random() - 0.5); // move each point
});
});
for (var x = 0; x < polyPts.length; x++) {drawPolygon(x);}
stepNum += 1;
if (stepNum < numSteps) requestAnimationFrame(anim); // redo anim'n until all anim'n steps done
}
anim(); // start the animation
function drawPolygon(x) {
var ptNums = polyPts[x];
var currCoords = [pts[ptNums[0]], pts[ptNums[1]], pts[ptNums[2]]].join();
// creates a string of coordinates; note that [[1,2],[3,4],[5,6]].join() yields "1,2,3,4,5,6"
polyElems[x].setAttribute('points', currCoords);
svg.appendChild(polyElems[x]);
}
<svg></svg>

Categories