Please bear with me, I'm still new to Javascript...
Here are two fiddles. The only difference between them is one has a text object as a title and the other does not; but this difference profoundly affects what happens when I mouseover the circle objects.
What I want is the behavior of the second fiddle, when there is a title like the first fiddle.
I've been trying to figure out how to get my function out of the loop so JSHint stops giving me an error... I have a feeling this behavior is caused by my lack of understanding of closure, binding, etc.
Fiddle 1 with title, mouseover is weird
Fiddle 2 no title, mouseover works
//Code for Fiddle 1: the only difference in Fiddle 2 is the indicated portion is commented out
var rsr = Raphael("holder", '1160', '1400');
// in the next fiddle this will be commented out
rsr.text(220, 50, 'Title').attr({
'font-size': 32,
'text-anchor': 'middle'
});
// end of comment out in Fiddle 2
var cir = []; // an array of circles
var xco = [206, 144.9, 317.4, 317.5]; // x coordinates of circle centers
var yco = [167.7, 231.8, 191.4, 256.8]; // y coordinates of circle centers
var rd = [25.5, 46, 34, 18.5]; // radii of circles
var circcolr = ['#939dac', '#000000', '#ecea5a', '#0da9f2']; // fill colors of circles
for (var i = 0; i < 4; i++) { // loop to draw circles
cir[i] = rsr.circle(xco[i], yco[i], rd[i]);
cir[i].attr({
fill: circcolr[i],
'fill-opacity': 1,
stroke: 'none'
});
}
for (var i in cir) {
(function (st) {
console.log(st.id);
st.node.onmouseover = function () {
st.animate({
r: rd[st.id] * 1.2
}, 200);
};
st.node.onmouseout = function () {
st.animate({
r: rd[st.id]
}, 100);
};
})(cir[i]);
}
The answer is pretty much there in the console logging you're already doing.
In the first case you create 5 objects the title and 4 circles so the title has id = 0 and the circles have id = 1, 2, 3, and 4.
In the second case you're only creating the circles so they have id = 0, 1, 2, 3
When you index into the rd array you're off by one in the title case. Changing the code at the end to this makes it work the same...
for (var i in cir) {
(function (cir, i) {
console.log(cir[i]);
cir[i].node.onmouseover = function () {
cir[i].animate({
r: rd[i] * 1.2
}, 200);
};
cir[i].node.onmouseout = function () {
cir[i].animate({
r: rd[i]
}, 100);
};
})(cir, i);
}
Related
I want to have the user click two positions and then draw a line between them. This is totally functional and easy. And it also works if the click is outside the -180;180 range.
createPolyLine(outside2, userLocation);
//draw polyline
function createPolyLine(loc1, loc2) {
var latlongs = [loc1, loc2];
console.log(loc1);
var polyline = new L.Polyline(latlongs, {
color: 'white',
opacity: 1,
weight: 1,
clickable: false
}).addTo(map);
}
Now If a user clicks say on longitude of -600 I want to have it automatically wrap into the -180;180 area but only if its the closest path. If its closer to keep the click in the -360:-180 area then it should wrap into the -360;-180 area. Same for the other side in positive direction ofcourse.
Example image of what i mean:
Example of when its closer to use -360;-180 region and not wrap it into -180;180
Example of when its closer to use -180;180 and it would be wrong now
Based on second example but correctly wrapped now
What would be the most efficient way to achieve this auto wrap into either -360;-180 / -180;180 / 180;360 depending on where the closest line between two points would be?
Thanks to the response of Corey Alix I came up with a solution which feels kinda dirty but it works for all the test cases I tried against. In my scenario one point is always in -180;180 range.
// a is always in -180;180
function getPositions(a, b) {
b.lng %= 360;
let abs = Math.abs(a.lng - b.lng);
let absWrap = Math.abs(a.lng - b.wrap().lng);
if(absWrap < abs) {
b = b.wrap();
abs = absWrap;
}
let left = new L.LatLng(b.lat, b.lng - 360);
let right = new L.LatLng(b.lat, b.lng + 360);
let leftAbs = Math.abs(a.lng - left.lng);
if(leftAbs < abs) {
b = left;
abs = leftAbs;
}
if(Math.abs(a.lng - right.lng) < abs) {
b = right;
}
return [a, b];
}
//draw polyline
function createPolyLine(loc1, loc2) {
var latlongs = getPositions(loc2, loc1);
polyline = new L.Polyline(latlongs, {
color: 'white',
opacity: 1,
weight: 1,
clickable: false
}).addTo(map);
}
In JSXGraph I graph a plate (polygon) with 4 forces (arrows) acting on it. I intend to use the code for a variable number of forces (that's why it is more general than necessary for just 4 forces).
Force components can be shown and hidden. When a force arrow (not passing through the pivot point) is dragged, the "drag" event triggers an animation:
1st: showing the force and its arm, waiting for 2s
2nd: rotate the polygon in the direction of the force component (either clockwise or anti-clockwise).
Problem 1: The animation is slow and rough. I tried to use "suspendUpdate" and "unsuspendUpdate", but it made things even worse. Probably, I did not use it at the right place.
Problem 2: (probably related) I did not find a good way to clone the polygon for the animation. Or would it be best to store its coordinates? But if so, how would I reset the polygon (plus force arrow) to its original position after the rotation/animation?
My complete code is here: https://jsfiddle.net/bujowi/j1etgLsp/3/
You find the function called for the animation from line 244 onwards:
function animateRotation(x1, y1, x2, y2, sign)
Any comments and suggestions for improvements are welcome.
In the jsfiddle the animation appears to be rough since the the drag event is fired multiple times. Therefore, the animation runs in parallel multiple times. In the code below this is prevented by the flag is_animated. If true no new animation can be started.
A minor, more JSXGraph-ish issue is that the transformation is added to the JSXGraph elements in every step of the animation in setInterval. This may produce strange effects. It is much more clean to bind the transformation once to an element, but make the transformation dynamic, e.g. dependent on an angle variable:
var angle = 0.03;
let tRot = b1.create('transform', [function() { return sign * angle; }, pivot], { type: 'rotate' });
Additionally, this would make it possible to rotate the polygon back to the zero angle at the end of the animation. So, in setInterval only the angle variable is increased and the board is updated:
let idInterval = setInterval(function() {
angle += 0.03;
b1.update();
// ...
}, refreshTime);
Here is the complete new animation code (without rotating back at the end):
function animateRotation(x1, y1, x2, y2, sign) {
if (this.is_animated) {
return;
}
this.is_animated = true;
// Force arrow and arm (distance from pivot)
let Rot1 = b1.create('point', [x1, y1], { visible: false })
let Rot2 = b1.create('point', [x2, y2], { visible: false })
let rotArrow = b1.create('arrow', [Rot1, Rot2], { strokeColor: '#000', strokeWidth: 3, visible: true })
// Polygon
let PolyCopy = []
for (i = 0; i < poly_coord.length; i++) {
PolyCopy.push(b1.create('point', [poly_coord[i][0], poly_coord[i][1]], at))
}
let polyCopy = b1.create('polygon', PolyCopy, { strokeColor: myBlue, fillColor: myBlue })
// Hide all other force arrows
showAllElements(false)
// Show line of action and distance from pivot for 1s
let lineOfAction = b1.create('line', [[x1, y1], [x2, y2]], { strokeColor: '#000', strokeWidth: 2, dash: 1 })
let perpLine = b1.create('perpendicular', [lineOfAction, Pivot], { withLabel: false, visible: false })
let isecP = b1.create('intersection', [perpLine, lineOfAction, 0], { withLabel: false, visible: false })
let dist = b1.create('segment', [Pivot, isecP], { strokeColor: '#000', strokeWidth: 4 })
var that = this; // make "this" available in the interval function
// This is the animation: rotating the polygon for how many 'frames', refreshTime in ms
setTimeout(function() {
b1.removeObject(lineOfAction)
b1.removeObject(perpLine)
b1.removeObject(isecP)
b1.removeObject(dist)
var angle = 0.03;
let tRot = b1.create('transform', [function() { return sign * angle; }, pivot], { type: 'rotate' });
tRot.bindTo(PolyCopy);
tRot.bindTo([Rot1, Rot2]);
let frames = 0;
const refreshTime = 50;
let idInterval = setInterval(function() {
angle += 0.03;
b1.update();
if (frames++ > 20) {
that.is_animated = false;
b1.removeObject(polyCopy);
b1.removeObject(rotArrow);
showAllElements(true);
clearInterval(idInterval);
}
}, refreshTime)
}, 1000); // time for showing the line of action and distance
}
See it live at https://jsfiddle.net/fbdzx0ht/3/
Best wishes,
Alfred
im trying to put the marker-mid on the line element, im using SVG.js. For example im using this code:
var draw = SVG('yourdivid');
var line = draw.line( 100, 100, 0, 0).move(20, 20);
line.stroke({ color: '#000', width: 2};
now how can i put the marker at the mid of this line using the marker-mid?
i read the doc of svg.js and also this" https://developer.mozilla.org/en-US/docs/Web/SVG/Attribute/marker-mid#Examples"
they say that you can use the marker-mid element also with the line, i know that this marker works with the path element, but i need to use it with a line, can you help me please?
thank you for your help
You could just work out the mid point of the line yourself and draw a circle at that point.
var start = [100, 100];
var end = [0, 0];
function midPoint(start, end) {
return [
(start[0] + end[0]) / 2,
(start[1] + end[1]) / 2
];
}
var lineGroup = svg.group();
lineGroup.line(start[0][0], start[0][1], end[0][0], end[0][1]);
var dot = lineGroup.circle(3);
var mid = midPoint(start, end);
dot.cx(mid[0]);
dot.cy(mid[1]);
marker-mid does not work on lines. To have start and end markers, this code would do it:
var draw = SVG('yourdivid');
draw.line( 100, 100, 0, 0)
.move(20, 20)
.stroke({ color: '#000', width: 2})
.marker('start', 10, 10, function(add){
add.circle(10).center(5,5)
})
However, to have markers at the mid of a line see p0wdr.com's answer
I have this circle object:
function makeCircle(left, top, line1, line2, lineId, stationIndex, stationID) {
var c = new fabric.Circle({
left: left,
top: top,
strokeWidth: 0.2,
radius: 1.5,
fill: '#ffffff',
stroke: '#666',
// isMoving: false,
selectable: true,
});
c.hasControls = c.hasBorders = true;
c.stationID = stationID;
c.stationIndex = stationIndex;
c.line1 = line1;
c.line2 = line2;
return c;
}
I run in a loop , and insert some circles to a group by id of a line.
(every line has id ) :
circleGroup[lineId] = new fabric.Group([],{selectable: false,});
var circle = makeCircle(x, y, null, line, lineId, 0, circle1Id);
circleGroup[lineId].add(circle);
I want when click on edit() function, the circles in circleGroup[lineId] (lineId = 10120 for exemple ) can be selectable.
function edit(lineId) {
circleGroup[lineId].selectable = true;
canvas.renderAll();
}
But not happen nothing. the circles not moving when I click on them and try to move.
what the problem?
I'm not quite sure what you're after. Do you want the circles to become selectable/interactive, or do you want the circle groups to be selectable/interactive?
It looks like your edit function is making the circleGroup[lineID] selectable. Your code above doesn't show the circleGroup being made un-selectable.
If what you want is to make each of the objects in the circleGroup selectable. Try:
function edit(lineId) {
var group = circleGroup[lineId]
for (var i=0; i<group._objects.length; i++){
group._objects[i].selectable = true;
}
canvas.renderAll();
}
Note that when you click on one of your circles it will still be inside a group, so any interaction with the selection (drag, scale, rotate, etc) will be applied to the group.
If you want to make each circle interactive, you'll need to ungroup them first. Try following this: Grouping and Ungrouping Fabric.js objects
I'm trying to implement a button with a certain polygon on it, which when pressed changes the polygon to something else. For example the play icon on the button changing to stop icon. Ideally the icon should be a polygon with three points depicting the play symbol. After animation it turns into a polygon of four points(A square) depicting the stop symbol.
I tried doing it this way:
var paper = Snap('svg');
var tpts = [100,100,100,130,120,115];
var sqpts = [100,100,100,130,130,130,130,100];
var tri = paper.polygon(sqpts);
tri.attr({
id:"tri",
fill:"#555555"
});
sqrFunc = function(){
tri.animate({"points":sqpts},1000,mina.linear);
}
triFunc = function(){
tri.animate({"points":tpts},1000,mina.linear);
}
On clicking the button once I call sqrFunc and on clicking it again I call triFunc. I tried assigning different point arrays to "points" because when I query tri.attr("points") I get the assigned points as return value. However, trying to assign the arrays like this ends up in errors. Any help regarding this problem is greatly appreciated.
I don't get any error with your code, however the tranformation does not happen as wished because the triangle has only 3 points.
I fixed it with
var tpts = [100,100,100,100,100,130,120,115];
try this
var paper = Snap('svg');
var tpts = [100,100,100,100,100,130,120,115];
var sqpts = [100,100,100,130,130,130,130,100];
var tri = paper.polygon(tpts);
tri.attr({
id:"tri",
fill:"#555555"
});
sqrFunc = function(){
tri.animate({"points":sqpts},1000,mina.linear);
}
triFunc = function(){
tri.animate({"points":tpts},1000,mina.linear);
}
setTimeout(sqrFunc, 200);
setTimeout(triFunc, 1200);
another method is to use paths
var paper = Snap('svg');
var tri = paper.path("M 10 10, L 10 50, L 50 25, Z");
tri.attr({
id:"tri",
fill:"#555555"
});
sqrFunc = function(){
tri.animate({d:"M 10 10, L 10 50, L 50 50, L50 10,Z"},1000,mina.linear);
}
triFunc = function(){
tri.animate({d:"M 10 10, L 10 50, L 50 25, Z"},1000,mina.linear);
}
setTimeout(sqrFunc, 200);
setTimeout(triFunc, 1200);