I'm trying to expand the Connected Objects demo by allowing two nodes (shapes of Circle class) to be double referenced (A connects to B with Arrow1 and B connects to A with Arrow2). I work with react-konva package.
I have implemented a demo on Code Sandbox with some basic functionality.
On line 5, 6 you'll find the Nodes info, on line 21 there exists a high-order component that creates the Arrow based on the start Node and end Node position.
In the default example, the arrows are working as expected. If you try to set the value of redNode.x to 300 the arrows overlap. The same happens when blueNode.x is equal to -100. This has something to do with the way I calculate the arrows (I suspect the equations on line 38).
Also note that as redNode.x moves to value 300, the two arrows approach each other (this happens on other values too), which is something I do not want to happen. I expect the arrows to have the same shape when the two nodes change position and not to overlap or approach each other. Unfortunately, my lack of mathematics does not help me solve the problem. I also tried to create a custom shape using quadraticCurveTo method without success.
Thanks in advance for the help. I appreciate all the solutions.
There are many ways to make curved lines. Here is my attempt to make it better:
import React from "react";
import ReactDOM from "react-dom";
import { Stage, Layer, Circle, Arrow, Text } from "react-konva";
const BLUE_DEFAULTS = {
x: 100,
y: 100,
fill: "blue",
width: 30,
height: 30,
draggable: true
};
const RED_DEFAULTS = {
x: 100,
y: 300,
fill: "red",
width: 30,
height: 30,
draggable: true
};
const Edge = ({ node1, node2 }) => {
const dx = node1.x - node2.x;
const dy = node1.y - node2.y;
let angle = Math.atan2(-dy, dx);
const radius = 20;
const curvePower = 30;
const arrowStart = {
x: node2.x + -radius * Math.cos(angle + Math.PI),
y: node2.y + radius * Math.sin(angle + Math.PI)
};
const arrowEnd = {
x: node1.x + -radius * Math.cos(angle),
y: node1.y + radius * Math.sin(angle)
};
const arrowCurve = {
x:
(arrowStart.x + arrowEnd.x) / 2 +
curvePower * Math.cos(angle + Math.PI / 2),
y:
(arrowStart.y + arrowEnd.y) / 2 +
curvePower * Math.sin(angle - Math.PI / 2)
};
return (
<Arrow
tension={0.2}
points={[
arrowStart.x,
arrowStart.y,
arrowCurve.x,
arrowCurve.y,
arrowEnd.x,
arrowEnd.y
]}
stroke="#000"
fill="#000"
strokeWidth={3}
pointerWidth={6}
/>
);
};
const App = () => {
const [blueNode, updateBlueNode] = React.useState(BLUE_DEFAULTS);
const [redNode, updateRedNode] = React.useState(RED_DEFAULTS);
return (
<Stage width={window.innerWidth} height={window.innerHeight}>
<Layer>
<Text text="Drag any node to see connections change" />
<Edge node1={blueNode} node2={redNode} />
<Edge node1={redNode} node2={blueNode} />
<Circle
{...blueNode}
onDragMove={e => {
updateBlueNode({ ...blueNode, ...e.target.position() });
}}
/>
<Circle
{...redNode}
onDragMove={e => {
updateRedNode({ ...redNode, ...e.target.position() });
}}
/>
</Layer>
</Stage>
);
};
const rootElement = document.getElementById("root");
ReactDOM.render(<App />, rootElement);
Demo: https://codesandbox.io/s/react-konva-double-connected-objects-m5g22
Related
const coords = [
{
name: "Rijnstraat vervolg",
points: [
[695, 500],
[680, 480],
[580, 475],
[520, 460],
],
width: 10,
types: [types.car, types.truck, types.pedestrian, types.bike],
oneway: true,
},
...
]
I have an array that looks like the above and I want to make a function that generates a path (along the other paths, which are the black lines in the image) from a black or gray circle to another black or gray circle. So I want the function to take in a start and end point (black or gray circle) and return an array of points that follow the already existings paths. (Which are sort of like roads)
And the function can be described as someone who is trying to get to somewhere.
I already tried a recursive function that looks like this:
function calculatePathToShop(startPoint, shopPoint) {
const targetShopPoint = findClosestPointOnPath(shopPoint);
const targetPathIndex = findPathByPoint(targetShopPoint);
const connectedPaths = calculateConnectedPaths(targetPathIndex);
let startPathIndex = -1;
connectedPaths.forEach(path => {
const pathPoints = coords[path].points;
pathPoints.forEach(pathPoint => {
if (comparePoints(startPoint.point, pathPoint)) startPathIndex = path;
});
});
if (startPathIndex == -1) return false;
let startPathPoints = coords[startPathIndex].points;
let targetPathPoints = coords[targetPathIndex].points;
if (!comparePoints(startPoint.point, startPathPoints[0])) startPathPoints.reverse();
ctx.strokeStyle = "rgba(255, 0, 0, .05)";
}
This one generated a path (along the existing ones) to a shop point, which is almost the same as a gray point. But this worked for some starting points, but the rest would just straight up fail
So does anyone know an algorithm, or has a function/solution that I can use to generate the path that someone can walk along the road (the black lines in the image)
Full coords array, and part of my already existing code is found here: https://raw.githubusercontent.com/CodeFoxDev/people-simulation/main/func/paths.js
(The rest of the code is in the github repo itself)
Fixed step interpolation
To interpolate a line segment you divide the vector from the start pointing to the end by the number of steps.
EG
steps = 100;
start = {x: 50, y: 100}
end = {x: 150, y: 300}
step = {x: (end.x - start.x) / steps, y: (end.y - start.y) / steps};
Then loop that number of steps adding the vector to a position initialized to the start point.
points = []; // array of interpolated points
point = {...start} // set start position.
while (steps--) {
points.push({...point});
point.x += vec.x;
point.y += vec.y;
}
points.push({...end}); // last point at end
This will create different spacing for different line lengths.
Fixed distance interpolation
To get a constant spacing between points you will need to use the lines' length to get the number of steps.
pixelsPerStep = 2; // distance between points.
start = {x: 50, y: 100}
end = {x: 150, y: 300}
step = {x: end.x - start.x, y: end.y - start.y};
lineSteps = Math.hypot(step.x, step.y) / pixelsPerStep;
points = []; // array of interpolated points
for (i = 0; i < lineSteps ; i += 1) {
u = i / lineSteps;
points.push({x: start.x + step.x * u, y: start.y + step.y * u});
}
// check to add end point
Note that the last point may or may not be at the correct distance. Due to rounding errors in floating point numbers you will need to check if the last point is close to the correct spacing and whether or not to include it.
eg from code above
// add last point if within (0.01 * pixelsPerStep) pixels of correct spacing
if (Math.abs(lineSteps - i) < 0.01) {
points.push({...end});
}
Note Use the overflow lineSteps - i when interpolating many line segments, to carry the correct start offset to each subsequent line segment.
Example
The code below is an example of a constant spaced set of points interpolated from another set of points.
The example draws the new points in black dots. The original points are rendered in red.
Note that the distance between new points is constant and thus may not fall on the original (red) points.
Note that there is a check at the end to test if a last point should be added.
const ctx = canvas.getContext("2d");
const P2 = (x, y) => ({x, y});
const points = [
P2(100,90),
P2(300,210),
P2(350,110),
P2(50,10),
P2(6,219),
];
const interpolatedPoints = interpolatePath(points, 35);
drawPoints(interpolatedPoints, 2);
ctx.fillStyle = "RED";
drawPoints(points);
function drawPoints(points, size = 1) {
ctx.beginPath();
for (const p of points) {
ctx.rect(p.x - size, p.y - size, size * 2 + 1, size * 2 + 1);
}
ctx.fill();
}
function interpolatePath(path, pixelStep) {
const res = [];
var p2, i = 1, overflow = 0;
while (i < path.length) {
const p1 = path[i - 1];
p2 = path[i];
const dx = p2.x - p1.x;
const dy = p2.y - p1.y;
const len = Math.hypot(dx, dy) / pixelStep;
let j = overflow;
while (j < len) {
const u = j / len;
res.push(P2(p1.x + dx * u, p1.y + dy * u));
j++;
}
overflow = j - len;
i++;
}
// add last point if close to correct distance
if (Math.abs(overflow) < 0.01) {
res.push(P2(p2.x, p2.y));
}
return res;
}
<canvas id="canvas" width="400" height="400"></canvas>
This question already has an answer here:
How can I translate between client coordinates of a HTML video element and picture coordinates?
(1 answer)
Closed 23 days ago.
I have an image. for this i would like to get the touch events start, move and end.
I need the exact image-pixel of the touch.
The Problem is that the actual image is smaller than the HTMLImageElement.
(css style object-fit: scale-down)
I don't need the touch events for the HTMLImageElement but the actual image itself.
How do I archive this?
btw. I use React (if this helps) but I can (hopefully) adapt the answer myself.
Current Attempt:
<img ...
onTouchStart={(event) => {
const touch = event.changedTouches[0];
const rect = event.currentTarget.getBoundingClientRect();
const pxlX = touch.screenX - rect.left;
const pxlY = touch.screenY - rect.top;
// other stuff with (pxlX, pxlY)
}} />
My Solution in the end
import { useState } from "react";
import ImageSrc from "./assets/image.png";
export default function App() {
const [text, setText] = useState("undefined");
return <div className="w-screen h-screen overflow-hidden">
<img src={ImageSrc} alt="" className="object-scale-down w-full h-full" onMouseMove={(event) => {
const {x, y} = getXY(event);
setText(`${x}/${y}`);
}} />
<span className="fixed top-0 left-0 pointer-events-none">{text}</span>
</div>;
}
function getXY(event: React.MouseEvent<HTMLImageElement, MouseEvent>): {x: number, y: number} {
const img = event.currentTarget;
const [imgWidth, imgHeight] = [img.naturalWidth, img.naturalHeight];
const [boxWidth, boxHeight] = [img.width, img.height];
const imgRatio = imgWidth / imgHeight;
const boxRatio = boxWidth / boxHeight;
if (imgRatio < boxRatio) {
const x0 = (boxWidth - boxHeight * imgWidth/imgHeight) / 2;
const x = Math.round(event.nativeEvent.offsetX - x0);
return {x: x, y: event.nativeEvent.offsetY};
} else {
const y0 = (boxHeight - boxWidth * imgHeight/imgWidth) / 2;
const y = Math.round(event.nativeEvent.offsetY - y0);
return {x: event.nativeEvent.offsetX, y: y};
}
}
Use img.naturalHeight and img.naturalWidth to determine the position at which the effective image starts.
Briefly, the following quantity will be negative if the Y of the event is relative to a point above the image, so capture it in your event and check:
y0 = (400 - 400*mainImg.naturalHeight/mainImg.naturalWidth)/2;
var effectiveY = event.offsetY - y0;
A little math is involved here, feel free to ask if you need a general description when the image can be a portrait, or if you still need help detecting events below the image.
var y0;
window.addEventListener("load", function(ev) {
y0 = (400 - 400*mainImg.naturalHeight/mainImg.naturalWidth)/2;
});
mainImg.addEventListener("mousemove", function(ev) {
infoDiv.innerText = ev.offsetY - y0;
});
img {
object-fit: scale-down;
border: 1px solid #AAA;
}
effectiveY is:
<span id="infoDiv"></span>
<br>
<img id="mainImg" src="https://st.hzcdn.com/simgs/pictures/gardens/garden-with-oval-lawns-fenton-roberts-garden-design-img~1971dc9307c80c73_4-8900-1-60a5647.jpg" width=400 height=400>
I'm attempting to create a crude database diagram generator using D3, but I can't figure out how to get connectors between fields. I can get straight lines going from two points, but I wanted it to be rounded and like a path I guess.
I've tried to put together an example of just that specific issue, linking two text fields:
https://codesandbox.io/s/gifted-bardeen-5hbw2?fontsize=14&hidenavigation=1&theme=dark
Here's an example from dbdiagram.io of what I'm referring to:
I've been reading up on the d attribute and the various commands, but nothing seems even close. I suspect the forceSimulation method, especially the forceCenter function might be messing up the relative positioning when I use the lower-cased commands. But not 100% on that.
You can compute a connector path between 2 points by connectorPath routine:
const source = {x: 200, y: 120};
const target = {x: 50, y: 20};
const MAX_RADIUS = 15;
const connectorPath = (from, to) => {
if (from.y === to.y || from.x === to.x)
return `M ${from.x},${from.y} L ${to.x},${to.y}`;
const middle = (from.x + to.x) / 2;
const xFlag = from.x < to.x ? 1 : -1;
const yFlag = from.y < to.y ? 1 : -1;
const dX = Math.abs(from.x - to.x);
const dY = Math.abs(from.y - to.y);
const radius = Math.min(dX / 2, dY / 2, MAX_RADIUS);
return `M ${from.x},${from.y} H ${middle - radius * xFlag} Q ${middle},${from.y} ${middle},${from.y + radius * yFlag} V ${to.y - radius * yFlag} Q ${middle},${to.y} ${middle + radius * xFlag},${to.y} H ${to.x}`;
};
d3.select('#source').attr('cx', source.x).attr('cy', source.y);
d3.select('#target').attr('cx', target.x).attr('cy', target.y);
d3.select('#connector').attr('d', connectorPath(source, target));
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/5.7.0/d3.min.js"></script>
<svg width="300" height="200">
<path id="connector" stroke="blue" fill="none" />
<circle id="source" fill="red" r="5"/>
<circle id="target" fill="green" r="5"/>
</svg>
I have developed my circular progress bar from angular-circular-progress github.
My current input was:
I need to modify the end of the progress bar with a small CIRCLE and a VALUE CENTRE of the circular progress bar with real animation value based on svg movement. How I can do that? I really need help from you all guys.
My expected output should be:
My current snippet:
angular.module('myModule', ['angular-circular-progress'])
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.3.14/angular.min.js"></script>
<script src="http://pencil.my/assets/js/circularProgress.js"></script>
<div ng-app="myModule">
<circular-progress
value="80"
max="100"
orientation="1"
radius="100"
stroke="10"
base-color="#fff"
progress-color="#f9991d"
iterations="100"
animation="easeInOutCubic"
></circular-progress>
</div>
Have you look at ProgressBar.js ?
var bar = new ProgressBar.Circle(container, {
enter code herecolor: '#aaa',
// This has to be the same size as the maximum width to
// prevent clipping
strokeWidth: 4,
trailWidth: 1,
easing: 'easeInOut',
duration: 1400,
text: {
autoStyleContainer: false
},
from: { color: '#aaa', width: 1 },
to: { color: '#333', width: 4 },
// Set default step function for all animate calls
step: function(state, circle) {
circle.path.setAttribute('stroke', state.color);
circle.path.setAttribute('stroke-width', state.width);
var value = Math.round(circle.value() * 100);
if (value === 0) {
circle.setText('');
} else {
circle.setText(value);
}
Here is a Fiddle
Regards,
Alex
Create a group that has a transparent object the size of your circle and has the small circle at one edge, then rotate that group around its center. The internals of the SVG would look something like
<g id="pip" transform="rotate({{360/value}}, 50, 50)">
<!--contents of g-->
</g>
This assumes the diameter of the circle is 100.
Here is a function that converts a number from 0 to 100 into the x,y coordinates needed to give the circle object (drawn with canvas tools) a circular path:
function drawCircle(percentage, ctx) {
var angle = percentage / 100.0;
var xPos = 140 * Math.cos((angle * (2 * Math.PI)) - (Math.PI * .5));
var yPos = -140 * Math.sin((angle * (2 * Math.PI)) - (Math.PI * .5) );
ctx.fillStyle = "rgba(0, 0, 200, 0.5)";
ctx.beginPath();
ctx.arc (xPos + 150, 150 - yPos, 10, 0, 2 * Math.PI);
ctx.stroke();
}
Look at this plunker to see it in action:
https://plnkr.co/edit/ePMK7DVLB3OH15oUJzr1?p=preview
This takes the number (from 0 to 100), scales it, converts it to radians, then converts the radians to cartesian coordinates.
I'm trying to do something I thought would be rather simple. I've an object that I move around stepwise, i.e. I receive messages every say 100 milliseconds that tell me "your object has moved x pixels to the right and y pixels down". The code below simulates that by moving that object on a circle, but note that it is not known in advance where the object will be heading in the next step.
Anyway, that is pretty simple. But now I want to also tell the object, which is actually a set of subobjects, that it is being rotated.
Unfortunately, I am having trouble getting Raphaël to do what I want. I believe the reason is that while I can animate both translation and rotation independently, I have to set the center of the rotation when it starts. Obviously the center of the rotation changes as the object is moving.
Here's the code I'm using and you can view a live demo here. As you can see, the square rotates as expected, but the arrow rotates incorrectly.
// c&p this into http://raphaeljs.com/playground.html
var WORLD_SIZE = 400,
rect = paper.rect(WORLD_SIZE / 2 - 20, 0, 40, 40, 5).attr({ fill: 'red' }),
pointer = paper.path("M 200 20 L 200 50"),
debug = paper.text(25, 10, ""),
obj = paper.set();
obj.push(rect, pointer);
var t = 0,
step = 0.05;
setInterval(function () {
var deg = Math.round(Raphael.deg(t));
t += step;
debug.attr({ text: deg + '°' });
var dx = ((WORLD_SIZE - 40) / 2) * (Math.sin(t - step) - Math.sin(t)),
dy = ((WORLD_SIZE - 40) / 2) * (Math.cos(t - step) - Math.cos(t));
obj.animate({
translation: dx + ' ' + dy,
rotation: -deg
}, 100);
}, 100);
Any help is appreciated!
If you want do a translation and a rotation too, the raphael obj should be like that
obj.animate({
transform: "t" + [dx , dy] + "r" + (-deg)
}, 100);
Check out http://raphaeljs.com/animation.html
Look at the second animation from the top on the right.
Hope this helps!
Here's the code:
(function () {
var path1 = "M170,90c0-20 40,20 40,0c0-20 -40,20 -40,0z",
path2 = "M270,90c0-20 40,20 40,0c0-20 -40,20 -40,0z";
var t = r.path(path1).attr(dashed);
r.path(path2).attr({fill: "none", stroke: "#666", "stroke-dasharray": "- ", rotation: 90});
var el = r.path(path1).attr({fill: "none", stroke: "#fff", "stroke-width": 2}),
elattrs = [{translation: "100 0", rotation: 90}, {translation: "-100 0", rotation: 0}],
now = 0;
r.arrow(240, 90).node.onclick = function () {
el.animate(elattrs[now++], 1000);
if (now == 2) {
now = 0;
}
}; })();