How to get clicked element's position and id to change color and draw lines on it - konvajs

I'm developing a basic window frame configurator. I splitted glasses in function below. I want to change color when i clicked and get the position of clicked glass to draw openin direction lines.
I tried to understand and implement Lavrton's method(https://codesandbox.io/s/0xj7zml2zl?file=/src/index.js) but i couldn't succeed.
function glassDraw(piece, frameWidth, frameHeight) {
var glassGroup = new Konva.Group();
for (i = 0; i <piece; i++) {
var glass = new Konva.Rect({
name: 'Rect',
x: padding + (frameWidth / piece) * i,
y: padding,
width: frameWidth / piece,
height: frameHeight - padding * 2,
fill: 'lightblue',
id: 'glass'+i,
});
glassGroup.add(glass);
}
glassGroup.find("Rect").on('click', function (e) {
// get id of the cube i drag
var clickedId = e.target.attrs.id;
$('#theId').html(clickedId);
})
return glassGroup;
}
When i use getelementbyid method with id of konva element("glass"+i), it returns null. I think i need to get as a konva element.

You have to create a click listener for all of your rectangles.
for (let rect of glassGroup.children) {
rect.on('click', () => {
console.log(rect.x(), rect.y()); // current position of the object
console.log(rect.id()); // log id of the object
rect.fill('green'); // set color to green
layer.batchDraw(); // update layer (batchDraw just for better performance .draw() would work to)
})
}
Make sure you always update the stage by either call stage.draw() or layer.draw() (or batchDraw() for better performance) after changing something, otherwise your stage will not show anything of what you do.
If something with this code don't work feel free to ask.
Have a nice day.

Related

how to progressively add drawable to a canvas?

I have points generated one by one, and when a new point is generated, I want to draw a line segment connecting with the previous point. Like this:
var x by remember { mutableStateOf( 0.0f)}
var y by remember { mutableStateOf( 0.5f)}
var pStart by remember { mutableStateOf(Offset(0f, 0.5f))}
Canvas(modifier = Modifier.fillMaxSize()) {
canvasWidth = size.width
canvasHeight = size.height
val pEnd = Offset(x * canvasWidth, (1-y) * canvasHeight)
val col = if (pEnd.y < pStart.y) Color.Green else Color.Red
drawLine(
start = pStart,
end = pEnd,
strokeWidth = 4f,
color = col
)
pStart = pEnd
}
But this only draws the segment in a flash and no segments stay on the screen.
I know I can save the points to a list and redraw all the segments whenever a new point is added. But I just hope to economize. Is it possible?
There's no practical other way. You COULD in fact, keep track of just two points, adding a whole new Canvas (all Transparent and Filling the maximum Size, stacked on top of one another), for each extra point that is added. This does seem a bit impractical, but maybe try it out and do some benchmarking to see which one checks out. This is the only OTHER way I could think of, where you do not have to store all the points and recompose every time a point is added, since all the other lines would technically be frozen in space.
In response to the somewhat (unreasonably) aggressive comment below, here's some sample code. I assume you have a stream of new points coming in so a LiveData object is assumed to be the source of that, which I shall be converting to a MutableState<T> for my use-case.
var latestPoint by liveData.collectAsState()
var recordedPoint by remember { mutableStateOf(latestPoint) }
var triggerDraw by remember { mutableStateOf(false) }
var canvasList = mutableStateListOf<#Composable () -> Unit>Canvas>() // Canvas is the Composable
if(triggerDraw){
canvasList.add(
Canvas(){
/* you have the recordedPoint, and latestPoint, simply draw a line here */
}
)
triggerDraw = false
}
LaunchedEffect(latestPoint){
triggerDraw = true
}
canvasList.forEach {
it() // Invoke the Composable here
}
Thanks Dad!

Scaling IMAGE around pointer in Konva

Can't wrap my head around this and get it to work. Trying to transpose this sample on Konva, but can not get it to work with an image inside a layer, inside the stage.
The sample I am trying to reproduce is the "Zooming stage relative to pointer position" sample.
https://konvajs.org/docs/sandbox/Zooming_Relative_To_Pointer.html
Any help would generate kudos.
The trick with zooming from an arbitrary point such as the mouse position is to understand that you must relocate the stage to keep the point under the mouse consistent. I provide the working vanilla JS snippet below to illustrate the point. The guts are based on the original demo at the Konva docs site, just with more comments in the code. Translating into React should not be difficult once the basis of the technique is explained.
The gray area is the canvas, and the pink rect is the stage. Note that we start with the stage positioned into the canvas just to show that the stage can be moved like any other object.
The stage has a dot-pattern to help show the effects in the demo. The solution works for any layout of layers, shapes or images (as per OP question). Note that the shapes themselves do not need to be handled individually - the scaling is applied to the stage.
Move your mouse anywhere on the stage and scroll to see the zoom effect, noting how the top-left of the pink stage rectangle moves with the zoom. Now un-tick the checkbox to turn off the stage position adjustment - note now that the zoom is no longer respecting the mouse position because the stage position is not being moved.
Thus we have illustrated that it is necessary to move the stage sympathetically during the zoom but how do we know the amount to move by?
The tip to know is that you need to get the absolute distance of the mouse position over the stage before the new scale is applied, then apply the new scale to the stage, then use the computed distance * new scale to compute the new stage position. See the stage.on('wheel') event in the snippet for working code.
See also the codepen here.
// Making the stage
let stage = new Konva.Stage({
container: "container1",
width: $('#container1').width(),
height: $('#container1').height(),
draggable: true,
x: 40,
y: 60
});
// Make the layer
let layer = new Konva.Layer();
// make a rect to fill stage to show where it is.
let rect = new Konva.Rect({
x:0,
y: 0,
width: $('#container1').width(),
height: $('#container1').height(),
fill: 'magenta',
opacity: 0.3
})
layer.add(rect);
let grid = {w: $('#container1').width(), h: $('#container1').height()},
gridStep = 40,
mouseCircleRadius = 80,
circles = [],
dotRadius = 10;
// Make the grid of circles
for (let i = gridStep; i < grid.w; i = i + gridStep ){
for (let j = gridStep; j < grid.h; j = j + gridStep ){
let c = new Konva.Circle({ x: i, y: j, radius: dotRadius, fill: 'cyan'})
circles.push(c);
layer.add(c)
}
}
// Add layer to stage
stage.add(layer)
stage.on('mousemove', function (e) {
var pointer = stage.getPointerPosition();
// show the pointer and stage positions for illustration
$('#trace').html('Pointer.x = ' + pointer.x + ' stage.x() = ' + stage.x())
});
// this is the scale factor to be applied at each step.
var scaleBy = 1.01;
// this is the event that fires when the mouse wheel spins.
stage.on('wheel', (e) => {
e.evt.preventDefault();
// note the old scale to be used when deciding the current stage position
var oldScale = stage.scaleX();
// note the mouse pointer position relative to the stage at current scale
var pointer = stage.getPointerPosition();
// calculate the mouse pointer position at scale = 1.
// pointer.x is relative to the canvas - not affected by stage scale,
// stage.x() is the poistion of the stage on the canvas.
// This gives the distance from the pointer to the
var mousePointTo = {
x: (pointer.x - stage.x()) / oldScale,
y: (pointer.y - stage.y()) / oldScale,
};
// Calculate the new scale - slightly different calc for zoom in versus zoom out, as you would expect.
var newScale = (e.evt.deltaY > 0 ? oldScale * scaleBy : oldScale / scaleBy);
// Apply the new scale to the stage. Note that, assuming a zoom-in op, at this point in time the shapes on the stage would
// seem to have jumped down + right. We will shortly 'move' the stage left+up to counter this effect.
stage.scale({ x: newScale, y: newScale });
// To do that we have to calculate what the position of the stage must be relative to the mouse pointer position at the new scale.
// Note - remove the 'if' for your live code, the checkbox is for illustration only.
if ($('#fixstagepos').prop('checked')){
var newPos = {
x: pointer.x - mousePointTo.x * newScale,
y: pointer.y - mousePointTo.y * newScale,
};
// and apply the new position to the stage. Again in the case of a zoom-in op this has the effect of moving the stage left + up, countering the apparent movement
// caused by the change in scale.
stage.position(newPos);
}
});
.container {
width: 800px;
height: 400px;
background-color: silver;
margin: 5px;
}
#trace {
max-height: 200px;
overflow-y: scroll;
font-family: 'Courier'
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<script src="https://unpkg.com/konva#8/konva.min.js"></script>
<p id="info">Grey is the canvas and the pink rect shows the stage position. The stage is intitially positioned at {x: 40, y: 60} and can be dragged.</p>
<p>Move mouse over stage and scroll to zoom in & out. Use checkbox to see what happens without stage pos correction. </p>
<p>Apply correction to stage position <input id='fixstagepos' type='checkbox' checked='1' /></p>
<div id='container1' class='container'>
</div>
<p id='trace'></p>

Custom Hit Detection on Konva Group

I'm attempting to expand the click area of a group of shapes, but there appears to be no hitFunc property on groups.
var patternControl = new Konva.Group();
patternControl.hitFunc(function(context) {
context.beginPath();
context.arc(0, 0, outerRadius + patternWidth, 0, Math.PI * 2, true);
context.fillStrokeShape(this);
});
Is there any way to do apply custom hit functions to a group?
Only shapes can be used for hit detection. As a workaround, you can disable hits for all shapes with shape.listeting(false) and then add a "fake" shape to the group that will be used as a hit area:
var patternControl = new Konva.Group();
var hitShape = new Konva.Shape({
// make it transparent, so it is not visible
fill: 'rgba(0,0,0,0)',
hitFunc: (context, shape) => {
context.beginPath();
context.arc(0, 0, outerRadius + patternWidth, 0, Math.PI * 2, true);
context.fillStrokeShape(shape);
}
});
patternControl.add(hitShape);

Konva - visualize custom hit area

Is there an easy way to visualize a custom hit area shape?
As described here
https://konvajs.github.io/docs/events/Custom_Hit_Region.html
the hitFunc attribute can be set to a function that uses the supplied context to draw a custom hit area / region. Something like this:
var star = new Konva.Star({
...
hitFunc: function (context) {
context.beginPath()
context.arc(0, 0, this.getOuterRadius() + 10, 0, Math.PI * 2, true)
context.closePath()
context.fillStrokeShape(this)
}
})
For debugging purposes, I would like an easy way to toggle visual rendering of the shape (circle in this case), eg by filling it yellow.
Thanks :)
Currently, there is no public API for that. But you still can add hit canvas into the page somewhere and see how it looks:
const hitCanvas = layer.hitCanvas._canvas;
document.body.appendChild(hitCanvas);
// disable absolute position:
hitCanvas.style.position = '';
http://jsbin.com/mofocagupi/1/edit?js,output
Or you can add hit canvas on top of the stage and apply an opacity to make scene canvases visible:
const hitCanvas = layer.hitCanvas._canvas;
stage.getContainer().appendChild(hitCanvas);
hitCanvas.style.opacity = 0.5;
hitCanvas.style.pointerEvents = 'none';
http://jsbin.com/gelayolila/1/edit?js,output

openlayers-3 precompose, how to do layer clipping with a rectangular geometry

I'm trying to make a simple app doing something similar to the Layer Spy Example, but instead of a circle flowing the mouse pointer, I would like to do the clipping based on a rectangle which is always centered in the map.
(preferably with "fixed" size" i.e. changes "extent" when zooming)
Any help appreciated
Thanks
Frode
You can do it like in the example that you are referring to. But instead of context.arc() use context.rect() with a fixed position to clip the layer. Something like:
imagery.on('precompose', function(event) {
var ctx = event.context;
var pixelRatio = event.frameState.pixelRatio;
ctx.save();
ctx.beginPath();
var x = ctx.canvas.width / 2 - 100;
var y = ctx.canvas.height / 2 - 100;
ctx.rect(x, y, 100, 100);
ctx.clip();
});
http://jsfiddle.net/eo1c1x78/

Resources