JSFiddle - React, Tailwind, and code Playground
HTML
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>SVG Editor</title>
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/7.8.5/d3.min.js"></script>
</head>
<body>
<h1>SVG Editor</h1>
<button id="saveBtn">Save SVG</button>
<button id="rotateBtn" class="hidden">Rotate Selected Sofa</button>
<br><br>
<svg id="svgCanvas">
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
<!-- Создано Microsoft Visio, экспорт SVG Итерация 2 - изменил svg руками, сохранил его как visio.svg Страница-1 -->
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" xmlns:ev="http://www.w3.org/2001/xml-events"
xmlns:v="http://schemas.microsoft.com/visio/2003/SVGExtensions/" width="8.26772in" height="11.6929in"
viewBox="0 0 595.276 841.889" xml:space="preserve" color-interpolation-filters="sRGB" class="st23">
<v:documentProperties v:langID="1049">
<v:userDefs>
<v:ud v:nameU="VisAecDocument" v:val="VT0(1):26"/>
<v:ud v:nameU="VisAecVersion" v:val="VT0(15):26"/>
<v:ud v:nameU="Ifc__CustomPropsLevel" v:val="VT0(0):26"/>
<v:ud v:nameU="msvNoAutoConnect" v:val="VT0(1):26"/>
</v:userDefs>
</v:documentProperties>
<style type="text/css">
<![CDATA[
.st1 {fill:#789440;stroke:#923931;stroke-linecap:round;stroke-linejoin:round;stroke-width:1.25}
.st2 {fill:#789440;stroke:#000000;stroke-linecap:round;stroke-linejoin:round;stroke-width:0.749999}
.st3 {fill:#789440}
.st4 {stroke:#000000;stroke-linecap:round;stroke-linejoin:round;stroke-width:0.24}
.st5 {fill:#b95f3a;stroke:#000000;stroke-linecap:butt;stroke-width:0.75}
.st6 {fill:#310e00;stroke:none;stroke-linecap:butt;stroke-width:0.75}
.st7 {fill:#157224;stroke:none;stroke-linecap:butt;stroke-width:0.75}
.st8...
CSS
body {
font-family: Arial, sans-serif;
margin: 20px;
}
svg {
border: 1px solid #ccc;
width: 100%;
height: 100vh;
}
.draggable-group {
cursor: move;
}
.dragging {
stroke: red;
stroke-width: 2px;
}
.selected {
stroke: blue;
stroke-width: 2px;
}
.hidden {
display: none;
}
.normalTable, .sofaTable {
transform-origin: center center;
}
JavaScript
class Merger {
constructor(svgCanvas, proximityThreshold = 50) {
this.svgCanvas = svgCanvas;
this.groups = [];
this.proximityThreshold = proximityThreshold;
this.init();
}
init() {
this.updateDraggableGroups();
this.addSelectableFeature();
}
updateDraggableGroups() {
const groups = d3.select(this.svgCanvas).selectAll('.draggable-group').call(d3.drag()
.on('start', (event, d) => this.startDrag(event, d))
.on('drag', (event, d) => this.drag(event, d))
.on('end', (event, d) => this.endDrag(event, d)));
this.groups = groups.nodes();
console.log('Updated groups:', this.groups);
}
getRotationFromTransform(element) {
const transform = d3.select(element).attr('transform');
const rotateMatch = transform ? transform.match(/rotate\(([^)]+)\)/) : null;
return rotateMatch ? parseFloat(rotateMatch[1]) : 0;
}
addSelectableFeature() {
d3.select(this.svgCanvas).selectAll('.sofa').on('click', (event, d) => {
d3.selectAll('.selected').classed('selected', false);
d3.select(event.target.closest('.sofa')).classed('selected', true);
document.getElementById('rotateBtn').classList.remove('hidden');
});
}
rotateSelectedSofa() {
const selectedElement = d3.select('.selected');
if (!selectedElement.empty()) {
const transform = selectedElement.attr("transform");
const rotateMatch = transform ? transform.match(/rotate\(([^)]+)\)/) : null;
const currentRotation = rotateMatch ?...