I am looking for a generic piece of code (javascript) that would work with jquery UI to constrain movement(drag) of a div within an triangle.
similar to this (http://stackoverflow.com/questions/8515900/how-to-constrain-movement-within-the-area-of-a-circle) but a triangle, and not a circle.
I would prefer the triangle to be defined as a rapheal svg like this...
(function() {
Raphael.fn.triangle = function (cx, cy, r) {
r *= 1.75;
return this.path("M".concat(cx, ",", cy, "m0-", r * .58, "l", r * .5, ",", r * .87, "-", r, ",0z"));
};
var paper = Raphael(document.getElementById("triangle"), "100%", "100%");
var triangle = paper.triangle(100,100,90);
triangle.attr("fill", "#444444");
triangle.attr("stroke", "#444444");
$( "#draggable" ).draggable({ containment: "#triangle svgnode", scroll: false });
looking forward to solutions.
I would like to note that the draggable element could also be a svg node if that is easier.
See this answer which shows how to constrain a jquery draggable to an arbitrary path.
The trick is to alter the ui.position variables within the drag event, to constrain the movement.
Since none of the given answers actually show how to constrain a draggable to a triangular area, I thought I'd share this jsfiddle which demonstrates an actual working example.
I think the key here is not to focus so much on the "triangle" aspect but more importantly to realize a triangle is a polygon. This allows us to address the issue head-on using many existing algorithms that relate to a point and a polygon.
This 2D Graph library JavaScript 2D Graph Library provides all the tools we need to solve this problem. Mainly, each Shape has an associated function constrain which will constrain a Point to the inner area of a the Shape (the edge included) via a LineSegment that connects to the centroid of the Shape. (It also looks like you can set the center point for the Shape as a second argument which would prove handy for concave Polygon's.)
This jsFiddle Triangle Constraint via jQuery UI Draggable uses the jQuery UI Draggable drag callback in conjunction with the Graph library to do the constraint. It actually uses the coordinates of the SVG Polygon to construct the Graph Polygon only inverting the y-axis to switch between screen and Cartesian coordinates.
The set-up that takes place in document ready is fairly simple:
var points = $('polygon').attr('points').trim().split(' ').map(function(vertex) { var coordinates = vertex.split(','); return new aw.Graph.Point(Number(coordinates[0]), Number(-coordinates[1])); }),
triangle = new aw.Graph.Polygon(points);
$('.map-selector').draggable({
containment: '.map',
drag: function (event, ui) {
var left = ui.position.left, top = -ui.position.top;
var constrained = triangle.constrain(new aw.Graph.Point(left, top));
ui.position.left = constrained.x; ui.position.top = -constrained.y;
}
});
Cheers!
Related
I'm currently making a force network visualization that involves a large number of nodes and edges (over 50k+) using a new library called stardust.js. Stardust uses WebGL to make the rendering of nodes and edges much quicker than Canvas/D3.
However, I am unable to figure out how to add zoom and pan to this visualization.
According to this thread on stardust's google group, the creator of the stardust library mentions that there is no support for zoom and pan right now, but it is possible to implement this by setting in the mark specification by setting zoom and pan specifications as parameters.
import { Circle } from P2D;
mark MyMark(translateX: float, translateY: float, scale: float, x: float, y: float, radius: float) {
Circle(Vector2(x * scale + translateX, y * scale + translateY), radius);
}
https://stackoverflow.com/editing-help
// In the js code you can do something like:
marks.attr("translateX", 10).attr("translateY", 15).attr("scale", 2);
This library uses a kind of Typescript language where one defines "marks" (which is what all the nodes and edges are), and it should be possible to define these marks with the above parameters. But how can one implement this?
Is there an easier way to do this? Can one add a library like Pixi.js on to this visualization to make it zoom and pan?
There is no need to define custom marks (it can be done with custom marks).
The position of the objects is controlled by a Stardust.scale().
var positions = Stardust.array("Vector2")
.value(d => [d.x, d.y])
.data(nodes);
var positionScale = Stardust.scale.custom("array(pos, value)")
.attr("pos", "Vector2Array", positions)
By modifying the value function you can zoom and translate.
By attaching the zoom to the canvas the star dust drag is no longer working. But that is a different problem.
I used the example https://stardustjs.github.io/examples/graph/
In the zoom callback save the zoom parameters and request a new render of the graph.
var fps = new FPS();
var zoom_scale = 1.0, zoom_t_x = 0.0, zoom_t_y = 0.0;
d3.select(canvas).call(d3.zoom().on("zoom", zoomed));
function zoomed() {
zoom_scale = d3.event.transform.k;
zoom_t_x = d3.event.transform.x;
zoom_t_y = d3.event.transform.y;
requestRender();
}
function render() {
positions.value(d => [d.x*zoom_scale + zoom_t_x, d.y*zoom_scale + zoom_t_y]);
......
}
The example contains an error.
When you use a slider the simulation never stops because the alphaTarget is set to 0.3.
force.alphaTarget(0.3).restart();
It should be changed to
force.alpha(0.3).alphaTarget(0).restart();
We have a web application that displays a SVG map of an office. The map has small icons that represent users walking around with RF tags. This allows administrators of the system to see what rooms users are in. We are using Snap.SVG to load the office SVG file and manipulate it to display the user icons. The challenge is that the map scales to the size of the browser. Using JavaScript to determine the coordinates is not always accurate because the position of the SVG changes based on the browser size.
Here is an example of the map with the icons:
The icons are placed on the map based on X Y coordinates coming from our database. The values for the X Y coordinates are set for each location and were determined using Adobe Illustrator. Currently, we can only place one icon in a room at a time. Because we only have 1 set coordinates the icons overlap if more than one person is in a room at one time.
The second phase of this project is to allow users to draw on of the map to specify locations. Essentially, the user will set points and create a polygon to represent each location on the map. We would use the coordinates of the polygon along with the total area of the polygon to know where on the map we can place icons. This would allow users to define areas without a developer getting involved.
Here is an example of what we want to achieve .
I have been researching how to do this, but have not found anything outside of using something like the Google Maps API to draws polygons on a map. I did find this article that outlines how to dynamically pull points. We thought about using a grid system that is an overlay on the map and the user defines what grid elements are in what locations. So something like [A1,A2,B1,B2]. I persoanlly like the polygon approach as it is more visually appealing and is easier for a user to adopt.
We need some advice on where to start with this and if something like snap.svg is all we need or if we have to rely on other libraries in conjunction with snap.
Update:
With Ian's advice I found a fiddle that describes what he was talking about.
var S;
var pt;
var svg
var box;
window.onload = function(){
svg = $('#mysvg')[0];
S = Snap(svg);
console.log( S );
pt = pt = svg.createSVGPoint(); // create the point
// add the rectangle
box = S.rect(12,12, 12, 12);
box.attr({ fill : 'red', stroke : 'none' });
S.drag(
function(dx, dy, posX, posY, e){
//onmove
pt.x = posX - S.node.offsetLeft;
pt.y = posY - S.node.offsetTop;
console.log(pt.x + "," + pt.y);
// convert the mouse X and Y
//so that it's relative to the svg element
var transformed = pt.matrixTransform(svg.getCTM().inverse());
box.attr({ x : transformed.x, y : transformed.y });
},
function(){
//onstart
},
function(){
//onend
}
);
}
The Fiddle
Im trying to create an interactive seating layout like this Seats.io. However I dont need the exact features but just few things such as:
Plotting seats anywhere on the screen
Plotting list of seats from one point to another
Seats hover as circle when plotting from one mouse click point to another
After much research in Jquery and simultaneously on raphaeljs, I have decided to start working with raphaeljs. Im totally new to the vector graphics. So obviously there might be something that I may be missing. I have followed this fiddle to draw a straight line. I have also created another script to plot circles anywhere on the window(the circles will mean seats) following is the script
window.onload = function () {
var height = $(document).outerHeight(true);
var width = $(document).width();
var radius = 10;
var paper = Raphael(0, 0, width, height);
var i = 0;
$(document).click(function (e) {
i = i + 1;
var x = e.pageX;
var y = e.pageY;
var seat = paper.circle(x, y, radius)
.attr({stroke: "none", fill: "#f00", opacity: .4})
.data("i", i);
seat.mouseover(function () {
this.attr("opacity", 1);
});
seat.mouseout(function () {
this.attr("opacity", .4);
});
});
}
using the above script I'm able to plot circles(seats) on my screen. Now based on the fiddle example lines are drawn using 'path', so is it possible to load circles on every path and draw them as sequential line of circles one after the other, or do I have to take any different approach.
Also on a side note is there any opensource project or code for the Seats.io
Any help would be really appreciated
Ben from seats.io here.
http://raphaeljs.com/reference.html#Element.getPointAtLength is indeed what we use. You'll basically need to
calculate a helper path between start and end point. You already have that.
calculate the distance between seats (based on seat size): helperPath.getTotalLength() / (numberOfSeats - 1);
for each seat, call getPointAtLength and draw a circle around that
point: helperPath.getPointAtLength(distanceBetweenSeatsOnHelperPath * i++)
Obviously, it gets more interesting if you want to snap to a grid to align rows, curve rows, etc, but you should be able to get started with the above.
We've adapted Mike Bostock's original D3 + Leaflet example:
http://bost.ocks.org/mike/leaflet/
so that it does not redraw all paths on each zoom in Leaflet.
Our code is here: https://github.com/madeincluj/Leaflet.D3/blob/master/js/leaflet.d3.js
Specifically, the projection from geographical coordinates to pixels happens here:
https://github.com/madeincluj/Leaflet.D3/blob/master/js/leaflet.d3.js#L30-L35
We draw the SVG paths on the first load, then simply scale/translate the SVG to match the map.
This works very well, except for one issue: D3's path resampling, which looks great at the first zoom level, but looks progressively more broken once you start zooming in.
Is there a way to disable the resampling?
As to why we're doing this: We want to draw a lot of shapes (thousands) and redrawing them all on each zoom is impractical.
Edit
After some digging, seems that resampling happens here:
function d3_geo_pathProjectStream(project) {
var resample = d3_geo_resample(function(x, y) {
return project([ x * d3_degrees, y * d3_degrees ]);
});
return function(stream) {
return d3_geo_projectionRadians(resample(stream));
};
}
Is there a way to skip the resampling step?
Edit 2
What a red herring! We had switched back and forth between sending a raw function to d3.geo.path().projection and a d3.geo.transform object, to no avail.
But in fact the problem is with leaflet's latLngToLayerPoint, which (obviously!) rounds point.x & point.y to integers. Which means that the more zoomed out you are when you initialize the SVG rendering, the more precision you will lose.
The solution is to use a custom function like this:
function latLngToPoint(latlng) {
return map.project(latlng)._subtract(map.getPixelOrigin());
};
var t = d3.geo.transform({
point: function(x, y) {
var point = latLngToPoint(new L.LatLng(y, x));
return this.stream.point(point.x, point.y);
}
});
this.path = d3.geo.path().projection(t);
It's similar to leaflet's own latLngToLayerPoint, but without the rounding. (Note that map.getPixelOrigin() is rounded as well, so probably you'll need to rewrite it)
You learn something every day, don't you.
Coincidentally, I updated the tutorial recently to use the new d3.geo.transform feature, which makes it easy to implement a custom geometric transform. In this case the transform uses Leaflet’s built-in projection without any of D3’s advanced cartographic features, thus disabling adaptive resampling.
The new implementation looks like this:
var transform = d3.geo.transform({point: projectPoint}),
path = d3.geo.path().projection(transform);
function projectPoint(x, y) {
var point = map.latLngToLayerPoint(new L.LatLng(y, x));
this.stream.point(point.x, point.y);
}
As before, you can continue to pass a raw projection function to d3.geo.path, but you’ll get adaptive resampling and antimeridian cutting automatically. So to disable those features, you need to define a custom projection, and d3.geo.transform is an easy way to do this for simple point-based transformations.
I'm working on a project that uses SVG with Raphael.js. One component is a group of circles, each of which "wiggles" around randomly - that is, slowly moves along the x and y axes a small amount, and in random directions. Think of it like putting a marble on your palm and shaking your palm around slowly.
Is anyone aware of a Raphael.js plugin or code example that already accomplishes something like this? I'm not terribly particular about the effect - it just needs to be subtle/smooth and continuous.
If I need to create something on my own, do you have any suggestions for how I might go about it? My initial idea is along these lines:
Draw a circle on the canvas.
Start a loop that:
Randomly finds x and y coordinates within some circular boundary anchored on the circle's center point.
Animates the circle from its current location to those coordinates over a random time interval, using in/out easing to smooth the effect.
My concern is that this might look too mechanical - i.e., I assume it will look more like the circle is tracing a star pattern, or having a a seizure, or something like that. Ideally it would curve smoothly through the random points that it generates, but that seems far more complex.
If you can recommend any other code (preferably JavaScript) that I could adapt, that would be great too - e.g., a jQuery plugin or the like. I found one named jquery-wiggle, but that seems to only work along one axis.
Thanks in advance for any advice!
Something like the following could do it:
var paper = Raphael('canvas', 300, 300);
var circle_count = 40;
var wbound = 10; // how far an element can wiggle.
var circleholder = paper.set();
function rdm(from, to){
return Math.floor(Math.random() * (to - from + 1) + from);
}
// add a wiggle method to elements
Raphael.el.wiggle = function() {
var newcx = this.attrs.origCx + rdm(-wbound, wbound);
var newcy = this.attrs.origCy + rdm(-wbound, wbound);
this.animate({cx: newcx, cy: newcy}, 500, '<');
}
// draw our circles
// hackish: setting circle.attrs.origCx
for (var i=0;i<circle_count;i++) {
var cx = rdm(0, 280);
var cy = rdm(0, 280);
var rad = rdm(0, 15);
var circle = paper.circle(cx, cy, rad);
circle.attrs.origCx = cx;
circle.attrs.origCy = cy;
circleholder.push(circle);
}
// loop over all circles and wiggle
function wiggleall() {
for (var i=0;i<circleholder.length;i++) {
circleholder[i].wiggle();
}
}
// call wiggleAll every second
setInterval(function() {wiggleall()}, 1000);
http://jsfiddle.net/UDWW6/1/
Changing the easing, and delays between certain things happening should at least help in making things look a little more natural. Hope that helps.
You can accomplish a similar effect by extending Raphael's default easing formulas:
Raphael.easing_formulas["wiggle"] = function(n) { return Math.random() * 5 };
[shape].animate({transform:"T1,1"}, 500, "wiggle", function(e) {
this.transform("T0,0");
});
Easing functions take a ratio of time elapsed to total time and manipulate it. The returned value is applied to the properties being animated.
This easing function ignores n and returns a random value. You can create any wiggle you like by playing with the return formula.
A callback function is necessary if you want the shape to end up back where it began, since applying a transformation that does not move the shape does not produce an animation. You'll probably have to alter the transformation values.
Hope this is useful!
There is a very good set of easing effects available in Raphael.
Here's a random set of circles that are "given" bounce easing.
Dynamically add animation to objects
The full range of easing effects can be found here. You can play around with them and reference the latest documentation at the same time.
Putting calls in a loop is not the thing to do, though. Use callbacks, which are readily available.