Is there any way on KonvaJS to detach a shape from a group?
I create a group with 3 shapes. Then I add a mouseover event to the group.
Then, if I want to destroy the group I don't know how to do it and the group mouseover event still remains on all shapes.
Any ideas?
Thanks in advance.
Here is a snippet to learn from. Some notes:
When a group is created, it is at position(0,0) and has width and
height both set to 0, unless of course you set these params. But the
group does not have to conceptually surround the child objects.
Any shapes added to the group belong to it but do not effect the position
or size of the group.
When a group is removed, the child objects are
also removed. Note that shape.remove() is described as 'remove self
from parent, but don't destroy. You can reuse node later'. So group
still exists.
Correct way to un-group is as per the snippet which also accounts for group positioning.
// Set up the canvas / stage
var stage = new Konva.Stage({container: 'container1', width: 600, height: 300});
// Add a layer for line
var layer = new Konva.Layer({draggable: false});
stage.add(layer);
var rect1 = new Konva.Rect({x: 20, y: 60, width: 60, height: 30, fill: 'cyan'});
layer.add(rect1);
var rect2 = new Konva.Rect({x: 100, y: 80, width: 30, height: 60, fill: 'magenta'});
layer.add(rect2);
stage.draw();
var group;
$('#make-group').on('click', function(){
group = new Konva.Group({draggable: true, x: 20, y: 30, width: 400, height: 300});
group.add(rect1);
group.add(rect2);
layer.add(group);
$('#info').html('Rects are now in the group - see how they jump because group has (x,y). Click or mouseover one !');
group.on('click mouseover', function(){
$('#info').html('group event');
setTimeout(function(){ $('#info').html(''); }, 250)
})
$('button').show();
layer.draw();
})
$('#remove-group').on('click', function(){
$('#un-group').hide();
$('#info').html('group.remove() is the wrong solution - it removes the group AND children. Click group button.');
group.remove()
layer.draw()
})
$('#un-group').on('click', function(){
$('#remove-group').hide();
// If grouping shapes for draggability or event admin there is no need to set size or position,
// but if you did and you want to retain the position of the shapes without the group then
// use the getAbsolutePosition() function to get and set the positions.
var pos = rect1.getAbsolutePosition(); // get abs position
rect1.moveTo(layer) // move off the group and onto the layer
rect1.position({x: pos.x, y: pos.y}); // set the position.
pos = rect2.getAbsolutePosition();
rect2.moveTo(layer)
rect2.position({x: pos.x, y: pos.y});
group.removeChildren(); // remove children from the layer but don't destroy
group.destroy() // erase the layer and kill it.
layer.draw() // refresh the layer
$('#info').html('Rects are now back on the layer - click now and there is no group event.');
})
$('#info').html('Click the group button.');
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/konva/2.5.1/konva.min.js"></script>
<div style='position: absolute; z-index: 10;' >
<button id='make-group'>Group</button>
<button id='remove-group'>Remove-Group</button>
<button id='un-group'>Un-Group</button>
<p id='info' style='padding-left: 10px;'></p>
</div>
<div id='container1' style="width: 300px, height: 200px; background-color: silver;"></div>
Related
So, I've been working with vue-konva and I have something like this:
<v-container>
<v-stage ref="stage">
<v-layer ref="baseImage">
<v-image>
</v-layer>
<v-layer ref="annotationLayer">
<v-rect ref="eventBox">
<v-rect ref="rubberBox">
<v-rect ref="annotationRect">
</v-layer>
</v-stage>
<v-container>
Currently there are some issues if I want to draw new boxes, when there are other annotationRects already on the image. Because they are technically above the eventBox and rubberbox, they are "blocking" these two layers when the cursor is above an existing annotationRect.
But, I don't want to just constantly have eventBox and rubberBox be on top of annotationRect because I need to be able to interact with annotationRect to move them, resize them ,etc.
Is there a way for me to reorder eventBox, rubberBox, and annotationRect, i.e. changing the order to (bottom to top) annotationRect-eventBox-rubberBox from the original eventBox-rubberBox-annotationRect and back, on the fly, for example when the vue component receives an event from another component?
You need to define your eventBox, rubberBox, and annotationRect inside order array in the state of your app. Then you can use v-for directive to render items from the array:
<template>
<div>
<v-stage ref="stage" :config="stageSize" #click="changeOrder">
<v-layer>
<v-text :config="{text: 'Click me to change order', fontSize: 15}"/>
<v-rect v-for="item in items" v-bind:key="item.id" :config="item"/>
</v-layer>
<v-layer ref="dragLayer"></v-layer>
</v-stage>
</div>
</template>
<script>
const width = window.innerWidth;
const height = window.innerHeight;
export default {
data() {
return {
stageSize: {
width: width,
height: height
},
items: [
{ id: 1, x: 10, y: 50, width: 100, height: 100, fill: "red" },
{ id: 2, x: 50, y: 70, width: 100, height: 100, fill: "blue" }
]
};
},
methods: {
changeOrder() {
const first = this.items[0];
// remove first item:
this.items.splice(0, 1);
// add it to the top:
this.items.push(first);
}
}
};
</script>
DEmo: https://codesandbox.io/s/vue-konva-list-render-l70vs?file=/src/App.vue
Im just trying to let my user take a screenshot using the toDataUrl() function.
but, without a background rectangle, all pixel are transparent , and appear black .
so the solution is to dynamicly add a rectangle, generate the image, destroy the rectangle
saveImage(){
const stage=this.$parent.$refs.stage.getStage()
var stageRect = new Konva.Rect({
x:0,
y:0,
width: stage.attrs.width,
height: stage.attrs.height,
fill: 'green',
})
console.log(stage)
const backg=new Konva.Layer();
backg.add(stageRect)
stage.add(backg)
backg.setZIndex(0)
const dataURL = stage.toDataURL({ pixelRatio: 1, mimeType:"image/png" });
backg.destroy();
this.downloadURI(dataURL, 'stage.png');
},
it works (rectangle is created before all other layer) but... i can't get the size of the stage, i mean, the viewport because the user can zoom/dezoom the stage ....
any idea ?
Just use a scale to calculate background properties:
var stageRect = new Konva.Rect({({
x: -stage.x()/ stage.scaleX(),
y: -stage.y()/ stage.scaleY(),
width: stage.width() / stage.scaleX(),
height: stage.height() / stage.scaleY(),
fill: 'green',
});
Demo: https://jsbin.com/lehasitaje/2/edit?html,js,output
I'm drawing a closed line over a busy image as a background. It's working fine.
But my goal is to highlight the stroke as much as possible. Therefore, I'm looking for a way to make the line's stroke color 'negative/inverted' pixel of what's underneath that line.
For example:
<Line ... stroke="inverted" />
Possible? Achievable in some other way?
You may be able to use the globalCompositionOperation.
Mozilla docs here
Konva example here - see the parameters for the Konva.Rect() call. The example is for text, so not exactly as you require but hopefully you can adapt for the line requirement.
I tried to create a snippet to illustrate this but could not get exactly what you require. However, you can see how to apply the globalCompositionOperation parameter for a Konva line. Change the value of the comp variable to the other composition mode names from the Mozilla page to see their effects. The line is draggable.
An alternative may be to get the canvas pixel data, work out the pixels under the line and invert them individually.
// This is the color-composition mode
var comp = 'exclusion'
var stage = new Konva.Stage({
container: 'container',
width: 500,
height: 230
});
var layer = new Konva.Layer();
stage.add(layer)
var rect = new Konva.Rect({ x: 10, y: 0, width:500, height: 230,
fillLinearGradientStartPoint: { x: -50, y: -50 },
fillLinearGradientEndPoint: { x: 250, y: 150 },
fillLinearGradientColorStops: [0, 'red', 1, 'yellow']
})
layer.add(rect)
var ln = new Konva.Line({
points: [25, 70, 300, 20],
stroke: 'red',
strokeWidth: 15,
lineCap: 'round',
lineJoin: 'round',
draggable: true,
globalCompositeOperation: comp
});
layer.add(ln);
layer.draw()
stage.draw()
#container {
width: 500px;
height: 230px;
background-color: #ccc;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/konva/3.2.5/konva.js"></script>
<div id="container"></div>
I'm trying to change the stroke color of a hexagon on mouseover, and then back to the original color on mouseout.
My problem is that, if I redraw only the hexagon after updating the stroke color, the previous color lingers around the edges of the stroke.
hexagon.on('mouseover', function(e) {
e.target.stroke('red');
e.target.draw();
});
hexagon.on('mouseout', function(e) {
e.target.stroke('gray');
e.target.draw();
});
Demo at https://codepen.io/jsgarvin/pen/dmRJXj
Here the original color is gray, and it changes to red on mouse over, but on mouse out it changes back to gray with a red dusting around all of the edges.
If I redraw the entire layer though, it seems to do what I expect, but in my particular use case I expect to have several thousand hexagons, among other things, on the layer, and that seems inefficient to redraw the entire layer if I just need to update one hexagon. Is there a more correct way to do this that I'm overlooking? Thanks!
You need to draw the layer.
hexagon.on('mouseover', function(e) {
e.target.stroke('red');
e.target.draw();
layer.draw(); // <<<<< THIS LINE IS THE FIX
});
I found this out as I was coding an alternative which I include below in the snippet. It uses a second shape and we show & hide the two so as to provide the mouseover effect. I can imagine that this will not be viable in all cases but it might help someone out so here is a working snippet.
Left hand is your example copied from your Pen with the fix included, the right is the shape switcher, just for fun.
var stage = new Konva.Stage({
container: 'container',
width: 400,
height: 150
});
var layer = new Konva.Layer();
stage.add(layer);
var c = layer.getCanvas();
var ctx = c.getContext();
var hexagon = new Konva.RegularPolygon({
x: 75,
y: 75,
radius: 55,
sides: 6,
stroke: 'gray',
strokeWidth: 10
});
hexagon.on('mouseover', function(e) {
e.target.stroke('red');
e.target.draw();
layer.draw(); // <<<<< THIS LINE IS THE FIX
});
hexagon.on('mouseout', function(e) {
e.target.stroke('gray');
e.target.draw();
layer.draw(); // <<<<< THIS LINE IS THE FIX
});
var hexagon2 = new Konva.RegularPolygon({
x: 250,
y: 75,
radius: 55,
sides: 6,
stroke: 'gray',
strokeWidth: 10
});
hexagon2.on('mouseover', function(e) {
e.target.visible(false);
hexagon3.visible(true);
layer.draw();
});
var hexagon3 = new Konva.RegularPolygon({
x: 250,
y: 75,
radius: 55,
sides: 6,
stroke: 'red',
strokeWidth: 10,
visible: false
});
hexagon3.on('mouseout', function(e) {
e.target.visible(false);
hexagon2.visible(true);
layer.draw();
});
layer.add(hexagon);
layer.add(hexagon2);
layer.add(hexagon3);
stage.draw();
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<script src="https://cdn.rawgit.com/konvajs/konva/1.6.5/konva.min.js"></script>
<p>Left image is OP's version, right is shape-switching. Mouse-over the hexagons.</p>
<div id='container'></div>
i'm using Highcharts to create a pie, and I need to put user's profile image in a circle in the middle of it. I have managed to add the image but couldn't get it to be round, and I managed to add a circle but without image background :/
what it the best way to combine the tow?
by the way it must be part of the svg and not an absolute div on top of the pie, because the tooltip needs to be on top of that image with opacity
to add a circle I used this code :
var pixelX = 438;
var pixelY = 276;
var pixelR = 70;
// add my circle
chart.renderer.circle(pixelX, pixelY, pixelR)
.attr({
zIndex: 100,
align: 'center',
// fill: 'url(https://lh6.googleusercontent.com/-gaAgFzRLxQQ/AAAAAAAAAAI/AAAAAAAAAjc/ies0iU4BEqU/photo.jpg)',
color: 'url(https://lh6.googleusercontent.com/-gaAgFzRLxQQ/AAAAAAAAAAI/AAAAAAAAAjc/ies0iU4BEqU/photo.jpg)',
stroke: 'black',
'stroke-width': 2
})
.css({
backgroundImage :'url(https://lh6.googleusercontent.com/-gaAgFzRLxQQ/AAAAAAAAAAI/AAAAAAAAAjc/ies0iU4BEqU/photo.jpg)'
})
.add();
});
this example shows how to do it with pure SVG : http://jsfiddle.net/9zkfodwp/1/
resolved it by defining a pattern with the img, works fine but now need to find a way for the pattern to be no-repeat.
code used to add pattern:
function(chart) { // on complete
var r = chart.renderer,
pattern = r.createElement('pattern')
.attr({
id: 'pattern',
patternUnits: 'userSpaceOnUse',
x: 0,
y: 0,
width: 180,
height: 190,
viewBox: '0 0 135 135'
})
.add(r.defs);
r.rect(0, 0, 135, 135, 0)
.attr('fill', '#ddd')
.add(pattern);
r.image(profileImg,0,0,135,135)
.add(pattern);
});
and when I add the circle it can have a fill of the pattern:
// add my circle
this.circle = chart.renderer.circle(pixelX, pixelY, pixelR).attr({
fill: 'url(#pattern)'
});
this.circle.add();