My react web app has over 2000 blocks that created by svg path
.
Users can select the blocks they need and merge them to a large block.
The sides of selected blocks must be adjacent.
The problem is I have no idea how to handle irregular block like A0+A1+A2+A4
in snippet.
const data = Array.from({ length: 9 }).map((_, i) => ({
id: `A${i}`,
x: (i % 3) * 300,
y: Math.floor(i / 3) * 300,
p: [{ node: "L", x: 300, y: 0 },{ node: "L", x: 300, y: 300 },{ node: "L", x: 0, y: 300 }]
}));
const expectResult = [
{
id: "A0", x: 0, y: 0,
p: [{ node: "L", x: 900, y: 0 },{ node: "L", x: 900, y: 300 },{ node: "L", x: 600, y: 300 },{ node: "L", x: 600, y: 600 },{ node: "L", x: 300, y: 600 },{ node: "L", x: 300, y: 300 },{ node: "L", x: 0, y: 300 }],
},
{
id: "A3", x: 0, y: 300,
p: [{ node: "L", x: 300, y: 0 },{ node: "L", x: 300, y: 300 },{ node: "L", x: 600, y: 300 },{ node: "L", x: 600, y: 600 },{ node: "L", x: 0, y: 600 }],
},
{
id: "A5", x: 600, y: 300,
p: [{ node: "L", x: 300, y: 0 },{ node: "L", x: 300, y: 600 },{ node: "L", x: 0, y: 600 }],
},
];
const App = () => {
return (
<div>
original
<svg width="100%" viewBox="-100 -100 1200 1200">
{data.map((d) => (
<Booth key={d.id} d={d} />
))}
</svg>
expected result after merge by selected blocks
<svg width="100%" viewBox="-100 -100 1200 1200">
{expectResult.map((d) => (
<Booth key={d.id} d={d} />
))}
</svg>
</div>
);
};
const drawPath = (path) => path.map((p) => (p.node === "L" ? `${p.node}${p.x} ${p.y}` : `${p.node}${p.x1} ${p.y1} ${p.x2} ${p.y2} ${p.x} ${p.y}`)).join("") + "Z";
const Booth = ({ d }) => {
return (
<g key={d.id} id={d.id} transform={`translate(${d.x},${d.y})`}>
<path stroke={"black"} fill="none" strokeWidth={1} d={`M0 0${drawPath(d.p)}`} />
<text y={150} fontSize={80}>
{d.id}
</text>
</g>
);
};
ReactDOM.createRoot(document.getElementById("app")).render(<App />);
body {
margin: 0;
padding: 0;
}
<script src="https://unpkg.com/react@18/umd/react.production.min.js" crossorigin="true"></script>
<script src="https://unpkg.com/react-dom@18/umd/react-dom.production.min.js" crossorigin="true"></script>
<script src="https://unpkg.com/@babel/standalone/babel.min.js" crossorigin="true"></script>
<div id="app"></div>
I tried to use min and max to calculate the new block’s shape, but it can’t handle irregular block.
const data = Array.from({ length: 9 }).map((_, i) => ({
id: `A${i}`,
x: (i % 3) * 300,
y: Math.floor(i / 3) * 300,
p: [
{ node: "L", x: 300, y: 0 },
{ node: "L", x: 300, y: 300 },
{ node: "L", x: 0, y: 300 },
],
}));
const areas = [
{ id: "A0", blocks: ["A0", "A1", "A2", "A4"] },
{ id: "A3", blocks: ["A3", "A6", "A7"] },
{ id: "A5", blocks: ["A5", "A8"] },
];
const mergeBlock = (blocksData) => {
const filter = [];
return blocksData
.map((d1) => {
const area = areas.find((d2) => d1.id === d2.id);
const pos = blocksData.filter((d) => area && area.blocks.includes(d.id)).map((d) => ({ x: d.x, y: d.y }));
const x = pos.length > 0 ? Math.min(...pos.map((d) => d.x)) : d1.x;
const y = pos.length > 0 ? Math.min(...pos.map((d) => d.y)) : d1.y;
const w = pos.length > 0 ? Math.max(...pos.map((d) => d.x)) - x + 300 : d1.w;
const h = pos.length > 0 ? Math.max(...pos.map((d) => d.y)) - y + 300 : d1.h;
filter.push(...(area ? area.blocks.filter((d) => d !== d1.id) : []));
return {
...d1,
x,
y,
p: [
{ node: "L", x: w, y: 0 },
{ node: "L", x: w, y: h },
{ node: "L", x: 0, y: h },
],
};
})
.filter((d) => !filter.includes(d.id));
};
const App = () => {
return (
<div>
<svg width="100%" viewBox="-100 -100 1200 1200">
{mergeBlock(data).map((d) => (
<Booth key={d.id} d={d} />
))}
</svg>
</div>
);
};
const drawPath = (path) => path.map((p) => (p.node === "L" ? `${p.node}${p.x} ${p.y}` : `${p.node}${p.x1} ${p.y1} ${p.x2} ${p.y2} ${p.x} ${p.y}`)).join("") + "Z";
const Booth = ({ d }) => {
return (
<g key={d.id} id={d.id} transform={`translate(${d.x},${d.y})`}>
<path stroke={"black"} fill="none" strokeWidth={1} d={`M0 0${drawPath(d.p)}`} />
<text y={150} fontSize={80}>
{d.id}
</text>
</g>
);
};
ReactDOM.createRoot(document.getElementById("app")).render(<App />);
body {
margin: 0;
padding: 0;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/18.2.0/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/18.2.0/umd/react-dom.production.min.js"></script>
<script src="https://unpkg.com/@babel/standalone/babel.min.js" crossorigin="true"></script>
<div id="app"></div>