I am developing a radar which consists of concentric circular sectors using Raphael JS library. I have been able to create these sectors, however, I am having difficulty thinking up a suitable solution of how points (which are basically simple Raphael shapes- circles, triangles, etc) can be placed within each sector.
I am not sure but does a possible solution lie in using the getBBox() for each path? Keeping in mind that the bounding box for circular shapes have points that are not within the shape itself.
Draw a random invisible path inside the region
get a random point inside that path
and draw a random object using that point as center
var radius1 = 80;
var radius2 = 50;
var center = 250;
function circleToPath(c, r, d) {
if(d == 1) {
return "M "+(c-r)+","+c+" q 0,-"+r+" "+r+",-"+r+" "+r+",0 "+r+","+r+" 0,"+r+" -"+r+","+r+" -"+r+",0 "+"-"+r+",-"+r;
} else {
return "M "+(c-r)+","+c+" q 0,"+r+" "+r+","+r+" "+r+",0 "+r+",-"+r+" 0,-"+r+" -"+r+",-"+r+" -"+r+",0 "+"-"+r+","+r;
}
}
region = paper.path(circleToPath(center, radius1, 1) + circleToPath(center, radius2, 0) + "z").attr({fill: "red", "fill-opacity": 0.5,stroke: "none"});
for(i=0;i<5;i++){
randomRadius = Math.floor((Math.random() * (radius1 - radius2)) + radius2);
vir = paper.path(circleToPath(center, randomRadius, 1)).attr({fill: "none", stroke: "none"});
len = vir.getTotalLength();
pointCenter = vir.getPointAtLength(Math.floor(Math.random() * len));
paper.circle(pointCenter.x,pointCenter.y,(Math.floor(Math.random() * 15)) + 5);
}
http://jsfiddle.net/5L9g0xh4/
UPDATE:
A little bit cheating as path intersection exists but is not working well in Raphael
constrain the random point and then rotate each:
http://jsfiddle.net/crockz/opqhas0w/
Related
I am using Fabric js for my project.
I have a use case where I want an object to animate along the boundary of other fabric object. Similar to motion paths in power point. To implement this, I am creating a fabric.Path object and using this path, I am getting all the boundary points of the object and animating the object along these points. The code is as shown below.
<script src="./js/fabric.js"></script>
<canvas
id="c"
width="500"
height="500"
style="border: 1px solid #ccc"
></canvas>
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/1.12.1/jquery.min.js"></script>
<script id="main">
var canvas = new fabric.Canvas("c");
var circle = new fabric.Circle({
radius: 30,
fill: "#f55"
});
canvas.add(circle);
var line = new fabric.Path(
"M 0 0 L 200 100 L 170 200 z",
{
fill: "",
stroke: "black",
objectCaching : true
}
);
line.set({ name: "dummy" });
canvas.add(line);
var points = getPathValues("M 0 0 L 200 100 L 170 200 z", 1000);
function getPathValues(path_val, samples) {
var path = document.createElementNS(
"http://www.w3.org/2000/svg",
"path"
);
path.setAttribute("d", path_val);
var points = [];
var len = path.getTotalLength();
var step = (step = len / samples);
for (var i = 0; i <= len; i += step) {
var p = path.getPointAtLength(i);
points.push(p.x);
points.push(p.y);
}
return points;
}
var i = 0;
var interval = setInterval(function animate() {
i = i + 2;
if (i > points.length) {
// clearInterval(interval);
i = 0;
}
circle.left = line.left + points[i] - circle.radius;
circle.top = line.top + points[i + 1] - circle.radius;
canvas.renderAll();
}, 10);
With all this working well, Now when I scale or change position of the path object, I want to take the changed path, get the updated points and animate the object along those points. Now the problem is that when scale or change the position if the path object, The object.path for it is not getting updated automatically. I am not able to get the change path values which is needed for me to generate boundary points.
Is there any way to get the update path of the Fabric.Path object?
Is there any way to get the path of a normal fabric object?
Indeed the path data is transformed such that it becomes relative to the object's plane.
You should familiarize with relative planes and basics of matrix multiplication, at least the concepts.
This is why scale etc. don't affect it.
You need to apply the object's transformation matrix (via preTransform) to the points.
I can imagine this is too much.
That is why I have exposed fabric.util.sendPointToPlane. Check that out and it will save you a lot of headache.
fabric.util.sendPointToPlane(point, from, to), in your case fabric.util.sendPointToPlane(point, object.calcTransformMatrix(), null) will send the point to the canvas plane.
I wrote a post regarding relative planes but I can't find it, somewhere in fabric discussions
One way to apply links to text is to use the force.links() array for the text elements and centre the text on the midpoint of the link.
I have some nodes with bidirectional links, which I've rendered as paths that bend at their midpoint to ensure it's clear that there's two links between the two nodes.
For these bidirectional links, I want to move the text so that it sits correctly over the bending path.
To do this, I've attempted to calculate the intersection(s) of a circle centred on the centre point of the link and a line running perpendicular to the link that also passes through its centre. I think in principal this makes sense, and it seems to be half working, but I'm not sure how to define which coordinate returned through calculating the intersections to apply to which label, and how to stop them jumping between the curved links when I move the nodes around (see jsfiddle - https://jsfiddle.net/sL3au5fz/6/).
The function for calculating coordinates of text on arcing paths is as follows:
function calcLinkTextCoords(d,coord, calling) {
var x_coord, y_coord;
//find centre point of coords
var cp = [(d.target.x + d.source.x)/2, (d.target.y + d.source.y)/2];
// find perpendicular gradient of line running through coords
var pg = -1 / ((d.target.y - d.source.y)/(d.target.x - d.source.x));
// define radius of circle (distance from centre point text will appear)
var radius = Math.sqrt(Math.pow(d.target.x - d.source.x,2) + Math.pow(d.target.y - d.source.y,2)) / 5 ;
// find x coord where circle with radius 20 centred on d midpoint meets perpendicular line to d.
if (d.target.y < d.source.y) {
x_coord = cp[0] + (radius / Math.sqrt(1 + Math.pow(pg,2)));
} else {
x_coord = cp[0] - (radius / Math.sqrt(1 + Math.pow(pg,2)));
};
// find y coord where x coord is x_text and y coord falls on perpendicular line to d running through midpoint of d
var y_coord = pg * (x_coord - cp[0]) + cp[1];
return (coord == "x" ? x_coord : y_coord);
};
Any help either to fix the above or propose another way to achieve this would be appreciated.
Incidentally I've tried using textPath to line my text up with my links but I don't find that method to be performant when displaying upward of 30-40 nodes and links.
Update: Amended above function and now works as intended. Updated fiddle here:https://jsfiddle.net/o82c2s4x/6/
You can calculate the projection of the chord to x and y axis and add it to the source node coordinates:
function calcLinkTextCoords(d,coord) {
//find chord length
var dx = (d.target.x - d.source.x);
var dy = (d.target.y - d.source.y);
var chord = Math.sqrt(dx*dx + dy*dy);
//Saggita
// since radius is equal to chord
var sag = chord - Math.sqrt(chord*chord - Math.pow(chord/2,2));
//Find the angles
var t1 = Math.atan2(sag, chord/2);
var t2 = Math.atan2(dy,dx);
var teta = t1+t2;
var h = Math.sqrt(sag*sag + Math.pow(chord/2,2));
return ({x: d.source.x + h*Math.cos(teta),y: d.source.y + h*Math.sin(teta)});
};
Here is the updated JsFiddle
The API for Hull Geom states: "Assumes the vertices array is greater than three in length. If vertices is of length <= 3, returns []." (https://github.com/mbostock/d3/wiki/Hull-Geom)
I need to draw convex hulls around 2 nodes. I am using the force layout, so the convex hull needs to be dynamic in that it moves around the nodes if I click a node and drag it around. My code is currently based off of this example: http://bl.ocks.org/donaldh/2920551
For context, this is what I am trying to draw a convex hull around:
Here it works when there are 3 nodes:
Here is what I am trying to draw a convex hull around (doesn't work with the code from the example above because Hull Geom will only take arrays with 3+ vertices):
I understand the traditional use of a convex hull would never involve only two points, but I have tried drawing ellipses, rectangles, etc around the 2 nodes and it doesn't look anywhere near as good as the 3 nodes does.
I understand that Hull Geom ultimately just spits out a string that is used for pathing, so I could probably write a modified version of Hull Geom for 2 nodes.
Any suggestions on how to write a modified Hull Geom for 2 nodes or any general advice to solve my problem is really appreciated.
Basically, you need to at least one fake point very close to the line to achieve the desired result. This can be achieved in the groupPath function.
For d of length 2 you can create a temporary array and attach it to the result of the map function as follows:
var groupPath = function(d) {
var fakePoints = [];
if (d.values.length == 2)
{
//[dx, dy] is the direction vector of the line
var dx = d.values[1].x - d.values[0].x;
var dy = d.values[1].y - d.values[0].y;
//scale it to something very small
dx *= 0.00001; dy *= 0.00001;
//orthogonal directions to a 2D vector [dx, dy] are [dy, -dx] and [-dy, dx]
//take the midpoint [mx, my] of the line and translate it in both directions
var mx = (d.values[0].x + d.values[1].x) * 0.5;
var my = (d.values[0].y + d.values[1].y) * 0.5;
fakePoints = [ [mx + dy, my - dx],
[mx - dy, my + dx]];
//the two additional points will be sufficient for the convex hull algorithm
}
//do not forget to append the fakePoints to the input data
return "M" +
d3.geom.hull(d.values.map(function(i) { return [i.x, i.y]; })
.concat(fakePoints))
.join("L")
+ "Z";
}
Here a fiddle with a working example.
Isolin has a great solution, but it can be simplified. Instead of making the virtual point on the line at the midpoint, it's enough to add the fake points basically on top of an existing point...offset by an imperceptible amount. I adapted Isolin's code to also handle cases of groups with 1 or 2 nodes.
var groupPath = function(d) {
var fakePoints = [];
if (d.length == 1 || d.length == 2) {
fakePoints = [ [d[0].x + 0.001, d[0].y - 0.001],
[d[0].x - 0.001, d[0].y + 0.001],
[d[0].x - 0.001, d[0].y + 0.001]]; }
return "M" + d3.geom.hull(d.map(function(i) { return [i.x, i.y]; })
.concat(fakePoints)) //do not forget to append the fakePoints to the group data
.join("L") + "Z";
};
I want to animate a path (actually a set of paths, but I'll get to that) along a curved path.
RaphaelJS 2 removed the animateAlong method, for reasons I haven't been able to discern. Digging into the Raphael documentation's gears demo as abstracted by Zevan, I have got this far:
//adding a custom attribute to Raphael
(function() {
Raphael.fn.addGuides = function() {
this.ca.guide = function(g) {
return {
guide: g
};
};
this.ca.along = function(percent) {
var g = this.attr("guide");
var len = g.getTotalLength();
var point = g.getPointAtLength(percent * len);
var t = {
transform: "t" + [point.x, point.y]
};
return t;
};
};
})();
var paper = Raphael("container", 600, 600);
paper.addGuides();
// the paths
var circ1 = paper.circle(50, 150, 40);
var circ2 = paper.circle(150, 150, 40);
var circ3 = paper.circle(250, 150, 40);
var circ4 = paper.circle(350, 150, 40);
var arc1 = paper.path("M179,204c22.667-7,37,5,38,9").attr({'stroke-width': '2', 'stroke': 'red'});
// the animation
// works but not at the right place
circ3.attr({guide : arc1, along : 1})
.animate({along : 0}, 2000, "linear");
http://jsfiddle.net/hKGLG/4/
I want the third circle to animate along the red path. It is animating now, but at a distance from the red path equal to the third circle's original coordinates. The weird thing is that this happens whether the transform translate in the along object is relative (lowercase "t") or absolute (uppercase "T"). It also always animates in the same spot, even if I nudge it with a transform translation just before the animate call.
Any help very appreciated. I just got off the boat here in vector-land. Pointers are helpful--a working fiddle is even better.
You're just a hop, skip, and jump away from the functionality that you want. The confusion here concerns the interaction between transformations and object properties -- specifically, that transformations do not modify the original object properties. Translating simply adds to, rather than replaces, the original coordinates of your circles.
The solution is extremely straightforward. In your along method:
this.ca.along = function(percent) {
var box = this.getBBox( false ); // determine the fundamental location of the object before transformation occurs
var g = this.attr("guide");
var len = g.getTotalLength();
var point = g.getPointAtLength(percent * len);
var t = {
transform: "...T" + [point.x - ( box.x + ( box.width / 2 ) ), point.y - ( box.y + ( box.height / 2 ) )] // subtract the center coordinates of the object from the translation offset at this point in the guide.
};
return t;
Obviously, there's some room for optimization here (i.e., it might make sense to create all your circles at 0,0 and then translate them to the display coordinates you want, avoiding a lot of iterative math). But it's functional... see here.
One other caveat: the ...T translation won't effect any other transforms that have already been applied to a given circle. This implementation is not guaranteed to play nicely with other transforms.
I am a newbie to Raphael lib, my question is following:
can I use path specific methods (like getTotalLength() or getPointAtLength()) for a circle element - this would be quite helpfull (and at the begining I thought that circle somehow inherits from path - so that should be possible... but it simply does not work :( ), ie.
var cir = paper.circle(100, 100, 20);
var totalength=cir.getTotalLength();
paper.text(50,150,'Length=('+totalength+')',20);
var pt = cir.getPointAtLength(0);
paper.text(50,250,'Point=('+pt.x+','+pt.y+')',20);
thanks for any comments/hints/explanations on that,
Borys
Sadly you cannot. Circle is its own svg element. It wouldn't be too hard to write some functions that replicate these path-specific actions:
getTotalLength:
2*pi*radius
getPointAtLength:
You'd have to figure out where the circle's path 'starts', but with that set it's something like:
rad = (length / total_length) * 2*pi
y = center_y + (sin(rad) * radius)
x = center_x + (cos(rad) * radius)