Attempting to create a custom class that will display an image on the top left corner of a Fabric rectangle.
Extending the fabric class and calling ctx.drawImage isn't displaying anything.
var CustomRect = fabric.util.createClass(fabric.Rect, {
type: 'labeledRect',
initialize: function(options) {
options || (options = { });
const image = new Image();
image.src = signBtn;
this.callSuper('initialize', options);
this.set('label', options.label || '');
this.set('image', image);
},
toObject: function() {
return fabric.util.object.extend(this.callSuper('toObject'), {
label: this.get('label')
});
},
_render: function(ctx) {
this.callSuper('_render', ctx);
ctx.font = '20px Helvetica';
ctx.fillStyle = '#333';
ctx.fillText(this.label, -this.width/2, -this.height/2 + 20);
ctx.drawImage(this.image, this.left+10, this.top-5, 15, 15);
}
});
fillText is working fine and displaying the label properly.
Tried a few different things thinking the image isn't loaded. It's imported as an SVG. I've gotten it to display in other ways but prefer to have a custom class handle this instead of event handlers.
You need to set image after load. You can use image.onload event callback, or use fabric.util.loadImage
DEMO
fabric.CustomRect = fabric.util.createClass(fabric.Rect, {
type: 'customRect',
initialize: function(options) {
options || (options = { });
var that = this;
this.imageLoaded = false;
fabric.util.loadImage(options.signBtn,function(img){
that.set('image', img);
that.imageLoaded = true;
that.dirty = true;
//if canvas is global
canvas.renderAll();
},{
crossOrigin : 'annonymous'
});
this.callSuper('initialize', options);
this.set('label', options.label || '');
},
toObject: function() {
return fabric.util.object.extend(this.callSuper('toObject'), {
label: this.label,
image : this.image
});
},
_render: function(ctx) {
this.callSuper('_render', ctx);
ctx.font = '20px Helvetica';
ctx.fillStyle = '#333';
ctx.fillText(this.label, -this.width/2, -this.height/2 + 20);
this.imageLoaded && ctx.drawImage(this.image, 10, -5, 15, 15);
}
});
fabric.CustomRect.fromObject = function(object, callback) {
return fabric.Object._fromObject('CustomRect', object, callback);
};
var url = 'https://upload.wikimedia.org/wikipedia/en/8/80/Wikipedia-logo-v2.svg';
var canvas = new fabric.Canvas('canvas');
var rect = new fabric.CustomRect({
signBtn: url,
left : 10,
top : 10,
width : 200,
height: 200,
fill: 'green',
label : 'test'
});
canvas.add(rect);
function loadfromjson() {
var json = canvas.toJSON();
canvas.clear();
setTimeout(function() {
canvas.loadFromJSON(json, canvas.renderAll.bind(canvas));
}, 1000)
}
canvas{
border:2px solid #000;
}
<script src='https://rawgit.com/kangax/fabric.js/master/dist/fabric.js'></script>
<canvas id="canvas" width="300" height="300"></canvas>
<button onclick="loadfromjson()">loadfromjson </button>
I believe there are some missing values like this.width and this.height?
In addition to that, you have to wait until the image is loaded to draw it. You can do something like this:
var CustomRect = fabric.util.createClass(fabric.Rect, {
type: 'labeledRect',
initialize: function(options) {
options || (options = { });
const image = new Image();
image.src = 'https://i.stack.imgur.com/CYp8Y.jpg';
this.callSuper('initialize', options);
image.onload = (function() {
this.width = image.width;
this.height = image.height;
this.image_loaded = true;
}).bind(this);
this.set('label', options.label || '');
this.set('image', image);
},
toObject: function() {
return fabric.util.object.extend(this.callSuper('toObject'), {
label: this.get('label')
});
},
_render: function(ctx) {
if (this.image_loaded) {
this.callSuper('_render', ctx);
console.log(-this.width / 2, -this.height / 2);
ctx.font = '20px Helvetica';
ctx.fillStyle = '#333';
ctx.fillText(this.label, -this.width/2, -this.height/2 + 20);
ctx.drawImage(this.image, -this.width / 2, -this.height / 2);
}
}
});
Note these two changes:
image.onload = (function() {
this.width = image.width;
this.height = image.height;
this.image_loaded = true;
}).bind(this);
I set the image_loaded to true inside the initialize method once the image is loaded. Then in the _render method I check if the image is loaded, and then draw it.
if(this.loaded){
//............
ctx.drawImage(this.image, -this.width / 2, -this.height / 2);
}
Here's a fiddle: https://jsfiddle.net/nimeshka/enL61g0w/55/
You might need to click on the canvas to trigger the render event. Otherwise you won't see the rectangle in the output box. (I haven't added it)
Hope it helps :)
Related
Below is provided a script for changing image on click, changes will be affected in canvas.
The script is working properly in Firefox and Chrome but not in Safari
What could be the reason?
<script>
$(function() {
var house = document.getElementById("house");
var ctxHouse = house.getContext("2d");
var canvas = document.getElementById("canvas");
var ctx = canvas.getContext("2d");
var canvas2 = document.getElementById("canvas2");
var ctxHand = canvas2.getContext("2d");
var FabricLink;
var imageObj = new Image();
var red = new Image();
red.onload = function() {
canvas.width = red.width;
canvas.height = red.height;
var houseImage = new Image();
houseImage.onload = function() {
house.width = houseImage.width;
house.height = houseImage.height;
ctxHouse.drawImage(houseImage, 0, 0);
}
houseImage.src = "images/img.jpg";
ctx.drawImage(red, 0, 0);
imageObj.onload = function() {
var pattern = ctx.createPattern(imageObj, 'repeat');
ctx.globalCompositeOperation = 'source-in';
ctx.rect(0, 0, canvas.width, canvas.height);
ctx.fillStyle = pattern;
ctx.fill();
};
$(imageObj).attr('src', 'images/' + FabricLink + '.png');
}
red.src = "images/img.png";
var imageObj2 = new Image();
var blue = new Image();
blue.onload = function() {
canvas2.width = blue.width;
canvas2.height = blue.height;
var houseImage = new Image();
houseImage.onload = function() {
house.width = houseImage.width;
house.height = houseImage.height;
ctxHouse.drawImage(houseImage, 0, 0);
}
houseImage.src = "images/img.jpg";
ctxHand.drawImage(blue, 0, 0);
imageObj2.onload = function() {
var pattern = ctx.createPattern(imageObj2, 'repeat');
ctxHand.globalCompositeOperation = 'source-in';
ctxHand.rect(0, 0, canvas2.width, canvas2.height);
ctxHand.fillStyle = pattern;
ctxHand.fill();
};
$(imageObj2).attr('src', 'images/' + FabricLink + '.png');
}
blue.src = "images/img.png";
$('#style li').click(
function() {
//we get our current filename and use it for the src
var linkIndex = $(this).attr("data-filename");
$(red).attr('src', 'images/' + linkIndex + '.png');
}
);
$('#Sleeves li').click(
function() {
$("#canvas2").addClass("show-z");
//we get our current filename and use it for the src
var SleevesLink = $(this).attr("data-filename");
$(blue).attr('src', 'images/' + SleevesLink + '.png');
}
);
$('#Fabric li').click(
function() {
//we get our current filename and use it for the src
FabricLink = $(this).attr("data-filename");
$(imageObj2).attr('src', 'images/' + FabricLink + '.png');
$(imageObj).attr('src', 'images/' + FabricLink + '.png');
}
);
$("#Fabric li:first-child").click();
}); // end $(function(){});
</script>
While debugging, we could see that imageObj.onload is not getting fired in the first click , only on refresh the change occurs in Safari.
jsfiddle.net/qvfx3kz7 (In the fiddle images are not loading, but the complete code is added)
I am not sure what is exactly the problem with Safari, but what I can see is that you are over-complicating everything by trying to load your assets only when requested, which leads to this callback nightmare you have set up.
Instead, prepare your assets so that you can load it only once. Since you are using patterns, I will assume that each pattern is actually a small image.
So instead of loading every little pattern images, load a single sprite-sheet, which will contain all your patterns.
From there, you'll be able to generate dynamically your CanvasPattern synchronously, without having to care for any loading callback. In order to do this, you will have to use a second, off-screen, canvas, that you will use as he source of createPattern() method:
// really dummy implementation...
function AssetsLoader() {}
AssetsLoader.prototype = Object.create({
addImage: function(objs, cb) {
if (!objs || typeof objs !== 'object') return;
if (!Array.isArray(objs)) objs = [];
var toLoad = objs.length,
imgs = objs.map(loadImage, this);
function loadImage(obj) {
if (!obj.src) return;
this.toLoad++;
var img = new Image();
img.src = obj.src;
img.onload = onimgload;
img.onerror = onimgerror;
return obj.img = img;
}
function onimgload(evt) {
if (--toLoad <= 0 && typeof cb === 'function') cb(imgs);
}
function onimgerror(evt) {
console.warn('failed to load image at ', evt.target.src);
}
}
});
// Some info about our assets, there are an infinity of ways to deal with it,
// You would have to determine yourself what's best for your own case
var dests = {
sofa: {
name: 'sofa',
src: 'https://i.stack.imgur.com/ryO42.png'
},
shirt: {
name: 'shirt',
src: 'https://i.stack.imgur.com/cPNbe.png'
}
};
var patterns = {
name: 'patterns',
src: 'https://i.stack.imgur.com/TdIAJ.png',
positions: {
orange: {
x: 0,
y: 0,
width: 173,
height: 173,
out_width: 75,
out_height: 75
},
violet: {
x: 173,
y: 0,
width: 173,
height: 173,
out_width: 35,
out_height: 35
},
psyche: {
x: 0,
y: 173,
width: 173,
height: 173,
out_width: 25,
out_height: 25
},
pink: {
x: 173,
y: 173,
width: 173,
height: 173,
out_width: 125,
out_height: 125
}
}
}
var assets = new AssetsLoader();
// first load all our images, and only then, start the whole thing
assets.addImage([dests.shirt, dests.sofa, patterns], init);
function init() {
// populate our selects
for (var key in dests) {
dest_select.appendChild(new Option(key, key));
}
for (var key in patterns.positions) {
pat_select.appendChild(new Option(key, key));
}
dest_select.onchange = pat_select.onchange = draw;
var ctx = canvas.getContext('2d');
var offscreenCtx = document.createElement('canvas').getContext('2d');
draw();
function draw() {
var dest = dests[dest_select.value].img;
ctx.canvas.width = dest.width;
ctx.canvas.height = dest.height;
ctx.globalCompositeOperation = 'source-over';
ctx.clearRect(0, 0, canvas.width, canvas.height);
ctx.drawImage(dest, 0, 0, canvas.width, canvas.height);
ctx.globalCompositeOperation = 'source-in';
// here we generate the CanvasPattern
ctx.fillStyle = getPattern(pat_select.value);
ctx.fillRect(0, 0, canvas.width, canvas.height);
ctx.globalCompositeOperation = 'luminosity';
ctx.drawImage(dest, 0, 0, canvas.width, canvas.height);
}
// now that we have our patterns loaded in a single sprite-sheet,
// we can generate the CanvasPatterns synchronously
function getPattern(key) {
var pos = patterns.positions[key]; // get our pattern's position in our dictionary
// resize the offscreen canvas so it matches our pattern
offscreenCtx.canvas.width = pos.out_width;
offscreenCtx.canvas.height = pos.out_height;
offscreenCtx.drawImage(patterns.img, pos.x, pos.y, pos.width, pos.height, 0, 0, pos.out_width, pos.out_height);
return offscreenCtx.createPattern(offscreenCtx.canvas, 'repeat');
}
}
<select id="dest_select"></select>
<select id="pat_select"></select>
<br><br><br>
<canvas id="canvas"></canvas>
This is the code that creates the arrow:
I need to detect the orientation where the arrow is pointing both when starting or doing some transformation of the arrow
var Arrow = Fabric.util.createClass(Fabric.Line, Fabric.Observable, {
type: 'arrow',
positionArrow: 'rt',
initialize: function (points, options) {
this.callSuper('initialize', points, options);
this.on('modified', function() {
console.log(this.toObject())
});
},
_render: function(ctx, noTransform) {
this._setStrokeStyles(ctx);
this._drawArrowHead(ctx);
this.callSuper('_render', ctx, noTransform);
},
_drawArrowHead: function(ctx) {
var p = this.calcLinePoints();
var rot = Math.atan2(p.y2, p.x2);
ctx.save();
ctx.beginPath();
ctx.translate(p.x1, p.y1);
ctx.moveTo(0, 0);
ctx.rotate(rot);
ctx.lineTo(15, 7);
ctx.lineTo(3, 0);
ctx.lineTo(15, -7);
ctx.lineTo(0, 0);
ctx.stroke();
ctx.closePath();
ctx.restore();
},
_setStrokeStyles: function(ctx) {
if (this.stroke) {
ctx.lineWidth = this.strokeWidth;
ctx.lineCap = this.strokeLineCap;
ctx.lineJoin = this.strokeLineJoin;
ctx.miterLimit = this.strokeMiterLimit;
ctx.strokeStyle = this.stroke.toLive ? this.stroke.toLive(ctx) : this.stroke;
}
}
});
I have added an observer when the object is transformed in the initialization, but I have found the way to know in which position the arrow is pointing.
If you know how much you should rotate the canvas to draw an arrowHead, you should also be able to get the full angle reusing that code and adding the object angle.
fabric.Arrow = fabric.util.createClass(fabric.Line, {
type: 'arrow',
positionArrow: 'rt',
initialize: function (points, options) {
this.callSuper('initialize', points, options);
this.on('modified', function() {
var p = this.calcLinePoints();
var rot = Math.atan2(p.y2, p.x2);
var rotDeg = fabric.util.radiansToDegrees(rot);
console.log(this.angle + rotDeg)
});
},
_render: function(ctx) {
this.callSuper('_render', ctx);
this._setStrokeStyles(ctx);
this._drawArrowHead(ctx);
},
_drawArrowHead: function(ctx) {
var p = this.calcLinePoints();
var rot = Math.atan2(p.y2, p.x2);
ctx.save();
ctx.beginPath();
ctx.translate(p.x1, p.y1);
ctx.moveTo(0, 0);
ctx.rotate(rot);
ctx.lineTo(15, 7);
ctx.lineTo(3, 0);
ctx.lineTo(15, -7);
ctx.lineTo(0, 0);
ctx.stroke();
ctx.closePath();
ctx.restore();
},
_setStrokeStyles: function(ctx) {
if (this.stroke) {
ctx.lineWidth = this.strokeWidth;
ctx.lineCap = this.strokeLineCap;
ctx.lineJoin = this.strokeLineJoin;
ctx.miterLimit = this.strokeMiterLimit;
ctx.strokeStyle = this.stroke.toLive ? this.stroke.toLive(ctx) : this.stroke;
}
}
});
var canvas = new fabric.Canvas('c');
var arrow = new fabric.Arrow([10,10, 70,20]);
canvas.add(arrow);
<script src="https://cdnjs.cloudflare.com/ajax/libs/fabric.js/2.2.3/fabric.js"></script>
<canvas id="c" width="300" height="300" ></canvas>
I need to rotate/scale/flip/crop an image on an HTML page.
I'm using JCrop.js for image manipulation on client side. I'm able to do this successfully with a file selector, where the user selects an image file on their local computer. My requirement is to do the same, but for an image loaded from another URL (without selecting a local image).
Need help on the next step.
Here is the complete code that works well with local file selection:
HTML:
<img scr="externalURL" id="imgMain" alt=""/>
<input type="button" id="cropbutton" value="Crop" />
<input type="button" id="rotatebutton" value="Rotate" />
<input type="button" id="hflipbutton" value="Flip Horizontal" />
<input type="button" id="vflipbutton" value="Flip Vertical" />
JS:
var cropWidth = 800;
var cropHeight = 800;
var crop_max_width = 400;
var crop_max_height = 400;
var jcrop_api;
var canvas;
var context;
var image;
var prefsize;
$(document).ready(function () {
$("#file").change(function () {
loadImage(this);
});
$("#btnCrop").click(function () {
SaveData();
});
});
function SaveData() {
formData = new FormData($(this)[0]);
var blob = dataURLtoBlob(canvas.toDataURL('image/jpeg'));
formData.append("cropped_image[]", blob, "CroppedImage.jpeg");
$.ajax({
url: urlToSendData
type: "POST",
data: formData,
contentType: false,
cache: false,
processData: false,
success: function (data) {
alert("Image cropped!");
},
error: function (data) {
alert('There was an error');
}
});
}
function loadImage(input) {
if (input.files && input.files[0]) {
var reader = new FileReader();
canvas = null;
reader.onload = function (e) {
image = new Image();
image.onload = validateImage;
image.src = e.target.result;
}
reader.readAsDataURL(input.files[0]);
applyCrop();
}
}
function dataURLtoBlob(dataURL) {
var BASE64_MARKER = ';base64,';
if (dataURL.indexOf(BASE64_MARKER) == -1) {
var parts = dataURL.split(',');
var contentType = parts[0].split(':')[1];
var raw = decodeURIComponent(parts[1]);
return new Blob([raw], {
type: contentType
});
}
var parts = dataURL.split(BASE64_MARKER);
var contentType = parts[0].split(':')[1];
var raw = window.atob(parts[1]);
var rawLength = raw.length;
var uInt8Array = new Uint8Array(rawLength);
for (var i = 0; i < rawLength; ++i) {
uInt8Array[i] = raw.charCodeAt(i);
}
return new Blob([uInt8Array], {
type: contentType
});
}
function validateImage() {
if (canvas != null) {
image = new Image();
image.onload = restartJcrop;
image.src = canvas.toDataURL('image/png');
} else restartJcrop();
}
function restartJcrop() {
if (jcrop_api != null) {
jcrop_api.destroy();
}
$("#views").empty();
$("#views").append("<canvas id=\"canvas\">");
canvas = $("#canvas")[0];
context = canvas.getContext("2d");
canvas.width = image.width;
canvas.height = image.height;
context.drawImage(image, 0, 0);
$("#canvas").Jcrop({
onSelect: selectcanvas,
onRelease: clearcanvas,
boxWidth: crop_max_width,
boxHeight: crop_max_height,
allowResize: false,
allowSelect: false,
setSelect: [0, 0, cropWidth, cropHeight],
aspectRatio: 1
}, function () {
jcrop_api = this;
});
clearcanvas();
}
function clearcanvas() {
prefsize = {
x: 0,
y: 0,
w: canvas.width,
h: canvas.height,
};
}
function selectcanvas(coords) {
prefsize = {
x: Math.round(coords.x),
y: Math.round(coords.y),
w: Math.round(coords.w),
h: Math.round(coords.h)
};
}
function applyCrop() {
canvas.width = prefsize.w;
canvas.height = prefsize.h;
context.drawImage(image, prefsize.x, prefsize.y, prefsize.w, prefsize.h, 0, 0, canvas.width - 10, canvas.height - 10);
validateImage();
}
function applyScale(scale) {
if (scale == 1) return;
canvas.width = canvas.width * scale;
canvas.height = canvas.height * scale;
context.drawImage(image, 0, 0, canvas.width, canvas.height);
validateImage();
}
function applyRotate() {
canvas.width = image.height;
canvas.height = image.width;
context.clearRect(0, 0, canvas.width, canvas.height);
context.translate(image.height / 2, image.width / 2);
context.rotate(Math.PI / 2);
context.drawImage(image, -image.width / 2, -image.height / 2);
validateImage();
}
function applyHflip() {
context.clearRect(0, 0, canvas.width, canvas.height);
context.translate(image.width, 0);
context.scale(-1, 1);
context.drawImage(image, 0, 0);
validateImage();
}
function applyVflip() {
context.clearRect(0, 0, canvas.width, canvas.height);
context.translate(0, image.height);
context.scale(1, -1);
context.drawImage(image, 0, 0);
validateImage();
}
$("#scalebutton").click(function (e) {
var scale = prompt("Scale Factor:", "1");
applyScale(scale);
});
$("#rotatebutton").click(function (e) {
applyRotate();
});
$("#hflipbutton").click(function (e) {
applyHflip();
});
$("#vflipbutton").click(function (e) {
applyVflip();
});
Looks like loadImage reads the file and sets the image global.
As there is now already an image on the page, we should be able to skip this step.
So we can probably just set the image in ready like this:
$(document).ready(function () {
// (this code replaces the file selecting)
image = $("#imgMain"); // set image from the page
canvas = null; // this was done in loadImage
applyCrop(); // this was called from loadImage
// (this code is the same as before)
$("#btnCrop").click(function () {
SaveData();
});
});
>>> Jsfiddle
var c = document.getElementById("myCanvas");
var ctx = c.getContext("2d");
ctx.beginPath();
ctx.arc(55,95,30,0,2*Math.PI);
ctx.stroke();
var canvas = document.getElementById('myCanvas'),
context = canvas.getContext('2d');
make_base();
function make_base()
{
base_image = new Image();
base_image.src='https://www.gravatar.com/avatar/4af2cdbaf02d97ba88d5d6daff94fbae/?default=&s=80';
base_image.onload = function(){
context.drawImage(base_image, 20, 20);
}
}
<canvas id="myCanvas"
width="236"
height="413"
style="border:1px solid #000000;
position:absolute;
top:66px;
left:22px;"></canvas>
I have try to put the image in to canvas instead my circle position.
but i don't know how to resize square image to circle with same size of my draw circle and put in to the same position.
What should i do?
You have to use clip for this.
Here is Codepen for this http://codepen.io/sam83045/pen/eBKRPr?editors=0010
And your edited javascript code is as follows :
var c = document.getElementById("myCanvas");
var ctx = c.getContext("2d");
ctx.beginPath();
ctx.arc(55, 95, 30, 0, 2 * Math.PI);
ctx.clip();
ctx.stroke();
var canvas = document.getElementById('myCanvas'),
context = canvas.getContext('2d');
make_base();
function make_base() {
base_image = new Image();
base_image.src = 'https://www.gravatar.com/avatar/4af2cdbaf02d97ba88d5d6daff94fbae/?default=&s=80';
base_image.onload = function() {
context.drawImage(base_image, 16, 55);
}
}
window.onload=function(){
var c = document.getElementById("myCanvas");
var ctx = c.getContext("2d");
c.addEventListener("click",function(){
ctx.clearRect(0, 0, c.width, c.height);
var object = images.shift();
images.push(object);
create(ctx);
},false)
var loaded=0;
var error=0;
var images=new Array
(
{
image:null,
url:"https://www.gravatar.com/avatar/4af2cdbaf02d97ba88d5d6daff94fbae/?s=80",
expandW:0,
expandH:0,
clip:false,
visible:true,
shape:
{
x:0,
y:0,
rect:
{
w:30,
h:50
},
boundary:
function(){return {w:this.rect.w,h:this.rect.h,centerX:this.rect.w/2,centerY:this.rect.h/2};}
}
},
{
image:null,
url:"http://iconbug.com/data/7c/512/6e1cd685219a18b951b191ad04407324.png",
expandW:0,
expandH:0,
clip:true,
visible:true,
shape:
{
x:30,
y:30,
circle:
{
r:30
},
boundary:
function(){return {w:this.circle.r*2,h:this.circle.r*2,centerX:0,centerY:0};}
}
}
);
function loadImages(ctx)
{
for (var i=0;i<images.length;i++)
{
images[i].image= new Image();
images[i].image.src = images[i].url;
images[i].image.onerror=function(){
loaded++;
error++;
loadedText(images.length,loaded,error);
if(loaded===images.length)
{
create(ctx);
}
};
images[i].image.onload=function(){
loaded++;
loadedText(images.length,loaded,error);
if(loaded===images.length)
{
create(ctx);
}
};
}
}
function loadedText(sum,count,error)
{
if(error)
console.log((count-error)+" images loaded from "+count+"."+error+" images not loaded.");
else
console.log(count+" images loaded from "+count+".");
}
function create(ctx)
{
for (var i=0;i<images.length;i++)
{
ctx.save();
if(images[i].image !==null && images[i].visible)
{
var object=images[i];
var boundary=object.shape.boundary();
var image=object.image;
var shape=object.shape;
if(shape.circle)
{
drawCircle(
shape.x,
shape.y,
shape.circle.r,
ctx,
object.clip
);
}
if(shape.rect)
{
drawRect(
shape.x,
shape.y,
shape.rect.w,
shape.rect.h,
ctx,
object.clip
);
}
if(!object.clip)
{
image.width=image.width*(boundary.w/image.width);
image.height=image.height*(boundary.h/image.height);
}
image.width=image.width+object.expandW;
image.height=image.height+object.expandH;
var x=(shape.x+boundary.centerX)-image.width/2;
var y=(shape.y+boundary.centerY)-image.height/2;
ctx.drawImage(image, x, y,image.width,image.height);
}
ctx.restore();
}
}
function drawCircle(x,y,r,ctx,clip){
ctx.beginPath();
ctx.arc(x, y, r, 0, 2 * Math.PI);
if(clip)
{
ctx.clip();
}
ctx.stroke();
}
function drawRect(x,y,w,h,ctx,clip){
ctx.beginPath();
ctx.rect(x, y, w, h);
if(clip)
{
ctx.clip();
}
ctx.stroke();
}
loadImages(ctx);
};
Add multiple images.
If you do not have many pictures, it is easier to load them at once.
On console (F12) you can see load progress.
Every image has is own options.
image- it will populated latter
url- address
expandW, expandH- you can manipulate Picture after clipping.
clip- do we make picture size of image or we clip shape from image.
visible- do we show image.
shape-clipping shape
shape options
x,y-position of shape
rect or circle type of shape.
boundary- we get shape centre position and shape with and height. (circle with and height is actually double of radius).
with mouse click on canvas you can cycle through all loaded pictures.
As suggested here, I should use canvas renderer so that images can be displayed in nodes. First of all I create a graphClass:
graphClass.g = {
nodes: [],
edges: []
}
Then, I create each node:
graphClass.g.nodes.push({
id: 'n' + data.id,
label: data.username,
x: Math.random(),
y: Math.random(),
size: 20,
type: 'image',
url: data.picture
});
where data.picture is an image URL.
(edges are not relevant here)
And finally, I instantiate Sigma:
s = new sigma({
graph: g,
renderer: {
container: document.getElementById('graph-container'),
type: 'canvas'
},
settings: {
defaultLabelColor: '#fff',
labelColor: '#fff',
enableEdgeHovering: false
}
});
This works as expected, except the images. I can't see them, and black nodes are displayed instead. Any idea of what I am doing wrong?
I've got it. I had to use the example at github
I've just copied this code:
sigma.utils.pkg('sigma.canvas.nodes');
sigma.canvas.nodes.image = (function() {
var _cache = {},
_loading = {},
_callbacks = {};
// Return the renderer itself:
var renderer = function(node, context, settings) {
var args = arguments,
prefix = settings('prefix') || '',
size = node[prefix + 'size'],
color = node.color || settings('defaultNodeColor'),
url = node.url;
if (_cache[url]) {
context.save();
// Draw the clipping disc:
context.beginPath();
context.arc(
node[prefix + 'x'],
node[prefix + 'y'],
node[prefix + 'size'],
0,
Math.PI * 2,
true
);
context.closePath();
context.clip();
// Draw the image
context.drawImage(
_cache[url],
node[prefix + 'x'] - size,
node[prefix + 'y'] - size,
2 * size,
2 * size
);
// Quit the "clipping mode":
context.restore();
// Draw the border:
context.beginPath();
context.arc(
node[prefix + 'x'],
node[prefix + 'y'],
node[prefix + 'size'],
0,
Math.PI * 2,
true
);
context.lineWidth = size / 5;
context.strokeStyle = node.color || settings('defaultNodeColor');
context.stroke();
} else {
sigma.canvas.nodes.image.cache(url);
sigma.canvas.nodes.def.apply(
sigma.canvas.nodes,
args
);
}
};
// Let's add a public method to cache images, to make it possible to
// preload images before the initial rendering:
renderer.cache = function(url, callback) {
if (callback)
_callbacks[url] = callback;
if (_loading[url])
return;
var img = new Image();
img.onload = function() {
_loading[url] = false;
_cache[url] = img;
if (_callbacks[url]) {
_callbacks[url].call(this, img);
delete _callbacks[url];
}
};
_loading[url] = true;
img.src = url;
};
return renderer;
})();
and instantiate sigma like this (caching images):
images.forEach(function(url) {
sigma.canvas.nodes.image.cache(
url,
function() {
if (++loaded === images.length) {
s = new sigma({
graph: g,
renderer: {
// IMPORTANT:
// This works only with the canvas renderer, so the
// renderer type set as "canvas" is necessary here.
container: document.getElementById('graph-container'),
type: 'canvas'
},
settings: {
maxNodeSize: 20,
defaultLabelColor: '#fff',
labelColor: '#fff',
enableEdgeHovering: false
}
});
}
}
);
});
And images are now displayed on nodes.