How to trace a click on the stroke in Konva-react - react-konva

Can you tell me how to track the click only on the stroke and not inside the body of the figure. The shapes in which you want to trace this can be anything: Star, Rect, etc. Perhaps you have encountered this.
I can't even imagine how it could be done.

You need to draw a shape with fillEnabled: false so only its stroke is visible. And only its stroke will listen to events. If you want to keep fill, then you have to use two shapes. One for fill, another for stroke and events detection.
const stage = new Konva.Stage({
container: 'container',
width: window.innerWidth,
height: window.innerHeight
});
const layer = new Konva.Layer();
stage.add(layer);
const shape = new Konva.Circle({
x: stage.width() / 2,
y: stage.height() / 2,
radius: 50,
stroke: 'green',
strokeWidth: 10,
fillEnabled: false
});
layer.add(shape);
shape.on('click', () => {
console.log('shape click');
})
<script src="https://unpkg.com/konva#^8/konva.min.js"></script>
<div id="container"></div>

Related

Echart Dragging points with more than 1 graphical element issue

I have created a chart with 3 points. I have added two graphical elements as circle and rectangle (line) for dragging the points.
Circle dragging should apply for all three points in the graph as in the expected behavior screenshot below.
Line dragging is a vertical shift for graph to move it vertically.
Circle dragging does not apply for all 3 points when I return both graphical elements in the elements array as in the code. It just applies only for 1st point.
When I return one graphical element at a time it works.
Current Behaviour
image of how chart works now
Below I have included the code which returns graphic elements
myChart.setOption({
graphic: data.map(function (item, dataIndex) {
let rec = {
type: 'group',
left: 'center',
position: myChart.convertToPixel('grid', item),
draggable: 'vertical',
ondrag: function (dx, dy) {
onPointDraggingLine(dataIndex, [this.x, this.y]);
},
children: [
{
type: 'rect',
top: '200x',
shape: {
width: 470,
height: 0
},
style: {
stroke: 'gray',
lineWidth: 5,
lineDash: [6]
},
cursor: 'move'
}
]
};
let onlyCrc = {
type: 'circle',
position: myChart.convertToPixel('grid', item),
draggable: true,
ondrag: function (dx, dy) {
onPointDragging(dataIndex, [this.x, this.y]);
},
shape: {
cx: 0,
cy: 0,
r: symbolSize / 2
},
invisible: false,
z: 100,
cursor: 'move'
};
return { elements: [rec, onlyCrc] };
})
});
I have tried to return graphical elements as array of objects .And I have tried by giving a condition for dataIndex while mapping. but didn't work. Expected behavior is as below.
Expected Behavior.
image of how chart should work
Below I have attached the whole code I tried in Codepen.
Link to Minimal Reproduction
https://codepen.io/rushaidrilaf/pen/bGKONRj
I was able to Solve this. Started to resolve it by pointing the dragging function to 2nd point in the graph and updating the position of circular graphical element while dragging the horizontal line.
solution can be viewed from this link
https://codepen.io/rushaidrilaf/pen/bGKONRj
enter code here

Using Matter.js, how to position SVG paths in a compound body?

I'm trying to create a capital letter "A" as an SVG shape for use with Matter.js but the letter shape displays incorrectly.
CodePen here and duplicated here:
function percentX(percent) {
return Math.round((percent / 100) * window.innerWidth);
}
function percentY(percent) {
return Math.round((percent / 100) * window.innerHeight);
}
const Engine = Matter.Engine,
Bodies = Matter.Bodies,
Body = Matter.Body,
Svg = Matter.Svg,
Vertices = Matter.Vertices,
Composite = Matter.Composite,
Render = Matter.Render,
Runner = Matter.Runner;
// create an engine
const engine = Engine.create(),
world = engine.world;
// create a renderer
const render = Render.create({
element: document.body,
engine: engine,
options: {
wireframes: false,
showInternalEdges: false,
width: percentX(100),
height: percentY(100),
background: "transparent"
}
});
let bodies = [],
bgColor = "#0A0618";
// SVGs
let vertexSets = [],
svgLetter,
svgLetterLegOne,
svgLetterLegTwo,
svgLetterCounter;
let letterX = percentX(60);
let letterY = percentY(20);
let letterXLegOne = percentX(60) - 40;
let letterYLegOne = percentY(20) + 40;
let letterXLegTwo = percentX(60) + 40;
let letterYLegTwo = percentY(20) + 40;
let letterSize = (window.innerWidth / 1000);
// A
// silhouette test (incorrectly displaying Batman ears)
$('#svg-test').find('path').each(function(i, path) {
svgTest = Bodies.fromVertices(
percentX(30),
letterY,
Vertices.scale(Svg.pathToVertices(path, 10),
letterSize,
letterSize), {
render: {
fillStyle: "white",
strokeStyle: "white",
lineWidth: 2
}
}, true);
vertexSets.push(svgTest);
});
// letter base shape
$('#svg-3').find('path').each(function(i, path) {
svgLetter = Bodies.fromVertices(
letterX,
letterY,
Vertices.scale(Svg.pathToVertices(path, 10),
letterSize,
letterSize), {
render: {
fillStyle: "yellow",
strokeStyle: "yellow",
lineWidth: 2
}
}, true);
vertexSets.push(svgLetter);
});
// left leg
$('#svg-3-leg-1').find('path').each(function(i, path) {
svgLetterLegOne = Bodies.fromVertices(
letterXLegOne,
letterYLegOne,
Vertices.scale(Svg.pathToVertices(path, 10),
letterSize,
letterSize), {
render: {
fillStyle: "green",
strokeStyle: "green",
lineWidth: 2,
isStatic: true
}
}, true);
vertexSets.push(svgLetterLegOne);
});
// right leg
$('#svg-3-leg-2').find('path').each(function(i, path) {
svgLetterLegTwo = Bodies.fromVertices(
letterXLegTwo,
letterYLegTwo,
Vertices.scale(Svg.pathToVertices(path, 10),
letterSize,
letterSize), {
render: {
fillStyle: "blue",
strokeStyle: "blue",
lineWidth: 2,
isStatic: true
}
}, true);
vertexSets.push(svgLetterLegTwo);
});
// counter (hole in the center), no need for offset repositioning
$('#svg-3-counter').find('path').each(function(i, path) {
svgLetterCounter = Bodies.fromVertices(
letterX,
letterY,
Vertices.scale(Svg.pathToVertices(path, 10),
letterSize,
letterSize), {
render: {
fillStyle: bgColor,
strokeStyle: bgColor,
lineWidth: 2
}
}, true);
vertexSets.push(svgLetterCounter);
});
// create compound body for letter "A"
var compoundBodyA = Body.create({
parts: [svgLetter, svgLetterLegOne, svgLetterLegTwo, svgLetterCounter]
});
// add A and O compound bodies to the world
Composite.add(world, [
compoundBodyA
]);
// add all SVGs to the world
Composite.add(world, vertexSets);
// run the renderer
Render.run(render);
// create runner
const runner = Runner.create();
// run the engine
Runner.run(runner, engine);
// hold in place for testing
world.gravity.y = 0;
world.gravity.x = 0;
html {
box-sizing: border-box;
}
*, *::before, *::after {
box-sizing: inherit;
}
*:focus, *::before:focus, *::after:focus {
outline: none;
}
* {
font-family: monaco, courier;
}
body {
margin: 0;
padding: 0;
background: #0A0618;
}
svg {
display: none;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/pathseg#1.2.1/pathseg.js"></script>
<script src="https://cdn.jsdelivr.net/npm/poly-decomp#0.3.0/build/decomp.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/matter-js/0.18.0/matter.min.js"></script>
<svg class="svg-letter" id="svg-test">
<path class="st0" d="M59.3,0h46.4l59,141h-50.8l-7.4-18.8h-49L50.3,141h-50L59.3,0z"/>
</svg>
<svg class="svg-letter" id="svg-3">
<path d="M57.2,122.2H7.9L59,0h46.4l51.1,122.2h-50.3H57.2z"/>
</svg>
<svg class="svg-letter" id="svg-3-leg-1">
<path d="M0,141l7.9-18.8h49.3L50,141H0z"/>
</svg>
<svg class="svg-letter" id="svg-3-leg-2">
<path d="M106.2,122.2h50.3l7.9,18.8h-50.8L106.2,122.2z"/>
</svg>
<svg class="svg-letter" id="svg-3-counter">
<path d="M94.6,89L81.8,55L69,89H94.6z"/>
</svg>
I know Matter.js can't always handle SVGs with compound paths (an internal path creating a knockout) so my plan was to have two separate paths, the silhouette of the shape and the knockout, and group them as a compound body. Only the silhouette isn't even displaying correctly (the white version on the left). For some reason, the shape always has those Batman ears which I can't get rid of.
So my new plan is to break the silhouette shape into three parts, the main body (in yellow) and two legs (green and blue). That allows all three parts to have only four sides which seems to prevent the bug.
My problem is positioning those two legs so that they are always precisely butting up against the main body shape. I can adjust the position offset to accomplish this but since I've set the width and height of the render object to be proportional to the width and height of the browser, the letter breaks apart if the page is loaded in any other sized window.
Using variables (lines 45–54), I've tried setting the width and height of the legs to reference the main body shape with offsets:
svgThree.position.x - 40,
svgThree.position.y + 40,
And I've tried keeping all units and offsets proportional:
percentX(60) - percentX(2),
percentY(20) + percentX(2),
But nothing works. Without setting the letter to exact pixel dimensions, is there any way to keep these three paths touching and in precise relation to each other across different browser sizes?
Alternatively, if there's any way to build that SVG shape to avoid that bug, I'd greatly appreciate such a solution.
(Using Chrome Version 102.0.5005.115)
It's not a perfect solution but the one I eventually went with was this:
Setting the letter size to a static number (0.8), instead of a derivation of the window width, allowed me to set the width and height of the two legs of the letter to a static offset distance from the main body shape, so the change in the original CodePen above would be to lines 48–54:
let letterXLegOne = letterX - 43;
let letterYLegOne = letterY + 49;
let letterXLegTwo = letterX + 43;
let letterYLegTwo = letterY + 49;
let letterSize = 0.8;
The fix is incorporated into this CodePen as a final product.

how to add only padding top and padding bottom to Konva Text

I want to just give padding top and padding bottom to my Text in canvas but when I give padding={20}, it gives padding to all the 4 sides.
Can you plase help me to get rid of left/right paddings.
At the current version 8.3.5, Konva does't support that directly.
As a workaround, you can use Konva.Rect for bounding box and transforming target. Then you can proxy rectangle changes to text object.
const stage = new Konva.Stage({
container: 'container',
width: window.innerWidth,
height: window.innerHeight
});
const layer = new Konva.Layer();
stage.add(layer);
const text = new Konva.Text({
x: 100,
y: 50,
width: 100,
text: 'hello, this is a long test for testing bounding boxes'
})
layer.add(text);
const padding = 20;
const rect = new Konva.Rect({
x: text.x(),
y: text.y() - padding,
height: text.height() + padding * 2,
width: text.width(),
fill: 'rgba(0, 255, 0, 0.05)',
draggable: true
});
layer.add(rect);
const tr = new Konva.Transformer({
nodes: [rect],
rotateEnabled: false,
enabledAnchors: ['middle-left', 'middle-right']
})
layer.add(tr);
rect.on('dragmove transform', () => {
text.setAttrs({
x: rect.x(),
y: rect.y() + padding,
width: rect.width() * rect.scaleX()
});
rect.height(text.height() + padding * 2);
})
<script src="https://unpkg.com/konva#^8/konva.min.js"></script>
<div id="container"></div>

Responsive sizing with paper.js

wondering what the best way of creating responsive canvas sizes. At the moment I have to double up all my code using the following pattern
var cicrle = new Path.Circle({ radius: 40, fillColor: 'red' })
circle.position = view.center
view.onResise = function(event){
circle.position = view.center
}
This is ok for one item, but once the items start adding up it a lot of doubling up of code.
Is there a better way of doing things?
Thanks in advance
Paper.js has 2 kinds of grouping classes: Group and Layer; if you update one of those's position, you'll update all its descendants positions at once.
By default, the created items are implicitely added to the active layer (project.activeLayer) so for a simple scene:
project.activeLayer.position = view.center;
should do the trick.
See this sketch for a demonstration.
new Path.Circle({
center: view.center,
radius: 50,
fillColor: 'orange'
});
new Path.Circle({
center: view.center + 50,
radius: 50,
fillColor: 'orange'
});
function onResize() {
project.activeLayer.position = view.center;
}

change the legend.y property on browser resize

we use the highchart control with Angular and bootstrap.
To adjust the vertical space between the chart and the legend (both are rendered by highchart as svg elements), we set the y property of the legend on page load (in the controller) like this:
$scope.chartContracts = {
options: {
chart: {
type: 'pie',
marginBottom: 50
},
legend : {
layout: 'horizontal',
align: 'center',
verticalAlign: 'bottom',
x: 0,
y: 4,
Now in one of the Bootstraps layouts, the spacing (y) should be -10 in stead of 4 because for some reason an extra spacing is added.
So it should be based on media query or something similar I guess?
The question is how to do this... I tried with css but I can't seem to style SVG elements (they are called <g>)?
You can check window width and then return correct value.
var wWidth = $(window).width(),
value;
if(wWidth < 400) {
value = 5;
} else {
value = 10;
}
//scope
y: value

Resources