How can I get canvas-relative position (top, left) of triangle inside an group as bellow image?
I followed this topic: How to get the canvas-relative position of an object that is in a group? but it only right when group is not rotated.
Working example you may find here: http://jsfiddle.net/mmalex/2rsevdLa/
Fabricjs provides a comprehensive explanation of how transformations are applied to the objects: http://fabricjs.com/using-transformations
Quick answer: the coordinates of an object inside the group is a point [0,0] transformed exactly how the object in the group was transformed.
Follow my comments in code to get the idea.
// 1. arrange canvas layout with group of two rectangles
var canvas = new fabric.Canvas(document.getElementById('c'));
var rect = new fabric.Rect({
width: 100,
height: 100,
left: 50,
top: 50,
fill: 'rgba(255,0,0,0.25)'
});
var smallRect = new fabric.Rect({
width: 12,
height: 12,
left: 150 - 12,
top: 50 + 50 - 12 / 2 - 10,
fill: 'rgba(250,250,0,0.5)'
});
// 2. add a position marker (red dot) for visibility and debug reasons
var refRect = new fabric.Rect({
width: 3,
height: 3,
left: 100,
top: 100,
fill: 'rgba(255,0,0,0.75)'
});
var group = new fabric.Group([rect, smallRect], {
originX: 'center',
originY: 'center'
});
canvas.add(group);
canvas.add(refRect);
canvas.renderAll();
// 3. calculate coordinates of child object in canvas space coords
function getCoords() {
// get transformation matrixes for object and group individually
var mGroup = group.calcTransformMatrix(true);
// flag true means that we need local transformation for the object,
// i.e. how object is positioned INSIDE the group
var mObject = smallRect.calcTransformMatrix(true);
console.log("group: ", fabric.util.qrDecompose(mGroup));
console.log("rect: ", fabric.util.qrDecompose(mObject));
// get total transformattions that were applied to the child object,
// the child is transformed in following order:
// canvas zoom and pan => group transformation => nested object => nested object => etc...
// for simplicity, ignore canvas zoom and pan
var mTotal = fabric.util.multiplyTransformMatrices(mGroup, mObject);
console.log("total: ", fabric.util.qrDecompose(mTotal));
// just apply transforms to origin to get what we want
var c = new fabric.Point(0, 0);
var p = fabric.util.transformPoint(c, mTotal);
console.log("coords: ", p);
document.getElementById("output").innerHTML = "Coords: " + JSON.stringify(p);
// do some chores, place red point
refRect.left = p.x - 3 / 2;
refRect.top = p.y - 3 / 2;
canvas.bringToFront(refRect);
canvas.renderAll();
}
a very simple way to get topleft is
var cords = object._getLeftTopCoords();
cords.x and cord.y will give you the result
Related
There are dozen of topics on Stackoverflow on this problem, none of them shows a solution. So the problem is painful for many.
The problem: when I move a path, it's BoundingRect is not moving.
Possible solutions:
the creators of the library recommend _setPath method, but there are no examples, I can't understand how to use it;
Somehow to set Left, Top, Width and Height separately to the bounding rect (pathLine.getBoundingRect()). I don't know how, it's not working with Set().
To set Left, Top, Width and Height to the path. If I do so, the bounding rect is correct, but pathline is also shifting. I can't make it work.
delete and add path from canvas every time it moves. Bad solution :(
Remarkable that _calcDimensions() works correct with path coords.
Which means there are methods that work with path, we just need to find a proper one.
var pathLine = new fabric.Path('M 10 10 L 10 100 L 100 100', {
fill: '',
stroke: 'black',
objectCaching: false
})
var rect = new fabric.Rect({
left: 100,
top: 100,
fill: 'grey',
width: 50,
height: 50,
});
function createCanvas(id){
canvas = new fabric.Canvas(id);
canvas.add(pathLine);
canvas.add(rect);
pathLine.perPixelTargetFind = true;
canvas.on('object:moving', function(e) {
rect.setCoords();
const { width, height, left, top } = pathLine._calcDimensions();
var bound = pathLine.getBoundingRect();
pathLine.path[2][1] = rect.left ;
pathLine.path[2][2] = rect.top ;
canvas.renderAll();
});
return canvas;
}
Any ideas?
My gpx file already contains elevation information for each trkpt and I can augment this with a speed for each trkpt. I would like to represent the elevation or the speed at each trkpt by varying the color of the track. For instance: slow is blue, fast is red.
How can I do this?
And this probably means: Which files and functions in Openlayers do I have to change to do this?
You can try the ol/style/FlowLine of ol-ext to achieve this.
Using this style, you can change the with/color of the feature along the line using a function. This example show how to: http://viglino.github.io/ol-ext/examples/style/map.style.flowline2.html.
You just have to calculate the width (or color) along the feature geometry varying according the speed or altitude:
const flowStyle = new ol.style.FlowLine({
width: function(f, step) {
// calculate the with of the feature f at the given step
// step is the curvilinear abscissa between 0,1
// (0: first coordinate, 1: last one)
const width = ...
return width;
}
});
#+
You should go with a stylefunction for the vector layer:
https://openlayers.org/en/v4.6.5/apidoc/ol.html#.StyleFunction
This function is checked for each feature to be displayed on the vector layer and the related style can be set/returned programmatically. For example:
function gpxStyle(feature) {
var style = null;
if (feature.get("speed")>="100") {
style = new ol.style.Style({
image: new ol.style.Circle({
radius: 6,
stroke: new ol.style.Stroke({
color: 'red',
width: 2
}),
fill: new ol.style.Fill({
color: 'red'
})
})
});
}
else {
style = new ol.style.Style({
image: new ol.style.Circle({
radius: 6,
stroke: new ol.style.Stroke({
color: 'blue',
width: 2
}),
fill: new ol.style.Fill({
color: 'blue'
})
})
});
}
return [style];
}
var gpxLayer = new ol.layer.Vector({
source: new ol.source.Vector(),
style: gpxStyle
});
I need to programmaticaly resize a group and it's contained elements on a fabric canvas.
By default, fabric applies scaling to objects. I can get around this easily enough by using the scalex & scaley to calculate the new height and width then I set them back to 1. This works fine for the group but I cant figure out how to set the new size for the objects contained in this group.
Eg. Before resize:
After resize:
My (typescript) code is like:
redraw() {
this.shapeGroup.set({
scaleX: 1,
scaleY: 1,
left: this.shape.origin.x,
top: this.shape.origin.y,
width: this.shape.extent.width + this.shape.line.lineWidth,
height: this.shape.extent.height + this.shape.line.lineWidth,
dirty: true
});
// const ac = this.shapeGroup.calcCoords();
this.shapeElement.set({
rx: this.shape.cornerRadius,
ry: this.shape.cornerRadius,
width: this.shape.extent.width,
height: this.shape.extent.height,
dirty: true
});
// this.shapeElement.setCoords(ac);
if (this.shape.text) {
this.textElement.set({
width: this.shapeElement.width - (this.cornerRadius * 2),
height: this.shapeElement.height - (this.cornerRadius * 2)
});
}
this.canvas.fabric.renderAll();
}
(this.shape is the underlying model of the object which is represented by 1 or more fabric objects in a group).
Has anyone done anything like this with success?
I used a similar code to resize the group items, so maybe this can lead you to a solution.
canvas.setActiveGroup(this.shapeGroup.setCoords());
var objs = canvas.getActiveGroup().getObjects();
for(i in objs){
objs[i].set({
...
});
}
I'm developing a diagram tool based on fabricjs. Our tool has our own collection of shape, which is svg based. My problem is when I scale the object, the border (stroke) scale as well. My question is: How can I scale the object but keep the stroke width fixed. Please check the attachments.
Thank you very much!
Here is an easy example where on scale of an object we keep a reference to the original stroke and calculate a new stroke based on the scale.
var canvas = new fabric.Canvas('c', { selection: false, preserveObjectStacking:true });
window.canvas = canvas;
canvas.add(new fabric.Rect({
left: 100,
top: 100,
width: 50,
height: 50,
fill: '#faa',
originX: 'left',
originY: 'top',
stroke: "#000",
strokeWidth: 1,
centeredRotation: true
}));
canvas.on('object:scaling', (e) => {
var o = e.target;
if (!o.strokeWidthUnscaled && o.strokeWidth) {
o.strokeWidthUnscaled = o.strokeWidth;
}
if (o.strokeWidthUnscaled) {
o.strokeWidth = o.strokeWidthUnscaled / o.scaleX;
}
})
canvas {
border: 1px solid #ccc;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/fabric.js/1.6.4/fabric.min.js"></script>
<canvas id="c" width="600" height="600"></canvas>
There is a property called: strokeUniform
Use it like this
shape.set({stroke: '#f55b76', strokeWidth:2, strokeUniform: true })
I have found what feels like an even better solution, works really well with SVG paths.
You can override fabricjs' _renderStroke method and add ctx.scale(1 / this.scaleX, 1 / this.scaleY); before ctx.stroke(); as shown below.
fabric.Object.prototype._renderStroke = function(ctx) {
if (!this.stroke || this.strokeWidth === 0) {
return;
}
if (this.shadow && !this.shadow.affectStroke) {
this._removeShadow(ctx);
}
ctx.save();
ctx.scale(1 / this.scaleX, 1 / this.scaleY);
this._setLineDash(ctx, this.strokeDashArray, this._renderDashedStroke);
this._applyPatternGradientTransform(ctx, this.stroke);
ctx.stroke();
ctx.restore();
};
You may also need to override fabric.Object.prototype._getTransformedDimensions to adjust the bounding box to account for the difference in size.
Also a more complete implementation would probably add a fabric object property to conditionally control this change for both overridden methods.
Another way is to draw a new object on scaled and remove the scaled one.
object.on({
scaled: function()
{
// store new widht and height
var new_width = this.getScaledWidth();
var new_height = this.getScaledHeight();
// remove object from canvas
canvas.remove(this);
// add new object with same size and original options like strokeWidth
canvas.add(new ...);
}
});
Works perfect for me.
I have a canvas built using fabricJS with the dimension of 600x500. I have added an image to this canvas which is of size 200x300 and also a text element just below it.
$canvasObj.toDataURL();
exports the whole canvas area including the white spaces surrounding the design on the canvas.
Is there a way to get the cropped output of the design on the canvas alone instead of all the whitespace?
This can be done by cloning objects to a group, getting the group boundingRect, and then passing the boundingRect parameters to toDataUrl() function (see fiddle).
e.g.
// make a new group
var myGroup = new fabric.Group();
canvas.add(myGroup);
// ensure originX/Y 'center' is being used, as text uses left/top by default.
myGroup.set({ originX: 'center', originY: 'center' });
// put canvas things in new group
var i = canvas.getObjects().length;
while (i--) {
var objType = canvas.item(i).get('type');
if (objType==="image" || objType==="text" || objType==="itext" || objType==="rect") {
var clone = fabric.util.object.clone(canvas.item(i));
myGroup.addWithUpdate(clone).setCoords();
// remove original lone object
canvas.remove(canvas.item(i));
}
}
canvas.renderAll();
// get bounding rect for new group
var i = canvas.getObjects().length;
while (i--) {
var objType = canvas.item(i).get('type');
if (objType==="group") {
var br = canvas.item(i).getBoundingRect();
}
}
fabric.log('cropped png dataURL: ', canvas.toDataURL({
format: 'png',
left: br.left,
top: br.top,
width: br.width,
height: br.height
}));
p.s. I should probably mention that i've not worked with image types, so i just guessed that it's called 'image'..