drawing lines (shift to draw multiple)

This commit is contained in:
Michael Peters 2024-01-27 11:52:37 -08:00
parent 4b269e5757
commit 7fe8047f23
3 changed files with 120 additions and 35 deletions

20
package-lock.json generated
View File

@ -10,10 +10,12 @@
"license": "ISC",
"dependencies": {
"@uidotdev/usehooks": "^2.4.1",
"lodash": "^4.17.21",
"react": "^18.2.0",
"react-dom": "^18.2.0"
},
"devDependencies": {
"@types/lodash": "^4.14.202",
"@types/react": "^18.0.26",
"@types/react-dom": "^18.0.10",
"css-loader": "^6.7.3",
@ -212,6 +214,12 @@
"integrity": "sha512-wOuvG1SN4Us4rez+tylwwwCV1psiNVOkJeM3AUWUNWg/jDQY2+HE/444y5gc+jBmRqASOm2Oeh5c1axHobwRKQ==",
"dev": true
},
"node_modules/@types/lodash": {
"version": "4.14.202",
"resolved": "https://registry.npmjs.org/@types/lodash/-/lodash-4.14.202.tgz",
"integrity": "sha512-OvlIYQK9tNneDlS0VN54LLd5uiPCBOp7gS5Z0f1mjoJYBrtStzgmJBxONW3U6OZqdtNzZPmn9BS/7WI7BFFcFQ==",
"dev": true
},
"node_modules/@types/mime": {
"version": "3.0.1",
"resolved": "https://registry.npmjs.org/@types/mime/-/mime-3.0.1.tgz",
@ -2331,8 +2339,7 @@
"node_modules/lodash": {
"version": "4.17.21",
"resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz",
"integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==",
"dev": true
"integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg=="
},
"node_modules/loose-envify": {
"version": "1.4.0",
@ -4522,6 +4529,12 @@
"integrity": "sha512-wOuvG1SN4Us4rez+tylwwwCV1psiNVOkJeM3AUWUNWg/jDQY2+HE/444y5gc+jBmRqASOm2Oeh5c1axHobwRKQ==",
"dev": true
},
"@types/lodash": {
"version": "4.14.202",
"resolved": "https://registry.npmjs.org/@types/lodash/-/lodash-4.14.202.tgz",
"integrity": "sha512-OvlIYQK9tNneDlS0VN54LLd5uiPCBOp7gS5Z0f1mjoJYBrtStzgmJBxONW3U6OZqdtNzZPmn9BS/7WI7BFFcFQ==",
"dev": true
},
"@types/mime": {
"version": "3.0.1",
"resolved": "https://registry.npmjs.org/@types/mime/-/mime-3.0.1.tgz",
@ -6150,8 +6163,7 @@
"lodash": {
"version": "4.17.21",
"resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz",
"integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==",
"dev": true
"integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg=="
},
"loose-envify": {
"version": "1.4.0",

View File

@ -10,6 +10,7 @@
"author": "",
"license": "ISC",
"devDependencies": {
"@types/lodash": "^4.14.202",
"@types/react": "^18.0.26",
"@types/react-dom": "^18.0.10",
"css-loader": "^6.7.3",
@ -28,6 +29,7 @@
},
"dependencies": {
"@uidotdev/usehooks": "^2.4.1",
"lodash": "^4.17.21",
"react": "^18.2.0",
"react-dom": "^18.2.0"
}

View File

@ -1,8 +1,10 @@
import { useWindowSize } from '@uidotdev/usehooks';
import { FC, useMemo, useState } from 'react';
import { useMouse, useWindowSize } from '@uidotdev/usehooks';
import { FC, MouseEvent, useCallback, useMemo, useState } from 'react';
import './grid.scss';
import * as lodash from 'lodash';
interface Point {
x: number;
y: number;
@ -15,6 +17,18 @@ interface Range {
y1: number;
}
function pointsToRange(p0: Point, p1: Point): Range {
return { x0: p0.x, y0: p0.y, x1: p1.x, y1: p1.y };
}
function snapToGrid(p: Point, gap: number): Point {
// snap a point to a grid
return {
x: gap * Math.round(p.x / gap),
y: gap * Math.round(p.y / gap),
};
}
const ORIGIN: Point = { x: 0, y: 0 };
function useWindowSizeCustom() {
@ -40,73 +54,130 @@ function useRange(offset: Point): Range {
return range;
}
function useMouseInRange(range: Range): Point {
const [mouse, _] = useMouse();
return { x: mouse.x + range.x0, y: mouse.y + range.y0 };
}
const Grid: FC = () => {
const [offset, setOffset] = useState<Point>(ORIGIN);
const range = useRange(offset);
const mousePoint = useMouseInRange(range);
const gap = 100;
const nodes = useMemo(() => {
const nodes = [];
for (let x = range.x0; x <= range.x1; x += gap) {
for (let y = range.y0; y <= range.y1; y += gap) {
nodes.push({ x, y });
}
}
return nodes;
}, [range, gap]);
const GAP = 20;
const gridLines = useMemo(() => {
const lines = [];
for (let x = range.x0; x <= range.x1; x += gap) {
for (let x = range.x0; x <= range.x1; x += GAP) {
lines.push({ ...range, x0: x, x1: x });
}
for (let y = range.y0; y <= range.y1; y += gap) {
for (let y = range.y0; y <= range.y1; y += GAP) {
lines.push({ ...range, y0: y, y1: y });
}
return lines;
}, [range, gap]);
}, [range, GAP]);
const nodeElements = useMemo(() => {
return nodes.map((point) => (
<circle cx={point.x} cy={point.y} r="5" fill="#123456" />
));
}, [nodes]);
const [userLines, setUserLines] = useState<Range[]>([]);
const [userStartPoint, setUserStartPoint] = useState<Point | null>(null);
const gridLineElements = useMemo(() => {
return gridLines.map((line) => (
return gridLines.map((line, idx) => (
<line
key={idx}
className="grid-line"
x1={line.x0}
y1={line.y0}
x2={line.x1}
y2={line.y1}
stroke="#123456"
stroke-width="1"
strokeWidth="2"
/>
));
}, [gridLines]);
const mid = useMemo(() => {
return {
x: (range.x1 - range.x0) / 2,
y: (range.y1 - range.y0) / 2,
};
}, [range]);
const userLineIndicatorElement = useMemo(() => {
if (lodash.isNull(userStartPoint)) return null;
const point = snapToGrid(mousePoint, GAP);
return (
<line
x1={userStartPoint.x}
y1={userStartPoint.y}
x2={point.x}
y2={point.y}
stroke="#ff0000"
strokeWidth="2"
/>
);
}, [userStartPoint, mousePoint]);
const userPointIndicatorElement = useMemo(() => {
const point = snapToGrid(mousePoint, GAP);
return (
<circle
className="node"
cx={point.x}
cy={point.y}
r="3"
fill="#ff0000"
/>
);
}, [userStartPoint, mousePoint]);
const userLineElements = useMemo(() => {
return userLines.map((line, idx) => (
<line
key={idx}
className="grid-line"
x1={line.x0}
y1={line.y0}
x2={line.x1}
y2={line.y1}
stroke="#ff0000"
strokeWidth="2"
/>
));
}, [userLines]);
const onGridClick = useCallback(
(event: MouseEvent<SVGSVGElement>) => {
console.log('grid click', event);
const pointRaw = { x: event.clientX, y: event.clientY };
const point = snapToGrid(pointRaw, GAP);
if (lodash.isEqual(userStartPoint, point)) return;
if (lodash.isNull(userStartPoint)) {
setUserStartPoint(point);
return;
}
const line = pointsToRange(userStartPoint, point);
setUserLines([...userLines, line]);
if (event.shiftKey == false) {
setUserStartPoint(null);
} else {
setUserStartPoint(point);
}
},
[userStartPoint, setUserStartPoint, setUserLines, GAP]
);
return (
<svg
className="grid"
xmlns="http://www.w3.org/2000/svg"
viewBox={`${range.x0} ${range.y0} ${range.x1} ${range.y1}`}
onClick={onGridClick}
>
{nodeElements}
{gridLineElements}
{userLineElements}
{userLineIndicatorElement}
{userPointIndicatorElement}
<text
x={range.x0 + 20}
y={range.y0 + 60} // of baseline
fill="#ffffff"
font-size="40px"
fontSize="40px"
>
GRID
</text>