I'm working on a project that requires groups of objects. I want to add something to a group after the group has already been drawn on the canvas. I put the gist of what I want to accomplish in the following jsfiddle: http://jsfiddle.net/85x3hzx7/2/
First off, I draw a grid in order to help visualize the position. I added the following line in order to have an origin that starts at the top left:
fabric.Object.prototype.originX = true; fabric.Object.prototype.originY = true;
Which is a line suggested by the creator of the library in order to use inheritance to set the origin to the top left for each object (see: Canvas coordinates in Fabric.js have offset). Leaving out this line of code gives a funky result. In the jsfiddle I added multiple blocks of code in order to try and accomplish my goal:
In code block 1, I add a Rectangle in a group and position that group to (100,100). This works nicely as expected:
In code block 2, I define a Circle and add it to the group. This results in some funky positioning and changing of the dimensions of the group:
Setting the circle to position (0,0) places it in what I think is the center of the previous bounding box, which doesn't make much sense to me since my origins are defined as top/left:
How do I position the circle so that it is in the top left corner of the rectangle AFTER having already created the group? There might be a bug at play here, or perhaps I'm not grasping the concept of positioning inside groups, and what adding something to a group does to the position/dimensions.
If you change the top and left coordinates of the circle as described below, it will position itself at the coordinates you desire.
var circle = new fabric.Circle({
radius: 10,
fill: 'red',
originX: 'left',
originY: 'top',
left:group.left-(group.width/2),
top:group.top-(group.height/2)
});
I am not too sure why I have to subtract half of group width and height from group's left and top. Here's your fiddle edited: http://jsfiddle.net/85x3hzx7/5/
Although, if you use the latest version of Fabric.js, you just have to set the left and top coordinates of new object to be added, to group's left and top coordinates. You can take a look at your code with latest version here: http://jsfiddle.net/rpko8z0r/3/
Please note that in new Fabric.js versions, default origin is set to top-left. So you don't need to set every object's origin to top-left, also following line of code is not needed.
fabric.Object.prototype.originX = true;
fabric.Object.prototype.originY = true;
Related
My question is regarding how to center multiple elements in one node after line wrapping. I'm utilizing code example given here on word wrapping: http://bl.ocks.org/mbostock/7555321 to create multiple tspan objects.
After creating/drawing all the tspan objects, I define the location of the X and Y. First I get the text area of the entire tspan and create a box around it:
var tspanbbox = d3.select(this).select("tspan").node().getBBox();
var node_bbox = {"height": tspanbbox.height+5, "width": tspanbbox.width+5};
var rect = d3.select(this).select('rect');
rect.attr("x", -node_bbox.width/2).attr("y", -node_bbox.height/2)
rect.attr("width", node_bbox.width).attr("height", node_bbox.height+10);
and then I added the attribute for the tspan, the text that's within rect.
tspan.attr("x", -node_bbox.x).attr("y", -node_bbox.y);
This code correctly prints a box around the area around the text, but there's overflowing text.
http://imgur.com/zxiRmxR
So what I'm trying to do is to center the group as an entity rather than the first tspan (what I'm assuming is the first line after the GO term).
If I try to alter the tspan attribute (as shown in the code above), only one of the tspan objects move. If I do it with all of them using selectAll("tspan"), all of them the tspans are grouped on the same y axis.
Is there any way of going about this properly?
If anyone else is running into this issue, you can check the number of lines inputted total and make adjustments on the y attribute based on how many lines are implemented (so it's a little manual and archaic but it works).
I have very little experience programming with graphics objects. I am currently tasked with exporting a document (.tiff image) with redacted annotations. The redacted annotation is just a black rectangle object. I am able to get the x coordinates, y coordinates, width and height properties through the .XMP data. There is also a property called rotatation. This is where I am getting stuck, applying the rotation.
So, imagine a document with a redaction on it blacking out the first paragraph. Then, using a tool in the editor the user rotates the document so that it is now laying on it's side. The client is able to render the redaction correctly because we are using the Atalasoft controls to get and display annotations. Now we have a web service that will go and retrieve that image with redactions. We are not able to use the Atalasoft controls in this service due to licensing issues so we just extract the .XMP data from the .tiff image and manually draw the redactions. The problem is, if the user rotates the document when the redaction is already on the document I am having a hard time getting the redaction to rotate correctly (due to my lack of knowledge on graphics programming). If I do not apply any rotation, the redaction is displayed where it was BEFORE the document had been rotated, thus redacting the wrong area of the document.
Here is what I have tried:
Dim rectangle As New Rectangle(xCoordinate, yCoordinate, width, height)
graphics.RotateTransform(rotation)
graphics.FillRectangle(Brushes.Black, rectangle)
When I do this, the redaction does not show up at all on the final document. I have read that I may need to call the following before applying the rotation:
graphics.TranslateTransform(x,y)
But I have no idea what I should be passing in as x and y. It seems like I just need to get the rotation to apply from the upper left corner of the rectangle, but I have yet to figure out a way to properly do this.
Thank you so much for any help or pushes in the right direction!
EDIT 1:
I have also tried this (taken from How can I rotate an RectangleF at a specific degree using Graphics object?).
Dim rectangle As New Rectangle(xCoordinate, yCoordinate, width, height)
Using rotationMatrix As New Matrix
rotationMatrix.RotateAt(rotation, New PointF(rectangle.Left + (rectangle.Width / 2), rectangle.Top + (rectangle.Height / 2)))
graphics.Transform = rotationMatrix
graphics.FillRectangle(Brushes.Black, rectangle)
graphics.ResetTransform()
End Using
Which does rotate the rectangle, but it ends up in the wrong spot so it is not redacting the correct portion of the document. Once again, when I display the document without any rotation transform, it looks like the redaction simply needs to be rotated using the upper left corner as an axis but I'm not quite sure how to accomplish that.
Figured it out. Here is how I am rotating a rectangle using the upper left-hand corner as the axis:
Dim rectangle As New Rectangle(xCoordinate, yCoordinate, width, height)
Using rotationMatrix As New Matrix
rotationMatrix.RotateAt(rotation, New PointF(rectangle.Left, rectangle.Top))
graphics.Transform = rotationMatrix
graphics.FillRectangle(Brushes.Black, rectangle)
graphics.ResetTransform()
End Using
I'm using Snap SVG to manipulate SVGs within a web app that I'm making. In this web app I have two rectangles that start out with one being inside of the other, call them rectInner and rectOuter. The aim is to allow the user to transform rectOuter (scale, rotate, translate) such that rectInner is always strictly inside of rectOuter. To be clear, rectInner will never move or be transformed.
My approach to this problem is to get the bounding box of both rectInner and rectOuter, and check to see if the first is strictly contained within the second. Snap SVG provides a function isBBoxIntersect(rectInner, rectOuter), but it only tells me if parts of the bounding boxes intersect, not if one is contained within the other.
Is there a simple way of doing this?
EDIT:
It seems now that I somewhat misunderstood the concept of bounding boxes, but the problem should be simpler. If I can find a way of calculating the four vertices of rectOuter after all of the transformations, then so long as the corners of rectInner are inside the of the path constructed from those vertices, the entire rectangle is. I think.
##### coffeescript
el = Snap('rect#outer')
mat = el.attr('transform').totalMatrix
left = +el.attr('x')
top = +el.attr('y')
right = left + (+el.attr('width'))
bottom = top + (+el.attr('height'))
console.log(left, top, right, bottom)
points = {
x: mat.x(left, top)
y: mat.y(left, top)
x2:mat.x(right, top)
y2:mat.y(right, top)
x3:mat.x(right, bottom)
y3:mat.y(right, bottom )
x4:mat.x(left, bottom)
y4:mat.y(left, bottom)
}
use matrix!
you can find more matrix in mat variable.
if totalMatrix didn't work then try another.
I'm trying to rotate and scale shapes within an SVG around their center point. I've looked into several libraries, including Jquery, Greensock, D3, RaphaelJS, but I haven't been able to find any that provide a straightforward way to accomplish this. Each animates the shape from the origin point (which I understand is the default). I want to be able to spin a shape around its center point or scale it up or down from the center point.
Here are a couple examples using Greensock and D3 that illustrate the default behavior: http://jsbin.com/AHEXiPa/1/edit?html,js,output
Each of these examples bounce in and out from the top left as opposed to remaining stationary and expanding from the center of the triangle out in all directions.
Can one of the libraries I mentioned accomplish this, or is there another library or method I should consider?
Ideally, I need to be able to apply the animation/transform to an existing object in the DOM. D3 is good at this for instance, but Raphael seems to require converting an SVG to Raphael first prior to injecting it into the DOM.
Really its a case of pick the library that suits your needs, and then you will figure a way. As BigBadaboom says, if you do a search, there are lots of solutions.
To try and combine your questions, as sometimes the tricky bit is using an existing DOM object, I've included an example in Snap.svg. You can often do something similar in most libraries.
jsfiddle here Fiddle using your existing html.
s = Snap("#mySVGContainer1"); // create a canvas from existing svg
var triangle1 = s.select("#myShape1").transform("r90"); //select&transform existing object
p = Snap("#mySVGContainer2");
var triangle2 = p.select("#myShape2");
var bbox = triangle2.getBBox(); //bounding box, centre cx/cy
//rotate and scale with transform string (raphael/snap format)
triangle2.animate({ transform: "r180," + bbox.cx + ',' + bbox.cy + "s3,3," + bbox.cx + "," + bbox.cy }, 2000);
For rotations, as #Ian points out, you can specify the center of rotation. For other transformations, changes are defined relative to the path's (0,0) point.
The easiest way to get transformations to work relative to the path's center is to either:
Define the path so that it is centered around the (0,0) point; or
Wrap the path in a <g> element, and then translate it so it is centered on the (0,0) point of the <g> element's coordinate system.
Then, you can apply rotations, scales and transforms (on the <g> element, if using) and they will all be nicely centred.
The trickiest part is figuring out the "center" of an arbitrary shape. #Ian's approach of using the center of the bounding box will usually give decent results. If your shape is a polygon there are d3 functions you could use.
Example showing a shape moving with the mouse, rotating and changing scale, all centered around the center of the bounding box:
http://fiddle.jshell.net/LgfE3/
Edit: simplier jsfiddle
I've been looking for a long time, and will settle for the following.
1. Design your svg shape at coordinate x:0,y:0.
2. Identify by hand the center of rotation, by example, center = [ x:50,y:100].
3. Build a spinIt() function such :
function spinIt() {
needle.transition()
.duration(2000)
.attrTween("transform", tween);
function tween() {
return d3.interpolateString("rotate(-180, 50, 100)", "rotate(90, 50, 100)");
}
}
4. Use it on a triger:
svg.on("click", spinIt);
http://jsfiddle.net/SHF2M/79/
I have a function that adds an imageOverlay and a semitransparent Rectangle on top of that image (so as to tint the image, and draw a keyline around it).
activeUserImage = new L.imageOverlay(imageUrl, imageBounds).addTo(map);
activeUserTile = new L.rectangle(imageBounds, {stroke: true, color: "#ffffff", opacity:1, weight: 1, fillColor: "#003572", fillOpacity: 0.7, clickable:true}).addTo(map);
this works great, but then I want to remove the image and rectangle with:
map.removeLayer(activeUserImage);
map.removeLayer(activeUserTile);
This seems to work well...
However when I try and add a second Image & Rectangle (using the same function) the rectangle SVG is being rendered underneath the image, so I don't see the colored overlay.
This seems to be because the element is being left behind from the first creation, and then when the image is being added a second time it appears in front of the SVG.
Q:
Is this a bug? Should the SVG element not be cleared too?
Can I adjust z-index of the image or SVG on creation?
should i be containing to rectangle in a different layer to the images? How?
Many Thanks
OK, so the Leaflet bringToFront() method didn't work, but instead I have used a bit of JQuery to force the same approach.
svgObj = $('.leaflet-overlay-pane svg');
svgObj.css('z-index', 9999);
This works, but still feels like a hack... however if (?) there is a bug in LEaflet, then maybe this will have to do???
Any better ideas?
The bringToFront() function alows you to bring layer to the top.
Search it in the docs.