I need a reference to Cytoscape Canvas Object and it's 2D context. How can I do this? I don't see any "id" attribute on the canvas.
Thanks
You could acquire Cytoscape's canvas element and its context in the following way ...
var canvas = document.querySelector('canvas[data-id="layer2-node"]');
var context = canvas.getContext('2d');
Here's an example, showing how you could use the context to draw something on the canvas ...
var cy = cytoscape({
container: document.getElementById('cy'),
elements: [
{ data: { id: 'a' } },
{ data: { id: 'b' } },
{
data: {
id: 'ab',
source: 'a',
target: 'b'
}
}
]
});
var canvas = document.querySelector('canvas[data-id="layer2-node"]');
var context = canvas.getContext('2d');
// draw rectangle using general canvas method
function draw() {
context.fillRect(canvas.width / 2.1, 60, 30, 30);
requestAnimationFrame(draw);
}
draw();
<script src="https://cdnjs.cloudflare.com/ajax/libs/cytoscape/3.1.0/cytoscape.min.js"></script>
<div id="cy" style="width: 100%; height: 100%; position: absolute; top: 0px; left: 0px;"></div>
Following #maxkfranz's (Cytoscape dev) recommendations, I develop a lightweight Cytoscape.js extension called cytoscape-canvas that creates a canvas over and/or under a graph. It enables that full flexibility you're looking for.
If you're interested, check out the readme!
You shouldn't try to grab Cytoscape's canvas. Cytoscape uses several canvases for performance reasons, and you can't guarantee which type of context is valid. It may use context2d or it may use webgl for performance. Even if a context2d is uses, you can't keep track of when or what state changes (e.g. transforms) have happened on the canvas. And by making your own changes, you may break assumptions Cytoscape has made.
If you want overlay content, you should make your own overlay canvas. You generally shouldn't modify dom elements or js objects that belong to a lib outside of public/documented API.
Related
I have a Image with a simple clipping Rect ( rotate 30 degree with size 200x200 to make a small view part of image ). My problem is how to prevent Image move or scale outside clipping rect ( that mean Image need alway include entire clipping Rect )?
Here is my code:
var canvasObject = document.getElementById("editorCanvas");
// set canvas equal size with div
$(canvasObject).width($("#canvasContainer").width());
$(canvasObject).height($("#canvasContainer").height());
var canvas = new fabric.Canvas('editorCanvas', {
backgroundColor: 'white',
selectionLineWidth: 2,
width: $("#canvasContainer").width(),
height: $("#canvasContainer").height()
});
// create a clipping path
var rectClippingPath = new fabric.Rect({
top: 100,
left: 200,
width: 200,
height: 200,
fill: 'red',
angle: 30,
absolutePositioned: true
});
// create image
var newImage = new fabric.Image();
newImage.clipPath = rectClippingPath;
canvas.add(newImage);
newImage.setSrc('http://fabricjs.com/assets/pug_small.jpg', (imageObject) => {
newImage.set({ left: 50, top: 50});
canvas.setActiveObject(newImage);
newImage.setCoords();
canvas.requestRenderAll();
})
#canvasContainer {
width: 100%;
height: 100vh;
background-color: gray;
}
<script src="https://code.jquery.com/jquery-3.3.1.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/fabric.js/3.6.6/fabric.min.js" integrity="sha512-XcwgBTqf2CXR/nsswCV1e0j9CjXo87APyBsATK2/l7MvTpcIG0QYKA87v5KIJ4RS6ytArv2pWD6UcRorKhYp1A==" crossorigin="anonymous"></script>
<div id="canvasContainer">
<canvas id="editorCanvas"></canvas>
</div>
And here are some example cases:
Here is expected result:
Thank you!
I can't see a way to achieve it based on your example. I think it needs a structural change. You can do it by having a parent element with all controls and then a child element (image object).
Whenever you move the parent frame you should force the photo to move by the same amount, whenever you scale the frame you should also proportionally scale the child photo, same with rotation.
Let's say you rotate the parent frame, now in order to manipulate the photo inside you will need some kind of activating trigger: double click or a button. That will make sure you are not moving the parent frame, but the child.
While you move the child photo you will need to force it to stay contained within the parent frame borders. This is not hard if both elements have a rotation of 0, 90, 180, 270, 360 degrees. It is much harder to make it nice and smooth for all other rotation angles. You can find a working, but not perfect solution here: Contain a rotated object inside another rotated object FabricJS. I also started working on a smoother solution for that, but it is not completed https://codesandbox.io/s/force-contain-of-object-inside-another-object-fabric-js-v3-j38sz.
Implementing this solution you will most probably encounter also an issue having with rotating. It's easy to rotate an child image and the parent if they have the same origin point, but if you move the child and try to rotate the parent, then you will need to rotate them considering a different origin point. Here is a solution to this: https://codesandbox.io/s/rotation-of-an-object-with-different-rotation-origin-fabric-js-362-8m4cb?file=/src/index.js
You can also visit this platform and have look at similar ideas https://diamondphoto.co.nz/. It is build in Fabric JS version 3.
I'm using FabricJS in my web application. As I draw shapes, I notice that the control widget for all the objects are rectangular. Can the shape of a control widget be modified to fit the actual shape of the object? Say, a circle with a circular control widget.
I went through the Controls Customization demo. But there is no option to modify the shape of a control widget.
I need to modify the shape of the control widgets for the following purpose:
In my application whenever an object is clicked, its fill colour is toggled between black and white. In this case(refer the image below), because the circle has a rectangular control widget, clicking the pointed pixel selects the circle instead of the rectangle. I also hide the control widget from appearing by using hasControls property. So, the user will not know anything about the control widget. So, is the shape of the control widget editable?
I don't think there is an easy way to do that with fabric.js without writing a whole bunch of extra code. Fabric.js always draws a rectangle bounding box and uses it for selection controls. However, since you're planning on hiding the controls anyway, it sounds like you might be better off using perPixelTargetFind.
When true, object detection happens on per-pixel basis rather than on
per-bounding-box
Here's a quick demo:
const canvas = new fabric.Canvas('c', {
preserveObjectStacking: true,
perPixelTargetFind: true
})
const circle = new fabric.Circle({
radius: 50,
left: 50,
top: 50,
stroke:'green',
fill: 'white',
hasControls: false,
hasBorders: false
})
const rect = new fabric.Rect({
width: 200,
height: 150,
left: 50,
top: 25,
stroke: 'green',
fill: 'black',
hasControls: false,
hasBorders: false
})
canvas.on('mouse:down', (e) => {
const obj = e.target
if (obj) {
obj.set({
fill: obj.fill === 'white' ? 'black' : 'white'
})
canvas.requestRenderAll()
}
})
canvas.add(circle, rect)
circle.bringToFront()
canvas.requestRenderAll()
body {
background: ivory;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/fabric.js/3.4.0/fabric.min.js"></script>
<canvas id="c" width="300" height="200"></canvas>
I have this fiddle where I have text arranged in a circle, I would like now to animate it and rotate the text in a clockwise/counter clockwise motion.
Every animation demo I have seen uses a container as the starting point however all the examples i could find about manipulating text in a circular arrangement have all started with the element. I have tried 100's of variations trying to get this working but I am either missing something or it's not possible with the construction i have used thus far.
Here is the fiddle for the circular text I have so far:
http://jsfiddle.net/jamesburt/Sa2G8/
<canvas id="canvas1" width="500" height="500"></canvas>
var can = document.getElementById('canvas1');
var ctx = can.getContext('2d');
Where as the animation examples start off with:
<div id="container"></div>
var stage = new Kinetic.Stage({container: 'container'});
I'm open to any ideas / rewrites needed as ultimately my goal is an animated text circle.
Also if this is easily accomplished in an alternative to KineticJS I'd be interested in trying that out.
Here is a demo I made using KineticJS: http://jsfiddle.net/Moonseeker/Xf7hp/
var stage = new Kinetic.Stage({
container: 'container',
width: 500,
height: 500
});
var layer = new Kinetic.Layer();
var myText = "My text in a circle. ";
var centerCoords = {x:250, y:250};
for(var i=0;i<myText.length;i++){
var rotation = i*360/myText.length;
var oneChar = new Kinetic.Text({
x: centerCoords.x,
y: centerCoords.y,
text: myText[i],
fontSize: 30,
fontFamily: 'Calibri',
fill: 'green',
offset: {x:0, y:100},
rotationDeg: rotation
});
layer.add(oneChar);
}
// add the layer to the stage
stage.add(layer);
var angularSpeed = Math.PI / 2;
var anim = new Kinetic.Animation(function(frame){
var angleDiff = frame.timeDiff * angularSpeed / 1000;
for(var i=0;i<layer.children.length;i++){
layer.children[i].rotate(angleDiff);
};
}, layer);
anim.start();
You can rotate at every direction or speed you wish, you can change the style of the circle.
You should be able to use layer.find('Text').each() instead of the for-loop for looping through the text to rotate but the KineticJS library at jsfiddle seems outdated as it doesn't know the find method.
One efficient way:
Render your text-around-a-circle on an offscreen canvas.
Save that offscreen canvas as an image using .toDataURL
Create a Kinetic.Image from that offscreen image.
You can then efficiently rotate/animate the Kinetic.Image as you need.
I have tried to find a solution on how to implement fixed score table to visible stage (canvas) area by using kineticjs library layers and stages. The basic idea is that shape layer can be scrolled and stage is wider than the visible browser area but the score table should be all the time fixed to certain position on the visible area like in many casual games.
What would be the best approach on creating this kind of fixed always visible shapes to stage/canvas? Is there a way to refer to visible area xy position?
Your best bet for making a fixed position group is either to use a second layer on the stage or to create two groups for your objects - one that remains in place and another that can be moved. I'd go with the dual-layer approach as this allows you to redraw the layers separately so that when one has updated information, you don't need to redraw the other.
cvsObj.page = new Kinetic.Layer();
cvsObj.dynamic = new Kinetic.Layer();
cvsObj.stage.add(cvsObj.page);
cvsObj.stage.add(cvsObj.dynamic);
I took the two layer approach but actually my challenge was how to implement 'page' static behavior. I figured out that as it seems that Kinetic layers/stages do not offer methods for this I need to use other means to make 'page' layer on top of viewable area. I used jquery scrollTop() and scrollLeft()functions for this. And layer method .setAbsolutePosition() is used to update position for whole layer. Here pageLayer position is updated and dynamicLayer not so naming might be bit confusing but reflects what you see on browser.
Here is a simple example what does what I was looking for. Now the green rectangle moves when you scroll stage and the red rectangle and the x/y position counter stay on visible area. I attach this solution here with tutorial type of content as it might be useful.
<script>
window.onload = function() {
var stage = new Kinetic.Stage({
container: 'container',
width: 1600, height: 1600
});
var layerDynamic = new Kinetic.Layer();
var layerPage = new Kinetic.Layer();
var rectGreen = new Kinetic.Rect({
x: 239, y: 75, width: 100, height: 50, fill: 'green'
});
var rectRed = new Kinetic.Rect({
x: 100, y: 75, width: 100, height: 50, fill: 'red'
});
var simpleText = new Kinetic.Text({
x: 1, y: 1, text: "Init", textFill: "black"
});
layerDynamic.add(rectGreen);
layerPage.add(rectRed);
layerPage.add(simpleText);
$(document).scroll(function(e) {
var visibleXTop = $(this).scrollTop();
var visibleYTop = $(this).scrollLeft();
simpleText.setAttrs({text: visibleXTop + ' ' + visibleYTop});
layerPage.setAbsolutePosition(visibleYTop , visibleXTop);
layerPage.draw();
});
stage.add(layerDynamic);
stage.add(layerPage);
};
</script>
I've run into this problem a lot with D3. A lot of the time I like to overlay HTML objects over my SVG.
My current strategy is creating an empty DIV next to the SVG element called .html-overlay. I position it according to the internal padding I set in the SVG for my main graphic (ex: 20px). Then I use the following function (with jQuery) to figure out where the HTML element should go:
//element is the object returned by D3 .append()
var getOffset: function(element) {
var $element = $(element[0][0]);
return {
left: $element.offset().left - $('.html-overlay').offset().left,
top: $element.offset().top - $('.html-overlay').offset().top
};
}
I wonder, there MUST be some internal (non-jQuery dependant) way to quickly get an element's offset. It's very useful (especially after an elements goes through multiple translations, rotations, scales, etc.)
It would also be great to have functions for figuring out the offset of the "center" of an element, the topmost point of element, bottommost, leftmost, rightmost, etc.
NOTE:
The getBoundingClientRect() doesn't give the correct numbers for some reason:
var $element = $(element[0][0]);
console.log($element.offset(), element[0][0].getBoundingClientRect())
Object
left: 328
top: 248.8333282470703
__proto__: Object
ClientRect
bottom: 376.83331298828125
height: 139.99998474121094
left: 328
right: 478
top: 236.8333282470703
width: 150
did you try
var xywh =element[0][0].getBoundingClientRect();
seems to have everything in it?
(original soution is in this post)
The problem is with getBoundingClientRect, which doesn't account for scroll position or the element's container position relative to the document. It will only report back that item's exact position relative to the document top and left coordinates.
I've created a d3 method which will report back the position of the element. It works by looping through parent elements until it finds the container SVG, then considers that item's position in the calculations. It returns what you would normally receive from getBoundingClientRect.
Here's the method:
d3.selection.prototype.position = function() {
var el = this.node();
var elPos = el.getBoundingClientRect();
var vpPos = getVpPos(el);
function getVpPos(el) {
if(el.parentElement.tagName === 'svg') {
return el.parentElement.getBoundingClientRect();
}
return getVpPos(el.parentElement);
}
return {
top: elPos.top - vpPos.top,
left: elPos.left - vpPos.left,
width: elPos.width,
bottom: elPos.bottom - vpPos.top,
height: elPos.height,
right: elPos.right - vpPos.left
};
};
And you can use this like so:
d3.select(element).position()
Note that I haven't actually added the code here to consider the scroll position.
Extending James Lai's answer to support modern versions of IE:
function getVpPos(el) {
if(el.parentNode.nodeName === 'svg') {
return el.parentNode.getBoundingClientRect();
}
return getVpPos(el.parentNode);
}
Note: parentElement is changed to parentNode
and tagName is changed to nodeName.