I'm trying to make a circle of rectangles, to be centered (vertically and horizontally) in an SVG
here's my code so far:
http://jsfiddle.net/JamThom/7G2pC/
var a = Snap(250,250);
for (var i=0;i<90;i++) {
var sqz = a.rect(0, 0, 14, 3)
.attr({
fill: '#666',
transform: "r"+i*4+",75,0"
})
.data("i",i);
}
In order to do this I believe I need to first select and group all of the rectangles, but as I created them in a for loop they don't have individual names and I don't know how to target them
any help appreciated
and apologies for the ameteur-ness of my code - this is my first experience with snap.svg
You can move it to the center using translate:
transform: "t50,125 r"+i*4+",75,0"
JSFiddle
Your SVG is 250x250 and the radius is 75, so the coordinates for t are 250 / 2 - 75 = 50 and 250 / 2 = 125. In the fiddle I used variables.
Just as a slight variation to helderdarocha for an answer, you can as you mention add them to a group and translate, like this fiddle here
var skillometer = Snap(size, size);
var g = skillometer.g();
g.attr({ transform: 't' + x + ',' + y });
for (var i = 0; i < 90; i++) {
var sqz = skillometer.rect(0, 0, 14, 3)
.attr({
fill: '#666',
transform: "r" + i * 4 + "," + radius + ",0"
}).data("i", i);
g.append( sqz );
}
// you can now animate or move the whole group as one.
g.animate({ transform: 't'+x+','+y+'s1.65' }, 5000 );
There's not a lot of difference, unless you want to move or animate the whole lot, in which case this group method may be preferable, as you can move the group in the future with a transform on just the group element (have included that just to highlight).
Related
After looking through similar questions posted to the forum and not finding something that helped me solve my own problem, I'm posting it.
I'm using SVG.js to generate SVG shapes in a web document. I'd like one of those shapes to ”follow” the mouse/cursor.
By that I mean: The shape has a fixed position/anchor point (at its original center) and it can only move a limited distance (let's say 50px) away from this fixed point.
I want the shape to move in the direction of the cursor, whenever the cursor moves, but never further than a defined distance away from its orignal position. I'm attaching a short animation to illustrate my description:
If the cursor were to disappear, the shape would snap back to its original center.
I know my way around Javascript, HTML and CSS. This type of element-manipulation is new to me and the math is giving my quite the headache, any help would be great.
It looks like I need the shape to basically rotate around its original center, with an angle relative to the cursor? I'm really unsure how to solve this. I have tried using a method to calculate the angle described in this post. My shape moves, but not as intended:
// init
var draw = SVG().addTo('body')
// draw
window.shape = draw.circle(25, 25).stroke({
color: '#000',
width: 2.5
}).fill("#fff");
shape.attr("id", "circle1");
shape.move(50, 50)
// move
var circle = $("#circle1");
var dist = 10;
$(document).mousemove(function(e) {
// angle
var circleCenter = [circle.offset().left + circle.width() / 2, circle.offset().top + circle.height() / 2];
var angle = Math.atan2(e.clientX - circleCenter[0], -(e.clientY - circleCenter[1])) * (180 / Math.PI);
var x = Math.sin(angle) * dist;
var y = (Math.cos(angle) * dist) * -1;
shape.animate().dmove(x, y);
})
<script src="https://cdnjs.cloudflare.com/ajax/libs/svg.js/3.0.16/svg.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
Note: It does not matter to me whether the solution depends on jQuery or not (ideally it doesn't).
After more fiddling around with some solutions to calculating angles and distances, I found the answer.
I'm using a fixed reference point to calculate the angle of the direct line between the center of the shape and the cursor. Then I move the shape relative to this reference point and by a given amount:
// Init canvas
var draw = SVG().addTo('body')
// Draw reference/anchor
var shape_marker_center = draw.circle(3,3).fill("#f00").move(150, 150);;
var grafikCenter = [shape_marker_center.attr("cx"), shape_marker_center.attr("cy")]
// Draw shapes
var shape = draw.circle(25, 25).stroke({color: '#000', width: 2.5 }).fill("none");
shape.attr("id", "circle1").attr({cx: grafikCenter[0], cy:grafikCenter[1]})
var shape2 = draw.circle(50, 50).stroke({color: '#000', width: 2.5 }).fill("none");
shape2.attr("id", "circle2").attr({cx: grafikCenter[0], cy:grafikCenter[1]})
var shape3 = draw.circle(75, 75).stroke({color: '#000', width: 2.5 }).fill("none");
shape3.attr("id", "circle3").attr({cx: grafikCenter[0], cy:grafikCenter[1]})
$(document).mousemove(function(e) {
var pointA = [shape_marker_center.attr("cx"), shape_marker_center.attr("cy")];
var pointB = [e.clientX, e.clientY];
var angle = Math.atan2(pointB[1] - pointA[1], pointB[0] - pointA[0]) * 180 / Math.PI ;
//
var distance_x_1 = Math.cos(angle*Math.PI/180) * 16;
var distance_y_1 = Math.sin(angle*Math.PI/180) * 16;
var distance_x_2 = Math.cos(angle*Math.PI/180) * 8;
var distance_y_2 = Math.sin(angle*Math.PI/180) * 8;
//
shape.center((grafikCenter[0] + distance_x_1), (grafikCenter[1] + distance_y_1));
shape2.center((grafikCenter[0] + (distance_x_2) ), (grafikCenter[1] + (distance_y_2)));
})
svg {
width: 100vw;
height: 100vh;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/svg.js/3.0.16/svg.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
first-time/long-time (quack, quack).
I'm a bit frustrated, and just about stumped, by this riddle I can't quite solve in Snap.svg. It's probably an oversight that I'll kick myself for missing, but I'm not seeing it at this point.
I have x & y data that I draw from a DOM element and store into a series of arrays, filter based on certain values in certain columns, and eventually create multiple instances of the ChartLine object in js. Basically, it sorts a certain column by quantity, assigns each value's row a color from a RainbowVis.js object, pushes all the relevant values from each row into an array for y, and draws a path on the line chart where y is the value and x is a steadily-increasing integer in a For loop.
What I'm currently doing here, in the draw() function, is: for each relevant column, create a <circle> with the variable "dot" with the object's x & y variables, assign the attributes, animate the radius from 0 to 8 in a quarter-second, and add the x & y values of i to a string to be used in a <path> I create right after the For loop. I then animate the path, etc., etc.
Without the setTimeout(), it works well. The circles and paths all animate simultaneously on load. However, I want to add a delay to each .animate with the number of milliseconds increasing by polyDelayInterval in each iteration, so each "dot" animates as the line arrives at it. At the VERY least, I want to animate all of the "dots" after the path is done animating.
The problem is, no matter what I've tried so far, I can only get the last set of "dots" (at the highest x value for each line) to animate; the rest stay at r:0. I've read several somewhat-similar posts, both here and elswhere; I've searched up and down the Snap.svg's docs on their site. I just cannot find what I'm doing wrong. Thanks in advance!
var svgMFLC = Snap('svg#ElementID');
function ChartLine(x, y, color, row) {
this.x = x;
this.y = y;
this.color = color;
this.row = row;
var propNames = Object.keys(this.row);
var yAdjust;
var resetX = this.x;
for (var i = 0; i < propNames.length; i++) { // get only the calculated score columns and their values
if (propNames[i].toLowerCase().includes(calcKeyword)) {
yAdjustedToChartArea = chartBottom - (this.row[propNames[i]] * yInterval);
this.y.push(yAdjustedToChartArea); // returns the value of that score column and pushes it to the y array
}
}
this.draw = function () {
var points = "M"; // the string that will determine the coordinates of each line
var dotShadow = svgMFLC.filter(Snap.filter.shadow(0, 0, 2, "#000000", 0.4));
var polyTime = 1500; // in milliseconds
var dot;
var polyDelayInterval = polyTime / (semesterCols.length - 1);
for (var i = 0; i < semesterCols.length; i++) { // for each data point, create a "dot"
dot = svgMFLC.circle(this.x, this.y[i], 0);
dot.attr({
fill: this.color,
stroke: "none",
filter: dotShadow,
class: "chartPointMFLC"
});
setTimeout(function () {
dot.animate({ r: 8 }, 250, mina.easeout);
}, polyDelayInterval * i);
points += this.x + " " + this.y[i] + " L";
this.x = this.x + xInterval;
}
points = points.slice(0, -2); // take away the excessive " L" from the end of the points string
var poly = svgMFLC.path(points);
var polyLength = poly.getTotalLength();
poly.attr({
fill: "none",
stroke: this.color,
class: "chartLineMFLC",
strokeDasharray: polyLength + " " + polyLength, // setting the strokeDash attributes will help create the "drawing the line" effect when animated
strokeDashoffset: polyLength
});
poly.animate({ strokeDashoffset: 0.00 }, polyTime);
this.x = resetX;
}
}
I can't put a tested solution up without the full code to test, but the problem is almost certainly that you at least need to get a closure for your 'dot' element.
So this line...
setTimeout(function () {
dot.animate({ r: 8 }, 250, mina.easeout);
}, polyDelayInterval * i);
When it comes to call that function, 'dot' will be the last 'dot' from the loop. So you need to create a closure (create functional scope for dot).
So something a bit like...
(function() {
var laterDot = dot;
setTimeout(function () {
laterDot.animate({ r: 8 }, 250, mina.easeout);
}, polyDelayInterval * i)
})();
How can i create something like this in QML using javascript?
Actually I know how to create rectangles in QML but want to do something like this. QML canvas can be of any size but whenever QML section is loaded multiple squares are generated with random sizes and colors without overlapping. When I'm trying to do this rectangles are generated in a list form.
I'm a web developer(ruby on rails oriented) but new to such javascript stuff. Any help will be appreciated.
As #ddriver already noticed, the simpliest decision is to loop through all children to find a room to a new rectangle.
Rectangle {
id: container
anchors.fill: parent
property var items: [];
Component {
id: rect
Rectangle {
color: Qt.rgba(Math.random(),Math.random(),Math.random(),1);
border.width: 1
border.color: "#999"
width: 50
height: 50
}
}
Component.onCompleted: {
var cnt = 50;
for(var i = 0;i < cnt;i ++) {
for(var t = 0;t < 10;t ++) {
var _x = Math.round(Math.random() * (mainWindow.width - 200));
var _y = Math.round(Math.random() * (mainWindow.height - 200));
var _width = Math.round(50 + Math.random() * 150);
var _height = Math.round(50 + Math.random() * 150);
if(checkCoord(_x,_y,_width,_height)) {
var item = rect.createObject(container,{ x: _x, y: _y, width: _width, height: _height });
container.items.push(item);
break;
}
}
}
}
function checkCoord(_x,_y,_width,_height) {
if(container.items.length === 0)
return true;
for(var j = 0;j < container.items.length;j ++) {
var item = container.children[j];
if(!(_x > (item.x+item.width) || (_x+_width) < item.x || _y > (item.y+item.height) || (_y+_height) < item.y))
return false;
}
return true;
}
}
Yes, this is not so wise solution but it still can be improved.
If you want efficiency, it will come at the cost of complexity - you will have to use some space partition algorithm. Otherwise, you could just generate random values until you get enough that are not overlapping.
Checking whether two rectangles overlap is simple - if none of the corners of rectangle B is inside rectangle A, then they don't overlap. A corner/point is inside a rectangle if its x and y values are in the range of the rectangle's x and width and y and height respectively.
In JS Math.random() will give you a number between 0 and 1, so if you want to make a random value for example between 50 and 200, you can do that via Math.random() * 150 + 50.
Have an array, add the initial rectangle value to it, then generate new rectangle values, check if they overlap with those already in the array, if not - add them to the array as well. Once you get enough rectangle values, go ahead and create the actual rectangles. Since all your rectangles are squares, you can only go away with 3 values per square - x, y and size.
Trying to create an interactive pie chart and raphaelJS seemed like the best solution. I have almost zero experience with it, (though I'm fairly proficient with jquery) so pardon if I come off as a complete idiot.
Question: I've created a functional interactive pie chart that displays text on hover. My problem is that I can't figure out how to style the text as I need to - adding and tags to the html doesn't have an effect
Here is the script - I'll try to just include the relevant sections:
var angle = -18,
total = 0,
start = 0,
process = function (j) {
var value = values[j],
angleplus = 360 * value / total,
popangle = angle + (angleplus / 2), // angle of text display
color = Raphael.hsb(start, .9, 1),
ms = 300,
delta = 30,
bcolor = Raphael.hsb(start, 1, 1),
p = sector(cx, cy, r, angle, angle + angleplus, {
fill: "90-" + bcolor + "-" + color,
stroke: "#F3F5F2",
"stroke-width": 4,
"stroke-opacity": 1,
opacity: .5}),
txt = paper.text(cx + (r + delta + 155) * Math.cos(0 * rad),
cy + (r + delta + 75) * Math.sin(-10 * rad),
labels[j]).attr({
fill: "#111",
stroke: "none",
opacity: 0,
"font-family": "Gotham",
"font-size": 14});
});
Here's the jsfiddle with the rest of the script and html (can't get it to run correctly though, works fine normally) - http://jsfiddle.net/9L286/
Currently, "txt" defines the style for text - I want to be able to style both the title ("item 1") and the description. Hopefully I'm just blind and there's a simple solution. If more info is needed, just ask. Thanks!
I'm trying to do something I thought would be rather simple. I've an object that I move around stepwise, i.e. I receive messages every say 100 milliseconds that tell me "your object has moved x pixels to the right and y pixels down". The code below simulates that by moving that object on a circle, but note that it is not known in advance where the object will be heading in the next step.
Anyway, that is pretty simple. But now I want to also tell the object, which is actually a set of subobjects, that it is being rotated.
Unfortunately, I am having trouble getting Raphaël to do what I want. I believe the reason is that while I can animate both translation and rotation independently, I have to set the center of the rotation when it starts. Obviously the center of the rotation changes as the object is moving.
Here's the code I'm using and you can view a live demo here. As you can see, the square rotates as expected, but the arrow rotates incorrectly.
// c&p this into http://raphaeljs.com/playground.html
var WORLD_SIZE = 400,
rect = paper.rect(WORLD_SIZE / 2 - 20, 0, 40, 40, 5).attr({ fill: 'red' }),
pointer = paper.path("M 200 20 L 200 50"),
debug = paper.text(25, 10, ""),
obj = paper.set();
obj.push(rect, pointer);
var t = 0,
step = 0.05;
setInterval(function () {
var deg = Math.round(Raphael.deg(t));
t += step;
debug.attr({ text: deg + '°' });
var dx = ((WORLD_SIZE - 40) / 2) * (Math.sin(t - step) - Math.sin(t)),
dy = ((WORLD_SIZE - 40) / 2) * (Math.cos(t - step) - Math.cos(t));
obj.animate({
translation: dx + ' ' + dy,
rotation: -deg
}, 100);
}, 100);
Any help is appreciated!
If you want do a translation and a rotation too, the raphael obj should be like that
obj.animate({
transform: "t" + [dx , dy] + "r" + (-deg)
}, 100);
Check out http://raphaeljs.com/animation.html
Look at the second animation from the top on the right.
Hope this helps!
Here's the code:
(function () {
var path1 = "M170,90c0-20 40,20 40,0c0-20 -40,20 -40,0z",
path2 = "M270,90c0-20 40,20 40,0c0-20 -40,20 -40,0z";
var t = r.path(path1).attr(dashed);
r.path(path2).attr({fill: "none", stroke: "#666", "stroke-dasharray": "- ", rotation: 90});
var el = r.path(path1).attr({fill: "none", stroke: "#fff", "stroke-width": 2}),
elattrs = [{translation: "100 0", rotation: 90}, {translation: "-100 0", rotation: 0}],
now = 0;
r.arrow(240, 90).node.onclick = function () {
el.animate(elattrs[now++], 1000);
if (now == 2) {
now = 0;
}
}; })();