Can't manipulate grouped objects in fabric.js after loading from JSON - fabricjs

I'm trying to implement load/save functionality in a fabric.js project (using fabric.js 5.2.1). The canvas reloads correctly, but I can no longer interact with objects in groups. Non-grouped objects are fine, but pretty much everything in my project is in a group.
<input type="button" id="loadJSON" value="Load JSON" />
<input type="button" id="colorize2" value="Background" />
<input type="button" id="colorize3" value="Square" />
<input type="button" id="colorize" value="Line" />
<canvas id="below" width="960" height="448" style="position:absolute; top:10; left:10;"></canvas>
var jsonString = '{"version":"5.2.1","objects":[{"type":"rect","version":"5.2.1","originX":"left","originY":"top","left":100,"top":100,"width":100,"height":100,"fill":"orange","stroke":null,"strokeWidth":1,"strokeDashArray":null,"strokeLineCap":"butt","strokeDashOffset":0,"strokeLineJoin":"miter","strokeUniform":false,"strokeMiterLimit":4,"scaleX":1,"scaleY":1,"angle":0,"flipX":false,"flipY":false,"opacity":1,"shadow":null,"visible":true,"backgroundColor":"","fillRule":"nonzero","paintFirst":"fill","globalCompositeOperation":"source-over","skewX":0,"skewY":0,"id":"square","rx":0,"ry":0,"selectable":true,"perPixelTargetFind":false,"centeredScaling":false,"centeredRotation":true,"borderColor":"rgb(178,204,255)","cornerColor":"rgb(178,204,255)","cornerSize":13,"transparentCorners":true},{"type":"group","version":"5.2.1","originX":"left","originY":"top","left":477.7,"top":25.62,"width":5.25,"height":396.17,"fill":"rgb(0,0,0)","stroke":null,"strokeWidth":0,"strokeDashArray":null,"strokeLineCap":"butt","strokeDashOffset":0,"strokeLineJoin":"miter","strokeUniform":false,"strokeMiterLimit":4,"scaleX":1,"scaleY":1,"angle":0,"flipX":false,"flipY":false,"opacity":1,"shadow":null,"visible":true,"backgroundColor":"","fillRule":"nonzero","paintFirst":"fill","globalCompositeOperation":"source-over","skewX":0,"skewY":0,"id":"ElementGroup","selectable":false,"perPixelTargetFind":false,"centeredScaling":false,"centeredRotation":true,"borderColor":"rgb(178,204,255)","cornerColor":"rgb(178,204,255)","cornerSize":13,"transparentCorners":true,"objects":[{"type":"path","version":"5.2.1","originX":"left","originY":"top","left":-2.63,"top":-198.09,"width":4.25,"height":395.17,"fill":"yellow","stroke":null,"strokeWidth":1,"strokeDashArray":null,"strokeLineCap":"butt","strokeDashOffset":0,"strokeLineJoin":"miter","strokeUniform":false,"strokeMiterLimit":4,"scaleX":1,"scaleY":1,"angle":0,"flipX":false,"flipY":false,"opacity":1,"shadow":null,"visible":true,"backgroundColor":"","fillRule":"nonzero","paintFirst":"fill","globalCompositeOperation":"source-over","skewX":0,"skewY":0,"id":"centerLine01","selectable":false,"perPixelTargetFind":false,"centeredScaling":false,"centeredRotation":true,"borderColor":"rgb(178,204,255)","cornerColor":"rgb(178,204,255)","cornerSize":13,"transparentCorners":true,"path":[["M",478.19727,26.119141],["L",478.19727,421.29297],["L",482.45117,421.29297],["L",482.45117,26.119141],["L",478.19727,26.119141],["z"]]}]}],"background":"green","perPixelTargetFind":false,"centeredScaling":false,"centeredRotation":false,"backgroundColor":"blue"}'
var below = new fabric.Canvas('below', {
enableRetinaScaling: false,
preserveObjectStacking: true,
backgroundColor: 'green'
});
rect = new fabric.Rect({
top: 100,
left: 100,
width: 100,
height: 100,
fill: 'blue',
id: 'square'
})
centerLine01 = new fabric.Path("M 478.19727 26.119141 L 478.19727 421.29297 L 482.45117 421.29297 L 482.45117 26.119141 L 478.19727 26.119141 z", {
fill: 'red',
id: 'centerLine01',
visible: true,
selectable: false,
evented: false
});
ElementGroup = new fabric.Group([centerLine01], {
id: 'ElementGroup',
visible: true,
selectable: false,
evented: false
})
below.add(rect)
below.add(ElementGroup)
below.renderAll()
$("#loadJSON").on("click", function(e) {
below.loadFromJSON(jsonString, below.renderAll.bind(below), function(o, object) {
below.add(object)
});
below.renderAll.bind(below)
console.log("JSON loaded!")
})
$("#colorize").on("click", function(e) {
const randomColor = "#" + Math.floor(Math.random() * 16777215).toString(16)
ElementGroup.getObjects().forEach(function(o) {
if (o.id == "centerLine01") {
o.set("fill", randomColor)
below.renderAll()
}
})
})
$("#colorize2").on("click", function(e) {
const randomColor = "#" + Math.floor(Math.random() * 16777215).toString(16)
below.setBackgroundColor(randomColor, below.renderAll.bind(below))
})
$("#colorize3").on("click", function(e) {
const randomColor = "#" + Math.floor(Math.random() * 16777215).toString(16)
below.getObjects().forEach(function(o) {
if (o.id == "square") {
o.set("fill", randomColor)
below.renderAll()
}
})
})
In this sample, the Background, Square and Line buttons all work out of the gate, but as soon as you reload the canvas from JSON the Line button no longer does anything, even though the object is still getting found (a simple console.log() verifies that). Not quite sure what the issue is, though.
jsfiddle here: https://jsfiddle.net/eriqjaffe/jo25d8qs/8/

Related

Updating font size from an input to fabric js canvas generated text

i want to update the font size from this
<input value="1" id="fontSize" type="text">
input to
$("#fontSize").on("change", () => {
let fontsize = document.querySelector("#fontSize");
sizeFromInput = fontsize.value;
canvas.getActiveObject().set("fontSize", sizeFromInput);
canvas.renderAll();
});
this is my text generation function in fabric js
$("#typeTool").click(() => {
var textEditable = new fabric.Textbox("ASR Textbox", {
width: 500,
editable: true,
fontFamily: "Delicious_500",
left: 100,
top: 100,
fontSize: 18,
fill: "#000",
});
canvas.add(textEditable);
});
I think your code is working as expected.
Maybe you could use the input event for an instant update as the change event fires in most browsers when content is changed and the element loses focus.
It will not fire for every single change as in the case input event.
https://stackoverflow.com/a/17047588/5646527
var canvas = new fabric.Canvas('c');
canvas.renderAll.bind(canvas);
$("#fontSize").on("change", () => {
let fontsize = document.querySelector("#fontSize");
sizeFromInput = fontsize.value;
var activeObject = canvas.getActiveObject();
if (activeObject) {
activeObject.set("fontSize", sizeFromInput);
}
canvas.renderAll();
});
$("#typeTool").click(() => {
var textEditable = new fabric.Textbox("ASR Textbox", {
width: 50,
editable: true,
left: 10,
top: 10,
fontSize: 18,
fill: "#000",
});
canvas.add(textEditable);
canvas
});
<script src="//cdnjs.cloudflare.com/ajax/libs/jquery/2.1.3/jquery.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/fabric#5.2.4-browser/dist/fabric.min.js"></script>
<button id="typeTool">Add Text</button>
<input type="text" id="fontSize" />
<br>
<canvas id="c" width="400" height="400"></canvas>
<br>
<p class="save">
</p>

What's the real meaning of randomize=false using cose-bilkent layout

What the real meaning of randomize=false using cose-bilkent layout ?
I can only read such a simple comment to describe this feature in this extension wiki page.
// Whether to enable incremental mode
randomize: true,
I also find another description for randomize in cytoscape.js-expand-collapse wiki page.
// recommended usage: use cose-bilkent layout with randomize: false to preserve mental map upon expand/collapse
So I have learned that randomize: false is to preserve mental map. But what is the real behavior ?
Suppose that I have run a topology graph using cose-bilkent layout, then call the following snippet to append new nodes and edges to the graph with randomize: false. What will be the expected result on incremental mode ? Should the old nodes and edges keep the relative position as so called mental map , while new nodes and edges should be positioned to a resonable coordinate?
var demoId = 10000;
document.getElementById("append").addEventListener("click", function() {
var tid = cy.nodes()[Math.floor(Math.random()*cy.nodes().length)].data().id;
var sid = ++demoId;
var data = {
nodes : [
{data:{"id":"id"+source, "name":"name"+source}}
],
edges :[
{data:{"id":"id"+source+"-"+target, "source":"id"+source, "target":target}}
]
};
cy.add(data);
cy.layout({
name: 'cose-bilkent',
animate: false,
randomize : false,
fit : false
}).run();
});
See my demo. My test result is that all nodes and edges will be relayout. You could watch some nodes and their neighborhood. The neighborhood will be changed after each layout. I can't get a clear mental map. So could the experts explain how incremental layout task effect here.
document.addEventListener("DOMContentLoaded", function() {
var cy = (window.cy = cytoscape({
container: document.getElementById("cy"),
ready: function() {
this.layout({
name: "cose-bilkent",
animationDuration: 1000
}).run();
},
style: [{
selector: "node",
style: {
'content': 'data(id)',
'text-valign': 'center',
'text-halign': 'center',
'background-color': 'blue',
'color':'red',
'width':'10px',
'height':'10px'
}
},
{
selector: ":parent",
style: {
"background-opacity": 0.333
}
},
{
selector: "edge",
style: {
width: 3,
"line-color": "#ad1a66"
}
}
],
elements: [{"group":"nodes","data":{"id":"p_1","parent":"n_72"}},{"group":"nodes","data":{"id":"p_298","parent":"n_72"}},{"group":"nodes","data":{"id":"p_4","parent":"n_72"}},{"group":"nodes","data":{"id":"p_5","parent":"n_72"}},{"group":"nodes","data":{"id":"p_9","parent":"n_72"}},{"group":"nodes","data":{"id":"p_32","parent":"n_72"}},{"group":"nodes","data":{"id":"p_607","parent":"n_72"}},{"group":"nodes","data":{"id":"p_57","parent":"n_72"}},{"group":"nodes","data":{"id":"p_242","parent":"n_72"}},{"group":"nodes","data":{"id":"p_64","parent":"n_72"}},{"group":"nodes","data":{"id":"p_77","parent":"n_72"}},{"group":"nodes","data":{"id":"p_81","parent":"n_72"}},{"group":"nodes","data":{"id":"p_82","parent":"n_72"}},{"group":"nodes","data":{"id":"p_289","parent":"n_72"}},{"group":"nodes","data":{"id":"p_803","parent":"n_72"}},{"group":"nodes","data":{"id":"n_0"}},{"group":"nodes","data":{"id":"n_72"}},{"group":"edges","data":{"source":"n_0","target":"n_0","id":"n_0-n_0"}},{"group":"edges","data":{"source":"n_0","target":"p_81","id":"n_0-p_81"}},{"group":"edges","data":{"source":"n_0","target":"p_4","id":"n_0-p_4"}},{"group":"edges","data":{"source":"n_0","target":"p_298","id":"n_0-p_298"}},{"group":"edges","data":{"source":"n_0","target":"p_803","id":"n_0-p_803"}},{"group":"edges","data":{"source":"n_0","target":"p_607","id":"n_0-p_607"}},{"group":"edges","data":{"source":"n_0","target":"p_1","id":"n_0-p_1"}},{"group":"edges","data":{"source":"n_0","target":"p_289","id":"n_0-p_289"}},{"group":"edges","data":{"source":"n_0","target":"p_57","id":"n_0-p_57"}},{"group":"edges","data":{"source":"n_0","target":"p_82","id":"n_0-p_82"}},{"group":"edges","data":{"source":"n_0","target":"p_32","id":"n_0-p_32"}},{"group":"edges","data":{"source":"p_77","target":"n_0","id":"p_77-n_0"}},{"group":"edges","data":{"source":"p_64","target":"n_0","id":"p_64-n_0"}},{"group":"edges","data":{"source":"p_607","target":"n_0","id":"p_607-n_0"}},{"group":"edges","data":{"source":"p_5","target":"n_0","id":"p_5-n_0"}},{"group":"edges","data":{"source":"p_9","target":"n_0","id":"p_9-n_0"}},{"group":"edges","data":{"source":"p_9","target":"p_803","id":"p_9-p_803"}},{"group":"edges","data":{"source":"p_242","target":"n_0","id":"p_242-n_0"}}],
}));
var demoId = 10000;
document.getElementById("append").addEventListener("click", function() {
var tid = cy.nodes()[Math.floor(Math.random()*cy.nodes().length)].data().id;
var sid = ++demoId;
var data = {
nodes : [
{data:{"id":sid}}
],
edges :[
{data:{"id":sid+"-"+tid, "source":sid, "target":tid}}
]
};
cy.add(data);
cy.layout({
name: 'cose-bilkent',
animate: false,
randomize : false,
fit : true
}).run();
});
});
body {
font-family: helvetica;
font-size: 14px;
}
#cy {
height: 100%;
width: 90%;
position: absolute;
}
h1 {
opacity: 0.5;
font-size: 1em;
}
button {
margin-right: 10px;
}
<script src="https://unpkg.com/cytoscape/dist/cytoscape.min.js"></script>
<!--polyfills are needed for this extension for old browsers like IE -->
<script src="https://cdnjs.cloudflare.com/ajax/libs/core-js/2.5.7/shim.min.js"></script>
<script src="https://unpkg.com/layout-base/layout-base.js"></script>
<script src="https://unpkg.com/cose-base/cose-base.js"></script>
<script src="https://cdn.jsdelivr.net/npm/cytoscape-cose-bilkent#4.1.0/cytoscape-cose-bilkent.min.js"></script>
<body>
<button id="append" class="button">Append</button>
<div id="cy"></div>
</body>

How to fetch coordinates of Polygon in React.JS using react-google-maps

I wants to fetch all the coordinates of a polygon drawn on Google's Map. And here is my code
import React from "react";
import { compose, withProps } from "recompose";
import {
withScriptjs,
withGoogleMap,
GoogleMap,
Marker
} from "react-google-maps";
//import withScriptjs from "react-google-maps/lib/async/withScriptjs";
import { DrawingManager } from "react-google-maps/lib/components/drawing/DrawingManager";
const MyMapComponent = compose(
withProps({
/**
* Note: create and replace your own key in the Google console.
* https://console.developers.google.com/apis/dashboard
* The key "AIzaSyBkNaAGLEVq0YLQMi-PYEMabFeREadYe1Q" can be ONLY used in this sandbox (no forked).
*/
googleMapURL:
"https://maps.googleapis.com/maps/api/js?key=AIzaSyALpmb4KhFoR2Kcvty21gzzegprl4ilIgs&v=3.exp&libraries=geometry,drawing,places",
loadingElement: <div style={{ height: `100%` }} />,
containerElement: <div style={{ height: `400px` }} />,
mapElement: <div style={{ height: `100%` }} />
}),
withScriptjs,
withGoogleMap
)(props => (
<GoogleMap
defaultZoom={8}
defaultCenter={new window.google.maps.LatLng(-34.397, 150.644)}
>
<DrawingManager
defaultDrawingMode={
window.google.maps.drawing.OverlayType.ControlPosition
}
defaultOptions={{
drawingControl: true,
drawingControlOptions: {
position: window.google.maps.ControlPosition.TOP_CENTER,
drawingModes: [
window.google.maps.drawing.OverlayType.CIRCLE,
window.google.maps.drawing.OverlayType.POLYGON,
window.google.maps.drawing.OverlayType.POLYLINE,
window.google.maps.drawing.OverlayType.RECTANGLE
]
},
circleOptions: {
fillColor: `#ffff00`,
fillOpacity: 1,
strokeWeight: 5,
clickable: false,
editable: true,
zIndex: 1
}
}}
/>
{props.isMarkerShown && (
<Marker position={{ lat: -34.397, lng: 150.644 }} />
)}
</GoogleMap>
));
My focus of work is to fetch all the coordinates of that polygon that should be drawn on Google Maps.I also wants to store these coordinates in MongoDB using mongoose and NodeJs as backend.
We can use this function to get all the coordinates of a polygon or any other reactangle.
function getPaths(polygon) {
var polygonBounds = polygon.getPath();
var bounds = [];
for (var i = 0; i < polygonBounds.length; i++) {
var point = {
lat: polygonBounds.getAt(i).lat(),
lng: polygonBounds.getAt(i).lng()
};
bounds.push(point);
}
console.log(bounds);
}
And in GoogleMap component, i simplified the above code by given way.
<DrawingManager
drawingMode={"polygon"}
onPolygonComplete={value => console.log(getPaths(value))} />

FabricJs Cannot read property 'className' of null

I just upgraded fabricJs from 1.4.0 to 1.4.13 so that i can use SetSrc() of Image class and when i tried to run my application i got this error :"Cannot read property 'className' of null". Here is the line of code where the error originated from
const options = {
id: client.ProfileId,
class: 'img_wifiClient',
left: (transX + client_left) * canvasFabric.scale,
top: (transY + client_top) * canvasFabric.scale,
selectable: true,
hasBorders: false,
hasControls: false,
padding: 0,
perPixelTargetFind: true,
width: 24,
height: 24,
originX: 'center',
originY: 'center'
}
let image = new fabric.Image('');//*This is where the error originated from*
canvasFabric.add(image);
let src;
if (client.IconName === 'default.png' || client.IconName === null)
{
src = '/Icones/wifi.png';
}
else
{
src = `/Icones/${client.IconName}`;
}
image.setSrc(src, function () {
image.setCoords();
canvasFabric.renderAll();
}, options);
Any help would be appreciated!!
I added an img element with id = "myIcone" under the canvas element in the Html file and added the following lines of code and everything works.
let image_element = document.getElementById('myIcone');
let image = new fabric.Image(image_element);
canvasFabric.add(image);
The rest of the code is the same as in the question snippet

canvas background turns black after rerender fabric.js

I have the following situation: I draw the canvas with a background image and this works:
var canvasSection = new fabric.Canvas('buildSection', {
selectionColor: 'rgba(94,213,94,0.2)',
width: widthS,
height: heightS,
hoverCursor: 'pointer',
backgroundColor: ({
source: 'image/bordenconfigurator/background/background1.png',
repeat: 'repeat'
})
});
But then I need to clear canvas and to redraw it. at this point when I try to set background, instead of an image I see a black screen. In console I can see that the background image is set however. This is the code I'm trying:
$('#cForm').on('change', function() {
if ($(this).val() == 'rectLandsc') {
widthS = 600;
heightS = 400;
} else if ($(this).val() == 'rectPortr') {
....
}
canvasSection.clear();
canvasSection.set({
fill: 'rgba(0,0,0,0)',
// backgroundColor: ({source: 'image/bordenconfigurator/background/background1.png', repeat: 'repeat'})
});
//canvasSection.backgroundColor({source: 'image/bordenconfigurator/background/background.png', repeat:'repeat'});
canvasSection.setBackgroundColor({
source: 'image/bordenconfigurator/background/background.png'
}),
canvasSection.renderAll.bind(canvas);
drawBaseForm();
canvasSection.renderAll();
console.log(canvasSection);
});
Is it a kind of a bug? Or am I doing something wrong? Could anyone help me out here?
canvas.setBackgroundColor({
source: 'image/bordenconfigurator/background/background1.png',
repeat: 'repeat'
}, canvas.renderAll.bind(canvas));
setBackgroundColor first parameter is color/pattern and second parameter is callback function.
DEMO
var canvas = new fabric.Canvas('c');
canvas.setBackgroundColor({
source: 'http://fabricjs.com/assets/pug_small.jpg'
}, canvas.renderAll.bind(canvas));
function clearCanvas(){
var background = canvas.backgroundColor;
canvas.clear();
setTimeout(function(){
canvas.setBackgroundColor(background,canvas.renderAll.bind(canvas));
},2000)
}
canvas{
border: 2px dotted black;
}
<script src="https://rawgithub.com/kangax/fabric.js/master/dist/fabric.js"></script>
<button onclick='clearCanvas()'>clear</button>
<canvas id="c" width="500" height="500"></canvas>

Resources