I used to manage object alignments selection on FabricJS with getActiveGroup as below :
canvas.on("selection:created", function(e) {
var activeObj = canvas.getActiveObject();
console.log('e.target.type', e.target.type);
if(activeObj.type === "group") {
console.log("Group created");
var groupWidth = e.target.getWidth();
var groupHeight = e.target.getHeight();
e.target.forEachObject(function(obj) {
var itemWidth = obj.getBoundingRect().width;
var itemHeight = obj.getBoundingRect().height;
$('#objAlignLeft').click(function() {
obj.set({
left: -(groupWidth / 2),
originX: 'left'
});
obj.setCoords();
canvas.renderAll();
});
...
}
});
more detailed here
But now that I use FabricJS 2 and that getActiveObject() has been removed, I don't know what to do. I read on the doc that we could use getActiveObjects(), but it does nothing.
Please how can I reproduce the action of this code with FabricJS 2 (where getActiveGroup isn't supported anymore) ?
Selections of more than one object have the type activeSelection. The group type is only used when you purposefully group multiple objects using new fabric.Group([ obj1, obj2]
When you create a multi-selection using the shift-key as opposed to drawing a selection box, you'll trigger the selection:created event only on the first object selected, while objects added to the selection will trigger the selection:updated event. By calling your alignment code from both the selection:created and selection:updated events you'll make sure that your code is executed every time a multi-selection is created.
Also, you can use getScaledWidth() and getScaledHeight() to get scaled dimensions, or just .width and .height if you just want the unscaled width/height values. Good luck!
canvas.on({
'selection:updated': function() {
manageSelection();
},
'selection:created': function() {
manageSelection();
}
});
function manageSelection() {
var activeObj = canvas.getActiveObject();
console.log('activeObj.type', activeObj.type);
if (activeObj.type === "activeSelection") {
console.log("Group created");
var groupWidth = activeObj.getScaledWidth();
var groupHeight = activeObj.getScaledHeight();
activeObj.forEachObject(function(obj) {
var itemWidth = obj.getBoundingRect().width;
var itemHeight = obj.getBoundingRect().height;
console.log(itemWidth);
$('#objAlignLeft').click(function() {
obj.set({
left: -(groupWidth / 2),
originX: 'left'
});
obj.setCoords();
canvas.renderAll();
});
});
}
}
Related
simple by using rapheal i successfully make animation along path , but i can't reverse the animation direction ,,, just how to make it animate to the other direction when clicking the same path .
var paper = Raphael(0,0,1024,768);
var pathOne = paper.path(['M', 15,15 , 100,75]).attr({'stroke-width':18}).data("id",1);
//and this is just the circle
var circle = paper.circle(0, 0, 13).attr({
fill: '#09c', cursor: 'pointer'
});
//make the path as custom attribute so it can ba accessed
function pathPicker(thatPath){
paper.customAttributes.pathFactor = function(distance) {
var point = thatPath.getPointAtLength(distance * thatPath.getTotalLength());
var dx = point.x,
dy = point.y;
return {
transform: ['t', dx, dy]
};
}
}
//initialize for first move
pathPicker(pathOne);
circle.attr({pathFactor: 0}); // Reset
//Asign first path and first move
function firstMove(){
circle.animate({pathFactor: 1}, 1000});
}
pathOne.click(function(){
firstMove();
});
I couldn't get the original to run, so here is something using the main bits that should suit...
There's not a lot to it, get the length of the path, iterate over the length, draw object at the path. It uses the Raphael customAttributes to be able to animate it. I've added a custom toggle to make it easy to switch between them.
These are the key changes..
var len = pathOne.getTotalLength();
paper.customAttributes.along = function (v) {
var point = pathOne.getPointAtLength(v * len);
return {
transform: "t" + [point.x, point.y] + "r" + point.alpha
};
};
circle.attr({ along: 0 });
function animateThere( val ) {
val = +!val; // toggle
pathOne.click( function() { animateThere( val ) } );
circle.animate({ along: val }, 2000 );
};
pathOne.click( function() { animateThere(0) } );
jsfiddle
For completeness, you may want to do some extra checks like only allow the click if the animation has finished or something, as there may be a problem if you quickly click a lot and it buffering up animations.
I built an app which consumes data from a redis channel(sellers) with socketio and push the data in realtime to the frontend. The dataset could contain up to a thousand rows so I'm thinking about using a datatable to represent the data in a clean way. The table elements will be updated regularly, but there will be no rows to add/remove, only updates.
The problem I'm facing is that I don't know which would be the proper way to implement it due to my inexperience in the visualization ecosystem. I've been toying with d3js but I think It'll be too difficult to have something ready quickly and also tried using the datatables js library but I failed to see how to make the datatable realtime.
This is the code excerpt from the front end:
socket.on('sellers', function(msg){
var seller = $.parseJSON(msg);
var sales = [];
var visits = [];
var conversion = [];
var items = seller['items'];
var data = [];
for(item in items) {
var item_data = items[item];
//data.push(item_data)
data.push([item_data['title'], item_data['today_visits'], item_data['sold_today'], item_data['conversion-rate']]);
}
//oTable.dataTable(data);
$(".chart").html("");
drawBar(data);
});
Using d3 to solve your problem is simple and elegant. I took a little time this morning to create a fiddle that you can adapt to your own needs:
http://jsfiddle.net/CelloG/47nxxhfu/
To use d3, you need to understand how it works with joining the data to the html elements. Check out http://bost.ocks.org/mike/join/ for a brief description by the author.
The code in the fiddle is:
var table = d3.select('#data')
// set up the table header
table.append('thead')
.append('tr')
.selectAll('th')
.data(['Title', 'Visits', 'Sold', 'Conversion Rate'])
.enter()
.append('th')
.text(function (d) { return d })
table.append('tbody')
// set up the data
// note that both the creation of the table AND the update is
// handled by the same code. The code must be run on each time
// the data is changed.
function setupData(data) {
// first, select the table and join the data to its rows
// just in case we have unsorted data, use the item's title
// as a key for mapping data on update
var rows = d3.select('tbody')
.selectAll('tr')
.data(data, function(d) { return d.title })
// if you do end up having variable-length data,
// uncomment this line to remove the old ones.
// rows.exit().remove()
// For new data, we create rows of <tr> containing
// a <td> for each item.
// d3.map().values() converts an object into an array of
// its values
var entertd = rows.enter()
.append('tr')
.selectAll('td')
.data(function(d) { return d3.map(d).values() })
.enter()
.append('td')
entertd.append('div')
entertd.append('span')
// now that all the placeholder tr/td have been created
// and mapped to their data, we populate the <td> with the data.
// First, we split off the individual data for each td.
// d3.map().entries() returns each key: value as an object
// { key: "key", value: value}
// to get a different color for each column, we set a
// class using the attr() function.
// then, we add a div with a fixed height and width
// proportional to the relative size of the value compared
// to all values in the input set.
// This is accomplished with a linear scale (d3.scale.linear)
// that maps the extremes of values to the width of the td,
// which is 100px
// finally, we display the value. For the title entry, the div
// is 0px wide
var td = rows.selectAll('td')
.data(function(d) { return d3.map(d).entries() })
.attr('class', function (d) { return d.key })
// the simple addition of the transition() makes the
// bars update smoothly when the data changes
td.select('div')
.transition()
.duration(800)
.style('width', function(d) {
switch (d.key) {
case 'conversion_rate' :
// percentage scale is static
scale = d3.scale.linear()
.domain([0, 1])
.range([0, 100])
break;
case 'today_visits':
case 'sold_today' :
scale = d3.scale.linear()
.domain(d3.extent(data, function(d1) { return d1[d.key] }))
.range([0, 100])
break;
default:
return '0px'
}
return scale(d.value) + 'px'
})
td.select('span')
.text(function(d) {
if (d.key == 'conversion_rate') {
return Math.round(100*d.value) + '%'
}
return d.value
})
}
setupData(randomizeData())
d3.select('#update')
.on('click', function() {
setupData(randomizeData())
})
// dummy randomized data: use this function for the socketio data
// instead
//
// socket.on('sellers', function(msg){
// setupData(JSON.parse(msg).items)
// })
function randomizeData() {
var ret = []
for (var i = 0; i < 1000; i++) {
ret.push({
title: "Item " + i,
today_visits: Math.round(Math.random() * 300),
sold_today: Math.round(Math.random() * 200),
conversion_rate: Math.random()
})
}
return ret
}
I am trying to create groupe using fabricjs
var group = new fabric.Group(
[ circle, this.callerObject ],
{globalCompositeOperation:'xor'}
);
console.log(group);
this.mainCanvas.add(group);
But which globalCompositeOperation I have not set it is not working it always give the same result. I can make it using clear canvas, but I want to know, can I do it using native fabricjs methods?
I find solution myself)) to make it work need add globalCompositeOperation to the second object.
this.callerObject.set('globalCompositeOperation','xor');
var group = new fabric.Group(
[ circle, this.callerObject ]
);
But it have new problem)) this is work across all images)
To solve problem with cross showing
I have convert group to dataUrl, and to save state of object create new group, with object and image from previous group.
createXorGroup: function(object){
var self = this;
var baseStateTop = this.callerObject.top;
var baseStateLeft = this.callerObject.left;
this.callerObject.set('globalCompositeOperation','xor');
this.callerObject.set('active', false);
var group = new fabric.Group([ object, this.callerObject ]);
fabric.Image.fromURL(
group.toDataURL(),
function(image){
image.setOriginToCenter && image.setOriginToCenter();
self.callerObject.set('globalCompositeOperation','source-over');
self.callerObject.set('opacity', 0);
group = new fabric.Group([ self.callerObject, image ]);
self.mainCanvas.add(group);
group.setOriginToCenter && group.setOriginToCenter();
group.set('top', baseStateTop).set('left', baseStateLeft).setCoords();
group.setCenterToOrigin && group.setCenterToOrigin();
self.mainCanvas.remove(self.callerObject);
group.inCircle = true;
group.set('active', true);
},
{
originX: 'center',
originY: 'center',
top: this.callerObject.top,
left: this.callerObject.left
}
);
}
It is not native fabricjs object, I have override some properties for my work, but I hope you understand the main aim and it will be helpful
Continue to work with this library
To make xor to svg I have write this:
setGlobalCompositeOperation: function(object, type){
if(object.imageType == 'svg'){
for (var i = 0; i < object.paths.length; i++) {
this.setGlobalCompositeOperation(object.paths[i], type);
}
}else{
object.set('globalCompositeOperation', type);
}
}
But this do not work for text in mozila 31.6.0(( I'm looking solution for text
I added new attributes for path svg:
var canvas = new fabric.Canvas('c');
var group = [];
fabric.loadSVGFromURL(svg_file, function(objects,options) {
var obj = fabric.util.groupSVGElements(objects, options);
obj.set({
left: 100,
top: 100
});
canvas.add(obj);
canvas.renderAll();
},
// add new attributes
function(item, object) {
object.set('id',item.getAttribute('id'));
object.set('tag_id', tag_id);
object.set('elem_id', elem_id );
group.push(object);
}
);
And:
// see new attributes
console.log(canvas.getObjects());
// save to JSON
console.log('json:', canvas.toJSON())
But new attributes not save.
Saving JSON in canvas with fabric.js not work for me.
I read Saving JSON in canvas with fabric.js,
it says to create a new class based on fabric.Image, it works for me,
but what to do with fabric.LoadSVGFromURL and fabric.util.groupSVGElements?
Need to create a new class or something else? Help me.
Solved:
https://github.com/kangax/fabric.js/issues/471
http://jsfiddle.net/Ypn2k/4/
You should try to set custom attribute in this way :
svgelement.toObject = (function (toObject) {
return function () {
return fabric.util.object.extend(toObject.call(this), {
"custom_attr_key": "Value",
"custom_attr_key": "Value",
});
};
})(svgelement.toObject);
and also pass this custom attribute key in toJson function like This :
canvas.toJSON(['custom_attr_key','custom_attr_key']);
how can use fabricjs to sync pencilbrush data between a client and b client.
now i can use socket.io do that common shaps but pencilbrush can't do
my example code
client a --send
canvas.on('object:modified', function(options){
console.info("object modified",options)
socket.emit('js_object_redraw', {id:data.target.id,object:data.target});
},false);
client b --recieve
socket.on('client_js_object_redraw', function (data) {
console.info("client_js_object_redraw:",data)
console.info("data.target.type",data.object.type)
var ele = canvas.getObjects()
for (var i=0;i<ele.length;i++) {
var cur_object = ele[i]
if (data.id == cur_object.id){
canvas.remove(cur_object);
draw_object_by_type(data)
}
}
});
function draw_object_by_type(data){
if(data.object.type=="rect"){
draw_rect(data);
}else if(data.object.type=="line"){
draw_line(data);
} else if(data.object.type=="triangle"){
draw_triangle(data);
}
......
}
function draw_rect(data){
var id = data.id;
var object = data.object
var rect = new fabric.Rect({
id:id,
left : object.left,
top :object.top,
width : 60,
height : 70,
scaleX: object.scaleX,
scaleY: object.scaleY,
angle: object.angle,
fill : object.fill
});
canvas.add(rect);
return rect;
}
nodejs server:
socket.on('js_object_redraw', function (data) {
socket.broadcast.emit('client_js_object_redraw',data)
});
=================================================================
pencilBrush
I can do it syc data
like :
when i draw line i emit event to b
but i can't earse the line.
when i clear line
like:
var ctx = canvas.getContext();
ctx.clearRect(x, y, 50, 50);
the next draw sharp ,the have remove line will show the canvas
how can i do that ,sync data between a and b client and can remove the sharp.
thanks,very much
RacerJs is good option for Synchronize data between multiple clients here is complete example https://github.com/codeparty/racer